diff --git a/server/model/adjustmentsHubViewModel.ts b/server/model/adjustmentsHubViewModel.ts
index c28ceaa2..86c5821b 100644
--- a/server/model/adjustmentsHubViewModel.ts
+++ b/server/model/adjustmentsHubViewModel.ts
@@ -1,5 +1,7 @@
import { Adjustment } from '../@types/adjustments/adjustmentsTypes'
import { IdentifyRemandDecision, RemandResult } from '../@types/identifyRemandPeriods/identifyRemandPeriodsTypes'
+import config from '../config'
+import { UnusedDeductionMessageType } from '../services/unusedDeductionsService'
import { calculateReleaseDatesCheckInformationUrl } from '../utils/utils'
import adjustmentTypes, { AdjustmentType } from './adjustmentTypes'
@@ -13,6 +15,8 @@ export type Message = {
export type MessageAction = 'CREATE' | 'REMOVE' | 'UPDATE' | 'REJECTED' | 'VALIDATION'
export default class AdjustmentsHubViewModel {
+ public checkInformationLink: string
+
constructor(
public prisonerNumber: string,
public adjustments: Adjustment[],
@@ -20,8 +24,10 @@ export default class AdjustmentsHubViewModel {
public remandDecision: IdentifyRemandDecision,
public roles: string[],
public message: Message,
- public serviceHasCalculatedUnusedDeductions: boolean,
- ) {}
+ public unusedDeductionMessage: UnusedDeductionMessageType,
+ ) {
+ this.checkInformationLink = `${config.services.calculateReleaseDatesUI.url}/calculation/${this.prisonerNumber}/check-information?hasErrors=true`
+ }
public deductions(): AdjustmentType[] {
return adjustmentTypes.filter(it =>
diff --git a/server/routes/adjustmentRoutes.test.ts b/server/routes/adjustmentRoutes.test.ts
index c9cb97a3..80fec2e2 100644
--- a/server/routes/adjustmentRoutes.test.ts
+++ b/server/routes/adjustmentRoutes.test.ts
@@ -129,7 +129,7 @@ describe('Adjustment routes tests', () => {
unusedDeductions,
])
identifyRemandPeriodsService.calculateRelevantRemand.mockResolvedValue(remandResult)
- unusedDeductionsService.serviceHasCalculatedUnusedDeductions.mockResolvedValue(true)
+ unusedDeductionsService.getCalculatedUnusedDeductionsMessage.mockResolvedValue('NONE')
additionalDaysAwardedService.shouldIntercept.mockResolvedValue({
type: 'NONE',
number: 0,
@@ -147,14 +147,14 @@ describe('Adjustment routes tests', () => {
expect(res.text).toContain('including 10 days unused')
})
})
- it('GET /{nomsId} hub unused deductions cannot be calculated', () => {
+ it('GET /{nomsId} hub unused deductions cannot be calculated because of unsupported sentence type', () => {
prisonerService.getStartOfSentenceEnvelope.mockResolvedValue({
earliestExcludingRecalls: new Date(),
earliestSentence: new Date(),
})
adjustmentsService.findByPerson.mockResolvedValue([remandAdjustment])
identifyRemandPeriodsService.calculateRelevantRemand.mockResolvedValue(remandResult)
- unusedDeductionsService.serviceHasCalculatedUnusedDeductions.mockResolvedValue(false)
+ unusedDeductionsService.getCalculatedUnusedDeductionsMessage.mockResolvedValue('UNSUPPORTED')
additionalDaysAwardedService.shouldIntercept.mockResolvedValue({
type: 'NONE',
number: 0,
@@ -164,7 +164,75 @@ describe('Adjustment routes tests', () => {
.get(`/${NOMS_ID}`)
.expect('Content-Type', /html/)
.expect(res => {
- expect(res.text).toContain('Unused remand/tagged bail time cannot be calculated')
+ expect(res.text).toContain(
+ 'Some of the details recorded in NOMIS cannot be used for a sentence calculation. This means unused deductions cannot be automatically calculated by this service. To add any unused remand, go to the sentence adjustments screen in NOMIS.',
+ )
+ })
+ })
+ it('GET /{nomsId} hub unused deductions cannot be calculated because of validation error', () => {
+ prisonerService.getStartOfSentenceEnvelope.mockResolvedValue({
+ earliestExcludingRecalls: new Date(),
+ earliestSentence: new Date(),
+ })
+ adjustmentsService.findByPerson.mockResolvedValue([remandAdjustment])
+ identifyRemandPeriodsService.calculateRelevantRemand.mockResolvedValue(remandResult)
+ unusedDeductionsService.getCalculatedUnusedDeductionsMessage.mockResolvedValue('VALIDATION')
+ additionalDaysAwardedService.shouldIntercept.mockResolvedValue({
+ type: 'NONE',
+ number: 0,
+ anyProspective: false,
+ })
+ return request(app)
+ .get(`/${NOMS_ID}`)
+ .expect('Content-Type', /html/)
+ .expect(res => {
+ expect(res.text).toContain(
+ 'Some of the data in NOMIS related to this person is incorrect. This means unused deductions cannot be automatically calculated.',
+ )
+ })
+ })
+ it('GET /{nomsId} hub unused deductions cannot be calculated because its a nomis adjustment', () => {
+ prisonerService.getStartOfSentenceEnvelope.mockResolvedValue({
+ earliestExcludingRecalls: new Date(),
+ earliestSentence: new Date(),
+ })
+ adjustmentsService.findByPerson.mockResolvedValue([remandAdjustment])
+ identifyRemandPeriodsService.calculateRelevantRemand.mockResolvedValue(remandResult)
+ unusedDeductionsService.getCalculatedUnusedDeductionsMessage.mockResolvedValue('NOMIS_ADJUSTMENT')
+ additionalDaysAwardedService.shouldIntercept.mockResolvedValue({
+ type: 'NONE',
+ number: 0,
+ anyProspective: false,
+ })
+ return request(app)
+ .get(`/${NOMS_ID}`)
+ .expect('Content-Type', /html/)
+ .expect(res => {
+ expect(res.text).toContain(
+ 'Existing deductions have been added on NOMIS. This means unused deductions cannot be automatically calculated. To add any unused remand, go to the sentence adjustments screen in NOMIS.',
+ )
+ })
+ })
+ it('GET /{nomsId} hub unused deductions cannot be calculated because of an exception', () => {
+ prisonerService.getStartOfSentenceEnvelope.mockResolvedValue({
+ earliestExcludingRecalls: new Date(),
+ earliestSentence: new Date(),
+ })
+ adjustmentsService.findByPerson.mockResolvedValue([remandAdjustment])
+ identifyRemandPeriodsService.calculateRelevantRemand.mockResolvedValue(remandResult)
+ unusedDeductionsService.getCalculatedUnusedDeductionsMessage.mockResolvedValue('UNKNOWN')
+ additionalDaysAwardedService.shouldIntercept.mockResolvedValue({
+ type: 'NONE',
+ number: 0,
+ anyProspective: false,
+ })
+ return request(app)
+ .get(`/${NOMS_ID}`)
+ .expect('Content-Type', /html/)
+ .expect(res => {
+ expect(res.text).toContain(
+ 'Unused remand/tagged bail time cannot be calculated. Any unused deductions must be entered in NOMIS.',
+ )
})
})
it('GET /{nomsId} with remand role', () => {
@@ -178,7 +246,7 @@ describe('Adjustment routes tests', () => {
})
identifyRemandPeriodsService.calculateRelevantRemand.mockResolvedValue(remandResult)
additionalDaysAwardedService.shouldIntercept.mockResolvedValue({ type: 'NONE', number: 0, anyProspective: false })
- unusedDeductionsService.serviceHasCalculatedUnusedDeductions.mockResolvedValue(true)
+ unusedDeductionsService.getCalculatedUnusedDeductionsMessage.mockResolvedValue('NONE')
return request(app)
.get(`/${NOMS_ID}`)
.expect('Content-Type', /html/)
diff --git a/server/routes/adjustmentRoutes.ts b/server/routes/adjustmentRoutes.ts
index f5109ea0..7aba1259 100644
--- a/server/routes/adjustmentRoutes.ts
+++ b/server/routes/adjustmentRoutes.ts
@@ -18,7 +18,7 @@ import FullPageError from '../model/FullPageError'
import { daysBetween } from '../utils/utils'
import RecallModel from '../model/recallModel'
import RecallForm from '../model/recallForm'
-import UnusedDeductionsService from '../services/unusedDeductionsService'
+import UnusedDeductionsService, { UnusedDeductionMessageType } from '../services/unusedDeductionsService'
import { Adjustment } from '../@types/adjustments/adjustmentsTypes'
export default class AdjustmentRoutes {
@@ -54,14 +54,11 @@ export default class AdjustmentRoutes {
const message = req.flash('message')
const messageExists = message && message[0]
- let serviceHasCalculatedUnusedDeductions = true
+ let unusedDeductionMessage: UnusedDeductionMessageType = 'NONE'
if (messageExists) {
this.adjustmentsStoreService.clear(req, nomsId)
- serviceHasCalculatedUnusedDeductions = await this.unusedDeductionsService.waitUntilUnusedRemandCreated(
- nomsId,
- token,
- )
+ unusedDeductionMessage = await this.unusedDeductionsService.waitUntilUnusedRemandCreated(nomsId, token)
}
const adjustments = await this.adjustmentsService.findByPerson(
@@ -71,7 +68,7 @@ export default class AdjustmentRoutes {
)
if (!messageExists) {
- serviceHasCalculatedUnusedDeductions = await this.unusedDeductionsService.serviceHasCalculatedUnusedDeductions(
+ unusedDeductionMessage = await this.unusedDeductionsService.getCalculatedUnusedDeductionsMessage(
nomsId,
adjustments,
token,
@@ -107,7 +104,7 @@ export default class AdjustmentRoutes {
remandDecision,
roles,
message && message[0] && (JSON.parse(message[0]) as Message),
- serviceHasCalculatedUnusedDeductions,
+ unusedDeductionMessage,
),
})
}
diff --git a/server/services/unusedDeductionsService.ts b/server/services/unusedDeductionsService.ts
index 22a7365b..acf4da8a 100644
--- a/server/services/unusedDeductionsService.ts
+++ b/server/services/unusedDeductionsService.ts
@@ -3,6 +3,8 @@ import { delay } from '../utils/utils'
import AdjustmentsService from './adjustmentsService'
import CalculateReleaseDatesService from './calculateReleaseDatesService'
+export type UnusedDeductionMessageType = 'NOMIS_ADJUSTMENT' | 'VALIDATION' | 'UNSUPPORTED' | 'UNKNOWN' | 'NONE'
+
export default class UnusedDeductionsService {
private maxTries = 6 // 3 seconds max wait
@@ -18,18 +20,13 @@ export default class UnusedDeductionsService {
}
/* Wait until calclulated unused deductions matches with adjustments database. */
- async waitUntilUnusedRemandCreated(nomsId: string, token: string): Promise