feat: hide parking from participants under 18
This commit is contained in:
@@ -6,6 +6,7 @@ namespace App\Form\Service;
|
|||||||
|
|
||||||
use App\BusProNet\Utility\DirectionMapper;
|
use App\BusProNet\Utility\DirectionMapper;
|
||||||
use App\Form\Service\Abstract\AbstractFieldStateProvider;
|
use App\Form\Service\Abstract\AbstractFieldStateProvider;
|
||||||
|
use App\Form\Service\Condition\AgeRangeCondition;
|
||||||
use App\Form\Service\Condition\ApplicantCondition;
|
use App\Form\Service\Condition\ApplicantCondition;
|
||||||
use App\Form\Service\Condition\AuthenticatedUserPersonalDataCondition;
|
use App\Form\Service\Condition\AuthenticatedUserPersonalDataCondition;
|
||||||
use App\Form\Service\Condition\BabyAgeCondition;
|
use App\Form\Service\Condition\BabyAgeCondition;
|
||||||
@@ -250,11 +251,16 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Show parking only when outbound transportation is PKW (hidden by default)
|
// Show parking only when outbound transportation is PKW AND participant is 18+ (hidden by default)
|
||||||
// Parking is offered at holiday destination for those arriving by car
|
// Parking is offered at holiday destination for those arriving by car
|
||||||
|
// Participants under 18 cannot drive in Germany, so parking is not relevant for them
|
||||||
|
$minimumDrivingAgeCondition = new AgeRangeCondition(18);
|
||||||
$this->fieldStateConditions['parking'] = [
|
$this->fieldStateConditions['parking'] = [
|
||||||
'hidden' => CompositeCondition::not(
|
'hidden' => CompositeCondition::or(
|
||||||
ServiceSubTypeCondition::equals('transportationOutbound', DirectionMapper::SUBTYPE_CAR_API)
|
CompositeCondition::not(
|
||||||
|
ServiceSubTypeCondition::equals('transportationOutbound', DirectionMapper::SUBTYPE_CAR_API)
|
||||||
|
),
|
||||||
|
CompositeCondition::not($minimumDrivingAgeCondition)
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use App\BusProNet\Utility\DirectionMapper;
|
|||||||
use App\Form\Model\BookingDto;
|
use App\Form\Model\BookingDto;
|
||||||
use App\Form\Service\Abstract\AbstractFieldStateProvider;
|
use App\Form\Service\Abstract\AbstractFieldStateProvider;
|
||||||
use App\Form\Service\Condition\AdditionalServicesMutabilityCondition;
|
use App\Form\Service\Condition\AdditionalServicesMutabilityCondition;
|
||||||
|
use App\Form\Service\Condition\AgeRangeCondition;
|
||||||
use App\Form\Service\Condition\ApplicantCondition;
|
use App\Form\Service\Condition\ApplicantCondition;
|
||||||
use App\Form\Service\Condition\AuthenticatedUserPersonalDataCondition;
|
use App\Form\Service\Condition\AuthenticatedUserPersonalDataCondition;
|
||||||
use App\Form\Service\Condition\BookingModeCondition;
|
use App\Form\Service\Condition\BookingModeCondition;
|
||||||
@@ -188,10 +189,15 @@ class EditFieldStateProvider extends AbstractFieldStateProvider
|
|||||||
'readonly' => $pickupsMutabilityCondition,
|
'readonly' => $pickupsMutabilityCondition,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Parking - shown only when outbound transportation is PKW, readonly if transportation not mutable
|
// Parking - shown only when outbound transportation is PKW AND participant is 18+, readonly if transportation not mutable
|
||||||
|
// Participants under 18 cannot drive in Germany, so parking is not relevant for them
|
||||||
|
$minimumDrivingAgeCondition = new AgeRangeCondition(18);
|
||||||
$this->fieldStateConditions['parking'] = [
|
$this->fieldStateConditions['parking'] = [
|
||||||
'hidden' => CompositeCondition::not(
|
'hidden' => CompositeCondition::or(
|
||||||
ServiceSubTypeCondition::equals('transportationOutbound', DirectionMapper::SUBTYPE_CAR_API)
|
CompositeCondition::not(
|
||||||
|
ServiceSubTypeCondition::equals('transportationOutbound', DirectionMapper::SUBTYPE_CAR_API)
|
||||||
|
),
|
||||||
|
CompositeCondition::not($minimumDrivingAgeCondition)
|
||||||
),
|
),
|
||||||
'readonly' => $transportationServicesMutabilityCondition,
|
'readonly' => $transportationServicesMutabilityCondition,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -8,14 +8,15 @@ use App\BusProNet\Constants;
|
|||||||
use App\BusProNet\Model\Service;
|
use App\BusProNet\Model\Service;
|
||||||
use App\BusProNet\Utility\DirectionMapper;
|
use App\BusProNet\Utility\DirectionMapper;
|
||||||
use App\Form\Model\BookingDto;
|
use App\Form\Model\BookingDto;
|
||||||
|
use App\Form\Model\ParticipantDto;
|
||||||
use App\Form\Service\Abstract\AbstractParticipantFieldHandler;
|
use App\Form\Service\Abstract\AbstractParticipantFieldHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles parking service selection for self-organized transportation.
|
* Handles parking service selection for self-organized transportation.
|
||||||
*
|
*
|
||||||
* Parking is only available when outbound transportation is PKW (car).
|
* Parking is only available when outbound transportation is PKW (car) AND
|
||||||
* This is because parking is needed at the destination for those arriving by car.
|
* participant is at least 18 years old (minimum driving age in Germany).
|
||||||
* Automatically clears parking when outbound transportation is not PKW.
|
* Automatically clears parking when conditions are not met.
|
||||||
*/
|
*/
|
||||||
class ParticipantParkingFieldHandler extends AbstractParticipantFieldHandler
|
class ParticipantParkingFieldHandler extends AbstractParticipantFieldHandler
|
||||||
{
|
{
|
||||||
@@ -24,9 +25,11 @@ class ParticipantParkingFieldHandler extends AbstractParticipantFieldHandler
|
|||||||
return 'parking';
|
return 'parking';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const MINIMUM_DRIVING_AGE = 18;
|
||||||
|
|
||||||
public function getDependencies(): array
|
public function getDependencies(): array
|
||||||
{
|
{
|
||||||
return ['transportationOutbound', 'transportationInbound'];
|
return ['transportationOutbound', 'transportationInbound', 'dateOfBirth'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,10 +57,10 @@ class ParticipantParkingFieldHandler extends AbstractParticipantFieldHandler
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if parking is applicable (outbound transportation is PKW)
|
// Check if parking is applicable (outbound transportation is PKW AND participant is 18+)
|
||||||
if (!$this->isParkingApplicable($participant)) {
|
if (false === $this->isParkingApplicable($participant)) {
|
||||||
$participant->parking = false; // Clear parking when outbound is not PKW
|
$participant->parking = false;
|
||||||
$participant->parkingService = null; // Clear parking service object
|
$participant->parkingService = null;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -77,17 +80,30 @@ class ParticipantParkingFieldHandler extends AbstractParticipantFieldHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if parking is applicable based on outbound transportation selection.
|
* Checks if parking is applicable based on outbound transportation and age.
|
||||||
*
|
*
|
||||||
* Parking is only needed when arriving by car at the destination.
|
* Parking is only applicable when:
|
||||||
|
* 1. Outbound transportation is PKW (arriving by car at destination)
|
||||||
|
* 2. Participant is at least 18 years old (minimum driving age in Germany)
|
||||||
*
|
*
|
||||||
* @param object $participant The participant DTO
|
* @param ParticipantDto $participant The participant DTO
|
||||||
*
|
*
|
||||||
* @return bool True if parking is applicable, false otherwise
|
* @return bool True if parking is applicable, false otherwise
|
||||||
*/
|
*/
|
||||||
private function isParkingApplicable(object $participant): bool
|
private function isParkingApplicable(ParticipantDto $participant): bool
|
||||||
{
|
{
|
||||||
return DirectionMapper::SUBTYPE_CAR_API === $participant->transportationOutbound?->subType;
|
// Must have PKW outbound transportation
|
||||||
|
if (DirectionMapper::SUBTYPE_CAR_API !== $participant->transportationOutbound?->subType) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be at least minimum driving age
|
||||||
|
$age = $participant->getAge();
|
||||||
|
if (null === $age || $age < self::MINIMUM_DRIVING_AGE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,253 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Tests\Form\Service;
|
||||||
|
|
||||||
|
use App\BusProNet\Model\Service;
|
||||||
|
use App\BusProNet\Model\Travel;
|
||||||
|
use App\BusProNet\Utility\DirectionMapper;
|
||||||
|
use App\Form\Model\BookingDto;
|
||||||
|
use App\Form\Model\ParticipantDto;
|
||||||
|
use App\Form\Service\ParticipantParkingFieldHandler;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class ParticipantParkingFieldHandlerTest extends TestCase
|
||||||
|
{
|
||||||
|
private ParticipantParkingFieldHandler $handler;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->handler = new ParticipantParkingFieldHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetFieldName(): void
|
||||||
|
{
|
||||||
|
$this->assertSame('parking', $this->handler->getFieldName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetDependencies(): void
|
||||||
|
{
|
||||||
|
$this->assertSame(['transportationOutbound', 'transportationInbound', 'dateOfBirth'], $this->handler->getDependencies());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShouldProcessAlwaysReturnsTrue(): void
|
||||||
|
{
|
||||||
|
$this->assertTrue($this->handler->shouldProcess([], BookingDto::MODE_CREATE, 0));
|
||||||
|
$this->assertTrue($this->handler->shouldProcess(['some' => 'data'], BookingDto::MODE_EDIT, 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testProcessFieldWithoutParticipant(): void
|
||||||
|
{
|
||||||
|
$travel = new Travel();
|
||||||
|
$bookingDto = new BookingDto($travel, 1);
|
||||||
|
$submittedData = ['parking' => true];
|
||||||
|
|
||||||
|
$this->handler->processField($submittedData, $bookingDto, 0);
|
||||||
|
|
||||||
|
$this->expectNotToPerformAssertions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testProcessFieldClearsParkingWhenTransportationIsNotPkw(): void
|
||||||
|
{
|
||||||
|
$busService = $this->createTransportationService(DirectionMapper::SUBTYPE_BUS_API);
|
||||||
|
|
||||||
|
$participant = new ParticipantDto();
|
||||||
|
$participant->dateOfBirth = new \DateTimeImmutable('-25 years');
|
||||||
|
$participant->transportationOutbound = $busService;
|
||||||
|
$participant->parking = true;
|
||||||
|
|
||||||
|
$travel = new Travel();
|
||||||
|
$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 testProcessFieldClearsParkingWhenParticipantIsUnder18(): void
|
||||||
|
{
|
||||||
|
$pkwService = $this->createTransportationService(DirectionMapper::SUBTYPE_CAR_API);
|
||||||
|
|
||||||
|
// 17-year-old participant
|
||||||
|
$participant = new ParticipantDto();
|
||||||
|
$participant->dateOfBirth = new \DateTimeImmutable('-17 years');
|
||||||
|
$participant->transportationOutbound = $pkwService;
|
||||||
|
$participant->parking = true;
|
||||||
|
|
||||||
|
$travel = new Travel();
|
||||||
|
$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 testProcessFieldClearsParkingWhenParticipantIsExactly17(): void
|
||||||
|
{
|
||||||
|
$pkwService = $this->createTransportationService(DirectionMapper::SUBTYPE_CAR_API);
|
||||||
|
|
||||||
|
// Participant who is exactly 17 (just before 18th birthday)
|
||||||
|
$participant = new ParticipantDto();
|
||||||
|
$participant->dateOfBirth = new \DateTimeImmutable('2007-06-15');
|
||||||
|
$participant->transportationOutbound = $pkwService;
|
||||||
|
$participant->parking = true;
|
||||||
|
|
||||||
|
$travel = new Travel();
|
||||||
|
$bookingDto = new BookingDto($travel, 1);
|
||||||
|
$bookingDto->participants = [$participant];
|
||||||
|
|
||||||
|
// Assume today is 2024-06-14 (one day before 17th birthday would make them 16)
|
||||||
|
// But since we use current date, we need to be careful with this test
|
||||||
|
// Let's use a fixed approach - participant born 17 years ago today is exactly 17
|
||||||
|
$participant->dateOfBirth = (new \DateTimeImmutable())->modify('-17 years');
|
||||||
|
|
||||||
|
$submittedData = ['parking' => true];
|
||||||
|
|
||||||
|
$this->handler->processField($submittedData, $bookingDto, 0);
|
||||||
|
|
||||||
|
$this->assertFalse($participant->parking);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testProcessFieldAllowsParkingForAdultWithPkw(): void
|
||||||
|
{
|
||||||
|
$pkwService = $this->createTransportationService(DirectionMapper::SUBTYPE_CAR_API);
|
||||||
|
|
||||||
|
// 25-year-old participant
|
||||||
|
$participant = new ParticipantDto();
|
||||||
|
$participant->dateOfBirth = new \DateTimeImmutable('-25 years');
|
||||||
|
$participant->transportationOutbound = $pkwService;
|
||||||
|
|
||||||
|
$travel = new Travel();
|
||||||
|
$bookingDto = new BookingDto($travel, 1);
|
||||||
|
$bookingDto->participants = [$participant];
|
||||||
|
|
||||||
|
$submittedData = ['parking' => '1'];
|
||||||
|
|
||||||
|
$this->handler->processField($submittedData, $bookingDto, 0);
|
||||||
|
|
||||||
|
$this->assertTrue($participant->parking);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testProcessFieldAllowsParkingForExactly18YearOld(): void
|
||||||
|
{
|
||||||
|
$pkwService = $this->createTransportationService(DirectionMapper::SUBTYPE_CAR_API);
|
||||||
|
|
||||||
|
// Exactly 18 years old
|
||||||
|
$participant = new ParticipantDto();
|
||||||
|
$participant->dateOfBirth = (new \DateTimeImmutable())->modify('-18 years');
|
||||||
|
$participant->transportationOutbound = $pkwService;
|
||||||
|
|
||||||
|
$travel = new Travel();
|
||||||
|
$bookingDto = new BookingDto($travel, 1);
|
||||||
|
$bookingDto->participants = [$participant];
|
||||||
|
|
||||||
|
$submittedData = ['parking' => true];
|
||||||
|
|
||||||
|
$this->handler->processField($submittedData, $bookingDto, 0);
|
||||||
|
|
||||||
|
$this->assertTrue($participant->parking);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testProcessFieldClearsParkingWhenNoDateOfBirth(): void
|
||||||
|
{
|
||||||
|
$pkwService = $this->createTransportationService(DirectionMapper::SUBTYPE_CAR_API);
|
||||||
|
|
||||||
|
$participant = new ParticipantDto();
|
||||||
|
$participant->dateOfBirth = null; // No date of birth
|
||||||
|
$participant->transportationOutbound = $pkwService;
|
||||||
|
$participant->parking = true;
|
||||||
|
|
||||||
|
$travel = new Travel();
|
||||||
|
$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 testProcessFieldClearsParkingWhenBabyParticipant(): void
|
||||||
|
{
|
||||||
|
$pkwService = $this->createTransportationService(DirectionMapper::SUBTYPE_CAR_API);
|
||||||
|
|
||||||
|
// Baby participant (1 year old)
|
||||||
|
$participant = new ParticipantDto();
|
||||||
|
$participant->dateOfBirth = new \DateTimeImmutable('-1 year');
|
||||||
|
$participant->transportationOutbound = $pkwService;
|
||||||
|
$participant->parking = true;
|
||||||
|
|
||||||
|
$travel = new Travel();
|
||||||
|
$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 testProcessFieldClearsParkingForChildParticipant(): void
|
||||||
|
{
|
||||||
|
$pkwService = $this->createTransportationService(DirectionMapper::SUBTYPE_CAR_API);
|
||||||
|
|
||||||
|
// 10-year-old child
|
||||||
|
$participant = new ParticipantDto();
|
||||||
|
$participant->dateOfBirth = new \DateTimeImmutable('-10 years');
|
||||||
|
$participant->transportationOutbound = $pkwService;
|
||||||
|
$participant->parking = true;
|
||||||
|
|
||||||
|
$travel = new Travel();
|
||||||
|
$bookingDto = new BookingDto($travel, 1);
|
||||||
|
$bookingDto->participants = [$participant];
|
||||||
|
|
||||||
|
$submittedData = ['parking' => true];
|
||||||
|
|
||||||
|
$this->handler->processField($submittedData, $bookingDto, 0);
|
||||||
|
|
||||||
|
$this->assertFalse($participant->parking);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testProcessFieldSetsParkingToFalseWhenNotSelected(): void
|
||||||
|
{
|
||||||
|
$pkwService = $this->createTransportationService(DirectionMapper::SUBTYPE_CAR_API);
|
||||||
|
|
||||||
|
$participant = new ParticipantDto();
|
||||||
|
$participant->dateOfBirth = new \DateTimeImmutable('-25 years');
|
||||||
|
$participant->transportationOutbound = $pkwService;
|
||||||
|
|
||||||
|
$travel = new Travel();
|
||||||
|
$bookingDto = new BookingDto($travel, 1);
|
||||||
|
$bookingDto->participants = [$participant];
|
||||||
|
|
||||||
|
$submittedData = ['parking' => false];
|
||||||
|
|
||||||
|
$this->handler->processField($submittedData, $bookingDto, 0);
|
||||||
|
|
||||||
|
$this->assertFalse($participant->parking);
|
||||||
|
$this->assertNull($participant->parkingService);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createTransportationService(string $subType): Service
|
||||||
|
{
|
||||||
|
$service = new Service();
|
||||||
|
$service->id = 100;
|
||||||
|
$service->label = 'Test Transportation';
|
||||||
|
$service->subType = $subType;
|
||||||
|
|
||||||
|
return $service;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user