Skip to content

Commit

Permalink
Merge pull request #2275 from ministryofjustice/feature/APS-1751_Crit…
Browse files Browse the repository at this point in the history
…eria_tranfer_to_space_booking

APS-1751 Criteria transfer to space booking
  • Loading branch information
bobmeredith authored Jan 9, 2025
2 parents a709cf2 + affc768 commit 5492e9b
Show file tree
Hide file tree
Showing 10 changed files with 125 additions and 53 deletions.
24 changes: 13 additions & 11 deletions integration_tests/pages/match/bookASpacePage.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { ApType, PlacementDates, PlacementRequestDetail, Premises } from '@approved-premises/api'
import {
ApType,
Cas1SpaceBookingCharacteristic,
Cas1SpaceCharacteristic,
PlacementDates,
PlacementRequestDetail,
Premises,
} from '@approved-premises/api'
import Page from '../page'
import paths from '../../../server/paths/match'
import { createQueryString, sentenceCase } from '../../../server/utils/utils'
import { DateFormats } from '../../../server/utils/dateUtils'
import {
filterOutAPTypes,
placementDates,
placementLength as placementLengthInDaysAndWeeks,
} from '../../../server/utils/match'
import { placementDates, placementLength as placementLengthInDaysAndWeeks } from '../../../server/utils/match'
import { placementCriteriaLabels } from '../../../server/utils/placementCriteriaUtils'
import { apTypeLabels } from '../../../server/utils/apTypeLabels'

