diff --git a/libs/commons/src/main/kotlin/uk/gov/justice/digital/hmpps/exception/InvalidRequestException.kt b/libs/commons/src/main/kotlin/uk/gov/justice/digital/hmpps/exception/InvalidRequestException.kt new file mode 100644 index 0000000000..22d12b43a2 --- /dev/null +++ b/libs/commons/src/main/kotlin/uk/gov/justice/digital/hmpps/exception/InvalidRequestException.kt @@ -0,0 +1,8 @@ +package uk.gov.justice.digital.hmpps.exception + +open class InvalidRequestException(message: String) : RuntimeException(message) { + constructor( + fieldName: String, + value: Any + ) : this("Invalid $fieldName of $value sent in payload") +} diff --git a/libs/oauth-server/src/main/kotlin/uk/gov/justice/digital/hmpps/advice/ControllerAdvice.kt b/libs/oauth-server/src/main/kotlin/uk/gov/justice/digital/hmpps/advice/ControllerAdvice.kt index 3168ca3085..6d2f14b21d 100644 --- a/libs/oauth-server/src/main/kotlin/uk/gov/justice/digital/hmpps/advice/ControllerAdvice.kt +++ b/libs/oauth-server/src/main/kotlin/uk/gov/justice/digital/hmpps/advice/ControllerAdvice.kt @@ -1,13 +1,12 @@ package uk.gov.justice.digital.hmpps.advice -import org.springframework.http.HttpStatus.BAD_REQUEST -import org.springframework.http.HttpStatus.CONFLICT -import org.springframework.http.HttpStatus.NOT_FOUND +import org.springframework.http.HttpStatus.* import org.springframework.http.ResponseEntity import org.springframework.web.bind.MethodArgumentNotValidException import org.springframework.web.bind.annotation.ExceptionHandler import org.springframework.web.bind.annotation.RestControllerAdvice import uk.gov.justice.digital.hmpps.exception.ConflictException +import uk.gov.justice.digital.hmpps.exception.InvalidRequestException import uk.gov.justice.digital.hmpps.exception.NotFoundException @RestControllerAdvice(basePackages = ["uk.gov.justice.digital.hmpps"]) @@ -32,4 +31,9 @@ class ControllerAdvice { fields = e.bindingResult.fieldErrors.map { FieldError(it.code, it.defaultMessage, it.field) } ) ) + + @ExceptionHandler(InvalidRequestException::class) + fun handleInvalidRequest(e: InvalidRequestException) = ResponseEntity + .status(BAD_REQUEST) + .body(ErrorResponse(status = BAD_REQUEST.value(), message = e.message)) } diff --git a/libs/prison-staff/build.gradle.kts b/libs/prison-staff/build.gradle.kts new file mode 100644 index 0000000000..5d5a687e52 --- /dev/null +++ b/libs/prison-staff/build.gradle.kts @@ -0,0 +1,21 @@ +import uk.gov.justice.digital.hmpps.extensions.ClassPathExtension + +dependencies { + compileOnly("org.springframework.boot:spring-boot-starter-data-jpa") + implementation(project(":libs:commons")) + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + implementation("org.springframework.boot:spring-boot-starter-validation") + testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("org.springframework.boot:spring-boot-starter-data-jpa") + testImplementation(libs.bundles.mockito) +} + +configure { + jacocoExclusions = listOf( + "**/exception/**", + "**/config/**", + "**/entity**", + "**/logging/**" + ) +} diff --git a/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/PrisonStaff.kt b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/PrisonStaff.kt new file mode 100644 index 0000000000..b830c53414 --- /dev/null +++ b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/PrisonStaff.kt @@ -0,0 +1,61 @@ +package uk.gov.justice.digital.hmpps.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 java.time.ZonedDateTime + +@EntityListeners(AuditingEntityListener::class) +@Entity(name = "PrisonStaff") +@Table(name = "staff") +class PrisonStaff( + + @Id + @Column(name = "staff_id") + @SequenceGenerator(name = "staff_id_seq", sequenceName = "staff_id_seq", allocationSize = 1) + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "staff_id_seq") + val id: Long = 0, + + @Column(name = "forename") + val forename: String, + + @Column(name = "surname") + val surname: String, + + @Column(name = "officer_code", columnDefinition = "char(7)") + val code: String, + + @Column(name = "probation_area_id") + val probationAreaId: Long, + + @Column(name = "start_date", updatable = false) + val startDate: ZonedDateTime = ZonedDateTime.now(), + + @CreatedDate + @Column(name = "created_datetime", updatable = false) + val createdDateTime: ZonedDateTime = ZonedDateTime.now(), + + @LastModifiedDate + @Column(name = "last_updated_datetime") + val lastModifiedDate: ZonedDateTime = ZonedDateTime.now(), + + @Column(name = "private", columnDefinition = "NUMBER", nullable = false) + var privateStaff: Boolean = false, + + @CreatedBy + @Column(name = "created_by_user_id", updatable = false) + var createdByUserId: Long = 0, + + @LastModifiedBy + @Column(name = "last_updated_user_id") + var lastModifiedUserId: Long = 0, + + @Version + @Column(name = "row_version") + val version: Long = 0 +) { + fun isUnallocated() = code.endsWith("U") +} diff --git a/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/PrisonStaffTeam.kt b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/PrisonStaffTeam.kt new file mode 100644 index 0000000000..e48f508225 --- /dev/null +++ b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/PrisonStaffTeam.kt @@ -0,0 +1,47 @@ +package uk.gov.justice.digital.hmpps.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 java.io.Serializable +import java.time.ZonedDateTime + +@EntityListeners(AuditingEntityListener::class) +@Entity +@Table(name = "staff_team") +@IdClass(StaffTeamId::class) +class PrisonStaffTeam( + + @Id + @Column(name = "staff_id") + val staffId: Long, + + @Id + @Column(name = "team_id") + val teamId: Long, + + @CreatedBy + @Column(name = "created_by_user_id", updatable = false) + var createdByUserId: Long = 0, + + @LastModifiedBy + @Column(name = "last_updated_user_id") + val lastModifiedUserId: Long = 0, + + @CreatedDate + @Column(name = "created_datetime", updatable = false) + val createdDateTime: ZonedDateTime = ZonedDateTime.now(), + + @LastModifiedDate + @Column(name = "last_updated_datetime") + val lastModifiedDate: ZonedDateTime = ZonedDateTime.now(), + + @Version + @Column(name = "row_version") + val version: Long = 0 +) + +data class StaffTeamId(val staffId: Long = 0, val teamId: Long = 0) : Serializable diff --git a/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/PrisonTeam.kt b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/PrisonTeam.kt new file mode 100644 index 0000000000..f54c9c1ba2 --- /dev/null +++ b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/PrisonTeam.kt @@ -0,0 +1,21 @@ +package uk.gov.justice.digital.hmpps.entity + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.Table +import org.hibernate.annotations.Immutable + +@Immutable +@Entity(name = "PrisonTeam") +@Table(name = "team") +class PrisonTeam( + + @Id + @Column(name = "team_id") + val id: Long, + + @Column(name = "code", columnDefinition = "char(6)") + val code: String + +) diff --git a/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Provider.kt b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Provider.kt new file mode 100644 index 0000000000..c5d7dc0b46 --- /dev/null +++ b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Provider.kt @@ -0,0 +1,37 @@ +package uk.gov.justice.digital.hmpps.entity + +import jakarta.persistence.* +import org.hibernate.annotations.Immutable + +@Immutable +@Entity(name = "Provider") +@Table(name = "probation_area") +class Provider( + @Id + @Column(name = "probation_area_id") + val id: Long, + + @Column(name = "code", columnDefinition = "char(3)") + val code: String, + + @OneToOne + @JoinColumn( + name = "institution_id", + referencedColumnName = "institution_id", + updatable = false + ) + val institution: Prison? = null +) + +@Immutable +@Entity(name = "Prison") +@Table(name = "r_institution") +class Prison( + @Id + @Column(name = "institution_id") + val id: Long, + + @Column(name = "nomis_cde_code") + val nomisCode: String + +) diff --git a/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/exceptions/InvalidEstablishmentCodeException.kt b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/exceptions/InvalidEstablishmentCodeException.kt new file mode 100644 index 0000000000..c24d296b29 --- /dev/null +++ b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/exceptions/InvalidEstablishmentCodeException.kt @@ -0,0 +1,4 @@ +package uk.gov.justice.digital.hmpps.exceptions + +class InvalidEstablishmentCodeException(establishmentCode: String) : + RuntimeException("Invalid establishment: $establishmentCode") diff --git a/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/exceptions/StaffCodeExhaustedException.kt b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/exceptions/StaffCodeExhaustedException.kt new file mode 100644 index 0000000000..8f53030ecd --- /dev/null +++ b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/exceptions/StaffCodeExhaustedException.kt @@ -0,0 +1,3 @@ +package uk.gov.justice.digital.hmpps.exceptions + +class StaffCodeExhaustedException(code: String) : RuntimeException("Officer codes exhausted for: $code") diff --git a/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/model/StaffName.kt b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/model/StaffName.kt new file mode 100644 index 0000000000..e9673aab05 --- /dev/null +++ b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/model/StaffName.kt @@ -0,0 +1,5 @@ +package uk.gov.justice.digital.hmpps.model + +import jakarta.validation.constraints.NotBlank + +data class StaffName(@NotBlank val forename: String, @NotBlank val surname: String) \ No newline at end of file diff --git a/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/PrisonProbationAreaRepository.kt b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/PrisonProbationAreaRepository.kt new file mode 100644 index 0000000000..907c33b919 --- /dev/null +++ b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/PrisonProbationAreaRepository.kt @@ -0,0 +1,8 @@ +package uk.gov.justice.digital.hmpps.repository + +import org.springframework.data.jpa.repository.JpaRepository +import uk.gov.justice.digital.hmpps.entity.Provider + +interface PrisonProbationAreaRepository : JpaRepository { + fun findByInstitutionNomisCode(nomisCode: String): Provider? +} diff --git a/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/PrisonStaffRepository.kt b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/PrisonStaffRepository.kt new file mode 100644 index 0000000000..54ed1d9710 --- /dev/null +++ b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/PrisonStaffRepository.kt @@ -0,0 +1,30 @@ +package uk.gov.justice.digital.hmpps.repository + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import uk.gov.justice.digital.hmpps.entity.PrisonStaff +import uk.gov.justice.digital.hmpps.exception.NotFoundException + +interface PrisonStaffRepository : JpaRepository { + fun findTopByProbationAreaIdAndForenameIgnoreCaseAndSurnameIgnoreCase( + probationAreaId: Long, + forename: String, + surname: String + ): PrisonStaff? + + @Query( + """ + select officer_code from staff + where regexp_like(officer_code, ?1, 'i') + order by officer_code desc + fetch next 1 rows only + """, + nativeQuery = true + ) + fun getLatestStaffReference(regex: String): String? + + fun findByCode(code: String): PrisonStaff? +} + +fun PrisonStaffRepository.getByCode(code: String) = + findByCode(code) ?: throw NotFoundException("Staff", "code", code) diff --git a/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/PrisonStaffTeamRepository.kt b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/PrisonStaffTeamRepository.kt new file mode 100644 index 0000000000..24db941298 --- /dev/null +++ b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/PrisonStaffTeamRepository.kt @@ -0,0 +1,7 @@ +package uk.gov.justice.digital.hmpps.repository + +import org.springframework.data.jpa.repository.JpaRepository +import uk.gov.justice.digital.hmpps.entity.PrisonStaffTeam +import uk.gov.justice.digital.hmpps.entity.StaffTeamId + +interface PrisonStaffTeamRepository : JpaRepository diff --git a/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/PrisonTeamRepository.kt b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/PrisonTeamRepository.kt new file mode 100644 index 0000000000..e0485446c4 --- /dev/null +++ b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/repository/PrisonTeamRepository.kt @@ -0,0 +1,8 @@ +package uk.gov.justice.digital.hmpps.repository + +import org.springframework.data.jpa.repository.JpaRepository +import uk.gov.justice.digital.hmpps.entity.PrisonTeam + +interface PrisonTeamRepository : JpaRepository { + fun findByCode(code: String): PrisonTeam? +} diff --git a/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/AssignmentService.kt b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/service/AssignmentService.kt similarity index 51% rename from projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/AssignmentService.kt rename to libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/service/AssignmentService.kt index 832a804821..8d0314c825 100644 --- a/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/AssignmentService.kt +++ b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/service/AssignmentService.kt @@ -1,20 +1,19 @@ -package uk.gov.justice.digital.hmpps.integrations.delius.service +package uk.gov.justice.digital.hmpps.service import org.springframework.stereotype.Service +import uk.gov.justice.digital.hmpps.entity.PrisonStaff import uk.gov.justice.digital.hmpps.exception.NotFoundException import uk.gov.justice.digital.hmpps.exceptions.InvalidEstablishmentCodeException -import uk.gov.justice.digital.hmpps.integrations.delius.entity.ProbationArea -import uk.gov.justice.digital.hmpps.integrations.delius.entity.Staff -import uk.gov.justice.digital.hmpps.integrations.delius.entity.Team -import uk.gov.justice.digital.hmpps.integrations.delius.model.StaffName -import uk.gov.justice.digital.hmpps.integrations.delius.repository.ProbationAreaRepository -import uk.gov.justice.digital.hmpps.integrations.delius.repository.TeamRepository +import uk.gov.justice.digital.hmpps.model.StaffName +import uk.gov.justice.digital.hmpps.repository.PrisonProbationAreaRepository +import uk.gov.justice.digital.hmpps.repository.PrisonTeamRepository import uk.gov.justice.digital.hmpps.retry.retry +import java.time.ZonedDateTime @Service class AssignmentService( - private val probationAreaRepository: ProbationAreaRepository, - private val teamRepository: TeamRepository, + private val probationAreaRepository: PrisonProbationAreaRepository, + private val teamRepository: PrisonTeamRepository, private val staffService: StaffService ) { @@ -27,14 +26,20 @@ class AssignmentService( ) val team = teamRepository.findByCode("${pa.code}CSN") ?: throw NotFoundException("Team", "code", "${pa.code}CSN") - val staff = getStaff(pa, team, staffName) + val staff = getStaff(pa.id, pa.code, team.id, staffName) return Triple(pa.id, team.id, staff.id) } - private fun getStaff(probationArea: ProbationArea, team: Team, staffName: StaffName): Staff { - val findStaff = { staffService.findStaff(probationArea.id, staffName) } + fun getStaff( + probationAreaId: Long, + probationAreaCode: String, + teamId: Long, + staffName: StaffName, + allocationDate: ZonedDateTime? = null + ): PrisonStaff { + val findStaff = { staffService.findStaff(probationAreaId, staffName) } return retry(3) { - findStaff() ?: staffService.create(probationArea, team, staffName) + findStaff() ?: staffService.create(probationAreaId, probationAreaCode, teamId, staffName, allocationDate) } } } diff --git a/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/OfficerCodeGenerator.kt b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/service/OfficerCodeGenerator.kt similarity index 78% rename from projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/OfficerCodeGenerator.kt rename to libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/service/OfficerCodeGenerator.kt index cd742ba5ed..8706bd82a6 100644 --- a/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/OfficerCodeGenerator.kt +++ b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/service/OfficerCodeGenerator.kt @@ -1,11 +1,11 @@ -package uk.gov.justice.digital.hmpps.integrations.delius.service +package uk.gov.justice.digital.hmpps.service import org.springframework.stereotype.Component import uk.gov.justice.digital.hmpps.exceptions.StaffCodeExhaustedException -import uk.gov.justice.digital.hmpps.integrations.delius.repository.StaffRepository +import uk.gov.justice.digital.hmpps.repository.PrisonStaffRepository @Component -class OfficerCodeGenerator(private val staffRepository: StaffRepository) { +class OfficerCodeGenerator(private val staffRepository: PrisonStaffRepository) { private val alphabet = ('A'..'Z').toList() fun generateFor(probationAreaCode: String, index: Int = 0): String { diff --git a/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/service/StaffService.kt b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/service/StaffService.kt new file mode 100644 index 0000000000..410d033647 --- /dev/null +++ b/libs/prison-staff/src/main/kotlin/uk/gov/justice/digital/hmpps/service/StaffService.kt @@ -0,0 +1,58 @@ +package uk.gov.justice.digital.hmpps.service + +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Propagation +import org.springframework.transaction.annotation.Transactional +import org.springframework.util.ConcurrentReferenceHashMap +import uk.gov.justice.digital.hmpps.entity.PrisonStaff +import uk.gov.justice.digital.hmpps.entity.PrisonStaffTeam +import uk.gov.justice.digital.hmpps.model.StaffName +import uk.gov.justice.digital.hmpps.repository.PrisonStaffRepository +import uk.gov.justice.digital.hmpps.repository.PrisonStaffTeamRepository +import uk.gov.justice.digital.hmpps.repository.getByCode +import java.time.ZonedDateTime + +@Service +class StaffService( + private val staffRepository: PrisonStaffRepository, + private val officerCodeGenerator: OfficerCodeGenerator, + private val staffTeamRepository: PrisonStaffTeamRepository +) { + + companion object { + private val mutexMap = ConcurrentReferenceHashMap() + private fun getMutex(key: Long) { + mutexMap.compute(key) { _, v -> v ?: Any() } + } + } + + @Transactional(propagation = Propagation.REQUIRES_NEW) + fun create( + paId: Long, + paCode: String, + teamId: Long, + staffName: StaffName, + startDate: ZonedDateTime? = null + ): PrisonStaff = synchronized(getMutex(paId)) { + val staff = staffRepository.save( + PrisonStaff( + forename = staffName.forename, + surname = staffName.surname, + probationAreaId = paId, + code = officerCodeGenerator.generateFor(paCode), + startDate = startDate ?: ZonedDateTime.now() + ) + ) + staffTeamRepository.save(PrisonStaffTeam(staff.id, teamId)) + return staff + } + + fun findStaff(probationAreaId: Long, staffName: StaffName) = + staffRepository.findTopByProbationAreaIdAndForenameIgnoreCaseAndSurnameIgnoreCase( + probationAreaId, + staffName.forename, + staffName.surname + ) + + fun getByCode(code: String) = staffRepository.getByCode(code) +} diff --git a/projects/prison-case-notes-to-probation/src/test/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/AssignmentServiceTest.kt b/libs/prison-staff/src/test/kotlin/uk/gov/justice/digital/hmpps/service/AssignmentServiceTest.kt similarity index 66% rename from projects/prison-case-notes-to-probation/src/test/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/AssignmentServiceTest.kt rename to libs/prison-staff/src/test/kotlin/uk/gov/justice/digital/hmpps/service/AssignmentServiceTest.kt index d56e2910ea..03dec9caec 100644 --- a/projects/prison-case-notes-to-probation/src/test/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/AssignmentServiceTest.kt +++ b/libs/prison-staff/src/test/kotlin/uk/gov/justice/digital/hmpps/service/AssignmentServiceTest.kt @@ -1,4 +1,4 @@ -package uk.gov.justice.digital.hmpps.integrations.delius.service +package uk.gov.justice.digital.hmpps.service import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.equalTo @@ -11,24 +11,24 @@ import org.mockito.junit.jupiter.MockitoExtension import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever -import uk.gov.justice.digital.hmpps.data.generator.PrisonCaseNoteGenerator -import uk.gov.justice.digital.hmpps.data.generator.ProbationAreaGenerator -import uk.gov.justice.digital.hmpps.data.generator.StaffGenerator -import uk.gov.justice.digital.hmpps.data.generator.TeamGenerator +import uk.gov.justice.digital.hmpps.entity.Prison +import uk.gov.justice.digital.hmpps.entity.PrisonStaff +import uk.gov.justice.digital.hmpps.entity.PrisonTeam +import uk.gov.justice.digital.hmpps.entity.Provider import uk.gov.justice.digital.hmpps.exception.NotFoundException import uk.gov.justice.digital.hmpps.exceptions.InvalidEstablishmentCodeException -import uk.gov.justice.digital.hmpps.integrations.delius.model.StaffName -import uk.gov.justice.digital.hmpps.integrations.delius.repository.ProbationAreaRepository -import uk.gov.justice.digital.hmpps.integrations.delius.repository.TeamRepository +import uk.gov.justice.digital.hmpps.model.StaffName +import uk.gov.justice.digital.hmpps.repository.PrisonProbationAreaRepository +import uk.gov.justice.digital.hmpps.repository.PrisonTeamRepository @ExtendWith(MockitoExtension::class) class AssignmentServiceTest { @Mock - lateinit var probationAreaRepository: ProbationAreaRepository + lateinit var probationAreaRepository: PrisonProbationAreaRepository @Mock - lateinit var teamRepository: TeamRepository + lateinit var teamRepository: PrisonTeamRepository @Mock lateinit var staffService: StaffService @@ -47,25 +47,25 @@ class AssignmentServiceTest { @Test fun `unable to find probation area`() { - whenever(probationAreaRepository.findByInstitutionNomisCode(PrisonCaseNoteGenerator.EXISTING_IN_BOTH.locationId)).thenReturn( + whenever(probationAreaRepository.findByInstitutionNomisCode("LEI")).thenReturn( null ) val ex = assertThrows { - assignmentService.findAssignment(PrisonCaseNoteGenerator.EXISTING_IN_BOTH.locationId, staffName) + assignmentService.findAssignment("LEI", staffName) } assertThat( ex.message, - equalTo("Probation Area not found for NOMIS institution: ${PrisonCaseNoteGenerator.EXISTING_IN_BOTH.locationId}") + equalTo("Probation Area not found for NOMIS institution: LEI") ) } @Test fun `unable to find team`() { - whenever(probationAreaRepository.findByInstitutionNomisCode(PrisonCaseNoteGenerator.EXISTING_IN_BOTH.locationId)) + whenever(probationAreaRepository.findByInstitutionNomisCode("LEI")) .thenReturn(ProbationAreaGenerator.DEFAULT) whenever(teamRepository.findByCode(TeamGenerator.DEFAULT.code)).thenReturn(null) val ex = assertThrows { - assignmentService.findAssignment(PrisonCaseNoteGenerator.EXISTING_IN_BOTH.locationId, staffName) + assignmentService.findAssignment("LEI", staffName) } assertThat( ex.message, @@ -79,12 +79,12 @@ class AssignmentServiceTest { val team = TeamGenerator.DEFAULT val staff = StaffGenerator.DEFAULT - whenever(probationAreaRepository.findByInstitutionNomisCode(PrisonCaseNoteGenerator.NEW_TO_DELIUS.locationId)) + whenever(probationAreaRepository.findByInstitutionNomisCode("LEI")) .thenReturn(probationArea) whenever(teamRepository.findByCode(team.code)).thenReturn(team) whenever(staffService.findStaff(probationArea.id, staffName)).thenReturn(staff) - val res = assignmentService.findAssignment(PrisonCaseNoteGenerator.NEW_TO_DELIUS.locationId, staffName) + val res = assignmentService.findAssignment("LEI", staffName) verify(staffService).findStaff(probationArea.id, staffName) assertThat(res.first, equalTo(probationArea.id)) @@ -97,18 +97,23 @@ class AssignmentServiceTest { val probationArea = ProbationAreaGenerator.DEFAULT val team = TeamGenerator.DEFAULT val newStaffCode = "C12A001" - val newStaff = StaffGenerator.generate(newStaffCode, staffName.forename, staffName.surname) + val newStaff = PrisonStaff( + code = newStaffCode, + forename = staffName.forename, + surname = staffName.surname, + probationAreaId = probationArea.id + ) - whenever(probationAreaRepository.findByInstitutionNomisCode(PrisonCaseNoteGenerator.NEW_TO_DELIUS.locationId)) + whenever(probationAreaRepository.findByInstitutionNomisCode("LEI")) .thenReturn(probationArea) whenever(teamRepository.findByCode(team.code)).thenReturn(team) whenever(staffService.findStaff(probationArea.id, staffName)).thenReturn(null) - whenever(staffService.create(probationArea, team, staffName)) + whenever(staffService.create(probationArea.id, probationArea.code, team.id, staffName, null)) .thenReturn(newStaff) - val res = assignmentService.findAssignment(PrisonCaseNoteGenerator.NEW_TO_DELIUS.locationId, staffName) + val res = assignmentService.findAssignment("LEI", staffName) - verify(staffService).create(probationArea, team, staffName) + verify(staffService).create(probationArea.id, probationArea.code, team.id, staffName) assertThat(res.first, equalTo(probationArea.id)) assertThat(res.second, equalTo(team.id)) assertThat(res.third, equalTo(newStaff.id)) @@ -119,20 +124,25 @@ class AssignmentServiceTest { val probationArea = ProbationAreaGenerator.DEFAULT val team = TeamGenerator.DEFAULT val newStaffCode = "C12A001" - val newStaff = StaffGenerator.generate(newStaffCode, staffName.forename, staffName.surname) + val newStaff = PrisonStaff( + code = newStaffCode, + forename = staffName.forename, + surname = staffName.surname, + probationAreaId = probationArea.id + ) - whenever(probationAreaRepository.findByInstitutionNomisCode(PrisonCaseNoteGenerator.NEW_TO_DELIUS.locationId)) + whenever(probationAreaRepository.findByInstitutionNomisCode("LEI")) .thenReturn(probationArea) whenever(teamRepository.findByCode(team.code)).thenReturn(team) whenever(staffService.findStaff(probationArea.id, staffName)) .thenReturn(null) .thenReturn(newStaff) - whenever(staffService.create(probationArea, team, staffName)) + whenever(staffService.create(probationArea.id, probationArea.code, team.id, staffName)) .thenThrow(RuntimeException()) - val res = assignmentService.findAssignment(PrisonCaseNoteGenerator.NEW_TO_DELIUS.locationId, staffName) + val res = assignmentService.findAssignment("LEI", staffName) - verify(staffService).create(probationArea, team, staffName) + verify(staffService).create(probationArea.id, probationArea.code, team.id, staffName) verify(staffService, times(2)).findStaff(probationArea.id, staffName) assertThat(res.first, equalTo(probationArea.id)) assertThat(res.second, equalTo(team.id)) @@ -144,19 +154,42 @@ class AssignmentServiceTest { val probationArea = ProbationAreaGenerator.DEFAULT val team = TeamGenerator.DEFAULT - whenever(probationAreaRepository.findByInstitutionNomisCode(PrisonCaseNoteGenerator.NEW_TO_DELIUS.locationId)) + whenever(probationAreaRepository.findByInstitutionNomisCode("LEI")) .thenReturn(probationArea) whenever(teamRepository.findByCode(team.code)).thenReturn(team) whenever(staffService.findStaff(probationArea.id, staffName)) .thenReturn(null) - whenever(staffService.create(probationArea, team, staffName)) + whenever(staffService.create(probationArea.id, probationArea.code, team.id, staffName)) .thenThrow(NotFoundException("Staff not found")) assertThrows { assignmentService.findAssignment( - PrisonCaseNoteGenerator.NEW_TO_DELIUS.locationId, + "LEI", staffName ) } } } + +object ProbationAreaGenerator { + val DEFAULT = Provider( + 1, + "PA1", + Prison(2, "LEI") + ) +} + +object TeamGenerator { + val DEFAULT = PrisonTeam(3, "${ProbationAreaGenerator.DEFAULT.code}CSN") +} + +object StaffGenerator { + val DEFAULT = PrisonStaff( + 4, + "Bob", + "Smith", + "${ProbationAreaGenerator.DEFAULT.code}A999", + ProbationAreaGenerator.DEFAULT.id + ) +} + diff --git a/projects/prison-case-notes-to-probation/src/test/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/OfficerCodeGeneratorTest.kt b/libs/prison-staff/src/test/kotlin/uk/gov/justice/digital/hmpps/service/OfficerCodeGeneratorTest.kt similarity index 88% rename from projects/prison-case-notes-to-probation/src/test/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/OfficerCodeGeneratorTest.kt rename to libs/prison-staff/src/test/kotlin/uk/gov/justice/digital/hmpps/service/OfficerCodeGeneratorTest.kt index a12069cc88..e89d1014a8 100644 --- a/projects/prison-case-notes-to-probation/src/test/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/OfficerCodeGeneratorTest.kt +++ b/libs/prison-staff/src/test/kotlin/uk/gov/justice/digital/hmpps/service/OfficerCodeGeneratorTest.kt @@ -1,4 +1,4 @@ -package uk.gov.justice.digital.hmpps.integrations.delius.service +package uk.gov.justice.digital.hmpps.service import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.containsString @@ -11,15 +11,14 @@ import org.mockito.InjectMocks import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.kotlin.whenever -import uk.gov.justice.digital.hmpps.data.generator.ProbationAreaGenerator import uk.gov.justice.digital.hmpps.exceptions.StaffCodeExhaustedException -import uk.gov.justice.digital.hmpps.integrations.delius.repository.StaffRepository +import uk.gov.justice.digital.hmpps.repository.PrisonStaffRepository @ExtendWith(MockitoExtension::class) class OfficerCodeGeneratorTest { @Mock - private lateinit var staffRepository: StaffRepository + private lateinit var staffRepository: PrisonStaffRepository @InjectMocks private lateinit var officerCodeGenerator: OfficerCodeGenerator diff --git a/projects/prison-case-notes-to-probation/src/test/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/StaffServiceTest.kt b/libs/prison-staff/src/test/kotlin/uk/gov/justice/digital/hmpps/service/StaffServiceTest.kt similarity index 59% rename from projects/prison-case-notes-to-probation/src/test/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/StaffServiceTest.kt rename to libs/prison-staff/src/test/kotlin/uk/gov/justice/digital/hmpps/service/StaffServiceTest.kt index 1c578ac46a..c2a35881af 100644 --- a/projects/prison-case-notes-to-probation/src/test/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/StaffServiceTest.kt +++ b/libs/prison-staff/src/test/kotlin/uk/gov/justice/digital/hmpps/service/StaffServiceTest.kt @@ -1,4 +1,4 @@ -package uk.gov.justice.digital.hmpps.integrations.delius.service +package uk.gov.justice.digital.hmpps.service import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.equalTo @@ -11,25 +11,23 @@ import org.mockito.Mockito.any import org.mockito.junit.jupiter.MockitoExtension import org.mockito.kotlin.verify import org.mockito.kotlin.whenever -import uk.gov.justice.digital.hmpps.data.generator.ProbationAreaGenerator -import uk.gov.justice.digital.hmpps.data.generator.TeamGenerator -import uk.gov.justice.digital.hmpps.integrations.delius.entity.Staff -import uk.gov.justice.digital.hmpps.integrations.delius.entity.StaffTeam -import uk.gov.justice.digital.hmpps.integrations.delius.model.StaffName -import uk.gov.justice.digital.hmpps.integrations.delius.repository.StaffRepository -import uk.gov.justice.digital.hmpps.integrations.delius.repository.StaffTeamRepository +import uk.gov.justice.digital.hmpps.entity.PrisonStaff +import uk.gov.justice.digital.hmpps.entity.PrisonStaffTeam +import uk.gov.justice.digital.hmpps.model.StaffName +import uk.gov.justice.digital.hmpps.repository.PrisonStaffRepository +import uk.gov.justice.digital.hmpps.repository.PrisonStaffTeamRepository @ExtendWith(MockitoExtension::class) class StaffServiceTest { @Mock - lateinit var staffRepository: StaffRepository + lateinit var staffRepository: PrisonStaffRepository @Mock lateinit var officerCodeGenerator: OfficerCodeGenerator @Mock - lateinit var staffTeamRepository: StaffTeamRepository + lateinit var staffTeamRepository: PrisonStaffTeamRepository @InjectMocks lateinit var staffService: StaffService @@ -42,17 +40,17 @@ class StaffServiceTest { val staffName = StaffName("forename", "surname") whenever(officerCodeGenerator.generateFor(probationArea.code)).thenReturn(newStaffCode) - whenever(staffRepository.save(any(Staff::class.java))).thenAnswer { it.arguments[0] } + whenever(staffRepository.save(any(PrisonStaff::class.java))).thenAnswer { it.arguments[0] } - val newStaff = staffService.create(probationArea, team, staffName) + val newStaff = staffService.create(probationArea.id, probationArea.code, team.id, staffName) - verify(staffRepository).save(any(Staff::class.java)) + verify(staffRepository).save(any(PrisonStaff::class.java)) assertThat(newStaff.forename, equalTo(staffName.forename)) assertThat(newStaff.surname, equalTo(staffName.surname)) assertThat(newStaff.probationAreaId, equalTo(probationArea.id)) assertThat(newStaff.code, equalTo(newStaffCode)) - val staffTeamCaptor = ArgumentCaptor.forClass(StaffTeam::class.java) + val staffTeamCaptor = ArgumentCaptor.forClass(PrisonStaffTeam::class.java) verify(staffTeamRepository).save(staffTeamCaptor.capture()) assertThat(staffTeamCaptor.value.staffId, equalTo(newStaff.id)) assertThat(staffTeamCaptor.value.teamId, equalTo(team.id)) diff --git a/projects/manage-pom-cases-and-delius/build.gradle.kts b/projects/manage-pom-cases-and-delius/build.gradle.kts index dcca69bda7..99778baa97 100644 --- a/projects/manage-pom-cases-and-delius/build.gradle.kts +++ b/projects/manage-pom-cases-and-delius/build.gradle.kts @@ -8,6 +8,7 @@ dependencies { implementation(project(":libs:messaging")) implementation(project(":libs:oauth-client")) implementation(project(":libs:oauth-server")) + implementation(project(":libs:prison-staff")) implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("org.springframework.boot:spring-boot-starter-data-jpa") diff --git a/projects/manage-pom-cases-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/AllocationMessagingIntegrationTest.kt b/projects/manage-pom-cases-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/AllocationMessagingIntegrationTest.kt index ee4d6b456c..94bbd7484a 100644 --- a/projects/manage-pom-cases-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/AllocationMessagingIntegrationTest.kt +++ b/projects/manage-pom-cases-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/AllocationMessagingIntegrationTest.kt @@ -22,12 +22,12 @@ import org.springframework.boot.test.mock.mockito.SpyBean import org.springframework.data.repository.findByIdOrNull import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator import uk.gov.justice.digital.hmpps.data.generator.ProviderGenerator +import uk.gov.justice.digital.hmpps.entity.PrisonStaff 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 -import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.Staff -import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.StaffRepository import uk.gov.justice.digital.hmpps.messaging.HmppsChannelManager +import uk.gov.justice.digital.hmpps.repository.PrisonStaffRepository import uk.gov.justice.digital.hmpps.resourceloader.ResourceLoader.notification import uk.gov.justice.digital.hmpps.telemetry.TelemetryService import java.time.ZonedDateTime @@ -49,7 +49,7 @@ internal class AllocationMessagingIntegrationTest { lateinit var telemetryService: TelemetryService @SpyBean - lateinit var staffRepository: StaffRepository + lateinit var staffRepository: PrisonStaffRepository @SpyBean lateinit var prisonManagerRepository: PrisonManagerRepository @@ -87,7 +87,7 @@ internal class AllocationMessagingIntegrationTest { channelManager.getChannel(queueName).publishAndWait(notification) - val captor = argumentCaptor() + val captor = argumentCaptor() verify(staffRepository).save(captor.capture()) assertThat(captor.firstValue.forename, equalTo("John")) assertThat(captor.firstValue.surname, equalTo("Smith")) @@ -128,7 +128,7 @@ internal class AllocationMessagingIntegrationTest { channelManager.getChannel(queueName).publishAndWait(notification) - val captor = argumentCaptor() + val captor = argumentCaptor() verify(staffRepository).save(captor.capture()) assertThat(captor.firstValue.forename, equalTo("James")) assertThat(captor.firstValue.surname, equalTo("Brown")) diff --git a/projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/provider/entity/Staff.kt b/projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/provider/entity/Staff.kt index df315d3c8f..ba1eac7eb9 100644 --- a/projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/provider/entity/Staff.kt +++ b/projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/provider/entity/Staff.kt @@ -1,16 +1,6 @@ package uk.gov.justice.digital.hmpps.integrations.delius.provider.entity -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.EntityListeners -import jakarta.persistence.GeneratedValue -import jakarta.persistence.GenerationType -import jakarta.persistence.Id -import jakarta.persistence.JoinColumn -import jakarta.persistence.OneToOne -import jakarta.persistence.SequenceGenerator -import jakarta.persistence.Table -import jakarta.persistence.Version +import jakarta.persistence.* import org.hibernate.annotations.Immutable import org.springframework.data.annotation.CreatedBy import org.springframework.data.annotation.CreatedDate diff --git a/projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/provider/entity/StaffTeam.kt b/projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/provider/entity/StaffTeam.kt index 82eaf0234f..1cd707f809 100644 --- a/projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/provider/entity/StaffTeam.kt +++ b/projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/provider/entity/StaffTeam.kt @@ -1,11 +1,6 @@ package uk.gov.justice.digital.hmpps.integrations.delius.provider.entity -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.EntityListeners -import jakarta.persistence.Id -import jakarta.persistence.IdClass -import jakarta.persistence.Version +import jakarta.persistence.* import org.springframework.data.annotation.CreatedBy import org.springframework.data.annotation.CreatedDate import org.springframework.data.annotation.LastModifiedBy @@ -17,6 +12,7 @@ import java.time.ZonedDateTime @EntityListeners(AuditingEntityListener::class) @Entity +@Table(name = "staff_team") @IdClass(StaffTeamId::class) class StaffTeam( diff --git a/projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/provider/entity/Team.kt b/projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/provider/entity/Team.kt index 54f25c512d..1a4e31fd15 100644 --- a/projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/provider/entity/Team.kt +++ b/projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/provider/entity/Team.kt @@ -1,12 +1,6 @@ package uk.gov.justice.digital.hmpps.integrations.delius.provider.entity -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.Id -import jakarta.persistence.JoinColumn -import jakarta.persistence.ManyToOne -import jakarta.persistence.OneToOne -import jakarta.persistence.Table +import jakarta.persistence.* import org.hibernate.annotations.Immutable import org.springframework.data.jpa.repository.EntityGraph import org.springframework.data.jpa.repository.JpaRepository @@ -59,6 +53,7 @@ class District( @Immutable @Entity +@Table(name = "probation_area") class ProbationArea( @Id @Column(name = "probation_area_id") diff --git a/projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/services/PrisonManagerService.kt b/projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/services/PrisonManagerService.kt index 8670c5e39f..8ea26b4b78 100644 --- a/projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/services/PrisonManagerService.kt +++ b/projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/services/PrisonManagerService.kt @@ -5,28 +5,23 @@ import org.springframework.transaction.annotation.Transactional import uk.gov.justice.digital.hmpps.api.model.Name import uk.gov.justice.digital.hmpps.datetime.DeliusDateFormatter import uk.gov.justice.digital.hmpps.datetime.DeliusDateTimeFormatter +import uk.gov.justice.digital.hmpps.entity.PrisonStaff import uk.gov.justice.digital.hmpps.integrations.delius.contact.entity.ContactType import uk.gov.justice.digital.hmpps.integrations.delius.person.entity.PersonRepository -import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.PrisonManager -import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.PrisonManagerRepository -import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.ProbationArea -import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.ProbationAreaRepository -import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.Staff -import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.Team -import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.TeamRepository -import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.getByCode -import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.getByNomisCdeCode +import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.* import uk.gov.justice.digital.hmpps.integrations.delius.reference.entity.ReferenceDataRepository import uk.gov.justice.digital.hmpps.integrations.delius.reference.entity.pomAllocationReason import uk.gov.justice.digital.hmpps.integrations.managepomcases.PomAllocation -import uk.gov.justice.digital.hmpps.retry.retry +import uk.gov.justice.digital.hmpps.model.StaffName +import uk.gov.justice.digital.hmpps.service.AssignmentService import java.time.ZonedDateTime @Service class PrisonManagerService( private val probationAreaRepository: ProbationAreaRepository, private val teamRepository: TeamRepository, - private val staffService: StaffService, + private val assignmentService: AssignmentService, + private val staffRepository: StaffRepository, private val prisonManagerRepository: PrisonManagerRepository, private val referenceDataRepository: ReferenceDataRepository, private val contactService: ContactService, @@ -41,7 +36,13 @@ class PrisonManagerService( ): PomAllocationResult { val probationArea = probationAreaRepository.getByNomisCdeCode(allocation.prison.code) val team = teamRepository.getByCode(probationArea.code + Team.POM_SUFFIX) - val staff = getStaff(probationArea, team, allocation.manager.name, allocationDate) + val staff = assignmentService.getStaff( + probationArea.id, + probationArea.code, + team.id, + allocation.manager.name.asStaffName(), + allocationDate + ) personRepository.findForUpdate(personId) val currentPom = prisonManagerRepository.findActiveManagerAtDate(personId, allocationDate) val newEndDate = currentPom?.endDate @@ -62,10 +63,10 @@ class PrisonManagerService( return if (currentPom?.isUnallocated() == false) { val probationArea = currentPom.probationArea val team = teamRepository.getByCode(probationArea.code + Team.UNALLOCATED_SUFFIX) - val staff = staffService.getStaffByCode(team.code + "U") + val staff = staffRepository.getByCode(team.code + "U") val newEndDate = currentPom.endDate ?: prisonManagerRepository.findFirstManagerAfterDate(personId, deallocationDate).firstOrNull()?.date - val newPom = currentPom.changeTo(personId, deallocationDate, probationArea, team, staff)!! + val newPom = currentPom.changeTo(personId, deallocationDate, probationArea, team, staff.toPrisonStaff())!! prisonManagerRepository.saveAndFlush(currentPom) newPom.endDate = newEndDate prisonManagerRepository.save(newPom) @@ -75,19 +76,7 @@ class PrisonManagerService( } } - private fun getStaff( - probationArea: ProbationArea, - team: Team, - staffName: Name, - allocationDate: ZonedDateTime - ): Staff { - val findStaff = { staffService.findStaff(probationArea.id, staffName) } - return retry(3) { - findStaff() ?: staffService.create(probationArea, team, staffName, allocationDate) - } - } - - private fun PrisonManager.hasChanged(probationArea: ProbationArea, team: Team, staff: Staff) = + private fun PrisonManager.hasChanged(probationArea: ProbationArea, team: Team, staff: PrisonStaff) = this.probationArea.id != probationArea.id || this.team.id != team.id || this.staff.id != staff.id fun PrisonManager.allocationNotes() = @@ -117,7 +106,7 @@ class PrisonManagerService( dateTime: ZonedDateTime, probationArea: ProbationArea, team: Team, - staff: Staff + staff: PrisonStaff ) = if (this?.hasChanged(probationArea, team, staff) != false) { val allocationReasonCode = when { @@ -130,7 +119,7 @@ class PrisonManagerService( probationArea = probationArea, allocationReason = referenceDataRepository.pomAllocationReason(allocationReasonCode.value), date = dateTime, - staff = staff, + staff = staff.toStaff(), team = team, responsibleOfficers = mutableListOf() ) @@ -161,6 +150,13 @@ class PrisonManagerService( } } +fun Name.asStaffName() = StaffName(forename, surname) enum class PomAllocationResult { PomAllocated, PomDeallocated, NoPomChange } + +fun PrisonStaff.toStaff() = + Staff(id = id, code = code, forename = forename, surname = surname, probationAreaId = probationAreaId) + +fun Staff.toPrisonStaff() = + PrisonStaff(id = id, code = code, forename = forename, surname = surname, probationAreaId = probationAreaId) \ No newline at end of file diff --git a/projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/services/StaffService.kt b/projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/services/StaffService.kt deleted file mode 100644 index e21aa693bf..0000000000 --- a/projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/services/StaffService.kt +++ /dev/null @@ -1,58 +0,0 @@ -package uk.gov.justice.digital.hmpps.services - -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Propagation -import org.springframework.transaction.annotation.Transactional -import org.springframework.util.ConcurrentReferenceHashMap -import uk.gov.justice.digital.hmpps.api.model.Name -import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.ProbationArea -import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.Staff -import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.StaffRepository -import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.StaffTeam -import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.StaffTeamRepository -import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.Team -import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.getByCode -import java.time.ZonedDateTime - -@Service -class StaffService( - private val staffRepository: StaffRepository, - private val officerCodeGenerator: OfficerCodeGenerator, - private val staffTeamRepository: StaffTeamRepository -) { - - companion object { - private val mutexMap = ConcurrentReferenceHashMap() - private fun getMutex(key: Long) { - mutexMap.compute(key) { _, v -> v ?: Any() } - } - } - - @Transactional(propagation = Propagation.REQUIRES_NEW) - fun create(pa: ProbationArea, team: Team, staffName: Name, startDate: ZonedDateTime): Staff = synchronized( - getMutex( - pa.id - ) - ) { - val staff = staffRepository.save( - Staff( - forename = staffName.forename, - surname = staffName.surname, - probationAreaId = pa.id, - code = officerCodeGenerator.generateFor(pa.code), - startDate = startDate - ) - ) - staffTeamRepository.save(StaffTeam(staff.id, team.id)) - return staff - } - - fun findStaff(probationAreaId: Long, staffName: Name) = - staffRepository.findTopByProbationAreaIdAndForenameIgnoreCaseAndSurnameIgnoreCase( - probationAreaId, - staffName.forename, - staffName.surname - ) - - fun getStaffByCode(code: String) = staffRepository.getByCode(code) -} diff --git a/projects/manage-pom-cases-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/services/OfficerCodeGeneratorTest.kt b/projects/manage-pom-cases-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/services/OfficerCodeGeneratorTest.kt deleted file mode 100644 index fc1e78c4d8..0000000000 --- a/projects/manage-pom-cases-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/services/OfficerCodeGeneratorTest.kt +++ /dev/null @@ -1,71 +0,0 @@ -package uk.gov.justice.digital.hmpps.services - -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.containsString -import org.hamcrest.Matchers.equalTo -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.ArgumentMatchers.anyString -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.whenever -import uk.gov.justice.digital.hmpps.data.generator.ProviderGenerator -import uk.gov.justice.digital.hmpps.integrations.delius.exceptions.StaffCodeExhaustedException -import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.StaffRepository - -@ExtendWith(MockitoExtension::class) -class OfficerCodeGeneratorTest { - - @Mock - private lateinit var staffRepository: StaffRepository - - @InjectMocks - private lateinit var officerCodeGenerator: OfficerCodeGenerator - - private val probationAreaCode: String = ProviderGenerator.DEFAULT_PROVIDER.code - - @Test - fun `if all possible options exhausted exception thrown`() { - whenever(staffRepository.getLatestStaffReference(anyString())) - .thenAnswer { - val regex = it.arguments[0] as String - val prefix = regex.substring(1, regex.length - 6) - prefix + "999" - } - - val ex = assertThrows { - officerCodeGenerator.generateFor(probationAreaCode) - } - - assertThat(ex.message, containsString(probationAreaCode)) - } - - @Test - fun `if no result returned for a given probation area A001 is used`() { - whenever(staffRepository.getLatestStaffReference(anyString())).thenReturn(null) - - val code = officerCodeGenerator.generateFor(probationAreaCode) - - assertThat(code, equalTo(probationAreaCode + "A001")) - } - - @Test - fun `roll over to next letter is successful`() { - whenever(staffRepository.getLatestStaffReference(anyString())) - .thenAnswer { - val regex = it.arguments[0] as String - val prefix = regex.substring(1, regex.length - 6) - if (prefix == "${probationAreaCode}A") { - prefix + "999" - } else { - "${probationAreaCode}B001" - } - } - - val code = officerCodeGenerator.generateFor(probationAreaCode) - - assertThat(code, equalTo(probationAreaCode + "B002")) - } -} diff --git a/projects/prison-case-notes-to-probation/build.gradle.kts b/projects/prison-case-notes-to-probation/build.gradle.kts index 2f628668cc..0b29b3b175 100644 --- a/projects/prison-case-notes-to-probation/build.gradle.kts +++ b/projects/prison-case-notes-to-probation/build.gradle.kts @@ -7,6 +7,7 @@ dependencies { implementation(project(":libs:commons")) implementation(project(":libs:messaging")) implementation(project(":libs:oauth-client")) + implementation(project(":libs:prison-staff")) implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("org.springframework.boot:spring-boot-starter-data-jpa") diff --git a/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/ProbationArea.kt b/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/ProbationArea.kt index 6d5f104df9..7040164660 100644 --- a/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/ProbationArea.kt +++ b/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/ProbationArea.kt @@ -1,15 +1,11 @@ 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.OneToOne -import jakarta.persistence.Table +import jakarta.persistence.* import org.hibernate.annotations.Immutable @Immutable @Entity +@Table(name = "probation_area") class ProbationArea( @Id @Column(name = "probation_area_id") diff --git a/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/Staff.kt b/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/Staff.kt index 34185cdd43..48a5a59807 100644 --- a/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/Staff.kt +++ b/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/Staff.kt @@ -1,13 +1,6 @@ package uk.gov.justice.digital.hmpps.integrations.delius.entity -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.EntityListeners -import jakarta.persistence.GeneratedValue -import jakarta.persistence.GenerationType -import jakarta.persistence.Id -import jakarta.persistence.SequenceGenerator -import jakarta.persistence.Version +import jakarta.persistence.* import org.springframework.data.annotation.CreatedBy import org.springframework.data.annotation.CreatedDate import org.springframework.data.annotation.LastModifiedBy @@ -18,6 +11,7 @@ import java.time.ZonedDateTime @EntityListeners(AuditingEntityListener::class) @Entity +@Table(name = "staff") class Staff( @Id diff --git a/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/StaffTeam.kt b/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/StaffTeam.kt index b51a33129b..9d808aa177 100644 --- a/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/StaffTeam.kt +++ b/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/entity/StaffTeam.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.EntityListeners -import jakarta.persistence.Id -import jakarta.persistence.IdClass -import jakarta.persistence.Version +import jakarta.persistence.* import org.springframework.data.annotation.CreatedBy import org.springframework.data.annotation.CreatedDate import org.springframework.data.annotation.LastModifiedBy @@ -16,6 +11,7 @@ import java.time.ZonedDateTime @EntityListeners(AuditingEntityListener::class) @Entity +@Table(name = "staff_team") @IdClass(StaffTeamId::class) class StaffTeam( diff --git a/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/model/DeliusCaseNote.kt b/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/model/DeliusCaseNote.kt index c015ed6121..4ed03a1909 100644 --- a/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/model/DeliusCaseNote.kt +++ b/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/model/DeliusCaseNote.kt @@ -4,6 +4,7 @@ import jakarta.validation.Valid import jakarta.validation.constraints.NotBlank import jakarta.validation.constraints.NotNull import uk.gov.justice.digital.hmpps.integrations.delius.entity.CaseNoteType +import uk.gov.justice.digital.hmpps.model.StaffName import java.time.ZonedDateTime data class DeliusCaseNote(val header: CaseNoteHeader, val body: CaseNoteBody) @@ -49,8 +50,6 @@ data class CaseNoteBody( fun String.isAlertType() = this == "ALERT ACTIVE" || this == "ALERT INACTIVE" -data class StaffName(@NotBlank val forename: String, @NotBlank val surname: String) - enum class BcstPathway(val keyword: String) { ACCOMMODATION("Accommodation"), ATTITUDES_THINKING_AND_BEHAVIOUR("Attitudes"), diff --git a/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/DeliusService.kt b/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/DeliusService.kt index f768135fd3..ed4ebdc044 100644 --- a/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/DeliusService.kt +++ b/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/DeliusService.kt @@ -14,6 +14,7 @@ import uk.gov.justice.digital.hmpps.integrations.delius.entity.CaseNote import uk.gov.justice.digital.hmpps.integrations.delius.entity.CaseNoteType import uk.gov.justice.digital.hmpps.integrations.delius.model.DeliusCaseNote import uk.gov.justice.digital.hmpps.integrations.delius.repository.* +import uk.gov.justice.digital.hmpps.service.AssignmentService import java.time.temporal.ChronoUnit @Service diff --git a/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/StaffService.kt b/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/StaffService.kt deleted file mode 100644 index 98c4897f09..0000000000 --- a/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/StaffService.kt +++ /dev/null @@ -1,49 +0,0 @@ -package uk.gov.justice.digital.hmpps.integrations.delius.service - -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Propagation -import org.springframework.transaction.annotation.Transactional -import org.springframework.util.ConcurrentReferenceHashMap -import uk.gov.justice.digital.hmpps.integrations.delius.entity.ProbationArea -import uk.gov.justice.digital.hmpps.integrations.delius.entity.Staff -import uk.gov.justice.digital.hmpps.integrations.delius.entity.StaffTeam -import uk.gov.justice.digital.hmpps.integrations.delius.entity.Team -import uk.gov.justice.digital.hmpps.integrations.delius.model.StaffName -import uk.gov.justice.digital.hmpps.integrations.delius.repository.StaffRepository -import uk.gov.justice.digital.hmpps.integrations.delius.repository.StaffTeamRepository - -@Service -class StaffService( - private val staffRepository: StaffRepository, - private val officerCodeGenerator: OfficerCodeGenerator, - private val staffTeamRepository: StaffTeamRepository -) { - - companion object { - private val mutexMap = ConcurrentReferenceHashMap() - private fun getMutex(key: Long) { - mutexMap.compute(key) { _, v -> v ?: Any() } - } - } - - @Transactional(propagation = Propagation.REQUIRES_NEW) - fun create(pa: ProbationArea, team: Team, staffName: StaffName): Staff = synchronized(getMutex(pa.id)) { - val staff = staffRepository.save( - Staff( - forename = staffName.forename, - surname = staffName.surname, - probationAreaId = pa.id, - code = officerCodeGenerator.generateFor(pa.code) - ) - ) - staffTeamRepository.save(StaffTeam(staff.id, team.id)) - return staff - } - - fun findStaff(probationAreaId: Long, staffName: StaffName) = - staffRepository.findTopByProbationAreaIdAndForenameIgnoreCaseAndSurnameIgnoreCase( - probationAreaId, - staffName.forename, - staffName.surname - ) -} diff --git a/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/prison/PrisonCaseNote.kt b/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/prison/PrisonCaseNote.kt index 04b74ea1c4..d63ff807f6 100644 --- a/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/prison/PrisonCaseNote.kt +++ b/projects/prison-case-notes-to-probation/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/prison/PrisonCaseNote.kt @@ -5,7 +5,7 @@ import uk.gov.justice.digital.hmpps.datetime.DeliusDateTimeFormatter import uk.gov.justice.digital.hmpps.integrations.delius.model.CaseNoteBody import uk.gov.justice.digital.hmpps.integrations.delius.model.CaseNoteHeader import uk.gov.justice.digital.hmpps.integrations.delius.model.DeliusCaseNote -import uk.gov.justice.digital.hmpps.integrations.delius.model.StaffName +import uk.gov.justice.digital.hmpps.model.StaffName import java.time.ZonedDateTime const val UNKNOWN_LOCATION = "UNK" diff --git a/projects/prison-case-notes-to-probation/src/test/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/DeliusServiceTest.kt b/projects/prison-case-notes-to-probation/src/test/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/DeliusServiceTest.kt index fd54c144e8..3abb256b6e 100644 --- a/projects/prison-case-notes-to-probation/src/test/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/DeliusServiceTest.kt +++ b/projects/prison-case-notes-to-probation/src/test/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/service/DeliusServiceTest.kt @@ -28,6 +28,7 @@ import uk.gov.justice.digital.hmpps.integrations.delius.repository.CaseNoteRepos import uk.gov.justice.digital.hmpps.integrations.delius.repository.CaseNoteTypeRepository import uk.gov.justice.digital.hmpps.integrations.delius.repository.OffenderRepository import uk.gov.justice.digital.hmpps.integrations.prison.toDeliusCaseNote +import uk.gov.justice.digital.hmpps.service.AssignmentService import java.time.ZonedDateTime import java.util.* diff --git a/projects/prison-case-notes-to-probation/src/test/kotlin/uk/gov/justice/digital/hmpps/integrations/prison/PrisonCaseNoteTest.kt b/projects/prison-case-notes-to-probation/src/test/kotlin/uk/gov/justice/digital/hmpps/integrations/prison/PrisonCaseNoteTest.kt index 357e589f29..fbc25cf61a 100644 --- a/projects/prison-case-notes-to-probation/src/test/kotlin/uk/gov/justice/digital/hmpps/integrations/prison/PrisonCaseNoteTest.kt +++ b/projects/prison-case-notes-to-probation/src/test/kotlin/uk/gov/justice/digital/hmpps/integrations/prison/PrisonCaseNoteTest.kt @@ -7,7 +7,7 @@ import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments.of import org.junit.jupiter.params.provider.MethodSource import uk.gov.justice.digital.hmpps.integrations.delius.model.BcstPathway -import uk.gov.justice.digital.hmpps.integrations.delius.model.StaffName +import uk.gov.justice.digital.hmpps.model.StaffName import java.time.ZonedDateTime class PrisonCaseNoteTest { diff --git a/projects/resettlement-passport-and-delius/build.gradle.kts b/projects/resettlement-passport-and-delius/build.gradle.kts index 877db8cd22..a0ba951910 100644 --- a/projects/resettlement-passport-and-delius/build.gradle.kts +++ b/projects/resettlement-passport-and-delius/build.gradle.kts @@ -6,6 +6,7 @@ dependencies { implementation(project(":libs:audit")) implementation(project(":libs:commons")) implementation(project(":libs:oauth-server")) + implementation(project(":libs:prison-staff")) implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("org.springframework.boot:spring-boot-starter-data-ldap") diff --git a/projects/resettlement-passport-and-delius/deploy/database/access.yml b/projects/resettlement-passport-and-delius/deploy/database/access.yml index 57784ec9a5..89ffb913ae 100644 --- a/projects/resettlement-passport-and-delius/deploy/database/access.yml +++ b/projects/resettlement-passport-and-delius/deploy/database/access.yml @@ -6,6 +6,8 @@ database: - audited_interaction - contact - contact_alert + - staff + - staff_team audit: username: ResettlementPassportAndDelius diff --git a/projects/resettlement-passport-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt b/projects/resettlement-passport-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt index 2db3ab6a84..368b2d72f1 100644 --- a/projects/resettlement-passport-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt +++ b/projects/resettlement-passport-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt @@ -7,25 +7,21 @@ import org.springframework.boot.context.event.ApplicationReadyEvent import org.springframework.context.ApplicationListener import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional -import uk.gov.justice.digital.hmpps.api.model.CreateAppointment import uk.gov.justice.digital.hmpps.audit.BusinessInteraction import uk.gov.justice.digital.hmpps.data.generator.* import uk.gov.justice.digital.hmpps.datetime.EuropeLondon -import uk.gov.justice.digital.hmpps.entity.BusinessInteractionCode -import uk.gov.justice.digital.hmpps.entity.Category -import uk.gov.justice.digital.hmpps.entity.Level -import uk.gov.justice.digital.hmpps.entity.Person +import uk.gov.justice.digital.hmpps.entity.* import uk.gov.justice.digital.hmpps.user.AuditUserRepository import java.time.LocalDate import java.time.LocalTime import java.time.ZonedDateTime -import java.util.* @Component @ConditionalOnProperty("seed.database") class DataLoader( private val auditUserRepository: AuditUserRepository, - private val em: EntityManager + private val em: EntityManager, + private val staffRepository: StaffRepository ) : ApplicationListener { @PostConstruct @@ -38,17 +34,24 @@ class DataLoader( BusinessInteractionCode.entries.forEach { em.persist(BusinessInteraction(IdGenerator.getAndIncrement(), it.code, ZonedDateTime.now())) } + ProviderGenerator.DEFAULT_STAFF = staffRepository.save(ProviderGenerator.DEFAULT_STAFF) + ProviderGenerator.EXISTING_CSN_STAFF = staffRepository.save(ProviderGenerator.EXISTING_CSN_STAFF) em.saveAll( NSITypeGenerator.DTR, NSIStatusGenerator.INITIATED, ReferenceDataGenerator.ADDRESS_STATUS, ReferenceDataGenerator.DTR_SUB_TYPE, + ProviderGenerator.DEFAULT_INSTITUTION, + ProviderGenerator.INSTITUTION_NO_TEAM, ProviderGenerator.DEFAULT_AREA, + ProviderGenerator.AREA_NO_TEAM, + ProviderGenerator.CSN_TEAM, ProviderGenerator.DEFAULT_TEAM, - ProviderGenerator.DEFAULT_STAFF, - ProviderGenerator.DEFAULT_STAFF_USER, + ProviderGenerator.generateStaffUser("john-smith", ProviderGenerator.DEFAULT_STAFF), PersonGenerator.DEFAULT, PersonGenerator.DEFAULT_MANAGER, + PersonGenerator.RP9_CONTACT_TYPE, + PersonGenerator.RP10_CONTACT_TYPE, NSIGenerator.DEFAULT, NSIManagerGenerator.DEFAULT, AddressGenerator.DEFAULT, diff --git a/projects/resettlement-passport-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt b/projects/resettlement-passport-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt index f328428735..227768b25b 100644 --- a/projects/resettlement-passport-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt +++ b/projects/resettlement-passport-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt @@ -1,9 +1,6 @@ package uk.gov.justice.digital.hmpps.data.generator -import uk.gov.justice.digital.hmpps.entity.Person -import uk.gov.justice.digital.hmpps.entity.PersonManager -import uk.gov.justice.digital.hmpps.entity.Staff -import uk.gov.justice.digital.hmpps.entity.Team +import uk.gov.justice.digital.hmpps.entity.* import java.time.LocalDate object PersonGenerator { @@ -20,6 +17,8 @@ object PersonGenerator { val DEFAULT_MANAGER = generateManager(DEFAULT) val CREATE_APPOINTMENT = generate("C123456", null, "James", "Brown", LocalDate.of(1990, 5, 12)) + val RP9_CONTACT_TYPE = generateContactType("RP9", "RP9") + val RP10_CONTACT_TYPE = generateContactType("RP10", "RP10") fun generate( crn: String, noms: String? = null, @@ -53,4 +52,10 @@ object PersonGenerator { softDeleted: Boolean = false, id: Long = IdGenerator.getAndIncrement() ) = PersonManager(person, team, staff, probationAreaId, softDeleted, active, id) + + fun generateContactType(code: String, description: String) = CaseNoteType( + id = IdGenerator.getAndIncrement(), + code = code, + description = description + ) } diff --git a/projects/resettlement-passport-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ProviderGenerator.kt b/projects/resettlement-passport-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ProviderGenerator.kt index 7261ae5f0b..994c23865b 100644 --- a/projects/resettlement-passport-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ProviderGenerator.kt +++ b/projects/resettlement-passport-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ProviderGenerator.kt @@ -1,21 +1,24 @@ package uk.gov.justice.digital.hmpps.data.generator -import uk.gov.justice.digital.hmpps.entity.ProbationArea -import uk.gov.justice.digital.hmpps.entity.Staff -import uk.gov.justice.digital.hmpps.entity.StaffUser -import uk.gov.justice.digital.hmpps.entity.Team +import uk.gov.justice.digital.hmpps.entity.* object ProviderGenerator { + val DEFAULT_INSTITUTION = generateNomisInstitution(code = "LDN") + val INSTITUTION_NO_TEAM = generateNomisInstitution(code = "MDL") + val DEFAULT_AREA = generateProbationArea() + val AREA_NO_TEAM = generateProbationArea(code = "MDL", institution = INSTITUTION_NO_TEAM) val DEFAULT_TEAM = generateTeam("N03DEF") - val DEFAULT_STAFF = generateStaff("N03DEF1", "John", "Smith", "James") - val DEFAULT_STAFF_USER = generateStaffUser("john-smith", DEFAULT_STAFF) + val CSN_TEAM = generateTeam("LDNCSN") + var DEFAULT_STAFF = generateStaff("N03DEF1", "John", "Smith", "James", probationAreaId = DEFAULT_AREA.id) + var EXISTING_CSN_STAFF = generateStaff("LDNA001", "Terry", "Nutkins", "James", probationAreaId = DEFAULT_AREA.id) fun generateProbationArea( id: Long = IdGenerator.getAndIncrement(), code: String = "LDN", - description: String = "London" - ) = ProbationArea(id, code, description) + description: String = "London", + institution: Institution? = DEFAULT_INSTITUTION + ) = ProbationArea(id, code, description, institution) fun generateTeam( code: String, @@ -28,12 +31,18 @@ object ProviderGenerator { forename: String, surname: String, middleName: String? = null, - id: Long = IdGenerator.getAndIncrement() - ) = Staff(code, forename, surname, middleName, null, id) + id: Long = IdGenerator.getAndIncrement(), + probationAreaId: Long + ) = Staff(code, forename, surname, middleName, null, probationAreaId, id = id) fun generateStaffUser( username: String, staff: Staff, id: Long = IdGenerator.getAndIncrement() ) = StaffUser(username, staff, id) + + fun generateNomisInstitution( + id: Long = IdGenerator.getAndIncrement(), + code: String + ) = Institution(id, code) } diff --git a/projects/resettlement-passport-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/CreateCaseNoteIntTests.kt b/projects/resettlement-passport-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/CreateCaseNoteIntTests.kt new file mode 100644 index 0000000000..7ce685c51f --- /dev/null +++ b/projects/resettlement-passport-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/CreateCaseNoteIntTests.kt @@ -0,0 +1,278 @@ +package uk.gov.justice.digital.hmpps + +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import uk.gov.justice.digital.hmpps.advice.ErrorResponse +import uk.gov.justice.digital.hmpps.api.model.CreateContact +import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator.DEFAULT +import uk.gov.justice.digital.hmpps.data.generator.ProviderGenerator.DEFAULT_AREA +import uk.gov.justice.digital.hmpps.entity.CaseNoteRepository +import uk.gov.justice.digital.hmpps.entity.StaffRepository +import uk.gov.justice.digital.hmpps.entity.TeamRepository +import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.contentAsJson +import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.withToken + +@AutoConfigureMockMvc +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +internal class CreateCaseNoteIntTests { + @Autowired + internal lateinit var mockMvc: MockMvc + + @Autowired + internal lateinit var caseNoteRepository: CaseNoteRepository + + @Autowired + internal lateinit var staffRepository: StaffRepository + + @Autowired + internal lateinit var teamRepository: TeamRepository + + @Test + fun `create contact for RP9 when author does not exist`() { + val crn = DEFAULT.crn + mockMvc.perform( + post("/nomis-case-note/${crn}") + .contentType(MediaType.APPLICATION_JSON) + .content( + """ + { + "type": "IMMEDIATE_NEEDS_REPORT", + "notes": "Testing", + "dateTime": "2023-02-12T10:15:00.382936Z[Europe/London]", + "author": { + "forename": "John", + "surname": "Brown", + "prisonCode": "LDN" + } + } + """ + ) + .withToken() + ).andExpect(status().isCreated) + + val contact = caseNoteRepository.findByPersonIdAndTypeCode( + DEFAULT.id, + CreateContact.CaseNoteType.IMMEDIATE_NEEDS_REPORT.code + ).first() + val staff = staffRepository.findById(contact.staffId).get() + val team = teamRepository.findById(contact.teamId).get() + assertThat(contact.notes, equalTo("Testing")) + assertThat(contact.type.code, equalTo(CreateContact.CaseNoteType.IMMEDIATE_NEEDS_REPORT.code)) + assertThat(staff.forename, equalTo("John")) + assertThat(staff.surname, equalTo("Brown")) + assertThat(staff.code, equalTo("LDNA002")) + assertThat(team.code, equalTo("LDNCSN")) + assertThat(contact.probationAreaId, equalTo(DEFAULT_AREA.id)) + } + + @Test + fun `create contact for RP10 where author already exists`() { + val crn = DEFAULT.crn + mockMvc.perform( + post("/nomis-case-note/${crn}") + .contentType(MediaType.APPLICATION_JSON) + .content( + """ + { + "type": "PRE_RELEASE_REPORT", + "notes": "Testing", + "dateTime": "2023-02-12T10:15:00.382936Z[Europe/London]", + "author": { + "forename": "Terry", + "surname": "Nutkins", + "prisonCode": "LDN" + } + } + """ + ) + .withToken() + ).andExpect(status().isCreated) + + val contact = + caseNoteRepository.findByPersonIdAndTypeCode(DEFAULT.id, CreateContact.CaseNoteType.PRE_RELEASE_REPORT.code) + .first() + val staff = staffRepository.findById(contact.staffId).get() + val team = teamRepository.findById(contact.teamId).get() + assertThat(contact.notes, equalTo("Testing")) + assertThat(contact.type.code, equalTo(CreateContact.CaseNoteType.PRE_RELEASE_REPORT.code)) + assertThat(staff.forename, equalTo("Terry")) + assertThat(staff.surname, equalTo("Nutkins")) + assertThat(staff.code, equalTo("LDNA001")) + assertThat(team.code, equalTo("LDNCSN")) + assertThat(contact.probationAreaId, equalTo(DEFAULT_AREA.id)) + } + + @Test + fun `return bad request when author prison code is not found`() { + val crn = DEFAULT.crn + val res = mockMvc.perform( + post("/nomis-case-note/${crn}") + .contentType(MediaType.APPLICATION_JSON) + .content( + """ + { + "type": "PRE_RELEASE_REPORT", + "notes": "Testing", + "dateTime": "2023-02-12T10:15:00.382936Z[Europe/London]", + "author": { + "forename": "John", + "surname": "Brown", + "prisonCode": "NHJ" + } + } + """ + ) + .withToken() + ).andExpect(status().isBadRequest).andReturn().response.contentAsJson() + + assertThat(res.message, equalTo("Probation Area not found for NOMIS institution: NHJ")) + } + + @Test + fun `return bad request when team code not found for prison code`() { + val crn = DEFAULT.crn + val res = mockMvc.perform( + post("/nomis-case-note/${crn}") + .contentType(MediaType.APPLICATION_JSON) + .content( + """ + { + "type": "PRE_RELEASE_REPORT", + "notes": "Testing", + "dateTime": "2023-02-12T10:15:00.382936Z[Europe/London]", + "author": { + "forename": "John", + "surname": "Brown", + "prisonCode": "MDL" + } + } + """ + ) + .withToken() + ).andExpect(status().isBadRequest).andReturn().response.contentAsJson() + + assertThat(res.message, equalTo("Team with code of MDLCSN not found")) + } + + @Test + fun `return bad request when not RP9 or RP10`() { + val crn = DEFAULT.crn + mockMvc.perform( + post("/nomis-case-note/${crn}") + .contentType(MediaType.APPLICATION_JSON) + .content( + """ + { + "type": "OTHER", + "notes": "Testing", + "dateTime": "2023-02-12T10:15:00.382936Z[Europe/London]", + "author": { + "forename": "John", + "surname": "Brown", + "prisonCode": "LDN" + } + } + """ + ) + .withToken() + ).andExpect(status().isBadRequest) + } + + @Test + fun `return bad request when an invalid datetime is provided`() { + val crn = DEFAULT.crn + mockMvc.perform( + post("/nomis-case-note/${crn}") + .contentType(MediaType.APPLICATION_JSON) + .content( + """ + { + "type": "PRE_RELEASE_REPORT", + "notes": "Testing", + "dateTime": "", + "author": { + "forename": "John", + "surname": "Brown", + "prisonCode": "LDN" + } + } + """ + ) + .withToken() + ).andExpect(status().isBadRequest) + } + + @Test + fun `return bad request when no notes provided`() { + val crn = DEFAULT.crn + mockMvc.perform( + post("/nomis-case-note/${crn}") + .contentType(MediaType.APPLICATION_JSON) + .content( + """ + { + "type": "PRE_RELEASE_REPORT", + "notes": "", + "dateTime": "2024-02-12T10:15:00.382936Z[Europe/London]", + "author": { + "forename": "John", + "surname": "Brown", + "prisonCode": "LDN" + } + } + """ + ) + .withToken() + ).andExpect(status().isBadRequest) + } + + @Test + fun `return bad request when no author provided`() { + val crn = DEFAULT.crn + mockMvc.perform( + post("/nomis-case-note/${crn}") + .contentType(MediaType.APPLICATION_JSON) + .content( + """ + { + "type": "PRE_RELEASE_REPORT", + "notes": "", + "dateTime": "2024-02-12T10:15:00.382936Z[Europe/London]" + } + """ + ) + .withToken() + ).andExpect(status().isBadRequest) + } + + @Test + fun `return not found when crn is not found`() { + mockMvc.perform( + post("/nomis-case-note/X123456") + .contentType(MediaType.APPLICATION_JSON) + .content( + """ + { + "type": "PRE_RELEASE_REPORT", + "notes": "Testing", + "dateTime": "2024-02-12T10:15:00.382936Z[Europe/London]", + "author": { + "forename": "John", + "surname": "Brown", + "prisonCode": "LDN" + } + } + """ + ) + .withToken() + ).andExpect(status().isNotFound) + } +} \ No newline at end of file diff --git a/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/controller/CaseNoteController.kt b/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/controller/CaseNoteController.kt new file mode 100644 index 0000000000..bad8612001 --- /dev/null +++ b/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/controller/CaseNoteController.kt @@ -0,0 +1,21 @@ +package uk.gov.justice.digital.hmpps.api.controller + +import jakarta.validation.Valid +import org.springframework.http.HttpStatus +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.* +import uk.gov.justice.digital.hmpps.api.model.CreateContact +import uk.gov.justice.digital.hmpps.service.CaseNoteService + +@RestController +@RequestMapping("/nomis-case-note") +class CaseNoteController(private val caseNoteService: CaseNoteService) { + + @PreAuthorize("hasRole('PROBATION_API__RESETTLEMENT_PASSPORT__APPOINTMENT_RW')") + @PostMapping("/{crn}") + @ResponseStatus(HttpStatus.CREATED) + fun createContact(@PathVariable crn: String, @RequestBody @Valid createContact: CreateContact) { + caseNoteService.createContact(crn, createContact) + } +} + diff --git a/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/CreateContact.kt b/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/CreateContact.kt new file mode 100644 index 0000000000..4e1285e158 --- /dev/null +++ b/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/CreateContact.kt @@ -0,0 +1,36 @@ +package uk.gov.justice.digital.hmpps.api.model + +import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.NotBlank +import java.time.ZonedDateTime + +data class CreateContact( + @Schema( + type = "string", + example = "IMMEDIATE_NEEDS_REPORT", + description = "Nomis contact type. Must either IMMEDIATE_NEEDS_REPORT or PRE_RELEASE_REPORT" + ) + val type: CaseNoteType, + val dateTime: ZonedDateTime, + @field:NotBlank + val notes: String, + + @NotBlank + val author: Author +) { + enum class CaseNoteType(val code: String) { + IMMEDIATE_NEEDS_REPORT("RP9"), + PRE_RELEASE_REPORT("RP10"), + } +} + +data class Author( + @field:NotBlank + val prisonCode: String, + + @field:NotBlank + val forename: String, + + @field:NotBlank + val surname: String +) \ No newline at end of file diff --git a/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/CaseNote.kt b/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/CaseNote.kt new file mode 100644 index 0000000000..a3e583df10 --- /dev/null +++ b/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/CaseNote.kt @@ -0,0 +1,96 @@ +package uk.gov.justice.digital.hmpps.entity + +import jakarta.persistence.* +import org.hibernate.annotations.Immutable +import org.springframework.data.annotation.CreatedBy +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.exception.NotFoundException +import java.time.LocalDate +import java.time.ZonedDateTime + +@EntityListeners(AuditingEntityListener::class) +@Entity(name = "ContactEntity") +@Table(name = "contact") +class CaseNote( + @Id + @Column(name = "contact_id", updatable = false) + @SequenceGenerator(name = "contact_id_seq", sequenceName = "contact_id_seq", allocationSize = 1) + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "contact_id_seq") + val id: Long = 0, + + @ManyToOne + @JoinColumn(name = "offender_id") + val person: Person, + + @ManyToOne + @JoinColumn(name = "contact_type_id", updatable = false) + val type: CaseNoteType, + + @Lob + val notes: String, + + @Column(name = "contact_date") + val date: LocalDate, + + @Column(name = "contact_start_time") + val startTime: ZonedDateTime, + + @Column(name = "staff_id") + val staffId: Long, + + @Column(name = "team_id") + val teamId: Long, + + @Column(updatable = false) + val probationAreaId: Long, + + @Column(name = "created_datetime", updatable = false) + val createdDateTime: ZonedDateTime = ZonedDateTime.now(), + + @CreatedBy + var createdByUserId: Long = 0, + + @LastModifiedDate + val lastUpdatedDateTime: ZonedDateTime = ZonedDateTime.now(), + + @LastModifiedBy + var lastUpdatedUserId: Long = 0, + + @Version + @Column(name = "row_version") + val version: Long = 0, + + @Column(updatable = false) + val partitionAreaId: Long = 0L, + + @Column(name = "event_id") + val eventId: Long? = null, + + @Column(updatable = false, columnDefinition = "number") + val softDeleted: Boolean = false +) + +@Immutable +@Entity +@Table(name = "r_contact_type") +class CaseNoteType( + @Id + @Column(name = "contact_type_id") + val id: Long, + + val code: String, + + val description: String, +) + +interface CaseNoteTypeRepository : JpaRepository { + + fun findByCode(code: String): CaseNoteType? +} + +fun CaseNoteTypeRepository.getCode(code: String) = + findByCode(code) ?: throw NotFoundException("CaseNoteType", "code", code) + diff --git a/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/CaseNoteRepository.kt b/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/CaseNoteRepository.kt new file mode 100644 index 0000000000..36270c75a4 --- /dev/null +++ b/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/CaseNoteRepository.kt @@ -0,0 +1,9 @@ +package uk.gov.justice.digital.hmpps.entity + +import org.springframework.data.jpa.repository.JpaRepository + +interface CaseNoteRepository : JpaRepository { + + fun findByPersonIdAndTypeCode(offenderId: Long, code: String): List +} + diff --git a/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/NsiManager.kt b/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/NsiManager.kt index 973aaaa085..56c0852308 100644 --- a/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/NsiManager.kt +++ b/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/NsiManager.kt @@ -1,10 +1,6 @@ package uk.gov.justice.digital.hmpps.entity -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.Id -import jakarta.persistence.JoinColumn -import jakarta.persistence.ManyToOne +import jakarta.persistence.* import org.hibernate.annotations.Immutable import org.hibernate.annotations.SQLRestriction import org.springframework.data.jpa.repository.JpaRepository @@ -43,6 +39,7 @@ class NsiManager( @Entity @Immutable +@Table(name = "probation_area") class ProbationArea( @Id @Column(name = "probation_area_id") @@ -52,11 +49,20 @@ class ProbationArea( val code: String, @Column(name = "description") - val description: String + val description: String, + + @OneToOne + @JoinColumn( + name = "institution_id", + referencedColumnName = "institution_id", + updatable = false + ) + val institution: Institution? = null ) @Entity +@Table(name = "team") @Immutable class Team( @Id @@ -70,6 +76,21 @@ class Team( val description: String ) +@Immutable +@Entity +@Table(name = "r_institution") +class Institution( + @Id + @Column(name = "institution_id") + val id: Long, + + @Column(name = "nomis_cde_code") + val nomisCode: String + +) + +interface TeamRepository : JpaRepository + interface NsiManagerRepository : JpaRepository { @Query( diff --git a/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Staff.kt b/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Staff.kt index 4b68a2880d..8769e70f4e 100644 --- a/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Staff.kt +++ b/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Staff.kt @@ -1,14 +1,17 @@ package uk.gov.justice.digital.hmpps.entity -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.Id -import jakarta.persistence.JoinColumn -import jakarta.persistence.OneToOne -import jakarta.persistence.Table +import jakarta.persistence.* import org.hibernate.annotations.Immutable +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.ZonedDateTime -@Immutable +@EntityListeners(AuditingEntityListener::class) @Entity @Table(name = "staff") class Staff( @@ -20,14 +23,38 @@ class Staff( val surname: String, @Column(name = "forename2") - val middleName: String?, + val middleName: String? = null, @OneToOne(mappedBy = "staff") - val user: StaffUser?, + val user: StaffUser? = null, + + @Column(name = "probation_area_id") + val probationAreaId: Long, + + @CreatedDate + @Column(name = "created_datetime", updatable = false) + val createdDateTime: ZonedDateTime = ZonedDateTime.now(), + + @LastModifiedDate + @Column(name = "last_updated_datetime") + val lastModifiedDate: ZonedDateTime = ZonedDateTime.now(), + + @Column(name = "private", columnDefinition = "NUMBER", nullable = false) + var privateStaff: Boolean = false, + + @CreatedBy + @Column(name = "created_by_user_id", updatable = false) + var createdByUserId: Long = 0, + + @LastModifiedBy + @Column(name = "last_updated_user_id") + var lastModifiedUserId: Long = 0, @Id @Column(name = "staff_id") - val id: Long + @SequenceGenerator(name = "staff_id_seq", sequenceName = "staff_id_seq", allocationSize = 1) + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "staff_id_seq") + val id: Long = 0, ) @Entity @@ -46,3 +73,17 @@ class StaffUser( @Column(name = "user_id") val id: Long ) + +interface StaffRepository : JpaRepository { + + @Query( + """ + select officer_code from staff + where regexp_like(officer_code, ?1, 'i') + order by officer_code desc + fetch next 1 rows only + """, + nativeQuery = true + ) + fun getLatestStaffReference(regex: String): String? +} diff --git a/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/StaffTeam.kt b/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/StaffTeam.kt new file mode 100644 index 0000000000..ae93e39732 --- /dev/null +++ b/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/StaffTeam.kt @@ -0,0 +1,48 @@ +package uk.gov.justice.digital.hmpps.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 java.io.Serializable +import java.time.ZonedDateTime + +@EntityListeners(AuditingEntityListener::class) +@Entity +@Table(name = "staff_team") +@IdClass(StaffTeamId::class) +class StaffTeam( + + @Id + @Column(name = "staff_id") + val staffId: Long, + + @Id + @Column(name = "team_id") + val teamId: Long, + + @CreatedBy + @Column(name = "created_by_user_id", updatable = false) + var createdByUserId: Long = 0, + + @LastModifiedBy + @Column(name = "last_updated_user_id") + var lastModifiedUserId: Long = 0, + + @CreatedDate + @Column(name = "created_datetime", updatable = false) + var createdDateTime: ZonedDateTime = ZonedDateTime.now(), + + @LastModifiedDate + @Column(name = "last_updated_datetime") + var lastModifiedDate: ZonedDateTime = ZonedDateTime.now(), + + @Version + @Column(name = "row_version") + var version: Long = 0 +) + +data class StaffTeamId(val staffId: Long = 0, val teamId: Long = 0) : Serializable + diff --git a/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/CaseNoteService.kt b/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/CaseNoteService.kt new file mode 100644 index 0000000000..86ea6ca5ef --- /dev/null +++ b/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/CaseNoteService.kt @@ -0,0 +1,51 @@ +package uk.gov.justice.digital.hmpps.service + +import org.springframework.stereotype.Service +import uk.gov.justice.digital.hmpps.api.model.CreateContact +import uk.gov.justice.digital.hmpps.audit.service.AuditableService +import uk.gov.justice.digital.hmpps.audit.service.AuditedInteractionService +import uk.gov.justice.digital.hmpps.entity.* +import uk.gov.justice.digital.hmpps.exception.InvalidRequestException +import uk.gov.justice.digital.hmpps.exception.NotFoundException +import uk.gov.justice.digital.hmpps.exceptions.InvalidEstablishmentCodeException +import uk.gov.justice.digital.hmpps.model.StaffName + +@Service +class CaseNoteService( + auditedInteractionService: AuditedInteractionService, + private val caseNoteRepository: CaseNoteRepository, + private val personRepository: PersonRepository, + private val caseNoteTypeRepository: CaseNoteTypeRepository, + private val assignmentService: AssignmentService, +) : AuditableService(auditedInteractionService) { + + fun createContact(crn: String, createContact: CreateContact) = audit(BusinessInteractionCode.ADD_CONTACT) { + val person = personRepository.getByCrn(crn) + val type = caseNoteTypeRepository.getCode(createContact.type.code) + + try { + val prisonStaff = assignmentService.findAssignment( + createContact.author.prisonCode, + StaffName(createContact.author.forename, createContact.author.surname) + ) + val caseNote = CaseNote( + person = person, + date = createContact.dateTime.toLocalDate(), + startTime = createContact.dateTime, + notes = createContact.notes, + staffId = prisonStaff.third, + teamId = prisonStaff.second, + probationAreaId = prisonStaff.first, + type = type + ) + caseNoteRepository.save(caseNote) + } catch (ex: Exception) { + when (ex) { + is InvalidEstablishmentCodeException, + is NotFoundException -> throw InvalidRequestException(ex.message!!) + + else -> throw ex + } + } + } +} diff --git a/projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/services/OfficerCodeGenerator.kt b/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/OfficerCodeGenerator.kt similarity index 71% rename from projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/services/OfficerCodeGenerator.kt rename to projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/OfficerCodeGenerator.kt index afed8b0f33..2a7e11abdb 100644 --- a/projects/manage-pom-cases-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/services/OfficerCodeGenerator.kt +++ b/projects/resettlement-passport-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/OfficerCodeGenerator.kt @@ -1,8 +1,8 @@ -package uk.gov.justice.digital.hmpps.services +package uk.gov.justice.digital.hmpps.service import org.springframework.stereotype.Component -import uk.gov.justice.digital.hmpps.integrations.delius.exceptions.StaffCodeExhaustedException -import uk.gov.justice.digital.hmpps.integrations.delius.provider.entity.StaffRepository +import uk.gov.justice.digital.hmpps.entity.StaffRepository +import uk.gov.justice.digital.hmpps.exception.InvalidRequestException @Component class OfficerCodeGenerator(private val staffRepository: StaffRepository) { @@ -10,7 +10,7 @@ class OfficerCodeGenerator(private val staffRepository: StaffRepository) { fun generateFor(probationAreaCode: String, index: Int = 0): String { if (index == alphabet.size) { - throw StaffCodeExhaustedException(probationAreaCode) + throw InvalidRequestException("Cannot generate another staff id for probationAreaCode ${probationAreaCode}") } val prefix = probationAreaCode.substring(0, 3) + alphabet[index] val latest = staffRepository.getLatestStaffReference("^$prefix\\d{3}$") diff --git a/settings.gradle.kts b/settings.gradle.kts index 06fafbe21a..0445f66e40 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -53,7 +53,8 @@ include( "libs:messaging", "libs:oauth-client", "libs:oauth-server", - "libs:limited-access" + "libs:limited-access", + "libs:prison-staff" ) // load children from the "projects" directory (and drop the prefix)