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