Skip to content

Commit

Permalink
Merge pull request #2243 from ministryofjustice/feature/aps-1222-impl…
Browse files Browse the repository at this point in the history
…ement-get-space-booking-api

APS-1222 - Implement get space booking api
  • Loading branch information
davidatkinsuk authored Sep 6, 2024
2 parents c5309b7 + af4789b commit 76a1aa4
Show file tree
Hide file tree
Showing 20 changed files with 431 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.Cas1SpaceBooki
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.Cas1SpaceBookingSummarySortField
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.NewCas1SpaceBooking
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.SortDirection
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.Cas1SpaceBookingEntity
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.UserPermission.CAS1_SPACE_BOOKING_LIST
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.UserPermission.CAS1_SPACE_BOOKING_VIEW
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.UserQualification
import uk.gov.justice.digital.hmpps.approvedpremisesapi.model.forCrn
import uk.gov.justice.digital.hmpps.approvedpremisesapi.service.OffenderService
Expand Down Expand Up @@ -50,13 +52,7 @@ class SpaceBookingController(
),
)

val person = offenderService.getPersonInfoResult(
booking.placementRequest.application.crn,
user.deliusUsername,
user.hasQualification(UserQualification.LAO),
)

return ResponseEntity.ok(spaceBookingTransformer.transformJpaToApi(person, booking))
return ResponseEntity.ok(toCas1SpaceBooking(booking))
}

