diff --git a/.github/actions/format-code/action.yml b/.github/actions/format-code/action.yml index de4e9f11af..a25e2bcdd5 100644 --- a/.github/actions/format-code/action.yml +++ b/.github/actions/format-code/action.yml @@ -60,7 +60,7 @@ runs: env: mask: ${{ inputs.mask }} - - uses: planetscale/ghcommit-action@c8ba2501e51d7257efb393109e6e10bc36a3f769 # v0.1.40 + - uses: planetscale/ghcommit-action@84c52a5164423e2cd66371ecae81d9049a2cf105 # v0.1.41 with: commit_message: ${{ inputs.commit_message }} repo: ${{ github.repository }} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 381baa9cef..8a1f6b97f4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionSha256Sum=a4b4158601f8636cdeeab09bd76afb640030bb5b144aafe261a5e8af027dc612 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a4269..b740cf1339 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. 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 ce27b61509..c1022c4533 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 @@ -9,9 +9,10 @@ import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional import uk.gov.justice.digital.hmpps.api.model.DocumentType import uk.gov.justice.digital.hmpps.data.generator.* -import uk.gov.justice.digital.hmpps.integrations.delius.event.courtappearance.entity.Outcome import uk.gov.justice.digital.hmpps.user.AuditUserRepository import java.time.LocalDate +import java.time.LocalTime +import java.time.ZoneId import java.time.ZonedDateTime @Component @@ -29,11 +30,9 @@ class DataLoader( @Transactional override fun onApplicationEvent(are: ApplicationReadyEvent) { em.saveAll( - DocumentEntityGenerator.COURT, DocumentEntityGenerator.INSTITUTIONAL_REPORT_TYPE, DocumentEntityGenerator.INSTITUTIONAL_REPORT, - DocumentEntityGenerator.R_INSTITUTION, AreaGenerator.PARTITION_AREA, ProviderEmployeeGenerator.PROVIDER_EMPLOYEE, ProviderGenerator.DEFAULT, @@ -41,11 +40,13 @@ class DataLoader( BoroughGenerator.DEFAULT, DistrictGenerator.DEFAULT, TeamGenerator.DEFAULT, + ReferenceDataGenerator.VIOLENCE, SentenceGenerator.MAIN_OFFENCE, SentenceGenerator.ADDITIONAL_OFFENCE, BusinessInteractionGenerator.UPDATE_CONTACT, ContactTypeGenerator.CONTACT_TYPE, + DisposalTypeGenerator.CURFEW_ORDER, ReferenceDataGenerator.DISPOSAL_TYPE, ReferenceDataGenerator.LENGTH_UNITS, ReferenceDataGenerator.TERMINATION_REASON, @@ -79,6 +80,21 @@ class DataLoader( ReferenceDataGenerator.DEFAULT_ADDRESS_STATUS, ReferenceDataGenerator.DEFAULT_ALLOCATION_REASON, ReferenceDataGenerator.DEFAULT_TIER, + ReferenceDataGenerator.REF_DISQ, + ReferenceDataGenerator.PRISON, + ReferenceDataGenerator.ACR, + ReferenceDataGenerator.EXP, + ReferenceDataGenerator.HDE, + ReferenceDataGenerator.LED, + ReferenceDataGenerator.PED, + ReferenceDataGenerator.PSSED, + ReferenceDataGenerator.POM1, + ReferenceDataGenerator.POM2, + ReferenceDataGenerator.SED, + ReferenceDataGenerator.CRN, + ReferenceDataGenerator.TRIAL, + CourtGenerator.PROBATIONARE_AREA, + CourtGenerator.BHAM, PersonGenerator.NEW_TO_PROBATION, PersonGenerator.CURRENTLY_MANAGED, PersonGenerator.PREVIOUSLY_MANAGED, @@ -97,50 +113,72 @@ class DataLoader( PersonGenerator.generatePersonManager(PersonGenerator.CURRENTLY_MANAGED) ) - val noSentenceEvent = SentenceGenerator.generateEvent(PersonGenerator.NO_SENTENCE) - val noSentenceManager = SentenceGenerator.generateOrderManager(noSentenceEvent, StaffGenerator.UNALLOCATED) - val outcome = Outcome(Outcome.Code.AWAITING_PSR.value, IdGenerator.getAndIncrement()) - val courtAppearance = SentenceGenerator.generateCourtAppearance(noSentenceEvent, outcome) + val noSentenceEvent = + SentenceGenerator.generateEvent(PersonGenerator.NO_SENTENCE, referralDate = LocalDate.now()) + val noSentenceManager = + SentenceGenerator.generateOrderManager( + noSentenceEvent, + StaffGenerator.UNALLOCATED, + CourtGenerator.PROBATIONARE_AREA, + ZonedDateTime.of(LocalDate.now(), LocalTime.NOON, ZoneId.of("Europe/London")), + ZonedDateTime.of(LocalDate.now().minusDays(1), LocalTime.NOON, ZoneId.of("Europe/London")) + ) + val outcome = SentenceGenerator.OUTCOME + val courtAppearance = SentenceGenerator.generateCourtAppearance(noSentenceEvent, outcome, ZonedDateTime.now()) em.saveAll(noSentenceEvent, noSentenceManager, outcome, courtAppearance) - val newEvent = SentenceGenerator.generateEvent(PersonGenerator.NEW_TO_PROBATION) + val newEvent = SentenceGenerator.generateEvent(PersonGenerator.NEW_TO_PROBATION, referralDate = LocalDate.now()) val newSentence = - SentenceGenerator.generateSentence(newEvent, ZonedDateTime.now(), ReferenceDataGenerator.DISPOSAL_TYPE) - val newManager = SentenceGenerator.generateOrderManager(newEvent, StaffGenerator.UNALLOCATED) + SentenceGenerator.generateSentence(newEvent, LocalDate.now(), DisposalTypeGenerator.CURFEW_ORDER) + val newManager = + SentenceGenerator.generateOrderManager( + newEvent, + StaffGenerator.UNALLOCATED, + CourtGenerator.PROBATIONARE_AREA, + ZonedDateTime.of(LocalDate.now().minusDays(1), LocalTime.NOON, ZoneId.of("Europe/London")), + ZonedDateTime.of(LocalDate.now().minusDays(3), LocalTime.NOON, ZoneId.of("Europe/London")) + ) em.saveAll(newEvent, newSentence, newManager) - val currentEvent = SentenceGenerator.generateEvent(PersonGenerator.CURRENTLY_MANAGED, inBreach = true) - val currentSentence = - SentenceGenerator.generateSentence( - currentEvent, - ZonedDateTime.now(), - ReferenceDataGenerator.DISPOSAL_TYPE, - entryLength = 12, - entryLengthUnits = ReferenceDataGenerator.LENGTH_UNITS, - lengthInDays = 99 - ) - val custody = SentenceGenerator.generateCustody(currentSentence, ReferenceDataGenerator.CUSTODIAL_STATUS) - val currentManager = SentenceGenerator.generateOrderManager(currentEvent, StaffGenerator.ALLOCATED) - val mainOffence = - SentenceGenerator.generateMainOffence(currentEvent, SentenceGenerator.MAIN_OFFENCE, LocalDate.now()) - val additionalOffence = SentenceGenerator.generateAdditionalOffence( - currentEvent, - SentenceGenerator.ADDITIONAL_OFFENCE, - LocalDate.now() - ) + val currentEvent = SentenceGenerator.CURRENTLY_MANAGED + val currentSentence = SentenceGenerator.CURRENT_SENTENCE + val custody = SentenceGenerator.CURRENT_CUSTODY + val currentManager = SentenceGenerator.CURRENT_ORDER_MANAGER + val mainOffence = SentenceGenerator.MAIN_OFFENCE_DEFAULT + val additionalOffence = SentenceGenerator.ADDITIONAL_OFFENCE_DEFAULT val requirement = SentenceGenerator.generateRequirement(disposal = currentSentence) val licenceCondition = SentenceGenerator.generateLicenseCondition(disposal = currentSentence) val breachNsi = SentenceGenerator.generateBreachNsi(disposal = currentSentence) val pssRequirement = SentenceGenerator.generatePssRequirement(custody.id) - val currentCourtAppearance = SentenceGenerator.generateCourtAppearance(currentEvent, outcome) + val currentCourtAppearance = SentenceGenerator.COURT_APPEARANCE val currentCourtReport = SentenceGenerator.generateCourtReport(currentCourtAppearance) val reportManager = SentenceGenerator.generateCourtReportManager(currentCourtReport) em.saveAll( currentEvent, currentSentence, + AdditionalSentenceGenerator.SENTENCE_DISQ, + ReferenceDataGenerator.HOURS_WORKED, + UnpaidWorkGenerator.UNPAID_WORK_DETAILS_1, + UnpaidWorkGenerator.APPT1, + UnpaidWorkGenerator.APPT2, + UnpaidWorkGenerator.APPT3, + UnpaidWorkGenerator.APPT4, + UnpaidWorkGenerator.APPT5, + UnpaidWorkGenerator.APPT6, + UnpaidWorkGenerator.APPT7, currentManager, + InstitutionGenerator.WSIHMP, custody, + SentenceGenerator.CONDITIONAL_RELEASE_KEY_DATE, + SentenceGenerator.LED_KEY_DATE, + SentenceGenerator.HDC_KEY_DATE, + SentenceGenerator.PAROLE_KEY_DATE, + SentenceGenerator.SENTENCE_KEY_DATE, + SentenceGenerator.EXPECTED_RELEASE_KEY_DATE, + SentenceGenerator.SUPERVISION_KEY_DATE, + SentenceGenerator.HANDOVER_START_KEY_DATE, + SentenceGenerator.HANDOVER_KEY_DATE, mainOffence, additionalOffence, requirement, @@ -152,15 +190,27 @@ class DataLoader( reportManager ) - val preEvent = SentenceGenerator.generateEvent(PersonGenerator.PREVIOUSLY_MANAGED, active = false) + val preEvent = + SentenceGenerator.generateEvent( + PersonGenerator.PREVIOUSLY_MANAGED, + referralDate = LocalDate.now(), + active = false + ) val preSentence = SentenceGenerator.generateSentence( preEvent, - ZonedDateTime.now(), - ReferenceDataGenerator.DISPOSAL_TYPE, - terminationDate = ZonedDateTime.now().minusDays(7), + LocalDate.now(), + DisposalTypeGenerator.CURFEW_ORDER, + terminationDate = LocalDate.now().minusDays(7), active = false ) - val preManager = SentenceGenerator.generateOrderManager(preEvent, StaffGenerator.ALLOCATED) + val preManager = + SentenceGenerator.generateOrderManager( + preEvent, + StaffGenerator.ALLOCATED, + CourtGenerator.PROBATIONARE_AREA, + ZonedDateTime.of(LocalDate.now().minusDays(7), LocalTime.NOON, ZoneId.of("Europe/London")), + ZonedDateTime.of(LocalDate.now().minusDays(10), LocalTime.NOON, ZoneId.of("Europe/London")) + ) em.saveAll(preEvent, preSentence, preManager) em.merge(CourtCaseNoteGenerator.CASE_NOTE) diff --git a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/entity/Entities.kt b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/entity/Entities.kt index de503b1b76..6daf5f1f6c 100644 --- a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/entity/Entities.kt +++ b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/entity/Entities.kt @@ -1,11 +1,6 @@ package uk.gov.justice.digital.hmpps.data.entity -import jakarta.persistence.Column -import jakarta.persistence.Convert -import jakarta.persistence.Entity -import jakarta.persistence.Id -import jakarta.persistence.Table -import org.hibernate.annotations.Immutable +import jakarta.persistence.* import org.hibernate.type.YesNoConverter import java.time.LocalDate @@ -87,18 +82,3 @@ class User( val surname: String ) -@Entity -@Immutable -@Table(name = "r_institution") -class Institution( - @Id - @Column(name = "institution_id") - val id: Long, - - @Column(name = "institution_name") - val name: String, - - @Column - @Convert(converter = YesNoConverter::class) - val establishment: Boolean -) diff --git a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/AdditionalSentenceGenerator.kt b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/AdditionalSentenceGenerator.kt new file mode 100644 index 0000000000..b6526b053d --- /dev/null +++ b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/AdditionalSentenceGenerator.kt @@ -0,0 +1,30 @@ +package uk.gov.justice.digital.hmpps.data.generator + +import uk.gov.justice.digital.hmpps.data.generator.ReferenceDataGenerator.REF_DISQ +import uk.gov.justice.digital.hmpps.data.generator.SentenceGenerator.CURRENTLY_MANAGED +import uk.gov.justice.digital.hmpps.integrations.delius.entity.ReferenceData +import uk.gov.justice.digital.hmpps.integrations.delius.event.entity.Event +import uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity.AdditionalSentence +import java.math.BigDecimal + +object AdditionalSentenceGenerator { + + val SENTENCE_DISQ = + generateSentence(amount = BigDecimal(7), length = 3, referenceData = REF_DISQ, event = CURRENTLY_MANAGED) + + fun generateSentence( + length: Long? = null, + amount: BigDecimal? = null, + notes: String? = null, + event: Event, + referenceData: ReferenceData + ) = AdditionalSentence( + IdGenerator.getAndIncrement(), + event, + referenceData, + amount, + length, + notes, + false, + ) +} \ No newline at end of file diff --git a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/CourtGenerator.kt b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/CourtGenerator.kt new file mode 100644 index 0000000000..89985b695f --- /dev/null +++ b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/CourtGenerator.kt @@ -0,0 +1,43 @@ +package uk.gov.justice.digital.hmpps.data.generator + +import uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity.Court +import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.ProbationAreaEntity +import java.time.LocalDate +import java.time.LocalTime +import java.time.ZoneId +import java.time.ZonedDateTime + +object CourtGenerator { + + val PROBATIONARE_AREA = ProbationAreaEntity( + true, + "West Midlands Region", + "N52", + null, + false, + IdGenerator.getAndIncrement(), + ) + + val BHAM = Court( + IdGenerator.getAndIncrement(), + "BRMNCC", + true, + "Birmingham Crown Court", + "0121 111 2222", + "0121 333 4444", + "Queen Elizabeth II Law Courts", + "1 Newton Street", + null, + "Birmingham", + "West Midlands", + "B4 7NA", + "England", + ReferenceDataGenerator.CRN.id, + ZonedDateTime.of(LocalDate.now().minusDays(7), LocalTime.NOON, ZoneId.of("Europe/London")), + ZonedDateTime.of(LocalDate.now(), LocalTime.NOON, ZoneId.of("Europe/London")), + PROBATIONARE_AREA.id, + null, + PROBATIONARE_AREA, + ReferenceDataGenerator.CRN + ) +} \ No newline at end of file diff --git a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/DisposalTypeGenerator.kt b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/DisposalTypeGenerator.kt new file mode 100644 index 0000000000..73217fd5ab --- /dev/null +++ b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/DisposalTypeGenerator.kt @@ -0,0 +1,9 @@ +package uk.gov.justice.digital.hmpps.data.generator + +import uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity.DisposalType + +object DisposalTypeGenerator { + + val CURFEW_ORDER = + DisposalType(IdGenerator.getAndIncrement(), "Curfew Order", "SP", cja2003Order = false, legacyOrder = true, 2) +} \ No newline at end of file diff --git a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/DocumentEntityGenerator.kt b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/DocumentEntityGenerator.kt index a2512d1d9c..0771d4c411 100644 --- a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/DocumentEntityGenerator.kt +++ b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/DocumentEntityGenerator.kt @@ -1,7 +1,7 @@ package uk.gov.justice.digital.hmpps.data.generator import uk.gov.justice.digital.hmpps.data.entity.Court -import uk.gov.justice.digital.hmpps.data.entity.Institution +//import uk.gov.justice.digital.hmpps.data.entity.Institution import uk.gov.justice.digital.hmpps.data.entity.InstitutionalReport import uk.gov.justice.digital.hmpps.integrations.delius.entity.DocumentEntity import uk.gov.justice.digital.hmpps.integrations.delius.entity.ReferenceData @@ -20,7 +20,7 @@ object DocumentEntityGenerator { dateRequested = LocalDate.of(2000, 1, 2) ) - val R_INSTITUTION = Institution(IdGenerator.getAndIncrement(), "test", false) +// val R_INSTITUTION = Institution(IdGenerator.getAndIncrement(), "test", false) fun generateDocument(personId: Long, primaryKeyId: Long?, type: String, tableName: String?) = DocumentEntity( diff --git a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/InstitutionGenerator.kt b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/InstitutionGenerator.kt new file mode 100644 index 0000000000..9359bdd55f --- /dev/null +++ b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/InstitutionGenerator.kt @@ -0,0 +1,20 @@ +package uk.gov.justice.digital.hmpps.data.generator + +import uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity.Institution +import uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity.InstitutionId + +object InstitutionGenerator { + + val WSIHMP = generate("WSIHMP", "WSI") + + fun generate(code: String, prisonId: String): Institution { + return Institution( + id = InstitutionId(IdGenerator.getAndIncrement(), true), + code = code, + description = "Test institution ($code)", + institutionName = "Test institution $code", + establishmentType = ReferenceDataGenerator.PRISON, + nomisCdeCode = prisonId + ) + } +} diff --git a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ReferenceDataGenerator.kt b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ReferenceDataGenerator.kt index 1baf046dda..3366214670 100644 --- a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ReferenceDataGenerator.kt +++ b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ReferenceDataGenerator.kt @@ -176,6 +176,24 @@ object ReferenceDataGenerator { IdGenerator.getAndIncrement() ) + val VIOLENCE = ReferenceData( + "VI", + "Violence", + IdGenerator.getAndIncrement() + ) + + val HOURS_WORKED = ReferenceData( + "HC", + "Hours Completed", + IdGenerator.getAndIncrement() + ) + + val REF_DISQ = ReferenceData( + "DISQ", + "Disqualified from Driving", + IdGenerator.getAndIncrement() + ) + val DEFAULT_ADDRESS_TYPE = ReferenceData( "AT", "Address Type", @@ -199,4 +217,76 @@ object ReferenceDataGenerator { "B2", IdGenerator.getAndIncrement() ) + + val PRISON = ReferenceData( + "E", + "prison", + IdGenerator.getAndIncrement() + ) + + val ACR = ReferenceData( + "ACR", + "Auto-Conditional Release Date", + IdGenerator.getAndIncrement() + ) + + val EXP = ReferenceData( + "EXP", + "Expected Release Date", + IdGenerator.getAndIncrement() + ) + + val HDE = ReferenceData( + "HDE", + "HDC Expected Date", + IdGenerator.getAndIncrement() + ) + + val LED = ReferenceData( + "LED", + "Licence Expiry Date", + IdGenerator.getAndIncrement() + ) + + val PED = ReferenceData( + "PED", + "Parole Eligibility Date", + IdGenerator.getAndIncrement() + ) + + val PSSED = ReferenceData( + "PSSED", + "Post-Sentence Supervision End Date", + IdGenerator.getAndIncrement() + ) + + val POM1 = ReferenceData( + "POM1", + "POM Handover Expected Start Date", + IdGenerator.getAndIncrement() + ) + + val POM2 = ReferenceData( + "POM2", + "RO responsibility handover from POM to OM Expected Date", + IdGenerator.getAndIncrement() + ) + + val SED = ReferenceData( + "SED", + "Sentence Expiry Date", + IdGenerator.getAndIncrement() + ) + + val CRN = ReferenceData( + "CRN", + "Crown Court", + IdGenerator.getAndIncrement() + ) + + val TRIAL = ReferenceData( + code = "T", + "Trial/Adjournment", + IdGenerator.getAndIncrement() + ) } diff --git a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/SentenceGenerator.kt b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/SentenceGenerator.kt index 0a2a4db8cd..a3cc568083 100644 --- a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/SentenceGenerator.kt +++ b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/SentenceGenerator.kt @@ -5,37 +5,99 @@ import uk.gov.justice.digital.hmpps.integrations.delius.event.courtappearance.en import uk.gov.justice.digital.hmpps.integrations.delius.event.courtappearance.entity.CourtReport import uk.gov.justice.digital.hmpps.integrations.delius.event.courtappearance.entity.Outcome import uk.gov.justice.digital.hmpps.integrations.delius.event.courtappearance.entity.ReportManager -import uk.gov.justice.digital.hmpps.integrations.delius.event.entity.AdditionalOffence -import uk.gov.justice.digital.hmpps.integrations.delius.event.entity.Event -import uk.gov.justice.digital.hmpps.integrations.delius.event.entity.LicenceCondition -import uk.gov.justice.digital.hmpps.integrations.delius.event.entity.MainOffence -import uk.gov.justice.digital.hmpps.integrations.delius.event.entity.Offence -import uk.gov.justice.digital.hmpps.integrations.delius.event.entity.OrderManager -import uk.gov.justice.digital.hmpps.integrations.delius.event.entity.Requirement +import uk.gov.justice.digital.hmpps.integrations.delius.event.entity.* import uk.gov.justice.digital.hmpps.integrations.delius.event.nsi.Nsi -import uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity.Custody -import uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity.Disposal -import uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity.PssRequirement +import uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity.* import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.Person +import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.ProbationAreaEntity import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.Staff +import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.Team import java.time.LocalDate +import java.time.LocalTime +import java.time.ZoneId import java.time.ZonedDateTime object SentenceGenerator { + + private val TIME_ZONE = ZoneId.of("Europe/London") + + val CURRENTLY_MANAGED = generateEvent( + PersonGenerator.CURRENTLY_MANAGED, + referralDate = LocalDate.now().minusDays(1), + inBreach = true, + breachDate = LocalDate.now().minusMonths(3), + court = CourtGenerator.BHAM + ) + + val CURRENT_SENTENCE = generateSentence( + CURRENTLY_MANAGED, + LocalDate.now(), + DisposalTypeGenerator.CURFEW_ORDER, + entryLength = 12, + entryLengthUnits = ReferenceDataGenerator.LENGTH_UNITS, + lengthInDays = 99 + ) + + val CURRENT_CUSTODY = generateCustody( + CURRENT_SENTENCE, + ReferenceDataGenerator.CUSTODIAL_STATUS, + "FD1234", + InstitutionGenerator.WSIHMP + ) + + val OUTCOME = Outcome( + Outcome.Code.AWAITING_PSR.value, + Outcome.Code.AWAITING_PSR.description, + IdGenerator.getAndIncrement() + ) + + val COURT_APPEARANCE = generateCourtAppearance( + CURRENTLY_MANAGED, + OUTCOME, + ZonedDateTime.of(LocalDate.now(), LocalTime.NOON, TIME_ZONE) + ) + + val CURRENT_ORDER_MANAGER = SentenceGenerator.generateOrderManager( + CURRENTLY_MANAGED, + StaffGenerator.ALLOCATED, + CourtGenerator.PROBATIONARE_AREA, + ZonedDateTime.of(LocalDate.now(), LocalTime.NOON, ZoneId.of("Europe/London")), + ZonedDateTime.of(LocalDate.now().minusDays(3), LocalTime.NOON, ZoneId.of("Europe/London")) + ) + + val CONDITIONAL_RELEASE_KEY_DATE = generateKeyDates(LocalDate.now(), CURRENT_CUSTODY, ReferenceDataGenerator.ACR) + val LED_KEY_DATE = generateKeyDates(LocalDate.now().plusDays(1), CURRENT_CUSTODY, ReferenceDataGenerator.LED) + val HDC_KEY_DATE = generateKeyDates(LocalDate.now().plusDays(2), CURRENT_CUSTODY, ReferenceDataGenerator.HDE) + val PAROLE_KEY_DATE = generateKeyDates(LocalDate.now().plusDays(3), CURRENT_CUSTODY, ReferenceDataGenerator.PED) + val SENTENCE_KEY_DATE = generateKeyDates(LocalDate.now().plusDays(4), CURRENT_CUSTODY, ReferenceDataGenerator.SED) + val EXPECTED_RELEASE_KEY_DATE = + generateKeyDates(LocalDate.now().plusDays(5), CURRENT_CUSTODY, ReferenceDataGenerator.EXP) + val SUPERVISION_KEY_DATE = + generateKeyDates(LocalDate.now().plusDays(6), CURRENT_CUSTODY, ReferenceDataGenerator.PSSED) + val HANDOVER_START_KEY_DATE = + generateKeyDates(LocalDate.now().plusDays(7), CURRENT_CUSTODY, ReferenceDataGenerator.POM1) + val HANDOVER_KEY_DATE = generateKeyDates(LocalDate.now().plusDays(8), CURRENT_CUSTODY, ReferenceDataGenerator.POM2) + fun generateSentence( event: Event, - startDate: ZonedDateTime, - disposalType: ReferenceData, + startDate: LocalDate, + disposalType: DisposalType, custody: Custody? = null, endDate: ZonedDateTime? = null, - terminationDate: ZonedDateTime? = null, + terminationDate: LocalDate? = null, entryLength: Long? = null, entryLengthUnits: ReferenceData? = null, lengthInDays: Long? = null, terminationReason: ReferenceData? = null, upw: Boolean = true, + effectiveLength: Long? = null, + entryLengthUnits2: ReferenceData? = null, + length2: Long? = null, + length: Long? = null, + enteredSentenceEndDate: LocalDate? = null, active: Boolean = true, softDeleted: Boolean = false, + unpaidWorkDetails: UpwDetails? = null, id: Long = IdGenerator.getAndIncrement() ) = Disposal( event, @@ -49,62 +111,178 @@ object SentenceGenerator { lengthInDays, terminationReason, upw, + effectiveLength, + entryLengthUnits2, + length2, + length, + enteredSentenceEndDate, active, softDeleted, + unpaidWorkDetails, id ) fun generateEvent( person: Person, + mainOffence: MainOffence? = null, + referralDate: LocalDate, inBreach: Boolean = false, + breachDate: LocalDate? = null, + court: Court? = null, active: Boolean = true, softDeleted: Boolean = false, id: Long = IdGenerator.getAndIncrement() - ) = Event(person, inBreach, LocalDate.now(), null, active, softDeleted, id) + ) = Event( + person, + mainOffence, + inBreach, + breachDate, + LocalDate.now(), + null, + active, + softDeleted, + id, + "1", + 2, + referralDate, + court = court + ) fun generateOrderManager( event: Event, - staff: Staff, + staff: Staff? = null, + probationArea: ProbationAreaEntity, + allocatedDate: ZonedDateTime, + endDate: ZonedDateTime, + team: Team? = null, active: Boolean = true, softDeleted: Boolean = false, id: Long = IdGenerator.getAndIncrement() - ) = OrderManager(event, staff, active, softDeleted, id) + ) = OrderManager(event, staff, active, probationArea, team, allocatedDate, endDate, softDeleted, id) fun generateCourtAppearance( event: Event, outcome: Outcome, + appearanceDate: ZonedDateTime, softDeleted: Boolean = false, id: Long = IdGenerator.getAndIncrement() - ) = CourtAppearance(event, outcome, DocumentEntityGenerator.COURT.courtId, softDeleted, id) + ) = CourtAppearance( + event, + outcome, + appearanceDate, + softDeleted, + ReferenceDataGenerator.TRIAL, + CourtGenerator.BHAM, + PersonGenerator.CURRENTLY_MANAGED, + id + ) fun generateCustody( disposal: Disposal, custodialStatus: ReferenceData, + prisonerNumber: String, + institution: Institution, id: Long = IdGenerator.getAndIncrement() - ) = Custody(disposal, custodialStatus, id = id) + ) = Custody(disposal, custodialStatus, prisonerNumber = prisonerNumber, institution = institution, id = id) + + val MAIN_OFFENCE = + generateOffence( + code = "00303", + description = "Main Offence", + offenceCategory = ReferenceDataGenerator.VIOLENCE, + mainCategoryCode = "003", + mainCategoryDescription = "Threats, conspiracy, or incitement to murder", + mainCategoryAbbreviation = "Threats, conspiracy, or incitement to murder", + subCategoryCode = "03", + subCategoryDescription = "Assisting offender by impeding his apprehension or prosecution in a case of murder", + form20Code = "21" + ) + val ADDITIONAL_OFFENCE = + generateOffence( + code = "00701", + description = "Additional Offence", + offenceCategory = ReferenceDataGenerator.VIOLENCE, + mainCategoryCode = "007", + mainCategoryDescription = "Endangering life at sea", + mainCategoryAbbreviation = "Endangering life at sea", + subCategoryCode = "01", + subCategoryDescription = "Sending unseaworthy ship to sea", + form20Code = "2" + ) + val MAIN_OFFENCE_DEFAULT = + generateMainOffence( + CURRENTLY_MANAGED, + MAIN_OFFENCE, + LocalDate.now(), + offenceCount = 1, + PersonGenerator.CURRENTLY_MANAGED.id, + ZonedDateTime.of(LocalDate.now().minusDays(3), LocalTime.NOON, TIME_ZONE), + ZonedDateTime.of(LocalDate.now().plusDays(1), LocalTime.NOON, TIME_ZONE), + ) + + val ADDITIONAL_OFFENCE_DEFAULT = generateAdditionalOffence( + CURRENTLY_MANAGED, + ADDITIONAL_OFFENCE, + LocalDate.now(), + ZonedDateTime.of(LocalDate.now().minusMonths(1), LocalTime.NOON, TIME_ZONE), + ZonedDateTime.of(LocalDate.now().plusMonths(1), LocalTime.NOON, TIME_ZONE), + ) - val MAIN_OFFENCE = SentenceGenerator.generateOffence("Main Offence") - val ADDITIONAL_OFFENCE = SentenceGenerator.generateOffence("Additional Offence") fun generateOffence( + code: String, description: String, + offenceCategory: ReferenceData, + mainCategoryCode: String, + mainCategoryDescription: String, + mainCategoryAbbreviation: String, + subCategoryCode: String, + subCategoryDescription: String, + abbreviation: String? = null, + form20Code: String? = null, + subCategoryAbbreviation: String? = null, + cjitCode: String? = null, id: Long = IdGenerator.getAndIncrement() - ) = Offence(id, description) + ) = + Offence( + id, + offenceCategory, + code, + description, + abbreviation, + mainCategoryCode, + mainCategoryDescription, + mainCategoryAbbreviation, + subCategoryCode, + subCategoryDescription, + form20Code, + subCategoryAbbreviation, + cjitCode + ) fun generateMainOffence( event: Event, offence: Offence, date: LocalDate, + offenceCount: Long, + offenderId: Long, + created: ZonedDateTime, + updated: ZonedDateTime, + tics: Long? = null, + verdict: String? = null, id: Long = IdGenerator.getAndIncrement(), softDeleted: Boolean = false - ) = MainOffence(id, event, offence, date, softDeleted) + ) = MainOffence(id, event, offence, date, offenceCount, tics, verdict, offenderId, created, updated, softDeleted) fun generateAdditionalOffence( event: Event, offence: Offence, date: LocalDate, + created: ZonedDateTime, + updated: ZonedDateTime, id: Long = IdGenerator.getAndIncrement(), - softDeleted: Boolean = false - ) = AdditionalOffence(event, offence, date, softDeleted, id) + softDeleted: Boolean = false, + offenceCount: Long? = null, + ) = AdditionalOffence(event, offence, date, softDeleted, offenceCount, created, updated, id) fun generateRequirement( id: Long = IdGenerator.getAndIncrement(), @@ -169,4 +347,7 @@ object SentenceGenerator { softDeleted = false, id = id ) + + fun generateKeyDates(date: LocalDate, custody: Custody, keyDateType: ReferenceData) = + KeyDate(IdGenerator.getAndIncrement(), date, custody, keyDateType) } diff --git a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt index 41aaa24533..d6ed64e8a4 100644 --- a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt +++ b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt @@ -1,5 +1,6 @@ package uk.gov.justice.digital.hmpps.data.generator +import uk.gov.justice.digital.hmpps.integrations.delius.entity.ReferenceData import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.Officer import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.OfficerPk import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.PartitionArea @@ -10,7 +11,8 @@ object StaffGenerator { val UNALLOCATED = generate("N01UATU") val ALLOCATED = generate("N01ABBA") val OFFICER = generateOfficer() - fun generate(code: String, id: Long = IdGenerator.getAndIncrement()) = Staff(code, "Bob", "Micheal", "Smith", id) + fun generate(code: String, id: Long = IdGenerator.getAndIncrement(), grade: ReferenceData? = null) = + Staff(code, "Bob", "Micheal", "Smith", grade, id) fun generateOfficer() = Officer( diff --git a/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/UnpaidWorkGenerator.kt b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/UnpaidWorkGenerator.kt new file mode 100644 index 0000000000..53ff6a1ddd --- /dev/null +++ b/projects/court-case-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/UnpaidWorkGenerator.kt @@ -0,0 +1,19 @@ +package uk.gov.justice.digital.hmpps.data.generator + +import uk.gov.justice.digital.hmpps.data.generator.SentenceGenerator.CURRENT_SENTENCE +import uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity.UpwAppointment +import uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity.UpwDetails + +object UnpaidWorkGenerator { + + val UNPAID_WORK_DETAILS_1 = + UpwDetails(IdGenerator.getAndIncrement(), CURRENT_SENTENCE, 0, ReferenceDataGenerator.HOURS_WORKED) + + val APPT1 = UpwAppointment(IdGenerator.getAndIncrement(), 3, "Y", "Y", 0, UNPAID_WORK_DETAILS_1) + val APPT2 = UpwAppointment(IdGenerator.getAndIncrement(), 4, "Y", "Y", 1, UNPAID_WORK_DETAILS_1) + val APPT3 = UpwAppointment(IdGenerator.getAndIncrement(), 0, "N", "N", 1, UNPAID_WORK_DETAILS_1) + val APPT4 = UpwAppointment(IdGenerator.getAndIncrement(), 0, "N", "Y", 1, UNPAID_WORK_DETAILS_1) + val APPT5 = UpwAppointment(IdGenerator.getAndIncrement(), 0, "N", "Y", 1, UNPAID_WORK_DETAILS_1) + val APPT6 = UpwAppointment(IdGenerator.getAndIncrement(), 0, null, null, 1, UNPAID_WORK_DETAILS_1) + val APPT7 = UpwAppointment(IdGenerator.getAndIncrement(), 0, "Y", "Y", 0, UNPAID_WORK_DETAILS_1) +} \ No newline at end of file diff --git a/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ConvictionIntegrationTest.kt b/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ConvictionIntegrationTest.kt new file mode 100644 index 0000000000..0761f024de --- /dev/null +++ b/projects/court-case-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ConvictionIntegrationTest.kt @@ -0,0 +1,248 @@ +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.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT +import org.springframework.test.web.servlet.MockMvc +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.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.CourtGenerator.BHAM +import uk.gov.justice.digital.hmpps.data.generator.CourtGenerator.PROBATIONARE_AREA +import uk.gov.justice.digital.hmpps.data.generator.DisposalTypeGenerator.CURFEW_ORDER +import uk.gov.justice.digital.hmpps.data.generator.InstitutionGenerator.WSIHMP +import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator +import uk.gov.justice.digital.hmpps.data.generator.ReferenceDataGenerator +import uk.gov.justice.digital.hmpps.data.generator.SentenceGenerator +import uk.gov.justice.digital.hmpps.data.generator.SentenceGenerator.ADDITIONAL_OFFENCE +import uk.gov.justice.digital.hmpps.data.generator.SentenceGenerator.COURT_APPEARANCE +import uk.gov.justice.digital.hmpps.data.generator.SentenceGenerator.CURRENT_ORDER_MANAGER +import uk.gov.justice.digital.hmpps.data.generator.SentenceGenerator.CURRENT_SENTENCE +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.test.MockMvcExtensions.contentAsJson +import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.withToken +import java.time.LocalDate + +@AutoConfigureMockMvc +@SpringBootTest(webEnvironment = RANDOM_PORT) +internal class ConvictionIntegrationTest { + @Autowired + lateinit var mockMvc: MockMvc + + @Test + fun `API call probation record not found`() { + mockMvc + .perform(get("/probation-case/A123456/convictions/1").withToken()) + .andExpect(status().isNotFound) + .andExpect(jsonPath("$.message").value("Person with crn of A123456 not found")) + } + + @Test + fun `API call sentence not found`() { + val crn = PersonGenerator.CURRENTLY_MANAGED.crn + + mockMvc + .perform(get("/probation-case/$crn/convictions/3").withToken()) + .andExpect(status().isNotFound) + .andExpect(jsonPath("$.message").value("Conviction with ID 3 for Offender with crn C123456 not found")) + } + + @Test + fun `API call retuns sentence and custodial status information by crn convictionId`() { + val crn = PersonGenerator.CURRENTLY_MANAGED.crn + val event = SentenceGenerator.CURRENTLY_MANAGED + val mainOffence = SentenceGenerator.MAIN_OFFENCE_DEFAULT + val additionalOffence = SentenceGenerator.ADDITIONAL_OFFENCE_DEFAULT + + val expectedMainOffenceDetail = + OffenceDetail( + MAIN_OFFENCE.code, + MAIN_OFFENCE.description, + MAIN_OFFENCE.abbreviation, + MAIN_OFFENCE.mainCategoryCode, + MAIN_OFFENCE.mainCategoryDescription, + MAIN_OFFENCE.mainCategoryAbbreviation, + MAIN_OFFENCE.ogrsOffenceCategory.description, + MAIN_OFFENCE.subCategoryCode, + MAIN_OFFENCE.subCategoryDescription, + MAIN_OFFENCE.form20Code, + MAIN_OFFENCE.subCategoryAbbreviation, + MAIN_OFFENCE.cjitCode + ) + val expectedAdditionalOffenceDetail = + OffenceDetail( + ADDITIONAL_OFFENCE.code, + ADDITIONAL_OFFENCE.description, + ADDITIONAL_OFFENCE.abbreviation, + ADDITIONAL_OFFENCE.mainCategoryCode, + ADDITIONAL_OFFENCE.mainCategoryDescription, + ADDITIONAL_OFFENCE.mainCategoryAbbreviation, + ADDITIONAL_OFFENCE.ogrsOffenceCategory.description, + ADDITIONAL_OFFENCE.subCategoryCode, + ADDITIONAL_OFFENCE.subCategoryDescription, + ADDITIONAL_OFFENCE.form20Code, + ADDITIONAL_OFFENCE.subCategoryAbbreviation, + ADDITIONAL_OFFENCE.cjitCode + ) + val expectedOffences = listOf( + Offence( + mainOffence.id, + mainOffence = true, + expectedMainOffenceDetail, + mainOffence.date, + mainOffence.offenceCount, + mainOffence.tics, + mainOffence.verdict, + mainOffence.offenderId, + mainOffence.created, + mainOffence.updated + ), + Offence( + additionalOffence.id, + mainOffence = false, + expectedAdditionalOffenceDetail, + additionalOffence.date, + additionalOffence.offenceCount, + tics = null, + verdict = null, + PersonGenerator.CURRENTLY_MANAGED.id, + additionalOffence.created, + additionalOffence.updated + ) + ) + val expectedSentence = Sentence( + CURRENT_SENTENCE.id, + CURRENT_SENTENCE.disposalType.description, + CURRENT_SENTENCE.entryLength, + CURRENT_SENTENCE.entryLengthUnit?.description, + CURRENT_SENTENCE.length2, + CURRENT_SENTENCE.entryLength2Unit?.description, + CURRENT_SENTENCE.length, + CURRENT_SENTENCE.effectiveLength, + CURRENT_SENTENCE.lengthInDays, + CURRENT_SENTENCE.enteredSentenceEndDate, + UnpaidWork( + UNPAID_WORK_DETAILS_1.upwLengthMinutes, 7, + Appointments(7, 3, 2, 1, 1), + ReferenceDataGenerator.HOURS_WORKED.description + ), + CURRENT_SENTENCE.startDate, + sentenceType = KeyValue(CURFEW_ORDER.sentenceType, CURFEW_ORDER.description), + additionalSentences = listOf( + AdditionalSentence( + SENTENCE_DISQ.id, + KeyValue(SENTENCE_DISQ.type.code, SENTENCE_DISQ.type.description), + SENTENCE_DISQ.amount, + SENTENCE_DISQ.length, + SENTENCE_DISQ.notes + ) + ), + failureToComplyLimit = CURFEW_ORDER.failureToComplyLimit, + cja2003Order = CURFEW_ORDER.cja2003Order, + legacyOrder = CURFEW_ORDER.legacyOrder + ) + val expectedResponse = Conviction( + event.id, event.eventNumber, + event.active, + event.inBreach, + 2, + event.breachEnd, + false, + event.convictionDate, + event.referralDate, + expectedOffences, + expectedSentence, + KeyValue("101", "Adjourned - Pre-Sentence Report"), + Custody( + "FD1234", + Institution( + WSIHMP.id.institutionId, + WSIHMP.id.establishment, + WSIHMP.code, + WSIHMP.description, + WSIHMP.institutionName, + KeyValue(WSIHMP.establishmentType.code, WSIHMP.establishmentType.description), + WSIHMP.private, + WSIHMP.nomisCdeCode + ), + CustodyRelatedKeyDates( + LocalDate.now(), + LocalDate.now().plusDays(1), + LocalDate.now().plusDays(2), + LocalDate.now().plusDays(3), + LocalDate.now().plusDays(4), + LocalDate.now().plusDays(5), + LocalDate.now().plusDays(6), + LocalDate.now().plusDays(7), + LocalDate.now().plusDays(8), + ), + KeyValue( + ReferenceDataGenerator.CUSTODIAL_STATUS.code, + ReferenceDataGenerator.CUSTODIAL_STATUS.description + ), + LocalDate.now() + ), + Court( + BHAM.id, + BHAM.code, + BHAM.selectable, + BHAM.courtName, + BHAM.telephoneNumber, + BHAM.faxNumber, + BHAM.buildingName, + BHAM.street, + BHAM.locality, + BHAM.town, + BHAM.county, + BHAM.postcode, + BHAM.country, + BHAM.courtTypeId, + BHAM.createdDatetime, + BHAM.lastUpdatedDatetime, + BHAM.probationAreaId, + BHAM.secureEmailAddress, + KeyValue(BHAM.probationArea.code, BHAM.probationArea.description), + KeyValue(BHAM.courtType.code, BHAM.courtType.description) + ), + CourtAppearanceBasic( + COURT_APPEARANCE.id, + COURT_APPEARANCE.appearanceDate, + COURT_APPEARANCE.court.code, + COURT_APPEARANCE.court.courtName, + KeyValue(COURT_APPEARANCE.appearanceType.code, COURT_APPEARANCE.appearanceType.description), + COURT_APPEARANCE.person.crn + ), + listOf( + OrderManager( + PROBATIONARE_AREA.id, + null, + CURRENT_ORDER_MANAGER.id, + ALLOCATED.getName(), + ALLOCATED.code, + CURRENT_ORDER_MANAGER.allocationDate, + CURRENT_ORDER_MANAGER.endDate, + null, + null, + PROBATIONARE_AREA.code + ) + ) + ) + + val response = mockMvc + .perform(get("/probation-case/$crn/convictions/${event.id}").withToken()) + .andExpect(status().is2xxSuccessful) + .andDo(print()) + .andReturn().response.contentAsJson() + + assertEquals(expectedResponse, response) + } +} \ No newline at end of file diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/conviction/Conviction.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/conviction/Conviction.kt new file mode 100644 index 0000000000..63707bff97 --- /dev/null +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/conviction/Conviction.kt @@ -0,0 +1,175 @@ +package uk.gov.justice.digital.hmpps.api.model.conviction + +import uk.gov.justice.digital.hmpps.api.model.KeyValue +import java.math.BigDecimal +import java.time.LocalDate +import java.time.ZonedDateTime + +data class Conviction( + val convictionId: Long, + val index: String, + val active: Boolean, + val inBreach: Boolean, + val failureToComplyCount: Long, + val breachEnd: LocalDate?, + val awaitingPsr: Boolean = false, + val convictionDate: LocalDate, + val referralDate: LocalDate, + val offences: List, + val sentence: Sentence?, + val latestCourtAppearanceOutcome: KeyValue?, + val custody: Custody? = null, + val responsibleCourt: Court?, + val courtAppearance: CourtAppearanceBasic?, + val orderManagers: List? +) + +data class Offence( + val offenceId: Long, + val mainOffence: Boolean, + val detail: OffenceDetail, + val offenceDate: LocalDate?, + val offenceCount: Long?, + val tics: Long?, + val verdict: String?, + val offenderId: Long, + val createdDatetime: ZonedDateTime, + val lastUpdatedDatetime: ZonedDateTime, +) + +data class OffenceDetail( + val code: String, + val description: String, + val abbreviation: String?, + val mainCategoryCode: String, + val mainCategoryDescription: String, + val mainCategoryAbbreviation: String, + val ogrsOffenceCategory: String, + val subCategoryCode: String, + val subCategoryDescription: String, + val form20Code: String?, + val subCategoryAbbreviation: String?, + val cjitCode: String? +) + +data class Sentence( + val sentenceId: Long, + val description: String, + val originalLength: Long?, + val originalLengthUnits: String?, + val secondLength: Long?, + val secondLengthUnits: String?, + val defaultLength: Long?, + val effectiveLength: Long?, + val lengthInDays: Long?, + val expectedSentenceEndDate: LocalDate?, + val unpaidWork: UnpaidWork?, + val startDate: LocalDate, + val terminationDate: LocalDate? = null, + val terminationReason: String? = null, + val sentenceType: KeyValue, + val additionalSentences: List, + val failureToComplyLimit: Long?, + val cja2003Order: Boolean, + val legacyOrder: Boolean, +) + +data class UnpaidWork( + val minutesOrdered: Long, + val minutesCompleted: Long, + val appointments: Appointments, + val status: String +) + +data class Appointments( + val total: Long, + val attended: Long, + val acceptableAbsences: Long, + val unacceptableAbsences: Long, + val noOutcomeRecorded: Long, +) + +data class AdditionalSentence( + val additionalSentenceId: Long, + val type: KeyValue, + val amount: BigDecimal?, + val length: Long?, + val notes: String?, +) + +data class Custody( + val bookingNumber: String, + val institution: Institution, + val keyDates: CustodyRelatedKeyDates, + val status: KeyValue, + val sentenceStartDate: LocalDate +) + +data class Institution( + val institutionId: Long, + val isEstablishment: Boolean, + val code: String, + val description: String, + val institutionName: String, + val establishmentType: KeyValue, + val isPrivate: Boolean?, + val nomsPrisonInstitutionCode: String? +) + +data class CustodyRelatedKeyDates( + val conditionalReleaseDate: LocalDate?, + val licenceExpiryDate: LocalDate?, + val hdcEligibilityDate: LocalDate?, + val paroleEligibilityDate: LocalDate?, + val sentenceExpiryDate: LocalDate?, + val expectedReleaseDate: LocalDate?, + val postSentenceSupervisionEndDate: LocalDate?, + val expectedPrisonOffenderManagerHandoverStartDate: LocalDate?, + val expectedPrisonOffenderManagerHandoverDate: LocalDate?, +) + +data class Court( + val courtId: Long, + val code: String, + val selectable: Boolean, + val courtName: String?, + val telephoneNumber: String?, + val fax: String?, + val buildingName: String, + val street: String?, + val locality: String?, + val town: String?, + val county: String?, + val postcode: String?, + val country: String?, + val courtTypeId: Long, + val createdDatetime: ZonedDateTime, + val lastUpdatedDatetime: ZonedDateTime, + val probationAreaId: Long, + val secureEmailAddress: String?, + val probationArea: KeyValue, + val courtType: KeyValue +) + +data class CourtAppearanceBasic( + val courtAppearanceId: Long, + val appearanceDate: ZonedDateTime, + val courtCode: String, + val courtName: String?, + val appearanceType: KeyValue, + val crn: String +) + +data class OrderManager( + val probationAreaId: Long, + val teamId: Long?, + val officerId: Long, + val name: String?, + val staffCode: String?, + val dateStartOfAllocation: ZonedDateTime, + val dateEndOfAllocation: ZonedDateTime, + val gradeCode: String?, + val teamCode: String?, + val probationAreaCode: String +) + 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 new file mode 100644 index 0000000000..42174a660a --- /dev/null +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/ConvictionResource.kt @@ -0,0 +1,20 @@ +package uk.gov.justice.digital.hmpps.api.resource + +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.integrations.delius.service.ConvictionService + +@RestController +@RequestMapping("probation-case/{crn}/convictions") +class ConvictionResource(private val convictionService: ConvictionService) { + + @PreAuthorize("hasRole('PROBATION_API__COURT_CASE__CASE_DETAIL')") + @GetMapping("/{convictionId}") + fun getConvictionForOffenderByCrnAndConvictionId( + @PathVariable crn: String, + @PathVariable convictionId: Long + ) = convictionService.getConvictionFor(crn, convictionId) +} diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/courtappearance/entity/CourtAppearance.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/courtappearance/entity/CourtAppearance.kt index f20441de41..d5eee45d48 100644 --- a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/courtappearance/entity/CourtAppearance.kt +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/courtappearance/entity/CourtAppearance.kt @@ -1,20 +1,17 @@ package uk.gov.justice.digital.hmpps.integrations.delius.event.courtappearance.entity -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.Id -import jakarta.persistence.JoinColumn -import jakarta.persistence.ManyToOne -import jakarta.persistence.OneToMany -import jakarta.persistence.OneToOne -import jakarta.persistence.Table +import jakarta.persistence.* 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.entity.ReferenceData import uk.gov.justice.digital.hmpps.integrations.delius.event.entity.Event +import uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity.Court +import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.Person import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.Staff import java.time.LocalDate +import java.time.ZonedDateTime @Entity @Immutable @@ -29,16 +26,31 @@ class CourtAppearance( @JoinColumn(name = "outcome_id") val outcome: Outcome, - @Column(name = "court_id") - val courtId: Long, + val appearanceDate: ZonedDateTime, @Column(name = "soft_deleted", columnDefinition = "number") - var softDeleted: Boolean, + val softDeleted: Boolean, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "appearance_type_id") + val appearanceType: ReferenceData, + + @ManyToOne + @JoinColumn(name = "court_id") + val court: Court, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "offender_id") + val person: Person, @Id @Column(name = "court_appearance_id") val id: Long -) +) { + fun isSentenceing(): Boolean { + return appearanceType.code == "S" + } +} interface CourtReportRepository : JpaRepository { @@ -59,12 +71,15 @@ class Outcome( @Column(name = "code_value") val code: String, + @Column(name = "code_description") + val description: String, + @Id @Column(name = "standard_reference_list_id") val id: Long ) { - enum class Code(val value: String) { - AWAITING_PSR("101") + enum class Code(val value: String, val description: String) { + AWAITING_PSR("101", "Adjourned - Pre-Sentence Report") } } diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/entity/Event.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/entity/Event.kt index a39208ec73..9423774c78 100644 --- a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/entity/Event.kt +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/entity/Event.kt @@ -1,20 +1,20 @@ package uk.gov.justice.digital.hmpps.integrations.delius.event.entity -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.Id -import jakarta.persistence.JoinColumn -import jakarta.persistence.ManyToOne -import jakarta.persistence.OneToOne -import jakarta.persistence.Table +import jakarta.persistence.* 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.exception.NotFoundException +import uk.gov.justice.digital.hmpps.integrations.delius.event.courtappearance.entity.CourtAppearance +import uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity.Court import uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity.Disposal import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.Person +import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.ProbationAreaEntity import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.Staff +import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.Team import java.time.LocalDate +import java.time.ZonedDateTime @Immutable @Entity @@ -26,9 +26,15 @@ class Event( @JoinColumn(name = "offender_id", nullable = false) val person: Person, + @OneToOne(mappedBy = "event") + val mainOffence: MainOffence? = null, + @Column(name = "in_breach", columnDefinition = "number") val inBreach: Boolean, + @Column(name = "breach_end") + val breachEnd: LocalDate? = null, + @Column(name = "conviction_date") val convictionDate: LocalDate, @@ -43,13 +49,38 @@ class Event( @Id @Column(name = "event_id", nullable = false) - val id: Long -) + val id: Long, + + @Column(name = "event_number") + val eventNumber: String, + + @Column(name = "ftc_count", nullable = false) + val failureToComplyCount: Long, + + @Column(name = "referral_date", nullable = false) + val referralDate: LocalDate, + + @OneToMany(mappedBy = "event") + val additionalOffences: List = emptyList(), + + @OneToMany(mappedBy = "event") + val courtAppearances: List = emptyList(), + + @OneToMany(mappedBy = "event") + val orderManagers: List = emptyList(), + + @ManyToOne + @JoinColumn(name = "court_id") + val court: Court?, + + ) interface EventRepository : JpaRepository { fun findAllByPerson(person: Person): List + fun findByPersonAndId(person: Person, id: Long): Event? + @Query( """ select @@ -67,6 +98,9 @@ interface EventRepository : JpaRepository { fun awaitingPSR(eventId: Long): Int } +fun EventRepository.getByPersonAndEventNumber(person: Person, eventId: Long) = findByPersonAndId(person, eventId) + ?: throw NotFoundException("Conviction with ID $eventId for Offender with crn ${person.crn} not found") + @Entity @Immutable @SQLRestriction("soft_deleted = 0 and active_flag = 1") @@ -79,11 +113,23 @@ class OrderManager( @ManyToOne @JoinColumn(name = "allocation_staff_id") - val staff: Staff, + val staff: Staff?, @Column(name = "active_flag", columnDefinition = "number") val active: Boolean, + @ManyToOne + @JoinColumn(name = "probation_area_id") + val probationArea: ProbationAreaEntity, + + @OneToOne + @JoinColumn(name = "allocation_team_id") + val team: Team?, + + val allocationDate: ZonedDateTime, + + val endDate: ZonedDateTime, + @Column(columnDefinition = "number") val softDeleted: Boolean, diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/entity/MainOffence.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/entity/MainOffence.kt index 06a663c8ff..59133106b2 100644 --- a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/entity/MainOffence.kt +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/entity/MainOffence.kt @@ -1,16 +1,12 @@ package uk.gov.justice.digital.hmpps.integrations.delius.event.entity -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.Id -import jakarta.persistence.JoinColumn -import jakarta.persistence.ManyToOne -import jakarta.persistence.OneToOne -import jakarta.persistence.Table +import jakarta.persistence.* import org.hibernate.annotations.Immutable import org.hibernate.annotations.SQLRestriction import org.springframework.data.jpa.repository.JpaRepository +import uk.gov.justice.digital.hmpps.integrations.delius.entity.ReferenceData import java.time.LocalDate +import java.time.ZonedDateTime @Immutable @Entity @@ -29,9 +25,25 @@ class MainOffence( @JoinColumn(name = "offence_id") val offence: Offence, - @Column(name = "offence_date") + @Column(name = "offence_date", nullable = false) val date: LocalDate, + @Column(nullable = false) + val offenceCount: Long, + + val tics: Long?, + + val verdict: String?, + + @Column(nullable = false) + val offenderId: Long, + + @Column(name = "created_datetime", nullable = false) + val created: ZonedDateTime, + + @Column(name = "last_updated_datetime", nullable = false) + val updated: ZonedDateTime, + @Column(updatable = false, columnDefinition = "NUMBER") val softDeleted: Boolean = false ) @@ -60,6 +72,14 @@ class AdditionalOffence( @Column(columnDefinition = "number") val softDeleted: Boolean, + val offenceCount: Long? = null, + + @Column(name = "created_datetime", nullable = false) + val created: ZonedDateTime, + + @Column(name = "last_updated_datetime", nullable = false) + val updated: ZonedDateTime, + @Id @Column(name = "additional_offence_id") val id: Long @@ -76,5 +96,31 @@ class Offence( @Id @Column(name = "offence_id") val id: Long, - val description: String + + @JoinColumn(name = "ogrs_offence_category_id", nullable = false) + @ManyToOne + val ogrsOffenceCategory: ReferenceData, + + val code: String, + + val description: String, + + val abbreviation: String? = null, + + val mainCategoryCode: String, + + val mainCategoryDescription: String, + + val mainCategoryAbbreviation: String, + + val subCategoryCode: String, + + val subCategoryDescription: String, + + val form20Code: String? = null, + + val subCategoryAbbreviation: String? = null, + + val cjitCode: String? = null + ) diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/sentence/entity/Court.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/sentence/entity/Court.kt new file mode 100644 index 0000000000..23ebd63534 --- /dev/null +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/sentence/entity/Court.kt @@ -0,0 +1,61 @@ +package uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity + +import jakarta.persistence.* +import org.hibernate.annotations.Immutable +import org.hibernate.type.YesNoConverter +import uk.gov.justice.digital.hmpps.integrations.delius.entity.ReferenceData +import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.ProbationAreaEntity +import java.time.ZonedDateTime + +@Entity(name = "conviction_court") +@Immutable +class Court( + @Id + @Column(name = "court_id") + val id: Long, + + val code: String, + + @Convert(converter = YesNoConverter::class) + val selectable: Boolean, + + val courtName: String?, + + val telephoneNumber: String?, + + val faxNumber: String?, + + val buildingName: String, + + val street: String?, + + val locality: String?, + + val town: String?, + + val county: String?, + + val postcode: String?, + + val country: String?, + + @Column(name = "court_type_id", updatable = false, insertable = false) + val courtTypeId: Long, + + val createdDatetime: ZonedDateTime, + + val lastUpdatedDatetime: ZonedDateTime, + + @Column(name = "probation_area_id", updatable = false, insertable = false) + val probationAreaId: Long, + + val secureEmailAddress: String?, + + @ManyToOne + @JoinColumn(name = "probation_area_id") + val probationArea: ProbationAreaEntity, + + @ManyToOne + @JoinColumn(name = "court_type_id") + val courtType: ReferenceData +) \ No newline at end of file diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/sentence/entity/Disposal.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/sentence/entity/Disposal.kt index ee389539ee..b415caef3c 100644 --- a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/sentence/entity/Disposal.kt +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/sentence/entity/Disposal.kt @@ -1,18 +1,15 @@ package uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.Id -import jakarta.persistence.JoinColumn -import jakarta.persistence.ManyToOne -import jakarta.persistence.OneToOne -import jakarta.persistence.Table +import jakarta.persistence.* import org.hibernate.annotations.Immutable import org.hibernate.annotations.SQLRestriction +import org.hibernate.type.YesNoConverter import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Query 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.ZonedDateTime @Entity @@ -27,17 +24,17 @@ class Disposal( val custody: Custody?, @Column(name = "disposal_date") - val startDate: ZonedDateTime, + val startDate: LocalDate, @ManyToOne - @JoinColumn(name = "disposal_type_id") - val disposalType: ReferenceData, + @JoinColumn(name = "disposal_type_id", nullable = false) + val disposalType: DisposalType, @Column(name = "notional_end_date") val endDate: ZonedDateTime? = null, @Column - val terminationDate: ZonedDateTime? = null, + val terminationDate: LocalDate? = null, @Column(name = "entry_length") val entryLength: Long? = null, @@ -56,12 +53,27 @@ class Disposal( @Column(name = "upw", columnDefinition = "number") val upw: Boolean = false, + val effectiveLength: Long? = null, + + @ManyToOne + @JoinColumn(name = "entry_length_2_units_id") + val entryLength2Unit: ReferenceData? = null, + + val length2: Long? = null, + + val length: Long? = null, + + val enteredSentenceEndDate: LocalDate? = null, + @Column(name = "active_flag", columnDefinition = "number") val active: Boolean, @Column(columnDefinition = "number") val softDeleted: Boolean, + @OneToOne(mappedBy = "disposal") + val unpaidWorkDetails: UpwDetails? = null, + @Id @Column(name = "disposal_id") val id: Long @@ -78,6 +90,99 @@ interface DisposalRepository : JpaRepository { fun getByCrn(crn: String): List } +@Entity(name = "conviction_upw_details") +@Immutable +class UpwDetails( + @Id + @Column(name = "upw_details_id") + val id: Long, + + @OneToOne + @JoinColumn(name = "disposal_id") + val disposal: Disposal, + + val upwLengthMinutes: Long, + + @ManyToOne + @JoinColumn(name = "upw_status_id") + val status: ReferenceData +) + +@Entity(name = "conviction_upw_appointment") +@Immutable +class UpwAppointment( + @Id + @Column(name = "upw_appointment_id") + val id: Long, + + val minutesCredited: Long?, + + @Column(columnDefinition = "char(1)") + val attended: String?, + + @Column(columnDefinition = "char(1)") + val complied: String?, + + val softDeleted: Long, + + @JoinColumn(name = "upw_details_id") + @ManyToOne + val upwDetails: UpwDetails, +) + +interface UpwAppointmentRepository : JpaRepository { + + @Query( + """ + SELECT COALESCE(SUM(u.minutesCredited), 0) AS sumMinutes, + COUNT (u.id) AS totalAppointments, + COUNT (CASE WHEN u.attended = 'Y' THEN 1 END) AS attended, + COUNT (CASE WHEN u.attended = 'N' AND u.complied = 'Y' THEN 1 END) AS acceptableAbsence, + COUNT (CASE WHEN u.attended = 'N' AND u.complied = 'N' THEN 1 END) AS unacceptableAbsence, + COUNT (CASE WHEN u.attended IS NULL AND u.complied is NULL THEN 1 END) AS noOutcomeRecorded + FROM conviction_upw_appointment u + JOIN u.upwDetails upd + JOIN upd.disposal d + WHERE d.id = :id + """ + ) + fun getUnpaidTimeWorked(id: Long): Unpaid +} + +interface Unpaid { + val sumMinutes: Long + val totalAppointments: Long + val attended: Long + val acceptableAbsence: Long + val unacceptableAbsence: Long + val noOutcomeRecorded: Long +} + +@Entity +@Immutable +class DisposalType( + + @Id + @Column(name = "disposal_type_id") + val id: Long, + + val description: String, + + val sentenceType: String, + + @Column(name = "cja2003") + @Convert(converter = YesNoConverter::class) + val cja2003Order: Boolean = false, + + @Column(name = "pre_cja2003") + @Convert(converter = YesNoConverter::class) + val legacyOrder: Boolean = false, + + @Column(name = "ftc_limit") + val failureToComplyLimit: Long? = null + +) + @Entity @Immutable @SQLRestriction("soft_deleted = 0") @@ -93,6 +198,18 @@ class Custody( @Column(columnDefinition = "number") val softDeleted: Boolean = false, + val prisonerNumber: String, + + @ManyToOne + @JoinColumns( + JoinColumn(name = "institution_id", referencedColumnName = "institution_id"), + JoinColumn(name = "establishment", referencedColumnName = "establishment") + ) + val institution: Institution, + + @OneToMany(mappedBy = "custody") + val keyDates: List = listOf(), + @Id @Column(name = "custody_id") val id: Long @@ -152,3 +269,37 @@ interface CustodyRepository : JpaRepository interface PssRequirementRepository : JpaRepository { fun findAllByCustodyId(custodyId: Long): List } + +@Immutable +@Entity +@Table(name = "additional_sentence") +@SQLRestriction("soft_deleted = 0") +class AdditionalSentence( + @Id + @Column(name = "additional_sentence_id") + val id: Long, + + @ManyToOne + @JoinColumn(name = "event_id", nullable = false) + val event: Event, + + @ManyToOne + @JoinColumn(name = "additional_sentence_type_id", nullable = false) + val type: ReferenceData, + + @Column(name = "amount") + val amount: BigDecimal? = null, + + @Column(name = "length") + val length: Long? = null, + + @Column(name = "notes", columnDefinition = "clob") + val notes: String? = null, + + @Column(columnDefinition = "number") + val softDeleted: Boolean = false, +) + +interface AdditionalSentenceRepository : JpaRepository { + fun getAllByEventId(id: Long): List +} \ No newline at end of file diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/sentence/entity/Institution.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/sentence/entity/Institution.kt new file mode 100644 index 0000000000..706ac5208f --- /dev/null +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/sentence/entity/Institution.kt @@ -0,0 +1,41 @@ +package uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity + +import jakarta.persistence.* +import org.hibernate.annotations.Immutable +import org.hibernate.type.YesNoConverter +import uk.gov.justice.digital.hmpps.integrations.delius.entity.ReferenceData +import java.io.Serializable + +@Immutable +@Entity +@Table(name = "r_institution") +class Institution( + @EmbeddedId + val id: InstitutionId, + + @Column(nullable = false, columnDefinition = "char(6)") + val code: String, + + val description: String, + + val institutionName: String, + + @ManyToOne + @JoinColumn(name = "establishment_type_id") + val establishmentType: ReferenceData, + + @Column(columnDefinition = "number") + val private: Boolean? = false, + + val nomisCdeCode: String? +) + +@Embeddable +data class InstitutionId( + @Column(name = "institution_id") + val institutionId: Long, + + @Column(name = "establishment") + @Convert(converter = YesNoConverter::class) + val establishment: Boolean +) : Serializable \ No newline at end of file diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/sentence/entity/KeyDate.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/sentence/entity/KeyDate.kt new file mode 100644 index 0000000000..5e9f6c5915 --- /dev/null +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/event/sentence/entity/KeyDate.kt @@ -0,0 +1,25 @@ +package uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity + +import jakarta.persistence.* +import org.hibernate.annotations.Immutable +import uk.gov.justice.digital.hmpps.integrations.delius.entity.ReferenceData +import java.time.LocalDate + +@Immutable +@Entity +class KeyDate( + + @Id + @Column(name = "key_date_id") + val id: Long, + + val keyDate: LocalDate, + + @ManyToOne + @JoinColumn(name = "custody_id") + val custody: Custody, + + @ManyToOne + @JoinColumn(name = "key_date_type_id") + val keyDateType: ReferenceData +) \ No newline at end of file diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/provider/entity/Staff.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/provider/entity/Staff.kt index be7ed42c09..76638ab3ab 100644 --- a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/provider/entity/Staff.kt +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/provider/entity/Staff.kt @@ -1,12 +1,8 @@ package uk.gov.justice.digital.hmpps.integrations.delius.provider.entity -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.Id -import jakarta.persistence.JoinColumn -import jakarta.persistence.ManyToOne -import jakarta.persistence.Table +import jakarta.persistence.* import org.hibernate.annotations.Immutable +import uk.gov.justice.digital.hmpps.integrations.delius.entity.ReferenceData @Immutable @Entity @@ -25,6 +21,10 @@ class Staff( @Column val surname: String, + @ManyToOne + @JoinColumn(name = "staff_grade_id") + val grade: ReferenceData?, + @Id @Column(name = "staff_id") val id: Long @@ -32,6 +32,13 @@ class Staff( fun isUnallocated(): Boolean { return code.endsWith("U") } + + fun getName(): String { + return when { + forename2 == null -> "$forename $surname" + else -> "$forename $forename2 $surname" + } + } } @Immutable diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/ConvictionService.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/ConvictionService.kt new file mode 100644 index 0000000000..4f3cf1b82b --- /dev/null +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/ConvictionService.kt @@ -0,0 +1,267 @@ +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.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.* +import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.PersonRepository +import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.getPerson +import uk.gov.justice.digital.hmpps.integrations.delius.event.entity.Offence as OffenceEntity +import uk.gov.justice.digital.hmpps.integrations.delius.event.entity.OrderManager as OrderManagerEntity +import uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity.AdditionalSentence as AdditionalSentenceEntity +import uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity.Court as CourtEntity +import uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity.Custody as CustodyEntity +import uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity.Institution as InstitutionEntity + +@Service +class ConvictionService( + private val personRepository: PersonRepository, + private val eventRepository: EventRepository, + private val upwAppointmentRepository: UpwAppointmentRepository, + private val additionalSentenceRepository: AdditionalSentenceRepository +) { + fun getConvictionFor(crn: String, eventId: Long): Conviction? { + val person = personRepository.getPerson(crn) + val event = eventRepository.getByPersonAndEventNumber(person, eventId) + + return event.toConviction() + } + + fun Event.toConviction(): Conviction = + Conviction( + id, + eventNumber, + active, + inBreach, + failureToComplyCount, + breachEnd, + eventRepository.awaitingPSR(id) == 1, + convictionDate, + referralDate, + toOffences(), + disposal?.toSentence(id), + toLatestCourtAppearanceOutcome(), + disposal?.custody?.toCustody(), + court?.toCourt(), + toLatestOrSentencingCourtAppearanceOf(), + orderManagers.map { it.toOrderManager() } + ) + + fun Event.toOffences(): List { + val mainOffence = listOf(mainOffence!!.toOffence()) + val additionalOffences = additionalOffences.map { it.toOffence() } + return mainOffence + additionalOffences + } + + fun Event.toLatestCourtAppearanceOutcome(): KeyValue? { + courtAppearances.maxByOrNull { it.appearanceDate } + ?.let { return KeyValue(it.outcome.code, it.outcome.description) } + ?: return null + } + + fun Event.toLatestOrSentencingCourtAppearanceOf(): CourtAppearanceBasic? { + return courtAppearances.filter { it.isSentenceing() }.maxByOrNull { it.appearanceDate } + ?.let { return it.toCourtAppearanceBasic() } + ?: courtAppearances.maxByOrNull { it.appearanceDate }?.let { return it.toCourtAppearanceBasic() } + } + + fun CourtAppearance.toCourtAppearanceBasic(): CourtAppearanceBasic = + CourtAppearanceBasic( + id, + appearanceDate, + court.code, + court.courtName, + KeyValue(appearanceType.code, appearanceType.description), + person.crn + ) + + fun MainOffence.toOffence(): Offence = + Offence( + id, + mainOffence = true, + detail = offence.toOffenceDetail(), + offenceDate = date, + offenceCount = offenceCount, + tics = tics, + verdict = verdict, + offenderId = event.person.id, + createdDatetime = created, + lastUpdatedDatetime = updated + ) + + fun AdditionalOffence.toOffence(): Offence = + Offence( + id, + mainOffence = false, + detail = offence.toOffenceDetail(), + offenceDate = date, + offenceCount = offenceCount, + tics = null, + verdict = null, + offenderId = event.person.id, + createdDatetime = created, + lastUpdatedDatetime = updated + ) + + fun OffenceEntity.toOffenceDetail(): OffenceDetail = + OffenceDetail( + code, + description, + abbreviation, + mainCategoryCode, + mainCategoryDescription, + mainCategoryAbbreviation, + ogrsOffenceCategory.description, + subCategoryCode, + subCategoryDescription, + form20Code, + subCategoryAbbreviation, + cjitCode + ) + + fun Disposal.toSentence(eventId: Long): Sentence = + Sentence( + id, + disposalType.description, + entryLength, + entryLengthUnit?.description, + length2, + entryLength2Unit?.description, + length, + effectiveLength, + lengthInDays, + enteredSentenceEndDate, + unpaidWorkDetails?.toUnpaidWork(id), + startDate, + terminationDate, + terminationReason?.description, + KeyValue(disposalType.sentenceType, disposalType.description), + additionalSentenceRepository.getAllByEventId(eventId).map { it.toAdditionalSentence() }, + disposalType.failureToComplyLimit, + disposalType.cja2003Order, + disposalType.legacyOrder + ) + + fun UpwDetails.toUnpaidWork(disposalId: Long): UnpaidWork { + val unpaidWork = upwAppointmentRepository.getUnpaidTimeWorked(disposalId) + + return UnpaidWork( + upwLengthMinutes, + unpaidWork.sumMinutes, + Appointments( + unpaidWork.totalAppointments, + unpaidWork.attended, + unpaidWork.acceptableAbsence, + unpaidWork.unacceptableAbsence, + unpaidWork.noOutcomeRecorded + ), + status.description + ) + } + + fun AdditionalSentenceEntity.toAdditionalSentence(): AdditionalSentence = + AdditionalSentence(id, KeyValue(type.code, type.description), amount, length, notes) + + fun CustodyEntity.toCustody(): Custody = + Custody( + prisonerNumber, + institution.toInstitution(), + populateKeyDates(keyDates), + KeyValue(status.code, status.description), + disposal.startDate + ) + + fun InstitutionEntity.toInstitution(): Institution = + Institution( + id.institutionId, + id.establishment, + code, description, + institutionName, + KeyValue(establishmentType.code, establishmentType.description), + private, + nomisCdeCode + ) + + fun populateKeyDates(keyDates: List): CustodyRelatedKeyDates { + val conditionalReleaseDate = + keyDates.firstOrNull { it.keyDateType.code == KeyDateTypes.AUTOMATIC_CONDITIONAL_RELEASE_DATE.code } + val licenceExpiryDate = keyDates.firstOrNull { it.keyDateType.code == KeyDateTypes.LICENCE_EXPIRY_DATE.code } + val hdcEligibilityDate = keyDates.firstOrNull { it.keyDateType.code == KeyDateTypes.HDC_EXPECTED_DATE.code } + val paroleEligibilityDate = + keyDates.firstOrNull { it.keyDateType.code == KeyDateTypes.PAROLE_ELIGIBILITY_DATE.code } + val sentenceExpiryDate = keyDates.firstOrNull { it.keyDateType.code == KeyDateTypes.SENTENCE_EXPIRY_DATE.code } + val expectedReleaseDate = + keyDates.firstOrNull { it.keyDateType.code == KeyDateTypes.EXPECTED_RELEASE_DATE.code } + val postSentenceSupervisionEndDate = + keyDates.firstOrNull { it.keyDateType.code == KeyDateTypes.POST_SENTENCE_SUPERVISION_END_DATE.code } + val expectedPrisonOffenderManagerHandoverStartDate = + keyDates.firstOrNull { it.keyDateType.code == KeyDateTypes.POM_HANDOVER_START_DATE.code } + val expectedPrisonOffenderManagerHandoverDate = + keyDates.firstOrNull { it.keyDateType.code == KeyDateTypes.RO_HANDOVER_DATE.code } + + return CustodyRelatedKeyDates( + conditionalReleaseDate?.keyDate, + licenceExpiryDate?.keyDate, + hdcEligibilityDate?.keyDate, + paroleEligibilityDate?.keyDate, + sentenceExpiryDate?.keyDate, + expectedReleaseDate?.keyDate, + postSentenceSupervisionEndDate?.keyDate, + expectedPrisonOffenderManagerHandoverStartDate?.keyDate, + expectedPrisonOffenderManagerHandoverDate?.keyDate, + ) + } + + fun CourtEntity.toCourt(): Court = Court( + id, + code, + selectable, + courtName, + telephoneNumber, + faxNumber, + buildingName, + street, + locality, + town, + county, + postcode, + country, + courtTypeId, + createdDatetime, + lastUpdatedDatetime, + probationAreaId, + secureEmailAddress, + KeyValue(probationArea.code, probationArea.description), + KeyValue(courtType.code, courtType.description), + ) + + fun OrderManagerEntity.toOrderManager(): OrderManager = + OrderManager( + probationArea.id, + team?.id, + id, + staff?.getName(), + staff?.code, + allocationDate, + endDate, + staff?.grade?.code, + team?.code, + probationArea.code + ) +} + +enum class KeyDateTypes(val code: String) { + AUTOMATIC_CONDITIONAL_RELEASE_DATE("ACR"), + EXPECTED_RELEASE_DATE("EXP"), + HDC_EXPECTED_DATE("HDE"), + PAROLE_ELIGIBILITY_DATE("PED"), + POST_SENTENCE_SUPERVISION_END_DATE("PSSED"), + POM_HANDOVER_START_DATE("POM1"), + RO_HANDOVER_DATE("POM2"), + LICENCE_EXPIRY_DATE("LED"), + SENTENCE_EXPIRY_DATE("SED") +} + + diff --git a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/OffenderService.kt b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/OffenderService.kt index 1fb07e25d8..c87a6b3e70 100644 --- a/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/OffenderService.kt +++ b/projects/court-case-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/OffenderService.kt @@ -371,8 +371,8 @@ fun Disposal.sentenceOf() = Sentence( entryLength, entryLengthUnit?.description, lengthInDays, - terminationDate?.toLocalDate(), - startDate.toLocalDate(), + terminationDate, + startDate, endDate?.toLocalDate(), terminationReason?.description ) diff --git a/projects/dps-and-delius/tech-docs/Gemfile.lock b/projects/dps-and-delius/tech-docs/Gemfile.lock index 043512b75c..0982b087bc 100644 --- a/projects/dps-and-delius/tech-docs/Gemfile.lock +++ b/projects/dps-and-delius/tech-docs/Gemfile.lock @@ -145,7 +145,8 @@ GEM rb-inotify (0.10.1) ffi (~> 1.0) redcarpet (3.5.1) - rexml (3.2.5) + rexml (3.2.8) + strscan (>= 3.0.9) rouge (3.30.0) sass (3.4.25) sassc (2.4.0) @@ -154,6 +155,7 @@ GEM sprockets (4.2.0) concurrent-ruby (~> 1.0) rack (>= 2.2.4, < 4) + strscan (3.1.0) temple (0.10.0) thor (1.2.1) tilt (2.0.11) diff --git a/projects/soc-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt b/projects/soc-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt index 1b9c5c5a23..d017f057a2 100644 --- a/projects/soc-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt +++ b/projects/soc-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt @@ -32,6 +32,7 @@ class DataLoader( ProbationAreaGenerator.NON_SELECTABLE_PA, ProbationAreaGenerator.NON_SELECTABLE_BOROUGH, ProbationAreaGenerator.NON_SELECTABLE_LDU, + CourtAppearanceGenerator.DEFAULT_OUTCOME, CourtAppearanceGenerator.DEFAULT_CA_TYPE, CourtAppearanceGenerator.DEFAULT_COURT, CourtAppearanceGenerator.DEFAULT_PERSON, @@ -46,6 +47,7 @@ class DataLoader( ConvictionEventGenerator.OTHER_OFFENCE, ConvictionEventGenerator.DISPOSAL_TYPE, ConvictionEventGenerator.DISPOSAL, + ConvictionEventGenerator.COURT_APPEARANCE, DetailsGenerator.INSTITUTION, DetailsGenerator.RELIGION, DetailsGenerator.NATIONALITY, diff --git a/projects/soc-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ConvictionEventGenerator.kt b/projects/soc-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ConvictionEventGenerator.kt index 081170b17d..85cf21671a 100644 --- a/projects/soc-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ConvictionEventGenerator.kt +++ b/projects/soc-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ConvictionEventGenerator.kt @@ -1,5 +1,6 @@ package uk.gov.justice.digital.hmpps.data.generator +import uk.gov.justice.digital.hmpps.data.generator.CourtAppearanceGenerator.DEFAULT_OUTCOME import uk.gov.justice.digital.hmpps.entity.* import java.time.LocalDate @@ -54,6 +55,12 @@ object ConvictionEventGenerator { DEFAULT_EVENT, LocalDate.now() ) + val COURT_APPEARANCE = ConvictionCourtAppearanceEntity( + IdGenerator.getAndIncrement(), + DEFAULT_EVENT, + DEFAULT_OUTCOME, + LocalDate.now() + ) val PERSON_2 = ConvictionEventPerson( IdGenerator.getAndIncrement(), diff --git a/projects/soc-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/CourtAppearanceGenerator.kt b/projects/soc-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/CourtAppearanceGenerator.kt index 759be5a4de..eac4cb2f4d 100644 --- a/projects/soc-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/CourtAppearanceGenerator.kt +++ b/projects/soc-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/CourtAppearanceGenerator.kt @@ -1,10 +1,6 @@ package uk.gov.justice.digital.hmpps.data.generator -import uk.gov.justice.digital.hmpps.entity.Court -import uk.gov.justice.digital.hmpps.entity.CourtAppearanceEntity -import uk.gov.justice.digital.hmpps.entity.CourtAppearanceEventEntity -import uk.gov.justice.digital.hmpps.entity.CourtAppearancePerson -import uk.gov.justice.digital.hmpps.entity.ReferenceData +import uk.gov.justice.digital.hmpps.entity.* import java.time.LocalDate object CourtAppearanceGenerator { @@ -26,6 +22,11 @@ object CourtAppearanceGenerator { "T", "Trial/Adjournment" ) + val DEFAULT_OUTCOME = ReferenceData( + IdGenerator.getAndIncrement(), + "505", + "Breach - Continued/Fine" + ) val DEFAULT_CA = CourtAppearanceEntity( LocalDate.now(), IdGenerator.getAndIncrement(), diff --git a/projects/soc-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ConvictionsIntegrationTest.kt b/projects/soc-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ConvictionsIntegrationTest.kt index e049c612f8..98564f9d43 100644 --- a/projects/soc-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ConvictionsIntegrationTest.kt +++ b/projects/soc-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ConvictionsIntegrationTest.kt @@ -9,6 +9,7 @@ import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status import uk.gov.justice.digital.hmpps.data.generator.ConvictionEventGenerator +import uk.gov.justice.digital.hmpps.data.generator.CourtAppearanceGenerator import uk.gov.justice.digital.hmpps.data.generator.KeyDateGenerator import uk.gov.justice.digital.hmpps.model.* import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.andExpectJson @@ -63,6 +64,7 @@ internal class ConvictionsIntegrationTest { ConvictionEventGenerator.DEFAULT_EVENT.convictionDate, ConvictionEventGenerator.DEFAULT_EVENT.referralDate, ConvictionEventGenerator.DISPOSAL_TYPE.description, + CourtAppearanceGenerator.DEFAULT_OUTCOME.description, listOf( Offence( ConvictionEventGenerator.MAIN_OFFENCE.id, @@ -103,6 +105,7 @@ internal class ConvictionsIntegrationTest { ConvictionEventGenerator.INACTIVE_EVENT.convictionDate, ConvictionEventGenerator.INACTIVE_EVENT.referralDate, "unknown", + "unknown", listOf(), null ) diff --git a/projects/soc-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/ConvictionEventEntity.kt b/projects/soc-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/ConvictionEventEntity.kt index fe319785bd..714d834d59 100644 --- a/projects/soc-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/ConvictionEventEntity.kt +++ b/projects/soc-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/ConvictionEventEntity.kt @@ -3,6 +3,7 @@ package uk.gov.justice.digital.hmpps.entity import jakarta.persistence.* import org.hibernate.annotations.Immutable import org.hibernate.annotations.SQLRestriction +import org.springframework.data.domain.PageRequest import org.springframework.data.jpa.repository.EntityGraph import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Query @@ -163,6 +164,30 @@ class Offence( val mainCategoryDescription: String, ) +@Entity +@Immutable +@Table(name = "court_appearance") +@SQLRestriction("soft_deleted = 0") +class ConvictionCourtAppearanceEntity( + @Id + @Column(name = "court_appearance_id") + val id: Long, + + @JoinColumn(name = "event_id") + @ManyToOne + val convictionEventEntity: ConvictionEventEntity, + + @ManyToOne + @JoinColumn(name = "outcome_id") + val outcome: ReferenceData, + + @Column(name = "appearance_date") + val appearanceDate: LocalDate, + + @Column(name = "soft_deleted", columnDefinition = "number", nullable = false) + val softDeleted: Boolean = false +) + interface ConvictionEventRepository : JpaRepository { @Query( """ @@ -214,6 +239,15 @@ interface ConvictionEventRepository : JpaRepository ] ) fun getAllByConvictionEventPersonNomsNumberAndActiveIsTrue(nomsNumber: String): List + + @Query( + """ + select ca.outcome.description from ConvictionCourtAppearanceEntity ca + where ca.convictionEventEntity.id = :eventId + order by ca.appearanceDate desc + """ + ) + fun findLatestCourtAppearanceOutcome(eventId: Long, pageRequest: PageRequest = PageRequest.of(0, 1)): String? } fun ConvictionEventRepository.getLatestConviction(personId: Long) = diff --git a/projects/soc-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/CourtAppearanceEntity.kt b/projects/soc-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/CourtAppearanceEntity.kt index 229312e9f2..d0623c7890 100644 --- a/projects/soc-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/CourtAppearanceEntity.kt +++ b/projects/soc-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/CourtAppearanceEntity.kt @@ -1,11 +1,6 @@ package uk.gov.justice.digital.hmpps.entity -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.Id -import jakarta.persistence.JoinColumn -import jakarta.persistence.ManyToOne -import jakarta.persistence.Table +import jakarta.persistence.* import org.hibernate.annotations.Immutable import org.hibernate.annotations.SQLRestriction import org.springframework.data.jpa.repository.JpaRepository @@ -105,7 +100,7 @@ interface CourtAppearanceRepository : JpaRepository where ca.appearanceDate >= :dateFrom and ca.courtAppearanceEventEntity.courtAppearancePerson.crn = :crn order by ca.appearanceDate desc - """ + """ ) fun findMostRecentCourtAppearancesByCrn(dateFrom: LocalDate, crn: String): List @@ -115,7 +110,7 @@ interface CourtAppearanceRepository : JpaRepository where ca.appearanceDate >= :dateFrom and ca.courtAppearanceEventEntity.courtAppearancePerson.nomsNumber = :nomsNumber order by ca.appearanceDate desc - """ + """ ) fun findMostRecentCourtAppearancesByNomsNumber(dateFrom: LocalDate, nomsNumber: String): List } diff --git a/projects/soc-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/Conviction.kt b/projects/soc-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/Conviction.kt index 0bb28d5b00..c61e36e3a1 100644 --- a/projects/soc-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/Conviction.kt +++ b/projects/soc-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/Conviction.kt @@ -7,6 +7,7 @@ data class Conviction( val convictionDate: LocalDate?, val referralDate: LocalDate, val outcome: String, + val latestCourtAppearanceOutcome: String, val offences: List, val sentence: Sentence? ) diff --git a/projects/soc-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/ConvictionService.kt b/projects/soc-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/ConvictionService.kt index ef1d4d4006..3352966cc5 100644 --- a/projects/soc-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/ConvictionService.kt +++ b/projects/soc-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/ConvictionService.kt @@ -61,6 +61,7 @@ class ConvictionService( convictionEventEntity.convictionDate, convictionEventEntity.referralDate, convictionEventEntity.disposal?.type?.description ?: "unknown", + convictionEventRepository.findLatestCourtAppearanceOutcome(convictionEventEntity.id) ?: "unknown", offences, convictionEventEntity.disposal?.asModel(custody) ) diff --git a/settings.gradle.kts b/settings.gradle.kts index 089ac5711f..8b28d0687a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -80,7 +80,7 @@ dependencyResolutionManagement { library("mockito-inline", "org.mockito:mockito-inline:5.2.0") bundle("mockito", listOf("mockito-kotlin", "mockito-inline")) library("insights", "com.microsoft.azure:applicationinsights-web:3.5.2") - library("sentry", "io.sentry:sentry-spring-boot-starter-jakarta:7.8.0") + library("sentry", "io.sentry:sentry-spring-boot-starter-jakarta:7.9.0") library( "opentelemetry-annotations", "io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations:2.3.0" diff --git a/tools/ingress-testing/api-client/gradle/wrapper/gradle-wrapper.properties b/tools/ingress-testing/api-client/gradle/wrapper/gradle-wrapper.properties index 381baa9cef..8a1f6b97f4 100644 --- a/tools/ingress-testing/api-client/gradle/wrapper/gradle-wrapper.properties +++ b/tools/ingress-testing/api-client/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionSha256Sum=a4b4158601f8636cdeeab09bd76afb640030bb5b144aafe261a5e8af027dc612 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/tools/ingress-testing/api-client/gradlew b/tools/ingress-testing/api-client/gradlew index 1aa94a4269..b740cf1339 100755 --- a/tools/ingress-testing/api-client/gradlew +++ b/tools/ingress-testing/api-client/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/tools/ingress-testing/api-client/gradlew.bat b/tools/ingress-testing/api-client/gradlew.bat index 25da30dbde..7101f8e467 100755 --- a/tools/ingress-testing/api-client/gradlew.bat +++ b/tools/ingress-testing/api-client/gradlew.bat @@ -1,92 +1,92 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega