*/ class TravelSnapshotRepository extends ServiceEntityRepository { public function __construct(ManagerRegistry $registry) { parent::__construct($registry, TravelSnapshot::class); } /** * Returns the exact snapshot for a travel date and hotel combination. */ public function findByDateAndHotel(int $dateId, int $hotelId): ?TravelSnapshot { return $this->findOneBy([ 'dateId' => $dateId, 'hotelId' => $hotelId, ]); } /** * Returns a fallback snapshot for the given date when hotel-specific lookup misses. */ public function findFirstByDateId(int $dateId): ?TravelSnapshot { return $this->createQueryBuilder('s') ->where('s.dateId = :dateId') ->setParameter('dateId', $dateId) ->orderBy('s.id', 'ASC') ->setMaxResults(1) ->getQuery() ->getOneOrNullResult(); } /** * Returns the first known product ID for a travel date, if available. */ public function findProductIdByDateId(int $dateId): ?int { $value = $this->createQueryBuilder('s') ->select('s.productId') ->where('s.dateId = :dateId') ->andWhere('s.productId IS NOT NULL') ->setParameter('dateId', $dateId) ->setMaxResults(1) ->getQuery() ->getOneOrNullResult(); if (null === $value) { return null; } return (int) $value['productId']; } /** * Returns all snapshots sorted deterministically for mapping aggregation. * * @return array */ public function findAllForMapping(?int $limit = null): array { $qb = $this->createQueryBuilder('s') ->orderBy('s.dateId', 'ASC') ->addOrderBy('s.hotelId', 'ASC'); if (null !== $limit) { $qb->setMaxResults($limit); } return $qb->getQuery()->getResult(); } /** * Returns snapshots eligible for extended availability refresh. * * @return array */ public function findRefreshCandidates( \DateTimeImmutable $dateToThreshold, \DateTimeImmutable $refreshBefore, int $limit, ): array { return $this->createQueryBuilder('s') // dateTo IS NULL: snapshots without a known end date are always considered active. ->where('s.dateTo IS NULL OR s.dateTo >= :dateToThreshold') // extendedRefreshedAt IS NULL: never-refreshed snapshots rank as oldest. ->andWhere('s.extendedRefreshedAt IS NULL OR s.extendedRefreshedAt < :refreshBefore') ->setParameter('dateToThreshold', $dateToThreshold) ->setParameter('refreshBefore', $refreshBefore) ->orderBy('s.extendedRefreshedAt', 'ASC') ->addOrderBy('s.id', 'ASC') ->setMaxResults($limit) ->getQuery() ->getResult(); } /** * Deletes snapshots whose travel end date is older than the given threshold. */ public function deleteExpiredSnapshots(\DateTimeImmutable $beforeDate): int { return (int) $this->createQueryBuilder('s') ->delete() // Snapshots with no known end date are excluded from purging as a safety net; // they must be cleaned up manually if they become stale. ->where('s.dateTo IS NOT NULL') ->andWhere('s.dateTo < :beforeDate') ->setParameter('beforeDate', $beforeDate) ->getQuery() ->execute(); } }