diff --git a/src/BusProNet/DataProcessor/PersonalDataSynchronizer.php b/src/BusProNet/DataProcessor/PersonalDataSynchronizer.php index 12de4f5..33c9d10 100644 --- a/src/BusProNet/DataProcessor/PersonalDataSynchronizer.php +++ b/src/BusProNet/DataProcessor/PersonalDataSynchronizer.php @@ -33,7 +33,7 @@ class PersonalDataSynchronizer * * @param array $participants The participants array from the form * @param Booking $bookingData The booking data object to update - * @param bool $isInternalAgencyBooking Whether to fill default names for canceled participants + * @param bool $isInternalAgencyBooking Whether to fill default names for participants with empty names */ public function updateParticipantPersonalData( array $participants, @@ -48,20 +48,18 @@ class PersonalDataSynchronizer $bookingData->participants[$participant->index]->firstName = $participant->firstName; $bookingData->participants[$participant->index]->name = $participant->lastName; - // For internal agency bookings, canceled participants with empty names get default values - // since they are read-only in the UI and cannot be fixed by applicants + // For internal agency bookings, participants with empty names get default values. + // Agencies pre-book slots with placeholder data (e.g. applicant surname, empty first name), + // and the BPN API rejects updates with empty name fields. if ($isInternalAgencyBooking) { - $status = $bookingData->participantsStatus[$participant->index] ?? null; - if ('S' === $status) { - $defaultName = sprintf('Teilnehmer:in %d', $participant->index + 1); - $personalData = $bookingData->participants[$participant->index]; + $defaultName = sprintf('Teilnehmer:in %d', $participant->index + 1); + $personalData = $bookingData->participants[$participant->index]; - if (null === $personalData->firstName || '' === $personalData->firstName) { - $personalData->firstName = $defaultName; - } - if (null === $personalData->name || '' === $personalData->name) { - $personalData->name = $defaultName; - } + if (null === $personalData->firstName || '' === $personalData->firstName) { + $personalData->firstName = $defaultName; + } + if (null === $personalData->name || '' === $personalData->name) { + $personalData->name = $defaultName; } } diff --git a/tests/BusProNet/DataProcessor/BookingDataProcessorTest.php b/tests/BusProNet/DataProcessor/BookingDataProcessorTest.php index adfe277..d6633d0 100644 --- a/tests/BusProNet/DataProcessor/BookingDataProcessorTest.php +++ b/tests/BusProNet/DataProcessor/BookingDataProcessorTest.php @@ -564,6 +564,132 @@ class BookingDataProcessorTest extends TestCase $this->assertEquals('', $canceledParticipant->name); } + public function testConfirmedParticipantEmptyNamesFilledForAgencyBooking(): void + { + $formData = $this->createFormDataWithConfirmedParticipantEmptyNames(); + + $this->processor->createUpdateRequestPayload($formData); + + // Confirmed participant (index 1) with empty names should have default names filled + $participant = $formData->booking->participants[1]; + $this->assertEquals('Teilnehmer:in 2', $participant->firstName); + $this->assertEquals('Teilnehmer:in 2', $participant->name); + + // Active participant (index 0) should retain original names from form + $activeParticipant = $formData->booking->participants[0]; + $this->assertEquals('Participant0', $activeParticipant->firstName); + $this->assertEquals('LastName', $activeParticipant->name); + } + + public function testConfirmedParticipantPartialEmptyNameFilledForAgencyBooking(): void + { + $formData = $this->createFormDataWithConfirmedParticipantPartialEmptyName(); + + $this->processor->createUpdateRequestPayload($formData); + + // Participant with lastName but empty firstName should get default firstName only + $participant = $formData->booking->participants[1]; + $this->assertEquals('Teilnehmer:in 2', $participant->firstName); + $this->assertEquals('Schnekenburger', $participant->name); + } + + private function createFormDataWithConfirmedParticipantEmptyNames(): BookingDto + { + $bookingDto = new BookingDto($this->createMockTravel(), 1); + $bookingDto->booking = $this->createMockBookingWithEmptyNameParticipant('F', '', ''); + $bookingDto->agencyCode = '0004'; + $bookingDto->participants = [ + $this->createMockParticipantDto(0, 'F'), + $this->createMockParticipantDtoWithEmptyNames(1, 'F'), + ]; + + return $bookingDto; + } + + private function createFormDataWithConfirmedParticipantPartialEmptyName(): BookingDto + { + $bookingDto = new BookingDto($this->createMockTravel(), 1); + $bookingDto->booking = $this->createMockBookingWithEmptyNameParticipant('F', '', 'Schnekenburger'); + $bookingDto->agencyCode = '0004'; + $bookingDto->participants = [ + $this->createMockParticipantDto(0, 'F'), + $this->createMockParticipantDtoWithPartialName(1, 'F', '', 'Schnekenburger'), + ]; + + return $bookingDto; + } + + private function createMockBookingWithEmptyNameParticipant( + string $status, + string $firstName, + string $lastName, + ): Booking { + $booking = new Booking(); + $booking->id = 123; + $booking->status = 'ACTIVE'; + $booking->agencyId = 999; + $booking->dateId = 1; + $booking->hotelId = 1; + $booking->paymentId = '1'; + $booking->paymentLabel = 'Credit Card'; + $booking->paymentType = 'CC'; + $booking->additionalServices = []; + $booking->transportationServices = []; + $booking->pickups = []; + $booking->dropOffs = []; + + $emptyNameParticipant = new PersonalData(); + $emptyNameParticipant->firstName = $firstName; + $emptyNameParticipant->name = $lastName; + $emptyNameParticipant->dateOfBirth = new \DateTimeImmutable('1990-01-01'); + $emptyNameParticipant->gender = 'M'; + $emptyNameParticipant->nationality = 'DE'; + $emptyNameParticipant->address = new Address(); + $emptyNameParticipant->communication = new Communication(); + + $booking->participants = [ + $this->createMockPersonalData('Participant0'), + $emptyNameParticipant, + ]; + $booking->participantsStatus = ['F', $status]; + $booking->applicant = $this->createMockPersonalData('Applicant'); + $booking->bankAccount = null; + $booking->rooms = []; + + return $booking; + } + + private function createMockParticipantDtoWithPartialName( + int $index, + string $status, + string $firstName, + string $lastName, + ): ParticipantDto { + $participant = new ParticipantDto(); + $participant->index = $index; + $participant->status = $status; + $participant->firstName = $firstName; + $participant->lastName = $lastName; + $participant->dateOfBirth = new \DateTimeImmutable('1990-01-01'); + $participant->gender = 'M'; + $participant->nationality = 'DE'; + $participant->height = '175'; + $participant->weight = '70'; + $participant->shoeSize = '40'; + $participant->email = null; + $participant->mobile = null; + $participant->courses = []; + $participant->additionalServices = []; + $participant->skiPass = null; + $participant->board = []; + $participant->rentals = []; + $participant->transportationOutbound = $this->createMockService(1); + $participant->transportationInbound = $this->createMockService(2); + $participant->pickup = null; + + return $participant; + } + private function createFormDataWithCanceledParticipantEmptyNames(): BookingDto { $bookingDto = new BookingDto($this->createMockTravel(), 1);