180 lines
6.2 KiB
PHP
180 lines
6.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Service;
|
|
|
|
use App\BusProNet\Model\Room;
|
|
use App\Form\Model\BookingDto;
|
|
|
|
/**
|
|
* Calculates pricing for room allocations in bookings.
|
|
*
|
|
* Handles room pricing in both create mode (from room selections) and
|
|
* edit mode (from booking entity data with individual participant prices).
|
|
*/
|
|
class RoomPricingCalculator
|
|
{
|
|
public const PRICING_MODE_ASSIGNMENT = 'assignment';
|
|
public const PRICING_MODE_SELECTION = 'selection';
|
|
|
|
/**
|
|
* Calculates pricing for all selected rooms.
|
|
*
|
|
* @param BookingDto $bookingDto The booking data containing room selections
|
|
*
|
|
* @return array<int, array<string, mixed>> Array of room pricing data with labels, quantities, and totals
|
|
*/
|
|
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) {
|
|
return $this->calculateRoomPricingFromBooking($bookingDto);
|
|
}
|
|
|
|
// In create mode, use room selections from the form
|
|
return $this->calculateRoomPricingFromSelections($bookingDto, $pricingMode);
|
|
}
|
|
|
|
/**
|
|
* Calculates total price for all rooms.
|
|
*/
|
|
public function calculateRoomTotal(
|
|
BookingDto $bookingDto,
|
|
string $pricingMode = self::PRICING_MODE_ASSIGNMENT,
|
|
): float {
|
|
$roomPricing = $this->calculateRoomPricing($bookingDto, $pricingMode);
|
|
|
|
return array_sum(array_column($roomPricing, 'totalPrice'));
|
|
}
|
|
|
|
/**
|
|
* Retrieves a room by ID from the booking's travel data.
|
|
*/
|
|
public function getRoomById(BookingDto $bookingDto, ?int $roomId): ?Room
|
|
{
|
|
if (null === $roomId) {
|
|
return null;
|
|
}
|
|
|
|
foreach ($bookingDto->travel->rooms as $room) {
|
|
if ($room->id === $roomId) {
|
|
return $room;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Calculates room pricing from form selections (create mode).
|
|
*
|
|
* @param BookingDto $bookingDto The booking data containing room selections
|
|
*
|
|
* @return array<int, array<string, mixed>> Array of room pricing data
|
|
*/
|
|
private function calculateRoomPricingFromSelections(BookingDto $bookingDto, string $pricingMode): array
|
|
{
|
|
$roomPricing = [];
|
|
$selectedRooms = $bookingDto->getSelectedRooms();
|
|
|
|
if (true === empty($selectedRooms)) {
|
|
return $roomPricing;
|
|
}
|
|
|
|
$assignmentCounts = $bookingDto->getRoomAssignmentCounts();
|
|
|
|
foreach ($selectedRooms as $roomSelection) {
|
|
$room = $this->getRoomById($bookingDto, $roomSelection->id);
|
|
if (null === $room || null === $room->price) {
|
|
continue;
|
|
}
|
|
|
|
// 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[] = [
|
|
'roomId' => $room->id,
|
|
'label' => $room->label,
|
|
'quantity' => $roomSelection->quantity,
|
|
'participantCount' => $participantCount,
|
|
'unitPrice' => $room->price,
|
|
'totalPrice' => $totalPrice,
|
|
];
|
|
}
|
|
|
|
return $roomPricing;
|
|
}
|
|
|
|
/**
|
|
* Calculates room pricing from booking entity data (edit mode).
|
|
*
|
|
* In edit mode, room prices come from the booking entity's individualPrice arrays.
|
|
* Each participant has their room price stored in the room's individualPrice array.
|
|
*
|
|
* @param BookingDto $bookingDto The booking data with booking entity
|
|
*
|
|
* @return array<int, array<string, mixed>> Array of room pricing data with labels, quantities, and totals
|
|
*/
|
|
private function calculateRoomPricingFromBooking(BookingDto $bookingDto): array
|
|
{
|
|
$roomPricing = [];
|
|
$roomGroups = [];
|
|
|
|
// Group participants by room and sum their individual prices
|
|
foreach ($bookingDto->booking->rooms as $room) {
|
|
if (false === isset($roomGroups[$room->id])) {
|
|
$roomGroups[$room->id] = [
|
|
'room' => $room,
|
|
'participantCount' => 0,
|
|
'totalPrice' => 0.0,
|
|
];
|
|
}
|
|
|
|
// Sum individual prices for all participants in this room
|
|
foreach ($room->mapping as $participantIndex) {
|
|
$individualPrice = $room->individualPrice[$participantIndex] ?? 0.0;
|
|
$roomGroups[$room->id]['totalPrice'] += $individualPrice;
|
|
++$roomGroups[$room->id]['participantCount'];
|
|
}
|
|
}
|
|
|
|
// Count current participant assignments from the DTO (reflects live state
|
|
// including new assignments not yet in the booking entity)
|
|
$assignmentCounts = $bookingDto->getRoomAssignmentCounts();
|
|
|
|
// Build pricing array
|
|
foreach ($roomGroups as $roomId => $data) {
|
|
$room = $data['room'];
|
|
$participantCount = $data['participantCount'];
|
|
$totalPrice = $data['totalPrice'];
|
|
|
|
// Calculate average unit price (price per person)
|
|
$unitPrice = $participantCount > 0 ? $totalPrice / $participantCount : 0.0;
|
|
|
|
// Use live assignment count for quantity display so the sidebar reflects
|
|
// new room assignments that haven't been persisted to the booking entity yet
|
|
$quantity = $assignmentCounts[$roomId] ?? $room->totalCount;
|
|
|
|
$roomPricing[] = [
|
|
'roomId' => $room->id,
|
|
'label' => $room->label,
|
|
'quantity' => $quantity,
|
|
'participantCount' => $participantCount,
|
|
'unitPrice' => $unitPrice,
|
|
'totalPrice' => $totalPrice,
|
|
];
|
|
}
|
|
|
|
return $roomPricing;
|
|
}
|
|
}
|