docs/api/appointment.md gains the service-reschedule endpoint, the service-mode section under PATCH, exclude_appointment_uuid and clinic_uuid on appointment-service-slots, and the my/appointments additions. All JSON bodies are real output captured from the running endpoints, not hand-written. New docs/architecture/booking-modes.md holds the endpoint/mode matrix, the duration contract with a worked example (35 + 10 buffer means a 45-minute step, so 11:00 is not offered even though it looks free), the reserve-entry rules, and a placeholder for the resource mode task 06 will add. Also fixes a pre-existing flaky test that blocked a green suite: NumericFieldNormalizerTest used a fixed national_code against db_test, which is never reset, so depending on execution order the endpoint rejected it as a duplicate. The test already looped for a unique mobile but not for the national code. Out of this task's scope, fixed and declared so the definition of done is actually green rather than apparently green. phpstan was measured against the pre-task commit rather than asserted: 14 errors in 9 files before, the same 14 in the same 9 files now. Task 00 complete: 1026 tests green across three consecutive runs, 604 frontend tests green, slot-mode contract frozen and verified. 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>
29 KiB
29 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 دارد (اسکرول داخلیِ خودش، نه صفحه). بررسی چشمی در بازبینی پایانی |
| ۳.۱۷ | همهٔ رشتهها فارسی |
⚠️ | همهٔ رشتههای جدید فارسیاند. ولی پروژه فایل 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 |
⚠️ | پیش از تغییرات این تسک هم بود (baseline). خارج از دامنهٔ این تسک |
| ج.۳ | db_test تاریخچهٔ migration جدا دارد |
⚠️ | 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 · اصلاح فرمول جمع مدت → تسک ۰۴ |