# تسک ۰۵ — بخش‌های نوبت و سازندهٔ برنامه **فاز:** ۱ (هسته) · **وابستگی:** ۰۲، ۰۴ · **زمان:** ۱۶-۲۰ ساعت --- ## هدف مهم‌ترین بخش مستند (بند ۷). یک نوبت یک تکه زمان پیوسته نیست: | بخش | مدت | اتاق | اپراتور | دستگاه | |---|---|---|---|---| | مالیدن کرم بی‌حسی | ۵ | اشغال | اشغال | آزاد | | انتظار اثر کرم | ۳۰ | اشغال | **آزاد** | آزاد | | خود لیزر | ۲۰ | اشغال | اشغال | اشغال | | مراقبت بعد | ۵ | اشغال | اشغال | آزاد | با مدل امروز اپراتور ۶۰ دقیقه قفل می‌شود در حالی که ۳۰ دقیقه کار می‌کند. نصف ظرفیت هدر می‌رود. ## وضعیت فعلی ```php // src/Appointment/Entity/Appointment.php private int $slotStart; // یک بازهٔ پیوسته private int $slotEnd; ``` هیچ مفهومی از بخش، و هیچ نیازمندی منبعی وجود ندارد. تنها منبعی که تداخلش بررسی می‌شود پزشک است (`AppointmentRepository::isSlotTaken`). ## دامنه **هست:** - `SegmentTemplate` — الگوی بخش‌های یک سرویس (و بخش‌های اضافهٔ هر `ServiceOption`) - `SegmentRequirement` — نیازمندی منبع هر بخش: نقش، تعداد، شرط مهارت، قید، نوع اشغال - `AppointmentPlanBuilder` — از (سرویس، آیتم‌ها، بیمار، شعبه) یک **برنامهٔ نوبت** می‌سازد - ادغام بخش‌های هم‌نوع، محاسبهٔ مدت هر بخش، چیدمان ترتیبی - `GET /api/v1/appointment-plan/preview` برای دیدن برنامه پیش از جستجوی وقت **نیست:** پیدا کردن منابع آزاد و زمان (تسک ۰۶)، ثبت اشغال (تسک ۰۷)، اعمال قوانین روی برنامه (تسک ۰۹ — نقطهٔ اتصالش اینجا آماده می‌شود). ## Endpoint ها | متد | مسیر | توضیح | |---|---|---| | GET/PUT | `/api/v1/service-item/{uuid}/segments` | الگوی بخش‌های سرویس | | PUT | `/api/v1/segment-template/{uuid}/requirements` | نیازمندی‌های منبع یک بخش | | POST | `/api/v1/appointment-plan/preview` | ساخت و برگرداندن برنامهٔ نوبت (بدون ثبت) | ## خروجی `POST /appointment-plan/preview` ```json { "success": true, "data": { "total_minutes": 60, "segments": [ { "sequence": 1, "name": "بی‌حسی موضعی", "offset_minutes": 0, "duration_minutes": 5, "patient_present": true, "requirements": [ { "role": "room", "count": 1, "occupancy": "exclusive", "candidates": 3 }, { "role": "operator", "count": 1, "occupancy": "exclusive", "candidates": 2 } ] }, { "sequence": 2, "name": "انتظار", "offset_minutes": 5, "duration_minutes": 30, "patient_present": true, "mergeable": true, "requirements": [ { "role": "room", "count": 1, "occupancy": "exclusive", "candidates": 3 } ] } ] } } ``` `candidates` تعداد منابع واجد شرایط است — اگر صفر باشد، برنامه ساخته نمی‌شود و خطای انسانی برمی‌گردد: «هیچ اپراتور خانمی با مهارت لیزر در این شعبه نیست» (مستند بند ۱۰). ## معیار پذیرش - ✅ موفق: سرویس «لیزر» با چهار بخش بالا تعریف می‌شود؛ `preview` برنامه‌ای با `total_minutes = 60` و چهار بخش با `offset_minutes` صحیح (۰، ۵، ۳۵، ۵۵) برمی‌گرداند. - ✅ موفق: انتخاب دو ناحیه (صورت + بیکینی) → بخش «آماده‌سازی» **یک بار** می‌آید (`mergeable=true` هم‌نوع‌ها ادغام می‌شوند) ولی بخش «لیزر» مدتش با `DurationCalculator` تسک ۰۴ محاسبه شده است. - ✅ موفق: سرویسی که هیچ `SegmentTemplate` ندارد → برنامه‌ای با **یک بخش** برابر کل مدت و نیازمندی پیش‌فرض (منبع `type=doctor`). این همان رفتار امروز است. - ❌ خطا: نیازمندی با مهارتی که هیچ منبعی در آن شعبه ندارد → `422` با `ERR_NO_ELIGIBLE_RESOURCE` و پیام فارسی شامل نقش و مهارت. - ❌ خطا: بخش با `duration_minutes <= 0` و بدون منبع مدت پویا → `422`. - ⚠️ مرزی: بخش با `requirements = []` (مثلاً «انتظار در خانه») → معتبر؛ زمان می‌گیرد، هیچ منبعی نمی‌گیرد. - ⚠️ مرزی: قید `same_gender_as_patient` وقتی جنسیت بیمار نامشخص است → نیازمندی نادیده گرفته **نمی‌شود**؛ `422` با پیام «برای این سرویس ثبت جنسیت بیمار الزامی است». - ⚠️ مرزی: `setup/cleanup` منبع در `preview` **نمایش داده نمی‌شود** ولی در `occupancy_offset` هر نیازمندی می‌آید تا تسک ۰۶ همان را استفاده کند. - ⚠️ مرزی: مجموع مدت بخش‌ها بیشتر از ۸ ساعت → `422` (حفاظت از جستجوی وقت). ## خروجی - `src/Appointment/Plan/` — entity ها، `AppointmentPlanBuilder`، DTO ها - `assets/admin/pages/ServiceSegmentsPage.tsx` + پیش‌نمایش برنامه - `docs/api/appointment-plan.md` - الگوهای آماده: `app:segment:seed-templates --preset=beauty|dental|physio` (مستند بند ۱۷)