# جریان کاربری — تسک ۰۵

## الف) کلینیک الگوی بخش‌های یک سرویس را تعریف می‌کند

```
پنل › خدمات › لیزر کندلا › تب «بخش‌های نوبت»
    │
    ├─ «افزودن بخش»
    │     نام: مالیدن کرم بی‌حسی
    │     نوع (کلید ادغام): آماده‌سازی
    │     مدت: ثابت — ۵ دقیقه
    │     بیمار حاضر است: بله
    │     با بخش‌های هم‌نوع ادغام شود: بله
    │     └─ نیازمندی‌ها:
    │           [اتاق] × ۱ — انحصاری
    │           [اپراتور] × ۱ — انحصاری — مهارت: لیزر آلکساندرایت — قید: هم‌جنس با بیمار
    │
    ├─ بخش ۲: انتظار اثر بی‌حسی — ثابت ۳۰ — ادغام‌پذیر
    │     نیازمندی: فقط [اتاق] × ۱ انحصاری        ← اپراتور اینجا آزاد است
    │
    ├─ بخش ۳: خود لیزر — سهمی ۱۰۰٪
    │     نیازمندی: [اتاق] × ۱ · [اپراتور] × ۱ · [دستگاه] × ۱ از استخر «لیزرهای آلکساندرایت»
    │
    └─ بخش ۴: مراقبت بعد — ثابت ۵
          نیازمندی: [اتاق] × ۱ · [اپراتور] × ۱
    │
    ▼
نوار پیش‌نمایش زمانی (زیر فرم، زنده):

  ┌─────┬───────────────────┬─────────────┬─────┐
  │ ۵'  │        ۳۰'        │     ۲۰'     │ ۵'  │
  │🏠👤 │        🏠         │  🏠 👤 🔧   │🏠👤 │
  └─────┴───────────────────┴─────────────┴─────┘
   کل: ۶۰ دقیقه · اپراتور واقعاً درگیر: ۳۰ دقیقه

    │
    ▼
«ذخیره» → PUT /api/v1/service-item/{uuid}/segments
```

خط «اپراتور واقعاً درگیر: ۳۰ دقیقه» مهم‌ترین بازخورد این صفحه است: کلینیک آنجا می‌فهمد
چرا این کار ارزشش را دارد.

---

## ب) بیمار سرویس و آیتم انتخاب می‌کند (سایت عمومی)

```
انتخاب پزشک/کلینیک
    │
    ▼
GET /api/v1/appointment-booking-services/{doctorUuid}
    → booking_mode = "resource"        ← حالت جدید
    → services[] با گروه‌های آیتم
    │
    ▼
بیمار انتخاب می‌کند: ناحیه = صورت + بیکینی · سطح انرژی = ۱۶
    │
    ▼
POST /api/v1/service-selection/validate        (تسک ۰۴، debounce)
    → valid: true · total_duration_minutes: 23 · total_price_rials: …
    │
    │ اگر valid=false:
    │     خطاها زیر همان گروه نمایش داده می‌شوند
    │     «انتخاب حداقل یک مورد از نواحی الزامی است»
    │     «صورت و فول‌بادی با هم قابل انتخاب نیستند»
    │     و دکمهٔ «ادامه» غیرفعال می‌ماند
    ▼
POST /api/v1/appointment-plan/preview
    → total_minutes: 68
      segments: [آماده‌سازی ۵ · انتظار ۳۰ · لیزر ۲۳ · مراقبت ۵ · تمیزکاری ۵]
    │
    │ اگر NoEligibleResourceException:
    │     «هیچ اپراتور خانمی با مهارت لیزر آلکساندرایت در شعبهٔ مرکزی موجود نیست»
    │     + پیشنهاد شعبهٔ دیگر (اگر داشته باشد)
    ▼
مرحلهٔ انتخاب زمان  →  تسک ۰۶
```

**نکته UX:** برنامهٔ نوبت به بیمار **نمایش داده نمی‌شود**. بیمار فقط «۶۸ دقیقه» و
«توضیحات آماده‌سازی» را می‌بیند. بخش‌ها جزئیات عملیاتی کلینیک‌اند؛ نشان دادنشان به بیمار
فقط سؤال می‌سازد.

استثنا: بخش‌هایی با `patient_present = false` نباید در مدت اعلامی به بیمار بیایند
(«تمیزکاری یونیت» ۵ دقیقهٔ بعد از رفتن بیمار است). پس دو عدد وجود دارد:

- `total_minutes` = ۶۸ (اشغال کلینیک) — برای موتور
- `patient_facing_minutes` = ۶۳ — برای نمایش

هر دو در پاسخ `preview` برگردند.

---

## ج) منشی از پنل نوبت می‌سازد

همان جریان ب، با دو تفاوت:

1. `forManagement = true` — بازهٔ رزرو و خاموش بودن نوبت‌دهی آنلاین اعمال نمی‌شود
   (همان رفتاری که `SlotCalculatorService::isWithinBookingWindow()` امروز دارد)
2. منشی می‌تواند **منبع را دستی انتخاب کند**: پاسخ `preview` برای هر نیازمندی
   `candidates` را با نام برمی‌گرداند و پنل یک `SearchableSelect` اختیاری نشان می‌دهد.
   خالی گذاشتن یعنی «تو انتخاب کن» (استراتژی تسک ۰۶).

---

## د) حالت خطا — هیچ منبعی موجود نیست

```
POST /appointment-plan/preview
    ▼
422 {
  "success": false,
  "errors": [{
    "code": "ERR_NO_ELIGIBLE_RESOURCE",
    "message": "هیچ اپراتور خانمی با مهارت لیزر آلکساندرایت در شعبهٔ مرکزی موجود نیست",
    "meta": { "segment": "خود لیزر", "role": "اپراتور", "branch_uuid": "…" }
  }]
}
```

`meta` اجباری است: پنل با آن می‌تواند مستقیم به صفحهٔ منابع همان شعبه لینک بدهد
(«افزودن اپراتور») و کلینیک در سه کلیک مشکل را حل کند، به‌جای اینکه با یک پیام
بن‌بست بماند.
