$submittedData The submitted participant form data * @param string $mode The booking mode (BookingDto::MODE_CREATE or MODE_EDIT) * @param int $participantIndex The index of the participant being processed * * @return bool True if processing should occur, false otherwise */ public function shouldProcess(array $submittedData, string $mode, int $participantIndex): bool { // In edit mode, insurance data is not available from API - skip processing entirely if (BookingDto::MODE_EDIT === $mode) { return false; } // Skip processing for dependent participants when bulk insurance booking is active if ($participantIndex > 0 && $this->isBulkInsuranceBookingActive($submittedData)) { return false; } return true; // Always process in create mode 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 $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; // Get selectable (non-complementary) insurances with caching $selectableInsurances = $this->insuranceService->getSelectableInsurances($bookingDto->travel); // Calculate travel price for eligibility checks $travelPrice = $this->priceCalculatorService->calculateIndividualParticipantPriceExcludingInsurance($bookingDto, $participantIndex); // 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) { // Handle synthetic "no insurance" option if (Insurance::NO_INSURANCE_ID === $selectedInsuranceId) { // Create synthetic insurance object to satisfy validation // This will be excluded from BPN XML transmission $noInsurance = new Insurance(); $noInsurance->id = Insurance::NO_INSURANCE_ID; $noInsurance->label = 'keine Versicherung gewünscht'; $noInsurance->price = 0.0; $participant->insurance = $noInsurance; return; // Skip all other processing for "no insurance" } $selectedInsurance = $this->findInsuranceById($selectableInsurances, $selectedInsuranceId); if (null !== $selectedInsurance) { $eligibleInsurances = $this->insuranceService->getEligibleInsurances($selectableInsurances, $participant, $bookingDto, $travelPrice); $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->insuranceService->reassignInsuranceForPriceChange( $selectableInsurances, $selectedInsurance, $participant, $bookingDto, $travelPrice ); $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; } } // Handle form resubmission with existing insurance (automatic reassignment check) if (null !== $currentInsurance && null !== $selectedInsuranceId) { // Preserve "no insurance" selection during resubmission if ($currentInsurance->isNoInsurance()) { return; } // Check if current insurance is still eligible with updated participant data $eligibleInsurances = $this->insuranceService->getEligibleInsurances($selectableInsurances, $participant, $bookingDto, $travelPrice); $isCurrentInsuranceStillEligible = $this->isInsuranceInList($currentInsurance, $eligibleInsurances); if (!$isCurrentInsuranceStillEligible) { // Current insurance no longer eligible - try to reassign to same type with new price tier $reassignedInsurance = $this->insuranceService->reassignInsuranceForPriceChange( $selectableInsurances, $currentInsurance, $participant, $bookingDto, $travelPrice ); 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 $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> 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 $insurances Array of available insurances * @param string $insuranceId The insurance ID to find * * @return Insurance|null The found insurance or null if not found */ private function findInsuranceById(array $insurances, string $insuranceId): ?Insurance { foreach ($insurances as $insurance) { if ($insurance->id === $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 $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) { 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 $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 $selectedInsuranceId, Insurance $currentInsurance): bool { return $currentInsurance->id === $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 $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 === $this->normalizeCheckboxValue($bulkInsuranceBooking)) { return false; } // Check if applicant has selected an insurance $applicantInsurance = $applicantData['insurance'] ?? null; return null !== $applicantInsurance; } }