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,126 @@
|
||||
# دیتابیس — تسک ۰۹
|
||||
|
||||
## `policies`
|
||||
|
||||
| ستون | نوع | توضیح |
|
||||
|---|---|---|
|
||||
| `id` | INT PK AI | |
|
||||
| `uuid` | VARCHAR(36) UNIQUE | |
|
||||
| `entity_type` / `entity_id` | VARCHAR(10) / INT NOT NULL | |
|
||||
| `branch_id` | INT NULL | NULL = همهٔ شعب — FK ON DELETE CASCADE |
|
||||
| `name` | VARCHAR(200) NOT NULL | «خدمات جراحی به جراح نیاز دارند» |
|
||||
| `category` | VARCHAR(20) NOT NULL | یکی از شش دسته |
|
||||
| `priority` | SMALLINT NOT NULL DEFAULT 0 | |
|
||||
| `specificity` | SMALLINT NOT NULL DEFAULT 0 | محاسبهشده هنگام ذخیره |
|
||||
| `version` | SMALLINT NOT NULL DEFAULT 1 | |
|
||||
| `valid_from` | INT NULL | |
|
||||
| `valid_to` | INT NULL | |
|
||||
| `active` | TINYINT(1) NOT NULL DEFAULT 0 | **پیشفرض غیرفعال** |
|
||||
| `conditions` | JSON NOT NULL | |
|
||||
| `effects` | JSON NOT NULL | |
|
||||
| `created_at`/`updated_at` | INT NOT NULL | |
|
||||
|
||||
```sql
|
||||
KEY idx_policies_lookup (entity_type, entity_id, category, active, valid_from)
|
||||
KEY idx_policies_branch (branch_id, category, active)
|
||||
```
|
||||
|
||||
`idx_policies_lookup` کوئری داغ است: «قوانین فعال دستهٔ X این محیط که امروز معتبرند».
|
||||
با ۶ دسته و معمولاً < ۵۰ قانون per محیط، این کوئری همیشه ارزان است — و باید بماند.
|
||||
اگر روزی قوانین به هزاران رسیدند، کش per (محیط، دسته) اضافه شود، نه ایندکس پیچیدهتر.
|
||||
|
||||
## `policy_version_log`
|
||||
|
||||
```sql
|
||||
CREATE TABLE policy_version_log (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
policy_id INT NOT NULL,
|
||||
version SMALLINT NOT NULL,
|
||||
snapshot JSON NOT NULL, -- کل قانون در آن نسخه، نه diff
|
||||
valid_from INT NULL,
|
||||
valid_to INT NULL,
|
||||
changed_by INT NULL, -- FK users ON DELETE SET NULL
|
||||
changed_at INT NOT NULL,
|
||||
UNIQUE KEY uniq_policy_version (policy_id, version),
|
||||
KEY idx_pvl_policy (policy_id, version),
|
||||
CONSTRAINT fk_pvl_policy FOREIGN KEY (policy_id) REFERENCES policies(id) ON DELETE CASCADE
|
||||
);
|
||||
```
|
||||
|
||||
`snapshot` کامل است، نه diff: بازسازی نسخهٔ ۳ از یک قانون که الان نسخهٔ ۹ است، باید یک
|
||||
`SELECT` باشد. حجم ناچیز (JSON چند کیلوبایتی × چند ده نسخه).
|
||||
|
||||
## هیچ تغییری در `discount_rules`
|
||||
|
||||
جدول و entity موجود دستنخورده میمانند. `PricingPolicyEngine` **هر دو** را میخواند:
|
||||
|
||||
```php
|
||||
$effects = array_merge(
|
||||
$this->discountEngine->evaluate($ctx), // DiscountRule موجود
|
||||
$this->pricingPolicies->evaluate($ctx), // Policy دستهٔ pricing
|
||||
);
|
||||
$combined = $this->combiner->combine($effects); // ترکیب واحد
|
||||
```
|
||||
|
||||
دلیل عدم مهاجرت در implementation_notes بند ۱.
|
||||
|
||||
## `applied_policy_ids` — تسک ۰۸
|
||||
|
||||
ستون از قبل در `price_snapshots` هست. قرارداد مقدارش اینجا تعیین میشود:
|
||||
|
||||
```json
|
||||
[
|
||||
{ "source": "policy", "id": 12, "version": 3, "name": "تخفیف VIP" },
|
||||
{ "source": "discount_rule", "id": 5, "version": 1, "name": "تخفیف تولد" }
|
||||
]
|
||||
```
|
||||
|
||||
`name` کپی متنی — همان دلیل `price_snapshot_lines.label`: قانون ممکن است حذف شود.
|
||||
|
||||
## ذخیرهٔ قوانین اعمالشده روی خودِ نوبت
|
||||
|
||||
قوانین غیرقیمتی هم باید ثبت شوند (مستند: «هر نوبت فهرست قانونهایی که رویش اعمال شده را
|
||||
ذخیره میکند»)، ولی `price_snapshots` جای قوانین منبع و زمان نیست.
|
||||
|
||||
```sql
|
||||
ALTER TABLE appointments
|
||||
ADD COLUMN applied_policies JSON NULL;
|
||||
```
|
||||
|
||||
همان قرارداد بالا. یک ستون JSON کافی است — هیچ کوئریای روی آن زده نمیشود، فقط برای
|
||||
آدیت و پاسخ به «چرا این نوبت ۷۵ دقیقه شد؟» خوانده میشود.
|
||||
|
||||
## Migration
|
||||
|
||||
```bash
|
||||
ddev exec php bin/console doctrine:migrations:diff --no-interaction
|
||||
ddev exec php bin/console doctrine:migrations:migrate --no-interaction
|
||||
```
|
||||
|
||||
بدون backfill. هیچ قانونی از قبل وجود ندارد و `DiscountRule` ها سر جایشان میمانند.
|
||||
|
||||
## نمونهٔ داده برای تست
|
||||
|
||||
```bash
|
||||
ddev exec php bin/console app:policy:seed-examples --tenant=clinic:12 --force
|
||||
```
|
||||
|
||||
پنج قانون نمونه (یکی از هر دسته جز `selection`)، همه با `active=false` تا کلینیک اول
|
||||
آزمایششان کند:
|
||||
|
||||
```
|
||||
resource | خدمات جراحی به جراح نیاز دارند
|
||||
timing | حداقل مدت درمان پیچیده یک ساعت است
|
||||
spacing | حداقل ۲۱ روز از جلسهٔ قبلی لیزر
|
||||
eligibility | بیمار زیر ۱۸ سال بدون رضایت والدین نمیشود
|
||||
pricing | بیمار VIP ده درصد تخفیف
|
||||
```
|
||||
|
||||
این نمونهها هم مستندات زندهاند و هم داده تست تسک ۱۰.
|
||||
|
||||
## طبقهبندی tenant
|
||||
|
||||
| جدول | وضعیت |
|
||||
|---|---|
|
||||
| `policies` | جفت tenant |
|
||||
| `policy_version_log` | `AGGREGATE_CHILDREN` → ریشه `Policy` |
|
||||
Reference in New Issue
Block a user