# دیتابیس — تسک ۰۲ ## `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` |