Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PI-2106: Added sentence status endpoint #4145

Merged
merged 3 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -125,19 +125,18 @@ class DataLoader(
PersonGenerator.generatePersonManager(PersonGenerator.CURRENTLY_MANAGED),
)

val noSentenceEvent =
SentenceGenerator.generateEvent(PersonGenerator.NO_SENTENCE, referralDate = LocalDate.now())
val noSentenceManager =
SentenceGenerator.generateOrderManager(
noSentenceEvent,
SentenceGenerator.NO_SENTENCE_EVENT,
StaffGenerator.UNALLOCATED,
CourtGenerator.PROBATION_AREA,
ZonedDateTime.of(LocalDate.now(), LocalTime.NOON, EuropeLondon),
ZonedDateTime.of(LocalDate.now().minusDays(1), LocalTime.NOON, EuropeLondon)
)
val outcome = SentenceGenerator.OUTCOME
val courtAppearance = SentenceGenerator.generateCourtAppearance(noSentenceEvent, outcome, LocalDateTime.now())
em.saveAll(noSentenceEvent, noSentenceManager, outcome, courtAppearance)
val courtAppearance =
SentenceGenerator.generateCourtAppearance(SentenceGenerator.NO_SENTENCE_EVENT, outcome, LocalDateTime.now())
em.saveAll(SentenceGenerator.NO_SENTENCE_EVENT, noSentenceManager, outcome, courtAppearance)

