Files
clinicpro/docs/new_feture/taskes/task-13-cancellation-waitlist/checklist.md
T
hamedandClaude Opus 5 b3c331f0cb perf(reports): read every resource's calendar in one batch, and close the owed tests
Writing the query-count test that task 14 owed showed the growth was real: one
resource cost 10 queries, six cost 33 — about five per resource, because the
available-minutes figure walked each resource's calendar on its own.

Holidays, tenant overrides and branch hours are identical for every resource in
a report, so they now load once outside the loop; shifts and exceptions load for
all resources in one query each. The batched path is a new method rather than a
change to rawAvailability, which the booking engine also calls. The test pins
the shape of the growth, not an exact count.

Also landed:

- app:segment:seed-templates with beauty, dental and physio presets. Building
  four segments and their requirements by hand is the first thing a new clinic
  must do and the most tedious; this gives them something to edit instead of an
  empty page. It refuses to touch a service that already has segments unless
  --force, and it will not invent resource types the tenant never defined.
- book-all is all-or-nothing, proven rather than asserted: with a calendar open
  one day a week and a 1-2 day protocol gap, session one finds a slot and
  session two cannot, and every session must come back planned.
- credit_refundable: false takes the credit back with a negative adjustment and
  deletes nothing — the ledger stays append-only.
- the segments editor has frontend tests, including that it sends back what the
  user sees and renders read-only without the permission.

useBranches now returns [] for a non-array payload instead of throwing
"branches.map is not a function" and taking the page down with it.

BookingLocationsScanTest built a Clinic around a Doctor loaded from a different
manager, which Doctrine treats as a new entity; it flushed fine most runs and
failed on cascade in others. It now loads the doctor from the same manager.

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

10 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 با ترتیب مشخص ⚠️ مراحل هست ولی یک تراکنش سراسری ندارد: آزادسازی ظرفیت باید حتی اگر کیف پول یا پیامک بشکند انجام شود؛ تراکنش واحد یعنی یک خطای پیامک، ظرفیت را برنگرداند
۱ جریمه در کیف پول با جفت محیط 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 مرزها در 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 ⚠️ سیاست محیط کامل است؛ جدول override سرویس‌ها ساخته نشد (اندپوینتش هست)
۵ WaitlistPage ⚠️ لیست با فیلتر وضعیت هست؛ تب «قابل تطبیق» ساخته نشد (اندپوینت matches هست)
۵ دکمهٔ لغو با محتوای 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 نمی‌کند ⚠️ معماری‌اش تضمین می‌کند (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 کامل ⚠️ جز ۵.۱ (جدول override) و ۵.۲ (تب «قابل تطبیق»)
۸.۹ سایت باید preview لغو را نشان دهد اندپوینت‌ها پنل‌محورند؛ اتصال nobat724_front بررسی نشد
۸.۱۰ clinic-pro-tauri بررسی شد همان
۸.۱۱ commit، سپس graphify update . دو کامیت جدا
۸.۱۲ موارد به‌تعویق با دلیل روزبخش/تبدیل/انقضا (۳.۷–۳.۹) و رویدادها → تسک ۱۴ · اتصال UI لغو (۵.۳) و نشان پرریسک (۵.۴) · flake تست (۸.۲)