From 2b42ba7785263416ab9e5e7402c0fa8ea695f19c Mon Sep 17 00:00:00 2001 From: Greg Hawkins Date: Tue, 7 Jan 2025 17:06:55 +0000 Subject: [PATCH] Refactor placement-request page information --- .../admin/placementApplications/showPage.ts | 17 +- .../tests/admin/placementRequests.cy.ts | 4 - .../placementRequestsController.test.ts | 2 + .../placementRequestsController.ts | 2 + .../testutils/factories/offlineApplication.ts | 16 ++ server/utils/match/index.test.ts | 104 ++++++++- server/utils/match/index.ts | 68 ++++-- .../adminIdentityBar.test.ts | 37 +-- .../placementRequests/adminIdentityBar.ts | 9 +- .../matchingInformationSummaryList.ts | 23 +- .../placementRequestSummaryList.test.ts | 214 ++++++++++++++++++ .../placementRequestSummaryList.ts | 34 +++ .../placementRequests/preferredApsRow.test.ts | 2 +- .../placementRequests/preferredApsRow.ts | 2 +- server/views/admin/placementRequests/show.njk | 16 +- 15 files changed, 458 insertions(+), 92 deletions(-) create mode 100644 server/testutils/factories/offlineApplication.ts create mode 100644 server/utils/placementRequests/placementRequestSummaryList.test.ts create mode 100644 server/utils/placementRequests/placementRequestSummaryList.ts diff --git a/integration_tests/pages/admin/placementApplications/showPage.ts b/integration_tests/pages/admin/placementApplications/showPage.ts index 4cdf55e74e..4d9058a23b 100644 --- a/integration_tests/pages/admin/placementApplications/showPage.ts +++ b/integration_tests/pages/admin/placementApplications/showPage.ts @@ -1,25 +1,16 @@ +import { ApprovedPremises, PlacementRequest, PlacementRequestDetail } from '@approved-premises/api' import Page from '../../page' -import { - ApprovedPremises, - FullPerson, - PlacementRequest, - PlacementRequestDetail, -} from '../../../../server/@types/shared' -import { adminSummary, matchingInformationSummary } from '../../../../server/utils/placementRequests' import { bookingSummaryList } from '../../../../server/utils/bookings' +import { placementRequestSummaryList } from '../../../../server/utils/placementRequests/placementRequestSummaryList' export default class ShowPage extends Page { constructor(private readonly placementRequest: PlacementRequestDetail) { - super((placementRequest.person as FullPerson).name) + super('Placement request') } shouldShowSummary(): void { - this.shouldContainSummaryListItems(adminSummary(this.placementRequest).rows) - } - - shouldShowMatchingInformationSummary(): void { - this.shouldContainSummaryListItems(matchingInformationSummary(this.placementRequest).rows) + this.shouldContainSummaryListItems(placementRequestSummaryList(this.placementRequest).rows) } clickPlacementRequest(placementRequest: PlacementRequest): void { diff --git a/integration_tests/tests/admin/placementRequests.cy.ts b/integration_tests/tests/admin/placementRequests.cy.ts index 7f4a21004e..b55a34b941 100644 --- a/integration_tests/tests/admin/placementRequests.cy.ts +++ b/integration_tests/tests/admin/placementRequests.cy.ts @@ -113,7 +113,6 @@ context('Placement Requests', () => { unmatchedPlacementRequests, unableToMatchPlacementRequests, matchedPlacementRequests, - preferredAps, matchedPlacementRequest, parolePlacementRequest, } = stubArtifacts() @@ -149,8 +148,6 @@ context('Placement Requests', () => { // And I should see the information about the placement request showPage.shouldShowSummary() - showPage.shouldShowMatchingInformationSummary() - showPage.shouldShowPreferredAps(preferredAps) // And I should not see any booking information showPage.shouldNotShowBookingInformation() @@ -173,7 +170,6 @@ context('Placement Requests', () => { // And I should see the information about the placement request showPage.shouldShowSummary() - showPage.shouldShowMatchingInformationSummary() showPage.shouldNotShowCreateBookingOption() showPage.shouldShowAmendBookingOption() diff --git a/server/controllers/admin/placementRequests/placementRequestsController.test.ts b/server/controllers/admin/placementRequests/placementRequestsController.test.ts index 534032c47f..4e67562d63 100644 --- a/server/controllers/admin/placementRequests/placementRequestsController.test.ts +++ b/server/controllers/admin/placementRequests/placementRequestsController.test.ts @@ -6,6 +6,7 @@ import PlacementRequestsController from './placementRequestsController' import { PlacementRequestService } from '../../../services' import { userDetailsFactory } from '../../../testutils/factories' import placementRequestDetail from '../../../testutils/factories/placementRequestDetail' +import { placementRequestSummaryList } from '../../../utils/placementRequests/placementRequestSummaryList' jest.mock('../../../utils/applications/utils') jest.mock('../../../utils/applications/getResponses') @@ -41,6 +42,7 @@ describe('PlacementRequestsController', () => { expect(response.render).toHaveBeenCalledWith('admin/placementRequests/show', { placementRequest, + placementRequestSummaryList: placementRequestSummaryList(placementRequest), }) expect(placementRequestService.getPlacementRequest).toHaveBeenCalledWith(token, 'some-uuid') }) diff --git a/server/controllers/admin/placementRequests/placementRequestsController.ts b/server/controllers/admin/placementRequests/placementRequestsController.ts index 0f054e19bf..8433680a17 100644 --- a/server/controllers/admin/placementRequests/placementRequestsController.ts +++ b/server/controllers/admin/placementRequests/placementRequestsController.ts @@ -1,5 +1,6 @@ import type { Request, Response, TypedRequestHandler } from 'express' import { PlacementRequestService } from '../../../services' +import { placementRequestSummaryList } from '../../../utils/placementRequests/placementRequestSummaryList' export default class PlacementRequestsController { constructor(private readonly placementRequestService: PlacementRequestService) {} @@ -10,6 +11,7 @@ export default class PlacementRequestsController { res.render('admin/placementRequests/show', { placementRequest, + placementRequestSummaryList: placementRequestSummaryList(placementRequest), }) } } diff --git a/server/testutils/factories/offlineApplication.ts b/server/testutils/factories/offlineApplication.ts new file mode 100644 index 0000000000..787e6cd35b --- /dev/null +++ b/server/testutils/factories/offlineApplication.ts @@ -0,0 +1,16 @@ +import { Factory } from 'fishery' +import { faker } from '@faker-js/faker/locale/en_GB' + +import type { OfflineApplication } from '@approved-premises/api' + +import { fullPersonFactory, restrictedPersonFactory } from './person' +import { DateFormats } from '../../utils/dateUtils' + +class OfflineApplicationFactory extends Factory {} + +export default OfflineApplicationFactory.define(() => ({ + type: 'CAS1', + id: faker.string.uuid(), + person: faker.helpers.arrayElement([fullPersonFactory.build(), restrictedPersonFactory.build()]), + createdAt: DateFormats.dateObjToIsoDateTime(faker.date.past()), +})) diff --git a/server/utils/match/index.test.ts b/server/utils/match/index.test.ts index 287cabe1d2..ff3ab0196d 100644 --- a/server/utils/match/index.test.ts +++ b/server/utils/match/index.test.ts @@ -1,5 +1,6 @@ import type { ApType, + Application, ApprovedPremisesApplication, Cas1SpaceBookingCharacteristic, FullPerson, @@ -7,6 +8,7 @@ import type { } from '@approved-premises/api' import { when } from 'jest-when' import type { SummaryListItem } from '@approved-premises/ui' +import applyPaths from '../../paths/apply' import paths from '../../paths/match' import { personFactory, @@ -23,6 +25,7 @@ import { apManagerDetailsRow, apTypeLabelsForRadioInput, apTypeRow, + apTypeWithViewTimelineActionRow, arrivalDateRow, calculateDepartureDate, characteristicsRow, @@ -49,10 +52,11 @@ import { placementLength, placementLengthRow, placementRequestSummaryListForMatching, - postcodeRow, + preferredPostcodeRow, premisesNameRow, redirectToSpaceBookingsNew, releaseTypeRow, + requestedOrEstimatedArrivalDateRow, requirementsHtmlString, spaceBookingPersonNeedsSummaryCardRows, spaceBookingPremisesSummaryCardRows, @@ -71,6 +75,7 @@ import { textValue } from '../applications/helpers' import { preferredApsRow } from '../placementRequests/preferredApsRow' import { placementRequirementsRow } from '../placementRequests/placementRequirementsRow' import applicationFactory from '../../testutils/factories/application' +import offlineApplicationFactory from '../../testutils/factories/offlineApplication' jest.mock('../retrieveQuestionResponseFromFormArtifact') @@ -111,6 +116,78 @@ describe('matchUtils', () => { }) }) + describe('requestedOrEstimatedArrivalDateRow', () => { + it('should return Estimated arrival date with date when is parole', () => { + const arrivalDate = '2024-01-28' + expect(requestedOrEstimatedArrivalDateRow(true, arrivalDate)).toEqual({ + key: { text: 'Estimated arrival date' }, + value: { text: 'Sun 28 Jan 2024' }, + }) + }) + + it('should return Requested arrival date with date when is not parole', () => { + const arrivalDate = '2024-01-28' + expect(requestedOrEstimatedArrivalDateRow(false, arrivalDate)).toEqual({ + key: { text: 'Requested arrival date' }, + value: { text: 'Sun 28 Jan 2024' }, + }) + }) + }) + + describe('apTypeRow', () => { + it.each(Object.keys(apTypeLabels) as Array)( + 'should return the correct type for AP Type %s', + (apType: ApType) => { + const placementRequestWithApType = placementRequestDetailFactory.build({ + type: apType, + }) + + expect(apTypeWithViewTimelineActionRow(placementRequestWithApType)).toEqual({ + key: { + text: 'Type of AP', + }, + value: { + text: apTypeLabels[apType], + }, + actions: { + items: [ + { + href: `${applyPaths.applications.show({ id: placementRequestWithApType.application.id })}?tab=timeline`, + text: 'View timeline', + }, + ], + }, + }) + }, + ) + + it('should return the correct type for AP Type normal without actions when placement-request has no application', () => { + const apType: ApType = 'normal' + const placementRequestWithApType = placementRequestDetailFactory.build({ + type: apType, + application: undefined, + }) + expect(apTypeWithViewTimelineActionRow(placementRequestWithApType)).toEqual({ + key: { + text: 'Type of AP', + }, + value: { + text: apTypeLabels[apType], + }, + }) + }) + }) + + describe('preferredPostcodeRow', () => { + it('returns preferred postcode', () => { + const postcode = 'B71' + expect(preferredPostcodeRow(postcode)).toEqual({ + key: { text: 'Preferred postcode' }, + value: { text: postcode }, + }) + }) + }) + describe('distanceRow', () => { const spaceSearchResult = spaceSearchResultFactory.build() const postcodeArea = 'HR1 2AF' @@ -417,7 +494,7 @@ describe('matchUtils', () => { departureDateRow(dates.endDate), placementLengthRow(dates.placementLength), releaseTypeRow(placementRequest), - licenceExpiryDateRow(placementRequest.application as ApprovedPremisesApplication), + licenceExpiryDateRow(placementRequest), totalCapacityRow(totalCapacity), apManagerDetailsRow(managerDetails), spaceRequirementsRow(filterOutAPTypes(placementRequest.essentialCriteria)), @@ -430,6 +507,27 @@ describe('matchUtils', () => { ) }) + it(`should generate the expected matching details when placement-request's application is undefined`, () => { + const undefinedApplication: Application = undefined + const placementRequestWithoutLicenceExpiry = { + ...placementRequest, + application: undefinedApplication, + } + expect( + occupancyViewSummaryListForMatchingDetails(totalCapacity, placementRequestWithoutLicenceExpiry, managerDetails), + ).toEqual(expectedMatchingDetailsSummaryListItems('')) + }) + + it(`should generate the expected matching details when placement-request's application is not of type ApprovedPremisesApplication`, () => { + const placementRequestWithoutLicenceExpiry = { + ...placementRequest, + application: offlineApplicationFactory.build(), + } + expect( + occupancyViewSummaryListForMatchingDetails(totalCapacity, placementRequestWithoutLicenceExpiry, managerDetails), + ).toEqual(expectedMatchingDetailsSummaryListItems('')) + }) + it(`should generate the expected matching details with blank licence expiry date when application's license-expiry date is not set`, () => { const placementRequestWithoutLicenceExpiry = { ...placementRequest, @@ -625,7 +723,7 @@ describe('matchUtils', () => { ), ), lengthOfStayRow(placementRequest.duration), - postcodeRow(placementRequest.location), + preferredPostcodeRow(placementRequest.location), apTypeRow(placementRequest.type), placementRequirementsRow(placementRequest, 'essential'), placementRequirementsRow(placementRequest, 'desirable'), diff --git a/server/utils/match/index.ts b/server/utils/match/index.ts index 041cb385e2..4913980b0d 100644 --- a/server/utils/match/index.ts +++ b/server/utils/match/index.ts @@ -34,6 +34,7 @@ import { placementRequirementsRow } from '../placementRequests/placementRequirem import { allReleaseTypes } from '../applications/releaseTypeUtils' import { placementDates } from './placementDates' import { occupancyCriteriaMap } from './occupancy' +import paths from '../../paths/apply' export { placementDates } from './placementDates' export { occupancySummary } from './occupancySummary' @@ -181,14 +182,12 @@ export const occupancyViewSummaryListForMatchingDetails = ( ): Array => { const placementRequestDates = placementDates(placementRequest.expectedArrival, placementRequest.duration) const essentialCharacteristics = filterOutAPTypes(placementRequest.essentialCriteria) - const application = placementRequest.application as ApprovedPremisesApplication - return [ arrivalDateRow(placementRequestDates.startDate), departureDateRow(placementRequestDates.endDate), placementLengthRow(placementRequestDates.placementLength), releaseTypeRow(placementRequest), - licenceExpiryDateRow(application), + licenceExpiryDateRow(placementRequest), totalCapacityRow(totalCapacity), apManagerDetailsRow(managerDetails), spaceRequirementsRow(essentialCharacteristics), @@ -244,6 +243,15 @@ export const arrivalDateRow = (arrivalDate: string) => ({ }, }) +export const requestedOrEstimatedArrivalDateRow = (isParole: boolean, arrivalDate: string) => ({ + key: { + text: isParole ? 'Estimated arrival date' : 'Requested arrival date', + }, + value: { + text: DateFormats.isoDateToUIDate(arrivalDate), + }, +}) + export const departureDateRow = (departureDate: string) => ({ key: { text: 'Expected departure date', @@ -280,6 +288,31 @@ export const apTypeRow = (apType: ApType) => ({ }, }) +export const apTypeWithViewTimelineActionRow = (placementRequest: PlacementRequestDetail) => { + const apTypeItem = { + key: { + text: 'Type of AP', + }, + value: { + text: apTypeLabels[placementRequest.type], + }, + } + if (placementRequest.application) { + return { + ...apTypeItem, + actions: { + items: [ + { + href: `${paths.applications.show({ id: placementRequest.application.id })}?tab=timeline`, + text: 'View timeline', + }, + ], + }, + } + } + return apTypeItem +} + export const addressRow = (spaceSearchResult: SpaceSearchResult) => ({ key: { text: 'Address', @@ -376,14 +409,21 @@ export const releaseTypeRow = (placementRequest: PlacementRequest) => ({ }, }) -export const licenceExpiryDateRow = (application: ApprovedPremisesApplication) => ({ - key: { - text: 'Licence expiry date', - }, - value: { - text: application.licenceExpiryDate ? DateFormats.isoDateToUIDate(application.licenceExpiryDate) : '', - }, -}) +export const licenceExpiryDateRow = (placementRequest: PlacementRequestDetail) => { + let licenceExpiryDate: string | undefined + if (placementRequest.application && 'licenceExpiryDate' in placementRequest.application) { + const application = placementRequest.application as ApprovedPremisesApplication + licenceExpiryDate = application.licenceExpiryDate + } + return { + key: { + text: 'Licence expiry date', + }, + value: { + text: licenceExpiryDate ? DateFormats.isoDateToUIDate(licenceExpiryDate) : '', + }, + } +} export const startDateObjFromParams = (params: { startDate: string } | ObjectWithDateParts<'startDate'>) => { if (params['startDate-day'] && params['startDate-month'] && params['startDate-year']) { @@ -444,9 +484,9 @@ export const lengthOfStayRow = (lengthInDays: number) => ({ }, }) -export const postcodeRow = (postcodeDistrict: PlacementRequestDetail['location']) => ({ +export const preferredPostcodeRow = (postcodeDistrict: PlacementRequestDetail['location']) => ({ key: { - text: 'Postcode', + text: 'Preferred postcode', }, value: { text: postcodeDistrict, @@ -464,7 +504,7 @@ export const placementRequestSummaryListForMatching = (placementRequest: Placeme DateFormats.dateObjToIsoDate(calculateDepartureDate(placementRequest.expectedArrival, placementRequest.duration)), ), lengthOfStayRow(placementRequest.duration), - postcodeRow(placementRequest.location), + preferredPostcodeRow(placementRequest.location), apTypeRow(placementRequest.type), ] diff --git a/server/utils/placementRequests/adminIdentityBar.test.ts b/server/utils/placementRequests/adminIdentityBar.test.ts index e19cb25884..2ab7f877fa 100644 --- a/server/utils/placementRequests/adminIdentityBar.test.ts +++ b/server/utils/placementRequests/adminIdentityBar.test.ts @@ -1,18 +1,12 @@ import { fromPartial } from '@total-typescript/shoehorn' import { ApprovedPremisesUserPermission } from '@approved-premises/api' -import { - personFactory, - placementRequestDetailFactory, - restrictedPersonFactory, - userDetailsFactory, -} from '../../testutils/factories' +import { personFactory, placementRequestDetailFactory, userDetailsFactory } from '../../testutils/factories' import { adminActions, adminIdentityBar, title } from './adminIdentityBar' import managePaths from '../../paths/manage' import matchPaths from '../../paths/match' import adminPaths from '../../paths/admin' import applyPaths from '../../paths/apply' -import { nameOrPlaceholderCopy } from '../personUtils' import { fullPersonFactory } from '../../testutils/factories/person' import config from '../../config' @@ -139,31 +133,7 @@ describe('adminIdentityBar', () => { const placementRequestDetail = placementRequestDetailFactory.build({ person: personFactory.build() }) expect(title(placementRequestDetail)).toMatchStringIgnoringWhitespace(` - Placement request -

${nameOrPlaceholderCopy(placementRequestDetail.person)}

- `) - }) - - it('should return Not Found if the person is unknown person', () => { - const placementRequestDetailWithRestrictedAccessOffender = placementRequestDetailFactory.build() - placementRequestDetailWithRestrictedAccessOffender.person = restrictedPersonFactory.build({ - type: 'UnknownPerson', - }) - - expect(title(placementRequestDetailWithRestrictedAccessOffender)).toMatchStringIgnoringWhitespace(` - Placement request -

Not Found CRN: ${placementRequestDetailWithRestrictedAccessOffender.person.crn}

- `) - }) - - it('should return Limited Access Offender if the person has no name', () => { - const placementRequestDetailWithRestrictedAccessOffender = placementRequestDetailFactory.build() - const person = personFactory.build({ isRestricted: true }) - placementRequestDetailWithRestrictedAccessOffender.person = person - - expect(title(placementRequestDetailWithRestrictedAccessOffender)).toMatchStringIgnoringWhitespace(` - Placement request -

${person.name}

+

Placement request

`) }) @@ -174,9 +144,8 @@ describe('adminIdentityBar', () => { }) expect(title(placementRequestDetail)).toMatchStringIgnoringWhitespace(` -Placement request

-${nameOrPlaceholderCopy(placementRequestDetail.person)} +Placement request Request for placement withdrawn

`) diff --git a/server/utils/placementRequests/adminIdentityBar.ts b/server/utils/placementRequests/adminIdentityBar.ts index d33e76c887..9ee6b6ffc0 100644 --- a/server/utils/placementRequests/adminIdentityBar.ts +++ b/server/utils/placementRequests/adminIdentityBar.ts @@ -5,7 +5,6 @@ import managePaths from '../../paths/manage' import matchPaths from '../../paths/match' import applyPaths from '../../paths/apply' import adminPaths from '../../paths/admin' -import { isUnknownPerson, nameOrPlaceholderCopy } from '../personUtils' import config from '../../config' import { hasPermission } from '../users' @@ -76,13 +75,9 @@ export const adminActions = ( } export const title = (placementRequest: PlacementRequestDetail) => { - const { person } = placementRequest - let heading = nameOrPlaceholderCopy( - person, - isUnknownPerson(person) ? `Not Found CRN: ${person.crn}` : `LAO: ${person.crn}`, - ) + let heading = '' if (placementRequest.isWithdrawn) { heading += `Request for placement withdrawn` } - return `Placement request

${heading}

` + return `

Placement request${heading}

` } diff --git a/server/utils/placementRequests/matchingInformationSummaryList.ts b/server/utils/placementRequests/matchingInformationSummaryList.ts index 081dc92572..d20b704430 100644 --- a/server/utils/placementRequests/matchingInformationSummaryList.ts +++ b/server/utils/placementRequests/matchingInformationSummaryList.ts @@ -1,9 +1,20 @@ import { PlacementRequestDetail } from '@approved-premises/api' -import { SummaryListWithCard } from '@approved-premises/ui' +import { SummaryListItem, SummaryListWithCard } from '@approved-premises/ui' import { preferredApsRow } from './preferredApsRow' import { placementRequirementsRow } from './placementRequirementsRow' export const matchingInformationSummary = (placementRequest: PlacementRequestDetail): SummaryListWithCard => { + return { + card: { + title: { + text: 'Information for Matching', + }, + }, + rows: matchingInformationSummaryRows(placementRequest), + } +} + +export const matchingInformationSummaryRows = (placementRequest: PlacementRequestDetail): Array => { const rows = [] const preferredAps = preferredApsRow(placementRequest) @@ -25,13 +36,5 @@ export const matchingInformationSummary = (placementRequest: PlacementRequestDet }, }) } - - return { - card: { - title: { - text: 'Information for Matching', - }, - }, - rows, - } + return rows } diff --git a/server/utils/placementRequests/placementRequestSummaryList.test.ts b/server/utils/placementRequests/placementRequestSummaryList.test.ts new file mode 100644 index 0000000000..30d707c6f6 --- /dev/null +++ b/server/utils/placementRequests/placementRequestSummaryList.test.ts @@ -0,0 +1,214 @@ +import { Application } from '@approved-premises/api' +import { SummaryListItem } from '@approved-premises/ui' +import { applicationFactory, placementRequestDetailFactory } from '../../testutils/factories' +import offlineApplicationFactory from '../../testutils/factories/offlineApplication' +import { placementRequestSummaryList } from './placementRequestSummaryList' +import { DateFormats } from '../dateUtils' +import { apTypeLabels } from '../apTypeLabels' + +describe('placementRequestSummaryList', () => { + const application = applicationFactory.build({ + licenceExpiryDate: '2030-11-23', + }) + const placementRequest = placementRequestDetailFactory.build({ + releaseType: 'hdc', + expectedArrival: '2025-10-02', + duration: 52, + essentialCriteria: ['hasTactileFlooring'], + application, + notes: 'Test notes', + }) + + it('should generate the expected summary list', () => { + expect(placementRequestSummaryList(placementRequest).rows).toEqual( + expectedSummaryListItems( + false, + placementRequest.application.id, + application.licenceExpiryDate, + placementRequest.location, + ), + ) + }) + + it('should generate the expected summary list when is withdrawn', () => { + const isWithdrawn = true + const withdrawnPlacementRequest = { + ...placementRequest, + isWithdrawn, + } + expect(placementRequestSummaryList(withdrawnPlacementRequest).rows).toEqual( + expectedSummaryListItems( + isWithdrawn, + placementRequest.application.id, + application.licenceExpiryDate, + placementRequest.location, + ), + ) + }) + + it(`should generate the expected summary list when placement-request's application is undefined`, () => { + const undefinedApplication: Application = undefined + const placementRequestWithoutLicenceExpiry = { + ...placementRequest, + application: undefinedApplication, + } + expect(placementRequestSummaryList(placementRequestWithoutLicenceExpiry).rows).toEqual( + expectedSummaryListItems(false, undefined, '', placementRequest.location), + ) + }) + + it(`should generate the expected summary list when placement-request's application is not of type ApprovedPremisesApplication`, () => { + const placementRequestWithOfflineApplication = { + ...placementRequest, + application: offlineApplicationFactory.build(), + } + expect(placementRequestSummaryList(placementRequestWithOfflineApplication).rows).toEqual( + expectedSummaryListItems( + false, + placementRequestWithOfflineApplication.application.id, + '', + placementRequest.location, + ), + ) + }) + + it(`should generate the expected summary list with blank licence expiry date when applications license-expiry date is not set`, () => { + const placementRequestWithoutLicenceExpiry = { + ...placementRequest, + application: applicationFactory.build({ + licenceExpiryDate: undefined, + }), + } + expect(placementRequestSummaryList(placementRequestWithoutLicenceExpiry).rows).toEqual( + expectedSummaryListItems( + false, + placementRequestWithoutLicenceExpiry.application.id, + '', + placementRequest.location, + ), + ) + }) + + const expectedSummaryListItems = ( + isWithdrawn: boolean, + applicationId: string, + expectedLicenceExpiryDate: string, + expectedPostcode: string, + ): Array => { + const apTypeListItem = generateApTypeListItem(applicationId) + const rows = [ + { + key: { + text: 'Requested arrival date', + }, + value: { + text: 'Thu 2 Oct 2025', + }, + }, + { + key: { + text: 'Expected departure date', + }, + value: { + text: 'Sun 23 Nov 2025', + }, + }, + { + key: { + text: 'Length of stay', + }, + value: { + text: '7 weeks, 3 days', + }, + }, + { + key: { + text: 'Release type', + }, + value: { + text: 'Home detention curfew (HDC)', + }, + }, + { + key: { + text: 'Licence expiry date', + }, + value: { + text: expectedLicenceExpiryDate ? DateFormats.isoDateToUIDate(expectedLicenceExpiryDate) : '', + }, + }, + apTypeListItem, + { + key: { + text: 'Preferred postcode', + }, + value: { + text: expectedPostcode, + }, + }, + { + key: { + text: 'Essential Criteria', + }, + value: { + html: '
  • Tactile flooring
', + }, + }, + { + key: { + text: 'Desirable Criteria', + }, + value: { + html: '
    ', + }, + }, + { + key: { + text: 'Observations from assessor', + }, + value: { + text: 'Test notes', + }, + }, + ] + if (isWithdrawn) { + const statusRow = { + key: { + text: 'Status', + }, + value: { + html: ` + Withdrawn + `, + }, + } + rows.splice(7, 0, statusRow) + } + return rows + } + + const generateApTypeListItem = (applicationId: string) => { + const apTypeListItem = { + key: { + text: 'Type of AP', + }, + value: { + text: apTypeLabels[placementRequest.type], + }, + } + if (applicationId) { + return { + ...apTypeListItem, + actions: { + items: [ + { + href: `/applications/${applicationId}?tab=timeline`, + text: 'View timeline', + }, + ], + }, + } + } + return apTypeListItem + } +}) diff --git a/server/utils/placementRequests/placementRequestSummaryList.ts b/server/utils/placementRequests/placementRequestSummaryList.ts new file mode 100644 index 0000000000..68b54d71d3 --- /dev/null +++ b/server/utils/placementRequests/placementRequestSummaryList.ts @@ -0,0 +1,34 @@ +import { PlacementRequestDetail } from '../../@types/shared' +import { SummaryList, SummaryListItem } from '../../@types/ui' +import { withdrawnStatusTag } from '../applications/utils' +import { + apTypeWithViewTimelineActionRow, + departureDateRow, + lengthOfStayRow, + licenceExpiryDateRow, + placementDates, + preferredPostcodeRow, + releaseTypeRow, + requestedOrEstimatedArrivalDateRow, +} from '../match' +import { matchingInformationSummaryRows } from './matchingInformationSummaryList' + +export const placementRequestSummaryList = (placementRequest: PlacementRequestDetail): SummaryList => { + const dates = placementDates(placementRequest.expectedArrival, String(placementRequest.duration)) + const rows: Array = [ + requestedOrEstimatedArrivalDateRow(placementRequest.isParole, dates.startDate), + departureDateRow(dates.endDate), + lengthOfStayRow(placementRequest.duration), + releaseTypeRow(placementRequest), + licenceExpiryDateRow(placementRequest), + apTypeWithViewTimelineActionRow(placementRequest), + preferredPostcodeRow(placementRequest.location), + ] + + if (placementRequest.isWithdrawn) { + rows.push(withdrawnStatusTag) + } + + const matchingInformationRows = matchingInformationSummaryRows(placementRequest) + return { rows: rows.concat(matchingInformationRows) } +} diff --git a/server/utils/placementRequests/preferredApsRow.test.ts b/server/utils/placementRequests/preferredApsRow.test.ts index 229377a2e2..e0b3b614ef 100644 --- a/server/utils/placementRequests/preferredApsRow.test.ts +++ b/server/utils/placementRequests/preferredApsRow.test.ts @@ -20,7 +20,7 @@ describe('preferredApsRow', () => { const row = preferredApsRow(placementRequest) as SummaryListItem - expect(row.key).toEqual({ text: 'Preferred APs' }) + expect(row.key).toEqual({ text: 'Preferred AP' }) expect((row.value as HtmlItem).html).toMatchStringIgnoringWhitespace(`
    1. ${premises[0].name}
    2. diff --git a/server/utils/placementRequests/preferredApsRow.ts b/server/utils/placementRequests/preferredApsRow.ts index cbc9bcd4ac..cf7f105163 100644 --- a/server/utils/placementRequests/preferredApsRow.ts +++ b/server/utils/placementRequests/preferredApsRow.ts @@ -8,7 +8,7 @@ export const preferredApsRow = (placementRequest: PlacementRequestDetail): Summa if (premises.length) { const apList = premises.map(p => `
    3. ${p.name}
    4. `) return { - key: { text: 'Preferred APs' }, + key: { text: 'Preferred AP' }, value: { html: `
        ${apList.join('')}
      ` }, } } diff --git a/server/views/admin/placementRequests/show.njk b/server/views/admin/placementRequests/show.njk index 0a4fdabe54..3a4b6f7922 100644 --- a/server/views/admin/placementRequests/show.njk +++ b/server/views/admin/placementRequests/show.njk @@ -1,6 +1,7 @@ {% from "govuk/components/summary-list/macro.njk" import govukSummaryList %} {%- from "moj/components/identity-bar/macro.njk" import mojIdentityBar -%} {% from "govuk/components/notification-banner/macro.njk" import govukNotificationBanner %} +{% from "govuk/components/back-link/macro.njk" import govukBackLink %} {% from "../../components/keyDetails/macro.njk" import keyDetails %} {% extends "../../partials/layout.njk" %} @@ -12,6 +13,13 @@ {{ keyDetails(MatchUtils.keyDetails(placementRequest)) }} {% endblock %} +{% block beforeContent %} + {{ govukBackLink({ + text: "Back", + href: paths.admin.cruDashboard.index({}) + }) }} +{% endblock %} + {% block content %} {% include "../../_messages.njk" %} @@ -23,7 +31,7 @@
      -
      +
      {% if placementRequest.isParole %} {% set html %}

      @@ -49,7 +57,7 @@ }) }} {% endif %} -

        + - {{ govukSummaryList(PlacementRequestUtils.adminSummary(placementRequest)) }} - - {{ govukSummaryList(PlacementRequestUtils.matchingInformationSummary(placementRequest)) }} + {{ govukSummaryList(placementRequestSummaryList) }} {% if placementRequest.status === 'matched' %}

        Booked Placement