diff --git a/projects/common-platform-and-delius/deploy/database/access.yml b/projects/common-platform-and-delius/deploy/database/access.yml index db47e38201..e5b1ee6307 100644 --- a/projects/common-platform-and-delius/deploy/database/access.yml +++ b/projects/common-platform-and-delius/deploy/database/access.yml @@ -3,7 +3,16 @@ database: username_key: /common-platform-and-delius/db-username password_key: /common-platform-and-delius/db-password + tables: + - offender + - offender_manager + - equality + - audited_interaction + - address + packages: + - offender_support_api # for generating crn + audit: username: CommonPlatformAndDelius forename: Common Platform - surname: Service + surname: Service \ No newline at end of file diff --git a/projects/common-platform-and-delius/deploy/values-dev.yml b/projects/common-platform-and-delius/deploy/values-dev.yml index 2da8364e6f..a8c31413d2 100644 --- a/projects/common-platform-and-delius/deploy/values-dev.yml +++ b/projects/common-platform-and-delius/deploy/values-dev.yml @@ -1,5 +1,3 @@ -enabled: false - generic-service: ingress: host: common-platform-and-delius-dev.hmpps.service.justice.gov.uk diff --git a/projects/common-platform-and-delius/deploy/values-preprod.yml b/projects/common-platform-and-delius/deploy/values-preprod.yml index 3aa1c2df9d..a46cf293f4 100644 --- a/projects/common-platform-and-delius/deploy/values-preprod.yml +++ b/projects/common-platform-and-delius/deploy/values-preprod.yml @@ -1,5 +1,3 @@ -enabled: false - generic-service: ingress: host: common-platform-and-delius-preprod.hmpps.service.justice.gov.uk diff --git a/projects/common-platform-and-delius/deploy/values.yaml b/projects/common-platform-and-delius/deploy/values.yaml index 456c964b0f..74b48d27df 100644 --- a/projects/common-platform-and-delius/deploy/values.yaml +++ b/projects/common-platform-and-delius/deploy/values.yaml @@ -23,6 +23,8 @@ generic-service: SENTRY_DSN: SENTRY_DSN common-platform-and-delius-queue: MESSAGING_CONSUMER_QUEUE: QUEUE_NAME + hmpps-domain-events-topic: + MESSAGING_PRODUCER_TOPIC: topic_arn generic-prometheus-alerts: targetApplication: common-platform-and-delius diff --git a/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt b/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt index f6ea94c9a7..0434e94531 100644 --- a/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt +++ b/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt @@ -1,16 +1,19 @@ package uk.gov.justice.digital.hmpps.data import jakarta.annotation.PostConstruct +import jakarta.persistence.EntityManager import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.boot.context.event.ApplicationReadyEvent import org.springframework.context.ApplicationListener import org.springframework.stereotype.Component -import uk.gov.justice.digital.hmpps.data.generator.UserGenerator +import org.springframework.transaction.annotation.Transactional +import uk.gov.justice.digital.hmpps.data.generator.* import uk.gov.justice.digital.hmpps.user.AuditUserRepository @Component @ConditionalOnProperty("seed.database") class DataLoader( + private val entityManager: EntityManager, private val auditUserRepository: AuditUserRepository ) : ApplicationListener { @@ -19,7 +22,26 @@ class DataLoader( auditUserRepository.save(UserGenerator.AUDIT_USER) } + @Transactional override fun onApplicationEvent(are: ApplicationReadyEvent) { - // Perform dev/test database setup here, using JPA repositories and generator classes... + entityManager.run { + persist(BusinessInteractionGenerator.INSERT_PERSON) + persist(BusinessInteractionGenerator.INSERT_ADDRESS) + persist(DatasetGenerator.GENDER) + persist(DatasetGenerator.OM_ALLOCATION_REASON) + persist(DatasetGenerator.ADDRESS_STATUS) + persist(DatasetGenerator.ADDRESS_TYPE) + persist(ReferenceDataGenerator.GENDER_MALE) + persist(ReferenceDataGenerator.GENDER_FEMALE) + persist(ReferenceDataGenerator.INITIAL_ALLOCATION) + persist(ReferenceDataGenerator.MAIN_ADDRESS_STATUS) + persist(ReferenceDataGenerator.AWAITING_ASSESSMENT) + persist(ProviderGenerator.DEFAULT) + persist(TeamGenerator.ALLOCATED) + persist(TeamGenerator.UNALLOCATED) + persist(StaffGenerator.UNALLOCATED) + persist(StaffGenerator.ALLOCATED) + persist(CourtGenerator.UNKNOWN_COURT_N07_PROVIDER) + } } } diff --git a/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/BusinessInteractionGenerator.kt b/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/BusinessInteractionGenerator.kt new file mode 100644 index 0000000000..c6d09dcbaf --- /dev/null +++ b/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/BusinessInteractionGenerator.kt @@ -0,0 +1,18 @@ +package uk.gov.justice.digital.hmpps.data.generator + +import uk.gov.justice.digital.hmpps.audit.BusinessInteraction +import uk.gov.justice.digital.hmpps.integrations.delius.audit.BusinessInteractionCode +import java.time.ZonedDateTime + +object BusinessInteractionGenerator { + val INSERT_PERSON = BusinessInteraction( + IdGenerator.getAndIncrement(), + BusinessInteractionCode.INSERT_PERSON.code, + ZonedDateTime.now().minusMonths(6) + ) + val INSERT_ADDRESS = BusinessInteraction( + IdGenerator.getAndIncrement(), + BusinessInteractionCode.INSERT_ADDRESS.code, + ZonedDateTime.now().minusMonths(6) + ) +} diff --git a/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/MessageGenerator.kt b/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/MessageGenerator.kt index 5ed8de87fc..6b34942680 100644 --- a/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/MessageGenerator.kt +++ b/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/MessageGenerator.kt @@ -1,8 +1,16 @@ package uk.gov.justice.digital.hmpps.data.generator -import uk.gov.justice.digital.hmpps.message.HmppsDomainEvent +import uk.gov.justice.digital.hmpps.messaging.CommonPlatformHearing import uk.gov.justice.digital.hmpps.resourceloader.ResourceLoader object MessageGenerator { - val EXAMPLE = ResourceLoader.message("example-message") + val COMMON_PLATFORM_EVENT = ResourceLoader.message("common-platform-hearing") + val COMMON_PLATFORM_EVENT_DOB_ERROR = + ResourceLoader.message("common-platform-hearing-dob-error") + val COMMON_PLATFORM_EVENT_NO_CASES = + ResourceLoader.message("common-platform-hearing-no-cases") + val COMMON_PLATFORM_EVENT_BLANK_ADDRESS = + ResourceLoader.message("common-platform-hearing-blank-address") + val COMMON_PLATFORM_EVENT_NO_REMAND = + ResourceLoader.message("common-platform-hearing-no-remand") } diff --git a/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonAddressGenerator.kt b/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonAddressGenerator.kt new file mode 100644 index 0000000000..fa2b186162 --- /dev/null +++ b/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonAddressGenerator.kt @@ -0,0 +1,24 @@ +package uk.gov.justice.digital.hmpps.data.generator + +import uk.gov.justice.digital.hmpps.integrations.delius.entity.Person +import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.PersonAddress +import java.time.LocalDate + +object PersonAddressGenerator { + val MAIN_ADDRESS = generate(person = PersonGenerator.DEFAULT) + + fun generate( + id: Long? = IdGenerator.getAndIncrement(), + person: Person, + notes: String? = null, + postcode: String? = null, + ) = PersonAddress( + id = id, + start = LocalDate.now(), + status = ReferenceDataGenerator.MAIN_ADDRESS_STATUS, + person = person, + notes = notes, + postcode = postcode, + type = ReferenceDataGenerator.AWAITING_ASSESSMENT + ) +} diff --git a/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt b/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt new file mode 100644 index 0000000000..9054d5584f --- /dev/null +++ b/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt @@ -0,0 +1,51 @@ +package uk.gov.justice.digital.hmpps.data.generator + +import uk.gov.justice.digital.hmpps.integrations.delius.entity.* +import java.time.LocalDate +import java.time.LocalDateTime +import java.util.* +import kotlin.random.Random + +object PersonGenerator { + val DEFAULT = generate(crn = "A000001") + + fun generate( + crn: String, + id: Long? = IdGenerator.getAndIncrement() + ) = Person( + id = id, + crn = crn, + forename = UUID.randomUUID().toString().substring(0, 15), + surname = UUID.randomUUID().toString().substring(0, 15), + dateOfBirth = LocalDate.now().minusYears(Random.nextInt(16, 76).toLong()), + gender = if (Random.nextBoolean()) ReferenceDataGenerator.GENDER_MALE else ReferenceDataGenerator.GENDER_FEMALE, + surnameSoundex = "surnameSoundex", + firstNameSoundex = "firstNameSoundex", + middleNameSoundex = null + ) +} + +object PersonManagerGenerator { + + val DEFAULT = generate(person = PersonGenerator.DEFAULT) + + fun generate( + id: Long = IdGenerator.getAndIncrement(), + person: Person, + provider: Provider = ProviderGenerator.DEFAULT, + team: Team = TeamGenerator.UNALLOCATED, + staff: Staff = StaffGenerator.UNALLOCATED, + allocationDate: LocalDateTime = LocalDateTime.of(1900, 1, 1, 0, 0), + allocationReason: ReferenceData = ReferenceDataGenerator.INITIAL_ALLOCATION + ) = PersonManager( + id = id, + person = person, + provider = provider, + staff = staff, + staffEmployeeID = staff.id, + team = team, + trustProviderTeamId = team.id, + allocationDate = allocationDate, + allocationReason = allocationReason + ) +} diff --git a/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ReferenceDataGenerator.kt b/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ReferenceDataGenerator.kt new file mode 100644 index 0000000000..82d5722d6f --- /dev/null +++ b/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ReferenceDataGenerator.kt @@ -0,0 +1,57 @@ +package uk.gov.justice.digital.hmpps.data.generator + +import uk.gov.justice.digital.hmpps.data.generator.DatasetGenerator.ADDRESS_STATUS +import uk.gov.justice.digital.hmpps.data.generator.DatasetGenerator.ADDRESS_TYPE +import uk.gov.justice.digital.hmpps.data.generator.DatasetGenerator.GENDER +import uk.gov.justice.digital.hmpps.data.generator.DatasetGenerator.OM_ALLOCATION_REASON +import uk.gov.justice.digital.hmpps.integrations.delius.entity.* + +object ReferenceDataGenerator { + + val GENDER_FEMALE = generate(ReferenceData.GenderCode.FEMALE.deliusValue, GENDER.id, "Female") + val GENDER_MALE = generate(ReferenceData.GenderCode.MALE.deliusValue, GENDER.id, "Male") + val INITIAL_ALLOCATION = generate( + ReferenceData.StandardRefDataCode.INITIAL_ALLOCATION.code, + OM_ALLOCATION_REASON.id, + "Initial Allocation" + ) + val MAIN_ADDRESS_STATUS = + generate(ReferenceData.StandardRefDataCode.ADDRESS_MAIN_STATUS.code, ADDRESS_STATUS.id, "Main") + val AWAITING_ASSESSMENT = + generate(ReferenceData.StandardRefDataCode.AWAITING_ASSESSMENT.code, ADDRESS_TYPE.id, "Awaiting Assessment") + + fun generate( + code: String, + datasetId: Long, + description: String = "Description of $code", + id: Long = IdGenerator.getAndIncrement() + ) = ReferenceData(id = id, code = code, datasetId = datasetId, description = description) +} + +object DatasetGenerator { + val ALL_DATASETS = DatasetCode.entries.map { Dataset(IdGenerator.getAndIncrement(), it) }.associateBy { it.code } + val GENDER = ALL_DATASETS[DatasetCode.GENDER]!! + val OM_ALLOCATION_REASON = ALL_DATASETS[DatasetCode.OM_ALLOCATION_REASON]!! + val ADDRESS_TYPE = ALL_DATASETS[DatasetCode.ADDRESS_TYPE]!! + val ADDRESS_STATUS = ALL_DATASETS[DatasetCode.ADDRESS_STATUS]!! +} + +object CourtGenerator { + val UNKNOWN_COURT_N07_PROVIDER = generate(code = "UNKNCT", ouCode = "A00AA00") + + fun generate( + id: Long = IdGenerator.getAndIncrement(), + code: String, + selectable: Boolean = true, + courtName: String = "Court not known", + provider: Provider = ProviderGenerator.DEFAULT, + ouCode: String? = null, + ) = Court( + id = id, + code = code, + selectable = selectable, + name = courtName, + provider = provider, + ouCode = ouCode + ) +} \ No newline at end of file diff --git a/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt b/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt new file mode 100644 index 0000000000..202e3479e1 --- /dev/null +++ b/projects/common-platform-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt @@ -0,0 +1,42 @@ +package uk.gov.justice.digital.hmpps.data.generator + +import uk.gov.justice.digital.hmpps.integrations.delius.entity.Provider +import uk.gov.justice.digital.hmpps.integrations.delius.entity.Staff +import uk.gov.justice.digital.hmpps.integrations.delius.entity.Team +import java.util.concurrent.atomic.AtomicLong + +object StaffGenerator { + val UNALLOCATED = generate("N07UATU") + val ALLOCATED = generate("N07T01A") + fun generate( + code: String, + id: Long = IdGenerator.getAndIncrement(), + ) = + Staff(code = code, id = id) +} + +object ProviderGenerator { + val DEFAULT = generate() + fun generate(id: Long = IdGenerator.getAndIncrement()) = Provider( + id = id, + selectable = true, + code = "N07", + description = "London", + endDate = null, + ) +} + +object TeamGenerator { + private val teamCodeGenerator = AtomicLong(1) + val ALLOCATED = generate(code = "N07T01") + val UNALLOCATED = generate(code = "N07UAT") + + fun generate( + code: String = "N07${teamCodeGenerator.getAndIncrement().toString().padStart(3, '0')}", + description: String = "Description of Team $code", + ) = Team( + id = IdGenerator.getAndIncrement(), + code = code, + description = description, + ) +} diff --git a/projects/common-platform-and-delius/src/dev/resources/db/h2.sql b/projects/common-platform-and-delius/src/dev/resources/db/h2.sql new file mode 100644 index 0000000000..89bdf0a265 --- /dev/null +++ b/projects/common-platform-and-delius/src/dev/resources/db/h2.sql @@ -0,0 +1,8 @@ +DROP SCHEMA IF EXISTS offender_support_api CASCADE; + +CREATE SCHEMA offender_support_api; + +CREATE ALIAS offender_support_api.getNextCRN AS +'String getNextCRN() { + return "A111111"; +}'; \ No newline at end of file diff --git a/projects/common-platform-and-delius/src/dev/resources/db/oracle.sql b/projects/common-platform-and-delius/src/dev/resources/db/oracle.sql new file mode 100644 index 0000000000..5f651dd9cc --- /dev/null +++ b/projects/common-platform-and-delius/src/dev/resources/db/oracle.sql @@ -0,0 +1,13 @@ +CREATE OR REPLACE PACKAGE offender_support_api AS + FUNCTION getNextCRN RETURN VARCHAR2; +END offender_support_api; + +GRANT EXECUTE ON offender_support_api TO delius_app_schema; + +CREATE OR REPLACE PACKAGE BODY offender_support_api AS + FUNCTION getNextCRN RETURN VARCHAR2 IS + BEGIN + RETURN 'A111111'; + END getNextCRN; +END offender_support_api; +/ \ No newline at end of file diff --git a/projects/common-platform-and-delius/src/dev/resources/messages/common-platform-hearing-blank-address.json b/projects/common-platform-and-delius/src/dev/resources/messages/common-platform-hearing-blank-address.json new file mode 100644 index 0000000000..75e68cf81b --- /dev/null +++ b/projects/common-platform-and-delius/src/dev/resources/messages/common-platform-hearing-blank-address.json @@ -0,0 +1,21 @@ +{ + "Type": "Notification", + "MessageId": "aa2c2828-167f-529b-8e19-73735a2fb85c", + "TopicArn": "arn:aws:sns:eu-west-2:000000000000:example", + "Message": "{\"hearing\":{\"id\":\"00000000-0000-0000-0000-000000000000\",\"courtCentre\":{\"id\":\"00000000-0000-0000-0000-000000000000\",\"code\":\"A00AA00\",\"roomId\":\"00000000-0000-0000-0000-000000000000\",\"roomName\":\"Courtroom 01\"},\"type\":{\"id\":\"00000000-0000-0000-0000-000000000000\",\"description\":\"First hearing\",\"welshDescription\":null},\"jurisdictionType\":\"MAGISTRATES\",\"hearingDays\":[{\"sittingDay\":\"2024-01-01T12:00:00\",\"listedDurationMinutes\":30,\"listingSequence\":1}],\"prosecutionCases\":[{\"id\":\"00000000-0000-0000-0000-000000000000\",\"initiationCode\":\"A\",\"prosecutionCaseIdentifier\":{\"prosecutionAuthorityCode\":\"AAAAA\",\"prosecutionAuthorityId\":\"00000000-0000-0000-0000-000000000000\",\"caseURN\":\"00AA000000\"},\"defendants\":[{\"id\":\"00000000-0000-0000-0000-000000000000\",\"offences\":[{\"id\":\"00000000-0000-0000-0000-000000000000\",\"offenceDefinitionId\":\"00000000-0000-0000-0000-000000000000\",\"offenceCode\":\"AA00000\",\"offenceTitle\":\"Example Offense Title\",\"wording\":\"Example of the offense committed\",\"offenceLegislation\":\"Example legislation\",\"listingNumber\":1,\"judicialResults\":[{\"isConvictedResult\":true,\"label\":\"Remanded in custody\",\"judicialResultTypeId\":\"00000000-0000-0000-0000-000000000000\",\"resultText\":\"RI - Remanded in custody\"}],\"plea\":null,\"verdict\":null}],\"prosecutionCaseId\":\"00000000-0000-0000-0000-000000000000\",\"personDefendant\":{\"personDetails\":{\"gender\":\"MALE\",\"lastName\":\"Example Last Name\",\"middleName\":null,\"firstName\":\"Example First Name\",\"dateOfBirth\":\"2000-01-01\",\"address\":{\"address1\":null,\"address2\":null,\"address3\":null,\"address4\":null,\"address5\":null,\"postcode\":null},\"contact\":{\"home\":null,\"mobile\":\"07000000000\",\"work\":null},\"ethnicity\":{\"observedEthnicityDescription\":\"White\",\"selfDefinedEthnicityDescription\":\"British\"}}},\"legalEntityDefendant\":null,\"masterDefendantId\":\"00000000-0000-0000-0000-000000000000\",\"pncId\":\"00000000000A\",\"croNumber\":\"000000/00A\"}],\"caseStatus\":\"ACTIVE\",\"caseMarkers\":[]}]}}", + "Timestamp": "2022-07-27T14:22:08.509Z", + "SignatureVersion": "1", + "Signature": "EXAMPLE", + "SigningCertURL": "https://sns.eu-west-2.amazonaws.com/EXAMPLE.pem", + "UnsubscribeURL": "https://sns.eu-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=EXAMPLE", + "MessageAttributes": { + "messageType": { + "Type": "String", + "Value": "COMMON_PLATFORM_HEARING" + }, + "hearingEventType": { + "Type": "String", + "Value": "ConfirmedOrUpdated" + } + } +} \ No newline at end of file diff --git a/projects/common-platform-and-delius/src/dev/resources/messages/common-platform-hearing-dob-error.json b/projects/common-platform-and-delius/src/dev/resources/messages/common-platform-hearing-dob-error.json new file mode 100644 index 0000000000..f1d063a494 --- /dev/null +++ b/projects/common-platform-and-delius/src/dev/resources/messages/common-platform-hearing-dob-error.json @@ -0,0 +1,21 @@ +{ + "Type": "Notification", + "MessageId": "aa2c2828-167f-529b-8e19-73735a2fb85c", + "TopicArn": "arn:aws:sns:eu-west-2:000000000000:example", + "Message": "{\"hearing\":{\"id\":\"00000000-0000-0000-0000-000000000000\",\"courtCentre\":{\"id\":\"00000000-0000-0000-0000-000000000000\",\"code\":\"A00AA00\",\"roomId\":\"00000000-0000-0000-0000-000000000000\",\"roomName\":\"Courtroom 01\"},\"type\":{\"id\":\"00000000-0000-0000-0000-000000000000\",\"description\":\"First hearing\",\"welshDescription\":null},\"jurisdictionType\":\"MAGISTRATES\",\"hearingDays\":[{\"sittingDay\":\"2024-01-01T12:00:00\",\"listedDurationMinutes\":30,\"listingSequence\":1}],\"prosecutionCases\":[{\"id\":\"00000000-0000-0000-0000-000000000000\",\"initiationCode\":\"A\",\"prosecutionCaseIdentifier\":{\"prosecutionAuthorityCode\":\"AAAAA\",\"prosecutionAuthorityId\":\"00000000-0000-0000-0000-000000000000\",\"caseURN\":\"00AA000000\"},\"defendants\":[{\"id\":\"00000000-0000-0000-0000-000000000000\",\"offences\":[{\"id\":\"00000000-0000-0000-0000-000000000000\",\"offenceDefinitionId\":\"00000000-0000-0000-0000-000000000000\",\"offenceCode\":\"AA00000\",\"offenceTitle\":\"Example Offense Title\",\"wording\":\"Example of the offense committed\",\"offenceLegislation\":\"Example legislation\",\"listingNumber\":1,\"judicialResults\":[{\"isConvictedResult\":true,\"label\":\"Remanded in custody\",\"judicialResultTypeId\":\"00000000-0000-0000-0000-000000000000\",\"resultText\":\"RI - Remanded in custody\"}],\"plea\":null,\"verdict\":null}],\"prosecutionCaseId\":\"00000000-0000-0000-0000-000000000000\",\"personDefendant\":{\"personDetails\":{\"gender\":\"MALE\",\"lastName\":\"Example Last Name\",\"middleName\":null,\"firstName\":\"Example First Name\",\"dateOfBirth\":\"2020-01-01\",\"address\":{\"address1\":\"Example Address Line 1\",\"address2\":\"Example Address Line 2\",\"address3\":\"Example Address Line 3\",\"address4\":null,\"address5\":null,\"postcode\":\"AA1 1AA\"},\"contact\":{\"home\":\"01234567890\",\"mobile\":\"07000000000\",\"work\":null},\"ethnicity\":{\"observedEthnicityDescription\":\"White\",\"selfDefinedEthnicityDescription\":\"British\"}}},\"legalEntityDefendant\":null,\"masterDefendantId\":\"00000000-0000-0000-0000-000000000000\",\"pncId\":\"00000000000A\",\"croNumber\":\"000000/00A\"}],\"caseStatus\":\"ACTIVE\",\"caseMarkers\":[]}]}}", + "Timestamp": "2022-07-27T14:22:08.509Z", + "SignatureVersion": "1", + "Signature": "EXAMPLE", + "SigningCertURL": "https://sns.eu-west-2.amazonaws.com/EXAMPLE.pem", + "UnsubscribeURL": "https://sns.eu-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=EXAMPLE", + "MessageAttributes": { + "messageType": { + "Type": "String", + "Value": "COMMON_PLATFORM_HEARING" + }, + "hearingEventType": { + "Type": "String", + "Value": "ConfirmedOrUpdated" + } + } +} \ No newline at end of file diff --git a/projects/common-platform-and-delius/src/dev/resources/messages/common-platform-hearing-no-cases.json b/projects/common-platform-and-delius/src/dev/resources/messages/common-platform-hearing-no-cases.json new file mode 100644 index 0000000000..fc5ac34f87 --- /dev/null +++ b/projects/common-platform-and-delius/src/dev/resources/messages/common-platform-hearing-no-cases.json @@ -0,0 +1,21 @@ +{ + "Type": "Notification", + "MessageId": "aa2c2828-167f-529b-8e19-73735a2fb85c", + "TopicArn": "arn:aws:sns:eu-west-2:000000000000:example", + "Message": "{\"hearing\":{\"id\":\"00000000-0000-0000-0000-000000000000\",\"courtCentre\":{\"id\":\"00000000-0000-0000-0000-000000000000\",\"code\":\"A00AA00\",\"roomId\":\"00000000-0000-0000-0000-000000000000\",\"roomName\":\"Courtroom 01\"},\"type\":{\"id\":\"00000000-0000-0000-0000-000000000000\",\"description\":\"First hearing\",\"welshDescription\":null},\"jurisdictionType\":\"MAGISTRATES\",\"hearingDays\":[{\"sittingDay\":\"2024-01-01T12:00:00\",\"listedDurationMinutes\":30,\"listingSequence\":1}],\"prosecutionCases\":[]}}", + "Timestamp": "2022-07-27T14:22:08.509Z", + "SignatureVersion": "1", + "Signature": "EXAMPLE", + "SigningCertURL": "https://sns.eu-west-2.amazonaws.com/EXAMPLE.pem", + "UnsubscribeURL": "https://sns.eu-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=EXAMPLE", + "MessageAttributes": { + "messageType": { + "Type": "String", + "Value": "COMMON_PLATFORM_HEARING" + }, + "hearingEventType": { + "Type": "String", + "Value": "ConfirmedOrUpdated" + } + } +} \ No newline at end of file diff --git a/projects/common-platform-and-delius/src/dev/resources/messages/common-platform-hearing-no-remand.json b/projects/common-platform-and-delius/src/dev/resources/messages/common-platform-hearing-no-remand.json new file mode 100644 index 0000000000..f88a497a1f --- /dev/null +++ b/projects/common-platform-and-delius/src/dev/resources/messages/common-platform-hearing-no-remand.json @@ -0,0 +1,20 @@ +{ + "Type": "Notification", + "MessageId": "aa2c2828-167f-529b-8e19-73735a2fb85c", + "TopicArn": "arn:aws:sns:eu-west-2:000000000000:example", + "Message" : "{\"hearing\":{\"id\":\"00000000-0000-0000-0000-000000000000\",\"courtCentre\":{\"id\":\"00000000-0000-0000-0000-000000000000\",\"code\":\"A00AA00\",\"roomId\":\"00000000-0000-0000-0000-000000000000\",\"roomName\":\"Courtroom 01\"},\"type\":{\"id\":\"00000000-0000-0000-0000-000000000000\",\"description\":\"First hearing\",\"welshDescription\":null},\"jurisdictionType\":\"MAGISTRATES\",\"hearingDays\":[{\"sittingDay\":\"2024-01-01T12:00:00\",\"listedDurationMinutes\":30,\"listingSequence\":1}],\"prosecutionCases\":[{\"id\":\"00000000-0000-0000-0000-000000000000\",\"initiationCode\":\"A\",\"prosecutionCaseIdentifier\":{\"prosecutionAuthorityCode\":\"AAAAA\",\"prosecutionAuthorityId\":\"00000000-0000-0000-0000-000000000000\",\"caseURN\":\"00AA000000\"},\"defendants\":[{\"id\":\"00000000-0000-0000-0000-000000000000\",\"offences\":[{\"id\":\"00000000-0000-0000-0000-000000000000\",\"offenceDefinitionId\":\"00000000-0000-0000-0000-000000000000\",\"offenceCode\":\"AA00000\",\"offenceTitle\":\"Example Offense Title\",\"wording\":\"Example of the offense committed\",\"offenceLegislation\":\"Example legislation\",\"listingNumber\":1,\"judicialResults\":[],\"plea\":null,\"verdict\":null}],\"prosecutionCaseId\":\"00000000-0000-0000-0000-000000000000\",\"personDefendant\":{\"personDetails\":{\"gender\":\"MALE\",\"lastName\":\"Example Last Name\",\"middleName\":null,\"firstName\":\"Example First Name\",\"dateOfBirth\":\"2000-01-01\",\"address\":{\"address1\":\"Example Address Line 1\",\"address2\":\"Example Address Line 2\",\"address3\":\"Example Address Line 3\",\"address4\":null,\"address5\":null,\"postcode\":\"AA1 1AA\"},\"contact\":{\"home\":\"01234567890\",\"mobile\":\"07000000000\",\"work\":null},\"ethnicity\":{\"observedEthnicityDescription\":\"White\",\"selfDefinedEthnicityDescription\":\"British\"}}},\"legalEntityDefendant\":null,\"masterDefendantId\":\"00000000-0000-0000-0000-000000000000\",\"pncId\":\"00000000000A\",\"croNumber\":\"000000/00A\"}],\"caseStatus\":\"ACTIVE\",\"caseMarkers\":[]}]}}", "Timestamp": "2022-07-27T14:22:08.509Z", + "SignatureVersion": "1", + "Signature": "EXAMPLE", + "SigningCertURL": "https://sns.eu-west-2.amazonaws.com/EXAMPLE.pem", + "UnsubscribeURL": "https://sns.eu-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=EXAMPLE", + "MessageAttributes": { + "messageType": { + "Type": "String", + "Value": "COMMON_PLATFORM_HEARING" + }, + "hearingEventType": { + "Type": "String", + "Value": "ConfirmedOrUpdated" + } + } +} \ No newline at end of file diff --git a/projects/common-platform-and-delius/src/dev/resources/messages/common-platform-hearing.json b/projects/common-platform-and-delius/src/dev/resources/messages/common-platform-hearing.json new file mode 100644 index 0000000000..8e61a1e5cd --- /dev/null +++ b/projects/common-platform-and-delius/src/dev/resources/messages/common-platform-hearing.json @@ -0,0 +1,21 @@ +{ + "Type": "Notification", + "MessageId": "aa2c2828-167f-529b-8e19-73735a2fb85c", + "TopicArn": "arn:aws:sns:eu-west-2:000000000000:example", + "Message": "{\"hearing\":{\"id\":\"00000000-0000-0000-0000-000000000000\",\"courtCentre\":{\"id\":\"00000000-0000-0000-0000-000000000000\",\"code\":\"A00AA00\",\"roomId\":\"00000000-0000-0000-0000-000000000000\",\"roomName\":\"Courtroom 01\"},\"type\":{\"id\":\"00000000-0000-0000-0000-000000000000\",\"description\":\"First hearing\",\"welshDescription\":null},\"jurisdictionType\":\"MAGISTRATES\",\"hearingDays\":[{\"sittingDay\":\"2024-01-01T12:00:00\",\"listedDurationMinutes\":30,\"listingSequence\":1}],\"prosecutionCases\":[{\"id\":\"00000000-0000-0000-0000-000000000000\",\"initiationCode\":\"A\",\"prosecutionCaseIdentifier\":{\"prosecutionAuthorityCode\":\"AAAAA\",\"prosecutionAuthorityId\":\"00000000-0000-0000-0000-000000000000\",\"caseURN\":\"00AA000000\"},\"defendants\":[{\"id\":\"00000000-0000-0000-0000-000000000000\",\"offences\":[{\"id\":\"00000000-0000-0000-0000-000000000000\",\"offenceDefinitionId\":\"00000000-0000-0000-0000-000000000000\",\"offenceCode\":\"AA00000\",\"offenceTitle\":\"Example Offense Title\",\"wording\":\"Example of the offense committed\",\"offenceLegislation\":\"Example legislation\",\"listingNumber\":1,\"judicialResults\":[{\"isConvictedResult\":true,\"label\":\"Remanded in custody\",\"judicialResultTypeId\":\"00000000-0000-0000-0000-000000000000\",\"resultText\":\"RI - Remanded in custody\"}],\"plea\":null,\"verdict\":null}],\"prosecutionCaseId\":\"00000000-0000-0000-0000-000000000000\",\"personDefendant\":{\"personDetails\":{\"gender\":\"MALE\",\"lastName\":\"Example Last Name\",\"middleName\":null,\"firstName\":\"Example First Name\",\"dateOfBirth\":\"2000-01-01\",\"address\":{\"address1\":\"Example Address Line 1\",\"address2\":\"Example Address Line 2\",\"address3\":\"Example Address Line 3\",\"address4\":null,\"address5\":null,\"postcode\":\"AA1 1AA\"},\"contact\":{\"home\":\"01234567890\",\"mobile\":\"07000000000\",\"work\":null},\"ethnicity\":{\"observedEthnicityDescription\":\"White\",\"selfDefinedEthnicityDescription\":\"British\"}}},\"legalEntityDefendant\":null,\"masterDefendantId\":\"00000000-0000-0000-0000-000000000000\",\"pncId\":\"00000000000A\",\"croNumber\":\"000000/00A\"}],\"caseStatus\":\"ACTIVE\",\"caseMarkers\":[]}]}}", + "Timestamp": "2022-07-27T14:22:08.509Z", + "SignatureVersion": "1", + "Signature": "EXAMPLE", + "SigningCertURL": "https://sns.eu-west-2.amazonaws.com/EXAMPLE.pem", + "UnsubscribeURL": "https://sns.eu-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=EXAMPLE", + "MessageAttributes": { + "messageType": { + "Type": "String", + "Value": "COMMON_PLATFORM_HEARING" + }, + "hearingEventType": { + "Type": "String", + "Value": "ConfirmedOrUpdated" + } + } +} \ No newline at end of file diff --git a/projects/common-platform-and-delius/src/dev/resources/messages/example-message.json b/projects/common-platform-and-delius/src/dev/resources/messages/example-message.json deleted file mode 100644 index 3ec3f10524..0000000000 --- a/projects/common-platform-and-delius/src/dev/resources/messages/example-message.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "Type": "Notification", - "MessageId": "aa2c2828-167f-529b-8e19-73735a2fb85c", - "TopicArn": "arn:aws:sns:eu-west-2:000000000000:example", - "Message": "{\"eventType\":\"example\",\"version\":1,\"description\":\"Example event\",\"detailUrl\":\"http://localhost:{wiremock.port}/example/123\",\"occurredAt\":\"2022-07-27T15:22:08.452612281+01:00\",\"additionalInformation\":{\"exampleId\":\"123\"},\"personReference\":{\"identifiers\":[{\"type\":\"CRN\",\"value\":\"A000001\"}]}}", - "Timestamp": "2022-07-27T14:22:08.509Z", - "SignatureVersion": "1", - "Signature": "EXAMPLE", - "SigningCertURL": "https://sns.eu-west-2.amazonaws.com/EXAMPLE.pem", - "UnsubscribeURL": "https://sns.eu-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=EXAMPLE", - "MessageAttributes": { - "eventType": { - "Type": "String", - "Value": "example" - } - } -} \ No newline at end of file diff --git a/projects/common-platform-and-delius/src/dev/resources/simulations/__files/probation-search-multiple-results.json b/projects/common-platform-and-delius/src/dev/resources/simulations/__files/probation-search-multiple-results.json new file mode 100644 index 0000000000..a1150c6bb5 --- /dev/null +++ b/projects/common-platform-and-delius/src/dev/resources/simulations/__files/probation-search-multiple-results.json @@ -0,0 +1,33 @@ +{ + "matches": [ + { + "offender": { + "offenderId": 1, + "title": "Mr", + "firstName": "Bill", + "middleNames": [], + "surname": "Jones", + "dateOfBirth": "1960-04-07", + "gender": "Male", + "otherIds": { + "crn": "A000002" + } + } + }, + { + "offender": { + "offenderId": 2, + "title": "Mr", + "firstName": "Bob", + "middleNames": [], + "surname": "Smith", + "dateOfBirth": "1960-04-07", + "gender": "Male", + "otherIds": { + "crn": "A000001" + } + } + } + ], + "matchedBy": "ALL_SUPPLIED" +} diff --git a/projects/common-platform-and-delius/src/dev/resources/simulations/__files/probation-search-no-results.json b/projects/common-platform-and-delius/src/dev/resources/simulations/__files/probation-search-no-results.json new file mode 100644 index 0000000000..a83d51cf3a --- /dev/null +++ b/projects/common-platform-and-delius/src/dev/resources/simulations/__files/probation-search-no-results.json @@ -0,0 +1,4 @@ +{ + "matches": [], + "matchedBy": "NONE" +} diff --git a/projects/common-platform-and-delius/src/dev/resources/simulations/__files/probation-search-single-result.json b/projects/common-platform-and-delius/src/dev/resources/simulations/__files/probation-search-single-result.json new file mode 100644 index 0000000000..2ffb6260bc --- /dev/null +++ b/projects/common-platform-and-delius/src/dev/resources/simulations/__files/probation-search-single-result.json @@ -0,0 +1,83 @@ +{ + "matches": [ + { + "offender": { + "offenderId": 1, + "title": "Mr", + "firstName": "Bob", + "middleNames": [], + "surname": "Smith", + "dateOfBirth": "1960-04-07", + "gender": "Smith", + "otherIds": { + "crn": "A000001", + "croNumber": "AB12/123456C", + "niNumber": "JJ123456C" + }, + "contactDetails": { + "phoneNumbers": [], + "emailAddresses": [] + }, + "offenderProfile": { + "offenderLanguages": { + "requiresInterpreter": false + }, + "previousConviction": { + "detail": {} + } + }, + "offenderManagers": [ + { + "staff": { + "code": "N57UATU", + "forenames": "Unallocated", + "surname": "Staff" + }, + "partitionArea": "National Data", + "softDeleted": false, + "team": { + "code": "N57UAT", + "description": "Unallocated Team(N57)", + "district": { + "code": "N57UAT", + "description": "Unallocated Level 3(N57)" + }, + "borough": { + "code": "N57UAT", + "description": "Unallocated Level2(N57)" + } + }, + "probationArea": { + "code": "N57", + "description": "Kent Surrey Sussex Region", + "nps": false + }, + "fromDate": "1900-01-01", + "active": true, + "allocationReason": { + "code": "IN1", + "description": "Initial Allocation" + } + } + ], + "softDeleted": false, + "currentDisposal": "0", + "partitionArea": "National Data", + "currentRestriction": true, + "restrictionMessage": "This is a restricted offender record. Please contact a system administrator", + "currentExclusion": false, + "exclusionMessage": "You are excluded from viewing this offender record. Please contact a system administrator", + "currentTier": "UD0", + "activeProbationManagedSentence": false, + "probationStatus": { + "status": "NOT_SENTENCED", + "inBreach": false, + "preSentenceActivity": false, + "awaitingPsr": false + }, + "age": 63 + } + } + ], + "matchedBy": "ALL_SUPPLIED" +} diff --git a/projects/common-platform-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt b/projects/common-platform-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt index 2c6461b551..266a378bb2 100644 --- a/projects/common-platform-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt +++ b/projects/common-platform-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt @@ -1,43 +1,324 @@ package uk.gov.justice.digital.hmpps -import org.junit.jupiter.api.Test +import com.github.tomakehurst.wiremock.WireMockServer +import com.github.tomakehurst.wiremock.client.WireMock.* +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers +import org.junit.jupiter.api.* +import org.junit.jupiter.api.Assertions.* +import org.mockito.Mockito import org.mockito.Mockito.atLeastOnce -import org.mockito.kotlin.verify +import org.mockito.kotlin.* import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.mock.mockito.MockBean +import org.springframework.boot.test.mock.mockito.SpyBean +import uk.gov.justice.digital.hmpps.audit.entity.AuditedInteraction +import uk.gov.justice.digital.hmpps.audit.service.AuditedInteractionService import uk.gov.justice.digital.hmpps.data.generator.MessageGenerator +import uk.gov.justice.digital.hmpps.integrations.delius.audit.BusinessInteractionCode +import uk.gov.justice.digital.hmpps.integrations.delius.entity.Person +import uk.gov.justice.digital.hmpps.integrations.delius.entity.PersonRepository +import uk.gov.justice.digital.hmpps.integrations.delius.entity.ReferenceData +import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.PersonAddress +import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.PersonAddressRepository +import uk.gov.justice.digital.hmpps.message.HmppsDomainEvent 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.service.PersonService import uk.gov.justice.digital.hmpps.telemetry.TelemetryMessagingExtensions.notificationReceived -import java.util.concurrent.TimeoutException +import uk.gov.justice.digital.hmpps.telemetry.TelemetryService +import java.time.LocalDate -@SpringBootTest +@TestMethodOrder(MethodOrderer.OrderAnnotation::class) +@AutoConfigureMockMvc +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) internal class IntegrationTest { @Value("\${messaging.consumer.queue}") lateinit var queueName: String + @Value("\${messaging.producer.topic}") + lateinit var topicName: String + @Autowired lateinit var channelManager: HmppsChannelManager + @Autowired + lateinit var hmppsChannelManager: HmppsChannelManager + + @Autowired + lateinit var wireMockServer: WireMockServer + @MockBean lateinit var telemetryService: TelemetryService + @SpyBean + lateinit var auditedInteractionService: AuditedInteractionService + + @SpyBean + lateinit var personRepository: PersonRepository + + @SpyBean + lateinit var addressRepository: PersonAddressRepository + + @SpyBean + lateinit var personService: PersonService + + @BeforeEach + fun setup() { + doReturn("A111111").whenever(personService).generateCrn() + } + + @Test + fun `Message is logged to telemetry`() { + val notification = Notification(message = MessageGenerator.COMMON_PLATFORM_EVENT) + channelManager.getChannel(queueName).publishAndWait(notification) + verify(telemetryService, atLeastOnce()).notificationReceived(notification) + } + + @Test + fun `When a probation search match is detected no insert is performed`() { + wireMockServer.stubFor( + post(urlPathEqualTo("/probation-search/match")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBodyFile("probation-search-single-result.json") + ) + ) + + val notification = Notification(message = MessageGenerator.COMMON_PLATFORM_EVENT) + channelManager.getChannel(queueName).publishAndWait(notification) + thenNoRecordsAreInserted() + } + + @Test + fun `When a message with no prosecution cases is found no insert is performed`() { + wireMockServer.stubFor( + post(urlPathEqualTo("/probation-search/match")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBodyFile("probation-search-no-results.json") + ) + ) + + val notification = Notification(message = MessageGenerator.COMMON_PLATFORM_EVENT_NO_CASES) + channelManager.getChannel(queueName).publishAndWait(notification) + thenNoRecordsAreInserted() + } + + @Test + fun `When a message without a judicial result of remanded in custody is found`() { + wireMockServer.stubFor( + post(urlPathEqualTo("/probation-search/match")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBodyFile("probation-search-no-results.json") + ) + ) + + val notification = Notification(message = MessageGenerator.COMMON_PLATFORM_EVENT_NO_REMAND) + channelManager.getChannel(queueName).publishAndWait(notification) + thenNoRecordsAreInserted() + } + + @Test + fun `When a person under 10 years old is found no insert is performed`() { + wireMockServer.stubFor( + post(urlPathEqualTo("/probation-search/match")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBodyFile("probation-search-no-results.json") + ) + ) + + val notification = Notification(message = MessageGenerator.COMMON_PLATFORM_EVENT_DOB_ERROR) + channelManager.getChannel(queueName).publishAndWait(notification) + thenNoRecordsAreInserted() + } + @Test - fun `message is logged to telemetry`() { - // Given a message - val notification = Notification(message = MessageGenerator.EXAMPLE) + fun `When a probation search match is not detected then a person is inserted`() { + wireMockServer.stubFor( + post(urlPathEqualTo("/probation-search/match")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBodyFile("probation-search-no-results.json") + ) + ) - // When it is received - try { - channelManager.getChannel(queueName).publishAndWait(notification) - } catch (_: TimeoutException) { - // Note: Remove this try/catch when the MessageListener logic has been implemented + val notification = Notification(message = MessageGenerator.COMMON_PLATFORM_EVENT) + channelManager.getChannel(queueName).publishAndWait(notification) + + verify(personService).insertPerson(any(), any()) + + verify(personRepository).save(check { + assertThat(it.forename, Matchers.equalTo("Example First Name")) + assertThat(it.surname, Matchers.equalTo("Example Last Name")) + assertThat(it.mobileNumber, Matchers.equalTo("07000000000")) + assertThat(it.telephoneNumber, Matchers.equalTo("01234567890")) + }) + + verify(auditedInteractionService).createAuditedInteraction( + eq(BusinessInteractionCode.INSERT_PERSON), + any(), + eq(AuditedInteraction.Outcome.SUCCESS), + any(), + anyOrNull() + ) + } + + @Test + fun `When a hearing with an address is received then an address record is inserted`() { + wireMockServer.stubFor( + post(urlPathEqualTo("/probation-search/match")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBodyFile("probation-search-no-results.json") + ) + ) + + val notification = Notification(message = MessageGenerator.COMMON_PLATFORM_EVENT) + channelManager.getChannel(queueName).publishAndWait(notification) + + verify(personService).insertAddress(any()) + + verify(addressRepository).save(check { + assertThat(it.start, Matchers.equalTo(LocalDate.now())) + assertNull(it.endDate) + assertNotNull(it.notes) + assertThat(it.softDeleted, Matchers.equalTo(false)) + assertThat(it.status.code, Matchers.equalTo(ReferenceData.StandardRefDataCode.ADDRESS_MAIN_STATUS.code)) + assertThat(it.noFixedAbode, Matchers.equalTo(false)) + assertThat(it.type.code, Matchers.equalTo(ReferenceData.StandardRefDataCode.AWAITING_ASSESSMENT.code)) + assertThat(it.typeVerified, Matchers.equalTo(false)) + }) + + verify(auditedInteractionService).createAuditedInteraction( + eq(BusinessInteractionCode.INSERT_ADDRESS), + any(), + eq(AuditedInteraction.Outcome.SUCCESS), + any(), + anyOrNull() + ) + } + + @Test + fun `When a hearing with an empty address is received then an address record is not inserted`() { + wireMockServer.stubFor( + post(urlPathEqualTo("/probation-search/match")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBodyFile("probation-search-no-results.json") + ) + ) + + val notification = Notification(message = MessageGenerator.COMMON_PLATFORM_EVENT_BLANK_ADDRESS) + channelManager.getChannel(queueName).publishAndWait(notification) + + verify(personService, never()).insertAddress(any()) + + verify(addressRepository, never()).save(any()) + + verify(auditedInteractionService, never()).createAuditedInteraction( + eq(BusinessInteractionCode.INSERT_ADDRESS), + any(), + eq(AuditedInteraction.Outcome.SUCCESS), + any(), + anyOrNull() + ) + } + + @Order(1) + @Test + fun `engagement created and address created sns messages are published on insert person`() { + wireMockServer.stubFor( + post(urlPathEqualTo("/probation-search/match")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBodyFile("probation-search-no-results.json") + ) + ) + + val notification = Notification(message = MessageGenerator.COMMON_PLATFORM_EVENT) + channelManager.getChannel(queueName).publishAndWait(notification) + + verify(personService).insertPerson(any(), any()) + + verify(personRepository).save(check { + assertThat(it.forename, Matchers.equalTo("Example First Name")) + assertThat(it.surname, Matchers.equalTo("Example Last Name")) + assertThat(it.mobileNumber, Matchers.equalTo("07000000000")) + assertThat(it.telephoneNumber, Matchers.equalTo("01234567890")) + }) + + val topic = hmppsChannelManager.getChannel(topicName) + val messages = topic.pollFor(2) + val messageTypes = messages.mapNotNull { it.eventType } + + assertThat( + messageTypes.sorted(), + Matchers.equalTo( + listOf( + "probation-case.address.created", + "probation-case.engagement.created" + ) + ) + ) + + messages.forEach { message -> + val event = message.message as HmppsDomainEvent + + when (event.eventType) { + "probation-case.engagement.created" -> { + assertEquals(1, event.version) + assertEquals("probation-case.engagement.created", event.eventType) + assertEquals("A probation case record for a person has been created in Delius", event.description) + assertNotNull(event.personReference.findCrn()) + assertNull(event.detailUrl) + assertTrue(event.additionalInformation.isEmpty()) + } + + "probation-case.address.created" -> { + assertEquals(1, event.version) + assertEquals("probation-case.address.created", event.eventType) + assertEquals("A new address has been created on the probation case", event.description) + assertNotNull(event.personReference.findCrn()) + assertNull(event.detailUrl) + + with(event.additionalInformation) { + assertTrue(containsKey("addressStatus")) + assertTrue(containsKey("addressId")) + } + } + + else -> fail("Unexpected event type: ${event.eventType}") + } } + } - // Then it is logged to telemetry - verify(telemetryService, atLeastOnce()).notificationReceived(notification) + private fun thenNoRecordsAreInserted() { + verify(personService, never()).insertAddress(any()) + verify(addressRepository, never()).save(any()) + verify(personRepository, never()).save(any()) + verify(auditedInteractionService, Mockito.never()) + .createAuditedInteraction(any(), any(), eq(AuditedInteraction.Outcome.SUCCESS), any(), anyOrNull()) } -} +} \ No newline at end of file diff --git a/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/config/RestClientConfig.kt b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/config/RestClientConfig.kt index d81e4461ed..9341e1a5a1 100644 --- a/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/config/RestClientConfig.kt +++ b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/config/RestClientConfig.kt @@ -5,17 +5,12 @@ import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.web.client.RestClient import uk.gov.justice.digital.hmpps.config.security.createClient -import uk.gov.justice.digital.hmpps.integrations.example.ExampleClient +import uk.gov.justice.digital.hmpps.integrations.client.ProbationSearchClient @Configuration class RestClientConfig(private val oauth2Client: RestClient) { @Bean - fun exampleClient(@Value("\${integrations.example.url}") apiBaseUrl: String): ExampleClient { - return createClient( - oauth2Client.mutate() - .baseUrl(apiBaseUrl) - .build() - ) - } + fun probationSearchClient(@Value("\${integrations.probation-search.url}") apiBaseUrl: String): ProbationSearchClient = + createClient(oauth2Client.mutate().baseUrl(apiBaseUrl).build()) } diff --git a/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/client/ProbationSearchClient.kt b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/client/ProbationSearchClient.kt new file mode 100644 index 0000000000..4824a83091 --- /dev/null +++ b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/client/ProbationSearchClient.kt @@ -0,0 +1,53 @@ +package uk.gov.justice.digital.hmpps.integrations.client + +import com.fasterxml.jackson.annotation.JsonFormat +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.service.annotation.PostExchange +import java.time.LocalDate + +interface ProbationSearchClient { + @PostExchange(url = "/match") + fun match(@RequestBody body: ProbationMatchRequest): ProbationMatchResponse +} + +data class ProbationMatchRequest( + val firstName: String, + val surname: String, + @JsonFormat(pattern = "yyyy-MM-dd") + val dateOfBirth: LocalDate, + val nomsNumber: String? = null, + val activeSentence: Boolean = false, + val pncNumber: String? = null, + val croNumber: String? = null, +) + +data class ProbationMatchResponse( + val matches: List, + val matchedBy: String, +) + +data class OffenderMatch( + val offender: OffenderDetail, +) + +data class OffenderDetail( + val otherIds: IDs, + val previousSurname: String? = null, + val title: String? = null, + val firstName: String? = null, + val middleNames: List? = null, + val surname: String? = null, + val dateOfBirth: LocalDate? = null, + val gender: String? = null, + val currentDisposal: String? = null, +) + +data class IDs( + val crn: String, + val pncNumber: String? = null, + val croNumber: String? = null, + val niNumber: String? = null, + val nomsNumber: String? = null, + val immigrationNumber: String? = null, + val mostRecentPrisonerNumber: String? = null, +) \ No newline at end of file diff --git a/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/audit/BusinessInteractionCode.kt b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/audit/BusinessInteractionCode.kt new file mode 100644 index 0000000000..f2b5feeea2 --- /dev/null +++ b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/audit/BusinessInteractionCode.kt @@ -0,0 +1,8 @@ +package uk.gov.justice.digital.hmpps.integrations.delius.audit + +import uk.gov.justice.digital.hmpps.audit.InteractionCode + +enum class BusinessInteractionCode(override val code: String) : InteractionCode { + INSERT_PERSON("OIBI025"), + INSERT_ADDRESS("OIBI029") +} diff --git a/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/Equality.kt b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/Equality.kt new file mode 100644 index 0000000000..d648334960 --- /dev/null +++ b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/Equality.kt @@ -0,0 +1,45 @@ +package uk.gov.justice.digital.hmpps.integrations.delius.entity + +import jakarta.persistence.* +import org.springframework.data.annotation.CreatedBy +import org.springframework.data.annotation.CreatedDate +import org.springframework.data.annotation.LastModifiedBy +import org.springframework.data.annotation.LastModifiedDate +import org.springframework.data.jpa.domain.support.AuditingEntityListener +import org.springframework.data.jpa.repository.JpaRepository +import java.time.ZonedDateTime + +@Entity +@Table(name = "equality") +@SequenceGenerator(name = "equality_id_seq", sequenceName = "equality_id_seq", allocationSize = 1) +@EntityListeners(AuditingEntityListener::class) +class Equality( + @Id + @Column(name = "equality_id") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "equality_id_seq") + val id: Long? = null, + + @Column(name = "offender_id") + val personId: Long, + + @CreatedBy + var createdByUserId: Long = 0, + + @CreatedDate + var createdDatetime: ZonedDateTime = ZonedDateTime.now(), + + @LastModifiedBy + var lastUpdatedUserId: Long = 0, + + @LastModifiedDate + var lastUpdatedDatetime: ZonedDateTime = ZonedDateTime.now(), + + @Column + val softDeleted: Boolean, + + @Column + @Version + val rowVersion: Long = 0L +) + +interface EqualityRepository : JpaRepository \ No newline at end of file diff --git a/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/Person.kt b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/Person.kt new file mode 100644 index 0000000000..a67d7cb702 --- /dev/null +++ b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/Person.kt @@ -0,0 +1,107 @@ +package uk.gov.justice.digital.hmpps.integrations.delius.entity + +import jakarta.persistence.* +import org.hibernate.annotations.SQLRestriction +import org.springframework.data.annotation.CreatedBy +import org.springframework.data.annotation.CreatedDate +import org.springframework.data.annotation.LastModifiedBy +import org.springframework.data.annotation.LastModifiedDate +import org.springframework.data.jpa.domain.support.AuditingEntityListener +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import java.time.LocalDate +import java.time.ZonedDateTime + +@Entity +@Table(name = "offender") +@SQLRestriction("soft_deleted = 0") +@SequenceGenerator(name = "offender_id_seq", sequenceName = "offender_id_seq", allocationSize = 1) +@EntityListeners(AuditingEntityListener::class) +class Person( + @Id + @Column(name = "offender_id") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "offender_id_seq") + val id: Long? = null, + + @Column(columnDefinition = "char(7)") + val crn: String, + + @Column + val croNumber: String? = null, + + @Column(columnDefinition = "char(13)") + val pncNumber: String? = null, + + @Column(name = "first_name", length = 35) + val forename: String, + + @Column + val surnameSoundex: String, + + @Column + val firstNameSoundex: String, + + @Column + val middleNameSoundex: String?, + + @Column(name = "second_name", length = 35) + val secondName: String? = null, + + @Column(name = "surname", length = 35) + val surname: String, + + @Column(name = "date_of_birth_date") + val dateOfBirth: LocalDate, + + @ManyToOne + @JoinColumn(name = "gender_id") + val gender: ReferenceData, + + @Column(name = "telephone_number") + val telephoneNumber: String? = null, + + @Column(name = "mobile_number") + val mobileNumber: String? = null, + + @Column(columnDefinition = "number") + val softDeleted: Boolean = false, + + @Column(name = "current_disposal", columnDefinition = "number") + val currentDisposal: Boolean = false, + + @Column(name = "current_restriction", columnDefinition = "number") + val currentRestriction: Boolean = false, + + @Column(name = "pending_transfer", columnDefinition = "number") + val pendingTransfer: Boolean = false, + + @Column + @Version + val rowVersion: Long = 0L, + + @CreatedDate + var createdDatetime: ZonedDateTime = ZonedDateTime.now(), + + @LastModifiedDate + var lastUpdatedDatetime: ZonedDateTime = ZonedDateTime.now(), + + @LastModifiedDate + var lastUpdatedDatetimeDiversit: ZonedDateTime = ZonedDateTime.now(), + + @LastModifiedBy + var lastUpdatedUserIdDiversity: Long = 0, + + @CreatedBy + var createdByUserId: Long = 0, + + @LastModifiedBy + var lastUpdatedUserId: Long = 0, + + @Column + val partitionAreaId: Long = 0L +) + +interface PersonRepository : JpaRepository { + @Query("SELECT SOUNDEX(:name) FROM DUAL", nativeQuery = true) + fun getSoundex(name: String): String +} \ No newline at end of file diff --git a/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/PersonAddress.kt b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/PersonAddress.kt new file mode 100644 index 0000000000..ba29020bce --- /dev/null +++ b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/PersonAddress.kt @@ -0,0 +1,112 @@ +package uk.gov.justice.digital.hmpps.integrations.delius.person.entity + +import jakarta.persistence.* +import org.hibernate.type.YesNoConverter +import org.springframework.data.annotation.CreatedBy +import org.springframework.data.annotation.CreatedDate +import org.springframework.data.annotation.LastModifiedBy +import org.springframework.data.annotation.LastModifiedDate +import org.springframework.data.jpa.domain.support.AuditingEntityListener +import org.springframework.data.jpa.repository.JpaRepository +import uk.gov.justice.digital.hmpps.integrations.delius.entity.Person +import uk.gov.justice.digital.hmpps.integrations.delius.entity.ReferenceData +import java.time.LocalDate +import java.time.ZonedDateTime + +@Entity +@Table(name = "offender_address") +@EntityListeners(AuditingEntityListener::class) +@SequenceGenerator(name = "offender_address_id_generator", sequenceName = "offender_address_id_seq", allocationSize = 1) +class PersonAddress( + @Id + @Column(name = "offender_address_id") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "offender_address_id_generator") + val id: Long? = null, + + @Column(name = "start_date") + val start: LocalDate? = null, + + @Column(name = "end_date") + val endDate: LocalDate? = null, + + @Column(name = "partition_area_id", nullable = false) + val partitionAreaId: Long = 0, + + @Column(name = "soft_deleted", updatable = false, columnDefinition = "NUMBER") + val softDeleted: Boolean = false, + + @Column(name = "row_version") + @Version + val rowVersion: Long = 0L, + + @ManyToOne + @JoinColumn(name = "address_status_id") + val status: ReferenceData, + + @Convert(converter = YesNoConverter::class) + @Column(name = "no_fixed_abode") + val noFixedAbode: Boolean? = false, + + @ManyToOne + @JoinColumn(name = "offender_id") + val person: Person, + + @Column(name = "notes", columnDefinition = "clob") + val notes: String? = null, + + @Column(name = "address_number") + val addressNumber: String? = null, + + @Column(name = "street_name") + val streetName: String? = null, + + @Column(name = "district") + val district: String? = null, + + @Column(name = "town_city") + val town: String? = null, + + @CreatedDate + @Column(nullable = false) + var createdDatetime: ZonedDateTime = ZonedDateTime.now(), + + @Column(name = "county") + val county: String? = null, + + @Column(nullable = false) + @LastModifiedDate + var lastUpdatedDatetime: ZonedDateTime = ZonedDateTime.now(), + + @Column(name = "building_name") + val buildingName: String? = null, + + @Column(name = "postcode") + val postcode: String? = null, + + @Column(nullable = false) + @CreatedBy + var createdByUserId: Long = 0, + + @Column(name = "telephone_number") + val telephoneNumber: String? = null, + + @Column(nullable = false) + @LastModifiedBy + var lastUpdatedUserId: Long = 0, + + @Column(name = "awaiting_assessment", columnDefinition = "NUMBER", nullable = false) + val awaitingAssessment: Boolean = false, + + @ManyToOne + @JoinColumn(name = "address_type_id") + val type: ReferenceData, + + @Column(name = "type_verified") + @Convert(converter = YesNoConverter::class) + val typeVerified: Boolean? = false, + + @Column(name = "approved_premises_residence_id") + val approvedPremisesResidenceId: Long? = null, +) + +interface PersonAddressRepository : JpaRepository \ No newline at end of file diff --git a/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/PersonManager.kt b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/PersonManager.kt new file mode 100644 index 0000000000..36ace96365 --- /dev/null +++ b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/PersonManager.kt @@ -0,0 +1,120 @@ +package uk.gov.justice.digital.hmpps.integrations.delius.entity + +import jakarta.persistence.* +import org.hibernate.annotations.Immutable +import org.hibernate.annotations.SQLRestriction +import org.springframework.data.annotation.CreatedBy +import org.springframework.data.annotation.CreatedDate +import org.springframework.data.annotation.LastModifiedBy +import org.springframework.data.annotation.LastModifiedDate +import org.springframework.data.jpa.domain.support.AuditingEntityListener +import org.springframework.data.jpa.repository.JpaRepository +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.ZonedDateTime + +@Entity +@Table(name = "offender_manager") +@SQLRestriction("soft_deleted = 0 and active_flag = 1") +@SequenceGenerator(name = "offender_manager_id_seq", sequenceName = "offender_manager_id_seq", allocationSize = 1) +@EntityListeners(AuditingEntityListener::class) +class PersonManager( + @Id + @Column(name = "offender_manager_id") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "offender_id_seq") + val id: Long? = null, + + @Column(name = "allocation_date") + val allocationDate: LocalDateTime, + + @ManyToOne + @JoinColumn(name = "allocation_reason_id") + val allocationReason: ReferenceData, + + @Column(name = "end_date") + val endDate: LocalDate? = null, + + @ManyToOne + @JoinColumn(name = "offender_id") + val person: Person, + + @Version + @Column(name = "row_version") + val version: Long = 0, + + @Column(name = "soft_deleted", columnDefinition = "number") + val softDeleted: Boolean = false, + + @ManyToOne + @JoinColumn(name = "allocation_staff_id") + val staff: Staff, + + @Column(name = "staff_employee_id") + val staffEmployeeID: Long, + + @ManyToOne + @JoinColumn(name = "team_id") + val team: Team, + + @Column(name = "trust_provider_team_id") + val trustProviderTeamId: Long, + + @ManyToOne + @JoinColumn(name = "probation_area_id") + val provider: Provider, + + @Column(name = "active_flag", columnDefinition = "number") + val active: Boolean = true, + + @CreatedDate + var createdDatetime: ZonedDateTime = ZonedDateTime.now(), + + @LastModifiedDate + var lastUpdatedDatetime: ZonedDateTime = ZonedDateTime.now(), + + @CreatedBy + var createdByUserId: Long = 0, + + @LastModifiedBy + var lastUpdatedUserId: Long = 0, + + @Column + val partitionAreaId: Long = 0L +) + +@Immutable +@Entity +@Table(name = "staff") +class Staff( + @Id + @Column(name = "staff_id") + val id: Long, + + @Column(name = "officer_code", columnDefinition = "char(7)") + val code: String, +) + +@Immutable +@Entity +@Table(name = "team") +class Team( + @Id + @Column(name = "team_id") + val id: Long = 0, + + @Column(columnDefinition = "char(6)") + val code: String, + + @Column + val description: String, +) + +interface PersonManagerRepository : JpaRepository + +interface TeamRepository : JpaRepository { + fun findByCode(code: String): Team +} + +interface StaffRepository : JpaRepository { + fun findByCode(code: String): Staff +} \ No newline at end of file diff --git a/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/ReferenceData.kt b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/ReferenceData.kt new file mode 100644 index 0000000000..86098cab07 --- /dev/null +++ b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/ReferenceData.kt @@ -0,0 +1,156 @@ +package uk.gov.justice.digital.hmpps.integrations.delius.entity + +import jakarta.persistence.* +import org.hibernate.annotations.Immutable +import org.hibernate.type.YesNoConverter +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import uk.gov.justice.digital.hmpps.exception.NotFoundException +import java.time.LocalDate + +@Entity +@Immutable +@Table(name = "r_standard_reference_list") +class ReferenceData( + @Id + @Column(name = "standard_reference_list_id", nullable = false) + val id: Long, + + @Column(name = "code_value", length = 100, nullable = false) + val code: String, + + @Column(name = "reference_data_master_id", nullable = false) + val datasetId: Long, + + @Column(name = "code_description", length = 500, nullable = false) + val description: String, +) { + enum class GenderCode(val commonPlatformValue: String, val deliusValue: String) { + MALE("MALE", "M"), + FEMALE("FEMALE", "F"), + OTHER("OTHER", "O"), + NOT_KNOWN("NOT KNOWN", "N") + } + + enum class StandardRefDataCode(val code: String) { + INITIAL_ALLOCATION("IN1"), + ADDRESS_MAIN_STATUS("M"), + AWAITING_ASSESSMENT("A16") + } +} + +@Immutable +@Entity +@Table(name = "r_reference_data_master") +class Dataset( + @Id + @Column(name = "reference_data_master_id") + val id: Long, + + @Convert(converter = DatasetCodeConverter::class) + @Column(name = "code_set_name", nullable = false) + val code: DatasetCode +) + +enum class DatasetCode(val value: String) { + OM_ALLOCATION_REASON("OM ALLOCATION REASON"), + ADDRESS_STATUS("ADDRESS STATUS"), + ADDRESS_TYPE("ADDRESS TYPE"), + GENDER("GENDER"); + + companion object { + private val index = DatasetCode.entries.associateBy { it.value } + fun fromString(value: String): DatasetCode = + index[value] ?: throw IllegalArgumentException("Invalid DatasetCode") + } +} + +@Converter +class DatasetCodeConverter : AttributeConverter { + override fun convertToDatabaseColumn(attribute: DatasetCode): String = attribute.value + + override fun convertToEntityAttribute(dbData: String): DatasetCode = DatasetCode.fromString(dbData) +} + +@Entity +@Immutable +@Table(name = "probation_area") +class Provider( + @Id + @Column(name = "probation_area_id") + val id: Long, + + @Column(name = "code", columnDefinition = "char(3)") + val code: String, + + @Column + val description: String, + + @Convert(converter = YesNoConverter::class) + val selectable: Boolean = true, + + @Column + val endDate: LocalDate? = null +) + +@Entity +@Immutable +@Table(name = "court") +class Court( + @Id + @Column(name = "court_id", nullable = false) + val id: Long, + + @Column(columnDefinition = "char(6)", nullable = false) + val code: String, + + @Convert(converter = YesNoConverter::class) + val selectable: Boolean = true, + + @Column(name = "court_name", length = 80) + val name: String, + + @ManyToOne + @JoinColumn(name = "probation_area_id") + val provider: Provider, + + @Column(name = "court_ou_code") + val ouCode: String? +) + +interface ReferenceDataRepository : JpaRepository { + @Query( + """ + select rd from ReferenceData rd + join Dataset ds on rd.datasetId = ds.id + where ds.code = :datasetCode and rd.code = :code + """ + ) + fun findByCodeAndDatasetCode(code: String, datasetCode: DatasetCode): ReferenceData? +} + +fun ReferenceDataRepository.initialAllocationReason() = + findByCodeAndDatasetCode( + ReferenceData.StandardRefDataCode.INITIAL_ALLOCATION.code, + DatasetCode.OM_ALLOCATION_REASON + ) + ?: throw NotFoundException( + "Allocation Reason", + "code", + ReferenceData.StandardRefDataCode.INITIAL_ALLOCATION.code + ) + +fun ReferenceDataRepository.mainAddressStatus() = + findByCodeAndDatasetCode(ReferenceData.StandardRefDataCode.ADDRESS_MAIN_STATUS.code, DatasetCode.ADDRESS_STATUS) + ?: throw NotFoundException("Address Status", "code", ReferenceData.StandardRefDataCode.ADDRESS_MAIN_STATUS.code) + +fun ReferenceDataRepository.awaitingAssessmentAddressType() = + findByCodeAndDatasetCode(ReferenceData.StandardRefDataCode.AWAITING_ASSESSMENT.code, DatasetCode.ADDRESS_TYPE) + ?: throw NotFoundException("Address Type", "code", ReferenceData.StandardRefDataCode.AWAITING_ASSESSMENT.code) + +interface CourtRepository : JpaRepository { + fun findByOuCode(ouCode: String): Court? +} + +fun CourtRepository.getByOuCode(ouCode: String) = + findByOuCode(ouCode) ?: throw NotFoundException("Court", "ouCode", ouCode) diff --git a/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/example/ExampleClient.kt b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/example/ExampleClient.kt deleted file mode 100644 index c164d63afd..0000000000 --- a/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/example/ExampleClient.kt +++ /dev/null @@ -1,9 +0,0 @@ -package uk.gov.justice.digital.hmpps.integrations.example - -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.service.annotation.GetExchange - -interface ExampleClient { - @GetExchange(value = "/example/{inputId}") - fun getExampleAPICall(@PathVariable("inputId") inputId: String): String -} diff --git a/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/CommonPlatformHearing.kt b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/CommonPlatformHearing.kt new file mode 100644 index 0000000000..692732d26d --- /dev/null +++ b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/CommonPlatformHearing.kt @@ -0,0 +1,133 @@ +package uk.gov.justice.digital.hmpps.messaging + +import org.openfolder.kotlinasyncapi.annotation.channel.Message +import java.time.LocalDate +import java.time.ZonedDateTime + +@Message +data class CommonPlatformHearing( + val hearing: Hearing +) + +data class Hearing( + val id: String, + val courtCentre: CourtCentre, + val type: HearingType, + val jurisdictionType: String, + val hearingDays: List, + val prosecutionCases: List +) + +data class ProsecutionCase( + val id: String, + val initiationCode: String, + val prosecutionCaseIdentifier: ProsecutionCaseIdentifier, + val defendants: List, + val caseStatus: String, + val caseMarkers: List = emptyList() +) + +data class ProsecutionCaseIdentifier( + val prosecutionAuthorityCode: String, + val prosecutionAuthorityId: String, + val caseURN: String +) + +data class Defendant( + val id: String, + val offences: List, + val prosecutionCaseId: String, + val personDefendant: PersonDefendant?, + val legalEntityDefendant: Any? = null, + val masterDefendantId: String, + val pncId: String, + val croNumber: String +) + +data class Offence( + val id: String, + val offenceDefinitionId: String, + val offenceCode: String, + val offenceTitle: String, + val wording: String, + val offenceLegislation: String, + val listingNumber: Int, + val judicialResults: List, + val plea: Plea? = null, + val verdict: Verdict? = null +) + +data class JudicialResult( + val isConvictedResult: Boolean?, + val label: String?, + val judicialResultTypeId: String?, + val resultText: String? +) + +data class Plea( + val pleaValue: String?, + val pleaDate: LocalDate +) + +data class Verdict( + val verdictDate: ZonedDateTime?, + val verdictType: VerdictType +) + +data class VerdictType( + val description: String?, +) + +data class PersonDefendant( + val personDetails: PersonDetails +) + +data class PersonDetails( + val gender: String, + val lastName: String, + val middleName: String? = null, + val firstName: String, + val dateOfBirth: LocalDate, + val address: Address, + val contact: Contact?, + val ethnicity: Ethnicity +) + +data class Address( + val address1: String? = null, + val address2: String? = null, + val address3: String? = null, + val address4: String? = null, + val address5: String? = null, + val postcode: String? = null +) + +data class Contact( + val home: String? = null, + val mobile: String, + val work: String? = null +) + +data class HearingType( + val id: String, + val description: String, + val welshDescription: String? = null +) + +data class Ethnicity( + val observedEthnicityDescription: String, + val selfDefinedEthnicityDescription: String +) + +data class CourtCentre( + val id: String, + val code: String, + val roomId: String, + val roomName: String +) + +data class HearingDay( + val sittingDay: ZonedDateTime, + val listedDurationMinutes: Int, + val listingSequence: Int +) \ No newline at end of file diff --git a/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Converter.kt b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Converter.kt new file mode 100644 index 0000000000..cdb5725335 --- /dev/null +++ b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Converter.kt @@ -0,0 +1,22 @@ +package uk.gov.justice.digital.hmpps.messaging + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +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.message.Notification + +@Primary +@Component +class Converter(objectMapper: ObjectMapper) : NotificationConverter(objectMapper) { + override fun getMessageType() = CommonPlatformHearing::class + + override fun fromMessage(message: String): Notification { + val stringMessage = objectMapper.readValue(message, jacksonTypeRef>()) + return Notification( + message = objectMapper.readValue(stringMessage.message, CommonPlatformHearing::class.java), + attributes = stringMessage.attributes + ) + } +} \ No newline at end of file diff --git a/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt index 1411b9fb01..a27d0f6308 100644 --- a/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt +++ b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt @@ -1,29 +1,84 @@ 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.Publish import org.springframework.stereotype.Component import uk.gov.justice.digital.hmpps.converter.NotificationConverter -import uk.gov.justice.digital.hmpps.message.HmppsDomainEvent +import uk.gov.justice.digital.hmpps.integrations.client.ProbationMatchRequest +import uk.gov.justice.digital.hmpps.integrations.client.ProbationSearchClient import uk.gov.justice.digital.hmpps.message.Notification -import uk.gov.justice.digital.hmpps.telemetry.TelemetryService +import uk.gov.justice.digital.hmpps.service.PersonService import uk.gov.justice.digital.hmpps.telemetry.TelemetryMessagingExtensions.notificationReceived +import uk.gov.justice.digital.hmpps.telemetry.TelemetryService @Component @Channel("common-platform-and-delius-queue") class Handler( - override val converter: NotificationConverter, - private val telemetryService: TelemetryService -) : NotificationHandler { - @Publish( - messages = [ - // TODO list the event types here that this service will subscribe to. For example, - // Message(name = "approved-premises/application-assessed"), - // Message(title = "probation-case.prison-identifier.added"), - ] - ) - override fun handle(notification: Notification) { + override val converter: NotificationConverter, + private val notifier: Notifier, + private val telemetryService: TelemetryService, + private val personService: PersonService, + private val probationSearchClient: ProbationSearchClient +) : NotificationHandler { + + @Publish(messages = [Message(title = "COMMON_PLATFORM_HEARING", payload = Schema(CommonPlatformHearing::class))]) + override fun handle(notification: Notification) { telemetryService.notificationReceived(notification) - TODO("Not yet implemented") + + // Filter hearing message for defendants with a convicted judicial result of Remanded in custody + val defendants = notification.message.hearing.prosecutionCases + .flatMap { it.defendants } + .filter { defendant -> + defendant.offences.any { offence -> + offence.judicialResults.any { judicialResult -> + judicialResult.isConvictedResult == true && judicialResult.label == "Remanded in custody" + } + } + } + if (defendants.isEmpty()) { + return + } + + val courtCode = notification.message.hearing.courtCentre.code + + defendants.forEach { defendant -> + val matchRequest = defendant.toProbationMatchRequest() + val matchedPersonResponse = probationSearchClient.match(matchRequest) + + if (matchedPersonResponse.matches.isNotEmpty()) { + return + } + + // Insert each defendant as a person record + val savedEntities = personService.insertPerson(defendant, courtCode) + + notifier.caseCreated(savedEntities.person) + savedEntities.address?.let { notifier.addressCreated(it) } + + telemetryService.trackEvent( + "PersonCreated", mapOf( + "hearingId" to notification.message.hearing.id, + "CRN" to savedEntities.person.crn, + "personId" to savedEntities.person.id.toString(), + "personManagerId" to savedEntities.personManager.id.toString(), + "equalityId" to savedEntities.equality.id.toString(), + "addressId" to savedEntities.address?.id.toString() + ) + ) + } + } + + fun Defendant.toProbationMatchRequest(): ProbationMatchRequest { + val personDetails = + this.personDefendant?.personDetails ?: throw IllegalArgumentException("Person details are required") + return ProbationMatchRequest( + firstName = personDetails.firstName, + surname = personDetails.lastName, + dateOfBirth = personDetails.dateOfBirth, + pncNumber = this.pncId, + croNumber = this.croNumber + ) } } diff --git a/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Notifier.kt b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Notifier.kt new file mode 100644 index 0000000000..5f4a393905 --- /dev/null +++ b/projects/common-platform-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.integrations.delius.entity.Person +import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.PersonAddress +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.engagement.created", payload = Schema(HmppsDomainEvent::class)), + Message(title = "probation-case.address.created", payload = Schema(HmppsDomainEvent::class)) + ] + ) + fun caseCreated(person: Person) { + topicPublisher.publish( + Notification( + message = HmppsDomainEvent( + version = 1, + eventType = "probation-case.engagement.created", + description = "A probation case record for a person has been created in Delius", + personReference = PersonReference( + identifiers = listOf( + PersonIdentifier("CRN", person.crn), + ), + ), + ), + attributes = MessageAttributes("probation-case.engagement.created") + ) + ) + } + + fun addressCreated(personAddress: PersonAddress) { + 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", personAddress.person.crn), + ), + ), + additionalInformation = mapOf( + "addressStatus" to personAddress.status.description, + "addressId" to personAddress.id.toString() + ) + ), + attributes = MessageAttributes("probation-case.address.created") + ) + ) + } +} \ No newline at end of file diff --git a/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/InsertPersonResult.kt b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/InsertPersonResult.kt new file mode 100644 index 0000000000..ab2dfb41d5 --- /dev/null +++ b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/InsertPersonResult.kt @@ -0,0 +1,13 @@ +package uk.gov.justice.digital.hmpps.service + +import uk.gov.justice.digital.hmpps.integrations.delius.entity.Equality +import uk.gov.justice.digital.hmpps.integrations.delius.entity.Person +import uk.gov.justice.digital.hmpps.integrations.delius.entity.PersonManager +import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.PersonAddress + +data class InsertPersonResult( + val person: Person, + val personManager: PersonManager, + val equality: Equality, + var address: PersonAddress? +) \ No newline at end of file diff --git a/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/PersonService.kt b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/PersonService.kt new file mode 100644 index 0000000000..f6e7406df8 --- /dev/null +++ b/projects/common-platform-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/PersonService.kt @@ -0,0 +1,157 @@ +package uk.gov.justice.digital.hmpps.service + +import org.springframework.jdbc.core.JdbcTemplate +import org.springframework.jdbc.core.simple.SimpleJdbcCall +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import uk.gov.justice.digital.hmpps.audit.service.AuditableService +import uk.gov.justice.digital.hmpps.audit.service.AuditedInteractionService +import uk.gov.justice.digital.hmpps.integrations.delius.audit.BusinessInteractionCode +import uk.gov.justice.digital.hmpps.integrations.delius.entity.* +import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.PersonAddress +import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.PersonAddressRepository +import uk.gov.justice.digital.hmpps.messaging.Address +import uk.gov.justice.digital.hmpps.messaging.Defendant +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.Period + +@Service +class PersonService( + jdbcTemplate: JdbcTemplate, + auditedInteractionService: AuditedInteractionService, + private val personRepository: PersonRepository, + private val courtRepository: CourtRepository, + private val equalityRepository: EqualityRepository, + private val personManagerRepository: PersonManagerRepository, + private val teamRepository: TeamRepository, + private val staffRepository: StaffRepository, + private val referenceDataRepository: ReferenceDataRepository, + private val personAddressRepository: PersonAddressRepository +) : AuditableService(auditedInteractionService) { + + private val generateCrn = SimpleJdbcCall(jdbcTemplate) + .withCatalogName("offender_support_api") + .withFunctionName("getNextCRN") + + @Transactional + fun insertPerson(defendant: Defendant, courtCode: String): InsertPersonResult = + audit(BusinessInteractionCode.INSERT_PERSON) { audit -> + + val dateOfBirth = defendant.personDefendant?.personDetails?.dateOfBirth + ?: throw IllegalArgumentException("Date of birth not found in message") + + // Under 10 years old validation + dateOfBirth.let { + val age = Period.between(it, LocalDate.now()).years + require(age > 10) { + "Date of birth would indicate person is under ten years old: $it" + } + } + + // Person record + val savedPerson = personRepository.save(defendant.toPerson()) + + val courtLinkedProvider = courtRepository.getByOuCode(courtCode).provider + val initialAllocation = referenceDataRepository.initialAllocationReason() + val unallocatedTeam = teamRepository.findByCode(courtLinkedProvider.code + "UAT") + val unallocatedStaff = staffRepository.findByCode(unallocatedTeam.code + "U") + + // Person manager record + val manager = PersonManager( + person = savedPerson, + staff = unallocatedStaff, + team = unallocatedTeam, + provider = courtLinkedProvider, + softDeleted = false, + active = true, + allocationReason = initialAllocation, + staffEmployeeID = unallocatedStaff.id, + trustProviderTeamId = unallocatedTeam.id, + allocationDate = LocalDateTime.of(1900, 1, 1, 0, 0) + + ) + val savedManager = personManagerRepository.save(manager) + + // Equality record + val equality = Equality( + id = null, + personId = savedPerson.id!!, + softDeleted = false, + ) + + val savedEquality = equalityRepository.save(equality) + + val savedAddress = + defendant.personDefendant.personDetails.address.takeIf { it.containsInformation() }?.let { + insertAddress( + PersonAddress( + id = null, + start = LocalDate.now(), + status = referenceDataRepository.mainAddressStatus(), + person = savedPerson, + notes = it.buildNotes(), + postcode = it.postcode, + type = referenceDataRepository.awaitingAssessmentAddressType() + ) + ) + } + audit["offenderId"] = savedPerson.id + InsertPersonResult(savedPerson, savedManager, savedEquality, savedAddress) + } + + @Transactional + fun insertAddress(address: PersonAddress): PersonAddress = audit(BusinessInteractionCode.INSERT_ADDRESS) { audit -> + val savedAddress = personAddressRepository.save(address) + audit["addressId"] = address.id!! + savedAddress + } + + fun generateCrn(): String { + return generateCrn.executeFunction(String::class.java) + } + + fun String.toDeliusGender() = ReferenceData.GenderCode.entries.find { it.commonPlatformValue == this }?.deliusValue + ?: throw IllegalStateException("Gender not found: $this") + + fun Defendant.toPerson(): Person { + val personDetails = personDefendant?.personDetails ?: throw IllegalArgumentException("No person found") + val genderCode = personDetails.gender.toDeliusGender() + + return Person( + id = null, + crn = generateCrn(), + croNumber = this.croNumber, + pncNumber = this.pncId, + forename = personDetails.firstName, + secondName = personDetails.middleName, + telephoneNumber = personDetails.contact?.home, + mobileNumber = personDetails.contact?.mobile, + surname = personDetails.lastName, + dateOfBirth = personDetails.dateOfBirth, + gender = referenceDataRepository.findByCodeAndDatasetCode(genderCode, DatasetCode.GENDER)!!, + softDeleted = false, + surnameSoundex = personRepository.getSoundex(personDetails.lastName), + middleNameSoundex = personDetails.middleName?.let { personRepository.getSoundex(it) }, + firstNameSoundex = personRepository.getSoundex(personDetails.firstName), + ) + } + + fun Address?.containsInformation(): Boolean { + return this != null && listOf( + this.address1, this.address2, this.address3, + this.address4, this.address5, this.postcode + ).any { !it.isNullOrBlank() } + } + + fun Address.buildNotes(): String { + return listOf( + "Address record automatically created by common-platform-delius-service with the following information:", + "Address1: ${this.address1 ?: "N/A"}", + "Address2: ${this.address2 ?: "N/A"}", + "Address3: ${this.address3 ?: "N/A"}", + "Address4: ${this.address4 ?: "N/A"}", + "Postcode: ${this.postcode ?: "N/A"}" + ).joinToString("\n") + } +} \ No newline at end of file diff --git a/projects/common-platform-and-delius/src/main/resources/application.yml b/projects/common-platform-and-delius/src/main/resources/application.yml index 38684787bb..f6742dd927 100644 --- a/projects/common-platform-and-delius/src/main/resources/application.yml +++ b/projects/common-platform-and-delius/src/main/resources/application.yml @@ -49,7 +49,12 @@ server.shutdown: immediate spring: datasource.url: jdbc:h2:file:./dev;MODE=Oracle;DEFAULT_NULL_ORDERING=HIGH;AUTO_SERVER=true;AUTO_SERVER_PORT=9092 - jpa.hibernate.ddl-auto: create-drop + sql.init.mode: always + sql.init.data-locations: classpath:db/h2.sql + jpa: + hibernate.ddl-auto: create-drop + +messaging.producer.topic: domain-events seed.database: true wiremock.enabled: true @@ -58,8 +63,7 @@ context.initializer.classes: uk.gov.justice.digital.hmpps.wiremock.WireMockIniti messaging.consumer.queue: message-queue integrations: - example: - url: http://localhost:${wiremock.port}/example + probation-search.url: http://localhost:${wiremock.port}/probation-search oauth2: client-id: common-platform-and-delius @@ -74,10 +78,6 @@ logging.level: spring.config.activate.on-profile: integration-test spring.datasource.url: jdbc:h2:mem:./test;MODE=Oracle;DEFAULT_NULL_ORDERING=HIGH ---- -spring.config.activate.on-profile: oracle -spring.datasource.url: 'jdbc:tc:oracle:slim-faststart:///XEPDB1' - --- spring.config.activate.on-profile: delius-db spring: diff --git a/projects/common-platform-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/messaging/HandlerTest.kt b/projects/common-platform-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/messaging/HandlerTest.kt index 5345a539df..b4ebaf9bdd 100644 --- a/projects/common-platform-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/messaging/HandlerTest.kt +++ b/projects/common-platform-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/messaging/HandlerTest.kt @@ -5,13 +5,23 @@ import org.junit.jupiter.api.extension.ExtendWith import org.mockito.InjectMocks import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.any +import org.mockito.kotlin.never import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever import uk.gov.justice.digital.hmpps.converter.NotificationConverter import uk.gov.justice.digital.hmpps.data.generator.MessageGenerator -import uk.gov.justice.digital.hmpps.message.HmppsDomainEvent +import uk.gov.justice.digital.hmpps.data.generator.PersonAddressGenerator +import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator +import uk.gov.justice.digital.hmpps.data.generator.PersonManagerGenerator +import uk.gov.justice.digital.hmpps.integrations.client.* +import uk.gov.justice.digital.hmpps.integrations.delius.entity.Equality import uk.gov.justice.digital.hmpps.message.Notification -import uk.gov.justice.digital.hmpps.telemetry.TelemetryService +import uk.gov.justice.digital.hmpps.service.InsertPersonResult +import uk.gov.justice.digital.hmpps.service.PersonService import uk.gov.justice.digital.hmpps.telemetry.TelemetryMessagingExtensions.notificationReceived +import uk.gov.justice.digital.hmpps.telemetry.TelemetryService +import java.time.LocalDate @ExtendWith(MockitoExtension::class) internal class HandlerTest { @@ -19,24 +29,81 @@ internal class HandlerTest { lateinit var telemetryService: TelemetryService @Mock - lateinit var converter: NotificationConverter + lateinit var converter: NotificationConverter + + @Mock + lateinit var personService: PersonService + + @Mock + lateinit var probationSearchClient: ProbationSearchClient + + @Mock + lateinit var notifier: Notifier @InjectMocks lateinit var handler: Handler @Test - fun `message is logged to telemetry`() { - // Given a message - val notification = Notification(message = MessageGenerator.EXAMPLE) - - // When it is received - try { - handler.handle(notification) - } catch (_: NotImplementedError) { - // Note: Remove this try/catch when the Handler logic has been implemented - } - - // Then it is logged to telemetry + fun `inserts records when probation search match is not found`() { + whenever(probationSearchClient.match(any())).thenReturn( + ProbationMatchResponse( + matches = emptyList(), + matchedBy = "NONE" + ) + ) + whenever(personService.insertPerson(any(), any())).thenReturn( + InsertPersonResult( + person = PersonGenerator.DEFAULT, + personManager = PersonManagerGenerator.DEFAULT, + equality = Equality(id = 1L, personId = 1L, softDeleted = false), + address = PersonAddressGenerator.MAIN_ADDRESS, + ) + ) + + val notification = Notification(message = MessageGenerator.COMMON_PLATFORM_EVENT) + handler.handle(notification) + verify(telemetryService).notificationReceived(notification) + verify(personService).insertPerson(any(), any()) + verify(notifier).caseCreated(any()) + verify(notifier).addressCreated(any()) + } + + @Test + fun `does not insert person or address when match is found`() { + val fakeMatchResponse = ProbationMatchResponse( + matches = listOf( + OffenderMatch( + offender = OffenderDetail( + otherIds = IDs(crn = "X123456", pncNumber = "00000000000Z"), + firstName = "Name", + surname = "Name", + dateOfBirth = LocalDate.of(1980, 1, 1) + ) + ) + ), + matchedBy = "PNC" + ) + + whenever(probationSearchClient.match(any())).thenReturn(fakeMatchResponse) + + val notification = Notification(message = MessageGenerator.COMMON_PLATFORM_EVENT) + handler.handle(notification) + + verify(telemetryService).notificationReceived(notification) + verify(personService, never()).insertPerson(any(), any()) + verify(notifier, never()).caseCreated(any()) + verify(notifier, never()).addressCreated(any()) + } + + @Test + fun `When defendants with remanded in custody are not found then no inserts occur`() { + val notification = Notification(message = MessageGenerator.COMMON_PLATFORM_EVENT_NO_REMAND) + + handler.handle(notification) + verify(telemetryService).notificationReceived(notification) + verify(personService, never()).insertPerson(any(), any()) + verify(notifier, never()).caseCreated(any()) + verify(notifier, never()).addressCreated(any()) } } diff --git a/projects/common-platform-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/messaging/NotifierTest.kt b/projects/common-platform-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/messaging/NotifierTest.kt new file mode 100644 index 0000000000..4972813672 --- /dev/null +++ b/projects/common-platform-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/messaging/NotifierTest.kt @@ -0,0 +1,44 @@ +package uk.gov.justice.digital.hmpps.messaging + +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.* +import org.springframework.boot.test.system.CapturedOutput +import org.springframework.boot.test.system.OutputCaptureExtension +import uk.gov.justice.digital.hmpps.data.generator.PersonAddressGenerator +import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator +import uk.gov.justice.digital.hmpps.message.Notification +import uk.gov.justice.digital.hmpps.publisher.NotificationPublisher + +@ExtendWith(MockitoExtension::class) +@ExtendWith(OutputCaptureExtension::class) +class NotifierTest { + @Mock + lateinit var topicPublisher: NotificationPublisher + + lateinit var notifier: Notifier + + @BeforeEach + fun setUp() { + notifier = Notifier(topicPublisher) + } + + @Test + fun `test person created notification`(output: CapturedOutput) { + doNothing().whenever(topicPublisher).publish(any>()) + notifier.caseCreated(PersonGenerator.DEFAULT) + verify(topicPublisher, times(1)).publish(any>()) + verifyNoMoreInteractions(topicPublisher) + } + + @Test + fun `test address created notification`(output: CapturedOutput) { + doNothing().whenever(topicPublisher).publish(any>()) + notifier.addressCreated(PersonAddressGenerator.MAIN_ADDRESS) + verify(topicPublisher, times(1)).publish(any>()) + verifyNoMoreInteractions(topicPublisher) + } +} \ No newline at end of file diff --git a/projects/risk-assessment-scores-to-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/Person.kt b/projects/risk-assessment-scores-to-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/Person.kt index dd50faf1db..a13e52467e 100644 --- a/projects/risk-assessment-scores-to-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/Person.kt +++ b/projects/risk-assessment-scores-to-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/Person.kt @@ -1,11 +1,6 @@ package uk.gov.justice.digital.hmpps.integrations.delius.entity -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.Id -import jakarta.persistence.JoinColumn -import jakarta.persistence.ManyToOne -import jakarta.persistence.Table +import jakarta.persistence.* import org.hibernate.annotations.Immutable import org.springframework.data.jpa.repository.JpaRepository import uk.gov.justice.digital.hmpps.exception.NotFoundException @@ -49,7 +44,13 @@ class Person( val dateOfBirth: LocalDate, @Column(updatable = false, columnDefinition = "number") - val softDeleted: Boolean = false + val softDeleted: Boolean = false, + + @Column + val exclusionMessage: String? = "You are excluded from viewing this record. Please contact a system administrator", + + @Column + val restrictionMessage: String? = "This is a restricted record. Please contact a system administrator" ) interface PersonRepository : JpaRepository {