Add booking details endpoint with guest, rooms, balance
All checks were successful
build-and-deploy / build-deploy (push) Successful in 1m34s
All checks were successful
build-and-deploy / build-deploy (push) Successful in 1m34s
This commit is contained in:
@@ -8,6 +8,7 @@ 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
|
||||
import com.android.trisolarisserver.controller.dto.BookingDetailResponse
|
||||
import com.android.trisolarisserver.controller.dto.BookingExpectedDatesUpdateRequest
|
||||
import com.android.trisolarisserver.controller.dto.BookingLinkGuestRequest
|
||||
import com.android.trisolarisserver.controller.dto.BookingNoShowRequest
|
||||
@@ -25,6 +26,7 @@ import com.android.trisolarisserver.models.room.RateSource
|
||||
import com.android.trisolarisserver.models.property.Role
|
||||
import com.android.trisolarisserver.repo.AppUserRepo
|
||||
import com.android.trisolarisserver.repo.GuestVehicleRepo
|
||||
import com.android.trisolarisserver.repo.PaymentRepo
|
||||
import com.android.trisolarisserver.repo.PropertyRepo
|
||||
import com.android.trisolarisserver.repo.RoomRepo
|
||||
import com.android.trisolarisserver.repo.RoomStayRepo
|
||||
@@ -57,7 +59,8 @@ class BookingFlow(
|
||||
private val roomBoardEvents: RoomBoardEvents,
|
||||
private val guestVehicleRepo: GuestVehicleRepo,
|
||||
private val guestRatingRepo: GuestRatingRepo,
|
||||
private val guestDocumentRepo: GuestDocumentRepo
|
||||
private val guestDocumentRepo: GuestDocumentRepo,
|
||||
private val paymentRepo: PaymentRepo
|
||||
) {
|
||||
|
||||
@PostMapping
|
||||
@@ -193,6 +196,76 @@ class BookingFlow(
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/{bookingId}")
|
||||
@Transactional
|
||||
fun getBooking(
|
||||
@PathVariable propertyId: UUID,
|
||||
@PathVariable bookingId: UUID,
|
||||
@AuthenticationPrincipal principal: MyPrincipal?
|
||||
): BookingDetailResponse {
|
||||
requireRole(
|
||||
propertyAccess,
|
||||
propertyId,
|
||||
principal,
|
||||
Role.ADMIN,
|
||||
Role.MANAGER,
|
||||
Role.STAFF,
|
||||
Role.HOUSEKEEPING,
|
||||
Role.FINANCE
|
||||
)
|
||||
val booking = requireBooking(propertyId, bookingId)
|
||||
val stays = roomStayRepo.findByBookingId(bookingId)
|
||||
val activeRooms = stays.filter { it.toAt == null }
|
||||
val roomsToShow = if (activeRooms.isNotEmpty()) activeRooms else stays
|
||||
val roomNumbers = roomsToShow.map { it.room.roomNumber }
|
||||
.distinct()
|
||||
.sorted()
|
||||
|
||||
val guest = booking.primaryGuest
|
||||
val signatureUrl = guest?.signaturePath?.let {
|
||||
"/properties/$propertyId/guests/${guest.id}/signature/file"
|
||||
}
|
||||
|
||||
val totalNightlyRate = roomsToShow.sumOf { it.nightlyRate ?: 0L }
|
||||
val expectedPay = computeExpectedPay(stays, booking.property.timezone)
|
||||
val amountCollected = paymentRepo.sumAmountByBookingId(bookingId)
|
||||
val pending = expectedPay - amountCollected
|
||||
|
||||
return BookingDetailResponse(
|
||||
id = booking.id!!,
|
||||
status = booking.status.name,
|
||||
guestId = guest?.id,
|
||||
guestName = guest?.name,
|
||||
guestPhone = guest?.phoneE164,
|
||||
guestNationality = guest?.nationality,
|
||||
guestAddressText = guest?.addressText,
|
||||
guestSignatureUrl = signatureUrl,
|
||||
roomNumbers = roomNumbers,
|
||||
source = booking.source,
|
||||
fromCity = booking.fromCity,
|
||||
toCity = booking.toCity,
|
||||
memberRelation = booking.memberRelation?.name,
|
||||
transportMode = booking.transportMode?.name,
|
||||
checkInAt = booking.checkinAt?.toString(),
|
||||
checkOutAt = booking.checkoutAt?.toString(),
|
||||
expectedCheckInAt = booking.expectedCheckinAt?.toString(),
|
||||
expectedCheckOutAt = booking.expectedCheckoutAt?.toString(),
|
||||
adultCount = booking.adultCount,
|
||||
childCount = booking.childCount,
|
||||
maleCount = booking.maleCount,
|
||||
femaleCount = booking.femaleCount,
|
||||
totalGuestCount = booking.totalGuestCount,
|
||||
expectedGuestCount = booking.expectedGuestCount,
|
||||
notes = booking.notes,
|
||||
registeredByName = booking.createdBy?.name,
|
||||
registeredByPhone = booking.createdBy?.phoneE164,
|
||||
totalNightlyRate = totalNightlyRate,
|
||||
expectedPay = expectedPay,
|
||||
amountCollected = amountCollected,
|
||||
pending = pending
|
||||
)
|
||||
}
|
||||
|
||||
@PostMapping("/{bookingId}/link-guest")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@Transactional
|
||||
@@ -619,6 +692,27 @@ 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 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
|
||||
|
||||
@@ -75,6 +75,40 @@ data class BookingListItem(
|
||||
val notes: String?
|
||||
)
|
||||
|
||||
data class BookingDetailResponse(
|
||||
val id: UUID,
|
||||
val status: String,
|
||||
val guestId: UUID?,
|
||||
val guestName: String?,
|
||||
val guestPhone: String?,
|
||||
val guestNationality: String?,
|
||||
val guestAddressText: String?,
|
||||
val guestSignatureUrl: String?,
|
||||
val roomNumbers: List<Int>,
|
||||
val source: String?,
|
||||
val fromCity: String?,
|
||||
val toCity: String?,
|
||||
val memberRelation: String?,
|
||||
val transportMode: String?,
|
||||
val checkInAt: String?,
|
||||
val checkOutAt: String?,
|
||||
val expectedCheckInAt: String?,
|
||||
val expectedCheckOutAt: String?,
|
||||
val adultCount: Int?,
|
||||
val childCount: Int?,
|
||||
val maleCount: Int?,
|
||||
val femaleCount: Int?,
|
||||
val totalGuestCount: Int?,
|
||||
val expectedGuestCount: Int?,
|
||||
val notes: String?,
|
||||
val registeredByName: String?,
|
||||
val registeredByPhone: String?,
|
||||
val totalNightlyRate: Long,
|
||||
val expectedPay: Long,
|
||||
val amountCollected: Long,
|
||||
val pending: Long
|
||||
)
|
||||
|
||||
data class BookingLinkGuestRequest(
|
||||
val guestId: UUID
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user