fix: adapt changes in create mode for edit mode, cleanup
This commit is contained in:
@@ -15,6 +15,7 @@ use App\Entity\User;
|
|||||||
use App\Form\BookingEditType;
|
use App\Form\BookingEditType;
|
||||||
use App\Form\BookingParticipantType;
|
use App\Form\BookingParticipantType;
|
||||||
use App\Form\Model\BookingDto;
|
use App\Form\Model\BookingDto;
|
||||||
|
use App\Form\Model\ParticipantEditDto;
|
||||||
use App\Form\Service\ParticipantFieldHandlerRegistry;
|
use App\Form\Service\ParticipantFieldHandlerRegistry;
|
||||||
use App\Htmx\HxTrait;
|
use App\Htmx\HxTrait;
|
||||||
use App\Security\Crypt;
|
use App\Security\Crypt;
|
||||||
@@ -244,10 +245,15 @@ class IndexController extends AbstractController
|
|||||||
return $this->redirectToRoute('app_booking_edit', ['id' => $id]);
|
return $this->redirectToRoute('app_booking_edit', ['id' => $id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create wrapper DTO for email uniqueness validation
|
||||||
|
$wrapper = new ParticipantEditDto(
|
||||||
|
participant: $participant,
|
||||||
|
bookingContext: $bookingDto,
|
||||||
|
);
|
||||||
|
|
||||||
// Create form for participant with booking context
|
// Create form for participant with booking context
|
||||||
$form = $this->createForm(BookingParticipantType::class, $participant, [
|
$form = $this->createForm(BookingParticipantType::class, $wrapper, [
|
||||||
'booking_context' => $bookingDto,
|
'booking_context' => $bookingDto,
|
||||||
'edit_mode' => true,
|
|
||||||
'validation_groups' => ['booking_edit'],
|
'validation_groups' => ['booking_edit'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -327,10 +333,15 @@ class IndexController extends AbstractController
|
|||||||
? $this->travelDataService->getMutabilityData($bookingData->dateId)
|
? $this->travelDataService->getMutabilityData($bookingData->dateId)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
// Create wrapper DTO for form
|
||||||
|
$wrapper = new ParticipantEditDto(
|
||||||
|
participant: $bookingDto->participants[$index],
|
||||||
|
bookingContext: $bookingDto,
|
||||||
|
);
|
||||||
|
|
||||||
// Create form with validation disabled
|
// Create form with validation disabled
|
||||||
$form = $this->createForm(BookingParticipantType::class, $bookingDto->participants[$index], [
|
$form = $this->createForm(BookingParticipantType::class, $wrapper, [
|
||||||
'booking_context' => $bookingDto,
|
'booking_context' => $bookingDto,
|
||||||
'edit_mode' => true,
|
|
||||||
'validation_groups' => false,
|
'validation_groups' => false,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class BookingParticipantType extends AbstractType
|
|||||||
/** @var BookingDto $bookingContext */
|
/** @var BookingDto $bookingContext */
|
||||||
$bookingContext = $options['booking_context'];
|
$bookingContext = $options['booking_context'];
|
||||||
|
|
||||||
// Select field state provider based on edit_mode option
|
// Select field state provider based on booking mode
|
||||||
$this->fieldStateProvider = BookingDto::MODE_EDIT === $bookingContext->getMode()
|
$this->fieldStateProvider = BookingDto::MODE_EDIT === $bookingContext->getMode()
|
||||||
? $this->editFieldStateProvider
|
? $this->editFieldStateProvider
|
||||||
: $this->createFieldStateProvider;
|
: $this->createFieldStateProvider;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ 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 hidden for authenticated users.
|
||||||
*
|
*
|
||||||
* When a participant is linked to an existing BPN account (has addressId and personId),
|
* When a participant is linked to an existing BPN account (has personId),
|
||||||
* their personal data should not be editable during the booking process. Changes to
|
* 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
|
* master personal data should only happen through the dedicated personal data management
|
||||||
* interface to prevent:
|
* interface to prevent:
|
||||||
@@ -18,6 +18,10 @@ use App\Form\Service\Contract\FieldConditionInterface;
|
|||||||
* - Disconnecting bookings from the user's account
|
* - Disconnecting bookings from the user's account
|
||||||
* - Data inconsistencies between booking and account data
|
* - Data inconsistencies between booking and account data
|
||||||
*
|
*
|
||||||
|
* Note: In edit mode, the BPN API may not return addressId in booking responses,
|
||||||
|
* so we rely on personId alone to identify authenticated users. The personId is
|
||||||
|
* sufficient to link a participant to an existing BPN account.
|
||||||
|
*
|
||||||
* When this condition is satisfied (returns true), the template should:
|
* When this condition is satisfied (returns true), the template should:
|
||||||
* - Hide the form fields for personal data
|
* - Hide the form fields for personal data
|
||||||
* - Display the values as static, read-only text
|
* - Display the values as static, read-only text
|
||||||
@@ -46,9 +50,10 @@ class AuthenticatedUserPersonalDataCondition implements FieldConditionInterface
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide personal data fields if participant has BPN account IDs
|
// Hide personal data fields if participant has BPN person ID
|
||||||
// (indicates prepopulation from authenticated user)
|
// In edit mode, bookings may not include addressId, so we check personId only
|
||||||
return null !== $participant->addressId && null !== $participant->personId;
|
// The personId alone is sufficient to identify a participant linked to a BPN account
|
||||||
|
return null !== $participant->personId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use App\BusProNet\Utility\DirectionMapper;
|
|||||||
use App\Form\Service\Abstract\AbstractFieldStateProvider;
|
use App\Form\Service\Abstract\AbstractFieldStateProvider;
|
||||||
use App\Form\Service\Condition\AdditionalServicesMutabilityCondition;
|
use App\Form\Service\Condition\AdditionalServicesMutabilityCondition;
|
||||||
use App\Form\Service\Condition\ApplicantCondition;
|
use App\Form\Service\Condition\ApplicantCondition;
|
||||||
|
use App\Form\Service\Condition\AuthenticatedUserPersonalDataCondition;
|
||||||
use App\Form\Service\Condition\CompositeCondition;
|
use App\Form\Service\Condition\CompositeCondition;
|
||||||
use App\Form\Service\Condition\DateOfBirthProvidedCondition;
|
use App\Form\Service\Condition\DateOfBirthProvidedCondition;
|
||||||
use App\Form\Service\Condition\FieldValueCondition;
|
use App\Form\Service\Condition\FieldValueCondition;
|
||||||
@@ -43,7 +44,12 @@ class EditFieldStateProvider extends AbstractFieldStateProvider
|
|||||||
$transportationServicesMutabilityCondition = new TransportationServicesMutabilityCondition();
|
$transportationServicesMutabilityCondition = new TransportationServicesMutabilityCondition();
|
||||||
$pickupsMutabilityCondition = new PickupsMutabilityCondition();
|
$pickupsMutabilityCondition = new PickupsMutabilityCondition();
|
||||||
|
|
||||||
|
// Authenticated user personal data protection
|
||||||
|
// Hide personal data fields for participants linked to BPN accounts (prevents duplicate records)
|
||||||
|
$authenticatedUserCondition = new AuthenticatedUserPersonalDataCondition();
|
||||||
|
|
||||||
// Make all personal data fields readonly if participant not mutable
|
// Make all personal data fields readonly if participant not mutable
|
||||||
|
// OR hidden if participant is linked to BPN account (authenticated user)
|
||||||
// Note: First participant is now treated as independent from applicant and can be edited
|
// Note: First participant is now treated as independent from applicant and can be edited
|
||||||
$personalDataFields = [
|
$personalDataFields = [
|
||||||
'firstName',
|
'firstName',
|
||||||
@@ -56,15 +62,38 @@ class EditFieldStateProvider extends AbstractFieldStateProvider
|
|||||||
];
|
];
|
||||||
foreach ($personalDataFields as $field) {
|
foreach ($personalDataFields as $field) {
|
||||||
$this->fieldStateConditions[$field] = [
|
$this->fieldStateConditions[$field] = [
|
||||||
|
'hidden' => $authenticatedUserCondition,
|
||||||
'readonly' => CompositeCondition::not(new MutabilityCondition()),
|
'readonly' => CompositeCondition::not(new MutabilityCondition()),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Address fields - readonly if participant not mutable
|
// Address fields - hidden for authenticated users, readonly if participant not mutable
|
||||||
$this->fieldStateConditions['address'] = [
|
$this->fieldStateConditions['address'] = [
|
||||||
|
'hidden' => $authenticatedUserCondition,
|
||||||
'readonly' => CompositeCondition::not(new MutabilityCondition()),
|
'readonly' => CompositeCondition::not(new MutabilityCondition()),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
];
|
||||||
|
|
||||||
// Conditional visibility for service fields (same as create flow)
|
// Conditional visibility for service fields (same as create flow)
|
||||||
$rentalCondition = new RentalSelectionCondition();
|
$rentalCondition = new RentalSelectionCondition();
|
||||||
$skiPassCondition = new SkiPassSelectionCondition();
|
$skiPassCondition = new SkiPassSelectionCondition();
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
{% import _self as macros %}
|
{% import _self as macros %}
|
||||||
|
|
||||||
|
{# Macro to render a form field or static value #}
|
||||||
|
{% macro field_or_static(form, fieldName, label, staticValue, options = {}) %}
|
||||||
|
{% if form[fieldName] is defined %}
|
||||||
|
{{ form_row(form[fieldName], options) }}
|
||||||
|
{% else %}
|
||||||
|
<div>
|
||||||
|
<label class="font-semibold mb-1 block">{{ label }}</label>
|
||||||
|
<div class="text-sm text-gray-600">{{ staticValue ?: '-' }}</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
{# Macro to render a field or placeholder with consistent fieldset structure #}
|
{# Macro to render a field or placeholder with consistent fieldset structure #}
|
||||||
{% macro service_field(form, fieldName, label, options = {}, undefinedLabel = 'Nicht wählbar') %}
|
{% macro service_field(form, fieldName, label, options = {}, undefinedLabel = 'Nicht wählbar') %}
|
||||||
{% if form[fieldName] is defined %}
|
{% if form[fieldName] is defined %}
|
||||||
@@ -46,88 +58,34 @@
|
|||||||
<div id="participant-form" class="space-y-4">
|
<div id="participant-form" class="space-y-4">
|
||||||
{# Personal data section #}
|
{# Personal data section #}
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
{% if form.firstName is defined %}
|
{{ macros.field_or_static(form, 'firstName', 'Vorname', form.vars.data.participant.firstName) }}
|
||||||
{{ form_row(form.firstName) }}
|
{{ macros.field_or_static(form, 'lastName', 'Nachname', form.vars.data.participant.lastName) }}
|
||||||
{% else %}
|
|
||||||
<div>
|
|
||||||
<label class="font-semibold mb-1 block">Vorname</label>
|
|
||||||
<div class="text-sm text-gray-600">{{ form.vars.data.participant.firstName }}</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if form.lastName is defined %}
|
{{ macros.field_or_static(
|
||||||
{{ form_row(form.lastName) }}
|
form,
|
||||||
{% else %}
|
'dateOfBirth',
|
||||||
<div>
|
'Geburtsdatum',
|
||||||
<label class="font-semibold mb-1 block">Nachname</label>
|
form.vars.data.participant.dateOfBirth ? form.vars.data.participant.dateOfBirth|date('d.m.Y') : null,
|
||||||
<div class="text-sm text-gray-600">{{ form.vars.data.participant.lastName }}</div>
|
{
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if form.dateOfBirth is defined %}
|
|
||||||
{{ form_row(form.dateOfBirth, {
|
|
||||||
'attr': {
|
'attr': {
|
||||||
'hx-trigger': 'change',
|
'hx-trigger': 'change',
|
||||||
'hx-post': path(refreshRouteName, refreshRouteParams|default({index: participantIndex})),
|
'hx-post': path(refreshRouteName, refreshRouteParams|default({index: participantIndex})),
|
||||||
'hx-target': '#main-content',
|
'hx-target': '#main-content',
|
||||||
'hx-swap': 'innerHTML'
|
'hx-swap': 'innerHTML'
|
||||||
}
|
}
|
||||||
}) }}
|
}
|
||||||
{% else %}
|
) }}
|
||||||
<div>
|
|
||||||
<label class="font-semibold mb-1 block">Geburtsdatum</label>
|
|
||||||
<div class="text-sm text-gray-600">{{ form.vars.data.participant.dateOfBirth ? form.vars.data.participant.dateOfBirth|date('d.m.Y') : '-' }}</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if form.gender is defined %}
|
{% set genderLabel = form.vars.data.participant.gender == 'M' ? 'männlich' : (form.vars.data.participant.gender == 'W' ? 'weiblich' : (form.vars.data.participant.gender == 'D' ? 'divers' : null)) %}
|
||||||
{{ form_row(form.gender) }}
|
{{ macros.field_or_static(form, 'gender', 'Geschlecht', genderLabel) }}
|
||||||
{% else %}
|
|
||||||
<div>
|
|
||||||
<label class="font-semibold mb-1 block">Geschlecht</label>
|
|
||||||
<div class="text-sm text-gray-600">
|
|
||||||
{% if form.vars.data.participant.gender == 'M' %}
|
|
||||||
männlich
|
|
||||||
{% elseif form.vars.data.participant.gender == 'W' %}
|
|
||||||
weiblich
|
|
||||||
{% elseif form.vars.data.participant.gender == 'D' %}
|
|
||||||
divers
|
|
||||||
{% else %}
|
|
||||||
-
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if form.nationality is defined %}
|
{{ macros.field_or_static(form, 'nationality', 'Nationalität', form.vars.data.participant.nationality) }}
|
||||||
{{ form_row(form.nationality) }}
|
|
||||||
{% else %}
|
|
||||||
<div>
|
|
||||||
<label class="font-semibold mb-1 block">Nationalität</label>
|
|
||||||
<div class="text-sm text-gray-600">{{ form.vars.data.participant.nationality ?: '-' }}</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Contact information #}
|
{# Contact information #}
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
{% if form.email is defined %}
|
{{ macros.field_or_static(form, 'email', 'E-Mail', form.vars.data.participant.email) }}
|
||||||
{{ form_row(form.email) }}
|
{{ macros.field_or_static(form, 'mobile', 'Telefon (mobil)', form.vars.data.participant.mobile) }}
|
||||||
{% else %}
|
|
||||||
<div>
|
|
||||||
<label class="font-semibold mb-1 block">E-Mail</label>
|
|
||||||
<div class="text-sm text-gray-600">{{ form.vars.data.participant.email }}</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if form.mobile is defined %}
|
|
||||||
{{ form_row(form.mobile) }}
|
|
||||||
{% else %}
|
|
||||||
<div>
|
|
||||||
<label class="font-semibold mb-1 block">Telefon (mobil)</label>
|
|
||||||
<div class="text-sm text-gray-600">{{ form.vars.data.participant.mobile ?: '-' }}</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Address #}
|
{# Address #}
|
||||||
|
|||||||
Reference in New Issue
Block a user