bookingContext->getMode()) { return; } if (false === $this->participant->isApplicant()) { $applicant = $this->bookingContext->getParticipant(0); if (true === $applicant?->bulkInsuranceBooking) { return; } if (null !== $applicant?->insurance && true === $applicant->insurance->familyInsurance) { return; } } if (null === $this->participant->insurance) { $context->buildViolation('Bitte auswählen') ->atPath('participant.insurance') ->addViolation(); } } /** * Reports voucher codes BusPro rejected. * * ParticipantVoucherFieldHandler checks the codes with BusPro while handling the submission and * keeps the outcome on the participant, since the htmx refresh rebuilds the form and drops form * errors. This turns it into a violation so the step cannot be left with a rejected code. */ #[Assert\Callback(groups: ['booking_create', 'booking_edit'])] public function validateVoucherCodes(ExecutionContextInterface $context): void { if (null !== $this->participant->voucherCodeError) { $context->buildViolation($this->participant->voucherCodeError) ->atPath('participant.voucherCode') ->addViolation(); } if (null !== $this->participant->promoVoucherCode && null !== $this->participant->promoVoucherError) { $context->buildViolation($this->participant->promoVoucherError) ->atPath('participant.voucherCode') ->addViolation(); } } /** * Validates that non-applicant adult participants have unique email addresses. * * Exempt from validation: * - Applicant (index 0): Can share email with dependents they're booking for * - Children (under 16): Can share email addresses with adults * * Email comparison is case-insensitive and whitespace is normalized. */ #[Assert\Callback(groups: ['booking_create', 'booking_edit'])] public function validateEmailUniqueness(ExecutionContextInterface $context): void { // Skip for internal agency bookings — staff use shared placeholder emails if ($this->bookingContext->isInternalAgencyBooking()) { return; } // In regular create/edit submission flows, the applicant can share an email with dependents. if (true === $this->participant->isApplicant()) { return; } // Skip validation for children (under 16) if (true === $this->participant->isChild()) { return; } // Skip if email is null or empty (handled by @Email and @NotBlank constraints) if (null === $this->participant->email || u($this->participant->email)->trim()->isEmpty()) { return; } // Normalize current participant's email for comparison $normalizedEmail = u($this->participant->email)->trim()->lower()->toString(); // Check against all adult participants in booking context foreach ($this->bookingContext->participants as $index => $otherParticipant) { // Skip self-comparison (same participant index) if ($index === $this->participant->index) { continue; } // Skip children (they can share email addresses) if (true === $otherParticipant->isChild()) { continue; } // Skip null or empty emails if (null === $otherParticipant->email || u($otherParticipant->email)->trim()->isEmpty()) { continue; } // Compare normalized emails $otherNormalizedEmail = u($otherParticipant->email)->trim()->lower()->toString(); if ($normalizedEmail === $otherNormalizedEmail) { // Add violation to participant.email path $context->buildViolation('Diese E-Mail Adresse wird bereits von einem anderen Teilnehmer verwendet') ->atPath('participant.email') ->addViolation(); // Stop after first duplicate found (no need to report multiple times) return; } } } }