Files
myep/src/Form/Service/ParticipantInsuranceFieldHandler.php
T
2025-09-30 14:37:49 +02:00

262 lines
11 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Form\Service;
use App\BusProNet\Model\Insurance;
use App\Form\Model\BookingCreateDto;
use App\Form\Model\BookingDtoInterface;
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
'pickupOutbound', // Affects travel price
'pickupInbound', // 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.
*
* @param array<string, mixed> $submittedData The submitted participant form data
* @param int $participantIndex The index of the participant being processed
*
* @return bool Always returns true for insurance selection fields
*/
public function shouldProcess(array $submittedData, int $participantIndex): bool
{
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 BookingDtoInterface $bookingDto The booking DTO to update (create or edit)
* @param int $participantIndex The index of the participant being processed
*/
public function processField(array $submittedData, BookingDtoInterface $bookingDto, int $participantIndex): void
{
$participant = $bookingDto->getParticipant($participantIndex);
if (null === $participant || !$bookingDto instanceof BookingCreateDto) {
return;
}
$selectedInsuranceId = $this->getFieldValue($submittedData, $this->getFieldName());
$currentInsurance = $participant->insurance;
$availableInsurances = $bookingDto->travel->insurances ?? [];
// 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;
}
} 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
);
$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 BookingDtoInterface $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, BookingDtoInterface $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;
}
}