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,97 @@
|
||||
# تسک ۰۹ — موتور قوانین ششدستهای
|
||||
|
||||
**فاز:** ۲ (قوانین) · **وابستگی:** ۰۵، ۰۶، ۰۸ · **زمان:** ۲۰-۲۴ ساعت
|
||||
|
||||
---
|
||||
|
||||
## هدف
|
||||
|
||||
مستند بند ۸: شش دستهٔ قانون، هر کدام با نقطهٔ اجرای مشخص، شرط از فهرست بسته،
|
||||
اولویتدار، نسخهبندیشده، و **بدون کد دلخواه**.
|
||||
|
||||
| دسته | کِی اجرا میشود | نقطهٔ اتصال در کد |
|
||||
|---|---|---|
|
||||
| انتخاب (`selection`) | موقع انتخاب آیتم | `ServiceSelectionValidator` (تسک ۰۴) |
|
||||
| صلاحیت بیمار (`eligibility`) | قبل از جستجوی وقت | `AvailabilityEngine::search` ابتدا · `BookingService::confirm` مرحلهٔ ۳ |
|
||||
| منبع (`resource`) | موقع ساخت برنامه | `AppointmentPlanBuilder` مرحلهٔ ۷ (تسک ۰۵) |
|
||||
| زمان (`timing`) | موقع ساخت برنامه | همان |
|
||||
| فاصلهٔ زمانی (`spacing`) | موقع جستجوی وقت | `AvailabilityEngine` مرحلهٔ ۶ |
|
||||
| قیمت (`pricing`) | بعد از نهایی شدن برنامه | `PricingEngine` مرحلهٔ ۳ (تسک ۰۸) |
|
||||
|
||||
همهٔ این قلابها در تسکهای ۰۴ تا ۰۸ از قبل بهصورت no-op گذاشته شدهاند. این تسک
|
||||
پُرشان میکند.
|
||||
|
||||
## وضعیت فعلی
|
||||
|
||||
`DiscountRule` + `DiscountEngine` تنها موتور قانون موجود است و **الگوی درستی** دارد:
|
||||
|
||||
```php
|
||||
// src/Discount/Entity/DiscountRule.php
|
||||
public const TYPES = [TYPE_PATIENT_TAG, TYPE_INVOICE_AMOUNT, TYPE_SPECIFIC_PATIENT,
|
||||
TYPE_OCCASION, TYPE_SERVICE, TYPE_VISIT_COUNT]; // enum بسته ✅
|
||||
private int $priority; // ✅
|
||||
private bool $combinable; // ✅
|
||||
private ?int $validFrom; // ✅
|
||||
private ?int $validTo; // ✅
|
||||
```
|
||||
|
||||
فقط دستهٔ «قیمت» را پوشش میدهد، نسخهبندی ندارد، و محیط آزمایش ندارد.
|
||||
|
||||
## دامنه
|
||||
|
||||
**هست:**
|
||||
- `Policy` — قانون با دسته، شرط، نتیجه، اولویت، بازهٔ اعتبار، نسخه
|
||||
- `PolicyVersionLog` — تاریخچهٔ نسخهها (قانون ویرایش نمیشود، نسخه میگیرد)
|
||||
- `ConditionEvaluator` — ارزیابی شرط از فهرست بسته
|
||||
- شش موتور دسته، هر کدام یک کلاس
|
||||
- حل تناقض: اولویت → اختصاصیبودن → قدمت
|
||||
- ترکیب اثرها طبق جدول مستند بند ۸
|
||||
- ثبت قانونهای اعمالشده روی نوبت (`applied_policy_ids` تسک ۰۸)
|
||||
|
||||
**نیست:** فرم ساخت قانون و محیط آزمایش (تسک ۱۰ — همان تسک UI است).
|
||||
`DiscountRule` موجود **مهاجرت نمیکند**؛ کنار `Policy` دستهٔ `pricing` زندگی میکند
|
||||
(دلیل در implementation_notes).
|
||||
|
||||
## Endpoint ها
|
||||
|
||||
| متد | مسیر | توضیح |
|
||||
|---|---|---|
|
||||
| GET | `/api/v1/policies` | لیست با فیلتر دسته/وضعیت |
|
||||
| POST | `/api/v1/policy` | ساخت (نسخهٔ ۱) |
|
||||
| GET | `/api/v1/policy/{uuid}` | جزئیات + تاریخچهٔ نسخه |
|
||||
| POST | `/api/v1/policy/{uuid}/version` | نسخهٔ جدید با تاریخ شروع |
|
||||
| POST | `/api/v1/policy/{uuid}/activate` \| `/deactivate` | |
|
||||
| GET | `/api/v1/policy-schema` | فهرست بستهٔ فیلدها، عملگرها و اثرها per دسته |
|
||||
|
||||
`GET /policy-schema` برای تسک ۱۰ حیاتی است: فرم ساخت قانون از همین schema ساخته میشود،
|
||||
نه از کد hard-coded در فرانت.
|
||||
|
||||
## معیار پذیرش
|
||||
|
||||
- ✅ موفق: قانون «خدمات دسته جراحی به جراح نیاز دارند» (دسته `resource`) →
|
||||
`POST /appointment-plan/preview` برای سرویسی در آن دسته، یک نیازمندی اضافه دارد.
|
||||
- ✅ موفق: قانون «حداقل ۷ روز از جلسهٔ قبلی» (دسته `spacing`) → جستجوی وقت برای بیماری که
|
||||
۳ روز پیش جلسه داشته، روزهای زودتر از روز هفتم را برنمیگرداند.
|
||||
- ✅ موفق: قانون «بیمار زیر ۱۸ سال بدون رضایت والدین نمیشود» (دسته `eligibility`) →
|
||||
`confirm` با `422` و پیام قابل فهم رد میشود.
|
||||
- ✅ موفق: قانون «بیمار VIP ۱۰٪ تخفیف» (دسته `pricing`) → در `price_snapshot_lines` یک
|
||||
ردیف `discount` با نام قانون ظاهر میشود و شناسه+نسخه در `applied_policy_ids` ثبت میشود.
|
||||
- ✅ موفق (**قانون پنجم مستند**): بعد از ثبت نوبت، قانون نسخهٔ ۲ میگیرد →
|
||||
نوبت قبلی همان نسخهٔ ۱ را در `applied_policy_ids` دارد و فاکتورش تغییر نمیکند.
|
||||
- ✅ موفق: دو قانون «حداقل مدت» با مقادیر ۴۵ و ۶۰ دقیقه → **بیشترین** برنده است (۶۰).
|
||||
- ✅ موفق: دو قانون «اضافه کردن زمان» ۱۰ و ۵ دقیقه → **جمع** میشوند (۱۵).
|
||||
- ✅ موفق: یک قانون «ممنوعیت» → کل عملیات رد میشود، حتی اگر ده قانون مجازکننده باشند.
|
||||
- ❌ خطا: شرط با فیلد خارج از فهرست بسته → `422` با نام فیلد مجاز.
|
||||
- ❌ خطا: نتیجه با نوع اثر ناسازگار با دسته → `422`.
|
||||
- ⚠️ مرزی: دو قانون با اولویت مساوی → اختصاصیتر (شعبه بر محیط، سرویس بر دسته) برنده.
|
||||
- ⚠️ مرزی: باز هم مساوی → قانون **قدیمیتر** برنده (مستند بند ۸).
|
||||
- ⚠️ مرزی: قانون با `valid_from` آینده → در محاسبهٔ امروز اعمال نمیشود.
|
||||
- ⚠️ مرزی: هیچ قانونی وجود ندارد → همهچیز مثل قبل کار میکند (تست سازگاری).
|
||||
- ⚠️ مرزی: قانون `spacing` باید **قابل تبدیل به کوئری** باشد؛ ارزیابی per-slot در PHP
|
||||
برای ۹۰ روز غیرقابل قبول است (مستند بند ۸ صریح).
|
||||
|
||||
## خروجی
|
||||
|
||||
- `src/Policy/`
|
||||
- `docs/api/policy.md` + سند معماری `docs/architecture/policy-engine.md`
|
||||
- پر کردن همهٔ قلابهای no-op تسکهای ۰۴ تا ۰۸
|
||||
Reference in New Issue
Block a user