Skip to content

Commit

Permalink
Map 288 dps create reception moves screen (#2591)
Browse files Browse the repository at this point in the history
* MAP-288 added consider-risks-reception page

* MAP-288 add test, refactor and linting

* MAP-288 amend data-test attr
  • Loading branch information
GurnankCheema authored Oct 17, 2023
1 parent 4394316 commit 091bc55
Show file tree
Hide file tree
Showing 14 changed files with 1,121 additions and 68 deletions.
12 changes: 9 additions & 3 deletions backend/controllers/cellMove/cellMoveUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,17 @@ export const getNonAssociationsInEstablishment = async (

export const getBackLinkData = (referer, offenderNo) => {
const backLink = referer || `/prisoner/${offenderNo}/cell-move/search-for-cell`
let backLinkText = 'Return to select an available cell'

if (backLink.includes('search-for-cell')) {
backLinkText = 'Return to search for a cell'
} else if (backLink.includes('consider-risks-reception')) {
backLinkText = 'Return to consider risks of reception move'
}

return {
backLink,
backLinkText: backLink.includes('search-for-cell')
? 'Return to search for a cell'
: 'Return to select an available cell',
backLinkText,
}
}

Expand Down
157 changes: 157 additions & 0 deletions backend/controllers/receptionMove/considerRisksReception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { alertFlagLabels, cellMoveAlertCodes } from '../../shared/alertFlagValues'
import { putLastNameFirst, formatName, formatLocation, hasLength } from '../../utils'
import { getNonAssociationsInEstablishment, translateCsra, userHasAccess } from '../cellMove/cellMoveUtils'
import logger from '../../log'

export default ({ oauthApi, prisonApi, movementsService, nonAssociationsApi, logError }) => {
const view = async (req, res) => {
const { offenderNo } = req.params

try {
const userRoles = oauthApi.userRoles(res.locals)
const userCaseLoads = await prisonApi.userCaseLoads(res.locals)

const { activeCaseLoadId } = req.session.userDetails

const [prisonerDetails, assessments] = await Promise.all([
prisonApi.getDetails(res.locals, offenderNo, true),
prisonApi.getCsraAssessments(res.locals, [offenderNo]),
])

if (!userHasAccess({ userRoles, userCaseLoads, offenderCaseload: prisonerDetails.agencyId })) {
logger.info('User does not have correct roles')
return res.render('notFound.njk', { url: '/prisoner-search' })
}

const displayLinkToPrisonersMostRecentCsra =
hasLength(assessments) &&
assessments.sort((a, b) => b.assessmentDate.localeCompare(a.assessmentDate))[0].assessmentComment

const nonAssociations = await nonAssociationsApi.getNonAssociationsLegacy(res.locals, offenderNo)

const prisonersActiveAlertCodes = prisonerDetails.alerts
.filter((alert) => !alert.expired)
.map((alert) => alert.alertCode)

const prisonerAlerts = alertFlagLabels.filter((alertFlag) =>
alertFlag.alertCodes.some(
(alert) => prisonersActiveAlertCodes.includes(alert) && cellMoveAlertCodes.includes(alert)
)
)

const prisonerDetailsWithFormattedLocation = {
...prisonerDetails,
assignedLivingUnit: {
...prisonerDetails.assignedLivingUnit,
description: formatLocation(prisonerDetails.assignedLivingUnit.description),
},
}

const offenderNumbersOfAllNonAssociations = nonAssociations.nonAssociations.map(
(offender) => offender.offenderNonAssociation.offenderNo
)
const offendersInReception = await movementsService.getOffendersInReception(res.locals, activeCaseLoadId)
const offenderNumbersOfAllInReception = offendersInReception.map((offender) => offender.offenderNo)
const offenderCsraStatus = await movementsService.getCsraForMultipleOffenders(
res.locals,
offenderNumbersOfAllInReception
)

const otherOffenders = offendersInReception
.sort((left, right) => left.lastName.localeCompare(right.lastName, 'en', { ignorePunctuation: true }))
.map((offender) => ({
offenderNo: offender.offenderNo,
name: putLastNameFirst(offender.firstName, offender.lastName),
nonAssociation: offenderNumbersOfAllNonAssociations.includes(offender.offenderNo),
csraClassification:
offenderCsraStatus.find((statuses) => statuses.offenderNo === offender.offenderNo)?.classification ||
'Not entered',
displayCsraLink: offenderCsraStatus.find((statuses) => statuses.offenderNo === offender.offenderNo)
?.assessmentComment,
alerts: offender.alerts
.map((alertCode) => alertFlagLabels.find((alertLabel) => alertLabel.alertCodes.includes(alertCode)))
.filter(Boolean)
.map((alertLabel) => ({ classes: alertLabel.classes, label: alertLabel.label }))
.sort((left, right) => left.label.localeCompare(right.label, 'en', { ignorePunctuation: true })),
}))

const nonAssociationsInEstablishment = await getNonAssociationsInEstablishment(
nonAssociations,
res.locals,
prisonApi
)
const sortedNonAssociationsInReceptionWithinCurrentEstablishment = nonAssociationsInEstablishment
.sort((left, right) => {
if (left.effectiveDate < right.effectiveDate) return 1
if (right.effectiveDate < left.effectiveDate) return -1
if (left.offenderNonAssociation.lastName > right.offenderNonAssociation.lastName) return 1
if (right.offenderNonAssociation.lastName > left.offenderNonAssociation.lastName) return -1
return 0
})
.filter((nonAssociationPrisoner) =>
offenderNumbersOfAllInReception.includes(nonAssociationPrisoner.offenderNonAssociation.offenderNo)
)

const { firstName, lastName } = await prisonApi.getDetails(res.locals, offenderNo)

const nonAssociationsRows = sortedNonAssociationsInReceptionWithinCurrentEstablishment?.map((nonAssociation) => ({
name: putLastNameFirst(
nonAssociation.offenderNonAssociation.firstName,
nonAssociation.offenderNonAssociation.lastName
),
prisonNumber: nonAssociation.offenderNonAssociation.offenderNo,
type: nonAssociation.typeDescription,
selectedOffenderKey: `${formatName(firstName, lastName)} is`,
selectedOffenderRole: nonAssociation.reasonDescription,
otherOffenderKey: `${formatName(
nonAssociation.offenderNonAssociation.firstName,
nonAssociation.offenderNonAssociation.lastName
)} is`,
otherOffenderRole: nonAssociation.offenderNonAssociation.reasonDescription,
comment: nonAssociation.comments || 'Not entered',
}))

return res.render('receptionMoves/considerRisksReception.njk', {
reverseOrderPrisonerName: putLastNameFirst(prisonerDetails.firstName, prisonerDetails.lastName).trim(),
prisonerName: formatName(prisonerDetails.firstName, prisonerDetails.lastName),
prisonerAlerts,
prisonerDetails: prisonerDetailsWithFormattedLocation,
nonAssociationLink: `/prisoner/${offenderNo}/cell-move/non-associations`,
searchForCellRootUrl: `/prisoner/${offenderNo}/cell-move/search-for-cell`,
offenderDetailsUrl: `/prisoner/${offenderNo}/cell-move/prisoner-details`,
csraDetailsUrl: `/prisoner/${offenderNo}/cell-move/cell-sharing-risk-assessment-details`,
displayLinkToPrisonersMostRecentCsra,
convertedCsra: translateCsra(prisonerDetails.csraClassificationCode),
backUrl: `/prisoners/${offenderNo}/location-details`,
hasNonAssociations: nonAssociationsInEstablishment?.length > 0,
nonAssociationsRows,
offendersInReception: otherOffenders,
inReceptionCount: `${otherOffenders.length} people in reception`,
errors: req.flash('errors'),
})
} catch (error) {
logError(req.originalUrl, error, 'error getting consider-risks-reception')
res.locals.homeUrl = `/prisoner/${offenderNo}`
throw error
}
}

const submit = async (req, res) => {
const { offenderNo } = req.params
const { considerRisksReception } = req.body

if (!considerRisksReception) {
const errors = []
errors.push({ href: '#considerRisksReception', text: 'Select yes or no' })
req.flash('errors', errors)
return res.redirect(`/prisoner/${offenderNo}/reception-move/consider-risks-reception`)
}

if (considerRisksReception === 'yes') {
return res.redirect(`/prisoner/${offenderNo}/reception-move/confirm-reception-move`)
}
return res.redirect(`/prisoner/${offenderNo}/location-details`)
}

return { view, submit }
}
13 changes: 13 additions & 0 deletions backend/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import attendanceChangeRouter from './routes/attendanceChangesRouter'
import covidRouter from './routes/covidRouter'
import prisonerSearchRouter from './routes/prisonerSearchRouter'
import cellMoveRouter from './routes/cellMoveRouter'
import receptionMoveRouter from './routes/receptionMoveRouter'
import establishmentRollRouter from './routes/establishmentRollRouter'
import changeSomeonesCellRouter from './routes/changeSomeonesCellRouter'
import globalSearchRouter from './routes/globalSearchRouter'
Expand Down Expand Up @@ -325,6 +326,18 @@ const setup = ({
})
)

router.use(
'/prisoner/:offenderNo/reception-move',
receptionMoveRouter({
oauthApi,
prisonApi,
systemOauthClient,
incentivesApi,
nonAssociationsApi,
logError,
})
)

router.use(
'/establishment-roll',
establishmentRollRouter({
Expand Down
22 changes: 22 additions & 0 deletions backend/routes/receptionMoveRouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import express from 'express'
import considerRisksController from '../controllers/receptionMove/considerRisksReception'
import { movementsServiceFactory } from '../services/movementsService'

const router = express.Router({ mergeParams: true })

const controller = ({ oauthApi, prisonApi, systemOauthClient, incentivesApi, nonAssociationsApi, logError }) => {
const movementsService = movementsServiceFactory(prisonApi, systemOauthClient, incentivesApi)
const { view, submit } = considerRisksController({
oauthApi,
prisonApi,
movementsService,
nonAssociationsApi,
logError,
})
router.get('/consider-risks-reception', view)
router.post('/consider-risks-reception', submit)

return router
}

export default (dependencies) => controller(dependencies)
6 changes: 6 additions & 0 deletions backend/services/movementsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import moment from 'moment'
import { toMap } from '../utils'

export const movementsServiceFactory = (prisonApi, systemOauthClient, incentivesApi) => {
const getCsraForMultipleOffenders = async (systemContext, offenderNumbers) => {
const results = await prisonApi.getCsraAssessments(systemContext, offenderNumbers)
return results
}

const getAssessmentMap = async (context, offenderNumbers) => {
const assessments = (await prisonApi.getAssessments(context, { code: 'CATEGORY', offenderNumbers })) || []
return toMap('offenderNo', assessments)
Expand Down Expand Up @@ -183,6 +188,7 @@ export const movementsServiceFactory = (prisonApi, systemOauthClient, incentives
getOffendersCurrentlyOutOfLivingUnit,
getOffendersCurrentlyOutOfAgency,
getOffendersEnRoute,
getCsraForMultipleOffenders,
}
}

Expand Down
19 changes: 18 additions & 1 deletion backend/tests/cellMove/cellMoveUtils.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import moment from 'moment'
import { getNonAssociationsInEstablishment } from '../../controllers/cellMove/cellMoveUtils'
import { getNonAssociationsInEstablishment, getBackLinkData } from '../../controllers/cellMove/cellMoveUtils'

describe('Cell move utils', () => {
const nonAssociations = {
Expand Down Expand Up @@ -244,4 +244,21 @@ describe('Cell move utils', () => {
)
})
})

describe('getBackLinkData', () => {
it('returns correct back link text for cell search journey', () => {
const text = getBackLinkData('search-for-cell', 'A12345BC')
expect(text.backLinkText).toBe('Return to search for a cell')
})

it('returns correct back link text for reception move journey', () => {
const text = getBackLinkData('consider-risks-reception', 'A12345BC')
expect(text.backLinkText).toBe('Return to consider risks of reception move')
})

it('returns correct back link text when neither cell search or reception move journeys', () => {
const text = getBackLinkData('something else', 'A12345BC')
expect(text.backLinkText).toBe('Return to select an available cell')
})
})
})
Loading

0 comments on commit 091bc55

Please sign in to comment.