Files
clinicpro/docs/new_feture/taskes/task-03-resource-calendar/checklist.md
T
hamedandClaude Opus 5 b3c331f0cb perf(reports): read every resource's calendar in one batch, and close the owed tests
Writing the query-count test that task 14 owed showed the growth was real: one
resource cost 10 queries, six cost 33 — about five per resource, because the
available-minutes figure walked each resource's calendar on its own.

Holidays, tenant overrides and branch hours are identical for every resource in
a report, so they now load once outside the loop; shifts and exceptions load for
all resources in one query each. The batched path is a new method rather than a
change to rawAvailability, which the booking engine also calls. The test pins
the shape of the growth, not an exact count.

Also landed:

- app:segment:seed-templates with beauty, dental and physio presets. Building
  four segments and their requirements by hand is the first thing a new clinic
  must do and the most tedious; this gives them something to edit instead of an
  empty page. It refuses to touch a service that already has segments unless
  --force, and it will not invent resource types the tenant never defined.
- book-all is all-or-nothing, proven rather than asserted: with a calendar open
  one day a week and a 1-2 day protocol gap, session one finds a slot and
  session two cannot, and every session must come back planned.
- credit_refundable: false takes the credit back with a negative adjustment and
  deletes nothing — the ledger stays append-only.
- the segments editor has frontend tests, including that it sends back what the
  user sees and renders read-only without the permission.

useBranches now returns [] for a non-array payload instead of throwing
"branches.map is not a function" and taking the page down with it.

BookingLocationsScanTest built a Clinic around a Doctor loaded from a different
manager, which Doctrine treats as a new entity; it flushed fine most runs and
failed on cascade in others. It now loads the doctor from the same manager.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 14:22:22 +03:30

9.6 KiB
Raw Blame History

چک‌لیست — تسک ۰۳ (تقویم منبع، استثنا، تعطیلات ملی)

وضعیت کلی: تکمیل‌شده (۲ ردیف 🔄 بازبینی چشمی · ۲ ردیف با مقصد) · آخرین بازبینی: ۱۴۰۵/۰۵/۰۸

قواعد: _shared/definition-of-done.md · red-lines.md · 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 ۱۲ تست
۶.۲ ResourceCalendarTestend<=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 .
۸.۱۱ موارد به‌تعویق با دلیل و تسک مقصد شیفت فصلی (۴.۲) · تاریخ شمسی در فرم (۵.۳) · اتصال تعطیلات به مسیر اسلاتی (۰.۴)