# روش‌های نوبت‌دهی هر برنامهٔ هفتگی (`weekly_schedules`) یک روش نوبت‌دهی دارد که در `setting.meta.booking_mode` می‌نشیند. روش، **per محل** است نه per پزشک: یک پزشک می‌تواند در مطب شخصی اسلاتی و در کلینیک سرویسی باشد، چون هر محل برنامهٔ مستقل خودش را دارد (`UNIQUE(doctor_id, entity_type, entity_id)`). | روش | مقدار | مدت نوبت از کجا می‌آید | |---|---|---| | اسلاتی | `slot` (پیش‌فرض) | `duration_per_patient` هر شیفت — اسلات‌های هم‌اندازه | | سرویسی | `service` | مجموع مدت سرویس‌های انتخابی + `buffer_minutes` بین دو نوبت | `booking_mode` پس از **اولین ثبت** قفل می‌شود ({@see \App\Appointment\Entity\WeeklySchedule::getStoredBookingMode()}) و پنل توگل را غیرفعال می‌کند. تا وقتی `null` است قابل تغییر است. --- ## ⛔ حالت اسلاتی منجمد است منطق `slot` تولیدی و زنده است. در فاز موتور نوبت‌دهی چندمنبعی هیچ تسکی اجازهٔ تغییرش را ندارد. فهرست کامل قفل‌شده‌ها: [docs/new_feture/taskes/_shared/red-lines.md](../new_feture/taskes/_shared/red-lines.md) اجبار خودکار: ```bash ddev exec php bin/phpunit --group=slot-mode-frozen ``` سه چیز را قفل می‌کند: شکل پاسخ `appointment-slots`، شکل پاسخ `month-availability`، و امضای متدهای عمومی `SlotCalculatorService`. fixture ها در `tests/Appointment/fixtures/` **read-only** اند — اگر تست قرمز شد، کد باید برگردد نه fixture. --- ## ماتریس: کدام endpoint در کدام حالت | endpoint | `slot` | `service` | |---|---|---| | `GET /api/v1/appointment-slots` | ✅ | — | | `GET /api/v1/appointment-settings/month-availability/{doctorUuid}` | ✅ | ✅ (فقط «این روز ظرفیت دارد؟») | | `GET /api/v1/appointment-booking-services/{doctorUuid}` | ✅ (`booking_mode` را می‌گوید) | ✅ | | `GET /api/v1/appointment-service-slots` | ❌ ۴۲۲ | ✅ | | `GET /api/v1/appointment-booking-locations/{doctorUuid}` | ✅ | ✅ | | `POST /api/v1/appointment` | ✅ | ✅ | | `PATCH /api/v1/appointment/{uuid}` | ✅ (هر مدتی) | ✅ (مدت اعتبارسنجی می‌شود) | | `POST /api/v1/appointment/{uuid}/service-reschedule` | ❌ ۴۲۲ `ERR_APPOINTMENT_004` | ✅ | `appointment-service-slots` تنها endpointی است که حالت را صریحاً رد می‌کند (`این پزشک در حالت نوبت‌دهی سرویسی نیست`). بقیه یا هر دو حالت را می‌پذیرند یا بی‌اثرند. --- ## حالت سرویسی — قرارداد مدت ``` مدت نوبت = مجموع مدت سرویس‌های انتخابی (یا override منشی per سرویس) پایان نوبت = شروع + مدت ← بافر جزو نوبت نیست گام کاندیدها = مدت + بافر ← فاصلهٔ بین دو نوبت ``` مثال واقعی (شیفت ۰۹:۰۰–۱۲:۰۰، سرویس ۲۰+۱۵ دقیقه، بافر ۱۰): ``` ۰۹:۰۰–۰۹:۳۵ ۰۹:۴۵–۱۰:۲۰ ۱۰:۳۰–۱۱:۰۵ ۱۱:۱۵–۱۱:۵۰ ``` گام ۴۵ دقیقه است، پس **۱۱:۰۰ پیشنهاد نمی‌شود** حتی اگر آزاد به نظر برسد. هر مسیری که زمان می‌گیرد باید عضویت در همین فهرست را بسنجد، نه فقط «اشغال نیست»: `isSlotTaken()` تنها تداخل با نوبت دیگر را می‌گوید، ولی فهرست پیشنهادی شیفت، تعطیلی، `date_override`، پنجرهٔ رزرو و بافر را هم اعمال می‌کند. ### تنها مرجع محاسبه `App\Appointment\Service\ServiceBookingCalculator` — مالکیت محیط، وجود، فعال‌بودن و مدت، همه یک‌جا. سه مصرف‌کننده دارد: `appointment-service-slots`، اعتبارسنجی `PATCH`، و `ServiceRescheduleService`. > ⚠️ فرمول فعلی **جمع سادهٔ** مدت‌هاست. مستند موتور نوبت‌دهی (بند ۵) این را رد می‌کند — > آماده‌سازی چند بار حساب می‌شود و ظرفیت الکی پر می‌شود. اصلاحش («زمان تنها / زمان اضافه») > کار تسک ۰۴ است و همان‌جا **یک خط** از این کلاس عوض می‌شود. ### ستون‌های ثبت‌شده روی نوبت | ستون | معنا | |---|---| | `service_total_minutes` | مدت محاسبه‌شده در لحظهٔ ثبت. در حالت اسلاتی `NULL` | | `service_buffer_minutes` | بافر مؤثر در لحظهٔ ثبت — تغییر تنظیمات نوبت‌های ثبت‌شده را عوض نمی‌کند | `slot_end - slot_start` همان عدد را دارد ولی نمی‌گوید عمدی بود یا دستی؛ و نوبت رزرو (`slot_start == slot_end`) هیچ جای دیگری مدت نگه نمی‌دارد. backfill نوبت‌های قدیمی: ```bash ddev exec php bin/console app:appointment:backfill-service-duration # dry-run ddev exec php bin/console app:appointment:backfill-service-duration --force ``` --- ## نوبت رزرو (`is_reserve`) نوبت رزرو **روزی** است نه ساعتی: `slot_start == slot_end == نیمه‌شب`، `active_slot_key` تهی، و هیچ بازه‌ای اشغال نمی‌کند. | عملیات | رفتار | |---|---| | ثبت سرویس‌ها روی رزرو | ✅ سرویس‌ها و `service_total_minutes` ذخیره می‌شوند تا تبدیل بعدی مدت را از دست ندهد | | اعتبارسنجی مدت | معاف — مدتی برای سنجیدن ندارد | | `service-reschedule` | ❌ ۴۲۲ — رزرو زمان ندارد | | تبدیل به نوبت زمان‌دار | `PATCH` با `is_reserve: false` + `slot_start`/`slot_end` | **endpoint جدایی برای تبدیل وجود ندارد و لازم نیست:** `rescheduleTo($start, $end, $isReserve)` خودش `refreshActiveSlotKey()` را صدا می‌زند و کلید یکتایی را بازتولید می‌کند. --- ## حالت سوم (`resource`) — هنوز نیامده تسک ۰۶ فاز موتور چندمنبعی حالت `resource` را اضافه می‌کند: برنامهٔ چندبخشی روی چند منبع (اتاق، دستگاه، اپراتور). ارتقا **یک‌طرفه** خواهد بود و مشروط بر نبودِ نوبت فعال آینده. تا آن زمان، این سند دو حالت دارد و همین ماتریس معتبر است.