For booking that are agency initiated (groups) different rules apply concerning mutability and requirement of personal data and whether the first participant is the applicant or not. This commit adds logic to evaluate the agency id assigned to the booking data to decide.
249 lines
7.8 KiB
PHP
249 lines
7.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Service;
|
|
|
|
use App\Form\Model\BookingDto;
|
|
use App\Form\Model\ParticipantEditDto;
|
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
|
|
|
/**
|
|
* Extracts card display data for participants in card-based booking flows.
|
|
*
|
|
* Provides participant name, room assignment, and individual pricing for
|
|
* display in the card overview UI.
|
|
*/
|
|
class ParticipantCardDataService
|
|
{
|
|
public function __construct(
|
|
private readonly BookingPriceCalculatorService $priceCalculator,
|
|
private readonly ValidatorInterface $validator,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Get card data for a single participant.
|
|
*
|
|
* @return array{name: string, email: string, roomName: string, price: string, isCanceled: bool}
|
|
*/
|
|
public function getCardData(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));
|
|
}
|
|
|
|
// 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<int, array{name: string, email: string, roomName: string, price: string, isCanceled: bool}>
|
|
*/
|
|
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<string>}
|
|
*/
|
|
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<int, array{name: string, email: string, roomName: string, price: string, isCanceled: bool, isValid: bool, errorMessages: array<string>}>
|
|
*/
|
|
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<string> 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;
|
|
}
|
|
}
|