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

151 lines
6.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# جریان کاربری — تسک ۰۷
## الف) مسیر موفق — بیمار از سایت عمومی
```
[تسک ۰۶] بیمار ساعت ۰۹:۰۰ را انتخاب می‌کند
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` بنویس، وگرنه دو راه انجام یک کار
گیج‌کننده می‌شود.