Skip to content

Commit

Permalink
Merge pull request #2733 from ministryofjustice/CAS-1261-seed-job-cas…
Browse files Browse the repository at this point in the history
…3-referral-rejected

Create a seed job for CAS3 referral rejection
  • Loading branch information
muhammad-elabdulla authored Dec 19, 2024
2 parents 337f112 + 70ef572 commit 5cdc2a1
Show file tree
Hide file tree
Showing 10 changed files with 290 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import java.util.UUID
interface ReferralRejectionReasonRepository : JpaRepository<ReferralRejectionReasonEntity, UUID> {
@Query("SELECT m FROM ReferralRejectionReasonEntity m WHERE m.serviceScope = :serviceName OR m.serviceScope = '*' ORDER BY m.sortOrder")
fun findAllByServiceScope(serviceName: String): List<ReferralRejectionReasonEntity>

@Query("SELECT rr FROM ReferralRejectionReasonEntity rr WHERE rr.serviceScope = :serviceName AND rr.name = :name AND rr.isActive = true")
fun findByNameAndActive(name: String, serviceName: String): ReferralRejectionReasonEntity?
}

@Entity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import uk.gov.justice.digital.hmpps.approvedpremisesapi.seed.cas1.Cas1WithdrawPl
import uk.gov.justice.digital.hmpps.approvedpremisesapi.seed.cas2.Cas2ApplicationsSeedJob
import uk.gov.justice.digital.hmpps.approvedpremisesapi.seed.cas2.ExternalUsersSeedJob
import uk.gov.justice.digital.hmpps.approvedpremisesapi.seed.cas2.NomisUsersSeedJob
import uk.gov.justice.digital.hmpps.approvedpremisesapi.seed.cas3.Cas3ReferralRejectionSeedJob
import uk.gov.justice.digital.hmpps.approvedpremisesapi.seed.cas3.Cas3UsersSeedJob
import uk.gov.justice.digital.hmpps.approvedpremisesapi.seed.cas3.TemporaryAccommodationBedspaceSeedJob
import uk.gov.justice.digital.hmpps.approvedpremisesapi.seed.cas3.TemporaryAccommodationPremisesSeedJob
Expand Down Expand Up @@ -120,6 +121,7 @@ class SeedService(
SeedFileType.approvedPremisesSpacePlanningDryRun -> getBean(Cas1PlanSpacePlanningDryRunSeedJob::class)
SeedFileType.approvedPremisesImportDeliusBookingManagementData -> getBean(Cas1ImportDeliusBookingDataSeedJob::class)
SeedFileType.approvedPremisesUpdateSpaceBooking -> getBean(Cas1UpdateSpaceBookingSeedJob::class)
SeedFileType.temporaryAccommodationReferralRejection -> getBean(Cas3ReferralRejectionSeedJob::class)
}

val seedStarted = LocalDateTime.now()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package uk.gov.justice.digital.hmpps.approvedpremisesapi.seed.cas3

import org.slf4j.LoggerFactory
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Component
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.ServiceName
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.AssessmentDecision
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.AssessmentRepository
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ReferralRejectionReasonRepository
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.TemporaryAccommodationAssessmentEntity
import uk.gov.justice.digital.hmpps.approvedpremisesapi.seed.SeedJob
import java.time.OffsetDateTime
import java.util.UUID

