feat: pre-flight check of agency bookings

addresses #869bqxr4q
This commit is contained in:
Björn Fromme
2026-07-10 14:33:09 +02:00
parent f9142e3080
commit b41b19d4c6
26 changed files with 1818 additions and 304 deletions
+109 -51
View File
@@ -28,6 +28,18 @@ class ParticipantCardAssembler
* Get card data for a single participant.
*/
public function getCardData(BookingDto $bookingDto, int $index): ParticipantCardDataDto
{
return $this->buildCardData($bookingDto, $index);
}
/**
* @param array<int, float>|null $precomputedPrices
*/
private function buildCardData(
BookingDto $bookingDto,
int $index,
?array $precomputedPrices = null,
): ParticipantCardDataDto
{
$participant = $bookingDto->participants[$index] ?? null;
@@ -45,7 +57,7 @@ class ParticipantCardAssembler
$roomName = $this->getRoomName($bookingDto, $participant);
// Calculate pricing state for display
$priceData = $this->getPriceData($bookingDto, $index);
$priceData = $this->getPriceData($bookingDto, $index, $precomputedPrices);
// Check if participant is canceled
$isCanceled = $participant->isCanceled();
@@ -67,9 +79,10 @@ class ParticipantCardAssembler
public function getAllCardsData(BookingDto $bookingDto): array
{
$cardsData = [];
$prices = $this->calculateBulkParticipantPrices($bookingDto);
foreach ($bookingDto->participants as $index => $_participant) {
$cardsData[$index] = $this->getCardData($bookingDto, $index);
$cardsData[$index] = $this->buildCardData($bookingDto, $index, $prices);
}
return $cardsData;
@@ -122,8 +135,14 @@ class ParticipantCardAssembler
*
* Returns a dash marker when the price is zero and no room is assigned,
* indicating incomplete configuration rather than a zero-cost booking.
*
* @param array<int, float>|null $precomputedPrices
*/
private function getPriceData(BookingDto $bookingDto, int $index): ParticipantCardPriceDto
private function getPriceData(
BookingDto $bookingDto,
int $index,
?array $precomputedPrices = null,
): ParticipantCardPriceDto
{
// Check if canceled (only possible in edit mode when booking property is set)
$isCanceled = ($bookingDto->booking?->participantsStatus[$index] ?? null) === 'S';
@@ -150,7 +169,7 @@ class ParticipantCardAssembler
}
// For active participants: existing price calculation logic
$prices = $this->priceCalculator->calculateAllParticipantIndividualPrices($bookingDto);
$prices = $precomputedPrices ?? $this->priceCalculator->calculateAllParticipantIndividualPrices($bookingDto);
$price = $prices[$index] ?? 0.0;
@@ -166,8 +185,92 @@ class ParticipantCardAssembler
/**
* Get card data for a single participant with validation state.
*/
public function getCardDataWithValidation(BookingDto $bookingDto, int $index): ParticipantCardDataDto
public function getCardDataWithValidation(
BookingDto $bookingDto,
int $index,
bool $forceStrictRequired = false,
): ParticipantCardDataDto
{
return $this->getCardDataWithValidationInternal(
$bookingDto,
$index,
$this->determineValidationGroups($bookingDto, $forceStrictRequired)
);
}
/**
* Get card data for all participants with validation state.
*
* @return array<int, ParticipantCardDataDto>
*/
public function getAllCardsDataWithValidation(BookingDto $bookingDto, bool $forceStrictRequired = false): array
{
$cardsData = [];
$prices = $this->calculateBulkParticipantPrices($bookingDto);
$validationGroups = $this->determineValidationGroups($bookingDto, $forceStrictRequired);
foreach ($bookingDto->participants as $index => $_participant) {
$cardsData[$index] = $this->getCardDataWithValidationInternal(
$bookingDto,
$index,
$validationGroups,
$prices
);
}
return $cardsData;
}
/**
* @return array<int, float>
*/
private function calculateBulkParticipantPrices(BookingDto $bookingDto): array
{
foreach ($bookingDto->participants as $index => $_participant) {
if (($bookingDto->booking?->participantsStatus[$index] ?? null) !== 'S') {
return $this->priceCalculator->calculateAllParticipantIndividualPrices($bookingDto);
}
}
return [];
}
/**
* Determines validation groups based on booking mode and applicant mutability.
*
* @return array<string> Validation groups to apply
*/
private function determineValidationGroups(BookingDto $bookingDto, bool $forceStrictRequired = false): array
{
$groups = [];
// Add mode-specific group
if (BookingDto::MODE_CREATE === $bookingDto->getMode()) {
$groups[] = 'booking_create';
$groups[] = 'strict_required';
} else {
$groups[] = 'booking_edit';
// Strict validation in edit mode except for internal agency bookings,
// unless the caller explicitly asks for the full validation set.
if (true === $forceStrictRequired || false === $bookingDto->isInternalAgencyBooking()) {
$groups[] = 'strict_required';
}
}
return $groups;
}
/**
* @param array<string> $validationGroups
* @param array<int, float>|null $precomputedPrices
*/
private function getCardDataWithValidationInternal(
BookingDto $bookingDto,
int $index,
array $validationGroups,
?array $precomputedPrices = null,
): ParticipantCardDataDto {
$participant = $bookingDto->participants[$index] ?? null;
if (null === $participant) {
@@ -175,17 +278,13 @@ class ParticipantCardAssembler
}
// Get basic card data
$cardData = $this->getCardData($bookingDto, $index);
$cardData = $this->buildCardData($bookingDto, $index, $precomputedPrices);
// 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);
@@ -199,45 +298,4 @@ class ParticipantCardAssembler
return $cardData->withValidation($isValid, $errorMessages);
}
/**
* Get card data for all participants with validation state.
*
* @return array<int, ParticipantCardDataDto>
*/
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';
// Strict validation in edit mode except for internal agency bookings
if (false === $bookingDto->isInternalAgencyBooking()) {
$groups[] = 'strict_required';
}
}
return $groups;
}
}