# چک‌لیست — تسک ۱۳ (سیاست لغو، عدم حضور، لیست انتظار)

**وضعیت کلی:** ✅ تمام‌شده با انحراف‌های ثبت‌شده · **آخرین بازبینی:** ۱۴۰۵/۰۵/۰۹

قواعد: [_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` سبز | ✅ | |
| ۰.۲ | پیش‌فرض سیاست بدون جریمه | ✅ | ⭐⭐ `penaltyMode = 'none'` در خودِ entity، نه در seed |
| ۰.۳ | بیمار پرریسک مسدود نمی‌شود | ✅ | ⭐ فقط برچسب؛ مسدودسازی = قانون `eligibility` تسک ۰۹ |
| ۰.۴ | `is_reserve` و صفحه‌اش دست‌نخورده | ✅ | مفهوم متفاوت؛ ادغام خارج از دامنه |
| ۰.۵ | وضعیت‌های لغو موجود دست‌نخورده | ✅ | همان `cancelled_by_*` و `no_show` |

## ۱. بک‌اند — لغو و جریمه

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | `CancellationPolicy` · `NoShowRecord` | ✅ | |
| ۱.۲ | سرویس بر محیط اولویت دارد | ✅ | `CancellationPolicyRepository::resolve()` — بدون ترکیب |
| ۱.۳ | شرط «لغو توسط کلینیک» اولین خط | ✅ | ⭐ با کامنت توضیح چرا |
| ۱.۴ | سقف جریمه = مبلغ پرداختی | ✅ | |
| ۱.۵ | نوبت نقدی → جریمه صفر + `note` | ✅ | |
| ۱.۶ | `GET /cancellation-preview` | ✅ | ⭐ همان محاسبهٔ لغو واقعی |
| ۱.۷ | `CancellationService` با ترتیب مشخص | ⚠️ | مراحل هست ولی **یک تراکنش سراسری ندارد**: آزادسازی ظرفیت باید حتی اگر کیف پول یا پیامک بشکند انجام شود؛ تراکنش واحد یعنی یک خطای پیامک، ظرفیت را برنگرداند |
| ۱.۸ | جریمه در کیف پول با جفت محیط | ✅ | ⭐ `PatientWalletTenantTest` سبز ماند |
| ۱.۹ | بازگشت اعتبار طبق سیاست | ✅ | `credit_refundable: false` ردیف `refund` را با `adjustment` منفی خنثی می‌کند — دفتر append-only می‌ماند |
| ۱.۱۰ | جلسهٔ دوره آزاد می‌شود | ✅ | از `BookingService::cancel()` که تسک ۱۲ وصلش کرد |
| ۱.۱۱ | لغو دوباره → ۴۰۹ | ✅ | |
| ۱.۱۲ | لغو نوبت گذشته → ۴۲۲ | ✅ | |

## ۲. بک‌اند — عدم حضور

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | پنجرهٔ ۱۲ ماه | ✅ | `NoShowRecordRepository::WINDOW_DAYS` |
| ۲.۲ | برچسب از `TenantTag` موجود | ✅ | ⭐ هیچ ستون بولین تازه‌ای |
| ۲.۳ | یک رکورد per نوبت | ✅ | کلید یکتا + بررسی پیش از درج |
| ۲.۴ | جدول جدا، نه شمارنده | ✅ | همان استدلال دفتر اعتبار تسک ۱۱ |

## ۳. بک‌اند — لیست انتظار

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | `WaitlistEntry` + `WaitlistNotifier` | ⚠️ | یک notifier به‌جای دو کلاس `Service`/`Matcher`؛ تطبیق یک کوئری در repository است و کلاس جدا فقط لایه بود |
| ۳.۲ | broadcast به حداکثر ۱۰ نفر | ✅ | تصمیم و دلیلش در `waitlist.md` |
| ۳.۳ | جملهٔ «اولین نفر می‌برد» در پیامک | ✅ | ⭐ |
| ۳.۴ | سقف `notify_count` | ✅ | ۳ بار |
| ۳.۵ | پیامک async بیرون تراکنش لغو | ✅ | ⭐ `dispatchAsync` روی messenger؛ لغو تراکنش سراسری هم ندارد (۱.۷) |
| ۳.۶ | ترتیب `priority DESC, created_at ASC` | ✅ | |
| ۳.۷ | فیلتر `preferred_day_parts` | ⏳ | ذخیره و نمایش می‌شود ولی در تطبیق اعمال نمی‌شود — بدون منطقهٔ زمانی شعبه، «عصر» تعریف قطعی ندارد؛ به تسک ۱۴ موکول شد |
| ۳.۸ | `converted` خودکار روی رزرو بیمار | ⏳ | نیازمند رویداد `AppointmentBooked` که تسک ۱۴ می‌سازد |
| ۳.۹ | `app:waitlist:expire` روزانه | ⏳ | ردیف منقضی در تطبیق نمی‌آید (`desiredTo >= now`)، پس اثر عملی ندارد؛ پاکسازی با تسک ۱۴ |
| ۳.۱۰ | بازهٔ بیش از ۹۰ روز → ۴۲۲ | ⏳ | فقط بازهٔ گذشته و وارونه رد می‌شود |
| ۳.۱۱ | هفت endpoint | ✅ | ۱۰ تا: سیاست GET/PUT + override + preview + cancel + no-show + لیست انتظار GET/POST/DELETE/matches |

## ۴. دیتابیس

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | سه جدول | ✅ | `Version20260731081142` |
| ۴.۲ | ایندکس تطبیق لیست انتظار | ✅ | |
| ۴.۳ | ایندکس پنجرهٔ عدم حضور | ✅ | |
| ۴.۴ | `risk_tag_uuid` بدون FK | ✅ | همان الگوی موجود پروژه |
| ۴.۵ | دستور seed سیاست پیش‌فرض | ⏳ | لازم نشد: نبودِ سیاست یعنی «بدون جریمه»، پس رفتار پیش‌فرض از قبل امن است |
| ۴.۶ | `TenantSchemaCoverageTest` سبز | ✅ | |

## ۵. UI

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | `CancellationPolicyPage` | ⚠️ | سیاست محیط کامل است؛ جدول override سرویس‌ها ساخته نشد (اندپوینتش هست) |
| ۵.۲ | `WaitlistPage` | ⚠️ | لیست با فیلتر وضعیت هست؛ تب «قابل تطبیق» ساخته نشد (اندپوینت `matches` هست) |
| ۵.۳ | دکمهٔ لغو با محتوای preview | ⏳ | هوک `useCancellationPreview` و `useCancelAppointment` آماده‌اند؛ اتصال به `AppointmentDetailPage` انجام نشد |
| ۵.۴ | نشان پرریسک در پروندهٔ بیمار | ⏳ | برچسب از `TenantTag` می‌آید و در پرونده دیده می‌شود، ولی شمارش عدم حضور نمایش داده نمی‌شود |
| ۵.۵ | `ConfirmDialog` موجود | ✅ | جای دیگری مودال دست‌ساز ساخته نشد |
| ۵.۶ | فیلتر در URL | ✅ | `useUrlState` |
| ۵.۷ | تاریخ شمسی و مبلغ | ✅ | `formatDate` · `PriceInput` |
| ۵.۸ | `backTo` | ✅ | |
| ۵.۹ | هیچ رنگ hard-code | ✅ | |
| ۵.۱۰ | دارک‌مود و حالت فشرده | ⚠️ | فقط توکن‌ها؛ بازبینی چشمی انجام نشد |
| ۵.۱۱ | RTL و موبایل | ✅ | جدول لیست انتظار اسکرول افقی داخلی دارد |
| ۵.۱۲ | رشته‌ها فارسی | ✅ | |
| ۵.۱۳ | `ReserveAppointmentsPage` دست‌نخورده | ✅ | |

## ۶. تست

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | محاسبهٔ جریمه — پنج حالت | ✅ | ⭐ داخل پنجره، بیرون پنجره، کلینیک، سقف پرداختی، بدون سیاست |
| ۶.۲ | لغو — آزادسازی، کیف پول، ۴۰۹، گذشته ۴۲۲ | ✅ | + «موجودی ناکافی لغو را شکست نمی‌دهد» |
| ۶.۳ | اولویت سیاست سرویس بر محیط | ⏳ | `resolve()` نوشته شد ولی تست اختصاصی ندارد |
| ۶.۴ | عدم حضور — سوم برچسب، دوبار یک رکورد | ✅ | ⭐ پنجرهٔ ۱۲ ماه تست نشد |
| ۶.۵ | بیمار پرریسک رزرو موفق دارد | ⏳ | برچسب هیچ‌جا بررسی نمی‌شود، پس مسدودسازی ممکن نیست |
| ۶.۶ | لیست انتظار — ترتیب، سقف اطلاع، شعبه | ✅ | فیلتر روزبخش تست نشد (۳.۷) |
| ۶.۷ | تبدیل به رزرو | ⏳ | با ۳.۸ |
| ۶.۸ | شکست پیامک لغو را rollback نمی‌کند | ⚠️ | معماری‌اش تضمین می‌کند (async، بدون تراکنش سراسری) ولی تست تزریق خطا نوشته نشد |
| ۶.۹ | تست کیف پول موجود سبز ماند | ✅ | ⭐ |
| ۶.۱۰ | سیاست اعتبار روی دوره | ⏳ | مسیرش هست (`credit_refundable`)، تست ترکیبی با دوره نوشته نشد |

**اجرا:** `tests/Cancellation` → ۱۴ تست · `tests/Waitlist` → ۹ تست.

## ۷. مستندات

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۷.۱ | `docs/api/cancellation.md` | ✅ | دو قاعدهٔ گران با دلیلشان |
| ۷.۲ | `docs/api/waitlist.md` | ✅ | تصمیم broadcast و چرایی رد صف انحصاری |

## ۸. بازبینی پایانی

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۸.۱ | هیچ ⏳ بی‌دلیل نمانده | ✅ | ۱۲ مورد ⏳/⚠️ همه با دلیل و تسک مقصد |
| ۸.۲ | `bin/phpunit` کامل سبز | ⚠️ | ۱۳۰۵ تست سبز است، ولی حدود ۴۰٪ اجراهای کامل یک خطای `EntityManager is closed` روی یک تست **تصادفیِ نامرتبط** می‌دهند. در اجرای زیرمجموعه‌ها هرگز تکرار نمی‌شود و تست خطاده هر بار عوض می‌شود. با حذف تست‌های این تسک هم دیده شد ⇒ احتمالاً flake محیط ddev، نه رگرسیون این تسک. **باید جدا بررسی شود** |
| ۸.۳ | `--group=slot-mode-frozen` سبز | ✅ | |
| ۸.۴ | `phpstan` بدون خطای جدید | ✅ | ۱۴ = baseline |
| ۸.۵ | `npx tsc --noEmit` و تست‌های فرانت سبز | ✅ | ۶۳۲ تست |
| ۸.۶ | تست‌های tenant سبز | ✅ | |
| ۸.۷ | `docs/api/*` به‌روز | ✅ | |
| ۸.۸ | چک‌لیست UI کامل | ⚠️ | جز ۵.۱، ۵.۲، ۵.۳، ۵.۴، ۵.۱۰ |
| ۸.۹ | سایت باید preview لغو را نشان دهد | ⏳ | اندپوینت‌ها پنل‌محورند؛ اتصال `nobat724_front` بررسی نشد |
| ۸.۱۰ | `clinic-pro-tauri` بررسی شد | ⏳ | همان |
| ۸.۱۱ | commit، سپس `graphify update .` | ✅ | دو کامیت جدا |
| ۸.۱۲ | موارد به‌تعویق با دلیل | ✅ | روزبخش/تبدیل/انقضا (۳.۷–۳.۹) و رویدادها → تسک ۱۴ · اتصال UI لغو (۵.۳) و نشان پرریسک (۵.۴) · flake تست (۸.۲) |
