docs(booking): document service booking mode and close task 00

docs/api/appointment.md gains the service-reschedule endpoint, the service-mode
section under PATCH, exclude_appointment_uuid and clinic_uuid on
appointment-service-slots, and the my/appointments additions. All JSON bodies are
real output captured from the running endpoints, not hand-written.

New docs/architecture/booking-modes.md holds the endpoint/mode matrix, the
duration contract with a worked example (35 + 10 buffer means a 45-minute step,
so 11:00 is not offered even though it looks free), the reserve-entry rules, and
a placeholder for the resource mode task 06 will add.

Also fixes a pre-existing flaky test that blocked a green suite:
NumericFieldNormalizerTest used a fixed national_code against db_test, which is
never reset, so depending on execution order the endpoint rejected it as a
duplicate. The test already looped for a unique mobile but not for the national
code. Out of this task's scope, fixed and declared so the definition of done is
actually green rather than apparently green.

phpstan was measured against the pre-task commit rather than asserted: 14 errors
in 9 files before, the same 14 in the same 9 files now.

Task 00 complete: 1026 tests green across three consecutive runs, 604 frontend
tests green, slot-mode contract frozen and verified.

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>
This commit is contained in:
hamed
2026-07-30 15:27:18 +03:30
co-authored by Claude Opus 5
parent 56a3c3c0d6
commit 9891c2e44a
4 changed files with 312 additions and 32 deletions
@@ -1,6 +1,6 @@
# چک‌لیست — تسک ۰۰ (تکمیل نوبت‌دهی سرویسی در clinicpro)
**وضعیت کلی:** 🔄 در حال انجام — قابلیت ۹ از ۱۰ تمام شد (مانده: مستندات + بازبینی پایانی)
**وضعیت کلی:** ✅ تمام‌شده — ۱۰ قابلیت از ۱۰
**آخرین بازبینی:** ۱۴۰۵/۰۵/۰۸
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
@@ -17,8 +17,8 @@ UI: [_shared/ui-conventions.md](../_shared/ui-conventions.md)
| ۰.۲ | ~~fixture ها با تاریخ ثابت‌اند، نه `time()`~~ → fixture **ساختاری** است | ✅ | **انحراف عمدی از متن تسک.** تاریخ ثابتِ گذشته را `isWithinBookingWindow` رد می‌کند و snapshot خالی چیزی را تضمین نمی‌کند. به‌جایش: برنامهٔ قطعی (هر ۷ روز یک شیفت ۰۹:۰۰–۱۱:۰۰/۳۰ دقیقه) روی `+3 days`، و epoch/uuid با placeholder نرمال می‌شوند. آنچه قفل می‌شود: کلیدها، ترتیب، نوع‌ها، ساعت‌های محلی |
| ۰.۳ | کامنت «read-only، هیچ تسکی به‌روزش نمی‌کند» بالای هر سه fixture | ✅ | کلید `_readme` در دو JSON (در loader حذف می‌شود) + docblock در فایل PHP |
| ۰.۴ | هیچ متد موجود `SlotCalculatorService` ویرایش نشد | ✅ | فقط `getServiceStartTimes` یک پارامتر **اختیاری** با پیش‌فرض `null` گرفت. **اثبات کارکرد تور ایمنی:** تست منجمد همان لحظه قرمز شد و دقیقاً همان پارامتر را نشان داد، در حالی که دو قرارداد پاسخ سبز ماندند. fixture امضا یک بار با تاریخچهٔ مکتوب به‌روز شد (header خودش مجاز کرده) |
| ۰.۵ | `GET /appointment-slots` بیت‌به‌بیت دست‌نخورده | | در پایان تسک تأیید می‌شود |
| ۰.۶ | `GET /month-availability/{doctorUuid}` دست‌نخورده | | در پایان تسک تأیید می‌شود |
| ۰.۵ | `GET /appointment-slots` بیت‌به‌بیت دست‌نخورده | | `SlotModeFrozenTest` سبز در پایان تسک |
| ۰.۶ | `GET /month-availability/{doctorUuid}` دست‌نخورده | | همان تست |
| ۰.۷ | `active_slot_key` و `refreshActiveSlotKey()` دست‌نخورده | ✅ | `setIsReserve()` **وجود ندارد**؛ toggle رزرو از قبل با `rescheduleTo($start,$end,$isReserve)` انجام می‌شود که خودش `refreshActiveSlotKey()` را صدا می‌زند ([Appointment.php:316](../../../src/Appointment/Entity/Appointment.php)). یادداشت قبلی چک‌لیست غلط بود |
| ۰.۸ | `isSlotTaken` امضا و معنا دست‌نخورده | ✅ | لمس نشد؛ فقط الگویش تکرار شد |
| ۰.۹ | `--group=slot-mode-frozen` سبز | ✅ | `OK (3 tests, 8 assertions)` — نیازمند `#[Group]` attribute بود، نه `@group` (PHPUnit 12 annotation را حذف کرده) |
@@ -87,8 +87,8 @@ UI: [_shared/ui-conventions.md](../_shared/ui-conventions.md)
| ۴.۲ | `ServiceBookingCalculatorTest` — موفق/خطا/مرزی | ✅ | ۱۳ تست / ۲۹ assertion سبز. شامل: جمع مدت + بافر · `endFor` بدون بافر · override منشی بدون تغییر پیش‌فرض سرویس · چهار مسیر خطا با کد/پیام/فیلد دقیق · uuid ناموجود از سرویسِ محیط دیگر **قابل تفکیک نیست** · `allowInactive` → warning · فهرست خالی → صفر · override نامعتبر (۰ و منفی) → fallback · پزشک بی‌برنامه → پیش‌فرض `slot` |
| ۴.۳ | `ServiceRescheduleTest` — شامل «حذف سرویس → مدت خودکار» | ✅ | ۱۵ تست / ۳۴ assertion. شامل: جابه‌جایی بی‌ارسال مدت · حذف سرویس → کوچک‌شدن خودکار · **زمان فعلیِ خود نوبت با سرویس بلندتر پذیرفته می‌شود** (اثر `excludeAppointmentId`) · سرویس غیرفعالِ موجود → warning · حالت اسلاتی → `ERR_APPOINTMENT_004` · بیرون شیفت → `ERR_APPOINTMENT_001` · رزرو → پیام ارجاع به ویرایش · `start` غایب · سرویس بیگانه · افزودن سرویس غیرفعال تازه · گذشته · نوبت بی‌سرویس · نسخهٔ کهنه → ۴۰۹ · نوبت شخص دیگر → ۴۰۳ · uuid ناموجود → ۴۰۴ |
| ۴.۴ | `PatchServiceDurationTest` — شامل «در حالت اسلاتی هیچ‌کدام اجرا نمی‌شود» | ✅ | ۹ تست / ۲۴ assertion. شامل: بازمحاسبهٔ مدت با تعویض سرویس · هم‌گامی ستون تکی · PATCH فقط-یادداشت بی‌اعتبارسنجی · رزرو معاف ولی مدت‌دار · تبدیل رزرو با همان PATCH · مدت ناسازگار → `ERR_APPOINTMENT_003` با عدد درست در پیام · سرویس بیگانه → ۴۲۲ · **حالت اسلاتی هر مدتی را می‌پذیرد و ستون سرویسی `null` می‌ماند** · نوبت سرویسیِ بی‌سرویس قفل نمی‌شود |
| ۴.۵ | `ConvertReserveTest` — شامل `active_slot_key` و رقابت | ⏳ | |
| ۴.۶ | `ServiceModeSectionDurationTest` موجود سبز ماند | | |
| ۴.۵ | ~~`ConvertReserveTest`~~ → پوشش در `PatchServiceDurationTest` | ✅ | فایل جدا ساخته نشد چون endpoint جدا ساخته نشد (ردیف ۱.۷). تبدیل رزرو در `testReserveConvertsToATimedAppointmentThroughPatch` و `testReserveEntryStoresServicesWithoutDurationCheck` پوشش دارد. **رقابت روی `active_slot_key`** جدا تست نشد: مکانیزمش دست‌نخورده است و `SlotUniquenessTest` موجود از قبل می‌سنجدش |
| ۴.۶ | `ServiceModeSectionDurationTest` موجود سبز ماند | | ۱۱ تست سبز در هر اجرا؛ همان تستی که بیت‌به‌بیت‌بودنِ استخراج `ServiceBookingCalculator` را تضمین کرد |
| ۴.۷ | `BookingTenantTest` موجود سبز ماند | ✅ | داخل `tests/Appointment` — کل ۳۰۹ تست `tests/Appointment` + `tests/Shared` سبز |
| ۴.۱۰ | `ServiceSlotExcludeSelfTest` — رفتار exclude | ✅ | ۶ تست / ۱۱ assertion. شامل: بازهٔ خودِ نوبت با exclude برمی‌گردد · مدت بلندتر روی همان ساعت · نوبتِ دیگری همچنان اشغال می‌ماند · `null` صریح و ضمنی خروجی یکسان · فیلتر repository فقط همان ردیف · exclude کردن نوبت رزرو بی‌اثر |
| ۴.۹ | `AppointmentServiceFieldsTest` — متدها و ستون‌های جدید | ✅ | ۹ تست / ۲۴ assertion. شامل: هم‌گامی ستون تکی · حفظ ترتیب · فهرست خالی → `null` · حالت اسلاتی هر دو ستون `null` · مدتِ `null` بافر را هم `null` می‌کند · تکراری‌ها dedup · نوبت قدیمیِ فقط-تکی · بقای مقادیر پس از flush/clear |
@@ -101,10 +101,10 @@ UI: [_shared/ui-conventions.md](../_shared/ui-conventions.md)
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | `docs/api/appointment.md` دو endpoint جدید + توسعهٔ PATCH | | |
| ۵.۲ | ماتریس «کدام endpoint در کدام حالت» | | |
| ۵.۳ | `docs/architecture/booking-modes.md` ساخته شد | | تسک ۰۶ حالت سوم را اضافه می‌کند |
| ۵.۴ | دو کد خطای جدید مستند شد | | |
| ۵.۱ | `docs/api/appointment.md` — endpoint جدید + توسعهٔ PATCH | | `service-reschedule` کامل · بخش «حالت نوبت‌دهی سرویسی» زیر PATCH · `exclude_appointment_uuid` و `clinic_uuid` · افزوده‌های `my/appointments`. **JSON واقعی** از اجرای واقعی endpoint (ابزار موقت `--group=dump-docs`، بعد حذف شد) — نه دست‌ساز |
| ۵.۲ | ماتریس «کدام endpoint در کدام حالت» | | در `docs/architecture/booking-modes.md` |
| ۵.۳ | `docs/architecture/booking-modes.md` ساخته شد | | ماتریس، قرارداد مدت با مثال واقعی (گام ۴۵ دقیقه → ۱۱:۰۰ پیشنهاد نمی‌شود)، بخش نوبت رزرو، و جای خالیِ حالت `resource` برای تسک ۰۶ |
| ۵.۴ | دو کد خطای جدید مستند شد | | `ERR_APPOINTMENT_003`/`_004` در جدول خطاهای هر دو endpoint، با نمونهٔ JSON واقعی |
## ۵.۴ کارِ کشف‌شده وسط اجرا (اعلام‌شده، نه بی‌صدا)
@@ -118,7 +118,7 @@ UI: [_shared/ui-conventions.md](../_shared/ui-conventions.md)
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ج.۱ | یک شکست flaky در اجرای ترکیبی `tests/Appointment tests/Shared` | ⚠️ | یک بار ۱ failure دید، سه اجرای بعدی سبز (۳۲۴ تست). نام تست ثبت نشد چون خروجی از دست رفت. **بازتولید نشد** — بدهی ثبت‌شده، نه «حل‌شده». احتمال: برخورد شمارهٔ موبایل تصادفی در `ApiTestCase::createUser` روی `db_test` که هرگز ریست نمی‌شود (خودِ کلاس این را مستند کرده) |
| ج.۱ | flaky در سوئیت کامل — **شناسایی و رفع شد** | ✅ | `NumericFieldNormalizerTest::testSecretaryCreatedWithPersianDigitsIsStoredLatin`. علت: `national_code` ثابتِ `۰۰۱۲۳۴۵۶۷۸` روی `db_test` که ریست نمی‌شود؛ بسته به ترتیب اجرا ۴۲۲ «تکراری» می‌گرفت. خودِ تست برای **موبایل** حلقهٔ یکتاسازی داشت ولی برای کد ملی نداشت. رفع: کد ملی تصادفی + assert پایین‌دستی از همان متغیر. سه اجرای کامل متوالی سبز. **خارج از دامنهٔ این تسک بود؛ اعلام و رفع شد تا DoD واقعاً سبز باشد، نه ظاهراً** |
| ج.۲ | یک PHPUnit Notice در `tests/Shared` | ⚠️ | پیش از تغییرات این تسک هم بود (baseline). خارج از دامنهٔ این تسک |
| ج.۳ | `db_test` تاریخچهٔ migration جدا دارد | ⚠️ | `doctrine:migrations:migrate` روی آن می‌شکند (`Table users already exists`)؛ ستون‌های جدید با `ALTER` دستی اضافه شدند. برای تسک‌های بعدی هم همین لازم است |
@@ -126,15 +126,15 @@ 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` بدون خطای جدید | 🔄 | `analyse src/Appointment`**No errors**. تحلیل کامل `src` ۱۴ خطا دارد ولی **هیچ‌کدام در فایل‌های این تسک نیست** (AuthController، BillingController، ClinicServiceController، ServiceItem، DoctorClaimService، InventoryService، PatientService، SecretaryService، HealthController) — از قبل بوده‌اند. در پایان تسک با baseline مقایسه می‌شود |
| ۶.۵ | `npx tsc --noEmit` بدون خطا | | |
| ۶.۶ | `yarn test` سبز | | |
| ۶.۷ | `TenantSchemaCoverageTest` + `TenantLookupInventoryTest` سبز | | |
| ۶.۸ | `docs/api/*` به‌روز شد | | |
| ۶.۹ | چک‌لیست UI (بخش ۳) کامل شد | ⏳ | |
| ۶.۱۰ | `nobat724_front` و `clinic-pro-tauri` دستی بررسی شدند | | `service_item` تکی هم‌گام است؟ |
| ۶.۱۱ | commit شد، سپس `graphify update .` | | |
| ۶.۱۲ | موارد به‌تعویق‌افتاده با دلیل و تسک مقصد ثبت شدند | | |
| ۶.۱ | همهٔ ردیف‌های بالا وضعیت نهایی دارند | ✅ | تنها ⏳ باقی‌مانده **۳.۱۳** است، با دلیل و تسک مقصد. سه ردیف 🔄: بررسی **چشمی** دارک‌مود/فشرده/موبایل (۳.۹/۳.۱۰/۳.۱۶) — استدلالی تأیید شدند ولی در مرورگر دیده نشدند. ⚠️ ها: ۳.۱۷ (پروژه فایل i18n ندارد) و ج.۲ (PHPUnit notice از قبل) |
| ۶.۲ | `ddev exec php bin/phpunit` کامل سبز | | **۱۰۲۶ تست / ۲۸۵۰ assertion، سه اجرای متوالی سبز.** برای رسیدن به این، یک flaky از قبل‌موجود رفع شد (ج.۱) |
| ۶.۳ | `--group=slot-mode-frozen` سبز | | `OK (3 tests, 8 assertions)` |
| ۶.۴ | `phpstan analyse` بدون خطای جدید | | **با baseline اندازه‌گیری شد، نه ادعا:** روی کامیت پیش از تسک ۱۴ خطا در ۹ فایل، و الان **دقیقاً همان ۱۴ خطا در همان ۹ فایل** — صفر خطای جدید. `analyse src/Appointment` تنها → `No errors` |
| ۶.۵ | `npx tsc --noEmit` بدون خطا | | خروجی خالی |
| ۶.۶ | `yarn test` سبز | | `86 files / 604 tests passed`. ⚠️ داخل ddev باینری esbuild پلتفرم اشتباه دارد (محیطی، از قبل)؛ روی host اجرا شد |
| ۶.۷ | `TenantSchemaCoverageTest` + `TenantLookupInventoryTest` سبز | | `OK (7 tests, 163 assertions)`. شمارندهٔ inventory با دلیل به‌روز شد |
| ۶.۸ | `docs/api/*` به‌روز شد | | `docs/api/appointment.md` + سند معماری جدید |
| ۶.۹ | چک‌لیست UI (بخش ۳) کامل شد | ✅ | با سه ردیف 🔄 (بررسی چشمی دارک‌مود/فشرده/موبایل انجام **نشد** — استدلالی تأیید شد) و یک ⏳ به‌تعویق‌افتاده |
| ۶.۱۰ | `nobat724_front` و `clinic-pro-tauri` دستی بررسی شدند | | **`nobat724_front`**: فقط `service_item_uuids[]` را می‌فرستد (`services/response.js:85`، `components/appointment/detail/SubmitData.js:152`)؛ هیچ‌جا `service_item` را نمی‌خواند → افزوده‌های additive نمی‌شکنند. **`clinic-pro-tauri`**: `src/service/response.js` فقط `appointment-settings/weekly-schedule` را صدا می‌زند، هیچ endpoint نوبت/سرویسی → متأثر نیست |
| ۶.۱۱ | commit شد، سپس `graphify update .` | | هر قابلیت **دو کامیت**: کد، سپس گراف جدا (طبق دستور کاربر) |
| ۶.۱۲ | موارد به‌تعویق‌افتاده با دلیل و تسک مقصد ثبت شدند | | ۳.۱۳ (URL state) · بررسی چشمی UI · اصلاح فرمول جمع مدت → تسک ۰۴ |