Skip to content

Commit

Permalink
Merge pull request #1301 from ministryofjustice/feature/APS-39-displa…
Browse files Browse the repository at this point in the history
…y-offender-status-at-time-of-submission

Feature/aps 39 display offender status at time of submission
  • Loading branch information
davidatkinsuk authored Jan 8, 2024
2 parents cc61c55 + edb5e07 commit 830bb8e
Show file tree
Hide file tree
Showing 27 changed files with 742 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.client.WebClient
import uk.gov.justice.digital.hmpps.approvedpremisesapi.model.prisonsapi.Alert
import uk.gov.justice.digital.hmpps.approvedpremisesapi.model.prisonsapi.InmateDetail
import uk.gov.justice.digital.hmpps.approvedpremisesapi.model.prisonsapi.PrisonerInPrisonSummary
import java.time.Duration

@Component
Expand Down Expand Up @@ -44,4 +45,8 @@ class PrisonsApiClient(
fun getAlerts(nomsNumber: String, alertCode: String) = getRequest<List<Alert>> {
path = "/api/offenders/$nomsNumber/alerts/v2?alertCodes=HA&sort=dateCreated&direction=DESC"
}

fun getPrisonTimeline(nomsNumber: String) = getRequest<PrisonerInPrisonSummary> {
path = "/api/offenders/$nomsNumber/prison-timeline"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,13 @@ WHERE taa.probation_region_id = :probationRegionId AND a.submitted_at IS NOT NUL

@Query("SELECT DISTINCT(a.nomsNumber) FROM ApplicationEntity a WHERE a.nomsNumber IS NOT NULL")
fun getDistinctNomsNumbers(): List<String>

@Query("SELECT ap FROM ApprovedPremisesApplicationEntity ap WHERE ap.submittedAt IS NOT NULL AND ap.inmateInOutStatusOnSubmission IS NULL")
fun getSubmittedApprovedPremisesApplicationsWithoutInOutStatus(pageable: Pageable?): Slice<ApprovedPremisesApplicationEntity>

@Modifying
@Query("UPDATE ApprovedPremisesApplicationEntity ap set ap.inmateInOutStatusOnSubmission = :inOutStatus where ap.id = :applicationId")
fun updateInOutStatus(applicationId: UUID, inOutStatus: String)
}

@Entity
Expand Down Expand Up @@ -291,6 +298,7 @@ class ApprovedPremisesApplicationEntity(
var targetLocation: String?,
@Enumerated(value = EnumType.STRING)
var status: ApprovedPremisesApplicationStatus,
var inmateInOutStatusOnSubmission: String?,
) : ApplicationEntity(
id,
crn,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package uk.gov.justice.digital.hmpps.approvedpremisesapi.migration

import org.slf4j.LoggerFactory
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Slice
import org.springframework.transaction.support.TransactionTemplate
import uk.gov.justice.digital.hmpps.approvedpremisesapi.client.ClientResult
import uk.gov.justice.digital.hmpps.approvedpremisesapi.client.PrisonsApiClient
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ApplicationRepository
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ApprovedPremisesApplicationEntity
import uk.gov.justice.digital.hmpps.approvedpremisesapi.model.prisonsapi.InOutStatus
import uk.gov.justice.digital.hmpps.approvedpremisesapi.model.prisonsapi.PrisonerInPrisonSummary
import java.time.OffsetDateTime
import javax.persistence.EntityManager

private const val THROTTLE_DELAY_SECONDS = 1 * 1000L

class InmateStatusOnSubmissionMigrationJob(
private val applicationRepository: ApplicationRepository,
private val entityManager: EntityManager,
private val pageSize: Int,
private val throttle: Boolean,
private val transactionTemplate: TransactionTemplate,
private val prisonsApiClient: PrisonsApiClient,
) : MigrationJob() {

override val shouldRunInTransaction = false

private val log = LoggerFactory.getLogger(this::class.java)

override fun process() {
var page = 1
var hasNext = true
var slice: Slice<ApprovedPremisesApplicationEntity>

while (hasNext) {
log.info("Getting page $page")
slice = applicationRepository.getSubmittedApprovedPremisesApplicationsWithoutInOutStatus(PageRequest.of(0, pageSize))
slice.content.forEach { application ->
transactionTemplate.executeWithoutResult {
updateInOutStatus(application)
}

if (throttle) {
Thread.sleep(THROTTLE_DELAY_SECONDS)
}
}
entityManager.clear()
hasNext = slice.hasNext()
page += 1
}
}

private fun updateInOutStatus(application: ApprovedPremisesApplicationEntity) {
log.info("Determine in out status for application ${application.id} submitted on ${application.submittedAt}")
when (val timelineResult = prisonsApiClient.getPrisonTimeline(application.nomsNumber!!)) {
is ClientResult.Success -> {
val inOutStatus = determineInOutStatus(application.submittedAt!!, timelineResult.body)
log.info("Status is $inOutStatus")
applicationRepository.updateInOutStatus(application.id, inOutStatus.name)
}
is ClientResult.Failure -> {
log.error("Unable to update application ${application.id}", timelineResult.toException())
}
}
}
}

fun determineInOutStatus(submissionDateTime: OffsetDateTime, inPrisonSummary: PrisonerInPrisonSummary): InOutStatus {
val dateOfInterest = submissionDateTime.toLocalDateTime()

val enclosingPeriod = inPrisonSummary.prisonPeriod?.firstOrNull { period ->
period.entryDate.isBefore(dateOfInterest) &&
(period.releaseDate == null || period.releaseDate.isAfter(dateOfInterest))
} ?: return InOutStatus.OUT

val inPrison = enclosingPeriod.movementDates.any { movement ->
movement.dateInToPrison!!.isBefore(dateOfInterest) &&
(movement.dateOutOfPrison == null || movement.dateOutOfPrison.isAfter(dateOfInterest))
}

return if (inPrison) InOutStatus.IN else InOutStatus.OUT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package uk.gov.justice.digital.hmpps.approvedpremisesapi.model.prisonsapi

import java.time.LocalDateTime

data class PrisonerInPrisonSummary(
val prisonPeriod: List<PrisonPeriod>?,
)

data class PrisonPeriod(
val entryDate: LocalDateTime,
val releaseDate: LocalDateTime? = null,
val movementDates: List<SignificantMovements>,
)

data class SignificantMovements(
val dateInToPrison: LocalDateTime? = null,
val dateOutOfPrison: LocalDateTime? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ class ApplicationService(
status = ApprovedPremisesApplicationStatus.STARTED,
sentenceType = null,
situation = null,
inmateInOutStatusOnSubmission = null,
)
}

Expand Down Expand Up @@ -743,6 +744,13 @@ class ApplicationService(
)
}

val inmateDetails = application.nomsNumber?.let { nomsNumber ->
when (val inmateDetailsResult = offenderService.getInmateDetailByNomsNumber(application.crn, nomsNumber)) {
is AuthorisableActionResult.Success -> inmateDetailsResult.entity
else -> null
}
}

application.apply {
isWomensApplication = submitApplication.isWomensApplication
isPipeApplication = submitApplication.isPipeApplication
Expand All @@ -755,6 +763,7 @@ class ApplicationService(
arrivalDate = getArrivalDate(submitApplication.arrivalDate)
sentenceType = submitApplication.sentenceType.toString()
situation = submitApplication.situation?.toString()
inmateInOutStatusOnSubmission = inmateDetails?.inOutStatus?.name
}

assessmentService.createApprovedPremisesAssessment(application)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package uk.gov.justice.digital.hmpps.approvedpremisesapi.service

import io.sentry.Sentry
import org.springframework.beans.factory.getBean
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.ApplicationContext
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Service
import org.springframework.transaction.support.TransactionTemplate
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.MigrationJobType
import uk.gov.justice.digital.hmpps.approvedpremisesapi.client.PrisonsApiClient
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ApplicationRepository
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.BookingRepository
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.UserRepository
import uk.gov.justice.digital.hmpps.approvedpremisesapi.migration.BookingStatusMigrationJob
import uk.gov.justice.digital.hmpps.approvedpremisesapi.migration.InmateStatusOnSubmissionMigrationJob
import uk.gov.justice.digital.hmpps.approvedpremisesapi.migration.MigrationJob
import uk.gov.justice.digital.hmpps.approvedpremisesapi.migration.MigrationLogger
import uk.gov.justice.digital.hmpps.approvedpremisesapi.migration.UpdateAllUsersFromCommunityApiJob
Expand All @@ -22,6 +25,7 @@ class MigrationJobService(
private val applicationContext: ApplicationContext,
private val transactionTemplate: TransactionTemplate,
private val migrationLogger: MigrationLogger,
@Value("\${migration-job.throttle-enabled}") private val throttle: Boolean,
) {
@Async
fun runMigrationJobAsync(migrationJobType: MigrationJobType) = runMigrationJob(migrationJobType, 50)
Expand All @@ -44,6 +48,15 @@ class MigrationJobService(
applicationContext.getBean(EntityManager::class.java),
pageSize,
)

MigrationJobType.inmateStatusOnSubmission -> InmateStatusOnSubmissionMigrationJob(
applicationContext.getBean(ApplicationRepository::class.java),
applicationContext.getBean(EntityManager::class.java),
pageSize,
throttle,
transactionTemplate,
applicationContext.getBean(PrisonsApiClient::class.java),
)
}

if (job.shouldRunInTransaction) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import uk.gov.justice.digital.hmpps.approvedpremisesapi.model.ApprovedPremisesAp
import uk.gov.justice.digital.hmpps.approvedpremisesapi.model.DomainEventSummary
import uk.gov.justice.digital.hmpps.approvedpremisesapi.model.PersonInfoResult
import uk.gov.justice.digital.hmpps.approvedpremisesapi.model.PersonRisks
import uk.gov.justice.digital.hmpps.approvedpremisesapi.model.prisonsapi.InOutStatus
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.ApplicationSummary as ApiApplicationSummary
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.ApprovedPremisesApplicationStatus as ApiApprovedPremisesApplicationStatus
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.ApprovedPremisesApplicationSummary as ApiApprovedPremisesApplicationSummary
Expand Down Expand Up @@ -88,6 +89,9 @@ class ApplicationsTransformer(
assessmentDecision = transformJpaDecisionToApi(latestAssessment?.decision),
assessmentId = latestAssessment?.id,
assessmentDecisionDate = latestAssessment?.submittedAt?.toLocalDate(),
personStatusOnSubmission = personTransformer.inOutStatusToPersonInfoApiStatus(
InOutStatus.entries.firstOrNull { it.name == jpa.inmateInOutStatusOnSubmission },
),
type = "CAS1",
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package uk.gov.justice.digital.hmpps.approvedpremisesapi.transformer

import org.springframework.stereotype.Component
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.FullPerson
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.PersonStatus
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.PersonType
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.RestrictedPerson
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.UnknownPerson
Expand Down Expand Up @@ -29,7 +30,7 @@ class PersonTransformer {
"Prefer to self-describe" -> personInfoResult.offenderDetailSummary.offenderProfile.selfDescribedGender
else -> personInfoResult.offenderDetailSummary.offenderProfile.genderIdentity
},
prisonName = inOutStatusToPersonInfoApiStatus(personInfoResult.inmateDetail?.inOutStatus).takeIf { it == FullPerson.Status.inCustody }?.let {
prisonName = inOutStatusToPersonInfoApiStatus(personInfoResult.inmateDetail?.inOutStatus).takeIf { it == PersonStatus.inCustody }?.let {
personInfoResult.inmateDetail?.assignedLivingUnit?.agencyName ?: personInfoResult.inmateDetail?.assignedLivingUnit?.agencyId
},
isRestricted = (personInfoResult.offenderDetailSummary.currentExclusion || personInfoResult.offenderDetailSummary.currentRestriction),
Expand All @@ -51,7 +52,7 @@ class PersonTransformer {
name = "${personInfoResult.summary.name.forename} ${personInfoResult.summary.name.surname}",
dateOfBirth = personInfoResult.summary.dateOfBirth,
sex = personInfoResult.summary.gender ?: "Not Found",
status = FullPerson.Status.unknown,
status = PersonStatus.unknown,
nomsNumber = personInfoResult.summary.nomsId,
ethnicity = personInfoResult.summary.profile?.ethnicity,
nationality = personInfoResult.summary.profile?.nationality,
Expand Down Expand Up @@ -81,16 +82,17 @@ class PersonTransformer {
nomsNumber = probationOffenderResult.probationOffenderDetail.otherIds.nomsNumber,
pncNumber = probationOffenderResult.probationOffenderDetail.otherIds.pncNumber ?: "Not found",
nationality = probationOffenderResult.probationOffenderDetail.offenderProfile?.nationality ?: "Not found",
prisonName = inOutStatusToPersonInfoApiStatus(probationOffenderResult.inmateDetail?.inOutStatus).takeIf { it == FullPerson.Status.inCustody }?.let {
prisonName = inOutStatusToPersonInfoApiStatus(probationOffenderResult.inmateDetail?.inOutStatus).takeIf { it == PersonStatus.inCustody }?.let {
probationOffenderResult.inmateDetail?.assignedLivingUnit?.agencyName
?: probationOffenderResult.inmateDetail?.assignedLivingUnit?.agencyId
},
isRestricted = (probationOffenderResult.probationOffenderDetail.currentExclusion ?: false || probationOffenderResult.probationOffenderDetail.currentRestriction ?: false),
)
private fun inOutStatusToPersonInfoApiStatus(inOutStatus: InOutStatus?) = when (inOutStatus) {
InOutStatus.IN -> FullPerson.Status.inCustody
InOutStatus.OUT -> FullPerson.Status.inCommunity
InOutStatus.TRN -> FullPerson.Status.inCustody
null -> FullPerson.Status.unknown

fun inOutStatusToPersonInfoApiStatus(inOutStatus: InOutStatus?) = when (inOutStatus) {
InOutStatus.IN -> PersonStatus.inCustody
InOutStatus.OUT -> PersonStatus.inCommunity
InOutStatus.TRN -> PersonStatus.inCustody
null -> PersonStatus.unknown
}
}
3 changes: 3 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,6 @@ upstream-timeout-ms: 10000
case-notes-service-upstream-timeout-ms: 30000

max-response-in-memory-size-bytes: 750000

migration-job:
throttle-enabled: true
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE approved_premises_applications ADD COLUMN "inmate_in_out_status_on_submission" TEXT NULL;
15 changes: 10 additions & 5 deletions src/main/resources/static/_shared.yml
Original file line number Diff line number Diff line change
Expand Up @@ -689,11 +689,7 @@ components:
genderIdentity:
type: string
status:
type: string
enum:
- InCustody
- InCommunity
- Unknown
$ref: '#/components/schemas/PersonStatus'
prisonName:
type: string
isRestricted:
Expand All @@ -703,6 +699,12 @@ components:
- dateOfBirth
- sex
- status
PersonStatus:
type: string
enum:
- InCustody
- InCommunity
- Unknown
NewArrival:
type: object
properties:
Expand Down Expand Up @@ -1570,6 +1572,8 @@ components:
submittedAt:
type: string
format: date-time
personStatusOnSubmission:
$ref: '#/components/schemas/PersonStatus'
required:
- createdByUserId
- schemaVersion
Expand Down Expand Up @@ -3175,6 +3179,7 @@ components:
MigrationJobType:
type: string
enum:
- update_inmate_status_on_submission
- update_all_users_from_community_api
- update_sentence_type_and_situation
- update_booking_status
Expand Down
15 changes: 10 additions & 5 deletions src/main/resources/static/codegen/built-api-spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4930,11 +4930,7 @@ components:
genderIdentity:
type: string
status:
type: string
enum:
- InCustody
- InCommunity
- Unknown
$ref: '#/components/schemas/PersonStatus'
prisonName:
type: string
isRestricted:
Expand All @@ -4944,6 +4940,12 @@ components:
- dateOfBirth
- sex
- status
PersonStatus:
type: string
enum:
- InCustody
- InCommunity
- Unknown
NewArrival:
type: object
properties:
Expand Down Expand Up @@ -5811,6 +5813,8 @@ components:
submittedAt:
type: string
format: date-time
personStatusOnSubmission:
$ref: '#/components/schemas/PersonStatus'
required:
- createdByUserId
- schemaVersion
Expand Down Expand Up @@ -7416,6 +7420,7 @@ components:
MigrationJobType:
type: string
enum:
- update_inmate_status_on_submission
- update_all_users_from_community_api
- update_sentence_type_and_situation
- update_booking_status
Expand Down
Loading

0 comments on commit 830bb8e

Please sign in to comment.