wip: modernized edit flow

This commit is contained in:
Björn Fromme
2025-10-08 21:09:53 +02:00
parent c3008d7f7a
commit cba0f747cd
75 changed files with 1662 additions and 747 deletions
@@ -9,7 +9,7 @@ use App\BusProNet\Model\Pickup;
use App\BusProNet\Model\Service;
use App\BusProNet\Utility\DirectionMapper;
use App\Form\Model\BookingCreateDto;
use App\Form\Model\BookingDtoInterface;
use App\Form\Model\BookingDto;
use App\Form\Model\BookingEditDto;
use App\Form\Service\Abstract\AbstractFieldOptionsProvider;
use App\Form\Service\Factory\ParticipantRoomChoiceLoaderFactory;
@@ -82,11 +82,11 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
protected function registerFieldOptionProviders(): void
{
// Room assignment field provider (available for both create and edit workflows)
$this->fieldOptionProviders['assignedRoomId'] = function (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) {
$this->fieldOptionProviders['assignedRoomId'] = function (BookingDto $bookingDto, int $participantIndex, array $options = []) {
$choiceLoader = null;
$disabled = false;
if ($bookingDto instanceof BookingCreateDto) {
if (BookingDto::MODE_CREATE === $bookingDto->getMode()) {
// Create context: use selected rooms from step 1
$choiceLoader = $this->roomChoiceLoaderFactory->createForCreate(
$bookingDto->participants,
@@ -95,7 +95,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
);
// Disable when only one room type selected (auto-assigned)
$disabled = 1 === count($bookingDto->getSelectedRooms());
} elseif ($bookingDto instanceof BookingEditDto) {
} elseif (BookingDto::MODE_EDIT === $bookingDto->getMode()) {
// Edit context: use already-booked rooms from booking
$choiceLoader = $this->roomChoiceLoaderFactory->createForEdit(
$bookingDto->participants,
@@ -121,7 +121,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
};
// Courses field provider - provides age-appropriate courses from travel data
$this->fieldOptionProviders['courses'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
$this->fieldOptionProviders['courses'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Kurse',
'multiple' => true,
'expanded' => true,
@@ -156,7 +156,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
];
// Additional services field provider - provides age-appropriate additional services with mandatory pre-selection
$this->fieldOptionProviders['additionalServices'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
$this->fieldOptionProviders['additionalServices'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Zusatzleistungen',
'multiple' => true,
'expanded' => true,
@@ -199,7 +199,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
];
// Board field provider - provides age-appropriate board options from travel data
$this->fieldOptionProviders['board'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
$this->fieldOptionProviders['board'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Verpflegung',
'multiple' => true,
'expanded' => true,
@@ -229,7 +229,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
];
// Rentals field provider - provides age-appropriate rental options filtered by selected skipass duration
$this->fieldOptionProviders['rentals'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
$this->fieldOptionProviders['rentals'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Leihmaterial',
'multiple' => true,
'expanded' => true,
@@ -268,7 +268,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
];
// Rental insurance field provider - provides rental insurance options when rental services are selected
$this->fieldOptionProviders['rentalInsurance'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
$this->fieldOptionProviders['rentalInsurance'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => $this->getRentalInsuranceCheckboxLabel($bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_RENTAL_INSURANCE, true, true)),
'required' => false,
'property_path' => 'rentalInsuranceSelected',
@@ -276,7 +276,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
];
// License plate field provider - provides text input for vehicle license plate when parking is selected
$this->fieldOptionProviders['licensePlate'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
$this->fieldOptionProviders['licensePlate'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Kennzeichen',
'required' => false,
'attr' => [
@@ -288,7 +288,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
];
// Skipass field provider - provides age-appropriate skipass options from travel data filtered by date range
$this->fieldOptionProviders['skiPass'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
$this->fieldOptionProviders['skiPass'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Skipass',
'multiple' => false,
'expanded' => true,
@@ -323,7 +323,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
];
// Room remarks field provider - provides textarea for room-specific remarks (only for 'mbz' rooms)
$this->fieldOptionProviders['remarksRoom'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
$this->fieldOptionProviders['remarksRoom'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Wünsche oder Anmerkungen zum Zimmer',
'required' => false,
'sanitize_html' => true,
@@ -335,7 +335,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
// Transportation field providers - handles outbound/inbound transportation and pickup selection
// Outbound Transportation
$this->fieldOptionProviders['transportationOutbound'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
$this->fieldOptionProviders['transportationOutbound'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Hinfahrt',
'choices' => $bookingDto->travel->getTransportationServicesByDirection(DirectionMapper::OUTBOUND_TRAVEL),
'choice_label' => fn (Service $service) => $this->formatTransportationServiceLabel($service),
@@ -366,7 +366,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
];
// Inbound Transportation
$this->fieldOptionProviders['transportationInbound'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
$this->fieldOptionProviders['transportationInbound'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Rückfahrt',
'choices' => $bookingDto->travel->getTransportationServicesByDirection(DirectionMapper::INBOUND_TRAVEL),
'choice_label' => fn (Service $service) => $this->formatTransportationServiceLabel($service),
@@ -398,7 +398,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
// Pickup (conditional - only shown when either transportation direction is bus)
// Uses outbound pickups list, applies to both directions
$this->fieldOptionProviders['pickup'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
$this->fieldOptionProviders['pickup'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Zu- und Ausstieg',
'choices' => $bookingDto->travel->pickupsOutbound,
'choice_label' => fn (?Pickup $pickup) => $this->formatPickupLabelWithPrice($pickup),
@@ -411,13 +411,14 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
// Parking (conditional - only shown when outbound transportation is PKW)
// Simple checkbox since there's only ever one parking type
$this->fieldOptionProviders['parking'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
$this->fieldOptionProviders['parking'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => $this->getParkingCheckboxLabel($bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_PARKING, true)),
'required' => false,
];
// Bulk insurance booking checkbox (applicant only - controls insurance assignment for all participants)
$this->fieldOptionProviders['bulkInsuranceBooking'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
// Only registered in create mode - insurance cannot be modified in edit mode due to API limitation
$this->fieldOptionProviders['bulkInsuranceBooking'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Für alle Teilnehmer buchen',
'required' => false,
'attr' => [
@@ -428,7 +429,8 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
];
// Insurance field provider - provides age and eligibility filtered insurances for participants
$this->fieldOptionProviders['insurance'] = fn (BookingDtoInterface $bookingDto, int $participantIndex, array $options = []) => [
// Only registered in create mode - insurance cannot be modified in edit mode due to API limitation
$this->fieldOptionProviders['insurance'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => 'Reiseversicherung',
'multiple' => false,
'expanded' => true,
@@ -461,12 +463,12 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
* is only available for the exact duration of the skipass.
*
* @param array $rentals Array of rental Service objects to filter
* @param BookingDtoInterface $bookingDto The booking DTO containing participant data
* @param BookingDto $bookingDto The booking DTO containing participant data
* @param int $participantIndex Index of the participant to evaluate
*
* @return array Filtered array of rentals matching skipass duration
*/
private function filterRentalsBySkiPassDuration(array $rentals, BookingDtoInterface $bookingDto, int $participantIndex): array
private function filterRentalsBySkiPassDuration(array $rentals, BookingDto $bookingDto, int $participantIndex): array
{
$participant = $bookingDto->getParticipant($participantIndex);
if (null === $participant || null === $participant->skiPass) {
@@ -586,12 +588,12 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
* Checks if a service should be rendered as read-only due to unavailability.
*
* @param Service $service The service to check
* @param BookingDtoInterface $bookingDto The booking DTO containing participant data
* @param BookingDto $bookingDto The booking DTO containing participant data
* @param int $participantIndex Index of the participant currently selecting services
*
* @return bool True if the service should be read-only due to unavailability
*/
private function isServiceUnavailableForParticipant(Service $service, BookingDtoInterface $bookingDto, int $participantIndex): bool
private function isServiceUnavailableForParticipant(Service $service, BookingDto $bookingDto, int $participantIndex): bool
{
if (!$bookingDto instanceof BookingCreateDto) {
// For non-create workflows, don't apply availability restrictions
@@ -609,12 +611,12 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
* returns empty array to be handled by field visibility conditions.
*
* @param array $services Array of Service objects to filter
* @param BookingDtoInterface $bookingDto The booking DTO containing participant data
* @param BookingDto $bookingDto The booking DTO containing participant data
* @param int $participantIndex Index of the participant to evaluate
*
* @return array Filtered array of available services
*/
private function filterServicesByAgeConstraints(array $services, BookingDtoInterface $bookingDto, int $participantIndex): array
private function filterServicesByAgeConstraints(array $services, BookingDto $bookingDto, int $participantIndex): array
{
$ageEvaluator = new ServiceAgeEvaluator();
@@ -664,12 +666,12 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
/**
* Gets eligible insurances for a participant based on eligibility criteria.
*
* @param BookingDtoInterface $bookingDto The booking DTO containing travel and participant data
* @param BookingDto $bookingDto The booking DTO containing travel and participant data
* @param int $participantIndex The index of the participant to get eligible insurances for
*
* @return array Array of eligible insurance objects filtered by age, family status, and other constraints
*/
private function getEligibleInsurances(BookingDtoInterface $bookingDto, int $participantIndex): array
private function getEligibleInsurances(BookingDto $bookingDto, int $participantIndex): array
{
$participant = $bookingDto->getParticipant($participantIndex);
if (null === $participant) {
@@ -682,11 +684,6 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
// They are only available as part of packages
$availableInsurances = array_filter($availableInsurances, fn ($insurance) => !$insurance->complementary);
// Only apply insurance filtering for BookingCreateDto (creation workflow)
if (!$bookingDto instanceof BookingCreateDto) {
return $availableInsurances;
}
// Use insurance matching service to filter based on eligibility criteria
return $this->insuranceMatchingService->getEligibleInsurances(
$availableInsurances,