feat: extract room grouping from booking service

This commit is contained in:
Björn Fromme
2026-04-11 18:28:32 +02:00
parent 8b72f8c277
commit f31ac660d2
8 changed files with 120 additions and 136 deletions
@@ -10,6 +10,7 @@ use App\Form\BookingCreateStep1Type;
use App\Form\Model\BookingDto;
use App\Htmx\HxTrait;
use App\Service\BookingService;
use App\Service\BookingRoomSelectionService;
use App\Service\BookingSessionService;
use App\Service\BookingSummaryDataService;
use App\Service\RoomPricingCalculator;
@@ -32,6 +33,7 @@ class Step1Controller extends AbstractController
public function __construct(
private readonly BookingService $bookingService,
private readonly BookingRoomSelectionService $roomSelectionService,
private readonly BookingSessionService $bookingSessionService,
private readonly BookingSummaryDataService $summaryDataService,
) {
@@ -84,7 +86,7 @@ class Step1Controller extends AbstractController
$summaryData = $this->summaryDataService->getSummaryData($bookingCreateDto, RoomPricingCalculator::PRICING_MODE_SELECTION);
$availableRooms = $bookingCreateDto->travel->getAvailableRooms();
$groupedRooms = $this->bookingService->groupRoomsBySelectionType($availableRooms);
$groupedRooms = $this->roomSelectionService->groupRoomsBySelectionType($availableRooms);
return $this->render('booking/create/step_1.html.twig', [
'bookingCreateDto' => $bookingCreateDto,
@@ -116,7 +118,7 @@ class Step1Controller extends AbstractController
// Get complete summary data (pricing, rooms, CMS data)
$summaryData = $this->summaryDataService->getSummaryData($bookingCreateDto, RoomPricingCalculator::PRICING_MODE_SELECTION);
$availableRooms = $bookingCreateDto->travel->getAvailableRooms();
$groupedRooms = $this->bookingService->groupRoomsBySelectionType($availableRooms);
$groupedRooms = $this->roomSelectionService->groupRoomsBySelectionType($availableRooms);
// The DTO is now updated with the latest selection.
// We can now render the blocks with the fresh data.
@@ -48,16 +48,6 @@ trait BookingCreateTrait
return $this->redirectToRoute($route);
}
/**
* Returns the total number of participants based on room selections.
*/
private function getParticipantsCount(BookingDto $bookingCreateDto): int
{
return $this
->bookingService
->getParticipantsCount($bookingCreateDto->roomSelections, $bookingCreateDto->travel);
}
/**
* Prepares template variables for the booking summary sidebar.
*
@@ -65,17 +55,13 @@ trait BookingCreateTrait
*/
private function getSummaryVariables(BookingDto $bookingCreateDto): array
{
$participantsCount = $this->getParticipantsCount($bookingCreateDto);
$summary = $this->bookingService->getRoomSummaryAndParticipantCount($bookingCreateDto);
$roomAssignmentCounts = $bookingCreateDto->getRoomAssignmentCounts();
$availableRooms = $bookingCreateDto->travel->getAvailableRooms();
$groupedSelectedRooms = $this->bookingService->groupRoomSelectionsByType($bookingCreateDto->getSelectedRooms(), $availableRooms);
$summary = $this->summaryDataService->getSummaryData($bookingCreateDto);
return [
'participantsCount' => $participantsCount,
'pricingData' => $summary['pricing'],
'assignmentCounts' => $roomAssignmentCounts,
'groupedSelectedRooms' => $groupedSelectedRooms,
'participantsCount' => $summary->participantCount,
'pricingData' => $summary->pricingData,
'assignmentCounts' => $summary->assignmentCounts,
'groupedSelectedRooms' => $summary->groupedSelectedRooms,
];
}
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace App\Service;
use App\BusProNet\Model\Room;
/**
* Groups room data for booking steps.
*/
class BookingRoomSelectionService
{
/**
* @param array<int, Room> $rooms
*
* @return array{by_pax: array<int, Room>, by_room: array<int, Room>}
*/
public function groupRoomsBySelectionType(array $rooms): array
{
$groups = [
Room::SELECTION_TYPE_BY_PAX => [],
Room::SELECTION_TYPE_BY_ROOM => [],
];
foreach ($rooms as $room) {
if (false !== stripos($room->label, 'bett')) {
$groups[Room::SELECTION_TYPE_BY_PAX][$room->id] = $room;
continue;
}
$groups[Room::SELECTION_TYPE_BY_ROOM][$room->id] = $room;
}
uasort($groups[Room::SELECTION_TYPE_BY_PAX], static fn (Room $a, Room $b): int => $a->maxPax <=> $b->maxPax);
uasort($groups[Room::SELECTION_TYPE_BY_ROOM], static fn (Room $a, Room $b): int => $a->maxPax <=> $b->maxPax);
return $groups;
}
}
+25 -104
View File
@@ -24,7 +24,6 @@ class BookingService
public function __construct(
private readonly BookingSessionService $bookingSessionService,
private readonly TravelDataService $travelDataService,
private readonly BookingPriceCalculatorService $priceCalculator,
private readonly ParticipantEligibilityService $participantEligibilityService,
private readonly BookingStatusRuleRegistry $bookingStatusRuleRegistry,
private readonly AgencyLoader $agencyLoader,
@@ -115,108 +114,6 @@ class BookingService
return $selection;
}
/**
* 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
*/
public function getParticipantsCount(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;
}
/**
* Returns a summary of selected rooms, participant count, and pricing information for a booking.
*
* @return array{selectedRooms: array, participantCount: int, pricing: array}
*/
public function getRoomSummaryAndParticipantCount(BookingDto $bookingDto): array
{
$selectedRooms = $bookingDto->getSelectedRooms();
// In edit mode, count actual participants; in create mode, calculate from room selections
if (BookingDto::MODE_EDIT === $bookingDto->getMode()) {
$participantCount = count($bookingDto->getParticipants());
} else {
$participantCount = $this->getParticipantsCount($selectedRooms, $bookingDto->travel);
}
$pricing = $this->priceCalculator->getPricingBreakdown($bookingDto);
return [
'selectedRooms' => $selectedRooms,
'participantCount' => $participantCount,
'pricing' => $pricing,
];
}
/**
* Groups available rooms by selection type ('by_pax' or 'by_room') and sorts them by maxPax.
*
* @param array<int, Room> $rooms Rooms indexed by room ID
*
* @return array{by_pax: array<int, Room>, by_room: array<int, Room>} Rooms grouped and sorted by maxPax (ascending)
*/
public function groupRoomsBySelectionType(array $rooms): array
{
$groups = [
Room::SELECTION_TYPE_BY_PAX => [],
Room::SELECTION_TYPE_BY_ROOM => [],
];
foreach ($rooms as $room) {
if (false !== stripos($room->label, 'bett')) {
$groups[Room::SELECTION_TYPE_BY_PAX][$room->id] = $room;
} else {
$groups[Room::SELECTION_TYPE_BY_ROOM][$room->id] = $room;
}
}
// Sort each group by maxPax (ascending order - smallest capacity first)
uasort($groups[Room::SELECTION_TYPE_BY_PAX], fn (Room $a, Room $b): int => $a->maxPax <=> $b->maxPax);
uasort($groups[Room::SELECTION_TYPE_BY_ROOM], fn (Room $a, Room $b): int => $a->maxPax <=> $b->maxPax);
return $groups;
}
/**
* Groups roomSelections by selection type ('by_pax' or 'by_room'), using Room::getSelectionType().
*
* @param array $roomSelections Array of selected RoomSelectionDto
* @param array<int, Room> $roomsById Rooms indexed by room ID
*
* @return array{by_pax: array, by_room: array}
*/
public function groupRoomSelectionsByType(array $roomSelections, array $roomsById): array
{
$groups = [
Room::SELECTION_TYPE_BY_PAX => [],
Room::SELECTION_TYPE_BY_ROOM => [],
];
foreach ($roomSelections as $roomSelection) {
$room = $roomsById[$roomSelection->id] ?? null;
if ($room) {
$type = $room->getSelectionType();
$groups[$type][] = $roomSelection;
}
}
return $groups;
}
/**
* Pre-selects default services for all participants.
*
@@ -768,7 +665,7 @@ class BookingService
?\Symfony\Component\Security\Core\User\UserInterface $user = null,
?callable $prepopulateCallback = null,
): void {
$participantsCount = $this->getParticipantsCount($bookingDto->roomSelections, $bookingDto->travel);
$participantsCount = $this->calculateParticipantsCount($bookingDto->roomSelections, $bookingDto->travel);
$existingParticipants = $bookingDto->participants;
$bookingDto->participants = [];
@@ -795,4 +692,28 @@ class BookingService
{
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;
}
}