Add available rooms range with average rate
All checks were successful
build-and-deploy / build-deploy (push) Successful in 34s

This commit is contained in:
androidlover5842
2026-01-29 11:33:43 +05:30
parent ff301d80fa
commit 33eebf06f6
2 changed files with 101 additions and 0 deletions

View File

@@ -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()) {

View File

@@ -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,