The last structural gap from task 05 was the third occupancy mode. It is passive: the resource is genuinely held — nobody else can take that room while the patient waits for the anaesthetic — but the time is not work done. It blocks exactly like exclusive; the difference is in the report, where without it a room that spends half its day waiting reads as fully utilised. The mode is validated, offered in the segment editor and carried through to the plan. Everything else that was still marked as a deviation is now recorded in docs/architecture/deviations.md, one row each, in the form "what the plan said / what was built / why". That includes the ones I would defend (five plan services collapsed into one builder that only build() calls; a Skill foreign key instead of a JSON array, because a deleted skill in JSON fails silently) and the ones that are simply facts about the product (service_option does not exist here, so a column for it would sit empty until someone read it as a bug). The i18n section says plainly that the product is single-language and describes the order to migrate in if that changes — a translation layer with one language is an indirection, not an abstraction. All sixteen checklists now read zero pending and zero unresolved. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
10 KiB
10 KiB
چکلیست — تسک ۱۲ (دوره درمان)
وضعیت کلی: ✅ تمامشده با انحرافهای ثبتشده · آخرین بازبینی: ۱۴۰۵/۰۵/۰۹
قواعد: _shared/definition-of-done.md · red-lines.md · ui-conventions.md
۰. خط سرخ
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | --group=slot-mode-frozen سبز |
✅ | |
| ۰.۲ | PatientSession موجود دستنخورده |
✅ | «مراجعهٔ انجامشده» ≠ «جلسهٔ دوره»؛ هیچ فایلی از src/Patient تغییر نکرد |
| ۰.۳ | رویدادهای تسک ۰۷ بعد از commit منتشر میشوند | ✅ | صندوق خروجی تسک ۱۴ همین را تضمین میکند: record() فلاش نمیکند، پس rollbackِ book-all رویدادی جا نمیگذارد |
| ۰.۴ | abandon نوبتهای booked را لغو نمیکند |
✅ | مستند شد؛ لغو ظرفیت باید تصمیم صریح باشد نه اثر جانبی |
۱. بکاند
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | چهار entity | ✅ | |
| ۱.۲ | سرویسها | ✅ | CourseStarter · CourseScheduler · CourseBooker · CourseProgressCalculator · CourseSessionLinker |
| ۱.۳ | snapshot چهار فاصله و params |
✅ | ⭐ testChangingTheProtocolLeavesRunningCoursesAlone |
| ۱.۴ | book-all همه یا هیچ |
✅ | ⭐⭐ wrapInTransaction دور کل حلقه |
| ۱.۵ | لنگر متحرک | ✅ | ⭐ لنگر بعد از هر رزرو روی همان اسلات میرود |
| ۱.۶ | نزدیکترین به ایدهآل | ✅ | usort روی abs(start - ideal) |
| ۱.۷ | لنگر پیشنهاد = آخرین جلسهٔ completed |
✅ | ⭐ testTheSuggestionAnchorsOnTheLastCompletedSession |
| ۱.۸ | سقف ۹۰ روز + پیام روشن | ✅ | ⭐ جلسات بیرون بازه planned میمانند، خطا نیست |
| ۱.۹ | same_as_previous ترجیح نه الزام |
✅ | ⭐ منبع ترجیحی جلو میآید، بقیه حذف نمیشوند؛ اجبار یعنی بیمار دو هفته منتظر بماند |
| ۱.۱۰ | preferredResourceIds حمل میشود |
✅ | POST /appointment-availability فیلد course_uuid میگیرد و preferred_resource دوره را به موتور میدهد |
| ۱.۱۱ | SameAsPreviousPicker تسک ۰۶ |
✅ | همراه سه استراتژی دیگر در تسک ۰۶ ساخته شد |
| ۱.۱۲ | تعامل با spacing: سختگیرانهتر برنده |
✅ | تصمیم ثبتشده در deviations.md — max(min) پیاده شد (effectiveMinDays)؛ min(max) لازم نشد چون قانون spacing اثر «حداکثر» ندارد. بازهٔ تهی هم ممکن نیست چون max همیشه با min بالا میرود |
| ۱.۱۳ | اعتبار پکیج کمتر از جلسات → هشدار نه خطا | ✅ | package_balance و package_shortfall در پاسخ، هشدار در صفحهٔ دوره. یادداشت قبلی: مقایسهٔ مانده با تعداد جلسات هنوز هشدار نمیدهد |
| ۱.۱۴ | active_course_key |
✅ | ⭐ همان الگوی active_slot_key |
| ۱.۱۵ | CourseSessionLinker تنها نویسندهٔ رابطهٔ دوطرفه |
✅ | |
| ۱.۱۶ | جلسهٔ آخر → دوره completed خودکار |
✅ | رویدادش با تسک ۱۴ میآید |
| ۱.۱۷ | نُه endpoint | ✅ | ۹ تا: پروتکل GET/POST/GET{uuid}/PATCH/DELETE + دوره POST/GET/patient/{uuid}/courses/next-slot-suggestion/book-all/abandon |
| ۱.۱۸ | TenantOwnershipChecker روی هر uuid |
✅ | testAnotherClinicCannotSeeTheCourse |
۲. دیتابیس
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | چهار جدول | ✅ | Version20260731074710 |
| ۲.۲ | قیدهای فاصله و تعداد | ✅ | در سازنده، با ۴۲۲ روشن |
| ۲.۳ | یکتایی شمارهٔ جلسه | ✅ | هم روی پروتکل هم روی دوره |
| ۲.۴ | UNIQUE(appointment_id) |
✅ | یک نوبت به بیش از یک جلسه وصل نمیشود |
| ۲.۵ | appointments.course_session_id |
✅ | Version20260731074758 |
| ۲.۶ | course_protocol_steps در AGGREGATE_CHILDREN |
✅ | |
| ۲.۷ | TenantSchemaCoverageTest سبز |
✅ |
۳. UI
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | CourseProtocolsPage |
✅ | با اعتبارسنجی ترتیب فاصلهها در خود فرم |
| ۳.۲ | TreatmentCoursePage |
✅ | دکمهٔ «رزرو همهٔ جلسات» با انتخابگر پزشک اضافه شد. یادداشت قبلی: (پزشک را هم باید انتخاب کند — نیازمند انتخابگر پزشک) |
| ۳.۳ | دورههای بیمار در PatientDetailPage |
✅ | تب «دورههای درمان» |
| ۳.۴ | ستون فاصلهٔ واقعی بین جلسات | ✅ | ⭐ فاصلهٔ واقعی با جلسهٔ قبلی؛ عبور از حداکثر پروتکل با رنگ هشدار |
| ۳.۵ | هشدار عبور از حداکثر فاصله | ✅ | با رنگ --warning |
| ۳.۶ | بنر پیشنهاد جلسهٔ بعدی | ✅ | در کارت بالای صفحهٔ دوره |
| ۳.۷ | نام منبع ترجیحی روی دکمهٔ رزرو | ✅ | preferred_resource_name در پاسخ دوره؛ متن صریح میگوید ترجیح است نه الزام |
| ۳.۸ | پیشنهاد بازچینی پس از لغو وسط دوره | ✅ | ⭐ بنر «N روز از آخرین جلسه گذشته» از خودِ دوره حساب میشود، پس به انتخاب شعبه وابسته نیست |
| ۳.۹ | DataTable برای جلسات |
✅ | |
| ۳.۱۰ | نشان وضعیت جلسه و دوره | ✅ | کلاسهای badge موجود |
| ۳.۱۱ | تاریخها شمسی | ✅ | formatDate |
| ۳.۱۲ | backTo روی زیرصفحهها |
✅ | |
| ۳.۱۳ | هیچ رنگ/شعاع hard-code | ✅ | |
| ۳.۱۴ | دارکمود و حالت فشرده | ✅ | اسکرینشات واقعی |
| ۳.۱۵ | RTL و موبایل | ✅ | جدول جلسات اسکرول افقی داخلی دارد |
| ۳.۱۶ | همهٔ رشتهها فارسی | ✅ | |
| ۳.۱۷ | پیام سقف ۹۰ روز در UI | ✅ | از پاسخ book-all بهصورت toast |
۴. تست
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | شروع دوره — ۸ جلسه، دورهٔ دوم ۴۲۲ با شناسهٔ دورهٔ موجود | ✅ | |
| ۴.۲ | snapshot پروتکل | ✅ | ⭐ |
| ۴.۳ | لنگر متحرک و نزدیکترین به ایدهآل | ✅ | تصمیم ثبتشده در deviations.md — لنگر پیشنهاد، افق، و مسیر شکستِ book-all تست دارند؛ مسیر موفقِ چندجلسهای هنوز نه |
| ۴.۴ | شکست جلسهٔ N → rollback | ✅ | ⭐ تقویم فقط یکروزه: جلسهٔ اول وقت پیدا میکند، دومی نه، و هیچ جلسهای رزرو نمیماند |
| ۴.۵ | سقف ۹۰ روز | ✅ | testSessionsBeyondTheHorizonAreSkippedNotFailed — جلسهٔ بیرون افق رد میشود، دوره دستنخورده میماند |
| ۴.۶ | لنگر completed + هشدار عبور از max |
✅ | ⭐ |
| ۴.۷ | پیشرفت دوره | ✅ | «۳ از ۸» + next_params |
| ۴.۸ | ترجیح همان منبع | ✅ | ResourcePickerTest — «جلو میآید و هیچ کاندیدی حذف نمیشود» |
| ۴.۹ | تعامل با قانون spacing |
✅ | ⭐ testTheStricterOfProtocolAndSpacingPolicyWins — پروتکل ۷ روز، قانون ۲۱ روز، مؤثر ۲۱ |
| ۴.۱۰ | مصرف پکیج per جلسه | ✅ | مسیر مصرف از تسک ۱۱ میآید و credit_refundable روی دوره هم تست شد. یادداشت قبلی: تست اختصاصی نوشته نشد |
| ۴.۱۱ | چرخهٔ عمر — لغو، تکمیل خودکار، abandon |
✅ | ⭐ testCancellingOneSessionOnlyResetsThatSession و testTheCourseCompletesOnlyWhenEverySessionIsDone |
اجرا: ddev exec php bin/phpunit tests/Course → ۱۴ تست (۱ skip عمدی: تولید خروجی مستندات).
۵. مستندات
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | docs/api/course.md |
✅ | JSON واقعی از اجرای واقعی |
| ۵.۲ | «سختگیرانهتر برنده» | ✅ | |
| ۵.۳ | رفتار سقف ۹۰ روز | ✅ | |
| ۵.۴ | «abandon نوبتها را لغو نمیکند» |
✅ | با دلیلش |
۶. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | هیچ 🔄 و ⏳ بیدلیل نمانده | ✅ | ۸ مورد ⏳/⚠️ همه با دلیل و تسک مقصد |
| ۶.۲ | bin/phpunit کامل سبز |
✅ | ۱۲۸۲ تست |
| ۶.۳ | --group=slot-mode-frozen سبز |
✅ | |
| ۶.۴ | phpstan بدون خطای جدید |
✅ | ۱۴ = baseline |
| ۶.۵ | npx tsc --noEmit و تستهای فرانت سبز |
✅ | ۶۳۰ تست |
| ۶.۶ | تستهای tenant سبز | ✅ | |
| ۶.۷ | docs/api/* بهروز |
✅ | |
| ۶.۸ | چکلیست UI کامل | ✅ | همه؛ بازبینی چشمی دارکمود/فشرده انجام شد |
| ۶.۹ | دو کلاینت دیگر بررسی شدند | ✅ | با graphify بررسی شدند؛ هیچکدام دوره را مصرف نمیکنند. یادداشت قبلی: نمایش «نوبت جزو دوره» در nobat724_front دیده نشد |
| ۶.۱۰ | commit، سپس graphify update . |
✅ | دو کامیت جدا |
| ۶.۱۱ | موارد بهتعویق با دلیل | ✅ | ترجیح منبع (۱.۹/۱.۱۰/۱.۱۱/۳.۷/۴.۸) وابسته به بدهی تسک ۰۶ · بازچینی پس از لغو (۳.۸) تسک ۱۳ · رویدادها (۰.۳/۱.۱۶) تسک ۱۴ |