Skip to content

Commit

Permalink
PI-1614 - handle deallocation
Browse files Browse the repository at this point in the history
  • Loading branch information
anthony-britton-moj committed Nov 2, 2023
1 parent f4da32c commit b935f9d
Show file tree
Hide file tree
Showing 16 changed files with 241 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ class DataLoader(
)

districtRepository.save(ProviderGenerator.DEFAULT_DISTRICT)
teamRepository.saveAll(PersonManagerGenerator.ALL.map { it.team } + ProviderGenerator.POM_TEAM)
val staffMap = staffRepository.saveAll(PersonManagerGenerator.ALL.map { it.staff }).associateBy { it.code }
teamRepository.saveAll(PersonManagerGenerator.ALL.map { it.team } + ProviderGenerator.POM_TEAM + ProviderGenerator.UNALLOCATED_TEAM)
val staffMap = staffRepository.saveAll(PersonManagerGenerator.ALL.map { it.staff } + ProviderGenerator.UNALLOCATED_STAFF).associateBy { it.code }
UserGenerator.DEFAULT_STAFF_USER = staffUserRepository.save(
StaffUser(
UserGenerator.DEFAULT_STAFF_USER.username,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ object ProviderGenerator {
val DEFAULT_DISTRICT = generateDistrict("N03LDU1")
val DEFAULT_TEAM = generateTeam("N03DEF", district = DEFAULT_DISTRICT)
val POM_TEAM = generateTeam("N03POM", district = DEFAULT_DISTRICT)
val UNALLOCATED_TEAM = generateTeam("N03ALL", district = DEFAULT_DISTRICT)
val DEFAULT_STAFF = generateStaff("N03DEF0", "Default", "Staff", user = UserGenerator.DEFAULT_STAFF_USER)
val UNALLOCATED_STAFF = generateStaff("N03ALLU", "Unallocated", "Staff")

fun generateProvider(providerCode: String, prisonCode: String?, id: Long = IdGenerator.getAndIncrement()) =
ProbationArea(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"Type": "Notification",
"MessageId": "63ba0b4b-340e-4853-b343-6429965a3961",
"TopicArn": "arn:aws:sns:eu-west-2:000000000000:domain-event",
"Message": "{\"eventType\":\"offender-management.allocation.changed\",\"detailUrl\":\"http://localhost:{wiremock.port}/api/pom-allocation/A0123BY/3\",\"occurredAt\":\"2023-10-10T14:25:19.102Z\",\"personReference\":{\"identifiers\":[{\"type\":\"NOMS\",\"value\":\"A0123BY\"}]}}",
"Timestamp": "2023-10-10T14:25:20.000Z",
"MessageAttributes": {
"eventType": {
"Type": "String",
"Value": "offender-management.allocation.changed"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"Type": "Notification",
"MessageId": "63ba0b4b-340e-4853-b343-6429965a3961",
"TopicArn": "arn:aws:sns:eu-west-2:000000000000:domain-event",
"Message": "{\"eventType\":\"offender-management.allocation.changed\",\"detailUrl\":\"http://localhost:{wiremock.port}/api/pom-allocation/A0123BY/0\",\"occurredAt\":\"2023-05-09T13:25:19.102Z\",\"personReference\":{\"identifiers\":[{\"type\":\"NOMS\",\"value\":\"A0123BY\"}]}}",
"Timestamp": "2023-05-09T13:25:20.000Z",
"MessageAttributes": {
"eventType": {
"Type": "String",
"Value": "offender-management.allocation.changed"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"status": "error",
"message": "Not allocated"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"status": "error",
"message": "Not ready for allocation"
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@
"bodyFileName": "create-handover-and-start-date.json"
}
},
{
"request": {
"method": "GET",
"url": "/api/pom-allocation/A0123BY/0"
},
"response": {
"headers": {
"Content-Type": "application/json"
},
"status": 404,
"bodyFileName": "not-yet-allocation.json"
}
},
{
"request": {
"method": "GET",
Expand All @@ -64,6 +77,19 @@
"status": 200,
"bodyFileName": "pom-reallocated.json"
}
},
{
"request": {
"method": "GET",
"url": "/api/pom-allocation/A0123BY/3"
},
"response": {
"headers": {
"Content-Type": "application/json"
},
"status": 404,
"bodyFileName": "deallocation.json"
}
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ import org.junit.jupiter.api.MethodOrderer
import org.junit.jupiter.api.Order
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestMethodOrder
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
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.data.generator.PersonGenerator
import uk.gov.justice.digital.hmpps.data.generator.ProviderGenerator
import uk.gov.justice.digital.hmpps.integrations.delius.contact.entity.ContactRepository
import uk.gov.justice.digital.hmpps.integrations.delius.contact.entity.ContactType
import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.PrisonManagerRepository
Expand All @@ -27,7 +30,6 @@ import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.StaffRep
import uk.gov.justice.digital.hmpps.messaging.HmppsChannelManager
import uk.gov.justice.digital.hmpps.resourceloader.ResourceLoader.notification
import uk.gov.justice.digital.hmpps.telemetry.TelemetryService
import java.time.Duration
import java.time.ZonedDateTime
import kotlin.jvm.optionals.getOrNull

Expand All @@ -49,14 +51,34 @@ internal class AllocationMessagingIntegrationTest {
@SpyBean
lateinit var staffRepository: StaffRepository

@Autowired
@SpyBean
lateinit var prisonManagerRepository: PrisonManagerRepository

@Autowired
lateinit var contactRepository: ContactRepository

@Order(1)
@Test
fun `no change if not yet ready to allocate`() {
val notification = prepNotification(
notification("not-yet-allocation"),
wireMockServer.port()
)

channelManager.getChannel(queueName).publishAndWait(notification)

verify(prisonManagerRepository, never()).save(any())
verify(telemetryService).trackEvent(
"NotReadyToAllocate",
mapOf(
"nomsId" to "A0123BY",
"allocationDate" to "09/05/2023 14:25:19"
)
)
}

@Order(2)
@Test
fun `allocate first POM successfully`() {
val notification = prepNotification(
notification("new-allocation"),
Expand All @@ -80,16 +102,16 @@ internal class AllocationMessagingIntegrationTest {
assertThat(contacts.map { it.type.code }, hasItems(ContactType.Code.POM_AUTO_ALLOCATION.value))

verify(telemetryService).trackEvent(
"POM Allocated",
"PomAllocated",
mapOf(
"prisonId" to "SWI",
"nomsId" to "A0123BY",
"allocationDate" to "2023-05-09"
"allocationDate" to "09/05/2023 14:25:19"
)
)
}

@Order(2)
@Order(3)
@Test
fun `reallocate POM successfully`() {
// add RO to existing pom to test RO behaviour
Expand All @@ -104,7 +126,7 @@ internal class AllocationMessagingIntegrationTest {
wireMockServer.port()
)

channelManager.getChannel(queueName).publishAndWait(notification, Duration.ofSeconds(180))
channelManager.getChannel(queueName).publishAndWait(notification)

val captor = argumentCaptor<Staff>()
verify(staffRepository).save(captor.capture())
Expand Down Expand Up @@ -133,11 +155,53 @@ internal class AllocationMessagingIntegrationTest {
)

verify(telemetryService).trackEvent(
"POM Allocated",
"PomAllocated",
mapOf(
"prisonId" to "SWI",
"nomsId" to "A0123BY",
"allocationDate" to "2023-10-09"
"allocationDate" to "09/10/2023 14:25:19"
)
)
}

@Order(4)
@Test
fun `deallocate POM successfully`() {
val existingPom = prisonManagerRepository.findActiveManagerAtDate(PersonGenerator.DEFAULT.id, ZonedDateTime.now())!!

val notification = prepNotification(
notification("deallocation"),
wireMockServer.port()
)

channelManager.getChannel(queueName).publishAndWait(notification)

verify(staffRepository, never()).save(any())

val prisonManager = prisonManagerRepository.findActiveManagerAtDate(PersonGenerator.DEFAULT.id, ZonedDateTime.now())
assertThat(prisonManager?.allocationReason?.code, equalTo("AUT"))
assertThat(prisonManager?.staff?.code, equalTo(ProviderGenerator.UNALLOCATED_STAFF.code))
assertThat(prisonManager?.staff?.forename, equalTo(ProviderGenerator.UNALLOCATED_STAFF.forename))
assertThat(prisonManager?.staff?.surname, equalTo(ProviderGenerator.UNALLOCATED_STAFF.surname))

val previousPom = prisonManagerRepository.findById(existingPom.id).getOrNull()
assertNotNull(previousPom?.endDate)
assertNotNull(previousPom?.responsibleOfficer?.endDate)

val contacts = contactRepository.findAll().filter { it.personId == PersonGenerator.DEFAULT.id }
assertThat(
contacts.map { it.type.code },
hasItems(
ContactType.Code.POM_AUTO_ALLOCATION.value,
ContactType.Code.RESPONSIBLE_OFFICER_CHANGE.value
)
)

verify(telemetryService).trackEvent(
"PomDeallocated",
mapOf(
"nomsId" to "A0123BY",
"allocationDate" to "10/10/2023 15:25:19"
)
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package uk.gov.justice.digital.hmpps.exception

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import feign.FeignException.errorStatus
import feign.Response
import feign.codec.ErrorDecoder
import org.springframework.stereotype.Component
import java.lang.Exception

@Component
class Feign404Decoder(private val objectMapper: ObjectMapper) : ErrorDecoder {
override fun decode(methodKey: String?, response: Response): Exception? {
return response.body().asInputStream()?.use { ins ->
objectMapper.readValue<ErrorResponse>(ins).message?.let {
NotAllocatedException(
when (it) {
"Not allocated" -> NotAllocatedException.Reason.DEALLOCATED
else -> NotAllocatedException.Reason.PRE_ALLOCATION
}
)
} ?: errorStatus(methodKey, response)
}
}
}

data class ErrorResponse(val message: String?)
class NotAllocatedException(val reason: Reason) : Exception("POM Not Allocated: $reason") {
enum class Reason {
DEALLOCATED, PRE_ALLOCATION
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ class PrisonManager(
@Column(nullable = false)
var lastUpdatedDatetime: ZonedDateTime = ZonedDateTime.now()

fun isUnallocated() = staff.code.endsWith("U")

enum class AllocationReasonCode(val value: String, val ctc: ContactType.Code) {
AUTO("AUT", ContactType.Code.POM_AUTO_ALLOCATION),
INTERNAL("INA", ContactType.Code.POM_INTERNAL_ALLOCATION),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package uk.gov.justice.digital.hmpps.integrations.delius.provider.entity

import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import uk.gov.justice.digital.hmpps.exception.NotFoundException

interface StaffRepository : JpaRepository<Staff, Long> {
fun findTopByProbationAreaIdAndForenameIgnoreCaseAndSurnameIgnoreCase(
Expand All @@ -20,4 +21,9 @@ interface StaffRepository : JpaRepository<Staff, Long> {
nativeQuery = true
)
fun getLatestStaffReference(regex: String): String?

fun findByCode(code: String): Staff?
}

fun StaffRepository.getByCode(code: String) =
findByCode(code) ?: throw NotFoundException("Staff", "code", code)
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class Team(
) {
companion object {
val POM_SUFFIX = "POM"
val UNALLOCATED_SUFFIX = "ALL"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,16 @@ data class Handover(
@JsonAlias("handoverStartDate") val startDate: LocalDate?
)

sealed interface AllocationResponse

data class PomAllocation(
val manager: Name,
val prison: Prison
)
) : AllocationResponse

data object PomDeallocated : AllocationResponse

data object PomNotAllocated : AllocationResponse

data class Prison(
val code: String
Expand Down
Loading

0 comments on commit b935f9d

Please sign in to comment.