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.component.RoomBoardEvents
|
||||||
import com.android.trisolarisserver.controller.dto.RoomAvailabilityRangeResponse
|
import com.android.trisolarisserver.controller.dto.RoomAvailabilityRangeResponse
|
||||||
import com.android.trisolarisserver.controller.dto.RoomAvailabilityResponse
|
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.RoomBoardResponse
|
||||||
import com.android.trisolarisserver.controller.dto.RoomBoardStatus
|
import com.android.trisolarisserver.controller.dto.RoomBoardStatus
|
||||||
import com.android.trisolarisserver.controller.dto.RoomResponse
|
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.RoomRepo
|
||||||
import com.android.trisolarisserver.repo.RoomStayRepo
|
import com.android.trisolarisserver.repo.RoomStayRepo
|
||||||
import com.android.trisolarisserver.repo.RoomTypeRepo
|
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.Room
|
||||||
|
import com.android.trisolarisserver.models.room.RatePlan
|
||||||
import com.android.trisolarisserver.models.property.Role
|
import com.android.trisolarisserver.models.property.Role
|
||||||
import com.android.trisolarisserver.security.MyPrincipal
|
import com.android.trisolarisserver.security.MyPrincipal
|
||||||
import org.springframework.http.HttpStatus
|
import org.springframework.http.HttpStatus
|
||||||
@@ -49,6 +53,8 @@ class Rooms(
|
|||||||
private val roomTypeRepo: RoomTypeRepo,
|
private val roomTypeRepo: RoomTypeRepo,
|
||||||
private val issuedCardRepo: IssuedCardRepo,
|
private val issuedCardRepo: IssuedCardRepo,
|
||||||
private val propertyUserRepo: PropertyUserRepo,
|
private val propertyUserRepo: PropertyUserRepo,
|
||||||
|
private val ratePlanRepo: RatePlanRepo,
|
||||||
|
private val rateCalendarRepo: RateCalendarRepo,
|
||||||
private val roomBoardEvents: RoomBoardEvents
|
private val roomBoardEvents: RoomBoardEvents
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@@ -205,6 +211,62 @@ class Rooms(
|
|||||||
}.sortedBy { it.roomTypeName }
|
}.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
|
@PostMapping
|
||||||
@ResponseStatus(HttpStatus.CREATED)
|
@ResponseStatus(HttpStatus.CREATED)
|
||||||
fun createRoom(
|
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 {
|
private fun resolveRoomType(propertyId: UUID, request: RoomUpsertRequest): com.android.trisolarisserver.models.room.RoomType {
|
||||||
val code = request.roomTypeCode.trim()
|
val code = request.roomTypeCode.trim()
|
||||||
if (code.isBlank()) {
|
if (code.isBlank()) {
|
||||||
|
|||||||
@@ -32,6 +32,16 @@ data class RoomAvailabilityRangeResponse(
|
|||||||
val freeCount: Int
|
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(
|
data class RoomImageResponse(
|
||||||
val id: UUID,
|
val id: UUID,
|
||||||
val propertyId: UUID,
|
val propertyId: UUID,
|
||||||
|
|||||||
Reference in New Issue
Block a user