Files
clinicpro/docs/new_feture/taskes/task-13-cancellation-waitlist/checklist.md
T
hamedandClaude Opus 5 e5b74ebab4 docs: settle every remaining row, and add the third occupancy mode
The last structural gap from task 05 was the third occupancy mode. It is
passive: the resource is genuinely held — nobody else can take that room while
the patient waits for the anaesthetic — but the time is not work done. It
blocks exactly like exclusive; the difference is in the report, where without
it a room that spends half its day waiting reads as fully utilised. The mode is
validated, offered in the segment editor and carried through to the plan.

Everything else that was still marked as a deviation is now recorded in
docs/architecture/deviations.md, one row each, in the form "what the plan said
/ what was built / why". That includes the ones I would defend (five plan
services collapsed into one builder that only build() calls; a Skill foreign
key instead of a JSON array, because a deleted skill in JSON fails silently)
and the ones that are simply facts about the product (service_option does not
exist here, so a column for it would sit empty until someone read it as a bug).

The i18n section says plainly that the product is single-language and describes
the order to migrate in if that changes — a translation layer with one language
is an indirection, not an abstraction.

All sixteen checklists now read zero pending and zero unresolved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 16:40:55 +03:30

11 KiB
Raw Blame History

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

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

قواعد: _shared/definition-of-done.md · red-lines.md · 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 — مراحل هست ولی یک تراکنش سراسری ندارد: آزادسازی ظرفیت باید حتی اگر کیف پول یا پیامک بشکند انجام شود؛ تراکنش واحد یعنی یک خطای پیامک، ظرفیت را برنگرداند
۱ جریمه در کیف پول با جفت محیط PatientWalletTenantTest سبز ماند
۱ بازگشت اعتبار طبق سیاست credit_refundable: false ردیف refund را با adjustment منفی خنثی می‌کند — دفتر append-only می‌ماند
۱.۱۰ جلسهٔ دوره آزاد می‌شود از BookingService::cancel() که تسک ۱۲ وصلش کرد
۱.۱۱ لغو دوباره → ۴۰۹
۱.۱۲ لغو نوبت گذشته → ۴۲۲

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

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

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

# مورد وضعیت یادداشت
۳.۱ WaitlistEntry + WaitlistNotifier تصمیم ثبت‌شده در 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 — معماری‌اش تضمین می‌کند (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 تست (۸.۲)