feat: sync contingents for TYPO3 calendar hotels
This commit is contained in:
1 parent
ec36b61275
commit
97c6330a9a
24 files changed
+1067
-180
No files matched your search
@@ -11,7 +11,9 @@ use App\Model\ContingentCalendarQuery;
|
||||
use App\Model\ContingentPricesQuery;
|
||||
use App\Repository\Groups\AccommodationPriceRepository;
|
||||
use App\Repository\Groups\AccommodationRepository;
|
||||
use App\Repository\Groups\ContingentSyncStateRepository;
|
||||
use App\Service\AccommodationPriceCoverage;
|
||||
use App\Service\ContingentHotelDirectory;
|
||||
use App\Service\ContingentSnapshotReader;
|
||||
use App\Service\PriceTimelineBuilder;
|
||||
use Carbon\CarbonImmutable;
|
||||
@@ -33,6 +35,8 @@ class ContingentController extends AbstractController
|
||||
private readonly ContingentSnapshotReader $snapshotReader,
|
||||
private readonly PriceTimelineBuilder $priceTimelineBuilder,
|
||||
private readonly AccommodationPriceCoverage $priceCoverage,
|
||||
private readonly ContingentHotelDirectory $hotelDirectory,
|
||||
private readonly ContingentSyncStateRepository $syncStateRepository,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
@@ -107,7 +111,7 @@ class ContingentController extends AbstractController
|
||||
// An empty or stale snapshot must not be served as though every day were blocked: that
|
||||
// is a plausible-looking 200 nobody can distinguish from real data. Fail the way this
|
||||
// endpoint always failed instead, so existing consumers need no change.
|
||||
$statuses = $this->snapshotReader->statusesFor($accommodation, $dateFrom, $dateTo);
|
||||
$statuses = $this->snapshotReader->statusesFor($query->hotelCode, $dateFrom, $dateTo);
|
||||
|
||||
if (null === $statuses) {
|
||||
return $this->json(['error' => 'Failed to fetch contingent data.'], Response::HTTP_BAD_GATEWAY);
|
||||
@@ -137,15 +141,16 @@ class ContingentController extends AbstractController
|
||||
* The snapshot as it is, for the iCal feed: no price rule, and days the snapshot lacks are
|
||||
* left out rather than blocked, so a portal shows them as free like it did with the feed
|
||||
* that read bpn-connect directly.
|
||||
*
|
||||
* Serves every synced hotel code, including TYPO3's calendar hotels that have no
|
||||
* accommodation (winter-only hotels).
|
||||
*/
|
||||
#[Route(path: '/contingents/availability', name: 'api_contingents_availability', methods: ['GET'])]
|
||||
public function availability(
|
||||
#[MapQueryString(validationFailedStatusCode: Response::HTTP_BAD_REQUEST)]
|
||||
ContingentCalendarQuery $query,
|
||||
): JsonResponse {
|
||||
$accommodation = $this->accommodationRepository->findOneBy(['calendarCode' => $query->hotelCode]);
|
||||
|
||||
if (null === $accommodation) {
|
||||
if (null === $this->hotelDirectory->find($query->hotelCode)) {
|
||||
$this->logger->error('Hotel not found for hotelcode '.$query->hotelCode);
|
||||
|
||||
return $this->json(['error' => 'Hotel not found for hotelCode.'], Response::HTTP_BAD_REQUEST);
|
||||
@@ -157,7 +162,7 @@ class ContingentController extends AbstractController
|
||||
|
||||
// Same reasoning as calendar(): a missing or stale snapshot must not pass for real data.
|
||||
// Here it matters even more, since an empty feed would show every day as free.
|
||||
$statuses = $this->snapshotReader->statusesFor($accommodation, $dateFrom, $dateTo);
|
||||
$statuses = $this->snapshotReader->statusesFor($query->hotelCode, $dateFrom, $dateTo);
|
||||
|
||||
if (null === $statuses) {
|
||||
return $this->json(['error' => 'Failed to fetch contingent data.'], Response::HTTP_BAD_GATEWAY);
|
||||
@@ -174,6 +179,44 @@ class ContingentController extends AbstractController
|
||||
return $this->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* One row per synced hotel code, for the overview of which hotel supports which calendar.
|
||||
*
|
||||
* `available` is whether `availability` currently serves the hotel; calendar and price table
|
||||
* additionally need an accommodation with prices, which `pricedFrom`/`pricedTo` show.
|
||||
*/
|
||||
#[Route(path: '/contingents/status', name: 'api_contingents_status', methods: ['GET'])]
|
||||
public function status(): JsonResponse
|
||||
{
|
||||
$states = $this->syncStateRepository->findAllIndexedByHotelCode();
|
||||
|
||||
$data = [];
|
||||
|
||||
foreach ($this->hotelDirectory->all() as $hotel) {
|
||||
$state = $states[$hotel->code] ?? null;
|
||||
$accommodation = $hotel->accommodation;
|
||||
$bounds = null !== $accommodation ? $this->priceRepository->findPricedDateBoundsByHotelCode($hotel->code) : null;
|
||||
|
||||
$data[] = [
|
||||
'hotelCode' => $hotel->code,
|
||||
'name' => $hotel->calendarHotel?->getName(),
|
||||
'sources' => array_map(static fn ($source) => $source->value, $hotel->sources()),
|
||||
'accommodation' => null !== $accommodation ? [
|
||||
'name' => $accommodation->getName(),
|
||||
'cmsCode' => $accommodation->getEffectiveCmsCode(),
|
||||
] : null,
|
||||
'pricedFrom' => null !== $bounds ? $bounds['from']->format('Y-m-d') : null,
|
||||
'pricedTo' => null !== $bounds ? $bounds['to']->format('Y-m-d') : null,
|
||||
'syncedAt' => $state?->getSyncedAt()?->format(\DateTimeInterface::ATOM),
|
||||
'lastError' => $state?->getLastError(),
|
||||
'failureCount' => $state?->getFailureCount() ?? 0,
|
||||
'available' => $this->snapshotReader->isCurrent($state),
|
||||
];
|
||||
}
|
||||
|
||||
return $this->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AccommodationPrice[] $prices
|
||||
* @param array<string, true> $covered
|
||||
|
||||
Reference in new issue
Block a user