# تسک ۱۰: ماژول نوبت‌دهی ## توضیح سیستم رزرو نوبت شامل نمایش اسلات‌های خالی، رزرو نوبت، لغو نوبت، روزهای غیرقابل رزرو و لیست نوبت‌های کاربر. ## Endpoint ها | متد | مسیر | توضیح | نیاز به Auth | |-----|------|-------|-------------| | GET | `/api/v1/appointment-slots` | اسلات‌های خالی دکتر در تاریخ | خیر | | POST | `/api/v1/appointment` | رزرو نوبت | بله | | GET | `/api/v1/appointment/not-available/{doctorId}` | روزهای غیرقابل رزرو | خیر | | GET | `/api/v1/appointment/my-appointments/{userId}` | نوبت‌های من | بله | | PATCH | `/api/v1/appointment/{uuid}/cancel` | لغو نوبت توسط کاربر | بله (Owner) | | PATCH | `/api/v1/appointment/{uuid}/status` | تغییر وضعیت نوبت | بله (Doctor/Secretary/Admin) | ## پیش‌نیازها - تسک ۰۱، ۰۲، ۰۵ (Doctor)، ۰۹ (تنظیمات)، ۱۵ (Payment) ## زمان تخمینی ۱۲ تا ۱۵ ساعت --- ## Status Machine نوبت ``` [ایجاد نوبت] ↓ waiting_for_payment ──→ (پرداخت موفق) ──→ reserved ↓ ↓ (لغو) ┌────────────┤ ↓ │ │ cancelled_by_patient checked_in (لغو دکتر) ↓ ↓ waiting cancelled_by_doctor ↓ in_progress ↓ ┌─────────────┴─────────────┐ ↓ ↓ visited no_show ↓ completed ``` **وضعیت‌ها:** | وضعیت | توضیح | چه کسی تغییر می‌دهد | |--------|-------|---------------------| | `waiting_for_payment` | منتظر پرداخت | سیستم — بعد از رزرو | | `reserved` | رزرو شده — پرداخت موفق | سیستم — بعد از تأیید پرداخت | | `checked_in` | بیمار به مطب رسیده | منشی/دکتر | | `waiting` | در صف انتظار مطب | منشی/دکتر | | `in_progress` | ویزیت در حال انجام | منشی/دکتر | | `visited` | ویزیت انجام شد | منشی/دکتر | | `no_show` | بیمار نیامد | منشی/دکتر | | `completed` | کامل شد | سیستم | | `cancelled_by_patient` | لغو توسط بیمار | بیمار (Owner) | | `cancelled_by_doctor` | لغو توسط دکتر | دکتر/Admin | | `postponed` | به تعویق افتاده | دکتر/Admin | --- ## فلوی کامل رزرو + پرداخت ``` POST /api/v1/appointment ↓ 1. بررسی اسلات: آیا time در آن date خالی است؟ 2. بررسی holiday/date_override 3. ایجاد appointment با status=waiting_for_payment 4. بازگشت uuid نوبت به کلاینت ↓ POST /api/v1/payment (در task-15) { appointment_uuid: "...", payment_method: "mellat" } ↓ 5. ایجاد payment با status=pending 6. دریافت payment_url از درگاه 7. redirect کاربر به درگاه ↓ [Callback از درگاه بانک] ↓ 8. تأیید پرداخت → payments.status = 'received' 9. appointments.status = 'reserved' 10. واریز کمیسیون به کیف پول نماینده (اگر از دامنه نماینده) ``` **⚠ نکته:** اگر در ۳۰ دقیقه پرداخت نشود → `waiting_for_payment` به `cancelled_by_system` تغییر کند (job) --- ## GET /api/v1/appointment-slots ``` Query params: doctor_uuid (الزامی) date (الزامی) — فرمت: YYYY-MM-DD ``` ```json { "success": true, "data": { "date": "2024-03-20", "doctor": { "uuid": "...", "name": "دکتر احمدی" }, "slots": [ { "time": "09:00", "available": true, "duration": 30 }, { "time": "09:30", "available": false, "duration": 30 }, { "time": "10:00", "available": true, "duration": 30 } ] } } ``` **منطق محاسبه اسلات‌های خالی:** ``` 1. بارگذاری weekly_schedule دکتر برای روز هفته مربوطه 2. بررسی date_override برای تاریخ مشخص 3. بررسی holiday (اگر تاریخ در بازه تعطیلی است → همه اسلات‌ها unavailable) 4. خواندن نوبت‌های موجود با status ≠ cancelled → آن اسلات‌ها unavailable 5. بازگشت لیست اسلات‌ها با وضعیت available/unavailable ``` --- ## POST /api/v1/appointment ```json // Request { "doctor_uuid": "61be915b-...", "date": "2024-03-20", "time": "09:00", "address_id": 39, "insurance_type_id": null, "notes": "درد معده دارم" } // Response 201 { "success": true, "data": { "uuid": "...", "doctor": { "uuid": "...", "name": "دکتر احمدی" }, "date": "2024-03-20", "time": "09:00", "status": "waiting_for_payment", "created_at": 1748000000 } } // Response 409 — اسلات گرفته شده { "success": false, "errors": [{ "code": "ERR_APPOINTMENT_001", "message": "اسلات انتخاب‌شده در دسترس نیست" }] } ``` --- ## PATCH /api/v1/appointment/{uuid}/cancel — لغو نوبت ```json // Request { "reason": "به دلیل بیماری نمی‌توانم بیایم" } // Response 200 { "success": true, "data": { "uuid": "...", "status": "cancelled_by_patient", "refund_status": "pending" } } // Response 400 — نوبت قابل لغو نیست { "success": false, "errors": [{ "code": "ERR_APPOINTMENT_002", "message": "نوبت در وضعیت فعلی قابل لغو نیست" }] } ``` **قوانین لغو:** - فقط نوبت‌های با status `waiting_for_payment` یا `reserved` قابل لغو هستند - اگر پرداخت شده (`reserved`) → `payments.status = 'refund'` و refund شروع می‌شود - لغو بعد از `checked_in` فقط توسط Admin/Doctor مجاز است --- ## PATCH /api/v1/appointment/{uuid}/status ```json // Request (Doctor/Secretary/Admin) { "status": "checked_in" } // Response 200 { "success": true, "data": { "uuid": "...", "status": "checked_in", "updated_at": 1748000000 } } ``` **Transition های مجاز:** ``` reserved → checked_in (Doctor/Secretary) checked_in → waiting (Doctor/Secretary) waiting → in_progress (Doctor/Secretary) in_progress → visited (Doctor/Secretary) in_progress → no_show (Doctor/Secretary) visited → completed (System/Doctor) reserved → cancelled_by_doctor (Doctor/Admin) reserved → postponed (Doctor/Admin) ``` --- ## GET /api/v1/appointment/not-available/{doctorId} ```json { "success": true, "data": { "not_available_dates": [ "2024-03-20", "2024-03-21", "2024-04-01" ] } } ``` **منطق:** - روزهایی که holiday هستند - روزهایی که date_override با `active=false` تعریف شده - روزهایی که همه اسلات‌ها پر هستند --- ## GET /api/v1/appointment/my-appointments/{userId} ``` Query params: status (اختیاری) — فیلتر بر اساس وضعیت page (اختیاری، پیش‌فرض 1) limit (اختیاری، پیش‌فرض 10) ``` ```json { "success": true, "data": [ { "uuid": "...", "doctor": { "uuid": "...", "name": "دکتر احمدی", "specialty": "قلب و عروق", "img": [{ "url": "..." }] }, "date": "2024-03-20", "time": "09:00", "status": "reserved", "payment_status": "received", "created_at": 1748000000 } ], "meta": { "totalRecords": 12, "totalPages": 2, "currentPage": 1 } } ``` --- ## نکات مهم - **Optimistic Locking:** هنگام رزرو اسلات، از Transaction + Lock استفاده شود تا race condition نباشد - **Expiry Job:** نوبت‌های `waiting_for_payment` بعد از ۳۰ دقیقه باید auto-cancel شوند (Symfony Scheduler) - **N+1 Prevention:** در لیست نوبت‌ها، دکتر و وضعیت پرداخت با eager loading بارگذاری شوند - **Timestamps:** همه تاریخ/زمان‌ها Unix timestamp (INT) ذخیره می‌شوند