fix: don't restore selections from draft for immutable fields

This commit is contained in:
Björn Fromme
2026-02-24 18:16:52 +01:00
parent 5a64c85fb6
commit 876cd5a8fa
2 changed files with 530 additions and 72 deletions
+82 -72
View File
@@ -331,91 +331,101 @@ class BookingEditDraftService
*/
private function applyServiceSelections(ParticipantDto $participant, array $data, Travel $travel): void
{
// 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);
// Additional services category — only apply draft data when services are mutable.
// When immutable, the booking's current services must be preserved as-is to avoid
// API rejection (stale draft data could differ from the locked booking state).
if (true === $travel->additionalServicesMutable) {
// 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;
if (null !== $resolved) {
$participant->skiPass = $resolved;
}
}
// 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) - 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) - 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) - 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) - 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'])) {
$resolvedFromDraft = $this->resolveServiceArray($data['additionalServices'], $travel->additionalServices);
$participant->additionalServices = $this->preserveMandatoryServices(
$resolvedFromDraft,
$participant->additionalServices,
$travel
);
}
}
// 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);
}
// Transportation category — only apply draft data when transportation is mutable
if (true === $travel->transportationServicesMutable) {
// 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;
}
}
// 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);
}
// 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;
}
}
// 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) - 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;
// Parking (boolean) - overwrite strategy: user can uncheck
if (true === array_key_exists('parking', $data)) {
$participant->parking = (bool) $data['parking'];
}
}
// 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'])) {
$resolvedFromDraft = $this->resolveServiceArray($data['additionalServices'], $travel->additionalServices);
$participant->additionalServices = $this->preserveMandatoryServices(
$resolvedFromDraft,
$participant->additionalServices,
$travel
);
}
// Pickups category — only apply draft data when pickups are mutable
if (true === $travel->pickupsMutable) {
// 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;
}
}
// 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;
// Drop-off (single) - merge strategy: only apply if resolves to valid drop-off
if (true === array_key_exists('dropOff', $data) && null !== $data['dropOff']) {
$resolved = $this->resolveDropOff($data['dropOff'], $travel);
if (null !== $resolved) {
$participant->dropOff = $resolved;
$participant->differentDropOff = true;
}
}
}
// 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) - 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;
}
}
// Drop-off (single) - merge strategy: only apply if resolves to valid drop-off
if (true === array_key_exists('dropOff', $data) && null !== $data['dropOff']) {
$resolved = $this->resolveDropOff($data['dropOff'], $travel);
if (null !== $resolved) {
$participant->dropOff = $resolved;
$participant->differentDropOff = true;
}
}
// Parking (boolean) - overwrite strategy: user can uncheck
if (true === array_key_exists('parking', $data)) {
$participant->parking = (bool) $data['parking'];
}
// Insurance (single) - merge strategy: only apply if resolves to valid insurance
// Insurance (not governed by mutability categories — always apply from draft)
if (true === array_key_exists('insurance', $data) && null !== $data['insurance']) {
$resolved = $this->resolveInsurance($data['insurance'], $travel);
if (null !== $resolved) {