diff --git a/projects/manage-supervision-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ContactIntegrationTest.kt b/projects/manage-supervision-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ContactIntegrationTest.kt index 533014b672..83cbb76e08 100644 --- a/projects/manage-supervision-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ContactIntegrationTest.kt +++ b/projects/manage-supervision-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ContactIntegrationTest.kt @@ -85,7 +85,7 @@ class ContactIntegrationTest { val contact2 = Contact("Bruce Wayne", null, null, "Description of N01", "Leicestershire All", "OMU B", LocalDate.now()) - val expected = ProfessionalContact(name, listOf(contact1, contact2)) + val expected = ProfessionalContact(name, listOf(contact2, contact1)) val response = mockMvc .perform( diff --git a/projects/manage-supervision-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/UserIntegrationTest.kt b/projects/manage-supervision-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/UserIntegrationTest.kt index 60d15b285e..ef4da26d9a 100644 --- a/projects/manage-supervision-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/UserIntegrationTest.kt +++ b/projects/manage-supervision-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/UserIntegrationTest.kt @@ -128,7 +128,7 @@ internal class UserIntegrationTest { val res = mockMvc .perform( post("/caseload/user/${user.username}/search").withToken() - .withJson(UserSearchFilter(nameOrCrn = null, nextContact = null, sentence = null)) + .withJson(UserSearchFilter(nameOrCrn = null, nextContactCode = null, sentenceCode = null)) ) .andExpect(status().isOk) .andReturn().response.contentAsJson() @@ -143,7 +143,7 @@ internal class UserIntegrationTest { val res = mockMvc .perform( post("/caseload/user/${user.username}/search").withToken() - .withJson(UserSearchFilter(nameOrCrn = "Blog", nextContact = null, sentence = null)) + .withJson(UserSearchFilter(nameOrCrn = "Blog", nextContactCode = null, sentenceCode = null)) ) .andExpect(status().isOk) .andReturn().response.contentAsJson() @@ -159,7 +159,13 @@ internal class UserIntegrationTest { val res = mockMvc .perform( post("/caseload/user/${user.username}/search").withToken() - .withJson(UserSearchFilter(nameOrCrn = "Caroline Blog", nextContact = null, sentence = null)) + .withJson( + UserSearchFilter( + nameOrCrn = "Caroline Blog", + nextContactCode = null, + sentenceCode = null + ) + ) ) .andExpect(status().isOk) .andReturn().response.contentAsJson() @@ -175,7 +181,7 @@ internal class UserIntegrationTest { val res = mockMvc .perform( post("/caseload/user/${user.username}/search").withToken() - .withJson(UserSearchFilter(nameOrCrn = null, nextContact = null, sentence = "Murder")) + .withJson(UserSearchFilter(nameOrCrn = null, nextContactCode = null, sentenceCode = "MAIN")) ) .andExpect(status().isOk) .andReturn().response.contentAsJson() @@ -192,7 +198,7 @@ internal class UserIntegrationTest { val res = mockMvc .perform( post("/caseload/user/${user.username}/search").withToken() - .withJson(UserSearchFilter(nameOrCrn = null, nextContact = "doorstep", sentence = null)) + .withJson(UserSearchFilter(nameOrCrn = null, nextContactCode = "CODI", sentenceCode = null)) ) .andExpect(status().isOk) .andReturn().response.contentAsJson() @@ -203,37 +209,37 @@ internal class UserIntegrationTest { } @Test - fun `caseload search returns null sentence type first case when sentence type is in the sort criteria as descending`() { + fun `caseload search returns null sentence type as last case when sentence type is in the sort criteria as descending`() { val user = USER val res = mockMvc .perform( post("/caseload/user/${user.username}/search?sortBy=sentence.desc").withToken() - .withJson(UserSearchFilter(nameOrCrn = null, nextContact = null, sentence = null)) + .withJson(UserSearchFilter(nameOrCrn = null, nextContactCode = null, sentenceCode = null)) ) .andExpect(status().isOk) .andReturn().response.contentAsJson() assertThat(res.caseload.size, equalTo(2)) - assertThat(res.caseload[0].crn, equalTo("X000005")) - assertThat(res.caseload[0].latestSentence, equalTo(null)) + assertThat(res.caseload[1].crn, equalTo("X000005")) + assertThat(res.caseload[1].latestSentence, equalTo(null)) } @Test - fun `caseload search returns null next appointment first case when next contact is in the sort criteria as descending`() { + fun `caseload search returns null next appointment as the last case when next contact is in the sort criteria as descending`() { val user = USER val res = mockMvc .perform( post("/caseload/user/${user.username}/search?sortBy=nextContact.desc").withToken() - .withJson(UserSearchFilter(nameOrCrn = null, nextContact = null, sentence = null)) + .withJson(UserSearchFilter(nameOrCrn = null, nextContactCode = null, sentenceCode = null)) ) .andExpect(status().isOk) .andReturn().response.contentAsJson() assertThat(res.caseload.size, equalTo(2)) - assertThat(res.caseload[0].crn, equalTo("X000005")) - assertThat(res.caseload[0].nextAppointment, equalTo(null)) + assertThat(res.caseload[1].crn, equalTo("X000005")) + assertThat(res.caseload[1].nextAppointment, equalTo(null)) } @Test @@ -243,7 +249,7 @@ internal class UserIntegrationTest { val res = mockMvc .perform( post("/caseload/user/${user.username}/search?sortBy=surname.asc").withToken() - .withJson(UserSearchFilter(nameOrCrn = null, nextContact = null, sentence = null)) + .withJson(UserSearchFilter(nameOrCrn = null, nextContactCode = null, sentenceCode = null)) ) .andExpect(status().isOk) .andReturn().response.contentAsJson() @@ -260,7 +266,7 @@ internal class UserIntegrationTest { val res = mockMvc .perform( post("/caseload/user/${user.username}/search?sortBy=surname.desc").withToken() - .withJson(UserSearchFilter(nameOrCrn = null, nextContact = null, sentence = null)) + .withJson(UserSearchFilter(nameOrCrn = null, nextContactCode = null, sentenceCode = null)) ) .andExpect(status().isOk) .andReturn().response.contentAsJson() @@ -268,6 +274,8 @@ internal class UserIntegrationTest { assertThat(res.caseload.size, equalTo(2)) assertThat(res.caseload[0].crn, equalTo("X000004")) assertThat(res.caseload[0].caseName.surname, equalTo("Surname")) + assertThat(res.metaData?.contactTypes?.size, equalTo(3)) + assertThat(res.metaData?.sentenceTypes?.size, equalTo(2)) } @Test @@ -277,7 +285,7 @@ internal class UserIntegrationTest { val res = mockMvc .perform( post("/caseload/user/${user.username}/search?sortBy=surname.desc.ssss").withToken() - .withJson(UserSearchFilter(nameOrCrn = null, nextContact = null, sentence = null)) + .withJson(UserSearchFilter(nameOrCrn = null, nextContactCode = null, sentenceCode = null)) ) .andExpect(status().isBadRequest) .andReturn().response.contentAsJson() @@ -291,7 +299,7 @@ internal class UserIntegrationTest { val res = mockMvc .perform( post("/caseload/user/${user.username}/search?sortBy=sausages.desc").withToken() - .withJson(UserSearchFilter(nameOrCrn = null, nextContact = null, sentence = null)) + .withJson(UserSearchFilter(nameOrCrn = null, nextContactCode = null, sentenceCode = null)) ) .andExpect(status().isBadRequest) .andReturn().response.contentAsJson() @@ -303,7 +311,7 @@ internal class UserIntegrationTest { mockMvc .perform( post("/caseload/user/NOT_KNOWN/search?sortBy=sentence.desc").withToken() - .withJson(UserSearchFilter(nameOrCrn = null, nextContact = null, sentence = null)) + .withJson(UserSearchFilter(nameOrCrn = null, nextContactCode = null, sentenceCode = null)) ) .andExpect(status().isNotFound) } diff --git a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/controller/CaseloadController.kt b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/controller/CaseloadController.kt index 7e1143b712..4e69e1db2f 100644 --- a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/controller/CaseloadController.kt +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/controller/CaseloadController.kt @@ -33,7 +33,7 @@ class CaseloadController(private val userService: UserService) { @RequestParam(required = false, defaultValue = "100") size: Int, @RequestParam(required = false, defaultValue = "nextContact.desc") sortBy: String, @RequestBody body: UserSearchFilter - ) = userService.searchUserCaseload(username, body, PageRequest.of(page, size, sort(sortBy))) + ) = userService.searchUserCaseload(username, body, PageRequest.of(page, size, sort(sortBy)), sortBy) @GetMapping("/user/{username}/teams") @Operation(summary = "Gets the users teams") diff --git a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/user/StaffCaseload.kt b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/user/StaffCaseload.kt index 2cae43e158..ff4b8ab266 100644 --- a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/user/StaffCaseload.kt +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/user/StaffCaseload.kt @@ -5,7 +5,19 @@ import uk.gov.justice.digital.hmpps.api.model.Name data class StaffCaseload( val totalPages: Int, val totalElements: Int, + val sortedBy: String? = null, val provider: String?, val staff: Name, - val caseload: List + val caseload: List, + val metaData: MetaData? = null +) + +data class MetaData( + val sentenceTypes: List, + val contactTypes: List +) + +data class KeyPair( + val code: String, + val description: String ) diff --git a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/user/UserSearchFilter.kt b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/user/UserSearchFilter.kt index d4fffd34ad..7d801fff13 100644 --- a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/user/UserSearchFilter.kt +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/user/UserSearchFilter.kt @@ -2,6 +2,6 @@ package uk.gov.justice.digital.hmpps.api.model.user data class UserSearchFilter( val nameOrCrn: String? = null, - val sentence: String? = null, - val nextContact: String? = null + val sentenceCode: String? = null, + val nextContactCode: String? = null ) diff --git a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/user/entity/User.kt b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/user/entity/User.kt index 2cdf64936d..c6e9b7ec35 100644 --- a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/user/entity/User.kt +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/user/entity/User.kt @@ -12,6 +12,7 @@ import org.springframework.data.jpa.repository.Query import uk.gov.justice.digital.hmpps.exception.NotFoundException import uk.gov.justice.digital.hmpps.integrations.delius.overview.entity.ContactType import uk.gov.justice.digital.hmpps.integrations.delius.overview.entity.MainOffence +import uk.gov.justice.digital.hmpps.integrations.delius.overview.entity.Offence import java.time.LocalDate import java.time.ZonedDateTime @@ -403,15 +404,15 @@ interface CaseloadRepository : JpaRepository { or upper(p.forename || ' ' || p.surname) like '%' || upper(:nameOrCrn) || '%' or upper(p.surname || ' ' || p.forename) like '%' || upper(:nameOrCrn) || '%' or upper(p.surname || ', ' || p.forename) like '%' || upper(:nameOrCrn) || '%') - and (:nextContact is null or (upper(naType.description) like '%' || upper(:nextContact) || '%')) - and (:sentence is null or (upper(moo.description) like '%' || upper(:sentence) || '%')) + and (:nextContactCode is null or (upper(trim(naType.code)) = upper(trim(:nextContactCode)))) + and (:sentenceCode is null or (upper(trim(moo.code)) = upper(trim(:sentenceCode)))) """ ) fun searchByStaffCode( staffCode: String, nameOrCrn: String?, - nextContact: String?, - sentence: String?, + nextContactCode: String?, + sentenceCode: String?, pageable: Pageable ): Page @@ -424,6 +425,25 @@ interface CaseloadRepository : JpaRepository { """ ) fun findByTeamCode(teamCode: String, pageable: Pageable): Page + + @Query( + """ + select distinct cont.type from Caseload c + join Contact cont on cont.personId = c.person.id + where c.staff.code = :staffCode and cont.type.attendanceContact = true + """ + ) + fun findContactTypesForStaff(staffCode: String): List + + @Query( + """ + select distinct e.mainOffence.offence from Caseload c + join Event e on e.personId = c.person.id and e.active = true and e.softDeleted = false + where e.mainOffence.offence is not null + and c.staff.code = :staffCode + """ + ) + fun findOffenceTypesForStaff(staffCode: String): List } enum class CaseloadOrderType(val sortColumn: String) { diff --git a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/UserService.kt b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/UserService.kt index ee3e7e82d1..4bef434bf6 100644 --- a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/UserService.kt +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/UserService.kt @@ -32,21 +32,33 @@ class UserService( } @Transactional - fun searchUserCaseload(username: String, searchFilter: UserSearchFilter, pageable: Pageable): StaffCaseload { + fun searchUserCaseload( + username: String, + searchFilter: UserSearchFilter, + pageable: Pageable, + sortedBy: String + ): StaffCaseload { val user = userRepository.getUser(username) val caseload = caseloadRepository.searchByStaffCode( user.staff!!.code, searchFilter.nameOrCrn, - searchFilter.nextContact, - searchFilter.sentence, + searchFilter.nextContactCode, + searchFilter.sentenceCode, pageable ) + val sentenceTypes = + caseloadRepository.findOffenceTypesForStaff(user.staff.code).map { KeyPair(it.code.trim(), it.description) } + val contactTypes = + caseloadRepository.findContactTypesForStaff(user.staff.code).map { KeyPair(it.code.trim(), it.description) } + return StaffCaseload( totalElements = caseload.totalElements.toInt(), totalPages = caseload.totalPages, provider = user.staff.provider.description, caseload = caseload.content.map { it.toStaffCase() }, staff = Name(forename = user.staff.forename, surname = user.staff.surname), + metaData = MetaData(sentenceTypes = sentenceTypes, contactTypes = contactTypes), + sortedBy = sortedBy ) } diff --git a/projects/manage-supervision-and-delius/src/main/resources/application.yml b/projects/manage-supervision-and-delius/src/main/resources/application.yml index f9a75eaacd..467c3ee613 100644 --- a/projects/manage-supervision-and-delius/src/main/resources/application.yml +++ b/projects/manage-supervision-and-delius/src/main/resources/application.yml @@ -1,5 +1,6 @@ # Default config server.shutdown: graceful + spring: jackson: default-property-inclusion: non_null @@ -8,6 +9,8 @@ spring: database-platform: org.hibernate.dialect.OracleDialect properties: hibernate: + order_by: + default_null_ordering: last timezone.default_storage: NORMALIZE query.mutation_strategy: org.hibernate.query.sqm.mutation.internal.inline.InlineMutationStrategy query.mutation_strategy.persistent: