Skip to content

Commit 96191b1

Browse files
committed
Add the page to invite to clinic from the children list
1 parent 4457b09 commit 96191b1

11 files changed

Lines changed: 317 additions & 29 deletions

File tree

app/controllers/patient.js

Lines changed: 164 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
PatientStatus,
77
ProgrammeType,
88
SessionPresetName,
9+
SessionStatus,
910
SessionType,
1011
VaccinationOutcome
1112
} from '../enums.js'
@@ -19,6 +20,11 @@ import {
1920
} from '../models.js'
2021
import { today } from '../utils/date.js'
2122
import { getResults, getPagination } from '../utils/pagination.js'
23+
import {
24+
ConjunctionType,
25+
programmeNamesListForSentence
26+
} from '../utils/programme.js'
27+
import { queryToQueryString } from '../utils/querystring.js'
2228
import { formatYearGroup, stringToArray } from '../utils/string.js'
2329

2430
export const patientController = {
@@ -122,12 +128,12 @@ export const patientController = {
122128
}
123129

124130
// Filter by programme clinic status
131+
let showingClinicReady = false
125132
if (filters.clinicStatus && filters.clinicStatus !== 'none') {
126-
response.locals.showingClinicReady =
127-
filters.clinicStatus === PatientClinicStatus.Ready
133+
showingClinicReady = filters.clinicStatus === PatientClinicStatus.Ready
134+
// Patient must have the selected clinic status for any of the selected programmes (if
135+
// there's a selected programme), or for *any* programme if not
128136
if (programme_id) {
129-
// Patient must have the selected clinic status for any of the selected programmes (if
130-
// there's a selected programme), or for *any* programme if not
131137
results = results.filter((patient) =>
132138
programme_ids.some(
133139
(programme_id) =>
@@ -202,8 +208,13 @@ export const patientController = {
202208

203209
// Results
204210
response.locals.patients = patients
211+
response.locals.showingClinicReady = showingClinicReady
212+
if (showingClinicReady) {
213+
data.clinicPatient_ids = results.map(({ uuid }) => uuid)
214+
}
205215
response.locals.results = getResults(results, request.query)
206216
response.locals.pages = getPagination(results, request.query)
217+
response.locals.query = request.query
207218

208219
// Programme filter options
209220
response.locals.programmeItems = programmes.map((programme) => ({
@@ -412,7 +423,7 @@ export const patientController = {
412423
response.render(`patient/programme`)
413424
},
414425

415-
inviteToClinic(request, response) {
426+
inviteOneToClinic(request, response) {
416427
const { patient_uuid } = request.params
417428
const { data } = request.session
418429
const { __ } = response.locals
@@ -425,21 +436,16 @@ export const patientController = {
425436
clinicProgramme_ids = stringToArray(clinicProgramme_ids)
426437
}
427438

428-
// Update the record of programmes for which the patient's been invited to clinic
429-
const patient = Patient.update(patient_uuid, { clinicProgramme_ids }, data)
430-
431439
// Send comms to parents and record in audit trail
440+
const patient = Patient.findOne(patient_uuid, data)
432441
patient.inviteToClinic(clinicProgramme_ids)
442+
Patient.update(patient.uuid, patient, data)
433443

434444
// Report the success
435-
const formatter = new Intl.ListFormat('en', {
436-
style: 'long',
437-
type: 'conjunction'
438-
})
439-
const selectedProgrammeNames = formatter.format(
440-
clinicProgramme_ids.map((programme_id) =>
441-
Programme.findOne(programme_id, data)?.name?.replace('Flu', 'flu')
442-
)
445+
const selectedProgrammeNames = programmeNamesListForSentence(
446+
clinicProgramme_ids,
447+
ConjunctionType.and,
448+
data
443449
)
444450
request.flash(
445451
'success',
@@ -452,6 +458,148 @@ export const patientController = {
452458
response.redirect(patient.uri)
453459
},
454460

461+
showInviteManyToClinic(request, response) {
462+
const { __, __mf } = response.locals
463+
const { data } = request.session
464+
const { clinicPatient_ids } = data
465+
const { programme_id } = request.query
466+
467+
const programmes = Programme.findAll(data)
468+
.filter((programme) => !programme.hidden)
469+
.sort((a, b) => a.name.localeCompare(b.name))
470+
471+
let programme_ids
472+
if (programme_id) {
473+
programme_ids = Array.isArray(programme_id)
474+
? programme_id
475+
: [programme_id]
476+
} else {
477+
programme_ids = programmes.map(({ id }) => id)
478+
}
479+
480+
// e.g. 271 children can be invited to clinic for HPV, MenACWY, or Td/IPV programmes.
481+
const childrenFragment = __mf(
482+
'patient.bulkInviteToClinic.childrenFragment',
483+
{ count: clinicPatient_ids.length }
484+
)
485+
const programmesFragment = programme_id
486+
? __mf('patient.bulkInviteToClinic.programmesFragment', {
487+
count: programme_ids.length,
488+
programmeNames: programmeNamesListForSentence(
489+
programme_ids,
490+
ConjunctionType.or,
491+
data
492+
)
493+
})
494+
: __('patient.bulkInviteToClinic.anyProgrammesFragment')
495+
response.locals.cohortSummary = __(
496+
'patient.bulkInviteToClinic.cohortSummary',
497+
{ children: childrenFragment, programmes: programmesFragment }
498+
)
499+
500+
// Create the programme checkboxes and their patient and clinic counts
501+
const checkboxItems = []
502+
const scheduledSessions = Session.findAll(data)
503+
.filter(({ type }) => type === SessionType.Clinic)
504+
.filter(({ status }) => status === SessionStatus.Planned)
505+
const invitableProgrammes = programmes.filter((programme) =>
506+
programme_ids.includes(programme.id)
507+
)
508+
for (const programme of invitableProgrammes) {
509+
const clinicReadyChildrenCount = clinicPatient_ids
510+
.map((id) => Patient.findOne(id, data))
511+
.filter(
512+
(patient) =>
513+
patient.programmes[programme.id].clinicStatus ===
514+
PatientClinicStatus.Ready
515+
).length
516+
if (clinicReadyChildrenCount > 0) {
517+
const scheduledClinicCount = scheduledSessions.filter((session) =>
518+
session.programme_ids.includes(programme.id)
519+
).length
520+
521+
const childrenHint = __mf(
522+
'patient.bulkInviteToClinic.programme.hint.children',
523+
{
524+
count: clinicReadyChildrenCount,
525+
programmeName: programme.name
526+
}
527+
)
528+
const clinicsHint = __mf(
529+
'patient.bulkInviteToClinic.programme.hint.clinics',
530+
{
531+
count: scheduledClinicCount,
532+
programmeName: programme.name
533+
}
534+
)
535+
checkboxItems.push({
536+
text: programme.name,
537+
value: programme.id,
538+
hint: {
539+
html: __('patient.bulkInviteToClinic.programme.hint.combined', {
540+
childrenHint,
541+
clinicsHint
542+
})
543+
}
544+
})
545+
}
546+
}
547+
548+
response.locals.checkboxItems = checkboxItems
549+
550+
response.render('patient/bulk-invite-to-clinic')
551+
},
552+
553+
inviteManyToClinic(request, response) {
554+
let { clinicProgramme_ids } = request.body
555+
const { __mf } = response.locals
556+
const { data } = request.session
557+
const { clinicPatient_ids } = data
558+
559+
// Tidy up any _unchecked values
560+
if (typeof clinicProgramme_ids === 'string') {
561+
clinicProgramme_ids = [clinicProgramme_ids]
562+
} else {
563+
clinicProgramme_ids = stringToArray(clinicProgramme_ids)
564+
}
565+
566+
// Invite each of the children to clinic for the subset of the selected programmes
567+
// that make sense for that child
568+
let invitedChildrenCount = 0
569+
for (const patient of clinicPatient_ids.map((id) =>
570+
Patient.findOne(id, data)
571+
)) {
572+
// Work out which of the selected programmes this patient was clinic-ready for
573+
const { clinicReadyProgramme_ids } = patient
574+
const invitedProgramme_ids = [
575+
...new Set(clinicReadyProgramme_ids).intersection(
576+
new Set(clinicProgramme_ids)
577+
)
578+
]
579+
580+
if (invitedProgramme_ids.length) {
581+
// Send comms to parents and record in audit trail
582+
patient.inviteToClinic(invitedProgramme_ids)
583+
Patient.update(patient.uuid, patient, data)
584+
585+
invitedChildrenCount++
586+
}
587+
}
588+
589+
request.flash(
590+
'success',
591+
__mf('patient.bulkInviteToClinic.success', {
592+
count: invitedChildrenCount
593+
})
594+
)
595+
596+
// Reset the cohort
597+
delete data.clinicPatient_ids
598+
599+
// Get back to the filter page as we left it
600+
response.redirect(`/patients${queryToQueryString(request.query)}`)
601+
},
602+
455603
archive(request, response) {
456604
const { account } = request.app.locals
457605
const { patient_uuid } = request.params

app/controllers/school.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,9 @@ export const schoolController = {
485485
(item) => item !== '_unchecked'
486486
)
487487
for (const patient_uuid of patient_uuids) {
488-
Patient.update(patient_uuid, { clinicProgramme_ids }, data)
488+
const patient = Patient.findOne(patient_uuid, data)
489+
patient.inviteToClinic(clinicProgramme_ids)
490+
Patient.update(patient_uuid, patient, data)
489491
}
490492

491493
request.flash(

app/controllers/session.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -845,11 +845,9 @@ export const sessionController = {
845845
clinicPatientSession = new PatientSession(clinicPatientSession, data)
846846
patientSession.patient.addToSession(clinicPatientSession)
847847

848-
Patient.update(
849-
patientSession.patient_uuid,
850-
{ clinicProgramme_ids: clinic.programme_ids },
851-
data
852-
)
848+
const patient = Patient.findOne(patientSession.patient_uuid, data)
849+
patient.inviteToClinic(clinic.programme_ids)
850+
Patient.update(patient.uuid, patient, data)
853851
}
854852
}
855853
}

app/filters.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import prototypeFilters from '@x-govuk/govuk-prototype-filters'
22
import _ from 'lodash'
33

44
import { ordinal } from './utils/number.js'
5+
import { queryToQueryString } from './utils/querystring.js'
56
import {
67
formatHighlight,
78
formatList,
@@ -183,5 +184,15 @@ export default (env) => {
183184
return array.filter((item) => item !== '')
184185
}
185186

187+
/**
188+
* Rebuild the querystring from the request.query object
189+
*
190+
* @param {object} query - the request.query object
191+
* @returns {string} - the rebuilt query string
192+
*/
193+
filters.asQueryString = function (query) {
194+
return queryToQueryString(query)
195+
}
196+
186197
return filters
187198
}

app/locales/en.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1358,6 +1358,31 @@ export const en = {
13581358
success:
13591359
'{{patientName}} has been invited to attend a clinic for {{selectedProgrammes}} vaccination'
13601360
},
1361+
bulkInviteToClinic: {
1362+
title: 'Invite parents to book a clinic appointment',
1363+
caption:
1364+
'{count, plural, one {1 child selected} other {{count} children selected}}',
1365+
childrenFragment:
1366+
'{count, plural, =0 {No children} one {1 child} other {{count} children}}',
1367+
programmesFragment:
1368+
'{count, plural, one {the {programmeNames} programme} other {the {programmeNames} programmes}}',
1369+
anyProgrammesFragment: 'at least one programme',
1370+
cohortSummary:
1371+
'{{children}} can be invited to clinic for {{programmes}}.',
1372+
programme: {
1373+
label: 'Which programmes do you want to send invitations for?',
1374+
hint: {
1375+
children:
1376+
'{count, plural, one {1 child can be invited for {programmeName}} other {{count} children can be invited for {programmeName}}}',
1377+
clinics:
1378+
'{count, plural, =0 {No clinics are scheduled for {programmeName}} one {1 clinic is scheduled for {programmeName}} other {{count} clinics are scheduled for {programmeName}}}',
1379+
combined: '{{childrenHint}}<br>{{clinicsHint}}'
1380+
}
1381+
},
1382+
confirm: 'Send clinic invitations',
1383+
success:
1384+
'{count, plural, one {1 child has been invited to clinic} other {{count} children have been invited to clinic}}'
1385+
},
13611386
auditEvents: {
13621387
label: 'Activity log'
13631388
},

app/models/patient.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
Impairment,
1111
NoticeType,
1212
NotifyEmailStatus,
13+
PatientClinicStatus,
1314
VaccinationOutcome
1415
} from '../enums.js'
1516
import {
@@ -349,6 +350,17 @@ export class Patient extends Child {
349350
return programmes
350351
}
351352

353+
/**
354+
* Get the IDs of programmes for which this patient can be invited to clinic
355+
*
356+
* @returns {Array<string>} the IDs of programmes for which this patient is clinic-ready
357+
*/
358+
get clinicReadyProgramme_ids() {
359+
return Object.values(this.programmes)
360+
.filter(({ clinicStatus }) => clinicStatus === PatientClinicStatus.Ready)
361+
.map(({ programme_id }) => programme_id)
362+
}
363+
352364
/**
353365
* Get replies
354366
*
@@ -668,6 +680,10 @@ export class Patient extends Child {
668680
* @param {Array<string>} programme_ids - The programmes for which the child's invited
669681
*/
670682
inviteToClinic(programme_ids) {
683+
this.clinicProgramme_ids = [
684+
...new Set(this.clinicProgramme_ids.concat(programme_ids))
685+
]
686+
671687
for (const parent of this.parents) {
672688
this.addEvent({
673689
name: activity.notify['invite-clinic'](parent),

app/routes/patient.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ const router = express.Router({ strict: true, mergeParams: true })
77
router.get('/', patient.readAll, patient.list)
88
router.post('/', patient.filterList)
99

10+
router.get('/invite-to-clinic', patient.readAll, patient.showInviteManyToClinic)
11+
router.post('/invite-to-clinic', patient.inviteManyToClinic)
12+
1013
router.param('patient_uuid', patient.read)
1114

1215
router.get('/:patient_uuid/edit', patient.edit)
@@ -19,7 +22,7 @@ router.post('/:patient_uuid/edit/:view', patient.updateForm)
1922
router.post('/:patient_uuid/new/note', patient.note)
2023

2124
router.post('/:patient_uuid/archive', patient.archive)
22-
router.post('/:patient_uuid/invite-to-clinic', patient.inviteToClinic)
25+
router.post('/:patient_uuid/invite-to-clinic', patient.inviteOneToClinic)
2326

2427
router.all('/:patient_uuid/programmes{/:programme_id}', patient.readProgramme)
2528
router.get('/:patient_uuid/programmes{/:programme_id}', patient.showProgramme)

0 commit comments

Comments
 (0)