Add checklists for tasks 11 to 14 covering credit ledger, treatment course, cancellation policies, and event utilization

- Created checklist for task 11: Package and Credit Ledger
- Created checklist for task 12: Treatment Course
- Created checklist for task 13: Cancellation Policy, No-Show, and Waitlist
- Created checklist for task 14: Domain Events and Utilization Reports
This commit is contained in:
hamed
2026-07-30 12:12:45 +03:30
parent 158dcb58aa
commit 70739691d1
16 changed files with 1726 additions and 53 deletions
@@ -128,6 +128,24 @@ JSON هفتگی per `(doctor, clinic)`، هر روز چند `session` با
- تنها منبعی که تداخلش بررسی می‌شود پزشک است؛ اگر دو سرویس هم‌زمان به یک پرسنل - تنها منبعی که تداخلش بررسی می‌شود پزشک است؛ اگر دو سرویس هم‌زمان به یک پرسنل
یا یک دستگاه نیاز داشته باشند، سیستم متوجه نمی‌شود. یا یک دستگاه نیاز داشته باشند، سیستم متوجه نمی‌شود.
### ۲-۵ب حالت سرویسی **نیمه‌کاره** است — پنج شکاف در چرخهٔ عمر نوبت
مسیر **رزرو** کار می‌کند، ولی بقیهٔ چرخهٔ عمر نه. این‌ها پیش‌نیاز موتور چندمنبعی‌اند و
تسک‌های [۰۰](task-00-service-mode-completion/) و [۰۰ب](task-00b-nobat724-service-mode/)
می‌بندندشان:
| # | شکاف | محل |
|---|---|---|
| ۱ | `PATCH /appointment/{uuid}` مدت دلخواه می‌پذیرد؛ بافر را نادیده می‌گیرد؛ فقط `service_item_uuid` تکی را به‌روز می‌کند در حالی که `service_items` (ManyToMany) دست‌نخورده می‌ماند | [AppointmentController.php:1077](../../../src/Appointment/Controller/AppointmentController.php) |
| ۲ | `AppointmentEditPage` سه فیلد آزاد `date`/`start`/`end` دارد و هیچ `ServiceSlotPicker` ای ندارد — منشی نوبت ۴۵ دقیقه‌ای را ۲۰ دقیقه می‌کند و سیستم قبول می‌کند | [AppointmentEditPage.tsx:74](../../../assets/admin/pages/AppointmentEditPage.tsx) |
| ۳ | نوبت رزرو (`is_reserve`) صریحاً از حالت سرویسی حذف شده (`serviceMode = mode === 'service' && !isReserve`) و مسیر تبدیل رزرو به نوبت سرویسی وجود ندارد | [NewAppointmentDrawer.tsx:72](../../../assets/admin/components/NewAppointmentDrawer.tsx) |
| ۴ | سایت عمومی چهار رنگ hard-code در مرحلهٔ انتخاب سرویس دارد (`#5559CE`, `#3B3B3B`, `#7A7A7A`, `bg-white`) و در دارک‌مود می‌شکند؛ همچنین مدت را **موازی با بک‌اند** حساب می‌کند | `nobat724_front/components/appointment/service/index.js` |
| ۵ | پنل کاربر سایت نام سرویس و مدت نوبت را نشان نمی‌دهد و مسیر جابه‌جایی سرویس‌آگاه ندارد | `nobat724_front/.../turns/Card.js` · `isTurnsDetails/*` |
نکتهٔ ۴ دو مشکل در یک فایل است: انحراف از دیزاین‌سیستم، و منبع دوم حقیقت برای مدت.
دومی مهم‌تر است — وقتی تسک ۰۴ فرمول را به «زمان تنها / زمان اضافه» عوض کند، سایت عدد
قدیمی نشان می‌دهد و بیمار مدتی می‌بیند که با مدت واقعی نوبتش نمی‌خواند.
### ۲-۶ ثبت نوبت و همزمانی ### ۲-۶ ثبت نوبت و همزمانی
[src/Appointment/Entity/Appointment.php](../../../src/Appointment/Entity/Appointment.php): [src/Appointment/Entity/Appointment.php](../../../src/Appointment/Entity/Appointment.php):
@@ -182,6 +200,7 @@ no_show, following_up, salon` + `AppointmentEvent` برای تاریخچه. تق
| بخش مستند | دارد | ندارد | تسک | | بخش مستند | دارد | ندارد | تسک |
|---|---|---|---| |---|---|---|---|
| — نوبت‌دهی سرویسی موجود | مسیر رزرو (پنل + سایت) | ویرایش، جابه‌جایی، رزرو، پنل بیمار، دیزاین‌سیستم سایت | **۰۰، ۰۰ب** |
| ۴ کلینیک/شعبه/اتاق | tenant دوسطحی | Branch، Room، ساعت کاری شعبه | ۰۱ | | ۴ کلینیک/شعبه/اتاق | tenant دوسطحی | Branch، Room، ساعت کاری شعبه | ۰۱ |
| ۵ تعریف خدمات | سرویس، قیمت، مدت، بیمه | گروه آیتم، دو نوع زمان، ناسازگاری، override شعبه | ۰۴ | | ۵ تعریف خدمات | سرویس، قیمت، مدت، بیمه | گروه آیتم، دو نوع زمان، ناسازگاری، override شعبه | ۰۴ |
| ۶ منابع | پرسنل بدون تقویم | نوع منبع، ظرفیت، مهارت، استخر، نیازمندی | ۰۲، ۰۳ | | ۶ منابع | پرسنل بدون تقویم | نوع منبع، ظرفیت، مهارت، استخر، نیازمندی | ۰۲، ۰۳ |
@@ -222,14 +241,27 @@ no_show, following_up, salon` + `AppointmentEvent` برای تاریخچه. تق
## ۵. ترتیب اجرا ## ۵. ترتیب اجرا
``` ```
۰۱ شعبه/اتاق۰۲ منابع و مهارت ── ۰۳ تقویم منبع ─┐ ۰۰ تکمیل سرویسی (clinicpro) ── ۰۰ب سازگارسازی سایت ← فاز ۰، پیش‌نیاز بقیه
└─ ۰۴ کاتالوگ خدمات v2 ── ۰۵ بخش‌های نوبت ─┴─ ۰۶ جستجوی وقت ── ۰۷ رزرو و ثبت
├─ ۰۱ شعبه/اتاق ─┬─ ۰۲ منابع و مهارت ── ۰۳ تقویم منبع ─┐
۰۸ قیمت‌گذاری و snapshot ────────────────┘ │ └─ ۰۴ کاتالوگ v2 ── ۰۵ بخش‌های نوبت ─┴─ ۰۶ جستجوی وقت ── ۰۷ رزرو و ثبت
۰۹ موتور قوانین ── ۱۰ فرم و sandbox قانون ۰۸ قیمت‌گذاری و snapshot ───────────────┘
۱۱ پکیج و دفتر اعتبار ── ۱۲ دوره درمان ── ۱۳ لغو/عدم‌حضور/لیست انتظار │ ۰۹ موتور قوانین ── ۱۰ فرم و sandbox قانون
۱۴ رویدادها و گزارش بهره‌وری │ ۱۱ پکیج و دفتر اعتبار ── ۱۲ دوره درمان ── ۱۳ لغو/عدم‌حضور/انتظار
│ │
└──────────────────────── ۱۴ رویدادها و گزارش بهره‌وری
``` ```
**فاز ۰ اختیاری نیست.** اگر حالت `resource` روی حالت `service` نیمه‌کاره ساخته شود، هر
باگ موجود سرویسی به موتور جدید ارث می‌رسد و تشخیص منبعش غیرممکن می‌شود.
## ۶. سه قاعدهٔ حاکم بر همهٔ تسک‌ها
| سند | چه می‌گوید |
|---|---|
| [_shared/red-lines.md](_shared/red-lines.md) | منطق اسلاتی به هیچ عنوان دست‌کاری نمی‌شود · فهرست کامل فایل‌های قفل‌شده · تست `--group=slot-mode-frozen` |
| [_shared/ui-conventions.md](_shared/ui-conventions.md) | هر صفحه یا بخش جدید عیناً با دیزاین‌سیستم موجود — توکن‌ها، کامپوننت‌های `ui/`، پنج قاعدهٔ غیرقابل‌مذاکره |
| [_shared/definition-of-done.md](_shared/definition-of-done.md) | هیچ تسکی بدون تکمیل چک‌لیستش تمام نیست — ✅ 🔄 ⏳ ⚠️ |
+117 -43
View File
@@ -3,17 +3,34 @@
پیاده‌سازی تدریجی [clinic-pro-mostanad-sade.md](../clinic-pro-mostanad-sade.md) روی کد موجود. پیاده‌سازی تدریجی [clinic-pro-mostanad-sade.md](../clinic-pro-mostanad-sade.md) روی کد موجود.
گزارش وضعیت فعلی و تحلیل شکاف: [00-current-state-report.md](00-current-state-report.md) گزارش وضعیت فعلی و تحلیل شکاف: [00-current-state-report.md](00-current-state-report.md)
> **پیش‌فرض کلیدی:** بازنویسی نداریم. نوبت‌دهی اسلاتی (`booking_mode=slot`) و نوبت‌دهی ---
> سرویسیِ فعلی (`booking_mode=service`) تا آخر این مسیر بدون تغییر رفتار کار می‌کنند.
> حالت جدید `booking_mode=resource` کنارشان اضافه می‌شود. ## ⛔ سه قاعده‌ای که پیش از هر تسکی باید بخوانی
| سند | چه می‌گوید |
|---|---|
| [_shared/red-lines.md](_shared/red-lines.md) | **منطق اسلاتی به هیچ عنوان دست‌کاری نمی‌شود** · نوبت‌دهی سرویسی در همین فاز کامل می‌شود |
| [_shared/ui-conventions.md](_shared/ui-conventions.md) | **هر صفحه یا بخش جدید عیناً با دیزاین‌سیستم موجود** — هیچ طراحی جدید |
| [_shared/definition-of-done.md](_shared/definition-of-done.md) | **هیچ تسکی بدون تکمیل چک‌لیستش تمام نیست** — ✅ 🔄 ⏳ ⚠️ |
در تناقض، این سه سند بر متن تسک‌ها برنده‌اند.
--- ---
## لیست تسک‌ها ## لیست تسک‌ها
| تسک | ماژول | Endpoint جدید | وابستگی | زمان | ### فاز ۰ — تثبیت وضعیت فعلی (پیش‌نیاز بقیه)
|-----|-------|--------------|---------|------|
| [۰۱](task-01-branch-room/) | شعبه و اتاق | ۸ | — | ۱۰-۱۲h | | تسک | ماژول | پروژه | Endpoint | وابستگی | زمان |
|-----|-------|-------|----------|---------|------|
| [۰۰](task-00-service-mode-completion/) | تکمیل نوبت‌دهی سرویسی | `clinicpro` | ۴ | — | ۱۴-۱۸h |
| [۰۰ب](task-00b-nobat724-service-mode/) | سازگارسازی سایت عمومی | `nobat724_front` | — | ۰۰ | ۱۰-۱۴h |
### فاز ۱ — هستهٔ چندمنبعی
| تسک | ماژول | Endpoint | وابستگی | زمان |
|-----|-------|----------|---------|------|
| [۰۱](task-01-branch-room/) | شعبه و اتاق | ۸ | ۰۰ | ۱۰-۱۲h |
| [۰۲](task-02-resource-model/) | منبع، نوع منبع، مهارت، استخر | ۱۴ | ۰۱ | ۱۴-۱۸h | | [۰۲](task-02-resource-model/) | منبع، نوع منبع، مهارت، استخر | ۱۴ | ۰۱ | ۱۴-۱۸h |
| [۰۳](task-03-resource-calendar/) | تقویم منبع، مرخصی، تعطیلات ملی | ۹ | ۰۱، ۰۲ | ۱۲-۱۴h | | [۰۳](task-03-resource-calendar/) | تقویم منبع، مرخصی، تعطیلات ملی | ۹ | ۰۱، ۰۲ | ۱۲-۱۴h |
| [۰۴](task-04-service-catalog-v2/) | کاتالوگ خدمات v2 (گروه آیتم، دو نوع زمان) | ۱۰ | ۰۱ | ۱۴-۱۶h | | [۰۴](task-04-service-catalog-v2/) | کاتالوگ خدمات v2 (گروه آیتم، دو نوع زمان) | ۱۰ | ۰۱ | ۱۴-۱۶h |
@@ -21,14 +38,29 @@
| [۰۶](task-06-availability-engine/) | موتور جستجوی وقت چندمنبعی | ۲ | ۰۳، ۰۵ | ۲۰-۲۴h | | [۰۶](task-06-availability-engine/) | موتور جستجوی وقت چندمنبعی | ۲ | ۰۳، ۰۵ | ۲۰-۲۴h |
| [۰۷](task-07-hold-and-book/) | رزرو موقت و ثبت نهایی چندمنبعی | ۴ | ۰۶ | ۱۶-۲۰h | | [۰۷](task-07-hold-and-book/) | رزرو موقت و ثبت نهایی چندمنبعی | ۴ | ۰۶ | ۱۶-۲۰h |
| [۰۸](task-08-pricing-snapshot/) | لیست قیمت بازه‌دار و snapshot فاکتور | ۷ | ۰۴، ۰۷ | ۱۲-۱۴h | | [۰۸](task-08-pricing-snapshot/) | لیست قیمت بازه‌دار و snapshot فاکتور | ۷ | ۰۴، ۰۷ | ۱۲-۱۴h |
### فاز ۲ — قوانین
| تسک | ماژول | Endpoint | وابستگی | زمان |
|-----|-------|----------|---------|------|
| [۰۹](task-09-policy-engine/) | موتور قوانین شش‌دسته‌ای | ۶ | ۰۵، ۰۶، ۰۸ | ۲۰-۲۴h | | [۰۹](task-09-policy-engine/) | موتور قوانین شش‌دسته‌ای | ۶ | ۰۵، ۰۶، ۰۸ | ۲۰-۲۴h |
| [۱۰](task-10-policy-admin-sandbox/) | فرم ساخت قانون + محیط آزمایش | ۲ | ۰۹ | ۱۰-۱۲h | | [۱۰](task-10-policy-admin-sandbox/) | فرم ساخت قانون + محیط آزمایش | ۲ | ۰۹ | ۱۰-۱۲h |
### فاز ۳ — کسب‌وکار
| تسک | ماژول | Endpoint | وابستگی | زمان |
|-----|-------|----------|---------|------|
| [۱۱](task-11-package-credit-ledger/) | پکیج و دفتر اعتبار جلسات | ۸ | ۰۸ | ۱۰-۱۲h | | [۱۱](task-11-package-credit-ledger/) | پکیج و دفتر اعتبار جلسات | ۸ | ۰۸ | ۱۰-۱۲h |
| [۱۲](task-12-treatment-course/) | دوره درمان | ۹ | ۰۷، ۱۱ | ۱۶-۲۰h | | [۱۲](task-12-treatment-course/) | دوره درمان | ۹ | ۰۷، ۱۱ | ۱۶-۲۰h |
| [۱۳](task-13-cancellation-waitlist/) | سیاست لغو، عدم حضور، لیست انتظار | ۷ | ۰۷ | ۱۰-۱۲h | | [۱۳](task-13-cancellation-waitlist/) | سیاست لغو، عدم حضور، لیست انتظار | ۷ | ۰۷ | ۱۰-۱۲h |
### فاز ۴ — بهینه‌سازی
| تسک | ماژول | Endpoint | وابستگی | زمان |
|-----|-------|----------|---------|------|
| [۱۴](task-14-events-utilization/) | رویدادهای دامنه و گزارش بهره‌وری | ۳ | ۰۷ | ۸-۱۰h | | [۱۴](task-14-events-utilization/) | رویدادهای دامنه و گزارش بهره‌وری | ۳ | ۰۷ | ۸-۱۰h |
**مجموع endpoint جدید: ~۹۲ · مجموع زمان: ۱۹۰ تا ۲۲۸ ساعت** **مجموع endpoint جدید: ~۹۶ · مجموع زمان: ۲۱۴ تا ۲۵۶ ساعت**
--- ---
@@ -37,57 +69,99 @@
``` ```
task-XX-name/ task-XX-name/
├── task.md ← شرح، دامنه، endpoint ها، معیار پذیرش، زمان ├── task.md ← شرح، دامنه، endpoint ها، معیار پذیرش، زمان
├── architecture.md ← فایل‌ها، entity ها، سرویس‌ها، لایه‌ها ├── architecture.md ← فایل‌ها، entity ها، سرویس‌ها، لایه‌ها، قواعد UI
├── database.md ← جداول، ستون‌ها، ایندکس‌ها، migration ├── database.md ← جداول، ستون‌ها، ایندکس‌ها، migration
├── implementation_notes.md ← نکات فنی، edge case، سازگاری عقب‌رو، تست ├── implementation_notes.md ← نکات فنی، edge case، سازگاری عقب‌رو، تست
├── checklist.md ← ☑️ وضعیت هر مورد: ✅ 🔄 ⏳ ⚠️
└── user_flow.md ← (تسک‌های پیچیده) جریان کاربری └── user_flow.md ← (تسک‌های پیچیده) جریان کاربری
``` ```
`checklist.md` **اجباری** است و ساختار ثابتی دارد:
```
۰. خط سرخ‌ها ۱. بک‌اند ۲. دیتابیس ۳. UI ۴. تست ۵. مستندات ۶. بازبینی پایانی
```
---
## چک‌لیست — قواعد
| نماد | معنی | اجازهٔ باقی‌ماندن در پایان تسک |
|---|---|---|
| ✅ | انجام‌شده و تأییدشده | بله |
| 🔄 | در حال انجام | **نه** |
| ⏳ | انجام‌نشده | **نه** — مگر با دلیل مکتوب و تسک مقصد |
| ⚠️ | نیازمند بررسی یا تست | **نه** — باید تعیین تکلیف شود |
**پیش از پایان هر تسک، همهٔ ردیف‌های چک‌لیست بازبینی می‌شوند و وضعیت نهایی می‌گیرند.**
هر تسک بخش «۶. بازبینی پایانی» دارد که تست‌ها، مستندات، UI و دو کلاینت دیگر را می‌سنجد.
--- ---
## ترتیب پیشنهادی اجرا ## ترتیب پیشنهادی اجرا
``` ```
۰۱۰۲ ── ۰۳ ─┐ ۰۰ ── ۰۰ب
└─ ۰۴ ── ۰۵ ─┴─ ۰۶ ── ۰۷ ─┬─ ۰۸ ─┬─ ۰۹ ── ۱۰
│ └─ ۱۱ ── ۱۲ ├─ ۰۱ ─┬─ ۰۲ ── ۰۳ ─┐
۱۳ └─ ۰۴ ── ۰۵ ─┴─ ۰۶ ── ۰۷ ─┬─ ۰۸ ─┬─ ۰۹ ─۱۰
└─ ۱۴ │ │ └─ ۱۱ ── ۱۲
│ ├─ ۱۳
│ └─ ۱۴
``` ```
فاز اول (هستهٔ قابل عرضه): ۰۱ تا ۰۸ — بعد از آن یک کلینیک زیبایی با اتاق، دستگاه و **فاز ۰ اختیاری نیست.** اگر حالت `resource` (تسک ۰۶) روی حالت `service` نیمه‌کاره ساخته
اپراتور می‌تواند واقعاً نوبت بگیرد. شود، هر باگ موجود سرویسی به موتور جدید ارث می‌رسد و تشخیص منبعش غیرممکن می‌شود.
فاز اول قابل عرضه: ۰۰ تا ۰۸ — بعد از آن یک کلینیک زیبایی با اتاق، دستگاه و اپراتور
می‌تواند واقعاً نوبت بگیرد.
---
## سه حالت نوبت‌دهی
| حالت | مقدار `WeeklySchedule.meta.booking_mode` | وضعیت |
|---|---|---|
| اسلاتی | `slot` | ⛔ **قفل** — پیش‌فرض، تولیدی، دست‌نخورده |
| سرویسی | `service` | 🔄 موجود ولی نیمه‌کاره → تسک ۰۰ و ۰۰ب کاملش می‌کنند |
| چندمنبعی | `resource` | ⏳ جدید — تسک ۰۶ به بعد |
`booking_mode` پس از اولین ثبت قفل می‌شود (`WeeklySchedule::getStoredBookingMode()`).
تسک ۰۶ یک استثنای کنترل‌شده اضافه می‌کند: ارتقای **یک‌طرفه** از `slot`/`service` به
`resource`، مشروط بر نبودِ نوبت فعال آینده. بازگشت ممنوع.
ماتریس کامل «کدام endpoint در کدام حالت» در `docs/architecture/booking-modes.md`
(تسک ۰۰ می‌سازد، تسک ۰۶ حالت سوم را اضافه می‌کند).
---
## اجبار خودکار خط سرخ
هر تسک باید این را سبز نگه دارد:
```bash
ddev exec php bin/phpunit --group=slot-mode-frozen
```
تسک ۰۰ این تست و سه fixture آن را می‌سازد. fixture ها بعد از آن **read-only** اند:
اگر تستی قرمز شد، **کد باید برگردد، نه fixture**.
--- ---
## قواعد مشترک همهٔ تسک‌ها ## قواعد مشترک همهٔ تسک‌ها
قواعد پروژه در [CLAUDE.md](../../../CLAUDE.md) و از [CLAUDE.md](../../../CLAUDE.md) و [docs/architecture/tenancy.md](../../architecture/tenancy.md).
[docs/architecture/tenancy.md](../../architecture/tenancy.md) بر همهٔ این تسک‌ها حاکم‌اند. فهرست کامل در [_shared/definition-of-done.md](_shared/definition-of-done.md):
مواردی که در هر تسک باید رعایت شوند:
1. هر entity جدید یا `TenantOwnedTrait` می‌گیرد یا در `GlobalTables` با دلیل ثبت می‌شود؛ 1. entity جدید یا `TenantOwnedTrait` می‌گیرد یا با دلیل در `GlobalTables` ثبت می‌شود
`TenantSchemaCoverageTest` را اجرا کن. 2. `entity_type, entity_id` ستون‌های **اول** هر ایندکس ترکیبیِ لیست
2. `entity_type, entity_id` ستون‌های **اول** هر ایندکس ترکیبی لیست. 3. هر uuid از request با `TenantOwnershipChecker` سنجیده می‌شود
3. هر uuid که از request می‌آید باید با `TenantOwnershipChecker` سنجیده شود؛ 4. timestamp ها `int` یونیکس؛ نمایش شمسی فقط در UI
`TenantLookupInventoryTest` شمارنده دارد. 5. کنترلر نازک · `BaseController` · `success/paginated/error`
4. timestamp ها `int` (Unix)، نه `DateTime`. نمایش شمسی فقط در UI. 6. API جدید فقط وقتی هیچ endpoint موجودی کافی نباشد — **دلیلش نوشته شود**
5. کنترلر نازک، `extends BaseController`، پاسخ با `success()/paginated()/error()`. 7. تست موفق + خطا + مرزی؛ بدون اجرای موفق تست، تسک تمام نیست
6. هر endpoint جدید یا تغییر یافته → به‌روزرسانی `docs/api/*.md` در همان نشست. 8. هر endpoint جدید یا تغییریافته → `docs/api/*` در همان نشست
7. تست موفق + خطا + مرزی برای هر تسک، وگرنه تسک تمام نیست. 9. رشته‌های UI فارسی از i18n؛ کد و کامیت و مستندات انگلیسی
8. رشته‌های UI فارسی، کد و کامیت انگلیسی. 10. سازگاری عقب‌رو: `nobat724_front` و `clinic-pro-tauri` در build خطا نمی‌دهند —
9. سازگاری عقب‌رو: `nobat724_front` و `clinic-pro-tauri` مصرف‌کنندهٔ همین APIها هستند بررسی دستی اجباری است (ردیف بازبینی پایانی هر تسک)
و در build خطا نمی‌دهند — هر تغییر قرارداد باید دستی بررسی شود. 11. اول commit، بعد `graphify update .`
---
## سازگاری با نوبت‌دهی فعلی
| حالت | منبع تنظیم | چه زمانی |
|---|---|---|
| `slot` | `WeeklySchedule.meta.booking_mode` | اسلات ثابت `duration_per_patient` — رفتار پیش‌فرض امروز |
| `service` | همان | طول = جمع مدت سرویس‌ها + buffer — پیاده‌شده، تک‌منبعی |
| `resource` | همان | **جدید** — برنامهٔ چندبخشی + چند منبع (تسک ۰۵ به بعد) |
`booking_mode` پس از اولین ثبت قفل می‌شود (`WeeklySchedule::getStoredBookingMode()`).
تسک ۰۶ باید مسیر ارتقای داوطلبانهٔ `service → resource` را باز کند، بدون اجبار.
@@ -0,0 +1,92 @@
# چک‌لیست — تسک ۰۱ (شعبه و اتاق)
**وضعیت کلی:** ⏳ شروع نشده · **آخرین بازبینی:**
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
[red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md)
---
## ۰. خط سرخ
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۰.۲ | `SlotCalculatorService` دست‌نخورده | ⏳ | این تسک به آن کاری ندارد |
| ۰.۳ | `location_id` در JSON برنامهٔ هفتگی دست‌نخورده | ⏳ | شعبه **بالای** آدرس می‌نشیند |
| ۰.۴ | `DoctorAddress` هیچ ستونی حذف/تغییر نداد | ⏳ | فقط `branch_id` تهی‌پذیر اضافه شد |
## ۱. بک‌اند
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | `Branch` + `BranchWorkingHours` + `Room` entity | ⏳ | |
| ۱.۲ | `BranchService` با گاردهای حذف قابل توسعه (`DeletionGuardInterface`) | ⏳ | تسک ۰۲ و ۰۷ گارد اضافه می‌کنند |
| ۱.۳ | `WorkingHoursService` — اعتبارسنجی و `sequence` سمت سرور | ⏳ | |
| ۱.۴ | ساعت با `start_minute`/`end_minute` عددی، نه رشتهٔ `"09:00"` | ⏳ | |
| ۱.۵ | هشت endpoint ساخته شد | ⏳ | |
| ۱.۶ | پزشک مستقل هم شعبه دارد (مطب = شعبه) | ⏳ | نه فقط `entity_type=clinic` |
| ۱.۷ | `app:branch:backfill` — dry-run پیش‌فرض، idempotent | ⏳ | |
| ۱.۸ | کنترلر نازک · `BaseController` · `success/paginated/error` | ⏳ | |
| ۱.۹ | `TenantOwnershipChecker` روی هر uuid از request | ⏳ | |
## ۲. دیتابیس
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | `branches` · `branch_working_hours` · `rooms` | ⏳ | |
| ۲.۲ | `entity_type, entity_id` ستون **اول** ایندکس‌های لیست | ⏳ | |
| ۲.۳ | `timezone` روی شعبه از روز اول | ⏳ | افزودن بعدی = backfill زمان‌دار |
| ۲.۴ | `rooms.capacity` — ظرفیت هم‌زمان | ⏳ | اتاق سه‌تخته = یک ردیف با ۳ |
| ۲.۵ | `branch_working_hours` در `GlobalTables::AGGREGATE_CHILDREN` | ⏳ | |
| ۲.۶ | `TenantSchemaCoverageTest` سبز | ⏳ | |
## ۳. UI
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | `BranchesPage` · `BranchFormPage` · `RoomsPage` | ⏳ | |
| ۳.۲ | `DataTable` با skeleton و empty state فارسی | ⏳ | |
| ۳.۳ | `PageHeader` با `backTo` روی زیرصفحه‌ها | ⏳ | |
| ۳.۴ | شهر/استان با `SearchableSelect` — هیچ `<select>` بومی | ⏳ | |
| ۳.۵ | وضعیت لیست در URL با `useUrlState` | ⏳ | |
| ۳.۶ | هیچ رنگ/شعاع/سایهٔ hard-code — همه از توکن | ⏳ | |
| ۳.۷ | دارک‌مود و حالت فشرده بررسی شد | ⏳ | |
| ۳.۸ | RTL و موبایل بررسی شد | ⏳ | |
| ۳.۹ | همهٔ رشته‌ها فارسی از i18n | ⏳ | |
| ۳.۱۰ | هشدار UI: «هیچ شعبهٔ فعالی باقی نمی‌ماند» | ⏳ | |
| ۳.۱۱ | مسیرها در `App.tsx` | ⏳ | |
## ۴. تست
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | `BranchCrudTest` — نقش‌ها، ۴۰۴ نه ۴۰۳ برای محیط دیگر | ⏳ | |
| ۴.۲ | `WorkingHoursTest``end<=start`، هم‌پوشانی، شبانه‌روزی `0..1440` | ⏳ | |
| ۴.۳ | `BranchDeletionTest` — شعبهٔ دارای اتاق فعال → ۴۲۲ | ⏳ | |
| ۴.۴ | `capacity=0` → ۴۲۲ | ⏳ | |
| ۴.۵ | `phpstan analyse src/Branch` بدون خطا | ⏳ | |
## ۵. مستندات
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | `docs/api/branch.md` + ثبت در `docs/api/README.md` | ⏳ | |
| ۵.۲ | تفسیر «شعبهٔ بدون ساعت کاری = تعریف‌نشده، نه همیشه‌باز» نوشته شد | ⏳ | تسک ۰۳ رویش حساب می‌کند |
| ۵.۳ | `docs/architecture/tenancy.md` جدول طبقه‌بندی به‌روز شد | ⏳ | |
## ۶. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | هیچ 🔄 و ⏳ بی‌دلیل نمانده | ⏳ | |
| ۶.۲ | `bin/phpunit` کامل سبز | ⏳ | |
| ۶.۳ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۶.۴ | `phpstan` بدون خطای جدید | ⏳ | |
| ۶.۵ | `npx tsc --noEmit` و `yarn test` سبز | ⏳ | |
| ۶.۶ | `TenantSchemaCoverageTest` + `TenantLookupInventoryTest` سبز | ⏳ | |
| ۶.۷ | `docs/api/*` به‌روز | ⏳ | |
| ۶.۸ | چک‌لیست UI کامل | ⏳ | |
| ۶.۹ | `nobat724_front` و `clinic-pro-tauri` بررسی شدند | ⏳ | این تسک قرارداد عمومی عوض نمی‌کند |
| ۶.۱۰ | commit، سپس `graphify update .` | ⏳ | |
| ۶.۱۱ | موارد به‌تعویق با دلیل و تسک مقصد | ⏳ | |
@@ -0,0 +1,100 @@
# چک‌لیست — تسک ۰۲ (منبع، نوع منبع، مهارت، استخر)
**وضعیت کلی:** ⏳ شروع نشده · **آخرین بازبینی:**
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
[red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md)
---
## ۰. خط سرخ
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۰.۲ | `Doctor` به `Resource` تبدیل **نشد** — فقط لینک شد | ⏳ | `appointments.doctor_id` سر جایش |
| ۰.۳ | `ClinicStaff` و `Room` هویت مستقل حفظ کردند | ⏳ | |
| ۰.۴ | `service_item_staff` و `ServiceItem.staffMembers` دست‌نخورده | ⏳ | |
| ۰.۵ | `WeeklySchedule.meta.buffer_minutes` دست‌نخورده | ⏳ | `setup/cleanup` مفهوم جداست |
## ۱. بک‌اند
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | `ResourceType` · `ClinicResource` · `Skill` · `ResourceSkill` | ⏳ | |
| ۱.۲ | `ResourcePool` · `ResourcePoolMember` | ⏳ | |
| ۱.۳ | نام کلاس `ClinicResource` (نه `Resource`) و جدول `clinic_resources` | ⏳ | |
| ۱.۴ | `ResourceLinker` — تنها نقطهٔ نگاشت پزشک/پرسنل/اتاق ↔ منبع | ⏳ | |
| ۱.۵ | حداکثر یکی از `doctor_id`/`staff_id`/`room_id` — قید در سازنده | ⏳ | |
| ۱.۶ | `capacity>1` روی `type=doctor` → ۴۲۲ | ⏳ | |
| ۱.۷ | `normalizeAttributes` — اسکالر، کلید `[a-z_]{1,40}`، سقف ۲۰ | ⏳ | |
| ۱.۸ | `ResourcePoolService` — اعضا هم‌شعبه و هم‌نوع، وگرنه ۴۲۲ | ⏳ | |
| ۱.۹ | `findEligible()` با `HAVING COUNT(DISTINCT skill) = n` | ⏳ | همهٔ مهارت‌ها، نه یکی |
| ۱.۱۰ | `StaffService` موجود `ResourceLinker::syncActive()` صدا می‌زند | ⏳ | نه lifecycle callback |
| ۱.۱۱ | چهارده endpoint | ⏳ | |
| ۱.۱۲ | `app:resource:backfill` — dry-run، idempotent | ⏳ | سه نوع سیستمی + پزشک/پرسنل/اتاق |
| ۱.۱۳ | `TenantOwnershipChecker` روی هر uuid از request | ⏳ | |
## ۲. دیتابیس
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | شش جدول ساخته شد | ⏳ | |
| ۲.۲ | سه UNIQUE تهی‌پذیر روی `doctor_id`/`staff_id`/`room_id` | ⏳ | |
| ۲.۳ | `idx_resource_skills_skill (skill_id, level)` | ⏳ | کوئری داغ تسک ۰۶ |
| ۲.۴ | `entity_type, entity_id` ستون اول ایندکس‌های لیست | ⏳ | |
| ۲.۵ | `resource_skills` و `resource_pool_members` در `AGGREGATE_CHILDREN` | ⏳ | |
| ۲.۶ | `TenantSchemaCoverageTest` سبز | ⏳ | |
## ۳. UI
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | `ResourcesPage` · `ResourceFormPage` · `ResourceTypesPage` · `SkillsPage` · `ResourcePoolsPage` | ⏳ | |
| ۳.۲ | فیلتر شعبه/نوع/فعال در URL با `useUrlState` | ⏳ | |
| ۳.۳ | شعبه و نوع با `SearchableSelect` | ⏳ | |
| ۳.۴ | مهارت‌ها با چیپ چندانتخابی | ⏳ | |
| ۳.۵ | `DataTable` با skeleton و empty state | ⏳ | |
| ۳.۶ | `backTo`/`BackButton` روی همهٔ زیرصفحه‌ها | ⏳ | |
| ۳.۷ | هیچ رنگ/شعاع hard-code | ⏳ | |
| ۳.۸ | دارک‌مود و حالت فشرده | ⏳ | |
| ۳.۹ | RTL و موبایل | ⏳ | |
| ۳.۱۰ | `setup/cleanup` با واحد فارسی «دقیقه» | ⏳ | |
| ۳.۱۱ | هشدار «این منبع N نوبت آیندهٔ فعال دارد» هنگام غیرفعال‌سازی | ⏳ | |
## ۴. تست
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | `ResourceCrudTest` — شعبهٔ محیط دیگر ۴۰۴، دو پل ۴۲۲، capacity | ⏳ | |
| ۴.۲ | `SkillAssignmentTest` — جایگزینی کامل، level، حذف در استفاده | ⏳ | |
| ۴.۳ | `ResourcePoolTest` — عضو شعبهٔ دیگر / نوع دیگر → ۴۲۲ | ⏳ | |
| ۴.۴ | `ResourceEligibilityTest` — دو مهارت، منبع با یکی برنمی‌گردد | ⏳ | |
| ۴.۵ | `BackfillResourceTest` — idempotent | ⏳ | |
| ۴.۶ | `TenantLookupInventoryTest` شمارنده به‌روز شد | ⏳ | |
## ۵. مستندات
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | `docs/api/resource.md` | ⏳ | |
| ۵.۲ | توضیح «ظرفیت ۳ = یک ردیف، نه سه منبع» | ⏳ | |
| ۵.۳ | تفاوت `setup/cleanup` منبع با `buffer_minutes` برنامه | ⏳ | |
| ۵.۴ | `docs/api/staff.md` بخش «رابطه با منبع» | ⏳ | |
| ۵.۵ | `docs/architecture/tenancy.md` به‌روز | ⏳ | |
## ۶. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | هیچ 🔄 و ⏳ بی‌دلیل نمانده | ⏳ | |
| ۶.۲ | `bin/phpunit` کامل سبز | ⏳ | |
| ۶.۳ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۶.۴ | `phpstan` بدون خطای جدید | ⏳ | |
| ۶.۵ | `npx tsc --noEmit` و `yarn test` سبز | ⏳ | |
| ۶.۶ | تست‌های tenant سبز | ⏳ | |
| ۶.۷ | `docs/api/*` به‌روز | ⏳ | |
| ۶.۸ | چک‌لیست UI کامل | ⏳ | |
| ۶.۹ | دو کلاینت دیگر بررسی شدند | ⏳ | |
| ۶.۱۰ | commit، سپس `graphify update .` | ⏳ | |
| ۶.۱۱ | موارد به‌تعویق با دلیل و تسک مقصد | ⏳ | |
@@ -0,0 +1,113 @@
# چک‌لیست — تسک ۰۳ (تقویم منبع، استثنا، تعطیلات ملی)
**وضعیت کلی:** ⏳ شروع نشده · **آخرین بازبینی:**
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
[red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md)
---
## ۰. خط سرخ
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۰.۲ | `SlotCalculatorService` **هیچ** تغییری نکرد | ⏳ | `ResourceAvailabilityService` سرویس موازی است |
| ۰.۳ | `WeeklySchedule` · `DateOverride` · `Holiday` موجود دست‌نخورده | ⏳ | |
| ۰.۴ | `HolidayResolver` به مسیر اسلاتی **وصل نشد** | ⏳ | تغییر رفتاری است؛ تسک جدا با تأیید محصول |
## ۱. `IntervalSet` — اول از همه
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | `src/Shared/Time/IntervalSet.php` ساخته شد **پیش از** entity ها | ⏳ | |
| ۱.۲ | قرارداد نیم‌باز `[start, end)` همه‌جا | ⏳ | |
| ۱.۳ | `IntervalSetTest` — هفت حالت مرزی سند | ⏳ | واحد، بدون DB |
| ۱.۴ | هر جای دیگری که بازه جمع/کم می‌کند از همین استفاده می‌کند | ⏳ | نه پیاده‌سازی دوم |
## ۲. بک‌اند
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | `ResourceCalendar` · `ResourceException` | ⏳ | |
| ۲.۲ | `NationalHoliday` · `TenantHolidayOverride` | ⏳ | |
| ۲.۳ | `ResourceAvailabilityService::rawWindows()` + `explainEmptyDay()` | ⏳ | پنج دلیل خالی‌بودن |
| ۲.۴ | `HolidayResolver::isClosedFor()` — تنها نقطهٔ ترکیب | ⏳ | |
| ۲.۵ | ترتیب تعطیلی: ملی + override محیط؛ منبع فقط می‌بندد نه باز می‌کند | ⏳ | |
| ۲.۶ | شعبهٔ بدون ساعت کاری = بی‌قید، **نه بسته** | ⏳ | همان تصمیم تسک ۰۱ |
| ۲.۷ | `union` استثناها بعد `subtract` — نه کسر پشت‌سرهم | ⏳ | |
| ۲.۸ | تبدیل روز هفته `(w+1)%7` عیناً مثل `SlotCalculatorService:359` | ⏳ | دو قرارداد = باگ قطعی |
| ۲.۹ | `DateTimeZone` صریح از `branch.timezone`، نه `date()` خالی | ⏳ | |
| ۲.۱۰ | نُه endpoint | ⏳ | |
| ۲.۱۱ | `app:holiday:import --year=` — idempotent | ⏳ | |
| ۲.۱۲ | `app:resource:calendar:backfill` از `WeeklySchedule` — dry-run | ⏳ | |
## ۳. کارایی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | `rawWindows` برای ۹۰ روز **دقیقاً ۵ کوئری** | ⏳ | |
| ۳.۲ | `AvailabilityQueryCountTest` عدد را قفل می‌کند | ⏳ | وگرنه اولین refactor می‌شکند |
| ۳.۳ | هیچ کوئری داخل حلقهٔ روز | ⏳ | |
| ۳.۴ | `idx_rex_resource_range` ساخته شد | ⏳ | |
## ۴. دیتابیس
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | چهار جدول | ⏳ | |
| ۴.۲ | `valid_from`/`valid_to` روی شیفت از روز اول | ⏳ | شیفت فصلی |
| ۴.۳ | نوع `blocked` در استثناها هست | ⏳ | تفکیک از مرخصی برای گزارش تسک ۱۴ |
| ۴.۴ | `national_holidays` در `GlobalTables::ENTITIES` با دلیل | ⏳ | |
| ۴.۵ | `resource_calendars` در `AGGREGATE_CHILDREN` | ⏳ | |
| ۴.۶ | `TenantSchemaCoverageTest` سبز | ⏳ | |
## ۵. UI
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | `ResourceCalendarPage` · `ResourceExceptionsPage` · `HolidaysSettingsPage` | ⏳ | |
| ۵.۲ | گرید هفت‌روزه با فرم ساده (بدون drag) | ⏳ | |
| ۵.۳ | بازهٔ تاریخ با `PersianDatePicker` | ⏳ | |
| ۵.۴ | `backTo`/`BackButton` روی هر سه | ⏳ | |
| ۵.۵ | تعطیلات سال با تیک «ما این روز کار می‌کنیم» | ⏳ | |
| ۵.۶ | هیچ رنگ/شعاع hard-code | ⏳ | |
| ۵.۷ | دارک‌مود و حالت فشرده | ⏳ | |
| ۵.۸ | RTL و موبایل | ⏳ | |
| ۵.۹ | دلیل خالی‌بودن روز با پیام فارسی نمایش داده می‌شود | ⏳ | پنج حالت |
## ۶. تست
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | `IntervalSetTest` | ⏳ | |
| ۶.۲ | `ResourceCalendarTest``end<=start`، هم‌پوشانی، شیفت شبانه → ۴۲۲ | ⏳ | |
| ۶.۳ | `ResourceAvailabilityTest` — تقاطع، شعبهٔ بی‌ساعت، نیم‌روزه، دو استثنای هم‌پوشان | ⏳ | |
| ۶.۴ | `AvailabilityQueryCountTest` | ⏳ | |
| ۶.۵ | `HolidayResolverTest` — ملی، override دو جهت | ⏳ | |
| ۶.۶ | `ImportNationalHolidaysTest` — idempotent | ⏳ | |
| ۶.۷ | بازهٔ > ۹۰ روز → ۴۲۲ | ⏳ | |
## ۷. مستندات
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۷.۱ | `docs/api/resource-calendar.md` | ⏳ | |
| ۷.۲ | `docs/api/appointment-settings.md` بخش «تفاوت با تقویم منبع» | ⏳ | دو سیستم قاطی نشوند |
| ۷.۳ | تصمیم «شیفت شبانه پشتیبانی نمی‌شود» مکتوب | ⏳ | |
## ۸. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۸.۱ | هیچ 🔄 و ⏳ بی‌دلیل نمانده | ⏳ | |
| ۸.۲ | `bin/phpunit` کامل سبز | ⏳ | |
| ۸.۳ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۸.۴ | `phpstan` بدون خطای جدید | ⏳ | |
| ۸.۵ | `npx tsc --noEmit` و `yarn test` سبز | ⏳ | |
| ۸.۶ | تست‌های tenant سبز | ⏳ | |
| ۸.۷ | `docs/api/*` به‌روز | ⏳ | |
| ۸.۸ | چک‌لیست UI کامل | ⏳ | |
| ۸.۹ | دو کلاینت دیگر بررسی شدند | ⏳ | |
| ۸.۱۰ | commit، سپس `graphify update .` | ⏳ | |
| ۸.۱۱ | موارد به‌تعویق با دلیل و تسک مقصد | ⏳ | اتصال `HolidayResolver` به مسیر اسلاتی |
@@ -0,0 +1,111 @@
# چک‌لیست — تسک ۰۴ (کاتالوگ خدمات v2)
**وضعیت کلی:** ⏳ شروع نشده · **آخرین بازبینی:**
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
[red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md)
---
## ۰. خط سرخ
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۰.۲ | نام `ServiceItem` عوض **نشد** | ⏳ | هفت جدول + سه ریپو رویش‌اند |
| ۰.۳ | `service_items.duration_minutes` حذف نشد | ⏳ | مدت پایهٔ سرویس می‌ماند |
| ۰.۴ | `ServiceSection` (بخش کلینیک) دست‌نخورده | ⏳ | مفهومش با دسته‌بندی فرق دارد |
| ۰.۵ | `BackwardCompatibilityTest`: سرویس بدون گروه → خروجی `appointment-service-slots` عیناً مثل قبل | ⏳ | ⭐ مهم‌ترین ردیف این تسک |
## ۱. بک‌اند
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | `ServiceCategory` درختی با materialized path | ⏳ | |
| ۱.۲ | `ItemGroup` با `min_select`/`max_select` | ⏳ | |
| ۱.۳ | `ServiceOption` («آیتم» مستند) با `solo_minutes`/`additional_minutes` | ⏳ | |
| ۱.۴ | `ServiceOptionRelation` — ناسازگاری متقارن، پیش‌نیاز جهت‌دار | ⏳ | |
| ۱.۵ | `assertNoCycle()` روی پیش‌نیاز — هنگام **ثبت**، نه ارزیابی | ⏳ | |
| ۱.۶ | `ServiceBranchOverride` با سه ستون تهی‌پذیر (override جزئی) | ⏳ | |
| ۱.۷ | `DurationCalculator` — اولین آیتم گروه solo، بقیه additional | ⏳ | |
| ۱.۸ | مرتب‌سازی نزولی بر `solo_minutes` + کامنت دلیل | ⏳ | قطعیت |
| ۱.۹ | `additional_minutes === null` → از `solo_minutes` (نه صفر) | ⏳ | |
| ۱.۱۰ | `ServiceSelectionValidator` — ترتیب شش‌مرحله‌ای، مالکیت محیط **اول** | ⏳ | |
| ۱.۱۱ | خطاها **همه با هم** برمی‌گردند، نه اولی | ⏳ | |
| ۱.۱۲ | `ServicePriceResolver` با override شعبه بر تعرفه | ⏳ | |
| ۱.۱۳ | `ServiceBookingCalculator` تسک ۰۰ به `DurationCalculator` وصل شد | ⏳ | ⭐ نقطهٔ اتصال — یک خط |
| ۱.۱۴ | ده endpoint | ⏳ | |
| ۱.۱۵ | `additional > solo` → ۴۲۲ | ⏳ | |
| ۱.۱۶ | سقف عمق درخت ۴ · جابه‌جایی با `UPDATE … REPLACE(path)` در تراکنش | ⏳ | |
## ۲. `POST /service-selection/validate` — عمومی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | مسیر در `security.yaml` whitelist شد | ⏳ | سایت بدون توکن صدا می‌زند |
| ۲.۲ | گارد دستی `TenantOwnershipChecker::belongsToPair()` روی همهٔ uuid ها | ⏳ | `TenantFilter` خاموش است |
| ۲.۳ | `symfony/rate-limiter` روی IP | ⏳ | enumerate کاتالوگ |
| ۲.۴ | uuid محیط دیگر → ۴۰۴ **بدون** هیچ اطلاعاتی در بدنه | ⏳ | |
## ۳. دیتابیس
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | چهار جدول جدید + `service_option_relations` | ⏳ | |
| ۳.۲ | سه ستون جدید روی `service_items` (همه تهی‌پذیر یا با default) | ⏳ | |
| ۳.۳ | `idx_svc_cat_path` برای شرط دسته‌ای تسک ۰۹ | ⏳ | |
| ۳.۴ | `entity_type, entity_id` ستون اول ایندکس‌های لیست | ⏳ | |
| ۳.۵ | `service_option_relations` در `AGGREGATE_CHILDREN` | ⏳ | |
| ۳.۶ | `TenantSchemaCoverageTest` + `TenantLookupInventoryTest` سبز | ⏳ | |
## ۴. UI
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | تب «گروه‌ها و آیتم‌ها» در `ServiceDetailPage` موجود | ⏳ | صفحهٔ جدید نه، تب |
| ۴.۲ | ویرایش inline `min/max` گروه | ⏳ | |
| ۴.۳ | جدول آیتم‌ها: نام، زمان تنها، زمان اضافه، قیمت، فعال | ⏳ | |
| ۴.۴ | ناسازگاری/پیش‌نیاز با `SearchableSelect` چندانتخابی | ⏳ | |
| ۴.۵ | **پیش‌نمایش زنده مدت** با debounce ۴۰۰ms | ⏳ | ⭐ بدون آن کل تسک بی‌اثر است |
| ۴.۶ | قیمت با `PriceInput` | ⏳ | |
| ۴.۷ | هیچ رنگ/شعاع hard-code | ⏳ | |
| ۴.۸ | دارک‌مود و حالت فشرده | ⏳ | |
| ۴.۹ | RTL و موبایل | ⏳ | |
| ۴.۱۰ | فرم با React Hook Form + Zod | ⏳ | |
| ۴.۱۱ | همهٔ رشته‌ها فارسی | ⏳ | |
| ۴.۱۲ | خطاهای اعتبارسنجی **زیر همان گروه** نمایش داده می‌شوند | ⏳ | |
## ۵. تست
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | `DurationCalculatorTest` — پنج حالت + **قطعیت** (جابه‌جایی ترتیب ورودی) | ⏳ | |
| ۵.۲ | `ServiceSelectionValidatorTest` — min/max/ناسازگار/پیش‌نیاز/چند خطا/۴۰۴ | ⏳ | |
| ۵.۳ | `ServicePriceResolverTest` — اولویت و override جزئی | ⏳ | |
| ۵.۴ | `ServiceCategoryTreeTest` — عمق، حذف، جابه‌جایی path | ⏳ | |
| ۵.۵ | `BackwardCompatibilityTest` | ⏳ | ⭐ |
| ۵.۶ | حلقهٔ پیش‌نیاز → ۴۲۲ | ⏳ | |
## ۶. مستندات
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | `docs/api/clinic-services.md`**جدول واژگان** عیناً از architecture | ⏳ | ⭐ بدون آن همه قاطی می‌کنند |
| ۶.۲ | endpoint های جدید | ⏳ | |
| ۶.۳ | یادآوری: `nobat724_front` قرارداد `service-selection/validate` را مصرف می‌کند | ⏳ | |
## ۷. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۷.۱ | هیچ 🔄 و ⏳ بی‌دلیل نمانده | ⏳ | |
| ۷.۲ | `bin/phpunit` کامل سبز | ⏳ | |
| ۷.۳ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۷.۴ | `phpstan` بدون خطای جدید | ⏳ | |
| ۷.۵ | `npx tsc --noEmit` و `yarn test` سبز | ⏳ | |
| ۷.۶ | تست‌های tenant سبز | ⏳ | |
| ۷.۷ | `docs/api/*` به‌روز | ⏳ | |
| ۷.۸ | چک‌لیست UI کامل | ⏳ | |
| ۷.۹ | ⚠️ مدت نوبت‌های چندسرویسی عوض می‌شود → `nobat724_front` و `clinic-pro-tauri` دستی بررسی شدند | ⏳ | ⭐ این تسک عدد را عوض می‌کند |
| ۷.۱۰ | commit، سپس `graphify update .` | ⏳ | |
| ۷.۱۱ | موارد به‌تعویق با دلیل و تسک مقصد | ⏳ | |
@@ -0,0 +1,104 @@
# چک‌لیست — تسک ۰۵ (بخش‌های نوبت و سازندهٔ برنامه)
**وضعیت کلی:** ⏳ شروع نشده · **آخرین بازبینی:**
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
[red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md)
---
## ۰. خط سرخ
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۰.۲ | `SlotCalculatorService` دست‌نخورده | ⏳ | |
| ۰.۳ | سرویس بدون `SegmentTemplate` → یک بخش مجازی با منبع `type=doctor` | ⏳ | ⭐ سازگاری حالت `service` فعلی |
| ۰.۴ | هیچ جدولی برای «برنامهٔ ساخته‌شده» ساخته نشد | ⏳ | DTO درون‌حافظه؛ ذخیره کار تسک ۰۷ |
## ۱. بک‌اند
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | `SegmentTemplate` · `SegmentRequirement` | ⏳ | |
| ۱.۲ | DTO های `AppointmentPlan` · `PlannedSegment` · `PlannedRequirement` (immutable) | ⏳ | |
| ۱.۳ | پنج سرویس جدا: `Builder` · `Assembler` · `DurationResolver` · `RequirementResolver` | ⏳ | نه یک کلاس بزرگ |
| ۱.۴ | `build()` تابع **خالص** است — بدون write، بدون cache، بدون `time()` | ⏳ | ⭐ تسک ۰۶ رویش حساب می‌کند |
| ۱.۵ | مرحلهٔ ۷ (`policies->applyToPlan`) از روز اول در امضا، حتی no-op | ⏳ | تسک ۰۹ پرش می‌کند |
| ۱.۶ | ادغام: `count` **بیشینه**، نه جمع | ⏳ | |
| ۱.۷ | `offset_minutes` نسبی است، نه timestamp مطلق | ⏳ | تسک ۰۶ برنامه را می‌لغزاند |
| ۱.۸ | `occupancyStartOffset`/`occupancyEndOffset` جدا از offset نمایشی | ⏳ | `setup/cleanup` |
| ۱.۹ | قید جنسیت بدون داده → ۴۲۲، **نه نادیده گرفتن** | ⏳ | ⭐ الزام جدی کلینیک زیبایی |
| ۱.۱۰ | `constraints` فهرست بسته؛ کلید ناشناخته → ۴۲۲ | ⏳ | |
| ۱.۱۱ | `NoEligibleResourceException` با پیام انسانی + `meta` | ⏳ | نقش، مهارت، شعبه |
| ۱.۱۲ | سه endpoint | ⏳ | |
| ۱.۱۳ | `patient_facing_minutes` جدا از `total_minutes` در پاسخ | ⏳ | بخش‌های بدون حضور بیمار |
| ۱.۱۴ | سقف‌ها اعمال شد: ۲۰ بخش · ۴۸۰ دقیقه · ۱۰ نیازمندی · ۲۰ آیتم | ⏳ | |
| ۱.۱۵ | `app:segment:seed-templates --preset=beauty\|dental\|physio` — dry-run | ⏳ | |
| ۱.۱۶ | `TenantOwnershipChecker` روی هر uuid از request | ⏳ | |
## ۲. دیتابیس
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | `segment_templates` · `segment_requirements` | ⏳ | |
| ۲.۲ | دقیقاً یکی از `service_item_id`/`service_option_id` — قید اپلیکیشنی | ⏳ | |
| ۲.۳ | دقیقاً یکی از `fixed_minutes`/`duration_share` | ⏳ | |
| ۲.۴ | جمع `duration_share` یک سرویس = ۱۰۰ (اعتبارسنجی هنگام ذخیره) | ⏳ | |
| ۲.۵ | `required_skills` به‌صورت JSON با دلیل مکتوب | ⏳ | نه جدول واسط |
| ۲.۶ | `segment_requirements` در `AGGREGATE_CHILDREN` | ⏳ | |
| ۲.۷ | `TenantSchemaCoverageTest` سبز | ⏳ | |
## ۳. UI
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | `ServiceSegmentsPage` به‌عنوان زیرصفحهٔ `ServiceDetailPage` | ⏳ | |
| ۳.۲ | لیست بخش‌ها با `sequence` عددی (بدون drag) | ⏳ | |
| ۳.۳ | نیازمندی‌ها: نقش با `SearchableSelect`، مهارت با چیپ، اشغال با رادیو | ⏳ | |
| ۳.۴ | هر گزینهٔ نوع اشغال توضیح فارسی دارد | ⏳ | انحصاری/اشتراکی/منفعل |
| ۳.۵ | **نوار پیش‌نمایش زمانی** با عرض متناسب مدت و آیکن منابع | ⏳ | ⭐ تنها راه فهم کاربر غیرفنی |
| ۳.۶ | خط «اپراتور واقعاً درگیر: N دقیقه» زیر نوار | ⏳ | ⭐ ارزش کار را نشان می‌دهد |
| ۳.۷ | `backTo` روی صفحه | ⏳ | |
| ۳.۸ | هیچ رنگ/شعاع hard-code — نوار پیش‌نمایش هم | ⏳ | |
| ۳.۹ | دارک‌مود و حالت فشرده | ⏳ | |
| ۳.۱۰ | RTL و موبایل — نوار پیش‌نمایش روی موبایل اسکرول افقی داخلی دارد | ⏳ | |
| ۳.۱۱ | همهٔ رشته‌ها فارسی | ⏳ | |
| ۳.۱۲ | `meta` خطای «هیچ منبعی» به لینک «افزودن منبع» تبدیل شد | ⏳ | بن‌بست نباشد |
## ۴. تست
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | `SegmentAssemblerTest` — ادغام، `count` بیشینه، غیر-mergeable | ⏳ | |
| ۴.۲ | `SegmentDurationResolverTest` — fixed ثابت، سهمی مقیاس‌پذیر، جمع = total | ⏳ | |
| ۴.۳ | `AppointmentPlanBuilderTest` — سناریوی کامل مستند (۰/۵/۳۵/۵۵، total=60) | ⏳ | |
| ۴.۴ | `AppointmentPlanBuilderTest`**قطعیت**: دو build یکسان → خروجی یکسان | ⏳ | |
| ۴.۵ | سرویس بدون الگو → یک بخش با منبع doctor | ⏳ | ⭐ |
| ۴.۶ | `RequirementResolverTest` — مهارت، هیچ کاندید، قید جنسیت، محیط دیگر | ⏳ | |
| ۴.۷ | `PlanLimitsTest` — ۲۱ بخش و ۴۸۱ دقیقه → ۴۲۲ | ⏳ | |
## ۵. مستندات
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | `docs/api/appointment-plan.md` | ⏳ | |
| ۵.۲ | جدول سه حالت اشغال و تفاوت عملی‌شان | ⏳ | |
| ۵.۳ | تفاوت `offset` نمایشی با `occupancy_offset` | ⏳ | |
| ۵.۴ | مثال کامل خروجی `preview` | ⏳ | |
## ۶. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | هیچ 🔄 و ⏳ بی‌دلیل نمانده | ⏳ | |
| ۶.۲ | `bin/phpunit` کامل سبز | ⏳ | |
| ۶.۳ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۶.۴ | `phpstan` بدون خطای جدید | ⏳ | |
| ۶.۵ | `npx tsc --noEmit` و `yarn test` سبز | ⏳ | |
| ۶.۶ | تست‌های tenant سبز | ⏳ | |
| ۶.۷ | `docs/api/*` به‌روز | ⏳ | |
| ۶.۸ | چک‌لیست UI کامل | ⏳ | |
| ۶.۹ | دو کلاینت دیگر بررسی شدند | ⏳ | `preview` هنوز مصرف نمی‌شود |
| ۶.۱۰ | commit، سپس `graphify update .` | ⏳ | |
| ۶.۱۱ | موارد به‌تعویق با دلیل و تسک مقصد | ⏳ | |
@@ -0,0 +1,119 @@
# چک‌لیست — تسک ۰۶ (موتور جستجوی وقت چندمنبعی)
**وضعیت کلی:** ⏳ شروع نشده · **آخرین بازبینی:**
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
[red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md)
---
## ۰. خط سرخ — حساس‌ترین تسک این فاز
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۰.۲ | `SlotCalculatorService` **هیچ** متدی عوض نشد | ⏳ | `AvailabilityEngine` کلاس موازی |
| ۰.۳ | `GET /appointment-slots` بیت‌به‌بیت دست‌نخورده | ⏳ | |
| ۰.۴ | `GET /appointment-service-slots` دست‌نخورده | ⏳ | حالت `service` موجود |
| ۰.۵ | `GET /month-availability/{doctorUuid}` دست‌نخورده | ⏳ | |
| ۰.۶ | `LegacyBookingUnchangedTest`: همهٔ تست‌های اسلاتی و سرویسی موجود سبز | ⏳ | ⭐ |
| ۰.۷ | انتخاب موتور فقط با `match($mode)` در کنترلر — **هیچ fallback خاموشی** | ⏳ | حالت اشتباه → `ERR_WRONG_BOOKING_MODE` |
| ۰.۸ | `DEFAULT_META['booking_mode']` همچنان `slot` | ⏳ | |
## ۱. بک‌اند
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | `AvailabilityEngine` · `CandidateGenerator` · `ResourceAllocator` · `OccupancyIndex` | ⏳ | |
| ۱.۲ | چهار استراتژی + `ResourcePickerInterface` با tagged_iterator | ⏳ | OCP |
| ۱.۳ | `DailyWindowCache`**فقط پنجرهٔ تقویمی**، هرگز اشغال | ⏳ | ⭐ |
| ۱.۴ | `MODE_RESOURCE` + `slot_granularity` + `picker_strategy` در `meta` | ⏳ | با اعتبارسنجی |
| ۱.۵ | `POST /appointment-settings/upgrade-booking-mode` — یک‌طرفه، با شرط | ⏳ | |
| ۱.۶ | دو endpoint جستجو | ⏳ | |
| ۱.۷ | `groupKey` = `(role, skills, constraints, indexInSegment)`؛ تطبیق بین‌بخشی روی سه جزء اول | ⏳ | ⭐ تلهٔ دو نیازمندی هم‌شکل در یک بخش |
| ۱.۸ | تخصیص حریصانه (بدون backtracking) + دلیل مکتوب | ⏳ | |
| ۱.۹ | دو گذر `setup/cleanup`: بیشینهٔ کاندیدها، بعد دقیق منبع انتخابی | ⏳ | |
| ۱.۱۰ | `hasRoom` شرط `expires_at > now` روی hold | ⏳ | |
| ۱.۱۱ | `reason` در پاسخ خالی: `no_resource`/`no_calendar`/`fully_booked`/`outside_window` | ⏳ | نه ۴۰۴، نه پیام واحد |
| ۱.۱۲ | قلاب `policies->filterSlots` از روز اول در امضا | ⏳ | تسک ۰۹ |
| ۱.۱۳ | سقف‌ها: بازه ۹۰ روز · `limit` ۲۰۰ · گام ≥۵ · کاندید ≤۵۰ per نیازمندی | ⏳ | |
| ۱.۱۴ | `TenantOwnershipChecker` روی هر uuid از request | ⏳ | |
## ۲. کارایی — بخشی از تسک، نه اختیاری
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | **سه** کوئری برای کل بازه؛ هیچ I/O داخل حلقه | ⏳ | ⭐ |
| ۲.۲ | هرس با «تنگ‌ترین منبع» پیاده شد | ⏳ | ~۸۰٪ کاندیدها حذف |
| ۲.۳ | `busy` مرتب + جستجوی دودویی در `OccupancyIndex` | ⏳ | |
| ۲.۴ | `app:dev:seed-availability-benchmark` | ⏳ | ۳ اتاق، ۲ اپراتور، ۳ دستگاه، ۵۰۰ نوبت |
| ۲.۵ | `AvailabilityPerformanceTest`: **< ۵۰۰ms** | ⏳ | |
| ۲.۶ | `AvailabilityPerformanceTest`: **≤ ۵ کوئری** | ⏳ | مهم‌تر از زمان — ماشین‌مستقل |
| ۲.۷ | ابطال کش: تقویم/استثنا/ساعت شعبه/تعطیلی → `win`؛ ثبت نوبت → فقط `month` | ⏳ | |
## ۳. دیتابیس
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | `resource_occupancy` **در این تسک** migrate شد (تعریف در تسک ۰۷) | ⏳ | وابستگی معکوس |
| ۳.۲ | `idx_occupancy_resource_range (resource_id, start_at, end_at, status)` | ⏳ | `resource_id` اول — نه tenant |
| ۳.۳ | ترتیب ستون‌های ایندکس در migration **دستی** نوشته شد | ⏳ | `diff` گاهی جابه‌جا می‌کند |
| ۳.۴ | `setMeta` کلیدهای جدید را با اعتبارسنجی می‌پذیرد | ⏳ | مقدار نامعتبر → مقدار فعلی |
## ۴. UI
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | `AppointmentSettingsPage` انتخاب حالت `resource` + گام + استراتژی | ⏳ | |
| ۴.۲ | چک‌لیست پیش از ارتقا با علامت ✓/✗ هر شرط | ⏳ | ⭐ بدون آن ارتقای اشتباه |
| ۴.۳ | تیک «می‌دانم برگشت‌ناپذیر است» اجباری | ⏳ | |
| ۴.۴ | جدول وقت‌ها با ستون «منابع پیشنهادی» و `SearchableSelect` per منبع | ⏳ | پنل |
| ۴.۵ | عوض کردن یک منبع → اعتبارسنجی **همان زمان**، نه کل لیست | ⏳ | |
| ۴.۶ | `reason` خالی‌بودن با پیام فارسی + دکمهٔ پیشنهادی | ⏳ | چهار حالت |
| ۴.۷ | `assignment` به بیمار نمایش داده **نمی‌شود** | ⏳ | فقط پنل |
| ۴.۸ | هیچ رنگ/شعاع hard-code | ⏳ | |
| ۴.۹ | دارک‌مود و حالت فشرده | ⏳ | |
| ۴.۱۰ | RTL و موبایل | ⏳ | |
| ۴.۱۱ | همهٔ رشته‌ها فارسی | ⏳ | |
| ۴.۱۲ | `ScheduleSection.tsx` موجود توسعه یافت، کامپوننت موازی ساخته نشد | ⏳ | |
## ۵. تست
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | `OccupancyIndexTest` — capacity، بازهٔ مماس، shared/exclusive | ⏳ | واحد |
| ۵.۲ | `CandidateGeneratorTest` — هرس، گذشته، برنامهٔ جا‌نشو | ⏳ | |
| ۵.۳ | `ResourceAllocatorTest` — منبع مشترک یکی؛ دو هم‌شکل در یک بخش دو منبع | ⏳ | |
| ۵.۴ | `CapacityReleaseTest`**آزادسازی ظرفیت** | ⏳ | ⭐⭐ بدون این تسک تأیید نمی‌شود |
| ۵.۵ | `StrategyTest` — سه استراتژی | ⏳ | |
| ۵.۶ | `BookingModeGuardTest` — حالت اشتباه دو طرفه ۴۲۲ + ارتقا با نوبت فعال | ⏳ | |
| ۵.۷ | `AvailabilityPerformanceTest` | ⏳ | |
| ۵.۸ | `LegacyBookingUnchangedTest` | ⏳ | ⭐ |
## ۶. مستندات
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | `docs/api/appointment-availability.md` | ⏳ | |
| ۶.۲ | جدول استراتژی‌ها | ⏳ | |
| ۶.۳ | محدودیت تخصیص حریصانه مکتوب | ⏳ | |
| ۶.۴ | ماتریس «کدام endpoint در کدام حالت» | ⏳ | |
| ۶.۵ | `docs/architecture/booking-modes.md` (تسک ۰۰) حالت سوم را گرفت | ⏳ | |
| ۶.۶ | در `docs/api/appointment.md` برجسته: کلاینت‌ها پس از ارتقا باید مسیر جدید بزنند | ⏳ | ⭐ |
## ۷. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۷.۱ | هیچ 🔄 و ⏳ بی‌دلیل نمانده | ⏳ | |
| ۷.۲ | `bin/phpunit` کامل سبز | ⏳ | |
| ۷.۳ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۷.۴ | `phpstan` بدون خطای جدید | ⏳ | |
| ۷.۵ | `npx tsc --noEmit` و `yarn test` سبز | ⏳ | |
| ۷.۶ | تست‌های tenant سبز | ⏳ | |
| ۷.۷ | `docs/api/*` به‌روز | ⏳ | |
| ۷.۸ | چک‌لیست UI کامل | ⏳ | |
| ۷.۹ | ⚠️ `nobat724_front` و `clinic-pro-tauri`: تا کلینیک ارتقا نداده، تغییری لازم نیست — تأیید شد | ⏳ | ⭐ |
| ۷.۱۰ | تسک frontend حالت `resource` برای سایت ثبت شد (خارج از این فاز) | ⏳ | |
| ۷.۱۱ | commit، سپس `graphify update .` | ⏳ | |
| ۷.۱۲ | موارد به‌تعویق با دلیل و تسک مقصد | ⏳ | |
@@ -0,0 +1,122 @@
# چک‌لیست — تسک ۰۷ (رزرو موقت و ثبت نهایی چندمنبعی)
**وضعیت کلی:** ⏳ شروع نشده · **آخرین بازبینی:**
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
[red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md)
---
## ۰. خط سرخ
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۰.۲ | `active_slot_key` و `refreshActiveSlotKey()` دست‌نخورده و **فعال** | ⏳ | دو تور ایمنی موازی |
| ۰.۳ | `slot_start`/`slot_end` باقی ماندند | ⏳ | چهار مصرف‌کننده رویشان کوئری می‌زنند |
| ۰.۴ | `is_reserve` دست‌نخورده — رزرو هیچ ردیف اشغالی نمی‌سازد | ⏳ | |
| ۰.۵ | `POST /api/v1/appointment` قدیمی بیت‌به‌بیت کار می‌کند | ⏳ | `LegacyBookingUnchangedTest` |
| ۰.۶ | `PAYMENT_TTL` و رفتار انقضای موجود حفظ شد | ⏳ | |
## ۱. تضمین همزمانی — قلب تسک
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | `resource_occupancy_slot` با `UNIQUE(resource_id, bucket, unit_index)` | ⏳ | ⭐ کل تضمین اینجاست |
| ۱.۲ | `BUCKET_SECONDS = 300` ثابت + کامنت هشدار تغییرش | ⏳ | |
| ۱.۳ | سطل‌ها با `intdiv($end - 1, 300)` — نه بدون `-1` | ⏳ | ⭐ وگرنه نوبت مجاور رد می‌شود |
| ۱.۴ | `unit_index` با **INSERT پشت‌سرهم**، نه `SELECT` قبلش | ⏳ | ⭐ پنجرهٔ رقابت |
| ۱.۵ | ردیف‌ها مرتب بر `(resource_id, bucket, unit_index)` درج می‌شوند | ⏳ | ⭐ جلوگیری از deadlock |
| ۱.۶ | `SlotTakenException` موجود بازاستفاده شد | ⏳ | |
| ۱.۷ | محدودیت گرانولاریتی ۵ دقیقه در مستندات صریح | ⏳ | |
## ۲. بک‌اند
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | `ResourceOccupancy` · `AppointmentSegment` | ⏳ | |
| ۲.۲ | `OccupancyWriter`**تنها** نویسندهٔ `resource_occupancy` | ⏳ | |
| ۲.۳ | `HoldService` · `BookingService` · `RescheduleService` | ⏳ | |
| ۲.۴ | **یک ردیف per (بخش × منبع)** — نه per نوبت | ⏳ | ⭐ آزادسازی ظرفیت |
| ۲.۵ | برنامه در `hold` **دوباره ساخته می‌شود**؛ `assignment` کلاینت فقط اعتبارسنجی می‌شود | ⏳ | ⭐ سه نشتی ثبت‌شده از همین شکل بودند |
| ۲.۶ | منبع باید **کاندید همان نیازمندی** باشد، نه فقط هم‌محیط | ⏳ | |
| ۲.۷ | `confirm` هفت مرحله در **یک** تراکنش | ⏳ | |
| ۲.۸ | `confirm` idempotent — دوباره روی همان hold خطا نمی‌دهد | ⏳ | |
| ۲.۹ | رویداد **بعد از** commit (`DispatchAfterCurrentBusStamp`) با uuid در payload | ⏳ | ⭐ تسک ۱۲ رویش حساب می‌کند |
| ۲.۱۰ | `reschedule`: اول hold جدید، بعد آزادسازی قدیم | ⏳ | ⭐ ترتیب |
| ۲.۱۱ | لغو = `status='released'` + **حذف فیزیکی** ردیف‌های سطل | ⏳ | |
| ۲.۱۲ | `setup/cleanup` در بازهٔ اشغال، نه در `appointment_segments` | ⏳ | |
| ۲.۱۳ | `STATUS_RESCHEDULED` + گذارهای مجاز | ⏳ | |
| ۲.۱۴ | `ExpireAppointmentsHandler` موجود توسعه یافت | ⏳ | آزادسازی + حذف سطل |
| ۲.۱۵ | قلاب‌های تسک ۰۸ و ۰۹ در `confirm` (مراحل ۳ و ۶) | ⏳ | |
| ۲.۱۶ | چهار endpoint | ⏳ | |
| ۲.۱۷ | دو کد خطا در `ErrorCodes.php` با پیام فارسی | ⏳ | `ERR_SLOT_TAKEN` · `ERR_HOLD_EXPIRED` |
## ۳. دیتابیس
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | `resource_occupancy` (BIGINT id) با چهار ایندکس | ⏳ | |
| ۳.۲ | `resource_occupancy_slot` با UNIQUE | ⏳ | |
| ۳.۳ | `appointment_segments` با snapshot `name`/`segment_type` | ⏳ | قانون پنجم |
| ۳.۴ | سه ستون تهی‌پذیر روی `appointments` | ⏳ | `branch_id` · `plan_total_minutes` · `patient_facing_minutes` |
| ۳.۵ | ترتیب ستون ایندکس‌ها **دستی** در migration | ⏳ | |
| ۳.۶ | `app:occupancy:backfill --force` — idempotent، نوبت‌های بی‌منبع را گزارش می‌کند | ⏳ | ⭐ بدون آن رزرو جدید روی نوبت قدیم می‌نشیند |
| ۳.۷ | `app:occupancy:prune --older-than=90d` | ⏳ | |
| ۳.۸ | `resource_occupancy_slot` در `AGGREGATE_CHILDREN` + هرگز کوئری مستقیم | ⏳ | |
| ۳.۹ | `TenantSchemaCoverageTest` سبز | ⏳ | |
## ۴. UI
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | تایمر شمارش معکوس hold در UI رزرو | ⏳ | |
| ۴.۲ | خطای `409` با پیام «این ساعت همین لحظه رزرو شد» + **لیست جایگزین خودکار** | ⏳ | ⭐ مستند بند ۱۷ |
| ۴.۳ | خطای `reschedule` شامل «نوبت فعلی تغییری نکرد» | ⏳ | ⭐ |
| ۴.۴ | مسدودسازی موردی منبع از صفحهٔ منابع | ⏳ | |
| ۴.۵ | تفکیک «مسدودسازی موردی» (occupancy) از «بلندمدت» (exception) در UI روشن است | ⏳ | دو راه یک کار گیج‌کننده است |
| ۴.۶ | هیچ رنگ/شعاع hard-code | ⏳ | |
| ۴.۷ | دارک‌مود و حالت فشرده | ⏳ | |
| ۴.۸ | RTL و موبایل | ⏳ | |
| ۴.۹ | همهٔ رشته‌ها فارسی | ⏳ | |
| ۴.۱۰ | `AppointmentDetailPage` بخش بخش‌های نوبت (فقط حالت `resource`) | ⏳ | |
## ۵. تست
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | `ConcurrentHoldTest`**دو اتصال واقعی**، دقیقاً یکی موفق | ⏳ | ⭐⭐ mock قبول نیست |
| ۵.۲ | `OccupancyWriterTest` — بازهٔ مماس، capacity، ترتیب INSERT | ⏳ | |
| ۵.۳ | `HoldLifecycleTest` — hold/انقضا/آزادسازی زودهنگام | ⏳ | |
| ۵.۴ | `BookingConfirmTest` — hold دیگری ۴۰۴، منقضی ۴۰۹، idempotent | ⏳ | |
| ۵.۵ | `CapacityReleaseIntegrationTest` | ⏳ | ⭐⭐ اپراتور در بازهٔ انتظار ردیف ندارد |
| ۵.۶ | `RescheduleTest` — شکست hold جدید → نوبت قدیم سالم | ⏳ | |
| ۵.۷ | `OccupancyBackfillTest` — idempotent | ⏳ | |
| ۵.۸ | `LegacyBookingUnchangedTest` | ⏳ | ⭐ |
| ۵.۹ | `BookingTenantTest` موجود سبز | ⏳ | |
## ۶. مستندات
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | `docs/api/appointment-booking.md` | ⏳ | |
| ۶.۲ | گرانولاریتی ۵ دقیقه و محدودیتش | ⏳ | |
| ۶.۳ | قرارداد `hold_uuid` و TTL | ⏳ | |
| ۶.۴ | تفکیک مسدودسازی موردی/بلندمدت | ⏳ | |
| ۶.۵ | `docs/architecture/booking-concurrency.md` — سطل زمانی + دلیل رد دو گزینهٔ دیگر | ⏳ | ⭐ شش ماه بعد زیر سؤال می‌رود |
## ۷. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۷.۱ | هیچ 🔄 و ⏳ بی‌دلیل نمانده | ⏳ | |
| ۷.۲ | `bin/phpunit` کامل سبز | ⏳ | |
| ۷.۳ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۷.۴ | `phpstan` بدون خطای جدید | ⏳ | |
| ۷.۵ | `npx tsc --noEmit` و `yarn test` سبز | ⏳ | |
| ۷.۶ | تست‌های tenant سبز | ⏳ | |
| ۷.۷ | `docs/api/*` به‌روز | ⏳ | |
| ۷.۸ | چک‌لیست UI کامل | ⏳ | |
| ۷.۹ | دو کلاینت دیگر بررسی شدند | ⏳ | `slot_start/slot_end` سالم است؟ |
| ۷.۱۰ | commit، سپس `graphify update .` | ⏳ | |
| ۷.۱۱ | موارد به‌تعویق با دلیل و تسک مقصد | ⏳ | |
@@ -0,0 +1,108 @@
# چک‌لیست — تسک ۰۸ (لیست قیمت بازه‌دار و snapshot فاکتور)
**وضعیت کلی:** ⏳ شروع نشده · **آخرین بازبینی:**
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
[red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md)
---
## ۰. خط سرخ
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۰.۲ | `Tariff` دست‌نخورده — سطر ۴ زنجیرهٔ `PriceResolver` | ⏳ | |
| ۰.۳ | `AppointmentInsuranceService` و `TenantServiceCoverage` بازنویسی **نشدند** | ⏳ | قاعدهٔ «اول بگرد» |
| ۰.۴ | `DiscountRule`/`DiscountEngine` دست‌نخورده | ⏳ | |
| ۰.۵ | `Invoice`/`InvoiceItem` دست‌نخورده و حذف نشدند | ⏳ | کار متفاوتی می‌کنند |
| ۰.۶ | ستون‌های موجود نوبت استفاده شدند، ستون جدید مالی روی `appointments` اضافه نشد | ⏳ | |
## ۱. بک‌اند
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | `PriceList` · `PriceListItem` · `PriceSnapshot` · `PriceSnapshotLine` · `DepositPolicy` | ⏳ | |
| ۱.۲ | `PricingEngine` — هفت مرحله، هر کدام سرویس مستقل | ⏳ | |
| ۱.۳ | مراحل ۳ و ۴ از روز اول در زنجیره، حتی no-op | ⏳ | تسک ۰۹ و ۱۱ |
| ۱.۴ | `PriceResolver` — ترتیب پنج‌گانه، هرگز صفر یا خطا | ⏳ | |
| ۱.۵ | تاریخ مبنا = `slot_start` (تاریخ رزرو)، نه `time()` | ⏳ | ⭐ دو تفسیر دارد |
| ۱.۶ | همهٔ محاسبات با `intdiv`، هیچ float در مسیر پول | ⏳ | |
| ۱.۷ | تخفیف **پشت‌سرهم**، نه جمع درصدها | ⏳ | ۴۰ سپس ۱۰ = ۴۶ |
| ۱.۸ | سقف جمع تخفیف اعمال شد | ⏳ | |
| ۱.۹ | `final = max(0, …)` + ردیف `adjustment` هنگام فعال شدن سقف | ⏳ | |
| ۱.۱۰ | `appliedPolicyIds` از روز اول ثبت می‌شود | ⏳ | |
| ۱.۱۱ | `PriceSnapshotLine.label` و `source_id` بدون FK (کپی متنی) | ⏳ | قانون پنجم |
| ۱.۱۲ | `DepositCalculator` روی ستون‌های موجود نوبت می‌نویسد | ⏳ | |
| ۱.۱۳ | `activate` تداخل بازه را می‌سنجد؛ لیست شعبه با محیط تداخل ندارد | ⏳ | |
| ۱.۱۴ | هفت endpoint | ⏳ | |
| ۱.۱۵ | قلاب مرحلهٔ ۶ `BookingService::confirm` وصل شد | ⏳ | |
| ۱.۱۶ | `TenantOwnershipChecker` روی هر uuid از request | ⏳ | |
## ۲. دیتابیس
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | پنج جدول | ⏳ | |
| ۲.۲ | `final_rials` و `amount_rials` از نوع **BIGINT** | ⏳ | ⭐ پکیج بزرگ از سقف INT عبور می‌کند |
| ۲.۳ | بقیهٔ `price_rials` ها `INT` ماندند | ⏳ | قیمت واحد عبور نمی‌کند |
| ۲.۴ | `UNIQUE(appointment_id)` روی snapshot | ⏳ | |
| ۲.۵ | `price_list_items` و `price_snapshot_lines` در `AGGREGATE_CHILDREN` | ⏳ | |
| ۲.۶ | `app:pricing:backfill-snapshots --force` — idempotent | ⏳ | نوبت‌های موجود فاکتور خالی نداشته باشند |
| ۲.۷ | `TenantSchemaCoverageTest` سبز | ⏳ | |
## ۳. UI
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | `PriceListsPage` · `PriceListFormPage` | ⏳ | |
| ۳.۲ | وضعیت شمسی: پیش‌نویس/فعال/منقضی با `StatusBadge` | ⏳ | |
| ۳.۳ | بازهٔ تاریخ با `PersianDatePicker` | ⏳ | |
| ۳.۴ | قیمت‌ها با `PriceInput` | ⏳ | |
| ۳.۵ | شعبه با `SearchableSelect` | ⏳ | |
| ۳.۶ | **«کپی از لیست قیمت قبلی»** | ⏳ | ⭐ با ۲۰۰ سرویس بدون آن لیست جدید ساخته نمی‌شود |
| ۳.۷ | کارت «فاکتور» در `AppointmentDetailPage` با ردیف‌های snapshot | ⏳ | |
| ۳.۸ | متن «قیمت بر اساس تاریخ نوبت محاسبه شده است» | ⏳ | |
| ۳.۹ | هیچ رنگ/شعاع hard-code | ⏳ | |
| ۳.۱۰ | دارک‌مود و حالت فشرده | ⏳ | |
| ۳.۱۱ | RTL و موبایل | ⏳ | |
| ۳.۱۲ | مبالغ با `formatRial` · تاریخ‌ها با `formatDate` | ⏳ | |
| ۳.۱۳ | وضعیت لیست در URL با `useUrlState` | ⏳ | |
| ۳.۱۴ | همهٔ رشته‌ها فارسی | ⏳ | |
## ۴. تست
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | `PriceResolverTest` — ترتیب پنج‌گانه + fallback | ⏳ | |
| ۴.۲ | `PricingEngineTest` — تخفیف پشت‌سرهم، سقف، منفی → صفر | ⏳ | |
| ۴.۳ | **invariant**: جمع ردیف‌ها = مبلغ نهایی، در همهٔ سناریوها | ⏳ | ⭐ |
| ۴.۴ | `PriceSnapshotImmutabilityTest` — قانون پنجم | ⏳ | ⭐ تغییر قیمت و حذف قانون |
| ۴.۵ | `PriceListActivationTest` — تداخل هم‌سطح ۴۲۲، شعبه/محیط بی‌تداخل | ⏳ | |
| ۴.۶ | `DepositCalculatorTest` — درصدی با min/max، اولویت سرویس | ⏳ | |
| ۴.۷ | `QuoteTenantTest` — سرویس محیط دیگر ۴۰۴ | ⏳ | |
| ۴.۸ | نوبت بدون سرویس (حالت `slot`) → snapshot با `visit_price_rials` | ⏳ | |
## ۵. مستندات
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | `docs/api/pricing.md` | ⏳ | |
| ۵.۲ | تصمیم «تاریخ رزرو، نه تاریخ ثبت» صریح | ⏳ | |
| ۵.۳ | `docs/architecture/insurance-billing-system.md` جدول `PriceSnapshot` vs `Invoice` | ⏳ | ⭐ وگرنه یکی حذف می‌شود |
## ۶. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | هیچ 🔄 و ⏳ بی‌دلیل نمانده | ⏳ | |
| ۶.۲ | `bin/phpunit` کامل سبز | ⏳ | |
| ۶.۳ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۶.۴ | `phpstan` بدون خطای جدید | ⏳ | |
| ۶.۵ | `npx tsc --noEmit` و `yarn test` سبز | ⏳ | |
| ۶.۶ | تست‌های tenant سبز | ⏳ | |
| ۶.۷ | `docs/api/*` به‌روز | ⏳ | |
| ۶.۸ | چک‌لیست UI کامل | ⏳ | |
| ۶.۹ | ⚠️ مبلغ نمایشی رزرو ممکن است عوض شود → دو کلاینت دستی بررسی شدند | ⏳ | |
| ۶.۱۰ | commit، سپس `graphify update .` | ⏳ | |
| ۶.۱۱ | موارد به‌تعویق با دلیل و تسک مقصد | ⏳ | |
@@ -0,0 +1,120 @@
# چک‌لیست — تسک ۰۹ (موتور قوانین شش‌دسته‌ای)
**وضعیت کلی:** ⏳ شروع نشده · **آخرین بازبینی:**
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
[red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md)
---
## ۰. خط سرخ
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۰.۲ | **هیچ قانونی روی حالت `slot` اعمال نمی‌شود** | ⏳ | ⭐ حتی اگر منطقی به نظر برسد |
| ۰.۳ | `DiscountRule` مهاجرت نکرد و دست‌نخورده ماند | ⏳ | |
| ۰.۴ | `NoPolicyRegressionTest`: بدون هیچ قانون، خروجی‌ها بیت‌به‌بیت مثل تسک ۰۸ | ⏳ | ⭐ |
| ۰.۵ | کد دلخواه در قانون **ممنوع** — فقط فهرست بسته | ⏳ | مستند بند ۸ |
| ۰.۶ | تودرتویی شرط ممنوع — فقط `all`/`any` یک‌سطحی | ⏳ | |
## ۱. بک‌اند
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | `Policy` · `PolicyVersionLog` | ⏳ | |
| ۱.۲ | `active = false` پیش‌فرض | ⏳ | تسک ۱۰ آزمایش را اجبار می‌کند |
| ۱.۳ | `FieldRegistry` — سه مسئولیت روی یک آرایه (schema/extract/assert) | ⏳ | ⭐ فیلد نمایشیِ بی‌ارزیابی ممکن نشود |
| ۱.۴ | `OperatorRegistry` با یازده عملگر شامل `days_since` | ⏳ | |
| ۱.۵ | `EffectRegistry` — اثر خارج از دسته → ۴۲۲ | ⏳ | |
| ۱.۶ | `Combiner` — جدول ترکیب مستند بند ۸، خالص و بدون I/O | ⏳ | |
| ۱.۷ | `PolicyResolver` — اولویت → اختصاصی‌بودن → قدمت | ⏳ | |
| ۱.۸ | `specificity` هنگام **ذخیره** محاسبه می‌شود، نه اجرا | ⏳ | |
| ۱.۹ | شش موتور جدا، هر کدام یک کلاس | ⏳ | نه یک `PolicyEngine` بزرگ |
| ۱.۱۰ | `evaluateIsolated()` روی هر شش موتور | ⏳ | ⭐ تسک ۱۰ به آن نیاز دارد — اینجا اضافه شود |
| ۱.۱۱ | `SpacingPolicyEngine::forbiddenRanges()` — کوئری، **نه حلقه per slot** | ⏳ | ⭐ |
| ۱.۱۲ | بازهٔ ممنوعه **پیش از** تولید کاندید به `CandidateGenerator` می‌رود | ⏳ | نه فیلتر بعدی |
| ۱.۱۳ | `combinable=false` → short-circuit؛ `deny` همیشه short-circuit | ⏳ | |
| ۱.۱۴ | فیلد بی‌مقدار → `false` **با لاگ**، نه سکوت | ⏳ | ⭐ قانون خاموش بی‌صدا |
| ۱.۱۵ | `PATCH` محتوای قانون وجود ندارد؛ فقط `name` و `active` | ⏳ | نسخه‌بندی |
| ۱.۱۶ | `policy_version_log` snapshot **کامل** نگه می‌دارد، نه diff | ⏳ | |
| ۱.۱۷ | `valid_from` گذشته در نسخهٔ جدید → ۴۲۲ | ⏳ | قانون پنجم |
| ۱.۱۸ | شش endpoint شامل `GET /policy-schema` | ⏳ | |
| ۱.۱۹ | `PricingPolicyEngine` هر دو منبع (`DiscountRule` + `Policy`) را ترکیب می‌کند | ⏳ | |
| ۱.۲۰ | `TenantOwnershipChecker` روی هر uuid از request | ⏳ | |
## ۲. پر کردن قلاب‌های تسک‌های قبل
| # | قلاب | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | تسک ۰۴ — `ServiceSelectionValidator``SelectionPolicyEngine` | ⏳ | |
| ۲.۲ | تسک ۰۵ — `AppointmentPlanBuilder` مرحلهٔ ۷`Resource` + `Timing` | ⏳ | |
| ۲.۳ | تسک ۰۶ — `AvailabilityEngine` مرحلهٔ ۶ → `Spacing` | ⏳ | |
| ۲.۴ | تسک ۰۷ — `BookingService::confirm` مرحلهٔ ۳ → `Eligibility` | ⏳ | |
| ۲.۵ | تسک ۰۸ — `PricingEngine` مرحلهٔ ۳ → `Pricing` | ⏳ | |
| ۲.۶ | **هیچ امضایی عوض نشد** | ⏳ | ⭐ دلیل گذاشتن قلاب‌ها از روز اول |
## ۳. دیتابیس
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | `policies` + `policy_version_log` | ⏳ | |
| ۳.۲ | `idx_policies_lookup (entity_type, entity_id, category, active, valid_from)` | ⏳ | |
| ۳.۳ | `appointments.applied_policies` (JSON تهی‌پذیر) | ⏳ | |
| ۳.۴ | قرارداد `applied_policy_ids` با `{id, version, name}` | ⏳ | `name` کپی متنی |
| ۳.۵ | `policy_version_log` در `AGGREGATE_CHILDREN` | ⏳ | |
| ۳.۶ | `app:policy:seed-examples` — پنج نمونه، همه `active=false` | ⏳ | |
| ۳.۷ | `TenantSchemaCoverageTest` سبز | ⏳ | |
## ۴. کارایی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | `AvailabilityPerformanceTest` **با قوانین فعال** سبز است | ⏳ | ⭐⭐ اگر قرمز شد، `spacing` حلقه می‌زند |
| ۴.۲ | `SpacingPolicyEngine` تعداد کوئری ثابت دارد، مستقل از تعداد اسلات | ⏳ | |
## ۵. UI
این تسک صفحه نمی‌سازد (تسک ۱۰ می‌سازد). فقط:
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | پیام‌های خطای `deny` فارسی و قابل فهم بیمار | ⏳ | نه نام قانون خام |
| ۵.۲ | خطای `add_requirement` بدون منبع شامل **نام قانون** | ⏳ | «قانون X جراح می‌خواهد ولی…» |
## ۶. تست
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | `ConditionEvaluatorTest` — همهٔ عملگرها × نوع‌ها، `all`/`any`، فیلد ناموجود | ⏳ | واحد |
| ۶.۲ | `CombinerTest` — شش قاعدهٔ جدول مستند | ⏳ | واحد |
| ۶.۳ | `PolicyResolverTest` — سه سناریوی حل تناقض + short-circuit | ⏳ | |
| ۶.۴ | `SpacingPolicyEngineTest` — بازهٔ ممنوعه + تعداد کوئری ثابت | ⏳ | |
| ۶.۵ | `PolicyVersioningTest` — قانون پنجم | ⏳ | ⭐ |
| ۶.۶ | `PolicyIntegrationTest` — چهار دسته end-to-end | ⏳ | |
| ۶.۷ | `PolicySchemaTest` — هر فیلد schema قابل extract است | ⏳ | ⭐ |
| ۶.۸ | `NoPolicyRegressionTest` | ⏳ | ⭐ |
## ۷. مستندات
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۷.۱ | `docs/api/policy.md` با فهرست کامل فیلد/عملگر/اثر | ⏳ | |
| ۷.۲ | قاعدهٔ «`DiscountRule` یا `Policy`؟» صریح | ⏳ | ⭐ |
| ۷.۳ | `docs/architecture/policy-engine.md` — حل تناقض، ترکیب، دلیل ممنوعیت کد دلخواه، دلیل عدم مهاجرت | ⏳ | |
## ۸. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۸.۱ | هیچ 🔄 و ⏳ بی‌دلیل نمانده | ⏳ | |
| ۸.۲ | `bin/phpunit` کامل سبز | ⏳ | |
| ۸.۳ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۸.۴ | `AvailabilityPerformanceTest` با قوانین فعال سبز | ⏳ | |
| ۸.۵ | `phpstan` بدون خطای جدید | ⏳ | |
| ۸.۶ | `npx tsc --noEmit` و `yarn test` سبز | ⏳ | |
| ۸.۷ | تست‌های tenant سبز | ⏳ | |
| ۸.۸ | `docs/api/*` به‌روز | ⏳ | |
| ۸.۹ | دو کلاینت دیگر بررسی شدند | ⏳ | پیام‌های `deny` در سایت درست نمایش داده می‌شوند؟ |
| ۸.۱۰ | commit، سپس `graphify update .` | ⏳ | |
| ۸.۱۱ | موارد به‌تعویق با دلیل و تسک مقصد | ⏳ | |
@@ -0,0 +1,102 @@
# چک‌لیست — تسک ۱۰ (فرم ساخت قانون و محیط آزمایش)
**وضعیت کلی:** ⏳ شروع نشده · **آخرین بازبینی:**
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
[red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md)
---
## ۰. خط سرخ
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۰.۲ | شبیه‌سازی **هیچ ردیفی** نمی‌نویسد (جز `policy_simulation_runs`) | ⏳ | ⭐⭐ با شمارش ردیف اثبات شود |
| ۰.۳ | نوبت‌های واقعی بیماران در شبیه‌سازی تغییر نکردند | ⏳ | |
## ۱. بک‌اند — شبیه‌ساز
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | `PolicySimulator` · `SimulationSampler` · `PolicySimulationRun` | ⏳ | |
| ۱.۲ | سه لایهٔ تضمین: DTO · تراکنش با rollback در `finally` · تست شمارش | ⏳ | ⭐ |
| ۱.۳ | `$this->em->clear()` بعد از rollback | ⏳ | ⭐ وگرنه entity کثیف در identity map |
| ۱.۴ | `PolicySimulationRun` **بعد از** rollback و در تراکنش جدا ثبت می‌شود | ⏳ | |
| ۱.۵ | `evaluateIsolated()` — فقط همان قانون، بدون `Resolver` و `Combiner` | ⏳ | |
| ۱.۶ | فیلتر شعبه و سرویس از **خودِ شرط قانون** استخراج می‌شود | ⏳ | وگرنه «۰٪ تحت تأثیر» گمراه‌کننده |
| ۱.۷ | سقف نمونه ۵۰؛ درخواست بیشتر → ۴۲۲ | ⏳ | |
| ۱.۸ | `PolicyTemplateRegistry` با پنج الگو | ⏳ | |
| ۱.۹ | `activate` شرط `simulate` **همان نسخه** را می‌سنجد | ⏳ | ⭐ نسخهٔ ۱ اجازهٔ نسخهٔ ۲ نمی‌دهد |
| ۱.۱۰ | محیط بدون نوبت → `simulate` خالی موفق، `activate` مجاز | ⏳ | ⭐ کلینیک جدید قفل نشود |
| ۱.۱۱ | چهار سطح شدت با آستانه‌های مستند | ⏳ | |
| ۱.۱۲ | دو endpoint | ⏳ | |
| ۱.۱۳ | `app:policy:prune-simulations` — آخرین اجرا per (policy, version) هرگز حذف نمی‌شود | ⏳ | `activate` به آن وابسته است |
## ۲. دیتابیس
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | `policy_simulation_runs` با جفت tenant | ⏳ | |
| ۲.۲ | `report` سقف ۵۰ ردیف | ⏳ | |
| ۲.۳ | `TenantSchemaCoverageTest` سبز | ⏳ | |
## ۳. UI
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | `PoliciesPage` · `PolicyFormPage` · `PolicySimulationPage` | ⏳ | |
| ۳.۲ | `PolicyConditionBuilder` **از `GET /policy-schema`** ساخته می‌شود | ⏳ | ⭐ هیچ فیلد hard-code |
| ۳.۳ | عملگرها per فیلد **فیلتر** می‌شوند، نه همه | ⏳ | ⭐ وگرنه ۴۲۲ بی‌توضیح |
| ۳.۴ | نوع ورودی مقدار از `schema.fields[f].type` | ⏳ | |
| ۳.۵ | همهٔ select ها `SearchableSelect`؛ هیچ `<select>` بومی | ⏳ | |
| ۳.۶ | انتخاب الگو → فرم کوتاه مقدارها (مسیر ۹۰٪ کاربران) | ⏳ | |
| ۳.۷ | ستون «وضعیت فعلی → با این قانون» در گزارش | ⏳ | ⭐ تنها چیزی که کاربر غیرفنی می‌فهمد |
| ۳.۸ | درصد تحت تأثیر + سطح شدت با رنگ توکن‌محور | ⏳ | |
| ۳.۹ | شدت `none` هم هشدار می‌دهد، با متن دو‌حالتی | ⏳ | |
| ۳.۱۰ | شدت `high` → متن «مطمئنید؟» روی دکمهٔ فعال‌سازی | ⏳ | |
| ۳.۱۱ | `ConfirmDialog` موجود برای فعال‌سازی | ⏳ | نه مودال دست‌ساز |
| ۳.۱۲ | `DataTable` برای لیست قوانین با فیلتر دسته/وضعیت در URL | ⏳ | |
| ۳.۱۳ | `backTo`/`BackButton` روی هر سه صفحه | ⏳ | |
| ۳.۱۴ | هیچ رنگ/شعاع hard-code — رنگ‌های شدت هم از توکن وضعیت | ⏳ | `--warning` `--danger` `--success` |
| ۳.۱۵ | دارک‌مود و حالت فشرده | ⏳ | |
| ۳.۱۶ | RTL و موبایل — جدول گزارش اسکرول افقی داخلی | ⏳ | |
| ۳.۱۷ | تاریخ‌ها شمسی | ⏳ | |
| ۳.۱۸ | همهٔ رشته‌ها فارسی | ⏳ | |
| ۳.۱۹ | نمایش تاریخچهٔ نسخه‌ها با diff | ⏳ | |
## ۴. تست
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | `PolicySimulatorTest` — شمارش ردیف قبل/بعد | ⏳ | ⭐⭐ |
| ۴.۲ | `PolicySimulatorTest` — استثنا در `evaluateIsolated` → rollback + clear | ⏳ | |
| ۴.۳ | `SimulationSamplerTest` — استخراج فیلتر، فقط confirmed/completed، سقف ۵۰ | ⏳ | |
| ۴.۴ | `PolicyActivationGuardTest` — چهار حالت | ⏳ | ⭐ |
| ۴.۵ | `PolicyTemplateTest` — هر الگو قانون معتبر تولید می‌کند (dataProvider) | ⏳ | ⭐ |
| ۴.۶ | `SeverityTest` — چهار آستانه | ⏳ | |
| ۴.۷ | `PolicyFormPage.test.tsx` — فیلد ساختگی از mock schema در UI ظاهر می‌شود | ⏳ | ⭐ |
| ۴.۸ | `PolicyFormPage.test.tsx` — عملگر نامعتبر برای نوع نمایش داده نمی‌شود | ⏳ | |
## ۵. مستندات
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | `docs/api/policy.md``simulate`، `policy-templates`، شرط جدید `activate` | ⏳ | |
| ۵.۲ | `docs/architecture/policy-engine.md` بخش «چرا آزمایش اجباری است» | ⏳ | ارجاع به ریسک دوم مستند |
## ۶. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | هیچ 🔄 و ⏳ بی‌دلیل نمانده | ⏳ | |
| ۶.۲ | `bin/phpunit` کامل سبز | ⏳ | |
| ۶.۳ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۶.۴ | `phpstan` بدون خطای جدید | ⏳ | |
| ۶.۵ | `npx tsc --noEmit` و `yarn test` سبز | ⏳ | |
| ۶.۶ | تست‌های tenant سبز | ⏳ | |
| ۶.۷ | `docs/api/*` به‌روز | ⏳ | |
| ۶.۸ | چک‌لیست UI کامل | ⏳ | |
| ۶.۹ | دو کلاینت دیگر بررسی شدند | ⏳ | این تسک قرارداد عمومی عوض نمی‌کند |
| ۶.۱۰ | commit، سپس `graphify update .` | ⏳ | |
| ۶.۱۱ | موارد به‌تعویق با دلیل و تسک مقصد | ⏳ | |
@@ -0,0 +1,114 @@
# چک‌لیست — تسک ۱۱ (پکیج و دفتر اعتبار جلسات)
**وضعیت کلی:** ⏳ شروع نشده · **آخرین بازبینی:**
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
[red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md)
---
## ۰. خط سرخ
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۰.۲ | **هیچ ستون `remaining`/`used_count`/`balance` در هیچ جدولی** | ⏳ | ⭐⭐ `LedgerSchemaTest` اجبار می‌کند |
| ۰.۳ | دفتر append-only — هیچ `remove`/`update` روی ردیف‌ها | ⏳ | |
| ۰.۴ | `WalletTransaction` و منطق کیف پول دست‌نخورده | ⏳ | مفهوم متفاوت |
## ۱. بک‌اند
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | `Package` · `PackageService` · `PatientPackage` · `SessionCreditLedger` | ⏳ | |
| ۱.۲ | `CreditLedgerService`**تنها** نویسندهٔ دفتر | ⏳ | |
| ۱.۳ | `balance()` = `SUM(delta)`، بدون هیچ مقدار ذخیره‌شده | ⏳ | ⭐ |
| ۱.۴ | پنج `kind` تعریف شد | ⏳ | |
| ۱.۵ | `quote` **هرگز** مصرف نمی‌کند؛ فقط `confirm` | ⏳ | ⭐⭐ رفرش صفحه = از دست رفتن جلسه |
| ۱.۶ | `PriceQuote` پرچم `packageWillBeConsumed` دارد | ⏳ | |
| ۱.۷ | مانده صفر → `false`، **نه استثنا** | ⏳ | ⭐ بیمار نقدی بپردازد |
| ۱.۸ | قفل بدبینانه `PESSIMISTIC_WRITE` روی ردیف پکیج | ⏳ | با جدول مقایسه با تسک ۰۷ |
| ۱.۹ | `catch UniqueConstraintViolationException` روی `consume` → idempotent | ⏳ | |
| ۱.۱۰ | FIFO — قدیمی‌ترین پکیج منقضی‌نشده | ⏳ | LIFO یعنی پول بیمار سوخته |
| ۱.۱۱ | `valid_to` هنگام **خرید** محاسبه و ذخیره می‌شود | ⏳ | |
| ۱.۱۲ | لغو → ردیف `refund`، نه حذف `consume` | ⏳ | |
| ۱.۱۳ | `TODO` با ارجاع به تسک ۱۳ برای سیاست بازگشت اعتبار | ⏳ | نه پرچم نیم‌کاره |
| ۱.۱۴ | `adjust` فقط با نقش مدیر و با `reason` اجباری | ⏳ | |
| ۱.۱۵ | `app:package:expire` روزانه — ردیف `expiry` با `delta = -balance` | ⏳ | |
| ۱.۱۶ | قلاب مرحلهٔ ۴ `PricingEngine` وصل شد | ⏳ | |
| ۱.۱۷ | هشت endpoint | ⏳ | |
| ۱.۱۸ | `TenantOwnershipChecker` روی هر uuid از request | ⏳ | |
## ۲. دیتابیس
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | چهار جدول | ⏳ | |
| ۲.۲ | `price_rials` و `price_paid_rials` از نوع **BIGINT** | ⏳ | پکیج بزرگ |
| ۲.۳ | `UNIQUE(appointment_id, kind)` روی دفتر | ⏳ | ⭐ جلوگیری از مصرف دوباره |
| ۲.۴ | `session_count`/`price_paid_rials` روی `patient_packages` **snapshot** اند | ⏳ | قانون پنجم |
| ۲.۵ | `ON DELETE RESTRICT` روی سرویسِ پکیج فروخته‌شده | ⏳ | |
| ۲.۶ | `package_services` در `AGGREGATE_CHILDREN` | ⏳ | |
| ۲.۷ | دفتر **جفت tenant** دارد (نه `ENTITIES` مثل کیف پول) | ⏳ | ⭐ دلیل مکتوب |
| ۲.۸ | `TenantSchemaCoverageTest` سبز | ⏳ | |
## ۳. UI
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | `PackagesPage` — تعریف با `PriceInput` و انتخاب سرویس | ⏳ | |
| ۳.۲ | کارت «پکیج‌ها» در `PatientDetailPage` با مانده و انقضا | ⏳ | |
| ۳.۳ | `PatientPackageLedgerPage` — جدول دفتر | ⏳ | |
| ۳.۴ | ستون «مانده تجمعی» **محاسبه‌شده در UI**، نه ستون DB | ⏳ | ⭐ به کاربر ثابت می‌کند عدد از کجاست |
| ۳.۵ | ستون‌های دفتر: تاریخ، نوع، تغییر، مانده تجمعی، دلیل، ثبت‌کننده، نوبت | ⏳ | |
| ۳.۶ | پیام «اعتبار پکیج تمام شده؛ این نوبت نقدی محاسبه می‌شود» | ⏳ | |
| ۳.۷ | `DataTable` با skeleton و empty state | ⏳ | |
| ۳.۸ | سرویس‌ها با `SearchableSelect` | ⏳ | |
| ۳.۹ | `backTo`/`BackButton` روی زیرصفحه‌ها | ⏳ | |
| ۳.۱۰ | هیچ رنگ/شعاع hard-code | ⏳ | |
| ۳.۱۱ | دارک‌مود و حالت فشرده | ⏳ | |
| ۳.۱۲ | RTL و موبایل | ⏳ | |
| ۳.۱۳ | مبالغ با `formatRial` · تاریخ با `formatDate` | ⏳ | |
| ۳.۱۴ | وضعیت لیست در URL با `useUrlState` | ⏳ | |
| ۳.۱۵ | همهٔ رشته‌ها فارسی | ⏳ | |
| ۳.۱۶ | دکمهٔ `adjust` فقط برای نقش مدیر نمایش داده می‌شود | ⏳ | `FeatureGate`/بررسی نقش |
## ۴. تست
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | `CreditLedgerTest``SUM(delta)` در همهٔ سناریوها، append-only | ⏳ | ⭐ |
| ۴.۲ | `LedgerSchemaTest` — هیچ ستون مانده در schema | ⏳ | ⭐⭐ |
| ۴.۳ | `QuoteDoesNotConsumeTest` — ده `quote` → مانده بی‌تغییر | ⏳ | ⭐⭐ |
| ۴.۴ | `ConcurrentConsumeTest` — مانده منفی نمی‌شود | ⏳ | |
| ۴.۵ | `IdempotentConsumeTest``confirm` دوبار → یک ردیف | ⏳ | |
| ۴.۶ | `FifoTest` | ⏳ | |
| ۴.۷ | `ExpiryTest` — ردیف `expiry` و حذف از finder | ⏳ | |
| ۴.۸ | `AdjustmentAuthTest` — منشی ۴۰۳، مدیر بی‌دلیل ۴۲۲ | ⏳ | |
| ۴.۹ | `PackageTenantTest` — پکیج محیط دیگر ۴۰۴ | ⏳ | |
| ۴.۱۰ | `PricingIntegrationTest` — ردیف `package` منفی + invariant تسک ۰۸ حفظ شد | ⏳ | ⭐ |
## ۵. مستندات
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | `docs/api/package.md` | ⏳ | |
| ۵.۲ | جدول مقایسهٔ قفل بدبینانه (این تسک) با سطل زمانی (تسک ۰۷) | ⏳ | ⭐ وگرنه «یکدست‌سازی» می‌شود |
| ۵.۳ | `docs/architecture/tenancy.md` — تفاوت دفتر اعتبار با کیف پول | ⏳ | ⭐ اشتباه گرفتنشان = نشتی مالی |
## ۶. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | هیچ 🔄 و ⏳ بی‌دلیل نمانده | ⏳ | |
| ۶.۲ | `bin/phpunit` کامل سبز | ⏳ | |
| ۶.۳ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۶.۴ | `PatientWalletTenantTest` موجود سبز ماند | ⏳ | |
| ۶.۵ | `phpstan` بدون خطای جدید | ⏳ | |
| ۶.۶ | `npx tsc --noEmit` و `yarn test` سبز | ⏳ | |
| ۶.۷ | تست‌های tenant سبز | ⏳ | |
| ۶.۸ | `docs/api/*` به‌روز | ⏳ | |
| ۶.۹ | چک‌لیست UI کامل | ⏳ | |
| ۶.۱۰ | دو کلاینت دیگر بررسی شدند | ⏳ | مبلغ صفر در رزرو درست نمایش داده می‌شود؟ |
| ۶.۱۱ | commit، سپس `graphify update .` | ⏳ | |
| ۶.۱۲ | موارد به‌تعویق با دلیل و تسک مقصد | ⏳ | سیاست بازگشت اعتبار → تسک ۱۳ |
@@ -0,0 +1,115 @@
# چک‌لیست — تسک ۱۲ (دوره درمان)
**وضعیت کلی:** ⏳ شروع نشده · **آخرین بازبینی:**
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
[red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md)
---
## ۰. خط سرخ
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۰.۲ | `PatientSession` موجود دست‌نخورده | ⏳ | «مراجعهٔ انجام‌شده» ≠ «جلسهٔ دوره» |
| ۰.۳ | رویدادهای تسک ۰۷ **بعد از** commit منتشر می‌شوند | ⏳ | ⭐ وگرنه در rollback هشت پیامک اشتباه |
| ۰.۴ | `abandon` نوبت‌های `booked` را **لغو نمی‌کند** | ⏳ | عمل برگشت‌ناپذیر روی ظرفیت |
## ۱. بک‌اند
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | `CourseProtocol` · `CourseProtocolStep` · `TreatmentCourse` · `CourseSession` | ⏳ | |
| ۱.۲ | `CourseStarter` · `CourseScheduler` · `CourseProgressCalculator` · `CourseSessionLinker` | ⏳ | |
| ۱.۳ | چهار فیلد فاصله و `params` **snapshot** می‌شوند | ⏳ | ⭐ قانون پنجم |
| ۱.۴ | `book-all` **همه یا هیچ** در یک تراکنش | ⏳ | ⭐⭐ رزرو نیمه‌کاره بدترین حالت |
| ۱.۵ | لنگر **متحرک** — فاصله از جلسهٔ قبلی، نه از شروع دوره | ⏳ | ⭐ |
| ۱.۶ | `findNearestInRange` — نزدیک‌ترین به **ایده‌آل**، نه اولین موجود | ⏳ | |
| ۱.۷ | لنگر پیشنهاد بعدی = آخرین جلسهٔ **`completed`**، نه `booked` | ⏳ | ⭐ |
| ۱.۸ | سقف ۹۰ روز → جلسات باقی `planned` + **پیام روشن** | ⏳ | ⭐ |
| ۱.۹ | `same_as_previous` ترجیح است نه الزام — fallback به `least_gap` | ⏳ | |
| ۱.۱۰ | `preferredResourceIds` در `PlanRequest` حمل می‌شود | ⏳ | |
| ۱.۱۱ | `SameAsPreviousPicker` تسک ۰۶ ورودی گرفت | ⏳ | |
| ۱.۱۲ | تعامل با `spacing`: `max(min)` و `min(max)`؛ بازهٔ تهی → ۴۲۲ روشن | ⏳ | سخت‌گیرانه‌تر برنده |
| ۱.۱۳ | اعتبار پکیج کمتر از جلسات → **هشدار**، نه خطا | ⏳ | |
| ۱.۱۴ | `active_course_key` با الگوی `active_slot_key` | ⏳ | ⭐ نه UNIQUE روی `status` |
| ۱.۱۵ | `CourseSessionLinker` هر دو سمت رابطه را هم‌زمان ست می‌کند | ⏳ | جای دیگری نه |
| ۱.۱۶ | جلسهٔ آخر `completed` → دوره `completed` خودکار + رویداد | ⏳ | |
| ۱.۱۷ | نُه endpoint | ⏳ | |
| ۱.۱۸ | `TenantOwnershipChecker` روی هر uuid از request | ⏳ | |
## ۲. دیتابیس
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | چهار جدول | ⏳ | |
| ۲.۲ | `min_days <= ideal_days <= max_days` و `session_count >= 2` | ⏳ | |
| ۲.۳ | `UNIQUE(protocol_id, session_number)` و `UNIQUE(course_id, session_number)` | ⏳ | |
| ۲.۴ | `UNIQUE(appointment_id)` روی `course_sessions` | ⏳ | |
| ۲.۵ | `appointments.course_session_id` تهی‌پذیر (رابطهٔ دوطرفه، عمدی) | ⏳ | |
| ۲.۶ | `course_protocol_steps` در `AGGREGATE_CHILDREN` | ⏳ | |
| ۲.۷ | `TenantSchemaCoverageTest` سبز | ⏳ | |
## ۳. UI
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | `CourseProtocolsPage` — پروتکل + جدول پارامتر جلسات | ⏳ | |
| ۳.۲ | `TreatmentCoursePage` — نوار پیشرفت، جدول جلسات، دو دکمهٔ رزرو | ⏳ | |
| ۳.۳ | کارت «دوره‌های درمان» در `PatientDetailPage` | ⏳ | |
| ۳.۴ | ستون «فاصله» عدد **واقعی** بین جلسات را نشان می‌دهد، نه ایده‌آل | ⏳ | ⭐ کلینیک نظم بیمار را می‌فهمد |
| ۳.۵ | نوار زرد هشدار عبور از حداکثر فاصله | ⏳ | |
| ۳.۶ | پیشنهاد جلسهٔ بعدی به‌صورت بنر پس از `completed` شدن جلسه | ⏳ | |
| ۳.۷ | نام منبع ترجیحی روی دکمهٔ رزرو («رزرو با اپراتور مریم») | ⏳ | |
| ۳.۸ | پس از لغو جلسهٔ وسط: پیشنهاد «بازچینی جلسات باقی‌مانده» با کلیک صریح | ⏳ | خودکار نه |
| ۳.۹ | `DataTable` برای جدول جلسات | ⏳ | |
| ۳.۱۰ | `StatusBadge` برای وضعیت جلسه و دوره | ⏳ | |
| ۳.۱۱ | تاریخ‌ها شمسی با `PersianDatePicker`/`formatDate` | ⏳ | |
| ۳.۱۲ | `backTo`/`BackButton` روی زیرصفحه‌ها | ⏳ | |
| ۳.۱۳ | هیچ رنگ/شعاع hard-code — نوار پیشرفت هم | ⏳ | |
| ۳.۱۴ | دارک‌مود و حالت فشرده | ⏳ | |
| ۳.۱۵ | RTL و موبایل | ⏳ | |
| ۳.۱۶ | همهٔ رشته‌ها فارسی | ⏳ | |
| ۳.۱۷ | پیام سقف ۹۰ روز در UI نمایش داده می‌شود | ⏳ | ⭐ وگرنه کاربر فکر می‌کند خراب است |
## ۴. تست
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | `CourseStarterTest` — ۸ جلسه، بدون پروتکل ۴۲۲، دورهٔ دوم ۴۲۲ با `meta` | ⏳ | |
| ۴.۲ | `ProtocolSnapshotTest` | ⏳ | ⭐ |
| ۴.۳ | `CourseSchedulerTest` — لنگر متحرک، نزدیک‌ترین به ایده‌آل | ⏳ | ⭐ |
| ۴.۴ | `CourseSchedulerTest` — شکست جلسهٔ N → rollback ۱..N-1 | ⏳ | ⭐⭐ |
| ۴.۵ | `CourseSchedulerTest` — سقف ۹۰ روز + پیام | ⏳ | |
| ۴.۶ | `NextSuggestionTest` — لنگر `completed`، هشدار عبور از max | ⏳ | |
| ۴.۷ | `CourseProgressTest` | ⏳ | |
| ۴.۸ | `SameResourcePreferenceTest` — fallback بدون خطا | ⏳ | |
| ۴.۹ | `CoursePolicyInteractionTest` — سخت‌گیرانه‌تر برنده، بازهٔ تهی ۴۲۲ | ⏳ | |
| ۴.۱۰ | `CoursePackageTest` — مصرف per جلسه، هشدار نه خطا | ⏳ | |
| ۴.۱۱ | `CourseLifecycleTest` — لغو، `no_show`، تکمیل خودکار، `abandon` | ⏳ | |
## ۵. مستندات
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | `docs/api/course.md` | ⏳ | |
| ۵.۲ | قاعدهٔ «سخت‌گیرانه‌تر برنده» بین پروتکل و قانون | ⏳ | |
| ۵.۳ | رفتار سقف ۹۰ روز | ⏳ | |
| ۵.۴ | «`abandon` نوبت‌ها را لغو نمی‌کند» صریح | ⏳ | |
## ۶. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | هیچ 🔄 و ⏳ بی‌دلیل نمانده | ⏳ | |
| ۶.۲ | `bin/phpunit` کامل سبز | ⏳ | |
| ۶.۳ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۶.۴ | `phpstan` بدون خطای جدید | ⏳ | |
| ۶.۵ | `npx tsc --noEmit` و `yarn test` سبز | ⏳ | |
| ۶.۶ | تست‌های tenant سبز | ⏳ | |
| ۶.۷ | `docs/api/*` به‌روز | ⏳ | |
| ۶.۸ | چک‌لیست UI کامل | ⏳ | |
| ۶.۹ | دو کلاینت دیگر بررسی شدند | ⏳ | نوبت‌های دوره در پنل بیمار درست دیده می‌شوند؟ |
| ۶.۱۰ | commit، سپس `graphify update .` | ⏳ | |
| ۶.۱۱ | موارد به‌تعویق با دلیل و تسک مقصد | ⏳ | |
@@ -0,0 +1,128 @@
# چک‌لیست — تسک ۱۳ (سیاست لغو، عدم حضور، لیست انتظار)
**وضعیت کلی:** ⏳ شروع نشده · **آخرین بازبینی:**
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
[red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md)
---
## ۰. خط سرخ
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۰.۲ | پیش‌فرض سیاست **بدون جریمه** (`penalty_mode='none'`) | ⏳ | ⭐⭐ وگرنه لحظهٔ deploy همه مشمول جریمه |
| ۰.۳ | بیمار پرریسک **مسدود نمی‌شود** — فقط برچسب | ⏳ | ⭐ مسدودسازی = قانون `eligibility` |
| ۰.۴ | `ReserveAppointmentsPage`/`is_reserve` دست‌نخورده | ⏳ | مفهوم متفاوت از لیست انتظار |
| ۰.۵ | وضعیت‌های لغو موجود (`cancelled_by_*`, `no_show`) دست‌نخورده | ⏳ | |
## ۱. بک‌اند — لغو و جریمه
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | `CancellationPolicy` · `NoShowRecord` | ⏳ | |
| ۱.۲ | `CancellationPolicyResolver` — سرویس بر محیط اولویت دارد | ⏳ | |
| ۱.۳ | `PenaltyCalculator` — شرط «لغو توسط کلینیک» **اولین خط** | ⏳ | ⭐ |
| ۱.۴ | سقف جریمه = مبلغ پرداختی (`min($penalty, $paid)`) | ⏳ | |
| ۱.۵ | نوبت نقدی → جریمه صفر + `note` | ⏳ | |
| ۱.۶ | `GET /cancellation-preview` پیش از لغو | ⏳ | ⭐ |
| ۱.۷ | `CancellationService` هفت مرحله در یک تراکنش | ⏳ | |
| ۱.۸ | جریمه در `WalletTransaction` با `setRecordedEntity()` | ⏳ | ⭐ وگرنه نشتی بین محیط‌ها |
| ۱.۹ | بازگشت اعتبار پکیج **طبق سیاست** (`credit_refundable`)، نه همیشه | ⏳ | تسک ۱۱ `TODO` را برمی‌دارد |
| ۱.۱۰ | `CourseSessionLinker::releaseSession()` صدا زده می‌شود | ⏳ | تسک ۱۲ |
| ۱.۱۱ | لغو دوباره → idempotent | ⏳ | |
| ۱.۱۲ | لغو نوبت گذشته → ۴۲۲ | ⏳ | |
## ۲. بک‌اند — عدم حضور
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | `NoShowTracker` با پنجرهٔ **۱۲ ماه** | ⏳ | نه کل تاریخ |
| ۲.۲ | برچسب پرریسک از `TenantTag` موجود، نه ستون بولین جدید | ⏳ | ⭐ |
| ۲.۳ | `UNIQUE(appointment_id)` → یک رکورد per نوبت | ⏳ | |
| ۲.۴ | جدول جدا، نه ستون شمارنده روی بیمار | ⏳ | همان استدلال دفتر اعتبار |
## ۳. بک‌اند — لیست انتظار
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | `WaitlistEntry` · `WaitlistService` · `WaitlistMatcher` | ⏳ | |
| ۳.۲ | **broadcast** به حداکثر ۱۰ نفر، اولین رزروکننده می‌برد | ⏳ | تصمیم مکتوب |
| ۳.۳ | متن پیامک شامل «اولین نفری که رزرو کند آن را می‌گیرد» | ⏳ | ⭐ اجباری |
| ۳.۴ | `notify_count` سقف دارد (پیشنهاد ۳) | ⏳ | جلوگیری از اسپم |
| ۳.۵ | اطلاع‌رسانی **async** روی رویداد، بیرون تراکنش لغو | ⏳ | ⭐ لغو مستقل از پیامک |
| ۳.۶ | ترتیب: `priority DESC, created_at ASC` | ⏳ | |
| ۳.۷ | `preferred_day_parts` در PHP فیلتر می‌شود | ⏳ | |
| ۳.۸ | بیمار که خودش نوبت گرفت → `converted` خودکار روی رویداد `AppointmentBooked` | ⏳ | ⭐ وگرنه پیامک اضافه می‌گیرد |
| ۳.۹ | `app:waitlist:expire` روزانه | ⏳ | |
| ۳.۱۰ | بازهٔ دلخواه > ۹۰ روز → ۴۲۲ | ⏳ | |
| ۳.۱۱ | هفت endpoint | ⏳ | |
## ۴. دیتابیس
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | سه جدول | ⏳ | |
| ۴.۲ | `idx_waitlist_match (service_item_id, branch_id, status, desired_from, desired_to)` | ⏳ | |
| ۴.۳ | `idx_no_show_patient (patient_record_id, recorded_at)` | ⏳ | کوئری پنجرهٔ ۱۲ ماه |
| ۴.۴ | `risk_tag_uuid` بدون FK (الگوی `DiscountRule.target_tag_uuid`) | ⏳ | |
| ۴.۵ | `app:cancellation:seed-default-policy` — محافظه‌کار | ⏳ | |
| ۴.۶ | `TenantSchemaCoverageTest` سبز | ⏳ | |
## ۵. UI
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | `CancellationPolicyPage` — سیاست محیط + جدول override سرویس‌ها | ⏳ | |
| ۵.۲ | `WaitlistPage` — لیست + تب «قابل تطبیق» | ⏳ | |
| ۵.۳ | دکمهٔ لغو → `ConfirmDialog` با محتوای **preview** | ⏳ | ⭐ نه لغو بعد جریمه |
| ۵.۴ | نشان «پرریسک» + شمارش عدم حضور در `PatientDetailPage` | ⏳ | |
| ۵.۵ | `ConfirmDialog` موجود استفاده شد، مودال دست‌ساز نه | ⏳ | |
| ۵.۶ | `DataTable` با فیلتر بازه/سرویس در URL | ⏳ | |
| ۵.۷ | تاریخ‌ها شمسی · مبالغ با `formatRial` | ⏳ | |
| ۵.۸ | `backTo`/`BackButton` روی زیرصفحه‌ها | ⏳ | |
| ۵.۹ | هیچ رنگ/شعاع hard-code — نشان پرریسک از `--danger-bg` | ⏳ | |
| ۵.۱۰ | دارک‌مود و حالت فشرده | ⏳ | |
| ۵.۱۱ | RTL و موبایل | ⏳ | |
| ۵.۱۲ | همهٔ رشته‌ها فارسی | ⏳ | |
| ۵.۱۳ | `ReserveAppointmentsPage` موجود دست‌نخورده ماند | ⏳ | ادغام خارج از دامنه |
## ۶. تست
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | `PenaltyCalculatorTest` — پنج حالت شامل «کلینیک همیشه صفر» | ⏳ | ⭐ |
| ۶.۲ | `CancellationServiceTest` — آزادسازی، `recorded_entity`، idempotent، گذشته ۴۲۲ | ⏳ | |
| ۶.۳ | `PolicyResolverTest` — اولویت سرویس | ⏳ | |
| ۶.۴ | `NoShowTrackerTest` — سوم برچسب، قدیمی‌تر از ۱۲ ماه نه، دوبار یک رکورد | ⏳ | |
| ۶.۵ | `NoShowTrackerTest` — بیمار پرریسک **رزرو موفق** دارد | ⏳ | ⭐ |
| ۶.۶ | `WaitlistMatcherTest` — سقف ۱۰، ترتیب، فیلتر روزبخش، `notify_count` | ⏳ | |
| ۶.۷ | `WaitlistConversionTest` | ⏳ | |
| ۶.۸ | `WaitlistAsyncTest` — شکست پیامک لغو را rollback نمی‌کند | ⏳ | ⭐ |
| ۶.۹ | `PatientWalletTenantTest` موجود سبز ماند | ⏳ | ⭐ |
| ۶.۱۰ | `CourseLifecycleTest` موجود — سیاست اعتبار اعمال شد | ⏳ | |
## ۷. مستندات
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۷.۱ | `docs/api/cancellation.md` — preview اجباری، کلینیک بی‌جریمه | ⏳ | |
| ۷.۲ | `docs/api/waitlist.md` — تصمیم broadcast و دلیلش | ⏳ | |
## ۸. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۸.۱ | هیچ 🔄 و ⏳ بی‌دلیل نمانده | ⏳ | |
| ۸.۲ | `bin/phpunit` کامل سبز | ⏳ | |
| ۸.۳ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۸.۴ | `phpstan` بدون خطای جدید | ⏳ | |
| ۸.۵ | `npx tsc --noEmit` و `yarn test` سبز | ⏳ | |
| ۸.۶ | تست‌های tenant سبز | ⏳ | |
| ۸.۷ | `docs/api/*` به‌روز | ⏳ | |
| ۸.۸ | چک‌لیست UI کامل | ⏳ | |
| ۸.۹ | ⚠️ سایت باید preview لغو را نشان دهد → `nobat724_front` بررسی و تسک ثبت شد | ⏳ | ⭐ |
| ۸.۱۰ | `clinic-pro-tauri` بررسی شد | ⏳ | |
| ۸.۱۱ | commit، سپس `graphify update .` | ⏳ | |
| ۸.۱۲ | موارد به‌تعویق با دلیل و تسک مقصد | ⏳ | |
@@ -0,0 +1,119 @@
# چک‌لیست — تسک ۱۴ (رویدادهای دامنه و گزارش بهره‌وری)
**وضعیت کلی:** ⏳ شروع نشده · **آخرین بازبینی:**
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
[red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md)
---
## ۰. خط سرخ
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۰.۲ | `AppointmentEvent` موجود دست‌نخورده | ⏳ | تاریخچهٔ وضعیت ≠ رویداد دامنه |
| ۰.۳ | پیامک‌های موجود (`Sms` domain) نشکستند | ⏳ | |
| ۰.۴ | گزارش با داده حدسی ساخته **نشد** | ⏳ | ⭐ بند ۱.۹ |
## ۱. بک‌اند — رویدادها
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | `DomainEvent` پایه + `DomainEventPublisher` + `DomainEventLog` | ⏳ | |
| ۱.۲ | payload **فقط uuid و اسکالر** — هیچ entity | ⏳ | ⭐ |
| ۱.۳ | هر رویداد `entityType`/`entityId` دارد | ⏳ | وگرنه پیامک محیط اشتباه |
| ۱.۴ | الگوی **outbox**: `record()` داخل تراکنش کاری، فقط persist | ⏳ | ⭐ |
| ۱.۵ | `PublishDomainEventHandler` + `scheduler` هر ۱۰ ثانیه | ⏳ | |
| ۱.۶ | `attempts < 5`؛ ردیف شکست‌خورده **حذف نمی‌شود** | ⏳ | |
| ۱.۷ | همهٔ `dispatch` های تسک‌های ۰۷ تا ۱۳ به `record()` تغییر کردند | ⏳ | ⭐ |
| ۱.۸ | چهارده رویداد بند ۱۶ مستند ثبت شدند | ⏳ | |
| ۱.۹ | idempotency در **مصرف‌کننده**، با `domain_events.uuid` | ⏳ | at-least-once |
| ۱.۱۰ | worker با loop-wrap برای Coolify | ⏳ | کانتینر خارج نشود |
| ۱.۱۱ | `app:events:prune --older-than=180d` | ⏳ | |
| ۱.۱۲ | `GET /domain-events` فقط `ROLE_ADMIN` | ⏳ | |
## ۲. بک‌اند — گزارش‌ها
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | `ResourceUtilizationReporter` با چهار عدد | ⏳ | |
| ۲.۲ | `available_minutes` **× `capacity`** منبع | ⏳ | ⭐ اتاق سه‌تخته سه برابر |
| ۲.۳ | `passive` در `occupied` هست، در `active` نه | ⏳ | |
| ۲.۴ | `setup/cleanup` در `occupied` هست | ⏳ | |
| ۲.۵ | `released` شمرده نمی‌شود (`status='booked'` فقط) | ⏳ | |
| ۲.۶ | `available = 0``utilization = null`، **نه صفر** | ⏳ | ⭐ معنای متفاوت |
| ۲.۷ | مرز بازه: `start_at >= from AND start_at < to` | ⏳ | نه `end_at <= to` |
| ۲.۸ | کوئری تجمعی با `GROUP BY`، بدون پیمایش | ⏳ | |
| ۲.۹ | **پیش از پیاده‌سازی** `plan-accuracy`: وجود `patient_sessions.started_at/ended_at` تأیید شد | ⏳ | ⭐ اگر نبود → تسک جدا، نه داده حدسی |
| ۲.۱۰ | `PlanAccuracyReporter` با آستانه‌های شدت | ⏳ | |
| ۲.۱۱ | انحراف **منفی** بزرگ هم `high` است | ⏳ | نصف ظرفیت هدر می‌رود |
| ۲.۱۲ | حداقل نمونه ۱۰، وگرنه `insufficient_data` | ⏳ | |
| ۲.۱۳ | بازه > ۹۰ روز → ۴۲۲ | ⏳ | |
| ۲.۱۴ | سه endpoint | ⏳ | |
## ۳. دیتابیس
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | `domain_events` (BIGINT id) با سه ایندکس | ⏳ | |
| ۳.۲ | `idx_de_pending (published_at, occurred_at)` | ⏳ | کوئری worker |
| ۳.۳ | هیچ جدول دیگری تغییر نکرد | ⏳ | |
| ۳.۴ | `TenantSchemaCoverageTest` سبز | ⏳ | |
## ۴. UI
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | `ResourceUtilizationPage` — جدول + نمودار `Recharts` | ⏳ | کتابخانهٔ موجود |
| ۴.۲ | `PlanAccuracyPage` — جدول انحراف با شدت | ⏳ | |
| ۴.۳ | ردیف‌های `active_ratio < 0.3` نشان هشدار دارند | ⏳ | |
| ۴.۴ | **tooltip توضیح `active_ratio` در خودِ UI** | ⏳ | ⭐ نه فقط در مستندات |
| ۴.۵ | `utilization = null``—` با tooltip «تقویم تعریف نشده» + لینک تنظیم | ⏳ | |
| ۴.۶ | لینک «ویرایش بخش‌های این سرویس» از `PlanAccuracyPage` | ⏳ | ⭐ گزارشی که راه اصلاح ندهد خوانده نمی‌شود |
| ۴.۷ | بازهٔ زمانی با `PersianDatePicker` | ⏳ | |
| ۴.۸ | وضعیت (بازه، فیلتر) در URL با `useUrlState` | ⏳ | |
| ۴.۹ | `DataTable` با skeleton و empty state | ⏳ | |
| ۴.۱۰ | رنگ نمودار از توکن‌های `--stat-*`، نه پالت پیش‌فرض Recharts | ⏳ | ⭐ |
| ۴.۱۱ | هیچ رنگ/شعاع hard-code | ⏳ | |
| ۴.۱۲ | دارک‌مود — نمودار هم در دارک خوانا است | ⏳ | ⭐ محور و legend |
| ۴.۱۳ | حالت فشرده | ⏳ | |
| ۴.۱۴ | RTL و موبایل — جدول و نمودار اسکرول افقی داخلی | ⏳ | |
| ۴.۱۵ | همهٔ رشته‌ها فارسی · اعداد با `formatNumber` | ⏳ | |
| ۴.۱۶ | `backTo` روی صفحات گزارش | ⏳ | |
## ۵. تست
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | `OutboxTest` — record داخل تراکنش، rollback، انتشار، شکست، سقف تلاش | ⏳ | ⭐ |
| ۵.۲ | `EventPayloadTest` — reflection روی همهٔ زیرکلاس‌ها: فقط اسکالر | ⏳ | |
| ۵.۳ | `ResourceUtilizationTest` — شش سنجهٔ سند | ⏳ | ⭐ شامل `capacity` و `null` |
| ۵.۴ | `PlanAccuracyTest` — انحراف دوطرفه، نمونهٔ کم | ⏳ | |
| ۵.۵ | `ReportAuthTest` — منشی ۴۰۳، بازه ۴۲۲ | ⏳ | |
| ۵.۶ | `ReportQueryCountTest` — تعداد کوئری مستقل از تعداد منبع | ⏳ | |
## ۶. مستندات
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | `docs/api/reports.md` — معنی هر عدد + جدول `active_ratio` | ⏳ | |
| ۶.۲ | `docs/architecture/domain-events.md` — قرارداد، فهرست، outbox، idempotency | ⏳ | |
| ۶.۳ | جدول تفاوت `AppointmentEvent` با `DomainEventLog` | ⏳ | ⭐ وگرنه یکی حذف می‌شود |
## ۷. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۷.۱ | هیچ 🔄 و ⏳ بی‌دلیل نمانده | ⏳ | |
| ۷.۲ | `bin/phpunit` کامل سبز | ⏳ | |
| ۷.۳ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۷.۴ | `phpstan` بدون خطای جدید | ⏳ | |
| ۷.۵ | `npx tsc --noEmit` و `yarn test` سبز | ⏳ | |
| ۷.۶ | تست‌های tenant سبز | ⏳ | |
| ۷.۷ | `docs/api/*` به‌روز | ⏳ | |
| ۷.۸ | چک‌لیست UI کامل | ⏳ | |
| ۷.۹ | پیامک‌های موجود سرتاسر تست شدند (outbox نشکستشان) | ⏳ | ⭐ |
| ۷.۱۰ | دو کلاینت دیگر بررسی شدند | ⏳ | |
| ۷.۱۱ | commit، سپس `graphify update .` | ⏳ | |
| ۷.۱۲ | موارد به‌تعویق با دلیل و تسک مقصد | ⏳ | `plan-accuracy` اگر داده نبود |