Files
clinicpro/docs/new_feture/taskes/task-00-service-mode-completion/task.md
T
hamed 158dcb58aa feat: implement service mode completion for nobat724_front
- Add task for completing service mode in clinicpro with detailed objectives and acceptance criteria.
- Create architecture documentation for task 00b, outlining involved components and necessary changes.
- Develop checklist for task 00b to ensure all requirements are met.
- Document implementation notes for task 00b, emphasizing API contract checks and design system adherence.
- Update task documentation for task 00b, specifying goals and current issues with service mode.
2026-07-30 11:56:08 +03:30

144 lines
9.5 KiB
Markdown

# تسک ۰۰ — تکمیل نوبت‌دهی سرویسی در clinicpro
**فاز:** ۰ (تثبیت وضعیت فعلی) · **وابستگی:** — · **زمان:** ۱۴-۱۸ ساعت
**پیش‌نیاز همهٔ تسک‌های ۰۱ به بعد**
---
## ⛔ خط سرخ
منطق اسلاتی (`booking_mode = 'slot'`) در این تسک **به هیچ عنوان** دست‌کاری نمی‌شود.
فهرست کامل قفل‌شده‌ها: [_shared/red-lines.md](../_shared/red-lines.md).
این تسک fixture و تست `--group=slot-mode-frozen` را **می‌سازد** — همان تستی که همهٔ
تسک‌های بعدی باید سبز نگهش دارند.
---
## هدف
حالت `booking_mode = 'service'` در مسیر **رزرو** کار می‌کند، ولی در بقیهٔ چرخهٔ عمر نوبت
غایب است. این تسک آن را کامل می‌کند تا موتور چندمنبعی (تسک ۰۶) روی پایهٔ سالم ساخته شود.
## وضعیت فعلی — چه کار می‌کند و چه نمی‌کند
### ✅ کار می‌کند
| مسیر | فایل |
|---|---|
| انتخاب سرویس و اسلات در رزرو عمومی | `GET /api/v1/appointment-booking-services/{doctorUuid}` · `GET /api/v1/appointment-service-slots` |
| محاسبهٔ زمان‌های شروع بر اساس مدت سرویس | `SlotCalculatorService::getServiceStartTimes()` |
| ثبت نوبت با چند سرویس | `POST /api/v1/appointment` + `appointment_service_items` |
| توگل روش نوبت‌دهی در تنظیمات | `assets/admin/components/schedule/ScheduleSection.tsx` |
| ساخت نوبت از پنل | `assets/admin/pages/AppointmentCreatePage.tsx` + `components/appointments/ServiceSlotPicker.tsx` + `hooks/useDoctorBookingServices.ts` |
| ساخت سریع از drawer | `assets/admin/components/NewAppointmentDrawer.tsx` |
| رزرو از سایت | `nobat724_front/components/appointment/*` |
### ❌ کار نمی‌کند — شکاف‌های این تسک
**۱. ویرایش و جابه‌جایی نوبت، حالت سرویسی را نمی‌شناسد.**
`PATCH /api/v1/appointment/{uuid}` ([AppointmentController.php:1077](../../../src/Appointment/Controller/AppointmentController.php#L1077)):
```php
$hasStart = array_key_exists('slot_start', $data);
$hasEnd = array_key_exists('slot_end', $data);
// … فقط این دو بررسی می‌شوند:
if ($newEnd <= $newStart) { /* 422 */ }
if ($this->appointmentRepo->isSlotTaken($doctor, $newStart, $newEnd, $id)) { /* 409 */ }
```
سه مشکل:
- مدت دلخواه پذیرفته می‌شود؛ هیچ بررسی‌ای که `slot_end - slot_start` با مجموع مدت
سرویس‌های نوبت بخواند وجود ندارد
- `buffer_minutes` نادیده گرفته می‌شود — نوبت جدید می‌تواند چسبیده به نوبت بعدی بنشیند
- فقط `service_item_uuid` تکی به‌روز می‌شود؛ `service_items` (ManyToMany) دست‌نخورده
می‌ماند → نوبت با سرویس‌های قبلی و مدت جدید ناسازگار می‌شود
**۲. `AppointmentEditPage.tsx` ورودی دستی ساعت دارد.**
سه فیلد `date`/`start`/`end` آزاد + یک `SearchableSelect` تکی برای سرویس
([AppointmentEditPage.tsx:74-76](../../../assets/admin/pages/AppointmentEditPage.tsx#L74)).
هیچ `ServiceSlotPicker` ای نیست، هیچ چند-سرویسی نیست.
نتیجه: منشی نوبت سرویسیِ ۴۵ دقیقه‌ای را ویرایش می‌کند، ۲۰ دقیقه می‌گذارد، سیستم قبول
می‌کند، و بیمار بعدی روی نوبت اول می‌نشیند.
**۳. نوبت رزرو (`is_reserve`) در حالت سرویسی معنا ندارد.**
`NewAppointmentDrawer.tsx:72` صریح: `$serviceMode = bookingMode === 'service' && !isReserve`.
پس نوبت رزرو همیشه اسلاتی رفتار می‌کند و `ReserveAppointmentsPage.tsx` فقط
`service_item?.name` تکی نشان می‌دهد. تبدیل رزرو به نوبت واقعی هم مسیر سرویسی ندارد.
**۴. `patient_facing` بودن مدت جایی نمایش داده نمی‌شود.**
پاسخ `appointment-service-slots` مدت کل را می‌دهد ولی نوبت ثبت‌شده هیچ‌جا نگه نمی‌دارد
که این مدت از کدام سرویس‌ها و چه بافری آمده. لیست نوبت‌ها فقط `slot_start/slot_end` دارد.
## دامنه
**هست:**
- `ServiceBookingCalculator` — یک سرویس واحد که «مدت مجاز یک ترکیب سرویس» را حساب می‌کند
(استخراج منطق تکرارشدهٔ `serviceSlots()` از کنترلر)
- اعتبارسنجی حالت سرویسی در `PATCH /appointment/{uuid}`
- endpoint جابه‌جایی سرویس‌آگاه: `POST /api/v1/appointment/{uuid}/service-reschedule`
- `ServiceSlotPicker` در `AppointmentEditPage`
- حالت سرویسی برای نوبت رزرو + تبدیل رزرو به نوبت
- ستون‌های `service_total_minutes` و `service_buffer_minutes` روی `appointments`
- fixture و تست `--group=slot-mode-frozen`
**نیست:** بخش‌های نوبت، چند منبع، قوانین (تسک ۰۵ به بعد). `nobat724_front` (تسک ۰۰ب).
## Endpoint ها
| متد | مسیر | توضیح |
|---|---|---|
| POST | `/api/v1/appointment/{uuid}/service-reschedule` | جابه‌جایی سرویس‌آگاه: سرویس‌ها + زمان شروع؛ مدت را خودش حساب می‌کند |
| PATCH | `/api/v1/appointment/{uuid}` | **توسعه** — در حالت سرویسی مدت را اعتبارسنجی می‌کند و `service_item_uuids[]` می‌پذیرد |
| POST | `/api/v1/appointment/{uuid}/convert-reserve` | تبدیل نوبت رزرو به نوبت زمان‌دار (هر دو حالت) |
| GET | `/api/v1/appointment-service-slots` | **توسعه** — پارامتر `exclude_appointment_uuid` برای جابه‌جایی |
هیچ endpoint اسلاتی‌ای تغییر نمی‌کند. `GET /appointment-slots` دست‌نخورده.
## معیار پذیرش
- ✅ موفق: نوبت سرویسیِ «لیزر صورت (۲۰) + بیکینی (۱۵)» با مدت ۳۵ دقیقه.
`POST /appointment/{uuid}/service-reschedule` با زمان جدید و همان سرویس‌ها →
`200` و `slot_end - slot_start = 35 * 60` دقیقاً.
- ✅ موفق: همان endpoint با حذف بیکینی → مدت خودکار ۲۰ دقیقه می‌شود، بدون اینکه کلاینت
عددی بفرستد.
- ✅ موفق: `GET /appointment-service-slots?…&exclude_appointment_uuid={uuid}` بازهٔ خودِ
نوبت را اشغال حساب نمی‌کند، پس زمان فعلی‌اش در فهرست می‌آید.
- ✅ موفق: `AppointmentEditPage` برای نوبت سرویسی، `ServiceSlotPicker` نشان می‌دهد و
ورودی دستی ساعت را **پنهان** می‌کند؛ برای نوبت اسلاتی، دقیقاً رفتار امروز.
- ✅ موفق: نوبت رزرو در حالت سرویسی سرویس‌هایش را ذخیره می‌کند و
`POST /convert-reserve` با زمان انتخابی، نوبت زمان‌دار با مدت درست می‌سازد.
- ✅ موفق (**خط سرخ**): `ddev exec php bin/phpunit --group=slot-mode-frozen` سبز است و
fixture قرارداد اسلاتی بیت‌به‌بیت تغییر نکرده.
- ❌ خطا: `PATCH` با `slot_end - slot_start` ناسازگار با مدت سرویس‌ها →
`422` `ERR_SERVICE_DURATION_MISMATCH` با پیام فارسی شامل مدت درست.
- ❌ خطا: `service-reschedule` روی نوبت **اسلاتی**`422` `ERR_WRONG_BOOKING_MODE`.
- ❌ خطا: `service-reschedule` با زمان شروعی که در `getServiceStartTimes` نیست →
`422` با پیام «این زمان برای مدت انتخابی در دسترس نیست».
- ❌ خطا: سرویس محیط دیگر در `service_item_uuids[]``404` (بدون لو دادن وجودش).
- ⚠️ مرزی: نوبتی که سرویس‌هایش غیرفعال (`bookable=false`) شده‌اند → جابه‌جایی مجاز است
با `warnings[]`؛ افزودن سرویس غیرفعال ممنوع.
- ⚠️ مرزی: `buffer_minutes` تغییر کرد بعد از ثبت نوبت → نوبت موجود سالم می‌ماند؛
فقط جابه‌جایی جدید بافر جدید را می‌گیرد.
- ⚠️ مرزی: جابه‌جایی به روزی که برنامهٔ هفتگی آن محیط عوض شده → همان اعتبارسنجی
`getServiceStartTimes`، پس خودکار پوشش داده می‌شود.
- ⚠️ مرزی: نوبت سرویسی بدون هیچ سرویس (داده قدیمی) → مدت موجود حفظ می‌شود و
`warnings[]` می‌گوید سرویس ثبت نشده. **رد نمی‌شود.**
- ⚠️ مرزی: `PATCH` بدون `slot_start` روی نوبت سرویسی (فقط تغییر یادداشت) → بدون
اعتبارسنجی مدت، مثل امروز.
## خروجی
- `src/Appointment/Service/ServiceBookingCalculator.php` + توسعهٔ کنترلر
- `assets/admin/pages/AppointmentEditPage.tsx` توسعه‌یافته
- `assets/admin/pages/ReserveAppointmentsPage.tsx` توسعه‌یافته
- migration دو ستون تهی‌پذیر
- `tests/Appointment/SlotModeFrozenTest.php` + fixture
- `docs/api/appointment.md` به‌روزرسانی
- [checklist.md](checklist.md) کامل‌شده