Files
clinicpro/docs/new_feture/taskes/task-00-service-mode-completion/checklist.md
T
hamedandClaude Opus 5 6c2e075eea feat(booking): persist service duration and allow full service replacement
Appointment gains:
  - replaceServiceItems(): full replacement that unconditionally syncs the
    legacy single serviceItem column. addServiceItem() only fills it when null,
    which would leave stale service names in the four consumers that read
    service_item (admin lists, public site, desktop app).
  - currentServiceUuids(): input-order uuids, falling back to the single column
    for appointments created before multi-service support.
  - service_total_minutes / service_buffer_minutes (both nullable, NULL in slot
    mode). slot_end - slot_start carries the number but cannot say whether it
    was intentional, and a reserve entry has slot_start == slot_end so its
    duration had nowhere to live.

Existing columns untouched: slot_start, slot_end, active_slot_key, is_reserve
verified unchanged via SHOW COLUMNS.

Task: docs/new_feture/taskes/task-00-service-mode-completion/
Slot-mode contract: unchanged (--group=slot-mode-frozen green)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 12:47:48 +03:30

13 KiB
Raw Blame History

چک‌لیست — تسک ۰۰ (تکمیل نوبت‌دهی سرویسی در clinicpro)

وضعیت کلی: 🔄 در حال انجام — قابلیت ۳ از ۱۰ تمام شد (خط سرخ · ServiceBookingCalculator · فیلدهای سرویسی نوبت) آخرین بازبینی: ۱۴۰۵/۰۵/۰۸

قواعد: _shared/definition-of-done.md · خط سرخ‌ها: _shared/red-lines.md · UI: _shared/ui-conventions.md


۰. خط سرخ — منطق اسلاتی

# مورد وضعیت یادداشت
۰.۱ SlotModeFrozenTest + سه fixture ساخته شد پیش از هر تغییر کد tests/Appointment/SlotModeFrozenTest.php + tests/Appointment/fixtures/ — هیچ کد تولیدی‌ای هنوز لمس نشده
۰ fixture ها با تاریخ ثابت‌اند، نه time() → fixture ساختاری است انحراف عمدی از متن تسک. تاریخ ثابتِ گذشته را isWithinBookingWindow رد می‌کند و snapshot خالی چیزی را تضمین نمی‌کند. به‌جایش: برنامهٔ قطعی (هر ۷ روز یک شیفت ۰۹:۰۰–۱۱:۰۰/۳۰ دقیقه) روی +3 days، و epoch/uuid با placeholder نرمال می‌شوند. آنچه قفل می‌شود: کلیدها، ترتیب، نوع‌ها، ساعت‌های محلی
۰ کامنت «read-only، هیچ تسکی به‌روزش نمی‌کند» بالای هر سه fixture کلید _readme در دو JSON (در loader حذف می‌شود) + docblock در فایل PHP
۰ هیچ متد موجود SlotCalculatorService ویرایش نشد قابلیت ۴ — فقط پارامتر اختیاری excludeAppointmentId روی getServiceStartTimes؛ fixture امضا یک بار به‌روز می‌شود (header خودش این را مجاز کرده)
۰.۵ GET /appointment-slots بیت‌به‌بیت دست‌نخورده در پایان تسک تأیید می‌شود
۰ GET /month-availability/{doctorUuid} دست‌نخورده در پایان تسک تأیید می‌شود
۰.۷ active_slot_key و refreshActiveSlotKey() دست‌نخورده setIsReserve() وجود ندارد؛ toggle رزرو از قبل با rescheduleTo($start,$end,$isReserve) انجام می‌شود که خودش refreshActiveSlotKey() را صدا می‌زند (Appointment.php:316). یادداشت قبلی چک‌لیست غلط بود
۰ isSlotTaken امضا و معنا دست‌نخورده در پایان تسک تأیید می‌شود
۰ --group=slot-mode-frozen سبز OK (3 tests, 8 assertions) — نیازمند #[Group] attribute بود، نه @group (PHPUnit 12 annotation را حذف کرده)
۰.۱۰ baseline: کل tests/Appointment پیش از تغییرات سبز OK (166 tests, 382 assertions)

۱. بک‌اند

