docs(booking): record task 00 checklist through feature 8

Marks two task-text assumptions as unfounded with the evidence (DataTable
migration, i18n file), defers the URL-state row to a task that owns it, and logs
the three pieces of work discovered mid-run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-07-30 15:05:23 +03:30
co-authored by Claude Opus 5
parent 9502ed61f6
commit f1ea7bb161
@@ -1,6 +1,6 @@
# چک‌لیست — تسک ۰۰ (تکمیل نوبت‌دهی سرویسی در clinicpro)
**وضعیت کلی:** 🔄 در حال انجام — قابلیت ۷ از ۱۰ تمام شد (مانده: ReserveAppointmentsPage، backfill، مستندات)
**وضعیت کلی:** 🔄 در حال انجام — قابلیت ۸ از ۱۰ تمام شد (مانده: backfill، مستندات)
**آخرین بازبینی:** ۱۴۰۵/۰۵/۰۸
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
@@ -64,19 +64,19 @@ UI: [_shared/ui-conventions.md](../_shared/ui-conventions.md)
| ۳.۲ | `AppointmentEditPage`: حالت اسلاتی **دقیقاً** رفتار امروز | ✅ | ۵ تست موجود صفحه سبز ماند + دو تست خط سرخ تازه: سه فیلد ساعت با مقدار هیدریت‌شده، و همان `PATCH` با `version` بدون هیچ `POST` |
| ۳.۳ | ورودی دستی ساعت در حالت سرویسی **پنهان**، نه disabled | ✅ | `queryByLabelText('ساعت شروع')` در حالت سرویسی `null` است. `SearchableSelect` سرویس تکی هم پنهان می‌شود — نگه‌داشتنش دو منبع برای یک چیز بود |
| ۳.۴ | `ServiceSlotPicker` موجود بازاستفاده شد؛ نسخهٔ موازی ساخته نشد | ✅ | دو prop اختیاری: `excludeAppointmentUuid` و `initialSelection`. رفتار فعلی‌اش در `AppointmentCreatePage` و `AppointmentsPage` دست‌نخورده (هر دو prop `undefined`). یک اصلاح لازم شد: effect ریست روی تعویض پزشک، اجرای نخست را معاف کند وگرنه `initialSelection` همان لحظه پاک می‌شد |
| ۳.۵ | `ReserveAppointmentsPage`: سرویس‌ها + دکمهٔ تبدیل | | |
| ۳.۶ | `ReserveAppointmentsPage` از جدول خام به `DataTable` مهاجرت کرد | ⏳ | `<td style={td}>` حذف شد |
| ۳.۷ | مدت و بافر فارسی با واحد: «۳۵ دقیقه (+۱۰ دقیقه فاصله)» | 🔄 | در `AppointmentEditPage` هست («زمان انتخابی: ۱۶:۰۰ (+۱۰ دقیقه فاصله)») و `ServiceSlotPicker` خودش «مدت کل: N دقیقه» را نشان می‌دهد. `ReserveAppointmentsPage` مانده — قابلیت ۸ |
| ۳.۸ | هیچ رنگ/شعاع/سایهٔ hard-code — همه از توکن‌های `styles.css` | | |
| ۳.۹ | دارک‌مود (`data-theme="dark"`) بررسی شد | | |
| ۳.۱۰ | حالت فشرده (`data-density="compact"`) بررسی شد | | |
| ۳.۵ | `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` روی هر دو صفحه | | |
| ۳.۱۳ | وضعیت لیست رزروها در URL با `useUrlState` | ⏳ | |
| ۳.۱۲ | `backTo`/`BackButton` روی هر دو صفحه | | هر دو از قبل داشتند (`BackButton fallback="/admin/appointments"`) — دست‌نخورده ماند |
| ۳.۱۳ | وضعیت لیست رزروها در URL با `useUrlState` | ⏳ | **به تعویق افتاد.** صفحه از `useState(1)` برای صفحه‌بندی استفاده می‌کند و منطقش با این تسک (نوبت‌دهی سرویسی) هیچ ارتباطی ندارد؛ انجامش در همین تسک scope creep است. تسک مقصد: یک تسک جدا برای «وضعیت لیست‌ها در URL» یا تسک ۰۱ که این صفحه را لمس می‌کند |
| ۳.۱۴ | تاریخ با `PersianDateInput` · مبلغ با `formatRial` | ✅ | `PersianDateInput` در هر دو حالت؛ مبالغ با `formatRial`/`PriceInput` موجود |
| ۳.۱۵ | RTL بررسی شد (`ms/me` نه `ml/mr`) | | |
| ۳.۱۶ | موبایل بررسی شد — بدون اسکرول افقی | | |
| ۳.۱۷ | همهٔ رشته‌ها فارسی و از i18n | ⏳ | |
| ۳.۱۵ | 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` |
## ۴. تست
@@ -93,6 +93,7 @@ UI: [_shared/ui-conventions.md](../_shared/ui-conventions.md)
| ۴.۱۰ | `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` هم سبز ماند |
| ۴.۱۲ | `MyAppointmentsServiceFieldsTest` | ✅ | ۶ تست / ۱۷ assertion: فهرست کامل سرویس‌ها · مدت و بافر · `clinic_uuid` · فهرست رزرو هم مدت دارد · نوبت بی‌سرویس آرایهٔ خالی (نه null) · **صفحه‌بندی با سه سرویس روی یک نوبت نمی‌شکند** |
| ۴.۱۱ | کل vitest سبز | ✅ | `86 files / 604 tests passed` روی host (داخل ddev باینری esbuild پلتفرم اشتباه دارد — مسئلهٔ محیطی از قبل) |
## ۵. مستندات
@@ -104,6 +105,14 @@ UI: [_shared/ui-conventions.md](../_shared/ui-conventions.md)
| ۵.۳ | `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()` نبود | ✅ | بدون آن، صفحهٔ ویرایش نمی‌داند روش نوبت‌دهی را از کدام برنامه بپرسد |
## ۵.۵ یافته‌های جانبی
| # | مورد | وضعیت | یادداشت |