Add booking billable nights preview API and detail field
All checks were successful
build-and-deploy / build-deploy (push) Successful in 35s
All checks were successful
build-and-deploy / build-deploy (push) Successful in 35s
This commit is contained in:
@@ -10,6 +10,8 @@ import com.android.trisolarisserver.component.booking.BookingEvents
|
||||
import com.android.trisolarisserver.component.auth.PropertyAccess
|
||||
import com.android.trisolarisserver.component.room.RoomBoardEvents
|
||||
import com.android.trisolarisserver.controller.dto.booking.BookingCancelRequest
|
||||
import com.android.trisolarisserver.controller.dto.booking.BookingBillableNightsRequest
|
||||
import com.android.trisolarisserver.controller.dto.booking.BookingBillableNightsResponse
|
||||
import com.android.trisolarisserver.controller.dto.booking.BookingBulkCheckInRequest
|
||||
import com.android.trisolarisserver.controller.dto.booking.BookingCheckOutRequest
|
||||
import com.android.trisolarisserver.controller.dto.booking.BookingCreateRequest
|
||||
@@ -298,6 +300,44 @@ class BookingFlow(
|
||||
return bookingSnapshotBuilder.build(propertyId, bookingId)
|
||||
}
|
||||
|
||||
@PostMapping("/{bookingId}/billable-nights")
|
||||
fun previewBillableNights(
|
||||
@PathVariable propertyId: UUID,
|
||||
@PathVariable bookingId: UUID,
|
||||
@AuthenticationPrincipal principal: MyPrincipal?,
|
||||
@RequestBody(required = false) request: BookingBillableNightsRequest?
|
||||
): BookingBillableNightsResponse {
|
||||
requireRole(
|
||||
propertyAccess,
|
||||
propertyId,
|
||||
principal,
|
||||
Role.ADMIN,
|
||||
Role.MANAGER,
|
||||
Role.STAFF,
|
||||
Role.HOUSEKEEPING,
|
||||
Role.FINANCE
|
||||
)
|
||||
val booking = requireBooking(propertyId, bookingId)
|
||||
val resolvedRequest = request ?: BookingBillableNightsRequest()
|
||||
val (startAt, endAt) = resolveBillableNightsWindow(booking, resolvedRequest)
|
||||
if (!endAt.isAfter(startAt)) {
|
||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid date range")
|
||||
}
|
||||
val nights = billableNights(
|
||||
startAt = startAt,
|
||||
endAt = endAt,
|
||||
timezone = booking.property.timezone,
|
||||
billingMode = booking.billingMode,
|
||||
billingCheckinTime = booking.billingCheckinTime,
|
||||
billingCheckoutTime = booking.billingCheckoutTime
|
||||
)
|
||||
return BookingBillableNightsResponse(
|
||||
bookingId = bookingId,
|
||||
status = booking.status.name,
|
||||
billableNights = nights
|
||||
)
|
||||
}
|
||||
|
||||
@GetMapping("/{bookingId}/stream")
|
||||
fun streamBooking(
|
||||
@PathVariable propertyId: UUID,
|
||||
@@ -753,6 +793,43 @@ class BookingFlow(
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveBillableNightsWindow(
|
||||
booking: com.android.trisolarisserver.models.booking.Booking,
|
||||
request: BookingBillableNightsRequest
|
||||
): Pair<OffsetDateTime, OffsetDateTime> {
|
||||
return when (booking.status) {
|
||||
BookingStatus.OPEN -> {
|
||||
val expectedCheckIn = parseOffset(request.expectedCheckInAt)
|
||||
?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "expectedCheckInAt required for OPEN booking")
|
||||
val expectedCheckOut = parseOffset(request.expectedCheckOutAt)
|
||||
?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "expectedCheckOutAt required for OPEN booking")
|
||||
Pair(expectedCheckIn, expectedCheckOut)
|
||||
}
|
||||
BookingStatus.CHECKED_IN -> {
|
||||
if (request.expectedCheckInAt != null) {
|
||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "expectedCheckInAt not allowed for CHECKED_IN booking")
|
||||
}
|
||||
val checkInAt = booking.checkinAt ?: booking.expectedCheckinAt
|
||||
?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "checkInAt missing for booking")
|
||||
val expectedCheckOut = parseOffset(request.expectedCheckOutAt)
|
||||
?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "expectedCheckOutAt required for CHECKED_IN booking")
|
||||
Pair(checkInAt, expectedCheckOut)
|
||||
}
|
||||
BookingStatus.CHECKED_OUT,
|
||||
BookingStatus.CANCELLED,
|
||||
BookingStatus.NO_SHOW -> {
|
||||
if (request.expectedCheckInAt != null || request.expectedCheckOutAt != null) {
|
||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "expected dates not accepted for closed booking")
|
||||
}
|
||||
val startAt = booking.checkinAt ?: booking.expectedCheckinAt
|
||||
?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "checkInAt missing for booking")
|
||||
val endAt = booking.checkoutAt ?: booking.expectedCheckoutAt
|
||||
?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "checkOutAt missing for booking")
|
||||
Pair(startAt, endAt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseTransportMode(value: String): TransportMode {
|
||||
return try {
|
||||
TransportMode.valueOf(value)
|
||||
|
||||
Reference in New Issue
Block a user