@Component
class Cas3ReferralRejectionSeedJob(
private val assessmentRepository: AssessmentRepository,
private val referralRejectionReasonRepository: ReferralRejectionReasonRepository,
) : SeedJob<Cas3ReferralRejectionSeedCsvRow>(
requiredHeaders = setOf(
"assessment_id",
"rejection_reason",
"rejection_reason_detail",
"is_withdrawn",
),
runInTransaction = false,
) {
private val log = LoggerFactory.getLogger(this::class.java)

override fun deserializeRow(columns: Map<String, String>) = Cas3ReferralRejectionSeedCsvRow(
assessmentId = UUID.fromString(columns["assessment_id"]!!.trim()),
rejectionReason = columns["rejection_reason"]!!.trim(),
rejectionReasonDetail = columns["rejection_reason_detail"]!!.trim(),
isWithdrawn = columns["is_withdrawn"]!!.trim().equals("true", ignoreCase = true),
)

override fun processRow(row: Cas3ReferralRejectionSeedCsvRow) {
rejectAssessment(row)
}

@SuppressWarnings("TooGenericExceptionCaught")
private fun rejectAssessment(row: Cas3ReferralRejectionSeedCsvRow) {
val assessment =
assessmentRepository.findByIdOrNull(row.assessmentId) ?: error("Assessment with id ${row.assessmentId} not found")

if (assessment.reallocatedAt != null) {
error("The application has been reallocated, this assessment is read only")
}

if (assessment is TemporaryAccommodationAssessmentEntity) {
val rejectionReason = referralRejectionReasonRepository.findByNameAndActive(row.rejectionReason, ServiceName.temporaryAccommodation.value)
?: error("Rejection reason ${row.rejectionReason} not found")

try {
assessment.submittedAt = OffsetDateTime.now()
assessment.decision = AssessmentDecision.REJECTED
assessment.completedAt = null
assessment.referralRejectionReason = rejectionReason
assessment.referralRejectionReasonDetail = row.rejectionReasonDetail
assessment.isWithdrawn = row.isWithdrawn

assessmentRepository.save(assessment)
} catch (e: Throwable) {
log.error("Failed to update assessment with id ${row.assessmentId}", e)
error("Failed to update assessment with id ${row.assessmentId}")
}

log.info("Assessment with id ${row.assessmentId} has been successfully rejected")
} else {
error("Assessment with id ${row.assessmentId} is not a temporary accommodation assessment")
}
}
}

data class Cas3ReferralRejectionSeedCsvRow(
val assessmentId: UUID,
val rejectionReason: String,
val rejectionReasonDetail: String?,
val isWithdrawn: Boolean,
)
1 change: 1 addition & 0 deletions src/main/resources/static/_shared.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3732,6 +3732,7 @@ components:
- approved_premises_space_planning_dry_run
- approved_premises_import_delius_booking_management_data
- approved_premises_update_space_booking
- temporary_accommodation_referral_rejection
SeedFromExcelFileType:
type: string
enum:
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/static/codegen/built-api-spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8033,6 +8033,7 @@ components:
- approved_premises_space_planning_dry_run
- approved_premises_import_delius_booking_management_data
- approved_premises_update_space_booking
- temporary_accommodation_referral_rejection
SeedFromExcelFileType:
type: string
enum:
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/static/codegen/built-cas1-api-spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4954,6 +4954,7 @@ components:
- approved_premises_space_planning_dry_run
- approved_premises_import_delius_booking_management_data
- approved_premises_update_space_booking
- temporary_accommodation_referral_rejection
SeedFromExcelFileType:
type: string
enum:
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/static/codegen/built-cas2-api-spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4323,6 +4323,7 @@ components:
- approved_premises_space_planning_dry_run
- approved_premises_import_delius_booking_management_data
- approved_premises_update_space_booking
- temporary_accommodation_referral_rejection
SeedFromExcelFileType:
type: string
enum:
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/static/codegen/built-cas3-api-spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3831,6 +3831,7 @@ components:
- approved_premises_space_planning_dry_run
- approved_premises_import_delius_booking_management_data
- approved_premises_update_space_booking
- temporary_accommodation_referral_rejection
SeedFromExcelFileType:
type: string
enum:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package uk.gov.justice.digital.hmpps.approvedpremisesapi.integration.seed.cas3

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.data.repository.findByIdOrNull
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.SeedFileType
import uk.gov.justice.digital.hmpps.approvedpremisesapi.integration.givens.givenAUser
import uk.gov.justice.digital.hmpps.approvedpremisesapi.integration.seed.SeedTestBase
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.AssessmentDecision
import uk.gov.justice.digital.hmpps.approvedpremisesapi.seed.CsvBuilder
import uk.gov.justice.digital.hmpps.approvedpremisesapi.seed.cas3.Cas3ReferralRejectionSeedCsvRow
import uk.gov.justice.digital.hmpps.approvedpremisesapi.util.randomStringLowerCase
import java.time.OffsetDateTime

