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

View File

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