Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
anthony-britton-moj authored Jan 15, 2025
1 parent b3fdb81 commit 8407425
Show file tree
Hide file tree
Showing 16 changed files with 319 additions and 108 deletions.
2 changes: 2 additions & 0 deletions projects/prison-case-notes-to-probation/deploy/values-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ generic-service:
LOGGING_LEVEL_COM_AMAZON_SQS: DEBUG
LOGGING_LEVEL_UK_GOV_DIGITAL_JUSTICE_HMPPS: DEBUG
SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_HMPPS-AUTH_TOKEN-URI: http://hmpps-auth.hmpps-auth-dev.svc.cluster.local/auth/oauth/token
INTEGRATIONS_PRISON_CASE_NOTES_BASE_URL: https://dev.offender-case-notes.service.justice.gov.uk


generic-prometheus-alerts:
businessHoursOnly: true
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ generic-service:
env:
SENTRY_ENVIRONMENT: preprod
SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_HMPPS-AUTH_TOKEN-URI: http://hmpps-auth.hmpps-auth-preprod.svc.cluster.local/auth/oauth/token
INTEGRATIONS_PRISON_CASE_NOTES_BASE_URL: https://preprod.offender-case-notes.service.justice.gov.uk

generic-prometheus-alerts:
businessHoursOnly: true
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ generic-service:
env:
SENTRY_ENVIRONMENT: prod
SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_HMPPS-AUTH_TOKEN-URI: http://hmpps-auth.hmpps-auth-prod.svc.cluster.local/auth/oauth/token
INTEGRATIONS_PRISON_CASE_NOTES_BASE_URL: https://offender-case-notes.service.justice.gov.uk
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class CaseNotesDataLoader(
StaffGenerator.DEFAULT = staffRepository.save(StaffGenerator.DEFAULT)

offenderRepository.save(OffenderGenerator.DEFAULT)
offenderRepository.save(OffenderGenerator.NEW_IDENTIFIER)

eventRepository.save(EventGenerator.CUSTODIAL_EVENT)
disposalTypeRepository.save(EventGenerator.CUSTODIAL_EVENT.disposal!!.disposalType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ object CaseNoteMessageGenerator {
val NEW_TO_DELIUS: HmppsDomainEvent = ResourceLoader.message("case-note-new-to-delius")
val NOT_FOUND: HmppsDomainEvent = ResourceLoader.message("case-note-not-found")
val RESETTLEMENT_PASSPORT: HmppsDomainEvent = ResourceLoader.message("resettlement-passport-casenote")
val NOMS_NUMBER_ADDED: HmppsDomainEvent = ResourceLoader.message("noms-number-added")
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ import uk.gov.justice.digital.hmpps.integrations.delius.entity.Offender

object OffenderGenerator {
const val EXISTING_OFFENDER_ID = "AA0001A"
const val NEW_PRISON_IDENTIFIER = "A4578BX"
val DEFAULT = Offender(IdGenerator.getAndIncrement(), EXISTING_OFFENDER_ID)
val NEW_IDENTIFIER = Offender(IdGenerator.getAndIncrement(), NEW_PRISON_IDENTIFIER)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"Type": "Notification",
"Message": "{\"eventType\":\"probation-case.prison-identifier.added\",\"version\":1,\"occurredAt\":\"2025-01-15T09:47:39+01:00\",\"publishedAt\":\"2025-01-15T09:47:39+01:00\",\"description\":\"A prisoner identifier has been added\",\"personReference\":{\"identifiers\":[{\"type\":\"NOMS\",\"value\":\"A4578BX\"}]}}",
"Timestamp": "2025-01-15T09:47:39+01:00",
"MessageAttributes": {
"eventType": {
"Type": "String",
"Value": "probation-case.prison-identifier.added"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"content": [
{
"caseNoteId": "ff0cfa55-3193-41c9-9332-7766c03a06ff",
"eventId": 77701,
"offenderIdentifier": "A4578BX",
"type": "GEN",
"subType": "OSE",
"creationDateTime": "2025-01-12T11:22:33+01:00",
"occurrenceDateTime": "2025-01-11T10:11:22+01:00",
"authorName": "Some Name",
"text": "note content",
"locationId": "LEI",
"amendments": []
},
{
"caseNoteId": "2dd32070-4af0-4d3c-ad8d-5108a43a9322",
"eventId": 77702,
"offenderIdentifier": "A4578BX",
"type": "ALERT",
"subType": "ACTIVE",
"creationDateTime": "2025-01-12T11:22:33+01:00",
"occurrenceDateTime": "2025-01-11T10:11:22+01:00",
"authorName": "Some Name",
"text": "note content",
"locationId": "LEI",
"amendments": []
},
{
"caseNoteId": "7f283166-2c7e-404e-9b0c-fa21cfbd338e",
"eventId": 77704,
"offenderIdentifier": "A4578BX",
"type": "KA",
"subType": "KS",
"creationDateTime": "2025-01-12T11:22:33+01:00",
"occurrenceDateTime": "2025-01-11T10:11:22+01:00",
"authorName": "Some Name",
"text": "note content",
"locationId": "LEI",
"amendments": []
},
{
"caseNoteId": "d9e02743-3e8a-4ab8-923b-01d4214afeaf",
"eventId": 77705,
"offenderIdentifier": "A4578BX",
"type": "PRISON",
"subType": "RELEASE",
"creationDateTime": "2025-01-12T11:22:33+01:00",
"occurrenceDateTime": "2025-01-11T10:11:22+01:00",
"authorName": "Some Name",
"text": "note content",
"locationId": "LEI",
"amendments": []
},
{
"caseNoteId": "bdc8b4af-01a3-44b5-9160-23c4d289f9a2",
"eventId": 77703,
"offenderIdentifier": "A4578BX",
"type": "TRANSFER",
"subType": "FROMTOL",
"creationDateTime": "2025-01-12T11:22:33+01:00",
"occurrenceDateTime": "2025-01-11T10:11:22+01:00",
"authorName": "Some Name",
"text": "note content",
"locationId": "TRN",
"amendments": []
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,20 @@
"developerMessage": "Resource with id [AA0001A] not found."
}
}
},
{
"request": {
"method": "POST",
"url": "/search/case-notes/A4578BX"
},
"response": {
"headers": {
"Accept": "application/json",
"Content-Type": "application/json"
},
"status": 200,
"bodyFileName": "migrate-for-new-identifier.json"
}
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import uk.gov.justice.digital.hmpps.audit.repository.AuditedInteractionRepositor
import uk.gov.justice.digital.hmpps.data.generator.*
import uk.gov.justice.digital.hmpps.datetime.DeliusDateTimeFormatter
import uk.gov.justice.digital.hmpps.integrations.delius.repository.CaseNoteRepository
import uk.gov.justice.digital.hmpps.integrations.delius.repository.OffenderRepository
import uk.gov.justice.digital.hmpps.integrations.delius.repository.StaffRepository
import uk.gov.justice.digital.hmpps.messaging.HmppsChannelManager
import uk.gov.justice.digital.hmpps.telemetry.TelemetryService
Expand All @@ -32,6 +33,9 @@ const val CASE_NOTE_MERGED = "CaseNoteMerged"
@SpringBootTest
class CaseNotesIntegrationTest {

@Autowired
private lateinit var offenderRepository: OffenderRepository

@Value("\${messaging.consumer.queue}")
private lateinit var queueName: String

Expand Down Expand Up @@ -167,4 +171,23 @@ class CaseNotesIntegrationTest {
val staff = staffRepository.findById(saved.staffId).orElseThrow()
assertThat(staff.code, equalTo("${ProbationAreaGenerator.DEFAULT.code}B001"))
}

@Test
fun `migrate case notes succesfully when noms number added`() {
val offender = requireNotNull(offenderRepository.findByNomsIdAndSoftDeletedIsFalse("A4578BX"))
val originals = caseNoteRepository.findAll().filter { it.offenderId == offender.id }
assert(originals.isEmpty())

channelManager.getChannel(queueName).publishAndWait(
prepMessage(CaseNoteMessageGenerator.NOMS_NUMBER_ADDED, wireMockserver.port())
)

verify(telemetryService).trackEvent(
eq("CaseNotesMigrated"),
eq(mapOf("nomsId" to "A4578BX", "cause" to "probation-case.prison-identifier.added", "count" to "4")),
anyMap()
)
val saved = caseNoteRepository.findAll().filter { it.offenderId == offender.id }
assertThat(saved.size, equalTo(4))
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,44 @@
package uk.gov.justice.digital.hmpps.integrations.prison

import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.service.annotation.GetExchange
import org.springframework.web.service.annotation.PostExchange
import java.net.URI
import java.time.LocalDateTime

interface PrisonCaseNotesClient {
@GetExchange
fun getCaseNote(baseUrl: URI): PrisonCaseNote?

@PostExchange
fun searchCaseNotes(
uri: URI,
@RequestBody searchCaseNotes: SearchCaseNotes
): CaseNotesResults
}

data class SearchCaseNotes(
val typeSubTypes: Set<TypeSubTypeRequest>,
val occurredFrom: LocalDateTime? = null,
val occurredTo: LocalDateTime? = null,
val includeSensitive: Boolean = true,
val page: Int = 1,
val size: Int = Int.MAX_VALUE,
val sort: String = "occurredAt,desc",
) {
companion object {
val TYPES_OF_INTEREST = setOf(
TypeSubTypeRequest("PRISON", setOf("RELEASE")),
TypeSubTypeRequest("TRANSFER", setOf("FROMTOL")),
TypeSubTypeRequest("GEN", setOf("OSE")),
TypeSubTypeRequest("ALERT"),
TypeSubTypeRequest("OMIC"),
TypeSubTypeRequest("OMIC_OPD"),
TypeSubTypeRequest("KA"),
)
}
}

data class TypeSubTypeRequest(val type: String, val subTypes: Set<String> = setOf())

data class CaseNotesResults(val content: List<PrisonCaseNote>)
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package uk.gov.justice.digital.hmpps.messaging

import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.web.client.HttpStatusCodeException
import uk.gov.justice.digital.hmpps.datetime.DeliusDateTimeFormatter
import uk.gov.justice.digital.hmpps.exceptions.OffenderNotFoundException
import uk.gov.justice.digital.hmpps.integrations.delius.service.DeliusService
import uk.gov.justice.digital.hmpps.integrations.prison.PrisonCaseNote
import uk.gov.justice.digital.hmpps.integrations.prison.PrisonCaseNoteFilters
import uk.gov.justice.digital.hmpps.integrations.prison.PrisonCaseNotesClient
import uk.gov.justice.digital.hmpps.integrations.prison.toDeliusCaseNote
import uk.gov.justice.digital.hmpps.message.HmppsDomainEvent
import uk.gov.justice.digital.hmpps.telemetry.TelemetryService
import java.net.URI

@Service
class CaseNotePublished(
private val prisonCaseNotesClient: PrisonCaseNotesClient,
private val deliusService: DeliusService,
private val telemetryService: TelemetryService,
) {
fun handle(event: HmppsDomainEvent) {
val caseNoteId = event.additionalInformation["caseNoteId"]
if (caseNoteId == null) {
telemetryService.trackEvent(
"MissingCaseNoteId",
mapOf(
"eventType" to event.eventType,
"nomsNumber" to event.personReference.findNomsNumber()!!
)
)
return
}

val prisonCaseNote = try {
prisonCaseNotesClient.getCaseNote(URI.create(event.detailUrl!!))
} catch (ex: HttpStatusCodeException) {
when (ex.statusCode) {
HttpStatus.NOT_FOUND -> {
telemetryService.trackEvent("CaseNoteNotFound", mapOf("detailUrl" to event.detailUrl!!))
return
}

else -> throw ex
}
}

val reasonToIgnore: Lazy<String?> = lazy {
PrisonCaseNoteFilters.filters.firstOrNull { it.predicate.invoke(prisonCaseNote!!) }?.reason
}

if (prisonCaseNote == null || reasonToIgnore.value != null) {
val reason = if (prisonCaseNote == null) {
"case note was not able to be retrieved"
} else {
reasonToIgnore.value!!
}
telemetryService.trackEvent(
"CaseNoteIgnored",
(prisonCaseNote?.properties() ?: emptyMap()) + ("reason" to reason)
)
return
}

try {
deliusService.mergeCaseNote(prisonCaseNote.toDeliusCaseNote(event.occurredAt))
telemetryService.trackEvent("CaseNoteMerged", prisonCaseNote.properties())
} catch (e: Exception) {
telemetryService.trackEvent(
"CaseNoteMergeFailed",
prisonCaseNote.properties() + ("exception" to (e.message ?: ""))
)
if (e !is OffenderNotFoundException) throw e
}
}

private fun PrisonCaseNote.properties() = mapOf(
"caseNoteId" to id,
"type" to type,
"subType" to subType,
"eventId" to eventId.toString(),
"created" to DeliusDateTimeFormatter.format(creationDateTime),
"occurrence" to DeliusDateTimeFormatter.format(occurrenceDateTime),
"location" to locationId
)
}
Loading

0 comments on commit 8407425

Please sign in to comment.