From f82d92b3ed6f829166814cca9090a913db707ecb Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Mon, 15 Jun 2026 16:18:00 +0330 Subject: [PATCH] feat(appointment): enforce booking window in slot calculation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../Service/SlotCalculatorService.php | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/Appointment/Service/SlotCalculatorService.php b/src/Appointment/Service/SlotCalculatorService.php index 54dfd030..fa8d5888 100644 --- a/src/Appointment/Service/SlotCalculatorService.php +++ b/src/Appointment/Service/SlotCalculatorService.php @@ -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 [];