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 $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); } }