ai removed boilerplate
All checks were successful
build-and-deploy / build-deploy (push) Successful in 31s

This commit is contained in:
androidlover5842
2026-01-31 06:35:06 +05:30
parent 812211f62b
commit f7c0cf5c18
15 changed files with 151 additions and 292 deletions

View File

@@ -7,65 +7,30 @@ import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter
import java.io.IOException
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
@Component
class GuestDocumentEvents(
private val guestDocumentRepo: GuestDocumentRepo,
private val objectMapper: ObjectMapper
) {
private val emitters: MutableMap<GuestDocKey, CopyOnWriteArrayList<SseEmitter>> = ConcurrentHashMap()
private val hub = SseHub<GuestDocKey>("guest-documents") { key ->
buildSnapshot(key.propertyId, key.guestId)
}
fun subscribe(propertyId: UUID, guestId: UUID): SseEmitter {
val key = GuestDocKey(propertyId, guestId)
val emitter = SseEmitter(0L)
emitters.computeIfAbsent(key) { CopyOnWriteArrayList() }.add(emitter)
emitter.onCompletion { emitters[key]?.remove(emitter) }
emitter.onTimeout { emitters[key]?.remove(emitter) }
emitter.onError { emitters[key]?.remove(emitter) }
try {
emitter.send(SseEmitter.event().name("guest-documents").data(buildSnapshot(propertyId, guestId)))
} catch (_: IOException) {
emitters[key]?.remove(emitter)
}
return emitter
return hub.subscribe(key)
}
fun emit(propertyId: UUID, guestId: UUID) {
val key = GuestDocKey(propertyId, guestId)
val list = emitters[key] ?: return
val data = buildSnapshot(propertyId, guestId)
val dead = mutableListOf<SseEmitter>()
for (emitter in list) {
try {
emitter.send(SseEmitter.event().name("guest-documents").data(data))
} catch (_: IOException) {
dead.add(emitter)
}
}
if (dead.isNotEmpty()) {
list.removeAll(dead.toSet())
}
hub.emit(key)
}
@Scheduled(fixedDelayString = "25000")
fun heartbeat() {
emitters.forEach { (_, list) ->
val dead = mutableListOf<SseEmitter>()
for (emitter in list) {
try {
emitter.send(SseEmitter.event().name("ping").data("ok"))
} catch (_: IOException) {
dead.add(emitter)
}
}
if (dead.isNotEmpty()) {
list.removeAll(dead.toSet())
}
}
hub.heartbeat()
}
private fun buildSnapshot(propertyId: UUID, guestId: UUID): List<GuestDocumentResponse> {

View File

@@ -7,63 +7,28 @@ import com.android.trisolarisserver.repo.RoomStayRepo
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter
import java.io.IOException
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
@Component
class RoomBoardEvents(
private val roomRepo: RoomRepo,
private val roomStayRepo: RoomStayRepo
) {
private val emitters: MutableMap<UUID, CopyOnWriteArrayList<SseEmitter>> = ConcurrentHashMap()
private val hub = SseHub<UUID>("room-board") { propertyId ->
buildSnapshot(propertyId)
}
fun subscribe(propertyId: UUID): SseEmitter {
val emitter = SseEmitter(0L)
emitters.computeIfAbsent(propertyId) { CopyOnWriteArrayList() }.add(emitter)
emitter.onCompletion { emitters[propertyId]?.remove(emitter) }
emitter.onTimeout { emitters[propertyId]?.remove(emitter) }
emitter.onError { emitters[propertyId]?.remove(emitter) }
try {
emitter.send(SseEmitter.event().name("room-board").data(buildSnapshot(propertyId)))
} catch (_: IOException) {
emitters[propertyId]?.remove(emitter)
}
return emitter
return hub.subscribe(propertyId)
}
fun emit(propertyId: UUID) {
val data = buildSnapshot(propertyId)
val list = emitters[propertyId] ?: return
val dead = mutableListOf<SseEmitter>()
for (emitter in list) {
try {
emitter.send(SseEmitter.event().name("room-board").data(data))
} catch (_: IOException) {
dead.add(emitter)
}
}
if (dead.isNotEmpty()) {
list.removeAll(dead.toSet())
}
hub.emit(propertyId)
}
@Scheduled(fixedDelayString = "25000")
fun heartbeat() {
emitters.forEach { (_, list) ->
val dead = mutableListOf<SseEmitter>()
for (emitter in list) {
try {
emitter.send(SseEmitter.event().name("ping").data("ok"))
} catch (_: IOException) {
dead.add(emitter)
}
}
if (dead.isNotEmpty()) {
list.removeAll(dead.toSet())
}
}
hub.heartbeat()
}
private fun buildSnapshot(propertyId: UUID): List<RoomBoardResponse> {

View File

@@ -0,0 +1,59 @@
package com.android.trisolarisserver.component
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter
import java.io.IOException
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
class SseHub<K>(
private val eventName: String,
private val snapshot: (K) -> Any
) {
private val emitters: MutableMap<K, CopyOnWriteArrayList<SseEmitter>> = ConcurrentHashMap()
fun subscribe(key: K): SseEmitter {
val emitter = SseEmitter(0L)
emitters.computeIfAbsent(key) { CopyOnWriteArrayList() }.add(emitter)
emitter.onCompletion { emitters[key]?.remove(emitter) }
emitter.onTimeout { emitters[key]?.remove(emitter) }
emitter.onError { emitters[key]?.remove(emitter) }
try {
emitter.send(SseEmitter.event().name(eventName).data(snapshot(key)))
} catch (_: IOException) {
emitters[key]?.remove(emitter)
}
return emitter
}
fun emit(key: K) {
val list = emitters[key] ?: return
val data = snapshot(key)
val dead = mutableListOf<SseEmitter>()
for (emitter in list) {
try {
emitter.send(SseEmitter.event().name(eventName).data(data))
} catch (_: IOException) {
dead.add(emitter)
}
}
if (dead.isNotEmpty()) {
list.removeAll(dead.toSet())
}
}
fun heartbeat() {
emitters.forEach { (_, list) ->
val dead = mutableListOf<SseEmitter>()
for (emitter in list) {
try {
emitter.send(SseEmitter.event().name("ping").data("ok"))
} catch (_: IOException) {
dead.add(emitter)
}
}
if (dead.isNotEmpty()) {
list.removeAll(dead.toSet())
}
}
}
}

View File

@@ -13,7 +13,6 @@ import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.server.ResponseStatusException
import java.time.LocalDate
import java.util.UUID
@RestController
@@ -38,7 +37,10 @@ class BookingBalances(
if (booking.property.id != propertyId) {
throw ResponseStatusException(HttpStatus.NOT_FOUND, "Booking not found for property")
}
val expected = computeExpectedPay(bookingId, booking.property.timezone)
val expected = computeExpectedPay(
roomStayRepo.findByBookingId(bookingId),
booking.property.timezone
)
val collected = paymentRepo.sumAmountByBookingId(bookingId)
val pending = expected - collected
return BookingBalanceResponse(
@@ -47,26 +49,4 @@ class BookingBalances(
pending = pending
)
}
private fun computeExpectedPay(bookingId: UUID, timezone: String?): Long {
val stays = roomStayRepo.findByBookingId(bookingId)
if (stays.isEmpty()) return 0
val now = nowForProperty(timezone)
var total = 0L
stays.forEach { stay ->
val rate = stay.nightlyRate ?: 0L
if (rate == 0L) return@forEach
val start = stay.fromAt.toLocalDate()
val endAt = stay.toAt ?: now
val end = endAt.toLocalDate()
val nights = daysBetweenInclusive(start, end)
total += rate * nights
}
return total
}
private fun daysBetweenInclusive(start: LocalDate, end: LocalDate): Long {
val diff = end.toEpochDay() - start.toEpochDay()
return if (diff <= 0) 1L else diff
}
}

View File

@@ -716,47 +716,6 @@ class BookingFlow(
}
}
private fun computeExpectedPay(stays: List<RoomStay>, timezone: String?): Long {
if (stays.isEmpty()) return 0
val now = nowForProperty(timezone)
var total = 0L
stays.forEach { stay ->
val rate = stay.nightlyRate ?: 0L
if (rate == 0L) return@forEach
val start = stay.fromAt.toLocalDate()
val endAt = stay.toAt ?: now
val end = endAt.toLocalDate()
val nights = daysBetweenInclusive(start, end)
total += rate * nights
}
return total
}
private fun computeExpectedPayTotal(
stays: List<RoomStay>,
expectedCheckoutAt: OffsetDateTime?,
timezone: String?
): Long {
if (stays.isEmpty()) return 0
val now = nowForProperty(timezone)
var total = 0L
stays.forEach { stay ->
val rate = stay.nightlyRate ?: 0L
if (rate == 0L) return@forEach
val start = stay.fromAt.toLocalDate()
val endAt = stay.toAt ?: expectedCheckoutAt ?: now
val end = endAt.toLocalDate()
val nights = daysBetweenInclusive(start, end)
total += rate * nights
}
return total
}
private fun daysBetweenInclusive(start: java.time.LocalDate, end: java.time.LocalDate): Long {
val diff = end.toEpochDay() - start.toEpochDay()
return if (diff <= 0) 1L else diff
}
private fun isTransportModeAllowed(
property: com.android.trisolarisserver.models.property.Property,
mode: TransportMode

View File

@@ -40,3 +40,11 @@ internal fun requireRole(
propertyAccess.requireAnyRole(propertyId, resolved.userId, *roles)
return resolved
}
internal fun requireSuperAdmin(appUserRepo: AppUserRepo, principal: MyPrincipal?): AppUser {
val user = requireUser(appUserRepo, principal)
if (!user.superAdmin) {
throw ResponseStatusException(HttpStatus.FORBIDDEN, "Super admin only")
}
return user
}

View File

@@ -8,6 +8,7 @@ import com.android.trisolarisserver.repo.PropertyRepo
import com.android.trisolarisserver.repo.RoomStayRepo
import org.springframework.http.HttpStatus
import org.springframework.web.server.ResponseStatusException
import java.time.LocalDate
import java.time.OffsetDateTime
import java.time.ZoneId
import java.util.UUID
@@ -75,6 +76,14 @@ internal fun parseOffset(value: String?): OffsetDateTime? {
}
}
internal fun parseDate(value: String, errorMessage: String): LocalDate {
return try {
LocalDate.parse(value.trim())
} catch (_: Exception) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, errorMessage)
}
}
internal fun nowForProperty(timezone: String?): OffsetDateTime {
val zone = try {
if (timezone.isNullOrBlank()) ZoneId.of("Asia/Kolkata") else ZoneId.of(timezone)
@@ -83,3 +92,44 @@ internal fun nowForProperty(timezone: String?): OffsetDateTime {
}
return OffsetDateTime.now(zone)
}
internal fun computeExpectedPay(stays: List<RoomStay>, timezone: String?): Long {
if (stays.isEmpty()) return 0
val now = nowForProperty(timezone)
var total = 0L
stays.forEach { stay ->
val rate = stay.nightlyRate ?: 0L
if (rate == 0L) return@forEach
val start = stay.fromAt.toLocalDate()
val endAt = stay.toAt ?: now
val end = endAt.toLocalDate()
val nights = daysBetweenInclusive(start, end)
total += rate * nights
}
return total
}
internal fun computeExpectedPayTotal(
stays: List<RoomStay>,
expectedCheckoutAt: OffsetDateTime?,
timezone: String?
): Long {
if (stays.isEmpty()) return 0
val now = nowForProperty(timezone)
var total = 0L
stays.forEach { stay ->
val rate = stay.nightlyRate ?: 0L
if (rate == 0L) return@forEach
val start = stay.fromAt.toLocalDate()
val endAt = stay.toAt ?: expectedCheckoutAt ?: now
val end = endAt.toLocalDate()
val nights = daysBetweenInclusive(start, end)
total += rate * nights
}
return total
}
internal fun daysBetweenInclusive(start: LocalDate, end: LocalDate): Long {
val diff = end.toEpochDay() - start.toEpochDay()
return if (diff <= 0) 1L else diff
}

View File

@@ -214,25 +214,4 @@ class PayuPaymentLinksController(
}
}
private fun computeExpectedPay(stays: List<com.android.trisolarisserver.models.room.RoomStay>, timezone: String?): Long {
if (stays.isEmpty()) return 0
val now = nowForProperty(timezone)
var total = 0L
stays.forEach { stay ->
val rate = stay.nightlyRate ?: 0L
if (rate == 0L) return@forEach
val start = stay.fromAt.toLocalDate()
val endAt = stay.toAt ?: now
val end = endAt.toLocalDate()
val nights = daysBetweenInclusive(start, end)
total += rate * nights
}
return total
}
private fun daysBetweenInclusive(start: java.time.LocalDate, end: java.time.LocalDate): Long {
val diff = end.toEpochDay() - start.toEpochDay()
return if (diff <= 0) 1L else diff
}
}

