Skip to content

Commit 9b0e096

Browse files
Per programme clinic invitations, with filters
1 parent 9fe6516 commit 9b0e096

11 files changed

Lines changed: 155 additions & 8 deletions

File tree

app/controllers/patient.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ export const patientController = {
6666
},
6767

6868
readAll(request, response, next) {
69-
const { option, programme_id, q, yearGroup } = request.query
69+
const { invitedToClinic, option, programme_id, q, yearGroup } =
70+
request.query
7071
const { data } = request.session
7172

7273
const programmes = Programme.findAll(data)
@@ -120,6 +121,19 @@ export const patientController = {
120121
)
121122
}
122123

124+
// Filter by programme clinic invitations
125+
if (programme_id && invitedToClinic === 'true') {
126+
results = results.filter(
127+
(patient) => patient.programmes[programme_id]?.invitedToClinic
128+
)
129+
} else if (invitedToClinic === 'true') {
130+
results = results.filter((patient) =>
131+
Object.values(patient.programmes).some(
132+
(programme) => programme.invitedToClinic
133+
)
134+
)
135+
}
136+
123137
// Filter by status
124138
if (filters.report && filters.report !== 'none') {
125139
const ids = programme_ids || programmes.map((programme) => programme.id)
@@ -196,6 +210,7 @@ export const patientController = {
196210
}))
197211

