feat: integrate with new bpn-connect api for groups swiss feed

This commit is contained in:
Björn Fromme
2026-06-10 14:42:49 +02:00
parent 960aff0e40
commit 9b9eeff871
@@ -26,10 +26,7 @@ namespace EP\EpProducts\Service;
* This copyright notice MUST APPEAR in all copies of the script! * This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/ ***************************************************************/
use Doctrine\DBAL\Exception as DBALException; use EP\EpProducts\BpnConnect\ApiClient;
use EP\EpProducts\Domain\Model\Contingent;
use EP\EpProducts\Domain\Model\Hotel;
use EP\EpProducts\Domain\Repository\ContingentRepository;
use EP\EpProducts\Domain\Repository\HotelRepository; use EP\EpProducts\Domain\Repository\HotelRepository;
use League\Period\Period; use League\Period\Period;
use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareInterface;
@@ -48,9 +45,9 @@ class GroupsSwissService implements SingletonInterface, LoggerAwareInterface
protected $hotelRepository; protected $hotelRepository;
/** /**
* @var ContingentRepository * @var ApiClient
*/ */
protected $contingentRepository; protected $apiClient;
/** /**
* @var HttpClient * @var HttpClient
@@ -59,12 +56,12 @@ class GroupsSwissService implements SingletonInterface, LoggerAwareInterface
/** /**
* @param HotelRepository $hotelRepository * @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->hotelRepository = $hotelRepository;
$this->contingentRepository = $contingentRepository; $this->apiClient = $apiClient;
$this->httpClient = HttpClient::create(); $this->httpClient = HttpClient::create();
} }
@@ -79,27 +76,31 @@ class GroupsSwissService implements SingletonInterface, LoggerAwareInterface
return false; return false;
} }
try { $hotelCode = trim((string) $hotel->getCode());
$events = $this->contingentRepository->getEvents( if ('' === $hotelCode) {
\DateTime::createFromImmutable($range->getStartDate()), $this->logger->error(sprintf('Hotel with uid %d has no hotel code', $hotelUid));
\DateTime::createFromImmutable($range->getEndDate()), return false;
['hotel' => $hotel]
);
} }
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; return false;
} }
$this->resetChart($range, $apiKey, $houseNumber); $this->resetChart($range, $apiKey, $houseNumber);
if (count($events) > 0) { $this->generateChart(
$this->generateChart( $calendarRows,
$events, $apiKey,
$apiKey, $houseNumber
$houseNumber );
);
}
return true; return true;
} }
@@ -120,78 +121,72 @@ class GroupsSwissService implements SingletonInterface, LoggerAwareInterface
} }
/** /**
* @param array $contingents * @param array $calendarRows
* @param string $apiKey * @param string $apiKey
* @param int $houseNumber * @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; $item = null;
$itemStatus = null; $itemStatus = null;
$lastDate = null;
// Iterate over all contingents foreach ($calendarRows as $row) {
foreach ($contingents as $contingent) { if (!isset($row['date'])) {
/** @var Contingent $contingent */ continue;
// Get actual or 'virtual' status of current contingent }
$contingentStatus = $this->getContingentStatus($contingent);
if (null === $item && Contingent::STATUS_OK !== $contingentStatus) { $currentDate = new \DateTimeImmutable($row['date']);
// Make contingent status the current status $currentStatus = $this->normalizeStatus((string) ($row['status'] ?? 'OK'));
$itemStatus = $contingentStatus;
// Create fresh chart item if (null !== $lastDate && $currentDate > $lastDate->add(new \DateInterval('P1D'))) {
$item = [ if (null !== $item) {
'from' => $contingent->getBegin(), $this->addItem($item, $this->translateStatusToGroupsSwissState($itemStatus), $apiKey, $houseNumber);
'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) {
$item = null; $item = null;
} else { $itemStatus = null;
$item = [
'from' => $contingent->getBegin(),
'status' => $contingentStatus,
];
} }
} }
}
}
/** if ('OK' === $currentStatus) {
* @param Contingent $contingent if (null !== $item) {
* @return int $item['to'] = $lastDate;
*/ $this->addItem($item, $this->translateStatusToGroupsSwissState($itemStatus), $apiKey, $houseNumber);
protected function getContingentStatus(Contingent $contingent): int $item = null;
{ $itemStatus = null;
$contingentStatus = $contingent->getStatus(); }
$lastDate = $currentDate;
continue;
}
// All status other than OK are to be applied immediately if (null === $item) {
if (Contingent::STATUS_BLOCKED === $contingentStatus || Contingent::STATUS_ONREQUEST === $contingentStatus) { $item = [
return $contingentStatus; '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 if (null !== $item) {
$percentageAvailable = $contingent->getPercentageAvailable(); $item['to'] = $lastDate;
$this->addItem($item, $this->translateStatusToGroupsSwissState($itemStatus), $apiKey, $houseNumber);
if ($percentageAvailable <= 20) {
return Contingent::STATUS_BLOCKED;
} }
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())); $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;
}
} }