diff --git a/projects/approved-premises-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt b/projects/approved-premises-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt index 64eba840df..d254ca2d3d 100644 --- a/projects/approved-premises-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt +++ b/projects/approved-premises-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt @@ -156,6 +156,8 @@ class DataLoader( staffRepository.save(StaffGenerator.CRU_WOMENS_ESTATE) staffUserRepository.save(StaffGenerator.CRU_WOMENS_ESTATE_USER) + staffRepository.save(StaffGenerator.STAFF_WITHOUT_USERNAME) + val personManagerStaff = StaffGenerator.generate(code = "N54A001") staffRepository.save(personManagerStaff) val person = PersonGenerator.DEFAULT diff --git a/projects/approved-premises-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt b/projects/approved-premises-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt index ac3223b28e..9d5c3879d8 100644 --- a/projects/approved-premises-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt +++ b/projects/approved-premises-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt @@ -25,6 +25,7 @@ object StaffGenerator { val CRU_WOMENS_ESTATE = generate( name = "CRU Womens Estate" ) + val STAFF_WITHOUT_USERNAME = generate() val DEFAULT_STAFF_USER = generateStaffUser("john-smith", DEFAULT_STAFF) val JIM_SNOW_USER = generateStaffUser("JIMSNOWLDAP", JIM_SNOW) diff --git a/projects/approved-premises-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/StaffControllerIntegrationTest.kt b/projects/approved-premises-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/StaffControllerIntegrationTest.kt index cadee9ab1a..8c83819a54 100644 --- a/projects/approved-premises-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/StaffControllerIntegrationTest.kt +++ b/projects/approved-premises-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/StaffControllerIntegrationTest.kt @@ -92,4 +92,26 @@ class StaffControllerIntegrationTest { assertThat(res.probationArea.description, equalTo(StaffGenerator.DEFAULT_STAFF.probationArea.description)) assertThat(res.active, equalTo(true)) } + + @Test + fun `get staff by code`() { + val staffCode = StaffGenerator.DEFAULT_STAFF.code + val res = mockMvc.perform(get("/staff?code=${staffCode}").withToken()) + .andExpect(status().isOk).andReturn().response.contentAsJson() + assertThat(res.username, equalTo(StaffGenerator.DEFAULT_STAFF.user!!.username)) + assertThat(res.name.surname, equalTo(StaffGenerator.DEFAULT_STAFF.surname)) + assertThat(res.name.forename, equalTo(StaffGenerator.DEFAULT_STAFF.forename)) + assertThat(res.code, equalTo(StaffGenerator.DEFAULT_STAFF.code)) + } + + @Test + fun `get staff without username by code`() { + val staffCode = StaffGenerator.STAFF_WITHOUT_USERNAME.code + val res = mockMvc.perform(get("/staff?code=${staffCode}").withToken()) + .andExpect(status().isOk).andReturn().response.contentAsJson() + assertThat(res.username, equalTo(null)) + assertThat(res.name.surname, equalTo(StaffGenerator.STAFF_WITHOUT_USERNAME.surname)) + assertThat(res.name.forename, equalTo(StaffGenerator.STAFF_WITHOUT_USERNAME.forename)) + assertThat(res.code, equalTo(StaffGenerator.STAFF_WITHOUT_USERNAME.code)) + } } diff --git a/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/StaffController.kt b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/StaffController.kt index ee9dfcf3bf..cfc151476c 100644 --- a/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/StaffController.kt +++ b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/controller/StaffController.kt @@ -11,10 +11,10 @@ import org.springframework.web.bind.annotation.RestController import uk.gov.justice.digital.hmpps.service.StaffService @RestController +@PreAuthorize("hasRole('PROBATION_API__APPROVED_PREMISES__CASE_DETAIL')") class StaffController( private val staffService: StaffService ) { - @PreAuthorize("hasRole('PROBATION_API__APPROVED_PREMISES__CASE_DETAIL')") @Operation( summary = "List all members of staff that are keyworkers in the Approved Premises", description = """An Approved Premises is defined in Delius as part of reference data. @@ -32,14 +32,15 @@ class StaffController( @PageableDefault(value = 100) pageable: Pageable = Pageable.ofSize(100) ) = staffService.getStaffInApprovedPremises(code, keyWorker, pageable) - @PreAuthorize("hasRole('PROBATION_API__APPROVED_PREMISES__CASE_DETAIL')") - @Operation( - summary = "Get the staff name by username", - description = """Returns the Staff name associated with the given username. - """ - ) + @Operation(summary = "Get the staff details by username") @GetMapping(value = ["/staff/{userName}"]) fun getStaffByUsername( @PathVariable userName: String ) = staffService.getStaffByUsername(userName) + + @Operation(summary = "Get the staff details by code") + @GetMapping(value = ["/staff"], params = ["code"]) + fun getStaffByCode( + @RequestParam code: String + ) = staffService.getStaffByCode(code) } diff --git a/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/person/offence/entity/Offence.kt b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/person/offence/entity/Offence.kt index cbd7f00c44..951cdae8f7 100644 --- a/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/person/offence/entity/Offence.kt +++ b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/person/offence/entity/Offence.kt @@ -1,12 +1,6 @@ package uk.gov.justice.digital.hmpps.integrations.delius.person.offence.entity -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.Id -import jakarta.persistence.JoinColumn -import jakarta.persistence.ManyToOne -import jakarta.persistence.OneToOne -import jakarta.persistence.Table +import jakarta.persistence.* import org.hibernate.annotations.Immutable import org.hibernate.annotations.SQLRestriction import org.springframework.data.jpa.repository.JpaRepository @@ -15,11 +9,13 @@ import uk.gov.justice.digital.hmpps.integrations.delius.approvedpremises.referra import java.time.LocalDate interface CaseOffence { + val id: Long val code: String val description: String val date: LocalDate? val main: Boolean val eventNumber: String + val eventId: Long } @Immutable @@ -91,11 +87,25 @@ class Offence( interface MainOffenceRepository : JpaRepository { @Query( """ - select mo.offence.code as code, mo.offence.description as description, mo.date as date, true as main, mo.event.number as eventNumber + select + mo.offence.id as id, + mo.offence.code as code, + mo.offence.description as description, + mo.date as date, + true as main, + mo.event.number as eventNumber, + mo.event.id as eventId from MainOffence mo where mo.event.personId = :personId and mo.event.active = true union all - select ao.offence.code, ao.offence.description, ao.date, false, ao.event.number + select + ao.offence.id, + ao.offence.code, + ao.offence.description, + ao.date, + false, + ao.event.number, + ao.event.id from AdditionalOffence ao where ao.event.personId = :personId and ao.event.active = true """ diff --git a/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/StaffResponse.kt b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/StaffResponse.kt index 234bf2a07b..279c09cbc7 100644 --- a/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/StaffResponse.kt +++ b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/StaffResponse.kt @@ -29,7 +29,7 @@ data class StaffDetail( val staffIdentifier: Long, val teams: List = emptyList(), val probationArea: ProbationArea, - val username: String, + val username: String?, val name: PersonName, val code: String, val active: Boolean diff --git a/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/StaffService.kt b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/StaffService.kt index 57570a8f8d..daae5fa43b 100644 --- a/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/StaffService.kt +++ b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/StaffService.kt @@ -10,6 +10,7 @@ import uk.gov.justice.digital.hmpps.integrations.delius.approvedpremises.Approve import uk.gov.justice.digital.hmpps.integrations.delius.staff.LdapUser import uk.gov.justice.digital.hmpps.integrations.delius.staff.Staff import uk.gov.justice.digital.hmpps.integrations.delius.staff.StaffRepository +import uk.gov.justice.digital.hmpps.integrations.delius.staff.getByCode import uk.gov.justice.digital.hmpps.ldap.findByUsername import uk.gov.justice.digital.hmpps.model.* @@ -42,11 +43,12 @@ class StaffService( fun getStaffByUsername(username: String) = staffRepository.findByUsername(username)?.toStaffDetail(ldapTemplate.findByUsername(username)) - ?: throw NotFoundException( - "Staff", - "username", - username - ) + ?: throw NotFoundException("Staff", "username", username) + + fun getStaffByCode(code: String) = + staffRepository.getByCode(code).let { + it.toStaffDetail(it.user?.username?.let { username -> ldapTemplate.findByUsername(username) }) + } fun Staff.toResponse(approvedPremisesCode: String) = StaffResponse( code = code, @@ -68,7 +70,7 @@ class StaffService( endDate = it.endDate ) }, - username = user!!.username, + username = user?.username, name = PersonName(forename, surname, middleName), code = code, probationArea = ProbationArea( diff --git a/projects/feature-flags/container/Dockerfile b/projects/feature-flags/container/Dockerfile index 3577984966..ef53d844e1 100644 --- a/projects/feature-flags/container/Dockerfile +++ b/projects/feature-flags/container/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/flipt-io/flipt:v1.51.0 +FROM ghcr.io/flipt-io/flipt:v1.51.1 # Run any pending migrations on startup CMD ["sh", "-c", "./flipt migrate && ./flipt"] \ No newline at end of file diff --git a/projects/manage-supervision-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ProbationHistoryIntegrationTest.kt b/projects/manage-supervision-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ProbationHistoryIntegrationTest.kt new file mode 100644 index 0000000000..b95d944e32 --- /dev/null +++ b/projects/manage-supervision-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ProbationHistoryIntegrationTest.kt @@ -0,0 +1,66 @@ +package uk.gov.justice.digital.hmpps + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders +import org.springframework.test.web.servlet.result.MockMvcResultMatchers +import uk.gov.justice.digital.hmpps.api.model.sentence.History +import uk.gov.justice.digital.hmpps.api.model.sentence.ProbationHistory +import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator +import uk.gov.justice.digital.hmpps.data.generator.personalDetails.PersonDetailsGenerator +import uk.gov.justice.digital.hmpps.service.toSummary +import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.contentAsJson +import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.withToken +import java.time.LocalDate +import uk.gov.justice.digital.hmpps.api.model.sentence.SentenceSummary + +@AutoConfigureMockMvc +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class ProbationHistoryIntegrationTest { + @Autowired + lateinit var mockMvc: MockMvc + + @Test + fun `no probation history`() { + val response = mockMvc + .perform( + MockMvcRequestBuilders.get("/sentence/${PersonDetailsGenerator.PERSONAL_DETAILS.crn}/probation-history") + .withToken() + ) + .andExpect(MockMvcResultMatchers.status().isOk) + .andReturn().response.contentAsJson() + + val expected = History( + PersonDetailsGenerator.PERSONAL_DETAILS.toSummary(), + listOf(), + ProbationHistory(0, null, 0, 0) + ) + + assertEquals(expected, response) + } + + @Test + fun `get probation history`() { + val response = mockMvc + .perform( + MockMvcRequestBuilders.get("/sentence/${PersonGenerator.OVERVIEW.crn}/probation-history").withToken() + ) + .andExpect(MockMvcResultMatchers.status().isOk) + .andReturn().response.contentAsJson() + + val expected = History( + PersonGenerator.OVERVIEW.toSummary(), + listOf( + SentenceSummary(1234567, "Pre-Sentence"), + SentenceSummary(7654321, "Default Sentence Type") + ), + ProbationHistory(2, LocalDate.now().minusDays(7), 2, 2) + ) + + assertEquals(expected, response) + } +} \ No newline at end of file diff --git a/projects/manage-supervision-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/SentenceIntegrationTest.kt b/projects/manage-supervision-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/SentenceIntegrationTest.kt index 2ffb3b5372..385b140f4e 100644 --- a/projects/manage-supervision-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/SentenceIntegrationTest.kt +++ b/projects/manage-supervision-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/SentenceIntegrationTest.kt @@ -41,14 +41,14 @@ class SentenceIntegrationTest { .andReturn().response.contentAsJson() val expected = SentenceOverview( - PersonDetailsGenerator.PERSONAL_DETAILS.toSummary(), listOf(), ProbationHistory(0, null, 0, 0) + PersonDetailsGenerator.PERSONAL_DETAILS.toSummary() ) assertEquals(expected, response) } @Test - fun `get active sentences`() { + fun `get latest active sentence`() { val response = mockMvc .perform(MockMvcRequestBuilders.get("/sentence/${PersonGenerator.OVERVIEW.crn}").withToken()) .andExpect(MockMvcResultMatchers.status().isOk) @@ -57,153 +57,172 @@ class SentenceIntegrationTest { val expected = SentenceOverview( PersonGenerator.OVERVIEW.toSummary(), listOf( - Sentence( - OffenceDetails( - "1234567", - Offence("Another Murder", 1), + SentenceSummary(1234567, "Pre-Sentence"), + SentenceSummary(7654321, "Default Sentence Type") + ), + Sentence( + OffenceDetails( + "1234567", + Offence("Another Murder", 1), + LocalDate.now(), + "overview", + emptyList() + ), + Conviction(null, null, null, listOf()), + null, + listOf(), + listOf(), + null, + listOf() + ) + ) + + assertEquals(expected, response) + } + + @Test + fun `get active sentence by event number`() { + val response = mockMvc + .perform(MockMvcRequestBuilders.get("/sentence/${PersonGenerator.OVERVIEW.crn}?number=7654321").withToken()) + .andExpect(MockMvcResultMatchers.status().isOk) + .andReturn().response.contentAsJson() + + val expected = SentenceOverview( + PersonGenerator.OVERVIEW.toSummary(), + listOf( + SentenceSummary(1234567, "Pre-Sentence"), + SentenceSummary(7654321, "Default Sentence Type") + ), + Sentence( + OffenceDetails( + "7654321", + Offence("Murder", 1), + LocalDate.now(), + "overview", + listOf( + Offence("Burglary", 1), + Offence("Assault", 1) + ) + ), + Conviction( + "Hull Court", + "Birmingham Court", + LocalDate.now(), + listOf(AdditionalSentence(3, null, null, "Disqualified from Driving")) + ), + Order("Default Sentence Type", 12, null, LocalDate.now().minusDays(14)), + listOf( + Requirement( + "F", + LocalDate.now().minusDays(1), LocalDate.now(), - "overview", - emptyList() + LocalDate.now().minusDays(2), + LocalDate.now().minusDays(3), + null, + "1 days RAR, 1 completed", + 12, + null, + "my notes", + Rar(completed = 1, scheduled = 0, totalDays = 1) ), - Conviction(null, null, null, listOf()), - null, - listOf(), - listOf(), - null, - listOf() + Requirement( + "W", + LocalDate.now().minusDays(1), + LocalDate.now(), + LocalDate.now().minusDays(2), + LocalDate.now().minusDays(3), + null, + "Unpaid Work - Intensive", + 12, + null, + "my notes", + null + ) ), - Sentence( - OffenceDetails( - "7654321", - Offence("Murder", 1), + listOf( + CourtDocument(COURT_DOCUMENT.alfrescoId, LocalDate.now().minusDays(1), "court report"), + CourtDocument(EVENT_DOCUMENT.alfrescoId, LocalDate.now().minusDays(3), "event report") + ), + "3 minutes completed (of 12 hours)", + listOf( + LicenceCondition( + LC_WITH_NOTES.id, + LIC_COND_MAIN_CAT.description, + LIC_COND_SUB_CAT.description, + LocalDate.now().minusDays(7), LocalDate.now(), - "overview", listOf( - Offence("Burglary", 1), - Offence("Assault", 1) - ) - ), - Conviction( - "Hull Court", - "Birmingham Court", - LocalDate.now(), - listOf(AdditionalSentence(3, null, null, "Disqualified from Driving")) - ), - Order("Default Sentence Type", 12, null, LocalDate.now().minusDays(14)), - listOf( - Requirement( - "F", - LocalDate.now().minusDays(1), - LocalDate.now(), - LocalDate.now().minusDays(2), - LocalDate.now().minusDays(3), - null, - "1 days RAR, 1 completed", - 12, - null, - "my notes", - Rar(completed = 1, scheduled = 0, totalDays = 1) - ), - Requirement( - "W", - LocalDate.now().minusDays(1), - LocalDate.now(), - LocalDate.now().minusDays(2), - LocalDate.now().minusDays(3), - null, - "Unpaid Work - Intensive", - 12, - null, - "my notes", - null - ) - ), - listOf( - CourtDocument(COURT_DOCUMENT.alfrescoId, LocalDate.now().minusDays(1), "court report"), - CourtDocument(EVENT_DOCUMENT.alfrescoId, LocalDate.now().minusDays(3), "event report") - ), - "3 minutes completed (of 12 hours)", - listOf( - LicenceCondition( - LC_WITH_NOTES.id, - LIC_COND_MAIN_CAT.description, - LIC_COND_SUB_CAT.description, - LocalDate.now().minusDays(7), - LocalDate.now(), - listOf( - LicenceConditionNote( - 0, - "Joe Root", - LocalDate.of(2024, 4, 23), - """ + LicenceConditionNote( + 0, + "Joe Root", + LocalDate.of(2024, 4, 23), + """ You must not drink any alcohol until Wednesday 7th August 2024 unless your probation officer says you can. You will need to wear an electronic tag all the time so we can check this. """.trimIndent(), - false - ), - LicenceConditionNote( - 1, - "CVL Service", - LocalDate.of(2024, 4, 22), - """ + false + ), + LicenceConditionNote( + 1, + "CVL Service", + LocalDate.of(2024, 4, 22), + """ Licence Condition created automatically from the Create and Vary a licence system of\nAllow person(s) as designated by your supervising officer to install an electronic monitoring tag on you and access to install any associated equipment in your property, and for the purpose of ensuring that equipment is functioning correctly. You must not damage or tamper with these devices and ensure that the tag is charged, and report to your supervising officer and the EM provider immediately if the tag or the associated equipment are not working correctly. This will be for the purpose of monitoring your alcohol abstinence licence condition(s) unless otherwise authorised by your supervising officer. Licence Condition created automatically from the Create and Vary a licence system of\nAllow person(s) as designated by your supervising officer to install an electronic monitoring tag on you and access to install any associated equipment in your property, and for the purpose of ensuring that equipment is functioning correctly. You must not damage or tamper with these devices and ensure that the tag is charged, and report to your supervising officer and the EM provider immediately if the tag or the associated equipment are not working correctly. This will be for the purpose of monitoring your alcohol abstinence licence condition(s) unless otherwise authorised by your supervising officer.Licence Condition created automatically from the Create and Vary a licence system of\nAllow person(s) as desi """.trimIndent(), - true - ) + true ) - ), - LicenceCondition( - LC_WITHOUT_NOTES.id, - LIC_COND_MAIN_CAT.description, - imposedReleasedDate = LocalDate.now().minusDays(14), - licenceConditionNotes = listOf() - ), - LicenceCondition( - LC_WITH_NOTES_WITHOUT_ADDED_BY.id, - LIC_COND_MAIN_CAT.description, - LIC_COND_SUB_CAT.description, - LocalDate.now().minusDays(7), - LocalDate.now(), - listOf( - LicenceConditionNote( - 0, - note = "He shall not contact or associate with Peter Jones without the prior approval of the supervising officer;", - hasNoteBeenTruncated = false - ) + ) + ), + LicenceCondition( + LC_WITHOUT_NOTES.id, + LIC_COND_MAIN_CAT.description, + imposedReleasedDate = LocalDate.now().minusDays(14), + licenceConditionNotes = listOf() + ), + LicenceCondition( + LC_WITH_NOTES_WITHOUT_ADDED_BY.id, + LIC_COND_MAIN_CAT.description, + LIC_COND_SUB_CAT.description, + LocalDate.now().minusDays(7), + LocalDate.now(), + listOf( + LicenceConditionNote( + 0, + note = "He shall not contact or associate with Peter Jones without the prior approval of the supervising officer;", + hasNoteBeenTruncated = false ) - ), - LicenceCondition( - LC_WITH_1500_CHAR_NOTE.id, - LIC_COND_MAIN_CAT.description, - LIC_COND_SUB_CAT.description, - LocalDate.now().minusDays(7), - LocalDate.now(), - listOf( - LicenceConditionNote( - 0, - "Tom Brady", - LocalDate.of(2024, 10, 29), - """ + ) + ), + LicenceCondition( + LC_WITH_1500_CHAR_NOTE.id, + LIC_COND_MAIN_CAT.description, + LIC_COND_SUB_CAT.description, + LocalDate.now().minusDays(7), + LocalDate.now(), + listOf( + LicenceConditionNote( + 0, + "Tom Brady", + LocalDate.of(2024, 10, 29), + """ Needs to stay home every evening """.trimIndent(), - false - ), - LicenceConditionNote( - 1, - "Harry Kane", - LocalDate.of(2024, 10, 29), - """ + false + ), + LicenceConditionNote( + 1, + "Harry Kane", + LocalDate.of(2024, 10, 29), + """ ${LicenceConditionGenerator.NOTE_1500_CHARS} """.trimIndent(), - false - ) + false ) ) ) ) - ), - ProbationHistory(2, LocalDate.now().minusDays(7), 2, 2) + ) ) assertEquals(expected, response) diff --git a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/controller/SentenceController.kt b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/controller/SentenceController.kt index 73887544f0..952e4ce0f0 100644 --- a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/controller/SentenceController.kt +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/controller/SentenceController.kt @@ -3,10 +3,7 @@ package uk.gov.justice.digital.hmpps.api.controller import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.security.access.prepost.PreAuthorize -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController +import org.springframework.web.bind.annotation.* import uk.gov.justice.digital.hmpps.service.* @RestController @@ -23,7 +20,14 @@ class SentenceController( @GetMapping @Operation(summary = "Display active events") - fun getOverview(@PathVariable crn: String) = sentenceService.getEvents(crn) + fun getOverview( + @PathVariable crn: String, + @RequestParam(required = false) number: String?, + ) = sentenceService.getEvents(crn, number) + + @GetMapping("/probation-history") + @Operation(summary = "Display probation history") + fun getProbationHistory(@PathVariable crn: String) = sentenceService.getProbationHistory(crn) @GetMapping("/previous-orders") @Operation(summary = "Display inactive events") diff --git a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/sentence/ProbationHistory.kt b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/sentence/ProbationHistory.kt index 02670ff459..bbe71b3752 100644 --- a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/sentence/ProbationHistory.kt +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/sentence/ProbationHistory.kt @@ -1,7 +1,14 @@ package uk.gov.justice.digital.hmpps.api.model.sentence +import uk.gov.justice.digital.hmpps.api.model.PersonSummary import java.time.LocalDate +data class History( + val personSummary: PersonSummary, + val sentenceSummaryList: List = emptyList(), + val probationHistory: ProbationHistory +) + data class ProbationHistory( val numberOfTerminatedEvents: Int, val dateOfMostRecentTerminatedEvent: LocalDate?, diff --git a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/sentence/SentenceOverview.kt b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/sentence/SentenceOverview.kt index 3b748a87ae..1fd898e4ed 100644 --- a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/sentence/SentenceOverview.kt +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/sentence/SentenceOverview.kt @@ -4,6 +4,11 @@ import uk.gov.justice.digital.hmpps.api.model.PersonSummary data class SentenceOverview( val personSummary: PersonSummary, - val sentences: List, - val probationHistory: ProbationHistory, + val sentenceSummaryList: List = emptyList(), + val sentence: Sentence? = null +) + +data class SentenceSummary( + val eventNumber: Long, + val description: String ) diff --git a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/sentence/entity/SentenceRepository.kt b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/sentence/entity/SentenceRepository.kt index d5ef29ff91..fb762a48d6 100644 --- a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/sentence/entity/SentenceRepository.kt +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/sentence/entity/SentenceRepository.kt @@ -8,16 +8,18 @@ import uk.gov.justice.digital.hmpps.integrations.delius.overview.entity.Person interface EventSentenceRepository : JpaRepository { @Query( - "SELECT e FROM Event e " + - "LEFT JOIN FETCH e.disposal d " + - "LEFT JOIN FETCH d.type t " + - "LEFT JOIN FETCH e.court c " + - "LEFT JOIN FETCH e.mainOffence m " + - "LEFT JOIN FETCH e.additionalOffences ao " + - "LEFT JOIN FETCH m.offence mo " + - "LEFT JOIN FETCH ao.offence aoo " + - "WHERE e.personId = :id " + - "ORDER BY e.dateCreated DESC " + """ + SELECT e FROM Event e + LEFT JOIN FETCH e.disposal d + LEFT JOIN FETCH d.type t + LEFT JOIN FETCH e.court c + LEFT JOIN FETCH e.mainOffence m + LEFT JOIN FETCH e.additionalOffences ao + LEFT JOIN FETCH m.offence mo + LEFT JOIN FETCH ao.offence aoo + WHERE e.personId = :id + ORDER BY e.dateCreated DESC + """ ) fun findSentencesByPersonId(id: Long): List diff --git a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/SentenceService.kt b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/SentenceService.kt index 61c249acc6..9670515071 100644 --- a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/SentenceService.kt +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/SentenceService.kt @@ -27,17 +27,31 @@ class SentenceService( private val upwAppointmentRepository: UpwAppointmentRepository, private val licenceConditionRepository: LicenceConditionRepository ) { - fun getEvents(crn: String): SentenceOverview { + fun getEvents(crn: String, eventNumber: String?): SentenceOverview { val person = personRepository.getPerson(crn) - val (activeEvents, inactiveEvents) = eventRepository.findSentencesByPersonId(person.id).partition { it.active } + val activeEvents = eventRepository.findSentencesByPersonId(person.id).filter { + it.active + } return SentenceOverview( personSummary = person.toSummary(), - sentences = activeEvents.map { - val courtAppearance = courtAppearanceRepository.getFirstCourtAppearanceByEventIdOrderByDate(it.id) - val additionalSentences = additionalSentenceRepository.getAllByEventId(it.id) - it.toSentence(courtAppearance, additionalSentences, crn) - }, + activeEvents.map { it.toSentenceSummary() }, + sentence = activeEvents.firstOrNull { + when (eventNumber) { + null -> true + else -> eventNumber == it.eventNumber + } + }?.toSentence(crn) + ) + } + + fun getProbationHistory(crn: String): History { + val person = personRepository.getPerson(crn) + val (activeEvents, inactiveEvents) = eventRepository.findSentencesByPersonId(person.id).partition { it.active } + + return History( + personSummary = person.toSummary(), + activeEvents.map { it.toSentenceSummary() }, ProbationHistory( inactiveEvents.count(), getMostRecentTerminatedDateFromInactiveEvents(inactiveEvents), @@ -47,8 +61,16 @@ class SentenceService( ) } - fun Event.toSentence(courtAppearance: CourtAppearance?, additionalSentences: List, crn: String) = - Sentence( + fun Event.toSentenceSummary() = SentenceSummary( + eventNumber.toLong(), + disposal?.type?.description ?: "Pre-Sentence" + ) + + fun Event.toSentence(crn: String): Sentence { + val courtAppearance = courtAppearanceRepository.getFirstCourtAppearanceByEventIdOrderByDate(id) + val additionalSentences = additionalSentenceRepository.getAllByEventId(id) + + return Sentence( OffenceDetails( eventNumber = eventNumber, offence = mainOffence?.let { Offence(it.offence.description, it.offenceCount) }, @@ -73,8 +95,9 @@ class SentenceService( licenceConditionRepository.findAllByDisposalId(disposal.id).map { it.toLicenceCondition() } - } ?: emptyList(), + } ?: emptyList() ) + } fun ExtraSentence.toAdditionalSentence(): AdditionalSentence = AdditionalSentence(length, amount, notes, type.description) diff --git a/projects/manage-supervision-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/SentenceServiceTest.kt b/projects/manage-supervision-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/SentenceServiceTest.kt index 386a43b05d..e1617c5d24 100644 --- a/projects/manage-supervision-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/SentenceServiceTest.kt +++ b/projects/manage-supervision-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/SentenceServiceTest.kt @@ -89,16 +89,13 @@ class SentenceServiceTest { whenever(eventRepository.findSentencesByPersonId(PersonGenerator.OVERVIEW.id)).thenReturn( listOf() ) - whenever(offenderManagerRepository.countOffenderManagersByPerson(PersonGenerator.OVERVIEW)).thenReturn( - 0 - ) val expected = SentenceOverview( PersonGenerator.OVERVIEW.toSummary(), - listOf(), ProbationHistory(0, null, 0, 0) + emptyList() ) - val response = service.getEvents(PersonGenerator.OVERVIEW.crn) + val response = service.getEvents(PersonGenerator.OVERVIEW.crn, null) assertEquals(expected, response) @@ -199,78 +196,76 @@ class SentenceServiceTest { whenever(requirementRepository.sumTotalUnpaidWorkHoursByDisposal(event.disposal!!.id)).thenReturn(70) whenever(upwAppointmentRepository.calculateUnpaidTimeWorked(event.disposal!!.id)).thenReturn(3936) - val response = service.getEvents(PersonGenerator.OVERVIEW.crn) + val response = service.getEvents(PersonGenerator.OVERVIEW.crn, null) val expected = SentenceOverview( PersonGenerator.OVERVIEW.toSummary(), - listOf( - Sentence( - OffenceDetails( - "123457", - Offence("Murder", 1), - LocalDate.now(), - "overview", - listOf( - Offence("Burglary", 1) - ) - ), - Conviction( - "Hull Court", - null, - null, - listOf( - AdditionalSentence(3, null, null, "Disqualified from Driving"), - AdditionalSentence(null, 500, "fine notes", "Fine") - ) - ), - Order("Default Sentence Type", 12, null, LocalDate.now().minusDays(14)), + listOf(SentenceSummary(123457, "Default Sentence Type")), + Sentence( + OffenceDetails( + "123457", + Offence("Murder", 1), + LocalDate.now(), + "overview", listOf( - Requirement( - requirement1._code, - requirement1._expectedStartDate, - requirement1._startDate, - requirement1._expectedEndDate, - requirement1._terminationDate, - requirement1._terminationReason, - "${requirement1._description} - ${requirement1._codeDescription}", - requirement1._length, - requirement1.lengthUnitValue, - requirement1._notes, - null - ), - Requirement( - requirement2._code, - requirement2._expectedStartDate, - requirement2._startDate, - requirement2._expectedEndDate, - requirement2._terminationDate, - requirement2._terminationReason, - "3 days RAR, 1 completed", - requirement2._length, - requirement2.lengthUnitValue, - requirement2._notes, - Rar(1, 2, 3) - ), - Requirement( - requirement3._code, - requirement3._expectedStartDate, - requirement3._startDate, - requirement3._expectedEndDate, - requirement3._terminationDate, - requirement3._terminationReason, - requirement3._description, - requirement3._length, - requirement3.lengthUnitValue, - requirement3._notes, - null - ) + Offence("Burglary", 1) + ) + ), + Conviction( + "Hull Court", + null, + null, + listOf( + AdditionalSentence(3, null, null, "Disqualified from Driving"), + AdditionalSentence(null, 500, "fine notes", "Fine") + ) + ), + Order("Default Sentence Type", 12, null, LocalDate.now().minusDays(14)), + listOf( + Requirement( + requirement1._code, + requirement1._expectedStartDate, + requirement1._startDate, + requirement1._expectedEndDate, + requirement1._terminationDate, + requirement1._terminationReason, + "${requirement1._description} - ${requirement1._codeDescription}", + requirement1._length, + requirement1.lengthUnitValue, + requirement1._notes, + null + ), + Requirement( + requirement2._code, + requirement2._expectedStartDate, + requirement2._startDate, + requirement2._expectedEndDate, + requirement2._terminationDate, + requirement2._terminationReason, + "3 days RAR, 1 completed", + requirement2._length, + requirement2.lengthUnitValue, + requirement2._notes, + Rar(1, 2, 3) ), - listOf(CourtDocument("A001", LocalDate.now(), "Pre Sentence Event")), - "65 hours 36 minutes completed (of 70 hours)", - listOf() - ) - ), - ProbationHistory(0, null, 0, 0) + Requirement( + requirement3._code, + requirement3._expectedStartDate, + requirement3._startDate, + requirement3._expectedEndDate, + requirement3._terminationDate, + requirement3._terminationReason, + requirement3._description, + requirement3._length, + requirement3.lengthUnitValue, + requirement3._notes, + null + ) + ), + listOf(CourtDocument("A001", LocalDate.now(), "Pre Sentence Event")), + "65 hours 36 minutes completed (of 70 hours)", + listOf() + ) ) assertEquals(expected, response) @@ -302,12 +297,12 @@ class SentenceServiceTest { whenever(requirementRepository.sumTotalUnpaidWorkHoursByDisposal(event.disposal!!.id)).thenReturn(1) whenever(upwAppointmentRepository.calculateUnpaidTimeWorked(event.disposal!!.id)).thenReturn(0) - val response = service.getEvents(PersonGenerator.OVERVIEW.crn) + val response = service.getEvents(PersonGenerator.OVERVIEW.crn, null) val expected = "0 minutes completed (of 1 hour)" - assertEquals(expected, response.sentences[0].unpaidWorkProgress) + assertEquals(expected, response.sentence!!.unpaidWorkProgress) } @Test @@ -323,12 +318,12 @@ class SentenceServiceTest { whenever(requirementRepository.sumTotalUnpaidWorkHoursByDisposal(event.disposal!!.id)).thenReturn(1) whenever(upwAppointmentRepository.calculateUnpaidTimeWorked(event.disposal!!.id)).thenReturn(1) - val response = service.getEvents(PersonGenerator.OVERVIEW.crn) + val response = service.getEvents(PersonGenerator.OVERVIEW.crn, null) val expected = "1 minute completed (of 1 hour)" - assertEquals(expected, response.sentences[0].unpaidWorkProgress) + assertEquals(expected, response.sentence!!.unpaidWorkProgress) } @Test @@ -344,11 +339,11 @@ class SentenceServiceTest { whenever(requirementRepository.sumTotalUnpaidWorkHoursByDisposal(event.disposal!!.id)).thenReturn(2) whenever(upwAppointmentRepository.calculateUnpaidTimeWorked(event.disposal!!.id)).thenReturn(2) - val response = service.getEvents(PersonGenerator.OVERVIEW.crn) + val response = service.getEvents(PersonGenerator.OVERVIEW.crn, null) val expected = "2 minutes completed (of 2 hours)" - assertEquals(expected, response.sentences[0].unpaidWorkProgress) + assertEquals(expected, response.sentence!!.unpaidWorkProgress) } @Test @@ -364,7 +359,7 @@ class SentenceServiceTest { whenever(requirementRepository.sumTotalUnpaidWorkHoursByDisposal(event.disposal!!.id)).thenReturn(1) whenever(upwAppointmentRepository.calculateUnpaidTimeWorked(event.disposal!!.id)).thenReturn(60) - val response = service.getEvents(PersonGenerator.OVERVIEW.crn) + val response = service.getEvents(PersonGenerator.OVERVIEW.crn, null) val expected = Requirement( requirement1._code, @@ -380,7 +375,7 @@ class SentenceServiceTest { null ) - assertEquals(expected, response.sentences[0].requirements[0]) + assertEquals(expected, response.sentence!!.requirements[0]) } @Test @@ -396,11 +391,11 @@ class SentenceServiceTest { whenever(requirementRepository.sumTotalUnpaidWorkHoursByDisposal(event.disposal!!.id)).thenReturn(2) whenever(upwAppointmentRepository.calculateUnpaidTimeWorked(event.disposal!!.id)).thenReturn(61) - val response = service.getEvents(PersonGenerator.OVERVIEW.crn) + val response = service.getEvents(PersonGenerator.OVERVIEW.crn, null) val expected = "1 hour 1 minute completed (of 2 hours)" - assertEquals(expected, response.sentences[0].unpaidWorkProgress) + assertEquals(expected, response.sentence!!.unpaidWorkProgress) } @Test @@ -416,11 +411,11 @@ class SentenceServiceTest { whenever(requirementRepository.sumTotalUnpaidWorkHoursByDisposal(event.disposal!!.id)).thenReturn(2) whenever(upwAppointmentRepository.calculateUnpaidTimeWorked(event.disposal!!.id)).thenReturn(62) - val response = service.getEvents(PersonGenerator.OVERVIEW.crn) + val response = service.getEvents(PersonGenerator.OVERVIEW.crn, null) val expected = "1 hour 2 minutes completed (of 2 hours)" - assertEquals(expected, response.sentences[0].unpaidWorkProgress) + assertEquals(expected, response.sentence!!.unpaidWorkProgress) } @Test @@ -436,7 +431,7 @@ class SentenceServiceTest { whenever(requirementRepository.sumTotalUnpaidWorkHoursByDisposal(event.disposal!!.id)).thenReturn(1) whenever(upwAppointmentRepository.calculateUnpaidTimeWorked(event.disposal!!.id)).thenReturn(120) - val response = service.getEvents(PersonGenerator.OVERVIEW.crn) + val response = service.getEvents(PersonGenerator.OVERVIEW.crn, null) val expected = Requirement( requirement1._code, @@ -452,7 +447,7 @@ class SentenceServiceTest { null ) - assertEquals(expected, response.sentences[0].requirements[0]) + assertEquals(expected, response.sentence!!.requirements[0]) } @Test @@ -468,11 +463,11 @@ class SentenceServiceTest { whenever(requirementRepository.sumTotalUnpaidWorkHoursByDisposal(event.disposal!!.id)).thenReturn(3) whenever(upwAppointmentRepository.calculateUnpaidTimeWorked(event.disposal!!.id)).thenReturn(121) - val response = service.getEvents(PersonGenerator.OVERVIEW.crn) + val response = service.getEvents(PersonGenerator.OVERVIEW.crn, null) val expected = "2 hours 1 minute completed (of 3 hours)" - assertEquals(expected, response.sentences[0].unpaidWorkProgress) + assertEquals(expected, response.sentence!!.unpaidWorkProgress) } @Test @@ -488,11 +483,11 @@ class SentenceServiceTest { whenever(requirementRepository.sumTotalUnpaidWorkHoursByDisposal(event.disposal!!.id)).thenReturn(3) whenever(upwAppointmentRepository.calculateUnpaidTimeWorked(event.disposal!!.id)).thenReturn(122) - val response = service.getEvents(PersonGenerator.OVERVIEW.crn) + val response = service.getEvents(PersonGenerator.OVERVIEW.crn, null) val expected = "2 hours 2 minutes completed (of 3 hours)" - assertEquals(expected, response.sentences[0].unpaidWorkProgress) + assertEquals(expected, response.sentence!!.unpaidWorkProgress) } data class RequirementDetails( diff --git a/projects/redrive-dead-letter-queues/container/Dockerfile b/projects/redrive-dead-letter-queues/container/Dockerfile index 796f6d5f69..cc07940775 100644 --- a/projects/redrive-dead-letter-queues/container/Dockerfile +++ b/projects/redrive-dead-letter-queues/container/Dockerfile @@ -1,4 +1,4 @@ -FROM public.ecr.aws/aws-cli/aws-cli:2.19.1 +FROM public.ecr.aws/aws-cli/aws-cli:2.19.2 USER root SHELL ["/bin/bash", "-o", "pipefail", "-c"] diff --git a/settings.gradle.kts b/settings.gradle.kts index ceda17c8a3..5f1020817f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -73,16 +73,16 @@ dependencyResolutionManagement { create("libs") { library("asyncapi", "org.openfolder:kotlin-asyncapi-spring-web:3.0.3") library("aws-autoconfigure", "io.awspring.cloud:spring-cloud-aws-autoconfigure:3.2.1") - library("aws-query-protocol", "software.amazon.awssdk:aws-query-protocol:2.29.6") + library("aws-query-protocol", "software.amazon.awssdk:aws-query-protocol:2.29.7") library("aws-sns", "io.awspring.cloud:spring-cloud-aws-starter-sns:3.2.1") library("aws-sqs", "io.awspring.cloud:spring-cloud-aws-starter-sqs:3.2.1") library("aws-starter", "io.awspring.cloud:spring-cloud-aws-starter:3.2.1") - library("aws-sts", "software.amazon.awssdk:sts:2.29.6") + library("aws-sts", "software.amazon.awssdk:sts:2.29.7") library("azure-app-insights", "com.microsoft.azure:applicationinsights-web:3.6.2") - library("azure-identity", "com.azure:azure-identity:1.13.3") + library("azure-identity", "com.azure:azure-identity:1.14.1") library("flipt", "io.flipt:flipt-java:1.1.1") library("html2md", "com.vladsch.flexmark:flexmark-html2md-converter:0.64.8") - library("microsoft-graph", "com.microsoft.graph:microsoft-graph:6.16.0") + library("microsoft-graph", "com.microsoft.graph:microsoft-graph:6.19.0") library("mockito-inline", "org.mockito:mockito-inline:5.2.0") library("mockito-kotlin", "org.mockito.kotlin:mockito-kotlin:5.4.0") library("notify", "uk.gov.service.notify:notifications-java-client:5.2.1-RELEASE")