# دیتابیس — تسک ۰۱ MariaDB 11.8 · Doctrine ORM 3.6 · همهٔ timestamp ها `INT` (Unix) ## `branches` | ستون | نوع | توضیح | |---|---|---| | `id` | INT PK AI | | | `uuid` | VARCHAR(36) UNIQUE | ارجاع خارجی | | `entity_type` | VARCHAR(10) NOT NULL | `doctor` \| `clinic` | | `entity_id` | INT NOT NULL | | | `name` | VARCHAR(150) NOT NULL | | | `phone` | VARCHAR(20) NULL | | | `address` | TEXT NULL | | | `city_id` | INT NULL | FK منطقی به `categories.id` با `bundle='city'` | | `province_id` | INT NULL | همان الگو با `bundle='state'` | | `latitude` | DOUBLE NULL | | | `longitude` | DOUBLE NULL | | | `timezone` | VARCHAR(40) NOT NULL DEFAULT 'Asia/Tehran' | | | `active` | TINYINT(1) NOT NULL DEFAULT 1 | | | `created_at` / `updated_at` | INT NOT NULL | | ایندکس‌ها: ```sql KEY idx_branches_tenant (entity_type, entity_id, active) UNIQUE KEY uniq_branches_uuid (uuid) ``` > `entity_type, entity_id` ستون‌های اول‌اند — شرطِ `TenantFilter` وگرنه از ایندکس استفاده نمی‌کند. `timezone` از روز اول هست چون مستند بند ۹ می‌گوید ذخیره‌سازی UTC و نمایش محلی؛ امروز همه‌جا `Asia/Tehran` است ولی افزودن ستون بعداً یعنی backfill روی داده‌های زمان‌دار. ## `branch_working_hours` | ستون | نوع | توضیح | |---|---|---| | `id` | INT PK AI | | | `branch_id` | INT NOT NULL | FK → `branches.id` ON DELETE CASCADE | | `day_of_week` | TINYINT NOT NULL | ۰=شنبه … ۶=جمعه | | `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_branch_day_seq (branch_id, day_of_week, sequence) KEY idx_bwh_branch_day (branch_id, day_of_week, active) ``` بدون ستون tenant — فرزند aggregate با ریشهٔ `branches` است و uuid از request نمی‌گیرد (همیشه از راه `/branch/{uuid}/working-hours` لود می‌شود). در `GlobalTables::AGGREGATE_CHILDREN` با ریشهٔ صریح ثبت شود. ## `rooms` | ستون | نوع | توضیح | |---|---|---| | `id` | INT PK AI | | | `uuid` | VARCHAR(36) UNIQUE | **از request می‌آید** → پس جفت tenant خودش را دارد | | `entity_type` / `entity_id` | VARCHAR(10)/INT NOT NULL | از `branch` در سازنده مشتق می‌شود | | `branch_id` | INT NOT NULL | FK → `branches.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_branch (branch_id, active) ``` ## تغییر جدول موجود ```sql ALTER TABLE doctor_addresses ADD COLUMN branch_id INT NULL, ADD CONSTRAINT fk_doctor_addresses_branch FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE SET NULL, ADD KEY idx_doctor_addresses_branch (branch_id); ``` هیچ ستونی حذف یا تغییر نوع نمی‌دهد. `location_id` در JSON برنامهٔ هفتگی دست‌نخورده می‌ماند. ## Migration ```bash ddev exec php bin/console doctrine:migrations:diff --no-interaction ddev exec php bin/console doctrine:migrations:migrate --no-interaction ddev exec php bin/console app:branch:backfill # dry-run ddev exec php bin/console app:branch:backfill --force ``` ## طبقه‌بندی tenant | جدول | وضعیت | ثبت در | |---|---|---| | `branches` | جفت tenant | `TenantOwnedTrait` | | `rooms` | جفت tenant | `TenantOwnedTrait` (uuid از request می‌آید) | | `branch_working_hours` | فرزند aggregate | `GlobalTables::AGGREGATE_CHILDREN` → ریشه `Branch` | بعد از migration: ```bash ddev exec php bin/phpunit tests/Shared/TenantSchemaCoverageTest.php ```