Files
myep/src/Form/Service/ParticipantBoardFieldHandler.php
T
Björn Fromme 957e20d763 fix: preserve selected services from booking even when unavailable
When editing a booking, services that were previously booked but are no
longer available in the travel catalog were being dropped. This caused
API error 650 ("Anzahl Leistung stimmt nicht mit Teilnehmerzuordnung
überein") because the service participant counts no longer matched.

The fix ensures that in edit mode, booked services are merged with
travel data services in both:
- Form rendering (ParticipantFieldOptionsProvider): so checkboxes appear
- Handler validation (AbstractParticipantFieldHandler): so selections
  are accepted

This allows users to keep their existing service selections or
deliberately replace them with other available options. Create mode
remains unchanged.
2026-03-16 12:02:28 +01:00

120 lines
4.0 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Form\Service;
use App\BusProNet\Constants;
use App\BusProNet\Model\Service;
use App\Form\Model\BookingDto;
use App\Form\Service\Abstract\AbstractParticipantFieldHandler;
/**
* Handles processing of the board field for booking participants.
*
* This handler manages board/meal option selections for participants in the booking
* creation process. It processes the board field from form submissions,
* filters out age-inappropriate options, and updates the participant DTO with only
* valid selections.
*
* Dependencies: dateOfBirth (must be processed first for age evaluation)
*/
class ParticipantBoardFieldHandler extends AbstractParticipantFieldHandler
{
public function getFieldName(): string
{
return 'board';
}
public function getDependencies(): array
{
return ['dateOfBirth'];
}
/**
* Determines if this handler should process the field based on submitted data.
*
* For service selection fields like board options, we always need to process
* to handle cases where all selections are cleared (field not present in data).
* This ensures the participant DTO is updated with an empty array when no
* services are selected.
*
* @param array<string, mixed> $submittedData The submitted participant form data
* @param string $mode The booking mode (BookingDto::MODE_CREATE or MODE_EDIT)
* @param int $participantIndex The index of the participant being processed
*
* @return bool Always returns true for service selection fields
*/
public function shouldProcess(array $submittedData, string $mode, int $participantIndex): bool
{
return true; // Always process to handle deselection cases
}
public function processField(array $submittedData, BookingDto $bookingDto, int $participantIndex): void
{
$participant = $this->getParticipant($bookingDto, $participantIndex);
if (null === $participant) {
return;
}
$selectedBoard = $this->getFieldValue($submittedData, $this->getFieldName()) ?? [];
// Get available board options from travel data (includes booked options in edit mode)
$availableBoard = $this->getAvailableServicesWithBooked(
$bookingDto,
$participantIndex,
Constants::TOKEN_BOARD
);
$validSelections = $this->filterValidServiceSelections(
$selectedBoard,
$availableBoard,
$bookingDto,
$participantIndex
);
$participant->board = $validSelections;
}
private function filterValidServiceSelections(
array $selectedServices,
array $availableServices,
BookingDto $bookingDto,
int $participantIndex,
): array {
$validSelections = [];
foreach ($selectedServices as $selectedService) {
if ($this->isServiceValidForParticipant($selectedService, $availableServices, $bookingDto, $participantIndex)) {
// Convert the selected service ID/data back to the actual Service object
$serviceObject = $this->findServiceInAvailableServices($selectedService, $availableServices);
if (null !== $serviceObject) {
$validSelections[] = $serviceObject;
}
}
}
return $validSelections;
}
private function isServiceValidForParticipant(
mixed $selectedService,
array $availableServices,
BookingDto $bookingDto,
int $participantIndex,
): bool {
$service = $this->findServiceInAvailableServices($selectedService, $availableServices);
if (null === $service) {
return false;
}
$ageEvaluator = new ServiceAgeEvaluator();
if (false === $ageEvaluator->canEvaluate($service)) {
return true;
}
return $ageEvaluator->isServiceAvailableForParticipant($service, $bookingDto, $participantIndex);
}
}