wip: backport form handing system from create flow to edit flow

This commit is contained in:
Björn Fromme
2026-03-16 11:59:10 +01:00
parent ba0a86cbea
commit 9eeed7f992
10 changed files with 1369 additions and 220 deletions
+4 -1
View File
@@ -69,7 +69,10 @@ class BookingCreateStep2Type extends AbstractType
private function addParticipantsField(FormInterface $form): void
{
$form->add('participants', CollectionType::class, [
'entry_type' => BookingCreateParticipantType::class,
'entry_type' => BookingParticipantType::class,
'entry_options' => [
'edit_mode' => false,
],
'allow_add' => false,
'allow_delete' => false,
]);
+18 -34
View File
@@ -2,8 +2,6 @@
namespace App\Form;
use App\BusProNet\Constants;
use App\BusProNet\Model\Service;
use App\Form\Model\BookingEditDto;
use App\Form\Service\ParticipantFieldHandlerRegistry;
use Symfony\Component\Form\AbstractType;
@@ -33,25 +31,10 @@ class BookingEditType extends AbstractType
$data = $event->getData();
$form = $event->getForm();
$travelData = $data->travel;
$form->add('participants', CollectionType::class, [
'entry_type' => BookingEditParticipantType::class,
'entry_type' => BookingParticipantType::class,
'entry_options' => [
'selectable_courses' => $this->mergeSelectableServices($data, Constants::TOKEN_COURSES),
'selectable_ski_passes' => $this->mergeSelectableServices($data, Constants::TOKEN_SKI_PASS),
'selectable_services' => $this->mergeSelectableServices($data, Constants::TOKEN_ADDITIONAL),
'selectable_board' => $this->mergeSelectableServices($data, Constants::TOKEN_BOARD),
'selectable_rentals' => $this->mergeSelectableServices($data, Constants::TOKEN_RENTALS),
'selectable_transportation_services_to' => $travelData
->getTransportationServicesByDirection('HIN', false),
'selectable_transportation_services_fro' => $travelData
->getTransportationServicesByDirection('RUECK', false),
'selectable_pickups' => $travelData->pickupsOutbound,
'personal_data_mutable' => $travelData->participantDataMutable,
'additional_services_mutable' => $travelData->additionalServicesMutable,
'transportation_services_mutable' => $travelData->transportationServicesMutable,
'pickups_mutable' => $travelData->pickupsMutable,
'applicant_id' => $data->booking->applicant->personId,
'edit_mode' => true,
],
'allow_add' => false,
'allow_delete' => false,
@@ -62,26 +45,27 @@ class BookingEditType extends AbstractType
{
$form = $event->getForm();
$submittedData = $event->getData();
/** @var BookingEditDto $bookingDto */
$bookingDto = $form->getData();
$this->participantFieldHandlerRegistry->processFields($submittedData, $bookingDto);
}
private function mergeSelectableServices(BookingEditDto $data, string|array $subType): array
{
// combine selectable services from travel data with additional services
// from booking data
$selectableServices = $data->travel->getAdditionalServicesBySubTypes($subType);
$selectableServiceIds = array_map(function (Service $service) {
return $service->id;
}, $selectableServices);
// Process field handlers and synchronize submitted data with cleaned DTO state
$cleanedSubmittedData = $this->participantFieldHandlerRegistry->processFieldsAndSync($submittedData, $bookingDto);
$event->setData($cleanedSubmittedData);
foreach ($data->booking->getAdditionalServicesByGroup($subType) as $item) {
if (false === in_array($item->id, $selectableServiceIds)) {
$selectableServices[] = $item;
}
// Rebuild the 'participants' field with the updated DTO
if ($form->has('participants')) {
$form->remove('participants');
}
return $selectableServices;
$form->add('participants', CollectionType::class, [
'entry_type' => BookingParticipantType::class,
'entry_options' => [
'edit_mode' => true,
],
'allow_add' => false,
'allow_delete' => false,
]);
}
public function configureOptions(OptionsResolver $resolver): void
@@ -6,7 +6,9 @@ use App\BusProNet\Form\CountryType;
use App\Form\Model\BookingDtoInterface;
use App\Form\Model\ParticipantDto;
use App\Form\Service\Contract\FieldOptionsProviderInterface;
use App\Form\Service\Contract\FieldStateProviderInterface;
use App\Form\Service\CreateFieldStateProvider;
use App\Form\Service\EditFieldStateProvider;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
@@ -20,16 +22,24 @@ use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class BookingCreateParticipantType extends AbstractType
class BookingParticipantType extends AbstractType
{
private FieldStateProviderInterface $fieldStateProvider;
public function __construct(
private readonly FieldOptionsProviderInterface $fieldOptionsProvider,
private readonly CreateFieldStateProvider $fieldStateProvider,
private readonly CreateFieldStateProvider $createFieldStateProvider,
private readonly EditFieldStateProvider $editFieldStateProvider,
) {
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
// Select field state provider based on edit_mode option
$this->fieldStateProvider = $options['edit_mode']
? $this->editFieldStateProvider
: $this->createFieldStateProvider;
$builder
->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$this->onPreSetData($event);
@@ -341,8 +351,10 @@ class BookingCreateParticipantType extends AbstractType
$resolver->setDefaults([
'data_class' => ParticipantDto::class,
'selected_rooms' => [],
'edit_mode' => false,
]);
$resolver->setAllowedTypes('selected_rooms', 'array');
$resolver->setAllowedTypes('edit_mode', 'bool');
}
}
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace App\Form\Service\Condition;
use App\Form\Model\BookingDtoInterface;
use App\Form\Service\Contract\FieldConditionInterface;
/**
* Condition that checks if additional services are mutable in the edit flow.
*/
class AdditionalServicesMutabilityCondition implements FieldConditionInterface
{
public function evaluate(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool
{
return false === $bookingDto->travel->additionalServicesMutable;
}
public function getDependentFields(): array
{
return [];
}
public function getDescription(): string
{
return 'Additional services are not mutable (edit flow)';
}
}
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace App\Form\Service\Condition;
use App\Form\Model\BookingDtoInterface;
use App\Form\Service\Contract\FieldConditionInterface;
/**
* Condition that checks if pickups are mutable in the edit flow.
*/
class PickupsMutabilityCondition implements FieldConditionInterface
{
public function evaluate(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool
{
return false === $bookingDto->travel->pickupsMutable;
}
public function getDependentFields(): array
{
return [];
}
public function getDescription(): string
{
return 'Pickups are not mutable (edit flow)';
}
}
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace App\Form\Service\Condition;
use App\Form\Model\BookingDtoInterface;
use App\Form\Service\Contract\FieldConditionInterface;
/**
* Condition that checks if transportation services are mutable in the edit flow.
*/
class TransportationServicesMutabilityCondition implements FieldConditionInterface
{
public function evaluate(BookingDtoInterface $bookingDto, int $participantIndex, array $formData): bool
{
return false === $bookingDto->travel->transportationServicesMutable;
}
public function getDependentFields(): array
{
return [];
}
public function getDescription(): string
{
return 'Transportation services are not mutable (edit flow)';
}
}
+104 -2
View File
@@ -4,10 +4,19 @@ declare(strict_types=1);
namespace App\Form\Service;
use App\BusProNet\Utility\DirectionMapper;
use App\Form\Service\Abstract\AbstractFieldStateProvider;
use App\Form\Service\Condition\AdditionalServicesMutabilityCondition;
use App\Form\Service\Condition\ApplicantCondition;
use App\Form\Service\Condition\CompositeCondition;
use App\Form\Service\Condition\DateOfBirthProvidedCondition;
use App\Form\Service\Condition\FieldValueCondition;
use App\Form\Service\Condition\MutabilityCondition;
use App\Form\Service\Condition\PickupsMutabilityCondition;
use App\Form\Service\Condition\RentalSelectionCondition;
use App\Form\Service\Condition\ServiceSubTypeCondition;
use App\Form\Service\Condition\SkiPassSelectionCondition;
use App\Form\Service\Condition\TransportationServicesMutabilityCondition;
/**
* Field state provider for the booking edit workflow.
@@ -24,11 +33,16 @@ class EditFieldStateProvider extends AbstractFieldStateProvider
* Registers field state conditions for the edit workflow.
*
* This method defines the conditional logic for field states in the
* booking edit process. It makes personal data fields readonly when
* the participant is the applicant or when the field is not mutable.
* booking edit process. It makes fields readonly based on mutability flags
* and applies the same conditional visibility logic as the create flow.
*/
protected function registerFieldStateConditions(): void
{
// Mutability conditions
$additionalServicesMutabilityCondition = new AdditionalServicesMutabilityCondition();
$transportationServicesMutabilityCondition = new TransportationServicesMutabilityCondition();
$pickupsMutabilityCondition = new PickupsMutabilityCondition();
// Make all personal data fields readonly if not mutable OR if applicant
$personalDataFields = [
'firstName',
@@ -47,5 +61,93 @@ class EditFieldStateProvider extends AbstractFieldStateProvider
),
];
}
// Conditional visibility for service fields (same as create flow)
$rentalCondition = new RentalSelectionCondition();
$skiPassCondition = new SkiPassSelectionCondition();
$dateOfBirthProvidedCondition = new DateOfBirthProvidedCondition();
// Hide body dimensions unless rentals are selected
$this->fieldStateConditions['bodyDimensions'] = [
'hidden' => CompositeCondition::not($rentalCondition),
];
// Age-dependent service fields - hidden until birth date provided
// Also readonly if services not mutable
$this->fieldStateConditions['courses'] = [
'hidden' => CompositeCondition::not($dateOfBirthProvidedCondition),
'readonly' => $additionalServicesMutabilityCondition,
];
$this->fieldStateConditions['additionalServices'] = [
'hidden' => CompositeCondition::not($dateOfBirthProvidedCondition),
'readonly' => $additionalServicesMutabilityCondition,
];
$this->fieldStateConditions['board'] = [
'hidden' => CompositeCondition::not($dateOfBirthProvidedCondition),
'readonly' => $additionalServicesMutabilityCondition,
];
// Skipass - hidden until birth date, readonly if services not mutable
$this->fieldStateConditions['skiPass'] = [
'hidden' => CompositeCondition::not($dateOfBirthProvidedCondition),
'readonly' => $additionalServicesMutabilityCondition,
];
// Rentals - shown only when skipass selected, readonly if services not mutable
$this->fieldStateConditions['rentals'] = [
'hidden' => CompositeCondition::or(
CompositeCondition::not($dateOfBirthProvidedCondition),
CompositeCondition::not($skiPassCondition)
),
'readonly' => $additionalServicesMutabilityCondition,
];
// Rental insurance - shown only when rentals selected, readonly if services not mutable
$this->fieldStateConditions['rentalInsurance'] = [
'hidden' => CompositeCondition::not($rentalCondition),
'readonly' => $additionalServicesMutabilityCondition,
];
// Transportation fields - hidden until birth date, readonly if transportation not mutable
$this->fieldStateConditions['transportationOutbound'] = [
'hidden' => CompositeCondition::not($dateOfBirthProvidedCondition),
'readonly' => $transportationServicesMutabilityCondition,
];
$this->fieldStateConditions['transportationInbound'] = [
'hidden' => CompositeCondition::not($dateOfBirthProvidedCondition),
'readonly' => $transportationServicesMutabilityCondition,
];
// Pickup fields - shown only when transportation is BUS, readonly if pickups not mutable
$this->fieldStateConditions['pickupOutbound'] = [
'hidden' => CompositeCondition::not(
ServiceSubTypeCondition::equals('transportationOutbound', DirectionMapper::SUBTYPE_BUS_API)
),
'readonly' => $pickupsMutabilityCondition,
];
$this->fieldStateConditions['pickupInbound'] = [
'hidden' => CompositeCondition::not(
ServiceSubTypeCondition::equals('transportationInbound', DirectionMapper::SUBTYPE_BUS_API)
),
'readonly' => $pickupsMutabilityCondition,
];
// Parking - shown only when outbound transportation is PKW, readonly if transportation not mutable
$this->fieldStateConditions['parking'] = [
'hidden' => CompositeCondition::not(
ServiceSubTypeCondition::equals('transportationOutbound', DirectionMapper::SUBTYPE_CAR_API)
),
'readonly' => $transportationServicesMutabilityCondition,
];
// License plate - shown only when parking selected, readonly if transportation not mutable
$this->fieldStateConditions['licensePlate'] = [
'hidden' => FieldValueCondition::equals('parking', false),
'readonly' => $transportationServicesMutabilityCondition,
];
}
}