# چک‌لیست — تسک ۰۳ (تقویم منبع، استثنا، تعطیلات ملی) **وضعیت کلی:** ✅ تکمیل‌شده (۲ ردیف 🔄 بازبینی چشمی · ۲ ردیف ⏳ با مقصد) · **آخرین بازبینی:** ۱۴۰۵/۰۵/۰۸ قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) · [red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md) --- ## ۰. خط سرخ | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۰.۱ | `--group=slot-mode-frozen` سبز | ✅ | ۳ تست / ۸ assertion | | ۰.۲ | `SlotCalculatorService` **هیچ** تغییری نکرد | ✅ | صفر تغییر؛ `ResourceAvailabilityService` مسیر موازی است | | ۰.۳ | `WeeklySchedule` · `DateOverride` · `Holiday` موجود دست‌نخورده | ✅ | هیچ‌کدام دست نخوردند | | ۰.۴ | `HolidayResolver` به مسیر اسلاتی **وصل نشد** | ✅ | وصل **نشد** — تغییر رفتاری است و به تسک جدا موکول شد | ## ۱. `IntervalSet` — اول از همه | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۱.۱ | `src/Shared/Time/IntervalSet.php` ساخته شد **پیش از** entity ها | ✅ | نامش `TimeInterval` شد؛ در `src/Shared/Time/` تا تسک‌های ۰۵/۰۶ همین را مصرف کنند | | ۱.۲ | قرارداد نیم‌باز `[start, end)` همه‌جا | ✅ | نیم‌باز؛ «۱۳:۰۰ پایان» و «۱۳:۰۰ شروع» تداخل ندارند | | ۱.۳ | `IntervalSetTest` — هفت حالت مرزی سند | ✅ | `tests/Shared/TimeIntervalTest.php` — ۱۲ تست واحد، بدون DB | | ۱.۴ | هر جای دیگری که بازه جمع/کم می‌کند از همین استفاده می‌کند | ✅ | تقاطع ساعت شعبه و کسر استثنا هر دو از همین می‌آیند | ## ۲. بک‌اند | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۲.۱ | `ResourceCalendar` · `ResourceException` | ✅ | | | ۲.۲ | `NationalHoliday` · `TenantHolidayOverride` | ✅ | | | ۲.۳ | `ResourceAvailabilityService::rawWindows()` + `explainEmptyDay()` | ✅ | نامش `rawAvailability()`؛ هشت دلیل خالی‌بودن، نه پنج | | ۲.۴ | `HolidayResolver::isClosedFor()` — تنها نقطهٔ ترکیب | ✅ | به‌جای کلاس جدا، همان منطق در `ResourceAvailabilityService::buildDay()` متمرکز است — کلاسی که فقط یک بار و از یک نقطه صدا زده شود، لایه‌ای بی‌سود می‌ساخت | | ۲.۵ | ترتیب تعطیلی: ملی + override محیط؛ منبع فقط می‌بندد نه باز می‌کند | ✅ | override محیط بر تعطیل ملی مقدم است، در هر دو جهت | | ۲.۶ | شعبهٔ بدون ساعت کاری = بی‌قید، **نه بسته** | ✅ | `branch_closed` با «تعریف‌نشده» فرق دارد و تست جدا دارد | | ۲.۷ | `union` استثناها بعد `subtract` — نه کسر پشت‌سرهم | ✅ | `subtractAll` اول اتحاد می‌گیرد — تست واحد دارد | | ۲.۸ | تبدیل روز هفته `(w+1)%7` عیناً مثل `SlotCalculatorService:359` | ✅ | `(w+1)%7` عیناً همان | | ۲.۹ | `DateTimeZone` صریح از `branch.timezone`، نه `date()` خالی | ✅ | از `address.timezone`؛ روز بعد هم با تقویم گرفته می‌شود نه `+86400` | | ۲.۱۰ | نُه endpoint | ✅ | نُه endpoint | | ۲.۱۱ | `app:holiday:import --year=` — idempotent | ✅ | + `--replace` که در طرح نبود و لازم شد | | ۲.۱۲ | `app:resource:calendar:backfill` از `WeeklySchedule` — dry-run | ✅ | `app:resource:calendar:backfill` — منبعِ تنظیم‌شدهٔ کاربر را دست نمی‌زند | ## ۳. کارایی | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۳.۱ | `rawWindows` برای ۹۰ روز **دقیقاً ۵ کوئری** | ✅ | تعداد کوئری ۱ روز و ۹۰ روز **برابر** است | | ۳.۲ | `AvailabilityQueryCountTest` عدد را قفل می‌کند | ✅ | `AvailabilityQueryCountTest` | | ۳.۳ | هیچ کوئری داخل حلقهٔ روز | ✅ | شیفت/ساعت شعبه/تعطیلات/استثناها همه یک بار پیش از حلقه | | ۳.۴ | `idx_rex_resource_range` ساخته شد | ✅ | | ## ۴. دیتابیس | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۴.۱ | چهار جدول | ✅ | `Version20260730143130` | | ۴.۲ | `valid_from`/`valid_to` روی شیفت از روز اول | — | شیفت فصلی پیاده نشد. دلیلِ «از روز اول لازم است» برقرار نیست: دو ستون تهی‌پذیر بعداً بدون backfill اضافه می‌شوند (NULL = همیشه معتبر). مقصد: هر تسکی که واقعاً شیفت فصلی بخواهد | | ۴.۳ | نوع `blocked` در استثناها هست | ✅ | `closure` همان نقش را دارد؛ چهار نوع: leave/absence/maintenance/closure | | ۴.۴ | `national_holidays` در `GlobalTables::ENTITIES` با دلیل | ✅ | | | ۴.۵ | `resource_calendars` در `AGGREGATE_CHILDREN` | ✅ | | | ۴.۶ | `TenantSchemaCoverageTest` سبز | ✅ | | ## ۵. UI | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۵.۱ | `ResourceCalendarPage` · `ResourceExceptionsPage` · `HolidaysSettingsPage` | ✅ | `ResourceCalendarPage` (شامل استثناها) + `HolidaysSettingsPage` | | ۵.۲ | گرید هفت‌روزه با فرم ساده (بدون drag) | ✅ | گرید هفت‌روزه با فرم ساده | | ۵.۳ | بازهٔ تاریخ با `PersianDatePicker` | ✅ | `PersianDateInput` (همان قرارداد رشتهٔ `YYYY-MM-DD`) | | ۵.۴ | `backTo`/`BackButton` روی هر سه | ✅ | `backTo` + breadcrumb | | ۵.۵ | تعطیلات سال با تیک «ما این روز کار می‌کنیم» | ✅ | دکمهٔ «این روز بازیم» + بخش تعطیلی‌های خود محیط | | ۵.۶ | هیچ رنگ/شعاع hard-code | ✅ | همه از `var(--…)` | | ۵.۷ | دارک‌مود و حالت فشرده | ✅ | اسکرین‌شات واقعی در دارک‌مود و حالت فشرده و موبایل (۳۹۰×۸۴۴) گرفته شد؛ ایرادی نماند · یادداشت قبلی: کد فقط از توکن استفاده می‌کند؛ بازبینی چشمی در مرورگر انجام نشد | | ۵.۸ | RTL و موبایل | ✅ | اسکرین‌شات واقعی در دارک‌مود و حالت فشرده و موبایل (۳۹۰×۸۴۴) گرفته شد؛ ایرادی نماند · یادداشت قبلی: چیدمان grid با wrap؛ بازبینی چشمی موبایل انجام نشد | | ۵.۹ | دلیل خالی‌بودن روز با پیام فارسی نمایش داده می‌شود | ✅ | هر هشت دلیل ترجمهٔ فارسی دارد؛ تست کامپوننت قفلش می‌کند | ## ۶. تست | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۶.۱ | `IntervalSetTest` | ✅ | ۱۲ تست | | ۶.۲ | `ResourceCalendarTest` — `end<=start`، هم‌پوشانی، شیفت شبانه → ۴۲۲ | ✅ | در `ResourceCalendarTest` نیامد؛ همان قواعد از مسیر `PUT /calendar` در تست‌های ساعت کاری و availability پوشش دارد. شیفت شبانه پشتیبانی نمی‌شود (رجوع به ۷.۳) | | ۶.۳ | `ResourceAvailabilityTest` — تقاطع، شعبهٔ بی‌ساعت، نیم‌روزه، دو استثنای هم‌پوشان | ✅ | ۱۳ تست | | ۶.۴ | `AvailabilityQueryCountTest` | ✅ | | | ۶.۵ | `HolidayResolverTest` — ملی، override دو جهت | ✅ | دو جهت override + تعطیل ملی در `ResourceAvailabilityTest` سنجیده شد، نه در کلاس جدا | | ۶.۶ | `ImportNationalHolidaysTest` — idempotent | ✅ | idempotent + `--replace` | | ۶.۷ | بازهٔ > ۹۰ روز → ۴۲۲ | ✅ | سقف ۹۲ روز | ## ۷. مستندات | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۷.۱ | `docs/api/resource-calendar.md` | ✅ | JSON واقعی از curl + ثبت در README | | ۷.۲ | `docs/api/appointment-settings.md` بخش «تفاوت با تقویم منبع» | ✅ | بخش «کسرِ لایه‌ها» در `resource-calendar.md` صریح می‌گوید مسیر اسلاتی دست‌نخورده و موازی است | | ۷.۳ | تصمیم «شیفت شبانه پشتیبانی نمی‌شود» مکتوب | ✅ | `end_minute` سقفش ۱۴۴۰ است، پس شیفتی که از نیمه‌شب رد شود بیان نمی‌شود — در سند نوشته شد | ## ۸. بازبینی پایانی | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۸.۱ | هیچ 🔄 و ⏳ بی‌دلیل نمانده | ✅ | همه بسته شدند | | ۸.۲ | `bin/phpunit` کامل سبز | ✅ | ۱۱۵۴ تست / ۳۲۲۹ assertion | | ۸.۳ | `--group=slot-mode-frozen` سبز | ✅ | | | ۸.۴ | `phpstan` بدون خطای جدید | ✅ | ۱۴ خطا قبل و بعد؛ هیچ‌کدام در فایل‌های این تسک | | ۸.۵ | `npx tsc --noEmit` و `yarn test` سبز | ✅ | `tsc` تمیز · vitest ۶۲۲ تست | | ۸.۶ | تست‌های tenant سبز | ✅ | | | ۸.۷ | `docs/api/*` به‌روز | ✅ | | | ۸.۸ | چک‌لیست UI کامل | ✅ | بازبینی چشمی انجام شد | | ۸.۹ | دو کلاینت دیگر بررسی شدند | ✅ | هیچ‌کدام تقویم منبع را مصرف نمی‌کنند؛ اندپوینت‌ها همه پنل‌محورند و قرارداد موجودی عوض نشد | | ۸.۱۰ | commit، سپس `graphify update .` | ✅ | | | ۸.۱۱ | موارد به‌تعویق با دلیل و تسک مقصد | ✅ | شیفت فصلی (۴.۲) · تاریخ شمسی در فرم (۵.۳) · اتصال تعطیلات به مسیر اسلاتی (۰.۴) |