diff --git a/backend/controllers/cellMove/cellMoveUtils.ts b/backend/controllers/cellMove/cellMoveUtils.ts index 5a81a3ccc..9a7acaa4f 100644 --- a/backend/controllers/cellMove/cellMoveUtils.ts +++ b/backend/controllers/cellMove/cellMoveUtils.ts @@ -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, } } diff --git a/backend/controllers/receptionMove/considerRisksReception.ts b/backend/controllers/receptionMove/considerRisksReception.ts new file mode 100644 index 000000000..2734fa7ad --- /dev/null +++ b/backend/controllers/receptionMove/considerRisksReception.ts @@ -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 } +} diff --git a/backend/routes.ts b/backend/routes.ts index 8b03d95fd..05900144b 100644 --- a/backend/routes.ts +++ b/backend/routes.ts @@ -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' @@ -325,6 +326,18 @@ const setup = ({ }) ) + router.use( + '/prisoner/:offenderNo/reception-move', + receptionMoveRouter({ + oauthApi, + prisonApi, + systemOauthClient, + incentivesApi, + nonAssociationsApi, + logError, + }) + ) + router.use( '/establishment-roll', establishmentRollRouter({ diff --git a/backend/routes/receptionMoveRouter.ts b/backend/routes/receptionMoveRouter.ts new file mode 100644 index 000000000..2c1a8f85f --- /dev/null +++ b/backend/routes/receptionMoveRouter.ts @@ -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) diff --git a/backend/services/movementsService.ts b/backend/services/movementsService.ts index 227212af2..69ef261ad 100644 --- a/backend/services/movementsService.ts +++ b/backend/services/movementsService.ts @@ -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) @@ -183,6 +188,7 @@ export const movementsServiceFactory = (prisonApi, systemOauthClient, incentives getOffendersCurrentlyOutOfLivingUnit, getOffendersCurrentlyOutOfAgency, getOffendersEnRoute, + getCsraForMultipleOffenders, } } diff --git a/backend/tests/cellMove/cellMoveUtils.test.ts b/backend/tests/cellMove/cellMoveUtils.test.ts index 2bc039406..7526760e3 100644 --- a/backend/tests/cellMove/cellMoveUtils.test.ts +++ b/backend/tests/cellMove/cellMoveUtils.test.ts @@ -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 = { @@ -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') + }) + }) }) diff --git a/backend/tests/receptionMove/considerRisksReceptionController.test.ts b/backend/tests/receptionMove/considerRisksReceptionController.test.ts new file mode 100644 index 000000000..d1f5fd7f7 --- /dev/null +++ b/backend/tests/receptionMove/considerRisksReceptionController.test.ts @@ -0,0 +1,229 @@ +import considerRisksReception from '../../controllers/receptionMove/considerRisksReception' +import logger from '../../log' + +jest.mock('../../log') + +const someOffenderNumber = 'A12345' +const someBookingId = -10 +const someAgency = 'LEI' + +const oauthApi = { + userRoles: jest.fn(), +} + +const prisonApi = { + getDetails: jest.fn(), + getCsraAssessments: jest.fn(), + userCaseLoads: jest.fn(), +} + +const movementsService = { + getOffendersInReception: jest.fn(), + getCsraForMultipleOffenders: jest.fn(), +} + +const nonAssociationsApi = { + getNonAssociationsLegacy: jest.fn(), +} + +const logError = jest.fn() +logger.info = jest.fn() + +const res = { locals: { homeUrl: `prisoner/${someOffenderNumber}` }, redirect: jest.fn(), render: jest.fn() } +let controller +let req + +describe('Consider risks reception', () => { + beforeEach(() => { + oauthApi.userRoles.mockReturnValue([{ roleCode: 'CELL_MOVE' }]) + + prisonApi.getDetails.mockResolvedValue({ + offenderNo: someOffenderNumber, + bookingId: someBookingId, + firstName: 'John ', + lastName: 'Doe', + alertsCodes: [], + alerts: [], + assignedLivingUnit: { description: 'Cell-1' }, + assessments: [], + agencyId: 'LEI', + }) + prisonApi.userCaseLoads.mockResolvedValue([{ caseLoadId: 'LEI' }]) + prisonApi.getCsraAssessments.mockResolvedValue([{}]) + + movementsService.getOffendersInReception.mockResolvedValue([]) + movementsService.getCsraForMultipleOffenders.mockResolvedValue([{}]) + nonAssociationsApi.getNonAssociationsLegacy.mockResolvedValue({ + nonAssociations: [], + }) + + res.render = jest.fn() + + req = { + originalUrl: 'original-url', + params: { + offenderNo: someOffenderNumber, + }, + flash: jest.fn(), + query: {}, + session: { + userDetails: { + activeCaseLoadId: someAgency, + }, + }, + } + + controller = considerRisksReception({ oauthApi, prisonApi, movementsService, nonAssociationsApi, logError }) + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + describe('page', () => { + it('should make the correct api calls', async () => { + await controller.view(req, res) + expect(prisonApi.getDetails).toHaveBeenNthCalledWith( + 1, + { homeUrl: `prisoner/${someOffenderNumber}` }, + someOffenderNumber, + true + ) + expect(prisonApi.getCsraAssessments).toHaveBeenCalledWith({ homeUrl: `prisoner/${someOffenderNumber}` }, [ + someOffenderNumber, + ]) + expect(movementsService.getCsraForMultipleOffenders).toHaveBeenCalledWith( + { homeUrl: `prisoner/${someOffenderNumber}` }, + [] + ) + expect(nonAssociationsApi.getNonAssociationsLegacy).toHaveBeenCalledWith( + { homeUrl: `prisoner/${someOffenderNumber}` }, + someOffenderNumber + ) + }) + + it('should check user has correct roles', async () => { + oauthApi.userRoles.mockReturnValue([{ roleCode: 'SOME_OTHER_ROLE' }]) + await controller.view(req, res) + expect(res.render).toHaveBeenCalledWith('notFound.njk', { url: '/prisoner-search' }) + expect(logger.info).toBeCalled() + }) + + it('should populate view model with prisoner details', async () => { + await controller.view(req, res) + + expect(res.render).toHaveBeenCalledWith( + 'receptionMoves/considerRisksReception.njk', + expect.objectContaining({ + prisonerAlerts: [], + prisonerDetails: { + alerts: [], + alertsCodes: [], + assessments: [], + assignedLivingUnit: { description: 'Cell-1' }, + bookingId: -10, + firstName: 'John ', + lastName: 'Doe', + offenderNo: 'A12345', + agencyId: 'LEI', + }, + reverseOrderPrisonerName: 'Doe, John', + nonAssociationsRows: [], + offendersInReception: [], + inReceptionCount: '0 people in reception', + }) + ) + }) + it('should populate view model with other prisoners in reception', async () => { + movementsService.getOffendersInReception.mockResolvedValue([ + { + offenderNo: 'A123', + firstName: 'Max', + lastName: 'Mercedes', + + alerts: [], + }, + { + offenderNo: 'B123', + firstName: 'Jack', + lastName: 'Simpson', + alerts: [], + }, + ]) + await controller.view(req, res) + + expect(res.render).toHaveBeenCalledWith( + 'receptionMoves/considerRisksReception.njk', + expect.objectContaining({ + inReceptionCount: '2 people in reception', + offendersInReception: [ + { + alerts: [], + csraClassification: 'Not entered', + displayCsraLink: undefined, + name: 'Mercedes, Max', + nonAssociation: false, + offenderNo: 'A123', + }, + { + alerts: [], + csraClassification: 'Not entered', + displayCsraLink: undefined, + name: 'Simpson, Jack', + nonAssociation: false, + offenderNo: 'B123', + }, + ], + }) + ) + }) + + it('should populate view model with correct urls', async () => { + prisonApi.getCsraAssessments.mockResolvedValue([ + { assessmentDate: ' 2020-10-10T10:00', assessmentComment: 'comment 1' }, + ]) + await controller.view(req, res) + + expect(res.render).toHaveBeenCalledWith( + 'receptionMoves/considerRisksReception.njk', + expect.objectContaining({ + backUrl: '/prisoners/A12345/location-details', + csraDetailsUrl: '/prisoner/A12345/cell-move/cell-sharing-risk-assessment-details', + displayLinkToPrisonersMostRecentCsra: 'comment 1', + nonAssociationLink: '/prisoner/A12345/cell-move/non-associations', + offenderDetailsUrl: '/prisoner/A12345/cell-move/prisoner-details', + searchForCellRootUrl: '/prisoner/A12345/cell-move/search-for-cell', + }) + ) + }) + + it('should not flash errors', async () => { + req.body = { considerRisksReception: 'yes' } + await controller.submit(req, res) + expect(req.flash).not.toHaveBeenCalled() + expect(res.redirect).toHaveBeenCalledWith(`/prisoner/${someOffenderNumber}/reception-move/confirm-reception-move`) + }) + it('should flash errors', async () => { + req.body = {} + await controller.submit(req, res) + expect(req.flash).toHaveBeenCalledWith('errors', [{ href: '#considerRisksReception', text: 'Select yes or no' }]) + expect(res.redirect).toHaveBeenCalledWith( + `/prisoner/${someOffenderNumber}/reception-move/consider-risks-reception` + ) + }) + + it('should redirect to previous page', async () => { + req.body = { considerRisksReception: 'no' } + await controller.submit(req, res) + expect(res.redirect).toHaveBeenCalledWith(`/prisoner/${someOffenderNumber}/location-details`) + }) + + it('should throw error when call to upstream api rejects', async () => { + const error = new Error('Network error') + movementsService.getCsraForMultipleOffenders.mockRejectedValue(error) + await expect(controller.view(req, res)).rejects.toThrowError(error) + expect(res.locals.homeUrl).toBe(`/prisoner/${someOffenderNumber}`) + expect(logError).toHaveBeenCalledWith('original-url', error, 'error getting consider-risks-reception') + }) + }) +}) diff --git a/package-lock.json b/package-lock.json index 056c72d81..324c52d8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6240,17 +6240,17 @@ } }, "node_modules/cheerio": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.9.tgz", - "integrity": "sha512-QF6XVdrLONO6DXRF5iaolY+odmhj2CLj+xzNod7INPWMi/x9X4SOylH0S/vaPpX+AUU6t04s34SQNh7DbkuCng==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", "dependencies": { - "cheerio-select": "^1.4.0", - "dom-serializer": "^1.3.1", - "domhandler": "^4.2.0", - "htmlparser2": "^6.1.0", - "parse5": "^6.0.1", - "parse5-htmlparser2-tree-adapter": "^6.0.1", - "tslib": "^2.2.0" + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" }, "engines": { "node": ">= 6" @@ -6260,24 +6260,177 @@ } }, "node_modules/cheerio-select": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.5.0.tgz", - "integrity": "sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", "dependencies": { - "css-select": "^4.1.3", - "css-what": "^5.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0", - "domutils": "^2.7.0" + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" }, "funding": { "url": "https://github.com/sponsors/fb55" } }, - "node_modules/cheerio/node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + "node_modules/cheerio-select/node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cheerio-select/node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cheerio-select/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/cheerio-select/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/cheerio-select/node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/cheerio-select/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/cheerio/node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } }, "node_modules/chokidar": { "version": "3.5.3", @@ -7094,6 +7247,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", + "dev": true, "dependencies": { "boolbase": "^1.0.0", "css-what": "^5.0.0", @@ -7119,6 +7273,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz", "integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==", + "dev": true, "engines": { "node": ">= 6" }, @@ -7711,9 +7866,9 @@ } }, "node_modules/domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", "funding": [ { "type": "github", @@ -10291,6 +10446,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -15179,14 +15335,55 @@ "node_modules/parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true }, "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", "dependencies": { - "parse5": "^6.0.1" + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, "node_modules/parseurl": { @@ -23818,36 +24015,136 @@ "dev": true }, "cheerio": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.9.tgz", - "integrity": "sha512-QF6XVdrLONO6DXRF5iaolY+odmhj2CLj+xzNod7INPWMi/x9X4SOylH0S/vaPpX+AUU6t04s34SQNh7DbkuCng==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", "requires": { - "cheerio-select": "^1.4.0", - "dom-serializer": "^1.3.1", - "domhandler": "^4.2.0", - "htmlparser2": "^6.1.0", - "parse5": "^6.0.1", - "parse5-htmlparser2-tree-adapter": "^6.0.1", - "tslib": "^2.2.0" + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" }, "dependencies": { - "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + } + }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + }, + "htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "requires": { + "entities": "^4.4.0" + } } } }, "cheerio-select": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.5.0.tgz", - "integrity": "sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", "requires": { - "css-select": "^4.1.3", - "css-what": "^5.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0", - "domutils": "^2.7.0" + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "dependencies": { + "css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" + }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + } + }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + } } }, "chokidar": { @@ -24477,6 +24774,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", + "dev": true, "requires": { "boolbase": "^1.0.0", "css-what": "^5.0.0", @@ -24498,7 +24796,8 @@ "css-what": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz", - "integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==" + "integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==", + "dev": true }, "cssesc": { "version": "3.0.0", @@ -24944,9 +25243,9 @@ } }, "domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" }, "domexception": { "version": "2.0.1", @@ -26899,6 +27198,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, "requires": { "domelementtype": "^2.0.1", "domhandler": "^4.0.0", @@ -30521,14 +30821,39 @@ "parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true }, "parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", "requires": { - "parse5": "^6.0.1" + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "dependencies": { + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + }, + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "requires": { + "entities": "^4.4.0" + } + } } }, "parseurl": { diff --git a/static/js/select-activity-location.js b/static/js/select-activity-location.js index bfb2ec7f3..a13f18206 100644 --- a/static/js/select-activity-location.js +++ b/static/js/select-activity-location.js @@ -1,4 +1,4 @@ -$(function() { +$(function () { const $date = $('#date') const $period = $('#period') const $location = $('#current-location') @@ -15,7 +15,7 @@ $(function() { window.location = '/manage-prisoner-whereabouts/select-location?date=' + dateValue + '&period=' + periodValue } - $date.on('change', function() { + $date.on('change', function () { const dateValue = $(this).val() const periodValue = $period.val() disableInputs() @@ -23,7 +23,7 @@ $(function() { reloadPage(dateValue, periodValue) }) - $period.on('change', function() { + $period.on('change', function () { const periodValue = $(this).val() const dateValue = $date.val() disableInputs() diff --git a/views/partials/layout.njk b/views/partials/layout.njk index 4999ff949..c835ef400 100644 --- a/views/partials/layout.njk +++ b/views/partials/layout.njk @@ -1,4 +1,14 @@ {% from "govuk/components/breadcrumbs/macro.njk" import govukBreadcrumbs %} +{% from "govuk/components/summary-list/macro.njk" import govukSummaryList %} +{% from "govuk/components/inset-text/macro.njk" import govukInsetText %} +{% from "govuk/components/warning-text/macro.njk" import govukWarningText %} +{% from "govuk/components/radios/macro.njk" import govukRadios %} +{% from "govuk/components/table/macro.njk" import govukTable %} +{% from "govuk/components/select/macro.njk" import govukSelect %} +{% from "govuk/components/radios/macro.njk" import govukRadios %} +{% from "govuk/components/button/macro.njk" import govukButton %} +{% from "govuk/components/back-link/macro.njk" import govukBackLink %} +{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} {% extends "govuk/template.njk" %} diff --git a/views/receptionMoves/considerRisksReception.njk b/views/receptionMoves/considerRisksReception.njk new file mode 100644 index 000000000..b6425e0e4 --- /dev/null +++ b/views/receptionMoves/considerRisksReception.njk @@ -0,0 +1,71 @@ +{% extends "../partials/layout.njk" %} +{% from "../macros/alertFlags.njk" import alertFlags %} + +{% set title = "Consider risks of moving this person to reception" %} + +{% block beforeContent %} + + {% if backUrl %} + {{ govukBackLink({ + text: "Back", + href: backUrl + }) }} + {% endif %} + +{% endblock %} + +{% block content %} + + {% if errors.length > 0 %} + {{ govukErrorSummary({ + titleText: "There is a problem", + errorList: errors, + attributes: { 'data-qa-errors': true } + }) }} + {% endif %} + +

{{ title }}

+ + {% include "./partials/prisonerInformation.njk" %} + + {% if nonAssociationsRows.length %} + {% include "./partials/nonAssociations.njk" %} + {% endif %} + + {% include "./partials/prisonersCurrentlyInReception.njk" %} + +
+ + + {{ govukRadios({ + classes: "govuk-radios--inline", + idPrefix: "considerRisksReception", + name: "considerRisksReception", + errorMessage: errors | findError('considerRisksReception'), + + fieldset: { + legend: { + text: "Are you sure you want to move "+ prisonerName + " to reception?", + classes: "govuk-fieldset__legend--s" + } + }, + items: [ + { + value: "yes", + text: "Yes" + }, + { + value: "no", + text: "No" + } + ] + }) }} + + {{ govukButton({ + text: "Continue", + preventDoubleClick: true, + type: "submit" + }) }} +
+ +{% endblock %} diff --git a/views/receptionMoves/partials/nonAssociations.njk b/views/receptionMoves/partials/nonAssociations.njk new file mode 100644 index 000000000..413c74be6 --- /dev/null +++ b/views/receptionMoves/partials/nonAssociations.njk @@ -0,0 +1,67 @@ +{{ govukWarningText({ + text: reverseOrderPrisonerName + " has a non-association with a prisoner in this location", + iconFallbackText: "Warning" +}) }} + +{% for nonAssociation in nonAssociationsRows %} + {{ govukInsetText({ + html: govukSummaryList({ + classes: "govuk-summary-list--no-border govuk-body", + attributes: {'data-test': 'non-association-summary-' + nonAssociation.prisonNumber}, + rows: [ + { + key: { + text: "Name" + }, + value: { + text: nonAssociation.name + } + }, + { + key: { + text: "Prison number" + }, + value: { + text: nonAssociation.prisonNumber + } + }, + { + key: { + text: "Type" + }, + value: { + text: nonAssociation.type + } + }, + { + key: { + text: nonAssociation.selectedOffenderKey + }, + value: { + text: nonAssociation.selectedOffenderRole + } + }, + { + key: { + text: nonAssociation.otherOffenderKey + }, + value: { + text: nonAssociation.otherOffenderRole + } + }, + { + key: { + text: "Comment" + }, + value: { + text: nonAssociation.comment + } + } + ] | removePaddingBottom + }), + classes: 'govuk-!-padding-top-0 govuk-!-padding-bottom-0' + }) }} + +{% endfor %} + +

You must have checked any local processes for non-associations.

diff --git a/views/receptionMoves/partials/prisonerInformation.njk b/views/receptionMoves/partials/prisonerInformation.njk new file mode 100644 index 000000000..e00558905 --- /dev/null +++ b/views/receptionMoves/partials/prisonerInformation.njk @@ -0,0 +1,50 @@ +
+
+
+

Name

+

{{ reverseOrderPrisonerName }}

+

+ View details + for {{ reverseOrderPrisonerName }} + +

+
+
+

Current location

+

{{ prisonerDetails.assignedLivingUnit.description }}

+
+
+

CSRA rating

+

{{ convertedCsra }}

+ + {% if displayLinkToPrisonersMostRecentCsra %} +

+ View details + for {{ reverseOrderPrisonerName }}'s CSRA + +

+ {% endif %} +
+
+

Relevant alerts

+ {% if prisonerAlerts.length %} +
+ {{ alertFlags(prisonerAlerts) }} +
+ {% else %} +

None

+ {% endif %} +
+
+

Non-associations

+ {% if hasNonAssociations %} + View non-associations + for {{ reverseOrderPrisonerName }} + + {% else %} +

0 in NOMIS. Check local processes.

+ {% endif %} +
+
+ +
diff --git a/views/receptionMoves/partials/prisonersCurrentlyInReception.njk b/views/receptionMoves/partials/prisonersCurrentlyInReception.njk new file mode 100644 index 000000000..9e12b0306 --- /dev/null +++ b/views/receptionMoves/partials/prisonersCurrentlyInReception.njk @@ -0,0 +1,80 @@ +{% from "../../macros/alertFlags.njk" import alertFlags %} + +{% macro name(name, offenderNo, nonAssocation) %} +
+
+

{{name}}

+

for {{ prisoner.name }} View details

+ {% if nonAssocation %} +
+ NON-ASSOCIATION +
+ {% endif %} +
+
+{% endmacro %} + +{% macro csra(csraClassification, offenderNo, name, displayCsraLink) %} +
+
+

{{csraClassification}}

+ {% if displayCsraLink %} +

+ + for {{ name }} + View details + +

+ {% endif %} +
+
+{% endmacro %} + +{% macro relavantAlerts(alert) %} +
+
+

{{alertFlags(alert) if alert | length else "None"}}

+
+
+{% endmacro %} + +{% set rows = [] %} +{% for offender in offendersInReception %} + {% set rows = (rows.push([ + { + html: name(offender.name, offender.offenderNo, offender.nonAssociation ), + classes:"govuk-!-width-one-quarter" + }, + { + html: csra(offender.csraClassification, offender.offenderNo, offender.name, offender.displayCsraLink), + classes:"govuk-!-width-one-quarter" + }, + { + html: relavantAlerts(offender.alerts) + } + ]), rows) %} +{% endfor %} + +

People currently in reception

+ +{{ govukInsetText({ + text: inReceptionCount +}) }} + +{{ govukTable({ + caption: "", + captionClasses: "govuk-table__caption--m", + firstCellIsHeader: false, + head: [ + { + text: "Name" + }, + { + text: "CSRA" + }, + { + text: "Relevant alerts" + } + ], + rows: rows +}) }}