# دیتابیس — تسک ۰۱

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
```
