diff --git a/src/main/java/uk/gov/justice/hmpps/prison/api/model/imprisonmentstatus/ImprisonmentStatusHistoryDto.kt b/src/main/java/uk/gov/justice/hmpps/prison/api/model/imprisonmentstatus/ImprisonmentStatusHistoryDto.kt new file mode 100644 index 000000000..4705f4fe0 --- /dev/null +++ b/src/main/java/uk/gov/justice/hmpps/prison/api/model/imprisonmentstatus/ImprisonmentStatusHistoryDto.kt @@ -0,0 +1,18 @@ +package uk.gov.justice.hmpps.prison.api.model.imprisonmentstatus + +import com.fasterxml.jackson.annotation.JsonInclude +import io.swagger.v3.oas.annotations.media.Schema +import java.time.LocalDate + +@Schema(description = "Represents an offenders imprisonment status at a point in history") +@JsonInclude(JsonInclude.Include.NON_NULL) +data class ImprisonmentStatusHistoryDto( + @Schema(description = "The imprisonment status") + val status: String, + + @Schema(description = "The date the status was effective from") + val effectiveDate: LocalDate, + + @Schema(description = "The agency the status was set by") + val agencyId: String, +) diff --git a/src/main/java/uk/gov/justice/hmpps/prison/api/resource/ImprisonmentStatusHistoryResource.kt b/src/main/java/uk/gov/justice/hmpps/prison/api/resource/ImprisonmentStatusHistoryResource.kt new file mode 100644 index 000000000..3689a79c8 --- /dev/null +++ b/src/main/java/uk/gov/justice/hmpps/prison/api/resource/ImprisonmentStatusHistoryResource.kt @@ -0,0 +1,59 @@ +package uk.gov.justice.hmpps.prison.api.resource + +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.responses.ApiResponses +import io.swagger.v3.oas.annotations.tags.Tag +import lombok.extern.slf4j.Slf4j +import org.springframework.validation.annotation.Validated +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.hmpps.prison.api.model.ErrorResponse +import uk.gov.justice.hmpps.prison.api.model.imprisonmentstatus.ImprisonmentStatusHistoryDto +import uk.gov.justice.hmpps.prison.security.VerifyOffenderAccess +import uk.gov.justice.hmpps.prison.service.imprisonmentstatus.ImprisonmentStatusHistoryService + +@Slf4j +@RestController +@Tag(name = "imprisonment-status-history") +@Validated +@RequestMapping( + value = ["\${api.base.path}/imprisonment-status-history"], + produces = ["application/json"], +) +class ImprisonmentStatusHistoryResource(private val imprisonmentStatusHistoryService: ImprisonmentStatusHistoryService) { + + @ApiResponses( + ApiResponse( + responseCode = "200", + description = "The imprisonment status history.", + content = [ + Content( + mediaType = "application/json", + schema = Schema(implementation = ImprisonmentStatusHistoryDto::class), + ), + ], + ), + ApiResponse( + responseCode = "404", + description = "Requested resource not found.", + content = [Content(mediaType = "application/json", schema = Schema(implementation = ErrorResponse::class))], + ), + ) + @Operation(summary = "Returns the details of all the historic imprisonment statuses for an offender.") + @GetMapping("/{offenderNo}") + @VerifyOffenderAccess(overrideRoles = ["GLOBAL_SEARCH", "VIEW_PRISONER_DATA"]) + fun getImprisonmentStatusHistory( + @PathVariable("offenderNo") @Parameter( + description = "The required offender id (mandatory)", + required = true, + ) offenderNo: String, + ): List { + return imprisonmentStatusHistoryService.getImprisonmentStatusHistory(offenderNo) + } +} diff --git a/src/main/java/uk/gov/justice/hmpps/prison/repository/jpa/model/ImprisonmentStatus.java b/src/main/java/uk/gov/justice/hmpps/prison/repository/jpa/model/ImprisonmentStatus.java index 2dc0bd818..82a311ebd 100644 --- a/src/main/java/uk/gov/justice/hmpps/prison/repository/jpa/model/ImprisonmentStatus.java +++ b/src/main/java/uk/gov/justice/hmpps/prison/repository/jpa/model/ImprisonmentStatus.java @@ -7,6 +7,7 @@ import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.ToString; +import lombok.With; import org.apache.commons.lang3.StringUtils; import org.hibernate.Hibernate; import org.hibernate.type.YesNoConverter; @@ -29,6 +30,7 @@ @Entity @Table(name = "IMPRISONMENT_STATUSES") @ToString(of = { "id", "status", "description"}) +@With public class ImprisonmentStatus extends AuditableEntity { @Id diff --git a/src/main/java/uk/gov/justice/hmpps/prison/repository/jpa/repository/OffenderImprisonmentStatusRepository.java b/src/main/java/uk/gov/justice/hmpps/prison/repository/jpa/repository/OffenderImprisonmentStatusRepository.java new file mode 100644 index 000000000..9f3f28c9a --- /dev/null +++ b/src/main/java/uk/gov/justice/hmpps/prison/repository/jpa/repository/OffenderImprisonmentStatusRepository.java @@ -0,0 +1,22 @@ +package uk.gov.justice.hmpps.prison.repository.jpa.repository; + +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; +import uk.gov.justice.hmpps.prison.repository.jpa.model.OffenderImprisonmentStatus; +import uk.gov.justice.hmpps.prison.repository.jpa.model.OffenderImprisonmentStatus.PK; + +import java.util.List; + +public interface OffenderImprisonmentStatusRepository extends CrudRepository { + + @Query(""" + select ois + from OffenderImprisonmentStatus ois + join ois.offenderBooking ob + join ob.offender o + join fetch ois.imprisonmentStatus is + where o.nomsId = :offenderId + """ + ) + List findByOffender(String offenderId); +} diff --git a/src/main/java/uk/gov/justice/hmpps/prison/service/imprisonmentstatus/ImprisonmentStatusHistoryService.kt b/src/main/java/uk/gov/justice/hmpps/prison/service/imprisonmentstatus/ImprisonmentStatusHistoryService.kt new file mode 100644 index 000000000..a6b57b3ea --- /dev/null +++ b/src/main/java/uk/gov/justice/hmpps/prison/service/imprisonmentstatus/ImprisonmentStatusHistoryService.kt @@ -0,0 +1,26 @@ +package uk.gov.justice.hmpps.prison.service.imprisonmentstatus + +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import uk.gov.justice.hmpps.prison.api.model.imprisonmentstatus.ImprisonmentStatusHistoryDto +import uk.gov.justice.hmpps.prison.repository.jpa.repository.OffenderImprisonmentStatusRepository + +@Service +@Transactional(readOnly = true) +class ImprisonmentStatusHistoryService( + private val offenderImprisonmentStatusRepository: OffenderImprisonmentStatusRepository, +) { + + fun getImprisonmentStatusHistory(offenderNo: String): List { + return offenderImprisonmentStatusRepository.findByOffender(offenderNo) + .groupBy { it.effectiveDate } + .map { (_, statuses) -> statuses.maxBy { it.imprisonStatusSeq } } + .map { + ImprisonmentStatusHistoryDto( + status = it.imprisonmentStatus.status, + effectiveDate = it.effectiveDate, + agencyId = it.agyLocId, + ) + } + } +} diff --git a/src/test/java/uk/gov/justice/hmpps/prison/api/resource/impl/ImprisonmentStatusHistoryResourceImplTest.kt b/src/test/java/uk/gov/justice/hmpps/prison/api/resource/impl/ImprisonmentStatusHistoryResourceImplTest.kt new file mode 100644 index 000000000..4653f030a --- /dev/null +++ b/src/test/java/uk/gov/justice/hmpps/prison/api/resource/impl/ImprisonmentStatusHistoryResourceImplTest.kt @@ -0,0 +1,26 @@ +package uk.gov.justice.hmpps.prison.api.resource.impl + +import org.junit.jupiter.api.Test +import org.springframework.http.MediaType + +class ImprisonmentStatusHistoryResourceImplTest : ResourceTest() { + @Test + fun courtDateResults() { + webTestClient.get() + .uri("/api/imprisonment-status-history/{offenderNo}", "A1180HL") + .headers(setAuthorisation(listOf("ROLE_VIEW_PRISON_DATA"))) + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectBody() + .jsonPath("$.length()").isEqualTo(3) + .jsonPath("[0].status").isEqualTo("FTR_ORA") + .jsonPath("[0].effectiveDate").isEqualTo("2016-03-29") + .jsonPath("[0].agencyId").isEqualTo("MDI") + .jsonPath("[1].status").isEqualTo("TRL") + .jsonPath("[1].effectiveDate").isEqualTo("2016-03-30") + .jsonPath("[1].agencyId").isEqualTo("MDI") + .jsonPath("[2].status").isEqualTo("DPP") + .jsonPath("[2].effectiveDate").isEqualTo("2016-03-31") + .jsonPath("[2].agencyId").isEqualTo("MDI") + } +} diff --git a/src/test/java/uk/gov/justice/hmpps/prison/service/imprisonmentstatus/ImprisonmentStatusHistoryServiceTest.kt b/src/test/java/uk/gov/justice/hmpps/prison/service/imprisonmentstatus/ImprisonmentStatusHistoryServiceTest.kt new file mode 100644 index 000000000..0e8e2de40 --- /dev/null +++ b/src/test/java/uk/gov/justice/hmpps/prison/service/imprisonmentstatus/ImprisonmentStatusHistoryServiceTest.kt @@ -0,0 +1,72 @@ +package uk.gov.justice.hmpps.prison.service.imprisonmentstatus + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import uk.gov.justice.hmpps.prison.api.model.imprisonmentstatus.ImprisonmentStatusHistoryDto +import uk.gov.justice.hmpps.prison.repository.jpa.model.ImprisonmentStatus +import uk.gov.justice.hmpps.prison.repository.jpa.model.OffenderImprisonmentStatus +import uk.gov.justice.hmpps.prison.repository.jpa.repository.OffenderImprisonmentStatusRepository +import java.time.LocalDate + +class ImprisonmentStatusHistoryServiceTest { + + private val offenderImprisonmentStatusRepository = mock() + private val imprisonmentStatusHistoryService = ImprisonmentStatusHistoryService( + offenderImprisonmentStatusRepository, + ) + + @Test + fun getInmateStatusHistory() { + val offenderId = "ABC123" + val remandStatus = ImprisonmentStatus().withStatus("SEC38") + val sentencedStatus = ImprisonmentStatus().withStatus("ADIMP_ORA20") + val recallStatus = ImprisonmentStatus().withStatus("LR") + val firstDate = LocalDate.of(2024, 1, 1) + val secondDate = LocalDate.of(2024, 2, 1) + whenever(offenderImprisonmentStatusRepository.findByOffender(offenderId)).thenReturn( + listOf( + OffenderImprisonmentStatus() + .withImprisonmentStatus(sentencedStatus) + .withEffectiveDate(firstDate) + .withImprisonStatusSeq(1) + .withAgyLocId("KMI"), + OffenderImprisonmentStatus() + .withImprisonmentStatus(remandStatus) + .withEffectiveDate(firstDate) + .withImprisonStatusSeq(2) + .withAgyLocId("KMI"), + OffenderImprisonmentStatus() + .withImprisonmentStatus(recallStatus) + .withEffectiveDate(secondDate) + .withImprisonStatusSeq(3) + .withAgyLocId("BMI"), + OffenderImprisonmentStatus() + .withImprisonmentStatus(sentencedStatus) + .withEffectiveDate(secondDate) + .withImprisonStatusSeq(4) + .withAgyLocId("BMI"), + ), + ) + + val result = imprisonmentStatusHistoryService.getImprisonmentStatusHistory(offenderId) + + assertThat( + result, + ).isEqualTo( + listOf( + ImprisonmentStatusHistoryDto( + status = "SEC38", + effectiveDate = firstDate, + agencyId = "KMI", + ), + ImprisonmentStatusHistoryDto( + status = "ADIMP_ORA20", + effectiveDate = secondDate, + agencyId = "BMI", + ), + ), + ) + } +}