# تسک ۰۶ — موتور جستجوی وقت چندمنبعی

**فاز:** ۱ (هسته) · **وابستگی:** ۰۳، ۰۵ · **زمان:** ۲۰-۲۴ ساعت

---

## هدف

مستند بند ۱۰: برنامهٔ نوبت (تسک ۰۵) را روی تقویم منابع (تسک ۰۳) بلغزان و بگو چه
ساعت‌هایی واقعاً ممکن‌اند — با پیشنهاد اینکه کدام منبع استفاده شود.
**هدف کارایی: جستجوی یک ماهه زیر نیم ثانیه.**

## وضعیت فعلی

```php
// SlotCalculatorService::getServiceStartTimes() — تک‌منبعی، یک بلوک پیوسته
$busy = $this->appointmentRepo->findBusyIntervals($doctor, $dayStart, $dayStart + 86400);
while ($t + $durSec <= $winEnd) {
    $conflict = $this->firstOverlap($t, $t + $needSec, $busy);
    …
}
```

فقط تداخل **پزشک** بررسی می‌شود. اتاق، دستگاه و اپراتور اصلاً وجود ندارند.

## دامنه

**هست:**
- `AvailabilityEngine` — ورودی: برنامهٔ نوبت + بازهٔ تاریخ + شعبه؛ خروجی: وقت‌های معتبر
  همراه با تخصیص منبع پیشنهادی
- تولید نقطه‌های شروع کاندید (پیش‌فرض هر ۱۵ دقیقه، قابل تنظیم per محیط)
- هرس زودهنگام کاندیدهای قطعاً ناممکن
- تخصیص منبع: تطبیق نیازمندی‌های هر بخش به منابع آزاد
- استراتژی انتخاب منبع: `least_gap` (پیش‌فرض) · `balanced` · `preserve_specialists` · `same_as_previous`
- کش روزانهٔ پنجرهٔ آزاد هر منبع
- endpoint عمومی و پنلی
- حالت `booking_mode = resource` روی `WeeklySchedule` و مسیر ارتقای داوطلبانه

**نیست:** ثبت اشغال و رزرو موقت (تسک ۰۷)، قوانین فاصلهٔ زمانی (تسک ۰۹ — قلاب اینجا گذاشته می‌شود).

## Endpoint ها

| متد | مسیر | توضیح |
|---|---|---|
| POST | `/api/v1/appointment-availability` | جستجوی وقت با برنامه (بدنه: سرویس، آیتم‌ها، شعبه، بازهٔ تاریخ) |
| GET | `/api/v1/appointment-availability/month` | روزهای دارای ظرفیت در یک ماه (سبک — فقط بولین per روز) |

## معیار پذیرش

- ✅ موفق: سناریوی مستند — سرویس لیزر با چهار بخش، شعبه با ۳ اتاق / ۲ اپراتور / ۳ دستگاه.
  جستجوی یک روز → لیست زمان‌های شروع، و برای هر زمان `assignment` شامل اتاق، اپراتور و
  دستگاه انتخابی.
- ✅ موفق (**آزادسازی ظرفیت — قلب کل پروژه**): بیمار الف نوبت ۱۰:۰۰-۱۱:۰۰ دارد
  (اپراتور فقط ۱۰:۰۰-۱۰:۰۵ و ۱۰:۳۵-۱۱:۰۰ درگیر است). جستجو برای بیمار ب باید زمانی
  در بازهٔ ۱۰:۰۵-۱۰:۳۵ پیدا کند اگر اتاق دومی آزاد باشد.
  **تست بدون این سناریو، تسک را تأیید نمی‌کند.**
- ✅ موفق: کارایی — جستجوی ۳۰ روزه با ۲۰ منبع و ۵۰۰ نوبت ثبت‌شده، **زیر ۵۰۰ms**.
  تست کارایی بخشی از تسک است، نه اختیاری.
- ✅ موفق: پزشکی که در حالت `slot` یا `service` است → این endpoint `422` با
  `ERR_WRONG_BOOKING_MODE` می‌دهد و مسیر قدیمی دست‌نخورده کار می‌کند.
- ❌ خطا: بازهٔ بزرگ‌تر از ۹۰ روز → `422`.
- ❌ خطا: شعبهٔ محیط دیگر → `404`.
- ❌ خطا: نیازمندی بدون منبع واجد شرایط → `422` با پیام انسانی (از تسک ۰۵).
- ⚠️ مرزی: منبع با `capacity=3` و دو نوبت هم‌زمان → سومی هنوز جا دارد، چهارمی نه.
- ⚠️ مرزی: `setup/cleanup` منبع → بازهٔ اشغال گسترده‌تر از بازهٔ بخش است و باید در
  بررسی تداخل لحاظ شود.
- ⚠️ مرزی: بخش با `occupancy=passive` → منبع را می‌گیرد ولی در گزارش بهره‌وری «کار» نیست.
- ⚠️ مرزی: نقطهٔ شروع در گذشته → حذف.
- ⚠️ مرزی: هیچ روزی ظرفیت ندارد → آرایهٔ خالی + `reason` قابل فهم، نه ۴۰۴.
- ⚠️ مرزی: منبع مشترک بین دو بخش غیرمجاور یک نوبت → **همان** منبع باید انتخاب شود
  (اپراتور بخش ۱ و بخش ۳ یکی است، نه دو نفر).

## خروجی

- `src/Appointment/Availability/`
- `docs/api/appointment-availability.md`
- تست کارایی با داده مصنوعی: `tests/Appointment/AvailabilityPerformanceTest.php`
- توسعهٔ `AppointmentSettingsPage.tsx` برای انتخاب حالت `resource` و استراتژی
