Add forecast occupancy logic for room availability range APIs
All checks were successful
build-and-deploy / build-deploy (push) Successful in 37s
All checks were successful
build-and-deploy / build-deploy (push) Successful in 37s
This commit is contained in:
@@ -1004,6 +1004,8 @@ ROOM TYPE + ROOMS + ROOM IMAGES
|
|||||||
What it does:
|
What it does:
|
||||||
|
|
||||||
- Returns free rooms in date range.
|
- 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:
|
Request body:
|
||||||
|
|
||||||
@@ -1031,6 +1033,7 @@ ROOM TYPE + ROOMS + ROOM IMAGES
|
|||||||
What it does:
|
What it does:
|
||||||
|
|
||||||
- Returns available rooms with average rate over date range.
|
- Returns available rooms with average rate over date range.
|
||||||
|
- Uses same forecast occupancy logic as availability-range.
|
||||||
|
|
||||||
Request body:
|
Request body:
|
||||||
|
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ class Rooms(
|
|||||||
val toAt = toDate.atStartOfDay(zone).toOffsetDateTime()
|
val toAt = toDate.atStartOfDay(zone).toOffsetDateTime()
|
||||||
|
|
||||||
val rooms = roomRepo.findByPropertyIdOrderByRoomNumber(propertyId)
|
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 freeRooms = rooms.filter { it.active && !it.maintenance && !occupiedRoomIds.contains(it.id) }
|
||||||
val grouped = freeRooms.groupBy { it.roomType.name }
|
val grouped = freeRooms.groupBy { it.roomType.name }
|
||||||
@@ -242,7 +242,7 @@ class Rooms(
|
|||||||
val toAt = toDate.atStartOfDay(zone).toOffsetDateTime()
|
val toAt = toDate.atStartOfDay(zone).toOffsetDateTime()
|
||||||
|
|
||||||
val rooms = roomRepo.findByPropertyIdOrderByRoomNumber(propertyId)
|
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 freeRooms = rooms.filter { it.active && !it.maintenance && !occupiedRoomIds.contains(it.id) }
|
||||||
if (freeRooms.isEmpty()) return emptyList()
|
if (freeRooms.isEmpty()) return emptyList()
|
||||||
|
|
||||||
@@ -447,6 +447,23 @@ class Rooms(
|
|||||||
.mapValues { (_, cards) -> cards.maxBy { it.expiresAt }.expiresAt }
|
.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) {
|
private fun prepareSse(response: HttpServletResponse) {
|
||||||
response.setHeader("Cache-Control", "no-cache")
|
response.setHeader("Cache-Control", "no-cache")
|
||||||
response.setHeader("Connection", "keep-alive")
|
response.setHeader("Connection", "keep-alive")
|
||||||
|
|||||||
@@ -30,6 +30,37 @@ interface RoomStayRepo : JpaRepository<RoomStay, UUID> {
|
|||||||
@Param("toAt") toAt: java.time.OffsetDateTime
|
@Param("toAt") toAt: java.time.OffsetDateTime
|
||||||
): List<UUID>
|
): 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("""
|
@Query("""
|
||||||
select rs
|
select rs
|
||||||
from RoomStay rs
|
from RoomStay rs
|
||||||
@@ -163,3 +194,9 @@ interface BookingRoomNumberRow {
|
|||||||
val bookingId: UUID
|
val bookingId: UUID
|
||||||
val roomNumber: Int
|
val roomNumber: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ActiveRoomStayForecastRow {
|
||||||
|
val roomId: UUID
|
||||||
|
val fromAt: java.time.OffsetDateTime
|
||||||
|
val expectedCheckoutAt: java.time.OffsetDateTime?
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user