Files
clinicpro/docs/new_feture/taskes/task-09-policy-engine/task.md
T
hamed 021d0eb6b2 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.
2026-07-30 11:43:58 +03:30

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 تسک‌های ۰۴ تا ۰۸