class Cas3ReferralRejectionSeedJobTest : SeedTestBase() {
@Test
fun `Reject an assessment update the assessment status to Rejected`() {
val user = givenAUser().first

val applicationSchema = temporaryAccommodationApplicationJsonSchemaEntityFactory.produceAndPersist {
withPermissiveSchema()
}

val assessmentSchema = temporaryAccommodationAssessmentJsonSchemaEntityFactory.produceAndPersist {
withPermissiveSchema()
}

val application = temporaryAccommodationApplicationEntityFactory.produceAndPersist {
withProbationRegion(user.probationRegion)
withCreatedByUser(user)
withApplicationSchema(applicationSchema)
withSubmittedAt(OffsetDateTime.now().minusDays(10))
}

val assessment = temporaryAccommodationAssessmentEntityFactory.produceAndPersist {
withApplication(application)
withAllocatedToUser(user)
withAssessmentSchema(assessmentSchema)
}

val rejectedReason = "Another reason (please add)"
val rejectedReasonDetail = randomStringLowerCase(30)

withCsv(
"cas3-referral-rejection-csv",
rowsToCsv(listOf(Cas3ReferralRejectionSeedCsvRow(assessment.id, rejectedReason, rejectedReasonDetail, false))),
)

seedService.seedData(SeedFileType.temporaryAccommodationReferralRejection, "cas3-referral-rejection-csv.csv")

val persistedAssessment = temporaryAccommodationAssessmentRepository.findByIdOrNull(assessment.id)!!
assertThat(persistedAssessment).isNotNull
assertThat(persistedAssessment.decision).isEqualTo(AssessmentDecision.REJECTED)
assertThat(persistedAssessment.completedAt).isNull()
assertThat(persistedAssessment.referralRejectionReason?.name).isEqualTo(rejectedReason)
assertThat(persistedAssessment.referralRejectionReasonDetail).isEqualTo(rejectedReasonDetail)
assertThat(persistedAssessment.isWithdrawn).isFalse()
}

private fun rowsToCsv(rows: List<Cas3ReferralRejectionSeedCsvRow>): String {
val builder = CsvBuilder()
.withUnquotedFields(
"assessment_id",
"rejection_reason",
"rejection_reason_detail",
"is_withdrawn",
)
.newRow()

rows.forEach {
builder
.withQuotedFields(it.assessmentId, it.rejectionReason, it.rejectionReasonDetail!!, it.isWithdrawn)
.newRow()
}

return builder.build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package uk.gov.justice.digital.hmpps.approvedpremisesapi.unit.seed.cas3

import io.mockk.every
import io.mockk.mockk
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.jupiter.api.Test
import org.springframework.data.repository.findByIdOrNull
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.ServiceName
import uk.gov.justice.digital.hmpps.approvedpremisesapi.factory.ApprovedPremisesApplicationEntityFactory
import uk.gov.justice.digital.hmpps.approvedpremisesapi.factory.ApprovedPremisesAssessmentEntityFactory
import uk.gov.justice.digital.hmpps.approvedpremisesapi.factory.TemporaryAccommodationApplicationEntityFactory
import uk.gov.justice.digital.hmpps.approvedpremisesapi.factory.TemporaryAccommodationAssessmentEntityFactory
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.AssessmentRepository
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ReferralRejectionReasonEntity
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ReferralRejectionReasonRepository
import uk.gov.justice.digital.hmpps.approvedpremisesapi.seed.cas3.Cas3ReferralRejectionSeedCsvRow
import uk.gov.justice.digital.hmpps.approvedpremisesapi.seed.cas3.Cas3ReferralRejectionSeedJob
import uk.gov.justice.digital.hmpps.approvedpremisesapi.util.randomStringLowerCase
import java.time.OffsetDateTime
import java.util.UUID

class Cas3ReferralRejectionSeedJobTest {
private val assessmentRepository = mockk<AssessmentRepository>()
private val referralRejectionReasonRepository = mockk<ReferralRejectionReasonRepository>()

private val seedJob = Cas3ReferralRejectionSeedJob(
assessmentRepository = assessmentRepository,
referralRejectionReasonRepository = referralRejectionReasonRepository,
)

@Test
fun `When the assessment is not Temporary Accommodation assessment expect error`() {
val assessmentId = UUID.randomUUID()

every { assessmentRepository.findByIdOrNull(assessmentId) } returns
ApprovedPremisesAssessmentEntityFactory()
.withApplication(
ApprovedPremisesApplicationEntityFactory()
.withDefaults()
.produce(),
)
.produce()

assertThatThrownBy {
seedJob.processRow(Cas3ReferralRejectionSeedCsvRow(assessmentId, "rejection reason", null, false))
}.hasMessage("Assessment with id $assessmentId is not a temporary accommodation assessment")
}

@Test
fun `When an assessment doesn't exist expect error`() {
val assessmentId = UUID.randomUUID()

every { assessmentRepository.findByIdOrNull(assessmentId) } returns null

assertThatThrownBy {
seedJob.processRow(Cas3ReferralRejectionSeedCsvRow(assessmentId, "rejection reason", randomStringLowerCase(20), false))
}.hasMessage("Assessment with id $assessmentId not found")
}

@Test
fun `When the application has been allocated expect error`() {
val assessmentId = UUID.randomUUID()

every { assessmentRepository.findByIdOrNull(assessmentId) } returns
TemporaryAccommodationAssessmentEntityFactory()
.withApplication(
TemporaryAccommodationApplicationEntityFactory()
.withDefaults()
.produce(),
)
.withReallocatedAt(OffsetDateTime.now())
.produce()

assertThatThrownBy {
seedJob.processRow(Cas3ReferralRejectionSeedCsvRow(assessmentId, "rejection reason", null, false))
}.hasMessage("The application has been reallocated, this assessment is read only")
}

@Test
fun `When the rejection reason doesn't exist expect error`() {
val assessmentId = UUID.randomUUID()
val notExistRejectionReason = "not exist rejection reason"

every { assessmentRepository.findByIdOrNull(assessmentId) } returns
TemporaryAccommodationAssessmentEntityFactory()
.withApplication(
TemporaryAccommodationApplicationEntityFactory()
.withDefaults()
.produce(),
)
.produce()

every { referralRejectionReasonRepository.findByNameAndActive(notExistRejectionReason, ServiceName.temporaryAccommodation.value) } returns null

assertThatThrownBy {
seedJob.processRow(Cas3ReferralRejectionSeedCsvRow(assessmentId, notExistRejectionReason, null, false))
}.hasMessage("Rejection reason $notExistRejectionReason not found")
}

@Test
fun `When save an assessment and an exception happened expect logging error`() {
val assessmentId = UUID.randomUUID()
val assessment = TemporaryAccommodationAssessmentEntityFactory()
.withApplication(
TemporaryAccommodationApplicationEntityFactory()
.withDefaults()
.produce(),
)
.produce()

every { assessmentRepository.findByIdOrNull(assessmentId) } returns assessment

every { referralRejectionReasonRepository.findByNameAndActive("rejection reason", ServiceName.temporaryAccommodation.value) } returns
ReferralRejectionReasonEntity(UUID.randomUUID(), "rejection reason", true, ServiceName.temporaryAccommodation.value, 1)

every { assessmentRepository.save(any()) } throws RuntimeException("Failed to update assessment with id $assessmentId")

assertThatThrownBy {
seedJob.processRow(Cas3ReferralRejectionSeedCsvRow(assessmentId, "rejection reason", null, false))
}.hasMessage("Failed to update assessment with id $assessmentId")
}
}

0 comments on commit 5cdc2a1

Please sign in to comment.