Task 01 planned a new `branches` table with `doctor_addresses.branch_id` bridging to it. That plan was wrong: the branch already exists and is called `DoctorAddress`. It carries name, address, telephone, coordinates, city/province FKs and an owner (`forDoctor` / `forClinic` + `type`), and the whole system already consumes it with exactly that meaning — `WeeklySchedule.sessions[].location_id` points at `doctor_addresses.id`, `appointment-booking-locations` calls each row a booking location, and nine CRUD endpoints plus four admin pages manage them. A parallel table would mean two sources of truth for one physical place and a branch that `location_id` never references. So no `branches` table and no duplicate branch CRUD. Only the three genuinely missing pieces: - `doctor_addresses.active` / `.timezone`, both NOT NULL with a default so existing rows need no backfill and no current behaviour changes. `active` is stored only — applying it to slot calculation is task 03, since touching `SlotCalculatorService` is off limits in this phase. - `branch_working_hours`, keyed to `doctor_addresses.id`. Minutes from midnight rather than "09:00" strings so range intersection stays arithmetic. PUT replaces all seven days; validation of the whole week runs before any DELETE, so an invalid sixth day cannot wipe the five valid ones and then answer 422. - `rooms`, with `capacity` as concurrency (a three-bed injection room is one resource with capacity 3, not three resources) and a deletion-guard iterator so tasks 02 and 07 can add reasons without editing RoomService. `BranchWorkingHours` first registered as an aggregate child of `DoctorAddress`; TenantSchemaCoverageTest rejected it correctly, because that root is itself declared global. It now carries a real tenant pair instead, derived in the constructor from the address's `type` — a total mapping, and the address is only ever listed in its own context, so nothing is hidden wrongly. RoomController checks ownership explicitly rather than trusting TenantFilter: hard isolation only applies to a *chosen* context, so a doctor who had not selected one could PATCH another clinic's room. Caught by RoomCrudTest::testForeignRoomIsNotFound, which failed with 200 before the fix. 35 tests, 97 assertions. Slot-mode frozen contract still green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
7.0 KiB
7.0 KiB
چکلیست — تسک ۰۱ (شعبه و اتاق)
وضعیت کلی: 🔄 در حال انجام · آخرین بازبینی: —
قواعد: _shared/definition-of-done.md · red-lines.md · ui-conventions.md · branch-is-doctor-address.md
۰. خط سرخ
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | --group=slot-mode-frozen سبز |
⏳ | |
| ۰.۲ | SlotCalculatorService دستنخورده |
⏳ | این تسک به آن کاری ندارد |
| ۰.۳ | location_id در JSON برنامهٔ هفتگی دستنخورده |
⏳ | شعبه = همان doctor_addresses.id |
| ۰.۴ | DoctorAddress هیچ ستونی حذف/تغییر نداد |
⏳ | فقط active و timezone با DEFAULT |
| ۰.۵ | active=false هیچ اثری بر محاسبهٔ اسلات ندارد |
⏳ | اعمالش تسک ۰۳ است |
۱. طرح
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | ⛔ جدول 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 |
⏳ | |
| ۲.۳ | BranchWorkingHours entity (فرزند aggregate) |
⏳ | |
| ۲.۴ | Room entity با TenantOwnedTrait و جفت مشتق از آدرس در سازنده |
⏳ | نه از بدنهٔ request |
| ۲.۵ | BranchResolver — تکنقطهٔ uuid آدرس → محیط جاری، ۴۰۴ نه ۴۰۳ |
⏳ | TenantFilter روی doctor_addresses کار نمیکند |
| ۲.۶ | WorkingHoursService — اعتبارسنجی کامل قبل از حذف (اتمی) |
⏳ | |
| ۲.۷ | ساعت با start_minute/end_minute عددی، نه رشتهٔ "09:00" |
⏳ | |
| ۲.۸ | sequence سمت سرور تخصیص مییابد، نه کلاینت |
⏳ | |
| ۲.۹ | RoomService با گارد حذف قابل توسعه (آرایهٔ تزریقی، نه زنجیرهٔ if) |
⏳ | تسک ۰۲ و ۰۷ گارد اضافه میکنند |
| ۲.۱۰ | شش endpoint ساخته شد | ⏳ | صفر endpoint CRUD شعبه — موجود است |
| ۲.۱۱ | پزشک مستقل هم شعبه دارد | ⏳ | type='personal' از قبل کار میکند |
| ۲.۱۲ | کنترلر نازک · BaseController · success/paginated/error |
⏳ |
۳. دیتابیس
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | branch_working_hours · rooms |
⏳ | |
| ۳.۲ | entity_type, entity_id ستون اول ایندکس rooms |
⏳ | |
| ۳.۳ | timezone روی آدرس از روز اول |
⏳ | افزودن بعدی = backfill زماندار |
| ۳.۴ | rooms.capacity — ظرفیت همزمان |
⏳ | اتاق سهتخته = یک ردیف با ۳ |
| ۳.۵ | branch_working_hours در GlobalTables::AGGREGATE_CHILDREN با ریشهٔ صریح |
⏳ | |
| ۳.۶ | ستونها و جدولها روی db_test هم ساخته شد |
⏳ | تاریخچهٔ migration جدا |
| ۳.۷ | TenantSchemaCoverageTest سبز |
⏳ | |
| ۳.۸ | TenantLookupInventoryTest سبز — repository جدید ثبت شد |
⏳ |
۴. UI
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | BranchesPage · BranchWorkingHoursPage · BranchRoomsPage |
⏳ | |
| ۴.۲ | DataTable با skeleton و empty state فارسی |
⏳ | |
| ۴.۳ | PageHeader با backTo روی زیرصفحهها |
⏳ | |
| ۴.۴ | هر select با SearchableSelect — هیچ <select> بومی |
⏳ | |
| ۴.۵ | وضعیت لیست در URL با useUrlState |
⏳ | |
| ۴.۶ | هیچ رنگ/شعاع/سایهٔ hard-code — همه از توکن | ⏳ | |
| ۴.۷ | دارکمود و حالت فشرده بررسی شد | ⏳ | |
| ۴.۸ | RTL و موبایل بررسی شد | ⏳ | |
| ۴.۹ | هشدار UI: «هیچ شعبهٔ فعالی باقی نمیماند» | ⏳ | |
| ۴.۱۰ | مسیرها در App.tsx + ورودی در SettingsMenuPage |
⏳ | |
| ۴.۱۱ | مجوز موجود appointment_settings استفاده شد، نه مجوز تازه |
⏳ |
۵. تست
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | WorkingHoursTest — هفت روز، end<=start، همپوشانی، 0..1440، آرایهٔ خالی |
⏳ | |
| ۵.۲ | اتمی بودن: بازهٔ نامعتبر در روز ششم → ۴۲۲ و شش روز قبلی دستنخورده | ⏳ | |
| ۵.۳ | RoomCrudTest — جفت tenant مشتق، capacity=0 → ۴۲۲ |
⏳ | |
| ۵.۴ | آدرس/اتاق محیط دیگر → ۴۰۴ (نه ۴۰۳) | ⏳ | |
| ۵.۵ | BranchAddressFieldsTest — پیشفرضها، timezone نامعتبر → ۴۲۲ |
⏳ | |
| ۵.۶ | phpstan analyse src/Branch بدون خطا |
⏳ |
۶. مستندات
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | docs/api/branch.md + ثبت در docs/api/README.md |
⏳ | JSON واقعی از curl |
| ۶.۲ | «شعبهٔ بدون ساعت کاری = تعریفنشده، نه همیشهباز» نوشته شد | ⏳ | تسک ۰۳ رویش حساب میکند |
| ۶.۳ | «active در این فاز بیاثر بر اسلات» نوشته شد |
⏳ | |
| ۶.۴ | docs/api/doctor.md — دو فیلد جدید در پاسخ ۹ endpoint آدرس |
⏳ | تغییر قرارداد است |
| ۶.۵ | docs/architecture/tenancy.md جدول طبقهبندی بهروز شد |
⏳ |
۷. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۷.۱ | هیچ 🔄 و ⏳ بیدلیل نمانده | ⏳ | |
| ۷.۲ | bin/phpunit کامل سبز |
⏳ | |
| ۷.۳ | --group=slot-mode-frozen سبز |
⏳ | |
| ۷.۴ | phpstan بدون خطای جدید (مقایسه با کامیت پیش از تسک) |
⏳ | |
| ۷.۵ | npx tsc --noEmit و yarn test سبز |
⏳ | |
| ۷.۶ | TenantSchemaCoverageTest + TenantLookupInventoryTest سبز |
⏳ | |
| ۷.۷ | docs/api/* بهروز |
⏳ | |
| ۷.۸ | چکلیست UI کامل | ⏳ | |
| ۷.۹ | nobat724_front و clinic-pro-tauri بررسی شدند |
⏳ | دو فیلد جدید additive است |
| ۷.۱۰ | commit، سپس graphify update .، سپس commit جدا |
⏳ | |
| ۷.۱۱ | موارد بهتعویق با دلیل و تسک مقصد | ⏳ |