ai removed boilerplate
All checks were successful
build-and-deploy / build-deploy (push) Successful in 31s
All checks were successful
build-and-deploy / build-deploy (push) Successful in 31s
This commit is contained in:
@@ -7,65 +7,30 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
|||||||
import org.springframework.scheduling.annotation.Scheduled
|
import org.springframework.scheduling.annotation.Scheduled
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter
|
||||||
import java.io.IOException
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
class GuestDocumentEvents(
|
class GuestDocumentEvents(
|
||||||
private val guestDocumentRepo: GuestDocumentRepo,
|
private val guestDocumentRepo: GuestDocumentRepo,
|
||||||
private val objectMapper: ObjectMapper
|
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 {
|
fun subscribe(propertyId: UUID, guestId: UUID): SseEmitter {
|
||||||
val key = GuestDocKey(propertyId, guestId)
|
val key = GuestDocKey(propertyId, guestId)
|
||||||
val emitter = SseEmitter(0L)
|
return hub.subscribe(key)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun emit(propertyId: UUID, guestId: UUID) {
|
fun emit(propertyId: UUID, guestId: UUID) {
|
||||||
val key = GuestDocKey(propertyId, guestId)
|
val key = GuestDocKey(propertyId, guestId)
|
||||||
val list = emitters[key] ?: return
|
hub.emit(key)
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scheduled(fixedDelayString = "25000")
|
@Scheduled(fixedDelayString = "25000")
|
||||||
fun heartbeat() {
|
fun heartbeat() {
|
||||||
emitters.forEach { (_, list) ->
|
hub.heartbeat()
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildSnapshot(propertyId: UUID, guestId: UUID): List<GuestDocumentResponse> {
|
private fun buildSnapshot(propertyId: UUID, guestId: UUID): List<GuestDocumentResponse> {
|
||||||
|
|||||||
@@ -7,63 +7,28 @@ import com.android.trisolarisserver.repo.RoomStayRepo
|
|||||||
import org.springframework.scheduling.annotation.Scheduled
|
import org.springframework.scheduling.annotation.Scheduled
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter
|
||||||
import java.io.IOException
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
class RoomBoardEvents(
|
class RoomBoardEvents(
|
||||||
private val roomRepo: RoomRepo,
|
private val roomRepo: RoomRepo,
|
||||||
private val roomStayRepo: RoomStayRepo
|
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 {
|
fun subscribe(propertyId: UUID): SseEmitter {
|
||||||
val emitter = SseEmitter(0L)
|
return hub.subscribe(propertyId)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun emit(propertyId: UUID) {
|
fun emit(propertyId: UUID) {
|
||||||
val data = buildSnapshot(propertyId)
|
hub.emit(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())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scheduled(fixedDelayString = "25000")
|
@Scheduled(fixedDelayString = "25000")
|
||||||
fun heartbeat() {
|
fun heartbeat() {
|
||||||
emitters.forEach { (_, list) ->
|
hub.heartbeat()
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildSnapshot(propertyId: UUID): List<RoomBoardResponse> {
|
private fun buildSnapshot(propertyId: UUID): List<RoomBoardResponse> {
|
||||||
|
|||||||
@@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,6 @@ import org.springframework.web.bind.annotation.PathVariable
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
import org.springframework.web.server.ResponseStatusException
|
import org.springframework.web.server.ResponseStatusException
|
||||||
import java.time.LocalDate
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -38,7 +37,10 @@ class BookingBalances(
|
|||||||
if (booking.property.id != propertyId) {
|
if (booking.property.id != propertyId) {
|
||||||
throw ResponseStatusException(HttpStatus.NOT_FOUND, "Booking not found for property")
|
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 collected = paymentRepo.sumAmountByBookingId(bookingId)
|
||||||
val pending = expected - collected
|
val pending = expected - collected
|
||||||
return BookingBalanceResponse(
|
return BookingBalanceResponse(
|
||||||
@@ -47,26 +49,4 @@ class BookingBalances(
|
|||||||
pending = pending
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
private fun isTransportModeAllowed(
|
||||||
property: com.android.trisolarisserver.models.property.Property,
|
property: com.android.trisolarisserver.models.property.Property,
|
||||||
mode: TransportMode
|
mode: TransportMode
|
||||||
|
|||||||
@@ -40,3 +40,11 @@ internal fun requireRole(
|
|||||||
propertyAccess.requireAnyRole(propertyId, resolved.userId, *roles)
|
propertyAccess.requireAnyRole(propertyId, resolved.userId, *roles)
|
||||||
return resolved
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.android.trisolarisserver.repo.PropertyRepo
|
|||||||
import com.android.trisolarisserver.repo.RoomStayRepo
|
import com.android.trisolarisserver.repo.RoomStayRepo
|
||||||
import org.springframework.http.HttpStatus
|
import org.springframework.http.HttpStatus
|
||||||
import org.springframework.web.server.ResponseStatusException
|
import org.springframework.web.server.ResponseStatusException
|
||||||
|
import java.time.LocalDate
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.util.UUID
|
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 {
|
internal fun nowForProperty(timezone: String?): OffsetDateTime {
|
||||||
val zone = try {
|
val zone = try {
|
||||||
if (timezone.isNullOrBlank()) ZoneId.of("Asia/Kolkata") else ZoneId.of(timezone)
|
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)
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,24 +257,4 @@ class PayuQrPayments(
|
|||||||
return request.remoteAddr?.trim()?.ifBlank { null }
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class Properties(
|
|||||||
@AuthenticationPrincipal principal: MyPrincipal?,
|
@AuthenticationPrincipal principal: MyPrincipal?,
|
||||||
@RequestBody request: PropertyCreateRequest
|
@RequestBody request: PropertyCreateRequest
|
||||||
): PropertyResponse {
|
): PropertyResponse {
|
||||||
val user = requireUser(principal)
|
val user = requireUser(appUserRepo, principal)
|
||||||
if (propertyRepo.existsByCode(request.code)) {
|
if (propertyRepo.existsByCode(request.code)) {
|
||||||
throw ResponseStatusException(HttpStatus.CONFLICT, "Property code already exists")
|
throw ResponseStatusException(HttpStatus.CONFLICT, "Property code already exists")
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ class Properties(
|
|||||||
fun listProperties(
|
fun listProperties(
|
||||||
@AuthenticationPrincipal principal: MyPrincipal?
|
@AuthenticationPrincipal principal: MyPrincipal?
|
||||||
): List<PropertyResponse> {
|
): List<PropertyResponse> {
|
||||||
val user = requireUser(principal)
|
val user = requireUser(appUserRepo, principal)
|
||||||
return if (user.superAdmin) {
|
return if (user.superAdmin) {
|
||||||
propertyRepo.findAll().map { it.toResponse() }
|
propertyRepo.findAll().map { it.toResponse() }
|
||||||
} else {
|
} else {
|
||||||
@@ -211,21 +211,6 @@ class Properties(
|
|||||||
return propertyRepo.save(property).toResponse()
|
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> {
|
private fun parseTransportModes(modes: Set<String>): MutableSet<TransportMode> {
|
||||||
return try {
|
return try {
|
||||||
modes.map { TransportMode.valueOf(it) }.toMutableSet()
|
modes.map { TransportMode.valueOf(it) }.toMutableSet()
|
||||||
|
|||||||
@@ -132,8 +132,8 @@ class RatePlans(
|
|||||||
requireRole(propertyAccess, propertyId, principal, Role.ADMIN, Role.MANAGER)
|
requireRole(propertyAccess, propertyId, principal, Role.ADMIN, Role.MANAGER)
|
||||||
val plan = ratePlanRepo.findByIdAndPropertyId(ratePlanId, propertyId)
|
val plan = ratePlanRepo.findByIdAndPropertyId(ratePlanId, propertyId)
|
||||||
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Rate plan not found")
|
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Rate plan not found")
|
||||||
val fromDate = parseDate(request.from)
|
val fromDate = parseDate(request.from, "Invalid date")
|
||||||
val toDate = parseDate(request.to)
|
val toDate = parseDate(request.to, "Invalid date")
|
||||||
if (toDate.isBefore(fromDate)) {
|
if (toDate.isBefore(fromDate)) {
|
||||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "to must be on/after from")
|
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "to must be on/after from")
|
||||||
}
|
}
|
||||||
@@ -174,8 +174,8 @@ class RatePlans(
|
|||||||
requireMember(propertyAccess, propertyId, principal)
|
requireMember(propertyAccess, propertyId, principal)
|
||||||
val plan = ratePlanRepo.findByIdAndPropertyId(ratePlanId, propertyId)
|
val plan = ratePlanRepo.findByIdAndPropertyId(ratePlanId, propertyId)
|
||||||
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Rate plan not found")
|
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Rate plan not found")
|
||||||
val fromDate = parseDate(from)
|
val fromDate = parseDate(from, "Invalid date")
|
||||||
val toDate = parseDate(to)
|
val toDate = parseDate(to, "Invalid date")
|
||||||
if (toDate.isBefore(fromDate)) {
|
if (toDate.isBefore(fromDate)) {
|
||||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "to must be on/after from")
|
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)
|
requireRole(propertyAccess, propertyId, principal, Role.ADMIN, Role.MANAGER)
|
||||||
val plan = ratePlanRepo.findByIdAndPropertyId(ratePlanId, propertyId)
|
val plan = ratePlanRepo.findByIdAndPropertyId(ratePlanId, propertyId)
|
||||||
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Rate plan not found")
|
?: 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)
|
val existing = rateCalendarRepo.findByRatePlanIdAndRateDate(plan.id!!, date)
|
||||||
?: return
|
?: return
|
||||||
rateCalendarRepo.delete(existing)
|
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> {
|
private fun datesBetween(from: LocalDate, to: LocalDate): Sequence<LocalDate> {
|
||||||
return generateSequence(from) { current ->
|
return generateSequence(from) { current ->
|
||||||
val next = current.plusDays(1)
|
val next = current.plusDays(1)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class RoomAmenities(
|
|||||||
@AuthenticationPrincipal principal: MyPrincipal?,
|
@AuthenticationPrincipal principal: MyPrincipal?,
|
||||||
@RequestBody request: AmenityUpsertRequest
|
@RequestBody request: AmenityUpsertRequest
|
||||||
): AmenityResponse {
|
): AmenityResponse {
|
||||||
requireSuperAdmin(principal)
|
requireSuperAdmin(appUserRepo, principal)
|
||||||
|
|
||||||
if (roomAmenityRepo.existsByName(request.name)) {
|
if (roomAmenityRepo.existsByName(request.name)) {
|
||||||
throw ResponseStatusException(HttpStatus.CONFLICT, "Amenity already exists")
|
throw ResponseStatusException(HttpStatus.CONFLICT, "Amenity already exists")
|
||||||
@@ -68,7 +68,7 @@ class RoomAmenities(
|
|||||||
@AuthenticationPrincipal principal: MyPrincipal?,
|
@AuthenticationPrincipal principal: MyPrincipal?,
|
||||||
@RequestBody request: AmenityUpsertRequest
|
@RequestBody request: AmenityUpsertRequest
|
||||||
): AmenityResponse {
|
): AmenityResponse {
|
||||||
requireSuperAdmin(principal)
|
requireSuperAdmin(appUserRepo, principal)
|
||||||
|
|
||||||
val amenity = roomAmenityRepo.findById(amenityId).orElse(null)
|
val amenity = roomAmenityRepo.findById(amenityId).orElse(null)
|
||||||
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Amenity not found")
|
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Amenity not found")
|
||||||
@@ -91,7 +91,7 @@ class RoomAmenities(
|
|||||||
@PathVariable amenityId: UUID,
|
@PathVariable amenityId: UUID,
|
||||||
@AuthenticationPrincipal principal: MyPrincipal?
|
@AuthenticationPrincipal principal: MyPrincipal?
|
||||||
) {
|
) {
|
||||||
requireSuperAdmin(principal)
|
requireSuperAdmin(appUserRepo, principal)
|
||||||
|
|
||||||
val amenity = roomAmenityRepo.findById(amenityId).orElse(null)
|
val amenity = roomAmenityRepo.findById(amenityId).orElse(null)
|
||||||
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Amenity not found")
|
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Amenity not found")
|
||||||
@@ -106,24 +106,6 @@ class RoomAmenities(
|
|||||||
roomAmenityRepo.delete(amenity)
|
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?) {
|
private fun validateIconKey(iconKey: String?) {
|
||||||
if (iconKey.isNullOrBlank()) return
|
if (iconKey.isNullOrBlank()) return
|
||||||
val file = Paths.get(pngRoot, "${iconKey}.png")
|
val file = Paths.get(pngRoot, "${iconKey}.png")
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class RoomImageTags(
|
|||||||
@AuthenticationPrincipal principal: MyPrincipal?,
|
@AuthenticationPrincipal principal: MyPrincipal?,
|
||||||
@RequestBody request: RoomImageTagUpsertRequest
|
@RequestBody request: RoomImageTagUpsertRequest
|
||||||
): RoomImageTagResponse {
|
): RoomImageTagResponse {
|
||||||
requireSuperAdmin(principal)
|
requireSuperAdmin(appUserRepo, principal)
|
||||||
if (roomImageTagRepo.existsByName(request.name)) {
|
if (roomImageTagRepo.existsByName(request.name)) {
|
||||||
throw ResponseStatusException(HttpStatus.CONFLICT, "Tag already exists")
|
throw ResponseStatusException(HttpStatus.CONFLICT, "Tag already exists")
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@ class RoomImageTags(
|
|||||||
@AuthenticationPrincipal principal: MyPrincipal?,
|
@AuthenticationPrincipal principal: MyPrincipal?,
|
||||||
@RequestBody request: RoomImageTagUpsertRequest
|
@RequestBody request: RoomImageTagUpsertRequest
|
||||||
): RoomImageTagResponse {
|
): RoomImageTagResponse {
|
||||||
requireSuperAdmin(principal)
|
requireSuperAdmin(appUserRepo, principal)
|
||||||
val tag = roomImageTagRepo.findById(tagId).orElse(null)
|
val tag = roomImageTagRepo.findById(tagId).orElse(null)
|
||||||
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Tag not found")
|
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Tag not found")
|
||||||
if (roomImageTagRepo.existsByNameAndIdNot(request.name, tagId)) {
|
if (roomImageTagRepo.existsByNameAndIdNot(request.name, tagId)) {
|
||||||
@@ -74,30 +74,13 @@ class RoomImageTags(
|
|||||||
@PathVariable tagId: UUID,
|
@PathVariable tagId: UUID,
|
||||||
@AuthenticationPrincipal principal: MyPrincipal?
|
@AuthenticationPrincipal principal: MyPrincipal?
|
||||||
) {
|
) {
|
||||||
requireSuperAdmin(principal)
|
requireSuperAdmin(appUserRepo, principal)
|
||||||
val tag = roomImageTagRepo.findById(tagId).orElse(null)
|
val tag = roomImageTagRepo.findById(tagId).orElse(null)
|
||||||
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Tag not found")
|
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Tag not found")
|
||||||
roomImageRepo.deleteTagLinks(tagId)
|
roomImageRepo.deleteTagLinks(tagId)
|
||||||
roomImageTagRepo.delete(tag)
|
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 {
|
private fun RoomImageTag.toResponse(): RoomImageTagResponse {
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import org.springframework.web.bind.annotation.RequestParam
|
|||||||
import org.springframework.web.bind.annotation.ResponseStatus
|
import org.springframework.web.bind.annotation.ResponseStatus
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
import org.springframework.web.server.ResponseStatusException
|
import org.springframework.web.server.ResponseStatusException
|
||||||
import java.time.LocalDate
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -69,7 +68,7 @@ class RoomTypes(
|
|||||||
}
|
}
|
||||||
val roomType = roomTypeRepo.findByPropertyIdAndCodeIgnoreCase(propertyId, roomTypeCode)
|
val roomType = roomTypeRepo.findByPropertyIdAndCodeIgnoreCase(propertyId, roomTypeCode)
|
||||||
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Room type not found")
|
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Room type not found")
|
||||||
val rateDate = parseDate(date)
|
val rateDate = parseDate(date, "Invalid date")
|
||||||
|
|
||||||
if (!ratePlanCode.isNullOrBlank()) {
|
if (!ratePlanCode.isNullOrBlank()) {
|
||||||
val plan = ratePlanRepo.findByPropertyIdOrderByCode(propertyId)
|
val plan = ratePlanRepo.findByPropertyIdOrderByCode(propertyId)
|
||||||
@@ -198,19 +197,6 @@ class RoomTypes(
|
|||||||
roomTypeRepo.save(roomType)
|
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 {
|
private fun RoomType.toResponse(): RoomTypeResponse {
|
||||||
|
|||||||
@@ -188,8 +188,8 @@ class Rooms(
|
|||||||
ResponseStatusException(HttpStatus.NOT_FOUND, "Property not found")
|
ResponseStatusException(HttpStatus.NOT_FOUND, "Property not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
val fromDate = parseDate(from)
|
val fromDate = parseDate(from, "Invalid date format")
|
||||||
val toDate = parseDate(to)
|
val toDate = parseDate(to, "Invalid date format")
|
||||||
if (!toDate.isAfter(fromDate)) {
|
if (!toDate.isAfter(fromDate)) {
|
||||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid date range")
|
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid date range")
|
||||||
}
|
}
|
||||||
@@ -226,8 +226,8 @@ class Rooms(
|
|||||||
ResponseStatusException(HttpStatus.NOT_FOUND, "Property not found")
|
ResponseStatusException(HttpStatus.NOT_FOUND, "Property not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
val fromDate = parseDate(from)
|
val fromDate = parseDate(from, "Invalid date format")
|
||||||
val toDate = parseDate(to)
|
val toDate = parseDate(to, "Invalid date format")
|
||||||
if (!toDate.isAfter(fromDate)) {
|
if (!toDate.isAfter(fromDate)) {
|
||||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid date range")
|
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid date range")
|
||||||
}
|
}
|
||||||
@@ -392,26 +392,12 @@ class Rooms(
|
|||||||
roomBoardEvents.emit(propertyId)
|
roomBoardEvents.emit(propertyId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun requirePrincipal(principal: MyPrincipal?) {
|
|
||||||
if (principal == null) {
|
|
||||||
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing principal")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isAgentOnly(roles: Set<Role>): Boolean {
|
private fun isAgentOnly(roles: Set<Role>): Boolean {
|
||||||
if (!roles.contains(Role.AGENT)) return false
|
if (!roles.contains(Role.AGENT)) return false
|
||||||
val privileged = setOf(Role.ADMIN, Role.MANAGER, Role.STAFF, Role.HOUSEKEEPING, Role.FINANCE)
|
val privileged = setOf(Role.ADMIN, Role.MANAGER, Role.STAFF, Role.HOUSEKEEPING, Role.FINANCE)
|
||||||
return roles.none { it in privileged }
|
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? {
|
private fun selectRatePlan(plans: List<RatePlan>?, ratePlanCode: String?): RatePlan? {
|
||||||
if (plans.isNullOrEmpty()) return null
|
if (plans.isNullOrEmpty()) return null
|
||||||
if (!ratePlanCode.isNullOrBlank()) {
|
if (!ratePlanCode.isNullOrBlank()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user