feat: merge draft data with api response for selected fields

This commit is contained in:
Björn Fromme
2026-01-12 18:25:51 +01:00
parent 7636ee41af
commit 5221cea53b
2 changed files with 135 additions and 27 deletions
+111 -27
View File
@@ -240,7 +240,7 @@ class BookingEditDraftService
if (true === array_key_exists('gender', $data)) {
$participant->gender = $data['gender'];
}
if (true === array_key_exists('nationality', $data)) {
if (true === array_key_exists('nationality', $data) && '' !== $data['nationality'] && null !== $data['nationality']) {
$participant->nationality = $data['nationality'];
}
}
@@ -308,66 +308,101 @@ class BookingEditDraftService
/**
* Applies service selections to participant, resolving IDs against Travel data.
*
* Single-select fields (radio buttons) use a merge strategy: draft values are only applied
* if they resolve to a valid service. This preserves API data when:
* - The draft was saved before certain services were assigned
* - The draft contains service IDs that no longer exist in current travel data
*
* Multi-select fields (checkboxes) and booleans use overwrite strategy: draft values
* always replace API data, since users can intentionally clear these selections.
*/
private function applyServiceSelections(ParticipantDto $participant, array $data, Travel $travel): void
{
// Ski pass (single service)
if (true === array_key_exists('skiPass', $data)) {
$participant->skiPass = $this->resolveService($data['skiPass'], $travel->additionalServices);
// Ski pass (single service) - merge strategy: only apply if resolves to valid service
if (true === array_key_exists('skiPass', $data) && null !== $data['skiPass']) {
$draftSkiPassId = $data['skiPass'];
$resolved = $this->resolveService($draftSkiPassId, $travel->additionalServices);
if (null !== $resolved) {
$participant->skiPass = $resolved;
}
}
// Courses (array)
// Courses (array) - overwrite strategy: user can deselect all
if (true === array_key_exists('courses', $data) && true === is_array($data['courses'])) {
$participant->courses = $this->resolveServiceArray($data['courses'], $travel->additionalServices);
}
// Board (array)
// Board (array) - overwrite strategy: user can deselect all
if (true === array_key_exists('board', $data) && true === is_array($data['board'])) {
$participant->board = $this->resolveServiceArray($data['board'], $travel->additionalServices);
}
// Rentals (array)
// Rentals (array) - overwrite strategy: user can deselect all
if (true === array_key_exists('rentals', $data) && true === is_array($data['rentals'])) {
$participant->rentals = $this->resolveServiceArray($data['rentals'], $travel->additionalServices);
}
// Rental insurance (single)
if (true === array_key_exists('rentalInsurance', $data)) {
$participant->rentalInsurance = $this->resolveService($data['rentalInsurance'], $travel->additionalServices);
$participant->rentalInsuranceSelected = null !== $participant->rentalInsurance;
// Rental insurance (single) - merge strategy: only apply if resolves to valid service
if (true === array_key_exists('rentalInsurance', $data) && null !== $data['rentalInsurance']) {
$resolved = $this->resolveService($data['rentalInsurance'], $travel->additionalServices);
if (null !== $resolved) {
$participant->rentalInsurance = $resolved;
$participant->rentalInsuranceSelected = true;
}
}
// Additional services (array)
// Additional services (array) - overwrite strategy with mandatory service preservation
// Additional services (array) - overwrite strategy with mandatory service preservation
// User can deselect optional services, but mandatory services from API must be preserved
if (true === array_key_exists('additionalServices', $data) && true === is_array($data['additionalServices'])) {
$participant->additionalServices = $this->resolveServiceArray($data['additionalServices'], $travel->additionalServices);
$resolvedFromDraft = $this->resolveServiceArray($data['additionalServices'], $travel->additionalServices);
$participant->additionalServices = $this->preserveMandatoryServices(
$resolvedFromDraft,
$participant->additionalServices,
$travel
);
}
// Transportation outbound (single)
if (true === array_key_exists('transportationOutbound', $data)) {
$participant->transportationOutbound = $this->resolveService($data['transportationOutbound'], $travel->transportationServices);
// Transportation outbound (single) - merge strategy: only apply if resolves to valid service
if (true === array_key_exists('transportationOutbound', $data) && null !== $data['transportationOutbound']) {
$resolved = $this->resolveService($data['transportationOutbound'], $travel->transportationServices);
if (null !== $resolved) {
$participant->transportationOutbound = $resolved;
}
}
// Transportation inbound (single)
if (true === array_key_exists('transportationInbound', $data)) {
$participant->transportationInbound = $this->resolveService($data['transportationInbound'], $travel->transportationServices);
// Transportation inbound (single) - merge strategy: only apply if resolves to valid service
if (true === array_key_exists('transportationInbound', $data) && null !== $data['transportationInbound']) {
$resolved = $this->resolveService($data['transportationInbound'], $travel->transportationServices);
if (null !== $resolved) {
$participant->transportationInbound = $resolved;
}
}
// Pickup (single)
if (true === array_key_exists('pickup', $data)) {
$participant->pickup = $this->resolvePickup($data['pickup'], $travel);
// Pickup (single) - merge strategy: only apply if resolves to valid pickup
if (true === array_key_exists('pickup', $data) && null !== $data['pickup']) {
$resolved = $this->resolvePickup($data['pickup'], $travel);
if (null !== $resolved) {
$participant->pickup = $resolved;
}
}
// Parking (boolean)
// Parking (boolean) - overwrite strategy: user can uncheck
if (true === array_key_exists('parking', $data)) {
$participant->parking = (bool) $data['parking'];
}
// Insurance (single)
if (true === array_key_exists('insurance', $data)) {
$participant->insurance = $this->resolveInsurance($data['insurance'], $travel);
// Insurance (single) - merge strategy: only apply if resolves to valid insurance
if (true === array_key_exists('insurance', $data) && null !== $data['insurance']) {
$resolved = $this->resolveInsurance($data['insurance'], $travel);
if (null !== $resolved) {
$participant->insurance = $resolved;
}
}
// Bulk insurance booking (boolean)
// Bulk insurance booking (boolean) - overwrite strategy: user can uncheck
if (true === array_key_exists('bulkInsuranceBooking', $data)) {
$participant->bulkInsuranceBooking = (bool) $data['bulkInsuranceBooking'];
}
@@ -447,4 +482,53 @@ class BookingEditDraftService
return $travel->insurances[$insuranceId] ?? null;
}
/**
* Preserves mandatory services from API data when applying draft.
*
* Mandatory services (like Ortstaxe) cannot be deselected by users and must
* always be present in the booking. When a draft was created before the agency
* assigned mandatory services, this method ensures those services are preserved
* from the fresh API data rather than being overwritten with stale draft data.
*
* The mandatory flag must be looked up from travel data since booking data
* doesn't include the pflicht attribute.
*
* @param array $draftServices Services resolved from draft data
* @param array $originalServices Services from fresh API data (booking assignments)
* @param Travel $travel Travel data containing mandatory flag on services
*
* @return array Merged array with draft services plus any missing mandatory services
*/
private function preserveMandatoryServices(array $draftServices, array $originalServices, Travel $travel): array
{
// Build lookup of service IDs already in the draft
$draftServiceIds = [];
foreach ($draftServices as $service) {
if (null !== $service->id) {
$draftServiceIds[$service->id] = true;
}
}
// Add mandatory services from original API data that aren't in draft
// Check mandatory status from travel data (pflicht attribute)
foreach ($originalServices as $service) {
if (false === isset($draftServiceIds[$service->id])) {
// Look up mandatory status from travel data
$travelService = $travel->additionalServices[$service->id] ?? null;
$isMandatory = null !== $travelService && true === $travelService->mandatory;
if ($isMandatory) {
$draftServices[] = $service;
$this->logger->debug('Preserved mandatory service from API during draft application', [
'service_id' => $service->id,
'service_label' => $service->label,
]);
}
}
}
return $draftServices;
}
}