From bb67395f1a47dc70ce6069baf150d225ffc416e6 Mon Sep 17 00:00:00 2001 From: pmcphee77 <150798161+pmcphee77@users.noreply.github.com> Date: Tue, 6 Aug 2024 09:12:45 +0100 Subject: [PATCH] PI-2103: Added get by nsiId (#4115) * PI-2103: Added get by nsiId * PI-2103: Added get by nsiId * PI-2303: Used CompletableFuture.allOf instead --- .../hmpps/data/generator/PersonGenerator.kt | 5 +- .../hmpps/data/generator/StaffGenerator.kt | 9 +- .../__files/get_single_nsi_C123456.json | 180 ++++++++++++++++++ .../simulations/mappings/get-convictions.json | 13 ++ ...onvictionByCrnAndEventIdIntegrationTest.kt | 4 +- .../hmpps/ConvictionByCrnIntegrationTest.kt | 2 +- ...NsisByCrnAndConvictionIdIntegrationTest.kt | 37 +++- .../digital/hmpps/OffenderIntegrationTest.kt | 8 +- .../digital/hmpps/ProxyIntegrationTest.kt | 10 +- ...uirementsByCrnAndEventIdIntegrationTest.kt | 4 +- .../RequirementsByEventIdIntegrationTest.kt | 4 +- .../hmpps/api/proxy/CommunityApiController.kt | 38 +++- .../hmpps/api/proxy/CommunityApiService.kt | 12 +- .../justice/digital/hmpps/api/proxy/Uri.kt | 6 + .../hmpps/api/resource/ConvictionResource.kt | 20 ++ .../api/resource/advice/ControllerAdvice.kt | 24 +++ .../api/resource/advice/ErrorResponse.kt | 6 + .../integrations/delius/event/nsi/Nsi.kt | 14 ++ .../delius/service/InterventionService.kt | 75 ++++---- .../delius/service/OffenderManagerService.kt | 2 +- .../src/main/resources/application.yml | 2 +- .../service/OffenderManagerServiceTest.kt | 90 +++++++++ 22 files changed, 496 insertions(+), 69 deletions(-) create mode 100644 projects/court-case-and-delius/src/dev/resources/simulations/__files/get_single_nsi_C123456.json create mode 100644 projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/advice/ControllerAdvice.kt create mode 100644 projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/advice/ErrorResponse.kt create mode 100644 projects/court-case-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/OffenderManagerServiceTest.kt diff --git a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt index 1eb67b34cd..ebfc539a22 100644 --- a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt +++ b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt @@ -22,6 +22,7 @@ import uk.gov.justice.digital.hmpps.data.generator.ReferenceDataGenerator.SEXUAL import uk.gov.justice.digital.hmpps.data.generator.ReferenceDataGenerator.TITLE import uk.gov.justice.digital.hmpps.integrations.delius.entity.ReferenceData import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.* +import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.Staff import java.time.LocalDate import java.time.ZonedDateTime @@ -131,13 +132,13 @@ object PersonGenerator { id = IdGenerator.getAndIncrement() ) - fun generatePersonManager(person: Person) = + fun generatePersonManager(person: Person, staff: Staff? = StaffGenerator.ALLOCATED) = PersonManager( id = IdGenerator.getAndIncrement(), trustProviderFlag = false, person = person, team = TeamGenerator.DEFAULT, - staff = StaffGenerator.ALLOCATED, + staff = staff, provider = ProviderGenerator.DEFAULT, date = ZonedDateTime.now().minusDays(2), allocationReason = DEFAULT_ALLOCATION_REASON, diff --git a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt index 2f314242e2..9b90530a0a 100644 --- a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt +++ b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt @@ -13,8 +13,13 @@ object StaffGenerator { val ALLOCATED = generate("N01ABBA") val OFFICER = generateOfficer() val STAFF_USER = generateStaffUser() - fun generate(code: String, id: Long = IdGenerator.getAndIncrement(), grade: ReferenceData? = null) = - Staff(code, "Bob", "Micheal", "Smith", CourtGenerator.PROBATION_AREA, grade, null, id = id) + fun generate( + code: String, + id: Long = IdGenerator.getAndIncrement(), + grade: ReferenceData? = null, + user: StaffUser? = null + ) = + Staff(code, "Bob", "Micheal", "Smith", CourtGenerator.PROBATION_AREA, grade, user = user, id = id) fun generateStaffUser() = StaffUser(ALLOCATED, "JoeBloggs", IdGenerator.getAndIncrement()) diff --git a/projects/court-case-and-delius/src/dev/resources/simulations/__files/get_single_nsi_C123456.json b/projects/court-case-and-delius/src/dev/resources/simulations/__files/get_single_nsi_C123456.json new file mode 100644 index 0000000000..fe9011a036 --- /dev/null +++ b/projects/court-case-and-delius/src/dev/resources/simulations/__files/get_single_nsi_C123456.json @@ -0,0 +1,180 @@ +{ + "nsiId": 0, + "nsiType": { + "code": "NSITYPE", + "description": "NSI Type desc" + }, + "nsiOutcome": { + "code": "BRE01", + "description": "this NSI is in breach" + }, + "requirement": { + "requirementId": 93, + "requirementNotes": "notes", + "commencementDate": "2024-01-01", + "startDate": "2024-01-02", + "terminationDate": "2024-01-03", + "expectedStartDate": "2024-01-04", + "expectedEndDate": "2024-01-05", + "createdDatetime": "2023-12-31T12:00:00Z", + "active": true, + "requirementTypeSubCategory": { + "code": "Sub", + "description": "Sub cat" + }, + "requirementTypeMainCategory": { + "code": "Main", + "description": "Main cat" + }, + "adRequirementTypeMainCategory": { + "code": "AdMain", + "description": "AdMain cat" + }, + "adRequirementTypeSubCategory": { + "code": "AdSub", + "description": "AdSub cat" + }, + "terminationReason": { + "code": "R1", + "description": "Released" + }, + "length": 3, + "lengthUnit": "months", + "restrictive": false, + "softDeleted": false + }, + "nsiStatus": { + "code": "SLI01", + "description": "Active" + }, + "statusDateTime": "2024-07-01T12:00:00+01:00", + "actualStartDate": "2024-07-26", + "expectedStartDate": "2024-01-01", + "actualEndDate": "2024-07-26", + "expectedEndDate": "2024-07-25", + "referralDate": "2024-07-27", + "length": 7, + "lengthUnit": "Months", + "nsiManagers": [ + { + "probationArea": { + "probationAreaId": 78, + "code": "N52", + "description": "West Midlands Region", + "organisation": { + "code": "ORG1", + "description": "Org 1" + } + }, + "team": { + "code": "T1", + "description": "Team1", + "telephone": "123", + "localDeliveryUnit": { + "code": "London", + "description": "LN1" + }, + "district": { + "code": "London", + "description": "LN1" + }, + "borough": { + "code": "LN1", + "description": "Default" + }, + "teamType": { + "code": "London", + "description": "LN1" + }, + "startDate": "2024-07-26" + }, + "staff": { + "username": "JoeBloggs", + "staffCode": "N01ABBA", + "staffIdentifier": 80, + "staff": { + "forenames": "Bob Micheal", + "surname": "Smith" + }, + "teams": [], + "probationArea": { + "probationAreaId": 78, + "code": "N52", + "description": "West Midlands Region", + "organisation": { + "code": "ORG1", + "description": "Org 1" + } + } + }, + "startDate": "2024-07-26" + }, + { + "probationArea": { + "probationAreaId": 78, + "code": "N52", + "description": "West Midlands Region", + "organisation": { + "code": "ORG1", + "description": "Org 1" + } + }, + "team": { + "code": "T1", + "description": "Team1", + "telephone": "123", + "localDeliveryUnit": { + "code": "London", + "description": "LN1" + }, + "district": { + "code": "London", + "description": "LN1" + }, + "borough": { + "code": "LN1", + "description": "Default" + }, + "teamType": { + "code": "London", + "description": "LN1" + }, + "startDate": "2024-07-26" + }, + "staff": { + "username": "JoeBloggs", + "staffCode": "N01ABBA", + "staffIdentifier": 80, + "staff": { + "forenames": "Bob Micheal", + "surname": "Smith" + }, + "teams": [], + "probationArea": { + "probationAreaId": 78, + "code": "N52", + "description": "West Midlands Region", + "organisation": { + "code": "ORG1", + "description": "Org 1" + } + } + }, + "startDate": "2024-07-19", + "endDate": "2024-07-25" + } + ], + "notes": "notes", + "intendedProvider": { + "probationAreaId": 78, + "code": "N52", + "description": "West Midlands Region", + "organisation": { + "code": "ORG1", + "description": "Org 1" + } + }, + "active": true, + "softDeleted": false, + "externalReference": "external ref" +} \ No newline at end of file diff --git a/projects/court-case-and-delius/src/dev/resources/simulations/mappings/get-convictions.json b/projects/court-case-and-delius/src/dev/resources/simulations/mappings/get-convictions.json index acfec3018a..726e79936a 100644 --- a/projects/court-case-and-delius/src/dev/resources/simulations/mappings/get-convictions.json +++ b/projects/court-case-and-delius/src/dev/resources/simulations/mappings/get-convictions.json @@ -39,6 +39,19 @@ "bodyFileName": "get_nsi_C123456.json" } }, + { + "request": { + "method": "GET", + "urlPathTemplate": "/secure/offenders/crn/{crn}/convictions/{convictionId}/nsis/{nsiId}" + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "bodyFileName": "get_single_nsi_C123456.json" + } + }, { "request": { "method": "GET", diff --git a/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ConvictionByCrnAndEventIdIntegrationTest.kt b/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ConvictionByCrnAndEventIdIntegrationTest.kt index 6c1b3e7810..4b5d9608ac 100644 --- a/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ConvictionByCrnAndEventIdIntegrationTest.kt +++ b/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ConvictionByCrnAndEventIdIntegrationTest.kt @@ -51,7 +51,7 @@ internal class ConvictionByCrnAndEventIdIntegrationTest { mockMvc .perform(get("/probation-case/A123456/convictions/1").withToken()) .andExpect(status().isNotFound) - .andExpect(jsonPath("$.message").value("Person with crn of A123456 not found")) + .andExpect(jsonPath("$.developerMessage").value("Person with crn of A123456 not found")) } @Test @@ -61,7 +61,7 @@ internal class ConvictionByCrnAndEventIdIntegrationTest { mockMvc .perform(get("/probation-case/$crn/convictions/3").withToken()) .andExpect(status().isNotFound) - .andExpect(jsonPath("$.message").value("Conviction with ID 3 for Offender with crn C123456 not found")) + .andExpect(jsonPath("$.developerMessage").value("Conviction with ID 3 for Offender with crn C123456 not found")) } @Test diff --git a/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ConvictionByCrnIntegrationTest.kt b/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ConvictionByCrnIntegrationTest.kt index 31402ee731..30c5b2a686 100644 --- a/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ConvictionByCrnIntegrationTest.kt +++ b/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ConvictionByCrnIntegrationTest.kt @@ -50,7 +50,7 @@ internal class ConvictionByCrnIntegrationTest { mockMvc .perform(get("/probation-case/A123456/convictions").withToken()) .andExpect(status().isNotFound) - .andExpect(jsonPath("$.message").value("Person with crn of A123456 not found")) + .andExpect(jsonPath("$.developerMessage").value("Person with crn of A123456 not found")) } @Test diff --git a/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/NsisByCrnAndConvictionIdIntegrationTest.kt b/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/NsisByCrnAndConvictionIdIntegrationTest.kt index a679a5bb9f..93c18d9e79 100644 --- a/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/NsisByCrnAndConvictionIdIntegrationTest.kt +++ b/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/NsisByCrnAndConvictionIdIntegrationTest.kt @@ -1,5 +1,7 @@ package uk.gov.justice.digital.hmpps +import org.hamcrest.MatcherAssert +import org.hamcrest.Matchers.equalTo import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired @@ -12,12 +14,14 @@ import org.springframework.test.web.servlet.result.MockMvcResultHandlers import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status import uk.gov.justice.digital.hmpps.api.model.* +import uk.gov.justice.digital.hmpps.api.resource.advice.ErrorResponse import uk.gov.justice.digital.hmpps.data.generator.NsiManagerGenerator import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator import uk.gov.justice.digital.hmpps.data.generator.RequirementsGenerator import uk.gov.justice.digital.hmpps.data.generator.SentenceGenerator import uk.gov.justice.digital.hmpps.data.generator.SentenceGenerator.BREACH_NSIS import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.Staff +import uk.gov.justice.digital.hmpps.integrations.delius.service.toNsi import uk.gov.justice.digital.hmpps.integrations.delius.service.toProbationArea import uk.gov.justice.digital.hmpps.integrations.delius.service.toTeam import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.contentAsJson @@ -55,7 +59,7 @@ internal class NsisByCrnAndConvictionIdIntegrationTest { .withToken() ) .andExpect(status().isNotFound) - .andExpect(jsonPath("$.message").value("Person with crn of A123456 not found")) + .andExpect(jsonPath("$.developerMessage").value("Person with crn of A123456 not found")) } @Test @@ -69,7 +73,7 @@ internal class NsisByCrnAndConvictionIdIntegrationTest { .withToken() ) .andExpect(status().isNotFound) - .andExpect(jsonPath("$.message").value("Conviction with ID 3 for Offender with crn C123456 not found")) + .andExpect(jsonPath("$.developerMessage").value("Conviction with ID 3 for Offender with crn C123456 not found")) } @Test @@ -146,4 +150,33 @@ internal class NsisByCrnAndConvictionIdIntegrationTest { probationArea.toProbationArea(false), grade?.keyValueOf() ) + + @Test + fun `nsi by nsiId`() { + val crn = PersonGenerator.CURRENTLY_MANAGED.crn + val expectedNsi = BREACH_NSIS.toNsi() + val actualNsi = mockMvc + .perform( + get("/probation-case/$crn/convictions/${BREACH_NSIS.eventId}/nsis/${BREACH_NSIS.id}") + .withToken() + ) + .andExpect(status().isOk) + .andReturn().response.contentAsJson() + + MatcherAssert.assertThat(actualNsi.nsiOutcome, equalTo(expectedNsi.nsiOutcome)) + } + + @Test + fun `nsi by nsiId not found`() { + val crn = PersonGenerator.CURRENTLY_MANAGED.crn + val response = mockMvc + .perform( + get("/probation-case/$crn/convictions/${BREACH_NSIS.eventId}/nsis/999") + .withToken() + ) + .andExpect(status().isNotFound) + .andReturn().response.contentAsJson() + + MatcherAssert.assertThat(response.developerMessage, equalTo("NSI with id 999 not found")) + } } \ No newline at end of file diff --git a/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/OffenderIntegrationTest.kt b/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/OffenderIntegrationTest.kt index b20c1c91e6..d53969ebd1 100644 --- a/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/OffenderIntegrationTest.kt +++ b/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/OffenderIntegrationTest.kt @@ -8,13 +8,14 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT import org.springframework.http.HttpStatus +import org.springframework.test.context.TestPropertySource import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status import org.springframework.test.web.servlet.result.isEqualTo import software.amazon.awssdk.utils.ImmutableMap -import uk.gov.justice.digital.hmpps.advice.ErrorResponse import uk.gov.justice.digital.hmpps.api.model.* +import uk.gov.justice.digital.hmpps.api.resource.advice.ErrorResponse import uk.gov.justice.digital.hmpps.data.generator.AreaGenerator.PARTITION_AREA import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator.ADDRESS @@ -33,6 +34,7 @@ import java.time.LocalDate @AutoConfigureMockMvc @SpringBootTest(webEnvironment = RANDOM_PORT) +@TestPropertySource(properties = ["lao-access.ignore-exclusions = false", "lao-access.ignore-restrictions = true"]) internal class OffenderIntegrationTest { @Autowired lateinit var mockMvc: MockMvc @@ -336,7 +338,7 @@ internal class OffenderIntegrationTest { .andExpect(status().isEqualTo(HttpStatus.FORBIDDEN.value())) .andReturn().response.contentAsJson() - assertThat(resp.message, equalTo(PersonGenerator.EXCLUDED_CASE.exclusionMessage)) + assertThat(resp.developerMessage, equalTo(PersonGenerator.EXCLUDED_CASE.exclusionMessage)) } @Test @@ -353,7 +355,7 @@ internal class OffenderIntegrationTest { .andExpect(status().isEqualTo(HttpStatus.FORBIDDEN.value())) .andReturn().response.contentAsJson() - assertThat(resp.message, equalTo(PersonGenerator.EXCLUDED_CASE.exclusionMessage)) + assertThat(resp.developerMessage, equalTo(PersonGenerator.EXCLUDED_CASE.exclusionMessage)) } @Test diff --git a/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ProxyIntegrationTest.kt b/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ProxyIntegrationTest.kt index 18bdc5240b..e08de218d3 100644 --- a/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ProxyIntegrationTest.kt +++ b/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ProxyIntegrationTest.kt @@ -14,6 +14,7 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT import org.springframework.boot.test.mock.mockito.MockBean import org.springframework.boot.test.mock.mockito.SpyBean +import org.springframework.test.context.TestPropertySource import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post @@ -29,6 +30,7 @@ import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.contentAsJson import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.withToken @AutoConfigureMockMvc +@TestPropertySource(properties = ["lao-access.ignore-exclusions = false", "lao-access.ignore-restrictions = true"]) @SpringBootTest(webEnvironment = RANDOM_PORT) internal class ProxyIntegrationTest { @@ -233,6 +235,10 @@ internal class ProxyIntegrationTest { "convictionId": "?", "nsiCodes": "?" }, + "CONVICTION_BY_NSIS_ID": { + "convictionId": "?", + "nsiId": "?" + }, "CONVICTION_BY_ID_PSS": { "convictionId": "?" } @@ -243,7 +249,7 @@ internal class ProxyIntegrationTest { .withToken() ).andExpect(status().is2xxSuccessful).andReturn().response.contentAsJson() - assertThat(res.totalNumberOfRequests, equalTo(8)) + assertThat(res.totalNumberOfRequests, equalTo(9)) assertThat(res.totalNumberOfCrns, equalTo(2)) assertThat(res.currentPageNumber, equalTo(1)) } @@ -313,5 +319,3 @@ internal class ProxyIntegrationTest { assertThat(res.totalNumberOfRequests, equalTo(1)) } } - -//{"status":403,"developerMessage":"This is a restricted record. Please contact a system administrator"} \ No newline at end of file diff --git a/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/PssRequirementsByCrnAndEventIdIntegrationTest.kt b/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/PssRequirementsByCrnAndEventIdIntegrationTest.kt index 49bae73193..fc37612de7 100644 --- a/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/PssRequirementsByCrnAndEventIdIntegrationTest.kt +++ b/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/PssRequirementsByCrnAndEventIdIntegrationTest.kt @@ -39,7 +39,7 @@ internal class PssRequirementsByCrnAndEventIdIntegrationTest { mockMvc .perform(get("/probation-case/A123456/convictions/1/pssRequirements").withToken()) .andExpect(status().isNotFound) - .andExpect(jsonPath("$.message").value("Person with crn of A123456 not found")) + .andExpect(jsonPath("$.developerMessage").value("Person with crn of A123456 not found")) } @Test @@ -49,7 +49,7 @@ internal class PssRequirementsByCrnAndEventIdIntegrationTest { mockMvc .perform(get("/probation-case/$crn/convictions/3/pssRequirements").withToken()) .andExpect(status().isNotFound) - .andExpect(jsonPath("$.message").value("Conviction with convictionId 3 not found")) + .andExpect(jsonPath("$.developerMessage").value("Conviction with convictionId 3 not found")) } @Test diff --git a/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/RequirementsByEventIdIntegrationTest.kt b/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/RequirementsByEventIdIntegrationTest.kt index 3fa0797ebf..8e35c2d223 100644 --- a/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/RequirementsByEventIdIntegrationTest.kt +++ b/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/RequirementsByEventIdIntegrationTest.kt @@ -41,7 +41,7 @@ internal class RequirementsByEventIdIntegrationTest { mockMvc .perform(get("/probation-case/A123456/convictions/123/requirements").withToken()) .andExpect(status().isNotFound) - .andExpect(jsonPath("$.message").value("Person with crn of A123456 not found")) + .andExpect(jsonPath("$.developerMessage").value("Person with crn of A123456 not found")) } @Test @@ -51,7 +51,7 @@ internal class RequirementsByEventIdIntegrationTest { mockMvc .perform(get("/probation-case/$crn/convictions/3/requirements").withToken()) .andExpect(status().isNotFound) - .andExpect(jsonPath("$.message").value("Conviction with convictionId 3 not found")) + .andExpect(jsonPath("$.developerMessage").value("Conviction with convictionId 3 not found")) } @Test diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/proxy/CommunityApiController.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/proxy/CommunityApiController.kt index 0102da0221..fef9b57eaf 100644 --- a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/proxy/CommunityApiController.kt +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/proxy/CommunityApiController.kt @@ -15,7 +15,6 @@ import uk.gov.justice.digital.hmpps.api.resource.ProbationRecordResource import uk.gov.justice.digital.hmpps.flags.FeatureFlags import uk.gov.justice.digital.hmpps.telemetry.TelemetryService import java.util.concurrent.CompletableFuture -import java.util.stream.Collectors @RestController @PreAuthorize("hasRole('PROBATION_API__COURT_CASE__CASE_DETAIL')") @@ -151,6 +150,28 @@ class CommunityApiController( return proxy(request) } + @GetMapping("/offenders/crn/{crn}/convictions/{convictionId}/nsis/{nsiId}") + fun nsisByNisId( + request: HttpServletRequest, + @PathVariable crn: String, + @PathVariable convictionId: Long, + @PathVariable nsiId: Long, + ): Any { + + sendComparisonReport( + mapOf( + "crn" to crn, + "convictionId" to convictionId, + "nsiId" to nsiId + ), Uri.CONVICTION_BY_NSIS_ID, request + ) + + if (featureFlags.enabled("ccd-conviction-nsis-by-id-enabled")) { + return convictionResource.getNsiByNsiId(crn, convictionId, nsiId) + } + return proxy(request) + } + @GetMapping("/offenders/crn/{crn}/convictions/{convictionId}/pssRequirements") fun pssByCrnAndConvictionId( request: HttpServletRequest, @@ -198,9 +219,10 @@ class CommunityApiController( val reports = personList.content.flatMap { person -> val convictionId = person.events.filter { it.disposal != null }.maxOfOrNull { it.id } val nsiCodes = person.nsis.filter { it.eventId == convictionId }.map { it.type.code.trim() } - runAll(person.crn, convictionId, nsiCodes, compare, headers).stream() - .map(CompletableFuture::join) - .collect(Collectors.toList()) + val nsiId = person.nsis.firstOrNull()?.id + val futures = runAll(person.crn, convictionId, nsiCodes, nsiId, compare, headers).toTypedArray() + CompletableFuture.allOf(*futures).join() + futures.map { it.join() }.toList() } val executed = reports.filter { it.testExecuted == true } @@ -226,8 +248,11 @@ class CommunityApiController( ) } - fun setParams(map: Map, convictionId: Long?, nsiCodes: List): Map { + fun setParams(map: Map, convictionId: Long?, nsiCodes: List, nsiId: Long?): Map { val new = map.toMutableMap() + if (map.containsKey("nsiId") && nsiId != null) { + new["nsiId"] = nsiId + } if (map.containsKey("convictionId") && convictionId != null) { new["convictionId"] = convictionId } @@ -241,11 +266,12 @@ class CommunityApiController( crn: String, convictionId: Long?, nsiCodes: List, + nsiId: Long?, compare: CompareAll, headers: Map ): List> { return compare.uriConfig.entries.map { - val params = setParams(it.value, convictionId, nsiCodes) + val params = setParams(it.value, convictionId, nsiCodes, nsiId) CompletableFuture.supplyAsync( { communityApiService.compare( diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/proxy/CommunityApiService.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/proxy/CommunityApiService.kt index 43e14f9c0b..2c12338bd3 100644 --- a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/proxy/CommunityApiService.kt +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/proxy/CommunityApiService.kt @@ -12,10 +12,8 @@ import org.springframework.security.access.AccessDeniedException import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Propagation import org.springframework.transaction.annotation.Transactional -import org.springframework.web.bind.MethodArgumentNotValidException import org.springframework.web.client.HttpStatusCodeException -import uk.gov.justice.digital.hmpps.advice.ControllerAdvice -import uk.gov.justice.digital.hmpps.exception.InvalidRequestException +import uk.gov.justice.digital.hmpps.api.resource.advice.CommunityApiControllerAdvice import uk.gov.justice.digital.hmpps.exception.NotFoundException import java.io.StringReader import java.lang.reflect.InvocationTargetException @@ -28,7 +26,7 @@ class CommunityApiService( private val mapper: ObjectMapper, private val communityApiClient: CommunityApiClient, private val applicationContext: ApplicationContext, - private val controllerAdvice: ControllerAdvice + private val controllerAdvice: CommunityApiControllerAdvice ) { fun getCcdJson(compare: Compare): String { @@ -45,10 +43,8 @@ class CommunityApiService( function.callBy(params) } catch (ex: InvocationTargetException) { when (val cause = ex.cause) { - is AccessDeniedException -> controllerAdvice.handleAccessDenied(cause) - is NotFoundException -> controllerAdvice.handleNotFound(cause) - is InvalidRequestException -> controllerAdvice.handleInvalidRequest(cause) - is MethodArgumentNotValidException -> controllerAdvice.handleMethodArgumentNotValid(cause) + is AccessDeniedException -> controllerAdvice.handleAccessDenied(cause).body + is NotFoundException -> controllerAdvice.handleNotFound(cause).body else -> throw ex } } diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/proxy/Uri.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/proxy/Uri.kt index 4a384adbb3..eb7202a62b 100644 --- a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/proxy/Uri.kt +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/proxy/Uri.kt @@ -43,6 +43,12 @@ enum class Uri( "getNsisByCrnAndConvictionId", listOf("crn", "convictionId", "nsiCodes"), ), + CONVICTION_BY_NSIS_ID( + "/secure/offenders/crn/{crn}/convictions/{convictionId}/nsis/{nsiId}", + "convictionResource", + "getNsiByNsiId", + listOf("crn", "convictionId", "nsiId"), + ), CONVICTION_BY_ID_PSS( "/secure/offenders/crn/{crn}/convictions/{convictionId}/pssRequirements", "convictionResource", diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/ConvictionResource.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/ConvictionResource.kt index a6493e0c6b..c7fa9a5161 100644 --- a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/ConvictionResource.kt +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/ConvictionResource.kt @@ -57,6 +57,26 @@ class ConvictionResource( @NotEmpty @RequestParam(required = true) nsiCodes: List ) = interventionService.getNsiByCodes(crn, convictionId, nsiCodes) + @GetMapping("/{convictionId}/nsis/{nsiId}") + fun getNsiByNsiId( + @Parameter(name = "crn", description = "CRN for the offender", example = "A123456", required = true) + @PathVariable crn: String, + @Parameter( + name = "convictionId", + description = "ID for the conviction / event", + example = "2500295345", + required = true + ) + @PathVariable convictionId: Long, + @Parameter( + name = "nsiId", + description = "ID for the nsi", + example = "2500295123", + required = true + ) + @PathVariable nsiId: Long + ) = interventionService.getNsiByNsiId(crn, convictionId, nsiId) + @GetMapping("/{convictionId}/pssRequirements") fun getPssRequirementsByConvictionId( @PathVariable crn: String, diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/advice/ControllerAdvice.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/advice/ControllerAdvice.kt new file mode 100644 index 0000000000..ba58a09b84 --- /dev/null +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/advice/ControllerAdvice.kt @@ -0,0 +1,24 @@ +package uk.gov.justice.digital.hmpps.api.resource.advice + +import org.springframework.http.HttpStatus.FORBIDDEN +import org.springframework.http.HttpStatus.NOT_FOUND +import org.springframework.http.ResponseEntity +import org.springframework.security.access.AccessDeniedException +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.bind.annotation.RestControllerAdvice +import uk.gov.justice.digital.hmpps.api.resource.ConvictionResource +import uk.gov.justice.digital.hmpps.api.resource.ProbationRecordResource +import uk.gov.justice.digital.hmpps.exception.NotFoundException + +@RestControllerAdvice(basePackageClasses = [ProbationRecordResource::class, ConvictionResource::class]) +class CommunityApiControllerAdvice { + @ExceptionHandler(NotFoundException::class) + fun handleNotFound(e: NotFoundException) = ResponseEntity + .status(NOT_FOUND) + .body(ErrorResponse(status = NOT_FOUND.value(), developerMessage = e.message)) + + @ExceptionHandler(AccessDeniedException::class) + fun handleAccessDenied(e: AccessDeniedException) = ResponseEntity + .status(FORBIDDEN) + .body(ErrorResponse(status = FORBIDDEN.value(), developerMessage = e.message)) +} diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/advice/ErrorResponse.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/advice/ErrorResponse.kt new file mode 100644 index 0000000000..dc4e45fe1b --- /dev/null +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/advice/ErrorResponse.kt @@ -0,0 +1,6 @@ +package uk.gov.justice.digital.hmpps.api.resource.advice + +data class ErrorResponse( + val status: Int, + val developerMessage: String? = null +) diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/nsi/Nsi.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/nsi/Nsi.kt index b73cee3d6b..3c885f085f 100644 --- a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/nsi/Nsi.kt +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/nsi/Nsi.kt @@ -4,6 +4,7 @@ import jakarta.persistence.* import org.hibernate.annotations.Immutable import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Query +import uk.gov.justice.digital.hmpps.exception.NotFoundException import uk.gov.justice.digital.hmpps.integrations.delius.entity.ReferenceData import uk.gov.justice.digital.hmpps.integrations.delius.event.conviction.entity.Requirement import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.ProbationAreaEntity @@ -13,6 +14,7 @@ import java.time.LocalDate import java.time.LocalDateTime @Entity +@Immutable @Table(name = "nsi") class Nsi( @@ -170,6 +172,18 @@ interface NsiRepository : JpaRepository { ) fun findAllBreachNSIByEventId(eventId: Long): List + @Query( + """ + select nsi from Nsi nsi + where nsi.id = :nsiId and nsi.eventId = :eventId + """ + + ) + fun findByNsiId(nsiId: Long, eventId: Long): Nsi? + fun findByPersonIdAndEventIdAndTypeCodeIn(personId: Long, eventId: Long, codes: List): List } +fun NsiRepository.getByNsiId(nsiId: Long, eventId: Long) = + findByNsiId(nsiId, eventId) ?: throw NotFoundException("NSI with id $nsiId not found") + diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/InterventionService.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/InterventionService.kt index 2d0e9fb3bd..1a264d37c8 100644 --- a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/InterventionService.kt +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/InterventionService.kt @@ -5,6 +5,7 @@ import uk.gov.justice.digital.hmpps.api.model.* import uk.gov.justice.digital.hmpps.integrations.delius.event.entity.EventRepository import uk.gov.justice.digital.hmpps.integrations.delius.event.entity.getByPersonAndEventNumber import uk.gov.justice.digital.hmpps.integrations.delius.event.nsi.NsiRepository +import uk.gov.justice.digital.hmpps.integrations.delius.event.nsi.getByNsiId import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.PersonRepository import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.getPerson import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.Staff @@ -28,42 +29,48 @@ class InterventionService( .map { it.toNsi() }) } - fun NsiEntity.toNsi(): Nsi = - Nsi( - id, - KeyValue(type.code, type.description), - subType?.keyValueOf(), - outcome?.keyValueOf(), - requirement?.toRequirementModel(), - KeyValue(nsiStatus.code, nsiStatus.description), - statusDate, - actualStartDate, - expectedStartDate, - actualEndDate, - expectedEndDate, - referralDate, - length, - "Months", - managers.map { it.toNsiManager() }, - notes, - intendedProvider?.toProbationArea(false), - active, - softDeleted, - externalReference, - this.toRecallRejectedOrWithdrawn(), - this.toOutcomeRecall() - ) - - fun NsiManagerEntity.toNsiManager(): NsiManager = - NsiManager( - probationArea.toProbationArea(false), - team.toTeam(), - staff.toStaffDetails(), - startDate, - endDate - ) + fun getNsiByNsiId(crn: String, convictionId: Long, nsiId: Long): Nsi { + val person = personRepository.getPerson(crn) + val event = eventRepository.getByPersonAndEventNumber(person, convictionId) + return nsiRepository.getByNsiId(nsiId, event.id).toNsi() + } } +fun NsiManagerEntity.toNsiManager(): NsiManager = + NsiManager( + probationArea.toProbationArea(false), + team.toTeam(), + staff.toStaffDetails(), + startDate, + endDate + ) + +fun NsiEntity.toNsi(): Nsi = + Nsi( + id, + KeyValue(type.code, type.description), + subType?.keyValueOf(), + outcome?.keyValueOf(), + requirement?.toRequirementModel(), + KeyValue(nsiStatus.code, nsiStatus.description), + statusDate, + actualStartDate, + expectedStartDate, + actualEndDate, + expectedEndDate, + referralDate, + length, + "Months", + managers.map { it.toNsiManager() }, + notes, + intendedProvider?.toProbationArea(false), + active, + softDeleted, + externalReference, + this.toRecallRejectedOrWithdrawn(), + this.toOutcomeRecall() + ) + fun Staff.toStaffDetails(): StaffDetails = StaffDetails( user?.username, code, diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/OffenderManagerService.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/OffenderManagerService.kt index a5583a5477..8b20df8e5f 100644 --- a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/OffenderManagerService.kt +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/OffenderManagerService.kt @@ -44,7 +44,7 @@ class OffenderManagerService( .map { u -> ldapTemplate.findByUsername(u.username) } .ifPresent { staffDetails: LdapUser -> offenderManager.telephoneNumber = staffDetails.telephoneNumber - offenderManager.emailAddress = staffDetails.email + offenderManager.emailAddress = staffDetails.email?.takeIf { email -> email.isNotBlank() } } return offenderManager } diff --git a/projects/court-case-and-delius/src/main/resources/application.yml b/projects/court-case-and-delius/src/main/resources/application.yml index cc366c693f..ea87985870 100644 --- a/projects/court-case-and-delius/src/main/resources/application.yml +++ b/projects/court-case-and-delius/src/main/resources/application.yml @@ -48,7 +48,7 @@ management: info.productId: HMPPS518 # https://developer-portal.hmpps.service.justice.gov.uk/products/185 lao-access: - ignore-exclusions: false + ignore-exclusions: true ignore-restrictions: true --- diff --git a/projects/court-case-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/OffenderManagerServiceTest.kt b/projects/court-case-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/OffenderManagerServiceTest.kt new file mode 100644 index 0000000000..119613e043 --- /dev/null +++ b/projects/court-case-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/OffenderManagerServiceTest.kt @@ -0,0 +1,90 @@ +package uk.gov.justice.digital.hmpps.service + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.whenever +import org.springframework.ldap.core.LdapTemplate +import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator.PRISON_MANAGER +import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator.generatePersonManager +import uk.gov.justice.digital.hmpps.data.generator.StaffGenerator +import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.Person +import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.PersonRepository +import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.LdapUser +import uk.gov.justice.digital.hmpps.integrations.delius.service.OffenderManagerService +import javax.naming.ldap.LdapName + +@ExtendWith(MockitoExtension::class) +class OffenderManagerServiceTest { + + @Mock + lateinit var personRepository: PersonRepository + + @Mock + lateinit var ldapTemplate: LdapTemplate + + @Mock + lateinit var person: Person + + private lateinit var offenderManagerService: OffenderManagerService + + @BeforeEach + fun setUp() { + offenderManagerService = OffenderManagerService(personRepository, ldapTemplate) + val staff = StaffGenerator.generate("N01ABBC", user = StaffGenerator.STAFF_USER) + whenever(person.offenderManagers).thenReturn(listOf(generatePersonManager(person, staff))) + whenever(person.prisonOffenderManagers).thenReturn(listOf(PRISON_MANAGER)) + whenever(personRepository.findByCrn(any())).thenReturn(person) + } + + @Test + fun `email is null when empty string `() { + val ldapUser = LdapUser( + dn = LdapName("cn=test"), + email = "", + forename = "", + surname = "", + telephoneNumber = "", + username = "TestUser" + ) + whenever(ldapTemplate.find(any(), eq(LdapUser::class.java))).thenReturn(listOf(ldapUser)) + val ret = offenderManagerService.getAllOffenderManagersForCrn("CRN", false) + assertEquals(null, ret[0].staff?.email) + } + + @Test + fun `email is null when null `() { + val ldapUser = LdapUser( + dn = LdapName("cn=test"), + email = null, + forename = "", + surname = "", + telephoneNumber = "", + username = "TestUser" + ) + whenever(ldapTemplate.find(any(), eq(LdapUser::class.java))).thenReturn(listOf(ldapUser)) + val ret = offenderManagerService.getAllOffenderManagersForCrn("CRN", false) + assertEquals(null, ret[0].staff?.email) + } + + @Test + fun `email is populated `() { + val ldapUser = LdapUser( + dn = LdapName("cn=test"), + email = "test", + forename = "", + surname = "", + telephoneNumber = "", + username = "TestUser" + ) + whenever(ldapTemplate.find(any(), eq(LdapUser::class.java))).thenReturn(listOf(ldapUser)) + val ret = offenderManagerService.getAllOffenderManagersForCrn("CRN", false) + assertEquals("test", ret[0].staff?.email) + } +} +