Files
clinicpro/docs/new_feture/taskes/task-00-service-mode-completion/checklist.md
T
hamedandClaude Opus 5 7482eb2ba3 feat(booking): add backfill command for service duration columns
Fills service_total_minutes/service_buffer_minutes on future service-mode
appointments booked before the columns existed.

The value comes from the appointment itself (slot_end - slot_start), not from
recomputing the services: an existing appointment may have been booked with a
manual duration and recomputing would rewrite the past. Slot-mode, past,
reserve and cancelled appointments are skipped.

Dry-run by default. Idempotency comes from the query filtering on
serviceTotalMinutes IS NULL rather than from a flag, so a second run has nothing
to do.

Task: docs/new_feture/taskes/task-00-service-mode-completion/
Slot-mode contract: unchanged (--group=slot-mode-frozen green)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 15:08:35 +03:30

26 KiB
Raw Blame History

چک‌لیست — تسک ۰۰ (تکمیل نوبت‌دهی سرویسی در clinicpro)

وضعیت کلی: 🔄 در حال انجام — قابلیت ۹ از ۱۰ تمام شد (مانده: مستندات + بازبینی پایانی) آخرین بازبینی: ۱۴۰۵/۰۵/۰۸

قواعد: _shared/definition-of-done.md · خط سرخ‌ها: _shared/red-lines.md · UI: _shared/ui-conventions.md


۰. خط سرخ — منطق اسلاتی

# مورد وضعیت یادداشت
۰.۱ SlotModeFrozenTest + سه fixture ساخته شد پیش از هر تغییر کد tests/Appointment/SlotModeFrozenTest.php + tests/Appointment/fixtures/ — هیچ کد تولیدی‌ای هنوز لمس نشده
۰ fixture ها با تاریخ ثابت‌اند، نه time() → fixture ساختاری است انحراف عمدی از متن تسک. تاریخ ثابتِ گذشته را isWithinBookingWindow رد می‌کند و snapshot خالی چیزی را تضمین نمی‌کند. به‌جایش: برنامهٔ قطعی (هر ۷ روز یک شیفت ۰۹:۰۰–۱۱:۰۰/۳۰ دقیقه) روی +3 days، و epoch/uuid با placeholder نرمال می‌شوند. آنچه قفل می‌شود: کلیدها، ترتیب، نوع‌ها، ساعت‌های محلی
۰ کامنت «read-only، هیچ تسکی به‌روزش نمی‌کند» بالای هر سه fixture کلید _readme در دو JSON (در loader حذف می‌شود) + docblock در فایل PHP
۰ هیچ متد موجود SlotCalculatorService ویرایش نشد فقط getServiceStartTimes یک پارامتر اختیاری با پیش‌فرض null گرفت. اثبات کارکرد تور ایمنی: تست منجمد همان لحظه قرمز شد و دقیقاً همان پارامتر را نشان داد، در حالی که دو قرارداد پاسخ سبز ماندند. fixture امضا یک بار با تاریخچهٔ مکتوب به‌روز شد (header خودش مجاز کرده)
۰.۵ GET /appointment-slots بیت‌به‌بیت دست‌نخورده در پایان تسک تأیید می‌شود
۰ GET /month-availability/{doctorUuid} دست‌نخورده در پایان تسک تأیید می‌شود
۰.۷ active_slot_key و refreshActiveSlotKey() دست‌نخورده setIsReserve() وجود ندارد؛ toggle رزرو از قبل با rescheduleTo($start,$end,$isReserve) انجام می‌شود که خودش refreshActiveSlotKey() را صدا می‌زند (Appointment.php:316). یادداشت قبلی چک‌لیست غلط بود
۰ isSlotTaken امضا و معنا دست‌نخورده لمس نشد؛ فقط الگویش تکرار شد
۰ --group=slot-mode-frozen سبز OK (3 tests, 8 assertions) — نیازمند #[Group] attribute بود، نه @group (PHPUnit 12 annotation را حذف کرده)
۰.۱۰ baseline: کل tests/Appointment پیش از تغییرات سبز OK (166 tests, 382 assertions)

