Files
myep/src/Service/ContingentSnapshotReader.php
T

94 lines
3.0 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Service;
use App\BpnConnect\Model\ContingentStatus;
use App\Entity\Groups\Accommodation;
use App\Repository\Groups\ContingentDayRepository;
use App\Repository\Groups\ContingentSyncStateRepository;
use Carbon\CarbonImmutable;
use Psr\Log\LoggerInterface;
/**
* Reads contingent availability out of the local snapshot, and says when it cannot be trusted.
*
* Callers get null rather than a silently empty result, because "no data" and "everything is
* blocked" are indistinguishable once the statuses are flattened — and the two current callers
* want opposite fallbacks: the API fails loud with a 502, the booking calendar falls back to
* price coverage alone.
*/
class ContingentSnapshotReader
{
/**
* How old the snapshot may get before we stop presenting it as current availability.
*
* The sync runs every 15 minutes during the day and hourly overnight, so even the sparsest
* cadence leaves five missed runs of slack: this only trips when the scheduler is genuinely
* broken, never on the normal cadence.
*/
private const string MAX_SNAPSHOT_AGE = '-6 hours';
public function __construct(
private readonly ContingentDayRepository $dayRepository,
private readonly ContingentSyncStateRepository $syncStateRepository,
private readonly LoggerInterface $logger,
) {
}
/**
* Per-day statuses keyed by Y-m-d, or null when there is no usable snapshot for this hotel.
*
* Days inside the range that the snapshot does not cover are simply absent from the map;
* what that means is the caller's decision.
*
* @return array<string, ContingentStatus>|null
*/
public function statusesFor(
Accommodation $accommodation,
\DateTimeImmutable $dateFrom,
\DateTimeImmutable $dateTo,
): ?array {
$hotelCode = $accommodation->getCalendarCode();
if (null === $hotelCode || '' === $hotelCode) {
return null;
}
if (!$this->isUsable($accommodation, $hotelCode)) {
return null;
}
$statuses = [];
foreach ($this->dayRepository->findByHotelCodeAndDateRange($hotelCode, $dateFrom, $dateTo) as $date => $day) {
$statuses[$date] = $day->getStatus();
}
return $statuses;
}
private function isUsable(Accommodation $accommodation, string $hotelCode): bool
{
$syncedAt = $this->syncStateRepository->findOneByAccommodation($accommodation)?->getSyncedAt();
if (null === $syncedAt) {
$this->logger->error('No contingent snapshot has been synced yet', ['hotelCode' => $hotelCode]);
return false;
}
if ($syncedAt < CarbonImmutable::now()->modify(self::MAX_SNAPSHOT_AGE)) {
$this->logger->error('Contingent snapshot is stale', [
'hotelCode' => $hotelCode,
'syncedAt' => $syncedAt->format(\DateTimeInterface::ATOM),
]);
return false;
}
return true;
}
}