Files
hamed 021d0eb6b2 feat: implement cancellation policy, no-show tracking, and waitlist management
- Add implementation notes for cancellation and waitlist features.
- Create task documentation outlining goals, current status, and acceptance criteria for cancellation policy and resource utilization reporting.
- Establish architecture for domain events and outbox pattern to ensure reliable event publishing.
- Define database schema for domain events and necessary queries for resource utilization and plan accuracy reports.
- Implement detailed implementation notes covering edge cases, testing strategies, and documentation requirements.
2026-07-30 11:43:58 +03:30

6.5 KiB
Raw Permalink Blame History

جریان کاربری — تسک ۰۷

الف) مسیر موفق — بیمار از سایت عمومی

[تسک ۰۶] بیمار ساعت ۰۹:۰۰ را انتخاب می‌کند
    │
    ▼
POST /api/v1/appointment-hold
{ "doctor_uuid":"…", "branch_uuid":"…", "service_item_uuid":"…",
  "option_uuids":["…"], "start": 1754…, "assignment": { "operator":"…", "room":"…", "device":"…" } }
    │
    ├─ سرور: برنامه را دوباره می‌سازد (به assignment اعتماد نمی‌کند)
    ├─ نوبت pending با expires_at = now + 900
    ├─ appointment_segments × ۵
    └─ resource_occupancy × (بخش × منبع) — بخش انتظار فقط اتاق
    ▼
201 { "hold_uuid":"…", "expires_at": 1754…, "total_price_rials": … }
    │
    │  ⏱ تایمر ۱۵ دقیقه‌ای در UI: «۱۴:۵۹ برای تکمیل رزرو»
    ▼
پرداخت بیعانه (اگر deposit_required)  →  درگاه  →  بازگشت
    ▼
POST /api/v1/appointment-confirm { "hold_uuid":"…" }
    │
    ├─ ۱ hold معتبر است؟
    ├─ ۲ قوانین صلاحیت و فاصله (تسک ۰۹)
    ├─ ۳ وضعیت → confirmed
    ├─ ۴ اشغال‌ها hold → booked
    ├─ ۵ snapshot قیمت (تسک ۰۸)
    └─ ۶ رویداد AppointmentBooked (بعد از commit)
    ▼
200 { "appointment_uuid":"…", "status":"confirmed" }
    ▼
پیامک تأییدیه (از راه رویداد، async)

ب) رقابت روی ساعت پرتقاضا

بیمار الف                          بیمار ب
   │  ۰۹:۰۰ را می‌بیند                │  ۰۹:۰۰ را می‌بیند
   │                                 │
   ▼ POST /appointment-hold          ▼ POST /appointment-hold
   │                                 │
   │  INSERT slot (res=7,b=…,u=0) ✓  │  INSERT slot (res=7,b=…,u=0) ✗ duplicate
   ▼                                 ▼
201 hold_uuid                    409 ERR_SLOT_TAKEN
                                     │
                                     ▼
                        UI: «این ساعت همین لحظه رزرو شد.»
                            + لیست به‌روزشدهٔ وقت‌های نزدیک
                              (خودکار، بدون کلیک دوباره)

پیام «این ساعت همین لحظه رزرو شد» + پیشنهاد جایگزین، همان کاری است که مستند بند ۱۷ برای ریسک «رقابت روی ساعت‌های پرتقاضا» می‌خواهد. 409 خالی بدون جایگزین یعنی بیمار می‌رود.


ج) hold منقضی می‌شود

hold ساخته شد ─── ۱۵ دقیقه ───▶ منقضی
                                  │
        ┌─────────────────────────┴──────────────────────────┐
        │                                                    │
   cron هر دقیقه                                   یا: بیمار confirm می‌زند
   ExpireAppointmentsHandler                                 │
        │                                                    ▼
        ├─ status → expired                          409 ERR_HOLD_EXPIRED
        ├─ occupancy → released                              │
        └─ occupancy_slot → DELETE                           ▼
        ▼                                       UI: «زمان رزرو شما به پایان رسید»
   زمان دوباره در جستجو ظاهر می‌شود                    + بازگشت به لیست وقت‌ها

نکته: حتی پیش از اجرای cron، hasRoom تسک ۰۶ شرط expires_at > now را دارد، پس hold مردهٔ چند ثانیه‌ای هم مانع کسی نمی‌شود. cron فقط تمیزکاری است.


د) منشی نوبت را جابه‌جا می‌کند

پنل › نوبت‌ها › جزئیات نوبت › «جابه‌جایی»
    │
    ▼
انتخاب تاریخ/ساعت جدید (همان UI تسک ۰۶، با forManagement=true)
    ▼
POST /api/v1/appointment/{uuid}/reschedule { "start": … , "assignment": {…} }
    │
    ├─ ۱ hold جدید ساخته می‌شود      ← اگر شکست: rollback کامل، نوبت قدیم سالم
    ├─ ۲ اشغال‌های قدیم released
    ├─ ۳ نوبت قدیم → rescheduled
    └─ ۴ لینک قدیم ↔ جدید در appointment_events
    ▼
200 { "new_appointment_uuid": "…" }
    ▼
پیامک اطلاع‌رسانی جابه‌جایی

اگر hold جدید 409 بدهد:

422 { "errors": [{ "code":"ERR_SLOT_TAKEN",
                   "message":"زمان جدید در دسترس نیست. نوبت فعلی تغییری نکرد." }] }

جملهٔ دوم پیام اجباری است — منشی باید بداند وضعیت فعلی امن است و لازم نیست چیزی را درست کند.


ه) لغو نوبت

لغو (بیمار یا پزشک یا منشی)
    │
    ├─ status → cancelled_by_user / cancelled_by_doctor
    ├─ active_slot_key → NULL          (مکانیزم موجود، دست‌نخورده)
    ├─ resource_occupancy → released
    └─ resource_occupancy_slot → DELETE
    ▼
ظرفیت فوری آزاد می‌شود
    ▼
[تسک ۱۳] لیست انتظار همان بازه اطلاع می‌گیرد

و) مسدودسازی دستی یک منبع

حالت خاصی که appointment_id = NULL را توضیح می‌دهد:

پنل › منابع › دستگاه کندلا ۲ › «مسدودسازی بازه»
    تاریخ/ساعت + دلیل: «سرویس دوره‌ای»
    ▼
یک ردیف resource_occupancy با appointment_id=NULL و status='booked'
(یا معادلاً یک resource_exception از تسک ۰۳ — هر دو کار می‌کنند)

تصمیم: مسدودسازی بلندمدت و تکرارشوندهresource_exception (تسک ۰۳). مسدودسازی موردی و کوتاهresource_occupancy با appointment_id=NULL. دلیل: دومی در همان ایندکس داغ می‌نشیند و در OccupancyIndex بدون کد اضافه دیده می‌شود. این تفکیک را در docs/api/appointment-booking.md بنویس، وگرنه دو راه انجام یک کار گیج‌کننده می‌شود.