participants[$index] ?? null; if (null === $participant) { throw new \InvalidArgumentException(sprintf('Participant at index %d does not exist', $index)); } // Extract participant name with fallback $name = $this->getParticipantName($bookingDto, $participant, $index); // Extract email $email = $participant->email ?? ''; // Extract room name $roomName = $this->getRoomName($bookingDto, $participant); // Calculate and format individual price $price = $this->getFormattedPrice($bookingDto, $index); // Check if participant is canceled $isCanceled = $participant->isCanceled(); return [ 'name' => $name, 'email' => $email, 'roomName' => $roomName, 'price' => $price, 'isCanceled' => $isCanceled, ]; } /** * Get card data for all participants. * * @return array */ public function getAllCardsData(BookingDto $bookingDto): array { $cardsData = []; foreach ($bookingDto->participants as $index => $participant) { $cardsData[$index] = $this->getCardData($bookingDto, $index); } return $cardsData; } /** * Get participant name with fallback to generic label. */ private function getParticipantName(BookingDto $bookingDto, object $participant, int $index): string { $firstName = $participant->firstName ?? ''; $lastName = $participant->lastName ?? ''; $name = trim($firstName.' '.$lastName); if ('' === $name) { // For internal agency bookings, first participant is not the applicant if (0 === $index && false === $bookingDto->isInternalAgencyBooking()) { return 'Anmelder:in'; } return 'Teilnehmer:in'; } return $name; } /** * Get room name from travel model. */ private function getRoomName(BookingDto $bookingDto, object $participant): string { $roomId = $participant->assignedRoomId ?? null; if (null === $roomId) { return 'Kein Zimmer zugewiesen'; } $room = $bookingDto->travel->getRoomById($roomId); if (null === $room) { return 'Unbekanntes Zimmer'; } return $room->label ?? 'Unbekanntes Zimmer'; } /** * Calculate and format individual participant price. * * Returns a dash (-) when the price is zero and no room is assigned, * indicating incomplete configuration rather than a zero-cost booking. */ private function getFormattedPrice(BookingDto $bookingDto, int $index): string { // Check if canceled (only possible in edit mode when booking property is set) $isCanceled = ($bookingDto->booking?->participantsStatus[$index] ?? null) === 'S'; if (true === $isCanceled) { // Calculate surcharge total for canceled participant if (null === $bookingDto->booking) { return '-'; } $surcharges = $bookingDto->booking->getSurchargesForParticipant($index); $surchargeTotal = 0.0; foreach ($surcharges as $surcharge) { $surchargeTotal += $surcharge->individualPrice[$index] ?? 0.0; } // Show nothing if no surcharges, otherwise show "x,xx € Stornokosten" if (0.0 === $surchargeTotal) { return '-'; } return number_format($surchargeTotal, 2, ',', '.').' € Stornokosten'; } // For active participants: existing price calculation logic $prices = $this->priceCalculator->calculateAllParticipantIndividualPrices($bookingDto); $price = $prices[$index] ?? 0.0; // Display dash when price is zero and no room assigned (incomplete configuration) $participant = $bookingDto->participants[$index] ?? null; if (0.0 === $price && (null === $participant || null === $participant->assignedRoomId)) { return '-'; } return number_format($price, 2, ',', '.').' €'; } /** * Get card data for a single participant with validation state. * * @return array{name: string, email: string, roomName: string, price: string, isCanceled: bool, isValid: bool, errorMessages: array} */ public function getCardDataWithValidation(BookingDto $bookingDto, int $index): array { $participant = $bookingDto->participants[$index] ?? null; if (null === $participant) { throw new \InvalidArgumentException(sprintf('Participant at index %d does not exist', $index)); } // Get basic card data $cardData = $this->getCardData($bookingDto, $index); // Wrap participant for validation $wrapper = new ParticipantEditDto( participant: $participant, bookingContext: $bookingDto, ); // Determine validation groups based on mode and mutability $validationGroups = $this->determineValidationGroups($bookingDto); // Validate the wrapper DTO $violations = $this->validator->validate($wrapper, null, $validationGroups); // Extract validation state $isValid = 0 === count($violations); $errorMessages = []; foreach ($violations as $violation) { $errorMessages[] = $violation->getMessage(); } return array_merge($cardData, [ 'isValid' => $isValid, 'errorMessages' => $errorMessages, ]); } /** * Get card data for all participants with validation state. * * @return array}> */ public function getAllCardsDataWithValidation(BookingDto $bookingDto): array { $cardsData = []; foreach ($bookingDto->participants as $index => $participant) { $cardsData[$index] = $this->getCardDataWithValidation($bookingDto, $index); } return $cardsData; } /** * Determines validation groups based on booking mode and applicant mutability. * * @return array Validation groups to apply */ private function determineValidationGroups(BookingDto $bookingDto): array { $groups = []; // Add mode-specific group if (BookingDto::MODE_CREATE === $bookingDto->getMode()) { $groups[] = 'booking_create'; $groups[] = 'strict_required'; } else { $groups[] = 'booking_edit'; // In edit mode, add strict_required only if applicant is immutable if (false === ($bookingDto->participants[0]?->mutable ?? true)) { $groups[] = 'strict_required'; } } return $groups; } }