diff --git a/src/main/kotlin/com/android/trisolarisserver/controller/Rooms.kt b/src/main/kotlin/com/android/trisolarisserver/controller/Rooms.kt index bd7468b..00c5032 100644 --- a/src/main/kotlin/com/android/trisolarisserver/controller/Rooms.kt +++ b/src/main/kotlin/com/android/trisolarisserver/controller/Rooms.kt @@ -10,6 +10,7 @@ import com.android.trisolarisserver.controller.dto.RoomResponse import com.android.trisolarisserver.controller.dto.RoomUpsertRequest import com.android.trisolarisserver.repo.PropertyRepo import com.android.trisolarisserver.repo.PropertyUserRepo +import com.android.trisolarisserver.repo.IssuedCardRepo import com.android.trisolarisserver.repo.RoomImageRepo import com.android.trisolarisserver.repo.RoomRepo import com.android.trisolarisserver.repo.RoomStayRepo @@ -31,6 +32,7 @@ import org.springframework.web.bind.annotation.RestController import org.springframework.web.server.ResponseStatusException import org.springframework.web.servlet.mvc.method.annotation.SseEmitter import java.time.LocalDate +import java.time.OffsetDateTime import java.time.ZoneId import java.nio.file.Files import java.nio.file.Paths @@ -45,6 +47,7 @@ class Rooms( private val roomStayRepo: RoomStayRepo, private val propertyRepo: PropertyRepo, private val roomTypeRepo: RoomTypeRepo, + private val issuedCardRepo: IssuedCardRepo, private val propertyUserRepo: PropertyUserRepo, private val roomBoardEvents: RoomBoardEvents ) { @@ -58,14 +61,15 @@ class Rooms( propertyAccess.requireMember(propertyId, principal!!.userId) val roles = propertyUserRepo.findRolesByPropertyAndUser(propertyId, principal.userId) val rooms = roomRepo.findByPropertyIdOrderByRoomNumber(propertyId) + val tempCardsByRoom = loadActiveTempCardsByRoom(propertyId) if (isAgentOnly(roles)) { val occupiedRoomIds = roomStayRepo.findOccupiedRoomIds(propertyId).toHashSet() return rooms .filter { it.active && !it.maintenance && !occupiedRoomIds.contains(it.id) } - .map { it.toRoomResponse() } + .map { it.toRoomResponse(tempCardsByRoom[it.id]) } } return rooms - .map { it.toRoomResponse() } + .map { it.toRoomResponse(tempCardsByRoom[it.id]) } } @GetMapping("/board") @@ -134,9 +138,10 @@ class Rooms( ): List { val rooms = roomRepo.findByPropertyIdOrderByRoomNumber(propertyId) val occupiedRoomIds = roomStayRepo.findOccupiedRoomIds(propertyId).toHashSet() + val tempCardsByRoom = loadActiveTempCardsByRoom(propertyId) return rooms .filter { it.active && !it.maintenance && !occupiedRoomIds.contains(it.id) } - .map { it.toRoomResponse() } + .map { it.toRoomResponse(tempCardsByRoom[it.id]) } } @GetMapping("/by-type/{roomTypeCode}") @@ -152,14 +157,15 @@ class Rooms( val rooms = roomRepo.findByPropertyIdOrderByRoomNumber(propertyId) .filter { it.roomType.id == roomType.id } + val tempCardsByRoom = loadActiveTempCardsByRoom(propertyId) if (availableOnly || (principal != null && isAgentOnly(propertyUserRepo.findRolesByPropertyAndUser(propertyId, principal.userId)))) { val occupiedRoomIds = roomStayRepo.findOccupiedRoomIds(propertyId).toHashSet() return rooms .filter { it.active && !it.maintenance && !occupiedRoomIds.contains(it.id) } - .map { it.toRoomResponse() } + .map { it.toRoomResponse(tempCardsByRoom[it.id]) } } - return rooms.map { it.toRoomResponse() } + return rooms.map { it.toRoomResponse(tempCardsByRoom[it.id]) } } @GetMapping("/availability-range") @@ -238,7 +244,9 @@ class Rooms( hasNfc = saved.hasNfc, active = saved.active, maintenance = saved.maintenance, - notes = saved.notes + notes = saved.notes, + tempCardActive = false, + tempCardExpiresAt = null ) roomBoardEvents.emit(propertyId) return response @@ -280,7 +288,9 @@ class Rooms( hasNfc = saved.hasNfc, active = saved.active, maintenance = saved.maintenance, - notes = saved.notes + notes = saved.notes, + tempCardActive = false, + tempCardExpiresAt = null ) roomBoardEvents.emit(propertyId) return response @@ -348,9 +358,16 @@ class Rooms( return roomTypeRepo.findByPropertyIdAndCodeIgnoreCase(propertyId, code) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Room type not found") } + + private fun loadActiveTempCardsByRoom(propertyId: UUID): Map { + val now = OffsetDateTime.now() + return issuedCardRepo.findActiveTempCardsForProperty(propertyId, now) + .groupBy { it.room.id ?: throw IllegalStateException("Room id is null") } + .mapValues { (_, cards) -> cards.maxBy { it.expiresAt }.expiresAt } + } } -private fun Room.toRoomResponse(): RoomResponse { +private fun Room.toRoomResponse(tempCardExpiresAt: OffsetDateTime? = null): RoomResponse { val roomId = id ?: throw IllegalStateException("Room id is null") return RoomResponse( id = roomId, @@ -360,6 +377,8 @@ private fun Room.toRoomResponse(): RoomResponse { hasNfc = hasNfc, active = active, maintenance = maintenance, - notes = notes + notes = notes, + tempCardActive = tempCardExpiresAt != null, + tempCardExpiresAt = tempCardExpiresAt?.toString() ) } diff --git a/src/main/kotlin/com/android/trisolarisserver/controller/dto/RoomDtos.kt b/src/main/kotlin/com/android/trisolarisserver/controller/dto/RoomDtos.kt index ac3e214..6820c8f 100644 --- a/src/main/kotlin/com/android/trisolarisserver/controller/dto/RoomDtos.kt +++ b/src/main/kotlin/com/android/trisolarisserver/controller/dto/RoomDtos.kt @@ -10,7 +10,9 @@ data class RoomResponse( val hasNfc: Boolean, val active: Boolean, val maintenance: Boolean, - val notes: String? + val notes: String?, + val tempCardActive: Boolean = false, + val tempCardExpiresAt: String? = null ) data class RoomBoardResponse( diff --git a/src/main/kotlin/com/android/trisolarisserver/repo/IssuedCardRepo.kt b/src/main/kotlin/com/android/trisolarisserver/repo/IssuedCardRepo.kt index 30fbdf3..bbe34bf 100644 --- a/src/main/kotlin/com/android/trisolarisserver/repo/IssuedCardRepo.kt +++ b/src/main/kotlin/com/android/trisolarisserver/repo/IssuedCardRepo.kt @@ -33,4 +33,17 @@ interface IssuedCardRepo : JpaRepository { @org.springframework.data.repository.query.Param("roomStayId") roomStayId: UUID, @org.springframework.data.repository.query.Param("now") now: java.time.OffsetDateTime ): Boolean + + @org.springframework.data.jpa.repository.Query(""" + select c + from IssuedCard c + where c.property.id = :propertyId + and c.roomStay is null + and c.revokedAt is null + and c.expiresAt > :now + """) + fun findActiveTempCardsForProperty( + @org.springframework.data.repository.query.Param("propertyId") propertyId: UUID, + @org.springframework.data.repository.query.Param("now") now: java.time.OffsetDateTime + ): List }