# دیتابیس — تسک ۰۱ MariaDB 11.8 · Doctrine ORM 3.6 · همهٔ timestamp ها `INT` (Unix) > ⛔ **جدول `branches` ساخته نمی‌شود.** «شعبه» همان `doctor_addresses` است — دلیل کامل در > [`_shared/branch-is-doctor-address.md`](../_shared/branch-is-doctor-address.md). آنچه در > نسخهٔ اول این فایل به‌عنوان جدول `branches` و ستون `doctor_addresses.branch_id` آمده بود > حذف شد. ## تغییر جدول موجود — `doctor_addresses` ```sql ALTER TABLE doctor_addresses ADD COLUMN active TINYINT(1) NOT NULL DEFAULT 1, ADD COLUMN timezone VARCHAR(40) NOT NULL DEFAULT 'Asia/Tehran'; ``` هیچ ستونی حذف یا تغییر نوع نمی‌دهد. هر دو `NOT NULL DEFAULT` دارند، پس ردیف‌های موجود بی‌نیاز از backfill درست می‌شوند و هیچ رفتار فعلی عوض نمی‌شود. `timezone` از همین حالا اضافه می‌شود چون بند ۹ مستند ذخیره‌سازی UTC با نمایش محلی می‌خواهد؛ افزودنش بعد از اینکه داده‌های زمان‌دار روی شعبه نشستند یعنی backfill پرریسک. ⚠️ `active` در این تسک **فقط ذخیره** می‌شود؛ فیلتر شدنش در محاسبهٔ اسلات کارِ تسک ۰۳ است (هر تغییری در `SlotCalculatorService` در فاز فعلی ممنوع است — `_shared/red-lines.md`). ## `branch_working_hours` | ستون | نوع | توضیح | |---|---|---| | `id` | INT PK AI | | | `address_id` | INT NOT NULL | FK → `doctor_addresses.id` ON DELETE CASCADE | | `day_of_week` | TINYINT NOT NULL | ۰=شنبه … ۶=جمعه — همان قرارداد `SlotCalculatorService` | | `sequence` | TINYINT NOT NULL DEFAULT 0 | بازهٔ چندم آن روز | | `start_minute` | SMALLINT NOT NULL | ۰..۱۴۴۰ از نیمه‌شب | | `end_minute` | SMALLINT NOT NULL | > `start_minute` | | `active` | TINYINT(1) NOT NULL DEFAULT 1 | | ```sql UNIQUE KEY uniq_bwh_address_day_seq (address_id, day_of_week, sequence) KEY idx_bwh_address_day (address_id, day_of_week, active) ``` بدون ستون tenant — **فرزند aggregate** با ریشهٔ `DoctorAddress`. هرگز با uuid از request لود نمی‌شود؛ تنها راه رسیدن به آن `/branch/{addressUuid}/working-hours` است که آدرس را از `TenantFilter` رد می‌کند. در `GlobalTables::AGGREGATE_CHILDREN` با ریشهٔ صریح ثبت می‌شود. دقت: چون `doctor_addresses` خودش `TenantOwnedTrait` ندارد (مالکیتش با `type` + `doctor_id`/`clinic_id` است)، `TenantFilter` روی خودِ آدرس هم اعمال نمی‌شود. پس فیلتر محیط برای این endpoint **دستی** است: `DoctorAddressRepository::findForContext()` که از قبل همین کار را می‌کند و در `clinic/{uuid}/addresses` هم همین‌طور استفاده شده. ## `rooms` | ستون | نوع | توضیح | |---|---|---| | `id` | INT PK AI | | | `uuid` | VARCHAR(36) UNIQUE | **از request می‌آید** → پس جفت tenant لازم دارد | | `entity_type` / `entity_id` | VARCHAR(10)/INT NOT NULL | در سازنده از `DoctorAddress` مشتق می‌شود | | `address_id` | INT NOT NULL | FK → `doctor_addresses.id` ON DELETE CASCADE | | `name` | VARCHAR(120) NOT NULL | | | `room_type` | VARCHAR(60) NULL | متن آزاد، تعریف کلینیک | | `capacity` | SMALLINT NOT NULL DEFAULT 1 | ظرفیت هم‌زمان | | `floor` | VARCHAR(20) NULL | ویژگی آزاد — مستند بند ۶ | | `active` | TINYINT(1) NOT NULL DEFAULT 1 | | | `created_at` / `updated_at` | INT NOT NULL | | ```sql KEY idx_rooms_tenant (entity_type, entity_id, active) KEY idx_rooms_address (address_id, active) ``` `entity_type, entity_id` ستون‌های اولِ ایندکس‌اند — شرط `TenantFilter` وگرنه از ایندکس استفاده نمی‌کند. اشتقاق جفت در سازنده (نه از بدنهٔ request): ```php $isClinic = $address->getType() === DoctorAddress::TYPE_CLINIC; $this->assignTenantPair( $isClinic ? 'clinic' : 'doctor', $isClinic ? (int) $address->getClinicId() : (int) $address->getDoctor()->getId(), ); ``` ## Migration ```bash ddev exec php bin/console doctrine:migrations:diff --no-interaction ddev exec php bin/console doctrine:migrations:migrate --no-interaction ``` هیچ command backfill لازم نیست — `app:branch:backfill` نسخهٔ اول برای پر کردن `branch_id` از `doctor_addresses` بود؛ حالا که جدول موازی ساخته نمی‌شود، موضوعش منتفی است. ⚠️ **`db_test` تاریخچهٔ migration جدا دارد** و `migrate` رویش با `Table 'users' already exists` می‌شکند. ستون‌ها را دستی اضافه کن وگرنه کل تست‌سوئیت با `Unknown column` قرمز می‌شود: ```bash ddev mysql -uroot -proot -e "ALTER TABLE db_test.doctor_addresses \ ADD active TINYINT(1) NOT NULL DEFAULT 1, \ ADD timezone VARCHAR(40) NOT NULL DEFAULT 'Asia/Tehran';" ``` و بعد از تولید migration، همان `CREATE TABLE` های `branch_working_hours` و `rooms` را هم روی `db_test` اجرا کن. ## طبقه‌بندی tenant | جدول | وضعیت | ثبت در | |---|---|---| | `doctor_addresses` | از قبل طبقه‌بندی‌شده — دست نمی‌خورد | همان‌جای فعلی | | `rooms` | جفت tenant | `TenantOwnedTrait` (uuid از request می‌آید) | | `branch_working_hours` | فرزند aggregate | `GlobalTables::AGGREGATE_CHILDREN` → ریشه `DoctorAddress` | بعد از migration: ```bash ddev exec php bin/phpunit tests/Shared/TenantSchemaCoverageTest.php ddev exec php bin/phpunit tests/Shared/TenantLookupInventoryTest.php ``` دومی هم لازم است: repository جدیدی که `findByUuid` دارد باید در `REVIEWED` ثبت شود.