The mergeable flag was stored, returned by the API and rendered in the editor while changing nothing. The reason was upstream: the builder only ever read the primary service's templates, and within one service two segments with the same name do not occur — so the dedupe it already had could never fire. Templates now come from the primary service plus every selected item, and same-named mergeable segments collapse to one. Rules, with their reasons: - the longest of the same-named segments survives — prepping two areas is not shorter than prepping the longer one alone - a duration_source: "items" segment also appears once even when it is not marked mergeable, because DurationCalculator has already summed every item and repeating the segment counts that time twice - the merged requirement count is the maximum, not the sum and not the first one seen: two areas do not need two rooms, but if one of them needed two operators, merging must not quietly demote that to one Also pins that the plan is deterministic: two previews of the same input are compared byte for byte. A plan that shifts between preview and booking means the user confirmed something that was not what got booked. Unrelated but found by running the suite on a Saturday: testPastStartsAreExcluded searched "last week's Saturday", which is today when today is Saturday, so this afternoon's slots were legitimately not in the past. It now searches two weeks back, which is unambiguous on every weekday. 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 |
⏳ | ساخته نشد؛ با UI تازه، الگوی نمونه دستی ساختنی است |
| ۱.۱۶ | 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 (۱.۱۵) · تست قطعیت (۴.۴) |