Add bulk check-in with per-room rates
All checks were successful
build-and-deploy / build-deploy (push) Successful in 1m35s

This commit is contained in:
androidlover5842
2026-01-29 11:12:23 +05:30
parent bef941f417
commit 173d3fa9ea
2 changed files with 104 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.BookingCancelRequest
import com.android.trisolarisserver.controller.dto.BookingCheckInRequest
import com.android.trisolarisserver.controller.dto.BookingBulkCheckInRequest
import com.android.trisolarisserver.controller.dto.BookingCheckOutRequest
import com.android.trisolarisserver.controller.dto.BookingCreateRequest
import com.android.trisolarisserver.controller.dto.BookingCreateResponse
@@ -267,6 +268,93 @@ class BookingFlow(
roomBoardEvents.emit(propertyId)
}
@PostMapping("/{bookingId}/check-in/bulk")
@ResponseStatus(HttpStatus.CREATED)
@Transactional
fun bulkCheckIn(
@PathVariable propertyId: UUID,
@PathVariable bookingId: UUID,
@AuthenticationPrincipal principal: MyPrincipal?,
@RequestBody request: BookingBulkCheckInRequest
) {
val actor = requireActor(propertyId, principal)
if (request.stays.isEmpty()) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "stays required")
}
val booking = requireBooking(propertyId, bookingId)
if (booking.status != BookingStatus.OPEN) {
throw ResponseStatusException(HttpStatus.CONFLICT, "Booking not open")
}
val roomIds = request.stays.map { it.roomId }
if (roomIds.distinct().size != roomIds.size) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Duplicate roomId in stays")
}
val rooms = request.stays.associate { stay ->
val room = roomRepo.findByIdAndPropertyId(stay.roomId, propertyId)
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Room not found")
if (!room.active || room.maintenance) {
throw ResponseStatusException(HttpStatus.CONFLICT, "Room not available")
}
stay.roomId to room
}
val occupied = roomStayRepo.findActiveRoomIds(propertyId, roomIds)
if (occupied.isNotEmpty()) {
throw ResponseStatusException(HttpStatus.CONFLICT, "Room already occupied")
}
val now = OffsetDateTime.now()
val checkInTimes = mutableListOf<OffsetDateTime>()
val checkOutTimes = mutableListOf<OffsetDateTime>()
request.stays.forEach { stay ->
val checkInAt = parseOffset(stay.checkInAt) ?: now
checkInTimes.add(checkInAt)
val checkOutAt = parseOffset(stay.checkOutAt)
if (checkOutAt != null) {
if (!checkOutAt.isAfter(checkInAt)) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid date range for stay")
}
checkOutTimes.add(checkOutAt)
}
val room = rooms.getValue(stay.roomId)
val newStay = RoomStay(
property = booking.property,
booking = booking,
room = room,
fromAt = checkInAt,
toAt = null,
rateSource = parseRateSource(stay.rateSource),
nightlyRate = stay.nightlyRate,
ratePlanCode = stay.ratePlanCode,
currency = stay.currency ?: booking.property.currency,
createdBy = actor
)
roomStayRepo.save(newStay)
}
val bookingCheckInAt = checkInTimes.minOrNull() ?: now
val bookingExpectedCheckout = checkOutTimes.maxOrNull()
booking.status = BookingStatus.CHECKED_IN
booking.checkinAt = bookingCheckInAt
if (bookingExpectedCheckout != null) {
booking.expectedCheckoutAt = bookingExpectedCheckout
}
booking.transportMode = request.transportMode?.let {
val mode = parseTransportMode(it)
if (!isTransportModeAllowed(booking.property, mode)) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Transport mode disabled")
}
mode
}
if (request.notes != null) booking.notes = request.notes
booking.updatedAt = now
bookingRepo.save(booking)
roomBoardEvents.emit(propertyId)
}
@PostMapping("/{bookingId}/check-out")
@ResponseStatus(HttpStatus.NO_CONTENT)
@Transactional

View File

@@ -13,6 +13,22 @@ data class BookingCheckInRequest(
val notes: String? = null
)
data class BookingCheckInStayRequest(
val roomId: UUID,
val checkInAt: String? = null,
val checkOutAt: String? = null,
val nightlyRate: Long? = null,
val rateSource: String? = null,
val ratePlanCode: String? = null,
val currency: String? = null
)
data class BookingBulkCheckInRequest(
val stays: List<BookingCheckInStayRequest>,
val transportMode: String? = null,
val notes: String? = null
)
data class BookingCreateRequest(
val source: String? = null,
val expectedCheckInAt: String,