feat(appointment): enforce booking window in slot calculation

buildAllSessions now rejects dates that are in the past, beyond the
doctor's booking window (today + value week|month), or when online
booking is disabled — gating overrides and weekly schedule alike. Add a
public hasAnyAvailability() for the month-availability endpoint. Window
config is read from the schedule meta, falling back to defaults.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-06-15 16:18:00 +03:30
co-authored by Claude Opus 4.8
parent 09aba878f8
commit f82d92b3ed
@@ -6,6 +6,7 @@ use App\Appointment\Repository\AppointmentRepository;
use App\Appointment\Repository\DateOverrideRepository;
use App\Appointment\Repository\HolidayRepository;
use App\Appointment\Repository\WeeklyScheduleRepository;
use App\Appointment\Entity\WeeklySchedule;
use App\Doctor\Entity\Doctor;
@@ -50,6 +51,44 @@ class SlotCalculatorService
], $sessions);
}
/**
* Whether a doctor has at least one slot on the given date.
* Lightweight check for the month-availability endpoint.
*/
public function hasAnyAvailability(Doctor $doctor, string $date): bool
{
return !empty($this->buildAllSessions($doctor, $date));
}
/**
* Booking is allowed only when online booking is enabled and the date is
* today..(today + window). Past dates are always rejected.
*/
private function isWithinBookingWindow(Doctor $doctor, int $dayStart): bool
{
$todayStart = (int) strtotime('today 00:00:00');
if ($dayStart < $todayStart) {
return false;
}
$meta = $this->getBookingMeta($doctor);
if (!($meta['online_booking_enabled'] ?? true)) {
return false;
}
$value = max(1, (int)($meta['booking_window_value'] ?? 1));
$unit = ($meta['booking_window_unit'] ?? 'month') === 'week' ? 'week' : 'month';
$maxStart = (int) strtotime("today +{$value} {$unit} 00:00:00");
return $dayStart <= $maxStart;
}
private function getBookingMeta(Doctor $doctor): array
{
$schedule = $this->scheduleRepo->findByDoctor($doctor);
return $schedule ? $schedule->getMeta() : WeeklySchedule::DEFAULT_META;
}
/**
* Core: build all sessions with their slots, grouped by shift.
*
@@ -60,6 +99,11 @@ class SlotCalculatorService
$dayStart = (int) strtotime($date . ' 00:00:00');
$dayEnd = $dayStart + 86400;
// 0. Online booking disabled or date outside the booking window
if (!$this->isWithinBookingWindow($doctor, $dayStart)) {
return [];
}
// 1. Blocked by holiday
if (!empty($this->holidayRepo->findActiveByDoctor($doctor, $dayStart, $dayEnd - 1))) {
return [];