Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

VB-5220 - Migrate a prisoner balance #50

Merged
merged 13 commits into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
import uk.gov.justice.digital.hmpps.visitallocationapi.dto.nomis.VisitAllocationPrisonerMigrationDto
import uk.gov.justice.digital.hmpps.visitallocationapi.dto.nomis.VisitAllocationPrisonerSyncDto
import uk.gov.justice.digital.hmpps.visitallocationapi.service.NomisSyncService
import uk.gov.justice.hmpps.kotlin.common.ErrorResponse

const val VO_NOMIS = "/visits/allocation/prisoner"
const val VO_PRISONER_MIGRATION: String = "$VO_NOMIS/migrate"
const val VO_PRISONER_SYNC: String = "$VO_NOMIS/sync"

@RestController
class NomisSyncController {
class NomisSyncController(val nomisSyncService: NomisSyncService) {
@PreAuthorize("hasRole('ROLE_VISIT_ALLOCATION_API__NOMIS_API')")
@PostMapping(VO_PRISONER_MIGRATION)
@Operation(
Expand All @@ -43,7 +44,10 @@ class NomisSyncController {
),
],
)
fun migratePrisonerVisitOrders(@RequestBody @Valid visitAllocationPrisonerMigrationDto: VisitAllocationPrisonerMigrationDto): ResponseEntity<Void> = ResponseEntity.status(HttpStatus.OK).build()
fun migratePrisonerVisitOrders(@RequestBody @Valid visitAllocationPrisonerMigrationDto: VisitAllocationPrisonerMigrationDto): ResponseEntity<Void> {
nomisSyncService.migratePrisoner(visitAllocationPrisonerMigrationDto)
return ResponseEntity.status(HttpStatus.OK).build()
}

@PreAuthorize("hasRole('ROLE_VISIT_ALLOCATION_API__NOMIS_API')")
@PostMapping(VO_PRISONER_SYNC)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotEmpty
import jakarta.validation.constraints.NotNull
import uk.gov.justice.digital.hmpps.visitallocationapi.enums.nomis.AdjustmentReasonCode
import uk.gov.justice.digital.hmpps.visitallocationapi.enums.nomis.ChangeSource
import uk.gov.justice.digital.hmpps.visitallocationapi.enums.nomis.ChangeLogSource
import java.time.LocalDate

data class VisitAllocationPrisonerSyncDto(
Expand Down Expand Up @@ -39,7 +39,7 @@ data class VisitAllocationPrisonerSyncDto(

@Schema(description = "The source of the change being made", example = "SYSTEM or STAFF", required = true)
@field:NotNull
val changeSource: ChangeSource,
val changeLogSource: ChangeLogSource,

@Schema(description = "Additional information on the sync reason", example = "Manually adjusted for phone credit", required = true)
@field:NotEmpty
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package uk.gov.justice.digital.hmpps.visitallocationapi.enums

@Suppress("unused")
enum class ChangeLogType {
MIGRATION,
// TODO: Add other change types as they're needed.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package uk.gov.justice.digital.hmpps.visitallocationapi.enums

@Suppress("unused")
enum class NegativeVisitOrderStatus {
USED,
REPAID,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package uk.gov.justice.digital.hmpps.visitallocationapi.enums

@Suppress("unused")
enum class NegativeVisitOrderType {
NEGATIVE_VO,
NEGATIVE_PVO,
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,5 @@ enum class VisitOrderStatus {
AVAILABLE,
EXPIRED,
ACCUMULATED,
SCHEDULED,
USED,
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package uk.gov.justice.digital.hmpps.visitallocationapi.enums.nomis

@Suppress("unused")
enum class ChangeSource {
enum class ChangeLogSource {
SYSTEM,
STAFF,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package uk.gov.justice.digital.hmpps.visitallocationapi.model.entity

import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.Table
import uk.gov.justice.digital.hmpps.visitallocationapi.enums.ChangeLogType
import uk.gov.justice.digital.hmpps.visitallocationapi.enums.nomis.ChangeLogSource
import java.time.LocalDateTime

@Entity
@Table(name = "CHANGE_LOG")
data class ChangeLog(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0L,

@Column(nullable = false)
val prisonerId: String,

@Column(nullable = false)
val changeTimestamp: LocalDateTime = LocalDateTime.now(),

@Column(nullable = false)
@Enumerated(EnumType.STRING)
val changeType: ChangeLogType,

@Column(nullable = false)
@Enumerated(EnumType.STRING)
val changeSource: ChangeLogSource,

@Column(nullable = false)
val userId: String,

@Column(nullable = false)
val comment: String? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package uk.gov.justice.digital.hmpps.visitallocationapi.model.entity

import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.Table
import uk.gov.justice.digital.hmpps.visitallocationapi.enums.NegativeVisitOrderStatus
import uk.gov.justice.digital.hmpps.visitallocationapi.enums.NegativeVisitOrderType
import java.time.LocalDate
import java.time.LocalDateTime

@Entity
@Table(name = "NEGATIVE_VISIT_ORDER")
data class NegativeVisitOrder(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0L,

@Column(nullable = false)
val prisonerId: String,

@Column(nullable = false)
@Enumerated(EnumType.STRING)
val status: NegativeVisitOrderStatus,

@Column(nullable = false)
@Enumerated(EnumType.STRING)
val type: NegativeVisitOrderType,

@Column(nullable = false)
val createdTimestamp: LocalDateTime = LocalDateTime.now(),

@Column(nullable = false)
val repaidDate: LocalDate? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package uk.gov.justice.digital.hmpps.visitallocationapi.model.entity

import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.Id
import jakarta.persistence.Table
import java.time.LocalDate

@Entity
@Table(name = "PRISONER_DETAILS")
data class PrisonerDetails(

@Id
@Column(nullable = false)
val prisonerId: String,

@Column(nullable = false)
val lastAllocatedDate: LocalDate,
)
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import jakarta.persistence.Table
import uk.gov.justice.digital.hmpps.visitallocationapi.enums.VisitOrderStatus
import uk.gov.justice.digital.hmpps.visitallocationapi.enums.VisitOrderType
import java.time.LocalDate
import java.time.LocalDateTime

@Entity
@Table(name = "VISIT_ORDER")
Expand All @@ -31,7 +32,7 @@ data class VisitOrder(
val status: VisitOrderStatus,

@Column(nullable = false)
val createdDate: LocalDate = LocalDate.now(),
val createdTimestamp: LocalDateTime = LocalDateTime.now(),

@Column(nullable = false)
val expiryDate: LocalDate? = null,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package uk.gov.justice.digital.hmpps.visitallocationapi.repository

import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import uk.gov.justice.digital.hmpps.visitallocationapi.model.entity.ChangeLog

@Repository
interface ChangeLogRepository : JpaRepository<ChangeLog, Long>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package uk.gov.justice.digital.hmpps.visitallocationapi.repository

import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import uk.gov.justice.digital.hmpps.visitallocationapi.model.entity.NegativeVisitOrder

@Repository
interface NegativeVisitOrderRepository : JpaRepository<NegativeVisitOrder, Long>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package uk.gov.justice.digital.hmpps.visitallocationapi.repository

import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import uk.gov.justice.digital.hmpps.visitallocationapi.model.entity.PrisonerDetails

@Repository
interface PrisonerDetailsRepository : JpaRepository<PrisonerDetails, Long>
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ import org.springframework.transaction.annotation.Transactional
import uk.gov.justice.digital.hmpps.visitallocationapi.enums.VisitOrderStatus
import uk.gov.justice.digital.hmpps.visitallocationapi.enums.VisitOrderType
import uk.gov.justice.digital.hmpps.visitallocationapi.model.entity.VisitOrder
import java.time.LocalDate
import java.time.LocalDateTime

@Repository
interface VisitOrderRepository : JpaRepository<VisitOrder, Long> {
@Query(
"SELECT vo.createdDate FROM VisitOrder vo WHERE vo.prisonerId = :prisonerId AND vo.type = :type ORDER BY vo.createdDate DESC LIMIT 1",
"SELECT vo.createdTimestamp FROM VisitOrder vo WHERE vo.prisonerId = :prisonerId AND vo.type = :type ORDER BY vo.createdTimestamp DESC LIMIT 1",
)
fun findLastAllocatedDate(
prisonerId: String,
type: VisitOrderType,
): LocalDate?
): LocalDateTime?

@Query(
"SELECT COUNT (vo) FROM VisitOrder vo WHERE vo.prisonerId = :prisonerId AND vo.type = :type AND vo.status = :status",
Expand All @@ -40,7 +40,7 @@ interface VisitOrderRepository : JpaRepository<VisitOrder, Long> {
WHERE prisoner_id = :prisonerId
AND type = 'VO'
AND status = 'ACCUMULATED'
ORDER BY created_date ASC
ORDER BY created_timestamp ASC
LIMIT :amount)
""",
nativeQuery = true,
Expand All @@ -59,7 +59,7 @@ interface VisitOrderRepository : JpaRepository<VisitOrder, Long> {
WHERE prisoner_id = :prisonerId
AND type = 'PVO'
AND status = 'AVAILABLE'
AND created_date < CURRENT_DATE - INTERVAL '28 days'
AND created_timestamp < CURRENT_TIMESTAMP - INTERVAL '28 days'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

created_timestamp::date < CURRENT_TIMESTAMP::date will set the time to start of day before doing this comparison or the time element might come into play. Might be worth adding a test to confirm.

""",
nativeQuery = true,
)
Expand All @@ -76,7 +76,7 @@ interface VisitOrderRepository : JpaRepository<VisitOrder, Long> {
WHERE prisoner_id = :prisonerId
AND type = :#{#type.name()}
AND status = 'AVAILABLE'
AND created_date < CURRENT_DATE - INTERVAL '28 days'
AND created_timestamp < CURRENT_TIMESTAMP - INTERVAL '28 days'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

""",
nativeQuery = true,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import uk.gov.justice.digital.hmpps.visitallocationapi.enums.VisitOrderType
import uk.gov.justice.digital.hmpps.visitallocationapi.model.entity.VisitOrder
import uk.gov.justice.digital.hmpps.visitallocationapi.repository.VisitOrderAllocationPrisonJobRepository
import uk.gov.justice.digital.hmpps.visitallocationapi.repository.VisitOrderRepository
import java.time.LocalDate
import java.time.LocalDateTime

@Transactional
Expand Down Expand Up @@ -116,13 +115,13 @@ class AllocationService(
prisonerId = prisonerId,
type = type,
status = VisitOrderStatus.AVAILABLE,
createdDate = LocalDate.now(),
createdTimestamp = LocalDateTime.now(),
expiryDate = null,
)

private fun isDueVO(prisonerId: String): Boolean {
val lastVODate = visitOrderRepository.findLastAllocatedDate(prisonerId, VisitOrderType.VO)
return lastVODate == null || lastVODate <= LocalDate.now().minusDays(14)
return lastVODate == null || lastVODate <= LocalDateTime.now().minusDays(14)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this still be LocalDate.now()?

}

private fun isDuePVO(prisonerId: String): Boolean {
Expand All @@ -133,7 +132,7 @@ class AllocationService(
return isDueVO(prisonerId)
}

return lastPVODate <= LocalDate.now().minusDays(28)
return lastPVODate <= LocalDateTime.now().minusDays(28)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

}

private fun generateVos(prisoner: PrisonerDto, prisonIncentivesForPrisonerLevel: PrisonIncentiveAmountsDto): List<VisitOrder> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package uk.gov.justice.digital.hmpps.visitallocationapi.service

import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import uk.gov.justice.digital.hmpps.visitallocationapi.dto.nomis.VisitAllocationPrisonerMigrationDto
import uk.gov.justice.digital.hmpps.visitallocationapi.enums.ChangeLogType
import uk.gov.justice.digital.hmpps.visitallocationapi.enums.nomis.ChangeLogSource
import uk.gov.justice.digital.hmpps.visitallocationapi.model.entity.ChangeLog
import uk.gov.justice.digital.hmpps.visitallocationapi.repository.ChangeLogRepository

@Transactional
@Service
class ChangeLogService(private val changeLogRepository: ChangeLogRepository) {
companion object {
val LOG: Logger = LoggerFactory.getLogger(this::class.java)
}

fun logMigrationChange(migrationChangeDto: VisitAllocationPrisonerMigrationDto) {
LOG.info("Logging change to change_log table for prisoner ${migrationChangeDto.prisonerId}, change - $migrationChangeDto")
changeLogRepository.save(
ChangeLog(
prisonerId = migrationChangeDto.prisonerId,
changeType = ChangeLogType.MIGRATION,
changeSource = ChangeLogSource.SYSTEM,
userId = "SYSTEM",
comment = "migrated prisoner ${migrationChangeDto.prisonerId}, with vo balance ${migrationChangeDto.voBalance} and pvo balance ${migrationChangeDto.pvoBalance} and lastAllocatedDate ${migrationChangeDto.lastVoAllocationDate}",
),
)
}
}
Loading
Loading