Merge range rate data into availability-range and remove old rate endpoint
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,7 @@ ROOM TYPE + ROOMS + ROOM IMAGES
|
||||
What it does:
|
||||
|
||||
- Returns free rooms in date range.
|
||||
- Includes per-room-type average rate (`averageRate`, `currency`, `ratePlanCode`).
|
||||
- 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.
|
||||
|
||||
@@ -1013,34 +1014,6 @@ ROOM TYPE + ROOMS + ROOM IMAGES
|
||||
|
||||
Query params:
|
||||
|
||||
- from=YYYY-MM-DD
|
||||
- to=YYYY-MM-DD
|
||||
|
||||
- Allowed roles: property member.
|
||||
|
||||
Error Codes
|
||||
|
||||
- 400 Bad Request (invalid date/range)
|
||||
- 401 Unauthorized
|
||||
- 403 Forbidden
|
||||
- 404 Not Found (property)
|
||||
|
||||
|
||||
- Availability with rate API is this one:
|
||||
|
||||
GET /properties/{propertyId}/rooms/available-range-with-rate
|
||||
|
||||
What it does:
|
||||
|
||||
- Returns available rooms with average rate over date range.
|
||||
- Uses same forecast occupancy logic as availability-range.
|
||||
|
||||
Request body:
|
||||
|
||||
- None.
|
||||
|
||||
Query params:
|
||||
|
||||
- from=YYYY-MM-DD
|
||||
- to=YYYY-MM-DD
|
||||
- ratePlanCode (optional)
|
||||
|
||||
@@ -27,16 +27,10 @@ data class RoomAvailabilityResponse(
|
||||
)
|
||||
|
||||
data class RoomAvailabilityRangeResponse(
|
||||
val roomTypeName: String,
|
||||
val freeRoomNumbers: List<Int>,
|
||||
val freeCount: Int
|
||||
)
|
||||
|
||||
data class RoomAvailabilityWithRateResponse(
|
||||
val roomId: UUID,
|
||||
val roomNumber: Int,
|
||||
val roomTypeCode: String,
|
||||
val roomTypeName: String,
|
||||
val freeRoomNumbers: List<Int>,
|
||||
val freeCount: Int,
|
||||
val averageRate: Double?,
|
||||
val currency: String,
|
||||
val ratePlanCode: String? = null
|
||||
|
||||
@@ -7,7 +7,6 @@ import com.android.trisolarisserver.component.auth.PropertyAccess
|
||||
import com.android.trisolarisserver.component.room.RoomBoardEvents
|
||||
import com.android.trisolarisserver.controller.dto.room.RoomAvailabilityRangeResponse
|
||||
import com.android.trisolarisserver.controller.dto.room.RoomAvailabilityResponse
|
||||
import com.android.trisolarisserver.controller.dto.room.RoomAvailabilityWithRateResponse
|
||||
import com.android.trisolarisserver.controller.dto.room.RoomBoardResponse
|
||||
import com.android.trisolarisserver.controller.dto.room.RoomBoardStatus
|
||||
import com.android.trisolarisserver.controller.dto.room.RoomResponse
|
||||
@@ -185,7 +184,8 @@ class Rooms(
|
||||
@PathVariable propertyId: UUID,
|
||||
@AuthenticationPrincipal principal: MyPrincipal?,
|
||||
@org.springframework.web.bind.annotation.RequestParam("from") from: String,
|
||||
@org.springframework.web.bind.annotation.RequestParam("to") to: String
|
||||
@org.springframework.web.bind.annotation.RequestParam("to") to: String,
|
||||
@org.springframework.web.bind.annotation.RequestParam("ratePlanCode", required = false) ratePlanCode: String?
|
||||
): List<RoomAvailabilityRangeResponse> {
|
||||
requirePrincipal(principal)
|
||||
propertyAccess.requireMember(propertyId, principal!!.userId)
|
||||
@@ -205,53 +205,13 @@ class Rooms(
|
||||
|
||||
val rooms = roomRepo.findByPropertyIdOrderByRoomNumber(propertyId)
|
||||
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 }
|
||||
return grouped.entries.map { (typeName, roomList) ->
|
||||
RoomAvailabilityRangeResponse(
|
||||
roomTypeName = typeName,
|
||||
freeRoomNumbers = roomList.map { it.roomNumber },
|
||||
freeCount = roomList.size
|
||||
)
|
||||
}.sortedBy { it.roomTypeName }
|
||||
}
|
||||
|
||||
@GetMapping("/available-range-with-rate")
|
||||
fun availableRoomsWithRate(
|
||||
@PathVariable propertyId: UUID,
|
||||
@AuthenticationPrincipal principal: MyPrincipal?,
|
||||
@org.springframework.web.bind.annotation.RequestParam("from") from: String,
|
||||
@org.springframework.web.bind.annotation.RequestParam("to") to: String,
|
||||
@org.springframework.web.bind.annotation.RequestParam("ratePlanCode", required = false) ratePlanCode: String?
|
||||
): List<RoomAvailabilityWithRateResponse> {
|
||||
requirePrincipal(principal)
|
||||
propertyAccess.requireMember(propertyId, principal!!.userId)
|
||||
|
||||
val property = propertyRepo.findById(propertyId).orElseThrow {
|
||||
ResponseStatusException(HttpStatus.NOT_FOUND, "Property not found")
|
||||
}
|
||||
|
||||
val fromDate = parseDate(from, "Invalid date format")
|
||||
val toDate = parseDate(to, "Invalid date format")
|
||||
if (!toDate.isAfter(fromDate)) {
|
||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid date range")
|
||||
}
|
||||
val zone = ZoneId.of(property.timezone)
|
||||
val fromAt = fromDate.atStartOfDay(zone).toOffsetDateTime()
|
||||
val toAt = toDate.atStartOfDay(zone).toOffsetDateTime()
|
||||
|
||||
val rooms = roomRepo.findByPropertyIdOrderByRoomNumber(propertyId)
|
||||
val occupiedRoomIds = findForecastOccupiedRoomIds(propertyId, fromAt, toAt)
|
||||
val freeRooms = rooms.filter { it.active && !it.maintenance && !occupiedRoomIds.contains(it.id) }
|
||||
if (freeRooms.isEmpty()) return emptyList()
|
||||
|
||||
val plans = ratePlanRepo.findByPropertyIdOrderByCode(propertyId)
|
||||
val plansByRoomType = plans.groupBy { it.roomType.id!! }
|
||||
val averageCache = mutableMapOf<UUID, RateAverage>()
|
||||
|
||||
return freeRooms.map { room ->
|
||||
val roomType = room.roomType
|
||||
val freeRooms = rooms.filter { it.active && !it.maintenance && !occupiedRoomIds.contains(it.id) }
|
||||
val grouped = freeRooms.groupBy { it.roomType.id!! }
|
||||
return grouped.values.map { roomList ->
|
||||
val roomType = roomList.first().roomType
|
||||
val chosenPlan = selectRatePlan(plansByRoomType[roomType.id!!], ratePlanCode)
|
||||
val average = when {
|
||||
chosenPlan != null -> averageCache.getOrPut(chosenPlan.id!!) {
|
||||
@@ -261,16 +221,16 @@ class Rooms(
|
||||
roomType.defaultRate != null -> RateAverage(roomType.defaultRate!!.toDouble(), property.currency, null)
|
||||
else -> RateAverage(null, property.currency, null)
|
||||
}
|
||||
RoomAvailabilityWithRateResponse(
|
||||
roomId = room.id!!,
|
||||
roomNumber = room.roomNumber,
|
||||
RoomAvailabilityRangeResponse(
|
||||
roomTypeCode = roomType.code,
|
||||
roomTypeName = roomType.name,
|
||||
freeRoomNumbers = roomList.map { it.roomNumber },
|
||||
freeCount = roomList.size,
|
||||
averageRate = average.averageRate,
|
||||
currency = average.currency,
|
||||
ratePlanCode = average.ratePlanCode
|
||||
)
|
||||
}
|
||||
}.sortedBy { it.roomTypeName }
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
|
||||
Reference in New Issue
Block a user