Three gaps on the treatment-course page, all of them about the difference between the protocol and reality. The sessions table listed each date but not the gap between them, leaving the operator to subtract two Jalali dates in their head. It now shows the real gap and colours it as a warning past the protocol maximum. A course cancelled mid-way stretches silently: the session goes back to planned and nobody is told. The suggestion endpoint does warn, but only once a branch is picked, so the warning could go unseen indefinitely. The page now derives "N days since the last session, past the protocol maximum" from the course itself, so it shows immediately. The course's preferred resource was applied by the engine but never named in the UI. The API now returns preferred_resource_name alongside the uuid, and the text says plainly that it is a preference — the engine moves it up the list, it does not hold the slot. Two backend tests that were owed: the stricter of the protocol spacing and a spacing policy wins (protocol 7 days, policy 21, effective 21 — otherwise a clinic's safety rule could be bypassed by writing a short protocol), and a session whose earliest possible date falls outside the 90-day horizon is skipped rather than failing book-all, leaving the course untouched. 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 منتشر میشوند | ⏳ | تسک ۱۴ رویدادها را میسازد؛ فعلاً book-all هیچ رویدادی منتشر نمیکند، پس خطر «هشت پیامک در rollback» وجود ندارد |
| ۰.۴ | 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: سختگیرانهتر برنده |
⚠️ | max(min) پیاده شد (effectiveMinDays)؛ min(max) لازم نشد چون قانون spacing اثر «حداکثر» ندارد. بازهٔ تهی هم ممکن نیست چون max همیشه با min بالا میرود |
| ۱.۱۳ | اعتبار پکیج کمتر از جلسات → هشدار نه خطا | ⚠️ | پکیج به دوره وصل میشود و پوششش بررسی میشود، ولی مقایسهٔ مانده با تعداد جلسات هنوز هشدار نمیدهد |
| ۱.۱۴ | 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 |
⚠️ | پیشرفت، جدول جلسات و پیشنهاد جلسهٔ بعدی هست؛ دکمهٔ book-all در UI نیست (پزشک را هم باید انتخاب کند — نیازمند انتخابگر پزشک) |
| ۳.۳ | دورههای بیمار در PatientDetailPage |
✅ | تب «دورههای درمان» |
| ۳.۴ | ستون فاصلهٔ واقعی بین جلسات | ✅ | ⭐ فاصلهٔ واقعی با جلسهٔ قبلی؛ عبور از حداکثر پروتکل با رنگ هشدار |
| ۳.۵ | هشدار عبور از حداکثر فاصله | ✅ | با رنگ --warning |
| ۳.۶ | بنر پیشنهاد جلسهٔ بعدی | ✅ | در کارت بالای صفحهٔ دوره |
| ۳.۷ | نام منبع ترجیحی روی دکمهٔ رزرو | ✅ | preferred_resource_name در پاسخ دوره؛ متن صریح میگوید ترجیح است نه الزام |
| ۳.۸ | پیشنهاد بازچینی پس از لغو وسط دوره | ✅ | ⭐ بنر «N روز از آخرین جلسه گذشته» از خودِ دوره حساب میشود، پس به انتخاب شعبه وابسته نیست |
| ۳.۹ | DataTable برای جلسات |
✅ | |
| ۳.۱۰ | نشان وضعیت جلسه و دوره | ✅ | کلاسهای badge موجود |
| ۳.۱۱ | تاریخها شمسی | ✅ | formatDate |
| ۳.۱۲ | backTo روی زیرصفحهها |
✅ | |
| ۳.۱۳ | هیچ رنگ/شعاع hard-code | ✅ | |
| ۳.۱۴ | دارکمود و حالت فشرده | ⚠️ | فقط توکنهای موجود؛ بازبینی چشمی انجام نشد |
| ۳.۱۵ | RTL و موبایل | ✅ | جدول جلسات اسکرول افقی داخلی دارد |
| ۳.۱۶ | همهٔ رشتهها فارسی | ✅ | |
| ۳.۱۷ | پیام سقف ۹۰ روز در UI | ✅ | از پاسخ book-all بهصورت toast |
۴. تست
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | شروع دوره — ۸ جلسه، دورهٔ دوم ۴۲۲ با شناسهٔ دورهٔ موجود | ✅ | |
| ۴.۲ | snapshot پروتکل | ✅ | ⭐ |
| ۴.۳ | لنگر متحرک و نزدیکترین به ایدهآل | ⚠️ | لنگر پیشنهاد و محاسبهٔ افق تست شد؛ اجرای کامل book-all با منابع و ساعت کاری هنوز تست ندارد |
| ۴.۴ | شکست جلسهٔ N → rollback | ⏳ | با ۴.۳ یک بسته است |
| ۴.۵ | سقف ۹۰ روز | ✅ | testSessionsBeyondTheHorizonAreSkippedNotFailed — جلسهٔ بیرون افق رد میشود، دوره دستنخورده میماند |
| ۴.۶ | لنگر completed + هشدار عبور از max |
✅ | ⭐ |
| ۴.۷ | پیشرفت دوره | ✅ | «۳ از ۸» + next_params |
| ۴.۸ | ترجیح همان منبع | ✅ | ResourcePickerTest — «جلو میآید و هیچ کاندیدی حذف نمیشود» |
| ۴.۹ | تعامل با قانون spacing |
✅ | ⭐ testTheStricterOfProtocolAndSpacingPolicyWins — پروتکل ۷ روز، قانون ۲۱ روز، مؤثر ۲۱ |
| ۴.۱۰ | مصرف پکیج per جلسه | ⚠️ | مسیر مصرف از تسک ۱۱ میآید (confirm هر نوبت)، پس دوره چیز تازهای لازم ندارد؛ تست اختصاصی نوشته نشد |
| ۴.۱۱ | چرخهٔ عمر — لغو، تکمیل خودکار، 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 کامل | ⚠️ | جز ۳.۲ (دکمهٔ book-all) و ۳.۱۴ |
| ۶.۹ | دو کلاینت دیگر بررسی شدند | ⚠️ | هیچ قرارداد عمومیای عوض نشد (فقط ستون تهیپذیر روی appointments)؛ نمایش «نوبت جزو دوره» در nobat724_front دیده نشد |
| ۶.۱۰ | commit، سپس graphify update . |
✅ | دو کامیت جدا |
| ۶.۱۱ | موارد بهتعویق با دلیل | ✅ | ترجیح منبع (۱.۹/۱.۱۰/۱.۱۱/۳.۷/۴.۸) وابسته به بدهی تسک ۰۶ · بازچینی پس از لغو (۳.۸) تسک ۱۳ · رویدادها (۰.۳/۱.۱۶) تسک ۱۴ |