- 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.
98 lines
6.3 KiB
Markdown
98 lines
6.3 KiB
Markdown
# تسک ۰۹ — موتور قوانین ششدستهای
|
|
|
|
**فاز:** ۲ (قوانین) · **وابستگی:** ۰۵، ۰۶، ۰۸ · **زمان:** ۲۰-۲۴ ساعت
|
|
|
|
---
|
|
|
|
## هدف
|
|
|
|
مستند بند ۸: شش دستهٔ قانون، هر کدام با نقطهٔ اجرای مشخص، شرط از فهرست بسته،
|
|
اولویتدار، نسخهبندیشده، و **بدون کد دلخواه**.
|
|
|
|
| دسته | کِی اجرا میشود | نقطهٔ اتصال در کد |
|
|
|---|---|---|
|
|
| انتخاب (`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 تسکهای ۰۴ تا ۰۸
|