docs(booking): record task 00 checklist state through feature 5

Also logs three side findings as debt rather than silently passing: an
unreproduced flaky failure in the combined suite, a pre-existing PHPUnit notice,
and db_test having a migration history separate from dev (later tasks will need
the same manual ALTER).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-07-30 12:59:09 +03:30
co-authored by Claude Opus 5
parent 02cceb1c72
commit 231735162e
@@ -1,6 +1,6 @@
# چک‌لیست — تسک ۰۰ (تکمیل نوبت‌دهی سرویسی در clinicpro)
**وضعیت کلی:** 🔄 در حال انجام — قابلیت ۴ از ۱۰ تمام شد
**وضعیت کلی:** 🔄 در حال انجام — قابلیت ۵ از ۱۰ تمام شد
**آخرین بازبینی:** ۱۴۰۵/۰۵/۰۸
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
@@ -32,10 +32,10 @@ UI: [_shared/ui-conventions.md](../_shared/ui-conventions.md)
| ۱.۲ | `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` | ⏳ | |
| ۱.۸ | نوبت رزرو در حالت سرویسی سرویس‌ها را ذخیره می‌کند | | |
| ۱.۵ | `PATCH /appointment/{uuid}` توسعه یافت — منطق جدید داخل `isServiceMode()` | | سرویس‌ها **پیش از** بلوک زمان حل می‌شوند چون مدت به آن‌ها وابسته است. بررسی مدت فقط وقتی `$movingToLiveSlot` است (نوبت رزرو معاف — `slot_start == slot_end`). در حالت اسلاتی `$duration === null` می‌ماند و هیچ خطی اجرا نمی‌شود |
| ۱.۶ | `PATCH` مقدار `service_item_uuids[]` می‌پذیرد | | وقتی فهرست کامل آمد، `service_item_uuid` تکی در حلقهٔ relations **نادیده** گرفته می‌شود (`continue`) — دو منبع برای یک چیز به نوبتِ ناسازگار می‌رسد. `allowInactive` فقط وقتی فهرست **نیامده** روشن است: نوبت موجود قفل نشود ولی افزودن سرویس غیرفعال رد شود |
| ۱.۷ | ~~`ReserveConversionService` + `POST /convert-reserve`~~ → توسعهٔ `PATCH` | ✅ | **endpoint جدید ساخته نشد و توجیه نداشت** (قاعدهٔ پروژه). `PATCH` از قبل رزرو→زمان‌دار را دارد: `rescheduleTo($start,$end,$isReserve)` که خودش `refreshActiveSlotKey()` را صدا می‌زند. تست `testReserveConvertsToATimedAppointmentThroughPatch` این را می‌سنجد |
| ۱.۸ | نوبت رزرو در حالت سرویسی سرویس‌ها را ذخیره می‌کند | | سرویس‌ها + `service_total_minutes` روی رزرو ذخیره می‌شوند تا تبدیل بعدی مدت را از دست ندهد؛ بررسی مدت روی خودِ رزرو اعمال نمی‌شود |
| ۱.۹ | `excludeAppointmentId` روی `getServiceStartTimes` و `findBusyIntervals` | ✅ | همان نام/جای/پیش‌فرضِ `isSlotTaken($doctor,$start,$end,?int $excludeId=null)`. در repository به `occupiedIntervals()` هم پاس داده می‌شود، پس `findBlockingIntervals` بی‌تغییر ماند |
| ۱.۱۰ | `Appointment::replaceServiceItems()` + `currentServiceUuids()` | ✅ | کنار `addServiceItem()` موجود؛ `currentServiceUuids()` ترتیب ورودی را حفظ می‌کند و برای نوبت قدیمیِ فقط-تکی هم uuid می‌دهد |
| ۱.۱۱ | `replaceServiceItems` مقدار `serviceItem` تکی را هم‌گام می‌کند | ✅ | برخلاف `addServiceItem()` که فقط وقتی `null` است ست می‌کند، این یکی **بی‌قید** بازنویسی می‌کند (و با فهرست خالی `null` می‌شود). چهار مصرف‌کننده روی `service_item` تکی خوانده‌اند |
@@ -51,7 +51,7 @@ UI: [_shared/ui-conventions.md](../_shared/ui-conventions.md)
|---|---|---|---|
| ۲.۱ | `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` با پیام فارسی | | شمارهٔ واقعی از خود فایل |
| ۲.۳ | دو کد خطای جدید در `ErrorCodes.php` با پیام فارسی | | `ERR_APPOINTMENT_003` (مدت نمی‌خواند) و `ERR_APPOINTMENT_004` (روش نوبت‌دهی ناسازگار) — **نه `_010`/`_011`** که متن تسک حدس زده بود؛ فایل فقط تا `_002` داشت |
| ۲.۴ | `app:appointment:backfill-service-duration` — dry-run پیش‌فرض، idempotent | ⏳ | |
| ۲.۵ | backfill مقدار را از خود نوبت می‌گیرد، نه بازمحاسبه از سرویس‌ها | ⏳ | |
| ۲.۶ | migration اجرا شد و `TenantSchemaCoverageTest` سبز | ✅ | dev با migration؛ **db_test با `ALTER` دستی** — تاریخچهٔ migration آن DB از dev جدا است (`Table users already exists`) و این روش نگهداشتِ موجودِ همان DB است |
@@ -86,7 +86,7 @@ UI: [_shared/ui-conventions.md](../_shared/ui-conventions.md)
| ۴.۱ | `SlotModeFrozenTest` — سه سنجه | ✅ | `OK (3 tests, 8 assertions)` |
| ۴.۲ | `ServiceBookingCalculatorTest` — موفق/خطا/مرزی | ✅ | ۱۳ تست / ۲۹ assertion سبز. شامل: جمع مدت + بافر · `endFor` بدون بافر · override منشی بدون تغییر پیش‌فرض سرویس · چهار مسیر خطا با کد/پیام/فیلد دقیق · uuid ناموجود از سرویسِ محیط دیگر **قابل تفکیک نیست** · `allowInactive` → warning · فهرست خالی → صفر · override نامعتبر (۰ و منفی) → fallback · پزشک بی‌برنامه → پیش‌فرض `slot` |
| ۴.۳ | `ServiceRescheduleTest` — شامل «حذف سرویس → مدت خودکار» | ⏳ | |
| ۴.۴ | `PatchServiceDurationTest` — شامل «در حالت اسلاتی هیچ‌کدام اجرا نمی‌شود» | | |
| ۴.۴ | `PatchServiceDurationTest` — شامل «در حالت اسلاتی هیچ‌کدام اجرا نمی‌شود» | | ۹ تست / ۲۴ assertion. شامل: بازمحاسبهٔ مدت با تعویض سرویس · هم‌گامی ستون تکی · PATCH فقط-یادداشت بی‌اعتبارسنجی · رزرو معاف ولی مدت‌دار · تبدیل رزرو با همان PATCH · مدت ناسازگار → `ERR_APPOINTMENT_003` با عدد درست در پیام · سرویس بیگانه → ۴۲۲ · **حالت اسلاتی هر مدتی را می‌پذیرد و ستون سرویسی `null` می‌ماند** · نوبت سرویسیِ بی‌سرویس قفل نمی‌شود |
| ۴.۵ | `ConvertReserveTest` — شامل `active_slot_key` و رقابت | ⏳ | |
| ۴.۶ | `ServiceModeSectionDurationTest` موجود سبز ماند | ⏳ | |
| ۴.۷ | `BookingTenantTest` موجود سبز ماند | ✅ | داخل `tests/Appointment` — کل ۳۰۹ تست `tests/Appointment` + `tests/Shared` سبز |
@@ -103,6 +103,14 @@ UI: [_shared/ui-conventions.md](../_shared/ui-conventions.md)
| ۵.۳ | `docs/architecture/booking-modes.md` ساخته شد | ⏳ | تسک ۰۶ حالت سوم را اضافه می‌کند |
| ۵.۴ | دو کد خطای جدید مستند شد | ⏳ | |
## ۵.۵ یافته‌های جانبی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ج.۱ | یک شکست flaky در اجرای ترکیبی `tests/Appointment tests/Shared` | ⚠️ | یک بار ۱ failure دید، سه اجرای بعدی سبز (۳۲۴ تست). نام تست ثبت نشد چون خروجی از دست رفت. **بازتولید نشد** — بدهی ثبت‌شده، نه «حل‌شده». احتمال: برخورد شمارهٔ موبایل تصادفی در `ApiTestCase::createUser` روی `db_test` که هرگز ریست نمی‌شود (خودِ کلاس این را مستند کرده) |
| ج.۲ | یک PHPUnit Notice در `tests/Shared` | ⚠️ | پیش از تغییرات این تسک هم بود (baseline). خارج از دامنهٔ این تسک |
| ج.۳ | `db_test` تاریخچهٔ migration جدا دارد | ⚠️ | `doctrine:migrations:migrate` روی آن می‌شکند (`Table users already exists`)؛ ستون‌های جدید با `ALTER` دستی اضافه شدند. برای تسک‌های بعدی هم همین لازم است |
## ۶. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
@@ -110,7 +118,7 @@ UI: [_shared/ui-conventions.md](../_shared/ui-conventions.md)
| ۶.۱ | همهٔ ردیف‌های بالا وضعیت نهایی دارند (هیچ 🔄 و ⏳ بی‌دلیل) | ⏳ | |
| ۶.۲ | `ddev exec php bin/phpunit` کامل سبز | ⏳ | |
| ۶.۳ | `ddev exec php bin/phpunit --group=slot-mode-frozen` سبز | ⏳ | |
| ۶.۴ | `phpstan analyse` بدون خطای جدید | | |
| ۶.۴ | `phpstan analyse` بدون خطای جدید | 🔄 | `analyse src/Appointment`**No errors**. تحلیل کامل `src` ۱۴ خطا دارد ولی **هیچ‌کدام در فایل‌های این تسک نیست** (AuthController، BillingController، ClinicServiceController، ServiceItem، DoctorClaimService، InventoryService، PatientService، SecretaryService، HealthController) — از قبل بوده‌اند. در پایان تسک با baseline مقایسه می‌شود |
| ۶.۵ | `npx tsc --noEmit` بدون خطا | ⏳ | |
| ۶.۶ | `yarn test` سبز | ⏳ | |
| ۶.۷ | `TenantSchemaCoverageTest` + `TenantLookupInventoryTest` سبز | ⏳ | |