diff --git a/src/BusProNet/DataProcessor/BookingDataProcessor.php b/src/BusProNet/DataProcessor/BookingDataProcessor.php index c7e20cd..2686e34 100644 --- a/src/BusProNet/DataProcessor/BookingDataProcessor.php +++ b/src/BusProNet/DataProcessor/BookingDataProcessor.php @@ -8,6 +8,7 @@ use App\BusProNet\Constants; use App\BusProNet\Model\Booking; use App\BusProNet\Model\Travel; use App\BusProNet\Utility\DirectionMapper; +use App\BusProNet\XmlLoader\AgencyLoader; use App\Form\Model\BankAccountDto; use App\Form\Model\BookingDto; use App\Form\Model\ParticipantDto; @@ -42,11 +43,12 @@ class BookingDataProcessor * * IMPORTANT: Address handling for edit mode * - ParticipantDto::fromPersonalData() clones address objects to prevent shared references - * - First participant's address is NOT auto-populated from applicant - * - Template uses placeholders to show applicant's address as hints - * - This prevents address data from being inadvertently modified during form binding + * - For normal bookings, participant[0]'s missing address/dimensions are filled from the + * applicant (same person in normal bookings, but BPN may omit fields in ) + * - For internal agency bookings, this copy is skipped: the applicant is a customer, not + * the first participant, so copying applicant data into participant[0] would be incorrect */ - public function createBookingDtoFromBooking(Booking $booking, Travel $travel): BookingDto + public function createBookingDtoFromBooking(Booking $booking, Travel $travel, bool $isInternalAgencyBooking = false): BookingDto { $dto = new BookingDto($travel, $booking->hotelId); @@ -68,11 +70,12 @@ class BookingDataProcessor $participantData = ParticipantDto::fromPersonalData($participant); $participantData->index = $index; - // First participant (applicant): copy data from applicant if participant data is empty - // BPN API may return full data only in but minimal/empty data in - if (0 === $index) { + // For normal bookings, applicant === participant[0]: copy address and body dimensions + // from applicant when BPN returns full data only in and minimal data in + // . Skipped for internal agency bookings where the applicant is + // a customer (a different entity from the first traveler). + if (0 === $index && false === $isInternalAgencyBooking) { // Copy address if first participant has no street (indicating empty/incomplete address) - // This allows applicant and first participant to be different people with different addresses $isEmpty = null === $participantData->address || null === $participantData->address->street || '' === trim($participantData->address->street); diff --git a/src/Service/BookingEditDataLoader.php b/src/Service/BookingEditDataLoader.php index 01b1544..0a7ad73 100644 --- a/src/Service/BookingEditDataLoader.php +++ b/src/Service/BookingEditDataLoader.php @@ -138,13 +138,21 @@ class BookingEditDataLoader $this->travelDataService->patchAvailabilities($travelData, $availabilities); $this->travelDataService->patchMutability($travelData, $mutableData); - $formData = $this->bookingDataProcessor->createBookingDtoFromBooking($bookingData, $travelData); - - // Set agency code for internal agency detection (used by field state conditions) - $formData->agencyCode = null !== $bookingData->agencyId + // Resolve agency code before building the DTO so the internal-agency flag can be + // passed to createBookingDtoFromBooking, preventing applicant data from being + // copied into participant[0] for agency bookings where they are different people. + $agencyCode = null !== $bookingData->agencyId ? $this->agencyLoader->loadById($bookingData->agencyId)?->code : null; + $formData = $this->bookingDataProcessor->createBookingDtoFromBooking( + $bookingData, + $travelData, + AgencyLoader::INTERNAL_AGENCY_CODE === $agencyCode, + ); + + $formData->agencyCode = $agencyCode; + // Set original fingerprint BEFORE applying draft, so dirty detection // compares against the original API data (not the draft-modified data) $formData->originalFingerprint = $this->fingerprintService->generateFingerprint($formData, true); diff --git a/src/Service/BookingEditSubmitGuard.php b/src/Service/BookingEditSubmitGuard.php index f092a45..89db821 100644 --- a/src/Service/BookingEditSubmitGuard.php +++ b/src/Service/BookingEditSubmitGuard.php @@ -34,7 +34,7 @@ class BookingEditSubmitGuard */ public function reconcileImmutableCategories(BookingDto $workingDto, Booking $freshBooking): bool { - $baselineDto = $this->bookingDataProcessor->createBookingDtoFromBooking($freshBooking, $workingDto->travel); + $baselineDto = $this->bookingDataProcessor->createBookingDtoFromBooking($freshBooking, $workingDto->travel, $workingDto->isInternalAgencyBooking()); // Booking-level D-4 cutoff used as submit-time safety net for rentals. $serviceCutoffReachedCondition = new TravelStartCutoffReachedCondition( TravelStartCutoffReachedCondition::DEFAULT_DAYS_BEFORE_START