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

## `segment_templates`

| ستون | نوع | توضیح |
|---|---|---|
| `id` | INT PK AI | |
| `uuid` | VARCHAR(36) UNIQUE | از request می‌آید |
| `entity_type` / `entity_id` | VARCHAR(10) / INT NOT NULL | |
| `owner_type` | VARCHAR(10) NOT NULL | `service` \| `option` |
| `service_item_id` | INT NULL | FK → `service_items.id` ON DELETE CASCADE |
| `service_option_id` | INT NULL | FK → `service_options.id` ON DELETE CASCADE |
| `name` | VARCHAR(150) NOT NULL | |
| `segment_type` | VARCHAR(40) NOT NULL | کلید ادغام |
| `sequence` | SMALLINT NOT NULL | |
| `fixed_minutes` | SMALLINT NULL | |
| `duration_share` | SMALLINT NULL | درصد ۰..۱۰۰ |
| `patient_present` | TINYINT(1) NOT NULL DEFAULT 1 | |
| `mergeable` | TINYINT(1) NOT NULL DEFAULT 0 | |
| `active` | TINYINT(1) NOT NULL DEFAULT 1 | |
| `created_at`/`updated_at` | INT NOT NULL | |

```sql
KEY idx_seg_tpl_tenant  (entity_type, entity_id, active)
KEY idx_seg_tpl_service (service_item_id, sequence)
KEY idx_seg_tpl_option  (service_option_id, sequence)
```

قیدهای اپلیکیشنی (در سازنده/سرویس، نه `CHECK`):
- دقیقاً یکی از `service_item_id` / `service_option_id` غیر-NULL و با `owner_type` سازگار
- دقیقاً یکی از `fixed_minutes` / `duration_share` غیر-NULL
- جمع `duration_share` بخش‌های یک سرویس = ۱۰۰ (اگر حداقل یکی سهمی باشد)

## `segment_requirements`

| ستون | نوع | توضیح |
|---|---|---|
| `id` | INT PK AI | |
| `uuid` | VARCHAR(36) UNIQUE | |
| `segment_template_id` | INT NOT NULL | FK ON DELETE CASCADE |
| `resource_type_id` | INT NOT NULL | FK → `resource_types.id` ON DELETE RESTRICT |
| `count` | SMALLINT NOT NULL DEFAULT 1 | |
| `resource_pool_id` | INT NULL | FK → `resource_pools.id` ON DELETE SET NULL |
| `specific_resource_id` | INT NULL | FK → `clinic_resources.id` ON DELETE SET NULL |
| `required_skills` | JSON NULL | آرایهٔ `skill_id` |
| `constraints` | JSON NULL | فهرست بسته — جدول architecture |
| `occupancy` | VARCHAR(10) NOT NULL DEFAULT 'exclusive' | `exclusive`\|`shared`\|`passive` |
| `sort_order` | SMALLINT NOT NULL DEFAULT 0 | |

```sql
KEY idx_seg_req_segment (segment_template_id, sort_order)
KEY idx_seg_req_pool    (resource_pool_id)
```

فرزند aggregate با ریشهٔ `SegmentTemplate` — uuid از request فقط در
`PUT /segment-template/{uuid}/requirements` می‌آید که خودش از ریشه لنگر می‌خورد،
پس ستون tenant لازم ندارد.

> `required_skills` عمداً JSON است نه جدول واسط: همیشه کامل خوانده و کامل جایگزین می‌شود،
> و هیچ کوئری‌ای از سمت مهارت به نیازمندی نمی‌رود. جدول واسط اینجا فقط سه JOIN اضافه
> به مسیر داغ تسک ۰۶ می‌آورد.

## هیچ جدول جدیدی برای «برنامهٔ ساخته‌شده» نیست

`AppointmentPlan` یک DTO درون‌حافظه‌ای است، نه entity. ذخیره‌اش وقتی معنی پیدا می‌کند که
نوبت ثبت شود — که کار تسک ۰۷ است (`appointment_segments`).

دلیل: برنامه یک تابع خالص از (سرویس، آیتم‌ها، بیمار، شعبه، قوانین فعال) است. ذخیره‌کردنش
پیش از ثبت یعنی نگه‌داشتن حالت موقتی که باید منقضی شود — همان مسئله‌ای که رزرو موقت
تسک ۰۷ حل می‌کند و دو مکانیزم موازی لازم نیست.

## 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:segment:seed-templates --preset=beauty --tenant=clinic:12 --force
```

`app:segment:seed-templates` سه پریست دارد (مستند بند ۱۷: «الگوی آماده برای هر نوع کلینیک»):

| پریست | بخش‌ها |
|---|---|
| `beauty` | آماده‌سازی ۵ · انتظار ۳۰ (فقط اتاق) · درمان (سهمی ۱۰۰) · مراقبت ۵ |
| `dental` | آماده‌سازی ۵ · درمان (سهمی ۱۰۰) · تمیزکاری یونیت ۱۰ (بدون حضور بیمار) |
| `physio` | درمان (سهمی ۱۰۰) |

dry-run پیش‌فرض. پریست‌ها روی سرویس‌های موجود اعمال نمی‌شوند مگر با `--service=<uuid>`.

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

| جدول | وضعیت |
|---|---|
| `segment_templates` | جفت tenant |
| `segment_requirements` | `AGGREGATE_CHILDREN` → ریشه `SegmentTemplate` |
