From 49473a67dd8002e2672dcb38e70dac74936817b2 Mon Sep 17 00:00:00 2001 From: ldlharper Date: Thu, 19 Sep 2024 15:00:26 +0100 Subject: [PATCH] ADJST-834 Making manual journey blocked for review remand tool. (#411) --- server/model/remandViewModel.ts | 11 ++++ server/routes/index.ts | 1 + server/routes/remandRoutes.test.ts | 50 ++++++++++++++++++- server/routes/remandRoutes.ts | 15 +++++- .../remand/partials/remandCard.njk | 28 ++++++----- .../views/pages/adjustments/remand/view.njk | 14 +++--- 6 files changed, 98 insertions(+), 21 deletions(-) diff --git a/server/model/remandViewModel.ts b/server/model/remandViewModel.ts index ca04d31a..a3130988 100644 --- a/server/model/remandViewModel.ts +++ b/server/model/remandViewModel.ts @@ -4,6 +4,7 @@ import { PrisonApiOffenderSentenceAndOffences } from '../@types/prisonApi/prison import { offencesForRemandAdjustment } from '../utils/utils' import UnusedDeductionsMessageViewModel from './unusedDeductionsMessageViewModel' import { UnusedDeductionMessageType } from '../services/unusedDeductionsService' +import { IdentifyRemandDecision } from '../@types/identifyRemandPeriods/identifyRemandPeriodsTypes' export default class RemandViewModel { public adjustments: Adjustment[] @@ -16,6 +17,8 @@ export default class RemandViewModel { private sentencesAndOffences: PrisonApiOffenderSentenceAndOffences[], unusedDeductionsMessageType: UnusedDeductionMessageType, inactiveWhenDeletedAdjustments: Adjustment[], + public roles: string[], + public remandDecision: IdentifyRemandDecision, ) { this.adjustments = allAdjustments.filter(it => it.adjustmentType === 'REMAND') this.unusedDeductionMessage = new UnusedDeductionsMessageViewModel( @@ -51,4 +54,12 @@ export default class RemandViewModel { public totalDays() { return this.adjustments.reduce((sum, it) => sum + it.days, 0) } + + public readonly() { + return this.hasIdentifyRemandRole() && this.remandDecision?.accepted !== false + } + + private hasIdentifyRemandRole(): boolean { + return this.roles.indexOf('REMAND_IDENTIFIER') !== -1 + } } diff --git a/server/routes/index.ts b/server/routes/index.ts index d4161343..19206da6 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -31,6 +31,7 @@ export default function routes(service: Services): Router { service.calculateReleaseDatesService, service.paramStoreService, service.unusedDeductionsService, + service.identifyRemandPeriodsService, ) const additionalDaysAwardedRoutes = new AdditionalDaysAwardedRoutes( diff --git a/server/routes/remandRoutes.test.ts b/server/routes/remandRoutes.test.ts index aaa4536c..e6c499ab 100644 --- a/server/routes/remandRoutes.test.ts +++ b/server/routes/remandRoutes.test.ts @@ -1,7 +1,7 @@ import dayjs from 'dayjs' import type { Express } from 'express' import request from 'supertest' -import { appWithAllRoutes } from './testutils/appSetup' +import { appWithAllRoutes, user } from './testutils/appSetup' import PrisonerService from '../services/prisonerService' import AdjustmentsService from '../services/adjustmentsService' import { PrisonApiOffence, PrisonApiOffenderSentenceAndOffences } from '../@types/prisonApi/prisonClientTypes' @@ -16,6 +16,7 @@ import { import { Adjustment } from '../@types/adjustments/adjustmentsTypes' import ParamStoreService from '../services/paramStoreService' import UnusedDeductionsService from '../services/unusedDeductionsService' +import IdentifyRemandPeriodsService from '../services/identifyRemandPeriodsService' jest.mock('../services/adjustmentsService') jest.mock('../services/prisonerService') @@ -23,6 +24,7 @@ jest.mock('../services/calculateReleaseDatesService') jest.mock('../services/adjustmentsStoreService') jest.mock('../services/paramStoreService') jest.mock('../services/unusedDeductionsService') +jest.mock('../services/identifyRemandPeriodsService') const prisonerService = new PrisonerService(null) as jest.Mocked const adjustmentsService = new AdjustmentsService(null) as jest.Mocked @@ -30,6 +32,7 @@ const calculateReleaseDatesService = new CalculateReleaseDatesService(null) as j const adjustmentsStoreService = new AdjustmentsStoreService() as jest.Mocked const paramStoreService = new ParamStoreService() as jest.Mocked const unusedDeductionsService = new UnusedDeductionsService(null, null) as jest.Mocked +const identifyRemandPeriodsService = new IdentifyRemandPeriodsService(null) as jest.Mocked const NOMS_ID = 'ABC123' const SESSION_ID = '123-abc' @@ -121,9 +124,15 @@ const mockedUnusedDeductionCalculationResponse = { validationMessages: [remandOverlapWithSentenceMessage], } as UnusedDeductionCalculationResponse +const defaultUser = user +const userWithRemandRole = { ...user, roles: ['REMAND_IDENTIFIER'] } + +let userInTest = defaultUser + let app: Express beforeEach(() => { + userInTest = defaultUser app = appWithAllRoutes({ services: { prisonerService, @@ -132,7 +141,9 @@ beforeEach(() => { calculateReleaseDatesService, paramStoreService, unusedDeductionsService, + identifyRemandPeriodsService, }, + userSupplier: () => userInTest, }) }) @@ -155,6 +166,43 @@ describe('Remand routes tests', () => { expect(res.text).toContain('From 01 Jan 2023 to 10 Jan 2023') expect(res.text).toContain('Doing a crime') expect(res.text).toContain('Heard at Court 1') + expect(res.text).toContain('edit-remand') + expect(res.text).toContain('delete-remand') + expect(res.text).toContain('Add new') + }) + }) + + it('GET /{nomsId}/remand/view will be readonly if identify remand role without remand decision', () => { + userInTest = userWithRemandRole + prisonerService.getSentencesAndOffencesFilteredForRemand.mockResolvedValue([sentenceAndOffenceBaseRecord]) + unusedDeductionsService.getCalculatedUnusedDeductionsMessageAndAdjustments.mockResolvedValue([ + 'NONE', + [remandAdjustment], + ]) + return request(app) + .get(`/${NOMS_ID}/remand/view`) + .expect(200) + .expect(res => { + expect(res.text).not.toContain('edit-remand') + expect(res.text).not.toContain('delete-remand') + expect(res.text).not.toContain('Add new') + }) + }) + it('GET /{nomsId}/remand/view will not be readonly if identify remand role with rejected remand decision', () => { + userInTest = userWithRemandRole + prisonerService.getSentencesAndOffencesFilteredForRemand.mockResolvedValue([sentenceAndOffenceBaseRecord]) + unusedDeductionsService.getCalculatedUnusedDeductionsMessageAndAdjustments.mockResolvedValue([ + 'NONE', + [remandAdjustment], + ]) + identifyRemandPeriodsService.getRemandDecision.mockResolvedValue({ accepted: false }) + return request(app) + .get(`/${NOMS_ID}/remand/view`) + .expect(200) + .expect(res => { + expect(res.text).toContain('edit-remand') + expect(res.text).toContain('delete-remand') + expect(res.text).toContain('Add new') }) }) diff --git a/server/routes/remandRoutes.ts b/server/routes/remandRoutes.ts index 410c7637..dfbc1a2b 100644 --- a/server/routes/remandRoutes.ts +++ b/server/routes/remandRoutes.ts @@ -23,6 +23,7 @@ import RemandChangeModel from '../model/remandChangeModel' import ParamStoreService from '../services/paramStoreService' import { Adjustment } from '../@types/adjustments/adjustmentsTypes' import UnusedDeductionsService from '../services/unusedDeductionsService' +import IdentifyRemandPeriodsService from '../services/identifyRemandPeriodsService' export default class RemandRoutes { constructor( @@ -32,6 +33,7 @@ export default class RemandRoutes { private readonly calculateReleaseDatesService: CalculateReleaseDatesService, private readonly paramStoreService: ParamStoreService, private readonly unusedDeductionsService: UnusedDeductionsService, + private readonly identifyRemandPeriodsService: IdentifyRemandPeriodsService, ) {} public add: RequestHandler = async (req, res): Promise => { @@ -314,7 +316,7 @@ export default class RemandRoutes { public view: RequestHandler = async (req, res): Promise => { const { nomsId } = req.params - const { username } = res.locals.user + const { username, roles } = res.locals.user const { prisonerNumber, bookingId } = res.locals.prisoner const [unusedDeductionMessage, adjustments] = await this.unusedDeductionsService.getCalculatedUnusedDeductionsMessageAndAdjustments(nomsId, bookingId, username) @@ -332,6 +334,15 @@ export default class RemandRoutes { ? await this.adjustmentsService.findByPersonAndStatus(nomsId, 'INACTIVE_WHEN_DELETED', username) : [] + let remandDecision + if (roles.includes('REMAND_IDENTIFIER')) { + try { + remandDecision = await this.identifyRemandPeriodsService.getRemandDecision(nomsId, username) + } catch { + // Nothing to do, remand review won't be displayed. + } + } + return res.render('pages/adjustments/remand/view', { model: new RemandViewModel( prisonerNumber, @@ -339,6 +350,8 @@ export default class RemandRoutes { sentencesAndOffences, unusedDeductionMessage, inactiveDeletedAdjustments, + roles, + remandDecision, ), }) } diff --git a/server/views/pages/adjustments/remand/partials/remandCard.njk b/server/views/pages/adjustments/remand/partials/remandCard.njk index 5ede71f2..e3c96b7b 100644 --- a/server/views/pages/adjustments/remand/partials/remandCard.njk +++ b/server/views/pages/adjustments/remand/partials/remandCard.njk @@ -3,7 +3,7 @@ {% from "govuk/components/summary-list/macro.njk" import govukSummaryList %} {% from "./remandOffence.njk" import offenceList %} -{% macro remandCard(adjustment, offenderNo, alternateSummaryListStyle) %} +{% macro remandCard(adjustment, offenderNo, alternateSummaryListStyle, showLinks) %} {% if alternateSummaryListStyle %} {% set summaryListClasses = "no-footer-summary-list" %} {% else %} @@ -12,18 +12,20 @@
{{ govukSummaryList({ diff --git a/server/views/pages/adjustments/remand/view.njk b/server/views/pages/adjustments/remand/view.njk index ba250af0..59d76e34 100644 --- a/server/views/pages/adjustments/remand/view.njk +++ b/server/views/pages/adjustments/remand/view.njk @@ -35,14 +35,16 @@
{% for adjustment in model.adjustmentsWithOffences() %} - {{ remandCard(adjustment, prisoner.prisonerNumber) }} + {{ remandCard(adjustment, prisoner.prisonerNumber, null, not model.readonly()) }} {% endfor %}
- + {% if not model.readonly() %} + + {% endif %}