# «شعبه» جدول تازه‌ای نیست — `doctor_addresses` است **این سند بر همهٔ تسک‌هایی که `branches` یا `branch_id` می‌گویند حاکم است.** نسخهٔ اول تسک ۰۱ یک جدول `branches` طراحی کرده بود؛ در اجرا معلوم شد آن موجودیت از قبل وجود دارد. تسک ۰۱ اصلاح شد و جدول ساخته **نشد**. هر جا در تسک‌های ۰۲، ۰۴، ۰۷، ۰۸، ۰۹، ۱۰، ۱۳ نوشته شده `branch_id INT NOT NULL FK → branches(id)`، بخوانید: ```sql address_id INT NOT NULL -- FK → doctor_addresses(id) ``` و هر جا `Branch $branch` نوشته شده، بخوانید `DoctorAddress $address`. --- ## چرا `App\Doctor\Entity\DoctorAddress` تمام چیزی است که یک شعبه لازم دارد: | نیاز شعبه | در `DoctorAddress` | |---|---| | نام | `name` | | آدرس | `address` | | تلفن | `telephone` | | مختصات | `latitude` / `longitude` | | شهر و استان | FK به entity `City` / `Province` | | مالک (محیط) | `forDoctor(Doctor)` یا `forClinic(int $clinicId)` + ستون `type` | | فعال/غیرفعال | `active` — **تسک ۰۱ اضافه کرد** | | منطقهٔ زمانی | `timezone` — **تسک ۰۱ اضافه کرد** | و از قبل در کل سیستم به همین معنا مصرف می‌شود: - `WeeklySchedule.setting[day].sessions[].location_id` → `doctor_addresses.id` - `SlotCalculatorService::buildSessionSlots()` آن را در هر اسلات کپی می‌کند - `GET /api/v1/appointment-booking-locations/{doctorUuid}` هر آدرس را «محل نوبت‌دهی» می‌نامد - `DoctorAddressRepository::findForContext($doctor, $clinicId)` چند آدرس per محیط می‌دهد - **۹ endpoint CRUD** موجود: `clinic/{uuid}/addresses` (۴) و `clinic-pro/doctor-address*` (۵) - UI ادمین: `ClinicDetailPage`، `ClinicFormPage`، `DoctorDetailPage`، `SettingsPage` ساختن جدول موازی یعنی دو منبع حقیقت برای نام/آدرس/تلفن/مختصات یک مکان فیزیکی، و شعبه‌ای که `location_id` هرگز به آن اشاره نمی‌کند — یعنی decorative. قاعدهٔ #۸ پروژه: «API/جدول جدید فقط وقتی هیچ موجودی — حتی با توسعه — کافی نباشد.» --- ## پیامد برای تسک‌های بعدی | تسک | چه چیزی عوض می‌شود | |---|---| | ۰۲ منابع | `clinic_resources.address_id` → `doctor_addresses(id)`. `ResourcePool` هم همین. «منابع مال شعبه‌اند» = مال یک آدرس‌اند | | ۰۳ تقویم منبع | ساعت کاری شعبه از `branch_working_hours` که به `doctor_addresses.id` کلید می‌خورد | | ۰۴ کاتالوگ | `service_branch_overrides.address_id` | | ۰۷ رزرو | `appointments.address_id` **از قبل وجود دارد** (`Appointment::$addressId`) — ستون جدید لازم نیست | | ۰۸ قیمت | `price_lists.address_id` | | ۰۹/۱۰ قوانین | `policies.address_id` (اختصاصی‌بودن per شعبه) | | ۱۳ لغو/انتظار | `waitlist_entries.address_id` | ⚠️ نکتهٔ تسک ۰۷: `Appointment` از قبل `address_id` دارد (ستون `addressId`, تهی‌پذیر) و `SlotCalculatorService::resolveSlotLocationId()` پرش می‌کند. پس آنجا هم ستون تازه لازم نیست. --- ## جفت tenant اتاق و منابع `DoctorAddress` ستون‌های `entity_type`/`entity_id` ندارد؛ مالکیتش با `type` + `doctor_id`/`clinic_id` بیان می‌شود. موجودیت‌های جدیدی که به آدرس کلید می‌خورند و `TenantOwnedTrait` دارند، جفتشان را در **سازنده از آدرس مشتق** می‌کنند: ```php // App\Branch\Entity\Room::__construct() $this->assignTenantPair( $address->getType() === DoctorAddress::TYPE_CLINIC ? 'clinic' : 'doctor', $address->getType() === DoctorAddress::TYPE_CLINIC ? (int) $address->getClinicId() : (int) $address->getDoctor()->getId(), ); ``` همان قاعدهٔ `docs/architecture/tenancy.md`: جفت در سازنده از ریشه مشتق می‌شود، نه از ورودی درخواست — پس هیچ نقطهٔ ساختی نمی‌تواند فراموشش کند و write-once می‌ماند. --- ## به‌روزرسانی پس از تسک ۰۲ منابع ساخته شدند و `clinic_resources.address_id` و `resource_pools.address_id` هر دو به `doctor_addresses(id)` می‌خورند — همان‌طور که جدول بالا پیش‌بینی کرده بود. یک تصحیح **اضافه** روی همان جدول: کلید یکتای منبعِ پزشک `(doctor_id, address_id)` است نه `(doctor_id)`. یک `WeeklySchedule` per جفت (پزشک، کلینیک) است ولی هر شیفتِ درونش `location_id` خودش را دارد، پس یک پزشک از قبل در چند آدرسِ یک محیط کار می‌کند. همین برای `(staff_id, address_id)` هم صادق است. `rooms` استثناست: اتاق ذاتاً در یک آدرس است، پس `UNIQUE(room_id)` کافی است. تسک‌های ۰۴/۰۷/۰۸/۰۹/۱۰/۱۳ که هنوز `branch_id` می‌گویند، همین الگو را دنبال کنند.