8.7 KiB
نوبتدهی آنلاین بر اساس سرویس (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را هم بفرست تا سازگاری حفظ شود. - منطق قفل دو-مرحلهای موجود (
postAppointment→expires_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پروژه اضافه کن — رندر مرحلهٔ سرویس در حالت سرویس، عدمرندر در حالت اسلاتی، محاسبهٔ مدت کل، adapterstart_times، افزودنservice_item_uuidsبه payload.