From 80e4e3af72e92685f0f1e94d961bce6df607b981 Mon Sep 17 00:00:00 2001 From: pmcphee77 <150798161+pmcphee77@users.noreply.github.com> Date: Tue, 6 Aug 2024 12:47:40 +0100 Subject: [PATCH] PI-2098-attendances-filter (#4130) * PI-2098-attendances-filter * PI-2098: Attendances filter --- .../justice/digital/hmpps/data/DataLoader.kt | 7 ++ .../hmpps/data/generator/ContactGenerator.kt | 48 +++++++++ .../__files/get_attendances_C123456.json | 14 +++ .../simulations/mappings/get-convictions.json | 13 +++ ...onvictionByCrnAndEventIdIntegrationTest.kt | 18 ++++ .../digital/hmpps/ProxyIntegrationTest.kt | 6 +- .../digital/hmpps/api/model/Attendances.kt | 21 ++++ .../hmpps/api/proxy/CommunityApiController.kt | 20 ++++ .../justice/digital/hmpps/api/proxy/Uri.kt | 6 ++ .../hmpps/api/resource/ConvictionResource.kt | 8 ++ .../delius/contact/entity/Attendance.kt | 99 +++++++++++++++++++ .../delius/service/AttendanceService.kt | 34 +++++++ 12 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ContactGenerator.kt create mode 100644 projects/court-case-and-delius/src/dev/resources/simulations/__files/get_attendances_C123456.json create mode 100644 projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/Attendances.kt create mode 100644 projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/contact/entity/Attendance.kt create mode 100644 projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/AttendanceService.kt diff --git a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt index cf5f00ce59..f7e0745a42 100644 --- a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt +++ b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt @@ -247,6 +247,13 @@ class DataLoader( em.persist(PersonGenerator.PRISON_MANAGER) em.persist(PersonGenerator.RESPONSIBLE_OFFICER) + + em.saveAll( + ContactGenerator.ATTENDANCE_OUTCOME, + ContactGenerator.ATTENDANCE_CONTACT_TYPE, + ContactGenerator.ATTENDANCE_CONTACT_1, + ContactGenerator.ATTENDANCE_CONTACT_2 + ) } } diff --git a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ContactGenerator.kt b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ContactGenerator.kt new file mode 100644 index 0000000000..3a7de36d46 --- /dev/null +++ b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ContactGenerator.kt @@ -0,0 +1,48 @@ +package uk.gov.justice.digital.hmpps.data.generator + +import uk.gov.justice.digital.hmpps.integrations.delius.contact.entity.AttendanceOutcome +import uk.gov.justice.digital.hmpps.integrations.delius.contact.entity.Contact +import uk.gov.justice.digital.hmpps.integrations.delius.contact.entity.ContactType +import java.time.LocalDate + +object ContactGenerator { + val ATTENDANCE_CONTACT_TYPE = ContactType( + IdGenerator.getAndIncrement(), + "C295", + "Attendance Type", + true, + true + ) + + val ATTENDANCE_OUTCOME = AttendanceOutcome( + IdGenerator.getAndIncrement(), + "Attendance Outcome" + ) + + val ATTENDANCE_CONTACT_1 = generateAttendanceContact( + PersonGenerator.CURRENTLY_MANAGED.id, + SentenceGenerator.CURRENTLY_MANAGED.id, + enforcementContact = true + ) + val ATTENDANCE_CONTACT_2 = generateAttendanceContact( + PersonGenerator.CURRENTLY_MANAGED.id, + SentenceGenerator.CURRENTLY_MANAGED.id, + enforcementContact = false, + outcome = ATTENDANCE_OUTCOME + ) + + fun generateAttendanceContact( + personId: Long, + eventId: Long?, + outcome: AttendanceOutcome? = null, + enforcementContact: Boolean? = null + ) = Contact( + id = IdGenerator.getAndIncrement(), + type = ATTENDANCE_CONTACT_TYPE, + outcome = outcome, + date = LocalDate.now().minusDays(1), + offenderId = personId, + eventId = eventId, + enforcementContact = enforcementContact + ) +} diff --git a/projects/court-case-and-delius/src/dev/resources/simulations/__files/get_attendances_C123456.json b/projects/court-case-and-delius/src/dev/resources/simulations/__files/get_attendances_C123456.json new file mode 100644 index 0000000000..e8c100066d --- /dev/null +++ b/projects/court-case-and-delius/src/dev/resources/simulations/__files/get_attendances_C123456.json @@ -0,0 +1,14 @@ +{ + "attendances": [ + { + "attended": false, + "complied": false, + "attendanceDate": "2024-08-05", + "contactId": 145, + "contactType": { + "code": "C295", + "description": "Attendance Type" + } + } + ] +} \ 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 726e79936a..f84fd920d6 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 @@ -1,5 +1,18 @@ { "mappings": [ + { + "request": { + "method": "GET", + "urlPathTemplate": "/secure/offenders/crn/{crn}/convictions/{convictionId}/attendancesFilter" + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "bodyFileName": "get_attendances_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 4b5d9608ac..1443a0657d 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 @@ -1,5 +1,7 @@ package uk.gov.justice.digital.hmpps +import org.hamcrest.MatcherAssert.assertThat +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 @@ -11,9 +13,11 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print 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.Attendances import uk.gov.justice.digital.hmpps.api.model.KeyValue import uk.gov.justice.digital.hmpps.api.model.conviction.* import uk.gov.justice.digital.hmpps.data.generator.AdditionalSentenceGenerator.SENTENCE_DISQ +import uk.gov.justice.digital.hmpps.data.generator.ContactGenerator.ATTENDANCE_CONTACT_1 import uk.gov.justice.digital.hmpps.data.generator.CourtGenerator.BHAM import uk.gov.justice.digital.hmpps.data.generator.CourtGenerator.PROBATION_AREA import uk.gov.justice.digital.hmpps.data.generator.DisposalTypeGenerator.CURFEW_ORDER @@ -28,6 +32,7 @@ import uk.gov.justice.digital.hmpps.data.generator.SentenceGenerator.CURRENT_SEN import uk.gov.justice.digital.hmpps.data.generator.SentenceGenerator.MAIN_OFFENCE import uk.gov.justice.digital.hmpps.data.generator.StaffGenerator.ALLOCATED import uk.gov.justice.digital.hmpps.data.generator.UnpaidWorkGenerator.UNPAID_WORK_DETAILS_1 +import uk.gov.justice.digital.hmpps.integrations.delius.service.toAttendance import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.contentAsJson import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.withToken import java.time.LocalDate @@ -253,4 +258,17 @@ internal class ConvictionByCrnAndEventIdIntegrationTest { assertEquals(expectedResponse.convictionId, response.convictionId) } + + @Test + fun `call convictions by id and attendancesFilter`() { + val crn = PersonGenerator.CURRENTLY_MANAGED.crn + val event = SentenceGenerator.CURRENTLY_MANAGED + + val response = mockMvc + .perform(get("/probation-case/$crn/convictions/${event.id}/attendancesFilter").withToken()) + .andExpect(status().isOk) + .andReturn().response.contentAsJson() + + assertThat(response.attendances[0], equalTo(ATTENDANCE_CONTACT_1.toAttendance())) + } } \ No newline at end of file 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 e08de218d3..bd5136e9c7 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 @@ -231,6 +231,10 @@ internal class ProxyIntegrationTest { "activeOnly": true, "excludeSoftDeleted": true }, + "CONVICTION_BY_ID_ATTENDANCES": { + "convictionId": "?", + "activeOnly": true + }, "CONVICTION_BY_ID_NSIS": { "convictionId": "?", "nsiCodes": "?" @@ -249,7 +253,7 @@ internal class ProxyIntegrationTest { .withToken() ).andExpect(status().is2xxSuccessful).andReturn().response.contentAsJson() - assertThat(res.totalNumberOfRequests, equalTo(9)) + assertThat(res.totalNumberOfRequests, equalTo(10)) assertThat(res.totalNumberOfCrns, equalTo(2)) assertThat(res.currentPageNumber, equalTo(1)) } diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/Attendances.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/Attendances.kt new file mode 100644 index 0000000000..94ddafbb9b --- /dev/null +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/Attendances.kt @@ -0,0 +1,21 @@ +package uk.gov.justice.digital.hmpps.api.model + +import java.time.LocalDate + +data class Attendances( + val attendances: List = emptyList() +) + +data class Attendance( + val attended: Boolean, + val complied: Boolean, + val attendanceDate: LocalDate, + val contactId: Long, + val outcome: String?, + val contactType: ContactTypeDetail +) + +data class ContactTypeDetail( + val code: String?, + val description: String? +) \ No newline at end of file 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 fef9b57eaf..e1c6c2a26f 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 @@ -150,6 +150,26 @@ class CommunityApiController( return proxy(request) } + @GetMapping("/offenders/crn/{crn}/convictions/{convictionId}/attendancesFilter") + fun convictionByIdAttendances( + request: HttpServletRequest, + @PathVariable crn: String, + @PathVariable convictionId: Long, + ): Any { + + sendComparisonReport( + mapOf( + "crn" to crn, + "convictionId" to convictionId + ), Uri.CONVICTION_BY_ID_ATTENDANCES, request + ) + + if (featureFlags.enabled("ccd-conviction-by-id-attendances")) { + return convictionResource.getConvictionAttendances(crn, convictionId) + } + return proxy(request) + } + @GetMapping("/offenders/crn/{crn}/convictions/{convictionId}/nsis/{nsiId}") fun nsisByNisId( request: HttpServletRequest, 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 eb7202a62b..2a666f8231 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_ID_ATTENDANCES( + "/secure/offenders/crn/{crn}/convictions/{convictionId}/attendancesFilter", + "convictionResource", + "getConvictionAttendances", + listOf("crn", "convictionId"), + ), CONVICTION_BY_NSIS_ID( "/secure/offenders/crn/{crn}/convictions/{convictionId}/nsis/{nsiId}", "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 c7fa9a5161..7cff421e2e 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 @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.Parameter import jakarta.validation.constraints.NotEmpty import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.* +import uk.gov.justice.digital.hmpps.integrations.delius.service.AttendanceService import uk.gov.justice.digital.hmpps.integrations.delius.service.ConvictionService import uk.gov.justice.digital.hmpps.integrations.delius.service.InterventionService import uk.gov.justice.digital.hmpps.integrations.delius.service.RequirementService @@ -15,6 +16,7 @@ class ConvictionResource( private val convictionService: ConvictionService, private val requirementService: RequirementService, private val interventionService: InterventionService, + private val attendanceService: AttendanceService ) { @GetMapping @@ -82,4 +84,10 @@ class ConvictionResource( @PathVariable crn: String, @PathVariable convictionId: Long ) = requirementService.getPssRequirementsByConvictionId(crn, convictionId) + + @GetMapping("/{convictionId}/attendancesFilter") + fun getConvictionAttendances( + @PathVariable crn: String, + @PathVariable convictionId: Long + ) = attendanceService.getAttendancesFor(crn, convictionId) } diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/contact/entity/Attendance.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/contact/entity/Attendance.kt new file mode 100644 index 0000000000..39f5e1f6b9 --- /dev/null +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/contact/entity/Attendance.kt @@ -0,0 +1,99 @@ +package uk.gov.justice.digital.hmpps.integrations.delius.contact.entity + +import jakarta.persistence.* +import org.hibernate.annotations.Immutable +import org.hibernate.type.YesNoConverter +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import java.time.LocalDate + +@Immutable +@Entity +@Table(name = "contact") +class Contact( + @Id + @Column(name = "contact_id") + val id: Long = 0, + + @Column(updatable = false) + val offenderId: Long, + + @ManyToOne + @JoinColumn(name = "contact_type_id") + val type: ContactType? = null, + + @Column(name = "contact_date") + val date: LocalDate, + + @Column(name = "enforcement", columnDefinition = "number") + val enforcementContact: Boolean? = null, + + @Column(name = "event_id") + val eventId: Long? = null, + + @Column(name = "attended") + @Convert(converter = YesNoConverter::class) + val attended: Boolean? = null, + + @Column(name = "complied") + @Convert(converter = YesNoConverter::class) + val complied: Boolean? = null, + + @ManyToOne + @JoinColumn(name = "contact_outcome_type_id") + val outcome: AttendanceOutcome? = null, + + @Column(columnDefinition = "NUMBER") + val softDeleted: Boolean = false +) + +@Immutable +@Entity +@Table(name = "r_contact_outcome_type") +class AttendanceOutcome( + @Id + @Column(name = "contact_outcome_type_id") + val id: Long, + + @Column + val description: String +) + +@Immutable +@Entity +@Table(name = "r_contact_type") +class ContactType( + @Id + @Column(name = "contact_type_id") + val id: Long, + + val code: String, + + val description: String, + + @Column(name = "national_standards_contact", length = 1) + @Convert(converter = YesNoConverter::class) + val nationalStandards: Boolean, + + @Column(name = "attendance_contact") + @Convert(converter = YesNoConverter::class) + val attendanceContact: Boolean = false, +) + +interface AttendanceRepository : JpaRepository { + + @Query( + """ + SELECT contact FROM Contact contact + LEFT OUTER JOIN AttendanceOutcome cot ON cot = contact.outcome + WHERE contact.offenderId = :personId + AND contact.eventId = :eventId + AND contact.date <= :contactDate + AND (contact.enforcementContact = true OR contact.outcome != null) + AND contact.type.attendanceContact = true + AND contact.type.nationalStandards = true + """ + ) + fun findByOffenderAndEventId(eventId: Long, personId: Long, contactDate: LocalDate): List +} + diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/AttendanceService.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/AttendanceService.kt new file mode 100644 index 0000000000..6f8028afe1 --- /dev/null +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/AttendanceService.kt @@ -0,0 +1,34 @@ +package uk.gov.justice.digital.hmpps.integrations.delius.service + +import org.springframework.stereotype.Service +import uk.gov.justice.digital.hmpps.api.model.Attendance +import uk.gov.justice.digital.hmpps.api.model.Attendances +import uk.gov.justice.digital.hmpps.api.model.ContactTypeDetail +import uk.gov.justice.digital.hmpps.integrations.delius.contact.entity.AttendanceRepository +import uk.gov.justice.digital.hmpps.integrations.delius.contact.entity.Contact +import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.PersonRepository +import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.getPerson +import java.time.LocalDate + +@Service +class AttendanceService( + private val personRepository: PersonRepository, + private val attendanceRepository: AttendanceRepository +) { + + fun getAttendancesFor(crn: String, eventId: Long): Attendances { + val person = personRepository.getPerson(crn) + val attendances = + attendanceRepository.findByOffenderAndEventId(eventId, person.id, LocalDate.now()).map { it.toAttendance() } + return Attendances(attendances) + } +} + +fun Contact.toAttendance() = Attendance( + attended = attended ?: false, + complied = complied ?: false, + attendanceDate = date, + contactId = id, + outcome = outcome?.description, + contactType = ContactTypeDetail(code = type?.code, description = type?.description) +)