From b0b608f12ebf86fd80af9e52051eeb64fdf4e990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Wed, 7 Jan 2026 18:35:09 +0100 Subject: [PATCH] feat: load hotel data locally for sidebar --- src/BusProNet/XmlLoader/HotelLoader.php | 7 ++- src/Service/BookingSummaryDataService.php | 67 +++++++++++++++------- src/Service/CmsDataService.php | 42 ++++++++++++++ templates/booking/_summary_hotel.html.twig | 14 +++-- 4 files changed, 101 insertions(+), 29 deletions(-) diff --git a/src/BusProNet/XmlLoader/HotelLoader.php b/src/BusProNet/XmlLoader/HotelLoader.php index dcae452..76b7fc2 100644 --- a/src/BusProNet/XmlLoader/HotelLoader.php +++ b/src/BusProNet/XmlLoader/HotelLoader.php @@ -36,12 +36,17 @@ class HotelLoader extends AbstractLoader } public function mapCodeToId(string $hotelCode, ?string $filename = 'hotel.xml'): ?int + { + return $this->loadByCode($hotelCode, $filename)?->id; + } + + public function loadByCode(string $hotelCode, ?string $filename = 'hotel.xml'): ?Hotel { $hotels = $this->loadAll($filename); foreach ($hotels as $hotel) { if ($hotelCode === $hotel->code) { - return $hotel->id; + return $hotel; } } diff --git a/src/Service/BookingSummaryDataService.php b/src/Service/BookingSummaryDataService.php index 7b93ce4..b894867 100644 --- a/src/Service/BookingSummaryDataService.php +++ b/src/Service/BookingSummaryDataService.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace App\Service; +use App\BusProNet\Model\Hotel; +use App\BusProNet\XmlLoader\HotelLoader; use App\Form\Model\BookingDto; use App\Form\Model\BookingSummaryDto; use Psr\Log\LoggerInterface; @@ -21,6 +23,7 @@ class BookingSummaryDataService public function __construct( private readonly BookingPriceCalculatorService $priceCalculator, private readonly CmsDataService $cmsDataService, + private readonly HotelLoader $hotelLoader, private readonly CacheInterface $cache, private readonly LoggerInterface $logger, ) { @@ -128,43 +131,49 @@ class BookingSummaryDataService } /** - * Fetches CMS data for a product and hotel combination. + * Fetches hotel display data combining local base hotel data with CMS images. * - * 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. + * Uses normalized codes (first 3 characters, or characters 4-6 for 'SER' codes) + * for both local hotel lookup and CMS image retrieval. 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): ?array { - if (null === $productCode) { + $normalizedHotelCode = $this->cmsDataService->normalizeCode($hotelCode); + + if (null === $normalizedHotelCode) { return null; } - $cacheKey = sprintf('cms_data.%s.%s', $productCode, $hotelCode ?? 'none'); + $cacheKey = sprintf('cms_data.%s.%s', $normalizedHotelCode, $normalizedHotelCode); try { - return $this->cache->get($cacheKey, function (ItemInterface $item) use ($productCode, $hotelCode) { + return $this->cache->get($cacheKey, function (ItemInterface $item) use ($productCode, $hotelCode, $normalizedHotelCode) { $item->expiresAfter(3600); // 1 hour - $result = $this->cmsDataService->getProductDetails($productCode, $hotelCode); + // Fetch base hotel from local BusProNet data + $baseHotel = $this->hotelLoader->loadByCode($normalizedHotelCode); - // 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', - ]); + // Fetch CMS images (nice to have) + $images = $this->cmsDataService->getProductImages($productCode, $hotelCode); - return null; - } - - return $result; + // Return combined data structure compatible with templates + return [ + 'hotel' => [ + 'name' => $baseHotel?->name, + 'address' => $this->formatHotelAddress($baseHotel), + 'images' => $images, + ], + ]; }); } catch (\Throwable $e) { - $this->logger->error('Failed to fetch CMS data', [ + $this->logger->error('Failed to fetch hotel display data', [ 'product_code' => $productCode, 'hotel_code' => $hotelCode, + 'normalized_hotel_code' => $normalizedHotelCode, 'exception' => $e->getMessage(), ]); @@ -172,6 +181,20 @@ class BookingSummaryDataService } } + /** + * Formats a hotel's address from street and city. + */ + private function formatHotelAddress(?Hotel $hotel): ?string + { + if (null === $hotel) { + return null; + } + + $parts = array_filter([$hotel->street, $hotel->city]); + + return [] === $parts ? null : implode("\n", $parts); + } + /** * Fetches CMS data for the product and hotel in the booking. */ @@ -180,8 +203,8 @@ class BookingSummaryDataService $productCode = $bookingDto->travel->productCode; $hotelCode = $bookingDto->travel->hotel?->code; - if (null === $productCode) { - $this->logger->debug('Cannot fetch CMS data: product code is not available', [ + if (null === $hotelCode) { + $this->logger->debug('Cannot fetch hotel data: hotel code is not available', [ 'travel_id' => $bookingDto->travel->id, ]); diff --git a/src/Service/CmsDataService.php b/src/Service/CmsDataService.php index fb44822..2fb3bbd 100644 --- a/src/Service/CmsDataService.php +++ b/src/Service/CmsDataService.php @@ -11,6 +11,48 @@ class CmsDataService { } + /** + * Normalizes a product or hotel code for CMS lookup. + * + * Returns the first 3 characters of the code, except for codes + * starting with 'SER' where characters 4-6 are returned instead. + */ + public function normalizeCode(?string $code): ?string + { + if (null === $code || 3 > strlen($code)) { + return null; + } + + if (str_starts_with($code, 'SER') && 6 <= strlen($code)) { + return substr($code, 3, 3); + } + + return substr($code, 0, 3); + } + + /** + * Fetches only the hotel images from the CMS using normalized codes. + * + * @return array|null The images array or null if unavailable + */ + public function getProductImages(string $productCode, ?string $hotelCode = null): ?array + { + $normalizedProduct = $this->normalizeCode($productCode); + $normalizedHotel = $this->normalizeCode($hotelCode); + + if (null === $normalizedProduct) { + return null; + } + + $result = $this->getProductDetails($normalizedProduct, $normalizedHotel); + + if (true === isset($result['success']) && false === $result['success']) { + return null; + } + + return $result['hotel']['images'] ?? null; + } + public function getProductDetails(string $productCode, ?string $hotelCode = null): array { try { diff --git a/templates/booking/_summary_hotel.html.twig b/templates/booking/_summary_hotel.html.twig index 3e758db..abd81b1 100644 --- a/templates/booking/_summary_hotel.html.twig +++ b/templates/booking/_summary_hotel.html.twig @@ -1,11 +1,13 @@ -{% if summaryData.cmsData.hotel.images is defined %} +{% if summaryData.cmsData.hotel.name %}
- {{ summaryData.cmsData.hotel.images.resized.l[0].alt }} -
+ {% if summaryData.cmsData.hotel.images.resized.l[0] is defined %} + {{ summaryData.cmsData.hotel.images.resized.l[0].alt }} + {% endif %} +
{{ summaryData.cmsData.hotel.name }} - {% if summaryData.cmsData.hotel.address is defined %} + {% if summaryData.cmsData.hotel.address %} {{ summaryData.cmsData.hotel.address | nl2br }} {% endif %}