feat: add service-based booking mode to appointment scheduling
- Introduced a new booking mode in WeeklySchedule to support service-based appointments. - Updated SlotCalculatorService to calculate available start times based on selected service durations and buffer times. - Enhanced AppointmentController to handle service items during booking, calculating slot_end on the server side. - Implemented validation to ensure at least one bookable service exists for doctors in service mode. - Added new API endpoint to retrieve available appointment slots based on selected services. - Updated MyAppointmentsController to accept service items during appointment creation. - Modified ServiceItem entity to include a bookable flag, allowing services to be marked for scheduling. - Created migration to add bookable column to service_items table. - Added tests for service-based slot calculations and validation logic.
This commit is contained in:
@@ -101,11 +101,15 @@ interface BookingMeta {
|
||||
online_booking_enabled: boolean;
|
||||
booking_window_value: number;
|
||||
booking_window_unit: 'week' | 'month';
|
||||
booking_mode: 'slot' | 'service';
|
||||
buffer_minutes: number;
|
||||
}
|
||||
const DEFAULT_BOOKING_META: BookingMeta = {
|
||||
online_booking_enabled: true,
|
||||
booking_window_value: 1,
|
||||
booking_window_unit: 'month',
|
||||
booking_mode: 'slot',
|
||||
buffer_minutes: 0,
|
||||
};
|
||||
interface WeeklyScheduleData { uuid: string; doctor_uuid: string; schedule: NewScheduleMap; meta?: BookingMeta; }
|
||||
|
||||
@@ -1384,6 +1388,53 @@ export function WeeklyScheduleTab({ doctorUuid, addresses, readOnly = false }: {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ─ روش نوبتدهی */}
|
||||
<div className="mb-3 rounded-xl border border-slate-200 dark:border-gray-700 overflow-hidden">
|
||||
<div className="px-4 py-3 bg-slate-50 dark:bg-gray-800/50">
|
||||
<span className="text-sm font-medium text-slate-700 dark:text-slate-200">روش نوبتدهی</span>
|
||||
</div>
|
||||
<div className="px-4 py-3 space-y-3">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{([['slot', 'اسلاتی (مدت ثابت)'], ['service', 'بر اساس سرویس']] as const).map(([val, lbl]) => (
|
||||
<button
|
||||
key={val}
|
||||
type="button"
|
||||
onClick={() => setMeta(m => ({ ...m, booking_mode: val }))}
|
||||
className={`px-3 py-1.5 text-sm rounded-lg border transition-colors ${
|
||||
meta.booking_mode === val
|
||||
? 'bg-[var(--primary)] text-white border-[var(--primary)]'
|
||||
: 'bg-white dark:bg-gray-900 text-slate-600 dark:text-slate-300 border-slate-200 dark:border-gray-700'
|
||||
}`}
|
||||
>
|
||||
{lbl}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{meta.booking_mode === 'service' ? (
|
||||
<>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-sm text-slate-600 dark:text-slate-400">فاصله بین نوبتها</span>
|
||||
<input
|
||||
type="number"
|
||||
min={0}
|
||||
value={meta.buffer_minutes}
|
||||
onChange={(e) => setMeta(m => ({ ...m, buffer_minutes: Math.max(0, Number(e.target.value) || 0) }))}
|
||||
className="w-16 text-center text-sm rounded-lg border border-slate-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 py-1.5 focus:outline-none focus:ring-0"
|
||||
/>
|
||||
<span className="text-sm text-slate-600 dark:text-slate-400">دقیقه</span>
|
||||
</div>
|
||||
<p className="text-xs text-slate-400 dark:text-slate-500 leading-relaxed">
|
||||
مدت هر نوبت از «مدت سرویس» انتخابشده تعیین میشود. لازم است حداقل یک سرویس با «نمایش در نوبتدهی» در بخش <span className="font-medium">سرویسها</span> تعریف کنید، وگرنه ذخیره نمیشود.
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<p className="text-xs text-slate-400 dark:text-slate-500 leading-relaxed">
|
||||
مدت هر نوبت از «زمان هر نوبت» در شیفتهای زیر تعیین میشود.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ─ نوبتدهی آنلاین */}
|
||||
<div className="mb-3 rounded-xl border border-slate-200 dark:border-gray-700 overflow-hidden">
|
||||
{/* header + toggle */}
|
||||
|
||||
Reference in New Issue
Block a user