From 33eebf06f63e1b3f27b37e36f58b54a3622e2627 Mon Sep 17 00:00:00 2001 From: androidlover5842 Date: Thu, 29 Jan 2026 11:33:43 +0530 Subject: [PATCH] Add available rooms range with average rate --- .../trisolarisserver/controller/Rooms.kt | 91 +++++++++++++++++++ .../controller/dto/RoomDtos.kt | 10 ++ 2 files changed, 101 insertions(+) diff --git a/src/main/kotlin/com/android/trisolarisserver/controller/Rooms.kt b/src/main/kotlin/com/android/trisolarisserver/controller/Rooms.kt index 00c5032..ac7f24f 100644 --- a/src/main/kotlin/com/android/trisolarisserver/controller/Rooms.kt +++ b/src/main/kotlin/com/android/trisolarisserver/controller/Rooms.kt @@ -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 { + 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() + + 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?, 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()) { diff --git a/src/main/kotlin/com/android/trisolarisserver/controller/dto/RoomDtos.kt b/src/main/kotlin/com/android/trisolarisserver/controller/dto/RoomDtos.kt index 6820c8f..6f07fc9 100644 --- a/src/main/kotlin/com/android/trisolarisserver/controller/dto/RoomDtos.kt +++ b/src/main/kotlin/com/android/trisolarisserver/controller/dto/RoomDtos.kt @@ -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,