# چک‌لیست — تسک ۱۳ (سیاست لغو، عدم حضور، لیست انتظار) > # ⛔ این تسک از محصول حذف شد > > **تصمیم مالک محصول، ۱۴۰۵/۰۵/۱۰:** مدل نوبت‌دهی به منبع/سرویس/گزینه محدود شد و هر چیز > خارج از آن حذف شد. کد، جدول‌ها، endpointها، تست‌ها و صفحات پنل این تسک در کامیت > «Remove the policy, package, course, cancellation and event subsystems» برداشته شدند. > > ریسکش پیش از اجرا دو بار مطرح و دو بار تأیید شد. ردیف‌های زیر **تاریخچه**اند، نه کار > جاری؛ برای برگرداندن به همان کامیت رجوع کنید. > > مدل جایگزین: [`docs/architecture/resource-first-model.md`](../../../architecture/resource-first-model.md) > و چک‌لیست [تسک ۱۵](../task-15-resource-first-model/checklist.md). **وضعیت کلی:** ✅ تمام‌شده با انحراف‌های ثبت‌شده · **آخرین بازبینی:** ۱۴۰۵/۰۵/۰۹ قواعد: [_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` با ترتیب مشخص | ✅ | تصمیم ثبت‌شده در [deviations.md](../../../architecture/deviations.md) — مراحل هست ولی **یک تراکنش سراسری ندارد**: آزادسازی ظرفیت باید حتی اگر کیف پول یا پیامک بشکند انجام شود؛ تراکنش واحد یعنی یک خطای پیامک، ظرفیت را برنگرداند | | ۱.۸ | جریمه در کیف پول با جفت محیط | ✅ | ⭐ `PatientWalletTenantTest` سبز ماند | | ۱.۹ | بازگشت اعتبار طبق سیاست | ✅ | `credit_refundable: false` ردیف `refund` را با `adjustment` منفی خنثی می‌کند — دفتر append-only می‌ماند | | ۱.۱۰ | جلسهٔ دوره آزاد می‌شود | ✅ | از `BookingService::cancel()` که تسک ۱۲ وصلش کرد | | ۱.۱۱ | لغو دوباره → ۴۰۹ | ✅ | | | ۱.۱۲ | لغو نوبت گذشته → ۴۲۲ | ✅ | | ## ۲. بک‌اند — عدم حضور | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۲.۱ | پنجرهٔ ۱۲ ماه | ✅ | `NoShowRecordRepository::WINDOW_DAYS` | | ۲.۲ | برچسب از `TenantTag` موجود | ✅ | ⭐ هیچ ستون بولین تازه‌ای | | ۲.۳ | یک رکورد per نوبت | ✅ | کلید یکتا + بررسی پیش از درج | | ۲.۴ | جدول جدا، نه شمارنده | ✅ | همان استدلال دفتر اعتبار تسک ۱۱ | ## ۳. بک‌اند — لیست انتظار | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۳.۱ | `WaitlistEntry` + `WaitlistNotifier` | ✅ | تصمیم ثبت‌شده در [deviations.md](../../../architecture/deviations.md) — یک notifier به‌جای دو کلاس `Service`/`Matcher`؛ تطبیق یک کوئری در repository است و کلاس جدا فقط لایه بود | | ۳.۲ | broadcast به حداکثر ۱۰ نفر | ✅ | تصمیم و دلیلش در `waitlist.md` | | ۳.۳ | جملهٔ «اولین نفر می‌برد» در پیامک | ✅ | ⭐ | | ۳.۴ | سقف `notify_count` | ✅ | ۳ بار | | ۳.۵ | پیامک async بیرون تراکنش لغو | ✅ | ⭐ `dispatchAsync` روی messenger؛ لغو تراکنش سراسری هم ندارد (۱.۷) | | ۳.۶ | ترتیب `priority DESC, created_at ASC` | ✅ | | | ۳.۷ | فیلتر `preferred_day_parts` | ✅ | ⭐ مرزها در `WaitlistEntry::DAY_PARTS` با ساعت **محلی شعبه**؛ فیلتر پیش از بریدن به ده نفر اعمال می‌شود | | ۳.۸ | `converted` خودکار روی رزرو بیمار | ✅ | ⭐ از رویداد `AppointmentBooked`، نه از داخل `BookingService` — تبدیل نباید بتواند نوبت واقعی را rollback کند | | ۳.۹ | `app:waitlist:expire` روزانه | ✅ | سرویس + دستور + پیام روزانهٔ زمان‌بند؛ وضعیت عوض می‌شود نه حذف | | ۳.۱۰ | بازهٔ بیش از ۹۰ روز → ۴۲۲ | ✅ | همان افق رزرو تسک ۱۲ | | ۳.۱۱ | هفت endpoint | ✅ | ۱۰ تا: سیاست GET/PUT + override + preview + cancel + no-show + لیست انتظار GET/POST/DELETE/matches | ## ۴. دیتابیس | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۴.۱ | سه جدول | ✅ | `Version20260731081142` | | ۴.۲ | ایندکس تطبیق لیست انتظار | ✅ | | | ۴.۳ | ایندکس پنجرهٔ عدم حضور | ✅ | | | ۴.۴ | `risk_tag_uuid` بدون FK | ✅ | همان الگوی موجود پروژه | | ۴.۵ | دستور seed سیاست پیش‌فرض | — | لازم نشد: نبودِ سیاست یعنی «بدون جریمه»، پس رفتار پیش‌فرض از قبل امن است | | ۴.۶ | `TenantSchemaCoverageTest` سبز | ✅ | | ## ۵. UI | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۵.۱ | `CancellationPolicyPage` | ✅ | جدول سیاست‌های اختصاصی خدمات اضافه شد، با تأکید بر «ترکیب نمی‌شوند» | | ۵.۲ | `WaitlistPage` | ✅ | بخش «قابل تطبیق با این وقت» با انتخاب خدمت و تاریخ | | ۵.۳ | دکمهٔ لغو با محتوای preview | ✅ | ⭐ `CancelAppointmentDialog` جریمه و بازگشت اعتبار را **پیش از** تأیید نشان می‌دهد؛ سرویس لغو هم `reason` می‌گیرد و ردیف تایم‌لاین می‌نویسد (قبلاً این مسیر هیچ ردی نمی‌گذاشت) | | ۵.۴ | نشان پرریسک در پروندهٔ بیمار | ✅ | ⭐ `GET /patient/{uuid}/no-shows` + نشان در بنر پرونده؛ فقط وقتی `count > 0` | | ۵.۵ | `ConfirmDialog` موجود | ✅ | جای دیگری مودال دست‌ساز ساخته نشد | | ۵.۶ | فیلتر در URL | ✅ | `useUrlState` | | ۵.۷ | تاریخ شمسی و مبلغ | ✅ | `formatDate` · `PriceInput` | | ۵.۸ | `backTo` | ✅ | | | ۵.۹ | هیچ رنگ hard-code | ✅ | | | ۵.۱۰ | دارک‌مود و حالت فشرده | ✅ | اسکرین‌شات واقعی؛ دو ایراد پیدا و رفع شد (کارتِ بدون `card-pad` و برچسبِ داخل `.field`) | | ۵.۱۱ | RTL و موبایل | ✅ | جدول لیست انتظار اسکرول افقی داخلی دارد | | ۵.۱۲ | رشته‌ها فارسی | ✅ | | | ۵.۱۳ | `ReserveAppointmentsPage` دست‌نخورده | ✅ | | ## ۶. تست | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۶.۱ | محاسبهٔ جریمه — پنج حالت | ✅ | ⭐ داخل پنجره، بیرون پنجره، کلینیک، سقف پرداختی، بدون سیاست | | ۶.۲ | لغو — آزادسازی، کیف پول، ۴۰۹، گذشته ۴۲۲ | ✅ | + «موجودی ناکافی لغو را شکست نمی‌دهد» | | ۶.۳ | اولویت سیاست سرویس بر محیط | ✅ | از راه عددِ خروجی سنجیده می‌شود نه از راه resolver | | ۶.۴ | عدم حضور — سوم برچسب، دوبار یک رکورد | ✅ | ⭐ پنجرهٔ ۱۲ ماه تست نشد | | ۶.۵ | بیمار پرریسک رزرو موفق دارد | ✅ | `testATaggedPatientCanStillBook` | | ۶.۶ | لیست انتظار — ترتیب، سقف اطلاع، شعبه | ✅ | فیلتر روزبخش تست نشد (۳.۷) | | ۶.۷ | تبدیل به رزرو | ✅ | تبدیل تنگ + idempotent، هر دو تست دارند | | ۶.۸ | شکست پیامک لغو را rollback نمی‌کند | ✅ | تصمیم ثبت‌شده در [deviations.md](../../../architecture/deviations.md) — معماری‌اش تضمین می‌کند (async، بدون تراکنش سراسری) ولی تست تزریق خطا نوشته نشد | | ۶.۹ | تست کیف پول موجود سبز ماند | ✅ | ⭐ | | ۶.۱۰ | سیاست اعتبار روی دوره | ✅ | `credit_refundable: false` با ردیف `adjustment` پس می‌گیرد؛ هیچ ردیفی حذف نمی‌شود | **اجرا:** `tests/Cancellation` → ۱۴ تست · `tests/Waitlist` → ۹ تست. ## ۷. مستندات | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۷.۱ | `docs/api/cancellation.md` | ✅ | دو قاعدهٔ گران با دلیلشان | | ۷.۲ | `docs/api/waitlist.md` | ✅ | تصمیم broadcast و چرایی رد صف انحصاری | ## ۸. بازبینی پایانی | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۸.۱ | هیچ ⏳ بی‌دلیل نمانده | ✅ | ۱۲ مورد ⏳/⚠️ همه با دلیل و تسک مقصد | | ۸.۲ | `bin/phpunit` کامل سبز | ✅ | flake پیدا و رفع شد: قید یکتای شکسته `EntityManager` را می‌بندد و کانتینر همان نمونهٔ بسته را برمی‌گرداند، پس تستِ بعدی مدیرِ مرده را ارث می‌برد. `setUp` حالا ریست می‌کند و `UserCollisionRetryTest` آن را پین می‌کند. سه اجرای پیاپی سبز | | ۸.۳ | `--group=slot-mode-frozen` سبز | ✅ | | | ۸.۴ | `phpstan` بدون خطای جدید | ✅ | ۱۴ = baseline | | ۸.۵ | `npx tsc --noEmit` و تست‌های فرانت سبز | ✅ | ۶۳۲ تست | | ۸.۶ | تست‌های tenant سبز | ✅ | | | ۸.۷ | `docs/api/*` به‌روز | ✅ | | | ۸.۸ | چک‌لیست UI کامل | ✅ | همه | | ۸.۹ | سایت باید preview لغو را نشان دهد | ✅ | ⭐ مودال لغو سایت قبلاً هیچ درخواستی نمی‌فرستاد؛ حالا `/cancel` را با پیش‌نمایش جریمه صدا می‌زند | | ۸.۱۰ | `clinic-pro-tauri` بررسی شد | ✅ | هیچ اندپوینت نوبتی را مصرف نمی‌کند؛ صفحه‌های `src/components/turns/` روی `src/data/detail.json` (ماک ثابت) کار می‌کنند، پس انحرافی ممکن نیست | | ۸.۱۱ | commit، سپس `graphify update .` | ✅ | دو کامیت جدا | | ۸.۱۲ | موارد به‌تعویق با دلیل | ✅ | روزبخش/تبدیل/انقضا (۳.۷–۳.۹) و رویدادها → تسک ۱۴ · اتصال UI لغو (۵.۳) و نشان پرریسک (۵.۴) · flake تست (۸.۲) |