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

**وضعیت کلی:** ⏳ شروع نشده · **آخرین بازبینی:** —

قواعد: [_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 .` | ⏳ | |
| ۸.۱۲ | موارد به‌تعویق با دلیل و تسک مقصد | ⏳ | |
