diff --git a/server/@types/adjustments/index.d.ts b/server/@types/adjustments/index.d.ts index eadd4792..44a47cc6 100644 --- a/server/@types/adjustments/index.d.ts +++ b/server/@types/adjustments/index.d.ts @@ -91,7 +91,7 @@ export interface components { schemas: { DlqMessage: { body: { - [key: string]: Record + [key: string]: Record | undefined } messageId: string } @@ -145,13 +145,8 @@ export interface components { } /** @description The details of an additional days awarded (ADA) adjustment */ AdditionalDaysAwardedDto: { - /** - * Format: int32 - * @description The id of the adjudication that resulted in the ADA - */ - adjudicationId: number - /** @description Is the ADA consecutive or concurrent */ - consecutive: boolean + /** @description The id of the adjudication that resulted in the ADA */ + adjudicationId: number[] } /** @description The adjustment and its identifier */ AdjustmentDto: { @@ -213,8 +208,11 @@ export interface components { prisonName?: string /** @description The person last updating this adjustment */ lastUpdatedBy?: string - /** @description The status of this adjustment */ - status?: string + /** + * @description The status of this adjustment + * @enum {string} + */ + status?: 'ACTIVE' | 'INACTIVE' | 'DELETED' /** * Format: date-time * @description The date and time this adjustment was last updated @@ -277,8 +275,6 @@ export interface components { pathItems: never } -export type $defs = Record - export type external = Record export interface operations { @@ -372,17 +368,11 @@ export interface operations { } responses: { /** @description Adjustment update */ - 200: { - content: never - } + 200: never /** @description Unauthorised, requires a valid Oauth2 token */ - 401: { - content: never - } + 401: never /** @description Adjustment not found */ - 404: { - content: never - } + 404: never } } /** @@ -398,17 +388,11 @@ export interface operations { } responses: { /** @description Adjustment deleted */ - 200: { - content: never - } + 200: never /** @description Unauthorised, requires a valid Oauth2 token */ - 401: { - content: never - } + 401: never /** @description Adjustment not found */ - 404: { - content: never - } + 404: never } } /** @@ -461,17 +445,11 @@ export interface operations { } responses: { /** @description Adjustment update */ - 200: { - content: never - } + 200: never /** @description Unauthorised, requires a valid Oauth2 token */ - 401: { - content: never - } + 401: never /** @description Adjustment not found */ - 404: { - content: never - } + 404: never } } /** @@ -487,17 +465,11 @@ export interface operations { } responses: { /** @description Adjustment deleted */ - 200: { - content: never - } + 200: never /** @description Unauthorised, requires a valid Oauth2 token */ - 401: { - content: never - } + 401: never /** @description Adjustment not found */ - 404: { - content: never - } + 404: never } } /** diff --git a/server/routes/additionalDaysAwardedRoutes.ts b/server/routes/additionalDaysAwardedRoutes.ts index 2face138..9d5e62ff 100644 --- a/server/routes/additionalDaysAwardedRoutes.ts +++ b/server/routes/additionalDaysAwardedRoutes.ts @@ -31,4 +31,22 @@ export default class AdditionalDaysAwardedRoutes { adasToReview, }) } + + public approve: RequestHandler = async (req, res): Promise => { + const { caseloads, token, username } = res.locals.user + const { nomsId } = req.params + const prisonerDetail = await this.prisonerService.getPrisonerDetail(nomsId, caseloads, token) + const startOfSentenceEnvelope = await this.prisonerService.getStartOfSentenceEnvelope( + prisonerDetail.bookingId, + token, + ) + await this.additionalDaysAwardedService.approveAdjudications( + prisonerDetail, + startOfSentenceEnvelope, + username, + token, + ) + + return res.redirect(`/${nomsId}`) + } } diff --git a/server/routes/adjustmentRoutes.test.ts b/server/routes/adjustmentRoutes.test.ts index c19ffc1d..efe2f773 100644 --- a/server/routes/adjustmentRoutes.test.ts +++ b/server/routes/adjustmentRoutes.test.ts @@ -454,7 +454,7 @@ describe('Adjustment routes tests', () => { it('GET /{nomsId}/{adjustmentType}/view', () => { prisonerService.getPrisonerDetail.mockResolvedValue(stubbedPrisonerData) adjustmentsService.findByPerson.mockResolvedValue([ - { ...radaAdjustment, id: 'this-is-an-id', lastUpdatedBy: 'Doris McNealy', status: 'Active', prisonName: 'Leeds' }, + { ...radaAdjustment, id: 'this-is-an-id', lastUpdatedBy: 'Doris McNealy', status: 'ACTIVE', prisonName: 'Leeds' }, ]) return request(app) diff --git a/server/routes/index.ts b/server/routes/index.ts index 1d82ccad..4c67a732 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -37,6 +37,7 @@ export default function routes(service: Services): Router { post('/:nomsId/review', adjustmentRoutes.submitReview) get('/:nomsId/ada/review', additionalDaysAwardedRoutes.review) + post('/:nomsId/ada/review', additionalDaysAwardedRoutes.approve) get('/:nomsId/:adjustmentTypeUrl/view', adjustmentRoutes.view) get('/:nomsId/:adjustmentTypeUrl/remove/:id', adjustmentRoutes.remove) diff --git a/server/services/additionalDaysAwardedService.test.ts b/server/services/additionalDaysAwardedService.test.ts index 176d6aec..d5a280b1 100644 --- a/server/services/additionalDaysAwardedService.test.ts +++ b/server/services/additionalDaysAwardedService.test.ts @@ -88,15 +88,15 @@ const adjudicationThreeNonAda = JSON.parse( const adjudicationOneAdjustment = { adjustmentType: 'ADDITIONAL_DAYS_AWARDED', - additionalDaysAwarded: { adjudicationId: 1525916 }, + additionalDaysAwarded: { adjudicationId: [1525916] }, } as Adjustment const adjudicationTwoAdjustment = { adjustmentType: 'ADDITIONAL_DAYS_AWARDED', - additionalDaysAwarded: { adjudicationId: 1525917 }, + additionalDaysAwarded: { adjudicationId: [1525917] }, } as Adjustment const adjudicationThreeAdjustment = { adjustmentType: 'ADDITIONAL_DAYS_AWARDED', - additionalDaysAwarded: { adjudicationId: 1525918 }, + additionalDaysAwarded: { adjudicationId: [1525918] }, } as Adjustment const adjustmentResponsesWithChargeNumber = [ diff --git a/server/services/additionalDaysAwardedService.ts b/server/services/additionalDaysAwardedService.ts index 53f00c35..aacfdc71 100644 --- a/server/services/additionalDaysAwardedService.ts +++ b/server/services/additionalDaysAwardedService.ts @@ -3,6 +3,8 @@ import { AdjudicationSearchResponse, IndividualAdjudication, Sanction } from '.. import { HmppsAuthClient } from '../data' import { Ada, AdasByDateCharged, AdasToReview } from '../@types/AdaTypes' import AdjustmentsClient from '../api/adjustmentsClient' +import { PrisonApiPrisoner } from '../@types/prisonApi/prisonClientTypes' +import { Adjustment } from '../@types/adjustments/adjustmentsTypes' /* The adjudications status from NOMIS DB mapped to the adjudications API status are listed here temporarily to make it easier to implement the stories which use the NOMIS status * 'AS_AWARDED' = 'Activated as Awarded' @@ -61,7 +63,7 @@ export default class AdditionalDaysAwardedService { ): Promise { const existingAdaChargeIds = (await new AdjustmentsClient(token).findByPerson(nomsId)) .filter(it => it.adjustmentType === 'ADDITIONAL_DAYS_AWARDED' && it.additionalDaysAwarded) - .map(ada => ada.additionalDaysAwarded.adjudicationId) + .flatMap(ada => ada.additionalDaysAwarded.adjudicationId) const systemToken = await this.hmppsAuthClient.getSystemClientToken(username) const adjudicationClient = new AdjudicationClient(systemToken) const adjudications: AdjudicationSearchResponse = await adjudicationClient.getAdjudications(nomsId) @@ -132,11 +134,13 @@ export default class AdditionalDaysAwardedService { const calculatedDays = chains .filter(it => it.length > 0) .map(chain => chain.reduce((acc, cur) => acc + cur.days, 0)) - + if (!calculatedDays.length) { + return 0 + } return Math.max(...calculatedDays) } - createChain(ada: Ada, chain: Ada[], consecCharges: Ada[]) { + private createChain(ada: Ada, chain: Ada[], consecCharges: Ada[]) { const consecFrom = consecCharges.find(it => it.consecutiveToSequence === ada.sequence) if (consecFrom) { chain.push(consecFrom) @@ -242,4 +246,58 @@ export default class AdditionalDaysAwardedService { ) .map(consecutiveAda => allAdas.find(sourceAda => sourceAda.sequence === consecutiveAda.consecutiveToSequence)) } + + public async approveAdjudications( + prisonerDetail: PrisonApiPrisoner, + startOfSentenceEnvelope: Date, + username: string, + token: string, + ) { + const allAdaAdjustments = (await new AdjustmentsClient(token).findByPerson(prisonerDetail.offenderNo)).filter( + it => it.adjustmentType === 'ADDITIONAL_DAYS_AWARDED', + ) + const existingAdaChargeIds = allAdaAdjustments + .filter(it => it.additionalDaysAwarded) + .flatMap(ada => ada.additionalDaysAwarded.adjudicationId) + const systemToken = await this.hmppsAuthClient.getSystemClientToken(username) + const adjudicationClient = new AdjudicationClient(systemToken) + const adjudications: AdjudicationSearchResponse = await adjudicationClient.getAdjudications( + prisonerDetail.offenderNo, + ) + const individualAdjudications = await Promise.all( + adjudications.results.content.map(async it => { + return adjudicationClient.getAdjudication(prisonerDetail.offenderNo, it.adjudicationNumber) + }), + ) + const allAdas: Ada[] = this.getAdas(individualAdjudications, startOfSentenceEnvelope, existingAdaChargeIds) + + const adas: AdasByDateCharged[] = this.getAdasByDateCharged(allAdas, AWARDED) + + const adjustments = adas.map(it => { + return { + person: prisonerDetail.offenderNo, + bookingId: prisonerDetail.bookingId, + adjustmentType: 'ADDITIONAL_DAYS_AWARDED', + fromDate: it.dateChargeProved.toISOString().substring(0, 10), + days: it.total, + prisonId: prisonerDetail.agencyId, + additionalDaysAwarded: { adjudicationId: it.charges.map(charge => charge.chargeNumber) }, + } as Adjustment + }) + + // Delete all unlinked ADAs. + await Promise.all( + allAdaAdjustments + .filter(it => !it.additionalDaysAwarded) + .map(it => { + return new AdjustmentsClient(token).delete(it.id) + }), + ) + // Create adjustments + await Promise.all( + adjustments.map(it => { + return new AdjustmentsClient(token).create(it) + }), + ) + } } diff --git a/server/views/pages/adjustments/ada/review.njk b/server/views/pages/adjustments/ada/review.njk index d4cc4ef6..09e4917a 100644 --- a/server/views/pages/adjustments/ada/review.njk +++ b/server/views/pages/adjustments/ada/review.njk @@ -1,5 +1,6 @@ {% extends "../../../partials/layout.njk" %} {% from "../../../macros/adaTable.njk" import adaTable %} +{% from "govuk/components/button/macro.njk" import govukButton %} {% set pageTitle = applicationName + " - Review additional days awarded" %} {% set mainClasses = "app-container govuk-body" %} @@ -24,7 +25,7 @@

Awarded ADAs

{{ adaTable('awarded-adas', adasToReview.adas, adasToReview.totalAdas, 'Total ADAs taken into calculation', 'Awarded ADAs will be included in the calculation.', 'No active ADA adjudications exist for this offender') }} - {% if adasToReview.suspended | length %} + {% if adasToReview.suspended | length %}

Suspended ADAs

{{ adaTable('suspended-adas', adasToReview.suspended, adasToReview.totalSuspended, 'Total suspended ADAs', 'Suspended ADAs will not be included in the calculation.') }} {% endif %} @@ -38,13 +39,19 @@ If you think some information is wrong, contact the team responsible for adjudications.
- - Approve - + +
+ + {{ govukButton({ + text: "Approve", + type: submit, + preventDoubleClick: true + }) }} +
Cancel and return to dashboard -{% endblock %} +{% endblock %} \ No newline at end of file