Expand All @@ -23,8 +26,9 @@ export default class BookASpacePage extends Page {
premisesName: Premises['name'],
premisesId: Premises['id'],
apType: ApType,
criteria: Array<Cas1SpaceBookingCharacteristic>,
) {
const queryString = createQueryString({ startDate, durationDays, premisesName, premisesId, apType })
const queryString = createQueryString({ startDate, durationDays, premisesName, premisesId, apType, criteria })
const path = `${paths.v2Match.placementRequests.spaceBookings.new({ id: placementRequest.id })}?${queryString}`
cy.visit(path)
return new BookASpacePage(premisesName)
Expand All @@ -35,17 +39,15 @@ export default class BookASpacePage extends Page {
startDate: string,
duration: PlacementDates['duration'],
apType: ApType,
criteria?: Array<Cas1SpaceCharacteristic>,
): void {
const { endDate, placementLength } = placementDates(startDate, duration.toString())
cy.get('dd').contains(apTypeLabels[apType])
cy.get('dd').contains(DateFormats.isoDateToUIDate(startDate))
cy.get('dd').contains(DateFormats.isoDateToUIDate(endDate))
cy.get('dd').contains(placementLengthInDaysAndWeeks(placementLength))
cy.get('dd').contains(sentenceCase(placementRequest.gender))
filterOutAPTypes(placementRequest.essentialCriteria).forEach(requirement => {
cy.get('li').contains(placementCriteriaLabels[requirement])
})
filterOutAPTypes(placementRequest.desirableCriteria).forEach(requirement => {
;(criteria || []).forEach(requirement => {
cy.get('li').contains(placementCriteriaLabels[requirement])
})
}
Expand Down
30 changes: 22 additions & 8 deletions integration_tests/tests/match/match.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import Page from '../../pages/page'
import { signIn } from '../signIn'

import ListPage from '../../pages/admin/placementApplications/listPage'
import { filterOutAPTypes, placementDates } from '../../../server/utils/match'
import { filterOutAPTypes, filterToSpaceBookingCharacteristics, placementDates } from '../../../server/utils/match'
import BookASpacePage from '../../pages/match/bookASpacePage'
import OccupancyViewPage from '../../pages/match/occupancyViewPage'
import applicationFactory from '../../../server/testutils/factories/application'
Expand Down Expand Up @@ -78,7 +78,7 @@ context('Placement Requests', () => {
// And the results should have links with the correct AP type and criteria
searchPage.shouldHaveSearchParametersInLinks(newSearchParameters)

// And the parameters should be submitted to the API
// // And the parameters should be submitted to the API
cy.task('verifySearchSubmit').then(requests => {
expect(requests).to.have.length(numberOfSearches)
const initialSearchRequestBody = JSON.parse(requests[0].body)
Expand Down Expand Up @@ -305,22 +305,36 @@ context('Placement Requests', () => {

// And there is a placement request waiting for me to match
const person = personFactory.build()
const essentialCharacteristics: Array<PlacementCriteria> = ['acceptsHateCrimeOffenders']
const desirableCharacteristics: Array<PlacementCriteria> = ['isCatered', 'hasEnSuite']
const essentialCharacteristics: Array<PlacementCriteria> = ['hasEnSuite']
const desirableCharacteristics: Array<PlacementCriteria> = ['isCatered']
const placementRequest = placementRequestDetailFactory.build({
person,
status: 'notMatched',
duration: durationDays,
essentialCriteria: essentialCharacteristics,
essentialCriteria: [],
desirableCriteria: desirableCharacteristics,
})

// When I visit the 'Book a space' page
cy.task('stubPlacementRequest', placementRequest)
const page = BookASpacePage.visit(placementRequest, startDate, durationDays, premisesName, premisesId, apType)
const page = BookASpacePage.visit(
placementRequest,
startDate,
durationDays,
premisesName,
premisesId,
apType,
filterToSpaceBookingCharacteristics(essentialCharacteristics),
)

// Then I should see the details of the space I am booking
page.shouldShowBookingDetails(placementRequest, startDate, durationDays, apType)
page.shouldShowBookingDetails(
placementRequest,
startDate,
durationDays,
apType,
filterToSpaceBookingCharacteristics(essentialCharacteristics),
)

// And when I complete the form
const requirements = spaceBookingRequirementsFactory.build()
Expand All @@ -346,7 +360,7 @@ context('Placement Requests', () => {
premisesId,
requirements: {
...spaceBooking.requirements,
essentialCharacteristics: placementRequest.essentialCriteria,
essentialCharacteristics,
},
})
})
Expand Down
3 changes: 1 addition & 2 deletions server/@types/ui/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
RiskTier,
RiskTierLevel,
RoshRisks,
Cas1SpaceCharacteristic as SpaceCharacteristic,
ApprovedPremisesUser as User,
UserQualification,
ApprovedPremisesUserRole as UserRole,
Expand Down Expand Up @@ -386,7 +385,7 @@ export interface SpaceSearchParametersUi {
requirements: {
apType: ApType
gender: Gender
spaceCharacteristics: Array<SpaceCharacteristic>
spaceCharacteristics: Array<Cas1SpaceCharacteristic>
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ export default class {
DateFormats.isoToDateObj(departureDate),
DateFormats.isoToDateObj(arrivalDate),
).toString(),
criteria: body.criteria ? body.criteria : undefined,
})
res.redirect(redirectUrl)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { NextFunction, Request, Response } from 'express'
import { DeepMocked, createMock } from '@golevelup/ts-jest'

import { faker } from '@faker-js/faker'
import { Cas1SpaceBookingCharacteristic } from '@approved-premises/api'
import SpaceBookingsController from './spaceBookingsController'

import { PlacementRequestService, SpaceService } from '../../../services'
Expand All @@ -11,9 +13,10 @@ import {
placementRequestDetailFactory,
spaceBookingRequirementsFactory,
} from '../../../testutils/factories'
import { filterOutAPTypes, placementDates } from '../../../utils/match'
import { filterOutAPTypes, occupancyViewLink, placementDates } from '../../../utils/match'
import paths from '../../../paths/admin'
import { fetchErrorsAndUserInput } from '../../../utils/validation'
import { occupancyCriteriaMap } from '../../../utils/match/occupancy'

jest.mock('../../../utils/validation')
describe('SpaceBookingsController', () => {
Expand Down Expand Up @@ -42,6 +45,18 @@ describe('SpaceBookingsController', () => {
const premisesName = 'Hope House'
const premisesId = 'abc123'
const apType = 'esap'
const criteria = faker.helpers.arrayElements(Object.keys(occupancyCriteriaMap), {
min: 0,
max: 3,
}) as Array<Cas1SpaceBookingCharacteristic>
const backLink = occupancyViewLink({
placementRequestId: placementRequestDetail.id,
premisesId,
apType,
startDate,
durationDays,
spaceCharacteristics: criteria,
})
;(fetchErrorsAndUserInput as jest.Mock).mockReturnValue({ errors: [], errorSummary: {}, userInput: {} })
placementRequestService.getPlacementRequest.mockResolvedValue(placementRequestDetail)

Expand All @@ -51,6 +66,7 @@ describe('SpaceBookingsController', () => {
premisesName,
premisesId,
apType,
criteria: criteria.join(','),
}

const params = { id: placementRequestDetail.id }
Expand All @@ -72,8 +88,9 @@ describe('SpaceBookingsController', () => {
errorSummary: {},
errors: [],
dates: placementDates(startDate, durationDays),
essentialCharacteristics: filterOutAPTypes(placementRequestDetail.essentialCriteria),
essentialCharacteristics: criteria,
desirableCharacteristics: filterOutAPTypes(placementRequestDetail.desirableCriteria),
backLink,
})
expect(placementRequestService.getPlacementRequest).toHaveBeenCalledWith(token, placementRequestDetail.id)
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
import type { Request, RequestHandler, Response, TypedRequestHandler } from 'express'
import type { ApType, Cas1NewSpaceBooking } from '@approved-premises/api'
import type { ApType, Cas1NewSpaceBooking, PlacementCriteria } from '@approved-premises/api'
import { PlacementRequestService, SpaceService } from '../../../services'
import { filterOutAPTypes, placementDates } from '../../../utils/match'
import {
filterOutAPTypes,
filterToSpaceBookingCharacteristics,
occupancyViewLink,
placementDates,
} from '../../../utils/match'
import { catchValidationErrorOrPropogate, fetchErrorsAndUserInput } from '../../../utils/validation'
import paths from '../../../paths/admin'
import matchPaths from '../../../paths/match'
import { createQueryString } from '../../../utils/utils'

interface NewRequest extends Request {
params: { id: string }
query: { startDate: string; durationDays: string; premisesName: string; premisesId: string; apType: ApType }
query: {
startDate: string
durationDays: string
premisesName: string
premisesId: string
apType: ApType
criteria: string
}
}

export default class {
Expand All @@ -21,9 +33,19 @@ export default class {
new(): TypedRequestHandler<Request, Response> {
return async (req: NewRequest, res: Response) => {
const placementRequest = await this.placementRequestService.getPlacementRequest(req.user.token, req.params.id)
const { startDate, durationDays, premisesName, premisesId, apType } = req.query
const { startDate, durationDays, premisesName, premisesId, apType, criteria } = req.query
const { errors, errorSummary } = fetchErrorsAndUserInput(req)

const essentialCharacteristics = filterToSpaceBookingCharacteristics(
(criteria ? criteria.split(',') : []) as Array<PlacementCriteria>,
)
const backLink = occupancyViewLink({
placementRequestId: placementRequest.id,
premisesId,
apType,
startDate,
durationDays,
spaceCharacteristics: essentialCharacteristics,
})
res.render('match/placementRequests/spaceBookings/new', {
pageHeading: `Book space in ${premisesName}`,
placementRequest,
Expand All @@ -33,10 +55,11 @@ export default class {
startDate,
durationDays,
dates: placementDates(startDate, durationDays),
essentialCharacteristics: filterOutAPTypes(placementRequest.essentialCriteria),
essentialCharacteristics,
desirableCharacteristics: filterOutAPTypes(placementRequest.desirableCriteria),
errors,
errorSummary,
backLink,
})
}
}
Expand Down
4 changes: 2 additions & 2 deletions server/testutils/factories/spaceSearchParameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { faker } from '@faker-js/faker/locale/en_GB'

import type { Cas1SpaceSearchParameters, Cas1SpaceSearchRequirements } from '@approved-premises/api'
import { DateFormats } from '../../utils/dateUtils'
import { filterOutAPTypes } from '../../utils/match'
import { filterOutAPTypes, filterToSpaceBookingCharacteristics } from '../../utils/match'
import { placementCriteria } from './placementRequest'
import postcodeAreas from '../../etc/postcodeAreas.json'
import { SpaceSearchParametersUi } from '../../@types/ui'
Expand Down Expand Up @@ -35,7 +35,7 @@ export const spaceSearchParametersUiFactory = Factory.define<SpaceSearchParamete
durationInDays: faker.number.int({ max: 100, min: 1 }).toString(),
requirements: {
apType: faker.helpers.arrayElement(['pipe', 'esap', 'rfap', 'mhapStJosephs', 'mhapElliottHouse']),
spaceCharacteristics: faker.helpers.arrayElements(filterOutAPTypes(placementCriteria)),
spaceCharacteristics: faker.helpers.arrayElements(filterToSpaceBookingCharacteristics(placementCriteria)),
gender: faker.helpers.arrayElement(['male', 'female']),
},
...startDateInputsValues,
Expand Down
32 changes: 19 additions & 13 deletions server/utils/match/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {
ApType,
ApprovedPremisesApplication,
Cas1SpaceCharacteristic,
Cas1SpaceBookingCharacteristic,
FullPerson,
PlacementCriteria,
} from '@approved-premises/api'
Expand Down Expand Up @@ -35,6 +35,7 @@ import {
encodeSpaceSearchResult,
essentialCharacteristicsRow,
filterOutAPTypes,
filterToSpaceBookingCharacteristics,
genderRow,
groupedCheckboxes,
groupedCriteria,
Expand Down Expand Up @@ -260,7 +261,7 @@ describe('matchUtils', () => {
const apType = 'pipe'
const startDate = '2025-04-14'
const durationDays = '84'
const spaceCharacteristics: Array<Cas1SpaceCharacteristic> = ['isWheelchairDesignated', 'isSingle']
const spaceCharacteristics: Array<Cas1SpaceBookingCharacteristic> = ['isWheelchairDesignated', 'isSingle']

const result = occupancyViewLink({
placementRequestId,
Expand All @@ -285,14 +286,16 @@ describe('matchUtils', () => {
const apType = 'pipe'
const startDate = '2025-04-14'
const durationDays = '84'
const spaceCharacteristics: Array<Cas1SpaceCharacteristic> = [
const spaceCharacteristics: Array<PlacementCriteria> = [
'isPIPE',
'isESAP',
'isMHAPStJosephs',
'isMHAPElliottHouse',
'isSemiSpecialistMentalHealth',
'isRecoveryFocussed',
'isWheelchairDesignated',
'isIAP',
'hasArsonInsuranceConditions',
'isSingle',
'acceptsHateCrimeOffenders',
'hasEnSuite',
'isArsonDesignated',
'isArsonSuitable',
]

Expand All @@ -302,7 +305,7 @@ describe('matchUtils', () => {
apType,
startDate,
durationDays,
spaceCharacteristics,
spaceCharacteristics: filterToSpaceBookingCharacteristics(spaceCharacteristics),
})

expect(result).toEqual(
Expand All @@ -322,6 +325,7 @@ describe('matchUtils', () => {
const apType = 'pipe'
const startDate = '2022-01-01'
const durationDays = '1'
const criteria: Array<Cas1SpaceBookingCharacteristic> = ['hasEnSuite', 'isArsonSuitable']

const result = redirectToSpaceBookingsNew({
placementRequestId,
Expand All @@ -330,16 +334,18 @@ describe('matchUtils', () => {
apType,
startDate,
durationDays,
criteria,
})

expect(result).toEqual(
`${paths.v2Match.placementRequests.spaceBookings.new({ id: placementRequestId })}${createQueryString(
{
premisesName,
premisesId,
apType,
startDate,
durationDays,
premisesName: 'Hope House',
premisesId: 'abc',
apType: 'pipe',
startDate: '2022-01-01',
durationDays: '1',
criteria: ['hasEnSuite', 'isArsonSuitable'],
},
{ addQueryPrefix: true, arrayFormat: 'repeat' },
)}`,
Expand Down
Loading

0 comments on commit 5492e9b

Please sign in to comment.