diff --git a/projects/manage-supervision-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt b/projects/manage-supervision-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt index 7a84788363..e8ef5f45a4 100644 --- a/projects/manage-supervision-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt +++ b/projects/manage-supervision-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt @@ -10,20 +10,42 @@ import java.time.ZonedDateTime object PersonGenerator { val OVERVIEW = generateOverview("X000004") - val EVENT_1 = generateEvent(OVERVIEW, eventNumber = "7654321") - val EVENT_2 = generateEvent(OVERVIEW, eventNumber = "1234567", inBreach = true) - val INACTIVE_EVENT_1 = generateEvent(OVERVIEW, eventNumber = "654321", inBreach = true, active = false) - val INACTIVE_EVENT_2 = generateEvent(OVERVIEW, eventNumber = "854321", inBreach = true, active = false) + val EVENT_1 = generateEvent(OVERVIEW, eventNumber = "7654321", notes = "overview", additionalOffences = emptyList()) + val EVENT_2 = generateEvent( + OVERVIEW, + eventNumber = "1234567", + inBreach = true, + notes = "overview", + additionalOffences = emptyList() + ) + val INACTIVE_EVENT_1 = generateEvent( + OVERVIEW, + eventNumber = "654321", + inBreach = true, + active = false, + notes = "inactive", + additionalOffences = emptyList() + ) + val INACTIVE_EVENT_2 = generateEvent( + OVERVIEW, + eventNumber = "854321", + inBreach = true, + active = false, + notes = "inactive", + additionalOffences = emptyList() + ) val OFFENCE_1 = generateOffence("Murder", "MAIN") val OFFENCE_2 = generateOffence("Another Murder", "MAINA") val MAIN_OFFENCE_1 = generateMainOffence( + 1, EVENT_1, OFFENCE_1, LocalDate.now() ) val MAIN_OFFENCE_2 = generateMainOffence( + 1, EVENT_2, OFFENCE_2, LocalDate.now() @@ -37,6 +59,7 @@ object PersonGenerator { val ADD_OFF_1 = generateOffence("Burglary", "ADD1") val ADDITIONAL_OFFENCE_1 = generateAdditionalOffence( + 1, EVENT_1, ADD_OFF_1, LocalDate.now() @@ -44,6 +67,7 @@ object PersonGenerator { val ADD_OFF_2 = generateOffence("Assault", "ADD2") val ADDITIONAL_OFFENCE_2 = generateAdditionalOffence( + 1, EVENT_1, ADD_OFF_2, LocalDate.now() @@ -82,7 +106,9 @@ object PersonGenerator { active: Boolean = true, inBreach: Boolean = false, disposal: Disposal? = null, - mainOffence: MainOffence? = null + mainOffence: MainOffence? = null, + notes: String, + additionalOffences: List ) = Event( id, @@ -91,7 +117,9 @@ object PersonGenerator { disposal = disposal, inBreach = inBreach, active = active, - mainOffence = mainOffence + mainOffence = mainOffence, + notes = notes, + additionalOffences = additionalOffences ) fun generateOverview( @@ -208,20 +236,22 @@ object PersonGenerator { ) = Offence(id, code, description) fun generateMainOffence( + offenceCount: Long, event: Event, offence: Offence, date: LocalDate, id: Long = IdGenerator.getAndIncrement(), softDeleted: Boolean = false - ) = MainOffence(id, event, date, offence, softDeleted) + ) = MainOffence(id, offenceCount, event, date, offence, softDeleted) fun generateAdditionalOffence( + offenceCount: Long, event: Event, offence: Offence, date: LocalDate, id: Long = IdGenerator.getAndIncrement(), softDeleted: Boolean = false - ) = AdditionalOffence(id, event, date, offence, softDeleted) + ) = AdditionalOffence(id, offenceCount, event, date, offence, softDeleted) fun generateRegisterType( code: String, 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 new file mode 100644 index 0000000000..8f16e0bc56 --- /dev/null +++ b/projects/manage-supervision-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/SentenceIntegrationTest.kt @@ -0,0 +1,61 @@ +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.MainOffence +import uk.gov.justice.digital.hmpps.api.model.sentence.Offence +import uk.gov.justice.digital.hmpps.api.model.sentence.SentenceOverview +import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator +import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.contentAsJson +import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.withToken +import java.time.LocalDate + +@AutoConfigureMockMvc +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class SentenceIntegrationTest { + @Autowired + lateinit var mockMvc: MockMvc + + @Test + fun `get active sentences`() { + val response = mockMvc + .perform(MockMvcRequestBuilders.get("/sentence/${PersonGenerator.OVERVIEW.crn}").withToken()) + .andExpect(MockMvcResultMatchers.status().isOk) + .andReturn().response.contentAsJson() + + val expected = SentenceOverview( + listOf( + MainOffence( + Offence("Murder", 1), + LocalDate.of(2024, 3, 12), + "overview", + listOf( + Offence("Burglary", 1), + Offence("Assault", 1) + ) + ), + MainOffence( + Offence("Another Murder", 1), + LocalDate.of(2024, 3, 12), + "overview", + emptyList() + ) + ) + ) + + assertEquals(expected, response) + } + + @Test + fun `unauthorized status returned`() { + mockMvc + .perform(MockMvcRequestBuilders.get("/sentence/X123456")) + .andExpect(MockMvcResultMatchers.status().isUnauthorized) + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..7bf940a555 --- /dev/null +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/controller/SentenceController.kt @@ -0,0 +1,21 @@ +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 uk.gov.justice.digital.hmpps.service.SentenceService + +@RestController +@Tag(name = "Sentence") +@RequestMapping("/sentence/{crn}") +@PreAuthorize("hasRole('PROBATION_API__MANAGE_A_SUPERVISION__CASE_DETAIL')") +class SentenceController(private val sentenceService: SentenceService) { + + @GetMapping + @Operation(summary = "Display the most recent ‘Active Event’ ") + fun getOverview(@PathVariable crn: String) = sentenceService.getMostRecentActiveEvent(crn) +} \ No newline at end of file diff --git a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/sentence/MainOffence.kt b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/sentence/MainOffence.kt new file mode 100644 index 0000000000..e36218e034 --- /dev/null +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/sentence/MainOffence.kt @@ -0,0 +1,10 @@ +package uk.gov.justice.digital.hmpps.api.model.sentence + +import java.time.LocalDate + +data class MainOffence( + val offence: Offence, + val dateOfOffence: LocalDate, + val notes: String, + val additionalOffences: List +) diff --git a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/sentence/Offence.kt b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/sentence/Offence.kt new file mode 100644 index 0000000000..dbaeb5398c --- /dev/null +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/sentence/Offence.kt @@ -0,0 +1,6 @@ +package uk.gov.justice.digital.hmpps.api.model.sentence + +data class Offence( + val description: String, + val count: Long +) 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 new file mode 100644 index 0000000000..81ccdbb8a6 --- /dev/null +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/sentence/SentenceOverview.kt @@ -0,0 +1,6 @@ +package uk.gov.justice.digital.hmpps.api.model.sentence + +data class SentenceOverview( + val offence: List + +) diff --git a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/overview/entity/Event.kt b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/overview/entity/Event.kt index ea4836a0c0..9684e82ccb 100644 --- a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/overview/entity/Event.kt +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/overview/entity/Event.kt @@ -37,6 +37,9 @@ class Event( @OneToMany(mappedBy = "event") val additionalOffences: List = emptyList(), + @Column(name = "notes") + val notes: String, + @Column(columnDefinition = "number") val softDeleted: Boolean = false ) @@ -121,6 +124,9 @@ class MainOffence( @Column(name = "main_offence_id") val id: Long, + @Column(name = "offence_count") + val offenceCount: Long, + @OneToOne @JoinColumn(name = "event_id", nullable = false) val event: Event, @@ -145,6 +151,9 @@ class AdditionalOffence( @Column(name = "additional_offence_id") val id: Long, + @Column(name = "offence_count") + val offenceCount: Long, + @ManyToOne @JoinColumn(name = "event_id", nullable = false) val event: Event?, diff --git a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/sentence/entity/Sentence.kt b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/sentence/entity/Sentence.kt new file mode 100644 index 0000000000..e1217aa112 --- /dev/null +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/sentence/entity/Sentence.kt @@ -0,0 +1,40 @@ +package uk.gov.justice.digital.hmpps.integrations.delius.sentence.entity + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.Table +import org.hibernate.annotations.Immutable +import org.hibernate.annotations.SQLRestriction +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import uk.gov.justice.digital.hmpps.integrations.delius.overview.entity.Event + +interface EventSentenceRepository : JpaRepository { + + @Immutable + @Entity + @Table(name = "offender") + @SQLRestriction("soft_deleted = 0") + class Person( + @Id + @Column(name = "offender_id") + val id: Long, + + @Column(columnDefinition = "char(7)") + val crn: String + ) + + @Query( + "SELECT e FROM Event e " + + "JOIN Person p ON p.id = e.personId " + + "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 p.crn = :crn " + + "AND e.active = true " + ) + fun findActiveSentencesByCrn(crn: String): 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 new file mode 100644 index 0000000000..72eb0663b5 --- /dev/null +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/SentenceService.kt @@ -0,0 +1,33 @@ +package uk.gov.justice.digital.hmpps.service + +import org.springframework.stereotype.Service +import uk.gov.justice.digital.hmpps.api.model.sentence.MainOffence +import uk.gov.justice.digital.hmpps.api.model.sentence.Offence +import uk.gov.justice.digital.hmpps.api.model.sentence.SentenceOverview +import uk.gov.justice.digital.hmpps.integrations.delius.overview.entity.Event +import uk.gov.justice.digital.hmpps.integrations.delius.sentence.entity.EventSentenceRepository + +@Service +class SentenceService( + private val eventRepository: EventSentenceRepository +) { + fun getMostRecentActiveEvent(crn: String): SentenceOverview { + val events = eventRepository.findActiveSentencesByCrn(crn) + + return SentenceOverview(events.map { it.toOffence() }) + } + + fun Event.toOffence() = mainOffence?.let { mainOffence -> + MainOffence(offence = Offence( + mainOffence.offence.description, mainOffence.offenceCount + ), + dateOfOffence = mainOffence.date, + notes = notes, + additionalOffences = additionalOffences.map { + Offence( + description = it.offence.description, count = it.offenceCount + ) + } + ) + } +} \ No newline at end of file diff --git a/projects/manage-supervision-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/OverviewServiceTest.kt b/projects/manage-supervision-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/OverviewServiceTest.kt index 7b5c653fac..7f98edb362 100644 --- a/projects/manage-supervision-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/OverviewServiceTest.kt +++ b/projects/manage-supervision-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/OverviewServiceTest.kt @@ -60,7 +60,9 @@ internal class OverviewServiceTest { inBreach = false, disposal = PersonGenerator.ACTIVE_ORDER, eventNumber = "654321", - mainOffence = PersonGenerator.MAIN_OFFENCE_1 + mainOffence = PersonGenerator.MAIN_OFFENCE_1, + notes = "overview", + additionalOffences = emptyList() ), generateEvent( person = PersonGenerator.OVERVIEW, @@ -68,7 +70,9 @@ internal class OverviewServiceTest { inBreach = false, disposal = PersonGenerator.ACTIVE_ORDER, eventNumber = "654321", - mainOffence = PersonGenerator.MAIN_OFFENCE_2 + mainOffence = PersonGenerator.MAIN_OFFENCE_2, + notes = "overview", + additionalOffences = emptyList() ), PersonGenerator.INACTIVE_EVENT_1, PersonGenerator.INACTIVE_EVENT_2 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 new file mode 100644 index 0000000000..c1ad63149f --- /dev/null +++ b/projects/manage-supervision-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/SentenceServiceTest.kt @@ -0,0 +1,97 @@ +package uk.gov.justice.digital.hmpps.service + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.InjectMocks +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever +import uk.gov.justice.digital.hmpps.api.model.sentence.MainOffence +import uk.gov.justice.digital.hmpps.api.model.sentence.Offence +import uk.gov.justice.digital.hmpps.api.model.sentence.SentenceOverview +import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator +import uk.gov.justice.digital.hmpps.integrations.delius.overview.entity.PersonOverviewRepository +import uk.gov.justice.digital.hmpps.integrations.delius.sentence.entity.EventSentenceRepository +import java.time.LocalDate + +@ExtendWith(MockitoExtension::class) +class SentenceServiceTest { + + @Mock + lateinit var eventRepository: EventSentenceRepository + + @InjectMocks + lateinit var service: SentenceService + + @Test + fun `no active sentences`() { + + whenever(eventRepository.findActiveSentencesByCrn(PersonGenerator.OVERVIEW.crn)).thenReturn( + listOf(PersonGenerator.INACTIVE_EVENT_1) + ) + + val response = service.getMostRecentActiveEvent(PersonGenerator.OVERVIEW.crn) + + assertEquals(SentenceOverview(listOf(null)), response) + verify(eventRepository, times(1)).findActiveSentencesByCrn(PersonGenerator.OVERVIEW.crn) + + verifyNoMoreInteractions(eventRepository) + } + + @Test + fun `recent active sentences`() { + + whenever(eventRepository.findActiveSentencesByCrn(PersonGenerator.OVERVIEW.crn)).thenReturn( + listOf( + PersonGenerator.generateEvent( + person = PersonGenerator.OVERVIEW, + active = true, + inBreach = true, + disposal = PersonGenerator.ACTIVE_ORDER, + eventNumber = "123457", + mainOffence = PersonGenerator.MAIN_OFFENCE_1, + notes = "overview", + additionalOffences = listOf(PersonGenerator.ADDITIONAL_OFFENCE_1) + ), + PersonGenerator.generateEvent( + person = PersonGenerator.OVERVIEW, + active = true, + inBreach = true, + disposal = PersonGenerator.ACTIVE_ORDER, + eventNumber = "123456", + mainOffence = PersonGenerator.MAIN_OFFENCE_2, + notes = "overview", + additionalOffences = emptyList() + ) + ) + ) + + val response = service.getMostRecentActiveEvent(PersonGenerator.OVERVIEW.crn) + + val expected = SentenceOverview( + listOf( + MainOffence( + Offence("Murder", 1), + LocalDate.of(2024, 3, 12), + "overview", + listOf(Offence("Burglary", 1)) + ), + MainOffence( + Offence("Another Murder", 1), + LocalDate.of(2024, 3, 12), + "overview", + emptyList() + ) + ) + ) + + assertEquals(expected, response) + verify(eventRepository, times(1)).findActiveSentencesByCrn(PersonGenerator.OVERVIEW.crn) + + verifyNoMoreInteractions(eventRepository) + } +} \ No newline at end of file