Skip to content

Commit

Permalink
ADJUST1-277 Edit links for add remand. (#103)
Browse files Browse the repository at this point in the history
* ADJUST1-277 Edit links for add remand.

* ADJUST1-277 Fix linting.

* ADJUST1-277 Fixing content.
  • Loading branch information
ldlharper authored Nov 29, 2023
1 parent 9f22292 commit c40f093
Show file tree
Hide file tree
Showing 15 changed files with 137 additions and 45 deletions.
7 changes: 7 additions & 0 deletions server/@types/AdjustmentTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Adjustment } from './adjustments/adjustmentsTypes'

type SessionAdjustment = Adjustment & {
complete?: boolean
}

export default SessionAdjustment
4 changes: 2 additions & 2 deletions server/@types/express/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Adjustment } from '../adjustments/adjustmentsTypes'
import SessionAdjustment from '../AdjustmentTypes'

export default {}

Expand All @@ -7,7 +7,7 @@ declare module 'express-session' {
interface SessionData {
returnTo: string
nowInMinutes: number
adjustments: { string?: { string?: Adjustment } }
adjustments: { string?: { string?: SessionAdjustment } }
additionalDayApprovals: { string?: Date }
additionalDayPadas: { string?: string[] }
}
Expand Down
19 changes: 19 additions & 0 deletions server/model/remandDatesModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import SessionAdjustment from '../@types/AdjustmentTypes'
import { PrisonApiPrisoner } from '../@types/prisonApi/prisonClientTypes'
import RemandDatesForm from './remandDatesForm'

export default class RemandDatesModel {
constructor(
public id: string,
public prisonerDetail: PrisonApiPrisoner,
public adjustments: SessionAdjustment[],
public form: RemandDatesForm,
) {}

public backlink(): string {
if (this.adjustments.length > 1 || this.adjustments[0].complete) {
return `/${this.prisonerDetail.offenderNo}/remand/review`
}
return `/${this.prisonerDetail.offenderNo}`
}
}
5 changes: 3 additions & 2 deletions server/model/remandOffencesForm.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import SessionAdjustment from '../@types/AdjustmentTypes'
import { Adjustment } from '../@types/adjustments/adjustmentsTypes'
import AbstractForm from './abstractForm'
import ValidationError from './validationError'

export default class RemandOffencesForm extends AbstractForm<RemandOffencesForm> {
chargeId: string | string[]

toAdjustment(adjustment: Adjustment): Adjustment {
return { ...adjustment, remand: { chargeId: [].concat(this.chargeId).map(it => Number(it)) } }
toAdjustment(adjustment: Adjustment): SessionAdjustment {
return { ...adjustment, remand: { chargeId: [].concat(this.chargeId).map(it => Number(it)) }, complete: true }
}

async validation(): Promise<ValidationError[]> {
Expand Down
31 changes: 31 additions & 0 deletions server/model/remandReviewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export default class RemandReviewModel {
.reduce((sum, current) => sum + current, 0)
}

public backlink(): string {
return `/${this.prisonerDetail.offenderNo}/remand/offences/add/${this.adjustmentIds[0]}`
}

public adjustmentSummary(id: string) {
const adjustment = this.adjustments[id]
const offences = this.sentencesAndOffences.flatMap(it =>
Expand All @@ -38,6 +42,15 @@ export default class RemandReviewModel {
'DD MMMM YYYY',
)}`,
},
actions: {
items: [
{
href: `/${this.prisonerDetail.offenderNo}/remand/dates/add/${id}`,
text: 'Edit',
visuallyHiddenText: 'remand',
},
],
},
},
{
key: {
Expand All @@ -48,6 +61,15 @@ export default class RemandReviewModel {
${offences.map(it => `<li>${it.offenceDescription}</li>`).join('')}
</ul>`,
},
actions: {
items: [
{
href: `/${this.prisonerDetail.offenderNo}/remand/offences/add/${id}`,
text: 'Edit',
visuallyHiddenText: 'remand',
},
],
},
},
{
key: {
Expand All @@ -56,6 +78,15 @@ export default class RemandReviewModel {
value: {
text: daysBetween(new Date(adjustment.fromDate), new Date(adjustment.toDate)),
},
actions: {
items: [
{
href: `/${this.prisonerDetail.offenderNo}/remand/session/remove/${id}`,
text: 'Remove',
visuallyHiddenText: 'remand',
},
],
},
},
],
}
Expand Down
11 changes: 9 additions & 2 deletions server/model/remandSelectOffencesModel.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Adjustment } from '../@types/adjustments/adjustmentsTypes'
import SessionAdjustment from '../@types/AdjustmentTypes'
import { PrisonApiOffenderSentenceAndOffences, PrisonApiPrisoner } from '../@types/prisonApi/prisonClientTypes'
import { daysBetween, groupBy } from '../utils/utils'
import RemandOffencesForm from './remandOffencesForm'
Expand All @@ -9,13 +9,20 @@ export default class RemandSelectOffencesModel {
constructor(
public id: string,
public prisonerDetail: PrisonApiPrisoner,
public adjustment: Adjustment,
public adjustment: SessionAdjustment,
public form: RemandOffencesForm,
sentencesAndOffences: PrisonApiOffenderSentenceAndOffences[],
) {
this.cases = groupBy(sentencesAndOffences, (sent: PrisonApiOffenderSentenceAndOffences) => sent.caseSequence)
}

public backlink(): string {
if (this.adjustment.complete) {
return `/${this.prisonerDetail.offenderNo}/remand/review`
}
return `/${this.prisonerDetail.offenderNo}/remand/dates/add/${this.id}`
}

public days(): number {
return daysBetween(new Date(this.adjustment.fromDate), new Date(this.adjustment.toDate))
}
Expand Down
1 change: 1 addition & 0 deletions server/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export default function routes(service: Services): Router {
post('/:nomsId/remand/dates/:addOrEdit/:id', remandRoutes.submitDates)
get('/:nomsId/remand/offences/:addOrEdit/:id', remandRoutes.offences)
post('/:nomsId/remand/offences/:addOrEdit/:id', remandRoutes.submitOffences)
get('/:nomsId/remand/session/remove/:id', remandRoutes.removeFromSession)
get('/:nomsId/remand/review', remandRoutes.review)
post('/:nomsId/remand/review', remandRoutes.submitReview)
get('/:nomsId/remand/save', remandRoutes.save)
Expand Down
28 changes: 24 additions & 4 deletions server/routes/remandRoutes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import AdjustmentsService from '../services/adjustmentsService'
import { PrisonApiOffenderSentenceAndOffences, PrisonApiPrisoner } from '../@types/prisonApi/prisonClientTypes'
import AdjustmentsStoreService from '../services/adjustmentsStoreService'
import './testutils/toContainInOrder'
import { Adjustment } from '../@types/adjustments/adjustmentsTypes'
import CalculateReleaseDatesService from '../services/calculateReleaseDatesService'
import SessionAdjustment from '../@types/AdjustmentTypes'

jest.mock('../services/adjustmentsService')
jest.mock('../services/prisonerService')
Expand Down Expand Up @@ -62,20 +62,21 @@ const stubbedSentencesAndOffences = [
const blankAdjustment = {
person: NOMS_ID,
bookingId: stubbedPrisonerData.bookingId,
} as Adjustment
} as SessionAdjustment

const adjustmentWithDates = {
...blankAdjustment,
fromDate: '2023-01-01',
toDate: '2023-01-10',
} as Adjustment
} as SessionAdjustment

const adjustmentWithDatesAndCharges = {
...adjustmentWithDates,
remand: {
chargeId: [1, 2],
},
} as Adjustment
complete: true,
} as SessionAdjustment

let app: Express

Expand Down Expand Up @@ -105,14 +106,18 @@ describe('Adjustment routes tests', () => {
})

it('GET /{nomsId}/remand/dates/add', () => {
const adjustments = {}
adjustments[SESSION_ID] = blankAdjustment
prisonerService.getPrisonerDetail.mockResolvedValue(stubbedPrisonerData)
adjustmentsStoreService.getAll.mockReturnValue(adjustments)
adjustmentsStoreService.getById.mockReturnValue(blankAdjustment)
return request(app)
.get(`/${NOMS_ID}/remand/dates/add/${SESSION_ID}`)
.expect('Content-Type', /html/)
.expect(res => {
expect(res.text).toContain('Anon')
expect(res.text).toContain('Nobody')
expect(res.text).toContain(`<a href="/${NOMS_ID}" class="govuk-back-link">Back</a>`)
expect(res.text).toContain('Remand start date')
expect(res.text).toContain('Remand end date')
expect(res.text).toContain('Continue')
Expand All @@ -139,7 +144,10 @@ describe('Adjustment routes tests', () => {
})

it('POST /{nomsId}/remand/dates/add empty form validation', () => {
const adjustments = {}
adjustments[SESSION_ID] = blankAdjustment
prisonerService.getPrisonerDetail.mockResolvedValue(stubbedPrisonerData)
adjustmentsStoreService.getAll.mockReturnValue(adjustments)
adjustmentsStoreService.getById.mockReturnValue(blankAdjustment)
return request(app)
.post(`/${NOMS_ID}/remand/dates/add/${SESSION_ID}`)
Expand All @@ -150,7 +158,10 @@ describe('Adjustment routes tests', () => {
})

it('POST /{nomsId}/remand/dates/add to date after from', () => {
const adjustments = {}
adjustments[SESSION_ID] = blankAdjustment
prisonerService.getPrisonerDetail.mockResolvedValue(stubbedPrisonerData)
adjustmentsStoreService.getAll.mockReturnValue(adjustments)
adjustmentsStoreService.getById.mockReturnValue(blankAdjustment)
return request(app)
.post(`/${NOMS_ID}/remand/dates/add/${SESSION_ID}`)
Expand All @@ -170,7 +181,10 @@ describe('Adjustment routes tests', () => {
})

it('POST /{nomsId}/remand/dates/add dates in future', () => {
const adjustments = {}
adjustments[SESSION_ID] = blankAdjustment
prisonerService.getPrisonerDetail.mockResolvedValue(stubbedPrisonerData)
adjustmentsStoreService.getAll.mockReturnValue(adjustments)
adjustmentsStoreService.getById.mockReturnValue(blankAdjustment)
return request(app)
.post(`/${NOMS_ID}/remand/dates/add/${SESSION_ID}`)
Expand Down Expand Up @@ -200,6 +214,9 @@ describe('Adjustment routes tests', () => {
.expect(res => {
expect(res.text).toContain('Anon')
expect(res.text).toContain('Nobody')
expect(res.text).toContain(
`<a href="/${NOMS_ID}/remand/dates/add/${SESSION_ID}" class="govuk-back-link">Back</a>`,
)
expect(res.text).toContainInOrder(['10', 'day(s)'])
expect(res.text).toContainInOrder([
'Court 1',
Expand Down Expand Up @@ -249,6 +266,9 @@ describe('Adjustment routes tests', () => {
.expect(res => {
expect(res.text).toContain('Anon')
expect(res.text).toContain('Nobody')
expect(res.text).toContain(
`<a href="/${NOMS_ID}/remand/offences/add/${SESSION_ID}" class="govuk-back-link">Back</a>`,
)
expect(res.text).toContain('Review remand details')
expect(res.text).toContainInOrder([
'Remand period',
Expand Down
25 changes: 23 additions & 2 deletions server/routes/remandRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import RemandSaveModel from '../model/remandSaveModel'
import { Adjustment } from '../@types/adjustments/adjustmentsTypes'
import { daysBetween } from '../utils/utils'
import { Message } from '../model/adjustmentsHubViewModel'
import RemandDatesModel from '../model/remandDatesModel'

export default class RemandRoutes {
constructor(
Expand Down Expand Up @@ -43,13 +44,14 @@ export default class RemandRoutes {
throw FullPageError.notFoundError()
}
const adjustment = this.adjustmentsStoreService.getById(req, nomsId, id)
const adjustments = Object.values(this.adjustmentsStoreService.getAll(req, nomsId))

const prisonerDetail = await this.prisonerService.getPrisonerDetail(nomsId, caseloads, token)

const form = RemandDatesForm.fromAdjustment(adjustment)

return res.render('pages/adjustments/remand/dates', {
model: { prisonerDetail, form, addOrEdit, id },
model: new RemandDatesModel(id, prisonerDetail, adjustments, form),
})
}

Expand All @@ -63,14 +65,18 @@ export default class RemandRoutes {
await adjustmentForm.validate(() => this.prisonerService.getSentencesAndOffences(prisonerDetail.bookingId, token))

if (adjustmentForm.errors.length) {
const adjustments = Object.values(this.adjustmentsStoreService.getAll(req, nomsId))
return res.render('pages/adjustments/remand/dates', {
model: { prisonerDetail, form: adjustmentForm, addOrEdit, id },
model: new RemandDatesModel(id, prisonerDetail, adjustments, adjustmentForm),
})
}

const adjustment = this.adjustmentsStoreService.getById(req, nomsId, id)
this.adjustmentsStoreService.store(req, nomsId, id, adjustmentForm.toAdjustment(adjustment))

if (adjustment.complete) {
return res.redirect(`/${nomsId}/remand/review`)
}
return res.redirect(`/${nomsId}/remand/offences/${addOrEdit}/${id}`)
}

Expand Down Expand Up @@ -123,6 +129,15 @@ export default class RemandRoutes {
const { nomsId } = req.params

const adjustments = this.adjustmentsStoreService.getAll(req, nomsId)
Object.keys(adjustments).forEach(it => {
if (!adjustments[it].complete) {
this.adjustmentsStoreService.remove(req, nomsId, it)
delete adjustments[it]
}
})
if (!Object.keys(adjustments).length) {
return res.redirect(`/${nomsId}`)
}
const prisonerDetail = await this.prisonerService.getPrisonerDetail(nomsId, caseloads, token)
const sentencesAndOffences = await this.prisonerService.getSentencesAndOffences(prisonerDetail.bookingId, token)

Expand Down Expand Up @@ -191,6 +206,12 @@ export default class RemandRoutes {
return res.redirect(`/${nomsId}/success?message=${JSON.stringify(message)}`)
}

public removeFromSession: RequestHandler = async (req, res): Promise<void> => {
const { nomsId, id } = req.params
this.adjustmentsStoreService.remove(req, nomsId, id)
return res.redirect(`/${nomsId}/remand/review`)
}

private makeSessionAdjustmentsReadyForCalculation(sessionadjustments: { string?: Adjustment }): Adjustment[] {
return Object.values(sessionadjustments).map(it => {
return {
Expand Down
16 changes: 10 additions & 6 deletions server/services/adjustmentsStoreService.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Request } from 'express'
import { randomUUID } from 'crypto'
import { Adjustment } from '../@types/adjustments/adjustmentsTypes'
import SessionAdjustment from '../@types/AdjustmentTypes'

export default class AdjustmentsStoreService {
private initSessionForNomsId(req: Request, nomsId: string) {
Expand All @@ -18,13 +18,13 @@ export default class AdjustmentsStoreService {
}

/* Functions for forms that create adjustments one at a time */
public getOnly(req: Request, nomsId: string): Adjustment {
public getOnly(req: Request, nomsId: string): SessionAdjustment {
this.initSessionForNomsId(req, nomsId)
const key = Object.keys(req.session.adjustments[nomsId])[0]
return req.session.adjustments[nomsId][key]
}

public storeOnly(req: Request, nomsId: string, adjustment: Adjustment) {
public storeOnly(req: Request, nomsId: string, adjustment: SessionAdjustment) {
this.initSessionForNomsId(req, nomsId)
const keys = Object.keys(req.session.adjustments[nomsId])
if (keys.length) {
Expand All @@ -37,20 +37,24 @@ export default class AdjustmentsStoreService {
}

/* Functions for forms that create multiple */
public store(req: Request, nomsId: string, reqId: string, adjustment: Adjustment): string {
public store(req: Request, nomsId: string, reqId: string, adjustment: SessionAdjustment): string {
this.initSessionForNomsId(req, nomsId)
const id = reqId || randomUUID()
req.session.adjustments[nomsId][id] = adjustment
return id
}

public getById(req: Request, nomsId: string, id: string): Adjustment {
public getById(req: Request, nomsId: string, id: string): SessionAdjustment {
this.initSessionForNomsId(req, nomsId)
return req.session.adjustments[nomsId][id]
}

public getAll(req: Request, nomsId: string): { string?: Adjustment } {
public getAll(req: Request, nomsId: string): { string?: SessionAdjustment } {
this.initSessionForNomsId(req, nomsId)
return req.session.adjustments[nomsId]
}

public remove(req: Request, nomsId: string, id: string) {
delete req.session.adjustments[nomsId][id]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<p class="govuk-body">ADAs cannot be added manually.</p>
<p class="govuk-body">To add an ADAs, contact the team who manage adjudications.</p>
{% else %}
<p class="govuk-body">This person is being recalled on license. ADAs cannot be added for a person during licence recall with this service.</p>
<p class="govuk-body">This person is being recalled on license. ADAs cannot be added for a person during a licence recall with this service.</p>
<p class="govuk-body">Email <a href="mailto:calculatereleasedates@digital.justice.gov.uk?subject=Adjustments service - ADA within recall period">calculatereleasedates@digital.justice.gov.uk</a> and ask the team to add the ADA information.</p>
{% endif %}
</div>
Expand Down
Loading

0 comments on commit c40f093

Please sign in to comment.