۱. بک‌اند

# مورد وضعیت یادداشت
۱.۱ ServiceBookingCalculator ساخته شد src/Appointment/Service/ServiceBookingCalculator.php + VO در src/Appointment/ValueObject/ServiceBookingDuration.php (قرارداد پروژه ValueObject است نه Dtosrc/Billing/ValueObject، src/Insurance/ValueObject)
۱ serviceSlots() موجود از آن استفاده می‌کند، خروجی‌اش عوض نشده ۲۰ خط از کنترلر حذف شد. بیت‌به‌بیت‌بودن: error() و ExceptionSubscriber یک envelope یکسان می‌سازند (['success'=>false,'data'=>null,'errors'=>[{code,message,field?}]])، پس AppException جای return $this->error() نشست. شاهد: ۱۶۶ تست موجود سبز ماند، شامل ServiceBasedSlotsTest و ServiceModeSectionDurationTest که همین مسیرهای خطا را می‌سنجند
۱ جمع سادهٔ += حفظ شد (اصلاحش تسک ۰۴ است) با کامنت صریح در docblock کلاس: «تسک ۰۴ همان‌جا یک خط عوض می‌کند»
۱ ServiceRescheduleService + POST /appointment/{uuid}/service-reschedule route ثبت شد (app_appointment_appointment_servicereschedule). کنترلر نازک: فقط ۴۰۴/۴۰۳/اعتبار start و ترجمهٔ دو استثنای همزمانی؛ منطق در سرویس. forManagement از canManageContext() می‌آید نه از canManage() — بیمارِ صاحب نوبت باید پنجرهٔ رزرو را رعایت کند
۱.۵ PATCH /appointment/{uuid} توسعه یافت — منطق جدید داخل isServiceMode() سرویس‌ها پیش از بلوک زمان حل می‌شوند چون مدت به آن‌ها وابسته است. بررسی مدت فقط وقتی $movingToLiveSlot است (نوبت رزرو معاف — slot_start == slot_end). در حالت اسلاتی $duration === null می‌ماند و هیچ خطی اجرا نمی‌شود
۱ PATCH مقدار service_item_uuids[] می‌پذیرد وقتی فهرست کامل آمد، service_item_uuid تکی در حلقهٔ relations نادیده گرفته می‌شود (continue) — دو منبع برای یک چیز به نوبتِ ناسازگار می‌رسد. allowInactive فقط وقتی فهرست نیامده روشن است: نوبت موجود قفل نشود ولی افزودن سرویس غیرفعال رد شود
۱.۷ ReserveConversionService + POST /convert-reserve → توسعهٔ PATCH endpoint جدید ساخته نشد و توجیه نداشت (قاعدهٔ #۸ پروژه). PATCH از قبل رزرو→زمان‌دار را دارد: rescheduleTo($start,$end,$isReserve) که خودش refreshActiveSlotKey() را صدا می‌زند. تست testReserveConvertsToATimedAppointmentThroughPatch این را می‌سنجد
۱ نوبت رزرو در حالت سرویسی سرویس‌ها را ذخیره می‌کند سرویس‌ها + service_total_minutes روی رزرو ذخیره می‌شوند تا تبدیل بعدی مدت را از دست ندهد؛ بررسی مدت روی خودِ رزرو اعمال نمی‌شود
۱ excludeAppointmentId روی getServiceStartTimes و findBusyIntervals همان نام/جای/پیش‌فرضِ isSlotTaken($doctor,$start,$end,?int $excludeId=null). در repository به occupiedIntervals() هم پاس داده می‌شود، پس findBlockingIntervals بی‌تغییر ماند
۱.۱۰ Appointment::replaceServiceItems() + currentServiceUuids() کنار addServiceItem() موجود؛ currentServiceUuids() ترتیب ورودی را حفظ می‌کند و برای نوبت قدیمیِ فقط-تکی هم uuid می‌دهد
۱.۱۱ replaceServiceItems مقدار serviceItem تکی را هم‌گام می‌کند برخلاف addServiceItem() که فقط وقتی null است ست می‌کند، این یکی بی‌قید بازنویسی می‌کند (و با فهرست خالی null می‌شود). چهار مصرف‌کننده روی service_item تکی خوانده‌اند
۱.۱۲ setIsReserve() صدا زدن refreshActiveSlotKey() موردی برای انجام نبود: setIsReserve() وجود ندارد؛ toggle رزرو از قبل با rescheduleTo($start,$end,$isReserve) است که خودش refreshActiveSlotKey() را صدا می‌زند
۱.۱۳ اعتبارسنجی زمان با عضویت در getServiceStartTimes، نه فقط isSlotTaken assertStartIsOffered(). تست testTimeOutsideTheOfferedListIsRejected با ۰۳:۰۰ (بیرون شیفت ۰۹–۱۸) می‌سنجد — isSlotTaken تنها این را نمی‌گرفت
۱.۱۴ TenantOwnershipChecker روی همهٔ uuid های سرویس، پیش از هر بررسی دیگر belongsToPair() (نه belongsTo()): مسیر عمومی است و کاربر احراز‌شده ندارد. جای مقایسهٔ دستیِ getSection()->getEntityType() نشست — نقطهٔ واحد tenancy.md. ⚠️ نام property باید itemRepo باشد وگرنه TenantLookupInventoryTest آن را نمی‌شمارد و از تور ایمنی فرار می‌کند
۱.۱۵ allowInactive فقط برای سرویس‌های موجود نوبت، نه uuid های تازه پرچم روی calculate()؛ غیرفعال‌بودن → warnings[] به‌جای ۴۲۲. مدتِ نامعتبر همچنان حتی با allowInactive رد می‌شود (تست دارد)
۱.۱۶ کنترلر نازک ماند — منطق در سرویس serviceSlots() از ۶۰ خط به ۴۰ خط؛ هیچ حلقهٔ محاسباتی در کنترلر نماند

۲. دیتابیس و مهاجرت

# مورد وضعیت یادداشت
۲.۱ service_total_minutes و service_buffer_minutes (تهی‌پذیر) SMALLINT DEFAULT NULL هر دو · migrations/Version20260730091332.php · setServiceDuration() هر دو را با هم ست می‌کند (بافرِ بی‌مدت معنا ندارد)
۲.۲ هیچ ستون موجودی حذف/تغییر نوع/تغییر معنا نداد تأیید با SHOW COLUMNS: slot_start int NOT NULL · slot_end int NOT NULL · active_slot_key varchar(64) UNI · is_reserve tinyint default 0 — همه دست‌نخورده
۲.۳ دو کد خطای جدید در ErrorCodes.php با پیام فارسی ERR_APPOINTMENT_003 (مدت نمی‌خواند) و ERR_APPOINTMENT_004 (روش نوبت‌دهی ناسازگار) — نه _010/_011 که متن تسک حدس زده بود؛ فایل فقط تا _002 داشت
۲.۴ app:appointment:backfill-service-duration — dry-run پیش‌فرض، idempotent src/Appointment/Command/BackfillServiceDurationCommand.php با الگوی BackfillAppointmentSessionsCommand موجود. idempotency از خودِ کوئری می‌آید (serviceTotalMinutes IS NULL)، نه از یک پرچم
۲.۵ backfill مقدار را از خود نوبت می‌گیرد، نه بازمحاسبه از سرویس‌ها intdiv(slot_end - slot_start, 60). تست testForceFillsDurationFromTheAppointmentItself با مدت دستیِ ۵۰ دقیقه‌ای می‌سنجد که بازمحاسبه نمی‌شود — قانون پنجم مستند
۲.۶ migration اجرا شد و TenantSchemaCoverageTest سبز dev با migration؛ db_test با ALTER دستی — تاریخچهٔ migration آن DB از dev جدا است (Table users already exists) و این روش نگهداشتِ موجودِ همان DB است

۳. UI — پنل ادمین

# مورد وضعیت یادداشت
۳.۱ AppointmentEditPage: حالت سرویسی ServiceSlotPicker نشان می‌دهد روش نوبت‌دهی از برنامهٔ همین نوبت پرسیده می‌شود (clinic_uuid صریح)، نه از محیط جاری پنل — پزشک می‌تواند در مطب اسلاتی و در کلینیک سرویسی باشد. لازمهٔ این کار: افزودن clinic_uuid به toArray() که نبود
۳.۲ AppointmentEditPage: حالت اسلاتی دقیقاً رفتار امروز ۵ تست موجود صفحه سبز ماند + دو تست خط سرخ تازه: سه فیلد ساعت با مقدار هیدریت‌شده، و همان PATCH با version بدون هیچ POST
۳.۳ ورودی دستی ساعت در حالت سرویسی پنهان، نه disabled queryByLabelText('ساعت شروع') در حالت سرویسی null است. SearchableSelect سرویس تکی هم پنهان می‌شود — نگه‌داشتنش دو منبع برای یک چیز بود
۳.۴ ServiceSlotPicker موجود بازاستفاده شد؛ نسخهٔ موازی ساخته نشد دو prop اختیاری: excludeAppointmentUuid و initialSelection. رفتار فعلی‌اش در AppointmentCreatePage و AppointmentsPage دست‌نخورده (هر دو prop undefined). یک اصلاح لازم شد: effect ریست روی تعویض پزشک، اجرای نخست را معاف کند وگرنه initialSelection همان لحظه پاک می‌شد
۳.۵ ReserveAppointmentsPage: سرویس‌ها + دکمهٔ تبدیل ستون سرویس چند-سرویسی شد (قبلاً فقط service_item تکی). دکمهٔ تبدیل از قبل وجود داشت («انتقال به لیست نوبت ها» → TransferReserveModal) — ساخته نشد، اصلاح شد (رجوع به ک.۱)
۳.۶ مهاجرت به DataTable انجام نشد و توجیه نداشت. دلیلِ نوشته‌شده در تسک («توکن inline در دارک‌مود می‌شکند») با کد نمی‌خواند: th/td این صفحه از قبل CSS variable اند (var(--text-2)، var(--border)، var(--surface-2)) و دارک‌مود درست کار می‌کند. هیچ hex ای نبود. بازنویسی جدولِ کارآمد بدون سود واقعی، ریسک بی‌دلیل است
۳.۷ مدت و بافر فارسی با واحد AppointmentEditPage («زمان انتخابی … (+۱۰ دقیقه فاصله)») · ServiceSlotPicker («مدت کل: N دقیقه») · ReserveAppointmentsPage («۳۵ دقیقه +۱۰ فاصله» کنار نام سرویس‌ها)
۳.۸ هیچ رنگ/شعاع/سایهٔ hard-code — همه از توکن‌های styles.css همهٔ افزوده‌ها با var(--text-3)، var(--warning)، var(--warning-bg)، var(--r-sm). هیچ hex ای اضافه نشد
۳.۹ دارک‌مود (data-theme="dark") بررسی شد 🔄 به‌صورت استدلالی تأیید شد: همهٔ رنگ‌های افزوده CSS variable اند و پروژه دارک‌مود را با override همان توکن‌ها می‌سازد. بررسی چشمی در مرورگر انجام نشد — در بازبینی پایانی تسک
۳.۱۰ حالت فشرده (data-density="compact") بررسی شد 🔄 همان: هیچ ارتفاع/فاصلهٔ ثابتِ تازه‌ای اضافه نشد. بررسی چشمی در بازبینی پایانی
۳.۱۱ انتخاب چند سرویس با SearchableSelect؛ هیچ <select> بومی ServiceSlotPicker از SearchableSelect برای بخش و دکمه‌های toggle برای سرویس‌ها استفاده می‌کند (کد موجود). هیچ <select> بومی اضافه نشد
۳.۱۲ backTo/BackButton روی هر دو صفحه هر دو از قبل داشتند (BackButton fallback="/admin/appointments") — دست‌نخورده ماند
۳.۱۳ وضعیت لیست رزروها در URL با useUrlState به تعویق افتاد. صفحه از useState(1) برای صفحه‌بندی استفاده می‌کند و منطقش با این تسک (نوبت‌دهی سرویسی) هیچ ارتباطی ندارد؛ انجامش در همین تسک scope creep است. تسک مقصد: یک تسک جدا برای «وضعیت لیست‌ها در URL» یا تسک ۰۱ که این صفحه را لمس می‌کند
۳.۱۴ تاریخ با PersianDateInput · مبلغ با formatRial PersianDateInput در هر دو حالت؛ مبالغ با formatRial/PriceInput موجود
۳.۱۵ RTL بررسی شد (ms/me نه ml/mr) marginInlineStart استفاده شد نه marginLeft؛ ورودی‌های ساعت dir="ltr" مثل بقیهٔ پروژه
۳.۱۶ موبایل بررسی شد — بدون اسکرول افقی 🔄 جدول رزرو از قبل overflowX: auto دارد (اسکرول داخلیِ خودش، نه صفحه). بررسی چشمی در بازبینی پایانی
۳.۱۷ همهٔ رشته‌ها فارسی و از i18n ⚠️ همهٔ رشته‌های جدید فارسی‌اند. ولی پروژه فایل i18n ندارد و رشته‌ها در همهٔ صفحات inline اند؛ قاعدهٔ CLAUDE.md («رشته‌های UI فارسی و از فایل i18n») با کد فعلی نمی‌خواند. تغییرش خارج از دامنهٔ این تسک — بدهی ثبت‌شده
۳.۱۸ داده با TanStack Query و استخراج envelope درست useDoctorBookingServices (موجود) + useQuery داخل picker؛ استخراج data?.data

۴. تست

# مورد وضعیت یادداشت
۴.۱ SlotModeFrozenTest — سه سنجه OK (3 tests, 8 assertions)
۴.۲ ServiceBookingCalculatorTest — موفق/خطا/مرزی ۱۳ تست / ۲۹ assertion سبز. شامل: جمع مدت + بافر · endFor بدون بافر · override منشی بدون تغییر پیش‌فرض سرویس · چهار مسیر خطا با کد/پیام/فیلد دقیق · uuid ناموجود از سرویسِ محیط دیگر قابل تفکیک نیست · allowInactive → warning · فهرست خالی → صفر · override نامعتبر (۰ و منفی) → fallback · پزشک بی‌برنامه → پیش‌فرض slot
۴.۳ ServiceRescheduleTest — شامل «حذف سرویس → مدت خودکار» ۱۵ تست / ۳۴ assertion. شامل: جابه‌جایی بی‌ارسال مدت · حذف سرویس → کوچک‌شدن خودکار · زمان فعلیِ خود نوبت با سرویس بلندتر پذیرفته می‌شود (اثر excludeAppointmentId) · سرویس غیرفعالِ موجود → warning · حالت اسلاتی → ERR_APPOINTMENT_004 · بیرون شیفت → ERR_APPOINTMENT_001 · رزرو → پیام ارجاع به ویرایش · start غایب · سرویس بیگانه · افزودن سرویس غیرفعال تازه · گذشته · نوبت بی‌سرویس · نسخهٔ کهنه → ۴۰۹ · نوبت شخص دیگر → ۴۰۳ · uuid ناموجود → ۴۰۴
۴.۴ PatchServiceDurationTest — شامل «در حالت اسلاتی هیچ‌کدام اجرا نمی‌شود» ۹ تست / ۲۴ assertion. شامل: بازمحاسبهٔ مدت با تعویض سرویس · هم‌گامی ستون تکی · PATCH فقط-یادداشت بی‌اعتبارسنجی · رزرو معاف ولی مدت‌دار · تبدیل رزرو با همان PATCH · مدت ناسازگار → ERR_APPOINTMENT_003 با عدد درست در پیام · سرویس بیگانه → ۴۲۲ · حالت اسلاتی هر مدتی را می‌پذیرد و ستون سرویسی null می‌ماند · نوبت سرویسیِ بی‌سرویس قفل نمی‌شود
۴.۵ ConvertReserveTest — شامل active_slot_key و رقابت
۴.۶ ServiceModeSectionDurationTest موجود سبز ماند
۴.۷ BookingTenantTest موجود سبز ماند داخل tests/Appointment — کل ۳۰۹ تست tests/Appointment + tests/Shared سبز
۴.۱۰ ServiceSlotExcludeSelfTest — رفتار exclude ۶ تست / ۱۱ assertion. شامل: بازهٔ خودِ نوبت با exclude برمی‌گردد · مدت بلندتر روی همان ساعت · نوبتِ دیگری همچنان اشغال می‌ماند · null صریح و ضمنی خروجی یکسان · فیلتر repository فقط همان ردیف · exclude کردن نوبت رزرو بی‌اثر
۴.۹ AppointmentServiceFieldsTest — متدها و ستون‌های جدید ۹ تست / ۲۴ assertion. شامل: هم‌گامی ستون تکی · حفظ ترتیب · فهرست خالی → null · حالت اسلاتی هر دو ستون null · مدتِ null بافر را هم null می‌کند · تکراری‌ها dedup · نوبت قدیمیِ فقط-تکی · بقای مقادیر پس از flush/clear
۴.۸ AppointmentEditPage — دو حالت فایل جدید AppointmentEditPage.serviceMode.test.tsx: ۹ تست. شامل: پنهان‌بودن ورودی ساعت · دکمهٔ غیرفعال + راهنما · exclude_appointment_uuid در query · service-reschedule بدون هیچ مدتی · PATCH بعدی بی‌version · هشدار سرویس غیرفعال · و سه تست خط سرخ (اسلاتی و رزرو). فایل موجود AppointmentEditPage.test.tsx هم سبز ماند
۴.۱۳ BackfillServiceDurationTest ۷ تست / ۱۱ assertion: مقدار از خود نوبت (نه بازمحاسبه) · dry-run هیچ نمی‌نویسد · اجرای دوم بی‌اثر · حالت اسلاتی رد · گذشته رد · رزرو رد · لغوشده رد
۴.۱۲ MyAppointmentsServiceFieldsTest ۶ تست / ۱۷ assertion: فهرست کامل سرویس‌ها · مدت و بافر · clinic_uuid · فهرست رزرو هم مدت دارد · نوبت بی‌سرویس آرایهٔ خالی (نه null) · صفحه‌بندی با سه سرویس روی یک نوبت نمی‌شکند
۴.۱۱ کل vitest سبز 86 files / 604 tests passed روی host (داخل ddev باینری esbuild پلتفرم اشتباه دارد — مسئلهٔ محیطی از قبل)

۵. مستندات

# مورد وضعیت یادداشت
۵.۱ docs/api/appointment.md — دو endpoint جدید + توسعهٔ PATCH
۵ ماتریس «کدام endpoint در کدام حالت»
۵ docs/architecture/booking-modes.md ساخته شد تسک ۰۶ حالت سوم را اضافه می‌کند
۵ دو کد خطای جدید مستند شد

۵.۴ کارِ کشف‌شده وسط اجرا (اعلام‌شده، نه بی‌صدا)

# مورد وضعیت یادداشت
ک.۱ باگ واقعی: تبدیل رزرو→نوبت در TransferReserveModal نوبت با مدت صفر در نیمه‌شب می‌ساخت از appointment_time/end_time می‌خواند که روی یک رزرو هر دو 00:00 اند. پیش از این تسک بی‌صدا نوبت خراب می‌ساخت؛ با اعتبارسنجی جدید حالا ERR_APPOINTMENT_003 می‌گرفت. اصلاح: حالت سرویسی → ServiceSlotPicker، حالت اسلاتی → دو ورودی ساعت اجباری. جهت نوبت→رزرو دست‌نخورده
ک.۲ GET /my/appointments سریالایزر جدا دارد و فیلدهای سرویسی را نمی‌داد array-hydration است، نه toArray(). service_items (کوئری دوم، بدون ضرب ردیف و بدون N+1) + clinic_uuid + مدت/بافر اضافه شد. پیش‌نیاز پنهانِ تسک ۰۰ب بود که چک‌لیستش آن را «بررسی کن» گذاشته بود
ک.۳ clinic_uuid در Appointment::toArray() نبود بدون آن، صفحهٔ ویرایش نمی‌داند روش نوبت‌دهی را از کدام برنامه بپرسد

۵.۵ یافته‌های جانبی

# مورد وضعیت یادداشت
ج.۱ یک شکست flaky در اجرای ترکیبی tests/Appointment tests/Shared ⚠️ یک بار ۱ failure دید، سه اجرای بعدی سبز (۳۲۴ تست). نام تست ثبت نشد چون خروجی از دست رفت. بازتولید نشد — بدهی ثبت‌شده، نه «حل‌شده». احتمال: برخورد شمارهٔ موبایل تصادفی در ApiTestCase::createUser روی db_test که هرگز ریست نمی‌شود (خودِ کلاس این را مستند کرده)
ج.۲ یک PHPUnit Notice در tests/Shared ⚠️ پیش از تغییرات این تسک هم بود (baseline). خارج از دامنهٔ این تسک
ج.۳ db_test تاریخچهٔ migration جدا دارد ⚠️ doctrine:migrations:migrate روی آن می‌شکند (Table users already exists)؛ ستون‌های جدید با ALTER دستی اضافه شدند. برای تسک‌های بعدی هم همین لازم است

۶. بازبینی پایانی

# مورد وضعیت یادداشت
۶.۱ همهٔ ردیف‌های بالا وضعیت نهایی دارند (هیچ 🔄 و بی‌دلیل)
۶.۲ ddev exec php bin/phpunit کامل سبز
۶.۳ ddev exec php bin/phpunit --group=slot-mode-frozen سبز
۶.۴ phpstan analyse بدون خطای جدید 🔄 analyse src/AppointmentNo errors. تحلیل کامل src ۱۴ خطا دارد ولی هیچ‌کدام در فایل‌های این تسک نیست (AuthController، BillingController، ClinicServiceController، ServiceItem، DoctorClaimService، InventoryService، PatientService، SecretaryService، HealthController) — از قبل بوده‌اند. در پایان تسک با baseline مقایسه می‌شود
۶.۵ npx tsc --noEmit بدون خطا
۶.۶ yarn test سبز
۶.۷ TenantSchemaCoverageTest + TenantLookupInventoryTest سبز
۶.۸ docs/api/* به‌روز شد
۶.۹ چک‌لیست UI (بخش ۳) کامل شد
۶.۱۰ nobat724_front و clinic-pro-tauri دستی بررسی شدند service_item تکی هم‌گام است؟
۶.۱۱ commit شد، سپس graphify update .
۶.۱۲ موارد به‌تعویق‌افتاده با دلیل و تسک مقصد ثبت شدند