# معماری — تسک ۰۳ ## ساختار فایل ``` src/Resource/ ├── Entity/ │ ├── ResourceCalendar.php # شیفت تکرارشوندهٔ هفتگی │ └── ResourceException.php # مرخصی/غیبت/سرویس دستگاه/تعطیلی موردی ├── Controller/ │ ├── ResourceCalendarController.php │ └── ResourceExceptionController.php ├── Service/ │ ├── ResourceCalendarService.php │ └── ResourceAvailabilityService.php ← قلب این تسک └── Repository/… src/Holiday/ ├── Entity/ │ ├── NationalHoliday.php │ └── TenantHolidayOverride.php ├── Controller/HolidayController.php ├── Service/HolidayResolver.php └── Command/ImportNationalHolidaysCommand.php ``` ## `ResourceAvailabilityService` — قرارداد ```php final class ResourceAvailabilityService { /** * بازه‌های آزادِ خام یک منبع (بدون در نظر گرفتن نوبت‌ها). * خروجی: بازه‌های مرتب و ادغام‌شده، بر حسب Unix timestamp. * * @return array */ public function rawWindows(ClinicResource $resource, int $from, int $to): array; /** * چرا این روز خالی است. null یعنی خالی نیست. * همان قرارداد SlotCalculatorService::explainEmptyDay — پنل به دلیل نیاز دارد. */ public function explainEmptyDay(ClinicResource $resource, int $dayStart): ?string; public const EMPTY_NO_CALENDAR = 'no_calendar'; public const EMPTY_NATIONAL_HOLIDAY = 'national_holiday'; public const EMPTY_RESOURCE_EXCEPTION = 'resource_exception'; public const EMPTY_OUTSIDE_BRANCH_HOURS = 'outside_branch_hours'; public const EMPTY_DAY_OFF = 'day_off'; } ``` ## الگوریتم `rawWindows` ``` ورودی: منبع، [from, to) ۱. یک بار برای کل بازه واکشی کن (نه per-day): - branch_working_hours شعبهٔ منبع (۱ کوئری) - resource_calendars منبع (۱ کوئری) - resource_exceptions متداخل با بازه (۱ کوئری) - national_holidays متداخل با بازه (۱ کوئری) - tenant_holiday_overrides محیط (۱ کوئری) ۲. برای هر روز از from تا to: الف) اگر تعطیل رسمی است و override با is_working=true ندارد → روز را رد کن ب) بازه‌های شیفت منبع آن روزِ هفته را بگیر ج) اگر شعبه ساعت کاری تعریف‌شده دارد → تقاطع بگیر اگر ندارد → بازهٔ منبع دست‌نخورده می‌ماند د) استثناهای متداخل را کسر کن (اتحاد استثناها، بعد تفاضل) ه) بازه‌های حاصل را به لیست اضافه کن ۳. ادغام بازه‌های مجاور و مرتب‌سازی ``` **پنج کوئری ثابت برای هر بازه، نه رشد خطی با تعداد روز.** این دقیقاً همان کاری است که `SlotCalculatorService::findNextAvailableStart()` امروز برای پزشک می‌کند و باید حفظ شود؛ تسک ۰۶ روی همین حساب می‌کند که بتواند زیر نیم ثانیه بماند. ## عملیات بازه — یک جای واحد تقاطع، اتحاد، تفاضل و ادغامِ بازه‌ها در سه تسک بعدی هم لازم است. یک کلاس بدون وابستگی: ```php // src/Shared/Time/IntervalSet.php final class IntervalSet { /** @param array $intervals */ public static function normalize(array $intervals): array; // مرتب + ادغام مجاور public static function intersect(array $a, array $b): array; public static function subtract(array $from, array $minus): array; public static function union(array $a, array $b): array; public static function totalSeconds(array $intervals): int; } ``` خالص و بدون I/O → تست واحد سریع و بدون دیتابیس. هر جای دیگری که بازه جمع/کم می‌کند باید از این استفاده کند، وگرنه سه پیاده‌سازی با سه باگ مرزی متفاوت خواهیم داشت. ## تعطیلات رسمی ```php class NationalHoliday // سراسری — GlobalTables::ENTITIES { private int $date; // نیمه‌شب روز، Unix private string $title; // «عید فطر» private bool $isOfficial; // تعطیل رسمی یا مناسبت غیرتعطیل } class TenantHolidayOverride // per محیط { use TenantOwnedTrait; private int $date; private bool $isWorking; // true = این تعطیل رسمی برای ما کاری است private ?string $note; } ``` `HolidayResolver::isClosedFor(EntityContext $ctx, int $dayStart): bool` تنها نقطهٔ ترکیب این دو است. `Holiday` موجود (per پزشک) دست‌نخورده می‌ماند و در حالت `slot`/`service` همچنان مرجع است؛ در حالت `resource` هر دو منبع اعمال می‌شوند (اتحاد). ## import تعطیلات ```bash ddev exec php bin/console app:holiday:import --year=1405 --file=var/holidays-1405.json ``` فایل JSON با تاریخ شمسی؛ تبدیل با `jalaali-js` معادل PHP در `src/Shared/Time/JalaliDate.php` (اگر نبود، بساز). عمداً از سرویس آنلاین نمی‌خوانیم: تعطیلات ایران سالانه با مصوبه تغییر می‌کنند و وابستگی به یک API خارجی یعنی جستجوی وقت به آن گره می‌خورد. ## پنل ادمین - `ResourceCalendarPage.tsx` — گرید هفت‌روزه، هر روز چند بازه، درگ ندارد (فرم ساده) - `ResourceExceptionsPage.tsx` — لیست + `PersianDatePicker` برای بازه - `HolidaysSettingsPage.tsx` — تعطیلات رسمی سال با تیک «ما این روز کار می‌کنیم» - هر سه زیرصفحه‌اند → `backTo` اجباری