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

## `resource_types`

| ستون | نوع | توضیح |
|---|---|---|
| `id` | INT PK AI | |
| `uuid` | VARCHAR(36) UNIQUE | |
| `entity_type` / `entity_id` | VARCHAR(10) / INT NOT NULL | |
| `code` | VARCHAR(40) NOT NULL | `doctor`, `staff`, `room`, `device`, … |
| `name` | VARCHAR(100) NOT NULL | نام نمایشی فارسی |
| `is_system` | TINYINT(1) NOT NULL DEFAULT 0 | نوع‌های `doctor/staff/room` توسط backfill ساخته می‌شوند و حذف نمی‌شوند |
| `active` | TINYINT(1) NOT NULL DEFAULT 1 | |
| `created_at`/`updated_at` | INT NOT NULL | |

```sql
KEY idx_resource_types_tenant (entity_type, entity_id, active)
UNIQUE KEY uniq_rt_tenant_code (entity_type, entity_id, code)
```

## `clinic_resources`

| ستون | نوع | توضیح |
|---|---|---|
| `id` | INT PK AI | |
| `uuid` | VARCHAR(36) UNIQUE | |
| `entity_type` / `entity_id` | VARCHAR(10) / INT NOT NULL | از `branch` مشتق می‌شود |
| `branch_id` | INT NOT NULL | FK → `branches.id` ON DELETE RESTRICT |
| `resource_type_id` | INT NOT NULL | FK → `resource_types.id` ON DELETE RESTRICT |
| `name` | VARCHAR(150) NOT NULL | |
| `capacity` | SMALLINT NOT NULL DEFAULT 1 | ظرفیت هم‌زمان |
| `setup_minutes` | SMALLINT NOT NULL DEFAULT 0 | آماده‌سازی پیش از بیمار |
| `cleanup_minutes` | SMALLINT NOT NULL DEFAULT 0 | تمیزکاری پس از بیمار |
| `attributes` | JSON NULL | اسکالر فقط، حداکثر ۲۰ کلید |
| `doctor_id` | INT NULL UNIQUE | FK → `doctors.id` ON DELETE CASCADE |
| `staff_id` | INT NULL UNIQUE | FK → `clinic_staff.id` ON DELETE CASCADE |
| `room_id` | INT NULL UNIQUE | FK → `rooms.id` ON DELETE CASCADE |
| `active` | TINYINT(1) NOT NULL DEFAULT 1 | |
| `created_at`/`updated_at` | INT NOT NULL | |

```sql
KEY idx_resources_tenant       (entity_type, entity_id, active)
KEY idx_resources_branch_type  (branch_id, resource_type_id, active)
UNIQUE KEY uniq_resource_doctor (doctor_id)
UNIQUE KEY uniq_resource_staff  (staff_id)
UNIQUE KEY uniq_resource_room   (room_id)
```

سه کلید یکتای تهی‌پذیر تضمین می‌کنند یک پزشک/پرسنل/اتاق بیش از یک منبع نگیرد.
MariaDB چند `NULL` را در UNIQUE می‌پذیرد، پس دستگاه‌های بدون پل مشکلی ندارند.

> **قید سطح اپلیکیشن (نه DB):** حداکثر یکی از `doctor_id`/`staff_id`/`room_id` غیر-NULL.
> در سازندهٔ entity اجبار شود؛ MariaDB `CHECK` چندستونی را قابل اتکا اجرا نمی‌کند.

## `skills` و `resource_skills`

