Add available rooms range with average rate
All checks were successful
build-and-deploy / build-deploy (push) Successful in 34s
All checks were successful
build-and-deploy / build-deploy (push) Successful in 34s
This commit is contained in:
@@ -4,6 +4,7 @@ import com.android.trisolarisserver.component.PropertyAccess
|
||||
import com.android.trisolarisserver.component.RoomBoardEvents
|
||||
import com.android.trisolarisserver.controller.dto.RoomAvailabilityRangeResponse
|
||||
import com.android.trisolarisserver.controller.dto.RoomAvailabilityResponse
|
||||
import com.android.trisolarisserver.controller.dto.RoomAvailabilityWithRateResponse
|
||||
import com.android.trisolarisserver.controller.dto.RoomBoardResponse
|
||||
import com.android.trisolarisserver.controller.dto.RoomBoardStatus
|
||||
import com.android.trisolarisserver.controller.dto.RoomResponse
|
||||
@@ -15,7 +16,10 @@ import com.android.trisolarisserver.repo.RoomImageRepo
|
||||
import com.android.trisolarisserver.repo.RoomRepo
|
||||
import com.android.trisolarisserver.repo.RoomStayRepo
|
||||
import com.android.trisolarisserver.repo.RoomTypeRepo
|
||||
import com.android.trisolarisserver.repo.RatePlanRepo
|
||||
import com.android.trisolarisserver.repo.RateCalendarRepo
|
||||
import com.android.trisolarisserver.models.room.Room
|
||||
import com.android.trisolarisserver.models.room.RatePlan
|
||||
import com.android.trisolarisserver.models.property.Role
|
||||
import com.android.trisolarisserver.security.MyPrincipal
|
||||
import org.springframework.http.HttpStatus
|
||||
@@ -49,6 +53,8 @@ class Rooms(
|
||||
private val roomTypeRepo: RoomTypeRepo,
|
||||
private val issuedCardRepo: IssuedCardRepo,
|
||||
private val propertyUserRepo: PropertyUserRepo,
|
||||
private val ratePlanRepo: RatePlanRepo,
|
||||
private val rateCalendarRepo: RateCalendarRepo,
|
||||
private val roomBoardEvents: RoomBoardEvents
|
||||
) {
|
||||
|
||||
@@ -205,6 +211,62 @@ class Rooms(
|
||||
}.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)
|
||||
val toDate = parseDate(to)
|
||||
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 = roomStayRepo.findOccupiedRoomIdsBetween(propertyId, fromAt, toAt).toHashSet()
|
||||
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 chosenPlan = selectRatePlan(plansByRoomType[roomType.id!!], ratePlanCode)
|
||||
val average = when {
|
||||
chosenPlan != null -> averageCache.getOrPut(chosenPlan.id!!) {
|
||||
val avg = averageRateForPlan(chosenPlan, fromDate, toDate)
|
||||
RateAverage(avg, chosenPlan.currency, chosenPlan.code)
|
||||
}
|
||||
roomType.defaultRate != null -> RateAverage(roomType.defaultRate!!.toDouble(), property.currency, null)
|
||||
else -> RateAverage(null, property.currency, null)
|
||||
}
|
||||
RoomAvailabilityWithRateResponse(
|
||||
roomId = room.id!!,
|
||||
roomNumber = room.roomNumber,
|
||||
roomTypeCode = roomType.code,
|
||||
roomTypeName = roomType.name,
|
||||
averageRate = average.averageRate,
|
||||
currency = average.currency,
|
||||
ratePlanCode = average.ratePlanCode
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
fun createRoom(
|
||||
@@ -350,6 +412,35 @@ class Rooms(
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectRatePlan(plans: List<RatePlan>?, ratePlanCode: String?): RatePlan? {
|
||||
if (plans.isNullOrEmpty()) return null
|
||||
if (!ratePlanCode.isNullOrBlank()) {
|
||||
return plans.firstOrNull { it.code.equals(ratePlanCode, ignoreCase = true) }
|
||||
}
|
||||
return plans.firstOrNull()
|
||||
}
|
||||
|
||||
private fun averageRateForPlan(plan: RatePlan, fromDate: LocalDate, toDate: LocalDate): Double {
|
||||
val overrides = rateCalendarRepo
|
||||
.findByRatePlanIdAndRateDateBetweenOrderByRateDateAsc(plan.id!!, fromDate, toDate)
|
||||
.associateBy { it.rateDate }
|
||||
var total = 0L
|
||||
var days = 0
|
||||
var date = fromDate
|
||||
while (!date.isAfter(toDate)) {
|
||||
days += 1
|
||||
total += overrides[date]?.rate ?: plan.baseRate
|
||||
date = date.plusDays(1)
|
||||
}
|
||||
return if (days == 0) 0.0 else total.toDouble() / days
|
||||
}
|
||||
|
||||
private data class RateAverage(
|
||||
val averageRate: Double?,
|
||||
val currency: String,
|
||||
val ratePlanCode: String?
|
||||
)
|
||||
|
||||
private fun resolveRoomType(propertyId: UUID, request: RoomUpsertRequest): com.android.trisolarisserver.models.room.RoomType {
|
||||
val code = request.roomTypeCode.trim()
|
||||
if (code.isBlank()) {
|
||||
|
||||
@@ -32,6 +32,16 @@ data class RoomAvailabilityRangeResponse(
|
||||
val freeCount: Int
|
||||
)
|
||||
|
||||
data class RoomAvailabilityWithRateResponse(
|
||||
val roomId: UUID,
|
||||
val roomNumber: Int,
|
||||
val roomTypeCode: String,
|
||||
val roomTypeName: String,
|
||||
val averageRate: Double?,
|
||||
val currency: String,
|
||||
val ratePlanCode: String? = null
|
||||
)
|
||||
|
||||
data class RoomImageResponse(
|
||||
val id: UUID,
|
||||
val propertyId: UUID,
|
||||
|
||||
Reference in New Issue
Block a user