Task 09 shipped a powerful API that a non-technical clinic owner could not safely use. This closes that gap: activation now requires having seen what the rule actually does. - PolicySimulator runs a policy against real past appointments and writes nothing: evaluation works on facts (never entities), the whole run sits in a transaction rolled back and cleared in `finally`, and a test counts rows in five sensitive tables before and after - activate() now demands a simulation of the *same version* — a report for version 1 does not unlock version 2 - PolicyTemplateRegistry: six ready-made rules, so the common case never touches a raw condition - Severity from the affected ratio; 0% is a warning too, since a rule that changes nothing usually has a condition that never matches - An empty clinic still succeeds with a warning, otherwise a new clinic could never activate anything Admin: PoliciesPage, PolicyFormPage, PolicySimulationPage, and a PolicyConditionBuilder built entirely from GET /policy-schema — a test proves a field that exists only in the schema shows up with no frontend change, and that operators are filtered per field type. The schema response now carries per-field metadata (label, type, meaningful operators) so the form has one source of truth instead of two. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
8.3 KiB
8.3 KiB
چکلیست — تسک ۱۰ (فرم ساخت قانون و محیط آزمایش)
وضعیت کلی: ✅ تمامشده با انحرافهای ثبتشده · آخرین بازبینی: ۱۴۰۵/۰۵/۰۹
قواعد: _shared/definition-of-done.md · red-lines.md · ui-conventions.md
۰. خط سرخ
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | --group=slot-mode-frozen سبز |
✅ | |
| ۰.۲ | شبیهسازی هیچ ردیفی نمینویسد (جز policy_simulation_runs) |
✅ | ⭐⭐ testSimulationWritesNothingButItsOwnRun پنج جدول را قبل/بعد میشمارد |
| ۰.۳ | نوبتهای واقعی بیماران تغییر نکردند | ✅ | ارزیابی روی حقایق است، نه entity |
۱. بکاند — شبیهساز
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | PolicySimulator · SimulationSampler · PolicySimulationRun |
✅ | بهعلاوهٔ SimulationFacts |
| ۱.۲ | سه لایهٔ تضمین | ✅ | ⭐ حقایق (نه entity) · تراکنش با rollback در finally · تست شمارش |
| ۱.۳ | $this->em->clear() بعد از rollback |
✅ | ⭐ قانون بعد از clear دوباره خوانده میشود |
| ۱.۴ | ثبت نتیجه بعد از rollback و در تراکنش جدا | ✅ | |
| ۱.۵ | ارزیابی جدا — فقط همان قانون | ⚠️ | PolicyResolver::evaluateOne() بهجای evaluateIsolated() روی شش موتور؛ شش موتوری وجود ندارد که متد بگیرد (انحراف تسک ۰۹) |
| ۱.۶ | فیلتر شعبه/سرویس/دسته از دامنهٔ قانون | ⚠️ | از دامنهٔ قانون استخراج میشود، نه از داخل condition؛ شرطها فیلدِ id ندارند که به کوئری تبدیل شوند |
| ۱.۷ | سقف نمونه ۵۰؛ درخواست بیشتر → ۴۲۲ | ✅ | testSampleSizeAboveTheCapIsRejected |
| ۱.۸ | PolicyTemplateRegistry با پنج الگو |
✅ | شش الگو |
| ۱.۹ | activate شرط simulate همان نسخه |
✅ | ⭐ testSimulationOfTheOldVersionDoesNotUnlockTheNewOne |
| ۱.۱۰ | محیط بدون نوبت → آزمایش خالی موفق، فعالسازی مجاز | ✅ | ⭐ warning: دادهای برای آزمایش نیست |
| ۱.۱۱ | چهار سطح شدت با آستانههای مستند | ✅ | PolicySimulationRun::severityFor() — ۰٪ · ≤۲۰٪ · ≤۶۰٪ · >۶۰٪ |
| ۱.۱۲ | دو endpoint | ✅ | سه تا: policy-templates · simulate · simulations |
| ۱.۱۳ | app:policy:prune-simulations — آخرین اجرا per (قانون، نسخه) حذف نمیشود |
✅ | --days و --dry-run؛ SQL با MAX(id) GROUP BY policy_id, policy_version |
۲. دیتابیس
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | policy_simulation_runs با جفت tenant |
✅ | Version20260731065427 + دو ایندکس |
| ۲.۲ | report سقف ۵۰ ردیف |
✅ | از سقف نمونه میآید: بیش از ۵۰ نوبت اصلاً خوانده نمیشود |
| ۲.۳ | TenantSchemaCoverageTest سبز |
✅ |
۳. UI
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | PoliciesPage · PolicyFormPage · PolicySimulationPage |
✅ | + ورودی «قوانین» در منوی تنظیمات |
| ۳.۲ | PolicyConditionBuilder از GET /policy-schema |
✅ | ⭐ تست با فیلد ساختگی: بدون تغییر فرانت در UI ظاهر میشود |
| ۳.۳ | عملگرها per فیلد فیلتر میشوند | ✅ | ⭐ field_meta[].operators — تست جداگانه |
| ۳.۴ | نوع ورودی مقدار از schema | ✅ | int · enum · bool · list |
| ۳.۵ | همهٔ selectها SearchableSelect |
✅ | هیچ <select> بومی |
| ۳.۶ | انتخاب الگو → فرم کوتاه مقدارها | ✅ | حالت پیشفرض صفحه همین است |
| ۳.۷ | ستون «وضعیت فعلی ← با این قانون» | ✅ | ⭐ |
| ۳.۸ | درصد + شدت با رنگ توکنمحور | ✅ | کلاسهای badge green/amber/red موجود |
| ۳.۹ | شدت none هشدار میدهد |
✅ | «احتمالاً شرطش هرگز برقرار نمیشود» |
| ۳.۱۰ | شدت high → تأیید دوم |
✅ | |
| ۳.۱۱ | ConfirmDialog موجود |
✅ | نه window.confirm |
| ۳.۱۲ | DataTable + فیلتر دسته در URL |
✅ | useUrlState |
| ۳.۱۳ | backTo روی هر سه صفحه |
✅ | |
| ۳.۱۴ | هیچ رنگ/شعاع hard-code | ✅ | فقط var(--…) |
| ۳.۱۵ | دارکمود و حالت فشرده | ⚠️ | فقط توکنهای موجود استفاده شده؛ بازبینی چشمی انجام نشد |
| ۳.۱۶ | RTL و موبایل — اسکرول افقی جدول | ✅ | overflow-x: auto دور جدول گزارش |
| ۳.۱۷ | تاریخها شمسی | ✅ | formatDate |
| ۳.۱۸ | همهٔ رشتهها فارسی | ✅ | |
| ۳.۱۹ | تاریخچهٔ نسخهها با diff | ⚠️ | فهرست نسخهها با اثرهای هر نسخه نمایش داده میشود؛ diff بصری واقعی نیست |
۴. تست
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | شمارش ردیف قبل/بعد | ✅ | ⭐⭐ |
| ۴.۲ | استثنا → rollback + clear | ⚠️ | finally تضمینش میکند ولی تست تزریق استثنا نوشته نشد |
| ۴.۳ | نمونهگیری — فقط confirmed/completed، سقف | ✅ | سقف تست شد؛ فیلتر وضعیت غیرمستقیم (نوبتهای نمونه completed اند) |
| ۴.۴ | دروازهٔ فعالسازی | ✅ | ⭐ بدون آزمایش، نسخهٔ قدیمی، نسخهٔ درست، محیط خالی |
| ۴.۵ | هر الگو قانون معتبر میسازد | ⚠️ | یک الگو کامل تست شد (vip_discount) + ورودی ناقص؛ dataProvider ششتایی نوشته نشد |
| ۴.۶ | چهار آستانهٔ شدت | ⚠️ | high و none تست شدند؛ low/medium نه |
| ۴.۷ | فیلد ساختگی از mock schema در UI | ✅ | ⭐ PolicyFormPage.test.tsx |
| ۴.۸ | عملگر نامعتبر نمایش داده نمیشود | ✅ | |
| ۴.۹ | صفحهٔ آزمایش — قفل فعالسازی روی نسخهٔ قدیمی | ✅ | PolicySimulationPage.test.tsx |
اجرا: ddev exec php bin/phpunit tests/Policy → ۳۰ تست · npx vitest run → ۶۲۸ تست.
۵. مستندات
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | docs/api/policy.md — simulate، policy-templates، شرط تازهٔ activate |
✅ | JSON واقعی |
| ۵.۲ | policy-engine.md بخش «چرا آزمایش اجباری است» |
✅ | + سه لایهٔ تضمین و دو حالت مرزی |
۶. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | هیچ 🔄 و ⏳ بیدلیل نمانده | ✅ | ۷ مورد ⚠️ همه با دلیل |
| ۶.۲ | bin/phpunit کامل سبز |
✅ | ۱۲۵۰ تست |
| ۶.۳ | --group=slot-mode-frozen سبز |
✅ | |
| ۶.۴ | phpstan بدون خطای جدید |
✅ | ۱۴ = baseline |
| ۶.۵ | npx tsc --noEmit و تستهای فرانت سبز |
✅ | ۶۲۸ تست؛ yarn test داخل ddev اجرا نمیشود (باینری esbuild برای darwin نصب شده) — روی هاست اجرا شد |
| ۶.۶ | تستهای tenant سبز | ✅ | |
| ۶.۷ | docs/api/* بهروز |
✅ | |
| ۶.۸ | چکلیست UI کامل | ✅ | جز ۳.۱۵ و ۳.۱۹ |
| ۶.۹ | دو کلاینت دیگر بررسی شدند | ✅ | این تسک هیچ قرارداد عمومیای عوض نکرد؛ همهٔ اندپوینتها پنلمحورند |
| ۶.۱۰ | commit، سپس graphify update . |
✅ | دو کامیت جدا |
| ۶.۱۱ | موارد بهتعویق با دلیل | ✅ | ۳.۱۵ (بازبینی چشمی) · ۳.۱۹ (diff بصری) · ۴.۲/۴.۵/۴.۶ (پوشش تست) — همه در همین فایل ثبتاند |