dateFrom = CarbonImmutable::now()->addMonth()->toDateTimeImmutable(); $bookingDto = new BookingDto($travel, 1); $bookingDto->booking = new Booking(); $bookingDto->agencyCode = AgencyLoader::INTERNAL_AGENCY_CODE; $bookingDto->participants = [ $this->createCompleteParticipant(0), $this->createInternalAgencyParticipantNeedingAttention(1), ]; $checker = new BookingEditPreFlightChecker($this->createValidator()); $result = $checker->findMissingValueLabelsByParticipantIndex($bookingDto); $this->assertSame( [1 => ['Vorname', 'Nachname', 'E-Mail']], $result ); } public function testScanAttentionDoesNotFlagMissingAddressForNonApplicantParticipantsInNonInternalAgencyEdit(): void { $travel = new Travel(); $travel->dateFrom = CarbonImmutable::now()->addMonth()->toDateTimeImmutable(); $bookingDto = new BookingDto($travel, 1); $bookingDto->booking = new Booking(); $bookingDto->agencyCode = 'OTHER'; $bookingDto->participants = [ $this->createCompleteParticipant(0), $this->createParticipantWithMissingAddressOnly(1), ]; $checker = new BookingEditPreFlightChecker($this->createValidator()); $result = $checker->findMissingValueLabelsByParticipantIndex($bookingDto); $this->assertSame([], $result); } public function testScanAttentionIgnoresBlankBodyDimensionsEvenWhenRentalsAreSelected(): void { $travel = new Travel(); $travel->dateFrom = CarbonImmutable::now()->addMonth()->toDateTimeImmutable(); $bookingDto = new BookingDto($travel, 1); $bookingDto->booking = new Booking(); $bookingDto->agencyCode = AgencyLoader::INTERNAL_AGENCY_CODE; $participant = $this->createCompleteParticipant(0); $participant->rentals = [$this->createRentalService()]; $participant->height = null; $participant->weight = null; $participant->shoeSize = null; $bookingDto->participants = [$participant]; $checker = new BookingEditPreFlightChecker($this->createValidator()); $result = $checker->findMissingValueLabelsByParticipantIndex($bookingDto); $this->assertSame([], $result); } public function testScanAttentionShowsBodyDimensionLabelsWhenProvidedValuesAreOutOfRange(): void { $bookingDto = $this->createBookingDto(true); $participant = $this->createCompleteParticipant(0); $participant->rentals = [$this->createRentalService()]; $participant->height = '999'; $participant->weight = '999'; $participant->shoeSize = '999'; $bookingDto->participants = [$participant]; $checker = new BookingEditPreFlightChecker($this->createValidator()); $result = $checker->findMissingValueLabelsByParticipantIndex($bookingDto); $this->assertSame( [0 => ['Körpergröße', 'Gewicht', 'Schuhgröße']], $result ); } public function testScanAttentionSuppressesOutOfRangeBodyDimensionLabelsWhenSelection1475Applies(): void { $bookingDto = $this->createBookingDto(true, [1475]); $participant = $this->createCompleteParticipant(0); $participant->rentals = [$this->createRentalService()]; $participant->height = '999'; $participant->weight = '999'; $participant->shoeSize = '999'; $bookingDto->participants = [$participant]; $checker = new BookingEditPreFlightChecker($this->createValidator()); $result = $checker->findMissingValueLabelsByParticipantIndex($bookingDto); $this->assertSame([], $result); } public function testScanAttentionShowsPickupAndDropOffLabelsWhenBusTransportRequiresThem(): void { $travel = new Travel(); $travel->dateFrom = CarbonImmutable::now()->addMonth()->toDateTimeImmutable(); $bookingDto = new BookingDto($travel, 1); $bookingDto->booking = new Booking(); $bookingDto->agencyCode = AgencyLoader::INTERNAL_AGENCY_CODE; $participant = $this->createCompleteParticipant(0); $participant->transportationOutbound = $this->createBusService(); $participant->transportationInbound = $this->createBusService(); $participant->differentDropOff = true; $participant->pickup = null; $participant->dropOff = null; $bookingDto->participants = [$participant]; $checker = new BookingEditPreFlightChecker($this->createValidator()); $result = $checker->findMissingValueLabelsByParticipantIndex($bookingDto); $this->assertSame( [0 => ['Zustieg', 'Ausstieg']], $result ); } private function createCompleteParticipant(int $index): ParticipantDto { $participant = new ParticipantDto(); $participant->index = $index; $participant->firstName = 'Max'; $participant->lastName = 'Mustermann'; $participant->gender = 'M'; $participant->nationality = 'D'; $participant->dateOfBirth = new \DateTimeImmutable('1990-01-01'); $participant->email = 'max@example.com'; $participant->assignedRoomId = 1; $participant->transportationOutbound = $this->createTransportService(); $participant->transportationInbound = $this->createTransportService(); $participant->address = new Address(); $participant->address->street = 'Test Street 1'; $participant->address->postCode = '12345'; $participant->address->city = 'Test City'; $participant->address->country = 'DE'; return $participant; } private function createInternalAgencyParticipantNeedingAttention(int $index): ParticipantDto { $participant = $this->createCompleteParticipant($index); $participant->firstName = null; $participant->lastName = null; $participant->email = null; $participant->address->street = null; return $participant; } private function createParticipantWithMissingAddressOnly(int $index): ParticipantDto { $participant = $this->createCompleteParticipant($index); $participant->email = sprintf('other%d@example.com', $index); $participant->address = new Address(); return $participant; } private function createTransportService(): Service { $service = new Service(); $service->subType = 'TRAIN'; return $service; } private function createBusService(): Service { $service = new Service(); $service->subType = 'BUS'; return $service; } private function createRentalService(): Service { $service = new Service(); $service->subType = 'VER'; return $service; } private function createValidator(): ValidatorInterface { $voucherValidator = $this->createMock(VoucherValidator::class); $bookingPriceCalculator = $this->createMock(BookingPriceCalculator::class); $participantEligibilityChecker = $this->createMock(ParticipantEligibilityChecker::class); $participantEligibilityChecker ->method('isParticipantEligible') ->willReturn(false); $validatorFactory = new class( $voucherValidator, $bookingPriceCalculator, $participantEligibilityChecker, new ServiceAgeEvaluator(), ) implements ConstraintValidatorFactoryInterface { public function __construct( private readonly VoucherValidator $voucherValidator, private readonly BookingPriceCalculator $bookingPriceCalculator, private readonly ParticipantEligibilityChecker $participantEligibilityChecker, private readonly ServiceAgeEvaluator $serviceAgeEvaluator, ) { } public function getInstance(Constraint $constraint): ConstraintValidatorInterface { $className = $constraint->validatedBy(); if (EmailValidator::class === $className) { return new EmailValidator(Email::VALIDATION_MODE_HTML5); } if (PurchaseVoucherValidator::class === $className) { return new PurchaseVoucherValidator($this->voucherValidator); } if (PromoVoucherValidator::class === $className) { return new PromoVoucherValidator($this->voucherValidator, $this->bookingPriceCalculator); } if (MandatoryAdditionalServicesSelectedValidator::class === $className) { return new MandatoryAdditionalServicesSelectedValidator( $this->participantEligibilityChecker, $this->serviceAgeEvaluator ); } return new $className(); } }; return Validation::createValidatorBuilder() ->enableAttributeMapping() ->setConstraintValidatorFactory($validatorFactory) ->getValidator(); } /** * @param list $selectionIds */ private function createBookingDto(bool $internalAgency, array $selectionIds = []): BookingDto { $travel = new Travel(); $travel->dateFrom = CarbonImmutable::now()->addMonth()->toDateTimeImmutable(); $this->applySelectionIds($travel, $selectionIds); $bookingDto = new BookingDto($travel, 1); $bookingDto->booking = new Booking(); $bookingDto->agencyCode = $internalAgency ? AgencyLoader::INTERNAL_AGENCY_CODE : 'OTHER'; return $bookingDto; } /** * @param list $selectionIds */ private function applySelectionIds(Travel $travel, array $selectionIds): void { if ([] === $selectionIds) { return; } $group = new CrmSelectionGroup(); $group->id = 1; $group->selections = []; foreach ($selectionIds as $selectionId) { $selection = new CrmSelection(); $selection->id = $selectionId; $group->selections[] = $selection; } $travel->selectionGroups = [1 => $group]; } }