From d8fe9a3875b1b54b8f21c6ecb40ac7b4c79b71f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Wed, 4 Nov 2026 10:10:37 +0100 Subject: [PATCH] fix: stop counting held seats in edit usage --- src/Service/ServiceAvailabilityCalculator.php | 8 ++++ .../ServiceAvailabilityCalculatorTest.php | 43 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/Service/ServiceAvailabilityCalculator.php b/src/Service/ServiceAvailabilityCalculator.php index cd85ce03..9fb3f638 100644 --- a/src/Service/ServiceAvailabilityCalculator.php +++ b/src/Service/ServiceAvailabilityCalculator.php @@ -219,6 +219,10 @@ class ServiceAvailabilityCalculator /** * Calculate how many times each service has been selected by other participants. * + * Only new selections count. In edit mode the form is prefilled with the live booking, whose + * seats BusPro has already deducted from frei; counting them again would report a contingent + * as exhausted while seats are still free. + * * @param BookingDto $bookingDto The booking data with participant selections * @param int $currentParticipantIndex The index of the participant currently filling the form * @@ -238,6 +242,10 @@ class ServiceAvailabilityCalculator $participantUsage = $this->countParticipantServiceUsage($participant); foreach ($participantUsage as $serviceId => $count) { + if (null !== $bookingDto->booking && $bookingDto->booking->hasServiceForParticipant($index, $serviceId)) { + --$count; + } + $serviceUsage[$serviceId] = ($serviceUsage[$serviceId] ?? 0) + $count; } } diff --git a/tests/Service/ServiceAvailabilityCalculatorTest.php b/tests/Service/ServiceAvailabilityCalculatorTest.php index 66301679..912e734f 100644 --- a/tests/Service/ServiceAvailabilityCalculatorTest.php +++ b/tests/Service/ServiceAvailabilityCalculatorTest.php @@ -112,6 +112,30 @@ class ServiceAvailabilityCalculatorTest extends TestCase $this->assertFalse($this->calculator->isServiceUnavailable(1, $bookingDto, 0)); } + public function testSeatsHeldByOtherParticipantsDoNotCountTowardUsageInEditMode(): void + { + // BusPro reports frei="3" after deducting the seats this booking already holds + $parkingService = $this->createParkingService(id: 1, available: 3); + $bookingDto = $this->createEditBookingDtoWithServices([$parkingService]); + $this->holdParkingForOtherParticipants($bookingDto, $parkingService, [1, 2, 3, 4, 5, 6, 7, 8]); + + $this->assertFalse($this->calculator->isServiceUnavailable(1, $bookingDto, 0)); + $this->assertSame(3, $this->calculator->calculateRemainingAvailability($bookingDto, 0)[1]); + } + + public function testNewSelectionByOtherParticipantStillCountsTowardUsageInEditMode(): void + { + $parkingService = $this->createParkingService(id: 1, available: 1); + $bookingDto = $this->createEditBookingDtoWithServices([$parkingService]); + + $otherParticipant = new ParticipantDto(); + $otherParticipant->parkingService = $parkingService; + $bookingDto->participants[1] = $otherParticipant; + + // Participant 1 picks the last slot in this edit without holding it in the booking + $this->assertTrue($this->calculator->isServiceUnavailable(1, $bookingDto, 0)); + } + public function testAutoBookedOnRequestServiceStaysAvailableInEditMode(): void { $service = $this->createSkiPass(id: 1, available: 10, status: Constants::STATUS_ON_REQUEST); @@ -236,4 +260,23 @@ class ServiceAvailabilityCalculatorTest extends TestCase return $bookingDto; } + + /** + * Prefills the given participants with the parking service, as the edit form does for a + * service they hold in the live booking. + * + * @param list $participantIndexes + */ + private function holdParkingForOtherParticipants(BookingDto $bookingDto, Service $parkingService, array $participantIndexes): void + { + $held = clone $parkingService; + $held->mapping = $participantIndexes; + $bookingDto->booking->additionalServices[$parkingService->id] = $held; + + foreach ($participantIndexes as $index) { + $participant = new ParticipantDto(); + $participant->parkingService = $parkingService; + $bookingDto->participants[$index] = $participant; + } + } }