Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement physical attributes NOMIS to DPS synchronisation #666

Merged
merged 3 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ parameters:
default: dps-releases
test-executor-packages:
type: string
default: "activities adjudications alerts appointments courtsentencing csip incidents locations sentencing visits"
default: "activities adjudications alerts appointments courtsentencing csip incidents locations prisonperson sentencing visits"
test-executors-count:
type: integer
default: 11
default: 12

jobs:
validate:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import java.util.concurrent.CompletableFuture

@Service
class PrisonPersonEventListener(
private val prisonPersonService: PrisonPersonSynchronisationService,
private val objectMapper: ObjectMapper,
private val eventFeatureSwitch: EventFeatureSwitch,
) {
Expand All @@ -36,7 +37,7 @@ class PrisonPersonEventListener(
val eventType = sqsMessage.MessageAttributes!!.eventType.Value
if (eventFeatureSwitch.isEnabled(eventType)) {
when (eventType) {
"OFFENDER_PHYSICAL_ATTRIBUTES-CHANGED" -> log.info("Received physical attributes changed event")
"OFFENDER_PHYSICAL_ATTRIBUTES-CHANGED" -> prisonPersonService.physicalAttributesChanged(sqsMessage.Message.fromJson())
else -> log.info("Received a message I wasn't expecting {}", eventType)
}
} else {
Expand All @@ -56,3 +57,8 @@ private fun asCompletableFuture(
): CompletableFuture<Void> = CoroutineScope(Dispatchers.Default).future {
process()
}.thenAccept { }

data class PhysicalAttributesChangedEvent(
val offenderIdDisplay: String,
val bookingId: Long,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package uk.gov.justice.digital.hmpps.prisonerfromnomismigration.prisonperson

import com.microsoft.applicationinsights.TelemetryClient
import org.springframework.stereotype.Service
import uk.gov.justice.digital.hmpps.prisonerfromnomismigration.helpers.trackEvent
import uk.gov.justice.digital.hmpps.prisonerfromnomismigration.nomissync.model.BookingPhysicalAttributesResponse
import uk.gov.justice.digital.hmpps.prisonerfromnomismigration.nomissync.model.PhysicalAttributesResponse
import uk.gov.justice.digital.hmpps.prisonerfromnomismigration.nomissync.model.PrisonerPhysicalAttributesResponse
import java.time.LocalDateTime

@Service
class PrisonPersonSynchronisationService(
private val nomisApiService: PrisonPersonNomisApiService,
private val dpsApiService: PrisonPersonDpsApiService,
private val telemetryClient: TelemetryClient,
) {

private val synchronisationUser = "DPS_SYNCHRONISATION"

suspend fun physicalAttributesChanged(event: PhysicalAttributesChangedEvent) {
val offenderNo = event.offenderIdDisplay
val bookingId = event.bookingId
val telemetry = mutableMapOf(
"offenderNo" to offenderNo,
"bookingId" to bookingId.toString(),
)

val dpsResponse = try {
val nomisResponse = nomisApiService.getPhysicalAttributes(offenderNo)

val booking = nomisResponse.bookings.find { it.bookingId == bookingId }
?: throw PhysicalAttributesChangedException("Booking with physical attributes not found for bookingId=$bookingId")
val physicalAttributes = booking.findLastModifiedPhysicalAttributes()

getIgnoreReason(nomisResponse, physicalAttributes)?.let { ignoreReason ->
telemetry["reason"] = ignoreReason
telemetryClient.trackEvent("physical-attributes-synchronisation-ignored", telemetry)
return
}

dpsApiService.syncPhysicalAttributes(
offenderNo,
physicalAttributes.heightCentimetres,
physicalAttributes.weightKilograms,
booking.startDateTime.toLocalDateTime(),
booking.endDateTime?.toLocalDateTime(),
(physicalAttributes.modifiedDateTime ?: physicalAttributes.createDateTime).toLocalDateTime(),
physicalAttributes.createdBy,
)
} catch (e: Exception) {
telemetry["error"] = e.message.toString()
telemetryClient.trackEvent("physical-attributes-synchronisation-error", telemetry)
throw e
}

telemetryClient.trackEvent(
"physical-attributes-synchronisation-updated",
mapOf(
"offenderNo" to offenderNo,
"bookingId" to bookingId.toString(),
// TODO SDIT-1816 we should add attributeSeq in the telemetry so we know which one we've synchronised - need to return it from the API
"physicalAttributesHistoryId" to dpsResponse.physicalAttributesHistoryId.toString(),
),
)
}

private fun getIgnoreReason(
nomisResponse: PrisonerPhysicalAttributesResponse,
physicalAttributes: PhysicalAttributesResponse,
): String? =
if (nomisResponse.isNewAndEmpty()) {
"New physical attributes are empty"
} else if (physicalAttributes.updatedBySync()) {
"The physical attributes were created by $synchronisationUser"
} else {
null
}

private fun PrisonerPhysicalAttributesResponse.isNew() =
bookings.size == 1 &&
bookings.first().physicalAttributes.size == 1 &&
bookings.first().physicalAttributes.first().modifiedDateTime == null

private fun PrisonerPhysicalAttributesResponse.isNewAndEmpty() =
isNew() &&
bookings.first().physicalAttributes.first().heightCentimetres == null &&
bookings.first().physicalAttributes.first().weightKilograms == null

private fun PhysicalAttributesResponse.updatedBySync() =
if (modifiedDateTime != null) {
modifiedBy == synchronisationUser
} else {
createdBy == synchronisationUser
}

private fun BookingPhysicalAttributesResponse.findLastModifiedPhysicalAttributes() =
physicalAttributes
.maxBy {
if (it.modifiedDateTime != null) {
it.modifiedDateTime.toLocalDateTime()
} else {
it.createDateTime.toLocalDateTime()
}
}

private fun String.toLocalDateTime() = LocalDateTime.parse(this)
}

class PhysicalAttributesChangedException(message: String) : IllegalArgumentException(message)
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ class PrisonPersonNomisApiMockServer(private val objectMapper: ObjectMapper) {
),
),
),
) =
) = stubGetPhysicalAttributes(response)

fun stubGetPhysicalAttributes(response: PrisonerPhysicalAttributesResponse) =
nomisApi.stubFor(
get(urlEqualTo("/prisoners/$offenderNo/physical-attributes")).willReturn(
get(urlEqualTo("/prisoners/${response.offenderNo}/physical-attributes")).willReturn(
aResponse()
.withHeader("Content-Type", "application/json")
.withStatus(HttpStatus.OK.value())
Expand Down
Loading