From 301b14f51833a2395d565ad78a6dad9f72d80e78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Thu, 19 Mar 2026 16:30:04 +0100 Subject: [PATCH] feat: dedicated room pricing calculation for step 1 of create flow --- .../Booking/Create/Step1Controller.php | 5 ++-- src/Service/BookingPriceCalculatorService.php | 30 +++++++++++++------ src/Service/BookingSummaryDataService.php | 7 +++-- src/Service/RoomPricingCalculator.php | 28 ++++++++++++----- .../BookingPriceCalculatorServiceTest.php | 29 ++++++++++++++++++ 5 files changed, 79 insertions(+), 20 deletions(-) diff --git a/src/Controller/Booking/Create/Step1Controller.php b/src/Controller/Booking/Create/Step1Controller.php index 6ef5670..eda214c 100644 --- a/src/Controller/Booking/Create/Step1Controller.php +++ b/src/Controller/Booking/Create/Step1Controller.php @@ -11,6 +11,7 @@ use App\Form\Model\BookingDto; use App\Htmx\HxTrait; use App\Service\BookingService; use App\Service\BookingSummaryDataService; +use App\Service\RoomPricingCalculator; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -78,7 +79,7 @@ class Step1Controller extends AbstractController } // Get complete summary data (pricing, rooms, CMS data) - $summaryData = $this->summaryDataService->getSummaryData($bookingCreateDto); + $summaryData = $this->summaryDataService->getSummaryData($bookingCreateDto, RoomPricingCalculator::PRICING_MODE_SELECTION); $availableRooms = $bookingCreateDto->travel->getAvailableRooms(); $groupedRooms = $this->bookingService->groupRoomsBySelectionType($availableRooms); @@ -111,7 +112,7 @@ class Step1Controller extends AbstractController $form->handleRequest($request); // Get complete summary data (pricing, rooms, CMS data) - $summaryData = $this->summaryDataService->getSummaryData($bookingCreateDto); + $summaryData = $this->summaryDataService->getSummaryData($bookingCreateDto, RoomPricingCalculator::PRICING_MODE_SELECTION); $availableRooms = $bookingCreateDto->travel->getAvailableRooms(); $groupedRooms = $this->bookingService->groupRoomsBySelectionType($availableRooms); diff --git a/src/Service/BookingPriceCalculatorService.php b/src/Service/BookingPriceCalculatorService.php index f8b780c..a702fdf 100644 --- a/src/Service/BookingPriceCalculatorService.php +++ b/src/Service/BookingPriceCalculatorService.php @@ -29,11 +29,14 @@ class BookingPriceCalculatorService * * @return array{rooms: array, services: array, grandTotal: float} Complete pricing breakdown */ - public function getPricingBreakdown(BookingDto $bookingDto): array + public function getPricingBreakdown( + BookingDto $bookingDto, + string $roomPricingMode = RoomPricingCalculator::PRICING_MODE_ASSIGNMENT, + ): array { - $roomPricing = $this->roomPricingCalculator->calculateRoomPricing($bookingDto); + $roomPricing = $this->roomPricingCalculator->calculateRoomPricing($bookingDto, $roomPricingMode); $servicePricing = $this->servicePricingCalculator->calculateServicePricing($bookingDto); - $grandTotal = $this->calculateGrandTotal($bookingDto); + $grandTotal = $this->calculateGrandTotal($bookingDto, $roomPricingMode); $result = [ 'rooms' => $roomPricing, @@ -64,9 +67,12 @@ class BookingPriceCalculatorService * * @return array Array of room pricing data with labels, quantities, and totals */ - public function calculateRoomPricing(BookingDto $bookingDto): array + public function calculateRoomPricing( + BookingDto $bookingDto, + string $roomPricingMode = RoomPricingCalculator::PRICING_MODE_ASSIGNMENT, + ): array { - return $this->roomPricingCalculator->calculateRoomPricing($bookingDto); + return $this->roomPricingCalculator->calculateRoomPricing($bookingDto, $roomPricingMode); } /** @@ -90,9 +96,12 @@ class BookingPriceCalculatorService * * @return float The grand total price */ - public function calculateGrandTotal(BookingDto $bookingDto): float + public function calculateGrandTotal( + BookingDto $bookingDto, + string $roomPricingMode = RoomPricingCalculator::PRICING_MODE_ASSIGNMENT, + ): float { - $roomTotal = $this->roomPricingCalculator->calculateRoomTotal($bookingDto); + $roomTotal = $this->roomPricingCalculator->calculateRoomTotal($bookingDto, $roomPricingMode); $serviceTotal = $this->servicePricingCalculator->calculateServiceTotal($bookingDto); return $roomTotal + $serviceTotal; @@ -101,9 +110,12 @@ class BookingPriceCalculatorService /** * Calculates total price for all rooms. */ - public function calculateRoomTotal(BookingDto $bookingDto): float + public function calculateRoomTotal( + BookingDto $bookingDto, + string $roomPricingMode = RoomPricingCalculator::PRICING_MODE_ASSIGNMENT, + ): float { - return $this->roomPricingCalculator->calculateRoomTotal($bookingDto); + return $this->roomPricingCalculator->calculateRoomTotal($bookingDto, $roomPricingMode); } /** diff --git a/src/Service/BookingSummaryDataService.php b/src/Service/BookingSummaryDataService.php index ceac599..a24565b 100644 --- a/src/Service/BookingSummaryDataService.php +++ b/src/Service/BookingSummaryDataService.php @@ -34,7 +34,10 @@ class BookingSummaryDataService /** * Get complete summary data for booking sidebar. */ - public function getSummaryData(BookingDto $bookingDto): BookingSummaryDto + public function getSummaryData( + BookingDto $bookingDto, + string $roomPricingMode = RoomPricingCalculator::PRICING_MODE_ASSIGNMENT, + ): BookingSummaryDto { // Get selected rooms (for Step1 controller compatibility) $selectedRooms = $bookingDto->getSelectedRooms(); @@ -66,7 +69,7 @@ class BookingSummaryDataService } // Get detailed pricing breakdown - $pricingData = $this->priceCalculator->getPricingBreakdown($bookingDto); + $pricingData = $this->priceCalculator->getPricingBreakdown($bookingDto, $roomPricingMode); // Fetch CMS data (images, etc.) $cmsData = $this->getCmsData($bookingDto); diff --git a/src/Service/RoomPricingCalculator.php b/src/Service/RoomPricingCalculator.php index a027bfd..9e2a3ad 100644 --- a/src/Service/RoomPricingCalculator.php +++ b/src/Service/RoomPricingCalculator.php @@ -15,6 +15,9 @@ use App\Form\Model\BookingDto; */ class RoomPricingCalculator { + public const PRICING_MODE_ASSIGNMENT = 'assignment'; + public const PRICING_MODE_SELECTION = 'selection'; + /** * Calculates pricing for all selected rooms. * @@ -22,7 +25,10 @@ class RoomPricingCalculator * * @return array Array of room pricing data with labels, quantities, and totals */ - public function calculateRoomPricing(BookingDto $bookingDto): array + public function calculateRoomPricing( + BookingDto $bookingDto, + string $pricingMode = self::PRICING_MODE_ASSIGNMENT, + ): array { // In edit mode, use room data from the booking entity if (BookingDto::MODE_EDIT === $bookingDto->getMode() && null !== $bookingDto->booking) { @@ -30,15 +36,18 @@ class RoomPricingCalculator } // In create mode, use room selections from the form - return $this->calculateRoomPricingFromSelections($bookingDto); + return $this->calculateRoomPricingFromSelections($bookingDto, $pricingMode); } /** * Calculates total price for all rooms. */ - public function calculateRoomTotal(BookingDto $bookingDto): float + public function calculateRoomTotal( + BookingDto $bookingDto, + string $pricingMode = self::PRICING_MODE_ASSIGNMENT, + ): float { - $roomPricing = $this->calculateRoomPricing($bookingDto); + $roomPricing = $this->calculateRoomPricing($bookingDto, $pricingMode); return array_sum(array_column($roomPricing, 'totalPrice')); } @@ -68,7 +77,7 @@ class RoomPricingCalculator * * @return array Array of room pricing data */ - private function calculateRoomPricingFromSelections(BookingDto $bookingDto): array + private function calculateRoomPricingFromSelections(BookingDto $bookingDto, string $pricingMode): array { $roomPricing = []; $selectedRooms = $bookingDto->getSelectedRooms(); @@ -85,8 +94,13 @@ class RoomPricingCalculator continue; } - // Pricing in create flow is based on actual participant assignments per room. - $participantCount = $assignmentCounts[$room->id] ?? 0; + // Step 1 displays intended room pricing based on selected quantity and + // minimum room occupancy (minPax). Subsequent steps calculate based on + // actual participant assignments. + $participantCount = self::PRICING_MODE_SELECTION === $pricingMode + ? (($room->minPax ?? 1) * $roomSelection->quantity) + : ($assignmentCounts[$room->id] ?? 0) + ; $totalPrice = $participantCount * $room->price; $roomPricing[] = [ diff --git a/tests/Service/BookingPriceCalculatorServiceTest.php b/tests/Service/BookingPriceCalculatorServiceTest.php index 988204c..d261773 100644 --- a/tests/Service/BookingPriceCalculatorServiceTest.php +++ b/tests/Service/BookingPriceCalculatorServiceTest.php @@ -259,6 +259,35 @@ class BookingPriceCalculatorServiceTest extends TestCase $this->assertEquals(0.0, $result[0]['totalPrice']); } + public function testCalculateRoomPricingUsesSelectionModeForStepOneSummary(): void + { + $room = new Room(); + $room->id = 3; + $room->price = 229.0; + $room->minPax = 2; + $room->label = 'Doppelzimmer Dusche/WC'; + + $travel = new Travel(); + $travel->rooms = [$room]; + + $roomSelection = new RoomSelectionDto(); + $roomSelection->id = 3; + $roomSelection->quantity = 1; + + $bookingDto = new BookingDto($travel, 1); + $bookingDto->roomSelections = [$roomSelection]; + $bookingDto->participants = [ + $this->createParticipantWithAssignedRoom(3), + $this->createParticipantWithAssignedRoom(3), + ]; + + $result = $this->service->calculateRoomPricing($bookingDto, RoomPricingCalculator::PRICING_MODE_SELECTION); + + $this->assertCount(1, $result); + $this->assertEquals(2, $result[0]['participantCount']); + $this->assertEquals(458.0, $result[0]['totalPrice']); + } + public function testCalculateRoomPricingInEditModeUsesStoredIndividualPrices(): void { $travel = new Travel();