diff --git a/src/Controller/Booking/CreateStep2Controller.php b/src/Controller/Booking/CreateStep2Controller.php
index 8a05ac5..eb69e6c 100644
--- a/src/Controller/Booking/CreateStep2Controller.php
+++ b/src/Controller/Booking/CreateStep2Controller.php
@@ -8,6 +8,7 @@ use App\Controller\Traits\HtmxControllerTrait;
use App\Form\BookingCreateStep2Type;
use App\Form\Model\BookingCreateDto;
use App\Form\Model\ParticipantDto;
+use App\Service\BookingPriceCalculatorService;
use App\Service\BookingService;
use App\Service\TravelDataService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -29,6 +30,7 @@ class CreateStep2Controller extends AbstractController
public function __construct(
private readonly BookingService $bookingService,
+ private readonly BookingPriceCalculatorService $priceCalculator,
private readonly TravelDataService $travelDataService,
) {
}
@@ -80,12 +82,14 @@ class CreateStep2Controller extends AbstractController
$roomAssignmentCounts = $this->bookingService->getRoomAssignmentCounts($bookingCreateDto);
$availableRooms = $bookingCreateDto->travel->getAvailableRooms();
$groupedSelectedRooms = $this->bookingService->groupRoomSelectionsByType($bookingCreateDto->getSelectedRooms(), $availableRooms);
+ $participantPrices = $this->priceCalculator->calculateAllParticipantIndividualPrices($bookingCreateDto);
return $this->render('booking/create_step_2.html.twig', [
'bookingCreateDto' => $bookingCreateDto,
'participantsCount' => $participantsCount,
'pricingData' => $summary['pricing'],
'assignmentCounts' => $roomAssignmentCounts,
+ 'participantPrices' => $participantPrices,
'form' => $form->createView(),
'groupedSelectedRooms' => $groupedSelectedRooms,
]);
@@ -132,6 +136,7 @@ class CreateStep2Controller extends AbstractController
$roomAssignmentCounts = $this->bookingService->getRoomAssignmentCounts($bookingCreateDto);
$availableRooms = $bookingCreateDto->travel->getAvailableRooms();
$groupedSelectedRooms = $this->bookingService->groupRoomSelectionsByType($bookingCreateDto->getSelectedRooms(), $availableRooms);
+ $participantPrices = $this->priceCalculator->calculateAllParticipantIndividualPrices($bookingCreateDto);
// The DTO is now updated with the latest selection and submitted data has been cleaned.
// We can now render the blocks with the fresh data.
@@ -144,6 +149,7 @@ class CreateStep2Controller extends AbstractController
'participantsCount' => $participantsCount,
'pricingData' => $summary['pricing'],
'assignmentCounts' => $roomAssignmentCounts,
+ 'participantPrices' => $participantPrices,
'groupedSelectedRooms' => $groupedSelectedRooms,
]
);
diff --git a/src/Service/BookingPriceCalculatorService.php b/src/Service/BookingPriceCalculatorService.php
index 4491229..b6d1eda 100644
--- a/src/Service/BookingPriceCalculatorService.php
+++ b/src/Service/BookingPriceCalculatorService.php
@@ -66,12 +66,17 @@ class BookingPriceCalculatorService
continue;
}
- $totalPrice = $roomSelection->quantity * $room->price;
+ // Calculate participant count for this room selection
+ $participantCount = $room->minPax * $roomSelection->quantity;
+
+ // Each participant pays the full room price
+ $totalPrice = $participantCount * $room->price;
$roomPricing[] = [
'roomId' => $room->id,
'label' => $room->label,
'quantity' => $roomSelection->quantity,
+ 'participantCount' => $participantCount,
'unitPrice' => $room->price,
'totalPrice' => $totalPrice,
];
@@ -171,6 +176,68 @@ class BookingPriceCalculatorService
return '€'.$this->formatPrice($price);
}
+ /**
+ * Calculates the total price for an individual participant.
+ *
+ * This method calculates the complete price breakdown for a single participant,
+ * including their room allocation (full room price) and all selected services.
+ *
+ * @param BookingDtoInterface $bookingDto The booking data containing all participants
+ * @param int $participantIndex The index of the participant to calculate for
+ *
+ * @return float The total price for the specified participant
+ */
+ public function calculateIndividualParticipantPrice(BookingDtoInterface $bookingDto, int $participantIndex): float
+ {
+ if (false === $bookingDto instanceof BookingCreateDto) {
+ return 0.0;
+ }
+
+ $participant = $bookingDto->getParticipant($participantIndex);
+ if (null === $participant) {
+ return 0.0;
+ }
+
+ $totalPrice = 0.0;
+
+ // Add room price if participant is assigned to a room
+ if (null !== $participant->assignedRoomId) {
+ $room = $this->getRoomById($bookingDto, $participant->assignedRoomId);
+ if (null !== $room && null !== $room->price) {
+ // Each participant pays the full room price
+ $totalPrice += $room->price;
+ }
+ }
+
+ // Add service prices for this participant
+ $totalPrice += $this->calculateParticipantServiceTotal($participant);
+
+ return $totalPrice;
+ }
+
+ /**
+ * Calculates individual prices for all participants in a booking.
+ *
+ * @param BookingDtoInterface $bookingDto The booking data containing all participants
+ *
+ * @return array Array indexed by participant index containing individual prices
+ */
+ public function calculateAllParticipantIndividualPrices(BookingDtoInterface $bookingDto): array
+ {
+ if (false === $bookingDto instanceof BookingCreateDto) {
+ return [];
+ }
+
+ $participantPrices = [];
+ $participants = $bookingDto->getParticipants();
+
+ foreach ($participants as $index => $participant) {
+ $participantPrices[$index] = $this->calculateIndividualParticipantPrice($bookingDto, $index);
+ }
+
+ return $participantPrices;
+ }
+
/**
* Aggregates all transportation-related services and pricing into separate line items.
*
@@ -405,6 +472,68 @@ class BookingPriceCalculatorService
$serviceAggregation[$serviceKey]['totalPrice'] += $service->price * $quantity;
}
+ /**
+ * Calculates the total service cost for a single participant.
+ *
+ * @param ParticipantDto $participant The participant to calculate services for
+ *
+ * @return float The total service cost for this participant
+ */
+ private function calculateParticipantServiceTotal(ParticipantDto $participant): float
+ {
+ $serviceTotal = 0.0;
+
+ // Single service selections
+ if (null !== $participant->skiPass && null !== $participant->skiPass->price) {
+ $serviceTotal += $participant->skiPass->price;
+ }
+
+ if (null !== $participant->rentalInsurance && null !== $participant->rentalInsurance->price) {
+ $serviceTotal += $participant->rentalInsurance->price;
+ }
+
+ // Transportation services
+ if (null !== $participant->transportationOutbound && null !== $participant->transportationOutbound->price) {
+ $serviceTotal += $participant->transportationOutbound->price;
+ }
+
+ if (null !== $participant->transportationInbound && null !== $participant->transportationInbound->price) {
+ $serviceTotal += $participant->transportationInbound->price;
+ }
+
+ if (null !== $participant->pickupOutbound && null !== $participant->pickupOutbound->price) {
+ $serviceTotal += $participant->pickupOutbound->price;
+ }
+
+ if (null !== $participant->pickupInbound && null !== $participant->pickupInbound->price) {
+ $serviceTotal += $participant->pickupInbound->price;
+ }
+
+ if (null !== $participant->parkingService && null !== $participant->parkingService->price) {
+ $serviceTotal += $participant->parkingService->price;
+ }
+
+ // Multiple service selections
+ $multipleServiceArrays = [
+ 'courses' => $participant->courses,
+ 'additionalServices' => $participant->additionalServices,
+ 'board' => $participant->board,
+ 'rentals' => $participant->rentals,
+ ];
+
+ foreach ($multipleServiceArrays as $serviceArray) {
+ if (true === is_array($serviceArray)) {
+ foreach ($serviceArray as $service) {
+ if ($service instanceof Service && null !== $service->price) {
+ $serviceTotal += $service->price;
+ }
+ }
+ }
+ }
+
+ return $serviceTotal;
+ }
+
/**
* Retrieves a room by ID from the booking's travel data.
*/
diff --git a/templates/booking/create_step_2.html.twig b/templates/booking/create_step_2.html.twig
index d191ce2..6e0965c 100644
--- a/templates/booking/create_step_2.html.twig
+++ b/templates/booking/create_step_2.html.twig
@@ -16,7 +16,14 @@