override fun premisesPremisesIdSpaceBookingsGet(
Expand Down Expand Up @@ -104,11 +100,14 @@ class SpaceBookingController(
.body(summaries)
}

override fun premisesPremisesIdSpaceBookingsBookingIdGet(
premisesId: UUID,
bookingId: UUID,
): ResponseEntity<Cas1SpaceBooking> {
return super.premisesPremisesIdSpaceBookingsBookingIdGet(premisesId, bookingId)
override fun getSpaceBookingById(premisesId: UUID, bookingId: UUID): ResponseEntity<Cas1SpaceBooking> {
userAccessService.ensureCurrentUserHasPermission(CAS1_SPACE_BOOKING_VIEW)

val booking = extractEntityFromCasResult(spaceBookingService.getBooking(premisesId, bookingId))

return ResponseEntity
.ok()
.body(toCas1SpaceBooking(booking))
}

override fun premisesPremisesIdSpaceBookingsBookingIdArrivalPost(
Expand All @@ -134,4 +133,21 @@ class SpaceBookingController(
): ResponseEntity<Unit> {
return super.premisesPremisesIdSpaceBookingsBookingIdKeyworkerPost(premisesId, bookingId, cas1AssignKeyWorker)
}

private fun toCas1SpaceBooking(booking: Cas1SpaceBookingEntity): Cas1SpaceBooking {
val user = userService.getUserForRequest()

val person = offenderService.getPersonInfoResult(
booking.placementRequest.application.crn,
user.deliusUsername,
user.hasQualification(UserQualification.LAO),
)

val otherBookingsInPremiseForCrn = spaceBookingService.getBookingsForPremisesAndCrn(
premisesId = booking.premises.id,
crn = booking.crn,
).filter { it.id != booking.id }

return spaceBookingTransformer.transformJpaToApi(person, booking, otherBookingsInPremiseForCrn)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,22 @@ interface Cas1SpaceBookingRepository : JpaRepository<Cas1SpaceBookingEntity, UUI
premisesId: UUID,
pageable: Pageable?,
): Page<Cas1SpaceBookingSearchResult>

@Query(
value = """
SELECT
Cast(b.id as varchar),
b.canonical_arrival_date as canonicalArrivalDate,
b.canonical_departure_date as canonicalDepartureDate
FROM cas1_space_bookings b
WHERE b.premises_id = :premisesId AND b.crn = :crn
""",
nativeQuery = true,
)
fun findByPremisesIdAndCrn(
premisesId: UUID,
crn: String,
): List<Cas1SpaceBookingAtPremises>
}

interface Cas1SpaceBookingSearchResult {
Expand All @@ -72,6 +88,12 @@ interface Cas1SpaceBookingSearchResult {
val keyWorkerName: String?
}

interface Cas1SpaceBookingAtPremises {
val id: UUID
val canonicalArrivalDate: LocalDate
val canonicalDepartureDate: LocalDate
}

@Entity
@Table(name = "cas1_space_bookings")
data class Cas1SpaceBookingEntity(
Expand All @@ -82,7 +104,7 @@ data class Cas1SpaceBookingEntity(
val premises: ApprovedPremisesEntity,
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "approved_premises_application_id")
val application: ApprovedPremisesApplicationEntity?,
val application: ApprovedPremisesApplicationEntity,
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "placement_request_id")
val placementRequest: PlacementRequestEntity,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ interface PremisesRepository : JpaRepository<PremisesEntity, UUID> {
)
fun findAllApprovedPremisesSummary(probationRegionId: UUID?, apAreaId: UUID?): List<ApprovedPremisesSummary>

@Query("SELECT p as premises FROM ApprovedPremisesEntity p WHERE p.id = :id")
fun findApprovedPremisesByIdOrNull(id: UUID): ApprovedPremisesEntity?

@Query("SELECT p as premises, $BED_COUNT_QUERY as bedCount FROM PremisesEntity p WHERE TYPE(p) = :type")
fun <T : PremisesEntity> findAllByType(type: Class<T>): List<PremisesWithBedCount>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ enum class UserRole(val service: ServiceName, val cas1ApiValue: ApprovedPremises
UserPermission.CAS1_OUT_OF_SERVICE_BED_CREATE,
UserPermission.CAS1_SPACE_BOOKING_LIST,
UserPermission.CAS1_PREMISES_VIEW_SUMMARY,
UserPermission.CAS1_SPACE_BOOKING_VIEW,
),
),
CAS1_WORKFLOW_MANAGER(
Expand Down Expand Up @@ -485,6 +486,7 @@ enum class UserPermission {
CAS1_VIEW_MANAGE_TASKS,
CAS1_VIEW_OUT_OF_SERVICE_BEDS,
CAS1_SPACE_BOOKING_LIST,
CAS1_SPACE_BOOKING_VIEW,
CAS1_PREMISES_VIEW_SUMMARY,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ class PremisesService(
premisesRepository.findAllByProbationRegionAndType(probationRegionId, it)
} ?: listOf()

fun getApprovedPremises(premisesId: UUID) = premisesRepository.findApprovedPremisesByIdOrNull(premisesId)

fun getPremises(premisesId: UUID): PremisesEntity? = premisesRepository.findByIdOrNull(premisesId)

fun getPremisesSummary(premisesId: UUID): List<BookingSummary> = premisesRepository.getBookingSummariesForPremisesId(premisesId)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package uk.gov.justice.digital.hmpps.approvedpremisesapi.service.cas1

import kotlinx.datetime.toKotlinDatePeriod
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.Cas1SpaceBookingResidency
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.Cas1SpaceBookingSummarySortField
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ApprovedPremisesEntity
Expand Down Expand Up @@ -29,14 +31,15 @@ class Cas1SpaceBookingService(
private val cas1SpaceSearchRepository: Cas1SpaceSearchRepository,
private val cas1BookingDomainEventService: Cas1BookingDomainEventService,
) {
@Transactional
fun createNewBooking(
premisesId: UUID,
placementRequestId: UUID,
arrivalDate: LocalDate,
departureDate: LocalDate,
createdBy: UserEntity,
): CasResult<Cas1SpaceBookingEntity> = validatedCasResult {
val premises = premisesService.getPremises(premisesId) as? ApprovedPremisesEntity
val premises = premisesService.getApprovedPremises(premisesId)
if (premises == null) {
"$.premisesId" hasValidationError "doesNotExist"
}
Expand Down Expand Up @@ -103,7 +106,7 @@ class Cas1SpaceBookingService(
filterCriteria: SpaceBookingFilterCriteria,
pageCriteria: PageCriteria<Cas1SpaceBookingSummarySortField>,
): CasResult<Pair<List<Cas1SpaceBookingSearchResult>, PaginationMetadata?>> {
if (premisesService.getPremises(premisesId) !is ApprovedPremisesEntity) return CasResult.NotFound("premises", premisesId.toString())
if (premisesService.getApprovedPremises(premisesId) == null) return CasResult.NotFound("premises", premisesId.toString())

val page = cas1SpaceBookingRepository.search(
filterCriteria.residency?.name,
Expand All @@ -128,6 +131,19 @@ class Cas1SpaceBookingService(
)
}

fun getBooking(premisesId: UUID, bookingId: UUID): CasResult<Cas1SpaceBookingEntity> {
if (premisesService.getApprovedPremises(premisesId) !is ApprovedPremisesEntity) return CasResult.NotFound("premises", premisesId.toString())

val booking = cas1SpaceBookingRepository.findByIdOrNull(bookingId) ?: return CasResult.NotFound("booking", bookingId.toString())

return CasResult.Success(booking)
}

fun getBookingsForPremisesAndCrn(premisesId: UUID, crn: String) = cas1SpaceBookingRepository.findByPremisesIdAndCrn(
premisesId = premisesId,
crn = crn,
)

data class SpaceBookingFilterCriteria(
val residency: Cas1SpaceBookingResidency?,
val crnOrName: String?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ class UserTransformer(
UserPermission.CAS1_VIEW_MANAGE_TASKS -> ApiUserPermission.viewManageTasks
UserPermission.CAS1_VIEW_OUT_OF_SERVICE_BEDS -> ApiUserPermission.viewOutOfServiceBeds
UserPermission.CAS1_SPACE_BOOKING_LIST -> ApiUserPermission.spaceBookingList
UserPermission.CAS1_SPACE_BOOKING_VIEW -> ApiUserPermission.spaceBookingView
UserPermission.CAS1_PREMISES_VIEW_SUMMARY -> ApiUserPermission.premisesViewSummary
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package uk.gov.justice.digital.hmpps.approvedpremisesapi.transformer.cas1
import org.springframework.stereotype.Component
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.Cas1KeyWorkerAllocation
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.Cas1SpaceBooking
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.Cas1SpaceBookingDates
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.Cas1SpaceBookingSummary
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.NamedId
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.ServiceName
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.StaffMember
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.Cas1SpaceBookingAtPremises
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.Cas1SpaceBookingEntity
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.Cas1SpaceBookingSearchResult
import uk.gov.justice.digital.hmpps.approvedpremisesapi.model.PersonInfoResult
Expand All @@ -21,32 +23,67 @@ class Cas1SpaceBookingTransformer(
private val spaceBookingRequirementsTransformer: Cas1SpaceBookingRequirementsTransformer,
private val userTransformer: UserTransformer,
) {
fun transformJpaToApi(person: PersonInfoResult, jpa: Cas1SpaceBookingEntity) = Cas1SpaceBooking(
id = jpa.id,
person = personTransformer.transformModelToPersonApi(person),
requirements = spaceBookingRequirementsTransformer.transformJpaToApi(jpa.placementRequest.placementRequirements),
premises = NamedId(
id = jpa.premises.id,
name = jpa.premises.name,
),
apArea = jpa.premises.probationRegion.apArea!!.let {
NamedId(
id = it.id,
name = it.name,
fun transformJpaToApi(
person: PersonInfoResult,
jpa: Cas1SpaceBookingEntity,
otherBookingsAtPremiseForCrn: List<Cas1SpaceBookingAtPremises>,
): Cas1SpaceBooking {
val placementRequest = jpa.placementRequest
val application = placementRequest.application
return Cas1SpaceBooking(
id = jpa.id,
applicationId = application.id,
assessmentId = placementRequest.assessment.id,
person = personTransformer.transformModelToPersonApi(person),
requirements = spaceBookingRequirementsTransformer.transformJpaToApi(placementRequest.placementRequirements),
premises = NamedId(
id = jpa.premises.id,
name = jpa.premises.name,
),
apArea = jpa.premises.probationRegion.apArea!!.let {
NamedId(
id = it.id,
name = it.name,
)
},
bookedBy = userTransformer.transformJpaToApi(jpa.createdBy, ServiceName.approvedPremises),
expectedArrivalDate = jpa.expectedArrivalDate,
expectedDepartureDate = jpa.expectedDepartureDate,
createdAt = jpa.createdAt.toInstant(),
tier = application.riskRatings?.tier?.value?.level,
keyWorkerAllocation = jpa.extractKeyWorkerAllocation(),
actualArrivalDate = jpa.actualArrivalDateTime,
actualDepartureDate = jpa.actualDepartureDateTime,
canonicalArrivalDate = jpa.canonicalArrivalDate,
canonicalDepartureDate = jpa.canonicalDepartureDate,
otherBookingsInPremisesForCrn = otherBookingsAtPremiseForCrn.map { it.toSpaceBookingDate() },
)
}

private fun Cas1SpaceBookingAtPremises.toSpaceBookingDate() =
Cas1SpaceBookingDates(
id = this.id,
canonicalArrivalDate = this.canonicalArrivalDate,
canonicalDepartureDate = this.canonicalDepartureDate,
)

private fun Cas1SpaceBookingEntity.extractKeyWorkerAllocation(): Cas1KeyWorkerAllocation? {
val staffCode = keyWorkerStaffCode
val name = keyWorkerName
val assignedAt = keyWorkerAssignedAt
return if (staffCode != null && name != null && assignedAt != null) {
Cas1KeyWorkerAllocation(
keyWorker = StaffMember(
code = staffCode,
keyWorker = true,
name = name,
),
allocatedAt = assignedAt.toLocalDate(),
)
},
bookedBy = userTransformer.transformJpaToApi(jpa.createdBy, ServiceName.approvedPremises),
expectedArrivalDate = jpa.expectedArrivalDate,
expectedDepartureDate = jpa.expectedDepartureDate,
createdAt = jpa.createdAt.toInstant(),
tier = null,
keyWorker = null,
actualDepartureDate = null,
actualArrivalDate = null,
canonicalArrivalDate = jpa.canonicalArrivalDate,
canonicalDepartureDate = jpa.canonicalDepartureDate,
otherBookingsInPremises = listOf(),
)
} else {
null
}
}

fun transformSearchResultToSummary(
searchResult: Cas1SpaceBookingSearchResult,
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/static/_shared.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3135,6 +3135,7 @@ components:
- cas1_view_manage_tasks
- cas1_view_out_of_service_beds
- cas1_space_booking_list
- cas1_space_booking_view
- cas1_premises_view_summary
TemporaryAccommodationUserRole:
type: string
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/static/cas1-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,7 @@ paths:
get:
tags:
- space bookings
operationId: "getSpaceBookingById"
summary: Returns space booking information for a given id
parameters:
- name: premisesId
Expand Down
18 changes: 13 additions & 5 deletions src/main/resources/static/cas1-schemas.yml
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,12 @@ components:
id:
type: string
format: uuid
applicationId:
type: string
format: uuid
assessmentId:
type: string
format: uuid
person:
$ref: '_shared.yml#/components/schemas/Person'
tier:
Expand All @@ -261,10 +267,10 @@ components:
format: date
actualArrivalDate:
type: string
format: date
format: date-time
actualDepartureDate:
type: string
format: date
format: date-time
canonicalArrivalDate:
description: actual arrival date or, if not known, the expected arrival date
type: string
Expand All @@ -276,14 +282,16 @@ components:
createdAt:
type: string
format: date-time
keyWorker:
keyWorkerAllocation:
$ref: '#/components/schemas/Cas1KeyWorkerAllocation'
otherBookingsInPremises:
otherBookingsInPremisesForCrn:
type: array
items:
$ref: '#/components/schemas/Cas1SpaceBookingDates'
required:
- id
- applicationId
- assessmentId
- person
- requirements
- premises
Expand All @@ -294,7 +302,7 @@ components:
- canonicalArrivalDate
- canonicalDepartureDate
- createdAt
- otherBookingsInPremises
- otherBookingsInPremisesForCrn
Cas1SpaceBookingDates:
type: object
properties:
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/static/codegen/built-api-spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7601,6 +7601,7 @@ components:
- cas1_view_manage_tasks
- cas1_view_out_of_service_beds
- cas1_space_booking_list
- cas1_space_booking_view
- cas1_premises_view_summary
TemporaryAccommodationUserRole:
type: string
Expand Down
Loading

0 comments on commit 76a1aa4

Please sign in to comment.