perf(reports): read every resource's calendar in one batch, and close the owed tests
Writing the query-count test that task 14 owed showed the growth was real: one resource cost 10 queries, six cost 33 — about five per resource, because the available-minutes figure walked each resource's calendar on its own. Holidays, tenant overrides and branch hours are identical for every resource in a report, so they now load once outside the loop; shifts and exceptions load for all resources in one query each. The batched path is a new method rather than a change to rawAvailability, which the booking engine also calls. The test pins the shape of the growth, not an exact count. Also landed: - app:segment:seed-templates with beauty, dental and physio presets. Building four segments and their requirements by hand is the first thing a new clinic must do and the most tedious; this gives them something to edit instead of an empty page. It refuses to touch a service that already has segments unless --force, and it will not invent resource types the tenant never defined. - book-all is all-or-nothing, proven rather than asserted: with a calendar open one day a week and a 1-2 day protocol gap, session one finds a slot and session two cannot, and every session must come back planned. - credit_refundable: false takes the credit back with a negative adjustment and deletes nothing — the ledger stays append-only. - the segments editor has frontend tests, including that it sends back what the user sees and renders read-only without the permission. useBranches now returns [] for a non-array payload instead of throwing "branches.map is not a function" and taking the page down with it. BookingLocationsScanTest built a Clinic around a Doctor loaded from a different manager, which Doctrine treats as a new entity; it flushed fine most runs and failed on cascade in others. It now loads the doctor from the same manager. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -147,10 +147,24 @@
|
||||
| `segment_templates` | جفت محیط (از بخشِ سرویس مشتق میشود) |
|
||||
| `segment_requirements` | `AGGREGATE_CHILDREN` — ریشه `SegmentTemplate` |
|
||||
|
||||
## الگوی نمونه
|
||||
|
||||
```bash
|
||||
ddev exec php bin/console app:segment:seed-templates --service=<uuid> --preset=beauty
|
||||
```
|
||||
|
||||
سه الگو: `beauty` (آمادهسازی · بیحسی · انتظار · کار اصلی · تمیزکاری) · `dental`
|
||||
(معاینه · درمان · ضدعفونی یونیت) · `physio` (ارزیابی · جلسهٔ درمان · استراحت).
|
||||
|
||||
نقطهٔ شروع است نه پیکربندی نهایی: کلینیک از روی چیزی که میبیند ویرایش میکند، نه از روی
|
||||
صفحهٔ خالی. روی سرویسی که از قبل بخش دارد **کاری نمیکند** مگر `--force` — بازنویسی
|
||||
خاموشِ چیزی که کلینیک خودش ساخته، بدترین رفتار ممکن است. نقشی که آن محیط تعریف نکرده،
|
||||
ساخته نمیشود و در خروجی گزارش میشود؛ نوع منبع تصمیم کلینیک است.
|
||||
|
||||
## تستها
|
||||
|
||||
```bash
|
||||
ddev exec php bin/phpunit tests/Appointment/AppointmentPlanTest.php # ۱۴ تست
|
||||
ddev exec php bin/phpunit tests/Appointment/AppointmentPlanTest.php # ۱۵ تست
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -194,5 +194,5 @@
|
||||
## تستها
|
||||
|
||||
```bash
|
||||
ddev exec php bin/phpunit tests/Cancellation # ۱۴ تست
|
||||
ddev exec php bin/phpunit tests/Cancellation # ۱۷ تست
|
||||
```
|
||||
|
||||
@@ -145,3 +145,14 @@
|
||||
```bash
|
||||
ddev exec php bin/phpunit tests/Report # ۱۶ تست
|
||||
```
|
||||
|
||||
## هزینهٔ کوئری
|
||||
|
||||
`occupied` و `active` هرکدام یک کوئری `GROUP BY` اند، و تقویم همهٔ منابع هم دستهای خوانده
|
||||
میشود (`ResourceAvailabilityService::rawAvailabilityForAll`): تعطیلات و استثناهای محیط و
|
||||
ساعت شعبه برای همهٔ منابع یکیاند و بیرون حلقه میآیند، شیفت و استثنای هر منبع هم با یک
|
||||
کوئری برای همه.
|
||||
|
||||
پیش از این هر منبع پنج کوئری اضافه میآورد و گزارشِ یک کلینیک چهلمنبعی دویست کوئری
|
||||
میشد. تست `testQueryCountDoesNotGrowWithTheNumberOfResources` همین را نگه میدارد —
|
||||
عددِ دقیق را پین نمیکند، فقط رشدِ خطی را رد میکند.
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
| # | مورد | وضعیت | یادداشت |
|
||||
|---|---|---|---|
|
||||
| ۴.۱ | چهار جدول | ✅ | `Version20260730143130` |
|
||||
| ۴.۲ | `valid_from`/`valid_to` روی شیفت از روز اول | ⏳ | شیفت فصلی پیاده نشد. دلیلِ «از روز اول لازم است» برقرار نیست: دو ستون تهیپذیر بعداً بدون backfill اضافه میشوند (NULL = همیشه معتبر). مقصد: هر تسکی که واقعاً شیفت فصلی بخواهد |
|
||||
| ۴.۲ | `valid_from`/`valid_to` روی شیفت از روز اول | — | شیفت فصلی پیاده نشد. دلیلِ «از روز اول لازم است» برقرار نیست: دو ستون تهیپذیر بعداً بدون backfill اضافه میشوند (NULL = همیشه معتبر). مقصد: هر تسکی که واقعاً شیفت فصلی بخواهد |
|
||||
| ۴.۳ | نوع `blocked` در استثناها هست | ✅ | `closure` همان نقش را دارد؛ چهار نوع: leave/absence/maintenance/closure |
|
||||
| ۴.۴ | `national_holidays` در `GlobalTables::ENTITIES` با دلیل | ✅ | |
|
||||
| ۴.۵ | `resource_calendars` در `AGGREGATE_CHILDREN` | ✅ | |
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
| ۱.۱۲ | سه endpoint | ✅ | `GET/PUT segments` + `POST appointment-plan/preview` |
|
||||
| ۱.۱۳ | `patient_facing_minutes` در پاسخ | ⚠️ | در API نیست؛ UI از `patient_present` هر بخش خودش جمع میزند |
|
||||
| ۱.۱۴ | سقفها | ⚠️ | ۴۸۰ دقیقه اعمال میشود؛ سقف ۲۰ بخش / ۱۰ نیازمندی / ۲۰ آیتم اعمال نشد |
|
||||
| ۱.۱۵ | `app:segment:seed-templates` | ⏳ | ساخته نشد؛ با UI تازه، الگوی نمونه دستی ساختنی است |
|
||||
| ۱.۱۵ | `app:segment:seed-templates` | ✅ | سه الگو (`beauty`/`dental`/`physio`)؛ بدون `--force` بازنویسی نمیکند |
|
||||
| ۱.۱۶ | `TenantOwnershipChecker` روی هر uuid | ✅ | |
|
||||
|
||||
## ۲. دیتابیس
|
||||
@@ -80,7 +80,7 @@
|
||||
| ۴.۵ | سرویس بدون الگو | ✅ | ⭐ |
|
||||
| ۴.۶ | حل نیازمندی — مهارت، بیکاندید، جنسیت، محیط دیگر | ✅ | |
|
||||
| ۴.۷ | سقفها → ۴۲۲ | ⚠️ | سقف ۴۸۰ دقیقه تست شد؛ بقیه سقف ندارند (۱.۱۴) |
|
||||
| ۴.۸ | تست فرانت ویرایشگر بخشها | ⏳ | تب ساخته شد ولی تست ندارد؛ منطق سنگینش در بکاند است که ۱۱ تست دارد |
|
||||
| ۴.۸ | تست فرانت ویرایشگر بخشها | ✅ | بارگذاری، ذخیرهٔ همان چیزی که کاربر میبیند، و حالت فقطخواندنی |
|
||||
|
||||
**اجرا:** `ddev exec php bin/phpunit tests/Appointment/AppointmentPlanTest.php` → ۱۱ تست.
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|---|---|---|---|
|
||||
| ۰.۱ | `--group=slot-mode-frozen` سبز | ✅ | |
|
||||
| ۰.۲ | `PatientSession` موجود دستنخورده | ✅ | «مراجعهٔ انجامشده» ≠ «جلسهٔ دوره»؛ هیچ فایلی از `src/Patient` تغییر نکرد |
|
||||
| ۰.۳ | رویدادهای تسک ۰۷ بعد از commit منتشر میشوند | ⏳ | تسک ۱۴ رویدادها را میسازد؛ فعلاً `book-all` هیچ رویدادی منتشر نمیکند، پس خطر «هشت پیامک در rollback» وجود ندارد |
|
||||
| ۰.۳ | رویدادهای تسک ۰۷ بعد از commit منتشر میشوند | ✅ | صندوق خروجی تسک ۱۴ همین را تضمین میکند: `record()` فلاش نمیکند، پس rollbackِ `book-all` رویدادی جا نمیگذارد |
|
||||
| ۰.۴ | `abandon` نوبتهای `booked` را لغو نمیکند | ✅ | مستند شد؛ لغو ظرفیت باید تصمیم صریح باشد نه اثر جانبی |
|
||||
|
||||
## ۱. بکاند
|
||||
@@ -79,8 +79,8 @@
|
||||
|---|---|---|---|
|
||||
| ۴.۱ | شروع دوره — ۸ جلسه، دورهٔ دوم ۴۲۲ با شناسهٔ دورهٔ موجود | ✅ | |
|
||||
| ۴.۲ | snapshot پروتکل | ✅ | ⭐ |
|
||||
| ۴.۳ | لنگر متحرک و نزدیکترین به ایدهآل | ⚠️ | لنگر پیشنهاد و محاسبهٔ افق تست شد؛ اجرای کامل `book-all` با منابع و ساعت کاری هنوز تست ندارد |
|
||||
| ۴.۴ | شکست جلسهٔ N → rollback | ⏳ | با ۴.۳ یک بسته است |
|
||||
| ۴.۳ | لنگر متحرک و نزدیکترین به ایدهآل | ⚠️ | لنگر پیشنهاد، افق، و مسیر شکستِ `book-all` تست دارند؛ مسیر موفقِ چندجلسهای هنوز نه |
|
||||
| ۴.۴ | شکست جلسهٔ N → rollback | ✅ | ⭐ تقویم فقط یکروزه: جلسهٔ اول وقت پیدا میکند، دومی نه، و **هیچ** جلسهای رزرو نمیماند |
|
||||
| ۴.۵ | سقف ۹۰ روز | ✅ | `testSessionsBeyondTheHorizonAreSkippedNotFailed` — جلسهٔ بیرون افق رد میشود، دوره دستنخورده میماند |
|
||||
| ۴.۶ | لنگر `completed` + هشدار عبور از max | ✅ | ⭐ |
|
||||
| ۴.۷ | پیشرفت دوره | ✅ | «۳ از ۸» + `next_params` |
|
||||
|
||||
@@ -53,10 +53,10 @@
|
||||
| ۳.۴ | سقف `notify_count` | ✅ | ۳ بار |
|
||||
| ۳.۵ | پیامک async بیرون تراکنش لغو | ✅ | ⭐ `dispatchAsync` روی messenger؛ لغو تراکنش سراسری هم ندارد (۱.۷) |
|
||||
| ۳.۶ | ترتیب `priority DESC, created_at ASC` | ✅ | |
|
||||
| ۳.۷ | فیلتر `preferred_day_parts` | ⏳ | ذخیره و نمایش میشود ولی در تطبیق اعمال نمیشود — بدون منطقهٔ زمانی شعبه، «عصر» تعریف قطعی ندارد؛ به تسک ۱۴ موکول شد |
|
||||
| ۳.۸ | `converted` خودکار روی رزرو بیمار | ⏳ | نیازمند رویداد `AppointmentBooked` که تسک ۱۴ میسازد |
|
||||
| ۳.۹ | `app:waitlist:expire` روزانه | ⏳ | ردیف منقضی در تطبیق نمیآید (`desiredTo >= now`)، پس اثر عملی ندارد؛ پاکسازی با تسک ۱۴ |
|
||||
| ۳.۱۰ | بازهٔ بیش از ۹۰ روز → ۴۲۲ | ⏳ | فقط بازهٔ گذشته و وارونه رد میشود |
|
||||
| ۳.۷ | فیلتر `preferred_day_parts` | ✅ | ⭐ مرزها در `WaitlistEntry::DAY_PARTS` با ساعت **محلی شعبه**؛ فیلتر پیش از بریدن به ده نفر اعمال میشود |
|
||||
| ۳.۸ | `converted` خودکار روی رزرو بیمار | ✅ | ⭐ از رویداد `AppointmentBooked`، نه از داخل `BookingService` — تبدیل نباید بتواند نوبت واقعی را rollback کند |
|
||||
| ۳.۹ | `app:waitlist:expire` روزانه | ✅ | سرویس + دستور + پیام روزانهٔ زمانبند؛ وضعیت عوض میشود نه حذف |
|
||||
| ۳.۱۰ | بازهٔ بیش از ۹۰ روز → ۴۲۲ | ✅ | همان افق رزرو تسک ۱۲ |
|
||||
| ۳.۱۱ | هفت endpoint | ✅ | ۱۰ تا: سیاست GET/PUT + override + preview + cancel + no-show + لیست انتظار GET/POST/DELETE/matches |
|
||||
|
||||
## ۴. دیتابیس
|
||||
@@ -67,7 +67,7 @@
|
||||
| ۴.۲ | ایندکس تطبیق لیست انتظار | ✅ | |
|
||||
| ۴.۳ | ایندکس پنجرهٔ عدم حضور | ✅ | |
|
||||
| ۴.۴ | `risk_tag_uuid` بدون FK | ✅ | همان الگوی موجود پروژه |
|
||||
| ۴.۵ | دستور seed سیاست پیشفرض | ⏳ | لازم نشد: نبودِ سیاست یعنی «بدون جریمه»، پس رفتار پیشفرض از قبل امن است |
|
||||
| ۴.۵ | دستور seed سیاست پیشفرض | — | لازم نشد: نبودِ سیاست یعنی «بدون جریمه»، پس رفتار پیشفرض از قبل امن است |
|
||||
| ۴.۶ | `TenantSchemaCoverageTest` سبز | ✅ | |
|
||||
|
||||
## ۵. UI
|
||||
@@ -77,7 +77,7 @@
|
||||
| ۵.۱ | `CancellationPolicyPage` | ⚠️ | سیاست محیط کامل است؛ جدول override سرویسها ساخته نشد (اندپوینتش هست) |
|
||||
| ۵.۲ | `WaitlistPage` | ⚠️ | لیست با فیلتر وضعیت هست؛ تب «قابل تطبیق» ساخته نشد (اندپوینت `matches` هست) |
|
||||
| ۵.۳ | دکمهٔ لغو با محتوای preview | ✅ | ⭐ `CancelAppointmentDialog` جریمه و بازگشت اعتبار را **پیش از** تأیید نشان میدهد؛ سرویس لغو هم `reason` میگیرد و ردیف تایملاین مینویسد (قبلاً این مسیر هیچ ردی نمیگذاشت) |
|
||||
| ۵.۴ | نشان پرریسک در پروندهٔ بیمار | ⏳ | برچسب از `TenantTag` میآید و در پرونده دیده میشود، ولی شمارش عدم حضور نمایش داده نمیشود |
|
||||
| ۵.۴ | نشان پرریسک در پروندهٔ بیمار | ✅ | ⭐ `GET /patient/{uuid}/no-shows` + نشان در بنر پرونده؛ فقط وقتی `count > 0` |
|
||||
| ۵.۵ | `ConfirmDialog` موجود | ✅ | جای دیگری مودال دستساز ساخته نشد |
|
||||
| ۵.۶ | فیلتر در URL | ✅ | `useUrlState` |
|
||||
| ۵.۷ | تاریخ شمسی و مبلغ | ✅ | `formatDate` · `PriceInput` |
|
||||
@@ -94,14 +94,14 @@
|
||||
|---|---|---|---|
|
||||
| ۶.۱ | محاسبهٔ جریمه — پنج حالت | ✅ | ⭐ داخل پنجره، بیرون پنجره، کلینیک، سقف پرداختی، بدون سیاست |
|
||||
| ۶.۲ | لغو — آزادسازی، کیف پول، ۴۰۹، گذشته ۴۲۲ | ✅ | + «موجودی ناکافی لغو را شکست نمیدهد» |
|
||||
| ۶.۳ | اولویت سیاست سرویس بر محیط | ⏳ | `resolve()` نوشته شد ولی تست اختصاصی ندارد |
|
||||
| ۶.۳ | اولویت سیاست سرویس بر محیط | ✅ | از راه عددِ خروجی سنجیده میشود نه از راه resolver |
|
||||
| ۶.۴ | عدم حضور — سوم برچسب، دوبار یک رکورد | ✅ | ⭐ پنجرهٔ ۱۲ ماه تست نشد |
|
||||
| ۶.۵ | بیمار پرریسک رزرو موفق دارد | ⏳ | برچسب هیچجا بررسی نمیشود، پس مسدودسازی ممکن نیست |
|
||||
| ۶.۵ | بیمار پرریسک رزرو موفق دارد | ✅ | `testATaggedPatientCanStillBook` |
|
||||
| ۶.۶ | لیست انتظار — ترتیب، سقف اطلاع، شعبه | ✅ | فیلتر روزبخش تست نشد (۳.۷) |
|
||||
| ۶.۷ | تبدیل به رزرو | ⏳ | با ۳.۸ |
|
||||
| ۶.۷ | تبدیل به رزرو | ✅ | تبدیل تنگ + idempotent، هر دو تست دارند |
|
||||
| ۶.۸ | شکست پیامک لغو را rollback نمیکند | ⚠️ | معماریاش تضمین میکند (async، بدون تراکنش سراسری) ولی تست تزریق خطا نوشته نشد |
|
||||
| ۶.۹ | تست کیف پول موجود سبز ماند | ✅ | ⭐ |
|
||||
| ۶.۱۰ | سیاست اعتبار روی دوره | ⏳ | مسیرش هست (`credit_refundable`)، تست ترکیبی با دوره نوشته نشد |
|
||||
| ۶.۱۰ | سیاست اعتبار روی دوره | ✅ | `credit_refundable: false` با ردیف `adjustment` پس میگیرد؛ هیچ ردیفی حذف نمیشود |
|
||||
|
||||
**اجرا:** `tests/Cancellation` → ۱۴ تست · `tests/Waitlist` → ۹ تست.
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
| ۸.۵ | `npx tsc --noEmit` و تستهای فرانت سبز | ✅ | ۶۳۲ تست |
|
||||
| ۸.۶ | تستهای tenant سبز | ✅ | |
|
||||
| ۸.۷ | `docs/api/*` بهروز | ✅ | |
|
||||
| ۸.۸ | چکلیست UI کامل | ⚠️ | جز ۵.۱، ۵.۲، ۵.۴ |
|
||||
| ۸.۸ | چکلیست UI کامل | ⚠️ | جز ۵.۱ (جدول override) و ۵.۲ (تب «قابل تطبیق») |
|
||||
| ۸.۹ | سایت باید preview لغو را نشان دهد | ⏳ | اندپوینتها پنلمحورند؛ اتصال `nobat724_front` بررسی نشد |
|
||||
| ۸.۱۰ | `clinic-pro-tauri` بررسی شد | ⏳ | همان |
|
||||
| ۸.۱۱ | commit، سپس `graphify update .` | ✅ | دو کامیت جدا |
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
| ۲.۵ | `released` شمرده نمیشود | ✅ | `BLOCKING_STATUSES` |
|
||||
| ۲.۶ | `available = 0` → `utilization = null` | ✅ | ⭐ تست دارد |
|
||||
| ۲.۷ | مرز بازه | ✅ | همپوشانی بازهای (`start < to AND end > from`) — دقیقتر از مرز روی یک سر |
|
||||
| ۲.۸ | کوئری تجمعی بدون پیمایش | ⚠️ | `occupied` و `active` هر کدام یک کوئری `GROUP BY` اند؛ ولی `available` per منبع از تقویم خوانده میشود (منطق شیفت/تعطیلات در SQL نمیآید) |
|
||||
| ۲.۸ | کوئری تجمعی بدون پیمایش | ✅ | ⭐ `rawAvailabilityForAll` تقویم همهٔ منابع را دستهای میخواند؛ تعطیلات/ساعت شعبه بیرون حلقه |
|
||||
| ۲.۹ | تأیید وجود دادهٔ واقعی پیش از پیادهسازی | ✅ | ⭐ `patient_sessions` زمان شروع/پایان مراجعه ندارد، پس مبنای «واقعی» فاصلهٔ اسلات شد و همین در سند نوشته شد |
|
||||
| ۲.۱۰ | آستانههای شدت | ✅ | ۳۰/۱۵/۵ درصد |
|
||||
| ۲.۱۱ | انحراف منفی هم `high` | ✅ | ⭐ قدر مطلق |
|
||||
@@ -72,7 +72,7 @@
|
||||
| ۴.۵ | `utilization = null` → `—` با توضیح | ⚠️ | `—` و `title` هست؛ لینک «تنظیم تقویم» اضافه نشد |
|
||||
| ۴.۶ | لینک اصلاح از `PlanAccuracyPage` | ✅ | ⭐ «ویرایش بخشهای این خدمت» |
|
||||
| ۴.۷ | بازه با `PersianDatePicker` | ⚠️ | انتخابگر بازهٔ آماده (هفته/ماه/سهماه) — برای گزارشی که همیشه «تا امروز» است سادهتر و کمخطاتر |
|
||||
| ۴.۸ | وضعیت در URL | ⏳ | بازه و شعبه در state محلیاند |
|
||||
| ۴.۸ | وضعیت در URL | ✅ | `useUrlState` روی هر دو گزارش |
|
||||
| ۴.۹ | `DataTable` با skeleton و empty state | ✅ | |
|
||||
| ۴.۱۰ | رنگ نمودار از توکنها | — | نمودار ندارد (۴.۱) |
|
||||
| ۴.۱۱ | هیچ رنگ hard-code | ✅ | |
|
||||
@@ -88,10 +88,10 @@
|
||||
|---|---|---|---|
|
||||
| ۵.۱ | صندوق خروجی — rollback، انتشار، شکست، سقف تلاش | ✅ | ⭐ |
|
||||
| ۵.۲ | payload فقط اسکالر | ✅ | مقادیر تودرتو و object حذف میشوند |
|
||||
| ۵.۳ | بهرهوری — سنجهها | ⚠️ | `utilization = null` تست شد؛ سناریوی کامل با اشغال واقعی و `capacity` تست نشد (نیازمند نوبت با بخشهای ثبتشده) |
|
||||
| ۵.۳ | بهرهوری — سنجهها | ✅ | ⭐ اشغال شامل انتظار، «کار مفید» نه — با نوبت و بخشهای واقعی |
|
||||
| ۵.۴ | دقت برنامه — انحراف دوطرفه و نمونهٔ کم | ✅ | ⭐ |
|
||||
| ۵.۵ | دسترسی و بازه | ✅ | ۴۲۲ بازه، ۴۰۳ رویدادها، جداسازی محیط |
|
||||
| ۵.۶ | تعداد کوئری مستقل از تعداد منبع | ⏳ | با ۲.۸ یک بسته است |
|
||||
| ۵.۶ | تعداد کوئری مستقل از تعداد منبع | ✅ | رشدِ خطی رد میشود؛ عددِ دقیق پین نمیشود |
|
||||
|
||||
**اجرا:** `ddev exec php bin/phpunit tests/Report` → ۱۶ تست.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user