The product owner reverted the dark-mode work on nobat724_front, so six rows in task-00b that either claimed dark mode was verified or listed it as debt no longer describe reality. They are now marked out-of-scope with the date and the reason, and the underlying finding is kept as a recorded fact rather than a pending task. Nine admin-panel dark-mode rows across tasks 00-14 are untouched: the admin SPA's dark mode is a separate, working mechanism and was never part of the revert. Also closed nine panel visual-review rows with the screenshots taken this session, and five public-site rows verified in a real browser. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
30 KiB
30 KiB
چکلیست — تسک ۰۰ (تکمیل نوبتدهی سرویسی در clinicpro)
وضعیت کلی: ✅ تمامشده — ۱۰ قابلیت از ۱۰ آخرین بازبینی: ۱۴۰۵/۰۵/۰۸
قواعد: _shared/definition-of-done.md · خط سرخها: _shared/red-lines.md · UI: _shared/ui-conventions.md
۰. خط سرخ — منطق اسلاتی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | SlotModeFrozenTest + سه fixture ساخته شد پیش از هر تغییر کد |
✅ | tests/Appointment/SlotModeFrozenTest.php + tests/Appointment/fixtures/ — هیچ کد تولیدیای هنوز لمس نشده |
| ۰.۲ | time() |
✅ | انحراف عمدی از متن تسک. تاریخ ثابتِ گذشته را isWithinBookingWindow رد میکند و snapshot خالی چیزی را تضمین نمیکند. بهجایش: برنامهٔ قطعی (هر ۷ روز یک شیفت ۰۹:۰۰–۱۱:۰۰/۳۰ دقیقه) روی +3 days، و epoch/uuid با placeholder نرمال میشوند. آنچه قفل میشود: کلیدها، ترتیب، نوعها، ساعتهای محلی |
| ۰.۳ | کامنت «read-only، هیچ تسکی بهروزش نمیکند» بالای هر سه fixture | ✅ | کلید _readme در دو JSON (در loader حذف میشود) + docblock در فایل PHP |
| ۰.۴ | هیچ متد موجود SlotCalculatorService ویرایش نشد |
✅ | فقط getServiceStartTimes یک پارامتر اختیاری با پیشفرض null گرفت. اثبات کارکرد تور ایمنی: تست منجمد همان لحظه قرمز شد و دقیقاً همان پارامتر را نشان داد، در حالی که دو قرارداد پاسخ سبز ماندند. fixture امضا یک بار با تاریخچهٔ مکتوب بهروز شد (header خودش مجاز کرده) |
| ۰.۵ | GET /appointment-slots بیتبهبیت دستنخورده |
✅ | SlotModeFrozenTest سبز در پایان تسک |
| ۰.۶ | 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 است نه Dto — src/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-reservePATCH |
✅ | 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 دارد (اسکرول داخلیِ خودش، نه صفحه). بررسی چشمی در بازبینی پایانی |
| ۳.۱۷ | همهٔ رشتهها فارسی |
✅ | تصمیم ثبتشده در deviations.md — همهٔ رشتههای جدید فارسیاند. ولی پروژه فایل 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 میماند · نوبت سرویسیِ بیسرویس قفل نمیشود |
| ۴.۵ | ConvertReserveTestPatchServiceDurationTest |
✅ | فایل جدا ساخته نشد چون endpoint جدا ساخته نشد (ردیف ۱.۷). تبدیل رزرو در testReserveConvertsToATimedAppointmentThroughPatch و testReserveEntryStoresServicesWithoutDurationCheck پوشش دارد. رقابت روی active_slot_key جدا تست نشد: مکانیزمش دستنخورده است و SlotUniquenessTest موجود از قبل میسنجدش |
| ۴.۶ | ServiceModeSectionDurationTest موجود سبز ماند |
✅ | ۱۱ تست سبز در هر اجرا؛ همان تستی که بیتبهبیتبودنِ استخراج ServiceBookingCalculator را تضمین کرد |
| ۴.۷ | 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 |
✅ | service-reschedule کامل · بخش «حالت نوبتدهی سرویسی» زیر PATCH · exclude_appointment_uuid و clinic_uuid · افزودههای my/appointments. JSON واقعی از اجرای واقعی endpoint (ابزار موقت --group=dump-docs، بعد حذف شد) — نه دستساز |
| ۵.۲ | ماتریس «کدام endpoint در کدام حالت» | ✅ | در docs/architecture/booking-modes.md |
| ۵.۳ | docs/architecture/booking-modes.md ساخته شد |
✅ | ماتریس، قرارداد مدت با مثال واقعی (گام ۴۵ دقیقه → ۱۱:۰۰ پیشنهاد نمیشود)، بخش نوبت رزرو، و جای خالیِ حالت resource برای تسک ۰۶ |
| ۵.۴ | دو کد خطای جدید مستند شد | ✅ | ERR_APPOINTMENT_003/_004 در جدول خطاهای هر دو endpoint، با نمونهٔ JSON واقعی |
۵.۴ کارِ کشفشده وسط اجرا (اعلامشده، نه بیصدا)
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ک.۱ | باگ واقعی: تبدیل رزرو→نوبت در 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 در سوئیت کامل — شناسایی و رفع شد | ✅ | NumericFieldNormalizerTest::testSecretaryCreatedWithPersianDigitsIsStoredLatin. علت: national_code ثابتِ ۰۰۱۲۳۴۵۶۷۸ روی db_test که ریست نمیشود؛ بسته به ترتیب اجرا ۴۲۲ «تکراری» میگرفت. خودِ تست برای موبایل حلقهٔ یکتاسازی داشت ولی برای کد ملی نداشت. رفع: کد ملی تصادفی + assert پاییندستی از همان متغیر. سه اجرای کامل متوالی سبز. خارج از دامنهٔ این تسک بود؛ اعلام و رفع شد تا DoD واقعاً سبز باشد، نه ظاهراً |
| ج.۲ | یک PHPUnit Notice در tests/Shared |
✅ | تصمیم ثبتشده در deviations.md — پیش از تغییرات این تسک هم بود (baseline). خارج از دامنهٔ این تسک |
| ج.۳ | db_test تاریخچهٔ migration جدا دارد |
✅ | تصمیم ثبتشده در deviations.md — doctrine:migrations:migrate روی آن میشکند (Table users already exists)؛ ستونهای جدید با ALTER دستی اضافه شدند. برای تسکهای بعدی هم همین لازم است |
۶. بازبینی پایانی
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | همهٔ ردیفهای بالا وضعیت نهایی دارند | ✅ | تنها ⏳ باقیمانده ۳.۱۳ است، با دلیل و تسک مقصد. سه ردیف 🔄: بررسی چشمی دارکمود/فشرده/موبایل (۳.۹/۳.۱۰/۳.۱۶) — استدلالی تأیید شدند ولی در مرورگر دیده نشدند. ⚠️ ها: ۳.۱۷ (پروژه فایل i18n ندارد) و ج.۲ (PHPUnit notice از قبل) |
| ۶.۲ | ddev exec php bin/phpunit کامل سبز |
✅ | ۱۰۲۶ تست / ۲۸۵۰ assertion، سه اجرای متوالی سبز. برای رسیدن به این، یک flaky از قبلموجود رفع شد (ج.۱) |
| ۶.۳ | --group=slot-mode-frozen سبز |
✅ | OK (3 tests, 8 assertions) |
| ۶.۴ | phpstan analyse بدون خطای جدید |
✅ | با baseline اندازهگیری شد، نه ادعا: روی کامیت پیش از تسک ۱۴ خطا در ۹ فایل، و الان دقیقاً همان ۱۴ خطا در همان ۹ فایل — صفر خطای جدید. analyse src/Appointment تنها → No errors |
| ۶.۵ | npx tsc --noEmit بدون خطا |
✅ | خروجی خالی |
| ۶.۶ | yarn test سبز |
✅ | 86 files / 604 tests passed. ⚠️ داخل ddev باینری esbuild پلتفرم اشتباه دارد (محیطی، از قبل)؛ روی host اجرا شد |
| ۶.۷ | TenantSchemaCoverageTest + TenantLookupInventoryTest سبز |
✅ | OK (7 tests, 163 assertions). شمارندهٔ inventory با دلیل بهروز شد |
| ۶.۸ | docs/api/* بهروز شد |
✅ | docs/api/appointment.md + سند معماری جدید |
| ۶.۹ | چکلیست UI (بخش ۳) کامل شد | ✅ | با سه ردیف 🔄 (بررسی چشمی دارکمود/فشرده/موبایل انجام نشد — استدلالی تأیید شد) و یک ⏳ بهتعویقافتاده |
| ۶.۱۰ | nobat724_front و clinic-pro-tauri دستی بررسی شدند |
✅ | nobat724_front: فقط service_item_uuids[] را میفرستد (services/response.js:85، components/appointment/detail/SubmitData.js:152)؛ هیچجا service_item را نمیخواند → افزودههای additive نمیشکنند. clinic-pro-tauri: src/service/response.js فقط appointment-settings/weekly-schedule را صدا میزند، هیچ endpoint نوبت/سرویسی → متأثر نیست |
| ۶.۱۱ | commit شد، سپس graphify update . |
✅ | هر قابلیت دو کامیت: کد، سپس گراف جدا (طبق دستور کاربر) |
| ۶.۱۲ | موارد بهتعویقافتاده با دلیل و تسک مقصد ثبت شدند | ✅ | ۳.۱۳ (URL state) · بررسی چشمی UI · اصلاح فرمول جمع مدت → تسک ۰۴ |