# چک‌لیست — تسک ۱۲ (دوره درمان) > # ⛔ این تسک از محصول حذف شد > > **تصمیم مالک محصول، ۱۴۰۵/۰۵/۱۰:** مدل نوبت‌دهی به منبع/سرویس/گزینه محدود شد و هر چیز > خارج از آن حذف شد. کد، جدول‌ها، endpointها، تست‌ها و صفحات پنل این تسک در کامیت > «Remove the policy, package, course, cancellation and event subsystems» برداشته شدند. > > ریسکش پیش از اجرا دو بار مطرح و دو بار تأیید شد. ردیف‌های زیر **تاریخچه**اند، نه کار > جاری؛ برای برگرداندن به همان کامیت رجوع کنید. > > مدل جایگزین: [`docs/architecture/resource-first-model.md`](../../../architecture/resource-first-model.md) > و چک‌لیست [تسک ۱۵](../task-15-resource-first-model/checklist.md). **وضعیت کلی:** ✅ تمام‌شده با انحراف‌های ثبت‌شده · **آخرین بازبینی:** ۱۴۰۵/۰۵/۰۹ قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) · [red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/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](../../../architecture/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](../../../architecture/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 .` | ✅ | دو کامیت جدا | | ۶.۱۱ | موارد به‌تعویق با دلیل | ✅ | ترجیح منبع (۱.۹/۱.۱۰/۱.۱۱/۳.۷/۴.۸) وابسته به بدهی تسک ۰۶ · بازچینی پس از لغو (۳.۸) تسک ۱۳ · رویدادها (۰.۳/۱.۱۶) تسک ۱۴ |