feat: consolidate micro-services and unify draft merge logic
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\BusProNet\DataProvider\CountryDataProvider;
|
||||
use App\BusProNet\Model\Hotel;
|
||||
use App\BusProNet\XmlLoader\HotelLoader;
|
||||
use App\Form\Model\AcceptedVouchersDto;
|
||||
use App\Form\Model\BookingDto;
|
||||
use App\Form\Model\BookingSummaryDto;
|
||||
use App\Form\Model\BookingSummaryPricingDto;
|
||||
use App\Form\Model\BookingSummaryVoucherDto;
|
||||
use App\Model\BookingSummaryCmsHotelDto;
|
||||
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 BookingSummaryAssembler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly BookingPriceCalculator $priceCalculator,
|
||||
private readonly CmsDataProvider $cmsDataService,
|
||||
private readonly HotelLoader $hotelLoader,
|
||||
private readonly CountryDataProvider $countryDataProvider,
|
||||
private readonly CacheInterface $cache,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complete summary data for booking sidebar.
|
||||
*/
|
||||
public function getSummaryData(
|
||||
BookingDto $bookingDto,
|
||||
string $roomPricingMode = RoomPricingCalculator::PRICING_MODE_ASSIGNMENT,
|
||||
): BookingSummaryDto
|
||||
{
|
||||
// Get selected rooms (for Step1 controller compatibility)
|
||||
$selectedRooms = $bookingDto->getSelectedRooms();
|
||||
|
||||
// Calculate individual prices for all participants
|
||||
$participantPrices = $this->priceCalculator->calculateAllParticipantIndividualPrices($bookingDto);
|
||||
|
||||
// Get room assignment counts
|
||||
$roomCounts = [];
|
||||
foreach ($bookingDto->participants as $participant) {
|
||||
if (null !== $participant->assignedRoomId) {
|
||||
$roomCounts[$participant->assignedRoomId] = ($roomCounts[$participant->assignedRoomId] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Get detailed pricing breakdown
|
||||
$pricingData = $this->priceCalculator->getPricingBreakdown($bookingDto, $roomPricingMode);
|
||||
|
||||
// Fetch CMS data (images, etc.)
|
||||
$productCode = $bookingDto->travel->productCode;
|
||||
$hotelCode = $bookingDto->travel->hotel?->code;
|
||||
|
||||
if (null === $hotelCode) {
|
||||
$this->logger->debug('Cannot fetch hotel data: hotel code is not available', [
|
||||
'travel_id' => $bookingDto->travel->id,
|
||||
]);
|
||||
|
||||
$cmsData = null;
|
||||
} else {
|
||||
$cmsData = $this->getCmsDataForProduct($productCode, $hotelCode);
|
||||
}
|
||||
|
||||
$participantCount = $this->calculateParticipantCount($bookingDto);
|
||||
|
||||
$acceptedVouchers = $bookingDto->getAcceptedVouchers($participantPrices);
|
||||
|
||||
// Calculate payable amount after voucher deductions
|
||||
$payableAmount = $this->calculatePayableAmount($pricingData['grandTotal'], $acceptedVouchers);
|
||||
|
||||
$pricing = new BookingSummaryPricingDto(
|
||||
rooms: $pricingData['rooms'],
|
||||
services: $pricingData['services'],
|
||||
surcharges: $pricingData['surcharges'] ?? null,
|
||||
assignmentCounts: $roomCounts,
|
||||
grandTotal: $pricingData['grandTotal'],
|
||||
);
|
||||
|
||||
$vouchers = new BookingSummaryVoucherDto(
|
||||
acceptedVouchers: $acceptedVouchers,
|
||||
payableAmount: $payableAmount,
|
||||
);
|
||||
|
||||
return new BookingSummaryDto(
|
||||
selectedRooms: $selectedRooms,
|
||||
participantCount: $participantCount,
|
||||
pricing: $pricing,
|
||||
vouchers: $vouchers,
|
||||
cmsData: $cmsData,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the payable amount after voucher deductions.
|
||||
*
|
||||
* @param AcceptedVouchersDto|null $acceptedVouchers Accepted vouchers for discount calculation
|
||||
*/
|
||||
private function calculatePayableAmount(float $grandTotal, ?AcceptedVouchersDto $acceptedVouchers): float
|
||||
{
|
||||
if (null === $acceptedVouchers) {
|
||||
return $grandTotal;
|
||||
}
|
||||
|
||||
return max(0.0, $grandTotal - $acceptedVouchers->getTotalDiscount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches hotel display data combining local base hotel data with CMS images.
|
||||
*
|
||||
* Local hotel data provides name and address while CMS provides images
|
||||
* as a nice-to-have enhancement.
|
||||
*
|
||||
* Data is cached for 1 hour. This method can be called early in the booking
|
||||
* flow to warm the cache.
|
||||
*/
|
||||
public function getCmsDataForProduct(?string $productCode, ?string $hotelCode): ?BookingSummaryCmsHotelDto
|
||||
{
|
||||
if (null === $hotelCode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cacheKey = sprintf('cms_data.%s.%s', $productCode, $hotelCode);
|
||||
|
||||
try {
|
||||
return $this->cache->get($cacheKey, function (ItemInterface $item) use ($productCode, $hotelCode) {
|
||||
$item->expiresAfter(3600); // 1 hour
|
||||
|
||||
// Fetch base hotel from local BusProNet data
|
||||
$baseHotel = $this->hotelLoader->loadByCode($hotelCode);
|
||||
|
||||
// Fetch CMS images (nice to have)
|
||||
$images = $this->cmsDataService->getProductImages($productCode, $hotelCode);
|
||||
|
||||
return new BookingSummaryCmsHotelDto(
|
||||
name: $baseHotel?->name,
|
||||
address: $this->formatHotelAddress($baseHotel),
|
||||
images: $images,
|
||||
);
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('Failed to fetch hotel display data', [
|
||||
'product_code' => $productCode,
|
||||
'hotel_code' => $hotelCode,
|
||||
'exception' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a hotel's address from street, city and country.
|
||||
*/
|
||||
private function formatHotelAddress(?Hotel $hotel): ?string
|
||||
{
|
||||
if (null === $hotel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$countryName = $this->countryDataProvider->get($hotel->country)?->name;
|
||||
$parts = array_filter([$hotel->city, $countryName]);
|
||||
|
||||
return [] === $parts ? null : implode("\n", $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the participant count for the summary sidebar.
|
||||
*
|
||||
* In create step 1, derives the expected count from room selections.
|
||||
* In later create steps and edit mode, uses the actual participant list.
|
||||
*/
|
||||
private function calculateParticipantCount(BookingDto $bookingDto): int
|
||||
{
|
||||
if (BookingDto::MODE_CREATE === $bookingDto->getMode() && 1 === $bookingDto->currentStep) {
|
||||
$totalCapacity = 0;
|
||||
$availableRooms = $bookingDto->travel->getAvailableRooms();
|
||||
|
||||
foreach ($bookingDto->roomSelections as $selection) {
|
||||
if ($selection->quantity > 0 && isset($availableRooms[$selection->id])) {
|
||||
$room = $availableRooms[$selection->id];
|
||||
$totalCapacity += $selection->quantity * ($room->maxPax ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
return $totalCapacity;
|
||||
}
|
||||
|
||||
return count($bookingDto->participants);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user