# تسک ۰۳ — تقویم منبع، استثنا، تعطیلات ملی

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

---

## هدف

مستند بند ۹ ساعت آزاد را از کسر هفت لایه می‌سازد. امروز چهار لایه داریم و همه روی
**پزشک** سوارند. این تسک لایه‌های غایب را اضافه می‌کند و آن‌ها را روی **منبع** می‌نشاند:

```
ساعت کاری شعبه           ← تسک ۰۱ ساخت، اینجا وارد محاسبه می‌شود
– شیفت منبع              ← این تسک
– تعطیلات رسمی کشور       ← این تسک
– مرخصی و غیبت           ← این تسک
– سرویس دوره‌ای دستگاه     ← این تسک (همان جدول استثنا با نوع دیگر)
– نوبت‌های ثبت‌شده         ← موجود (تسک ۰۷ به resource_occupancy منتقل می‌کند)
– رزروهای موقت           ← موجود
– آماده‌سازی و تمیزکاری    ← تسک ۰۲ ستون‌ها را ساخت، تسک ۰۶ اعمال می‌کند
```

## وضعیت فعلی

- `WeeklySchedule` — JSON هفتگی per `(doctor, clinic)` با `sessions[]`
- `DateOverride` — تنظیم یک روز خاص per `(doctor, clinic)`
- `Holiday` — بازهٔ تعطیلی per `(doctor, clinic)`، دستی
- جدول تعطیلات رسمی کشور **وجود ندارد**؛ هر پزشک باید دستی ثبت کند

## دامنه

**هست:** `ResourceCalendar` (شیفت تکرارشوندهٔ منبع)، `ResourceException` (مرخصی، غیبت،
سرویس دوره‌ای، تعطیلی موردی)، `NationalHoliday` (جدول کشوری با import شمسی) و
`TenantHolidayOverride` (کلینیکی که پنجشنبه کار می‌کند)، و یک سرویس واحد
`ResourceAvailabilityService` که «ساعت آزاد یک منبع در یک بازه» را می‌دهد.

**نیست:** تقاطع چند منبع و برنامهٔ چندبخشی (تسک ۰۶)، ثبت اشغال (تسک ۰۷).

## Endpoint ها

| متد | مسیر | توضیح |
|---|---|---|
| GET | `/api/v1/resource/{uuid}/calendar` | شیفت‌های هفتگی منبع |
| PUT | `/api/v1/resource/{uuid}/calendar` | جایگزینی کامل شیفت‌ها |
| GET | `/api/v1/resource/{uuid}/exceptions` | مرخصی/سرویس، با فیلتر بازه |
| POST | `/api/v1/resource/{uuid}/exception` | ثبت استثنا |
| PATCH/DELETE | `/api/v1/resource-exception/{uuid}` | |
| GET | `/api/v1/resource/{uuid}/availability?from&to` | ساعت آزاد خام (بدون نوبت) — برای پنل |
| GET | `/api/v1/national-holidays?year=1405` | تعطیلات رسمی سال |
| POST | `/api/v1/holiday-overrides` | تغییر تعطیلی رسمی توسط محیط |
| DELETE | `/api/v1/holiday-override/{uuid}` | |

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

- ✅ موفق: منبع «اپراتور مریم» شیفت شنبه تا چهارشنبه ۹:۰۰-۱۷:۰۰ می‌گیرد →
  `GET /resource/{uuid}/availability?from=…&to=…` پنج بازه برمی‌گرداند و جمعه خالی است.
- ✅ موفق: مرخصی سه‌شنبه ثبت می‌شود → همان endpoint سه‌شنبه را خالی می‌دهد.
- ✅ موفق: تعطیل رسمی ۱۳ فروردین در `national_holidays` هست → آن روز برای همهٔ منابع
  همهٔ محیط‌ها خالی است، بدون هیچ ثبت دستی.
- ✅ موفق: کلینیکی که پنجشنبه کار می‌کند یک `holiday_override` با `is_working=true` ثبت
  می‌کند → فقط منابع همان محیط پنجشنبه باز می‌شوند.
- ❌ خطا: شیفت با `end_minute <= start_minute` → `422`.
- ❌ خطا: استثنا با `end < start` → `422`؛ استثنا روی منبع محیط دیگر → `404`.
- ⚠️ مرزی: شیفت منبع بیرون از ساعت کاری شعبه → **تقاطع** گرفته می‌شود، نه رد. اگر تقاطع
  خالی شد، پاسخ `availability` آن روز را خالی می‌دهد و دلیل `outside_branch_hours` را
  برمی‌گرداند.
- ⚠️ مرزی: شعبهٔ بدون ساعت کاری → شیفت منبع بی‌قید اعمال می‌شود (سازگاری با داده‌های موجود).
- ⚠️ مرزی: استثنای نیم‌روزه (۱۴:۰۰ تا ۱۸:۰۰) → فقط همان بازه کسر می‌شود، نه کل روز.
- ⚠️ مرزی: دو استثنای هم‌پوشان → مجاز، اتحاد گرفته می‌شود.

## خروجی

- `src/Resource/Calendar/` + `src/Resource/Service/ResourceAvailabilityService.php`
- صفحات: `ResourceCalendarPage.tsx`, `ResourceExceptionsPage.tsx`, `HolidaysSettingsPage.tsx`
- `docs/api/resource-calendar.md`
- دستور `app:holiday:import --year=1405` برای بارگذاری تعطیلات رسمی
