From 12d3f8ad3e46f2dd546fd5b6cf5f6b1aa8a57748 Mon Sep 17 00:00:00 2001 From: Rob Booth Date: Tue, 31 Dec 2024 13:00:51 +0000 Subject: [PATCH] Add SpaceBooking information to Subject Access Request API Modified query for obtaining information for offlineApplications to include SpaceBookings --- .../CAS1SubjectAccessRequestRepository.kt | 36 +++-- .../SubjectAccessRequestRepositoryBase.kt | 83 ++++++++++ .../SubjectAccessRequestService.kt | 2 + .../factory/Cas1SpaceBookingEntityFactory.kt | 2 +- .../CAS1SubjectAccessRequestServiceTest.kt | 123 +++++++++++++++ .../SubjectAccessRequestServiceTestBase.kt | 144 +++++++++++++++--- 6 files changed, 363 insertions(+), 27 deletions(-) diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/jpa/entity/CAS1SubjectAccessRequestRepository.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/jpa/entity/CAS1SubjectAccessRequestRepository.kt index 833b4e72d8..60f49b6e77 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/jpa/entity/CAS1SubjectAccessRequestRepository.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/jpa/entity/CAS1SubjectAccessRequestRepository.kt @@ -441,21 +441,39 @@ from select json_agg(offline_applications) as json from ( select - B.CRN, - B.noms_number , + CASE + WHEN B.id is NOT NULL THEN B.CRN + ELSE sb.crn + END AS crn, + CASE + WHEN B.id is NOT NULL THEN B.noms_number + ELSE null + END AS noms_number, oa.id as offline_application_id, - b.id as booking_id, + CASE + WHEN B.id is NOT NULL THEN b.id + ELSE sb.id + END AS booking_id, oa.created_at from offline_applications oa - inner join bookings b on b.offline_application_id = oa.id + left join bookings b on b.offline_application_id = oa.id + left join cas1_space_bookings sb on sb.offline_application_id = oa.id where (b.crn = :crn or - b.noms_number = :noms_number) - and - (:start_date::date is null or b.created_at >= :start_date) - and - (:end_date::date is null or b.created_at <= :end_date) + b.noms_number = :noms_number) + OR + (sb.crn = :crn) + and ( + (:start_date::date is null or b.created_at >= :start_date) + and + (:end_date::date is null or b.created_at <= :end_date) + ) + OR ( + (:start_date::date is null or sb.created_at >= :start_date) + and + (:end_date::date is null or sb.created_at <= :end_date) + ) ) offline_applications """.trimIndent(), MapSqlParameterSource() diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/jpa/entity/SubjectAccessRequestRepositoryBase.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/jpa/entity/SubjectAccessRequestRepositoryBase.kt index 53166d6136..7366759898 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/jpa/entity/SubjectAccessRequestRepositoryBase.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/jpa/entity/SubjectAccessRequestRepositoryBase.kt @@ -60,6 +60,89 @@ open class SubjectAccessRequestRepositoryBase(val jdbcTemplate: NamedParameterJd return toJsonString(result) } + fun spaceBookings( + crn: String?, + nomsNumber: String?, + startDate: LocalDateTime?, + endDate: LocalDateTime?, + ): String { + val result = jdbcTemplate.queryForMap( + """ + select json_agg(spaceBooking) as json + from ( + select + b.crn, + a.noms_number, + b.canonical_arrival_date, + b.canonical_departure_date, + b.expected_arrival_date, + b.expected_departure_date, + b.actual_arrival_date, + b.actual_arrival_time, + b.actual_departure_date, + b.actual_departure_time, + b.non_arrival_confirmed_at, + b.non_arrival_notes, + b.non_arrival_reason_id, + apa.risk_ratings -> 'tier' -> 'value' ->> 'level' as tier, + b.created_at, + b.key_worker_staff_code, + b.key_worker_assigned_at, + b.key_worker_name, + b.approved_premises_application_id, + b.offline_application_id, + p."name" as premises_name, + b.delius_event_number, + b.placement_request_id, + b.created_by_user_id, + b.departure_reason_id, + b.departure_notes, + b.departure_move_on_category_id, + b.cancellation_reason_notes, + b.cancellation_reason_id, + b.cancellation_occurred_at, + b.cancellation_recorded_at, + b.migrated_management_info_from, + b.version, + ( + SELECT STRING_AGG (characteristics.property_name, ',') + FROM cas1_space_bookings_criteria sbc + LEFT OUTER JOIN characteristics ON characteristics.id = sbc.characteristic_id + WHERE sbc.space_booking_id = b.id + GROUP by sbc.space_booking_id + ) AS characteristics_property_names, + CASE + WHEN apa.id IS NOT NULL THEN apa.name + ELSE offline_app.name + END as person_name + FROM + cas1_space_bookings b + LEFT JOIN premises p ON + b.premises_id = p.id + LEFT OUTER JOIN approved_premises_applications apa ON + b.approved_premises_application_id = apa.id + LEFT OUTER JOIN offline_applications offline_app ON + b.offline_application_id = offline_app.id + LEFT OUTER JOIN + applications a on + a.id = apa.id + where + (b.crn = :crn + or a.noms_number = :noms_number ) + and (:start_date::date is null or b.created_at >= :start_date) + and (:end_date::date is null or b.created_at <= :end_date) + ) spaceBooking + """.trimIndent(), + MapSqlParameterSource().addSarParameters( + crn, + nomsNumber, + startDate, + endDate, + ), + ) + return toJsonString(result) + } + fun bookingExtensions( crn: String?, nomsNumber: String?, diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/service/subjectaccessrequests/SubjectAccessRequestService.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/service/subjectaccessrequests/SubjectAccessRequestService.kt index 18a912d30a..32908e35c9 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/service/subjectaccessrequests/SubjectAccessRequestService.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/service/subjectaccessrequests/SubjectAccessRequestService.kt @@ -32,6 +32,7 @@ class SubjectAccessRequestService( val apAssessmentClarificationNotesJson = cas1SubjectAccessRequestRepository.getApprovedPremisesAssessmentClarificationNotes(crn, nomsNumber, startDate, endDate) val apBookingsJson = cas1SubjectAccessRequestRepository.bookings(crn, nomsNumber, startDate, endDate) + val apSpaceBookingsJson = cas1SubjectAccessRequestRepository.spaceBookings(crn, nomsNumber, startDate, endDate) val apBookingExtensionsJson = cas1SubjectAccessRequestRepository.bookingExtensions(crn, nomsNumber, startDate, endDate) val apCancellationsJson = cas1SubjectAccessRequestRepository.cancellations(crn, nomsNumber, startDate, endDate) val apBedMovesJson = cas1SubjectAccessRequestRepository.bedMoves(crn, nomsNumber, startDate, endDate) @@ -53,6 +54,7 @@ class SubjectAccessRequestService( "Assessments": $apAssessmentsJson, "AssessmentClarificationNotes" : $apAssessmentClarificationNotesJson, "Bookings": $apBookingsJson, + "SpaceBookings": $apSpaceBookingsJson, "OfflineApplications": $offlineApplicationsJson, "BookingExtensions": $apBookingExtensionsJson, "Cancellations": $apCancellationsJson, diff --git a/src/test/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/factory/Cas1SpaceBookingEntityFactory.kt b/src/test/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/factory/Cas1SpaceBookingEntityFactory.kt index 0111aa48d5..2b99930a1e 100644 --- a/src/test/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/factory/Cas1SpaceBookingEntityFactory.kt +++ b/src/test/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/factory/Cas1SpaceBookingEntityFactory.kt @@ -167,7 +167,7 @@ class Cas1SpaceBookingEntityFactory : Factory { this.departureReason = { departureReason } } - fun withMoveOnCategory(moveOnCategory: MoveOnCategoryEntity) = apply { + fun withMoveOnCategory(moveOnCategory: MoveOnCategoryEntity?) = apply { this.departureMoveOnCategory = { moveOnCategory } } diff --git a/src/test/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/integration/CAS1SubjectAccessRequestServiceTest.kt b/src/test/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/integration/CAS1SubjectAccessRequestServiceTest.kt index 874dc0d7ca..2717d49a75 100644 --- a/src/test/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/integration/CAS1SubjectAccessRequestServiceTest.kt +++ b/src/test/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/integration/CAS1SubjectAccessRequestServiceTest.kt @@ -20,6 +20,7 @@ import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.BedMoveEntity import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.BookingEntity import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.BookingNotMadeEntity import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.Cas1ApplicationUserDetailsEntity +import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.Cas1SpaceBookingEntity import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.PlacementApplicationDecision import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.PlacementApplicationEntity import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.PlacementApplicationWithdrawalReason @@ -36,6 +37,7 @@ import uk.gov.justice.digital.hmpps.approvedpremisesapi.util.randomStringMultiCa import java.time.LocalDate import java.time.OffsetDateTime +@SuppressWarnings("LargeClass") class CAS1SubjectAccessRequestServiceTest : SubjectAccessRequestServiceTestBase() { @Test @@ -51,6 +53,7 @@ class CAS1SubjectAccessRequestServiceTest : SubjectAccessRequestServiceTestBase( "Assessments": [ ], "AssessmentClarificationNotes": [ ], "Bookings": [ ], + "SpaceBookings": [ ], "OfflineApplications": [ ], "BookingExtensions": [ ], "Cancellations": [ ], @@ -82,6 +85,7 @@ class CAS1SubjectAccessRequestServiceTest : SubjectAccessRequestServiceTestBase( "Assessments": [ ], "AssessmentClarificationNotes": [ ], "Bookings": [ ], + "SpaceBookings": [ ], "OfflineApplications": [ ], "BookingExtensions": [ ], "Cancellations": [ ], @@ -116,6 +120,7 @@ class CAS1SubjectAccessRequestServiceTest : SubjectAccessRequestServiceTestBase( "Assessments": [ ], "AssessmentClarificationNotes": [ ], "Bookings": [ ], + "SpaceBookings": [ ], "OfflineApplications": [ ], "BookingExtensions": [ ], "Cancellations": [ ], @@ -153,6 +158,7 @@ class CAS1SubjectAccessRequestServiceTest : SubjectAccessRequestServiceTestBase( "Assessments": [ ], "AssessmentClarificationNotes": [ ], "Bookings": [ ], + "SpaceBookings": [ ], "OfflineApplications": [ ], "BookingExtensions": [ ], "Cancellations": [ ], @@ -191,6 +197,7 @@ class CAS1SubjectAccessRequestServiceTest : SubjectAccessRequestServiceTestBase( "Assessments": [${approvedPremisesAssessmentJson(application, offenderDetails, assessment)}], "AssessmentClarificationNotes": [ ], "Bookings": [ ], + "SpaceBookings": [ ], "OfflineApplications": [ ], "BookingExtensions": [ ], "Cancellations": [ ], @@ -229,6 +236,7 @@ class CAS1SubjectAccessRequestServiceTest : SubjectAccessRequestServiceTestBase( clarificationNote, )}], "Bookings": [ ], + "SpaceBookings": [ ], "OfflineApplications": [ ], "BookingExtensions": [ ], "Cancellations": [ ], @@ -247,6 +255,100 @@ class CAS1SubjectAccessRequestServiceTest : SubjectAccessRequestServiceTestBase( assertJsonEquals(expectedJson, result) } + @Test + fun `Get CAS1 information - have a space booking`() { + val (offenderDetails, _) = givenAnOffender() + val application = approvedPremisesApplicationEntity(offenderDetails) + val nonArrivalReason = nonArrivalReasonEntityFactory.produceAndPersist() + val departureReason = departureReasonEntityFactory.produceAndPersist() + val moveOnCategory = moveOnCategoryEntityFactory.produceAndPersist() + val cancellationReason = cancellationReasonEntityFactory.produceAndPersist() + val booking = spaceBookingEntity( + offenderDetails = offenderDetails, + application = application, + nonArrivalReason = nonArrivalReason, + departureReason = departureReason, + moveOnCategory = moveOnCategory, + cancellationReason = cancellationReason, + ) + + val result = + sarService.getCAS1Result( + offenderDetails.otherIds.crn, + offenderDetails.otherIds.nomsNumber, + START_DATE, + END_DATE, + ) + + val expectedJson = """ + { + "Applications":[${approvedPremisesApplicationsJson(application, offenderDetails)}], + "ApplicationTimeline" :[ ], + "Assessments": [ ], + "AssessmentClarificationNotes": [ ], + "Bookings": [], + "SpaceBookings": [ ${spaceBookingsJson(booking)} ], + "OfflineApplications": [ ], + "BookingExtensions": [ ], + "Cancellations": [ ], + "BedMoves": [ ], + "Appeals": [ ], + "PlacementApplications": [ ], + "PlacementRequests": [ ], + "PlacementRequirements": [ ], + "PlacementRequirementCriteria" : [ ], + "BookingNotMades" : [ ], + "DomainEvents": [ ], + "DomainEventsMetadata": [ ] + } + """.trimIndent() + + assertJsonEquals(expectedJson, result) + } + + @Test + fun `Get CAS1 information - have a space booking with offline application`() { + val (offenderDetails, _) = givenAnOffender() + val offlineApplication = offlineApplicationEntity(offenderDetails) + val booking = spaceBookingEntity( + offenderDetails = offenderDetails, + offlineApplication = offlineApplication, + ) + + val result = + sarService.getCAS1Result( + offenderDetails.otherIds.crn, + offenderDetails.otherIds.nomsNumber, + START_DATE, + END_DATE, + ) + + val expectedJson = """ + { + "Applications":[ ], + "ApplicationTimeline" :[ ], + "Assessments": [ ], + "AssessmentClarificationNotes": [ ], + "Bookings": [], + "SpaceBookings": [ ${spaceBookingsJson(booking)} ], + "OfflineApplications": [${offlineApplicationForSpaceBookingJson(booking)}], + "BookingExtensions": [ ], + "Cancellations": [ ], + "BedMoves": [ ], + "Appeals": [ ], + "PlacementApplications": [ ], + "PlacementRequests": [ ], + "PlacementRequirements": [ ], + "PlacementRequirementCriteria" : [ ], + "BookingNotMades" : [ ], + "DomainEvents": [ ], + "DomainEventsMetadata": [ ] + } + """.trimIndent() + + assertJsonEquals(expectedJson, result) + } + @Test fun `Get CAS1 information - have a booking`() { val (offenderDetails, _) = givenAnOffender() @@ -264,6 +366,7 @@ class CAS1SubjectAccessRequestServiceTest : SubjectAccessRequestServiceTestBase( "Assessments": [ ], "AssessmentClarificationNotes": [ ], "Bookings": [${bookingsJson(booking)}], + "SpaceBookings": [ ], "OfflineApplications": [ ], "BookingExtensions": [ ], "Cancellations": [ ], @@ -299,6 +402,7 @@ class CAS1SubjectAccessRequestServiceTest : SubjectAccessRequestServiceTestBase( "Assessments": [ ], "AssessmentClarificationNotes": [ ], "Bookings":[${bookingsJson(booking)}], + "SpaceBookings": [ ], "OfflineApplications": [${offlineApplicationJson(booking)}], "BookingExtensions": [ ], "Cancellations": [ ], @@ -335,6 +439,7 @@ class CAS1SubjectAccessRequestServiceTest : SubjectAccessRequestServiceTestBase( "Assessments": [ ], "AssessmentClarificationNotes": [ ], "Bookings": [${bookingsJson(booking)}], + "SpaceBookings": [ ], "OfflineApplications": [ ], "BookingExtensions":[${bookingExtensionJson(bookingExtension)}], "Cancellations": [ ], @@ -371,6 +476,7 @@ class CAS1SubjectAccessRequestServiceTest : SubjectAccessRequestServiceTestBase( "Assessments": [ ], "AssessmentClarificationNotes": [ ], "Bookings": [${bookingsJson(booking)}], + "SpaceBookings": [ ], "OfflineApplications": [ ], "BookingExtensions": [ ], "Cancellations": [${cancellationJson(cancellation)}], @@ -407,6 +513,7 @@ class CAS1SubjectAccessRequestServiceTest : SubjectAccessRequestServiceTestBase( "Assessments": [ ], "AssessmentClarificationNotes": [ ], "Bookings": [${bookingsJson(booking)}], + "SpaceBookings": [ ], "OfflineApplications": [ ], "BookingExtensions": [ ], "Cancellations": [ ], @@ -441,6 +548,7 @@ class CAS1SubjectAccessRequestServiceTest : SubjectAccessRequestServiceTestBase( "Assessments": [${approvedPremisesAssessmentJson(application, offender, assessment)}], "AssessmentClarificationNotes": [ ], "Bookings": [ ], + "SpaceBookings": [ ], "OfflineApplications": [ ], "BookingExtensions": [ ], "Cancellations": [ ], @@ -474,6 +582,7 @@ class CAS1SubjectAccessRequestServiceTest : SubjectAccessRequestServiceTestBase( "Assessments": [ ], "AssessmentClarificationNotes": [ ], "Bookings": [ ], + "SpaceBookings": [ ], "OfflineApplications": [ ], "BookingExtensions": [ ], "Cancellations": [ ], @@ -512,6 +621,7 @@ class CAS1SubjectAccessRequestServiceTest : SubjectAccessRequestServiceTestBase( "AssessmentClarificationNotes": [ ], "Bookings": [${bookingsJson(booking)}], "OfflineApplications": [ ], + "SpaceBookings": [ ], "BookingExtensions": [ ], "Cancellations": [ ], "BedMoves": [ ], @@ -549,6 +659,7 @@ class CAS1SubjectAccessRequestServiceTest : SubjectAccessRequestServiceTestBase( "Assessments": [${approvedPremisesAssessmentJson(application, offender, assessment)}], "AssessmentClarificationNotes": [ ], "Bookings":[${bookingsJson(booking)}], + "SpaceBookings": [ ], "OfflineApplications": [ ], "BookingExtensions": [ ], "Cancellations": [ ], @@ -584,6 +695,7 @@ class CAS1SubjectAccessRequestServiceTest : SubjectAccessRequestServiceTestBase( "Assessments": [${approvedPremisesAssessmentJson(application, offender, assessment)}], "AssessmentClarificationNotes": [ ], "Bookings": [ ], + "SpaceBookings": [ ], "OfflineApplications": [ ], "BookingExtensions": [ ], "Cancellations": [ ], @@ -816,6 +928,17 @@ class CAS1SubjectAccessRequestServiceTest : SubjectAccessRequestServiceTestBase( } """.trimIndent() + private fun offlineApplicationForSpaceBookingJson(booking: Cas1SpaceBookingEntity) = + """ + { + "crn": "${booking.crn}", + "noms_number": ${if (booking.application?.nomsNumber != null) "\"${booking.application?.nomsNumber}\"" else null}, + "offline_application_id":"${booking.offlineApplication!!.id}", + "booking_id":"${booking.id}", + "created_at":"$CREATED_AT" + } + """.trimIndent() + private fun offlineApplicationJson(booking: BookingEntity) = """ { diff --git a/src/test/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/integration/SubjectAccessRequestServiceTestBase.kt b/src/test/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/integration/SubjectAccessRequestServiceTestBase.kt index e4670b1791..7f7332dbf1 100644 --- a/src/test/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/integration/SubjectAccessRequestServiceTestBase.kt +++ b/src/test/kotlin/uk/gov/justice/digital/hmpps/approvedpremisesapi/integration/SubjectAccessRequestServiceTestBase.kt @@ -4,17 +4,26 @@ import org.springframework.beans.factory.annotation.Autowired import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.BookingStatus import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.ServiceName import uk.gov.justice.digital.hmpps.approvedpremisesapi.factory.PersonRisksFactory +import uk.gov.justice.digital.hmpps.approvedpremisesapi.integration.givens.givenAPlacementRequest import uk.gov.justice.digital.hmpps.approvedpremisesapi.integration.givens.givenAProbationRegion +import uk.gov.justice.digital.hmpps.approvedpremisesapi.integration.givens.givenAUser import uk.gov.justice.digital.hmpps.approvedpremisesapi.integration.givens.givenAnApArea import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ApplicationEntity +import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ApprovedPremisesApplicationEntity import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ApprovedPremisesAssessmentEntity +import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ApprovedPremisesEntity import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.AssessmentClarificationNoteEntity import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.BookingEntity import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.CancellationEntity +import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.CancellationReasonEntity +import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.Cas1SpaceBookingEntity +import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.DepartureReasonEntity import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.DomainEventEntity import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.DomainEventType import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ExtensionEntity import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.MetaDataName +import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.MoveOnCategoryEntity +import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.NonArrivalReasonEntity import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.OfflineApplicationEntity import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.UserEntity import uk.gov.justice.digital.hmpps.approvedpremisesapi.model.ApprovedPremisesType @@ -26,6 +35,7 @@ import uk.gov.justice.digital.hmpps.approvedpremisesapi.service.subjectaccessreq import uk.gov.justice.digital.hmpps.approvedpremisesapi.util.randomStringMultiCaseWithNumbers import java.time.LocalDate import java.time.LocalDateTime +import java.time.LocalTime import java.time.OffsetDateTime import java.time.format.DateTimeFormatter import java.util.UUID @@ -33,6 +43,7 @@ import java.util.UUID open class SubjectAccessRequestServiceTestBase : IntegrationTestBase() { @Autowired lateinit var sarService: SubjectAccessRequestService + lateinit var premises: ApprovedPremisesEntity companion object { const val CREATED_AT = "2021-09-18T16:00:00+00:00" @@ -68,6 +79,8 @@ open class SubjectAccessRequestServiceTestBase : IntegrationTestBase() { var previousDepartureDateOnly = DEPARTED_AT.substring(0..9) var newDepartureDateOnly = NEW_DEPARTED_AT.substring(0..9) var cancellationDateOnly = CANCELLATION_DATE.substring(0..9) + var arrivedAtTime = ARRIVED_AT.substring(11..18) + var departedAtTime = DEPARTED_AT.substring(11..18) } protected fun OffsetDateTime.toStandardisedFormat(): String = this.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) @@ -99,6 +112,48 @@ open class SubjectAccessRequestServiceTestBase : IntegrationTestBase() { } """.trimIndent() + @SuppressWarnings("CyclomaticComplexMethod") + protected fun spaceBookingsJson(booking: Cas1SpaceBookingEntity): String = + """ + { + "crn": "${booking.crn}", + "noms_number": ${if (booking.application != null) "\"${booking.application!!.nomsNumber}\"" else "null"}, + "canonical_arrival_date": ${if (booking.canonicalArrivalDate != null) "\"${booking.canonicalArrivalDate}\"" else null}, + "canonical_departure_date": ${if (booking.canonicalDepartureDate != null) "\"${booking.canonicalDepartureDate}\"" else null}, + "expected_arrival_date": ${if (booking.expectedArrivalDate != null) "\"${booking.expectedArrivalDate}\"" else null}, + "expected_departure_date": ${if (booking.expectedDepartureDate != null) "\"${booking.expectedDepartureDate}\"" else null}, + "actual_arrival_date": ${if (booking.actualArrivalDate != null) "\"${booking.actualArrivalDate}\"" else null}, + "actual_arrival_time": ${if (booking.actualArrivalTime != null) "\"${booking.actualArrivalTime}:00\"" else null}, + "actual_departure_date": ${if (booking.actualDepartureDate != null) "\"${booking.actualDepartureDate}\"" else null}, + "actual_departure_time": ${if (booking.actualDepartureTime != null) "\"${booking.actualDepartureTime}:00\"" else null}, + "non_arrival_confirmed_at": ${if (booking.nonArrivalConfirmedAt != null) "\"$CREATED_AT\"" else null}, + "non_arrival_notes": ${if (booking.nonArrivalNotes != null) "\"${booking.nonArrivalNotes}\"" else null}, + "non_arrival_reason_id": ${if (booking.nonArrivalReason != null) "\"${booking.nonArrivalReason!!.id}\"" else null}, + "tier": ${if (booking.application?.riskRatings?.tier?.value?.level != null) "\"${booking.application?.riskRatings?.tier?.value?.level}\"" else null}, + "created_at": "$CREATED_AT", + "key_worker_staff_code": "${booking.keyWorkerStaffCode}", + "key_worker_assigned_at": "$CREATED_AT", + "key_worker_name": "${booking.keyWorkerName}", + "approved_premises_application_id": ${if (booking.application != null) "\"${booking.application!!.id}\"" else null}, + "offline_application_id": ${if (booking.offlineApplication != null) "\"${booking.offlineApplication!!.id}\"" else null}, + "premises_name": "${booking.premises.name}", + "person_name": ${if (booking.application != null) "\"${booking.application!!.name}\"" else "\"${booking.offlineApplication!!.name}\""}, + "delius_event_number": "${booking.deliusEventNumber}", + "placement_request_id": "${booking.placementRequest!!.id}", + "created_by_user_id": "${booking.createdBy!!.id}", + "departure_reason_id": ${if (booking.departureReason != null) "\"${booking.departureReason!!.id}\"" else null}, + "departure_notes": ${if (booking.departureNotes != null) "\"${booking.departureNotes}\"" else null}, + "departure_move_on_category_id": ${if (booking.departureMoveOnCategory != null) "\"${booking.departureMoveOnCategory!!.id}\"" else null}, + "cancellation_reason_notes": ${if (booking.cancellationReasonNotes != null) "\"${booking.cancellationReasonNotes}\"" else null}, + "cancellation_reason_id": ${if (booking.cancellationReason != null) "\"${booking.cancellationReason!!.id}\"" else null}, + "cancellation_occurred_at": ${if (booking.cancellationOccurredAt != null) "\"${booking.cancellationOccurredAt}\"" else null}, + "cancellation_recorded_at": "$CANCELLATION_DATE", + "characteristics_property_names": "${booking.criteria?.let{ it.map { criteria -> criteria.propertyName}.sortedBy{ propertyName -> propertyName }.joinToString(",")}}", + "migrated_management_info_from": ${if (booking.migratedManagementInfoFrom != null) "\"${booking.migratedManagementInfoFrom}\"" else null}, + "version": ${booking.version} + } + """.trimIndent() + protected fun bookingsJson(booking: BookingEntity): String = """ { @@ -202,6 +257,63 @@ open class SubjectAccessRequestServiceTestBase : IntegrationTestBase() { } } + @SuppressWarnings("LongParameterList") + protected fun spaceBookingEntity( + offenderDetails: OffenderDetailSummary, + application: ApprovedPremisesApplicationEntity? = null, + nonArrivalReason: NonArrivalReasonEntity? = null, + departureReason: DepartureReasonEntity? = null, + moveOnCategory: MoveOnCategoryEntity? = null, + cancellationReason: CancellationReasonEntity? = null, + offlineApplication: OfflineApplicationEntity? = null, + ): Cas1SpaceBookingEntity { + val (user, _) = givenAUser() + val (placementRequest) = givenAPlacementRequest( + placementRequestAllocatedTo = user, + assessmentAllocatedTo = user, + createdByUser = user, + ) + val bed = bedEntity() + val spaceBooking = + cas1SpaceBookingEntityFactory.produceAndPersist { + withPlacementRequest(placementRequest) + withCrn(offenderDetails.otherIds.crn) + withPremises(premises) + withApplication(application) + withCanonicalArrivalDate(LocalDate.parse(arrivedAtDateOnly)) + withCanonicalDepartureDate(LocalDate.parse(departedAtDateOnly)) + withExpectedArrivalDate(LocalDate.parse(arrivedAtDateOnly)) + withExpectedDepartureDate(LocalDate.parse(departedAtDateOnly)) + withActualArrivalDate(LocalDate.parse(arrivedAtDateOnly)) + withActualArrivalTime(LocalTime.parse(arrivedAtTime)) + withActualDepartureDate(LocalDate.parse(departedAtDateOnly)) + withActualDepartureTime(LocalTime.parse(departedAtTime)) + withCreatedAt(OffsetDateTime.parse(CREATED_AT)) + withKeyworkerStaffCode("KEYWORKERSTAFFCODE") + withKeyworkerName("KEYWORKERNAME") + withKeyworkerAssignedAt(OffsetDateTime.parse(CREATED_AT).toInstant()) + withCreatedBy(user) + withDeliusEventNumber("DELIUSEVENTNUMBER") + withNonArrivalConfirmedAt(OffsetDateTime.parse(CREATED_AT).toInstant()) + withNonArrivalNotes("NONARRIVALNOTES") + withNonArrivalReason(nonArrivalReason) + withDepartureNotes("DEPARTURENOTES") + withDepartureReason(departureReason) + withMoveOnCategory(moveOnCategory) + withCancellationOccurredAt(LocalDate.parse(cancellationDateOnly)) + withCancellationRecordedAt(OffsetDateTime.parse(CANCELLATION_DATE).toInstant()) + withCancellationReason(cancellationReason) + withCancellationReasonNotes("CANCELLATIONREASONNOTES") + withCriteria( + mutableListOf( + characteristicEntityFactory.produceAndPersist(), + ), + ) + withOfflineApplication(offlineApplication) + } + return spaceBooking + } + protected fun bookingEntity( offenderDetails: OffenderDetailSummary, application: ApplicationEntity, @@ -230,29 +342,27 @@ open class SubjectAccessRequestServiceTestBase : IntegrationTestBase() { return booking } - protected fun bedEntity() = bedEntityFactory.produceAndPersist { + protected fun bedEntity(premisesEntity: ApprovedPremisesEntity? = null) = bedEntityFactory.produceAndPersist { withName("a bed ${randomStringMultiCaseWithNumbers(5)}") withCode("a code ${randomStringMultiCaseWithNumbers(5)}") + premises = approvedPremisesEntityFactory.produceAndPersist { + withName("a premises ${randomStringMultiCaseWithNumbers(5)}") + withApCode("AP Code ${randomStringMultiCaseWithNumbers(5)}") + withLocalAuthorityArea( + localAuthorityEntityFactory.produceAndPersist { + withName("An LAA ${randomStringMultiCaseWithNumbers(5)}") + withIdentifier("LAA ID ${randomStringMultiCaseWithNumbers(5)}") + }, + ) + withProbationRegion( + probationRegionEntity(), + ) + } withRoom( roomEntityFactory.produceAndPersist { withCode("room code ${randomStringMultiCaseWithNumbers(5)}") withName("room name ${randomStringMultiCaseWithNumbers(5)}") - - withPremises( - approvedPremisesEntityFactory.produceAndPersist { - withName("a premises ${randomStringMultiCaseWithNumbers(5)}") - withApCode("AP Code ${randomStringMultiCaseWithNumbers(5)}") - withLocalAuthorityArea( - localAuthorityEntityFactory.produceAndPersist { - withName("An LAA ${randomStringMultiCaseWithNumbers(5)}") - withIdentifier("LAA ID ${randomStringMultiCaseWithNumbers(5)}") - }, - ) - withProbationRegion( - probationRegionEntity(), - ) - }, - ) + withPremises(premises) }, ) }