feat: implement cancellation policy, no-show tracking, and waitlist management
- Add implementation notes for cancellation and waitlist features. - Create task documentation outlining goals, current status, and acceptance criteria for cancellation policy and resource utilization reporting. - Establish architecture for domain events and outbox pattern to ensure reliable event publishing. - Define database schema for domain events and necessary queries for resource utilization and plan accuracy reports. - Implement detailed implementation notes covering edge cases, testing strategies, and documentation requirements.
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
# دیتابیس — تسک ۰۴
|
||||
|
||||
## تغییر جدول موجود: `service_items`
|
||||
|
||||
```sql
|
||||
ALTER TABLE service_items
|
||||
ADD COLUMN service_category_id INT NULL AFTER section_id,
|
||||
ADD COLUMN session_count SMALLINT NOT NULL DEFAULT 1, -- ۱ = تکجلسه، >۱ = دورهای
|
||||
ADD COLUMN preparation_note TEXT NULL, -- «آمادهسازی بیمار» — مستند بند ۵
|
||||
ADD CONSTRAINT fk_service_items_category
|
||||
FOREIGN KEY (service_category_id) REFERENCES service_categories(id) ON DELETE SET NULL,
|
||||
ADD KEY idx_service_items_category (service_category_id);
|
||||
```
|
||||
|
||||
`duration_minutes` موجود **حذف نمیشود** و همچنان «مدت پایهٔ سرویس» است. آیتمها روی آن
|
||||
اضافه میکنند. در حالت `booking_mode=service` فعلی هیچ تغییری در رفتار نیست چون هیچ
|
||||
سرویسی گروه ندارد و `totalMinutes` برابر همان `duration_minutes` میماند.
|
||||
|
||||
## `service_categories` — درختی
|
||||
|
||||
| ستون | نوع | توضیح |
|
||||
|---|---|---|
|
||||
| `id` | INT PK AI | |
|
||||
| `uuid` | VARCHAR(36) UNIQUE | |
|
||||
| `entity_type` / `entity_id` | VARCHAR(10) / INT NOT NULL | |
|
||||
| `parent_id` | INT NULL | FK → خودش، ON DELETE RESTRICT |
|
||||
| `name` | VARCHAR(150) NOT NULL | |
|
||||
| `path` | VARCHAR(255) NOT NULL | materialized path: `/1/7/23/` |
|
||||
| `depth` | TINYINT NOT NULL DEFAULT 0 | |
|
||||
| `sort_order` | SMALLINT NOT NULL DEFAULT 0 | |
|
||||
| `active` | TINYINT(1) NOT NULL DEFAULT 1 | |
|
||||
| `created_at`/`updated_at` | INT NOT NULL | |
|
||||
|
||||
```sql
|
||||
KEY idx_svc_cat_tenant (entity_type, entity_id, active)
|
||||
KEY idx_svc_cat_parent (parent_id, sort_order)
|
||||
KEY idx_svc_cat_path (path)
|
||||
```
|
||||
|
||||
**materialized path** بهجای adjacency خالص: تسک ۰۹ شرط «سرویس در دستهٔ جراحی یا
|
||||
زیردستههایش» را میخواهد و با `path LIKE '/1/7/%'` یک کوئری است، نه یک پیمایش بازگشتی.
|
||||
|
||||
سقف عمق: ۴. در `ServiceCategoryService` اجبار شود.
|
||||
|
||||
## `item_groups`
|
||||
|
||||
| ستون | نوع | توضیح |
|
||||
|---|---|---|
|
||||
| `id` | INT PK AI | |
|
||||
| `uuid` | VARCHAR(36) UNIQUE | |
|
||||
| `entity_type` / `entity_id` | VARCHAR(10) / INT NOT NULL | از سرویس مشتق میشود |
|
||||
| `service_item_id` | INT NOT NULL | FK → `service_items.id` ON DELETE CASCADE |
|
||||
| `name` | VARCHAR(150) NOT NULL | «نواحی موردنظر» |
|
||||
| `min_select` | SMALLINT NOT NULL DEFAULT 0 | ۰ = اختیاری |
|
||||
| `max_select` | SMALLINT NULL | NULL = نامحدود |
|
||||
| `sort_order` | SMALLINT NOT NULL DEFAULT 0 | |
|
||||
| `active` | TINYINT(1) NOT NULL DEFAULT 1 | |
|
||||
| `created_at`/`updated_at` | INT NOT NULL | |
|
||||
|
||||
```sql
|
||||
KEY idx_item_groups_tenant (entity_type, entity_id, active)
|
||||
KEY idx_item_groups_service (service_item_id, sort_order)
|
||||
```
|
||||
|
||||
## `service_options` — «آیتم» مستند
|
||||
|
||||
| ستون | نوع | توضیح |
|
||||
|---|---|---|
|
||||
| `id` | INT PK AI | |
|
||||
| `uuid` | VARCHAR(36) UNIQUE | از request میآید |
|
||||
| `entity_type` / `entity_id` | VARCHAR(10) / INT NOT NULL | |
|
||||
| `item_group_id` | INT NOT NULL | FK ON DELETE CASCADE |
|
||||
| `name` | VARCHAR(150) NOT NULL | |
|
||||
| `solo_minutes` | SMALLINT NOT NULL | «زمان تنها» |
|
||||
| `additional_minutes` | SMALLINT NULL | «زمان اضافه»؛ NULL → `solo_minutes` |
|
||||
| `price_rials` | INT NOT NULL DEFAULT 0 | |
|
||||
| `sort_order` | SMALLINT NOT NULL DEFAULT 0 | |
|
||||
| `active` | TINYINT(1) NOT NULL DEFAULT 1 | |
|
||||
| `created_at`/`updated_at` | INT NOT NULL | |
|
||||
|
||||
```sql
|
||||
KEY idx_service_options_tenant (entity_type, entity_id, active)
|
||||
KEY idx_service_options_group (item_group_id, sort_order)
|
||||
```
|
||||
|
||||
قید اپلیکیشنی: `additional_minutes <= solo_minutes` (زمان اضافه هرگز بیشتر از زمان تنها
|
||||
نیست — آمادهسازی که دو بار نمیشود). نقض → `422` با پیام روشن.
|
||||
|
||||
## `service_option_relations`
|
||||
|
||||
```sql
|
||||
CREATE TABLE service_option_relations (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
source_option_id INT NOT NULL,
|
||||
target_option_id INT NOT NULL,
|
||||
type VARCHAR(20) NOT NULL, -- incompatible | requires
|
||||
UNIQUE KEY uniq_sor (source_option_id, target_option_id, type),
|
||||
KEY idx_sor_target (target_option_id, type),
|
||||
CONSTRAINT fk_sor_source FOREIGN KEY (source_option_id) REFERENCES service_options(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_sor_target FOREIGN KEY (target_option_id) REFERENCES service_options(id) ON DELETE CASCADE
|
||||
);
|
||||
```
|
||||
|
||||
فرزند aggregate با ریشهٔ `ServiceOption`. قید اپلیکیشنی: `source != target`.
|
||||
|
||||
## `service_branch_overrides`
|
||||
|
||||
```sql
|
||||
CREATE TABLE service_branch_overrides (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
uuid VARCHAR(36) NOT NULL UNIQUE,
|
||||
entity_type VARCHAR(10) NOT NULL,
|
||||
entity_id INT NOT NULL,
|
||||
service_item_id INT NOT NULL,
|
||||
branch_id INT NOT NULL,
|
||||
price_rials INT NULL, -- NULL = ارث از سرویس
|
||||
duration_minutes SMALLINT NULL, -- NULL = ارث از سرویس
|
||||
bookable TINYINT(1) NULL, -- NULL = ارث؛ 0 = این شعبه ارائه نمیدهد
|
||||
created_at INT NOT NULL,
|
||||
updated_at INT NOT NULL,
|
||||
UNIQUE KEY uniq_sbo (service_item_id, branch_id),
|
||||
KEY idx_sbo_tenant (entity_type, entity_id),
|
||||
KEY idx_sbo_branch (branch_id),
|
||||
CONSTRAINT fk_sbo_service FOREIGN KEY (service_item_id) REFERENCES service_items(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_sbo_branch FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE
|
||||
);
|
||||
```
|
||||
|
||||
سه ستون تهیپذیرند تا override جزئی ممکن باشد: فقط قیمت، بدون دست زدن به مدت.
|
||||
|
||||
## Migration و backfill
|
||||
|
||||
```bash
|
||||
ddev exec php bin/console doctrine:migrations:diff --no-interaction
|
||||
ddev exec php bin/console doctrine:migrations:migrate --no-interaction
|
||||
```
|
||||
|
||||
backfill لازم نیست: سرویسهای موجود گروه ندارند، `DurationCalculator` مقدار
|
||||
`service_items.duration_minutes` را برمیگرداند و رفتار حالت `service` بدون تغییر میماند.
|
||||
|
||||
## طبقهبندی tenant
|
||||
|
||||
| جدول | وضعیت |
|
||||
|---|---|
|
||||
| `service_categories`, `item_groups`, `service_options`, `service_branch_overrides` | جفت tenant |
|
||||
| `service_option_relations` | `AGGREGATE_CHILDREN` → ریشه `ServiceOption` |
|
||||
|
||||
`TenantLookupInventoryTest` شمارنده دارد؛ `findByUuid` های جدید (`ServiceOptionRepository`,
|
||||
`ItemGroupRepository`) باید با `TenantOwnershipChecker` جفت شوند و بعد عدد بهروز شود.
|
||||
Reference in New Issue
Block a user