wip: insurance booking
This commit is contained in:
@@ -91,10 +91,18 @@ class ParticipantFieldHandlerRegistry
|
|||||||
/**
|
/**
|
||||||
* Processes all participant fields from submitted form data using registered handlers.
|
* Processes all participant fields from submitted form data using registered handlers.
|
||||||
*
|
*
|
||||||
* This is the main entry point for field processing. It iterates through all
|
* This is the main entry point for field processing. It processes handlers in dependency
|
||||||
* participants in the submitted data and applies the appropriate handlers in
|
* order, applying each handler to ALL participants before moving to the next handler.
|
||||||
* dependency order. Each handler determines whether it should process the
|
* This ensures that cross-participant logic (like family booking detection) has access
|
||||||
* participant's data and updates the booking DTO accordingly.
|
* to complete data from all participants.
|
||||||
|
*
|
||||||
|
* Processing order: handler-first, then participants
|
||||||
|
* - Process handler A for all participants
|
||||||
|
* - Process handler B for all participants
|
||||||
|
* - etc.
|
||||||
|
*
|
||||||
|
* This is critical for handlers that depend on booking-level state (like insurance
|
||||||
|
* family detection which needs all participants' ages to be processed first).
|
||||||
*
|
*
|
||||||
* @param array<string, mixed> $submittedData The submitted form data containing participants array
|
* @param array<string, mixed> $submittedData The submitted form data containing participants array
|
||||||
* @param BookingDtoInterface $bookingDto The booking DTO to update with processed field values (create or edit)
|
* @param BookingDtoInterface $bookingDto The booking DTO to update with processed field values (create or edit)
|
||||||
@@ -109,16 +117,17 @@ class ParticipantFieldHandlerRegistry
|
|||||||
// Get handlers sorted by dependency order (uses cache if available)
|
// Get handlers sorted by dependency order (uses cache if available)
|
||||||
$sortedHandlerNames = $this->getSortedHandlers();
|
$sortedHandlerNames = $this->getSortedHandlers();
|
||||||
|
|
||||||
// Process each participant's data
|
// Process each handler across all participants before moving to the next handler
|
||||||
foreach ($submittedData['participants'] as $participantIndex => $participantData) {
|
// This ensures booking-level state (like family booking detection) is accurate
|
||||||
// Skip invalid participant data
|
foreach ($sortedHandlerNames as $handlerName) {
|
||||||
if (false === is_array($participantData)) {
|
$handler = $this->handlers[$handlerName];
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply each handler in dependency order
|
// Apply this handler to all participants
|
||||||
foreach ($sortedHandlerNames as $handlerName) {
|
foreach ($submittedData['participants'] as $participantIndex => $participantData) {
|
||||||
$handler = $this->handlers[$handlerName];
|
// Skip invalid participant data
|
||||||
|
if (false === is_array($participantData)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Let each handler decide if it should process this participant's data
|
// Let each handler decide if it should process this participant's data
|
||||||
if ($handler->shouldProcess($participantData, (int) $participantIndex)) {
|
if ($handler->shouldProcess($participantData, (int) $participantIndex)) {
|
||||||
|
|||||||
@@ -416,7 +416,6 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
|
|||||||
),
|
),
|
||||||
'choice_label' => fn (?Insurance $insurance) => $this->formatInsuranceLabel($insurance),
|
'choice_label' => fn (?Insurance $insurance) => $this->formatInsuranceLabel($insurance),
|
||||||
'choice_value' => 'id',
|
'choice_value' => 'id',
|
||||||
'help' => 'Wählen Sie eine passende Reiseversicherung für diese Person aus.',
|
|
||||||
'attr' => [
|
'attr' => [
|
||||||
'hx-post' => $this->urlGenerator->generate('app_booking_create_step_2_refresh'),
|
'hx-post' => $this->urlGenerator->generate('app_booking_create_step_2_refresh'),
|
||||||
'hx-swap' => 'none',
|
'hx-swap' => 'none',
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ class ServiceAgeEvaluator
|
|||||||
* constraint requirements. Returns false if the participant doesn't
|
* constraint requirements. Returns false if the participant doesn't
|
||||||
* meet the age requirements or if no birth date is provided.
|
* meet the age requirements or if no birth date is provided.
|
||||||
*
|
*
|
||||||
|
* Age constraints are evaluated at the travel start date to ensure
|
||||||
|
* that services are appropriately filtered based on the participant's
|
||||||
|
* age at the time of travel, not their current age.
|
||||||
|
*
|
||||||
* @param Service $service The service to evaluate
|
* @param Service $service The service to evaluate
|
||||||
* @param BookingDtoInterface $bookingDto The booking containing participant data
|
* @param BookingDtoInterface $bookingDto The booking containing participant data
|
||||||
* @param int $participantIndex The index of the participant to evaluate
|
* @param int $participantIndex The index of the participant to evaluate
|
||||||
@@ -49,26 +53,30 @@ class ServiceAgeEvaluator
|
|||||||
return false; // Cannot evaluate without birth date
|
return false; // Cannot evaluate without birth date
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use travel start date as reference for absolute age calculations
|
||||||
|
$travelStartDate = $bookingDto->travel->dateFrom;
|
||||||
|
|
||||||
return match ($service->ageConstraintType) {
|
return match ($service->ageConstraintType) {
|
||||||
'absolute_age' => $this->evaluateAbsoluteAge($service, $participant->dateOfBirth),
|
'absolute_age' => $this->evaluateAbsoluteAge($service, $participant->dateOfBirth, $travelStartDate),
|
||||||
'birth_year' => $this->evaluateBirthYear($service, $participant->dateOfBirth),
|
'birth_year' => $this->evaluateBirthYear($service, $participant->dateOfBirth),
|
||||||
'mixed' => $this->evaluateAbsoluteAge($service, $participant->dateOfBirth)
|
'mixed' => $this->evaluateAbsoluteAge($service, $participant->dateOfBirth, $travelStartDate)
|
||||||
&& $this->evaluateBirthYear($service, $participant->dateOfBirth),
|
&& $this->evaluateBirthYear($service, $participant->dateOfBirth),
|
||||||
default => true, // No constraints or unknown type
|
default => true, // No constraints or unknown type
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluates absolute age constraints against participant's current age.
|
* Evaluates absolute age constraints against participant's age at travel date.
|
||||||
*
|
*
|
||||||
* @param Service $service The service with age constraints
|
* @param Service $service The service with age constraints
|
||||||
* @param \DateTimeImmutable $dateOfBirth The participant's date of birth
|
* @param \DateTimeImmutable $dateOfBirth The participant's date of birth
|
||||||
|
* @param \DateTimeImmutable|null $referenceDate The reference date for age calculation (travel start date)
|
||||||
*
|
*
|
||||||
* @return bool True if the participant meets the absolute age requirements
|
* @return bool True if the participant meets the absolute age requirements
|
||||||
*/
|
*/
|
||||||
private function evaluateAbsoluteAge(Service $service, \DateTimeImmutable $dateOfBirth): bool
|
private function evaluateAbsoluteAge(Service $service, \DateTimeImmutable $dateOfBirth, ?\DateTimeImmutable $referenceDate = null): bool
|
||||||
{
|
{
|
||||||
$age = $this->calculateAge($dateOfBirth);
|
$age = $this->calculateAge($dateOfBirth, $referenceDate);
|
||||||
|
|
||||||
if (null !== $service->ageFrom && $age < $service->ageFrom) {
|
if (null !== $service->ageFrom && $age < $service->ageFrom) {
|
||||||
return false;
|
return false;
|
||||||
@@ -105,20 +113,21 @@ class ServiceAgeEvaluator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates age in years from a date of birth.
|
* Calculates age in years from a date of birth at a reference date.
|
||||||
*
|
*
|
||||||
* Uses DateTimeImmutable to ensure accurate age calculations accounting
|
* Uses DateTimeImmutable to ensure accurate age calculations accounting
|
||||||
* for leap years and exact birth date anniversaries.
|
* for leap years and exact birth date anniversaries.
|
||||||
*
|
*
|
||||||
* @param \DateTimeImmutable $dateOfBirth The participant's date of birth
|
* @param \DateTimeImmutable $dateOfBirth The participant's date of birth
|
||||||
|
* @param \DateTimeImmutable|null $referenceDate The reference date for age calculation (defaults to today)
|
||||||
*
|
*
|
||||||
* @return int The calculated age in complete years
|
* @return int The calculated age in complete years
|
||||||
*/
|
*/
|
||||||
private function calculateAge(\DateTimeImmutable $dateOfBirth): int
|
private function calculateAge(\DateTimeImmutable $dateOfBirth, ?\DateTimeImmutable $referenceDate = null): int
|
||||||
{
|
{
|
||||||
$today = new \DateTimeImmutable();
|
$referenceDate = $referenceDate ?? new \DateTimeImmutable();
|
||||||
|
|
||||||
return $dateOfBirth->diff($today)->y;
|
return $dateOfBirth->diff($referenceDate)->y;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -95,7 +95,9 @@
|
|||||||
}
|
}
|
||||||
}) }}
|
}) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div></div>{# Empty div to maintain grid layout when not available #}
|
<div>
|
||||||
|
Skipass nicht wählbar
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if participant.courses is defined %}
|
{% if participant.courses is defined %}
|
||||||
{{ form_row(participant.courses, {
|
{{ form_row(participant.courses, {
|
||||||
@@ -106,7 +108,9 @@
|
|||||||
}
|
}
|
||||||
}) }}
|
}) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div></div>{# Empty div to maintain grid layout when not available #}
|
<div>
|
||||||
|
Kurse nicht wählbar
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if participant.additionalServices is defined %}
|
{% if participant.additionalServices is defined %}
|
||||||
{{ form_row(participant.additionalServices, {
|
{{ form_row(participant.additionalServices, {
|
||||||
@@ -117,7 +121,9 @@
|
|||||||
}
|
}
|
||||||
}) }}
|
}) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div></div>{# Empty div to maintain grid layout when not available #}
|
<div>
|
||||||
|
Zusatzleistungen nicht wählbar
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if participant.rentals is defined %}
|
{% if participant.rentals is defined %}
|
||||||
{{ form_row(participant.rentals, {
|
{{ form_row(participant.rentals, {
|
||||||
@@ -128,18 +134,27 @@
|
|||||||
}
|
}
|
||||||
}) }}
|
}) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div></div>{# Empty div to maintain grid layout when not available #}
|
<div>
|
||||||
|
Leihmaterial nicht wählbar
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if participant.rentalInsurance is defined %}
|
{% if participant.rentalInsurance is defined %}
|
||||||
{{ form_row(participant.rentalInsurance, {
|
<fieldset>
|
||||||
'attr': {
|
<legend class="font-semibold mb-1">
|
||||||
'hx-trigger': 'change',
|
Leihmaterial-Versicherung
|
||||||
'hx-post': path('app_booking_create_step_2_refresh'),
|
</legend>
|
||||||
'hx-swap': 'none'
|
{{ form_row(participant.rentalInsurance, {
|
||||||
}
|
'attr': {
|
||||||
}) }}
|
'hx-trigger': 'change',
|
||||||
|
'hx-post': path('app_booking_create_step_2_refresh'),
|
||||||
|
'hx-swap': 'none'
|
||||||
|
}
|
||||||
|
}) }}
|
||||||
|
</fieldset>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div></div>{# Empty div to maintain grid layout when not available #}
|
<div>
|
||||||
|
Leihmat.-Versicherung nicht wählbar
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if participant.board is defined %}
|
{% if participant.board is defined %}
|
||||||
{{ form_row(participant.board, {
|
{{ form_row(participant.board, {
|
||||||
@@ -150,7 +165,9 @@
|
|||||||
}
|
}
|
||||||
}) }}
|
}) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div></div>{# Empty div to maintain grid layout when not available #}
|
<div>
|
||||||
|
Verpflegung nicht wählbar
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if participant.insurance is defined %}
|
{% if participant.insurance is defined %}
|
||||||
{{ form_row(participant.insurance, {
|
{{ form_row(participant.insurance, {
|
||||||
@@ -161,7 +178,9 @@
|
|||||||
}
|
}
|
||||||
}) }}
|
}) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div></div>{# Empty div to maintain grid layout when not available #}
|
<div>
|
||||||
|
Versicherung nicht wählbar
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user