# تسک ۰۱ — شعبه (Branch) و اتاق (Room) **فاز:** ۱ (هسته) · **وابستگی:** — · **زمان:** ۱۰-۱۲ ساعت --- ## هدف سطح سوم مکان را به مدل اضافه کن: امروز `(entity_type, entity_id)` می‌گوید داده مال کدام محیط است، ولی نمی‌گوید در کدام **ساختمان** و کدام **اتاق**. مستند بند ۴ سه سطح می‌خواهد و «منابع همیشه مال شعبه‌اند چون فیزیکی‌اند» — بدون شعبه، تسک ۰۲ جایی برای نشستن ندارد. ## وضعیت فعلی - محل مراجعه امروز `DoctorAddress` است و `location_id` هر شیفت در `WeeklySchedule.setting[day].sessions[].location_id` به `doctor_addresses.id` اشاره می‌کند (`SlotCalculatorService::buildSessionSlots()` آن را در هر اسلات کپی می‌کند). - `Clinic` هیچ فیلد شعبه‌ای ندارد؛ یک آدرس متنی تخت دارد. - ساعت کاری شعبه وجود ندارد — ساعت کاری فقط روی برنامهٔ پزشک است. ## دامنه **هست:** entity های `Branch` و `Room`، ساعت کاری هفتگی شعبه، CRUD پنل ادمین، پل زدن `DoctorAddress.branch_id` برای اینکه شیفت‌های موجود بدون تغییر به شعبه نگاشت شوند. **نیست:** استفاده از شعبه در محاسبهٔ اسلات (تسک ۰۳)، اتاق به‌عنوان منبع قابل رزرو (تسک ۰۲). ## Endpoint ها | متد | مسیر | توضیح | |---|---|---| | GET | `/api/v1/branches` | لیست شعب محیط جاری (paginated) | | POST | `/api/v1/branch` | ساخت شعبه | | GET | `/api/v1/branch/{uuid}` | جزئیات + ساعت کاری | | PATCH | `/api/v1/branch/{uuid}` | ویرایش | | DELETE | `/api/v1/branch/{uuid}` | حذف (فقط بدون منبع/اتاق فعال) | | PUT | `/api/v1/branch/{uuid}/working-hours` | ثبت ساعت کاری هفتگی | | GET | `/api/v1/branch/{uuid}/rooms` | اتاق‌های شعبه | | POST/PATCH/DELETE | `/api/v1/room[/{uuid}]` | CRUD اتاق | ## معیار پذیرش - ✅ موفق: کلینیک با توکن مالک `POST /api/v1/branch` می‌زند → `201` و شعبه با `entity_type=clinic, entity_id=` ثبت می‌شود. `GET /api/v1/branches` همان را برمی‌گرداند. - ✅ موفق: `PUT /branch/{uuid}/working-hours` با هفت روز → `200`؛ `GET /branch/{uuid}` همان ساختار را با کلیدهای `0..6` (۰=شنبه) برمی‌گرداند. - ❌ خطا: کلینیک B با uuid شعبهٔ کلینیک A → `404` با `ERR_NOT_FOUND_001` (نه ۴۰۳ — طبق رفتار `TenantFilter`). - ❌ خطا: `DELETE` شعبه‌ای که اتاق فعال دارد → `422` با پیام فارسی «شعبه دارای اتاق فعال است». - ⚠️ مرزی: پزشک مستقل (محیط `doctor`) هم می‌تواند شعبه بسازد — «مطب» یک شعبه است. اولین شعبه از روی `DoctorAddress` موجود ساخته می‌شود، نه دستی. - ⚠️ مرزی: ساعت کاری با `end_time <= start_time` → `422`. - ⚠️ مرزی: شعبه بدون ساعت کاری معتبر است (وراثت: تسک ۰۳ آن را «همیشه باز» تفسیر نمی‌کند، «تعریف‌نشده» تفسیر می‌کند). ## خروجی - `src/Branch/` کامل با تست - `assets/admin/pages/BranchesPage.tsx` + `BranchFormPage.tsx` + `RoomsPage.tsx` - `docs/api/branch.md` - migration + دستور `app:branch:backfill` برای ساخت شعبهٔ اولیه از آدرس‌های موجود