Files
nobat724_front/.claude/prompt/service-based-online-booking.md
T

8.7 KiB
Raw Blame History

نوبت‌دهی آنلاین بر اساس سرویس (Service-first booking)

پروژه

nobat724_front (سایت عمومی نوبت‌دهی). Cross-repo: وابسته به پرامپت backend clinicpro/.claude/prompt/service-based-booking.md — آن اول اجرا شود؛ این پرامپت endpoint و متای آن را مصرف می‌کند.

زمینه

جریان فعلیِ نوبت‌گیری آنلاین یک wizard چندمرحله‌ای است بدون انتخاب سرویس: /appointment/[doctorId] → روز → ساعت (اسلات ثابت) → لاگین/OTP → فرم بیمار → پرداخت. اسلات‌ها از GET /api/v1/appointment-slots?doctor_uuid&date می‌آیند و همه هم‌اندازه‌اند.

بک‌اند حالت جدید «نوبت‌دهی بر اساس سرویس» را اضافه می‌کند: مدت نوبت = مجموع مدت سرویس‌های انتخابی، و endpoint جدید زمان‌های خالیِ کافی را برمی‌گرداند. سایت باید در این حالت اول سرویس را از بیمار بگیرد، سپس فقط زمان‌های خالیِ کافی را نشان دهد.

هدف / spec انگلیسی

When a doctor uses booking_mode = service, insert a service-selection step before the date/time step. The patient picks one or more services; the site fetches candidate start times sized to the summed service duration from the new backend endpoint, shows only those, locks the chosen time via postAppointment (with the service items), then continues to the existing login/pay flow. When the doctor is in slot mode, the current flow is unchanged. «نوبت آزاد» (reserve) is secretary-only and never shown online.

فایل‌های مرتبط

فایل نقش
services/response.js (L52 getAppointmentSlots, L57 getMonthAvailability, L62 postAppointment, L76 getDoctorServices) افزودن getServiceSlots + رساندن service_item_uuids به postAppointment
components/appointment/index.js (L45 AppointmentPage, state L59-62) افزودن state سرویس‌های انتخابی + مدت
components/appointment/Container.js (L42 elements[] step router) افزودن مرحلهٔ انتخاب سرویس در ابتدای wizard (حالت سرویس)
app/component/date/dateTime/index.js (L29-52 fetch اسلات) در حالت سرویس، فراخوانی endpoint سرویس با مدت مجموع
lib/appointmentSlots.js (L1 adaptSlots, L17 hasAvailable) adapter برای پاسخ start_times
components/appointment/detail/SubmitData.js (L138-165 payload) افزودن service_item_uuids به appointmentPayload
app/appointment/[doctorId]/page.js (L9, doctor fetch L17) رساندن booking_mode و لیست سرویس‌ها به AppointmentPage

وضعیت فعلی (کد واقعی)

fetch اسلات ثابت

// app/component/date/dateTime/index.js:29
request.getAppointmentSlots(doctor.uuid, dateStr).then((res) => {
  const parsed = adaptSlots(res);            // data.sessions[].slots
  ...
});
// services/response.js:52
getAppointmentSlots: (doctor_uuid, date) =>
  api.get(`api/v1/appointment-slots?doctor_uuid=${doctor_uuid}&date=${date}`),

payload رزرو — بدون سرویس

// components/appointment/detail/SubmitData.js:138
const appointmentPayload = {
  doctor_uuid, slot_start: selectedSlot.start, slot_end: selectedSlot.end,
  for_self, patient_national_code, patient_gender, city_id,
};   // هیچ service_id ندارد
request.postAppointment(appointmentPayload);   // POST api/v1/appointment

step router

// components/appointment/Container.js:42
const elements = [Date, Login, Verify, Detail, Paying, SuccessPay, FailedPay];
// step: 0 روز/ساعت, 1 لاگین, 2 OTP, 3 فرم, 4 پرداخت ...

وظایف

۱. سرویس در response.js

// services/response.js
getServiceSlots: (doctor_uuid, date, serviceItemUuids) => {
  const q = serviceItemUuids.map(u => `service_item_uuids[]=${encodeURIComponent(u)}`).join('&');
  return api.get(`api/v1/appointment-service-slots?doctor_uuid=${doctor_uuid}&date=${date}&${q}`);
},

