# جریان کاربری — تسک ۰۵ ## الف) کلینیک الگوی بخش‌های یک سرویس را تعریف می‌کند ``` پنل › خدمات › لیزر کندلا › تب «بخش‌های نوبت» │ ├─ «افزودن بخش» │ نام: مالیدن کرم بی‌حسی │ نوع (کلید ادغام): آماده‌سازی │ مدت: ثابت — ۵ دقیقه │ بیمار حاضر است: بله │ با بخش‌های هم‌نوع ادغام شود: بله │ └─ نیازمندی‌ها: │ [اتاق] × ۱ — انحصاری │ [اپراتور] × ۱ — انحصاری — مهارت: لیزر آلکساندرایت — قید: هم‌جنس با بیمار │ ├─ بخش ۲: انتظار اثر بی‌حسی — ثابت ۳۰ — ادغام‌پذیر │ نیازمندی: فقط [اتاق] × ۱ انحصاری ← اپراتور اینجا آزاد است │ ├─ بخش ۳: خود لیزر — سهمی ۱۰۰٪ │ نیازمندی: [اتاق] × ۱ · [اپراتور] × ۱ · [دستگاه] × ۱ از استخر «لیزرهای آلکساندرایت» │ └─ بخش ۴: مراقبت بعد — ثابت ۵ نیازمندی: [اتاق] × ۱ · [اپراتور] × ۱ │ ▼ نوار پیش‌نمایش زمانی (زیر فرم، زنده): ┌─────┬───────────────────┬─────────────┬─────┐ │ ۵' │ ۳۰' │ ۲۰' │ ۵' │ │🏠👤 │ 🏠 │ 🏠 👤 🔧 │🏠👤 │ └─────┴───────────────────┴─────────────┴─────┘ کل: ۶۰ دقیقه · اپراتور واقعاً درگیر: ۳۰ دقیقه │ ▼ «ذخیره» → 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` اجباری است: پنل با آن می‌تواند مستقیم به صفحهٔ منابع همان شعبه لینک بدهد («افزودن اپراتور») و کلینیک در سه کلیک مشکل را حل کند، به‌جای اینکه با یک پیام بن‌بست بماند.