feat: dedicated room pricing calculation for step 1 of create flow
This commit is contained in:
@@ -11,6 +11,7 @@ use App\Form\Model\BookingDto;
|
|||||||
use App\Htmx\HxTrait;
|
use App\Htmx\HxTrait;
|
||||||
use App\Service\BookingService;
|
use App\Service\BookingService;
|
||||||
use App\Service\BookingSummaryDataService;
|
use App\Service\BookingSummaryDataService;
|
||||||
|
use App\Service\RoomPricingCalculator;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
@@ -78,7 +79,7 @@ class Step1Controller extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get complete summary data (pricing, rooms, CMS data)
|
// 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();
|
$availableRooms = $bookingCreateDto->travel->getAvailableRooms();
|
||||||
$groupedRooms = $this->bookingService->groupRoomsBySelectionType($availableRooms);
|
$groupedRooms = $this->bookingService->groupRoomsBySelectionType($availableRooms);
|
||||||
@@ -111,7 +112,7 @@ class Step1Controller extends AbstractController
|
|||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
// Get complete summary data (pricing, rooms, CMS data)
|
// 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();
|
$availableRooms = $bookingCreateDto->travel->getAvailableRooms();
|
||||||
$groupedRooms = $this->bookingService->groupRoomsBySelectionType($availableRooms);
|
$groupedRooms = $this->bookingService->groupRoomsBySelectionType($availableRooms);
|
||||||
|
|
||||||
|
|||||||
@@ -29,11 +29,14 @@ class BookingPriceCalculatorService
|
|||||||
*
|
*
|
||||||
* @return array{rooms: array, services: array, grandTotal: float} Complete pricing breakdown
|
* @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);
|
$servicePricing = $this->servicePricingCalculator->calculateServicePricing($bookingDto);
|
||||||
$grandTotal = $this->calculateGrandTotal($bookingDto);
|
$grandTotal = $this->calculateGrandTotal($bookingDto, $roomPricingMode);
|
||||||
|
|
||||||
$result = [
|
$result = [
|
||||||
'rooms' => $roomPricing,
|
'rooms' => $roomPricing,
|
||||||
@@ -64,9 +67,12 @@ class BookingPriceCalculatorService
|
|||||||
*
|
*
|
||||||
* @return array Array of room pricing data with labels, quantities, and totals
|
* @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
|
* @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);
|
$serviceTotal = $this->servicePricingCalculator->calculateServiceTotal($bookingDto);
|
||||||
|
|
||||||
return $roomTotal + $serviceTotal;
|
return $roomTotal + $serviceTotal;
|
||||||
@@ -101,9 +110,12 @@ class BookingPriceCalculatorService
|
|||||||
/**
|
/**
|
||||||
* Calculates total price for all rooms.
|
* 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -34,7 +34,10 @@ class BookingSummaryDataService
|
|||||||
/**
|
/**
|
||||||
* Get complete summary data for booking sidebar.
|
* 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)
|
// Get selected rooms (for Step1 controller compatibility)
|
||||||
$selectedRooms = $bookingDto->getSelectedRooms();
|
$selectedRooms = $bookingDto->getSelectedRooms();
|
||||||
@@ -66,7 +69,7 @@ class BookingSummaryDataService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get detailed pricing breakdown
|
// Get detailed pricing breakdown
|
||||||
$pricingData = $this->priceCalculator->getPricingBreakdown($bookingDto);
|
$pricingData = $this->priceCalculator->getPricingBreakdown($bookingDto, $roomPricingMode);
|
||||||
|
|
||||||
// Fetch CMS data (images, etc.)
|
// Fetch CMS data (images, etc.)
|
||||||
$cmsData = $this->getCmsData($bookingDto);
|
$cmsData = $this->getCmsData($bookingDto);
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ use App\Form\Model\BookingDto;
|
|||||||
*/
|
*/
|
||||||
class RoomPricingCalculator
|
class RoomPricingCalculator
|
||||||
{
|
{
|
||||||
|
public const PRICING_MODE_ASSIGNMENT = 'assignment';
|
||||||
|
public const PRICING_MODE_SELECTION = 'selection';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates pricing for all selected rooms.
|
* Calculates pricing for all selected rooms.
|
||||||
*
|
*
|
||||||
@@ -22,7 +25,10 @@ class RoomPricingCalculator
|
|||||||
*
|
*
|
||||||
* @return array Array of room pricing data with labels, quantities, and totals
|
* @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
|
// In edit mode, use room data from the booking entity
|
||||||
if (BookingDto::MODE_EDIT === $bookingDto->getMode() && null !== $bookingDto->booking) {
|
if (BookingDto::MODE_EDIT === $bookingDto->getMode() && null !== $bookingDto->booking) {
|
||||||
@@ -30,15 +36,18 @@ class RoomPricingCalculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
// In create mode, use room selections from the form
|
// In create mode, use room selections from the form
|
||||||
return $this->calculateRoomPricingFromSelections($bookingDto);
|
return $this->calculateRoomPricingFromSelections($bookingDto, $pricingMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates total price for all rooms.
|
* 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'));
|
return array_sum(array_column($roomPricing, 'totalPrice'));
|
||||||
}
|
}
|
||||||
@@ -68,7 +77,7 @@ class RoomPricingCalculator
|
|||||||
*
|
*
|
||||||
* @return array Array of room pricing data
|
* @return array Array of room pricing data
|
||||||
*/
|
*/
|
||||||
private function calculateRoomPricingFromSelections(BookingDto $bookingDto): array
|
private function calculateRoomPricingFromSelections(BookingDto $bookingDto, string $pricingMode): array
|
||||||
{
|
{
|
||||||
$roomPricing = [];
|
$roomPricing = [];
|
||||||
$selectedRooms = $bookingDto->getSelectedRooms();
|
$selectedRooms = $bookingDto->getSelectedRooms();
|
||||||
@@ -85,8 +94,13 @@ class RoomPricingCalculator
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pricing in create flow is based on actual participant assignments per room.
|
// Step 1 displays intended room pricing based on selected quantity and
|
||||||
$participantCount = $assignmentCounts[$room->id] ?? 0;
|
// 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;
|
$totalPrice = $participantCount * $room->price;
|
||||||
|
|
||||||
$roomPricing[] = [
|
$roomPricing[] = [
|
||||||
|
|||||||
@@ -259,6 +259,35 @@ class BookingPriceCalculatorServiceTest extends TestCase
|
|||||||
$this->assertEquals(0.0, $result[0]['totalPrice']);
|
$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
|
public function testCalculateRoomPricingInEditModeUsesStoredIndividualPrices(): void
|
||||||
{
|
{
|
||||||
$travel = new Travel();
|
$travel = new Travel();
|
||||||
|
|||||||
Reference in New Issue
Block a user