Skip to content

Commit

Permalink
Merge branch 'main' into bug/PI-2257-make-recall-decisions-and-delius…
Browse files Browse the repository at this point in the history
…-not-handling-merged-crn-cases
  • Loading branch information
achimber-moj authored Jun 14, 2024
2 parents c4cabd0 + 47407ee commit da9d12a
Show file tree
Hide file tree
Showing 17 changed files with 229 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ class Handler(
Message(name = "approved-premises/application-assessed"),
Message(name = "approved-premises/application-withdrawn"),
Message(name = "approved-premises/booking-made"),
Message(name = "approved-premises/booking-changed"),
Message(name = "approved-premises/booking-cancelled"),
Message(messageId = "approved-premises.booking.changed", payload = Schema(HmppsDomainEvent::class)),
Message(messageId = "approved-premises.person.not-arrived", payload = Schema(HmppsDomainEvent::class)),
Message(messageId = "approved-premises.person.arrived", payload = Schema(HmppsDomainEvent::class)),
Message(messageId = "approved-premises.person.departed", payload = Schema(HmppsDomainEvent::class)),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
---
title: AsyncAPI Reference
source_url: 'https://github.com/ministryofjustice/hmpps-probation-integration-services/blob/main/projects/court-case-and-delius/tech-docs/source/asyncapi-reference.html.md.erb'
source_url: 'https://github.com/ministryofjustice/hmpps-probation-integration-services/blob/main/projects/create-and-vary-a-licence-and-delius/tech-docs/source/asyncapi-reference.html.md.erb'
weight: 30
---

# AsyncAPI Reference

<iframe src="https://studio.asyncapi.com/?url=https://ministryofjustice.github.io/hmpps-probation-integration-services/tech-docs/projects/court-case-and-delius/asyncapi-docs.json&readOnly" title="AsyncAPI Spec" width="100%" height="100%" frameborder=0 style="position: absolute; bottom: 0; background: white"></iframe>
<iframe src="https://studio.asyncapi.com/?url=https://ministryofjustice.github.io/hmpps-probation-integration-services/tech-docs/projects/create-and-vary-a-licence-and-delius/asyncapi-docs.json&readOnly" title="AsyncAPI Spec" width="100%" height="100%" frameborder=0 style="position: absolute; bottom: 0; background: white"></iframe>

<style>.app-pane__content { position: relative }</style>
<script>document.querySelector('main').parentElement.appendChild(document.querySelector('main > iframe'))</script>
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,12 @@ import org.springframework.boot.context.event.ApplicationReadyEvent
import org.springframework.context.ApplicationListener
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
import uk.gov.justice.digital.hmpps.data.generator.ContactTypeGenerator
import uk.gov.justice.digital.hmpps.data.generator.KeyDateGenerator
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.*
import uk.gov.justice.digital.hmpps.data.generator.SentenceGenerator.DEFAULT_CUSTODY
import uk.gov.justice.digital.hmpps.data.generator.SentenceGenerator.generateCustodialSentence
import uk.gov.justice.digital.hmpps.data.generator.SentenceGenerator.generateDisposal
import uk.gov.justice.digital.hmpps.data.generator.SentenceGenerator.generateEvent
import uk.gov.justice.digital.hmpps.data.generator.SentenceGenerator.generateOrderManager
import uk.gov.justice.digital.hmpps.data.generator.UserGenerator
import uk.gov.justice.digital.hmpps.data.repository.DatasetRepository
import uk.gov.justice.digital.hmpps.data.repository.DisposalRepository
import uk.gov.justice.digital.hmpps.data.repository.EventRepository
Expand Down Expand Up @@ -90,6 +86,8 @@ class DataLoader(
)
)
createPersonWithKeyDates(PersonGenerator.PERSON_WITH_KEYDATES, "38340A")

createPersonWithKeyDates(PersonGenerator.PERSON_WITH_KEYDATES_BY_CRN, "48340A")
}

