feat: extract booking participant count service

This commit is contained in:
Björn Fromme
2026-04-11 18:28:32 +02:00
parent f31ac660d2
commit dbc0b4acee
5 changed files with 182 additions and 59 deletions
@@ -9,6 +9,7 @@ use App\Controller\Booking\Traits\BookingExceptionHandlerTrait;
use App\Form\BookingCreateStep2Type;
use App\Form\Model\BookingDto;
use App\Service\BookingService;
use App\Service\BookingParticipantCountService;
use App\Service\BookingSessionService;
use App\Service\BookingSummaryDataService;
use App\Service\ParticipantCardDataService;
@@ -33,6 +34,7 @@ class Step2Controller extends AbstractController
public function __construct(
private readonly BookingService $bookingService,
private readonly BookingParticipantCountService $participantCountService,
private readonly BookingSessionService $bookingSessionService,
private readonly BookingSummaryDataService $summaryDataService,
private readonly TravelDataService $travelDataService,
@@ -64,7 +66,7 @@ class Step2Controller extends AbstractController
$this->travelDataService->enrichWithFreshAvailabilities($bookingCreateDto->travel);
// Ensure correct number of participants with prepopulation callback
$this->bookingService->ensureCorrectNumberOfParticipants(
$this->participantCountService->ensureCorrectNumberOfParticipants(
$bookingCreateDto,
$this->getUser(),
fn ($user, $participant) => $this->prepopulationService->prepopulateApplicantFromUser($user, $participant)
@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace App\Service;
use App\BusProNet\Model\Travel;
use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* Keeps participant-count shaping separate from booking orchestration.
*/
class BookingParticipantCountService
{
/**
* Ensures the booking DTO has the expected number of participant objects.
*
* @param callable|null $prepopulateCallback fn(UserInterface, ParticipantDto): ParticipantDto
*/
public function ensureCorrectNumberOfParticipants(
BookingDto $bookingDto,
?UserInterface $user = null,
?callable $prepopulateCallback = null,
): void {
$participantsCount = $this->calculateParticipantsCount($bookingDto->roomSelections, $bookingDto->travel);
$existingParticipants = $bookingDto->participants;
$bookingDto->participants = [];
for ($i = 0; $i < $participantsCount; ++$i) {
$participant = $existingParticipants[$i] ?? new ParticipantDto();
$participant->index = $i;
// Prepopulate applicant from authenticated user (index 0 only)
if (0 === $i && null !== $user && null !== $prepopulateCallback && $this->shouldPrepopulate($participant)) {
$participant = $prepopulateCallback($user, $participant);
}
$bookingDto->participants[$i] = $participant;
}
}
/**
* Calculates the total number of participants based on room selections.
*
* Multiplies each room's minimum occupancy (minPax) by the selected quantity
* to determine the total number of participants required for the booking.
*
* @param array $roomSelections Array of room selection DTOs
* @param Travel $travelData Travel data containing room information
*/
private function calculateParticipantsCount(array $roomSelections, Travel $travelData): int
{
$participantsCount = 0;
$rooms = $travelData->getAvailableRooms();
foreach ($roomSelections as $roomSelection) {
$room = $rooms[$roomSelection->id];
$participantsCount += $room->minPax * $roomSelection->quantity;
}
return $participantsCount;
}
/**
* Only prepopulate if the participant is fresh.
*/
private function shouldPrepopulate(ParticipantDto $participant): bool
{
return null === $participant->firstName || '' === $participant->firstName;
}
}
-56
View File
@@ -660,60 +660,4 @@ class BookingService
* @param \Symfony\Component\Security\Core\User\UserInterface|null $user Optional authenticated user for prepopulation
* @param callable|null $prepopulateCallback Callback to prepopulate applicant: fn(UserInterface, ParticipantDto): ParticipantDto
*/
public function ensureCorrectNumberOfParticipants(
BookingDto $bookingDto,
?\Symfony\Component\Security\Core\User\UserInterface $user = null,
?callable $prepopulateCallback = null,
): void {
$participantsCount = $this->calculateParticipantsCount($bookingDto->roomSelections, $bookingDto->travel);
$existingParticipants = $bookingDto->participants;
$bookingDto->participants = [];
for ($i = 0; $i < $participantsCount; ++$i) {
$participant = $existingParticipants[$i] ?? new ParticipantDto();
$participant->index = $i;
// Prepopulate applicant from authenticated user (index 0 only)
if (0 === $i && null !== $user && null !== $prepopulateCallback && $this->shouldPrepopulate($participant)) {
$participant = $prepopulateCallback($user, $participant);
}
$bookingDto->participants[$i] = $participant;
}
}
/**
* Determines if a participant should be prepopulated.
*
* Only prepopulates if the participant is "fresh" (no name set yet).
*/
private function shouldPrepopulate(ParticipantDto $participant): bool
{
return null === $participant->firstName || '' === $participant->firstName;
}
/**
* Calculates the total number of participants based on room selections.
*
* Multiplies each room's minimum occupancy (minPax) by the selected quantity
* to determine the total number of participants required for the booking.
*
* @param array $roomSelections Array of RoomSelectionDto objects
* @param Travel $travelData Travel data containing room information
*
* @return int Total number of participants required
*/
private function calculateParticipantsCount(array $roomSelections, Travel $travelData): int
{
$participantsCount = 0;
$rooms = $travelData->getAvailableRooms();
foreach ($roomSelections as $roomSelection) {
$room = $rooms[$roomSelection->id];
$participantsCount += $room->minPax * $roomSelection->quantity;
}
return $participantsCount;
}
}