fix: consider parking space availability

This commit is contained in:
Björn Fromme
2026-07-23 17:44:08 +02:00
parent fafc60eba9
commit b323c120f9
7 changed files with 237 additions and 4 deletions
@@ -135,6 +135,64 @@ class ParticipantFieldOptionsProviderMandatoryServiceTest extends TestCase
$this->assertSame('Parkplatz (€12,50)', $options['label']);
}
public function testParkingIsReadonlyWhenSoldOut(): void
{
$parkingService = new Service();
$parkingService->id = 3;
$parkingService->label = 'Parkplatz';
$parkingService->subType = Constants::TOKEN_PARKING;
$parkingService->available = 0;
$parkingService->price = 12.5;
$travel = $this->createTravelWithAdditionalServices([$parkingService]);
$bookingDto = new BookingDto($travel, 1);
$participant = new ParticipantDto();
$participant->index = 0;
$participant->dateOfBirth = $travel->dateFrom->modify('-25 years');
$bookingDto->participants[0] = $participant;
$serviceAvailabilityCalculator = $this->createMock(ServiceAvailabilityCalculator::class);
$serviceAvailabilityCalculator->method('isServiceUnavailable')
->with($parkingService->id, $bookingDto, 0)
->willReturn(true);
$provider = new ParticipantFieldOptionsProvider(
$serviceAvailabilityCalculator,
$this->createMock(InsuranceManager::class),
$this->createMock(BookingPriceCalculator::class),
new ServiceLabelFormatter(),
$this->createMock(TranslatorInterface::class)
);
$options = $provider->getFieldOptions('parking', $bookingDto, 0);
$this->assertTrue($options['attr']['readonly']);
$this->assertSame('ausgebucht', $options['attr']['data-tooltip']);
}
public function testParkingIsNotReadonlyWhenAvailable(): void
{
$parkingService = new Service();
$parkingService->id = 3;
$parkingService->label = 'Parkplatz';
$parkingService->subType = Constants::TOKEN_PARKING;
$parkingService->available = 10;
$parkingService->price = 12.5;
$travel = $this->createTravelWithAdditionalServices([$parkingService]);
$bookingDto = new BookingDto($travel, 1);
$participant = new ParticipantDto();
$participant->index = 0;
$participant->dateOfBirth = $travel->dateFrom->modify('-25 years');
$bookingDto->participants[0] = $participant;
$options = $this->provider->getFieldOptions('parking', $bookingDto, 0);
$this->assertArrayNotHasKey('attr', $options);
}
public function testNullParticipantReturnsEmptyOptions(): void
{
$mandatoryService = $this->createMandatoryService(1, 'Ortstaxe');
@@ -7,9 +7,11 @@ namespace App\Tests\Form\Service;
use App\BusProNet\Model\Service;
use App\BusProNet\Model\Travel;
use App\BusProNet\Utility\DirectionMapper;
use App\BusProNet\Constants;
use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
use App\Form\Service\ParticipantParkingFieldHandler;
use App\Service\ServiceAvailabilityCalculator;
use PHPUnit\Framework\TestCase;
class ParticipantParkingFieldHandlerTest extends TestCase
@@ -18,7 +20,7 @@ class ParticipantParkingFieldHandlerTest extends TestCase
protected function setUp(): void
{
$this->handler = new ParticipantParkingFieldHandler();
$this->handler = new ParticipantParkingFieldHandler(new ServiceAvailabilityCalculator());
}
public function testGetFieldName(): void
@@ -237,6 +239,50 @@ class ParticipantParkingFieldHandlerTest extends TestCase
$this->assertFalse($participant->parking);
}
public function testProcessFieldRejectsParkingWhenSoldOut(): void
{
$pkwService = $this->createTransportationService(DirectionMapper::SUBTYPE_CAR_API);
$parkingService = $this->createParkingService(available: 0);
$participant = new ParticipantDto();
$participant->dateOfBirth = new \DateTimeImmutable('-25 years');
$participant->transportationOutbound = $pkwService;
$travel = new Travel();
$travel->additionalServices = [$parkingService->id => $parkingService];
$bookingDto = new BookingDto($travel, 1);
$bookingDto->participants = [$participant];
$submittedData = ['parking' => true];
$this->handler->processField($submittedData, $bookingDto, 0);
$this->assertFalse($participant->parking);
$this->assertNull($participant->parkingService);
}
public function testProcessFieldAllowsParkingWhenAvailable(): void
{
$pkwService = $this->createTransportationService(DirectionMapper::SUBTYPE_CAR_API);
$parkingService = $this->createParkingService(available: 5);
$participant = new ParticipantDto();
$participant->dateOfBirth = new \DateTimeImmutable('-25 years');
$participant->transportationOutbound = $pkwService;
$travel = new Travel();
$travel->additionalServices = [$parkingService->id => $parkingService];
$bookingDto = new BookingDto($travel, 1);
$bookingDto->participants = [$participant];
$submittedData = ['parking' => true];
$this->handler->processField($submittedData, $bookingDto, 0);
$this->assertTrue($participant->parking);
$this->assertSame($parkingService, $participant->parkingService);
}
public function testProcessFieldSetsParkingToFalseWhenNotSelected(): void
{
$pkwService = $this->createTransportationService(DirectionMapper::SUBTYPE_CAR_API);
@@ -300,4 +346,15 @@ class ParticipantParkingFieldHandlerTest extends TestCase
return $service;
}
private function createParkingService(int $available): Service
{
$service = new Service();
$service->id = 200;
$service->label = 'Parkplatz';
$service->subType = Constants::TOKEN_PARKING;
$service->available = $available;
return $service;
}
}
@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace App\Tests\Service;
use App\BusProNet\Constants;
use App\BusProNet\Model\Service;
use App\BusProNet\Model\Travel;
use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
use App\Service\ServiceAvailabilityCalculator;
use PHPUnit\Framework\TestCase;
class ServiceAvailabilityCalculatorTest extends TestCase
{
private ServiceAvailabilityCalculator $calculator;
protected function setUp(): void
{
$this->calculator = new ServiceAvailabilityCalculator();
}
public function testParkingServiceIsUnavailableWhenSoldOut(): void
{
$parkingService = $this->createParkingService(id: 1, available: 0);
$bookingDto = $this->createBookingDtoWithServices([$parkingService]);
$this->assertTrue($this->calculator->isServiceUnavailable(1, $bookingDto, 0));
}
public function testParkingServiceIsAvailableWhenQuotaRemains(): void
{
$parkingService = $this->createParkingService(id: 1, available: 5);
$bookingDto = $this->createBookingDtoWithServices([$parkingService]);
$this->assertFalse($this->calculator->isServiceUnavailable(1, $bookingDto, 0));
}
public function testParkingSelectedByOtherParticipantCountsTowardUsage(): void
{
$parkingService = $this->createParkingService(id: 1, available: 1);
$bookingDto = $this->createBookingDtoWithServices([$parkingService]);
$otherParticipant = new ParticipantDto();
$otherParticipant->parkingService = $parkingService;
$bookingDto->participants[1] = $otherParticipant;
// Only one parking slot remains, and another participant already claimed it,
// so it must show unavailable for the current participant (index 0)
$this->assertTrue($this->calculator->isServiceUnavailable(1, $bookingDto, 0));
}
private function createParkingService(int $id, int $available): Service
{
$service = new Service();
$service->id = $id;
$service->label = 'Parkplatz';
$service->subType = Constants::TOKEN_PARKING;
$service->available = $available;
return $service;
}
private function createBookingDtoWithServices(array $services): BookingDto
{
$travel = new Travel();
$indexed = [];
foreach ($services as $service) {
$indexed[$service->id] = $service;
}
$travel->additionalServices = $indexed;
$bookingDto = new BookingDto($travel, 1);
$bookingDto->participants[0] = new ParticipantDto();
return $bookingDto;
}
}