diff --git a/projects/approved-premises-and-delius/deploy/values.yaml b/projects/approved-premises-and-delius/deploy/values.yaml index 2c05c5008b..4493743cbb 100644 --- a/projects/approved-premises-and-delius/deploy/values.yaml +++ b/projects/approved-premises-and-delius/deploy/values.yaml @@ -25,6 +25,8 @@ generic-service: SENTRY_DSN: SENTRY_DSN approved-premises-and-delius-queue: MESSAGING_CONSUMER_QUEUE: QUEUE_NAME + hmpps-domain-events-topic: + MESSAGING_PRODUCER_TOPIC: topic_arn generic-prometheus-alerts: targetApplication: approved-premises-and-delius \ No newline at end of file diff --git a/projects/approved-premises-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/MessagingIntegrationTest.kt b/projects/approved-premises-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/MessagingIntegrationTest.kt index 9e0e4365b7..f0f683716e 100644 --- a/projects/approved-premises-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/MessagingIntegrationTest.kt +++ b/projects/approved-premises-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/MessagingIntegrationTest.kt @@ -5,6 +5,7 @@ import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.containsString import org.hamcrest.Matchers.equalTo import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.MethodOrderer.OrderAnnotation import org.junit.jupiter.api.Order import org.junit.jupiter.api.Test @@ -36,6 +37,7 @@ import uk.gov.justice.digital.hmpps.integrations.delius.person.address.PersonAdd import uk.gov.justice.digital.hmpps.integrations.delius.referencedata.ApprovedPremisesCategoryCode import uk.gov.justice.digital.hmpps.integrations.delius.staff.StaffRepository import uk.gov.justice.digital.hmpps.integrations.delius.staff.getByCode +import uk.gov.justice.digital.hmpps.message.HmppsDomainEvent import uk.gov.justice.digital.hmpps.messaging.HmppsChannelManager import uk.gov.justice.digital.hmpps.messaging.crn import uk.gov.justice.digital.hmpps.messaging.telemetryProperties @@ -51,6 +53,9 @@ internal class MessagingIntegrationTest { @Value("\${messaging.consumer.queue}") lateinit var queueName: String + @Value("\${messaging.producer.topic}") + lateinit var topicName: String + @Autowired lateinit var channelManager: HmppsChannelManager @@ -81,6 +86,14 @@ internal class MessagingIntegrationTest { @Autowired private lateinit var staffRepository: StaffRepository + @BeforeEach + fun clearTopic() { + val topic = channelManager.getChannel(topicName) + do { + val message = topic.receive()?.also { topic.done(it.id) } + } while (message != null) + } + @Test fun `application submission creates an alert contact`() { // Given an application-submitted event @@ -299,6 +312,13 @@ internal class MessagingIntegrationTest { assertThat(main.postcode, equalTo(ap.postcode)) assertThat(main.telephoneNumber, equalTo(ap.telephoneNumber)) + // And a domain event is published for the new address + val domainEvent = channelManager.getChannel(topicName).receive()?.message as HmppsDomainEvent + assertThat(domainEvent.eventType, equalTo("probation-case.address.created")) + assertThat(domainEvent.crn(), equalTo(event.message.crn())) + assertThat(domainEvent.additionalInformation["addressId"], equalTo(main.id)) + assertThat(domainEvent.additionalInformation["addressStatus"], equalTo("Main Address")) + val keyWorker = staffRepository.getByCode("N54A001") val residences = residenceRepository.findAll().filter { it.personId == contact.person.id } assertThat(residences.size, equalTo(1)) @@ -350,6 +370,11 @@ internal class MessagingIntegrationTest { assertNull(personAddressRepository.findMainAddress(PersonGenerator.DEFAULT.id)) + val domainEvent = channelManager.getChannel(topicName).receive()?.message as HmppsDomainEvent + assertThat(domainEvent.eventType, equalTo("probation-case.address.updated")) + assertThat(domainEvent.crn(), equalTo(event.message.crn())) + assertThat(domainEvent.additionalInformation["addressStatus"], equalTo("Previous Address")) + val residence = residenceRepository.findAll().first { it.personId == contact.person.id } assertThat(residence.departureDate, equalTo(nsi.actualEndDate)) assertThat(residence.departureReasonId, equalTo(ReferenceDataGenerator.ORDER_EXPIRED.id)) diff --git a/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Notifier.kt b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Notifier.kt new file mode 100644 index 0000000000..eeb528ed5c --- /dev/null +++ b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Notifier.kt @@ -0,0 +1,64 @@ +package uk.gov.justice.digital.hmpps.messaging + +import org.openfolder.kotlinasyncapi.annotation.Schema +import org.openfolder.kotlinasyncapi.annotation.channel.Channel +import org.openfolder.kotlinasyncapi.annotation.channel.Message +import org.openfolder.kotlinasyncapi.annotation.channel.Subscribe +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.stereotype.Service +import uk.gov.justice.digital.hmpps.message.* +import uk.gov.justice.digital.hmpps.publisher.NotificationPublisher + +@Service +@Channel("hmpps-domain-events-topic") +class Notifier(@Qualifier("topicPublisher") private val topicPublisher: NotificationPublisher) { + @Subscribe( + messages = [ + Message(title = "probation-case.address.created", payload = Schema(HmppsDomainEvent::class)), + Message(title = "probation-case.address.updated", payload = Schema(HmppsDomainEvent::class)) + ] + ) + fun addressCreated(crn: String, addressId: Long, addressStatus: String) { + topicPublisher.publish( + Notification( + message = HmppsDomainEvent( + version = 1, + eventType = "probation-case.address.created", + description = "A new address has been created on the probation case", + personReference = PersonReference( + identifiers = listOf( + PersonIdentifier("CRN", crn), + ), + ), + additionalInformation = mapOf( + "addressStatus" to addressStatus, + "addressId" to addressId + ) + ), + attributes = MessageAttributes("probation-case.address.created") + ) + ) + } + + fun addressUpdated(crn: String, addressId: Long, addressStatus: String) { + topicPublisher.publish( + Notification( + message = HmppsDomainEvent( + version = 1, + eventType = "probation-case.address.updated", + description = "An address has been updated on the probation case", + personReference = PersonReference( + identifiers = listOf( + PersonIdentifier("CRN", crn), + ), + ), + additionalInformation = mapOf( + "addressStatus" to addressStatus, + "addressId" to addressId + ) + ), + attributes = MessageAttributes("probation-case.address.updated") + ) + ) + } +} \ No newline at end of file diff --git a/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/AddressService.kt b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/AddressService.kt index a5557c8edc..628737b809 100644 --- a/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/AddressService.kt +++ b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/AddressService.kt @@ -17,14 +17,19 @@ class AddressService( private val personAddressRepository: PersonAddressRepository, private val referenceDataRepository: ReferenceDataRepository ) { - fun updateMainAddress(person: Person, details: PersonArrived, ap: ApprovedPremises) { - endMainAddress(person, details.arrivedAt.toLocalDate()) - ap.arrival(person, details).apply(personAddressRepository::save) + fun updateMainAddress( + person: Person, + details: PersonArrived, + ap: ApprovedPremises + ): Pair { + val previous = endMainAddress(person, details.arrivedAt.toLocalDate()) + val current = ap.arrival(person, details).let(personAddressRepository::save) + return previous to current } - fun endMainAddress(person: Person, endDate: LocalDate) { + fun endMainAddress(person: Person, endDate: LocalDate): PersonAddress? { val currentMain = personAddressRepository.findMainAddress(person.id) - currentMain?.apply { + return currentMain?.also { val previousStatus = referenceDataRepository.previousAddressStatus() currentMain.status = previousStatus currentMain.endDate = endDate diff --git a/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/ApprovedPremisesService.kt b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/ApprovedPremisesService.kt index a266e33093..2e5d15f4cb 100644 --- a/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/ApprovedPremisesService.kt +++ b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/ApprovedPremisesService.kt @@ -12,6 +12,7 @@ import uk.gov.justice.digital.hmpps.integrations.delius.person.getByCrn import uk.gov.justice.digital.hmpps.integrations.delius.staff.StaffRepository import uk.gov.justice.digital.hmpps.integrations.delius.staff.getByCode import uk.gov.justice.digital.hmpps.message.HmppsDomainEvent +import uk.gov.justice.digital.hmpps.messaging.Notifier import uk.gov.justice.digital.hmpps.messaging.crn import uk.gov.justice.digital.hmpps.messaging.url @@ -24,7 +25,8 @@ class ApprovedPremisesService( private val eventRepository: EventRepository, private val contactService: ContactService, private val nsiService: NsiService, - private val referralService: ReferralService + private val referralService: ReferralService, + private val notifier: Notifier, ) { fun applicationSubmitted(event: HmppsDomainEvent) { val details = approvedPremisesApiClient.getApplicationSubmittedDetails(event.url()).eventDetails @@ -115,13 +117,18 @@ class ApprovedPremisesService( val details = approvedPremisesApiClient.getPersonArrivedDetails(event.url()).eventDetails val person = personRepository.getByCrn(event.crn()) val ap = approvedPremisesRepository.getApprovedPremises(details.premises.legacyApCode) - nsiService.personArrived(person, details, ap) + nsiService.personArrived(person, details, ap)?.let { (previousAddress, newAddress) -> + notifier.addressCreated(person.crn, newAddress.id, newAddress.status.description) + previousAddress?.let { notifier.addressUpdated(person.crn, it.id, it.status.description) } + } } fun personDeparted(event: HmppsDomainEvent) { val details = approvedPremisesApiClient.getPersonDepartedDetails(event.url()).eventDetails val person = personRepository.getByCrn(event.crn()) val ap = approvedPremisesRepository.getApprovedPremises(details.premises.legacyApCode) - nsiService.personDeparted(person, details, ap) + nsiService.personDeparted(person, details, ap)?.let { updatedAddress -> + notifier.addressUpdated(person.crn, updatedAddress.id, updatedAddress.status.description) + } } } diff --git a/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/NsiService.kt b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/NsiService.kt index b4a82e68b8..5da22f2981 100644 --- a/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/NsiService.kt +++ b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/NsiService.kt @@ -11,6 +11,7 @@ import uk.gov.justice.digital.hmpps.integrations.delius.contact.outcome.ContactO import uk.gov.justice.digital.hmpps.integrations.delius.contact.type.ContactTypeCode import uk.gov.justice.digital.hmpps.integrations.delius.nonstatutoryintervention.entity.* import uk.gov.justice.digital.hmpps.integrations.delius.person.Person +import uk.gov.justice.digital.hmpps.integrations.delius.person.address.PersonAddress import uk.gov.justice.digital.hmpps.integrations.delius.referencedata.ReferenceDataRepository import uk.gov.justice.digital.hmpps.integrations.delius.referencedata.referralCompleted import uk.gov.justice.digital.hmpps.integrations.delius.staff.StaffRepository @@ -38,7 +39,7 @@ class NsiService( person: Person, details: PersonArrived, ap: ApprovedPremises - ) { + ): Pair? { val externalReference = Nsi.EXT_REF_BOOKING_PREFIX + details.bookingId nsiRepository.findByPersonIdAndExternalReference(person.id, externalReference) ?: run { val staff = staffRepository.getByCode(details.keyWorker.staffCode) @@ -69,7 +70,6 @@ class NsiService( transferReason = transferReasonRepository.getNsiTransferReason() ) ) - addressService.updateMainAddress(person, details, ap) contactService.createContact( ContactDetails( date = details.arrivedAt, @@ -88,15 +88,16 @@ class NsiService( probationAreaCode = ap.probationArea.code ) referralService.personArrived(person, ap, details) + return addressService.updateMainAddress(person, details, ap) } + return null } - fun personDeparted(person: Person, details: PersonDeparted, ap: ApprovedPremises) { + fun personDeparted(person: Person, details: PersonDeparted, ap: ApprovedPremises): PersonAddress? { val nsi = nsiRepository.findByPersonIdAndExternalReference(person.id, Nsi.EXT_REF_BOOKING_PREFIX + details.bookingId) nsi?.actualEndDate = details.departedAt nsi?.outcome = referenceDataRepository.referralCompleted() - addressService.endMainAddress(person, details.departedAt.toLocalDate()) contactService.createContact( ContactDetails( date = details.departedAt, @@ -114,5 +115,6 @@ class NsiService( probationAreaCode = ap.probationArea.code ) referralService.personDeparted(person, details) + return addressService.endMainAddress(person, details.departedAt.toLocalDate()) } } diff --git a/projects/approved-premises-and-delius/src/main/resources/application.yml b/projects/approved-premises-and-delius/src/main/resources/application.yml index 9547021ad2..0cb9bb95cd 100644 --- a/projects/approved-premises-and-delius/src/main/resources/application.yml +++ b/projects/approved-premises-and-delius/src/main/resources/application.yml @@ -65,6 +65,7 @@ wiremock.enabled: true context.initializer.classes: uk.gov.justice.digital.hmpps.wiremock.WireMockInitialiser messaging.consumer.queue: message-queue +messaging.producer.topic: domain-events integrations: approved-premises-api: diff --git a/projects/approved-premises-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/ApprovedPremisesServiceTest.kt b/projects/approved-premises-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/ApprovedPremisesServiceTest.kt index a043d549dc..6998474f0a 100644 --- a/projects/approved-premises-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/ApprovedPremisesServiceTest.kt +++ b/projects/approved-premises-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/ApprovedPremisesServiceTest.kt @@ -40,6 +40,7 @@ import uk.gov.justice.digital.hmpps.integrations.delius.staff.Staff import uk.gov.justice.digital.hmpps.integrations.delius.staff.StaffRepository import uk.gov.justice.digital.hmpps.integrations.delius.team.Team import uk.gov.justice.digital.hmpps.integrations.delius.team.TeamRepository +import uk.gov.justice.digital.hmpps.messaging.Notifier import uk.gov.justice.digital.hmpps.messaging.crn import uk.gov.justice.digital.hmpps.messaging.url import uk.gov.justice.digital.hmpps.prepEvent @@ -131,6 +132,9 @@ internal class ApprovedPremisesServiceTest { @Mock lateinit var applicationStartedEvent: ApplicationStartedEvent + @Mock + lateinit var notifier: Notifier + lateinit var addressService: AddressService lateinit var contactService: ContactService lateinit var nsiService: NsiService @@ -191,7 +195,8 @@ internal class ApprovedPremisesServiceTest { eventRepository, contactService, nsiService, - referralService + referralService, + notifier, ) } @@ -304,6 +309,7 @@ internal class ApprovedPremisesServiceTest { givenAddressTypes(listOf(ReferenceDataGenerator.AP_ADDRESS_TYPE)) givenAuditUser() givenReferral(person, details.eventDetails.bookingId) + whenever(personAddressRepository.save(any())).thenAnswer { it.arguments[0] } approvedPremisesService.personArrived(personArrivedEvent) diff --git a/projects/cas3-and-delius/deploy/values.yaml b/projects/cas3-and-delius/deploy/values.yaml index 6d00b2e13c..dbb8f09d9c 100644 --- a/projects/cas3-and-delius/deploy/values.yaml +++ b/projects/cas3-and-delius/deploy/values.yaml @@ -22,6 +22,8 @@ generic-service: SENTRY_DSN: SENTRY_DSN cas3-and-delius-queue: MESSAGING_CONSUMER_QUEUE: QUEUE_NAME + hmpps-domain-events-topic: + MESSAGING_PRODUCER_TOPIC: topic_arn generic-prometheus-alerts: targetApplication: cas3-and-delius diff --git a/projects/cas3-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/CASIntegrationTest.kt b/projects/cas3-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/CASIntegrationTest.kt index 0eaea2d3b9..e33016b105 100644 --- a/projects/cas3-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/CASIntegrationTest.kt +++ b/projects/cas3-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/CASIntegrationTest.kt @@ -23,6 +23,7 @@ import uk.gov.justice.digital.hmpps.integrations.approvedpremises.* import uk.gov.justice.digital.hmpps.integrations.delius.entity.ContactRepository import uk.gov.justice.digital.hmpps.integrations.delius.entity.PersonAddressRepository import uk.gov.justice.digital.hmpps.integrations.delius.entity.PersonRepository +import uk.gov.justice.digital.hmpps.message.HmppsDomainEvent import uk.gov.justice.digital.hmpps.messaging.HmppsChannelManager import uk.gov.justice.digital.hmpps.messaging.crn import uk.gov.justice.digital.hmpps.resourceloader.ResourceLoader @@ -37,6 +38,9 @@ internal class CASIntegrationTest { @Value("\${messaging.consumer.queue}") lateinit var queueName: String + @Value("\${messaging.producer.topic}") + lateinit var topicName: String + @Autowired lateinit var channelManager: HmppsChannelManager @@ -182,6 +186,12 @@ internal class CASIntegrationTest { assertThat(address.streetName, equalTo("12 Church Street")) assertThat(address.county, equalTo("Bibbinghammcshireshire")) assertThat(address.postcode, equalTo("BB1 1BB")) + + val domainEvent = channelManager.getChannel(topicName).receive()?.message as HmppsDomainEvent + assertThat(domainEvent.eventType, equalTo("probation-case.address.created")) + assertThat(domainEvent.crn(), equalTo(event.message.crn())) + assertThat(domainEvent.additionalInformation["addressId"], equalTo(address.id)) + assertThat(domainEvent.additionalInformation["addressStatus"], equalTo("Main Address")) } @Test @@ -215,6 +225,12 @@ internal class CASIntegrationTest { assertThat(address!!.status.code, equalTo("P")) assertThat(contact.teamId, equalTo(ProviderGenerator.DEFAULT_TEAM.id)) assertThat(contact.staffId, equalTo(ProviderGenerator.DEFAULT_STAFF.id)) + + val domainEvent = channelManager.getChannel(topicName).receive()?.message as HmppsDomainEvent + assertThat(domainEvent.eventType, equalTo("probation-case.address.updated")) + assertThat(domainEvent.crn(), equalTo(event.message.crn())) + assertThat(domainEvent.additionalInformation["addressId"], equalTo(address.id)) + assertThat(domainEvent.additionalInformation["addressStatus"], equalTo("Main Address")) } @Test diff --git a/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt b/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt index 83fcd8d4ee..a9c1756674 100644 --- a/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt +++ b/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt @@ -5,30 +5,21 @@ import org.openfolder.kotlinasyncapi.annotation.channel.Channel import org.openfolder.kotlinasyncapi.annotation.channel.Message import org.openfolder.kotlinasyncapi.annotation.channel.Publish import org.springframework.stereotype.Component -import org.springframework.transaction.annotation.Transactional import uk.gov.justice.digital.hmpps.converter.NotificationConverter -import uk.gov.justice.digital.hmpps.datetime.DeliusDateTimeFormatter -import uk.gov.justice.digital.hmpps.integrations.approvedpremises.Cas3ApiClient -import uk.gov.justice.digital.hmpps.integrations.delius.AddressService -import uk.gov.justice.digital.hmpps.integrations.delius.ContactService -import uk.gov.justice.digital.hmpps.integrations.delius.entity.PersonRepository -import uk.gov.justice.digital.hmpps.integrations.delius.entity.getByCrn import uk.gov.justice.digital.hmpps.message.HmppsDomainEvent import uk.gov.justice.digital.hmpps.message.Notification +import uk.gov.justice.digital.hmpps.service.Cas3Service import uk.gov.justice.digital.hmpps.telemetry.TelemetryMessagingExtensions.notificationReceived import uk.gov.justice.digital.hmpps.telemetry.TelemetryService import java.net.URI @Component -@Transactional @Channel("cas3-and-delius-queue") class Handler( override val converter: NotificationConverter, private val telemetryService: TelemetryService, - private val contactService: ContactService, - private val addressService: AddressService, - private val cas3ApiClient: Cas3ApiClient, - private val personRepository: PersonRepository + private val cas3Service: Cas3Service, + private val notifier: Notifier, ) : NotificationHandler { @Publish( messages = [ @@ -75,76 +66,53 @@ class Handler( val event = notification.message when (event.eventType) { "accommodation.cas3.referral.submitted" -> { - contactService.createOrUpdateContact(event.crn()) { - cas3ApiClient.getApplicationSubmittedDetails(event.url()) - } + cas3Service.referralSubmitted(event) telemetryService.trackEvent("ApplicationSubmitted", event.telemetryProperties()) } "accommodation.cas3.booking.cancelled" -> { - contactService.createOrUpdateContact(event.crn()) { - cas3ApiClient.getBookingCancelledDetails(event.url()) - } + cas3Service.bookingCancelled(event) telemetryService.trackEvent("BookingCancelled", event.telemetryProperties()) } "accommodation.cas3.booking.confirmed" -> { - contactService.createOrUpdateContact(event.crn()) { - cas3ApiClient.getBookingConfirmedDetails(event.url()) - } + cas3Service.bookingConfirmed(event) telemetryService.trackEvent("BookingConfirmed", event.telemetryProperties()) } "accommodation.cas3.booking.provisionally-made" -> { - contactService.createOrUpdateContact(event.crn()) { - cas3ApiClient.getBookingProvisionallyMade(event.url()) - } + cas3Service.bookingProvisionallyMade(event) telemetryService.trackEvent("BookingProvisionallyMade", event.telemetryProperties()) } "accommodation.cas3.person.arrived" -> { - val person = personRepository.getByCrn(event.crn()) - val detail = cas3ApiClient.getPersonArrived(event.url()) - contactService.createOrUpdateContact(event.crn(), person) { - detail - } - addressService.updateMainAddress(person, detail.eventDetails) + val (previousAddress, newAddress) = cas3Service.personArrived(event) + notifier.addressCreated(event.crn(), newAddress.id, newAddress.status.description) + previousAddress?.let { notifier.addressUpdated(event.crn(), it.id, it.status.description) } telemetryService.trackEvent("PersonArrived", event.telemetryProperties()) } "accommodation.cas3.person.departed" -> { - val person = personRepository.getByCrn(event.crn()) - val detail = cas3ApiClient.getPersonDeparted(event.url()) - contactService.createOrUpdateContact(event.crn(), person) { - detail + cas3Service.personDeparted(event)?.let { updatedAddress -> + notifier.addressUpdated(event.crn(), updatedAddress.id, updatedAddress.status.description) } - addressService.endMainCAS3Address(person, detail.eventDetails.departedAt.toLocalDate()) telemetryService.trackEvent("PersonDeparted", event.telemetryProperties()) } "accommodation.cas3.person.arrived.updated" -> { - val person = personRepository.getByCrn(event.crn()) - val detail = cas3ApiClient.getPersonArrived(event.url()) - contactService.createOrUpdateContact( - event.crn(), - replaceNotes = false, - extraInfo = "Address details were updated: ${DeliusDateTimeFormatter.format(detail.timestamp)}" - ) { detail } - addressService.updateCas3Address(person, detail.eventDetails) + cas3Service.personArrivedUpdated(event)?.let { updatedAddress -> + notifier.addressUpdated(event.crn(), updatedAddress.id, updatedAddress.status.description) + } telemetryService.trackEvent("PersonArrivedUpdated", event.telemetryProperties()) } "accommodation.cas3.person.departed.updated" -> { - contactService.createOrUpdateContact(event.crn(), replaceNotes = false) { - cas3ApiClient.getPersonDeparted(event.url()) - } + cas3Service.personDepartedUpdated(event) telemetryService.trackEvent("PersonDepartedUpdated", event.telemetryProperties()) } "accommodation.cas3.booking.cancelled.updated" -> { - contactService.createOrUpdateContact(event.crn(), replaceNotes = false) { - cas3ApiClient.getBookingCancelledDetails(event.url()) - } + cas3Service.bookingCancelledUpdated(event) telemetryService.trackEvent("BookingCancelledUpdated", event.telemetryProperties()) } diff --git a/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Notifier.kt b/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Notifier.kt new file mode 100644 index 0000000000..eeb528ed5c --- /dev/null +++ b/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Notifier.kt @@ -0,0 +1,64 @@ +package uk.gov.justice.digital.hmpps.messaging + +import org.openfolder.kotlinasyncapi.annotation.Schema +import org.openfolder.kotlinasyncapi.annotation.channel.Channel +import org.openfolder.kotlinasyncapi.annotation.channel.Message +import org.openfolder.kotlinasyncapi.annotation.channel.Subscribe +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.stereotype.Service +import uk.gov.justice.digital.hmpps.message.* +import uk.gov.justice.digital.hmpps.publisher.NotificationPublisher + +@Service +@Channel("hmpps-domain-events-topic") +class Notifier(@Qualifier("topicPublisher") private val topicPublisher: NotificationPublisher) { + @Subscribe( + messages = [ + Message(title = "probation-case.address.created", payload = Schema(HmppsDomainEvent::class)), + Message(title = "probation-case.address.updated", payload = Schema(HmppsDomainEvent::class)) + ] + ) + fun addressCreated(crn: String, addressId: Long, addressStatus: String) { + topicPublisher.publish( + Notification( + message = HmppsDomainEvent( + version = 1, + eventType = "probation-case.address.created", + description = "A new address has been created on the probation case", + personReference = PersonReference( + identifiers = listOf( + PersonIdentifier("CRN", crn), + ), + ), + additionalInformation = mapOf( + "addressStatus" to addressStatus, + "addressId" to addressId + ) + ), + attributes = MessageAttributes("probation-case.address.created") + ) + ) + } + + fun addressUpdated(crn: String, addressId: Long, addressStatus: String) { + topicPublisher.publish( + Notification( + message = HmppsDomainEvent( + version = 1, + eventType = "probation-case.address.updated", + description = "An address has been updated on the probation case", + personReference = PersonReference( + identifiers = listOf( + PersonIdentifier("CRN", crn), + ), + ), + additionalInformation = mapOf( + "addressStatus" to addressStatus, + "addressId" to addressId + ) + ), + attributes = MessageAttributes("probation-case.address.updated") + ) + ) + } +} \ No newline at end of file diff --git a/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/AddressService.kt b/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/AddressService.kt similarity index 78% rename from projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/AddressService.kt rename to projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/AddressService.kt index eee4ba4e16..7b76c60430 100644 --- a/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/AddressService.kt +++ b/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/AddressService.kt @@ -1,4 +1,4 @@ -package uk.gov.justice.digital.hmpps.integrations.delius +package uk.gov.justice.digital.hmpps.service import org.springframework.stereotype.Service import uk.gov.justice.digital.hmpps.integrations.approvedpremises.PersonArrived @@ -11,17 +11,19 @@ class AddressService( private val referenceDataRepository: ReferenceDataRepository, private val personRepository: PersonRepository ) { - fun updateMainAddress(person: Person, details: PersonArrived) { - endMainAddress(person, details.arrivedAt.toLocalDate()) - toPersonAddress(person, details).apply(personAddressRepository::save) + fun updateMainAddress(person: Person, details: PersonArrived): Pair { + val previous = endMainAddress(person, details.arrivedAt.toLocalDate()) + val current = toPersonAddress(person, details).apply(personAddressRepository::save) + return previous to current } - fun updateCas3Address(person: Person, details: PersonArrived) { + fun updateCas3Address(person: Person, details: PersonArrived): PersonAddress? { personRepository.findForUpdate(person.id) val currentMain = personAddressRepository.findMainAddress(person.id) - if (currentMain?.type?.code == AddressTypeCode.CAS3.code) { - val addressLines = details.premises.addressLines - currentMain.apply { + return currentMain + ?.takeIf { it.type.code == AddressTypeCode.CAS3.code } + ?.apply { + val addressLines = details.premises.addressLines buildingName = addressLines.buildingName?.trim() streetName = addressLines.streetName.trim() district = addressLines.district?.trim() @@ -30,23 +32,22 @@ class AddressService( postcode = details.premises.postcode.trim() startDate = details.arrivedAt.toLocalDate() } - } } - fun endMainAddress(person: Person, endDate: LocalDate) { + fun endMainAddress(person: Person, endDate: LocalDate): PersonAddress? { personRepository.findForUpdate(person.id) val currentMain = personAddressRepository.findMainAddress(person.id) - currentMain?.apply { + return currentMain?.also { val previousStatus = referenceDataRepository.previousAddressStatus() currentMain.status = previousStatus currentMain.endDate = maxOf(endDate, currentMain.startDate) } } - fun endMainCAS3Address(person: Person, endDate: LocalDate) { + fun endMainCAS3Address(person: Person, endDate: LocalDate): PersonAddress? { personRepository.findForUpdate(person.id) val currentMain = personAddressRepository.findMainAddress(person.id) - currentMain?.apply { + return currentMain?.also { if (currentMain.type.code == AddressTypeCode.CAS3.code && currentMain.startDate <= endDate) { val previousStatus = referenceDataRepository.previousAddressStatus() currentMain.status = previousStatus diff --git a/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/Cas3Service.kt b/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/Cas3Service.kt new file mode 100644 index 0000000000..372e756910 --- /dev/null +++ b/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/Cas3Service.kt @@ -0,0 +1,82 @@ +package uk.gov.justice.digital.hmpps.service + +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import uk.gov.justice.digital.hmpps.datetime.DeliusDateTimeFormatter +import uk.gov.justice.digital.hmpps.integrations.approvedpremises.Cas3ApiClient +import uk.gov.justice.digital.hmpps.integrations.delius.entity.PersonAddress +import uk.gov.justice.digital.hmpps.integrations.delius.entity.PersonRepository +import uk.gov.justice.digital.hmpps.integrations.delius.entity.getByCrn +import uk.gov.justice.digital.hmpps.message.HmppsDomainEvent +import uk.gov.justice.digital.hmpps.messaging.crn +import uk.gov.justice.digital.hmpps.messaging.url + +@Service +@Transactional +class Cas3Service( + private val contactService: ContactService, + private val addressService: AddressService, + private val cas3ApiClient: Cas3ApiClient, + private val personRepository: PersonRepository +) { + fun referralSubmitted(event: HmppsDomainEvent) { + contactService.createOrUpdateContact(event.crn()) { + cas3ApiClient.getApplicationSubmittedDetails(event.url()) + } + } + + fun bookingCancelled(event: HmppsDomainEvent) { + contactService.createOrUpdateContact(event.crn()) { + cas3ApiClient.getBookingCancelledDetails(event.url()) + } + } + + fun bookingConfirmed(event: HmppsDomainEvent) { + contactService.createOrUpdateContact(event.crn()) { + cas3ApiClient.getBookingConfirmedDetails(event.url()) + } + } + + fun bookingProvisionallyMade(event: HmppsDomainEvent) { + contactService.createOrUpdateContact(event.crn()) { + cas3ApiClient.getBookingProvisionallyMade(event.url()) + } + } + + fun personArrived(event: HmppsDomainEvent): Pair { + val person = personRepository.getByCrn(event.crn()) + val detail = cas3ApiClient.getPersonArrived(event.url()) + contactService.createOrUpdateContact(event.crn(), person) { detail } + return addressService.updateMainAddress(person, detail.eventDetails) + } + + fun personDeparted(event: HmppsDomainEvent): PersonAddress? { + val person = personRepository.getByCrn(event.crn()) + val detail = cas3ApiClient.getPersonDeparted(event.url()) + contactService.createOrUpdateContact(event.crn(), person) { detail } + return addressService.endMainCAS3Address(person, detail.eventDetails.departedAt.toLocalDate()) + } + + fun personArrivedUpdated(event: HmppsDomainEvent): PersonAddress? { + val person = personRepository.getByCrn(event.crn()) + val detail = cas3ApiClient.getPersonArrived(event.url()) + contactService.createOrUpdateContact( + event.crn(), + replaceNotes = false, + extraInfo = "Address details were updated: ${DeliusDateTimeFormatter.format(detail.timestamp)}" + ) { detail } + return addressService.updateCas3Address(person, detail.eventDetails) + } + + fun personDepartedUpdated(event: HmppsDomainEvent) { + contactService.createOrUpdateContact(event.crn(), replaceNotes = false) { + cas3ApiClient.getPersonDeparted(event.url()) + } + } + + fun bookingCancelledUpdated(event: HmppsDomainEvent) { + contactService.createOrUpdateContact(event.crn(), replaceNotes = false) { + cas3ApiClient.getBookingCancelledDetails(event.url()) + } + } +} \ No newline at end of file diff --git a/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/ContactService.kt b/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/ContactService.kt similarity index 98% rename from projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/ContactService.kt rename to projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/ContactService.kt index 1420098e18..e95e85c5ed 100644 --- a/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/ContactService.kt +++ b/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/ContactService.kt @@ -1,4 +1,4 @@ -package uk.gov.justice.digital.hmpps.integrations.delius +package uk.gov.justice.digital.hmpps.service import org.springframework.stereotype.Service import uk.gov.justice.digital.hmpps.audit.service.AuditableService diff --git a/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/ProviderService.kt b/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/ProviderService.kt similarity index 95% rename from projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/ProviderService.kt rename to projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/ProviderService.kt index 7d8c03432c..e2ebb68408 100644 --- a/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/ProviderService.kt +++ b/projects/cas3-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/ProviderService.kt @@ -1,4 +1,4 @@ -package uk.gov.justice.digital.hmpps.integrations.delius +package uk.gov.justice.digital.hmpps.service import org.springframework.stereotype.Service import uk.gov.justice.digital.hmpps.integrations.approvedpremises.By diff --git a/projects/cas3-and-delius/src/main/resources/application.yml b/projects/cas3-and-delius/src/main/resources/application.yml index 395dce7df7..6675ac92f7 100644 --- a/projects/cas3-and-delius/src/main/resources/application.yml +++ b/projects/cas3-and-delius/src/main/resources/application.yml @@ -53,6 +53,7 @@ wiremock.enabled: true context.initializer.classes: uk.gov.justice.digital.hmpps.wiremock.WireMockInitialiser messaging.consumer.queue: message-queue +messaging.producer.topic: domain-events integrations: cas3-api: diff --git a/projects/cas3-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/ProviderServiceTest.kt b/projects/cas3-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/ProviderServiceTest.kt index 8cdf732d65..0753f6c79b 100644 --- a/projects/cas3-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/ProviderServiceTest.kt +++ b/projects/cas3-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/ProviderServiceTest.kt @@ -12,6 +12,7 @@ import uk.gov.justice.digital.hmpps.integrations.approvedpremises.By import uk.gov.justice.digital.hmpps.integrations.delius.entity.ProviderRepository import uk.gov.justice.digital.hmpps.integrations.delius.entity.StaffRepository import uk.gov.justice.digital.hmpps.integrations.delius.entity.TeamRepository +import uk.gov.justice.digital.hmpps.service.ProviderService import uk.gov.justice.digital.hmpps.telemetry.TelemetryService @ExtendWith(MockitoExtension::class)