diff --git a/src/Form/Service/Condition/ApplicantCondition.php b/src/Form/Service/Condition/ApplicantCondition.php deleted file mode 100644 index 68b8c78..0000000 --- a/src/Form/Service/Condition/ApplicantCondition.php +++ /dev/null @@ -1,45 +0,0 @@ - $formData Current form data (unused) - * - * @return bool True if the participant is the applicant, false otherwise - */ - public function evaluate(BookingDto $bookingDto, int $participantIndex, array $formData): bool - { - // Default: applicant is the first participant (index 0) - return 0 === $participantIndex; - } - - public function getDependentFields(): array - { - return []; - } - - public function getDescription(): string - { - return 'Checks if the participant is the applicant (index 0)'; - } -} diff --git a/src/Form/Service/Condition/AuthenticatedUserPersonalDataCondition.php b/src/Form/Service/Condition/AuthenticatedUserPersonalDataCondition.php index cf3ea21..4617d07 100644 --- a/src/Form/Service/Condition/AuthenticatedUserPersonalDataCondition.php +++ b/src/Form/Service/Condition/AuthenticatedUserPersonalDataCondition.php @@ -8,46 +8,37 @@ use App\Form\Model\BookingDto; use App\Form\Service\Contract\FieldConditionInterface; /** - * Condition that determines if personal data fields should be hidden for authenticated users. + * Condition that determines if personal data fields should be shown as static text. * - * ⚠️ IMPORTANT: This condition is used in CREATE MODE ONLY to protect authenticated - * applicant data during booking creation. It is NOT used in edit mode. + * This condition protects the logged-in user's personal data from being modified + * within the booking form. Instead, users should edit their data via the personal + * data form to prevent: + * - Creating duplicate customer records in BPN + * - Disconnecting bookings from the user's account + * - Causing data inconsistencies between booking and account data * - * In CREATE mode: - * - When a logged-in user creates a booking, their personal data is prepopulated from - * their BPN account (including personId). - * - Their personal data fields should be hidden and displayed as static text to prevent - * modifications that could: - * - Create duplicate customer records in BPN - * - Disconnect bookings from the user's account - * - Cause data inconsistencies between booking and account data + * The condition compares the participant's personId with the booking applicant's + * personId to determine if the participant IS the logged-in user: + * - Customer-initiated bookings: Participant 0's personId matches applicant's personId + * - Agency-initiated bookings: Participant 0's personId differs from applicant's personId * - * In EDIT mode: - * - DO NOT use this condition. Edit mode uses PersonalDataMutabilityCondition instead, - * which respects the BPN API's per-participant `mutable` flag (aenderungmoeglich). - * - The mutability flag determines editability for ALL participants uniformly in edit mode. - * - * When this condition is satisfied (returns true), the template should: - * - Hide the form fields for personal data - * - Display the values as static, read-only text via the field_or_static macro + * In CREATE mode: Checks if participant has a personId (prepopulated from BPN account) + * In EDIT mode: Compares participant's personId with applicant's personId */ class AuthenticatedUserPersonalDataCondition implements FieldConditionInterface { /** - * Evaluates if personal data fields should be hidden (not editable) in CREATE mode. + * Evaluates if personal data fields should be shown as static text. * - * Returns true when the participant has a personId set, indicating they are - * a logged-in user whose data was prepopulated from their BPN account. - * When true, personal data fields should be hidden and displayed as static text. + * In CREATE mode: Returns true when participant has a personId (prepopulated). + * In EDIT mode: Returns true when participant's personId matches applicant's personId, + * indicating the participant IS the logged-in user. * - * This condition is only used during booking creation, not in edit mode. - * Edit mode uses PersonalDataMutabilityCondition to respect BPN's mutability flag. - * - * @param BookingDto $bookingDto The current booking data (create flow) + * @param BookingDto $bookingDto The booking data * @param int $participantIndex The index of the participant being evaluated * @param array $formData Current form data (unused) * - * @return bool True if fields should be hidden (authenticated user in create mode) + * @return bool True if fields should be shown as static text */ public function evaluate(BookingDto $bookingDto, int $participantIndex, array $formData): bool { @@ -56,8 +47,18 @@ class AuthenticatedUserPersonalDataCondition implements FieldConditionInterface return false; } - // Hide personal data fields if participant has BPN person ID (authenticated user in create mode) - // The personId is set during prepopulation when a logged-in user starts creating a booking + // In edit mode, compare participant's personId with applicant's personId + // This distinguishes customer-initiated (match) from agency-initiated (no match) bookings + if (BookingDto::MODE_EDIT === $bookingDto->getMode()) { + $applicantPersonId = $bookingDto->booking?->applicant?->personId; + if (null === $applicantPersonId) { + return false; + } + + return $participant->personId === $applicantPersonId; + } + + // In create mode, check if participant has personId (prepopulated from BPN account) return null !== $participant->personId; } @@ -81,6 +82,6 @@ class AuthenticatedUserPersonalDataCondition implements FieldConditionInterface */ public function getDescription(): string { - return 'Personal data is not editable for authenticated users in create mode (prevents duplicate records)'; + return 'Personal data is shown as static text for the logged-in user (edit via personal data form)'; } } diff --git a/src/Form/Service/Condition/FirstParticipantCondition.php b/src/Form/Service/Condition/FirstParticipantCondition.php new file mode 100644 index 0000000..7104398 --- /dev/null +++ b/src/Form/Service/Condition/FirstParticipantCondition.php @@ -0,0 +1,45 @@ + $formData Current form data (unused) + * + * @return bool True if the participant is the first participant (index 0) + */ + public function evaluate(BookingDto $bookingDto, int $participantIndex, array $formData): bool + { + return 0 === $participantIndex; + } + + public function getDependentFields(): array + { + return []; + } + + public function getDescription(): string + { + return 'Checks if the participant is the first participant (index 0)'; + } +} diff --git a/src/Form/Service/CreateFieldStateProvider.php b/src/Form/Service/CreateFieldStateProvider.php index 59fc895..892680e 100644 --- a/src/Form/Service/CreateFieldStateProvider.php +++ b/src/Form/Service/CreateFieldStateProvider.php @@ -7,7 +7,6 @@ namespace App\Form\Service; use App\BusProNet\Utility\DirectionMapper; use App\Form\Service\Abstract\AbstractFieldStateProvider; use App\Form\Service\Condition\AgeRangeCondition; -use App\Form\Service\Condition\ApplicantCondition; use App\Form\Service\Condition\AuthenticatedUserPersonalDataCondition; use App\Form\Service\Condition\BabyAgeCondition; use App\Form\Service\Condition\BookingEligibilityCondition; @@ -16,6 +15,7 @@ use App\Form\Service\Condition\CompositeCondition; use App\Form\Service\Condition\DateOfBirthProvidedCondition; use App\Form\Service\Condition\FieldValueCondition; use App\Form\Service\Condition\FinalBookingOnlyCondition; +use App\Form\Service\Condition\FirstParticipantCondition; use App\Form\Service\Condition\MultipleParticipantsCondition; use App\Form\Service\Condition\RentalSelectionCondition; use App\Form\Service\Condition\RoomSelectionCondition; @@ -107,10 +107,10 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider 'static_text' => $authenticatedUserCondition, ]; - // Mobile field: static text for authenticated users, required for guest applicants + // Mobile field: static text for authenticated users, required for first participant (guest bookings) $this->fieldStateConditions['mobile'] = [ 'static_text' => $authenticatedUserCondition, - 'required' => new ApplicantCondition(), + 'required' => new FirstParticipantCondition(), ]; // Address subfields - must render as static text to prevent creating duplicate BPN records @@ -194,13 +194,13 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider // Bulk insurance booking conditions $bulkInsuranceBookingCondition = new BulkInsuranceBookingCondition(); - // Show bulk insurance booking checkbox ONLY for applicant (index 0) when: + // Show bulk insurance booking checkbox ONLY for first participant (index 0) when: // - Date of birth is provided // - There are multiple participants (bulk assignment is meaningless for single participant) $this->fieldStateConditions['bulkInsuranceBooking'] = [ 'hidden' => CompositeCondition::or( CompositeCondition::not($dateOfBirthProvidedCondition), // Hide until date of birth provided - CompositeCondition::not(new ApplicantCondition()), // Hide for non-applicants + CompositeCondition::not(new FirstParticipantCondition()), // Hide for non-first participants CompositeCondition::not(new MultipleParticipantsCondition()) // Hide when only one participant ), ]; @@ -274,11 +274,11 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider 'hidden' => FieldValueCondition::equals('parking', false), ]; - // Make address field required for applicant (participant index 0) + // Make address field required for first participant (index 0) // Also render as static text for authenticated users to prevent duplicate BPN records $this->fieldStateConditions['address'] = [ 'static_text' => $authenticatedUserCondition, - 'required' => new ApplicantCondition(), + 'required' => new FirstParticipantCondition(), ]; // Room assignment field: diff --git a/src/Form/Service/EditFieldStateProvider.php b/src/Form/Service/EditFieldStateProvider.php index f9c1117..1617891 100644 --- a/src/Form/Service/EditFieldStateProvider.php +++ b/src/Form/Service/EditFieldStateProvider.php @@ -9,12 +9,12 @@ use App\Form\Model\BookingDto; use App\Form\Service\Abstract\AbstractFieldStateProvider; use App\Form\Service\Condition\AdditionalServicesMutabilityCondition; use App\Form\Service\Condition\AgeRangeCondition; -use App\Form\Service\Condition\ApplicantCondition; use App\Form\Service\Condition\AuthenticatedUserPersonalDataCondition; use App\Form\Service\Condition\BookingModeCondition; use App\Form\Service\Condition\CompositeCondition; use App\Form\Service\Condition\DateOfBirthProvidedCondition; use App\Form\Service\Condition\FieldValueCondition; +use App\Form\Service\Condition\FirstParticipantCondition; use App\Form\Service\Condition\PersonalDataMutabilityCondition; use App\Form\Service\Condition\PickupsMutabilityCondition; use App\Form\Service\Condition\RentalSelectionCondition; @@ -48,16 +48,14 @@ class EditFieldStateProvider extends AbstractFieldStateProvider $transportationServicesMutabilityCondition = new TransportationServicesMutabilityCondition(); $pickupsMutabilityCondition = new PickupsMutabilityCondition(); - // Personal data protection in edit mode has TWO rules: - // 1. Applicant with personId (authenticated user) - prevents editing master personal data - // Only applies to applicant (index 0), not other participants with personId - // 2. BPN mutability flag (mutable=false) - respects BPN business rules for any participant - // Hide fields if EITHER condition is true - $applicantCondition = new ApplicantCondition(); + // Personal data protection in edit mode: + // 1. Participant with personId (linked to BPN account) - edit via personal data form instead + // 2. BPN mutability flag (mutable=false) - respects BPN business rules + // Show fields as static text if EITHER condition is true $authenticatedUserCondition = new AuthenticatedUserPersonalDataCondition(); $personalDataMutabilityCondition = new PersonalDataMutabilityCondition(); $personalDataHiddenCondition = CompositeCondition::or( - CompositeCondition::and($applicantCondition, $authenticatedUserCondition), + $authenticatedUserCondition, $personalDataMutabilityCondition ); @@ -122,12 +120,12 @@ class EditFieldStateProvider extends AbstractFieldStateProvider 'hidden' => CompositeCondition::not($rentalCondition), ]; - // Age-dependent service fields - hidden until birth date provided (except for applicant who always has DOB) + // Age-dependent service fields - hidden until birth date provided (except for first participant who always has DOB) // Also readonly if services not mutable - // For applicant in edit mode: DOB is always available (patched from booking), so never hide + // For first participant in edit mode: DOB is always available (patched from booking), so never hide $hideUntilDobCondition = CompositeCondition::and( CompositeCondition::not($dateOfBirthProvidedCondition), - CompositeCondition::not(new ApplicantCondition()) // Don't hide for applicant + CompositeCondition::not(new FirstParticipantCondition()) // Don't hide for first participant ); $this->fieldStateConditions['courses'] = [ @@ -145,14 +143,14 @@ class EditFieldStateProvider extends AbstractFieldStateProvider 'readonly' => $additionalServicesMutabilityCondition, ]; - // Skipass - hidden until birth date (except applicant), readonly if services not mutable + // Skipass - hidden until birth date (except first participant), readonly if services not mutable $this->fieldStateConditions['skiPass'] = [ 'hidden' => $hideUntilDobCondition, 'readonly' => $additionalServicesMutabilityCondition, ]; // Rentals - shown only when skipass selected, readonly if services not mutable - // For applicant: only check skipass, not DOB + // For first participant: only check skipass, not DOB $this->fieldStateConditions['rentals'] = [ 'hidden' => CompositeCondition::or( $hideUntilDobCondition, @@ -167,7 +165,7 @@ class EditFieldStateProvider extends AbstractFieldStateProvider 'readonly' => $additionalServicesMutabilityCondition, ]; - // Transportation fields - hidden until birth date (except applicant), readonly if transportation not mutable + // Transportation fields - hidden until birth date (except first participant), readonly if transportation not mutable $this->fieldStateConditions['transportationOutbound'] = [ 'hidden' => $hideUntilDobCondition, 'readonly' => $transportationServicesMutabilityCondition,