docs/architecture/resource-first-model.md describes the shape: the three entities, why an option is a ServiceItem rather than a fourth table, the four-level resolution chain, the two conditions on the eligibility filter and what each of them prevented, and why containment is a graph beside the display tree rather than the tree itself. docs/api/resource.md gains both offering endpoints with the response captured from a real call, including a row where the price comes from the branch and one where it comes from the resource — the two cases the *_source fields exist for. docs/api/appointment.md documents resource_uuid, the doctor inference, and the nullable resource/service_option in the response. The checklists for tasks 9 to 14 keep their rows but open with a banner saying the task was removed, when, by whose decision, and which commit to revert. They are history now; deleting them would erase the record of work that shipped and was then withdrawn. Verified end to end: 1304 tests, slot-mode-frozen green, phpstan at 14, tsc clean, 648 panel tests, and app:seed-scenarios --reset builds all three environments. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
12 KiB
12 KiB
چکلیست — تسک ۱۳ (سیاست لغو، عدم حضور، لیست انتظار)
⛔ این تسک از محصول حذف شد
تصمیم مالک محصول، ۱۴۰۵/۰۵/۱۰: مدل نوبتدهی به منبع/سرویس/گزینه محدود شد و هر چیز خارج از آن حذف شد. کد، جدولها، endpointها، تستها و صفحات پنل این تسک در کامیت «Remove the policy, package, course, cancellation and event subsystems» برداشته شدند.
ریسکش پیش از اجرا دو بار مطرح و دو بار تأیید شد. ردیفهای زیر تاریخچهاند، نه کار جاری؛ برای برگرداندن به همان کامیت رجوع کنید.
مدل جایگزین:
docs/architecture/resource-first-model.mdو چکلیست تسک ۱۵.
وضعیت کلی: ✅ تمامشده با انحرافهای ثبتشده · آخرین بازبینی: ۱۴۰۵/۰۵/۰۹
قواعد: _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 تست (۸.۲) |