View File

@@ -257,24 +257,4 @@ class PayuQrPayments(
return request.remoteAddr?.trim()?.ifBlank { null }
}
private fun computeExpectedPay(stays: List<com.android.trisolarisserver.models.room.RoomStay>, timezone: String?): Long {
if (stays.isEmpty()) return 0
val now = nowForProperty(timezone)
var total = 0L
stays.forEach { stay ->
val rate = stay.nightlyRate ?: 0L
if (rate == 0L) return@forEach
val start = stay.fromAt.toLocalDate()
val endAt = stay.toAt ?: now
val end = endAt.toLocalDate()
val nights = daysBetweenInclusive(start, end)
total += rate * nights
}
return total
}
private fun daysBetweenInclusive(start: java.time.LocalDate, end: java.time.LocalDate): Long {
val diff = end.toEpochDay() - start.toEpochDay()
return if (diff <= 0) 1L else diff
}
}

View File

@@ -42,7 +42,7 @@ class Properties(
@AuthenticationPrincipal principal: MyPrincipal?,
@RequestBody request: PropertyCreateRequest
): PropertyResponse {
val user = requireUser(principal)
val user = requireUser(appUserRepo, principal)
if (propertyRepo.existsByCode(request.code)) {
throw ResponseStatusException(HttpStatus.CONFLICT, "Property code already exists")
}
@@ -80,7 +80,7 @@ class Properties(
fun listProperties(
@AuthenticationPrincipal principal: MyPrincipal?
): List<PropertyResponse> {
val user = requireUser(principal)
val user = requireUser(appUserRepo, principal)
return if (user.superAdmin) {
propertyRepo.findAll().map { it.toResponse() }
} else {
@@ -211,21 +211,6 @@ class Properties(
return propertyRepo.save(property).toResponse()
}
private fun requirePrincipal(principal: MyPrincipal?) {
if (principal == null) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing principal")
}
}
private fun requireUser(principal: MyPrincipal?): com.android.trisolarisserver.models.property.AppUser {
if (principal == null) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing principal")
}
return appUserRepo.findById(principal.userId).orElseThrow {
ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not found")
}
}
private fun parseTransportModes(modes: Set<String>): MutableSet<TransportMode> {
return try {
modes.map { TransportMode.valueOf(it) }.toMutableSet()

View File

@@ -132,8 +132,8 @@ class RatePlans(
requireRole(propertyAccess, propertyId, principal, Role.ADMIN, Role.MANAGER)
val plan = ratePlanRepo.findByIdAndPropertyId(ratePlanId, propertyId)
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Rate plan not found")
val fromDate = parseDate(request.from)
val toDate = parseDate(request.to)
val fromDate = parseDate(request.from, "Invalid date")
val toDate = parseDate(request.to, "Invalid date")
if (toDate.isBefore(fromDate)) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "to must be on/after from")
}
@@ -174,8 +174,8 @@ class RatePlans(
requireMember(propertyAccess, propertyId, principal)
val plan = ratePlanRepo.findByIdAndPropertyId(ratePlanId, propertyId)
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Rate plan not found")
val fromDate = parseDate(from)
val toDate = parseDate(to)
val fromDate = parseDate(from, "Invalid date")
val toDate = parseDate(to, "Invalid date")
if (toDate.isBefore(fromDate)) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "to must be on/after from")
}
@@ -209,20 +209,12 @@ class RatePlans(
requireRole(propertyAccess, propertyId, principal, Role.ADMIN, Role.MANAGER)
val plan = ratePlanRepo.findByIdAndPropertyId(ratePlanId, propertyId)
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Rate plan not found")
val date = parseDate(rateDate)
val date = parseDate(rateDate, "Invalid date")
val existing = rateCalendarRepo.findByRatePlanIdAndRateDate(plan.id!!, date)
?: return
rateCalendarRepo.delete(existing)
}
private fun parseDate(value: String): LocalDate {
return try {
LocalDate.parse(value.trim())
} catch (_: Exception) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid date")
}
}
private fun datesBetween(from: LocalDate, to: LocalDate): Sequence<LocalDate> {
return generateSequence(from) { current ->
val next = current.plusDays(1)

View File

@@ -48,7 +48,7 @@ class RoomAmenities(
@AuthenticationPrincipal principal: MyPrincipal?,
@RequestBody request: AmenityUpsertRequest
): AmenityResponse {
requireSuperAdmin(principal)
requireSuperAdmin(appUserRepo, principal)
if (roomAmenityRepo.existsByName(request.name)) {
throw ResponseStatusException(HttpStatus.CONFLICT, "Amenity already exists")
@@ -68,7 +68,7 @@ class RoomAmenities(
@AuthenticationPrincipal principal: MyPrincipal?,
@RequestBody request: AmenityUpsertRequest
): AmenityResponse {
requireSuperAdmin(principal)
requireSuperAdmin(appUserRepo, principal)
val amenity = roomAmenityRepo.findById(amenityId).orElse(null)
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Amenity not found")
@@ -91,7 +91,7 @@ class RoomAmenities(
@PathVariable amenityId: UUID,
@AuthenticationPrincipal principal: MyPrincipal?
) {
requireSuperAdmin(principal)
requireSuperAdmin(appUserRepo, principal)
val amenity = roomAmenityRepo.findById(amenityId).orElse(null)
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Amenity not found")
@@ -106,24 +106,6 @@ class RoomAmenities(
roomAmenityRepo.delete(amenity)
}
private fun requirePrincipal(principal: MyPrincipal?) {
if (principal == null) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing principal")
}
}
private fun requireSuperAdmin(principal: MyPrincipal?) {
if (principal == null) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing principal")
}
val user = appUserRepo.findById(principal.userId).orElseThrow {
ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not found")
}
if (!user.superAdmin) {
throw ResponseStatusException(HttpStatus.FORBIDDEN, "Super admin only")
}
}
private fun validateIconKey(iconKey: String?) {
if (iconKey.isNullOrBlank()) return
val file = Paths.get(pngRoot, "${iconKey}.png")

View File

@@ -43,7 +43,7 @@ class RoomImageTags(
@AuthenticationPrincipal principal: MyPrincipal?,
@RequestBody request: RoomImageTagUpsertRequest
): RoomImageTagResponse {
requireSuperAdmin(principal)
requireSuperAdmin(appUserRepo, principal)
if (roomImageTagRepo.existsByName(request.name)) {
throw ResponseStatusException(HttpStatus.CONFLICT, "Tag already exists")
}
@@ -57,7 +57,7 @@ class RoomImageTags(
@AuthenticationPrincipal principal: MyPrincipal?,
@RequestBody request: RoomImageTagUpsertRequest
): RoomImageTagResponse {
requireSuperAdmin(principal)
requireSuperAdmin(appUserRepo, principal)
val tag = roomImageTagRepo.findById(tagId).orElse(null)
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Tag not found")
if (roomImageTagRepo.existsByNameAndIdNot(request.name, tagId)) {
@@ -74,30 +74,13 @@ class RoomImageTags(
@PathVariable tagId: UUID,
@AuthenticationPrincipal principal: MyPrincipal?
) {
requireSuperAdmin(principal)
requireSuperAdmin(appUserRepo, principal)
val tag = roomImageTagRepo.findById(tagId).orElse(null)
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Tag not found")
roomImageRepo.deleteTagLinks(tagId)
roomImageTagRepo.delete(tag)
}
private fun requirePrincipal(principal: MyPrincipal?) {
if (principal == null) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing principal")
}
}
private fun requireSuperAdmin(principal: MyPrincipal?) {
if (principal == null) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing principal")
}
val user = appUserRepo.findById(principal.userId).orElseThrow {
ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not found")
}
if (!user.superAdmin) {
throw ResponseStatusException(HttpStatus.FORBIDDEN, "Super admin only")
}
}
}
private fun RoomImageTag.toResponse(): RoomImageTagResponse {

View File

@@ -27,7 +27,6 @@ import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.server.ResponseStatusException
import java.time.LocalDate
import java.util.UUID
@RestController
@@ -69,7 +68,7 @@ class RoomTypes(
}
val roomType = roomTypeRepo.findByPropertyIdAndCodeIgnoreCase(propertyId, roomTypeCode)
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Room type not found")
val rateDate = parseDate(date)
val rateDate = parseDate(date, "Invalid date")
if (!ratePlanCode.isNullOrBlank()) {
val plan = ratePlanRepo.findByPropertyIdOrderByCode(propertyId)
@@ -198,19 +197,6 @@ class RoomTypes(
roomTypeRepo.save(roomType)
}
private fun requirePrincipal(principal: MyPrincipal?) {
if (principal == null) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing principal")
}
}
private fun parseDate(value: String): LocalDate {
return try {
LocalDate.parse(value.trim())
} catch (_: Exception) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid date")
}
}
}
private fun RoomType.toResponse(): RoomTypeResponse {

View File

@@ -188,8 +188,8 @@ class Rooms(
ResponseStatusException(HttpStatus.NOT_FOUND, "Property not found")
}
val fromDate = parseDate(from)
val toDate = parseDate(to)
val fromDate = parseDate(from, "Invalid date format")
val toDate = parseDate(to, "Invalid date format")
if (!toDate.isAfter(fromDate)) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid date range")
}
@@ -226,8 +226,8 @@ class Rooms(
ResponseStatusException(HttpStatus.NOT_FOUND, "Property not found")
}
val fromDate = parseDate(from)
val toDate = parseDate(to)
val fromDate = parseDate(from, "Invalid date format")
val toDate = parseDate(to, "Invalid date format")
if (!toDate.isAfter(fromDate)) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid date range")
}
@@ -392,26 +392,12 @@ class Rooms(
roomBoardEvents.emit(propertyId)
}
private fun requirePrincipal(principal: MyPrincipal?) {
if (principal == null) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing principal")
}
}
private fun isAgentOnly(roles: Set<Role>): Boolean {
if (!roles.contains(Role.AGENT)) return false
val privileged = setOf(Role.ADMIN, Role.MANAGER, Role.STAFF, Role.HOUSEKEEPING, Role.FINANCE)
return roles.none { it in privileged }
}
private fun parseDate(value: String): LocalDate {
return try {
LocalDate.parse(value.trim())
} catch (_: Exception) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid date format")
}
}
private fun selectRatePlan(plans: List<RatePlan>?, ratePlanCode: String?): RatePlan? {
if (plans.isNullOrEmpty()) return null
if (!ratePlanCode.isNullOrBlank()) {