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:
|
What it does:
|
||||||
|
|
||||||
- Returns free rooms in date range.
|
- 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).
|
- 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.
|
- Active stays with no expected checkout are treated as occupied.
|
||||||
|
|
||||||
@@ -1013,34 +1014,6 @@ ROOM TYPE + ROOMS + ROOM IMAGES
|
|||||||
|
|
||||||
Query params:
|
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
|
- from=YYYY-MM-DD
|
||||||
- to=YYYY-MM-DD
|
- to=YYYY-MM-DD
|
||||||
- ratePlanCode (optional)
|
- ratePlanCode (optional)
|
||||||
|
|||||||
@@ -27,16 +27,10 @@ data class RoomAvailabilityResponse(
|
|||||||
)
|
)
|
||||||
|
|
||||||
data class RoomAvailabilityRangeResponse(
|
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 roomTypeCode: String,
|
||||||
val roomTypeName: String,
|
val roomTypeName: String,
|
||||||
|
val freeRoomNumbers: List<Int>,
|
||||||
|
val freeCount: Int,
|
||||||
val averageRate: Double?,
|
val averageRate: Double?,
|
||||||
val currency: String,
|
val currency: String,
|
||||||
val ratePlanCode: String? = null
|
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.component.room.RoomBoardEvents
|
||||||
import com.android.trisolarisserver.controller.dto.room.RoomAvailabilityRangeResponse
|
import com.android.trisolarisserver.controller.dto.room.RoomAvailabilityRangeResponse
|
||||||
import com.android.trisolarisserver.controller.dto.room.RoomAvailabilityResponse
|
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.RoomBoardResponse
|
||||||
import com.android.trisolarisserver.controller.dto.room.RoomBoardStatus
|
import com.android.trisolarisserver.controller.dto.room.RoomBoardStatus
|
||||||
import com.android.trisolarisserver.controller.dto.room.RoomResponse
|
import com.android.trisolarisserver.controller.dto.room.RoomResponse
|
||||||
@@ -185,7 +184,8 @@ class Rooms(
|
|||||||
@PathVariable propertyId: UUID,
|
@PathVariable propertyId: UUID,
|
||||||
@AuthenticationPrincipal principal: MyPrincipal?,
|
@AuthenticationPrincipal principal: MyPrincipal?,
|
||||||
@org.springframework.web.bind.annotation.RequestParam("from") from: String,
|
@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> {
|
): List<RoomAvailabilityRangeResponse> {
|
||||||
requirePrincipal(principal)
|
requirePrincipal(principal)
|
||||||
propertyAccess.requireMember(propertyId, principal!!.userId)
|
propertyAccess.requireMember(propertyId, principal!!.userId)
|
||||||
@@ -205,53 +205,13 @@ class Rooms(
|
|||||||
|
|
||||||
val rooms = roomRepo.findByPropertyIdOrderByRoomNumber(propertyId)
|
val rooms = roomRepo.findByPropertyIdOrderByRoomNumber(propertyId)
|
||||||
val occupiedRoomIds = findForecastOccupiedRoomIds(propertyId, fromAt, toAt)
|
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 plans = ratePlanRepo.findByPropertyIdOrderByCode(propertyId)
|
||||||
val plansByRoomType = plans.groupBy { it.roomType.id!! }
|
val plansByRoomType = plans.groupBy { it.roomType.id!! }
|
||||||
val averageCache = mutableMapOf<UUID, RateAverage>()
|
val averageCache = mutableMapOf<UUID, RateAverage>()
|
||||||
|
val freeRooms = rooms.filter { it.active && !it.maintenance && !occupiedRoomIds.contains(it.id) }
|
||||||
return freeRooms.map { room ->
|
val grouped = freeRooms.groupBy { it.roomType.id!! }
|
||||||
val roomType = room.roomType
|
return grouped.values.map { roomList ->
|
||||||
|
val roomType = roomList.first().roomType
|
||||||
val chosenPlan = selectRatePlan(plansByRoomType[roomType.id!!], ratePlanCode)
|
val chosenPlan = selectRatePlan(plansByRoomType[roomType.id!!], ratePlanCode)
|
||||||
val average = when {
|
val average = when {
|
||||||
chosenPlan != null -> averageCache.getOrPut(chosenPlan.id!!) {
|
chosenPlan != null -> averageCache.getOrPut(chosenPlan.id!!) {
|
||||||
@@ -261,16 +221,16 @@ class Rooms(
|
|||||||
roomType.defaultRate != null -> RateAverage(roomType.defaultRate!!.toDouble(), property.currency, null)
|
roomType.defaultRate != null -> RateAverage(roomType.defaultRate!!.toDouble(), property.currency, null)
|
||||||
else -> RateAverage(null, property.currency, null)
|
else -> RateAverage(null, property.currency, null)
|
||||||
}
|
}
|
||||||
RoomAvailabilityWithRateResponse(
|
RoomAvailabilityRangeResponse(
|
||||||
roomId = room.id!!,
|
|
||||||
roomNumber = room.roomNumber,
|
|
||||||
roomTypeCode = roomType.code,
|
roomTypeCode = roomType.code,
|
||||||
roomTypeName = roomType.name,
|
roomTypeName = roomType.name,
|
||||||
|
freeRoomNumbers = roomList.map { it.roomNumber },
|
||||||
|
freeCount = roomList.size,
|
||||||
averageRate = average.averageRate,
|
averageRate = average.averageRate,
|
||||||
currency = average.currency,
|
currency = average.currency,
|
||||||
ratePlanCode = average.ratePlanCode
|
ratePlanCode = average.ratePlanCode
|
||||||
)
|
)
|
||||||
}
|
}.sortedBy { it.roomTypeName }
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
|
|||||||
Reference in New Issue
Block a user