feat: authenticated booking
This commit is contained in:
@@ -155,25 +155,35 @@ class BookingParticipantType extends AbstractType
|
||||
// Helper to get field state or empty array
|
||||
$getFieldState = fn (string $fieldName) => $allFieldStates[$fieldName] ?? [];
|
||||
|
||||
$form
|
||||
->add('firstName', TextType::class, $this->mergeFieldState([
|
||||
// Add personal data fields with conditional inclusion for authenticated users
|
||||
if ($this->fieldStateProvider->shouldIncludeField('firstName', $bookingDto, $participantIndex)) {
|
||||
$form->add('firstName', TextType::class, $this->mergeFieldState([
|
||||
'label' => 'Vorname',
|
||||
'sanitize_html' => true,
|
||||
'property_path' => 'participant.firstName',
|
||||
], $getFieldState('firstName')))
|
||||
->add('lastName', TextType::class, $this->mergeFieldState([
|
||||
], $getFieldState('firstName')));
|
||||
}
|
||||
|
||||
if ($this->fieldStateProvider->shouldIncludeField('lastName', $bookingDto, $participantIndex)) {
|
||||
$form->add('lastName', TextType::class, $this->mergeFieldState([
|
||||
'label' => 'Nachname',
|
||||
'sanitize_html' => true,
|
||||
'property_path' => 'participant.lastName',
|
||||
], $getFieldState('lastName')))
|
||||
->add('dateOfBirth', BirthdayType::class, $this->mergeFieldState([
|
||||
], $getFieldState('lastName')));
|
||||
}
|
||||
|
||||
if ($this->fieldStateProvider->shouldIncludeField('dateOfBirth', $bookingDto, $participantIndex)) {
|
||||
$form->add('dateOfBirth', BirthdayType::class, $this->mergeFieldState([
|
||||
'label' => 'Geburtsdatum',
|
||||
'widget' => 'text',
|
||||
'input' => 'datetime_immutable',
|
||||
'html5' => false,
|
||||
'property_path' => 'participant.dateOfBirth',
|
||||
], $getFieldState('dateOfBirth')))
|
||||
->add('gender', ChoiceType::class, $this->mergeFieldState([
|
||||
], $getFieldState('dateOfBirth')));
|
||||
}
|
||||
|
||||
if ($this->fieldStateProvider->shouldIncludeField('gender', $bookingDto, $participantIndex)) {
|
||||
$form->add('gender', ChoiceType::class, $this->mergeFieldState([
|
||||
'label' => 'Geschlecht',
|
||||
'required' => false,
|
||||
'placeholder' => 'keine Angabe',
|
||||
@@ -183,28 +193,41 @@ class BookingParticipantType extends AbstractType
|
||||
'divers' => 'D',
|
||||
],
|
||||
'property_path' => 'participant.gender',
|
||||
], $getFieldState('gender')))
|
||||
->add('nationality', CountryType::class, $this->mergeFieldState([
|
||||
], $getFieldState('gender')));
|
||||
}
|
||||
|
||||
if ($this->fieldStateProvider->shouldIncludeField('nationality', $bookingDto, $participantIndex)) {
|
||||
$form->add('nationality', CountryType::class, $this->mergeFieldState([
|
||||
'label' => 'Nationalität',
|
||||
'property' => 'nationality',
|
||||
'preferred_choices' => ['D', 'A', 'CH'],
|
||||
'property_path' => 'participant.nationality',
|
||||
], $getFieldState('nationality')))
|
||||
->add('email', EmailType::class, $this->mergeFieldState([
|
||||
], $getFieldState('nationality')));
|
||||
}
|
||||
|
||||
if ($this->fieldStateProvider->shouldIncludeField('email', $bookingDto, $participantIndex)) {
|
||||
$form->add('email', EmailType::class, $this->mergeFieldState([
|
||||
'label' => 'E-Mail',
|
||||
'property_path' => 'participant.email',
|
||||
], $getFieldState('email')))
|
||||
->add('mobile', TextType::class, $this->mergeFieldState([
|
||||
], $getFieldState('email')));
|
||||
}
|
||||
|
||||
if ($this->fieldStateProvider->shouldIncludeField('mobile', $bookingDto, $participantIndex)) {
|
||||
$form->add('mobile', TextType::class, $this->mergeFieldState([
|
||||
'label' => 'Telefon (mobil)',
|
||||
'required' => false,
|
||||
'sanitize_html' => true,
|
||||
'property_path' => 'participant.mobile',
|
||||
], $getFieldState('mobile')))
|
||||
->add('address', AddressType::class, $this->mergeFieldState([
|
||||
], $getFieldState('mobile')));
|
||||
}
|
||||
|
||||
if ($this->fieldStateProvider->shouldIncludeField('address', $bookingDto, $participantIndex)) {
|
||||
$form->add('address', AddressType::class, $this->mergeFieldState([
|
||||
'label' => 'Adresse',
|
||||
'required' => false,
|
||||
'property_path' => 'participant.address',
|
||||
], $getFieldState('address')));
|
||||
}
|
||||
|
||||
// Add body dimensions with state handling - use shouldIncludeField method
|
||||
if ($this->fieldStateProvider->shouldIncludeField('bodyDimensions', $bookingDto, $participantIndex)) {
|
||||
|
||||
@@ -128,6 +128,7 @@ class ParticipantDto
|
||||
$instance->mutable = $personalData->mutable;
|
||||
$instance->firstName = $personalData->firstName;
|
||||
$instance->lastName = $personalData->name;
|
||||
$instance->title = $personalData->title;
|
||||
$instance->gender = $personalData->gender;
|
||||
$instance->nationality = $personalData->nationality;
|
||||
$instance->email = $personalData->communication?->email;
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Service\Condition;
|
||||
|
||||
use App\Form\Model\BookingDto;
|
||||
use App\Form\Service\Contract\FieldConditionInterface;
|
||||
|
||||
/**
|
||||
* Condition that determines if personal data fields should be hidden for authenticated users.
|
||||
*
|
||||
* When a participant is linked to an existing BPN account (has addressId and personId),
|
||||
* their personal data should not be editable during the booking process. Changes to
|
||||
* master personal data should only happen through the dedicated personal data management
|
||||
* interface to prevent:
|
||||
* - Creating duplicate customer records in BPN
|
||||
* - Disconnecting bookings from the user's account
|
||||
* - Data inconsistencies between booking and account data
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* This follows the same pattern as bulk insurance booking.
|
||||
*/
|
||||
class AuthenticatedUserPersonalDataCondition implements FieldConditionInterface
|
||||
{
|
||||
/**
|
||||
* Evaluates if personal data fields should be hidden (not editable).
|
||||
*
|
||||
* Returns true when the participant has both addressId and personId set,
|
||||
* indicating they are linked to an existing BPN account. When true,
|
||||
* personal data fields should be hidden and displayed as static text.
|
||||
*
|
||||
* @param BookingDto $bookingDto The current booking data
|
||||
* @param int $participantIndex The index of the participant being evaluated
|
||||
* @param array<string, mixed> $formData Current form data (unused)
|
||||
*
|
||||
* @return bool True if fields should be hidden (participant linked to BPN account)
|
||||
*/
|
||||
public function evaluate(BookingDto $bookingDto, int $participantIndex, array $formData): bool
|
||||
{
|
||||
$participant = $bookingDto->getParticipant($participantIndex);
|
||||
if (null === $participant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hide personal data fields if participant has BPN account IDs
|
||||
// (indicates prepopulation from authenticated user)
|
||||
return null !== $participant->addressId && null !== $participant->personId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns field names that this condition depends on.
|
||||
*
|
||||
* This condition is based on addressId/personId which are set during prepopulation
|
||||
* and don't change during the form interaction, so no field dependencies.
|
||||
*
|
||||
* @return string[] Empty array - no field dependencies
|
||||
*/
|
||||
public function getDependentFields(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human-readable description of this condition.
|
||||
*
|
||||
* @return string Description of the authenticated user personal data protection logic
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Personal data is not editable when participant is linked to BPN account (prevents duplicate records)';
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ namespace App\Form\Service;
|
||||
use App\BusProNet\Utility\DirectionMapper;
|
||||
use App\Form\Service\Abstract\AbstractFieldStateProvider;
|
||||
use App\Form\Service\Condition\ApplicantCondition;
|
||||
use App\Form\Service\Condition\AuthenticatedUserPersonalDataCondition;
|
||||
use App\Form\Service\Condition\BookingEligibilityCondition;
|
||||
use App\Form\Service\Condition\BulkInsuranceBookingCondition;
|
||||
use App\Form\Service\Condition\CompositeCondition;
|
||||
@@ -73,6 +74,64 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider
|
||||
$rentalCondition = new RentalSelectionCondition();
|
||||
$skiPassCondition = new SkiPassSelectionCondition();
|
||||
|
||||
// Authenticated user personal data protection
|
||||
// Hide personal data fields for participants linked to BPN accounts (prevents duplicate records)
|
||||
$authenticatedUserCondition = new AuthenticatedUserPersonalDataCondition();
|
||||
|
||||
$this->fieldStateConditions['firstName'] = [
|
||||
'hidden' => $authenticatedUserCondition,
|
||||
];
|
||||
|
||||
$this->fieldStateConditions['lastName'] = [
|
||||
'hidden' => $authenticatedUserCondition,
|
||||
];
|
||||
|
||||
$this->fieldStateConditions['gender'] = [
|
||||
'hidden' => $authenticatedUserCondition,
|
||||
];
|
||||
|
||||
$this->fieldStateConditions['nationality'] = [
|
||||
'hidden' => $authenticatedUserCondition,
|
||||
];
|
||||
|
||||
$this->fieldStateConditions['dateOfBirth'] = [
|
||||
'hidden' => $authenticatedUserCondition,
|
||||
];
|
||||
|
||||
$this->fieldStateConditions['email'] = [
|
||||
'hidden' => $authenticatedUserCondition,
|
||||
];
|
||||
|
||||
// Mobile field: hidden for authenticated users, required for guest applicants
|
||||
$this->fieldStateConditions['mobile'] = [
|
||||
'hidden' => $authenticatedUserCondition,
|
||||
'required' => new ApplicantCondition(),
|
||||
];
|
||||
|
||||
// Address subfields - must be hidden to prevent creating duplicate BPN records
|
||||
$this->fieldStateConditions['address.street'] = [
|
||||
'hidden' => $authenticatedUserCondition,
|
||||
];
|
||||
|
||||
$this->fieldStateConditions['address.postCode'] = [
|
||||
'hidden' => $authenticatedUserCondition,
|
||||
];
|
||||
|
||||
$this->fieldStateConditions['address.city'] = [
|
||||
'hidden' => $authenticatedUserCondition,
|
||||
];
|
||||
|
||||
$this->fieldStateConditions['address.country'] = [
|
||||
'hidden' => $authenticatedUserCondition,
|
||||
];
|
||||
|
||||
$this->fieldStateConditions['address.district'] = [
|
||||
'hidden' => $authenticatedUserCondition,
|
||||
];
|
||||
|
||||
// Note: Body dimensions (height, weight, shoeSize) are NOT hidden for authenticated users
|
||||
// These are preferences/measurements that can be updated without creating duplicate records
|
||||
|
||||
// Hide body dimensions section unless rental services are selected
|
||||
$this->fieldStateConditions['bodyDimensions'] = [
|
||||
'hidden' => CompositeCondition::not($rentalCondition),
|
||||
@@ -195,11 +254,6 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider
|
||||
'hidden' => FieldValueCondition::equals('parking', false),
|
||||
];
|
||||
|
||||
// Make mobile field required for applicant (participant index 0)
|
||||
$this->fieldStateConditions['mobile'] = [
|
||||
'required' => new ApplicantCondition(),
|
||||
];
|
||||
|
||||
// Make address field required for applicant (participant index 0)
|
||||
$this->fieldStateConditions['address'] = [
|
||||
'required' => new ApplicantCondition(),
|
||||
|
||||
Reference in New Issue
Block a user