Files
clinicpro/docs/new_feture/taskes/task-01-branch-room/checklist.md
T
hamedandClaude Opus 5 d813843fcd feat(branch): admin UI for branch working hours and rooms, plus real API docs
Three pages, all on the existing design system: BranchesPage lists the current
environment's booking locations with their working-hours and active-room counts,
and two subpages edit the week and the rooms. The list page deliberately does not
create or rename a branch — clinic and doctor detail pages already do that, and
duplicating it would give one physical place two edit surfaces. Route permission
reuses `appointment_settings` rather than inventing a new one.

Two real bugs fell out of exercising this end to end:

`days` was serialising as a JSON *array*, not an object keyed "0".."6" — keys 0..6
are sequential so json_encode collapses them to a list. The client reads days["0"]
either way, so nothing looked broken, but the response shape was unstable: one
missing day would flip the same field to an object. The controller now casts to
stdClass and WorkingHoursTest::testDaysIsAJsonObjectNotAnArray pins it. Found by
curling the endpoint for the docs, not by any test.

`<input type="time">` caps at 23:59, so it can neither display nor produce the
legal end value 1440. An all-day range would have vanished from the form and been
corrupted by the first save. Ranges now carry an explicit end-of-day flag, with a
round-trip test proving 1440 survives.

docs/api/branch.md documents all eight endpoints with responses captured from real
curl runs against ddev, including the 422 and 404 bodies. doctor.md records that
active/timezone now appear on all nine existing address endpoints (additive), and
tenancy.md gains the two lessons this task taught: an aggregate child whose root is
itself declared global inherits no environment and needs a real pair, and
TenantFilter is not a substitute for an explicit ownership check because hard
isolation only applies to a *chosen* context.

Verified: phpunit 1067 tests / 2974 assertions green; slot-mode frozen contract
green; phpstan 14 errors before and after, none in touched files; tsc clean;
vitest 87 files / 612 tests green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 16:48:49 +03:30

9.4 KiB
Raw Blame History

چک‌لیست — تسک ۰۱ (شعبه و اتاق)

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

قواعد: _shared/definition-of-done.md · red-lines.md · ui-conventions.md · branch-is-doctor-address.md


۰. خط سرخ

# مورد وضعیت یادداشت
۰.۱ --group=slot-mode-frozen سبز --group=slot-mode-frozen — ۳ تست / ۸ assertion سبز
۰ SlotCalculatorService دست‌نخورده صفر تغییر در فایل
۰ location_id در JSON برنامهٔ هفتگی دست‌نخورده شعبه = همان doctor_addresses.id؛ JSON دست‌نخورده
۰ DoctorAddress هیچ ستونی حذف/تغییر نداد فقط دو ستون NOT NULL DEFAULT
۰.۵ active=false هیچ اثری بر محاسبهٔ اسلات ندارد فقط ذخیره می‌شود؛ در branch.md نوشته شد

۱. طرح

# مورد وضعیت یادداشت
۱.۱ جدول branches ساخته نشد — دلیل مکتوب _shared/branch-is-doctor-address.md
۱ task.md · architecture.md · database.md · implementation_notes.md تصحیح شد
۱ ارجاع‌های branch_id در تسک‌های ۰۲/۰۴/۰۷/۰۸/۰۹/۱۰/۱۳ با سند حاکم پوشش داده شد ۲۴ ارجاع — یک سند واحد در _shared به‌جای ویرایش ۲۴ نقطه

۲. بک‌اند

# مورد وضعیت یادداشت
۲.۱ DoctorAddress += active + timezone
۲.۲ timezone با DateTimeZone::listIdentifiers() اعتبارسنجی می‌شود، نه regex DateTimeZone::listIdentifiers() در setter
۲.۳ BranchWorkingHours entity (فرزند aggregate) ولی جفت tenant گرفت نه فرزند aggregate — ردیف ۳.۵
۲.۴ Room entity با TenantOwnedTrait و جفت مشتق از آدرس در سازنده جفت در سازنده از tenantEntityType/Id() آدرس
۲.۵ BranchResolver — تک‌نقطهٔ uuid آدرس → محیط جاری، ۴۰۴ نه ۴۰۳ ۴۰۴ می‌دهد؛ منشی هم پوشش دارد
۲.۶ WorkingHoursService — اعتبارسنجی کامل قبل از حذف (اتمی) WorkingHoursTest::testInvalidLaterDayLeavesTheStoredWeekUntouched
۲.۷ ساعت با start_minute/end_minute عددی، نه رشتهٔ "09:00"
۲.۸ sequence سمت سرور تخصیص می‌یابد، نه کلاینت بعد از usort تخصیص می‌یابد
۲.۹ RoomService با گارد حذف قابل توسعه (آرایهٔ تزریقی، نه زنجیرهٔ if) RoomDeletionGuardInterface + AutowireIterator + _instanceof
۲.۱۰ شش endpoint ساخته شد هشت شد نه شش: GET /branches و PATCH /branch/{uuid} هم لازم بودند
۲.۱۱ پزشک مستقل هم شعبه دارد type=personal از قبل کار می‌کرد
۲.۱۲ کنترلر نازک · BaseController · success/paginated/error

۳. دیتابیس

