Add forecast occupancy logic for room availability range APIs
All checks were successful
build-and-deploy / build-deploy (push) Successful in 37s

This commit is contained in:
androidlover5842
2026-02-04 17:22:28 +05:30
parent 0a65e022e0
commit 2950af3332
3 changed files with 59 additions and 2 deletions

View File

@@ -1004,6 +1004,8 @@ ROOM TYPE + ROOMS + ROOM IMAGES
What it does:
- Returns free rooms in date range.
- Uses forecast logic for active stays: if `toAt` is null, occupancy is considered up to booking `expectedCheckOutAt` (if present).
- Active stays with no expected checkout are treated as occupied.
Request body:
@@ -1031,6 +1033,7 @@ ROOM TYPE + ROOMS + ROOM IMAGES
What it does:
- Returns available rooms with average rate over date range.
- Uses same forecast occupancy logic as availability-range.
Request body:

View File

@@ -204,7 +204,7 @@ class Rooms(
val toAt = toDate.atStartOfDay(zone).toOffsetDateTime()
val rooms = roomRepo.findByPropertyIdOrderByRoomNumber(propertyId)
val occupiedRoomIds = roomStayRepo.findOccupiedRoomIdsBetween(propertyId, fromAt, toAt).toHashSet()
val occupiedRoomIds = findForecastOccupiedRoomIds(propertyId, fromAt, toAt)
val freeRooms = rooms.filter { it.active && !it.maintenance && !occupiedRoomIds.contains(it.id) }
val grouped = freeRooms.groupBy { it.roomType.name }
@@ -242,7 +242,7 @@ class Rooms(
val toAt = toDate.atStartOfDay(zone).toOffsetDateTime()
val rooms = roomRepo.findByPropertyIdOrderByRoomNumber(propertyId)
val occupiedRoomIds = roomStayRepo.findOccupiedRoomIdsBetween(propertyId, fromAt, toAt).toHashSet()
val occupiedRoomIds = findForecastOccupiedRoomIds(propertyId, fromAt, toAt)
val freeRooms = rooms.filter { it.active && !it.maintenance && !occupiedRoomIds.contains(it.id) }
if (freeRooms.isEmpty()) return emptyList()
@@ -447,6 +447,23 @@ class Rooms(
.mapValues { (_, cards) -> cards.maxBy { it.expiresAt }.expiresAt }
}
private fun findForecastOccupiedRoomIds(
propertyId: UUID,
fromAt: OffsetDateTime,
toAt: OffsetDateTime
): Set<UUID> {
val occupied = roomStayRepo.findOccupiedRoomIdsBetweenClosed(propertyId, fromAt, toAt).toMutableSet()
val activeRows = roomStayRepo.findActiveForecastRows(propertyId, toAt)
activeRows
.asSequence()
.filter { row ->
val expectedCheckoutAt = row.expectedCheckoutAt
expectedCheckoutAt == null || expectedCheckoutAt.isAfter(fromAt)
}
.mapTo(occupied) { it.roomId }
return occupied
}
private fun prepareSse(response: HttpServletResponse) {
response.setHeader("Cache-Control", "no-cache")
response.setHeader("Connection", "keep-alive")

View File

@@ -30,6 +30,37 @@ interface RoomStayRepo : JpaRepository<RoomStay, UUID> {
@Param("toAt") toAt: java.time.OffsetDateTime
): List<UUID>
@Query("""
select distinct rs.room.id
from RoomStay rs
where rs.property.id = :propertyId
and rs.isVoided = false
and rs.toAt is not null
and rs.fromAt < :toAt
and rs.toAt > :fromAt
""")
fun findOccupiedRoomIdsBetweenClosed(
@Param("propertyId") propertyId: UUID,
@Param("fromAt") fromAt: java.time.OffsetDateTime,
@Param("toAt") toAt: java.time.OffsetDateTime
): List<UUID>
@Query("""
select rs.room.id as roomId,
rs.fromAt as fromAt,
b.expectedCheckoutAt as expectedCheckoutAt
from RoomStay rs
join rs.booking b
where rs.property.id = :propertyId
and rs.isVoided = false
and rs.toAt is null
and rs.fromAt < :toAt
""")
fun findActiveForecastRows(
@Param("propertyId") propertyId: UUID,
@Param("toAt") toAt: java.time.OffsetDateTime
): List<ActiveRoomStayForecastRow>
@Query("""
select rs
from RoomStay rs
@@ -163,3 +194,9 @@ interface BookingRoomNumberRow {
val bookingId: UUID
val roomNumber: Int
}
interface ActiveRoomStayForecastRow {
val roomId: UUID
val fromAt: java.time.OffsetDateTime
val expectedCheckoutAt: java.time.OffsetDateTime?
}