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:
hamed
2026-07-15 23:15:45 +03:30
parent 6904361e32
commit 5937f7e176
16 changed files with 817 additions and 26 deletions
@@ -34,8 +34,19 @@ class AppointmentSettingsController extends BaseController
private readonly DoctorRepository $doctorRepo,
private readonly DoctorAddressRepository $addressRepo,
private readonly ClinicRepository $clinicRepo,
private readonly \App\ClinicService\Repository\ServiceItemRepository $itemRepo,
) {}
/**
* در حالت نوبت‌دهی سرویسی، پزشک باید حداقل یک سرویسِ «نمایش در نوبت‌دهی»
* (bookable) داشته باشد؛ وگرنه هیچ نوبتی قابل‌محاسبه نیست.
*/
private function serviceModeHasNoBookable(array $meta, \App\Doctor\Entity\Doctor $doctor): bool
{
return ($meta['booking_mode'] ?? WeeklySchedule::MODE_SLOT) === WeeklySchedule::MODE_SERVICE
&& $this->itemRepo->countBookableByEntity('doctor', $doctor->getId()) === 0;
}
// ── Weekly Schedule ───────────────────────────────────────────────────────
#[Route('/api/v1/appointment-settings/weekly-schedule', methods: ['POST'])]
@@ -69,6 +80,10 @@ class AppointmentSettingsController extends BaseController
$schedule->setMeta($data['meta']);
}
if ($this->serviceModeHasNoBookable($schedule->getMeta(), $doctor)) {
return $this->error(ErrorCodes::ERR_VALIDATION_001, 'برای نوبت‌دهی سرویسی حداقل یک سرویس با «نمایش در نوبت‌دهی» لازم است', 422, 'booking_mode');
}
$this->scheduleRepo->save($schedule);
return $this->success(['data' => $schedule->toArray()], 201);
@@ -103,6 +118,10 @@ class AppointmentSettingsController extends BaseController
$schedule->setMeta($data['meta']);
}
if ($this->serviceModeHasNoBookable($schedule->getMeta(), $schedule->getDoctor())) {
return $this->error(ErrorCodes::ERR_VALIDATION_001, 'برای نوبت‌دهی سرویسی حداقل یک سرویس با «نمایش در نوبت‌دهی» لازم است', 422, 'booking_mode');
}
$this->scheduleRepo->save($schedule);
return $this->success(['data' => $schedule->toArray()]);