# مورد وضعیت یادداشت
۱.۱ ServiceBookingCalculator ساخته شد src/Appointment/Service/ServiceBookingCalculator.php + VO در src/Appointment/ValueObject/ServiceBookingDuration.php (قرارداد پروژه ValueObject است نه Dtosrc/Billing/ValueObject، src/Insurance/ValueObject)
۱ serviceSlots() موجود از آن استفاده می‌کند، خروجی‌اش عوض نشده ۲۰ خط از کنترلر حذف شد. بیت‌به‌بیت‌بودن: error() و ExceptionSubscriber یک envelope یکسان می‌سازند (['success'=>false,'data'=>null,'errors'=>[{code,message,field?}]])، پس AppException جای return $this->error() نشست. شاهد: ۱۶۶ تست موجود سبز ماند، شامل ServiceBasedSlotsTest و ServiceModeSectionDurationTest که همین مسیرهای خطا را می‌سنجند
۱ جمع سادهٔ += حفظ شد (اصلاحش تسک ۰۴ است) با کامنت صریح در docblock کلاس: «تسک ۰۴ همان‌جا یک خط عوض می‌کند»
۱ ServiceRescheduleService + POST /appointment/{uuid}/service-reschedule
۱.۵ PATCH /appointment/{uuid} توسعه یافت — منطق جدید داخل isServiceMode()
۱ PATCH مقدار service_item_uuids[] می‌پذیرد
۱.۷ ReserveConversionService + POST /appointment/{uuid}/convert-reserve
۱ نوبت رزرو در حالت سرویسی سرویس‌ها را ذخیره می‌کند
۱ excludeAppointmentId روی getServiceStartTimes و findBusyIntervals همان الگوی isSlotTaken
۱.۱۰ Appointment::replaceServiceItems() + currentServiceUuids() کنار addServiceItem() موجود؛ currentServiceUuids() ترتیب ورودی را حفظ می‌کند و برای نوبت قدیمیِ فقط-تکی هم uuid می‌دهد
۱.۱۱ replaceServiceItems مقدار serviceItem تکی را هم‌گام می‌کند برخلاف addServiceItem() که فقط وقتی null است ست می‌کند، این یکی بی‌قید بازنویسی می‌کند (و با فهرست خالی null می‌شود). چهار مصرف‌کننده روی service_item تکی خوانده‌اند
۱.۱۲ setIsReserve() صدا زدن refreshActiveSlotKey() موردی برای انجام نبود: setIsReserve() وجود ندارد؛ toggle رزرو از قبل با rescheduleTo($start,$end,$isReserve) است که خودش refreshActiveSlotKey() را صدا می‌زند
۱.۱۳ اعتبارسنجی زمان با عضویت در getServiceStartTimes، نه فقط isSlotTaken
۱.۱۴ TenantOwnershipChecker روی همهٔ uuid های سرویس، پیش از هر بررسی دیگر belongsToPair() (نه belongsTo()): مسیر عمومی است و کاربر احراز‌شده ندارد. جای مقایسهٔ دستیِ getSection()->getEntityType() نشست — نقطهٔ واحد tenancy.md. ⚠️ نام property باید itemRepo باشد وگرنه TenantLookupInventoryTest آن را نمی‌شمارد و از تور ایمنی فرار می‌کند
۱.۱۵ allowInactive فقط برای سرویس‌های موجود نوبت، نه uuid های تازه پرچم روی calculate()؛ غیرفعال‌بودن → warnings[] به‌جای ۴۲۲. مدتِ نامعتبر همچنان حتی با allowInactive رد می‌شود (تست دارد)
۱.۱۶ کنترلر نازک ماند — منطق در سرویس serviceSlots() از ۶۰ خط به ۴۰ خط؛ هیچ حلقهٔ محاسباتی در کنترلر نماند

۲. دیتابیس و مهاجرت

# مورد وضعیت یادداشت
۲.۱ service_total_minutes و service_buffer_minutes (تهی‌پذیر) SMALLINT DEFAULT NULL هر دو · migrations/Version20260730091332.php · setServiceDuration() هر دو را با هم ست می‌کند (بافرِ بی‌مدت معنا ندارد)
۲.۲ هیچ ستون موجودی حذف/تغییر نوع/تغییر معنا نداد تأیید با SHOW COLUMNS: slot_start int NOT NULL · slot_end int NOT NULL · active_slot_key varchar(64) UNI · is_reserve tinyint default 0 — همه دست‌نخورده
۲.۳ دو کد خطای جدید در ErrorCodes.php با پیام فارسی شمارهٔ واقعی از خود فایل
۲.۴ app:appointment:backfill-service-duration — dry-run پیش‌فرض، idempotent
۲.۵ backfill مقدار را از خود نوبت می‌گیرد، نه بازمحاسبه از سرویس‌ها
۲.۶ migration اجرا شد و TenantSchemaCoverageTest سبز dev با migration؛ db_test با ALTER دستی — تاریخچهٔ migration آن DB از dev جدا است (Table users already exists) و این روش نگهداشتِ موجودِ همان DB است

۳. UI — پنل ادمین

