Skip to content

Commit

Permalink
PI-2700 Remove existing registrations in the same group (#4499)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcus-bcl authored Dec 17, 2024
1 parent ab153d3 commit caf23ac
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package uk.gov.justice.digital.hmpps.datetime

import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.temporal.Temporal

val DeliusDateTimeFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")
val DeliusDateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy")
fun Temporal.toDeliusDate(): String = DeliusDateFormatter.format(this)
fun Temporal.toDeliusDateTime(): String = DeliusDateTimeFormatter.format(this)

val EuropeLondon: ZoneId = ZoneId.of("Europe/London")
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import uk.gov.justice.digital.hmpps.enum.RiskType
import uk.gov.justice.digital.hmpps.integrations.delius.contact.entity.ContactType
import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.Event
import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.Person
import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.RegisterType
import uk.gov.justice.digital.hmpps.set
import uk.gov.justice.digital.hmpps.user.AuditUserRepository
import java.time.LocalDate
Expand Down Expand Up @@ -44,6 +45,8 @@ class DataLoader(
saveAll(ReferenceDataGenerator.LEVELS_DATASET, *ReferenceDataGenerator.LEVELS.toTypedArray())
saveAll(*ContactGenerator.TYPES.values.toTypedArray())
saveAll(*RegistrationGenerator.TYPES.values.toTypedArray())
saveAll(RegistrationGenerator.ALT_TYPE)
saveAll(RegistrationGenerator.DUPLICATE_GROUP)
saveAll(*ReferenceDataGenerator.REQ_MAIN_CATS.toTypedArray())
saveAll(
ReferenceDataGenerator.DOMAIN_EVENT_TYPE_DATASET,
Expand All @@ -65,10 +68,11 @@ class DataLoader(
PersonGenerator.PRISON_ASSESSMENT.withEvent(custodial = true)
PersonGenerator.NO_EXISTING_RISKS.withEvent()
PersonGenerator.EXISTING_RISKS.withEvent().withRisks(
RiskType.CHILDREN to RiskLevel.H,
RiskType.STAFF to RiskLevel.V,
RiskType.KNOWN_ADULT to RiskLevel.M,
RiskType.PUBLIC to RiskLevel.M,
RegistrationGenerator.TYPES[RiskType.CHILDREN.code]!! to RiskLevel.H,
RegistrationGenerator.TYPES[RiskType.STAFF.code]!! to RiskLevel.V,
RegistrationGenerator.TYPES[RiskType.KNOWN_ADULT.code]!! to RiskLevel.M,
RegistrationGenerator.TYPES[RiskType.PUBLIC.code]!! to RiskLevel.M,
RegistrationGenerator.ALT_TYPE to null,
)
PersonGenerator.FEATURE_FLAG.withEvent().withRiskOfSeriousHarm(V)
}
Expand Down Expand Up @@ -107,11 +111,11 @@ class DataLoader(
return this
}

private fun Person.withRisks(vararg risks: Pair<RiskType, RiskLevel>): Person {
private fun Person.withRisks(vararg risks: Pair<RegisterType, RiskLevel?>): Person {
risks.forEach { risk ->
val type = RegistrationGenerator.TYPES[risk.first.code]
val level = ReferenceDataGenerator.LEVELS.single { it.code == risk.second.code }
val contact = entityManager.merge(ContactGenerator.generateContact(this, type!!.registrationContactType!!))
val type = risk.first
val level = ReferenceDataGenerator.LEVELS.singleOrNull { it.code == risk.second?.code }
val contact = entityManager.merge(ContactGenerator.generateContact(this, type.registrationContactType!!))
val registration =
RegistrationGenerator.generate(this.id, LocalDate.parse("2023-06-14"), contact, type, level)
val reviewContact = entityManager.merge(ContactGenerator.generateContact(this, type.reviewContactType!!))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import uk.gov.justice.digital.hmpps.enum.RiskOfSeriousHarmType
import uk.gov.justice.digital.hmpps.enum.RiskType
import uk.gov.justice.digital.hmpps.integrations.delius.contact.entity.Contact
import uk.gov.justice.digital.hmpps.integrations.delius.contact.entity.ContactType
import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.RegisterDuplicateGroup
import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.RegisterType
import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.Registration
import uk.gov.justice.digital.hmpps.integrations.delius.referencedata.entity.ReferenceData
Expand All @@ -17,6 +18,13 @@ object RegistrationGenerator {
RiskType.entries.map { generateType(it.code, "Amber", "Risk to ${it.name.lowercase()}") }
).flatten().associateBy { it.code }

val ALT_TYPE = generateType("ALT1", "Amber", "ALT Risk to prisoner")

val DUPLICATE_GROUP = RegisterDuplicateGroup(
types = listOf(TYPES[RiskType.PRISONER.code]!!, ALT_TYPE),
id = IdGenerator.getAndIncrement()
)

val RiskOfSeriousHarmType.colour
get() = when (this) {
RiskOfSeriousHarmType.V -> "Red"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,7 @@ import uk.gov.justice.digital.hmpps.integrations.delius.assessment.entity.OasysA
import uk.gov.justice.digital.hmpps.integrations.delius.contact.entity.ContactRepository
import uk.gov.justice.digital.hmpps.integrations.delius.contact.entity.ContactType
import uk.gov.justice.digital.hmpps.integrations.delius.domainevent.entity.DomainEventRepository
import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.Person
import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.PersonRepository
import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.RegistrationRepository
import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.getByCrn
import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.*
import uk.gov.justice.digital.hmpps.message.HmppsDomainEvent
import uk.gov.justice.digital.hmpps.message.Notification
import uk.gov.justice.digital.hmpps.message.PersonIdentifier
Expand Down Expand Up @@ -351,6 +348,9 @@ internal class IntegrationTest {
assertThat(riskToChildrenBefore.reviews, hasSize(1))
val riskToPrisonerBefore = registrationRepository.findByPersonIdAndTypeCode(person.id, RiskType.PRISONER.code)
assertThat(riskToPrisonerBefore, hasSize(0))
val altRiskToPrisonerBefore =
registrationRepository.findByPersonIdAndTypeCode(person.id, RegistrationGenerator.ALT_TYPE.code).single()
assertThat(altRiskToPrisonerBefore.level?.code, nullValue())
val riskToStaffBefore =
registrationRepository.findByPersonIdAndTypeCode(person.id, RiskType.STAFF.code).single()
assertThat(riskToStaffBefore.level?.code, equalTo(RiskLevel.V.code))
Expand Down Expand Up @@ -386,6 +386,9 @@ internal class IntegrationTest {
assertThat(riskToPrisoner.level?.code, equalTo(RiskLevel.M.code))
assertThat(riskToPrisoner.reviews, hasSize(1))
assertThat(domainEvents.ofType(RiskType.PRISONER), hasSize(1))
val altRiskToPrisoner =
registrationRepository.findByPersonIdAndTypeCode(person.id, RegistrationGenerator.ALT_TYPE.code)
assertThat(altRiskToPrisoner, empty())

val riskToStaff = registrationRepository.findByPersonIdAndTypeCode(person.id, RiskType.STAFF.code).single()
assertThat(riskToStaff.level?.code, equalTo(RiskLevel.V.code))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.hibernate.type.YesNoConverter
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import org.springframework.data.jpa.repository.EntityGraph
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.contact.entity.Contact
import uk.gov.justice.digital.hmpps.integrations.delius.contact.entity.ContactType
Expand Down Expand Up @@ -121,6 +122,23 @@ class RegisterType(
val id: Long
)

@Immutable
@Entity
@Table(name = "r_register_duplicate_group")
class RegisterDuplicateGroup(
@ManyToMany
@JoinTable(
name = "r_register_type_dup_grp",
joinColumns = [JoinColumn(name = "register_group_id")],
inverseJoinColumns = [JoinColumn(name = "register_type_id")]
)
val types: List<RegisterType>,

@Id
@Column(name = "register_group_id")
val id: Long
)

@Entity
@Table(name = "registration_review")
@SQLRestriction("soft_deleted = 0")
Expand Down Expand Up @@ -202,12 +220,25 @@ interface RegistrationRepository : JpaRepository<Registration, Long> {
fun findByPersonIdAndTypeFlagCode(personId: Long, flagCode: String): List<Registration>

@EntityGraph(attributePaths = ["contact", "type.flag", "type.registrationContactType", "type.reviewContactType", "reviews.contact"])
fun findByPersonIdAndTypeCode(personId: Long, typeCode: String): List<Registration>
fun findByPersonIdAndTypeCodeIn(personId: Long, typeCodes: List<String>): List<Registration>
}

fun RegistrationRepository.findByPersonIdAndTypeCode(personId: Long, typeCode: String) =
findByPersonIdAndTypeCodeIn(personId, listOf(typeCode))

interface RegisterTypeRepository : JpaRepository<RegisterType, Long> {
@EntityGraph(attributePaths = ["flag", "registrationContactType", "reviewContactType"])
fun findByCode(code: String): RegisterType?

@Query(
"""
select distinct t2.code
from RegisterDuplicateGroup g
join g.types t1 on t1.code = :code
join g.types t2 on t2.code <> :code
"""
)
fun findOtherTypesInGroup(code: String): List<String>
}

fun RegisterTypeRepository.getByCode(code: String) =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package uk.gov.justice.digital.hmpps.service

import org.springframework.stereotype.Service
import uk.gov.justice.digital.hmpps.datetime.DeliusDateFormatter
import uk.gov.justice.digital.hmpps.datetime.toDeliusDate
import uk.gov.justice.digital.hmpps.enum.RiskLevel
import uk.gov.justice.digital.hmpps.enum.RiskOfSeriousHarmType
import uk.gov.justice.digital.hmpps.enum.RiskType
Expand Down Expand Up @@ -59,39 +59,41 @@ class RiskService(
private fun recordOtherRisks(person: Person, summary: AssessmentSummary): List<HmppsDomainEvent> {
if (!featureFlags.enabled("assessment-summary-additional-risks")) return emptyList()
return RiskType.entries.flatMap { riskType ->
val desiredLevel = riskType.riskLevel(summary) ?: return@flatMap emptyList()
val riskLevel = riskType.riskLevel(summary) ?: return@flatMap emptyList()
val registrations =
registrationRepository.findByPersonIdAndTypeCode(person.id, riskType.code).toMutableList()

val (matchingRegistrations, registrationsToRemove) = registrations.partition { existing ->
existing.level != null && existing.level.code == desiredLevel.code && desiredLevel != RiskLevel.L
}
// Deregister existing registrations if OASys identified the level as low risk
if (riskLevel == RiskLevel.L) return@flatMap registrations.map { person.removeRegistration(it) }

val deRegEvents = registrationsToRemove.map {
val contact = contactService.createContact(ContactDetail(DEREGISTRATION, notes = it.notes()), person)
it.deregister(contact)
it.deRegEvent(person.crn)
}
val regEvent = if (matchingRegistrations.isEmpty() && desiredLevel != RiskLevel.L) {
// Remove any existing registrations with a different level
val (matchingRegistrations, registrationsToRemove) = registrations
.partition { it.level != null && it.level.code == riskLevel.code }
val events = registrationsToRemove.map { person.removeRegistration(it) }.toMutableList()

// Add registration with the identified level if it doesn't already exist
if (matchingRegistrations.isEmpty()) {
val type = registerTypeRepository.getByCode(riskType.code)
val level = referenceDataRepository.registerLevel(desiredLevel.code)
val level = referenceDataRepository.registerLevel(riskLevel.code)
val notes =
"The OASys assessment of ${summary.furtherInformation.pOAssessmentDesc} on ${
DeliusDateFormatter.format(
summary.dateCompleted
)
} identified the ${type.description} to be ${level.description}"
val registration = registrations.addRegistration(person, type, level, notes)
registration.regEvent(person.crn)
} else null

if (desiredLevel != RiskLevel.L) {
// Always add a review if medium or higher, regardless of whether the register level has changed
registrationRepository.findByPersonIdAndTypeCode(person.id, riskType.code).singleOrNull()
?.let { it.withReview(it.reviewContact(person)) }
"The OASys assessment of ${summary.furtherInformation.pOAssessmentDesc} on ${summary.dateCompleted.toDeliusDate()} identified the ${type.description} to be ${level.description}"
events += registrations.addRegistration(person, type, level, notes).regEvent(person.crn)

// Registrations in the type's duplicate group should also be removed
registerTypeRepository.findOtherTypesInGroup(riskType.code)
.let { registrationRepository.findByPersonIdAndTypeCodeIn(person.id, it) }
.forEach {
val duplicateGroupNotes =
"De-registered when a ${type.description} registration was added on ${summary.dateCompleted.toDeliusDate()}."
events += person.removeRegistration(it, notes = duplicateGroupNotes)
}
}

return@flatMap listOfNotNull(regEvent) + deRegEvents
// Always add a review, regardless of whether the register level has changed
registrationRepository.findByPersonIdAndTypeCode(person.id, riskType.code).singleOrNull()
?.let { it.withReview(it.reviewContact(person)) }

return@flatMap events
}
}

Expand Down Expand Up @@ -128,6 +130,12 @@ class RiskService(
return registration
}

private fun Person.removeRegistration(it: Registration, notes: String? = null): HmppsDomainEvent {
val contact = contactService.createContact(ContactDetail(DEREGISTRATION, notes = notes ?: it.notes()), this)
it.deregister(contact)
return it.deRegEvent(crn)
}

private fun Registration.reviewContact(person: Person) = contactService.createContact(
ContactDetail(
ContactType.Code.REGISTRATION_REVIEW,
Expand All @@ -140,7 +148,7 @@ class RiskService(

fun reviewNotes(type: RegisterType, nextReviewDate: LocalDate?) = listOfNotNull(
"Type: ${type.flag.description} - ${type.description}",
nextReviewDate?.let { "Next Review Date: ${DeliusDateFormatter.format(it)}" }
nextReviewDate?.let { "Next Review Date: ${it.toDeliusDate()}" }
).joinToString(System.lineSeparator())

fun Registration.notes(): String = reviewNotes(type, nextReviewDate)

0 comments on commit caf23ac

Please sign in to comment.