val newEvent = SentenceGenerator.generateEvent(PersonGenerator.NEW_TO_PROBATION, referralDate = LocalDate.now())
val newSentence =
Expand Down Expand Up @@ -188,6 +187,8 @@ class DataLoader(
UnpaidWorkGenerator.APPT7,
currentManager,
custody,
SentenceGenerator.RELEASE_1,
SentenceGenerator.RELEASE_2,
SentenceGenerator.CONDITIONAL_RELEASE_KEY_DATE,
SentenceGenerator.LED_KEY_DATE,
SentenceGenerator.HDC_KEY_DATE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,18 @@ object SentenceGenerator {
lengthInDays = 99
)

val NO_SENTENCE_EVENT = generateEvent(PersonGenerator.NO_SENTENCE, referralDate = LocalDate.now())

val CURRENT_CUSTODY = generateCustody(
CURRENT_SENTENCE,
ReferenceDataGenerator.CUSTODIAL_STATUS,
"FD1234",
InstitutionGenerator.WSIHMP
)

val RELEASE_1 = generateRelease(LocalDateTime.now().minusDays(3))
val RELEASE_2 = generateRelease(LocalDateTime.now().minusDays(2))

val OUTCOME = Outcome(
Outcome.Code.AWAITING_PSR.value,
Outcome.Code.AWAITING_PSR.description,
Expand Down Expand Up @@ -407,4 +412,10 @@ object SentenceGenerator {

fun generateKeyDates(date: LocalDate, custody: Custody, keyDateType: ReferenceData) =
KeyDate(IdGenerator.getAndIncrement(), date, custody, keyDateType)

fun generateRelease(date: LocalDateTime) = Release(
id = IdGenerator.getAndIncrement(),
custody = CURRENT_CUSTODY,
date = date
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"sentenceId": 87,
"custodialType": {
"code": "C1",
"description": "Custodial status"
},
"sentence": {
"description": "Curfew Order"
},
"mainOffence": {
"description": "Main Offence"
},
"sentenceDate": "2024-08-07",
"actualReleaseDate": "2024-08-05",
"lengthUnit": "Months"
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
{
"mappings": [
{
"request": {
"method": "GET",
"urlPathTemplate": "/secure/offenders/crn/{crn}/convictions/{convictionId}/sentenceStatus"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"bodyFileName": "get_sentence_status_C123456.json"
}
},
{
"request": {
"method": "GET",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ 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.LicenceConditions
import uk.gov.justice.digital.hmpps.api.model.conviction.*
import uk.gov.justice.digital.hmpps.api.resource.advice.ErrorResponse
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
Expand Down Expand Up @@ -314,4 +315,30 @@ internal class ConvictionByCrnAndEventIdIntegrationTest {
assertThat(response.licenceConditions.size, equalTo(1))
assertThat(response.licenceConditions[0].licenceConditionNotes, equalTo("Licence Condition notes"))
}

@Test
fun `call convictions by id and sentence status`() {
val crn = PersonGenerator.CURRENTLY_MANAGED.crn
val event = SentenceGenerator.CURRENTLY_MANAGED

val response = mockMvc
.perform(get("/probation-case/$crn/convictions/${event.id}/sentenceStatus").withToken())
.andExpect(status().isOk)
.andReturn().response.contentAsJson<SentenceStatus>()

assertThat(response.actualReleaseDate, equalTo(SentenceGenerator.RELEASE_2.date.toLocalDate()))
}

@Test
fun `call convictions by id and sentence status not found`() {
val crn = PersonGenerator.NO_SENTENCE.crn
val event = SentenceGenerator.NO_SENTENCE_EVENT

val response = mockMvc
.perform(get("/probation-case/$crn/convictions/${event.id}/sentenceStatus").withToken())
.andExpect(status().isNotFound)
.andReturn().response.contentAsJson<ErrorResponse>()

assertThat(response.developerMessage, equalTo("Sentence not found for crn '$crn', convictionId '${event.id}'"))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,10 @@ internal class ProxyIntegrationTest {
"CONVICTION_BY_ID_LICENCE_CONDITIONS": {
"convictionId": "?",
"activeOnly": true
},
"CONVICTION_BY_ID_SENTENCE_STATUS": {
"convictionId": "?",
"activeOnly": true
}
}
}
Expand All @@ -265,7 +269,7 @@ internal class ProxyIntegrationTest {
.withToken()
).andExpect(status().is2xxSuccessful).andReturn().response.contentAsJson<CompareAllReport>()

assertThat(res.totalNumberOfRequests, equalTo(13))
assertThat(res.totalNumberOfRequests, equalTo(14))
assertThat(res.totalNumberOfCrns, equalTo(2))
assertThat(res.currentPageNumber, equalTo(1))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ data class Offence(
)

data class KeyValue(
val code: String,
val code: String? = null,
val description: String
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package uk.gov.justice.digital.hmpps.api.model.conviction

import uk.gov.justice.digital.hmpps.api.model.KeyValue
import java.time.LocalDate

data class SentenceStatus(
val sentenceId: Long,
val custodialType: KeyValue,
val sentence: KeyValue,
val mainOffence: KeyValue?,
val sentenceDate: LocalDate?,
val actualReleaseDate: LocalDate?,
val licenceExpiryDate: LocalDate?,
val pssEndDate: LocalDate? = null,
val length: Long?,
val lengthUnit: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ class CommunityApiController(
}

@GetMapping("/offenders/crn/{crn}/convictions/{convictionId}/licenceConditions")
fun convictionByIdCLicenceConditions(
fun convictionByIdLicenceConditions(
request: HttpServletRequest,
@PathVariable crn: String,
@PathVariable convictionId: Long,
Expand All @@ -272,6 +272,26 @@ class CommunityApiController(
return proxy(request)
}

@GetMapping("/offenders/crn/{crn}/convictions/{convictionId}/sentenceStatus")
fun convictionByIdSentenceStatus(
request: HttpServletRequest,
@PathVariable crn: String,
@PathVariable convictionId: Long,
): Any {

sendComparisonReport(
mapOf(
"crn" to crn,
"convictionId" to convictionId
), Uri.CONVICTION_BY_ID_SENTENCE_STATUS, request
)

if (featureFlags.enabled("ccd-conviction-by-id-sentence-status")) {
return convictionResource.getConvictionSentenceStatus(crn, convictionId)
}
return proxy(request)
}

@GetMapping("/**")
fun proxy(request: HttpServletRequest): ResponseEntity<String> {
val headers = request.headerNames.asSequence().associateWith { request.getHeader(it) }.toMutableMap()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,11 @@ enum class Uri(
"getConvictionLicenceConditions",
listOf("crn", "convictionId"),
),
CONVICTION_BY_ID_SENTENCE_STATUS(
"/secure/offenders/crn/{crn}/convictions/{convictionId}/sentenceStatus",
"convictionResource",
"getConvictionSentenceStatus",
listOf("crn", "convictionId"),
),
DUMMY("/dummy", "dummyResource", "getDummy", listOf("crn")),
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,10 @@ class ConvictionResource(
@PathVariable crn: String,
@PathVariable convictionId: Long
) = requirementService.getLicenceConditionsForConvictionId(crn, convictionId)

@GetMapping("/{convictionId}/sentenceStatus")
fun getConvictionSentenceStatus(
@PathVariable crn: String,
@PathVariable convictionId: Long
) = convictionService.sentenceStatusFor(crn, convictionId)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import uk.gov.justice.digital.hmpps.integrations.delius.entity.ReferenceData
import uk.gov.justice.digital.hmpps.integrations.delius.event.entity.Event
import java.math.BigDecimal
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZonedDateTime

@Entity
Expand Down Expand Up @@ -81,6 +82,25 @@ class Disposal(
val id: Long
)

@Immutable
@Entity
@Table(name = "release")
class Release(
@Id
@Column(name = "release_id")
val id: Long,

@ManyToOne
@JoinColumn(name = "custody_id")
val custody: Custody,

@Column(name = "actual_release_date")
val date: LocalDateTime,

@Column(name = "soft_deleted", columnDefinition = "number")
val softDeleted: Boolean = false
)

interface DisposalRepository : JpaRepository<Disposal, Long> {

@Query(
Expand Down Expand Up @@ -215,6 +235,12 @@ class Custody(
@OneToMany(mappedBy = "custody")
val keyDates: List<KeyDate> = listOf(),

@OneToMany(mappedBy = "custody")
val releases: List<Release> = emptyList(),

@Column(name = "pss_start_date")
val pssStartDate: LocalDate? = null,

@Id
@Column(name = "custody_id")
val id: Long
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package uk.gov.justice.digital.hmpps.integrations.delius.service
import org.springframework.stereotype.Service
import uk.gov.justice.digital.hmpps.api.model.KeyValue
import uk.gov.justice.digital.hmpps.api.model.conviction.*
import uk.gov.justice.digital.hmpps.exception.NotFoundException
import uk.gov.justice.digital.hmpps.integrations.delius.event.courtappearance.entity.CourtAppearance
import uk.gov.justice.digital.hmpps.integrations.delius.event.entity.*
import uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity.*
Expand Down Expand Up @@ -39,6 +40,13 @@ class ConvictionService(
return event.toConviction()
}

fun sentenceStatusFor(crn: String, eventId: Long): SentenceStatus {
val person = personRepository.getPerson(crn)
val event = eventRepository.getByPersonAndEventNumber(person, eventId)
return event.disposal?.let(Disposal::toSentenceStatus)
?: throw NotFoundException("Sentence not found for crn '$crn', convictionId '$eventId'")
}

fun Event.toConviction(): Conviction =
Conviction(
id,
Expand Down Expand Up @@ -280,4 +288,18 @@ enum class KeyDateTypes(val code: String) {
SENTENCE_EXPIRY_DATE("SED")
}

fun Disposal.toSentenceStatus() = SentenceStatus(
sentenceId = id,
custodialType = KeyValue(
custody?.status?.code ?: "NOT_IN_CUSTODY",
custody?.status?.description ?: "Not in custody"
),
sentence = KeyValue(description = disposalType.description),
mainOffence = event.mainOffence?.let { KeyValue(description = it.offence.description) },
sentenceDate = startDate,
actualReleaseDate = custody?.releases?.maxByOrNull { it.date }?.date?.toLocalDate(),
licenceExpiryDate = custody?.pssStartDate,
length = length,
lengthUnit = "Months"

)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package uk.gov.justice.digital.hmpps.service

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import uk.gov.justice.digital.hmpps.api.model.KeyValue
import uk.gov.justice.digital.hmpps.data.generator.SentenceGenerator
import uk.gov.justice.digital.hmpps.integrations.delius.service.toSentenceStatus

class ConvictionServiceTest {

@Test
fun `toSentenceStatus returns null when no values are present `() {

val sentenceStatus = SentenceGenerator.CURRENT_SENTENCE.toSentenceStatus()
assertEquals(null, sentenceStatus.actualReleaseDate)
assertEquals(KeyValue("NOT_IN_CUSTODY", "Not in custody"), sentenceStatus.custodialType)
assertEquals(null, sentenceStatus.mainOffence)
assertEquals(null, sentenceStatus.licenceExpiryDate)
}
}

Loading