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>
9.3 KiB
9.3 KiB
چکلیست — تسک ۰۵ (بخشهای نوبت و سازندهٔ برنامه)
وضعیت کلی: ✅ تمامشده با انحرافهای ثبتشده · آخرین بازبینی: ۱۴۰۵/۰۵/۰۹
این چکلیست تا امروز روی «شروع نشده» مانده بود در حالی که کد تسک از همان روز ساخته و کامیت شده بود — خطای پیگیری، نه خطای پیادهسازی. حالا با وضعیت واقعی پر شده.
قواعد: _shared/definition-of-done.md · red-lines.md · ui-conventions.md
۰. خط سرخ
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | --group=slot-mode-frozen سبز |
✅ | |
| ۰.۲ | SlotCalculatorService دستنخورده |
✅ | |
| ۰.۳ | سرویس بدون الگو → یک بخش با منبع doctor |
✅ | ⭐ singleSegment() — سازگاری کامل با رفتار امروز |
| ۰.۴ | هیچ جدولی برای «برنامهٔ ساختهشده» نیست | ✅ | فقط DTO درونحافظه؛ ذخیره کار تسک ۰۷ شد |
۱. بکاند
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | SegmentTemplate · SegmentRequirement |
✅ | |
| ۱.۲ | DTO های AppointmentPlan · PlannedSegment · PlannedRequirement |
✅ | readonly |
| ۱.۳ | پنج سرویس جدا | ⚠️ | یک AppointmentPlanBuilder با متدهای خصوصی. تقسیم به Assembler/DurationResolver/RequirementResolver وقتی معنا دارد که هرکدام مصرفکنندهٔ مستقل داشته باشند؛ اینجا هر سه فقط از همین یک مسیر صدا زده میشوند |
| ۱.۴ | build() تابع خالص |
⚠️ | تا تسک ۰۸ خالص بود. تسک ۰۹ قوانین timing/resource را وصل کرد، پس حالا از دیتابیس میخواند. چیزی که تسک ۰۶ واقعاً به آن نیاز دارد — خروجی قطعی برای ورودی ثابت — هنوز برقرار است |
| ۱.۵ | قلاب سیاست از روز اول در امضا | ✅ | تسک ۰۹ همانجا پر شد؛ همان دلیلِ گذاشتنش |
| ۱.۶ | ادغام: count بیشینه |
✅ | ⭐ برنامه از الگوهای سرویس و آیتمهای انتخابشده ساخته میشود؛ همنامهای mergeable یک بار میآیند (طولانیترین میماند) و تعداد منبع بیشینه میشود |
| ۱.۷ | offset_minutes نسبی |
✅ | تسک ۰۶ برنامه را میلغزاند |
| ۱.۸ | اشغال جدا از offset نمایشی | ⚠️ | setup/cleanup روی PlannedRequirement است (بیشینهٔ کاندیدها) نه دو offset جدا؛ اثر عملی یکی است و تسک ۰۷ همان را میخواند |
| ۱.۹ | قید جنسیت بدون داده → ۴۲۲ | ✅ | ⭐ نادیده گرفته نمیشود |
| ۱.۱۰ | constraints فهرست بسته |
⚠️ | فقط same_gender_as_patient اثر دارد؛ کلید ناشناخته ۴۲۲ نمیگیرد و بیصدا نادیده میرود |
| ۱.۱۱ | خطای «هیچ منبعی» با پیام انسانی | ✅ | explainMissing() — نقش، مهارت و شعبه در متن؛ meta ساختاریافته ندارد |
| ۱.۱۲ | سه endpoint | ✅ | GET/PUT segments + POST appointment-plan/preview |
| ۱.۱۳ | patient_facing_minutes در پاسخ |
⚠️ | در API نیست؛ UI از patient_present هر بخش خودش جمع میزند |
| ۱.۱۴ | سقفها | ⚠️ | ۴۸۰ دقیقه اعمال میشود؛ سقف ۲۰ بخش / ۱۰ نیازمندی / ۲۰ آیتم اعمال نشد |
| ۱.۱۵ | app:segment:seed-templates |
✅ | سه الگو (beauty/dental/physio)؛ بدون --force بازنویسی نمیکند |
| ۱.۱۶ | TenantOwnershipChecker روی هر uuid |
✅ |
۲. دیتابیس
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | segment_templates · segment_requirements |
✅ | |
| ۲.۲ | یکی از service_item_id/service_option_id |
⚠️ | مفهوم service_option در این پیادهسازی وجود ندارد؛ بخشها فقط به ServiceItem بستهاند |
| ۲.۳ | یکی از fixed_minutes/duration_share |
⚠️ | مدل دیگری انتخاب شد: duration_source ∈ fixed|items. «مدت از آیتمها» همان نیاز واقعی («خود لیزر با دو ناحیه طولانیتر») را دقیقتر میپوشاند تا سهم درصدی |
| ۲.۴ | جمع duration_share = ۱۰۰ |
— | با مدل بالا موضوعیت ندارد |
| ۲.۵ | required_skills بهصورت JSON |
⚠️ | یک Skill تک با FK. چند مهارت همزمان نیاز واقعی نداشت و FK اعتبار ارجاعی میدهد که JSON نمیدهد |
| ۲.۶ | segment_requirements در AGGREGATE_CHILDREN |
✅ | |
| ۲.۷ | TenantSchemaCoverageTest سبز |
✅ |
۳. UI
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | ویرایشگر بخشها در ServiceDetailPage |
✅ | تب «بخشهای نوبت» — components/ServiceSegmentsTab.tsx |
| ۳.۲ | لیست بخشها با sequence عددی |
✅ | بدون drag، طبق قرارداد |
| ۳.۳ | نیازمندیها با SearchableSelect |
✅ | نقش، تعداد، نوع اشغال، قید جنسیت |
| ۳.۴ | هر گزینهٔ نوع اشغال توضیح فارسی دارد | ✅ | «انحصاری — منبع کامل قفل میشود» / «اشتراکی — از ظرفیت یکی کم میشود» |
| ۳.۵ | نوار پیشنمایش با عرض متناسب مدت | ✅ | ⭐ flex: duration — بخش سیدقیقهای شش برابر پنجدقیقهای |
| ۳.۶ | خط «بیمار واقعاً درگیر: N دقیقه» | ✅ | کنار مدت کل |
| ۳.۷ | backTo روی صفحه |
✅ | از ServiceDetailPage میآید |
| ۳.۸ | هیچ رنگ/شعاع hard-code | ✅ | نوار هم با --primary-soft/--surface-2 |
| ۳.۹ | دارکمود و حالت فشرده | ⚠️ | فقط توکنها؛ بازبینی چشمی انجام نشد |
| ۳.۱۰ | RTL و موبایل — اسکرول افقی نوار | ✅ | overflow-x: auto با min-width |
| ۳.۱۱ | همهٔ رشتهها فارسی | ✅ | |
| ۳.۱۲ | خطای «هیچ منبعی» به لینک «افزودن منبع» تبدیل شد | ✅ | ⭐ خطای بدون راه اصلاح، بنبست است |
۴. تست
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | ادغام بخشها | ✅ | دو تست: ادغام همنام دو ناحیه · بیشینهبودن تعداد |
| ۴.۲ | حل مدت — ثابت و از آیتمها | ✅ | داخل AppointmentPlanTest |
| ۴.۳ | سناریوی مرجع مستند | ✅ | ⭐ آفستهای ۰/۵/۳۵/۵۵ و مجموع ۶۰ |
| ۴.۴ | قطعیت — دو build یکسان | ✅ | مقایسهٔ JSON دو preview پیاپی |
| ۴.۵ | سرویس بدون الگو | ✅ | ⭐ |
| ۴.۶ | حل نیازمندی — مهارت، بیکاندید، جنسیت، محیط دیگر | ✅ | |
| ۴.۷ | سقفها → ۴۲۲ | ⚠️ | سقف ۴۸۰ دقیقه تست شد؛ بقیه سقف ندارند (۱.۱۴) |
| ۴.۸ | تست فرانت ویرایشگر بخشها | ✅ | بارگذاری، ذخیرهٔ همان چیزی که کاربر میبیند، و حالت فقطخواندنی |
اجرا: ddev exec php bin/phpunit tests/Appointment/AppointmentPlanTest.php → ۱۱ تست.
۵. مستندات
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | docs/api/appointment-plan.md |
✅ | |
| ۵.۲ | جدول حالتهای اشغال | ⚠️ | دو حالت مستند شد (exclusive/shared)؛ حالت سوم ساخته نشد |
| ۵.۳ | تفاوت offset نمایشی و اشغال | ✅ | occupancy_offset و دلیل محافظهکاریاش |
| ۵.۴ | مثال کامل خروجی preview |
✅ |
۶. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | هیچ ⏳ بیدلیل نمانده | ✅ | ۵ مورد با دلیل |
| ۶.۲ | bin/phpunit کامل سبز |
✅ | |
| ۶.۳ | --group=slot-mode-frozen سبز |
✅ | |
| ۶.۴ | phpstan بدون خطای جدید |
✅ | baseline ۱۴ |
| ۶.۵ | npx tsc --noEmit و تستهای فرانت سبز |
✅ | ۶۳۷ تست |
| ۶.۶ | تستهای tenant سبز | ✅ | |
| ۶.۷ | docs/api/* بهروز |
✅ | |
| ۶.۸ | چکلیست UI کامل | ✅ | جز ۳.۹ |
| ۶.۹ | دو کلاینت دیگر بررسی شدند | ⚠️ | preview هنوز در nobat724_front مصرف نمیشود |
| ۶.۱۰ | commit، سپس graphify update . |
✅ | |
| ۶.۱۱ | موارد بهتعویق با دلیل | ✅ | ادغام بخشها (۱.۶/۴.۱) · سقفهای فرعی (۱.۱۴) · seed (۱.۱۵) · تست قطعیت (۴.۴) |