# Appointment Availability API — جستجوی وقت چندمنبعی > **Base:** `/api/v1` · **Auth:** JWT > وابسته به [appointment-plan.md](appointment-plan.md) و [resource-calendar.md](resource-calendar.md). --- ## چه چیزی را حل می‌کند بند ۱۰ مستند: برنامهٔ چندبخشی نوبت را روی تقویم منابع بلغزان و بگو چه ساعت‌هایی **واقعاً** ممکن‌اند، با پیشنهاد اینکه کدام منبع استفاده شود. مسیر قبلی فقط تداخل **پزشک** را می‌سنجید؛ اتاق، دستگاه و اپراتور اصلاً وجود نداشتند. ### چرا این ظرفیت آزاد می‌کند تخصیص **per نقش** است، نه per بخش. اپراتوری که در بخش «انتظار اثر کرم» نیازمندی ندارد، در آن دقایق بررسی نمی‌شود و برای بیمار دیگری آزاد است. نمونهٔ عینی (و تستِ مرجعِ این تسک): بیمار الف ۱۰:۰۰–۱۱:۰۰ نوبت دارد ولی اپراتور فقط ۱۰:۰۰–۱۰:۰۵ و ۱۰:۳۵–۱۱:۰۰ درگیر است. اگر اتاق دومی آزاد باشد، بیمار ب در بازهٔ ۱۰:۰۵–۱۰:۳۵ جا می‌شود. با مدل تک‌بازه‌ای، آن نیم‌ساعت هدر می‌رفت. ### چرا همان منبع در بخش‌های غیرمجاور یک منبع برای **همهٔ** بخش‌هایی که آن نقش را می‌خواهند انتخاب می‌شود. اپراتور بخش ۱ و بخش ۳ باید یک نفر باشد؛ انتخاب مستقل per بخش، دو نفر می‌داد و بیمار وسط کار تحویل شخص دیگری می‌شد. --- ## `POST /api/v1/appointment-availability` ```json { "service_uuid": "…", "branch_uuid": "…", "from": 1785529800, "to": 1785616200, "item_uuids": ["…"], "patient_gender": "female", "doctor_uuid": "…", "step_minutes": 15 } ``` `from`/`to` هر دو شامل‌اند، سقف **۹۰ روز**. `step_minutes` گام تولید کاندید است (پیش‌فرض ۱۵، حداقل ۵). **۲۰۰:** ```json { "success": true, "data": { "plan": { "total_minutes": 60, "segments": [ … ] }, "slots": [ { "start": 1785562200, "end": 1785565800, "assignment": { "room": [{ "uuid": "…", "name": "اتاق ۲" }], "operator": [{ "uuid": "…", "name": "اپراتور ۱" }], "device": [{ "uuid": "…", "name": "لیزر ۳" }] } } ], "reason": null } } ``` `plan` هم برمی‌گردد تا کلاینت مجبور نباشد جدا `preview` بزند. **فهرست خالی خطا نیست و ۴۰۴ هم نیست.** `reason: "no_capacity_in_range"` می‌آید تا کلاینت مجبور نباشد از خالی بودن حدس بزند — ممکن است واقعاً ظرفیتی نباشد. پاسخ حداکثر **۵۰۰** زمان دارد؛ جستجوی یک‌ماهه نباید هزاران ردیف برگرداند. ### خطاها | کد | HTTP | کِی | |---|---|---| | `ERR_WRONG_BOOKING_MODE` | ۴۲۲ | این محل روی `booking_mode = resource` نیست | | `ERR_NO_ELIGIBLE_RESOURCE` | ۴۲۲ | هیچ منبعی شرایط یک بخش را ندارد (از برنامه‌ساز) | | `ERR_VALIDATION_001` | ۴۲۲ | بازهٔ بیش از ۹۰ روز · `to < from` | | — | ۴۰۴ | سرویس یا شعبهٔ محیط دیگر | ## `GET /api/v1/appointment-availability/month` `?service_uuid=&branch_uuid=&from=&to=` → فقط `{ "days": [نیمه‌شبِ روزهای دارای ظرفیت] }`. عمداً سبک است: تقویم ماهانه نباید تخصیص منبع هر زمان را بسازد. --- ## حالت `booking_mode = resource` مقدار سومِ کنار `slot` و `service`. **افزودنی محض**: پیش‌فرض همچنان `slot` است و هیچ محیطی خودبه‌خود به این حالت نمی‌رود — ارتقا داوطلبانه و صریح است. محلی که روی این حالت نرفته باشد، همان `appointment-slots` / `appointment-service-slots` را دارد و مسیر قدیمی **دست‌نخورده** است. --- ## قواعدی که موتور رعایت می‌کند | مورد | رفتار | |---|---| | `setup/cleanup` منبع | بازهٔ اشغال **گسترده‌تر** از بازهٔ بخش است و در تداخل لحاظ می‌شود | | ظرفیت منبع | شمارش است نه حضور: اتاق سه‌تخته سه نوبت هم‌زمان می‌گیرد | | زمان گذشته | حذف می‌شود | | یک منبع، دو نقش هم‌زمان | مجاز نیست | ### کارایی هدف مستند: جستجوی یک‌ماهه **زیر نیم ثانیه**. همهٔ ورودی‌ها یک بار خوانده می‌شوند (تقویم منابع، اشغال‌ها) و بقیه در حافظه است؛ هیچ کوئری داخل حلقهٔ کاندید یا حلقهٔ روز نیست. کاندیدها هم فقط از پنجره‌های آزادِ **محدودکننده‌ترین نقش** ساخته می‌شوند — هرس زودهنگام، جستجوی یک‌ماهه را از ده‌ها هزار کاندید به چند صد می‌رساند. `tests/Appointment/AvailabilityPerformanceTest.php` این را با ۲۰ منبع و ۵۰۰ نوبت ثبت‌شده در ۳۰ روز می‌سنجد و بخشی از تسک است، نه اختیاری. --- ## `resource_occupancy` یک ردیف به‌ازای هر **(بخشِ نوبت × منبع)** — نه یکی به‌ازای کل نوبت. همین ریزدانگی است که ظرفیت آزاد می‌کند. `status` ∈ `booked` | `hold`. نوشتن در این جدول کارِ تسک بعدی (رزرو و ثبت) است؛ این تسک فقط می‌خواندش. ## تست‌ها ```bash ddev exec php bin/phpunit tests/Appointment/AvailabilityEngineTest.php # ۹ تست ddev exec php bin/phpunit tests/Appointment/AvailabilityPerformanceTest.php ```