From 9b9eeff8714537ecba7180ae2b8fc84e9318a171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Wed, 10 Jun 2026 14:40:28 +0200 Subject: [PATCH] feat: integrate with new bpn-connect api for groups swiss feed --- .../Classes/Service/GroupsSwissService.php | 192 ++++++++++-------- 1 file changed, 112 insertions(+), 80 deletions(-) diff --git a/public/typo3conf/ext/ep_products/Classes/Service/GroupsSwissService.php b/public/typo3conf/ext/ep_products/Classes/Service/GroupsSwissService.php index 0a1d25f1..4b139422 100644 --- a/public/typo3conf/ext/ep_products/Classes/Service/GroupsSwissService.php +++ b/public/typo3conf/ext/ep_products/Classes/Service/GroupsSwissService.php @@ -26,10 +26,7 @@ namespace EP\EpProducts\Service; * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ -use Doctrine\DBAL\Exception as DBALException; -use EP\EpProducts\Domain\Model\Contingent; -use EP\EpProducts\Domain\Model\Hotel; -use EP\EpProducts\Domain\Repository\ContingentRepository; +use EP\EpProducts\BpnConnect\ApiClient; use EP\EpProducts\Domain\Repository\HotelRepository; use League\Period\Period; use Psr\Log\LoggerAwareInterface; @@ -48,9 +45,9 @@ class GroupsSwissService implements SingletonInterface, LoggerAwareInterface protected $hotelRepository; /** - * @var ContingentRepository + * @var ApiClient */ - protected $contingentRepository; + protected $apiClient; /** * @var HttpClient @@ -59,12 +56,12 @@ class GroupsSwissService implements SingletonInterface, LoggerAwareInterface /** * @param HotelRepository $hotelRepository - * @param ContingentRepository $contingentRepository + * @param ApiClient $apiClient */ - public function __construct(HotelRepository $hotelRepository, ContingentRepository $contingentRepository) + public function __construct(HotelRepository $hotelRepository, ApiClient $apiClient) { $this->hotelRepository = $hotelRepository; - $this->contingentRepository = $contingentRepository; + $this->apiClient = $apiClient; $this->httpClient = HttpClient::create(); } @@ -79,27 +76,31 @@ class GroupsSwissService implements SingletonInterface, LoggerAwareInterface return false; } - try { - $events = $this->contingentRepository->getEvents( - \DateTime::createFromImmutable($range->getStartDate()), - \DateTime::createFromImmutable($range->getEndDate()), - ['hotel' => $hotel] - ); + $hotelCode = trim((string) $hotel->getCode()); + if ('' === $hotelCode) { + $this->logger->error(sprintf('Hotel with uid %d has no hotel code', $hotelUid)); + return false; } - catch (DBALException $e) { - $this->logger->error(sprintf('No dates found for hotel with uid %d', $hotelUid)); + + try { + $calendar = $this->apiClient->getCalendar( + $hotelCode, + \DateTime::createFromImmutable($range->getStartDate()), + \DateTime::createFromImmutable($range->getEndDate()->sub(new \DateInterval('P1D'))) + ); + $calendarRows = isset($calendar['data']) && is_array($calendar['data']) ? $calendar['data'] : []; + } catch (\Throwable $e) { + $this->logger->error(sprintf('No dates found for hotel with uid %d', $hotelUid), ['exception' => $e]); return false; } $this->resetChart($range, $apiKey, $houseNumber); - if (count($events) > 0) { - $this->generateChart( - $events, - $apiKey, - $houseNumber - ); - } + $this->generateChart( + $calendarRows, + $apiKey, + $houseNumber + ); return true; } @@ -120,78 +121,72 @@ class GroupsSwissService implements SingletonInterface, LoggerAwareInterface } /** - * @param array $contingents + * @param array $calendarRows * @param string $apiKey * @param int $houseNumber */ - protected function generateChart(array $contingents, $apiKey, $houseNumber): void + protected function generateChart(array $calendarRows, $apiKey, $houseNumber): void { + usort($calendarRows, function (array $left, array $right): int { + return strcmp((string) ($left['date'] ?? ''), (string) ($right['date'] ?? '')); + }); + $item = null; $itemStatus = null; + $lastDate = null; - // Iterate over all contingents - foreach ($contingents as $contingent) { - /** @var Contingent $contingent */ - // Get actual or 'virtual' status of current contingent - $contingentStatus = $this->getContingentStatus($contingent); + foreach ($calendarRows as $row) { + if (!isset($row['date'])) { + continue; + } - if (null === $item && Contingent::STATUS_OK !== $contingentStatus) { - // Make contingent status the current status - $itemStatus = $contingentStatus; - // Create fresh chart item - $item = [ - 'from' => $contingent->getBegin(), - 'status' => $contingentStatus, - ]; - } elseif (null !== $item && $itemStatus !== $contingentStatus) { - // Set end date of current chart item to previous day when following - // status is OK - $item['to'] = Contingent::STATUS_OK !== $contingentStatus ? - $contingent->getEnd() : $contingent->getEnd()->modify('-1 day'); - // Translate status code for Groups.swiss - $statusCode = Contingent::STATUS_ONREQUEST === $item['status'] ? 1 : 2; - // Post chart item to API - $this->addItem($item, $statusCode, $apiKey, $houseNumber); - // Make contingent status the current status - $itemStatus = $contingentStatus; - // Reset chart item or create fresh one depending on new status - if (Contingent::STATUS_OK === $contingentStatus) { + $currentDate = new \DateTimeImmutable($row['date']); + $currentStatus = $this->normalizeStatus((string) ($row['status'] ?? 'OK')); + + if (null !== $lastDate && $currentDate > $lastDate->add(new \DateInterval('P1D'))) { + if (null !== $item) { + $this->addItem($item, $this->translateStatusToGroupsSwissState($itemStatus), $apiKey, $houseNumber); $item = null; - } else { - $item = [ - 'from' => $contingent->getBegin(), - 'status' => $contingentStatus, - ]; + $itemStatus = null; } } - } - } - /** - * @param Contingent $contingent - * @return int - */ - protected function getContingentStatus(Contingent $contingent): int - { - $contingentStatus = $contingent->getStatus(); + if ('OK' === $currentStatus) { + if (null !== $item) { + $item['to'] = $lastDate; + $this->addItem($item, $this->translateStatusToGroupsSwissState($itemStatus), $apiKey, $houseNumber); + $item = null; + $itemStatus = null; + } + $lastDate = $currentDate; + continue; + } - // All status other than OK are to be applied immediately - if (Contingent::STATUS_BLOCKED === $contingentStatus || Contingent::STATUS_ONREQUEST === $contingentStatus) { - return $contingentStatus; + if (null === $item) { + $item = [ + 'from' => $currentDate, + 'to' => $currentDate, + ]; + $itemStatus = $currentStatus; + } elseif ($itemStatus !== $currentStatus) { + $item['to'] = $lastDate; + $this->addItem($item, $this->translateStatusToGroupsSwissState($itemStatus), $apiKey, $houseNumber); + $item = [ + 'from' => $currentDate, + 'to' => $currentDate, + ]; + $itemStatus = $currentStatus; + } else { + $item['to'] = $currentDate; + } + + $lastDate = $currentDate; } - // Determine 'virtual' status depending on availability - $percentageAvailable = $contingent->getPercentageAvailable(); - - if ($percentageAvailable <= 20) { - return Contingent::STATUS_BLOCKED; + if (null !== $item) { + $item['to'] = $lastDate; + $this->addItem($item, $this->translateStatusToGroupsSwissState($itemStatus), $apiKey, $houseNumber); } - - if ($percentageAvailable <= 80) { - return $contingent::STATUS_ONREQUEST; - } - - return Contingent::STATUS_OK; } /** @@ -232,4 +227,41 @@ class GroupsSwissService implements SingletonInterface, LoggerAwareInterface $this->logger->error(sprintf('A problem occured: %s', $e->getMessage())); } } + + /** + * @param string $status + * @return string + */ + protected function normalizeStatus(string $status): string + { + $status = strtoupper(trim($status)); + if ('ONREQUEST' === $status) { + return 'ON_REQUEST'; + } + + if (in_array($status, [ 'OK', 'ON_REQUEST', 'BLOCKED' ], true)) { + return $status; + } + + return 'OK'; + } + + /** + * @param string $status + * @return int + */ + protected function translateStatusToGroupsSwissState(string $status): int + { + $status = $this->normalizeStatus($status); + + if ('ON_REQUEST' === $status) { + return 1; + } + + if ('BLOCKED' === $status) { + return 2; + } + + return 0; + } }