# تسک ۰۳ — تقویم منبع، استثنا، تعطیلات ملی **فاز:** ۱ (هسته) · **وابستگی:** ۰۱، ۰۲ · **زمان:** ۱۲-۱۴ ساعت --- ## هدف مستند بند ۹ ساعت آزاد را از کسر هفت لایه می‌سازد. امروز چهار لایه داریم و همه روی **پزشک** سوارند. این تسک لایه‌های غایب را اضافه می‌کند و آن‌ها را روی **منبع** می‌نشاند: ``` ساعت کاری شعبه ← تسک ۰۱ ساخت، اینجا وارد محاسبه می‌شود – شیفت منبع ← این تسک – تعطیلات رسمی کشور ← این تسک – مرخصی و غیبت ← این تسک – سرویس دوره‌ای دستگاه ← این تسک (همان جدول استثنا با نوع دیگر) – نوبت‌های ثبت‌شده ← موجود (تسک ۰۷ به 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` برای بارگذاری تعطیلات رسمی