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

9.5 KiB

تسک ۰۰ — تکمیل نوبت‌دهی سرویسی در clinicpro

فاز: ۰ (تثبیت وضعیت فعلی) · وابستگی: — · زمان: ۱۴-۱۸ ساعت پیش‌نیاز همهٔ تسک‌های ۰۱ به بعد


خط سرخ

منطق اسلاتی (booking_mode = 'slot') در این تسک به هیچ عنوان دست‌کاری نمی‌شود. فهرست کامل قفل‌شده‌ها: _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):

$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). هیچ 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 کامل‌شده