feat: improved mutability checks for personal data in edit or create mode
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\Model;
|
||||
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* Represents a physical address with street, postal code, city, and country information.
|
||||
*
|
||||
@@ -15,18 +13,14 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
*/
|
||||
class Address
|
||||
{
|
||||
#[Assert\NotBlank(message: 'Bitte angeben', groups: ['personal_data', 'applicant_address'])]
|
||||
public ?string $street = null;
|
||||
|
||||
#[Assert\NotBlank(message: 'Bitte angeben', groups: ['personal_data', 'applicant_address'])]
|
||||
public ?string $postCode = null;
|
||||
|
||||
#[Assert\NotBlank(message: 'Bitte angeben', groups: ['personal_data', 'applicant_address'])]
|
||||
public ?string $city = null;
|
||||
|
||||
public ?string $district = null;
|
||||
|
||||
#[Assert\NotBlank(message: 'Bitte angeben', groups: ['personal_data', 'applicant_address'])]
|
||||
public ?string $country = null;
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,15 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
||||
*/
|
||||
class Insurance
|
||||
{
|
||||
/**
|
||||
* Special ID for the synthetic "no insurance" option.
|
||||
*
|
||||
* This option is injected into the insurance selection list to force explicit user choice
|
||||
* for legal compliance. When selected, it satisfies validation requirements but transmits
|
||||
* no insurance data to the BPN API.
|
||||
*/
|
||||
public const NO_INSURANCE_ID = '0';
|
||||
|
||||
#[Groups(['api:single', 'api:list'])]
|
||||
public ?string $id = null;
|
||||
|
||||
@@ -180,4 +189,17 @@ class Insurance
|
||||
|
||||
return array_values(array_unique($urls));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this is the synthetic "no insurance" option.
|
||||
*
|
||||
* The "no insurance" option is a UI construct used to force explicit user choice.
|
||||
* It should be excluded from BPN API transmission.
|
||||
*
|
||||
* @return bool True if this is the "no insurance" option
|
||||
*/
|
||||
public function isNoInsurance(): bool
|
||||
{
|
||||
return self::NO_INSURANCE_ID === $this->id;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user