194 lines
6.6 KiB
PHP
194 lines
6.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Service;
|
|
|
|
use App\Form\Model\BookingDto;
|
|
use App\Form\Model\BookingSummaryDto;
|
|
use Psr\Log\LoggerInterface;
|
|
use Symfony\Contracts\Cache\CacheInterface;
|
|
use Symfony\Contracts\Cache\ItemInterface;
|
|
|
|
/**
|
|
* Consolidates all booking summary data for display in sidebars.
|
|
*
|
|
* Provides pricing breakdowns, room assignments, participant counts,
|
|
* and CMS product information in a single service.
|
|
*/
|
|
class BookingSummaryDataService
|
|
{
|
|
public function __construct(
|
|
private readonly BookingPriceCalculatorService $priceCalculator,
|
|
private readonly CmsDataService $cmsDataService,
|
|
private readonly CacheInterface $cache,
|
|
private readonly LoggerInterface $logger,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Get complete summary data for booking sidebar.
|
|
*/
|
|
public function getSummaryData(BookingDto $bookingDto): BookingSummaryDto
|
|
{
|
|
// Get selected rooms (for Step1 controller compatibility)
|
|
$selectedRooms = $bookingDto->getSelectedRooms();
|
|
|
|
// Calculate individual prices for all participants
|
|
$participantPrices = $this->priceCalculator->calculateAllParticipantIndividualPrices($bookingDto);
|
|
|
|
// Calculate total price
|
|
$totalPrice = array_sum($participantPrices);
|
|
|
|
// Get room assignment counts
|
|
$roomCounts = [];
|
|
foreach ($bookingDto->participants as $participant) {
|
|
if (null !== $participant->assignedRoomId) {
|
|
$roomCounts[$participant->assignedRoomId] = ($roomCounts[$participant->assignedRoomId] ?? 0) + 1;
|
|
}
|
|
}
|
|
|
|
// Group selected rooms with counts (for display)
|
|
$groupedSelectedRooms = [];
|
|
foreach ($roomCounts as $roomId => $count) {
|
|
$room = $bookingDto->travel->getRoomById($roomId);
|
|
if (null !== $room) {
|
|
$groupedSelectedRooms[] = [
|
|
'room' => $room,
|
|
'count' => $count,
|
|
];
|
|
}
|
|
}
|
|
|
|
// Get detailed pricing breakdown
|
|
$pricingData = $this->priceCalculator->getPricingBreakdown($bookingDto);
|
|
|
|
// Fetch CMS data (images, etc.)
|
|
$cmsData = $this->getCmsData($bookingDto);
|
|
|
|
// Calculate participant count from room capacity (source of truth)
|
|
$participantCount = $this->calculateParticipantCountFromRooms($bookingDto);
|
|
|
|
// Calculate payable amount after voucher deductions
|
|
$payableAmount = $this->calculatePayableAmount($bookingDto, $pricingData['grandTotal'], $participantPrices);
|
|
|
|
return new BookingSummaryDto(
|
|
selectedRooms: $selectedRooms,
|
|
participantCount: $participantCount,
|
|
totalPrice: $pricingData['grandTotal'],
|
|
payableAmount: $payableAmount,
|
|
groupedSelectedRooms: $groupedSelectedRooms,
|
|
assignmentCounts: $roomCounts,
|
|
pricingData: $pricingData,
|
|
cmsData: $cmsData,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Calculates the payable amount after voucher deductions.
|
|
*
|
|
* @param array<int, float> $participantPrices Prices per participant for percentage voucher calculation
|
|
*/
|
|
private function calculatePayableAmount(BookingDto $bookingDto, float $grandTotal, array $participantPrices): float
|
|
{
|
|
$acceptedVouchers = $bookingDto->getAcceptedVouchers($participantPrices);
|
|
|
|
if (null === $acceptedVouchers) {
|
|
return $grandTotal;
|
|
}
|
|
|
|
return max(0.0, $grandTotal - $acceptedVouchers->getTotalDiscount());
|
|
}
|
|
|
|
/**
|
|
* Calculates participant count.
|
|
*
|
|
* In edit mode, counts actual participants. In create mode, calculates
|
|
* from room selections by multiplying quantity by maximum capacity (maxPax).
|
|
*/
|
|
private function calculateParticipantCountFromRooms(BookingDto $bookingDto): int
|
|
{
|
|
// In edit mode, use actual participant count
|
|
if (BookingDto::MODE_EDIT === $bookingDto->getMode()) {
|
|
return count($bookingDto->participants);
|
|
}
|
|
|
|
// In create mode, calculate from room selections
|
|
$totalCapacity = 0;
|
|
$availableRooms = $bookingDto->travel->getAvailableRooms();
|
|
|
|
foreach ($bookingDto->roomSelections as $selection) {
|
|
if ($selection->quantity > 0 && isset($availableRooms[$selection->roomId])) {
|
|
$room = $availableRooms[$selection->roomId];
|
|
$totalCapacity += $selection->quantity * ($room->maxPax ?? 0);
|
|
}
|
|
}
|
|
|
|
return $totalCapacity;
|
|
}
|
|
|
|
/**
|
|
* Fetches CMS data for a product and hotel combination.
|
|
*
|
|
* Data is cached for 1 hour as it rarely changes. Returns null
|
|
* if the API call fails or if product code is not available.
|
|
* This method can be called early in the booking flow to warm the cache.
|
|
*/
|
|
public function getCmsDataForProduct(?string $productCode, ?string $hotelCode): ?array
|
|
{
|
|
if (null === $productCode) {
|
|
return null;
|
|
}
|
|
|
|
$cacheKey = sprintf('cms_data.%s.%s', $productCode, $hotelCode ?? 'none');
|
|
|
|
try {
|
|
return $this->cache->get($cacheKey, function (ItemInterface $item) use ($productCode, $hotelCode) {
|
|
$item->expiresAfter(3600); // 1 hour
|
|
|
|
$result = $this->cmsDataService->getProductDetails($productCode, $hotelCode);
|
|
|
|
// Return null if API call failed
|
|
if (true === isset($result['success']) && false === $result['success']) {
|
|
$this->logger->warning('CMS API call failed', [
|
|
'product_code' => $productCode,
|
|
'hotel_code' => $hotelCode,
|
|
'message' => $result['message'] ?? 'Unknown error',
|
|
]);
|
|
|
|
return null;
|
|
}
|
|
|
|
return $result;
|
|
});
|
|
} catch (\Throwable $e) {
|
|
$this->logger->error('Failed to fetch CMS data', [
|
|
'product_code' => $productCode,
|
|
'hotel_code' => $hotelCode,
|
|
'exception' => $e->getMessage(),
|
|
]);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetches CMS data for the product and hotel in the booking.
|
|
*/
|
|
private function getCmsData(BookingDto $bookingDto): ?array
|
|
{
|
|
$productCode = $bookingDto->travel->productCode;
|
|
$hotelCode = $bookingDto->travel->hotel?->code;
|
|
|
|
if (null === $productCode) {
|
|
$this->logger->debug('Cannot fetch CMS data: product code is not available', [
|
|
'travel_id' => $bookingDto->travel->id,
|
|
]);
|
|
|
|
return null;
|
|
}
|
|
|
|
return $this->getCmsDataForProduct($productCode, $hotelCode);
|
|
}
|
|
}
|