# مورد وضعیت یادداشت
۳.۱ AppointmentEditPage: حالت سرویسی ServiceSlotPicker نشان می‌دهد
۳.۲ AppointmentEditPage: حالت اسلاتی دقیقاً رفتار امروز سه فیلد ساعت
۳.۳ ورودی دستی ساعت در حالت سرویسی پنهان، نه disabled
۳.۴ ServiceSlotPicker موجود بازاستفاده شد؛ نسخهٔ موازی ساخته نشد فقط prop excludeAppointmentUuid
۳.۵ ReserveAppointmentsPage: سرویس‌ها + دکمهٔ تبدیل
۳.۶ ReserveAppointmentsPage از جدول خام به DataTable مهاجرت کرد <td style={td}> حذف شد
۳.۷ مدت و بافر فارسی با واحد: «۳۵ دقیقه (+۱۰ دقیقه فاصله)»
۳.۸ هیچ رنگ/شعاع/سایهٔ hard-code — همه از توکن‌های styles.css
۳.۹ دارک‌مود (data-theme="dark") بررسی شد
۳.۱۰ حالت فشرده (data-density="compact") بررسی شد
۳.۱۱ انتخاب چند سرویس با SearchableSelect؛ هیچ <select> بومی
۳.۱۲ backTo/BackButton روی هر دو صفحه
۳.۱۳ وضعیت لیست رزروها در URL با useUrlState
۳.۱۴ تاریخ با PersianDateInput · مبلغ با formatRial
۳.۱۵ RTL بررسی شد (ms/me نه ml/mr)
۳.۱۶ موبایل بررسی شد — بدون اسکرول افقی
۳.۱۷ همهٔ رشته‌ها فارسی و از i18n
۳.۱۸ داده با TanStack Query و استخراج envelope درست

۴. تست

# مورد وضعیت یادداشت
۴.۱ SlotModeFrozenTest — سه سنجه OK (3 tests, 8 assertions)
۴.۲ ServiceBookingCalculatorTest — موفق/خطا/مرزی ۱۳ تست / ۲۹ assertion سبز. شامل: جمع مدت + بافر · endFor بدون بافر · override منشی بدون تغییر پیش‌فرض سرویس · چهار مسیر خطا با کد/پیام/فیلد دقیق · uuid ناموجود از سرویسِ محیط دیگر قابل تفکیک نیست · allowInactive → warning · فهرست خالی → صفر · override نامعتبر (۰ و منفی) → fallback · پزشک بی‌برنامه → پیش‌فرض slot
۴.۳ ServiceRescheduleTest — شامل «حذف سرویس → مدت خودکار»
۴.۴ PatchServiceDurationTest — شامل «در حالت اسلاتی هیچ‌کدام اجرا نمی‌شود»
۴.۵ ConvertReserveTest — شامل active_slot_key و رقابت
۴.۶ ServiceModeSectionDurationTest موجود سبز ماند
۴.۷ BookingTenantTest موجود سبز ماند داخل tests/Appointment — کل ۳۰۹ تست tests/Appointment + tests/Shared سبز
۴.۹ AppointmentServiceFieldsTest — متدها و ستون‌های جدید ۹ تست / ۲۴ assertion. شامل: هم‌گامی ستون تکی · حفظ ترتیب · فهرست خالی → null · حالت اسلاتی هر دو ستون null · مدتِ null بافر را هم null می‌کند · تکراری‌ها dedup · نوبت قدیمیِ فقط-تکی · بقای مقادیر پس از flush/clear
۴.۸ AppointmentEditPage.test.tsx — دو حالت

۵. مستندات

# مورد وضعیت یادداشت
۵.۱ docs/api/appointment.md — دو endpoint جدید + توسعهٔ PATCH
۵ ماتریس «کدام endpoint در کدام حالت»
۵ docs/architecture/booking-modes.md ساخته شد تسک ۰۶ حالت سوم را اضافه می‌کند
۵ دو کد خطای جدید مستند شد

۶. بازبینی پایانی

# مورد وضعیت یادداشت
۶.۱ همهٔ ردیف‌های بالا وضعیت نهایی دارند (هیچ 🔄 و بی‌دلیل)
۶.۲ ddev exec php bin/phpunit کامل سبز
۶.۳ ddev exec php bin/phpunit --group=slot-mode-frozen سبز
۶.۴ phpstan analyse بدون خطای جدید
۶.۵ npx tsc --noEmit بدون خطا
۶.۶ yarn test سبز
۶.۷ TenantSchemaCoverageTest + TenantLookupInventoryTest سبز
۶.۸ docs/api/* به‌روز شد
۶.۹ چک‌لیست UI (بخش ۳) کامل شد
۶.۱۰ nobat724_front و clinic-pro-tauri دستی بررسی شدند service_item تکی هم‌گام است؟
۶.۱۱ commit شد، سپس graphify update .
۶.۱۲ موارد به‌تعویق‌افتاده با دلیل و تسک مقصد ثبت شدند