319 lines
13 KiB
PHP
319 lines
13 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Form\Service;
|
|
|
|
use App\BusProNet\Model\Insurance;
|
|
use App\Form\Model\BookingDto;
|
|
use App\Form\Service\Abstract\AbstractParticipantFieldHandler;
|
|
use App\Service\InsuranceMatchingService;
|
|
|
|
/**
|
|
* Handles processing of the insurance field for booking participants.
|
|
*
|
|
* This handler manages insurance selections for individual participants in the booking
|
|
* creation process. It processes insurance field from form submissions, validates
|
|
* selections against participant eligibility criteria, and provides automatic
|
|
* reassignment when participant pricing changes.
|
|
*
|
|
* Key Features:
|
|
* - Validates submitted insurance selections against eligibility criteria
|
|
* - Automatically reassigns insurances when travel price changes make current selection invalid
|
|
* - Preserves user intent by maintaining same insurance type (subType + familyInsurance)
|
|
* - Uses InsuranceMatchingService for eligibility filtering and reassignment logic
|
|
*
|
|
* Auto-Reassignment Logic:
|
|
* When a participant's individual travel price changes (e.g., by adding services),
|
|
* their current insurance may no longer be eligible for the new price range.
|
|
* Instead of clearing the selection, this handler automatically reassigns
|
|
* to the same insurance type with the appropriate price tier.
|
|
*
|
|
* Dependencies: dateOfBirth (for age evaluation and insurance eligibility)
|
|
*/
|
|
class ParticipantInsuranceFieldHandler extends AbstractParticipantFieldHandler
|
|
{
|
|
public function __construct(
|
|
private readonly InsuranceMatchingService $insuranceMatchingService,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Returns the form field name this handler processes.
|
|
*
|
|
* @return string The field name 'insurance'
|
|
*/
|
|
public function getFieldName(): string
|
|
{
|
|
return 'insurance';
|
|
}
|
|
|
|
/**
|
|
* Returns the field dependencies for proper processing order.
|
|
*
|
|
* This handler must run AFTER all fields that affect travel price, since insurance
|
|
* reassignment logic depends on accurate price calculations. It also depends on
|
|
* dateOfBirth for age evaluation and insurance eligibility.
|
|
*
|
|
* @return string[] Array of field dependencies
|
|
*/
|
|
public function getDependencies(): array
|
|
{
|
|
return [
|
|
'dateOfBirth', // Required for age-based eligibility
|
|
'skiPass', // Affects travel price
|
|
'rentals', // Affects travel price
|
|
'courses', // Affects travel price
|
|
'additionalServices', // Affects travel price
|
|
'board', // Affects travel price
|
|
'transportationOutbound', // Affects travel price
|
|
'transportationInbound', // Affects travel price
|
|
'pickup', // Affects travel price
|
|
'parking', // Affects travel price
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Determines if this handler should process the field based on submitted data.
|
|
*
|
|
* For insurance selection fields, we always need to process to handle cases
|
|
* where the selection is cleared (field not present in data). This ensures
|
|
* the participant DTO is updated with null when no insurance is selected.
|
|
*
|
|
* However, when bulk insurance booking is active for a dependent participant,
|
|
* we skip processing to prevent overwriting the insurance assigned by the
|
|
* bulk insurance handler.
|
|
*
|
|
* @param array<string, mixed> $submittedData The submitted participant form data
|
|
* @param int $participantIndex The index of the participant being processed
|
|
*
|
|
* @return bool True if processing should occur, false if bulk insurance handles it
|
|
*/
|
|
public function shouldProcess(array $submittedData, int $participantIndex): bool
|
|
{
|
|
// Skip processing for dependent participants when bulk insurance booking is active
|
|
if ($participantIndex > 0 && $this->isBulkInsuranceBookingActive($submittedData)) {
|
|
return false;
|
|
}
|
|
|
|
return true; // Always process to handle deselection cases
|
|
}
|
|
|
|
/**
|
|
* Processes the insurance field for a specific participant.
|
|
*
|
|
* This method handles both explicit insurance selection from form data and automatic
|
|
* reassignment when the participant's travel price changes. It distinguishes between:
|
|
* 1. New selection: User explicitly changed their insurance choice
|
|
* 2. Resubmission: Form resubmitted with existing insurance (e.g., after adding rentals)
|
|
*
|
|
* For resubmissions, it checks if the current insurance is still eligible with updated
|
|
* participant data and automatically reassigns to the correct price tier if needed.
|
|
*
|
|
* @param array<string, mixed> $submittedData The submitted participant form data
|
|
* @param BookingDto $bookingDto The booking DTO to update (create or edit)
|
|
* @param int $participantIndex The index of the participant being processed
|
|
*/
|
|
public function processField(array $submittedData, BookingDto $bookingDto, int $participantIndex): void
|
|
{
|
|
$participant = $bookingDto->getParticipant($participantIndex);
|
|
if (null === $participant) {
|
|
return;
|
|
}
|
|
|
|
$selectedInsuranceId = $this->getFieldValue($submittedData, $this->getFieldName());
|
|
$currentInsurance = $participant->insurance;
|
|
$availableInsurances = $bookingDto->travel->insurances ?? [];
|
|
|
|
// Exclude complementary insurances from reassignment logic
|
|
// They are only available as part of packages and cannot be directly selected
|
|
$availableInsurances = array_filter($availableInsurances, fn ($insurance) => !$insurance->complementary);
|
|
|
|
// Determine if this is a new user selection or just form resubmission
|
|
$isNewSelection = null !== $selectedInsuranceId
|
|
&& (null === $currentInsurance || !$this->isSameInsurance($selectedInsuranceId, $currentInsurance));
|
|
|
|
// Handle explicit new insurance selection from user
|
|
if ($isNewSelection) {
|
|
$selectedInsurance = $this->findInsuranceById($availableInsurances, $selectedInsuranceId);
|
|
|
|
if (null !== $selectedInsurance) {
|
|
$eligibleInsurances = $this->insuranceMatchingService->getEligibleInsurances($availableInsurances, $participant, $bookingDto);
|
|
$isSelectedInsuranceEligible = $this->isInsuranceInList($selectedInsurance, $eligibleInsurances);
|
|
|
|
if ($isSelectedInsuranceEligible) {
|
|
// New selection is eligible - use it
|
|
$participant->insurance = $selectedInsurance;
|
|
} else {
|
|
// New selection is not eligible - try to find alternative in same type
|
|
$reassignedInsurance = $this->insuranceMatchingService->reassignInsuranceForPriceChange(
|
|
$availableInsurances,
|
|
$selectedInsurance,
|
|
$participant,
|
|
$bookingDto
|
|
);
|
|
$participant->insurance = $reassignedInsurance;
|
|
|
|
if (null !== $reassignedInsurance) {
|
|
$participant->addNotification(
|
|
'info',
|
|
sprintf('Versicherung automatisch angepasst: %s', $reassignedInsurance->label)
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
// Insurance not found - clear selection
|
|
$participant->insurance = null;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Handle form resubmission with existing insurance (automatic reassignment check)
|
|
if (null !== $currentInsurance && null !== $selectedInsuranceId) {
|
|
// Check if current insurance is still eligible with updated participant data
|
|
$eligibleInsurances = $this->insuranceMatchingService->getEligibleInsurances($availableInsurances, $participant, $bookingDto);
|
|
$isCurrentInsuranceStillEligible = $this->isInsuranceInList($currentInsurance, $eligibleInsurances);
|
|
|
|
if (!$isCurrentInsuranceStillEligible) {
|
|
// Current insurance no longer eligible - try to reassign to same type with new price tier
|
|
$reassignedInsurance = $this->insuranceMatchingService->reassignInsuranceForPriceChange(
|
|
$availableInsurances,
|
|
$currentInsurance,
|
|
$participant,
|
|
$bookingDto
|
|
);
|
|
|
|
if (null !== $reassignedInsurance && $reassignedInsurance->id !== $currentInsurance->id) {
|
|
$participant->addNotification(
|
|
'info',
|
|
sprintf('Versicherung automatisch angepasst an neuen Preis: %s', $reassignedInsurance->label)
|
|
);
|
|
}
|
|
|
|
$participant->insurance = $reassignedInsurance; // null if no suitable match found
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Handle explicit deselection (user removed insurance)
|
|
if (null === $selectedInsuranceId) {
|
|
$participant->insurance = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns field state modifications that should be applied after processing.
|
|
*
|
|
* Currently no field state modifications are needed for insurance selection.
|
|
*
|
|
* @param array<string, mixed> $submittedData The submitted participant form data
|
|
* @param BookingDto $bookingDto The booking DTO (potentially modified by processing)
|
|
* @param int $participantIndex The participant index being processed
|
|
*
|
|
* @return array<string, array<string, mixed>> Empty array - no field state modifications
|
|
*/
|
|
public function getFieldStateModifications(array $submittedData, BookingDto $bookingDto, int $participantIndex): array
|
|
{
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* Returns field names whose state is affected by this handler's processing.
|
|
*
|
|
* Currently no other fields are affected by insurance selection.
|
|
*
|
|
* @return string[] Empty array - no other fields are affected
|
|
*/
|
|
public function getAffectedFieldNames(): array
|
|
{
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* Finds an insurance by ID from the available insurances array.
|
|
*
|
|
* @param array<Insurance> $insurances Array of available insurances
|
|
* @param string|int $insuranceId The insurance ID to find
|
|
*
|
|
* @return Insurance|null The found insurance or null if not found
|
|
*/
|
|
private function findInsuranceById(array $insurances, string|int $insuranceId): ?Insurance
|
|
{
|
|
foreach ($insurances as $insurance) {
|
|
if ($insurance->id === $insuranceId || (string) $insurance->id === (string) $insuranceId) {
|
|
return $insurance;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Checks if a specific insurance exists in a list of insurances.
|
|
*
|
|
* @param Insurance $targetInsurance The insurance to find
|
|
* @param array<Insurance> $insuranceList The list to search in
|
|
*
|
|
* @return bool True if the insurance is found in the list
|
|
*/
|
|
private function isInsuranceInList(Insurance $targetInsurance, array $insuranceList): bool
|
|
{
|
|
foreach ($insuranceList as $insurance) {
|
|
if ($insurance->id === $targetInsurance->id || (string) $insurance->id === (string) $targetInsurance->id) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if the submitted insurance ID matches the current insurance.
|
|
*
|
|
* This is used to distinguish between a new user selection and a form resubmission
|
|
* with the existing insurance selection (e.g., when user adds rentals that change travel price).
|
|
*
|
|
* @param string|int $selectedInsuranceId The insurance ID from form submission
|
|
* @param Insurance $currentInsurance The currently assigned insurance from DTO
|
|
*
|
|
* @return bool True if they represent the same insurance
|
|
*/
|
|
private function isSameInsurance(string|int $selectedInsuranceId, Insurance $currentInsurance): bool
|
|
{
|
|
return (string) $currentInsurance->id === (string) $selectedInsuranceId;
|
|
}
|
|
|
|
/**
|
|
* Checks if bulk insurance booking is active for dependent participants.
|
|
*
|
|
* Bulk insurance is active when the applicant has enabled the bulkInsuranceBooking
|
|
* flag and has selected an insurance.
|
|
*
|
|
* @param array<string, mixed> $submittedData The submitted form data
|
|
*
|
|
* @return bool True if bulk insurance booking is active
|
|
*/
|
|
private function isBulkInsuranceBookingActive(array $submittedData): bool
|
|
{
|
|
// Check if applicant data exists
|
|
if (!isset($submittedData['participants'][0])) {
|
|
return false;
|
|
}
|
|
|
|
$applicantData = $submittedData['participants'][0];
|
|
|
|
// Check if bulk insurance booking is enabled
|
|
$bulkInsuranceBooking = $applicantData['bulkInsuranceBooking'] ?? false;
|
|
if (false === (bool) $bulkInsuranceBooking) {
|
|
return false;
|
|
}
|
|
|
|
// Check if applicant has selected an insurance
|
|
$applicantInsurance = $applicantData['insurance'] ?? null;
|
|
|
|
return null !== $applicantInsurance;
|
|
}
|
|
}
|