198212
// Clean up session data
213+
delete data.invitedToClinic
199214
delete data.option
200215
delete data.patientConsent
201216
delete data.patientDeferred
@@ -233,6 +248,7 @@ export const patientController = {
233248

234249
// Checkboxes
235250
for (const key of [
251+
'invitedToClinic',
236252
'option',
237253
'patientConsent',
238254
'patientDeferred',

app/controllers/school.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import wizard from '@x-govuk/govuk-prototype-wizard'
22
import _ from 'lodash'
33

44
import { PatientStatus } from '../enums.js'
5-
import { School } from '../models.js'
5+
import { Patient, School } from '../models.js'
66
import { generateNewSiteCode } from '../utils/location.js'
77
import { getResults, getPagination } from '../utils/pagination.js'
88
import { formatYearGroup } from '../utils/string.js'
@@ -96,7 +96,8 @@ export const schoolController = {
9696
},
9797

9898
readPatients(request, response, next) {
99-
const { option, programme_id, q, yearGroup } = request.query
99+
const { invitedToClinic, option, programme_id, q, yearGroup } =
100+
request.query
100101
const { data } = request.session
101102
const { school } = response.locals
102103

@@ -145,6 +146,19 @@ export const schoolController = {
145146
)
146147
}
147148

149+
// Filter by programme clinic invitations
150+
if (programme_id && invitedToClinic === 'true') {
151+
results = results.filter(
152+
(patient) => patient.programmes[programme_id]?.invitedToClinic
153+
)
154+
} else if (invitedToClinic === 'true') {
155+
results = results.filter((patient) =>
156+
Object.values(patient.programmes).some(
157+
(programme) => programme.invitedToClinic
158+
)
159+
)
160+
}
161+
148162
// Filter by status
149163
if (filters.report && filters.report !== 'none') {
150164
const ids =
@@ -223,6 +237,7 @@ export const schoolController = {
223237
}))
224238

225239
// Clean up session data
240+
delete data.invitedToClinic
226241
delete data.option
227242
delete data.patientConsent
228243
delete data.patientDeferred
@@ -252,6 +267,7 @@ export const schoolController = {
252267

253268
// Checkboxes
254269
for (const key of [
270+
'invitedToClinic',
255271
'option',
256272
'patientConsent',
257273
'patientDeferred',
@@ -464,7 +480,14 @@ export const schoolController = {
464480
(patient) => patient.uuid
465481
)
466482

467-
// TODO: Move patients to clinic
483+
// Invite parents to book into a clinic
484+
for (const patient of school.patients) {
485+
const clinicProgramme_ids = request.body.clinicProgramme_ids.filter(
486+
(item) => item !== '_unchecked'
487+
)
488+
489+
Patient.update(patient.uuid, { clinicProgramme_ids }, data)
490+
}
468491

469492
request.flash(
470493
'success',

app/datasets/schools.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ export default {
1212
id: '888888',
1313
urn: '888888',
1414
name: 'Unknown school',
15+
yearGroups: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13],
1516
team_id: '001',
1617
presetNames
1718
},
1819
'999999': {
1920
id: '999999',
2021
urn: '999999',
2122
name: 'Home-schooled',
23+
yearGroups: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13],
2224
team_id: '001',
2325
presetNames
2426
},

app/locales/en.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,6 +1083,9 @@ export const en = {
10831083
programmes: {
10841084
label: 'Vaccination programmes'
10851085
},
1086+
clinicProgramme_ids: {
1087+
label: 'Clinic invitations'
1088+
},
10861089
status: {
10871090
label: 'Status'
10881091
}
@@ -1539,9 +1542,17 @@ export const en = {
15391542
title: 'Invite parents to book a clinic appointment',
15401543
label: 'Send clinic invitations',
15411544
count:
1542-
'{count, plural, =0 {No children} one {1 child} other {# children}} have not been vaccinated. They have not been invited to a clinic yet.',
1545+
'{count, plural, =0 {No children} one {1 child} other {# children}} are due a vaccination for at least 1 programme. They have not been invited to a clinic yet.',
15431546
description:
1544-
'You can now send clinic booking invitations to their parents.\n\nThe next clinic is on %s.',
1547+
'You can now send clinic booking invitations to their parents.',
1548+
programme: 'Which programmes do you want to send invitations for?',
1549+
flu: '{count, plural, =0 {No children} one {1 child} other {# children}} have not had a flu vaccination',
1550+
hpv: '{count, plural, =0 {No children have} one {1 child has} other {# children have}} not had an HPV vaccination',
1551+
menacwy:
1552+
'{count, plural, =0 {No children have} one {1 child has} other {# children have}} not had an MenACWY vaccination',
1553+
mmr: '{count, plural, =0 {No children have} one {1 child has} other {# children have}} not had an MMR vaccination',
1554+
'td-ipv':
1555+
'{count, plural, =0 {No children have} one {1 child has} other {# children have}} not had a Td/IPV vaccination',
15451556
confirm: 'Send clinic invitations',
15461557
success:
15471558
'{count, plural, =0 {No children} one {1 child} other {# children}} invited to the clinic'

app/models/patient-programme.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ import {
2525
* @param {object} options - Options
2626
* @param {object} [context] - Global context
2727
* @property {object} [context] - Global context
28+
* @property {boolean} [invitedToClinic] - Invited to clinic
2829
* @property {string} patient_uuid - Patient UUID
2930
* @property {string} programme_id - Programme ID
3031
*/
3132
export class PatientProgramme {
3233
constructor(options, context) {
3334
this.context = context
35+
this.invitedToClinic = options?.invitedToClinic
3436
this.patient_uuid = options?.patient_uuid
3537
this.programme_id = options?.programme_id
3638
}

app/models/patient.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
Parent,
1212
PatientProgramme,
1313
PatientSession,
14+
Programme,
1415
Reply,
1516
Vaccination
1617
} from '../models.js'
@@ -45,6 +46,7 @@ import {
4546
* @property {Patient} [pendingChanges] - Pending changes to record values
4647
* @property {import('../enums.js').ArchiveRecordReason} [archiveReason] - Archival reason
4748
* @property {string} [archiveReasonOther] - Other archival reason
49+
* @property {Array<string} [clinicProgramme_ids] - Clinic programme invitations
4850
* @property {Array<import('./audit-event.js').AuditEvent>} events - Events
4951
* @property {Array<string>} [reply_uuids] - Reply IDs
5052
* @property {Array<string>} [patientSession_uuids] - Patient session IDs
@@ -71,6 +73,7 @@ export class Patient extends Child {
7173
this.archiveReasonOther = options?.archiveReasonOther
7274
this.pendingChanges = options?.pendingChanges || {}
7375

76+
this.clinicProgramme_ids = options?.clinicProgramme_ids || []
7477
this.events = options?.events || []
7578
this.reply_uuids = options?.reply_uuids || []
7679
this.patientSession_uuids = options?.patientSession_uuids || []
@@ -304,13 +307,20 @@ export class Patient extends Child {
304307
for (const programme of Object.values(programmesData).filter(
305308
(programme) => !programme.hidden
306309
)) {
307-
programmes[programme.id] = new PatientProgramme(
310+
const patientProgramme = new PatientProgramme(
308311
{
309312
patient_uuid: this.uuid,
310313
programme_id: programme.id
311314
},
312315
this.context
313316
)
317+
318+
// Patient invited to clinic if invitation needed and invitation sent
319+
patientProgramme.invitedToClinic =
320+
patientProgramme.inviteToSession &&
321+
this.clinicProgramme_ids.includes(programme.id)
322+
323+
programmes[programme.id] = patientProgramme
314324
}
315325

316326
return programmes
@@ -449,7 +459,10 @@ export class Patient extends Child {
449459
archiveReason: formatOther(this.archiveReasonOther, this.archiveReason),
450460
lastReminderDate: this.lastReminderDate
451461
? `Last reminder sent on ${this.lastReminderDate}`
452-
: 'No reminders sent'
462+
: 'No reminders sent',
463+
clinicProgramme_ids: this.clinicProgramme_ids
464+
.map((id) => Programme.findOne(id, this.context).nameTag)
465+
.join(' ')
453466
}
454467
}
455468

app/models/school.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,18 @@ export class School extends Location {
9191
return this.patients.filter((patient) => patient.hasMissingNhsNumber)
9292
}
9393

94+
/**
95+
* Get school pupils to invite to a (clinic) session
96+
*
97+
* @param {string} programmeId - Programme ID
98+
* @returns {Array<Patient>} Patient records
99+
*/
100+
patientsToInviteToSession(programmeId) {
101+
return this.patients.filter(
102+
(patient) => patient.programmes[programmeId].inviteToSession
103+
)
104+
}
105+
94106
/**
95107
* Get sessions run at this school
96108
*

app/views/_macros/patient-search.njk

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,21 @@
6262
decorate: "programme_id"
6363
}) if programmeItems }}
6464

65+
{{ checkboxes({
66+
classes: "nhsuk-checkboxes--small",
67+
fieldset: {
68+
legend: {
69+
classes: "nhsuk-fieldset__legend--s",
70+
text: __("patient.clinicProgramme_ids.label")
71+
}
72+
},
73+
items: [{
74+
text: 'Invited to clinic',
75+
value: 'true'
76+
}],
77+
decorate: "invitedToClinic"
78+
}) }}
79+
6580
{% for name, enums in params.checkboxFilters %}
6681
{{ checkboxes({
6782
classes: "nhsuk-checkboxes--small",

app/views/patient/list.njk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
value: patient.school.name | highlightQuery(data.q)
7575
},
7676
yearGroup: {},
77+
clinicProgramme_ids: {},
7778
report: {
7879
label: __("patientSession.report.label"),
7980
value: reportStatusHtml

app/views/school/invite-to-clinic.njk

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,55 @@
1414
}) | nhsukMarkdown }}
1515

1616
{{ __("school.inviteToClinic.description", "1 June 2026") | nhsukMarkdown }}
17+
18+
{{ checkboxes({
19+
fieldset: {
20+
legend: {
21+
text: __("school.inviteToClinic.programme"),
22+
size: "m"
23+
}
24+
},
25+
items: [{
26+
text: ProgrammeType.Flu,
27+
value: "flu",
28+
hint: {
29+
text: __mf("school.inviteToClinic.flu", {
30+
count: school.patientsToInviteToSession('flu').length
31+
})
32+
}
33+
} if school.patientsToInviteToSession('flu').length, {
34+
text: ProgrammeType.HPV,
35+
value: "hpv",
36+
hint: {
37+
text: __mf("school.inviteToClinic.hpv", {
38+
count: school.patientsToInviteToSession('hpv').length
39+
})
40+
}
41+
} if school.patientsToInviteToSession('hpv').length, {
42+
text: ProgrammeType.MenACWY,
43+
value: "menacwy",
44+
hint: {
45+
text: __mf("school.inviteToClinic.menacwy", {
46+
count: school.patientsToInviteToSession('menacwy').length
47+
})
48+
}
49+
} if school.patientsToInviteToSession('menacwy').length, {
50+
text: ProgrammeType.MMR,
51+
value: "mmr",
52+
hint: {
53+
text: __mf("school.inviteToClinic.mmr", {
54+
count: school.patientsToInviteToSession('mmr').length
55+
})
56+
}
57+
} if school.patientsToInviteToSession('mmr').length, {
58+
text: ProgrammeType.TdIPV,
59+
value: "td-ipv",
60+
hint: {
61+
text: __mf("school.inviteToClinic.td-ipv", {
62+
count: school.patientsToInviteToSession('td-ipv').length
63+
})
64+
}
65+
} if school.patientsToInviteToSession('td-ipv').length],
66+
decorate: "clinicProgramme_ids"
67+
}) }}
1768
{% endblock %}

0 commit comments

Comments
 (0)