private fun createPersonWithKeyDates(personRef: Person, bookingRef: String): Custody {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package uk.gov.justice.digital.hmpps.data.generator

import uk.gov.justice.digital.hmpps.messaging.CustodyDateChanged
import uk.gov.justice.digital.hmpps.messaging.ProbationOffenderEvent
import uk.gov.justice.digital.hmpps.resourceloader.ResourceLoader

object MessageGenerator {
val SENTENCE_DATE_CHANGED = ResourceLoader.message<CustodyDateChanged>("sentence-date-changed")
val SENTENCE_CHANGED = ResourceLoader.message<ProbationOffenderEvent>("sentence-changed")
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package uk.gov.justice.digital.hmpps.data.generator
import uk.gov.justice.digital.hmpps.integrations.delius.person.Person

object PersonGenerator {
val DEFAULT = Person(IdGenerator.getAndIncrement(), "A5089DY")
val PERSON_WITH_KEYDATES = Person(IdGenerator.getAndIncrement(), "A0001DY")
val DEFAULT = Person(IdGenerator.getAndIncrement(), "A500000", "A5089DY")
val PERSON_WITH_KEYDATES = Person(IdGenerator.getAndIncrement(), "A000001", "A0001DY")
val PERSON_WITH_KEYDATES_BY_CRN = Person(IdGenerator.getAndIncrement(), "A000002", "A0002DY")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"Type": "Notification",
"MessageId": "20e13002-d1be-56e7-be8c-66cdd7e23341",
"TopicArn": "arn:aws:sns:eu-west-2:754256621582:cloud-platform-Digital-Prison-Services-f221e27fcfcf78f6ab4f4c3cc165eee7",
"Message": "{\"eventType\":\"SENTENCE_CHANGED\",\"eventDatetime\":\"2020-02-25T11:24:32.935401\",\"offenderId\":1,\"crn\":\"A000002\",\"sourceId\":\"2500974056\"}",
"Timestamp": "2020-02-25T11:25:16.169Z",
"SignatureVersion": "1",
"Signature": "h5p3FnnbsSHxj53RFePh8HR40cbVvgEZa6XUVTlYs/yuqfDsi17MPA+bX4ijKmmTT2l6xG2xYhcmRAbJWQ4wrwncTBm2azgiwSO5keRNWYVdiC0rI484KLZboP1SDsE+Y7hOU/R0dz49q7+0yd+QIocPteKB/8xG7/6kjGStAZKf3cEdlxOwLhN+7RU1Yk2ENuwAJjVRtvlAa76yKB3xvL2hId7P7ZLmHGlzZDNZNYxbg9C8HGxteOzZ9ZeeQsWDf9jmZ+5+7dKXQoW9LeqwHxEAq2vuwSZ8uwM5JljXbtS5w1P0psXPYNoin2gU1F5MDK8RPzjUtIvjINx08rmEOA==",
"SigningCertURL": "https://sns.eu-west-2.amazonaws.com/SimpleNotificationService-a86cb10b4e1f29c941702d737128f7b6.pem",
"UnsubscribeURL": "https://sns.eu-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-2:754256621582:cloud-platform-Digital-Prison-Services-f221e27fcfcf78f6ab4f4c3cc165eee7:92545cfe-de5d-43e1-8339-c366bf0172aa",
"MessageAttributes": {
"eventType": {
"Type": "String",
"Value": "SENTENCE_CHANGED"
},
"id": {
"Type": "String",
"Value": "cb4645f2-d0c1-4677-806a-8036ed54bf69"
},
"contentType": {
"Type": "String",
"Value": "text/plain;charset=UTF-8"
},
"timestamp": {
"Type": "Number.java.lang.Long",
"Value": "1582629916147"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"bookingId": 1200837,
"bookingNo": "48340A",
"offenderNo": "A0002DY",
"firstName": "Jim",
"lastName": "Smith",
"agencyId": "MDI",
"activeFlag": true,
"dateOfBirth": "1970-01-01"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"bookingId": 1200837,
"bookingNo": "48340A",
"offenderNo": "A0002DY",
"active": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"bookingId": 1200837,
"sentenceExpiryDate": "2025-09-10",
"conditionalReleaseDate": "2022-11-25",
"conditionalReleaseOverrideDate": "2022-11-26",
"licenceExpiryDate": "2025-09-11",
"releaseDate": "2022-11-26",
"confirmedReleaseDate": "2022-11-27",
"homeDetentionCurfewEligibilityDate": "2022-10-28"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"mappings": [
{
"request": {
"method": "GET",
"urlPath": "/api/bookings/1200837"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"bodyFileName": "booking_1200837.json"
}
},
{
"request": {
"method": "GET",
"urlPath": "/api/bookings/offenderNo/A0002DY"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"bodyFileName": "booking_noms_A0002DY.json"
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"request": {
"method": "GET",
"urlPath": "/api/bookings/1200837/sentenceDetail"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"bodyFileName": "sentence_detail_1200837.json"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package uk.gov.justice.digital.hmpps
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test
import org.mockito.ArgumentMatchers.anyMap
import org.mockito.kotlin.check
Expand All @@ -21,11 +20,11 @@ import uk.gov.justice.digital.hmpps.integrations.delius.custody.date.Custody
import uk.gov.justice.digital.hmpps.integrations.delius.custody.date.CustodyDateType
import uk.gov.justice.digital.hmpps.integrations.delius.custody.date.CustodyRepository
import uk.gov.justice.digital.hmpps.integrations.delius.custody.date.contact.ContactRepository
import uk.gov.justice.digital.hmpps.message.MessageAttributes
import uk.gov.justice.digital.hmpps.message.Notification
import uk.gov.justice.digital.hmpps.messaging.HmppsChannelManager
import uk.gov.justice.digital.hmpps.telemetry.TelemetryService
import uk.gov.justice.digital.hmpps.telemetry.notificationReceived
import java.time.Duration
import java.time.LocalDate
import java.time.ZonedDateTime
import java.time.temporal.ChronoUnit
Expand Down Expand Up @@ -53,10 +52,10 @@ internal class IntegrationTest {
val notification = Notification(message = MessageGenerator.SENTENCE_DATE_CHANGED)

val first = CompletableFuture.runAsync {
channelManager.getChannel(queueName).publishAndWait(notification, Duration.ofMinutes(3))
channelManager.getChannel(queueName).publishAndWait(notification)
}
val second = CompletableFuture.runAsync {
channelManager.getChannel(queueName).publishAndWait(notification, Duration.ofMinutes(3))
channelManager.getChannel(queueName).publishAndWait(notification)
}

CompletableFuture.allOf(first, second).join()
Expand All @@ -83,6 +82,44 @@ internal class IntegrationTest {
)
}

@Test
fun `Custody Key Dates updated from SENTENCE_CHANGED event`() {
val notification = Notification(
message = MessageGenerator.SENTENCE_CHANGED,
attributes = MessageAttributes(eventType = "SENTENCE_CHANGED")
)

val first = CompletableFuture.runAsync {
channelManager.getChannel(queueName).publishAndWait(notification)
}
val second = CompletableFuture.runAsync {
channelManager.getChannel(queueName).publishAndWait(notification)
}

CompletableFuture.allOf(first, second).join()

verify(telemetryService, times(2)).notificationReceived(notification)

val custodyId =
custodyRepository.findCustodyId(PersonGenerator.PERSON_WITH_KEYDATES_BY_CRN.id, "48340A").first()
val custody = custodyRepository.findCustodyById(custodyId)
verifyUpdatedKeyDates(custody)

verify(telemetryService).trackEvent(
eq("KeyDatesUpdated"),
check {
assertThat(it[CustodyDateType.SENTENCE_EXPIRY_DATE.code], equalTo("2025-09-10"))
},
anyMap()
)

verify(telemetryService).trackEvent(
eq("KeyDatesUnchanged"),
anyMap(),
anyMap()
)
}

private fun verifyUpdatedKeyDates(custody: Custody) {
val sed = custody.keyDate(CustodyDateType.SENTENCE_EXPIRY_DATE.code)
val crd = custody.keyDate(CustodyDateType.AUTOMATIC_CONDITIONAL_RELEASE_DATE.code)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ class Person(
@Column(name = "offender_id")
val id: Long,

@Column(columnDefinition = "char(7)")
val crn: String,

@Column(name = "NOMS_NUMBER", columnDefinition = "char(7)")
val nomsId: String,
val nomsId: String?,

@Column(updatable = false, columnDefinition = "NUMBER")
val softDeleted: Boolean = false
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package uk.gov.justice.digital.hmpps.integrations.delius.person

import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query

interface PersonRepository : JpaRepository<Person, Long> {
fun findByNomsIdIgnoreCaseAndSoftDeletedIsFalse(nomsId: String): Person?

@Query("select p.nomsId from Person p where p.crn = :crn and p.softDeleted = false")
fun findNomsIdByCrn(crn: String): String?
}

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.springframework.context.annotation.Primary
import org.springframework.stereotype.Component
import uk.gov.justice.digital.hmpps.converter.NotificationConverter
import uk.gov.justice.digital.hmpps.integrations.delius.custody.date.CustodyDateUpdateService
import uk.gov.justice.digital.hmpps.integrations.delius.person.PersonRepository
import uk.gov.justice.digital.hmpps.message.HmppsDomainEvent
import uk.gov.justice.digital.hmpps.message.Notification
import uk.gov.justice.digital.hmpps.telemetry.TelemetryService
Expand All @@ -20,7 +21,8 @@ import uk.gov.justice.digital.hmpps.telemetry.notificationReceived
class Handler(
override val converter: KeyDateChangedEventConverter,
private val cduService: CustodyDateUpdateService,
private val telemetryService: TelemetryService
private val telemetryService: TelemetryService,
private val personRepository: PersonRepository
) : NotificationHandler<Any> {
@Publish(
messages = [
Expand All @@ -30,6 +32,7 @@ class Handler(
Message(messageId = "CONFIRMED_RELEASE_DATE-CHANGED", payload = Schema(CustodyDateChanged::class)),
Message(messageId = "KEY_DATE_ADJUSTMENT_UPSERTED", payload = Schema(CustodyDateChanged::class)),
Message(messageId = "KEY_DATE_ADJUSTMENT_DELETED", payload = Schema(CustodyDateChanged::class)),
Message(messageId = "SENTENCE_CHANGED", payload = Schema(ProbationOffenderEvent::class)),
]
)
override fun handle(notification: Notification<Any>) {
Expand All @@ -39,12 +42,22 @@ class Handler(
?.let { cduService.updateCustodyKeyDates(it) }

is CustodyDateChanged -> cduService.updateCustodyKeyDates(message.bookingId)
is ProbationOffenderEvent -> when (notification.eventType) {
"SENTENCE_CHANGED",
-> personRepository.findNomsIdByCrn(message.crn)?.let { cduService.updateCustodyKeyDates(it) }

else -> throw IllegalArgumentException("Unexpected offender event type: ${notification.eventType}")
}
}
}
}

@Message
data class CustodyDateChanged(val bookingId: Long)

@Message
data class ProbationOffenderEvent(val crn: String)

@Primary
@Component
class KeyDateChangedEventConverter(objectMapper: ObjectMapper) : NotificationConverter<Any>(objectMapper) {
Expand All @@ -59,6 +72,12 @@ class KeyDateChangedEventConverter(objectMapper: ObjectMapper) : NotificationCon
attributes = stringMessage.attributes
)
}
if (json.has("crn")) {
return Notification(
message = objectMapper.readValue(stringMessage.message, ProbationOffenderEvent::class.java),
attributes = stringMessage.attributes
)
}
return Notification(
message = objectMapper.readValue(stringMessage.message, HmppsDomainEvent::class.java),
attributes = stringMessage.attributes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ internal class CustodyDateUpdateServiceTest {

@Test
fun `Multiple matching custody logged to telemetry`() {
val booking = Booking(127, "FG37K", true, PersonGenerator.DEFAULT.nomsId)
val booking = Booking(127, "FG37K", true, PersonGenerator.DEFAULT.nomsId!!)

whenever(prisonApi.getSentenceDetail(booking.id)).thenReturn(SentenceDetail())
whenever(prisonApi.getBooking(booking.id, basicInfo = false, extraInfo = true)).thenReturn(booking)
Expand All @@ -93,7 +93,7 @@ internal class CustodyDateUpdateServiceTest {

@Test
fun `No matching custody logged to telemetry`() {
val booking = Booking(127, "FG37K", true, PersonGenerator.DEFAULT.nomsId)
val booking = Booking(127, "FG37K", true, PersonGenerator.DEFAULT.nomsId!!)

whenever(prisonApi.getSentenceDetail(booking.id)).thenReturn(SentenceDetail())
whenever(prisonApi.getBooking(booking.id, basicInfo = false, extraInfo = true)).thenReturn(booking)
Expand All @@ -111,7 +111,7 @@ internal class CustodyDateUpdateServiceTest {

@Test
fun `key date save and delete not called without appropriate key dates`() {
val booking = Booking(127, "FG37K", true, PersonGenerator.DEFAULT.nomsId)
val booking = Booking(127, "FG37K", true, PersonGenerator.DEFAULT.nomsId!!)
val custody = SentenceGenerator.generateCustodialSentence(
disposal = SentenceGenerator.generateDisposal(SentenceGenerator.generateEvent()),
bookingRef = booking.bookingNo
Expand Down
Loading

0 comments on commit da9d12a

Please sign in to comment.