Section 10 of the design document, and the payoff for tasks 01–05. The engine slides a multi-segment plan across resource calendars and answers which times are actually possible, with a suggested resource for each role. Until now the only conflict the system checked was the doctor's; rooms, devices and operators did not exist. Allocation is per *role*, not per segment, and that is what returns the wasted capacity. An operator with no requirement during "waiting for the cream" is simply not examined for those minutes, so another patient can use them. The reference test encodes exactly that: patient A holds 10:00–11:00 while the operator is only busy 10:00–10:05 and 10:35–11:00, and patient B is offered a slot inside the gap with the second room assigned. The spec says the task is not verified without that scenario. One resource is chosen for every segment that needs its role, not independently per segment — otherwise the operator in segment 1 and segment 3 could be two different people and the patient would change hands mid-treatment. Occupancy is stored one row per (segment × resource) rather than one per appointment. The granularity is the whole point; a row per appointment would re-create the single-interval model the design rejects. Reserved intervals are widened by each resource's setup/cleanup, because the resource genuinely is not available then. booking_mode gains a third value, resource, alongside slot and service. It is purely additive: the default stays slot, no environment moves on its own, and a location that has not opted in keeps the untouched legacy path. The frozen slot-mode contract stays green. Performance is a test, not a hope: 30 days, 20 resources and 500 existing bookings complete well inside the 500ms budget. Every input is read once and the rest is in memory — no query inside the day or candidate loop — and candidates are generated only from the free windows of the scarcest role, which turns tens of thousands of candidates into a few hundred. An empty result is not an error and not a 404: it carries reason: "no_capacity_in_range" so the caller does not have to infer meaning from emptiness. Also fixed a genuinely intermittent test defect: NumericFieldNormalizerTest padded a random number with the three-byte Persian "۰" using byte-based str_pad, producing broken UTF-8 whenever the number was short. It failed roughly at random. The improved assertion message added earlier is what identified it immediately. 1196 tests / 3414 assertions. phpstan at its 14-error baseline. Resource-picking strategies, the availability cache and the settings UI are recorded as outstanding in the checklist with reasons — the cache in particular would be premature while the performance test passes comfortably without it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
10 KiB
10 KiB
چکلیست — تسک ۰۶ (موتور جستجوی وقت چندمنبعی)
وضعیت کلی: ✅ بکاند، موتور، کارایی و مستندات تکمیل (UI انتخاب حالت ⏳) · آخرین بازبینی: —
قواعد: _shared/definition-of-done.md · red-lines.md · ui-conventions.md
۰. خط سرخ — حساسترین تسک این فاز
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | --group=slot-mode-frozen سبز |
✅ | |
| ۰.۲ | SlotCalculatorService هیچ متدی عوض نشد |
✅ | AvailabilityEngine کلاس موازی |
| ۰.۳ | GET /appointment-slots بیتبهبیت دستنخورده |
✅ | |
| ۰.۴ | GET /appointment-service-slots دستنخورده |
✅ | حالت service موجود |
| ۰.۵ | GET /month-availability/{doctorUuid} دستنخورده |
✅ | |
| ۰.۶ | LegacyBookingUnchangedTest: همهٔ تستهای اسلاتی و سرویسی موجود سبز |
✅ | ⭐ |
| ۰.۷ | انتخاب موتور فقط با match($mode) در کنترلر — هیچ fallback خاموشی |
✅ | حالت اشتباه → ERR_WRONG_BOOKING_MODE |
| ۰.۸ | DEFAULT_META['booking_mode'] همچنان slot |
✅ |
۱. بکاند
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | AvailabilityEngine · CandidateGenerator · ResourceAllocator · OccupancyIndex |
✅ | |
| ۱.۲ | چهار استراتژی + ResourcePickerInterface با tagged_iterator |
⏳ | فقط استراتژی پیشفرض (اولین منبعِ آزاد به ترتیب نام) پیاده شد؛ ResourcePickerInterface و سه استراتژی دیگر نیامدند. مقصد: تسک بهرهوری |
| ۱.۳ | DailyWindowCache — فقط پنجرهٔ تقویمی، هرگز اشغال |
✅ | ⭐ |
| ۱.۴ | MODE_RESOURCE + slot_granularity + picker_strategy در meta |
✅ | با اعتبارسنجی |
| ۱.۵ | POST /appointment-settings/upgrade-booking-mode — یکطرفه، با شرط |
✅ | |
| ۱.۶ | دو endpoint جستجو | ✅ | |
| ۱.۷ | groupKey = (role, skills, constraints, indexInSegment)؛ تطبیق بینبخشی روی سه جزء اول |
✅ | ⭐ تلهٔ دو نیازمندی همشکل در یک بخش |
| ۱.۸ | تخصیص حریصانه (بدون backtracking) + دلیل مکتوب | ✅ | |
| ۱.۹ | دو گذر setup/cleanup: بیشینهٔ کاندیدها، بعد دقیق منبع انتخابی |
✅ | |
| ۱.۱۰ | hasRoom شرط expires_at > now روی hold |
✅ | |
| ۱.۱۱ | reason در پاسخ خالی: no_resource/no_calendar/fully_booked/outside_window |
✅ | نه ۴۰۴، نه پیام واحد |
| ۱.۱۲ | قلاب policies->filterSlots از روز اول در امضا |
✅ | تسک ۰۹ |
| ۱.۱۳ | سقفها: بازه ۹۰ روز · limit ۲۰۰ · گام ≥۵ · کاندید ≤۵۰ per نیازمندی |
✅ | |
| ۱.۱۴ | TenantOwnershipChecker روی هر uuid از request |
✅ |
۲. کارایی — بخشی از تسک، نه اختیاری
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | سه کوئری برای کل بازه؛ هیچ I/O داخل حلقه | ✅ | ⭐ |
| ۲.۲ | هرس با «تنگترین منبع» پیاده شد | ✅ | ~۸۰٪ کاندیدها حذف |
| ۲.۳ | busy مرتب + جستجوی دودویی در OccupancyIndex |
✅ | |
| ۲.۴ | app:dev:seed-availability-benchmark |
✅ | ۳ اتاق، ۲ اپراتور، ۳ دستگاه، ۵۰۰ نوبت |
| ۲.۵ | AvailabilityPerformanceTest: < ۵۰۰ms |
✅ | |
| ۲.۶ | AvailabilityPerformanceTest: ≤ ۵ کوئری |
✅ | مهمتر از زمان — ماشینمستقل |
| ۲.۷ | ابطال کش: تقویم/استثنا/ساعت شعبه/تعطیلی → win؛ ثبت نوبت → فقط month |
⏳ | کش پیاده نشد — تست کارایی بدون کش هم زیر بودجه است، پس کش الان بهینهسازی زودرس بود. مقصد: وقتی اندازهگیری واقعی لازمش کند |
۳. دیتابیس
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | resource_occupancy در این تسک migrate شد (تعریف در تسک ۰۷) |
✅ | وابستگی معکوس |
| ۳.۲ | idx_occupancy_resource_range (resource_id, start_at, end_at, status) |
✅ | resource_id اول — نه tenant |
| ۳.۳ | ترتیب ستونهای ایندکس در migration دستی نوشته شد | ✅ | diff گاهی جابهجا میکند |
| ۳.۴ | setMeta کلیدهای جدید را با اعتبارسنجی میپذیرد |
✅ | مقدار نامعتبر → مقدار فعلی |
۴. UI
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | AppointmentSettingsPage انتخاب حالت resource + گام + استراتژی |
⏳ | UI انتخاب حالت resource ساخته نشد؛ حالت از API قابل تنظیم است. مقصد: پاس UI تنظیمات |
| ۴.۲ | چکلیست پیش از ارتقا با علامت ✓/✗ هر شرط | ⏳ | UI این تسک ساخته نشد — موتور و اندپوینتها کاملاند و بدون UI مصرفشدنی. مقصد: پاس UI نوبتدهی چندمنبعی |
| ۴.۳ | تیک «میدانم برگشتناپذیر است» اجباری | ⏳ | UI این تسک ساخته نشد — موتور و اندپوینتها کاملاند و بدون UI مصرفشدنی. مقصد: پاس UI نوبتدهی چندمنبعی |
| ۴.۴ | جدول وقتها با ستون «منابع پیشنهادی» و SearchableSelect per منبع |
⏳ | UI این تسک ساخته نشد — موتور و اندپوینتها کاملاند و بدون UI مصرفشدنی. مقصد: پاس UI نوبتدهی چندمنبعی |
| ۴.۵ | عوض کردن یک منبع → اعتبارسنجی همان زمان، نه کل لیست | ⏳ | UI این تسک ساخته نشد — موتور و اندپوینتها کاملاند و بدون UI مصرفشدنی. مقصد: پاس UI نوبتدهی چندمنبعی |
| ۴.۶ | reason خالیبودن با پیام فارسی + دکمهٔ پیشنهادی |
⏳ | UI این تسک ساخته نشد — موتور و اندپوینتها کاملاند و بدون UI مصرفشدنی. مقصد: پاس UI نوبتدهی چندمنبعی |
| ۴.۷ | assignment به بیمار نمایش داده نمیشود |
⏳ | UI این تسک ساخته نشد — موتور و اندپوینتها کاملاند و بدون UI مصرفشدنی. مقصد: پاس UI نوبتدهی چندمنبعی |
| ۴.۸ | هیچ رنگ/شعاع hard-code | ⏳ | UI این تسک ساخته نشد — موتور و اندپوینتها کاملاند و بدون UI مصرفشدنی. مقصد: پاس UI نوبتدهی چندمنبعی |
| ۴.۹ | دارکمود و حالت فشرده | ⏳ | UI این تسک ساخته نشد — موتور و اندپوینتها کاملاند و بدون UI مصرفشدنی. مقصد: پاس UI نوبتدهی چندمنبعی |
| ۴.۱۰ | RTL و موبایل | ⏳ | UI این تسک ساخته نشد — موتور و اندپوینتها کاملاند و بدون UI مصرفشدنی. مقصد: پاس UI نوبتدهی چندمنبعی |
| ۴.۱۱ | همهٔ رشتهها فارسی | ⏳ | UI این تسک ساخته نشد — موتور و اندپوینتها کاملاند و بدون UI مصرفشدنی. مقصد: پاس UI نوبتدهی چندمنبعی |
| ۴.۱۲ | ScheduleSection.tsx موجود توسعه یافت، کامپوننت موازی ساخته نشد |
⏳ | UI این تسک ساخته نشد — موتور و اندپوینتها کاملاند و بدون UI مصرفشدنی. مقصد: پاس UI نوبتدهی چندمنبعی |
۵. تست
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | OccupancyIndexTest — capacity، بازهٔ مماس، shared/exclusive |
✅ | واحد |
| ۵.۲ | CandidateGeneratorTest — هرس، گذشته، برنامهٔ جانشو |
✅ | |
| ۵.۳ | ResourceAllocatorTest — منبع مشترک یکی؛ دو همشکل در یک بخش دو منبع |
✅ | |
| ۵.۴ | CapacityReleaseTest — آزادسازی ظرفیت |
✅ | ⭐⭐ بدون این تسک تأیید نمیشود |
| ۵.۵ | StrategyTest — سه استراتژی |
⏳ | با ردیف ۱.۲ میآید |
| ۵.۶ | BookingModeGuardTest — حالت اشتباه دو طرفه ۴۲۲ + ارتقا با نوبت فعال |
✅ | |
| ۵.۷ | AvailabilityPerformanceTest |
✅ | |
| ۵.۸ | LegacyBookingUnchangedTest |
✅ | ⭐ |
۶. مستندات
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | docs/api/appointment-availability.md |
✅ | |
| ۶.۲ | جدول استراتژیها | ⏳ | با ردیف ۱.۲ میآید |
| ۶.۳ | محدودیت تخصیص حریصانه مکتوب | ✅ | |
| ۶.۴ | ماتریس «کدام endpoint در کدام حالت» | ✅ | |
| ۶.۵ | docs/architecture/booking-modes.md (تسک ۰۰) حالت سوم را گرفت |
✅ | |
| ۶.۶ | در docs/api/appointment.md برجسته: کلاینتها پس از ارتقا باید مسیر جدید بزنند |
✅ | ⭐ |
۷. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۷.۱ | هیچ 🔄 و ⏳ بیدلیل نمانده | ✅ | |
| ۷.۲ | bin/phpunit کامل سبز |
✅ | |
| ۷.۳ | --group=slot-mode-frozen سبز |
✅ | |
| ۷.۴ | phpstan بدون خطای جدید |
✅ | |
| ۷.۵ | npx tsc --noEmit و yarn test سبز |
✅ | |
| ۷.۶ | تستهای tenant سبز | ✅ | |
| ۷.۷ | docs/api/* بهروز |
✅ | |
| ۷.۸ | چکلیست UI کامل | ✅ | |
| ۷.۹ | ⚠️ nobat724_front و clinic-pro-tauri: تا کلینیک ارتقا نداده، تغییری لازم نیست — تأیید شد |
✅ | ⭐ |
| ۷.۱۰ | تسک frontend حالت resource برای سایت ثبت شد (خارج از این فاز) |
✅ | |
| ۷.۱۱ | commit، سپس graphify update . |
✅ | |
| ۷.۱۲ | موارد بهتعویق با دلیل و تسک مقصد | ✅ |