و اجازهٔ service_item_uuids در postAppointment (فقط pass-through payload). منبع لیست سرویس‌های پزشک: getDoctorServices (L76, api/v1/categorys/doctor_services) یا endpoint سرویس‌های واقعیِ کلینیک اگر پزشک ServiceItem دارد — بررسی کن کدام سرویس‌ها duration_minutes دارند (فقط همان‌ها قابل‌انتخاب برای این جریان‌اند).

۲. تشخیص حالت پزشک

از پاسخ doctor یا از getMonthAvailability مقدار booking_mode را بخوان (backend آن را در متای weekly-schedule دارد؛ اگر در پاسخ doctor نبود، از یک فیلد مناسب که backend اضافه می‌کند). در AppointmentPage:

  • booking_mode === 'service' → مرحلهٔ انتخاب سرویس فعال شود.
  • در غیر این صورت → دقیقاً جریان فعلی (هیچ تغییری).

۳. مرحلهٔ انتخاب سرویس (فقط حالت سرویس)

  • کامپوننت جدید components/appointment/service/index.js: لیست سرویس‌های پزشک با مدت و قیمت؛ انتخاب یک/چند سرویس؛ نمایش «مدت کل» = Σ duration_minutes.
  • در Container.js این مرحله را قبل از انتخاب روز قرار بده (حالت سرویس) و state سرویس‌ها را در AppointmentPage نگه‌دار.

۴. نمایش فقط زمان‌های کافی

  • در app/component/date/dateTime/index.js: اگر حالت سرویس است، به‌جای getAppointmentSlots از getServiceSlots(uuid, dateStr, selectedServiceUuids) استفاده کن.
  • lib/appointmentSlots.js: یک adapter برای پاسخ data.start_times (آرایهٔ {start,end,start_time,location_id}) اضافه کن که همان ساختار مورد انتظارِ لیست ساعت را بسازد (هر مورد یک دکمهٔ زمان). چون همه از قبل «کافی» هستند، is_available=true.
  • اگر start_times خالی بود: پیام «برای این سرویس در این روز زمان خالی کافی نیست» + هدایت به روز بعدِ دارای ظرفیت (از getMonthAvailability برای فعال/غیرفعال بودن روزها استفاده کن).

۵. قفل زمان هنگام ثبت

  • در SubmitData.js به appointmentPayload کلید service_item_uuids: [...] اضافه کن (حالت سرویس). slot_start از انتخاب می‌آید؛ slot_end را backend از مدت سرویس محاسبه می‌کند (به مقدار کلاینت اعتماد نمی‌شود) ولی همان selectedSlot.end را هم بفرست تا سازگاری حفظ شود.
  • منطق قفل دو-مرحله‌ای موجود (postAppointmentexpires_at → شمارش معکوس در paying/index.js → redirect به payment/order) دست‌نخورده بماند؛ فقط payload سرویس اضافه می‌شود. مدیریت خطای 409 (reset به step 0، SubmitData.js:178) همان بماند.

نکات مهم

  • «نوبت آزاد» آنلاین نیست. هیچ مسیری برای is_reserve در سایت اضافه نکن؛ فقط منشی در پنل. (در سایت «رزرو» فقط برچسب بازاریابی/عنوان است، نه مدل داده — Container.js:102, payment/[uuid]/page.js:206.)
  • حالت اسلاتی دست‌نخورده: وقتی booking_mode !== 'service'، هیچ کامپوننت/فراخوانیِ فعلی نباید تغییر رفتار بدهد. مرحلهٔ سرویس فقط شرطی render شود.
  • فقط سرویس‌های دارای مدت: سرویسی که duration_minutes ندارد نباید در این جریان قابل‌انتخاب باشد (backend هم ۴۲۲ می‌دهد) — در UI غیرفعال/مخفی کن.
  • الگوهای پروژه: App Router، await params؛ فراخوانی API از services/response.js (interceptor پاسخ را در services/api.js:73 به response.data تبدیل می‌کند)؛ تاریخ Jalali با moment/jalali-moment؛ RTL، فونت Vazir، MUI v5 + Tailwind؛ رشته‌های UI فارسی. slug پزشک = uuid.
  • تست: به nobat724-test-suite پروژه اضافه کن — رندر مرحلهٔ سرویس در حالت سرویس، عدم‌رندر در حالت اسلاتی، محاسبهٔ مدت کل، adapter start_times، افزودن service_item_uuids به payload.