feat: improved mutability checks for personal data in edit or create mode

This commit is contained in:
Björn Fromme
2025-10-28 15:34:09 +01:00
parent 51dbf20118
commit 5021f414d1
27 changed files with 435 additions and 233 deletions
@@ -69,6 +69,20 @@ class BookingDataProcessor
$participantData = ParticipantDto::fromPersonalData($participant);
$participantData->index = $index;
// First participant (applicant): copy address from applicant if participant address is empty
// BPN API may return full address only in <anmelder> but minimal/empty address in <teilnehmer id="1">
// Only copy if first participant has no street (indicating empty/incomplete address)
// This allows applicant and first participant to be different people with different addresses
if (0 === $index && null !== $booking->applicant->address) {
$isEmpty = null === $participantData->address
|| null === $participantData->address->street
|| '' === trim($participantData->address->street);
if ($isEmpty) {
$participantData->address = clone $booking->applicant->address;
}
}
// Extract service selections from booking and assign to participant DTO
$participantData->courses = $booking
->getAdditionalServicesForParticipantByGroup($index, Constants::TOKEN_COURSES);
@@ -429,13 +443,16 @@ class BookingDataProcessor
* Maps insurance to the participant. Adds new insurances to the booking if they don't exist.
* Insurance can be either individual or package-based, with automatic price-tier adjustment.
*
* IMPORTANT: Excludes synthetic "no insurance" option from BPN transmission.
*
* @param ParticipantDto $participant The participant data from the form
* @param Booking $bookingData The booking data object to update
* @param Travel $travelData The travel data containing available insurances
*/
private function processInsurance(ParticipantDto $participant, Booking $bookingData, Travel $travelData): void
{
if (null === $participant->insurance) {
// Skip if no insurance or if participant selected "keine Versicherung gewünscht"
if (null === $participant->insurance || $participant->insurance->isNoInsurance()) {
return;
}
@@ -760,13 +777,9 @@ class BookingDataProcessor
'nationalitaet' => $firstParticipant->nationality ?? '',
];
// Include BPN IDs for linking to existing records (authenticated users)
if (null !== $firstParticipant->addressId) {
$payload['anmelder']['idadresse'] = $firstParticipant->addressId;
}
if (null !== $firstParticipant->personId) {
$payload['anmelder']['idadresseperson'] = $firstParticipant->personId;
}
// DO NOT include personId or addressId in create mode
// BPN will automatically match existing customers by exact personal data (name, DOB, address)
// Including IDs would prevent automatic matching and could cause data inconsistencies
if (null !== $firstParticipant->dateOfBirth) {
$payload['anmelder']['geburtsdatum'] = $firstParticipant->dateOfBirth->format('d.m.Y');
@@ -798,13 +811,9 @@ class BookingDataProcessor
'nationalitaet' => $participant->nationality ?? '',
];
// Include BPN IDs for linking to existing records (authenticated users)
if (null !== $participant->addressId) {
$participantData['idadresse'] = $participant->addressId;
}
if (null !== $participant->personId) {
$participantData['idadresseperson'] = $participant->personId;
}
// DO NOT include personId or addressId in create mode
// BPN will automatically match existing customers by exact personal data (name, DOB, address)
// Including IDs would prevent automatic matching and could cause data inconsistencies
if (null !== $participant->dateOfBirth) {
$participantData['geburtsdatum'] = $participant->dateOfBirth->format('d.m.Y');
@@ -1085,6 +1094,7 @@ class BookingDataProcessor
* Collects insurance mappings.
*
* CRITICAL: Insurance data is only included in CREATE flow, not in UPDATE flow.
* IMPORTANT: Excludes synthetic "no insurance" option from BPN transmission.
*
* @return array<string, array<int>> Map of insurance ID to participant IDs
*/
@@ -1095,7 +1105,8 @@ class BookingDataProcessor
foreach ($bookingDto->participants as $index => $participant) {
$participantId = $index + 1;
if (null !== $participant->insurance) {
// Exclude synthetic "keine Versicherung gewünscht" option from BPN XML
if (null !== $participant->insurance && false === $participant->insurance->isNoInsurance()) {
$insuranceMap[$participant->insurance->id][] = $participantId;
}
}