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.
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
# تسک ۰۰ — تکمیل نوبتدهی سرویسی در 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) کاملشده
|
||||
Reference in New Issue
Block a user