```sql
CREATE TABLE skills (
  id INT PRIMARY KEY AUTO_INCREMENT,
  uuid VARCHAR(36) NOT NULL UNIQUE,
  entity_type VARCHAR(10) NOT NULL,
  entity_id INT NOT NULL,
  name VARCHAR(120) NOT NULL,
  active TINYINT(1) NOT NULL DEFAULT 1,
  created_at INT NOT NULL,
  updated_at INT NOT NULL,
  KEY idx_skills_tenant (entity_type, entity_id, active)
);

CREATE TABLE resource_skills (
  id INT PRIMARY KEY AUTO_INCREMENT,
  resource_id INT NOT NULL,
  skill_id INT NOT NULL,
  level TINYINT NOT NULL DEFAULT 1,          -- 1..5
  created_at INT NOT NULL,
  UNIQUE KEY uniq_resource_skill (resource_id, skill_id),
  KEY idx_resource_skills_skill (skill_id, level),
  CONSTRAINT fk_rs_resource FOREIGN KEY (resource_id) REFERENCES clinic_resources(id) ON DELETE CASCADE,
  CONSTRAINT fk_rs_skill    FOREIGN KEY (skill_id)    REFERENCES skills(id)           ON DELETE RESTRICT
);
```

`idx_resource_skills_skill (skill_id, level)` عمدی است: پرس‌وجوی داغِ تسک ۰۶
«کدام منابع مهارت X را دارند» از این سمت می‌آید.

`resource_skills` بدون ستون tenant — فرزند aggregate با ریشهٔ `ClinicResource`،
و uuid از request نمی‌گیرد (همیشه از `/resource/{uuid}/skills`).

## `resource_pools` و `resource_pool_members`

```sql
CREATE TABLE resource_pools (
  id INT PRIMARY KEY AUTO_INCREMENT,
  uuid VARCHAR(36) NOT NULL UNIQUE,
  entity_type VARCHAR(10) NOT NULL,
  entity_id INT NOT NULL,
  branch_id INT NOT NULL,
  resource_type_id INT NOT NULL,
  name VARCHAR(150) NOT NULL,
  active TINYINT(1) NOT NULL DEFAULT 1,
  created_at INT NOT NULL,
  updated_at INT NOT NULL,
  KEY idx_pools_tenant (entity_type, entity_id, active),
  KEY idx_pools_branch (branch_id, resource_type_id),
  CONSTRAINT fk_pool_branch FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE,
  CONSTRAINT fk_pool_type   FOREIGN KEY (resource_type_id) REFERENCES resource_types(id) ON DELETE RESTRICT
);

CREATE TABLE resource_pool_members (
  id INT PRIMARY KEY AUTO_INCREMENT,
  pool_id INT NOT NULL,
  resource_id INT NOT NULL,
  priority SMALLINT NOT NULL DEFAULT 0,      -- ترتیب ترجیح در استراتژی انتخاب
  UNIQUE KEY uniq_pool_resource (pool_id, resource_id),
  KEY idx_pool_members_resource (resource_id),
  CONSTRAINT fk_pm_pool     FOREIGN KEY (pool_id)     REFERENCES resource_pools(id)  ON DELETE CASCADE,
  CONSTRAINT fk_pm_resource FOREIGN KEY (resource_id) REFERENCES clinic_resources(id) ON DELETE CASCADE
);
```

## Migration و backfill

```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:resource:backfill              # dry-run
ddev exec php bin/console app:resource:backfill --force
```

`app:resource:backfill` برای هر محیط:
1. سه `resource_type` سیستمی می‌سازد (`doctor`, `staff`, `room`) اگر نباشند
2. هر `Room` فعال → یک منبع با `capacity` همان اتاق
3. هر `ClinicStaff` فعال → یک منبع `type=staff` در شعبهٔ اول محیط
4. هر `Doctor` دارای `WeeklySchedule` در آن محیط → یک منبع `type=doctor`

idempotent باشد: اجرای دوباره چیزی دوباره نمی‌سازد.

## طبقه‌بندی tenant

| جدول | وضعیت |
|---|---|
| `resource_types`, `clinic_resources`, `skills`, `resource_pools` | جفت tenant |
| `resource_skills`, `resource_pool_members` | `AGGREGATE_CHILDREN` |
