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>
This commit is contained in:
hamed
2026-07-30 15:08:35 +03:30
co-authored by Claude Opus 5
parent f1ea7bb161
commit 7482eb2ba3
3 changed files with 288 additions and 3 deletions
@@ -1,6 +1,6 @@
# چک‌لیست — تسک ۰۰ (تکمیل نوبت‌دهی سرویسی در clinicpro)
**وضعیت کلی:** 🔄 در حال انجام — قابلیت ۸ از ۱۰ تمام شد (مانده: backfill، مستندات)
**وضعیت کلی:** 🔄 در حال انجام — قابلیت ۹ از ۱۰ تمام شد (مانده: مستندات + بازبینی پایانی)
**آخرین بازبینی:** ۱۴۰۵/۰۵/۰۸
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
@@ -52,8 +52,8 @@ UI: [_shared/ui-conventions.md](../_shared/ui-conventions.md)
| ۲.۱ | `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 | | |
| ۲.۵ | backfill مقدار را از خود نوبت می‌گیرد، نه بازمحاسبه از سرویس‌ها | | |
| ۲.۴ | `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 — پنل ادمین
@@ -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` هم سبز ماند |
| ۴.۱۳ | `BackfillServiceDurationTest` | ✅ | ۷ تست / ۱۱ assertion: مقدار از خود نوبت (نه بازمحاسبه) · dry-run هیچ نمی‌نویسد · اجرای دوم بی‌اثر · حالت اسلاتی رد · گذشته رد · رزرو رد · لغوشده رد |
| ۴.۱۲ | `MyAppointmentsServiceFieldsTest` | ✅ | ۶ تست / ۱۷ assertion: فهرست کامل سرویس‌ها · مدت و بافر · `clinic_uuid` · فهرست رزرو هم مدت دارد · نوبت بی‌سرویس آرایهٔ خالی (نه null) · **صفحه‌بندی با سه سرویس روی یک نوبت نمی‌شکند** |
| ۴.۱۱ | کل vitest سبز | ✅ | `86 files / 604 tests passed` روی host (داخل ddev باینری esbuild پلتفرم اشتباه دارد — مسئلهٔ محیطی از قبل) |