feat: add raw contingent availability endpoint

This commit is contained in:
fromme committed 2026-10-29 11:19:31 +01:00
1 parent 7c8dbeef48
commit ec36b61275
6 files changed
+189 -12

No files matched your search

@@ -133,6 +133,47 @@ class ContingentController extends AbstractController
return $this->json($data);
}
/**
* 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.
*/
#[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) {
$this->logger->error('Hotel not found for hotelcode '.$query->hotelCode);
return $this->json(['error' => 'Hotel not found for hotelCode.'], Response::HTTP_BAD_REQUEST);
}
// Without a range: from today to wherever the snapshot ends.
$dateFrom = $query->dateFromDate() ?? CarbonImmutable::now()->setTime(0, 0);
$dateTo = $query->dateToDate();
// 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);
if (null === $statuses) {
return $this->json(['error' => 'Failed to fetch contingent data.'], Response::HTTP_BAD_GATEWAY);
}
ksort($statuses);
$data = [];
foreach ($statuses as $date => $status) {
$data[] = ['date' => (string) $date, 'status' => $status->value];
}
return $this->json($data);
}
/**
* @param AccommodationPrice[] $prices
* @param array<string, true> $covered
+1 -1
View File
@@ -34,7 +34,7 @@ final readonly class ContingentCalendarQuery
$hasFrom = null !== $from && '' !== $from;
$hasTo = null !== $to && '' !== $to;
// Neither supplied: the caller wants the full priced span, which the persisted prices define.
// Neither supplied: the caller wants the full span, which each endpoint defines for itself.
if (!$hasFrom && !$hasTo) {
return;
}
@@ -21,27 +21,31 @@ class ContingentDayRepository extends ServiceEntityRepository
/**
* Returns the snapshot days within [dateFrom, dateTo] for the accommodation
* identified by the given hotel code, keyed by Y-m-d.
* identified by the given hotel code, keyed by Y-m-d. A null dateTo means
* everything from dateFrom to the end of the snapshot.
*
* @return array<string, ContingentDay>
*/
public function findByHotelCodeAndDateRange(
string $hotelCode,
\DateTimeImmutable $dateFrom,
\DateTimeImmutable $dateTo,
?\DateTimeImmutable $dateTo,
): array {
/** @var ContingentDay[] $days */
$days = $this->createQueryBuilder('cd')
$qb = $this->createQueryBuilder('cd')
->join('cd.accommodation', 'a')
->where('a.calendarCode = :hotelCode')
->andWhere('cd.date >= :dateFrom')
->andWhere('cd.date <= :dateTo')
->setParameter('hotelCode', $hotelCode)
->setParameter('dateFrom', $dateFrom)
->setParameter('dateTo', $dateTo)
->orderBy('cd.date', 'ASC')
->getQuery()
->getResult();
->orderBy('cd.date', 'ASC');
if (null !== $dateTo) {
$qb->andWhere('cd.date <= :dateTo')
->setParameter('dateTo', $dateTo);
}
/** @var ContingentDay[] $days */
$days = $qb->getQuery()->getResult();
return $this->indexByDate($days);
}
+2 -2
View File
@@ -41,14 +41,14 @@ class ContingentSnapshotReader
* 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.
* what that means is the caller's decision. A null dateTo reads to the end of the snapshot.
*
* @return array<string, ContingentStatus>|null
*/
public function statusesFor(
Accommodation $accommodation,
\DateTimeImmutable $dateFrom,
\DateTimeImmutable $dateTo,
?\DateTimeImmutable $dateTo,
): ?array {
$hotelCode = $accommodation->getCalendarCode();