diff --git a/src/main/kotlin/com/android/trisolarisserver/controller/RoomStays.kt b/src/main/kotlin/com/android/trisolarisserver/controller/RoomStays.kt new file mode 100644 index 0000000..53e3e8d --- /dev/null +++ b/src/main/kotlin/com/android/trisolarisserver/controller/RoomStays.kt @@ -0,0 +1,74 @@ +package com.android.trisolarisserver.controller + +import com.android.trisolarisserver.component.PropertyAccess +import com.android.trisolarisserver.controller.dto.ActiveRoomStayResponse +import com.android.trisolarisserver.models.property.Role +import com.android.trisolarisserver.repo.PropertyUserRepo +import com.android.trisolarisserver.repo.RoomStayRepo +import com.android.trisolarisserver.security.MyPrincipal +import org.springframework.http.HttpStatus +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.server.ResponseStatusException +import java.util.UUID + +@RestController +class RoomStays( + private val propertyAccess: PropertyAccess, + private val propertyUserRepo: PropertyUserRepo, + private val roomStayRepo: RoomStayRepo +) { + + @GetMapping("/properties/{propertyId}/room-stays/active") + fun listActiveRoomStays( + @PathVariable propertyId: UUID, + @AuthenticationPrincipal principal: MyPrincipal? + ): List { + if (principal == null) { + throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing principal") + } + propertyAccess.requireMember(propertyId, principal.userId) + + val roles = propertyUserRepo.findRolesByPropertyAndUser(propertyId, principal.userId) + if (isAgentOnly(roles)) { + throw ResponseStatusException(HttpStatus.FORBIDDEN, "Agents cannot view active stays") + } + + return roomStayRepo.findActiveByPropertyIdWithDetails(propertyId).map { stay -> + val booking = stay.booking + val guest = booking.primaryGuest + val room = stay.room + val roomType = room.roomType + ActiveRoomStayResponse( + roomStayId = stay.id!!, + bookingId = booking.id!!, + guestId = guest?.id, + guestName = guest?.name, + guestPhone = guest?.phoneE164, + roomId = room.id!!, + roomNumber = room.roomNumber, + roomTypeId = roomType.id!!, + roomTypeName = roomType.name, + fromAt = stay.fromAt.toString(), + checkinAt = booking.checkinAt?.toString(), + expectedCheckoutAt = booking.expectedCheckoutAt?.toString() + ) + } + } + + private fun isAgentOnly(roles: Set): Boolean { + if (!roles.contains(Role.AGENT)) return false + val privileged = setOf( + Role.ADMIN, + Role.MANAGER, + Role.STAFF, + Role.HOUSEKEEPING, + Role.FINANCE, + Role.SUPERVISOR, + Role.GUIDE + ) + return roles.none { it in privileged } + } +} diff --git a/src/main/kotlin/com/android/trisolarisserver/controller/dto/RoomStayDtos.kt b/src/main/kotlin/com/android/trisolarisserver/controller/dto/RoomStayDtos.kt new file mode 100644 index 0000000..2dbc56a --- /dev/null +++ b/src/main/kotlin/com/android/trisolarisserver/controller/dto/RoomStayDtos.kt @@ -0,0 +1,18 @@ +package com.android.trisolarisserver.controller.dto + +import java.util.UUID + +data class ActiveRoomStayResponse( + val roomStayId: UUID, + val bookingId: UUID, + val guestId: UUID?, + val guestName: String?, + val guestPhone: String?, + val roomId: UUID, + val roomNumber: Int, + val roomTypeId: UUID, + val roomTypeName: String, + val fromAt: String, + val checkinAt: String?, + val expectedCheckoutAt: String? +) diff --git a/src/main/kotlin/com/android/trisolarisserver/repo/RoomStayRepo.kt b/src/main/kotlin/com/android/trisolarisserver/repo/RoomStayRepo.kt index e794e27..a512b6f 100644 --- a/src/main/kotlin/com/android/trisolarisserver/repo/RoomStayRepo.kt +++ b/src/main/kotlin/com/android/trisolarisserver/repo/RoomStayRepo.kt @@ -62,4 +62,17 @@ interface RoomStayRepo : JpaRepository { @Param("fromAt") fromAt: java.time.OffsetDateTime, @Param("toAt") toAt: java.time.OffsetDateTime ): Boolean + + @Query(""" + select rs + from RoomStay rs + join fetch rs.room r + join fetch r.roomType rt + join fetch rs.booking b + left join fetch b.primaryGuest g + where rs.property.id = :propertyId + and rs.toAt is null + order by r.roomNumber + """) + fun findActiveByPropertyIdWithDetails(@Param("propertyId") propertyId: UUID): List }