# مورد وضعیت یادداشت
۳.۱ branch_working_hours · rooms Version20260730125038
۳.۲ entity_type, entity_id ستون اول ایندکس rooms idx_rooms_tenant و idx_bwh_tenant
۳.۳ timezone روی آدرس از روز اول افزودن بعدی = backfill زمان‌دار
۳.۴ rooms.capacity — ظرفیت هم‌زمان حداقل ۱ در setter و سرویس
۳.۵ branch_working_hours در GlobalTables::AGGREGATE_CHILDREN با ریشهٔ صریح رد شد — ریشه سراسری است، پس جفت واقعی گرفت
۳.۶ ستون‌ها و جدول‌ها روی db_test هم ساخته شد دستی، چون db_test تاریخچهٔ جدا دارد
۳.۷ TenantSchemaCoverageTest سبز
۳.۸ TenantLookupInventoryTest سبز — repository جدید ثبت شد سبز بدون نیاز به ثبت تازه

۴. UI

# مورد وضعیت یادداشت
۴.۱ BranchesPage · BranchWorkingHoursPage · BranchRoomsPage
۴.۲ DataTable با skeleton و empty state فارسی
۴.۳ PageHeader با backTo روی زیرصفحه‌ها backTo + breadcrumb روی هر دو زیرصفحه
۴.۴ هر select با SearchableSelect — هیچ <select> بومی SearchableSelect برای منطقهٔ زمانی؛ هیچ <select> بومی
۴.۵ وضعیت لیست در URL با useUrlState جستجو و فیلتر وضعیت در URL
۴.۶ هیچ رنگ/شعاع/سایهٔ hard-code — همه از توکن همه از var(--…)
۴.۷ دارک‌مود و حالت فشرده بررسی شد 🔄 کد فقط از توکن استفاده می‌کند؛ بازبینی چشمی در مرورگر انجام نشد
۴.۸ RTL و موبایل بررسی شد 🔄 چیدمان flex/grid با wrap؛ بازبینی چشمی موبایل انجام نشد
۴.۹ هشدار UI: «هیچ شعبهٔ فعالی باقی نمی‌ماند» confirm + title روی تنها شعبهٔ فعال
۴.۱۰ مسیرها در App.tsx + ورودی در SettingsMenuPage سه مسیر + آیتم منو با MapPinIcon
۴.۱۱ مجوز موجود appointment_settings استفاده شد، نه مجوز تازه appointment_settings بازاستفاده شد

۵. تست

# مورد وضعیت یادداشت
۵.۱ WorkingHoursTest — هفت روز، end<=start، هم‌پوشانی، 0..1440، آرایهٔ خالی ۱۴ تست
۵ اتمی بودن: بازهٔ نامعتبر در روز ششم → ۴۲۲ و شش روز قبلی دست‌نخورده
۵ RoomCrudTest — جفت tenant مشتق، capacity=0 → ۴۲۲ ۱۲ تست
۵ آدرس/اتاق محیط دیگر → ۴۰۴ (نه ۴۰۳) هم شعبه و هم اتاق
۵.۵ BranchAddressFieldsTest — پیش‌فرض‌ها، timezone نامعتبر → ۴۲۲ ۱۰ تست شامل شمارش کوئری
۵ phpstan analyse src/Branch بدون خطا صفر خطا در src/Branch

۶. مستندات

# مورد وضعیت یادداشت
۶.۱ docs/api/branch.md + ثبت در docs/api/README.md JSON واقعی از curl روی ddev + ثبت در README
۶.۲ «شعبهٔ بدون ساعت کاری = تعریف‌نشده، نه همیشه‌باز» نوشته شد تسک ۰۳ رویش حساب می‌کند
۶.۳ «active در این فاز بی‌اثر بر اسلات» نوشته شد
۶.۴ docs/api/doctor.md — دو فیلد جدید در پاسخ ۹ endpoint آدرس doctor.md — تغییر additive روی ۹ اندپوینت
۶.۵ docs/architecture/tenancy.md جدول طبقه‌بندی به‌روز شد درسِ «ریشهٔ سراسری، فرزندِ محیط‌دار» + محدودیت chosen

۷. بازبینی پایانی

# مورد وضعیت یادداشت
۷.۱ هیچ 🔄 و بی‌دلیل نمانده ⚠️ دو 🔄 مانده، هر دو بازبینی چشمی UI با دلیل مکتوب
۷ bin/phpunit کامل سبز ۱۰۶۷ تست / ۲۹۷۴ assertion — صفر خطا
۷ --group=slot-mode-frozen سبز
۷ phpstan بدون خطای جدید (مقایسه با کامیت پیش از تسک) ۱۴ خطا قبل و بعد — هیچ‌کدام در فایل‌های این تسک
۷.۵ npx tsc --noEmit و yarn test سبز tsc صفر خطا · vitest ۸۷ فایل / ۶۱۲ تست
۷ TenantSchemaCoverageTest + TenantLookupInventoryTest سبز
۷.۷ docs/api/* به‌روز
۷ چک‌لیست UI کامل ⚠️ دو ردیف ۴.۷/۴.۸ بازبینی چشمی می‌خواهند
۷ nobat724_front و clinic-pro-tauri بررسی شدند هر دو آدرس را مصرف می‌کنند (app/doctor/[slug]/page.js و OfficeAddressesContent/workingDays/TurnsTabContent) ولی فیلدها را با نام می‌خوانند و برای نوشتن payload صریح می‌سازند (transformData) — دو فیلد additive نمی‌شکندشان. هیچ اندپوینت جدیدی مصرف‌کننده ندارد
۷.۱۰ commit، سپس graphify update .، سپس commit جدا
۷.۱۱ موارد به‌تعویق با دلیل و تسک مقصد گاردِ حذف اتاق ← تسک ۰۲/۰۷ · گاردِ حذف آدرس ← تسک ۰۷ · اعمال active ← تسک ۰۳