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.BookingCheckOutRequest
|
||||||
import com.android.trisolarisserver.controller.dto.BookingCreateRequest
|
import com.android.trisolarisserver.controller.dto.BookingCreateRequest
|
||||||
import com.android.trisolarisserver.controller.dto.BookingCreateResponse
|
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.BookingExpectedDatesUpdateRequest
|
||||||
import com.android.trisolarisserver.controller.dto.BookingLinkGuestRequest
|
import com.android.trisolarisserver.controller.dto.BookingLinkGuestRequest
|
||||||
import com.android.trisolarisserver.controller.dto.BookingNoShowRequest
|
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.models.property.Role
|
||||||
import com.android.trisolarisserver.repo.AppUserRepo
|
import com.android.trisolarisserver.repo.AppUserRepo
|
||||||
import com.android.trisolarisserver.repo.GuestVehicleRepo
|
import com.android.trisolarisserver.repo.GuestVehicleRepo
|
||||||
|
import com.android.trisolarisserver.repo.PaymentRepo
|
||||||
import com.android.trisolarisserver.repo.PropertyRepo
|
import com.android.trisolarisserver.repo.PropertyRepo
|
||||||
import com.android.trisolarisserver.repo.RoomRepo
|
import com.android.trisolarisserver.repo.RoomRepo
|
||||||
import com.android.trisolarisserver.repo.RoomStayRepo
|
import com.android.trisolarisserver.repo.RoomStayRepo
|
||||||
@@ -57,7 +59,8 @@ class BookingFlow(
|
|||||||
private val roomBoardEvents: RoomBoardEvents,
|
private val roomBoardEvents: RoomBoardEvents,
|
||||||
private val guestVehicleRepo: GuestVehicleRepo,
|
private val guestVehicleRepo: GuestVehicleRepo,
|
||||||
private val guestRatingRepo: GuestRatingRepo,
|
private val guestRatingRepo: GuestRatingRepo,
|
||||||
private val guestDocumentRepo: GuestDocumentRepo
|
private val guestDocumentRepo: GuestDocumentRepo,
|
||||||
|
private val paymentRepo: PaymentRepo
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@PostMapping
|
@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")
|
@PostMapping("/{bookingId}/link-guest")
|
||||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
@Transactional
|
@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(
|
private fun isTransportModeAllowed(
|
||||||
property: com.android.trisolarisserver.models.property.Property,
|
property: com.android.trisolarisserver.models.property.Property,
|
||||||
mode: TransportMode
|
mode: TransportMode
|
||||||
|
|||||||
@@ -75,6 +75,40 @@ data class BookingListItem(
|
|||||||
val notes: String?
|
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(
|
data class BookingLinkGuestRequest(
|
||||||
val guestId: UUID
|
val guestId: UUID
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user