From 2ca2ce1a53747fd083c78570ada025f8d0fd65b8 Mon Sep 17 00:00:00 2001 From: Paul Robert Lloyd Date: Tue, 12 May 2026 11:24:40 +0100 Subject: [PATCH 1/8] Support storing parents in session data --- app/data.js | 2 ++ app/models/parent.js | 35 ++++++++++++++++++++++++++++++++++- lib/create-data.js | 4 ++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/app/data.js b/app/data.js index 1ac5b4fcc..b5670c352 100644 --- a/app/data.js +++ b/app/data.js @@ -4,6 +4,7 @@ import clinics from '../.data/clinics.json' with { type: 'json' } import instructions from '../.data/instructions.json' with { type: 'json' } import moves from '../.data/moves.json' with { type: 'json' } import notices from '../.data/notices.json' with { type: 'json' } +import parents from '../.data/parents.json' with { type: 'json' } import patientSessions from '../.data/patient-sessions.json' with { type: 'json' } import patients from '../.data/patients.json' with { type: 'json' } import pdsRecords from '../.data/pds-records.json' with { type: 'json' } @@ -39,6 +40,7 @@ const data = { instructions, moves, notices, + parents, patients, patientSessions, pdsRecords, diff --git a/app/models/parent.js b/app/models/parent.js index a1c7caab6..e90dcfb61 100644 --- a/app/models/parent.js +++ b/app/models/parent.js @@ -5,6 +5,9 @@ import { formatOther, formatParent, stringToBoolean } from '../utils/string.js' /** * @class Parent + * @param {object} options - Options + * @param {object} [context] - Context + * @property {object} [context] - Context * @property {string} uuid - UUID * @property {string} [fullName] - Full name * @property {ParentalRelationship} [relationship] - Relationship to child @@ -20,7 +23,8 @@ import { formatOther, formatParent, stringToBoolean } from '../utils/string.js' * @property {string} [contactPreferenceDetails] - Contact method details */ export class Parent { - constructor(options) { + constructor(options, context) { + this.context = context this.uuid = options?.uuid || faker.string.uuid() this.fullName = options.fullName || '' this.relationship = options.relationship || ParentalRelationship.Unknown @@ -77,4 +81,33 @@ export class Parent { get ns() { return 'parent' } + + /** + * Find all + * + * @param {object} context - Context + * @returns {Array|undefined} Parents + * @static + */ + static findAll(context) { + return Object.values(context.parents).map( + (parent) => new Parent(parent, context) + ) + } + + /** + * Find one + * + * @param {string|string[]} uuid - Parent UUID + * @param {object} context - Context + * @returns {Parent|undefined} Parent + * @static + */ + static findOne(uuid, context) { + uuid = String(uuid) + + if (context?.parents?.[uuid]) { + return new Parent(context.parents[uuid], context) + } + } } diff --git a/lib/create-data.js b/lib/create-data.js index 96f6a8c8d..c36266d83 100644 --- a/lib/create-data.js +++ b/lib/create-data.js @@ -126,6 +126,9 @@ Array.from([...range(0, totalBatches)]).forEach(() => { context.batches[batch.id] = batch }) +// Parents +context.parents = {} + // Patients context.patients = {} Array.from([...range(0, totalPatients)]).forEach(() => { @@ -692,6 +695,7 @@ generateDataFile('.data/clinics.json', context.clinics) generateDataFile('.data/instructions.json', context.instructions) generateDataFile('.data/moves.json', context.moves) generateDataFile('.data/notices.json', context.notices) +generateDataFile('.data/parents.json', context.parents) generateDataFile('.data/patients.json', context.patients) generateDataFile('.data/patient-sessions.json', context.patientSessions) generateDataFile('.data/pds-records.json', context.pdsRecords) From 8f7d87f0679beb91654366fddadfb52a15e97431 Mon Sep 17 00:00:00 2001 From: Paul Robert Lloyd Date: Tue, 12 May 2026 12:20:11 +0100 Subject: [PATCH 2/8] Get parents on a PDS record from parents in session data --- app/generators/parent.js | 11 ++++++----- app/generators/pds-record.js | 32 ++++++-------------------------- app/models/child.js | 4 ++++ app/models/parent.js | 3 +++ app/models/patient.js | 2 -- app/models/pds-record.js | 33 ++++++--------------------------- lib/create-data.js | 13 +++++++++++++ 7 files changed, 38 insertions(+), 60 deletions(-) diff --git a/app/generators/parent.js b/app/generators/parent.js index 3b1467a16..90fe27dcb 100644 --- a/app/generators/parent.js +++ b/app/generators/parent.js @@ -10,11 +10,11 @@ import { Parent } from '../models.js' /** * Generate fake parent * - * @param {string} childLastName - Child’s last name + * @param {import('../models.js').Child|import('../models.js').Patient} patient - Child * @param {boolean} [isMum] - Parent is child’s mother * @returns {Parent} Parent */ -export function generateParent(childLastName, isMum) { +export function generateParent(patient, isMum) { // Relationship const relationship = isMum ? ParentalRelationship.Mum @@ -31,11 +31,11 @@ export function generateParent(childLastName, isMum) { switch (relationship) { case ParentalRelationship.Mum: firstName = faker.person.firstName('female').replace(`'`, '’') - lastName = childLastName + lastName = patient.lastName break case ParentalRelationship.Dad: firstName = faker.person.firstName('male').replace(`'`, '’') - lastName = childLastName + lastName = patient.lastName break default: firstName = faker.person.firstName().replace(`'`, '’') @@ -81,6 +81,7 @@ export function generateParent(childLastName, isMum) { tel, sms, ...(smsStatus && { smsStatus }) - }) + }), + patient_uuid: patient.uuid }) } diff --git a/app/generators/pds-record.js b/app/generators/pds-record.js index 9df0d6f88..cbbf8ca18 100644 --- a/app/generators/pds-record.js +++ b/app/generators/pds-record.js @@ -1,9 +1,6 @@ -import { fakerEN_GB as faker } from '@faker-js/faker' - import { PDSRecord } from '../models.js' import { generateChild } from './child.js' -import { generateParent } from './parent.js' /** * Generate fake PDS record @@ -13,28 +10,11 @@ import { generateParent } from './parent.js' export function generatePDSRecord() { const child = generateChild() - // Parents - const parent1 = generateParent(child.lastName, true) - - // PDS records provide only a subset of parent data - delete parent1.sms - delete parent1.contactPreference - delete parent1.contactPreferenceDetails - - let parent2 - const addSecondParent = faker.datatype.boolean(0.5) - if (addSecondParent) { - parent2 = generateParent(child.lastName) - - // PDS records provide only a subset of parent data - delete parent2.sms - delete parent2.contactPreference - delete parent2.contactPreferenceDetails - } + // PDS records provide only a subset of child data + delete child.preferredFirstName + delete child.preferredLastName + delete child.registrationGroup + delete child.school_id - return new PDSRecord({ - ...child, - parent1, - parent2 - }) + return new PDSRecord(child) } diff --git a/app/models/child.js b/app/models/child.js index beefa56da..42b0f7101 100644 --- a/app/models/child.js +++ b/app/models/child.js @@ -1,3 +1,5 @@ +import { fakerEN_GB as faker } from '@faker-js/faker' + import schools from '../datasets/schools.js' import { Adjustment, @@ -23,6 +25,7 @@ import { formatList, formatYearGroup, stringToArray } from '../utils/string.js' * @class Child * @param {object} options - Options * @param {object} [context] - Context + * @property {string} [uuid] - UUID * @property {object} [context] - Context * @property {string} [firstName] - First name * @property {string} [lastName] - Last name @@ -49,6 +52,7 @@ import { formatList, formatYearGroup, stringToArray } from '../utils/string.js' export class Child { constructor(options, context) { this.context = context + this.uuid = options?.uuid || faker.string.uuid() this.firstName = options?.firstName || '' this.lastName = options?.lastName || '' this.preferredFirstName = options?.preferredFirstName diff --git a/app/models/parent.js b/app/models/parent.js index e90dcfb61..0e70c2d0c 100644 --- a/app/models/parent.js +++ b/app/models/parent.js @@ -21,6 +21,7 @@ import { formatOther, formatParent, stringToBoolean } from '../utils/string.js' * @property {import('../enums.js').NotifySmsStatus} smsStatus - SMS status * @property {boolean} [contactPreference] - Preferred contact method * @property {string} [contactPreferenceDetails] - Contact method details + * @property {string} [patient_uuid] - Patient UUID */ export class Parent { constructor(options, context) { @@ -48,6 +49,8 @@ export class Parent { if (this.contactPreference) { this.contactPreferenceDetails = options?.contactPreferenceDetails } + + this.patient_uuid = options?.patient_uuid } /** diff --git a/app/models/patient.js b/app/models/patient.js index be5f91924..0573c6a37 100644 --- a/app/models/patient.js +++ b/app/models/patient.js @@ -41,7 +41,6 @@ import { * @augments Child * @param {object} options - Options * @param {object} [context] - Global context - * @property {string} [uuid] - UUID * @property {string} [nhsn] - NHS number * @property {boolean} [invalid] - Flagged as invalid * @property {boolean} [sensitive] - Flagged as sensitive @@ -64,7 +63,6 @@ export class Patient extends Child { const invalid = stringToBoolean(options?.invalid) const sensitive = stringToBoolean(options?.sensitive) - this.uuid = options?.uuid || faker.string.uuid() this.nhsn = options?.nhsn || this.nhsNumber this.invalid = invalid this.sensitive = sensitive diff --git a/app/models/pds-record.js b/app/models/pds-record.js index 9f66eb727..293f7b927 100644 --- a/app/models/pds-record.js +++ b/app/models/pds-record.js @@ -16,13 +16,11 @@ import { * @augments Child * @param {object} options - Options * @param {object} [context] - Global context - * @property {string} [uuid] - UUID * @property {string} [nhsn] - NHS number * @property {boolean} [invalid] - Flagged as invalid * @property {boolean} [sensitive] - Flagged as sensitive * @property {object} [address] - Address - * @property {Parent} [parent1] - Parent 1 - * @property {Parent} [parent2] - Parent 2 + * @property {Array} [parent_uuids] - Parent UUIDS */ export class PDSRecord extends Child { constructor(options, context) { @@ -31,18 +29,14 @@ export class PDSRecord extends Child { const invalid = stringToBoolean(options?.invalid) const sensitive = stringToBoolean(options?.sensitive) - this.uuid = options?.uuid || faker.string.uuid() this.nhsn = options?.nhsn || '999#######'.replace(/#+/g, (m) => faker.string.numeric(m.length)) this.invalid = invalid this.sensitive = sensitive this.address = !sensitive && options?.address ? options.address : undefined - this.parent1 = - !sensitive && options?.parent1 ? new Parent(options.parent1) : undefined - this.parent2 = - !sensitive && options?.parent2 ? new Parent(options.parent2) : undefined this.school_id = null + this.parent_uuids = options?.parent_uuids || [] } /** @@ -51,12 +45,7 @@ export class PDSRecord extends Child { * @returns {boolean} Has no parental details */ get hasNoContactDetails() { - return ( - !this.parent1?.email && - !this.parent1?.tel && - !this.parent2?.email && - !this.parent2?.tel - ) + return this.parents.every((parent) => !parent.email && !parent.tel) } /** @@ -71,20 +60,12 @@ export class PDSRecord extends Child { /** * Get parents (from record and replies) * - * @returns {Array} Parents + * @returns {Array|undefined} Parents */ get parents() { - const parents = new Map() - - if (this.parent1) { - parents.set(this.parent1.uuid, new Parent(this.parent1)) + if (!this.sensitive) { + return this.parent_uuids.map((uuid) => Parent.findOne(uuid, this.context)) } - - if (this.parent2) { - parents.set(this.parent2.uuid, new Parent(this.parent2)) - } - - return [...parents.values()] } /** @@ -116,8 +97,6 @@ export class PDSRecord extends Child { ...super.formatted, fullNameAndNhsn: formatWithSecondaryText(this.fullName, formattedNhsn), nhsn: formattedNhsn, - parent1: this.parent1 && formatParent(this.parent1), - parent2: this.parent2 && formatParent(this.parent2), parents: formatList(formattedParents) } } diff --git a/lib/create-data.js b/lib/create-data.js index c36266d83..8d799b550 100644 --- a/lib/create-data.js +++ b/lib/create-data.js @@ -33,6 +33,7 @@ import { generateClinicVaccinationPeriods } from '../app/generators/clinic-vacci import { generateConsent } from '../app/generators/consent.js' import { generateInstruction } from '../app/generators/instruction.js' import { generateNotice } from '../app/generators/notice.js' +import { generateParent } from '../app/generators/parent.js' import { generatePatient } from '../app/generators/patient.js' import { generatePDSRecord } from '../app/generators/pds-record.js' import { generateSession } from '../app/generators/session.js' @@ -140,6 +141,18 @@ Array.from([...range(0, totalPatients)]).forEach(() => { context.pdsRecords = {} Array.from([...range(0, 20)]).forEach(() => { const pdsRecord = generatePDSRecord() + + // Parents + const parent1 = generateParent(pdsRecord, true) + context.parents[parent1.uuid] = parent1 + pdsRecord.parent_uuids.push(parent1.uuid) + + if (faker.datatype.boolean(0.5)) { + const parent2 = generateParent(pdsRecord) + context.parents[parent2.uuid] = parent2 + pdsRecord.parent_uuids.push(parent2.uuid) + } + context.pdsRecords[pdsRecord.uuid] = pdsRecord }) From b6421332323dfddfe3ea7857ca627c7aff0d9934 Mon Sep 17 00:00:00 2001 From: Paul Robert Lloyd Date: Tue, 12 May 2026 13:31:20 +0100 Subject: [PATCH 3/8] Get parents on patient record from parents in session data --- app/controllers/patient-session.js | 4 ++-- app/generators/clinic-appointment.js | 4 ++-- app/generators/consent.js | 6 ++--- app/generators/patient.js | 12 ---------- app/locales/en.js | 6 ----- app/models/download.js | 2 +- app/models/patient.js | 33 +++++++++------------------- lib/create-data.js | 12 ++++++++++ 8 files changed, 30 insertions(+), 49 deletions(-) diff --git a/app/controllers/patient-session.js b/app/controllers/patient-session.js index 029e062ed..c30f75f75 100644 --- a/app/controllers/patient-session.js +++ b/app/controllers/patient-session.js @@ -316,7 +316,7 @@ export const patientSessionController = { request.flash( 'success', - __('patientSession.invite.success', { parent: patient.parent1 }) + __('patientSession.invite.success', { parent: patient.parents[0] }) ) return response.redirect(back) @@ -333,7 +333,7 @@ export const patientSessionController = { { createdBy_uid: account.uid }, - patient.parent1 + patient.parents[0] ) return response.redirect(back) diff --git a/app/generators/clinic-appointment.js b/app/generators/clinic-appointment.js index da62619ed..55d9a9b5e 100644 --- a/app/generators/clinic-appointment.js +++ b/app/generators/clinic-appointment.js @@ -61,8 +61,8 @@ export function generateClinicAppointment(patient, session, booking) { if (!booking.parent.fullName) { // First appointment, so set up the booking's parent booking.parent = - patient.parent1 || - patient.parent2 || + patient.parents[0] || + patient.parents[1] || generateParent(child.lastName, faker.datatype.boolean(0.5)) // ...and their relationship to this child parentalRelationship = booking.parent.relationship diff --git a/app/generators/consent.js b/app/generators/consent.js index d255b036a..02fca186c 100644 --- a/app/generators/consent.js +++ b/app/generators/consent.js @@ -39,9 +39,9 @@ export function generateConsent( // Parent let parent if (index === 0) { - parent = patientSession.patient.parent1 - } else if (index === 1 && patientSession.patient?.parent2) { - parent = patientSession.patient.parent2 + parent = patientSession.patient.parents[0] + } else if (index === 1 && patientSession.patient?.parents[1]) { + parent = patientSession.patient.parents[1] } // Can’t create a consent response if no parent associated with child diff --git a/app/generators/patient.js b/app/generators/patient.js index 51abfae7b..b5c4b263a 100644 --- a/app/generators/patient.js +++ b/app/generators/patient.js @@ -4,7 +4,6 @@ import schools from '../datasets/schools.js' import { Patient } from '../models.js' import { generateChild } from './child.js' -import { generateParent } from './parent.js' /** * Generate fake patient record @@ -14,15 +13,6 @@ import { generateParent } from './parent.js' export function generatePatient() { const child = generateChild() - // Parents - const parent1 = generateParent(child, true) - - let parent2 - const addSecondParent = faker.datatype.boolean(0.5) - if (addSecondParent) { - parent2 = generateParent(child) - } - // Pending changes const pendingChanges = {} const hasPendingChanges = faker.datatype.boolean(0.025) @@ -48,8 +38,6 @@ export function generatePatient() { return new Patient({ ...child, - parent1, - parent2, pendingChanges }) } diff --git a/app/locales/en.js b/app/locales/en.js index f7be8c9f0..7cd634b5e 100644 --- a/app/locales/en.js +++ b/app/locales/en.js @@ -1679,12 +1679,6 @@ export const en = { label: 'Give details' } }, - parent1: { - title: 'Details for first parent or guardian' - }, - parent2: { - title: 'Details for second parent or guardian' - }, programmes: { label: 'Vaccination programmes' }, diff --git a/app/models/download.js b/app/models/download.js index b58ae3c09..944c1a799 100644 --- a/app/models/download.js +++ b/app/models/download.js @@ -318,7 +318,7 @@ export class Download { firstName: vaccination.patient?.firstName, dob: vaccination.patient?.dob, address_line1: vaccination.patient?.address?.addressLine1, - parent: vaccination.patient?.parent1?.fullName, + parent: vaccination.patient?.parents[0]?.fullName, ethnicity: '', date: vaccination.createdAt, time: vaccination.createdAt, diff --git a/app/models/patient.js b/app/models/patient.js index 0573c6a37..b193df09c 100644 --- a/app/models/patient.js +++ b/app/models/patient.js @@ -45,14 +45,13 @@ import { * @property {boolean} [invalid] - Flagged as invalid * @property {boolean} [sensitive] - Flagged as sensitive * @property {object} [address] - Address - * @property {Parent} [parent1] - Parent 1 - * @property {Parent} [parent2] - Parent 2 * @property {Patient} [pendingChanges] - Pending changes to record values * @property {import('../enums.js').ArchiveRecordReason} [archiveReason] - Archival reason * @property {string} [archiveReasonOther] - Other archival reason * @property {Array} [clinicProgramme_ids] - Clinic programme invitations * @property {Array} events - Events * @property {Array} [reply_uuids] - Reply IDs + * @property {Array} [parent_uuids] - Parent UUIDS * @property {Array} [patientSession_uuids] - Patient session IDs * @property {Array} [vaccination_uuids] - Vaccination UUIDs */ @@ -67,10 +66,6 @@ export class Patient extends Child { this.invalid = invalid this.sensitive = sensitive this.address = !sensitive && options?.address ? options.address : undefined - this.parent1 = - !sensitive && options?.parent1 ? new Parent(options.parent1) : undefined - this.parent2 = - !sensitive && options?.parent2 ? new Parent(options.parent2) : undefined this.archiveReason = options?.archiveReason this.archiveReasonOther = options?.archiveReasonOther this.pendingChanges = options?.pendingChanges || {} @@ -78,6 +73,7 @@ export class Patient extends Child { this.clinicProgramme_ids = options?.clinicProgramme_ids || [] this.events = options?.events || [] this.reply_uuids = options?.reply_uuids || [] + this.parent_uuids = options?.parent_uuids || [] this.patientSession_uuids = options?.patientSession_uuids || [] this.vaccination_uuids = options?.vaccination_uuids || [] } @@ -114,12 +110,7 @@ export class Patient extends Child { * @returns {boolean} Has no parental details */ get hasNoContactDetails() { - return ( - !this.parent1?.email && - !this.parent1?.tel && - !this.parent2?.email && - !this.parent2?.tel - ) + return this.parents.every((parent) => !parent.email && !parent.tel) } /** @@ -180,19 +171,15 @@ export class Patient extends Child { get parents() { const parents = new Map() - if (this.parent1) { - parents.set(this.parent1.uuid, new Parent(this.parent1)) - } - - if (this.parent2) { - parents.set(this.parent2.uuid, new Parent(this.parent2)) + if (!this.sensitive) { + this.parent_uuids.forEach((uuid) => + parents.set(uuid, Parent.findOne(uuid, this.context)) + ) } // Add any new parents found in consent replies Object.values(this.replies).forEach(({ parent }) => { - if (parent && !parents.has(parent.uuid)) { - parents.set(parent.uuid, new Parent(parent)) - } + parents.set(parent.uuid, new Parent(parent)) }) return [...parents.values()] @@ -832,9 +819,9 @@ export class Patient extends Child { this.dod = removeDays(today(), 5) name = `Record updated with child’s date of death` break - case notice.type === NoticeType.NoNotify && this.parent1?.notify: + case notice.type === NoticeType.NoNotify && this.parents[0]?.notify: // Notify request to not share vaccination with GP - this.parent1.notify = false + this.parents[0].notify = false name = `Child gave consent for HPV and flu vaccinations under Gillick competence and does not want their parents to be notified.\n\nThese records are not automatically synced with GP records.\n\nYour team must let the child’s GP know they were vaccinated.` break case notice.type === NoticeType.Invalid: diff --git a/lib/create-data.js b/lib/create-data.js index 8d799b550..91f427aee 100644 --- a/lib/create-data.js +++ b/lib/create-data.js @@ -134,6 +134,18 @@ context.parents = {} context.patients = {} Array.from([...range(0, totalPatients)]).forEach(() => { const patient = generatePatient() + + // Parents + const parent1 = generateParent(patient, true) + context.parents[parent1.uuid] = parent1 + patient.parent_uuids.push(parent1.uuid) + + if (faker.datatype.boolean(0.5)) { + const parent2 = generateParent(patient) + context.parents[parent2.uuid] = parent2 + patient.parent_uuids.push(parent2.uuid) + } + context.patients[patient.uuid] = patient }) From 22f849e126338c82484aa346be444a7d382d26b2 Mon Sep 17 00:00:00 2001 From: Paul Robert Lloyd Date: Wed, 13 May 2026 14:42:47 +0100 Subject: [PATCH 4/8] Get parents on consent response from parents in session data --- app/controllers/give-or-refuse-consent.js | 13 ++--- app/controllers/reply.js | 53 +++++++------------ app/datasets/activity.js | 2 +- app/generators/consent.js | 20 ++----- app/models/patient.js | 8 +-- app/models/reply.js | 35 +++++++++++- .../form/check-answers.njk | 2 +- .../form/contact-preference.njk | 4 +- .../give-or-refuse-consent/form/parent.njk | 16 +++--- lib/create-data.js | 15 +++++- 10 files changed, 89 insertions(+), 79 deletions(-) diff --git a/app/controllers/give-or-refuse-consent.js b/app/controllers/give-or-refuse-consent.js index 108c9304e..503efcd73 100644 --- a/app/controllers/give-or-refuse-consent.js +++ b/app/controllers/give-or-refuse-consent.js @@ -8,9 +8,7 @@ import { SessionPresetName, SessionType } from '../enums.js' -import { generateChild } from '../generators/child.js' -import { generateParent } from '../generators/parent.js' -import { Consent, Session } from '../models.js' +import { Consent, Patient, Session } from '../models.js' import { getHealthQuestionPaths } from '../utils/consent.js' import { formatList, kebabToCamelCase } from '../utils/string.js' @@ -53,15 +51,14 @@ export const giveOrRefuseConsentController = { // Text and email messages if (view === 'emails' || view === 'texts') { - const child = generateChild() - const parent = generateParent(child.lastName) + const patient = Patient.findAll(data)[0] response.locals.assetsName = 'prototype' response.locals.consent = new Consent( { - child, - parent, + child: patient, + parent_uuid: patient.parent_uuids[0], session_id: session.id }, data @@ -158,7 +155,7 @@ export const giveOrRefuseConsentController = { // Parent journey [`/${session_id}/${consent_uuid}/new/parent`]: { [`/${session_id}/parental-responsibility`]: { - data: 'consent.parent.hasParentalResponsibility', + data: 'consent.parent_.hasParentalResponsibility', value: 'false' } }, diff --git a/app/controllers/reply.js b/app/controllers/reply.js index b6810f168..b330bf81a 100644 --- a/app/controllers/reply.js +++ b/app/controllers/reply.js @@ -117,7 +117,7 @@ export const replyController = { // Remove any parent details in reply if self consent if (reply.selfConsent) { - delete reply.parent + delete reply.parent_uuid } if (triage?.outcome) { @@ -291,32 +291,18 @@ export const replyController = { ...(referrer && { back: referrer }) } - const consentRefusals = Object.values(patientSession.replies).filter( - (reply) => reply.decision === ReplyDecision.Refused + response.locals.respondentItems = patientSession.patient.parents.map( + (parent) => ({ + text: formatParent(parent, false), + hint: { text: parent.tel }, + value: parent.uuid + }) ) - if (Object.values(consentRefusals).length > 0) { - response.locals.respondentItems = consentRefusals.map( - ({ parent, uuid }) => ({ - text: `${parent.fullName} (${parent.relationship})`, - hint: { text: parent.tel }, - value: uuid - }) - ) - } else { - response.locals.respondentItems = patientSession.patient.parents.map( - (parent, index) => ({ - text: formatParent(parent, false), - hint: { text: parent.tel }, - value: `parent-${index + 1}` - }) - ) - } - // Child can self consent if assessed as Gillick competent if (patientSession.gillick?.competent === GillickCompetent.True) { response.locals.respondentItems.unshift({ - text: `${reply?.patient?.fullName} (child)`, + text: `${patientSession.patient?.firstName} (child)`, value: 'self' }) } @@ -360,6 +346,9 @@ export const replyController = { const { data } = request.session let { paths, patientSession, triage, vaccination } = response.locals + // Add parents from global context to wizard + data.wizard.parents = data.parents + const reply = Reply.update(reply_uuid, request.body.reply, data.wizard) // Create parent based on choice of respondent @@ -367,27 +356,21 @@ export const replyController = { switch (respondent) { case 'new': // Consent response is from a new contact reply.method = ReplyMethod.Phone - reply.parent = new Parent({}) + reply.parent_uuid = Parent.create( + { + patient_uuid: reply.patient_uuid + }, + data.wizard + ).uuid reply.selfConsent = false break case 'self': reply.method = ReplyMethod.InPerson - reply.parent = null reply.selfConsent = true break - case 'parent-1': // Consent response is from CHIS record - reply.method = ReplyMethod.Phone - reply.parent = patientSession.patient.parents[0] - reply.selfConsent = false - break - case 'parent-2': // Consent response is from CHIS record - reply.method = ReplyMethod.Phone - reply.parent = patientSession.patient.parents[1] - reply.selfConsent = false - break default: // Consent response is an existing respondent reply.method = ReplyMethod.Phone - reply.parent = Reply.findOne(respondent, data).parent + reply.parent_uuid = respondent reply.selfConsent = false // Store reply that needs marked as invalid diff --git a/app/datasets/activity.js b/app/datasets/activity.js index bc27e828e..9e1b960ed 100644 --- a/app/datasets/activity.js +++ b/app/datasets/activity.js @@ -10,7 +10,7 @@ export default { created: ({ child, decision, parent, selfConsent }) => selfConsent ? `${decision} by ${child?.fullName} (child)` - : `${decision} by ${parent.fullNameAndRelationship}`, + : `${decision} by ${parent?.fullNameAndRelationship}`, updated: ({ decision, parent }) => `${decision} in updated response from ${parent.fullNameAndRelationship}`, followedUp: ({ confirmed, decision, parent }) => diff --git a/app/generators/consent.js b/app/generators/consent.js index 02fca186c..dea2f969a 100644 --- a/app/generators/consent.js +++ b/app/generators/consent.js @@ -22,7 +22,7 @@ import { * @param {import('../models.js').Programme} programme - Programme * @param {import('../models.js').Session} session - Session * @param {import('../models.js').PatientSession} patientSession - Patient session - * @param {number} index - Reply + * @param {import('../models.js').Parent} parent - Parent * @param {Date} [lastConsentCreatedAt] - Date previous consent response created * @returns {Consent|undefined} Consent */ @@ -30,20 +30,12 @@ export function generateConsent( programme, session, patientSession, - index, + parent, lastConsentCreatedAt ) { // Child const child = patientSession.patient - // Parent - let parent - if (index === 0) { - parent = patientSession.patient.parents[0] - } else if (index === 1 && patientSession.patient?.parents[1]) { - parent = patientSession.patient.parents[1] - } - // Can’t create a consent response if no parent associated with child if (!parent) { return @@ -54,13 +46,6 @@ export function generateConsent( return } - // If telephone number provided, sometimes add a communication need - if (parent.tel && faker.datatype.boolean(0.2)) { - parent.contactPreference = true - parent.contactPreferenceDetails = - 'I sometimes have difficulty hearing phone calls, so it’s best to send me a text message.' - } - // Decision const decision = faker.helpers.weightedArrayElement([ { value: ReplyDecision.Given, weight: 10 }, @@ -159,6 +144,7 @@ export function generateConsent( }), consultation }), + parent_uuid: parent.uuid, programme_id: programme.id, session_id: session.id }) diff --git a/app/models/patient.js b/app/models/patient.js index b193df09c..9b462270d 100644 --- a/app/models/patient.js +++ b/app/models/patient.js @@ -178,9 +178,11 @@ export class Patient extends Child { } // Add any new parents found in consent replies - Object.values(this.replies).forEach(({ parent }) => { - parents.set(parent.uuid, new Parent(parent)) - }) + Object.values(this.replies) + .filter(({ selfConsent }) => !selfConsent) + .forEach(({ parent }) => { + parents.set(parent.uuid, new Parent(parent)) + }) return [...parents.values()] } diff --git a/app/models/reply.js b/app/models/reply.js index c83c99d98..c6d99fc0c 100644 --- a/app/models/reply.js +++ b/app/models/reply.js @@ -47,7 +47,7 @@ import { * @property {string} [createdBy_uid] - User who created reply * @property {Date} [updatedAt] - Updated date * @property {import('./child.js').Child} [child] - Child - * @property {import('./parent.js').Parent} [parent] - Parent or guardian + * @property {import('./parent.js').Parent} [parent_] - Parent or guardian * @property {ReplyDecision} [decision] - Consent decision * @property {boolean} [alternative] - Consent for alternative vaccine * @property {boolean} [confirmed] - Decision confirmed @@ -65,6 +65,7 @@ import { * @property {string} [refusalReasonDetails] - Refusal reason details * @property {boolean} [selfConsent] - Reply given by child * @property {string} [note] - Note about this response + * @property {string} [parent_uuid] - Parent UUID * @property {string} patient_uuid - Patient UUID * @property {string} [programme_id] - Programme ID * @property {string} session_id - Session ID @@ -81,10 +82,11 @@ export class Reply { this.decision = this.confirmed === true ? ReplyDecision.Refused : options?.decision this.ethnicity = stringToBoolean(options?.ethnicity) - this.parent = options?.parent && new Parent(options.parent) this.method = options?.method this.selfConsent = options?.selfConsent this.note = options?.note || '' + this.parent_ = options?.parent_ && new Parent(options.parent_) + this.parent_uuid = options?.parent_uuid this.patient_uuid = options?.patient_uuid this.programme_id = options?.programme_id this.session_id = options?.session_id @@ -339,6 +341,34 @@ export class Reply { return Object.fromEntries(healthQuestionsForDecision) } + /** + * Get parent + * + * @returns {Parent|undefined} Parent + */ + get parent() { + try { + if (this.parent_uuid) { + return Parent.findOne(this.parent_uuid, this.context) + } + } catch (error) { + console.error('Reply.parent (get)', error.message) + } + } + + /** + * Set parent + */ + set parent(updates) { + try { + if (this.parent_uuid) { + Parent.update(this.parent_uuid, updates, this.context) + } + } catch (error) { + console.error('Reply.parent (set)', error.message) + } + } + /** * Get patient * @@ -353,6 +383,7 @@ export class Reply { console.error('Reply.patient', error.message) } } + /** * Get programme * diff --git a/app/views/give-or-refuse-consent/form/check-answers.njk b/app/views/give-or-refuse-consent/form/check-answers.njk index 711ba4cdb..5512cd1d8 100644 --- a/app/views/give-or-refuse-consent/form/check-answers.njk +++ b/app/views/give-or-refuse-consent/form/check-answers.njk @@ -148,7 +148,7 @@ headingSize: "m" }, lastRowBorder: false, - rows: summaryRows(consent.parent, { + rows: summaryRows(consent.parent_, { fullName: { href: editPath("parent") }, diff --git a/app/views/give-or-refuse-consent/form/contact-preference.njk b/app/views/give-or-refuse-consent/form/contact-preference.njk index d46224864..030043ab3 100644 --- a/app/views/give-or-refuse-consent/form/contact-preference.njk +++ b/app/views/give-or-refuse-consent/form/contact-preference.njk @@ -22,13 +22,13 @@ conditional: { html: textarea({ label: { text: __("consent.parent.contactPreferenceDetails.label") }, - decorate: "consent.parent.contactPreferenceDetails" + decorate: "consent.parent_.contactPreferenceDetails" }) } }, { text: __("consent.parent.contactPreference.no"), value: false }], - decorate: "consent.parent.contactPreference" + decorate: "consent.parent_.contactPreference" }) }} {% endblock %} diff --git a/app/views/give-or-refuse-consent/form/parent.njk b/app/views/give-or-refuse-consent/form/parent.njk index ca218fbbc..1649abc66 100644 --- a/app/views/give-or-refuse-consent/form/parent.njk +++ b/app/views/give-or-refuse-consent/form/parent.njk @@ -9,7 +9,7 @@ {{ input({ label: { text: __("consent.parent.fullName.label") }, - decorate: "consent.parent.fullName" + decorate: "consent.parent_.fullName" }) }} {%- set fosterCarerHtml = radios({ @@ -18,19 +18,19 @@ }, hint: { text: __("consent.parent.hasParentalResponsibility.hint") }, items: getBooleanItems(), - decorate: "consent.parent.hasParentalResponsibility" + decorate: "consent.parent_.hasParentalResponsibility" }) %} {%- set otherHtml = input({ label: { text: __("consent.parent.relationshipOther.label") }, - decorate: "consent.parent.relationshipOther" + decorate: "consent.parent_.relationshipOther" }) + radios({ fieldset: { legend: { text: __("consent.parent.hasParentalResponsibility.label") } }, hint: { text: __("consent.parent.hasParentalResponsibility.hint") }, items: getBooleanItems(), - decorate: "consent.parent.hasParentalResponsibility" + decorate: "consent.parent_.hasParentalResponsibility" }) %} {# Add conditional html for ‘Foster carer’ option #} @@ -44,19 +44,19 @@ legend: { text: __("consent.parent.relationship.label") } }, items: items, - decorate: "consent.parent.relationship" + decorate: "consent.parent_.relationship" }) }} {{ input({ label: { text: __("consent.parent.email.label") }, hint: { text: __("consent.parent.email.hint") }, - decorate: "consent.parent.email" + decorate: "consent.parent_.email" }) }} {{ input({ label: { text: __("consent.parent.tel.label") + " (optional)" }, hint: { text: __("consent.parent.tel.hint") }, - decorate: "consent.parent.tel" + decorate: "consent.parent_.tel" }) }} {{ checkboxes({ @@ -64,6 +64,6 @@ text: __("consent.parent.sms.label"), value: true }], - decorate: "consent.parent.sms" + decorate: "consent.parent_.sms" }) }} {% endblock %} diff --git a/lib/create-data.js b/lib/create-data.js index 91f427aee..01b89657e 100644 --- a/lib/create-data.js +++ b/lib/create-data.js @@ -323,12 +323,24 @@ for (const patientSession of Object.values(context.patientSessions)) { ]) Array.from([...range(0, maxReplies)]).forEach((_, index) => { let lastConsentCreatedAt + + const parent = generateParent(patient, index === 0) + + // If telephone number provided, sometimes add a communication need + if (parent.tel && faker.datatype.boolean(0.2)) { + parent.contactPreference = true + parent.contactPreferenceDetails = + 'I sometimes have difficulty hearing phone calls, so it’s best to send me a text message.' + } + + context.parents[parent.uuid] = parent + for (programme of session.programmes) { const consent = generateConsent( programme, session, patientSession, - index, + parent, lastConsentCreatedAt ) @@ -696,7 +708,6 @@ if (vaccinatedPatient) { // Update existing consent response to be self-consent from the child givenConsentReply.method = ReplyMethod.InPerson - givenConsentReply.parent = false givenConsentReply.selfConsent = true // Update consent response From c8f0fa50aaea19b8206881d5dd78566b98d8a6fe Mon Sep 17 00:00:00 2001 From: Paul Robert Lloyd Date: Tue, 12 May 2026 17:36:33 +0100 Subject: [PATCH 5/8] Manage parents --- app/controllers/parent.js | 134 ++++++++++++++++++++++++++++++ app/datasets/activity.js | 1 + app/locales/en.js | 23 ++++- app/models/parent.js | 80 ++++++++++++++++++ app/models/patient.js | 14 ++++ app/routes.js | 2 + app/routes/parent.js | 22 +++++ app/views/parent/action.njk | 31 +++++++ app/views/parent/form/edit.njk | 70 ++++++++++++++++ app/views/patient/contacts.njk | 11 +-- app/views/patient/form/parent.njk | 70 ---------------- 11 files changed, 381 insertions(+), 77 deletions(-) create mode 100644 app/controllers/parent.js create mode 100644 app/routes/parent.js create mode 100644 app/views/parent/action.njk create mode 100644 app/views/parent/form/edit.njk delete mode 100644 app/views/patient/form/parent.njk diff --git a/app/controllers/parent.js b/app/controllers/parent.js new file mode 100644 index 000000000..92367e3d0 --- /dev/null +++ b/app/controllers/parent.js @@ -0,0 +1,134 @@ +import { Parent, Patient } from '../models.js' + +export const parentController = { + /** + * @type {import("express").RequestParamHandler} + */ + read(request, response, next, parent_uuid) { + response.locals.parent = Parent.findOne(parent_uuid, request.session.data) + + next() + }, + + /** + * @type {import("express").RequestHandler} + */ + new(request, response) { + const { patient_uuid } = request.query + const { data } = request.session + + const parent = Parent.create( + { + patient_uuid + }, + data.wizard + ) + + return response.redirect(`${parent.uri}/new`) + }, + + /** + * @param {string} [type] - Form type + * @returns {import("express").RequestHandler} - Request handler + */ + update(type) { + return (request, response) => { + const { parent_uuid } = request.params + const { data } = request.session + const { __, back } = response.locals + + // Update session data + let parent + if (type === 'new') { + parent = Parent.create(data.wizard.parents[String(parent_uuid)], data) + + // Add parent to patient contacts + const patient = Patient.findOne(parent.patient_uuid, data) + patient.addContact(parent) + } else { + parent = Parent.update( + parent_uuid, + data.wizard.parents[String(parent_uuid)], + data + ) + } + + // Clean up session data + delete data.parent + delete data.wizard + + request.flash('success', __(`parent.${type}.success`, { parent })) + + return response.redirect(back) + } + }, + + /** + * @param {string} type - Form type + * @returns {import("express").RequestHandler} - Request handler + */ + readForm(type) { + return (request, response, next) => { + const { parent_uuid } = request.params + const { data } = request.session + + // Setup wizard if not already setup + let parent = Parent.findOne(parent_uuid, data.wizard) + if (!parent) { + parent = Parent.create(response.locals.parent, data.wizard) + } + + response.locals.parent = new Parent(parent, data) + response.locals.back = `/patients/${parent.patient_uuid}/contacts` + response.locals.type = type + + next() + } + }, + + /** + * @type {import("express").RequestHandler} + */ + showForm(request, response) { + return response.render(`parent/form/edit`) + }, + + updateForm(request, response, next) { + const { parent_uuid } = request.params + const { data } = request.session + + Parent.update(parent_uuid, request.body.parent, data.wizard) + + return next() + }, + + /** + * @param {string} type - Form type + * @returns {import("express").RequestHandler} - Request handler + */ + action(type) { + return (request, response) => { + const { parent } = response.locals + + response.render('parent/action', { + back: `/patients/${parent.patient_uuid}/contacts`, + type + }) + } + }, + + /** + * @type {import("express").RequestHandler} + */ + delete(request, response) { + const { parent_uuid } = request.params + const { data } = request.session + const { __, parent } = response.locals + + Parent.delete(parent_uuid, data) + + request.flash('success', __(`parent.delete.success`)) + + return response.redirect(`/patients/${parent.patient_uuid}/contacts`) + } +} diff --git a/app/datasets/activity.js b/app/datasets/activity.js index 9e1b960ed..c1150acab 100644 --- a/app/datasets/activity.js +++ b/app/datasets/activity.js @@ -78,6 +78,7 @@ export default { 'Consent, health information, triage outcome and PSD status expired', merged: (mergedPatient, patient) => `The record for ${mergedPatient.fullName} (date of birth ${mergedPatient.formatted.dob}) was merged with the record for ${patient.fullName} (date of birth ${patient.formatted.dob}) because they have the same NHS number (${mergedPatient.formatted.nhsn}).`, + contact: (contact) => `${contact.fullName} added to record`, updated: (source) => source ? `Record updated automatically after new details were imported in a ${source} upload` diff --git a/app/locales/en.js b/app/locales/en.js index 7cd634b5e..6074d251f 100644 --- a/app/locales/en.js +++ b/app/locales/en.js @@ -19,7 +19,8 @@ export const en = { actions: { label: 'Actions', change: 'Change', - remove: 'Remove', + delete: 'Delete', + edit: 'Edit', review: 'Review', update: 'Update', archive: 'Archive' @@ -1391,7 +1392,25 @@ export const en = { parent: { label: 'Parent or guardian', new: { - label: 'Add a new contact' + label: 'Add a new contact', + title: 'Add a new contact', + confirm: 'Add contact', + success: 'Contact added' + }, + edit: { + title: 'Edit contact', + confirm: 'Save changes', + success: 'Contact updated' + }, + delete: { + success: 'Contact deleted' + }, + action: { + title: 'Are you sure you want to {{type}} {{parent.fullName}}?', + description: + 'Deleting this contact will remove them from {{patient.firstName}}, but not from any other children.\n\nThis cannot be undone.', + confirm: 'Yes, %s this contact', + cancel: 'No, return to contacts' }, fullName: { label: 'Name' diff --git a/app/models/parent.js b/app/models/parent.js index 0e70c2d0c..1574afe09 100644 --- a/app/models/parent.js +++ b/app/models/parent.js @@ -1,6 +1,7 @@ import { fakerEN_GB as faker } from '@faker-js/faker' import { ParentalRelationship } from '../enums.js' +import { Patient } from '../models.js' import { formatOther, formatParent, stringToBoolean } from '../utils/string.js' /** @@ -62,6 +63,21 @@ export class Parent { return formatParent(this, false) } + /** + * Get patient + * + * @returns {Patient|undefined} Patient + */ + get patient() { + try { + if (this.patient_uuid) { + return Patient.findOne(this.patient_uuid, this.context) + } + } catch (error) { + console.error('Parent.patient', error.message) + } + } + /** * Get formatted values * @@ -85,6 +101,15 @@ export class Parent { return 'parent' } + /** + * Get URI + * + * @returns {string} URI + */ + get uri() { + return `/parents/${this.uuid}` + } + /** * Find all * @@ -113,4 +138,59 @@ export class Parent { return new Parent(context.parents[uuid], context) } } + + /** + * Create + * + * @param {object} parent - Parent + * @param {object} context - Context + * @returns {Parent} Created parent + * @static + */ + static create(parent, context) { + const createdParent = new Parent(parent) + + // Update context + context.parents = context.parents || {} + context.parents[createdParent.uuid] = createdParent + + return createdParent + } + + /** + * Update + * + * @param {string|string[]} uuid - Parent UUID + * @param {object} updates - Updates + * @param {object} context - Context + * @returns {Parent} Updated parent + * @static + */ + static update(uuid, updates, context) { + uuid = String(uuid) + + const updatedParent = Object.assign(Parent.findOne(uuid, context), updates) + + // Remove move context + delete updatedParent.context + + // Delete original move (with previous UUID) + delete context.parents[uuid] + + // Update context + context.parents[updatedParent.uuid] = updatedParent + + return updatedParent + } + + /** + * Delete + * + * @param {string|string[]} uuid - Parent UUID + * @param {object} context - Context + * @static + */ + static delete(uuid, context) { + delete context.parents[String(uuid)] + } } diff --git a/app/models/patient.js b/app/models/patient.js index 9b462270d..43856fabe 100644 --- a/app/models/patient.js +++ b/app/models/patient.js @@ -644,6 +644,20 @@ export class Patient extends Child { return archivedPatient } + /** + * Add contact to patient + * + * @param {import('./parent.js').Parent} parent - Parent + */ + addContact(parent) { + this.parent_uuids.push(parent.uuid) + + this.addEvent({ + name: activity.patient.contact(parent), + type: AuditEventType.Record + }) + } + /** * Add patient to session * diff --git a/app/routes.js b/app/routes.js index 3ff5b730b..eeb178d18 100644 --- a/app/routes.js +++ b/app/routes.js @@ -24,6 +24,7 @@ import { giveOrRefuseConsentRoutes } from './routes/give-or-refuse-consent.js' import { homeRoutes } from './routes/home.js' import { moveRoutes } from './routes/move.js' import { noticeRoutes } from './routes/notice.js' +import { parentRoutes } from './routes/parent.js' import { patientSessionRoutes } from './routes/patient-session.js' import { patientRoutes } from './routes/patient.js' import { pdsRecordRoutes } from './routes/pds-record.js' @@ -60,6 +61,7 @@ router.use('/moves', moveRoutes) router.use('/notices', noticeRoutes) router.use('/teams', teamRoutes) router.use('/teams/:team_id/clinics', clinicRoutes) +router.use('/parents', parentRoutes) router.use('/patients', patientRoutes) router.use('/pds', pdsRecordRoutes) router.use('/reports', reportRoutes) diff --git a/app/routes/parent.js b/app/routes/parent.js new file mode 100644 index 000000000..884193571 --- /dev/null +++ b/app/routes/parent.js @@ -0,0 +1,22 @@ +import express from 'express' + +import { parentController as parent } from '../controllers/parent.js' + +const router = express.Router({ strict: true }) + +router.get('/new', parent.new) + +router.param('parent_uuid', parent.read) + +router.get('/:parent_uuid/delete', parent.action('delete')) +router.post('/:parent_uuid/delete', parent.delete) + +router.all('/:parent_uuid/new', parent.readForm('new')) +router.get('/:parent_uuid/new', parent.showForm) +router.post('/:parent_uuid/new', parent.updateForm, parent.update('new')) + +router.all('/:parent_uuid/edit', parent.readForm('edit')) +router.get('/:parent_uuid/edit', parent.showForm) +router.post('/:parent_uuid/edit', parent.updateForm, parent.update('edit')) + +export const parentRoutes = router diff --git a/app/views/parent/action.njk b/app/views/parent/action.njk new file mode 100644 index 000000000..f2067e03b --- /dev/null +++ b/app/views/parent/action.njk @@ -0,0 +1,31 @@ +{% extends "_layouts/form.njk" %} + +{% block form %} + {{ appHeading({ + caption: parent.patient.fullName, + title: __("parent.action.title", { + parent: parent, + type: type + }) + }) }} + + {{ __("parent.action.description", { + patient: parent.patient + }) | nhsukMarkdown }} +{% endblock %} + +{% block afterForm %} + {{ appButtonGroup({ + buttons: [{ + text: __("parent.action.confirm", type), + variant: "warning", + attributes: { + formAction: parent.uri + "/" + type + } + }], + links: [{ + text: __("parent.action.cancel"), + href: back + }] + }) }} +{% endblock %} diff --git a/app/views/parent/form/edit.njk b/app/views/parent/form/edit.njk new file mode 100644 index 000000000..a83ee29c8 --- /dev/null +++ b/app/views/parent/form/edit.njk @@ -0,0 +1,70 @@ +{% extends "_layouts/form.njk" %} + +{% set confirmButtonText = __("parent." + type + ".confirm") %} +{% set title = __("parent." + type + ".title") %} + +{% block form %} + {{ appHeading({ + caption: parent.patient.fullName, + title: title + }) }} + + {{ input({ + label: { text: __("parent.fullName.label") }, + decorate: "parent.fullName" + }) }} + + {{ radios({ + fieldset: { + legend: { text: __("parent.relationship.label") } + }, + items: [{ + text: ParentalRelationship.Mum + }, { + text: ParentalRelationship.Dad + }, { + text: ParentalRelationship.Guardian + }, { + text: ParentalRelationship.Other, + conditional: { + html: input({ + label: { text: __("parent.relationshipOther.label") }, + decorate: "parent.relationshipOther" + }) + } + }], + decorate: "parent.relationship" + }) }} + + {{ input({ + label: { text: __("parent.email.label") }, + decorate: "parent.email" + }) }} + + {{ input({ + label: { text: __("parent.tel.label") }, + decorate: "parent.tel" + }) }} + + {{ radios({ + fieldset: { + legend: { + text: __("parent.contactPreference.label") + } + }, + items: [{ + text: __("parent.contactPreference.yes"), + value: true, + conditional: { + html: textarea({ + label: { text: __("parent.contactPreferenceDetails.label") }, + decorate: "parent.contactPreferenceDetails" + }) + } + }, { + text: __("parent.contactPreference.no"), + value: false + }], + decorate: "parent.contactPreference" + }) }} +{% endblock %} diff --git a/app/views/patient/contacts.njk b/app/views/patient/contacts.njk index 4e83288a7..ca221f0f8 100644 --- a/app/views/patient/contacts.njk +++ b/app/views/patient/contacts.njk @@ -28,7 +28,7 @@ {{ actionLink({ text: __("parent.new.label"), - href: "#" + href: "/parents/new?patient_uuid=" + patient.uuid }) }} {% for parent in patient.parents %} @@ -38,14 +38,15 @@ headingLevel: 4, actions: { items: [{ - text: "Edit", - href: patient.uri + "/edit/parent-" + loop.index + text: __("actions.edit"), + href: parent.uri + "/edit" }, { - text: "Remove", - href: "#" + text: __("actions.delete"), + href: parent.uri + "/delete" }] } }, + lastRowBorder: false, rows: summaryRows(parent, { email: {}, tel: {}, diff --git a/app/views/patient/form/parent.njk b/app/views/patient/form/parent.njk deleted file mode 100644 index a882f8c3f..000000000 --- a/app/views/patient/form/parent.njk +++ /dev/null @@ -1,70 +0,0 @@ -{% extends "_layouts/form.njk" %} - -{% set parent = "parent" + parentId %} -{% set title = __("patient." + parent + ".title") %} - -{% block form %} - {{ appHeading({ - caption: patient.fullName, - title: title - }) }} - - {{ input({ - label: { text: __("patient.parent.fullName.label") }, - decorate: "patient." + parent + ".fullName" - }) }} - - {{ radios({ - fieldset: { - legend: { text: __("patient.parent.relationship.label") } - }, - items: [{ - text: ParentalRelationship.Mum - }, { - text: ParentalRelationship.Dad - }, { - text: ParentalRelationship.Guardian - }, { - text: ParentalRelationship.Other, - conditional: { - html: input({ - label: { text: __("patient.parent.relationshipOther.label") }, - decorate: "patient." + parent + ".relationshipOther" - }) - } - }], - decorate: "patient." + parent + ".relationship" - }) }} - - {{ input({ - label: { text: __("patient.parent.email.label") }, - decorate: "patient." + parent + ".email" - }) }} - - {{ input({ - label: { text: __("patient.parent.tel.label") }, - decorate: "patient." + parent + ".tel" - }) }} - - {{ radios({ - fieldset: { - legend: { - text: __("patient.parent.contactPreference.label") - } - }, - items: [{ - text: __("patient.parent.contactPreference.yes"), - value: true, - conditional: { - html: textarea({ - label: { text: __("patient.parent.contactPreferenceDetails.label") }, - decorate: "patient." + parent + ".contactPreferenceDetails" - }) - } - }, { - text: __("patient.parent.contactPreference.no"), - value: false - }], - decorate: "patient." + parent + ".contactPreference" - }) }} -{% endblock %} From 102c2234860fc1f2352bdadd6530cdf4d9a3ccbb Mon Sep 17 00:00:00 2001 From: Paul Robert Lloyd Date: Tue, 12 May 2026 18:03:12 +0100 Subject: [PATCH 6/8] Fix generating circular references during data creation --- app/models/clinic-appointment.js | 2 +- app/models/parent.js | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/models/clinic-appointment.js b/app/models/clinic-appointment.js index dbce5abae..6a1b106f8 100644 --- a/app/models/clinic-appointment.js +++ b/app/models/clinic-appointment.js @@ -338,7 +338,7 @@ export class ClinicAppointment { } /** - * Remove 'context' so it's hidden from JSON.stringify, or we'll get + * Remove `context` so it’s hidden from JSON.stringify, or we’ll get * circular reference issues during saving * * @returns {object} Clinic appointment ready to be serialized to JSON diff --git a/app/models/parent.js b/app/models/parent.js index 1574afe09..f2bbd64ff 100644 --- a/app/models/parent.js +++ b/app/models/parent.js @@ -110,6 +110,17 @@ export class Parent { return `/parents/${this.uuid}` } + /** + * Remove `context` so it’s hidden from JSON.stringify, or we’ll get + * circular reference issues during saving + * + * @returns {object} Parent ready to be serialized to JSON + */ + toJSON() { + const { context, ...rest } = this + return rest + } + /** * Find all * From bb5b54062c2d41b99b44ce30773b59980f90ad91 Mon Sep 17 00:00:00 2001 From: Paul Robert Lloyd Date: Wed, 13 May 2026 18:29:14 +0100 Subject: [PATCH 7/8] Rename parent entity to contact --- app/controllers/activity.js | 18 +-- app/controllers/appointment.js | 2 +- app/controllers/book-into-a-clinic.js | 8 +- app/controllers/consent.js | 2 +- app/controllers/contact.js | 140 ++++++++++++++++++ app/controllers/give-or-refuse-consent.js | 12 +- app/controllers/parent.js | 134 ----------------- app/controllers/patient-session.js | 4 +- app/controllers/patient.js | 12 +- app/controllers/reply.js | 36 ++--- app/controllers/school.js | 2 +- app/data.js | 4 +- app/datasets/activity.js | 104 ++++++------- app/generators/clinic-appointment.js | 44 +++--- app/generators/consent.js | 18 +-- app/generators/{parent.js => contact.js} | 12 +- app/globals.js | 6 +- app/locales/en.js | 46 +++--- app/models.js | 2 +- app/models/clinic-appointment.js | 30 ++-- app/models/clinic-booking.js | 14 +- app/models/consent.js | 2 +- app/models/{parent.js => contact.js} | 65 ++++---- app/models/download.js | 2 +- app/models/patient-session.js | 36 ++--- app/models/patient.js | 83 ++++++----- app/models/pds-record.js | 38 ++--- app/models/reply.js | 66 ++++----- app/models/session.js | 2 +- app/models/upload.js | 6 +- app/routes.js | 6 +- app/routes/contact.js | 22 +++ app/routes/parent.js | 22 --- app/utils/reply.js | 4 +- app/utils/string.js | 34 ++--- app/views/_macros/contact-email.njk | 10 ++ app/views/_macros/contact-sms.njk | 10 ++ app/views/_macros/parent-email.njk | 10 -- app/views/_macros/parent-sms.njk | 10 -- .../form/contact-preference.njk | 16 +- .../book-into-a-clinic/form/extra-time.njk | 2 +- app/views/book-into-a-clinic/form/parent.njk | 24 +-- app/views/clinic-booking/show.njk | 8 +- app/views/consent/add.njk | 2 +- app/views/consent/link.njk | 8 +- app/views/consent/match.njk | 10 +- app/views/consent/show.njk | 8 +- app/views/{parent => contact}/action.njk | 16 +- app/views/contact/form/edit.njk | 70 +++++++++ app/views/give-or-refuse-consent/emails.njk | 4 +- .../form/check-answers.njk | 16 +- .../form/contact-preference.njk | 16 +- .../give-or-refuse-consent/form/contact.njk | 69 +++++++++ .../give-or-refuse-consent/form/parent.njk | 69 --------- app/views/parent/form/edit.njk | 70 --------- app/views/patient-session/_consent.njk | 28 ++-- app/views/patient/contacts.njk | 14 +- app/views/pds/form/result.njk | 2 +- app/views/reply/form/check-answers.njk | 18 +-- app/views/reply/form/contact.njk | 79 ++++++++++ app/views/reply/form/decision.njk | 2 +- app/views/reply/form/follow-up.njk | 16 +- .../{notify-parent.njk => notify-contact.njk} | 4 +- app/views/reply/form/parent.njk | 79 ---------- app/views/reply/form/programme.njk | 2 +- app/views/reply/form/refusal-notification.njk | 4 +- app/views/reply/show.njk | 14 +- app/views/session/reminders.njk | 2 +- lib/create-data.js | 48 +++--- 69 files changed, 907 insertions(+), 891 deletions(-) create mode 100644 app/controllers/contact.js delete mode 100644 app/controllers/parent.js rename app/generators/{parent.js => contact.js} (91%) rename app/models/{parent.js => contact.js} (74%) create mode 100644 app/routes/contact.js delete mode 100644 app/routes/parent.js create mode 100644 app/views/_macros/contact-email.njk create mode 100644 app/views/_macros/contact-sms.njk delete mode 100644 app/views/_macros/parent-email.njk delete mode 100644 app/views/_macros/parent-sms.njk rename app/views/{parent => contact}/action.njk (51%) create mode 100644 app/views/contact/form/edit.njk create mode 100644 app/views/give-or-refuse-consent/form/contact.njk delete mode 100644 app/views/give-or-refuse-consent/form/parent.njk delete mode 100644 app/views/parent/form/edit.njk create mode 100644 app/views/reply/form/contact.njk rename app/views/reply/form/{notify-parent.njk => notify-contact.njk} (76%) delete mode 100644 app/views/reply/form/parent.njk diff --git a/app/controllers/activity.js b/app/controllers/activity.js index c02a4c60e..7577dd0c1 100644 --- a/app/controllers/activity.js +++ b/app/controllers/activity.js @@ -5,7 +5,7 @@ import { ReplyDecision, ScreenOutcome } from '../enums.js' -import { generateParent } from '../generators/parent.js' +import { generateContact } from '../generators/contact.js' import { AuditEvent, Gillick, @@ -61,12 +61,12 @@ export const activityController = { (vaccination) => !vaccination.given ) - // Parent for use in Notify activities; force having both email and phone - const parent = generateParent(patient?.lastName) - parent.email = - parent.email || - `${parent.fullName.replace(' ', '.').toLowerCase()}@example.com` - parent.tel = parent.tel || '07700 900000' + // Contact for use in Notify activities; force having both email and phone + const contact = generateContact(patient?.lastName) + contact.email = + contact.email || + `${contact.fullName.replace(' ', '.').toLowerCase()}@example.com` + contact.tel = contact.tel || '07700 900000' const activityLog = [ { @@ -189,8 +189,8 @@ export const activityController = { 'vaccination-deleted' ].map((name) => auditEvent({ - name: activity.notify[name](parent), - messageRecipient: parent, + name: activity.notify[name](contact), + messageRecipient: contact, messageTemplate: name, patient_uuid: patient?.uuid, programme_ids: session?.programme_ids, diff --git a/app/controllers/appointment.js b/app/controllers/appointment.js index f5d9d41d0..7bc14b38f 100644 --- a/app/controllers/appointment.js +++ b/app/controllers/appointment.js @@ -145,7 +145,7 @@ export const appointmentController = { // // Add to session // patient.addToSession(patientSession) - // // Invite parent to give consent + // // Invite contact to give consent // patient.requestConsent(patientSession) // // Link consent with patient record diff --git a/app/controllers/book-into-a-clinic.js b/app/controllers/book-into-a-clinic.js index a4474792f..95945541a 100644 --- a/app/controllers/book-into-a-clinic.js +++ b/app/controllers/book-into-a-clinic.js @@ -95,7 +95,7 @@ export const bookIntoClinicController = { * - Child DOB * - ... * - Appointment time <-- final page of the per-child appointment journey; iterate to next child if required - * - Parent info + * - Contact info * - Check answers * - Health questions? * - Health question 1 <-- first page of the per-child health question journey @@ -147,10 +147,10 @@ export const bookIntoClinicController = { ), [`/${booking_uuid}/new/add-another`]: {}, - // Parent journey - [`/${booking_uuid}/new/parent`]: { + // Contact journey + [`/${booking_uuid}/new/contact`]: { [`/${booking_uuid}/new/offer-health-questions`]: () => - !request.session.data.booking?.parent?.tel + !request.session.data.booking?.contact?.tel }, [`/${booking_uuid}/new/contact-preference`]: {}, diff --git a/app/controllers/consent.js b/app/controllers/consent.js index f475404e7..6918f5693 100644 --- a/app/controllers/consent.js +++ b/app/controllers/consent.js @@ -182,7 +182,7 @@ export const consentController = { // Add to session patient.addToSession(patientSession) - // Invite parent to give consent + // Invite contact to give consent patient.requestConsent(patientSession) // Link consent with patient record diff --git a/app/controllers/contact.js b/app/controllers/contact.js new file mode 100644 index 000000000..4be06b48c --- /dev/null +++ b/app/controllers/contact.js @@ -0,0 +1,140 @@ +import { Contact, Patient } from '../models.js' + +export const contactController = { + /** + * @type {import("express").RequestParamHandler} + */ + read(request, response, next, contact_uuid) { + response.locals.contact = Contact.findOne( + contact_uuid, + request.session.data + ) + + next() + }, + + /** + * @type {import("express").RequestHandler} + */ + new(request, response) { + const { patient_uuid } = request.query + const { data } = request.session + + const contact = Contact.create( + { + patient_uuid + }, + data.wizard + ) + + return response.redirect(`${contact.uri}/new`) + }, + + /** + * @param {string} [type] - Form type + * @returns {import("express").RequestHandler} - Request handler + */ + update(type) { + return (request, response) => { + const { contact_uuid } = request.params + const { data } = request.session + const { __, back } = response.locals + + // Update session data + let contact + if (type === 'new') { + contact = Contact.create( + data.wizard.contacts[String(contact_uuid)], + data + ) + + // Add contact to patient contacts + const patient = Patient.findOne(contact.patient_uuid, data) + patient.addContact(contact) + } else { + contact = Contact.update( + contact_uuid, + data.wizard.contacts[String(contact_uuid)], + data + ) + } + + // Clean up session data + delete data.contact + delete data.wizard + + request.flash('success', __(`contact.${type}.success`, { contact })) + + return response.redirect(back) + } + }, + + /** + * @param {string} type - Form type + * @returns {import("express").RequestHandler} - Request handler + */ + readForm(type) { + return (request, response, next) => { + const { contact_uuid } = request.params + const { data } = request.session + + // Setup wizard if not already setup + let contact = Contact.findOne(contact_uuid, data.wizard) + if (!contact) { + contact = Contact.create(response.locals.contact, data.wizard) + } + + response.locals.contact = new Contact(contact, data) + response.locals.back = `/patients/${contact.patient_uuid}/contacts` + response.locals.type = type + + next() + } + }, + + /** + * @type {import("express").RequestHandler} + */ + showForm(request, response) { + return response.render(`contact/form/edit`) + }, + + updateForm(request, response, next) { + const { contact_uuid } = request.params + const { data } = request.session + + Contact.update(contact_uuid, request.body.contact, data.wizard) + + return next() + }, + + /** + * @param {string} type - Form type + * @returns {import("express").RequestHandler} - Request handler + */ + action(type) { + return (request, response) => { + const { contact } = response.locals + + response.render('contact/action', { + back: `/patients/${contact.patient_uuid}/contacts`, + type + }) + } + }, + + /** + * @type {import("express").RequestHandler} + */ + delete(request, response) { + const { contact_uuid } = request.params + const { data } = request.session + const { __, contact } = response.locals + + Contact.delete(contact_uuid, data) + + request.flash('success', __(`contact.delete.success`)) + + return response.redirect(`/patients/${contact.patient_uuid}/contacts`) + } +} diff --git a/app/controllers/give-or-refuse-consent.js b/app/controllers/give-or-refuse-consent.js index 503efcd73..8391541c9 100644 --- a/app/controllers/give-or-refuse-consent.js +++ b/app/controllers/give-or-refuse-consent.js @@ -58,7 +58,7 @@ export const giveOrRefuseConsentController = { response.locals.consent = new Consent( { child: patient, - parent_uuid: patient.parent_uuids[0], + contact_uuid: patient.contact_uuids[0], session_id: session.id }, data @@ -95,7 +95,7 @@ export const giveOrRefuseConsentController = { consent = new Consent(consent, data) return request.session.save((error) => { - if (!error) response.redirect(`${consent.parentUri}/new/child`) + if (!error) response.redirect(`${consent.publicUri}/new/child`) }) }, @@ -152,10 +152,10 @@ export const giveOrRefuseConsentController = { [`/${session_id}/${consent_uuid}/new/school`]: {} } : {}), - // Parent journey - [`/${session_id}/${consent_uuid}/new/parent`]: { + // Contact journey + [`/${session_id}/${consent_uuid}/new/contact`]: { [`/${session_id}/parental-responsibility`]: { - data: 'consent.parent_.hasParentalResponsibility', + data: 'consent.contact_.hasParentalResponsibility', value: 'false' } }, @@ -266,7 +266,7 @@ export const giveOrRefuseConsentController = { : ReplyDecision.OnlyMenACWY })) } else if (session.presetNames.includes(SessionPresetName.Flu)) { - // Flu: Ask which vaccine the parent would prefer + // Flu: Ask which vaccine the contact would prefer response.locals.decisionItems = [ { text: __('consent.decision.nasal.label'), diff --git a/app/controllers/parent.js b/app/controllers/parent.js deleted file mode 100644 index 92367e3d0..000000000 --- a/app/controllers/parent.js +++ /dev/null @@ -1,134 +0,0 @@ -import { Parent, Patient } from '../models.js' - -export const parentController = { - /** - * @type {import("express").RequestParamHandler} - */ - read(request, response, next, parent_uuid) { - response.locals.parent = Parent.findOne(parent_uuid, request.session.data) - - next() - }, - - /** - * @type {import("express").RequestHandler} - */ - new(request, response) { - const { patient_uuid } = request.query - const { data } = request.session - - const parent = Parent.create( - { - patient_uuid - }, - data.wizard - ) - - return response.redirect(`${parent.uri}/new`) - }, - - /** - * @param {string} [type] - Form type - * @returns {import("express").RequestHandler} - Request handler - */ - update(type) { - return (request, response) => { - const { parent_uuid } = request.params - const { data } = request.session - const { __, back } = response.locals - - // Update session data - let parent - if (type === 'new') { - parent = Parent.create(data.wizard.parents[String(parent_uuid)], data) - - // Add parent to patient contacts - const patient = Patient.findOne(parent.patient_uuid, data) - patient.addContact(parent) - } else { - parent = Parent.update( - parent_uuid, - data.wizard.parents[String(parent_uuid)], - data - ) - } - - // Clean up session data - delete data.parent - delete data.wizard - - request.flash('success', __(`parent.${type}.success`, { parent })) - - return response.redirect(back) - } - }, - - /** - * @param {string} type - Form type - * @returns {import("express").RequestHandler} - Request handler - */ - readForm(type) { - return (request, response, next) => { - const { parent_uuid } = request.params - const { data } = request.session - - // Setup wizard if not already setup - let parent = Parent.findOne(parent_uuid, data.wizard) - if (!parent) { - parent = Parent.create(response.locals.parent, data.wizard) - } - - response.locals.parent = new Parent(parent, data) - response.locals.back = `/patients/${parent.patient_uuid}/contacts` - response.locals.type = type - - next() - } - }, - - /** - * @type {import("express").RequestHandler} - */ - showForm(request, response) { - return response.render(`parent/form/edit`) - }, - - updateForm(request, response, next) { - const { parent_uuid } = request.params - const { data } = request.session - - Parent.update(parent_uuid, request.body.parent, data.wizard) - - return next() - }, - - /** - * @param {string} type - Form type - * @returns {import("express").RequestHandler} - Request handler - */ - action(type) { - return (request, response) => { - const { parent } = response.locals - - response.render('parent/action', { - back: `/patients/${parent.patient_uuid}/contacts`, - type - }) - } - }, - - /** - * @type {import("express").RequestHandler} - */ - delete(request, response) { - const { parent_uuid } = request.params - const { data } = request.session - const { __, parent } = response.locals - - Parent.delete(parent_uuid, data) - - request.flash('success', __(`parent.delete.success`)) - - return response.redirect(`/patients/${parent.patient_uuid}/contacts`) - } -} diff --git a/app/controllers/patient-session.js b/app/controllers/patient-session.js index c30f75f75..62d085463 100644 --- a/app/controllers/patient-session.js +++ b/app/controllers/patient-session.js @@ -316,7 +316,7 @@ export const patientSessionController = { request.flash( 'success', - __('patientSession.invite.success', { parent: patient.parents[0] }) + __('patientSession.invite.success', { contact: patient.contacts[0] }) ) return response.redirect(back) @@ -333,7 +333,7 @@ export const patientSessionController = { { createdBy_uid: account.uid }, - patient.parents[0] + patient.contacts[0] ) return response.redirect(back) diff --git a/app/controllers/patient.js b/app/controllers/patient.js index e255be51e..0b59f65bf 100644 --- a/app/controllers/patient.js +++ b/app/controllers/patient.js @@ -55,7 +55,7 @@ export const patientController = { current: currentPath === patient.uri }, { - text: __('patient.parents.label'), + text: __('patient.contacts.label'), href: `${patient.uri}/contacts`, current: currentPath === `${patient.uri}/contacts` }, @@ -413,10 +413,10 @@ export const patientController = { showForm(request, response) { let { view } = request.params - // Parent forms share same view - if (view.includes('parent')) { + // Contact forms share same view + if (view.includes('contact')) { response.locals.parentId = String(view).split('-')[1] - view = 'parent' + view = 'contact' } return response.render(`patient/form/${view}`) @@ -478,7 +478,7 @@ export const patientController = { clinicProgramme_ids = stringToArray(clinicProgramme_ids) } - // Send comms to parents and record in audit trail + // Send comms to contacts and record in audit trail const patient = Patient.findOne(patient_uuid, data) patient.inviteToClinic(clinicProgramme_ids) Patient.update(patient.uuid, patient, data) @@ -612,7 +612,7 @@ export const patientController = { ] if (invitedProgramme_ids.length) { - // Send comms to parents and record in audit trail + // Send comms to contacts and record in audit trail patient.inviteToClinic(invitedProgramme_ids) Patient.update(patient.uuid, patient, data) diff --git a/app/controllers/reply.js b/app/controllers/reply.js index b330bf81a..2bf0af592 100644 --- a/app/controllers/reply.js +++ b/app/controllers/reply.js @@ -8,7 +8,7 @@ import { VaccinationOutcome } from '../enums.js' import { - Parent, + Contact, PatientSession, Programme, Reply, @@ -16,7 +16,7 @@ import { } from '../models.js' import { today } from '../utils/date.js' import { countAnswersNeedingTriage } from '../utils/reply.js' -import { formatParent } from '../utils/string.js' +import { formatContact } from '../utils/string.js' import { getScreenOutcomesForConsentMethod, getScreenVaccineCriteria @@ -115,9 +115,9 @@ export const replyController = { reply = new Reply(Reply.findOne(reply_uuid, data.wizard), data) next = patientSession.uri - // Remove any parent details in reply if self consent + // Remove any contact details in reply if self consent if (reply.selfConsent) { - delete reply.parent_uuid + delete reply.contact_uuid } if (triage?.outcome) { @@ -231,13 +231,13 @@ export const replyController = { [`/${reply_uuid}/${type}/respondent`]: {}, ...(data.respondent !== 'self' && !reply.selfConsent && { - [`/${reply_uuid}/${type}/parent`]: {} + [`/${reply_uuid}/${type}/contact`]: {} }), ...(isMultiProgrammeSession && { [`/${reply_uuid}/${type}/programme`]: {} }), [`/${reply_uuid}/${type}/decision`]: { - [`/${reply_uuid}/${type}/${reply?.selfConsent && !patientSession.patient.post16 ? 'notify-parent' : 'health-answers'}`]: + [`/${reply_uuid}/${type}/${reply?.selfConsent && !patientSession.patient.post16 ? 'notify-contact' : 'health-answers'}`]: { data: 'reply.decision', value: ReplyDecision.Given @@ -251,7 +251,7 @@ export const replyController = { value: ReplyDecision.NoResponse } }, - [`/${reply_uuid}/${type}/notify-parent`]: {}, + [`/${reply_uuid}/${type}/notify-contact`]: {}, [`/${reply_uuid}/${type}/health-answers`]: { [`/${reply_uuid}/${type}/${countAnswersNeedingTriage(request.session.data.reply?.healthAnswers) ? 'triage' : 'check-answers'}`]: true }, @@ -291,11 +291,11 @@ export const replyController = { ...(referrer && { back: referrer }) } - response.locals.respondentItems = patientSession.patient.parents.map( - (parent) => ({ - text: formatParent(parent, false), - hint: { text: parent.tel }, - value: parent.uuid + response.locals.respondentItems = patientSession.patient.contacts.map( + (contact) => ({ + text: formatContact(contact, false), + hint: { text: contact.tel }, + value: contact.uuid }) ) @@ -346,17 +346,17 @@ export const replyController = { const { data } = request.session let { paths, patientSession, triage, vaccination } = response.locals - // Add parents from global context to wizard - data.wizard.parents = data.parents + // Add contacts from global context to wizard + data.wizard.contacts = data.contacts const reply = Reply.update(reply_uuid, request.body.reply, data.wizard) - // Create parent based on choice of respondent + // Create contact based on choice of respondent if (respondent) { switch (respondent) { case 'new': // Consent response is from a new contact reply.method = ReplyMethod.Phone - reply.parent_uuid = Parent.create( + reply.contact_uuid = Contact.create( { patient_uuid: reply.patient_uuid }, @@ -370,7 +370,7 @@ export const replyController = { break default: // Consent response is an existing respondent reply.method = ReplyMethod.Phone - reply.parent_uuid = respondent + reply.contact_uuid = respondent reply.selfConsent = false // Store reply that needs marked as invalid @@ -417,7 +417,7 @@ export const replyController = { const newReply = Reply.create( { child: patientSession.patient, - parent: reply.parent, + contact: reply.contact, patient_uuid: patientSession.patient_uuid, session_id: patientSession.session_id, programme_id: patientSession.programme_id, diff --git a/app/controllers/school.js b/app/controllers/school.js index 383139105..42a927c1f 100644 --- a/app/controllers/school.js +++ b/app/controllers/school.js @@ -535,7 +535,7 @@ export const schoolController = { // Find patients to invite to clinic const patient_uuids = school.patients.map((patient) => patient.uuid) - // Invite parents to book into a clinic + // Invite contacts to book into a clinic const clinicProgramme_ids = request.body.clinicProgramme_ids.filter( (item) => item !== '_unchecked' ) diff --git a/app/data.js b/app/data.js index b5670c352..4f6cecae0 100644 --- a/app/data.js +++ b/app/data.js @@ -1,10 +1,10 @@ import batches from '../.data/batches.json' with { type: 'json' } import clinicBookings from '../.data/clinic-bookings.json' with { type: 'json' } import clinics from '../.data/clinics.json' with { type: 'json' } +import contacts from '../.data/contacts.json' with { type: 'json' } import instructions from '../.data/instructions.json' with { type: 'json' } import moves from '../.data/moves.json' with { type: 'json' } import notices from '../.data/notices.json' with { type: 'json' } -import parents from '../.data/parents.json' with { type: 'json' } import patientSessions from '../.data/patient-sessions.json' with { type: 'json' } import patients from '../.data/patients.json' with { type: 'json' } import pdsRecords from '../.data/pds-records.json' with { type: 'json' } @@ -35,12 +35,12 @@ const data = { batches, clinicBookings, clinics, + contacts, defaultBatches: {}, downloads: {}, instructions, moves, notices, - parents, patients, patientSessions, pdsRecords, diff --git a/app/datasets/activity.js b/app/datasets/activity.js index c1150acab..6276848ef 100644 --- a/app/datasets/activity.js +++ b/app/datasets/activity.js @@ -7,20 +7,20 @@ export default { absent: (session) => `Absent from session at ${session.location.name}` }, consent: { - created: ({ child, decision, parent, selfConsent }) => + created: ({ child, decision, contact, selfConsent }) => selfConsent ? `${decision} by ${child?.fullName} (child)` - : `${decision} by ${parent?.fullNameAndRelationship}`, - updated: ({ decision, parent }) => - `${decision} in updated response from ${parent.fullNameAndRelationship}`, - followedUp: ({ confirmed, decision, parent }) => - `${confirmed ? 'Refusal confirmed' : decision} in followed-up response from ${parent.fullNameAndRelationship}`, - matched: ({ parent }) => - `Consent response from ${parent.fullNameAndRelationship} manually matched with child record`, - invalid: ({ parent }) => - `Consent response from ${parent.fullNameAndRelationship} marked as invalid`, - withdrawn: ({ parent }) => - `Consent response from ${parent.fullNameAndRelationship} withdrawn` + : `${decision} by ${contact?.fullNameAndRelationship}`, + updated: ({ decision, contact }) => + `${decision} in updated response from ${contact.fullNameAndRelationship}`, + followedUp: ({ confirmed, decision, contact }) => + `${confirmed ? 'Refusal confirmed' : decision} in followed-up response from ${contact.fullNameAndRelationship}`, + matched: ({ contact }) => + `Consent response from ${contact.fullNameAndRelationship} manually matched with child record`, + invalid: ({ contact }) => + `Consent response from ${contact.fullNameAndRelationship} marked as invalid`, + withdrawn: ({ contact }) => + `Consent response from ${contact.fullNameAndRelationship} withdrawn` }, gillick: { created: (gillick) => gillick.competent, @@ -30,46 +30,46 @@ export default { created: (type) => `${type} added` }, notify: { - invite: (parent) => - `Consent request sent to ${parent.fullNameAndRelationship}`, - 'invite-reminder': (parent) => - `Consent reminder sent to ${parent.fullNameAndRelationship}`, - 'invite-clinic': (parent) => - `Clinic invitation sent to ${parent.fullNameAndRelationship}`, - 'invite-clinic-reminder': (parent) => - `Clinic invitation reminder sent to ${parent.fullNameAndRelationship}`, - 'consent-given': (parent) => - `Confirmation of consent given sent to ${parent.fullNameAndRelationship}`, - 'consent-given-changed-school': (parent) => - `Confirmation of consent given (clinic booking needed) sent to ${parent.fullNameAndRelationship}`, - 'consent-needs-triage': (parent) => - `Confirmation of consent given (triage needed) sent to ${parent.fullNameAndRelationship}`, - 'consent-refused': (parent) => - `Confirmation of consent refused sent to ${parent.fullNameAndRelationship}`, - 'consent-followed-up': (parent) => - `Confirmation of follow-up decision to confirm refusal sent to ${parent.fullNameAndRelationship}`, - 'consent-unknown-contact': (parent) => - `Unknown parent contact details warning sent to ${parent.fullNameAndRelationship}`, - 'triage-delay-vaccination': (parent) => - `Confirmation of triage decision (delay vaccination) sent to ${parent.fullNameAndRelationship}`, - 'triage-do-not-vaccinate': (parent) => - `Confirmation of triage decision (unable to vaccinate) sent to ${parent.fullNameAndRelationship}`, - 'triage-invite-to-clinic': (parent) => - `Confirmation of triage decision (invite to clinic) sent to ${parent.fullNameAndRelationship}`, - 'triage-vaccinate': (parent) => - `Confirmation of triage decision (safe to vaccinate) sent to ${parent.fullNameAndRelationship}`, - 'triage-vaccinate-second-dose': (parent) => - `Confirmation of triage decision (2nd dose will be given in school) sent to ${parent.fullNameAndRelationship}`, - 'vaccination-reminder': (parent) => - `Session reminder sent to ${parent.fullNameAndRelationship}`, - 'vaccination-given': (parent) => - `Confirmation the vaccination was given sent to ${parent.fullNameAndRelationship}`, - 'vaccination-not-administered': (parent) => - `Confirmation the vaccination was not given sent to ${parent.fullNameAndRelationship}`, - 'vaccination-already-had': (parent) => - `Confirmation previous vaccination discovered since consent sent to ${parent.fullNameAndRelationship}`, - 'vaccination-deleted': (parent) => - `Apology for incorrect message sent to ${parent.fullNameAndRelationship}` + invite: (contact) => + `Consent request sent to ${contact.fullNameAndRelationship}`, + 'invite-reminder': (contact) => + `Consent reminder sent to ${contact.fullNameAndRelationship}`, + 'invite-clinic': (contact) => + `Clinic invitation sent to ${contact.fullNameAndRelationship}`, + 'invite-clinic-reminder': (contact) => + `Clinic invitation reminder sent to ${contact.fullNameAndRelationship}`, + 'consent-given': (contact) => + `Confirmation of consent given sent to ${contact.fullNameAndRelationship}`, + 'consent-given-changed-school': (contact) => + `Confirmation of consent given (clinic booking needed) sent to ${contact.fullNameAndRelationship}`, + 'consent-needs-triage': (contact) => + `Confirmation of consent given (triage needed) sent to ${contact.fullNameAndRelationship}`, + 'consent-refused': (contact) => + `Confirmation of consent refused sent to ${contact.fullNameAndRelationship}`, + 'consent-followed-up': (contact) => + `Confirmation of follow-up decision to confirm refusal sent to ${contact.fullNameAndRelationship}`, + 'consent-unknown-contact': (contact) => + `Unknown parent contact details warning sent to ${contact.fullNameAndRelationship}`, + 'triage-delay-vaccination': (contact) => + `Confirmation of triage decision (delay vaccination) sent to ${contact.fullNameAndRelationship}`, + 'triage-do-not-vaccinate': (contact) => + `Confirmation of triage decision (unable to vaccinate) sent to ${contact.fullNameAndRelationship}`, + 'triage-invite-to-clinic': (contact) => + `Confirmation of triage decision (invite to clinic) sent to ${contact.fullNameAndRelationship}`, + 'triage-vaccinate': (contact) => + `Confirmation of triage decision (safe to vaccinate) sent to ${contact.fullNameAndRelationship}`, + 'triage-vaccinate-second-dose': (contact) => + `Confirmation of triage decision (2nd dose will be given in school) sent to ${contact.fullNameAndRelationship}`, + 'vaccination-reminder': (contact) => + `Session reminder sent to ${contact.fullNameAndRelationship}`, + 'vaccination-given': (contact) => + `Confirmation the vaccination was given sent to ${contact.fullNameAndRelationship}`, + 'vaccination-not-administered': (contact) => + `Confirmation the vaccination was not given sent to ${contact.fullNameAndRelationship}`, + 'vaccination-already-had': (contact) => + `Confirmation previous vaccination discovered since consent sent to ${contact.fullNameAndRelationship}`, + 'vaccination-deleted': (contact) => + `Apology for incorrect message sent to ${contact.fullNameAndRelationship}` }, patient: { archived: (archive) => diff --git a/app/generators/clinic-appointment.js b/app/generators/clinic-appointment.js index 55d9a9b5e..283bfc54f 100644 --- a/app/generators/clinic-appointment.js +++ b/app/generators/clinic-appointment.js @@ -4,7 +4,7 @@ import { addMinutes, addYears } from 'date-fns' import { ParentalRelationship } from '../enums.js' import { ClinicAppointment } from '../models.js' -import { generateParent } from './parent.js' +import { generateContact } from './contact.js' const clinicSlotLength = Number(process.env.CLINIC_SLOT_LENGTH) || 10 @@ -52,35 +52,35 @@ export function generateClinicAppointment(patient, session, booking) { } } - // Set up the relationship to the child for this appointment. If the booking doesn't already have - // a parent set up, we'll create the booking and appointment's parent based on the first appointment's - // child details + // Set up the relationship to the child for this appointment. If the booking + // doesn’t already have a contact set up, we’ll create the booking and + // appointment’s contact based on the first appointment’s child details let parentalRelationship, parentalRelationshipOther, parentHasParentalResponsibility - if (!booking.parent.fullName) { - // First appointment, so set up the booking's parent - booking.parent = - patient.parents[0] || - patient.parents[1] || - generateParent(child.lastName, faker.datatype.boolean(0.5)) + if (!booking.contact.fullName) { + // First appointment, so set up the booking’s contact + booking.contact = + patient.contacts[0] || + patient.contacts[1] || + generateContact(child.lastName, faker.datatype.boolean(0.5)) // ...and their relationship to this child - parentalRelationship = booking.parent.relationship - parentalRelationshipOther = booking.parent.relationshipOther - parentHasParentalResponsibility = booking.parent.hasParentalResponsibility + parentalRelationship = booking.contact.relationship + parentalRelationshipOther = booking.contact.relationshipOther + parentHasParentalResponsibility = booking.contact.hasParentalResponsibility } else { - // This isn't the first appointment, so set up parent details similar to the first one - const parent = booking.parent + // This isn’t the first appointment, so set up contact details similar to the first one + const contact = booking.contact const mumOrDad = [ ParentalRelationship.Mum, ParentalRelationship.Dad - ].includes(parent.relationship) + ].includes(contact.relationship) if (mumOrDad) { // Mum or Dad initially, and most likely to stay that way if (faker.datatype.boolean(0.9)) { - parentalRelationship = parent.relationship - parentalRelationshipOther = parent.relationshipOther - parentHasParentalResponsibility = parent.hasParentalResponsibility + parentalRelationship = contact.relationship + parentalRelationshipOther = contact.relationshipOther + parentHasParentalResponsibility = contact.hasParentalResponsibility } else { parentalRelationship = faker.helpers.arrayElement([ ParentalRelationship.Fosterer, @@ -95,9 +95,9 @@ export function generateClinicAppointment(patient, session, booking) { } } else { // Fosterer, Guardian or Other - for these, we'll keep the relationship exactly the same - parentalRelationship = parent.relationship - parentalRelationshipOther = parent.relationshipOther - parentHasParentalResponsibility = parent.hasParentalResponsibility + parentalRelationship = contact.relationship + parentalRelationshipOther = contact.relationshipOther + parentHasParentalResponsibility = contact.hasParentalResponsibility } } diff --git a/app/generators/consent.js b/app/generators/consent.js index dea2f969a..15e18a592 100644 --- a/app/generators/consent.js +++ b/app/generators/consent.js @@ -22,7 +22,7 @@ import { * @param {import('../models.js').Programme} programme - Programme * @param {import('../models.js').Session} session - Session * @param {import('../models.js').PatientSession} patientSession - Patient session - * @param {import('../models.js').Parent} parent - Parent + * @param {import('../models.js').Contact} contact - Contact * @param {Date} [lastConsentCreatedAt] - Date previous consent response created * @returns {Consent|undefined} Consent */ @@ -30,19 +30,19 @@ export function generateConsent( programme, session, patientSession, - parent, + contact, lastConsentCreatedAt ) { // Child const child = patientSession.patient - // Can’t create a consent response if no parent associated with child - if (!parent) { + // Can’t create a consent response if no contact associated with child + if (!contact) { return } - // Can’t create a consent response if no contact details for parent - if (!parent.email && !parent.tel) { + // Can’t create a consent response if no contact details + if (!contact.email && !contact.tel) { return } @@ -58,7 +58,7 @@ export function generateConsent( const isFluProgramme = programme.type === ProgrammeType.Flu - // Has the parent given consent for alternative injected vaccine? + // Has the contact given consent for alternative injected vaccine? const alternative = isFluProgramme && decision === ReplyDecision.Given ? faker.datatype.boolean(0.75) @@ -112,7 +112,7 @@ export function generateConsent( to: sessionClosedBeforeToday ? session.closeAt : nowAt }), child, - parent, + contact, decision, method, ...(decision === ReplyDecision.Given && { alternative }), @@ -144,7 +144,7 @@ export function generateConsent( }), consultation }), - parent_uuid: parent.uuid, + contact_uuid: contact.uuid, programme_id: programme.id, session_id: session.id }) diff --git a/app/generators/parent.js b/app/generators/contact.js similarity index 91% rename from app/generators/parent.js rename to app/generators/contact.js index 90fe27dcb..a0ceb70ca 100644 --- a/app/generators/parent.js +++ b/app/generators/contact.js @@ -5,16 +5,16 @@ import { ParentalRelationship, NotifySmsStatus } from '../enums.js' -import { Parent } from '../models.js' +import { Contact } from '../models.js' /** - * Generate fake parent + * Generate fake contact * * @param {import('../models.js').Child|import('../models.js').Patient} patient - Child - * @param {boolean} [isMum] - Parent is child’s mother - * @returns {Parent} Parent + * @param {boolean} [isMum] - Contact is child’s mother + * @returns {Contact} Contact */ -export function generateParent(patient, isMum) { +export function generateContact(patient, isMum) { // Relationship const relationship = isMum ? ParentalRelationship.Mum @@ -67,7 +67,7 @@ export function generateParent(patient, isMum) { { value: NotifyEmailStatus.Technical, weight: 1 } ]) - return new Parent({ + return new Contact({ fullName: `${firstName} ${lastName}`, relationship, ...(relationship === ParentalRelationship.Other && { diff --git a/app/globals.js b/app/globals.js index 04166105f..3a97a669d 100644 --- a/app/globals.js +++ b/app/globals.js @@ -333,10 +333,10 @@ export default () => { * * @param {object} healthAnswers - Health answers * @param {string} edit - Edit link - * @param {string} [parentFacing] - Use parent-facing questions (‘your child’) + * @param {string} [publicFacing] - Use public-facing questions (‘your child’) * @returns {Array|undefined} Parameters for summary list component */ - globals.healthAnswerRows = function (healthAnswers, edit, parentFacing) { + globals.healthAnswerRows = function (healthAnswers, edit, publicFacing) { if (healthAnswers.length === 0) { return } @@ -358,7 +358,7 @@ export default () => { let keyText = healthQuestions[key].labelWithOptions || healthQuestions[key].label - keyText = parentFacing + keyText = publicFacing ? keyText.replace('the child', 'your child') : keyText diff --git a/app/locales/en.js b/app/locales/en.js index 6074d251f..c48f7afba 100644 --- a/app/locales/en.js +++ b/app/locales/en.js @@ -231,7 +231,7 @@ export const en = { gpSurgery: { label: 'GP surgery' }, - parent: { + contact: { label: 'Parent' } }, @@ -526,7 +526,7 @@ export const en = { confirm: 'Yes, remove this appointment', cancel: 'No, return to the previous page' }, - parent: { + contact: { title: 'About you', fullName: { label: 'Full name' @@ -601,7 +601,7 @@ export const en = { label: 'Cancel appointment' } }, - parent: { + contact: { title: 'Your details', change: { label: 'Change my details' @@ -629,23 +629,23 @@ export const en = { match: { label: 'Match', title: 'Search for a child record to match with {{child.fullName}}', - caption: 'Consent response from {{parent.formatted.fullName}}' + caption: 'Consent response from {{contact.formatted.fullName}}' }, link: { title: 'Link consent response with child record?', - caption: 'Consent response from {{parent.fullName}}', + caption: 'Consent response from {{contact.fullName}}', summary: 'Compare child details', confirm: 'Link response with record', success: - 'Consent response from {{consent.parent.fullName}} linked to [{{patient.fullName}}]({{patient.uri}})’s record' + 'Consent response from {{consent.contact.fullName}} linked to [{{patient.fullName}}]({{patient.uri}})’s record' }, add: { label: 'Create new record', title: 'Create a new child record from this consent response?', - caption: 'Consent response from {{parent.fullName}}', + caption: 'Consent response from {{contact.fullName}}', confirm: 'Create a new record from response', success: - '[{{patient.fullName}}]({{patient.uri}})’s record created from a consent response from {{consent.parent.fullName}}' + '[{{patient.fullName}}]({{patient.uri}})’s record created from a consent response from {{consent.contact.fullName}}' }, invalidate: { label: 'Archive', @@ -849,7 +849,7 @@ export const en = { label: 'No, skip the ethnicity questions' } }, - parent: { + contact: { summary: 'About you', title: 'About you', label: 'Parent', @@ -1106,7 +1106,7 @@ export const en = { 'You’ve told us that you do not want {{consent.child.fullName}} to get the {{session.vaccinationNames.sentenceCase}} at school' }, triage: { - // TODO: Parent may have given consent for two vaccinations for doubles + // TODO: Contact may have given consent for two vaccinations for doubles // so text should say either ‘vaccination is’ or ‘vaccinations are’ [ReplyDecision.Given]: 'As you answered ‘yes’ to one or more of the health questions, we need to check the {{session.vaccinationNames.sentenceCase}} is suitable for {{consent.child.fullName}}. We’ll review your answers and get in touch again soon.', @@ -1117,7 +1117,7 @@ export const en = { [ReplyDecision.OnlyTdIPV]: 'As you answered ‘yes’ to one or more of the health questions, we need to check the Td/IPV vaccination is suitable for {{consent.child.fullName}}. We’ll review your answers and get in touch again soon.' }, - description: 'We’ve sent a confirmation to <{{consent.parent.email}}>.' + description: 'We’ve sent a confirmation to <{{consent.contact.email}}>.' }, actions: { label: 'Actions' @@ -1389,7 +1389,7 @@ export const en = { cancel: 'No, return to notices' } }, - parent: { + contact: { label: 'Parent or guardian', new: { label: 'Add a new contact', @@ -1406,7 +1406,7 @@ export const en = { success: 'Contact deleted' }, action: { - title: 'Are you sure you want to {{type}} {{parent.fullName}}?', + title: 'Are you sure you want to {{type}} {{contact.fullName}}?', description: 'Deleting this contact will remove them from {{patient.firstName}}, but not from any other children.\n\nThis cannot be undone.', confirm: 'Yes, %s this contact', @@ -1663,13 +1663,13 @@ export const en = { yearGroupWithRegistration: { label: 'Year group' }, - parents: { + contacts: { label: 'Contacts' }, vaccinations: { label: 'Vaccinations' }, - parent: { + contact: { label: 'Parent or guardian', fullName: { label: 'Name' @@ -1835,7 +1835,7 @@ export const en = { }, invite: { label: 'Send consent request', - success: 'Consent request sent to {{parent.fullNameAndRelationship}}' + success: 'Consent request sent to {{contact.fullNameAndRelationship}}' }, replies: { pending: 'Consent requests', @@ -2003,7 +2003,7 @@ export const en = { label: 'GP surgery', title: 'Who is the child’s GP?' }, - parents: { + contacts: { label: 'Parents or guardians' }, add: { @@ -2131,15 +2131,15 @@ export const en = { child: { label: 'Child' }, - parent: { + contact: { label: 'Parent', title: { new: 'Details for parent or guardian', - edit: 'Details for {{parent.fullNameAndRelationship}}' + edit: 'Details for {{contact.fullNameAndRelationship}}' }, notify: { title: - 'Do you want to send {{parent.formatted.fullName}} an email and text message confirming their decision?', + 'Do you want to send {{contact.formatted.fullName}} an email and text message confirming their decision?', label: 'Notify parent' } }, @@ -2147,7 +2147,7 @@ export const en = { label: 'Programme', title: { Child: 'Which vaccination is the child giving consent for?', - Parent: 'Which vaccination are they giving consent for?' + Contact: 'Which vaccination are they giving consent for?' } }, method: { @@ -2159,7 +2159,7 @@ export const en = { title: { Child: 'Does the child agree to having the {{programme.vaccineName.sentenceCase}}?', - Parent: + Contact: 'Do they agree to {{patient.firstName}} having the {{programme.vaccineName.sentenceCase}}?' }, yes: { @@ -2521,7 +2521,7 @@ export const en = { description: 'Mavis automatically sends email and text reminders to parents who have not responded to the initial consent request.\n\nAutomatic reminders are sent 14, 7 and 3 days before a session.\n\nYou can also send reminders manually. Mavis will then skip the next automatic reminder if it’s due to be sent within 3 days.', activity: - '{{parents}} parents out of {{patients}} have not responded yet', + '{{contacts}} parents out of {{patients}} have not responded yet', preConfirm: 'Mavis will skip the next automatic reminder if it’s scheduled to be sent within 3 days.', confirm: 'Send manual consent reminders', diff --git a/app/models.js b/app/models.js index 5e8cd0096..60245a73a 100644 --- a/app/models.js +++ b/app/models.js @@ -17,7 +17,7 @@ export * from './models/gillick.js' export * from './models/instruction.js' export * from './models/move.js' export * from './models/notice.js' -export * from './models/parent.js' +export * from './models/contact.js' export * from './models/patient-programme.js' export * from './models/patient-session.js' export * from './models/patient.js' diff --git a/app/models/clinic-appointment.js b/app/models/clinic-appointment.js index 6a1b106f8..5ebab52f1 100644 --- a/app/models/clinic-appointment.js +++ b/app/models/clinic-appointment.js @@ -4,7 +4,7 @@ import { ReplyDecision } from '../enums.js' import { Child, ClinicBooking, - Parent, + Contact, Patient, Programme, Session @@ -30,7 +30,7 @@ import { * @property {string} [extraTimeReason] - The reason why the child needs extra time for their appointment * @property {import('../enums.js').ParentalRelationship} [parentalRelationship] - The relationship of the person booking the appointment to the child * @property {string} [parentalRelationshipOther] - User-defined parental relationship to the child for this appointment - * @property {boolean} [parentHasParentalResponsibility] - Does the parent/carer have legal parental responsibility for the child? + * @property {boolean} [parentHasParentalResponsibility] - Does the contact have legal parental responsibility for the child? * @property {string} [session_id] - The ID of the clinic session in which the appointment's booked * @property {Date} [startAt] - Slot start time * @property {Date} [endAt] - Slot end time @@ -159,7 +159,7 @@ export class ClinicAppointment { /** * Get the programmes for which this child is eligible * - * @returns {Array} The programmes from which the parent is able to choose + * @returns {Array} Programmes from which contact can choose */ get eligibleProgrammes() { const programme_ids = this.patient?.clinicReadyProgramme_ids ?? [] @@ -257,7 +257,7 @@ export class ClinicAppointment { .filter(Boolean) .join('
'), homeAddress: this.child.formatted.address, - parentalRelationship: this.parent?.formatted.relationship, + parentalRelationship: this.contact?.formatted.relationship, ...(fluVaccineType ? { fluVaccineType } : {}), ...(this.mmrAlternative !== undefined ? { @@ -285,20 +285,20 @@ export class ClinicAppointment { } /** - * Get the parent for this appointment's child + * Get the contact for this appointment’s child * - * @returns {Parent} parent with the correct relationship to this appointment's child + * @returns {Contact} Contact with the correct relationship to this appointment’s child */ - get parent() { - // Take most details from the parent in the booking - const parent = new Parent(this.booking?.parent ?? {}) - if (parent) { - parent.relationship = this.parentalRelationship - parent.relationshipOther = this.parentalRelationshipOther - parent.hasParentalResponsibility = this.parentHasParentalResponsibility + get contact() { + // Take most details from the contact in the booking + const contact = new Contact(this.booking?.contact ?? {}) + if (contact) { + contact.relationship = this.parentalRelationship + contact.relationshipOther = this.parentalRelationshipOther + contact.hasParentalResponsibility = this.parentHasParentalResponsibility } - return parent + return contact } /** @@ -310,7 +310,7 @@ export class ClinicAppointment { return { unmatched: formatLinkWithSecondaryText( this.uri.unmatched, - this.parent.fullNameAndRelationship, + this.contact.fullNameAndRelationship, `for ${this.child.fullName}` ) } diff --git a/app/models/clinic-booking.js b/app/models/clinic-booking.js index 003497af7..3f4c5b92b 100644 --- a/app/models/clinic-booking.js +++ b/app/models/clinic-booking.js @@ -1,7 +1,7 @@ import { fakerEN_GB as faker } from '@faker-js/faker' import _ from 'lodash' -import { ClinicAppointment, Parent } from '../models.js' +import { ClinicAppointment, Contact } from '../models.js' import { formatMonospace, stringToArray, @@ -15,7 +15,7 @@ import { * @property {object} [context] - Context * @property {string} uuid - Clinic booking UUID * @property {string} bookingReference - Booking reference number - * @property {Parent} parent - contact details for the parent making the booking; see appointments for parental relationship details + * @property {Contact} contact - Contact details for the booking; see appointments for parental relationship details * @property {Array} appointments - the appointments created in this booking (one per child) */ export class ClinicBooking { @@ -24,8 +24,8 @@ export class ClinicBooking { this.uuid = options?.uuid || faker.string.uuid() this.bookingReference = options?.bookingReference || ClinicBooking.generateReference() - this.parent = - (options?.parent && new Parent(options.parent)) ?? new Parent({}) + this.contact = + (options?.contact && new Contact(options.contact)) ?? new Contact({}) this.appointments = options?.appointments?.map( @@ -218,12 +218,12 @@ export class ClinicBooking { /** * Get rid of _unchecked values from checkboxes in the booking journey * - * @param {object} updates - new values posted from the booking jounrey + * @param {object} updates - new values posted from the booking journey */ static #sanitiseCheckboxUpdates(updates) { // Receive updates by SMS option - if (updates?.parent?.sms) { - updates.parent.sms = stringToBoolean(updates.parent.sms) || false + if (updates?.contact?.sms) { + updates.contact.sms = stringToBoolean(updates.contact.sms) || false } if (updates?.appointments) { diff --git a/app/models/consent.js b/app/models/consent.js index 4bb43480c..559df2449 100644 --- a/app/models/consent.js +++ b/app/models/consent.js @@ -25,7 +25,7 @@ export class Consent extends Reply { return { summary: formatLinkWithSecondaryText( this.uri, - this.parent.fullNameAndRelationship, + this.contact.fullNameAndRelationship, `for ${this.child.fullName}` ) } diff --git a/app/models/parent.js b/app/models/contact.js similarity index 74% rename from app/models/parent.js rename to app/models/contact.js index f2bbd64ff..ef6e033b6 100644 --- a/app/models/parent.js +++ b/app/models/contact.js @@ -2,10 +2,10 @@ import { fakerEN_GB as faker } from '@faker-js/faker' import { ParentalRelationship } from '../enums.js' import { Patient } from '../models.js' -import { formatOther, formatParent, stringToBoolean } from '../utils/string.js' +import { formatOther, formatContact, stringToBoolean } from '../utils/string.js' /** - * @class Parent + * @class Contact * @param {object} options - Options * @param {object} [context] - Context * @property {object} [context] - Context @@ -24,7 +24,7 @@ import { formatOther, formatParent, stringToBoolean } from '../utils/string.js' * @property {string} [contactPreferenceDetails] - Contact method details * @property {string} [patient_uuid] - Patient UUID */ -export class Parent { +export class Contact { constructor(options, context) { this.context = context this.uuid = options?.uuid || faker.string.uuid() @@ -60,7 +60,7 @@ export class Parent { * @returns {string} Full name and relationship */ get fullNameAndRelationship() { - return formatParent(this, false) + return formatContact(this, false) } /** @@ -74,7 +74,7 @@ export class Parent { return Patient.findOne(this.patient_uuid, this.context) } } catch (error) { - console.error('Parent.patient', error.message) + console.error('Contact.patient', error.message) } } @@ -98,7 +98,7 @@ export class Parent { * @returns {string} Namespace */ get ns() { - return 'parent' + return 'contact' } /** @@ -107,14 +107,14 @@ export class Parent { * @returns {string} URI */ get uri() { - return `/parents/${this.uuid}` + return `/contacts/${this.uuid}` } /** * Remove `context` so it’s hidden from JSON.stringify, or we’ll get * circular reference issues during saving * - * @returns {object} Parent ready to be serialized to JSON + * @returns {object} Contact ready to be serialized to JSON */ toJSON() { const { context, ...rest } = this @@ -125,83 +125,86 @@ export class Parent { * Find all * * @param {object} context - Context - * @returns {Array|undefined} Parents + * @returns {Array|undefined} Contacts * @static */ static findAll(context) { - return Object.values(context.parents).map( - (parent) => new Parent(parent, context) + return Object.values(context.contacts).map( + (contact) => new Contact(contact, context) ) } /** * Find one * - * @param {string|string[]} uuid - Parent UUID + * @param {string|string[]} uuid - Contact UUID * @param {object} context - Context - * @returns {Parent|undefined} Parent + * @returns {Contact|undefined} Contact * @static */ static findOne(uuid, context) { uuid = String(uuid) - if (context?.parents?.[uuid]) { - return new Parent(context.parents[uuid], context) + if (context?.contacts?.[uuid]) { + return new Contact(context.contacts[uuid], context) } } /** * Create * - * @param {object} parent - Parent + * @param {object} contact - Contact * @param {object} context - Context - * @returns {Parent} Created parent + * @returns {Contact} Created contact * @static */ - static create(parent, context) { - const createdParent = new Parent(parent) + static create(contact, context) { + const createdContact = new Contact(contact) // Update context - context.parents = context.parents || {} - context.parents[createdParent.uuid] = createdParent + context.contacts = context.contacts || {} + context.contacts[createdContact.uuid] = createdContact - return createdParent + return createdContact } /** * Update * - * @param {string|string[]} uuid - Parent UUID + * @param {string|string[]} uuid - Contact UUID * @param {object} updates - Updates * @param {object} context - Context - * @returns {Parent} Updated parent + * @returns {Contact} Updated contact * @static */ static update(uuid, updates, context) { uuid = String(uuid) - const updatedParent = Object.assign(Parent.findOne(uuid, context), updates) + const updatedContact = Object.assign( + Contact.findOne(uuid, context), + updates + ) // Remove move context - delete updatedParent.context + delete updatedContact.context // Delete original move (with previous UUID) - delete context.parents[uuid] + delete context.contacts[uuid] // Update context - context.parents[updatedParent.uuid] = updatedParent + context.contacts[updatedContact.uuid] = updatedContact - return updatedParent + return updatedContact } /** * Delete * - * @param {string|string[]} uuid - Parent UUID + * @param {string|string[]} uuid - Contact UUID * @param {object} context - Context * @static */ static delete(uuid, context) { - delete context.parents[String(uuid)] + delete context.contacts[String(uuid)] } } diff --git a/app/models/download.js b/app/models/download.js index 944c1a799..01b194fe3 100644 --- a/app/models/download.js +++ b/app/models/download.js @@ -318,7 +318,7 @@ export class Download { firstName: vaccination.patient?.firstName, dob: vaccination.patient?.dob, address_line1: vaccination.patient?.address?.addressLine1, - parent: vaccination.patient?.parents[0]?.fullName, + contact: vaccination.patient?.contacts[0]?.fullName, ethnicity: '', date: vaccination.createdAt, time: vaccination.createdAt, diff --git a/app/models/patient-session.js b/app/models/patient-session.js index 1cf4670c0..734ea7445 100644 --- a/app/models/patient-session.js +++ b/app/models/patient-session.js @@ -214,16 +214,16 @@ export class PatientSession { } /** - * Get names of parents who have requested a follow up + * Get names of contacts who have requested a follow up * - * @returns {Array|undefined} Parent names and relationships + * @returns {Array|undefined} Contact names and relationships */ - get parentsRequestingFollowUp() { + get contactsRequestingFollowUp() { if (this.responses) { return this.responses .filter((reply) => !reply.invalid) .filter((reply) => reply.declined) - .flatMap((reply) => reply.parent.fullNameAndRelationship) + .flatMap((reply) => reply.contact.fullNameAndRelationship) } } @@ -237,9 +237,9 @@ export class PatientSession { } /** - * Has every parent given consent for an injected vaccine? + * Has every contact given consent for an injected vaccine? * - * Some parents may give consent for the nasal spray, but also given consent + * Some contacts may give consent for the nasal spray, but also given consent * for the injection as an alternative * * @returns {boolean|undefined} Consent given for an injected vaccine @@ -251,7 +251,7 @@ export class PatientSession { } /** - * Has every parent given consent only for an injected vaccine? + * Has every contact given consent only for an injected vaccine? * * We need this so that we don’t offer multiple triage outcomes if consent has * only been given for the injected vaccine @@ -633,7 +633,7 @@ export class PatientSession { */ get consentDescription() { const relationships = filters.formatList(this.parentalRelationships) - const parentNames = filters.formatList(this.parentsRequestingFollowUp) + const contactNames = filters.formatList(this.contactsRequestingFollowUp) if (this.patient?.post16) { return `${this.patient.firstName} is old enough to self-consent.` @@ -655,7 +655,7 @@ export class PatientSession { case ConsentOutcome.Inconsistent: return 'You can only vaccinate if all respondents give consent.' case ConsentOutcome.Declined: - return `${parentNames} would like to speak to a member of the team about other options for their child’s vaccination.` + return `${contactNames} would like to speak to a member of the team about other options for their child’s vaccination.` case ConsentOutcome.Given: case ConsentOutcome.GivenForAlternativeInjection: case ConsentOutcome.GivenForIntranasal: @@ -1072,11 +1072,11 @@ export class PatientSession { messageTemplate = 'triage-vaccinate' } - if (this.patient?.parents) { - for (const parent of this.patient.parents) { + if (this.patient?.contacts) { + for (const contact of this.patient.contacts) { this.patient?.addEvent({ - name: activity.notify[messageTemplate](parent), - messageRecipient: parent, + name: activity.notify[messageTemplate](contact), + messageRecipient: contact, messageTemplate, createdAt: event.createdAt, patient_uuid: this.uuid, @@ -1156,13 +1156,13 @@ export class PatientSession { /** * Send reminder * - * @param {import('./audit-event.js').AuditEvent} event - Event - * @param {import('./parent.js').Parent} parent - Parent + * @param {import('../models.js').AuditEvent} event - Event + * @param {import('../models.js').Contact} contact - Contact */ - sendReminder(event, parent) { + sendReminder(event, contact) { this.patient?.addEvent({ - name: activity.notify['vaccination-reminder'](parent), - messageRecipient: parent, + name: activity.notify['vaccination-reminder'](contact), + messageRecipient: contact, messageTemplate: 'vaccination-reminder', type: AuditEventType.Reminder, createdBy_uid: event.createdBy_uid, diff --git a/app/models/patient.js b/app/models/patient.js index 43856fabe..e282f498f 100644 --- a/app/models/patient.js +++ b/app/models/patient.js @@ -16,8 +16,8 @@ import { import { AuditEvent, Child, + Contact, Move, - Parent, PatientProgramme, PatientSession, Reply, @@ -51,7 +51,7 @@ import { * @property {Array} [clinicProgramme_ids] - Clinic programme invitations * @property {Array} events - Events * @property {Array} [reply_uuids] - Reply IDs - * @property {Array} [parent_uuids] - Parent UUIDS + * @property {Array} [contact_uuids] - Contact UUIDS * @property {Array} [patientSession_uuids] - Patient session IDs * @property {Array} [vaccination_uuids] - Vaccination UUIDs */ @@ -73,7 +73,7 @@ export class Patient extends Child { this.clinicProgramme_ids = options?.clinicProgramme_ids || [] this.events = options?.events || [] this.reply_uuids = options?.reply_uuids || [] - this.parent_uuids = options?.parent_uuids || [] + this.contact_uuids = options?.contact_uuids || [] this.patientSession_uuids = options?.patientSession_uuids || [] this.vaccination_uuids = options?.vaccination_uuids || [] } @@ -105,12 +105,12 @@ export class Patient extends Child { } /** - * Has no parental contact details + * Has no contact details * - * @returns {boolean} Has no parental details + * @returns {boolean} Has no contact details */ get hasNoContactDetails() { - return this.parents.every((parent) => !parent.email && !parent.tel) + return this.contacts.every((contact) => !contact.email && !contact.tel) } /** @@ -164,27 +164,27 @@ export class Patient extends Child { } /** - * Get parents (from record and replies) + * Get contacts (from record and replies) * - * @returns {Array} Parents + * @returns {Array} Contacts */ - get parents() { - const parents = new Map() + get contacts() { + const contacts = new Map() if (!this.sensitive) { - this.parent_uuids.forEach((uuid) => - parents.set(uuid, Parent.findOne(uuid, this.context)) + this.contact_uuids.forEach((uuid) => + contacts.set(uuid, Contact.findOne(uuid, this.context)) ) } - // Add any new parents found in consent replies + // Add any new contacts found in consent replies Object.values(this.replies) .filter(({ selfConsent }) => !selfConsent) - .forEach(({ parent }) => { - parents.set(parent.uuid, new Parent(parent)) + .forEach(({ contact }) => { + contacts.set(contact.uuid, new Contact(contact)) }) - return [...parents.values()] + return [...contacts.values()] } get recordEvents() { @@ -441,9 +441,9 @@ export class Patient extends Child { * @returns {string} Tokens */ get tokenized() { - const parentTokens = [] - for (const parent of this.parents) { - parentTokens.push(tokenize(parent, ['fullName', 'tel', 'email'])) + const contactTokens = [] + for (const contact of this.contacts) { + contactTokens.push(tokenize(contact, ['fullName', 'tel', 'email'])) } const childTokens = tokenize(this, [ @@ -453,7 +453,7 @@ export class Patient extends Child { 'school.name' ]) - return [childTokens, parentTokens].join(' ') + return [childTokens, contactTokens].join(' ') } /** @@ -647,13 +647,13 @@ export class Patient extends Child { /** * Add contact to patient * - * @param {import('./parent.js').Parent} parent - Parent + * @param {import('../models').Contact} contact - Contact */ - addContact(parent) { - this.parent_uuids.push(parent.uuid) + addContact(contact) { + this.contact_uuids.push(contact.uuid) this.addEvent({ - name: activity.patient.contact(parent), + name: activity.patient.contact(contact), type: AuditEventType.Record }) } @@ -676,7 +676,7 @@ export class Patient extends Child { } /** - * Invite parent to book a clinic appointment + * Invite contact to book a clinic appointment * * @param {Array} programme_ids - The programmes for which the child's invited */ @@ -685,10 +685,10 @@ export class Patient extends Child { ...new Set(this.clinicProgramme_ids.concat(programme_ids)) ] - for (const parent of this.parents) { + for (const contact of this.contacts) { this.addEvent({ - name: activity.notify['invite-clinic'](parent), - messageRecipient: parent, + name: activity.notify['invite-clinic'](contact), + messageRecipient: contact, messageTemplate: 'invite-clinic', patient_uuid: this.uuid, programme_ids: programme_ids @@ -697,16 +697,19 @@ export class Patient extends Child { } /** - * Invite parent to give consent + * Invite contact to give consent * * @param {import('./patient-session.js').PatientSession} patientSession - Patient session */ requestConsent(patientSession) { - for (const parent of this.parents) { - if (parent.email && parent.emailStatus === NotifyEmailStatus.Delivered) { + for (const contact of this.contacts) { + if ( + contact.email && + contact.emailStatus === NotifyEmailStatus.Delivered + ) { this.addEvent({ - name: activity.notify.invite(parent), - messageRecipient: parent, + name: activity.notify.invite(contact), + messageRecipient: contact, messageTemplate: 'invite', createdAt: patientSession.session.openAt, patient_uuid: this.uuid, @@ -782,11 +785,11 @@ export class Patient extends Child { messageTemplate = 'vaccination-deleted' } - for (const parent of this.parents) { + for (const contact of this.contacts) { if (vaccination.outcome !== VaccinationOutcome.AlreadyVaccinated) { this.addEvent({ - name: activity.notify['vaccination-reminder'](parent), - messageRecipient: parent, + name: activity.notify['vaccination-reminder'](contact), + messageRecipient: contact, messageTemplate: 'vaccination-reminder', createdAt: removeDays(vaccination.createdAt, 7), patient_uuid: this.uuid, @@ -796,8 +799,8 @@ export class Patient extends Child { } this.addEvent({ - name: activity.notify[messageTemplate](parent), - messageRecipient: parent, + name: activity.notify[messageTemplate](contact), + messageRecipient: contact, messageTemplate, createdAt: vaccination.updatedAt || vaccination.createdAt, patient_uuid: this.uuid, @@ -835,9 +838,9 @@ export class Patient extends Child { this.dod = removeDays(today(), 5) name = `Record updated with child’s date of death` break - case notice.type === NoticeType.NoNotify && this.parents[0]?.notify: + case notice.type === NoticeType.NoNotify && this.contacts[0]?.notify: // Notify request to not share vaccination with GP - this.parents[0].notify = false + this.contacts[0].notify = false name = `Child gave consent for HPV and flu vaccinations under Gillick competence and does not want their parents to be notified.\n\nThese records are not automatically synced with GP records.\n\nYour team must let the child’s GP know they were vaccinated.` break case notice.type === NoticeType.Invalid: diff --git a/app/models/pds-record.js b/app/models/pds-record.js index 293f7b927..d146835fc 100644 --- a/app/models/pds-record.js +++ b/app/models/pds-record.js @@ -1,12 +1,12 @@ import { fakerEN_GB as faker } from '@faker-js/faker' import _ from 'lodash' -import { Child, Parent } from '../models.js' +import { Child, Contact } from '../models.js' import { tokenize } from '../utils/object.js' import { formatList, formatNhsNumber, - formatParent, + formatContact, formatWithSecondaryText, stringToBoolean } from '../utils/string.js' @@ -20,7 +20,7 @@ import { * @property {boolean} [invalid] - Flagged as invalid * @property {boolean} [sensitive] - Flagged as sensitive * @property {object} [address] - Address - * @property {Array} [parent_uuids] - Parent UUIDS + * @property {Array} [contact_uuids] - Contact UUIDS */ export class PDSRecord extends Child { constructor(options, context) { @@ -36,16 +36,16 @@ export class PDSRecord extends Child { this.sensitive = sensitive this.address = !sensitive && options?.address ? options.address : undefined this.school_id = null - this.parent_uuids = options?.parent_uuids || [] + this.contact_uuids = options?.contact_uuids || [] } /** - * Has no parental contact details + * Has no contact details * - * @returns {boolean} Has no parental details + * @returns {boolean} Has no contact details */ get hasNoContactDetails() { - return this.parents.every((parent) => !parent.email && !parent.tel) + return this.contacts.every((contact) => !contact.email && !contact.tel) } /** @@ -58,13 +58,15 @@ export class PDSRecord extends Child { } /** - * Get parents (from record and replies) + * Get contacts (from record and replies) * - * @returns {Array|undefined} Parents + * @returns {Array|undefined} Contacts */ - get parents() { + get contacts() { if (!this.sensitive) { - return this.parent_uuids.map((uuid) => Parent.findOne(uuid, this.context)) + return this.contact_uuids.map((uuid) => + Contact.findOne(uuid, this.context) + ) } } @@ -74,14 +76,14 @@ export class PDSRecord extends Child { * @returns {string} Tokens */ get tokenized() { - const parentTokens = [] - for (const parent of this.parents) { - parentTokens.push(tokenize(parent, ['fullName', 'tel', 'email'])) + const contactTokens = [] + for (const contact of this.contacts) { + contactTokens.push(tokenize(contact, ['fullName', 'tel', 'email'])) } const childTokens = tokenize(this, ['nhsn', 'fullName', 'postalCode']) - return [childTokens, parentTokens].join(' ') + return [childTokens, contactTokens].join(' ') } /** @@ -91,13 +93,15 @@ export class PDSRecord extends Child { */ get formatted() { const formattedNhsn = formatNhsNumber(this.nhsn, this.invalid) - const formattedParents = this.parents.map((parent) => formatParent(parent)) + const formattedContacts = this.contacts.map((contact) => + formatContact(contact) + ) return { ...super.formatted, fullNameAndNhsn: formatWithSecondaryText(this.fullName, formattedNhsn), nhsn: formattedNhsn, - parents: formatList(formattedParents) + contacts: formatList(formattedContacts) } } diff --git a/app/models/reply.js b/app/models/reply.js index c6d99fc0c..71916197b 100644 --- a/app/models/reply.js +++ b/app/models/reply.js @@ -16,7 +16,7 @@ import { } from '../enums.js' import { Child, - Parent, + Contact, Patient, Programme, Session, @@ -31,7 +31,7 @@ import { import { formatMarkdown, formatOther, - formatParent, + formatContact, formatTag, formatWithSecondaryText, stringToBoolean @@ -46,8 +46,8 @@ import { * @property {Date} [createdAt] - Created date * @property {string} [createdBy_uid] - User who created reply * @property {Date} [updatedAt] - Updated date - * @property {import('./child.js').Child} [child] - Child - * @property {import('./parent.js').Parent} [parent_] - Parent or guardian + * @property {import('./models.js').Child} [child] - Child + * @property {import('./models.js').Contact} [contact_] - Parent or guardian * @property {ReplyDecision} [decision] - Consent decision * @property {boolean} [alternative] - Consent for alternative vaccine * @property {boolean} [confirmed] - Decision confirmed @@ -65,7 +65,7 @@ import { * @property {string} [refusalReasonDetails] - Refusal reason details * @property {boolean} [selfConsent] - Reply given by child * @property {string} [note] - Note about this response - * @property {string} [parent_uuid] - Parent UUID + * @property {string} [contact_uuid] - Contact UUID * @property {string} patient_uuid - Patient UUID * @property {string} [programme_id] - Programme ID * @property {string} session_id - Session ID @@ -85,8 +85,8 @@ export class Reply { this.method = options?.method this.selfConsent = options?.selfConsent this.note = options?.note || '' - this.parent_ = options?.parent_ && new Parent(options.parent_) - this.parent_uuid = options?.parent_uuid + this.contact_ = options?.contact_ && new Contact(options.contact_) + this.contact_uuid = options?.contact_uuid this.patient_uuid = options?.patient_uuid this.programme_id = options?.programme_id this.session_id = options?.session_id @@ -168,8 +168,8 @@ export class Reply { * @returns {string|undefined} Full name */ get fullName() { - if (this.parent) { - return this.parent.fullName + if (this.contact) { + return this.contact.fullName } else if (this.child) { return this.child.fullName } @@ -181,8 +181,8 @@ export class Reply { * @returns {string|undefined} Relationship to child */ get relationship() { - if (this.parent) { - return this.parent.relationship + if (this.contact) { + return this.contact.relationship } else if (this.child) { return `${this.child.fullName} (child)` } @@ -196,7 +196,7 @@ export class Reply { get fullNameAndRelationship() { return this.selfConsent ? this.relationship - : formatParent(this.parent, false) + : formatContact(this.contact, false) } /** @@ -211,10 +211,10 @@ export class Reply { } const hasEmailGotEmail = - this.parent?.email && - this.parent?.emailStatus === NotifyEmailStatus.Delivered + this.contact?.email && + this.contact?.emailStatus === NotifyEmailStatus.Delivered const hasTelSmsGotSms = - this.parent?.tel && this.parent?.smsStatus === NotifySmsStatus.Delivered + this.contact?.tel && this.contact?.smsStatus === NotifySmsStatus.Delivered return hasEmailGotEmail || hasTelSmsGotSms } @@ -259,7 +259,7 @@ export class Reply { } /** - * Has parent given consent for an injected vaccine? + * Has contact given consent for an injected vaccine? * * @returns {boolean} Consent given for an injected vaccine */ @@ -342,30 +342,30 @@ export class Reply { } /** - * Get parent + * Get contact * - * @returns {Parent|undefined} Parent + * @returns {Contact|undefined} Contact */ - get parent() { + get contact() { try { - if (this.parent_uuid) { - return Parent.findOne(this.parent_uuid, this.context) + if (this.contact_uuid) { + return Contact.findOne(this.contact_uuid, this.context) } } catch (error) { - console.error('Reply.parent (get)', error.message) + console.error('Reply.contact (get)', error.message) } } /** - * Set parent + * Set contact */ - set parent(updates) { + set contact(updates) { try { - if (this.parent_uuid) { - Parent.update(this.parent_uuid, updates, this.context) + if (this.contact_uuid) { + Contact.update(this.contact_uuid, updates, this.context) } } catch (error) { - console.error('Reply.parent (set)', error.message) + console.error('Reply.contact (set)', error.message) } } @@ -453,9 +453,9 @@ export class Reply { }), createdBy: this.createdBy?.fullName || '', decisionStatus, - parent: formatParent(this.parent, true), - tel: this.parent && this.parent.tel, - email: this.parent && this.parent.email, + contact: formatContact(this.contact, true), + tel: this.contact && this.contact.tel, + email: this.contact && this.contact.email, programme: this.programme?.nameTag, refusalReason: formatOther(this.refusalReasonOther, this.refusalReason), refusalReasonDetails: formatMarkdown(this.refusalReasonDetails), @@ -482,11 +482,11 @@ export class Reply { } /** - * Get parent form URI + * Get public-facing form URI * - * @returns {string} Parent form URI + * @returns {string} Public-facing form URI */ - get parentUri() { + get publicUri() { return `${this.session.consentUrl}/${this.uuid}` } diff --git a/app/models/session.js b/app/models/session.js index f473805e2..a704333c9 100644 --- a/app/models/session.js +++ b/app/models/session.js @@ -481,7 +481,7 @@ export class Session { } /** - * Get the number of days parents have left to book their child into this clinic + * Get the number of days contacts have left to book their child into this clinic * * @returns {number} - the number of days before appointment booking closes */ diff --git a/app/models/upload.js b/app/models/upload.js index 9baa52d7b..660ccf000 100644 --- a/app/models/upload.js +++ b/app/models/upload.js @@ -101,11 +101,11 @@ export class Upload { } // Simulate a subset of patient records being new - // Use the existence of a second parent as a proxy for this + // Use the existence of a second contact as a proxy for this patients = patients.map((patient) => { patient.isNew = - patient.parent2 !== undefined && !patient.hasPendingChanges - patient.hasMatch = !patient.parent2 && !patient.hasPendingChanges + patient.contacts[1] !== undefined && !patient.hasPendingChanges + patient.hasMatch = !patient.contacts[1] && !patient.hasPendingChanges return patient }) diff --git a/app/routes.js b/app/routes.js index eeb178d18..f1205436d 100644 --- a/app/routes.js +++ b/app/routes.js @@ -18,13 +18,13 @@ import { bookIntoClinicRoutes } from './routes/book-into-a-clinic.js' import { clinicBookingRoutes } from './routes/clinic-booking.js' import { clinicRoutes } from './routes/clinic.js' import { consentRoutes } from './routes/consent.js' +import { contactRoutes } from './routes/contact.js' import { defaultBatchRoutes } from './routes/default-batch.js' import { downloadRoutes } from './routes/download.js' import { giveOrRefuseConsentRoutes } from './routes/give-or-refuse-consent.js' import { homeRoutes } from './routes/home.js' import { moveRoutes } from './routes/move.js' import { noticeRoutes } from './routes/notice.js' -import { parentRoutes } from './routes/parent.js' import { patientSessionRoutes } from './routes/patient-session.js' import { patientRoutes } from './routes/patient.js' import { pdsRecordRoutes } from './routes/pds-record.js' @@ -52,7 +52,7 @@ router.use('/', homeRoutes) router.use('/account', accountRoutes) router.use('/activity', activityRoutes) router.use('/appointments', appointmentRoutes) // all unmatched clinic appointments -router.use('/book-into-a-clinic', bookIntoClinicRoutes) // parent-facing clinic booking journey +router.use('/book-into-a-clinic', bookIntoClinicRoutes) // public-facing clinic booking journey router.use('/clinic-bookings', clinicBookingRoutes) // original explorations of clinic booking data router.use('/consents', consentRoutes) router.use('/downloads', downloadRoutes) @@ -61,7 +61,7 @@ router.use('/moves', moveRoutes) router.use('/notices', noticeRoutes) router.use('/teams', teamRoutes) router.use('/teams/:team_id/clinics', clinicRoutes) -router.use('/parents', parentRoutes) +router.use('/contacts', contactRoutes) router.use('/patients', patientRoutes) router.use('/pds', pdsRecordRoutes) router.use('/reports', reportRoutes) diff --git a/app/routes/contact.js b/app/routes/contact.js new file mode 100644 index 000000000..3410bf792 --- /dev/null +++ b/app/routes/contact.js @@ -0,0 +1,22 @@ +import express from 'express' + +import { contactController as contact } from '../controllers/contact.js' + +const router = express.Router({ strict: true }) + +router.get('/new', contact.new) + +router.param('contact_uuid', contact.read) + +router.get('/:contact_uuid/delete', contact.action('delete')) +router.post('/:contact_uuid/delete', contact.delete) + +router.all('/:contact_uuid/new', contact.readForm('new')) +router.get('/:contact_uuid/new', contact.showForm) +router.post('/:contact_uuid/new', contact.updateForm, contact.update('new')) + +router.all('/:contact_uuid/edit', contact.readForm('edit')) +router.get('/:contact_uuid/edit', contact.showForm) +router.post('/:contact_uuid/edit', contact.updateForm, contact.update('edit')) + +export const contactRoutes = router diff --git a/app/routes/parent.js b/app/routes/parent.js deleted file mode 100644 index 884193571..000000000 --- a/app/routes/parent.js +++ /dev/null @@ -1,22 +0,0 @@ -import express from 'express' - -import { parentController as parent } from '../controllers/parent.js' - -const router = express.Router({ strict: true }) - -router.get('/new', parent.new) - -router.param('parent_uuid', parent.read) - -router.get('/:parent_uuid/delete', parent.action('delete')) -router.post('/:parent_uuid/delete', parent.delete) - -router.all('/:parent_uuid/new', parent.readForm('new')) -router.get('/:parent_uuid/new', parent.showForm) -router.post('/:parent_uuid/new', parent.updateForm, parent.update('new')) - -router.all('/:parent_uuid/edit', parent.readForm('edit')) -router.get('/:parent_uuid/edit', parent.showForm) -router.post('/:parent_uuid/edit', parent.updateForm, parent.update('edit')) - -export const parentRoutes = router diff --git a/app/utils/reply.js b/app/utils/reply.js index 05b9d3851..a66071920 100644 --- a/app/utils/reply.js +++ b/app/utils/reply.js @@ -95,7 +95,7 @@ export function getConsentHealthAnswers(patientSession) { // Don’t modify original health answer const thisHealthAnswer = { ...healthAnswer } thisHealthAnswer.relationship = formatParentalRelationship( - response.parent + response.contact ) if (hasSingleResponse) { @@ -319,7 +319,7 @@ export const getHealthAnswers = (vaccine, healthCondition) => { answers[key] = enrichWithRealisticAnswer(key, healthCondition) } - // If asthma sub-question(s) has 'Yes’ answer, change parent answer to ‘Yes’ + // If asthma sub-question(s) has 'Yes’ answer, change contact answer to ‘Yes’ if ( [answers.asthmaSteroids?.answer, answers.asthmaAdmitted?.answer].includes( 'Yes' diff --git a/app/utils/string.js b/app/utils/string.js index fc73cebbf..8bdf233bc 100644 --- a/app/utils/string.js +++ b/app/utils/string.js @@ -325,30 +325,30 @@ export function formatNhsNumber(string, invalid) { } /** - * Format parent with optional display of contact details + * Format contact name with optional display of contact details * - * @param {import('../models.js').Parent} parent - Parent + * @param {import('../models.js').Contact} contact - Contact * @param {boolean} [includeContactDetails] - Include contact details - * @returns {string|undefined} Formatted parent HTML + * @returns {string|undefined} Formatted contact HTML */ -export function formatParent(parent, includeContactDetails = true) { - if (!parent) return +export function formatContact(contact, includeContactDetails = true) { + if (!contact) return - let string = parent.fullName || 'Parent or guardian' + let string = contact.fullName || 'Parent or guardian' // Add relationship, if provided - if (parent.fullName !== undefined && parent.relationship) { - string += ` (${lowerCaseFirst(parent.relationship)})` + if (contact.fullName !== undefined && contact.relationship) { + string += ` (${lowerCaseFirst(contact.relationship)})` } // Add telephone number, if provided - if (includeContactDetails && parent.tel) { - string += `
${parent.tel}` + if (includeContactDetails && contact.tel) { + string += `
${contact.tel}` } // Add email address, if provided - if (includeContactDetails && parent.email) { - string += `
${parent.email}` + if (includeContactDetails && contact.email) { + string += `
${contact.email}` } return string @@ -388,13 +388,13 @@ export function formatIdentifier(identifiedBy) { /** * Format parental relationship, falling back to name else unknown * - * @param {import('../models.js').Parent} parent - Parent - * @returns {string|undefined} Formatted parent HTML + * @param {import('../models.js').Contact} contact - Contact + * @returns {string|undefined} Formatted parental relationship HTML */ -export function formatParentalRelationship(parent) { - if (!parent) return +export function formatParentalRelationship(contact) { + if (!contact) return - return parent.relationship || parent.fullName || 'Name unknown' + return contact.relationship || contact.fullName || 'Name unknown' } /** diff --git a/app/views/_macros/contact-email.njk b/app/views/_macros/contact-email.njk new file mode 100644 index 000000000..ca0dd4700 --- /dev/null +++ b/app/views/_macros/contact-email.njk @@ -0,0 +1,10 @@ +{%- from "_macros/status.njk" import appStatus -%} +{% macro appContactEmail(contact, hideStatus) -%} +

{{ contact.email }}

+ {{ appStatus({ + classes: "app-status--small nhsuk-u-margin-top-1", + text: contact.emailStatus, + icon: "warning", + colour: "blue" + }) if not hideStatus and contact.emailStatus != NotifyEmailStatus.Delivered }} +{% endmacro %} diff --git a/app/views/_macros/contact-sms.njk b/app/views/_macros/contact-sms.njk new file mode 100644 index 000000000..7b24c7aa5 --- /dev/null +++ b/app/views/_macros/contact-sms.njk @@ -0,0 +1,10 @@ +{%- from "_macros/status.njk" import appStatus -%} +{% macro appContactSms(contact, hideStatus) -%} +

{{ contact.tel }}

+ {{ appStatus({ + classes: "app-status--small nhsuk-u-margin-top-1", + text: contact.smsStatus, + icon: "warning", + colour: "blue" + }) if not hideStatus and contact.smsStatus != NotifySmsStatus.Delivered }} +{% endmacro %} diff --git a/app/views/_macros/parent-email.njk b/app/views/_macros/parent-email.njk deleted file mode 100644 index 69b3deecc..000000000 --- a/app/views/_macros/parent-email.njk +++ /dev/null @@ -1,10 +0,0 @@ -{%- from "_macros/status.njk" import appStatus -%} -{% macro appParentEmail(parent, hideStatus) -%} -

{{ parent.email }}

- {{ appStatus({ - classes: "app-status--small nhsuk-u-margin-top-1", - text: parent.emailStatus, - icon: "warning", - colour: "blue" - }) if not hideStatus and parent.emailStatus != NotifyEmailStatus.Delivered }} -{% endmacro %} diff --git a/app/views/_macros/parent-sms.njk b/app/views/_macros/parent-sms.njk deleted file mode 100644 index d1c24fc5b..000000000 --- a/app/views/_macros/parent-sms.njk +++ /dev/null @@ -1,10 +0,0 @@ -{%- from "_macros/status.njk" import appStatus -%} -{% macro appParentSms(parent, hideStatus) -%} -

{{ parent.tel }}

- {{ appStatus({ - classes: "app-status--small nhsuk-u-margin-top-1", - text: parent.smsStatus, - icon: "warning", - colour: "blue" - }) if not hideStatus and parent.smsStatus != NotifySmsStatus.Delivered }} -{% endmacro %} diff --git a/app/views/book-into-a-clinic/form/contact-preference.njk b/app/views/book-into-a-clinic/form/contact-preference.njk index f72aa264c..5d298c69d 100644 --- a/app/views/book-into-a-clinic/form/contact-preference.njk +++ b/app/views/book-into-a-clinic/form/contact-preference.njk @@ -1,32 +1,32 @@ {% extends "_layouts/form.njk" %} -{% set title = __("clinicBooking.parent.contactPreference.title") %} +{% set title = __("clinicBooking.contact.contactPreference.title") %} {% block form %} {{ appHeading({ title: title }) }} - {{ __("clinicBooking.parent.contactPreference.description") | nhsukMarkdown }} + {{ __("clinicBooking.contact.contactPreference.description") | nhsukMarkdown }} {{ radios({ fieldset: { legend: { - text: __("clinicBooking.parent.contactPreference.label"), + text: __("clinicBooking.contact.contactPreference.label"), size: "s" } }, items: [{ - text: __("clinicBooking.parent.contactPreference.yes"), + text: __("clinicBooking.contact.contactPreference.yes"), conditional: { html: textarea({ - label: { text: __("clinicBooking.parent.contactPreferenceDetails.label") }, - decorate: "booking.parent.contactPreferenceDetails" + label: { text: __("clinicBooking.contact.contactPreferenceDetails.label") }, + decorate: "booking.contact.contactPreferenceDetails" }) } }, { - text: __("clinicBooking.parent.contactPreference.no") + text: __("clinicBooking.contact.contactPreference.no") }], - decorate: "booking.parent.contactPreference" + decorate: "booking.contact.contactPreference" }) }} {% endblock %} diff --git a/app/views/book-into-a-clinic/form/extra-time.njk b/app/views/book-into-a-clinic/form/extra-time.njk index a0bd43484..82369488b 100644 --- a/app/views/book-into-a-clinic/form/extra-time.njk +++ b/app/views/book-into-a-clinic/form/extra-time.njk @@ -3,7 +3,7 @@ {% set title = __("clinicBooking.extraTime.title", firstName) %} {% block form %} - {# Ask for a reason if the parent says the child does need extra time #} + {# Ask for a reason if the contact says the child does need extra time #} {%- set yesHtml = input({ label: { text: __("clinicBooking.extraTime.reason.label") }, decorate: "appointment.extraTimeReason" diff --git a/app/views/book-into-a-clinic/form/parent.njk b/app/views/book-into-a-clinic/form/parent.njk index 7127a30f2..2a1fa132b 100644 --- a/app/views/book-into-a-clinic/form/parent.njk +++ b/app/views/book-into-a-clinic/form/parent.njk @@ -1,6 +1,6 @@ {% extends "_layouts/form.njk" %} -{% set title = __("clinicBooking.parent.title") %} +{% set title = __("clinicBooking.contact.title") %} {% block form %} {% call fieldset({ @@ -12,29 +12,29 @@ }) %} {{ input({ - label: { text: __("clinicBooking.parent.fullName.label") }, - decorate: "booking.parent.fullName" + label: { text: __("clinicBooking.contact.fullName.label") }, + decorate: "booking.contact.fullName" }) }} {{ input({ - label: { text: __("clinicBooking.parent.email.label") }, - hint: { text: __("clinicBooking.parent.email.hint") }, - decorate: "booking.parent.email" + label: { text: __("clinicBooking.contact.email.label") }, + hint: { text: __("clinicBooking.contact.email.hint") }, + decorate: "booking.contact.email" }) }} {{ input({ - label: { text: __("clinicBooking.parent.tel.label") + " (optional)" }, - hint: { text: __("clinicBooking.parent.tel.hint") }, - decorate: "booking.parent.tel" + label: { text: __("clinicBooking.contact.tel.label") + " (optional)" }, + hint: { text: __("clinicBooking.contact.tel.hint") }, + decorate: "booking.contact.tel" }) }} {{ checkboxes({ items: [{ - text: __("clinicBooking.parent.sms.label"), + text: __("clinicBooking.contact.sms.label"), value: true }], - decorate: "booking.parent.sms" + decorate: "booking.contact.sms" }) }} - {% endcall %} + {% endcall %} {% endblock %} diff --git a/app/views/clinic-booking/show.njk b/app/views/clinic-booking/show.njk index 66854bfef..bcb1ce719 100644 --- a/app/views/clinic-booking/show.njk +++ b/app/views/clinic-booking/show.njk @@ -43,14 +43,14 @@ }) }} {% endfor %} - {# Parent details #} + {# Contact details #} {{ summaryList({ card: { - heading: __("clinicBooking.show.parent.title"), + heading: __("clinicBooking.show.contact.title"), headingSize: "m" }, lastRowBorder: false, - rows: summaryRows(clinicBooking.parent, { + rows: summaryRows(clinicBooking.contact, { fullName: {}, email: {}, phone: {}, @@ -61,7 +61,7 @@ {# TODO: need to figure out what routes I'm going to need for editing, but that can wait till I make the booking journey #} {{ button({ classes: "nhsuk-button--secondary", - text: __("clinicBooking.show.parent.change.label"), + text: __("clinicBooking.show.contact.change.label"), href: "/clinic-appointments/" + appointment.id + "/change" }) }} diff --git a/app/views/consent/add.njk b/app/views/consent/add.njk index acb0f2581..e255e00cf 100644 --- a/app/views/consent/add.njk +++ b/app/views/consent/add.njk @@ -7,7 +7,7 @@ {{ super() }} {{ appHeading({ - caption: __("consent.add.caption", { parent: consent.parent } ), + caption: __("consent.add.caption", { contact: consent.contact } ), title: title }) }} diff --git a/app/views/consent/link.njk b/app/views/consent/link.njk index cf419385b..3f4a30034 100644 --- a/app/views/consent/link.njk +++ b/app/views/consent/link.njk @@ -9,7 +9,7 @@ {{ super() }} {{ appHeading({ - caption: __("consent.link.caption", { parent: consent.parent } ), + caption: __("consent.link.caption", { contact: consent.contact } ), title: title }) }} @@ -38,8 +38,8 @@ gpSurgery: { value: consent.child.formatted.gpSurgery | highlightDifference(patient.formatted.gpSurgery) }, - parent: { - value: consent.parent.fullNameAndRelationship + contact: { + value: consent.contact.fullNameAndRelationship } }) }) }} @@ -58,7 +58,7 @@ address: {}, schoolName: {}, gpSurgery: {}, - parents: {} + contacts: {} }) }) }} diff --git a/app/views/consent/match.njk b/app/views/consent/match.njk index 49db5a655..684182797 100644 --- a/app/views/consent/match.njk +++ b/app/views/consent/match.njk @@ -31,9 +31,9 @@ dob: {}, postalCode: {}, schoolName: {}, - parent: { - label: __("consent.parent.label"), - value: consent.formatted.parent + contact: { + label: __("consent.contact.label"), + value: consent.formatted.contact } }) }) }} @@ -91,8 +91,8 @@ schoolName: { value: patient.school.name | highlightQuery(data.q) }, - parents: { - value: patient.formatted.parents | highlightQuery(data.q) + contacts: { + value: patient.formatted.contacts | highlightQuery(data.q) } }) }) }} diff --git a/app/views/consent/show.njk b/app/views/consent/show.njk index 5060ed547..6d04fb86f 100644 --- a/app/views/consent/show.njk +++ b/app/views/consent/show.njk @@ -1,6 +1,6 @@ {% extends "_layouts/default.njk" %} -{% set title = __("consent.show.title", consent.parent.formatted.fullName) %} +{% set title = __("consent.show.title", consent.contact.formatted.fullName) %} {% block content %}
@@ -40,11 +40,11 @@ {{ summaryList({ card: { - heading: __("parent.label"), + heading: __("contact.label"), headingSize: "m" }, lastRowBorder: false, - rows: summaryRows(consent.parent, { + rows: summaryRows(consent.contact, { fullName: {}, relationship: {}, hasParentalResponsibility: {}, @@ -53,7 +53,7 @@ contactPreference: {}, sms: {} }) - }) if consent.parent }} + }) if consent.contact }} {{ summaryList({ card: { diff --git a/app/views/parent/action.njk b/app/views/contact/action.njk similarity index 51% rename from app/views/parent/action.njk rename to app/views/contact/action.njk index f2067e03b..ed3b94aa9 100644 --- a/app/views/parent/action.njk +++ b/app/views/contact/action.njk @@ -2,29 +2,29 @@ {% block form %} {{ appHeading({ - caption: parent.patient.fullName, - title: __("parent.action.title", { - parent: parent, + caption: contact.patient.fullName, + title: __("contact.action.title", { + contact: contact, type: type }) }) }} - {{ __("parent.action.description", { - patient: parent.patient + {{ __("contact.action.description", { + patient: contact.patient }) | nhsukMarkdown }} {% endblock %} {% block afterForm %} {{ appButtonGroup({ buttons: [{ - text: __("parent.action.confirm", type), + text: __("contact.action.confirm", type), variant: "warning", attributes: { - formAction: parent.uri + "/" + type + formAction: contact.uri + "/" + type } }], links: [{ - text: __("parent.action.cancel"), + text: __("contact.action.cancel"), href: back }] }) }} diff --git a/app/views/contact/form/edit.njk b/app/views/contact/form/edit.njk new file mode 100644 index 000000000..5d61ca3c9 --- /dev/null +++ b/app/views/contact/form/edit.njk @@ -0,0 +1,70 @@ +{% extends "_layouts/form.njk" %} + +{% set confirmButtonText = __("contact." + type + ".confirm") %} +{% set title = __("contact." + type + ".title") %} + +{% block form %} + {{ appHeading({ + caption: contact.patient.fullName, + title: title + }) }} + + {{ input({ + label: { text: __("contact.fullName.label") }, + decorate: "contact.fullName" + }) }} + + {{ radios({ + fieldset: { + legend: { text: __("contact.relationship.label") } + }, + items: [{ + text: ParentalRelationship.Mum + }, { + text: ParentalRelationship.Dad + }, { + text: ParentalRelationship.Guardian + }, { + text: ParentalRelationship.Other, + conditional: { + html: input({ + label: { text: __("contact.relationshipOther.label") }, + decorate: "contact.relationshipOther" + }) + } + }], + decorate: "contact.relationship" + }) }} + + {{ input({ + label: { text: __("contact.email.label") }, + decorate: "contact.email" + }) }} + + {{ input({ + label: { text: __("contact.tel.label") }, + decorate: "contact.tel" + }) }} + + {{ radios({ + fieldset: { + legend: { + text: __("contact.contactPreference.label") + } + }, + items: [{ + text: __("contact.contactPreference.yes"), + value: true, + conditional: { + html: textarea({ + label: { text: __("contact.contactPreferenceDetails.label") }, + decorate: "contact.contactPreferenceDetails" + }) + } + }, { + text: __("contact.contactPreference.no"), + value: false + }], + decorate: "contact.contactPreference" + }) }} +{% endblock %} diff --git a/app/views/give-or-refuse-consent/emails.njk b/app/views/give-or-refuse-consent/emails.njk index 3392f83b3..874d388a9 100644 --- a/app/views/give-or-refuse-consent/emails.njk +++ b/app/views/give-or-refuse-consent/emails.njk @@ -17,8 +17,8 @@ email: data.team.email }, to: { - name: consent.parent.fullName, - email: consent.parent.email + name: consent.contact.fullName, + email: consent.contact.email }, child: consent.child, session: session, diff --git a/app/views/give-or-refuse-consent/form/check-answers.njk b/app/views/give-or-refuse-consent/form/check-answers.njk index 5512cd1d8..18ebb671d 100644 --- a/app/views/give-or-refuse-consent/form/check-answers.njk +++ b/app/views/give-or-refuse-consent/form/check-answers.njk @@ -144,28 +144,28 @@ {{ summaryList({ card: { - heading: __("consent.parent.summary"), + heading: __("consent.contact.summary"), headingSize: "m" }, lastRowBorder: false, - rows: summaryRows(consent.parent_, { + rows: summaryRows(consent.contact_, { fullName: { - href: editPath("parent") + href: editPath("contact") }, relationship: { - href: editPath("parent") + href: editPath("contact") }, hasParentalResponsibility: { - href: editPath("parent") + href: editPath("contact") }, email: { - href: editPath("parent") + href: editPath("contact") }, tel: { - href: editPath("parent") + href: editPath("contact") }, sms: { - href: editPath("parent") + href: editPath("contact") }, contactPreference: { href: editPath("contact-preference") diff --git a/app/views/give-or-refuse-consent/form/contact-preference.njk b/app/views/give-or-refuse-consent/form/contact-preference.njk index 030043ab3..022c7381a 100644 --- a/app/views/give-or-refuse-consent/form/contact-preference.njk +++ b/app/views/give-or-refuse-consent/form/contact-preference.njk @@ -1,34 +1,34 @@ {% extends "_layouts/form.njk" %} -{% set title = __("consent.parent.contactPreference.title") %} +{% set title = __("consent.contact.contactPreference.title") %} {% block form %} {{ appHeading({ title: title }) }} - {{ __("consent.parent.contactPreference.description") | nhsukMarkdown }} + {{ __("consent.contact.contactPreference.description") | nhsukMarkdown }} {{ radios({ fieldset: { legend: { - text: __("consent.parent.contactPreference.label"), + text: __("consent.contact.contactPreference.label"), size: "s" } }, items: [{ - text: __("consent.parent.contactPreference.yes"), + text: __("consent.contact.contactPreference.yes"), value: true, conditional: { html: textarea({ - label: { text: __("consent.parent.contactPreferenceDetails.label") }, - decorate: "consent.parent_.contactPreferenceDetails" + label: { text: __("consent.contact.contactPreferenceDetails.label") }, + decorate: "consent.contact_.contactPreferenceDetails" }) } }, { - text: __("consent.parent.contactPreference.no"), + text: __("consent.contact.contactPreference.no"), value: false }], - decorate: "consent.parent_.contactPreference" + decorate: "consent.contact_.contactPreference" }) }} {% endblock %} diff --git a/app/views/give-or-refuse-consent/form/contact.njk b/app/views/give-or-refuse-consent/form/contact.njk new file mode 100644 index 000000000..91db56626 --- /dev/null +++ b/app/views/give-or-refuse-consent/form/contact.njk @@ -0,0 +1,69 @@ +{% extends "_layouts/form.njk" %} + +{% set title = __("consent.contact.title") %} + +{% block form %} + {{ appHeading({ + title: title + }) }} + + {{ input({ + label: { text: __("consent.contact.fullName.label") }, + decorate: "consent.contact_.fullName" + }) }} + + {%- set fosterCarerHtml = radios({ + fieldset: { + legend: { text: __("consent.contact.hasParentalResponsibility.label") } + }, + hint: { text: __("consent.contact.hasParentalResponsibility.hint") }, + items: getBooleanItems(), + decorate: "consent.contact_.hasParentalResponsibility" + }) %} + + {%- set otherHtml = input({ + label: { text: __("consent.contact.relationshipOther.label") }, + decorate: "consent.contact_.relationshipOther" + }) + radios({ + fieldset: { + legend: { text: __("consent.contact.hasParentalResponsibility.label") } + }, + hint: { text: __("consent.contact.hasParentalResponsibility.hint") }, + items: getBooleanItems(), + decorate: "consent.contact_.hasParentalResponsibility" + }) %} + + {# Add conditional html for ‘Foster carer’ option #} + {% set items = injectConditionalHtml(parentalRelationshipItems, ParentalRelationship.Fosterer, fosterCarerHtml) %} + + {# Add conditional html for ‘Other’ option #} + {% set items = injectConditionalHtml(items, ParentalRelationship.Other, otherHtml) %} + + {{ radios({ + fieldset: { + legend: { text: __("consent.contact.relationship.label") } + }, + items: items, + decorate: "consent.contact_.relationship" + }) }} + + {{ input({ + label: { text: __("consent.contact.email.label") }, + hint: { text: __("consent.contact.email.hint") }, + decorate: "consent.contact_.email" + }) }} + + {{ input({ + label: { text: __("consent.contact.tel.label") + " (optional)" }, + hint: { text: __("consent.contact.tel.hint") }, + decorate: "consent.contact_.tel" + }) }} + + {{ checkboxes({ + items: [{ + text: __("consent.contact.sms.label"), + value: true + }], + decorate: "consent.contact_.sms" + }) }} +{% endblock %} diff --git a/app/views/give-or-refuse-consent/form/parent.njk b/app/views/give-or-refuse-consent/form/parent.njk deleted file mode 100644 index 1649abc66..000000000 --- a/app/views/give-or-refuse-consent/form/parent.njk +++ /dev/null @@ -1,69 +0,0 @@ -{% extends "_layouts/form.njk" %} - -{% set title = __("consent.parent.title") %} - -{% block form %} - {{ appHeading({ - title: title - }) }} - - {{ input({ - label: { text: __("consent.parent.fullName.label") }, - decorate: "consent.parent_.fullName" - }) }} - - {%- set fosterCarerHtml = radios({ - fieldset: { - legend: { text: __("consent.parent.hasParentalResponsibility.label") } - }, - hint: { text: __("consent.parent.hasParentalResponsibility.hint") }, - items: getBooleanItems(), - decorate: "consent.parent_.hasParentalResponsibility" - }) %} - - {%- set otherHtml = input({ - label: { text: __("consent.parent.relationshipOther.label") }, - decorate: "consent.parent_.relationshipOther" - }) + radios({ - fieldset: { - legend: { text: __("consent.parent.hasParentalResponsibility.label") } - }, - hint: { text: __("consent.parent.hasParentalResponsibility.hint") }, - items: getBooleanItems(), - decorate: "consent.parent_.hasParentalResponsibility" - }) %} - - {# Add conditional html for ‘Foster carer’ option #} - {% set items = injectConditionalHtml(parentalRelationshipItems, ParentalRelationship.Fosterer, fosterCarerHtml) %} - - {# Add conditional html for ‘Other’ option #} - {% set items = injectConditionalHtml(items, ParentalRelationship.Other, otherHtml) %} - - {{ radios({ - fieldset: { - legend: { text: __("consent.parent.relationship.label") } - }, - items: items, - decorate: "consent.parent_.relationship" - }) }} - - {{ input({ - label: { text: __("consent.parent.email.label") }, - hint: { text: __("consent.parent.email.hint") }, - decorate: "consent.parent_.email" - }) }} - - {{ input({ - label: { text: __("consent.parent.tel.label") + " (optional)" }, - hint: { text: __("consent.parent.tel.hint") }, - decorate: "consent.parent_.tel" - }) }} - - {{ checkboxes({ - items: [{ - text: __("consent.parent.sms.label"), - value: true - }], - decorate: "consent.parent_.sms" - }) }} -{% endblock %} diff --git a/app/views/parent/form/edit.njk b/app/views/parent/form/edit.njk deleted file mode 100644 index a83ee29c8..000000000 --- a/app/views/parent/form/edit.njk +++ /dev/null @@ -1,70 +0,0 @@ -{% extends "_layouts/form.njk" %} - -{% set confirmButtonText = __("parent." + type + ".confirm") %} -{% set title = __("parent." + type + ".title") %} - -{% block form %} - {{ appHeading({ - caption: parent.patient.fullName, - title: title - }) }} - - {{ input({ - label: { text: __("parent.fullName.label") }, - decorate: "parent.fullName" - }) }} - - {{ radios({ - fieldset: { - legend: { text: __("parent.relationship.label") } - }, - items: [{ - text: ParentalRelationship.Mum - }, { - text: ParentalRelationship.Dad - }, { - text: ParentalRelationship.Guardian - }, { - text: ParentalRelationship.Other, - conditional: { - html: input({ - label: { text: __("parent.relationshipOther.label") }, - decorate: "parent.relationshipOther" - }) - } - }], - decorate: "parent.relationship" - }) }} - - {{ input({ - label: { text: __("parent.email.label") }, - decorate: "parent.email" - }) }} - - {{ input({ - label: { text: __("parent.tel.label") }, - decorate: "parent.tel" - }) }} - - {{ radios({ - fieldset: { - legend: { - text: __("parent.contactPreference.label") - } - }, - items: [{ - text: __("parent.contactPreference.yes"), - value: true, - conditional: { - html: textarea({ - label: { text: __("parent.contactPreferenceDetails.label") }, - decorate: "parent.contactPreferenceDetails" - }) - } - }, { - text: __("parent.contactPreference.no"), - value: false - }], - decorate: "parent.contactPreference" - }) }} -{% endblock %} diff --git a/app/views/patient-session/_consent.njk b/app/views/patient-session/_consent.njk index cce7b68f3..ae83c4e64 100644 --- a/app/views/patient-session/_consent.njk +++ b/app/views/patient-session/_consent.njk @@ -1,5 +1,5 @@ -{% from "../_macros/parent-email.njk" import appParentEmail with context %} -{% from "../_macros/parent-sms.njk" import appParentSms with context %} +{% from "../_macros/contact-email.njk" import appContactEmail with context %} +{% from "../_macros/contact-sms.njk" import appContactSms with context %} {% set isVaccinated = patientSession.report == PatientStatus.Vaccinated %} {% call card({ @@ -74,15 +74,15 @@ lastRowBorder: false, rows: summaryRows(reply, { email: { - value: appParentEmail(reply.parent, reply.given) - } if reply.parent.email, + value: appContactEmail(reply.contact, reply.given) + } if reply.contact.email, tel: { - value: appParentSms(reply.parent, reply.given) - } if reply.parent.tel, + value: appContactSms(reply.contact, reply.given) + } if reply.contact.tel, contactPreference: { - label: __("parent.contactPreference.label"), - value: reply.parent.contactPreferenceDetails - } if reply.parent.contactPreferenceDetails, + label: __("contact.contactPreference.label"), + value: reply.contact.contactPreferenceDetails + } if reply.contact.contactPreferenceDetails, createdAt: {}, decisionStatus: {}, vaccineCriteria: {} @@ -91,7 +91,7 @@ {% endfor %} {% elif not patientSession.patient.hasNoContactDetails and not patientSession.patient.post16 %} {# Requests that have been sent but with no response yet #} - {# We don’t create replies so use parent details instead #} + {# We don’t create replies so use contact details instead #} {{ appHeading({ level: 3, @@ -99,15 +99,15 @@ title: __("patientSession.replies.pending") }) }} - {% for parent in patientSession.patient.parents %} + {% for contact in patientSession.patient.contacts %} {{ summaryList({ card: { classes: "app-card--compact app-card--offset", - heading: parent.fullNameAndRelationship, + heading: contact.fullNameAndRelationship, headingLevel: 4 }, lastRowBorder: false, - rows: summaryRows(parent, { + rows: summaryRows(contact, { email: {}, tel: {}, contactPreference: {}, @@ -120,7 +120,7 @@ }) } }) - }) if parent.email or parent.tel }} + }) if contact.email or contact.tel }} {% endfor %} {% endif %} {% endcall %} diff --git a/app/views/patient/contacts.njk b/app/views/patient/contacts.njk index ca221f0f8..3b4b89157 100644 --- a/app/views/patient/contacts.njk +++ b/app/views/patient/contacts.njk @@ -27,27 +27,27 @@ }) }} {{ actionLink({ - text: __("parent.new.label"), - href: "/parents/new?patient_uuid=" + patient.uuid + text: __("contact.new.label"), + href: "/contacts/new?patient_uuid=" + patient.uuid }) }} - {% for parent in patient.parents %} + {% for contact in patient.contacts %} {{ summaryList({ card: { - heading: parent.fullNameAndRelationship, + heading: contact.fullNameAndRelationship, headingLevel: 4, actions: { items: [{ text: __("actions.edit"), - href: parent.uri + "/edit" + href: contact.uri + "/edit" }, { text: __("actions.delete"), - href: parent.uri + "/delete" + href: contact.uri + "/delete" }] } }, lastRowBorder: false, - rows: summaryRows(parent, { + rows: summaryRows(contact, { email: {}, tel: {}, contactPreference: {} diff --git a/app/views/pds/form/result.njk b/app/views/pds/form/result.njk index b6dec1f82..125da300d 100644 --- a/app/views/pds/form/result.njk +++ b/app/views/pds/form/result.njk @@ -24,7 +24,7 @@ adjustments: {}, address: {}, gpSurgery: {}, - parents: {} + contacts: {} }) }) }} diff --git a/app/views/reply/form/check-answers.njk b/app/views/reply/form/check-answers.njk index bc009d1b8..7bc48c0d5 100644 --- a/app/views/reply/form/check-answers.njk +++ b/app/views/reply/form/check-answers.njk @@ -60,31 +60,31 @@ {{ summaryList({ card: { - heading: __("parent.label"), + heading: __("contact.label"), headingSize: "m" }, lastRowBorder: false, - rows: summaryRows(reply.parent, { + rows: summaryRows(reply.contact, { fullName: { - href: reply.uri + "/new/parent" + href: reply.uri + "/new/contact" }, relationship: { - href: reply.uri + "/new/parent" + href: reply.uri + "/new/contact" }, hasParentalResponsibility: { - href: reply.uri + "/new/parent" + href: reply.uri + "/new/contact" }, notify: { - href: reply.uri + "/new/notify-parent" + href: reply.uri + "/new/notify-contact" }, email: { - href: reply.uri + "/new/parent" + href: reply.uri + "/new/contact" }, tel: { - href: reply.uri + "/new/parent" + href: reply.uri + "/new/contact" } }) - }) if reply.parent.relationship or reply.parent.notify !== undefined }} + }) if reply.contact.relationship or reply.contact.notify !== undefined }} {{ summaryList({ classes: "app-summary-list--full-width", diff --git a/app/views/reply/form/contact.njk b/app/views/reply/form/contact.njk new file mode 100644 index 000000000..5b8de712e --- /dev/null +++ b/app/views/reply/form/contact.njk @@ -0,0 +1,79 @@ +{% extends "_layouts/form.njk" %} + +{% if reply.contact.fullName %} + {% set title = __("reply.contact.title.edit", { contact: reply.contact }) %} +{% else %} + {% set title = __("reply.contact.title.new") %} +{% endif %} + +{% block form %} + {{ appHeading({ + caption: patient.fullName, + title: title + }) }} + + {{ input({ + label: { text: __("contact.fullName.label") }, + decorate: "reply.contact.fullName" + }) }} + + {# `pop` removes `Other` and `Unknown` from ParentalRelationship array #} + {{ radios({ + fieldset: { + legend: { text: __("contact.relationship.label") } + }, + items: enumItems(ParentalRelationship) | pop | pop | push({ + text: ParentalRelationship.Other, + conditional: { + html: input({ + label: { text: __("contact.relationshipOther.label") }, + decorate: "reply.contact.relationshipOther" + }) + radios({ + fieldset: { + legend: { text: __("contact.hasParentalResponsibility.label") } + }, + hint: { text: __("contact.hasParentalResponsibility.hint") }, + items: [{ + text: "Yes" + }, { + text: "No" + }], + decorate: "reply.contact.hasParentalResponsibility" + }) + } + }), + decorate: "reply.contact.relationship" + }) }} + + {{ input({ + label: { text: __("contact.email.label") }, + decorate: "reply.contact.email" + }) }} + + {{ input({ + label: { text: __("contact.tel.label") + " (optional)" }, + decorate: "reply.contact.tel" + }) }} + + {{ radios({ + fieldset: { + legend: { + text: __("contact.contactPreference.label") + } + }, + items: [{ + text: __("contact.contactPreference.yes"), + value: true, + conditional: { + html: textarea({ + label: { text: __("contact.contactPreferenceDetails.label") }, + decorate: "reply.contact.contactPreferenceDetails" + }) + } + }, { + text: __("contact.contactPreference.no"), + value: false + }], + decorate: "reply.contact.contactPreference" + }) }} +{% endblock %} diff --git a/app/views/reply/form/decision.njk b/app/views/reply/form/decision.njk index fbe553b1d..97f8ca68b 100644 --- a/app/views/reply/form/decision.njk +++ b/app/views/reply/form/decision.njk @@ -3,7 +3,7 @@ {% if reply.selfConsent %} {% set title = __("reply.decision.title.Child", { programme: programme }) %} {% else %} - {% set title = __("reply.decision.title.Parent", { programme: programme, patient: patient }) %} + {% set title = __("reply.decision.title.Contact", { programme: programme, patient: patient }) %} {% endif %} {% block form %} diff --git a/app/views/reply/form/follow-up.njk b/app/views/reply/form/follow-up.njk index 71822d2d1..c061b41ca 100644 --- a/app/views/reply/form/follow-up.njk +++ b/app/views/reply/form/follow-up.njk @@ -13,20 +13,20 @@ html: summaryList({ lastRowBorder: false, rows: summaryRows(reply, { - parent: { - value: reply.parent.fullNameAndRelationship + contact: { + value: reply.contact.fullNameAndRelationship }, email: { - label: __("parent.email.label"), - value: reply.parent.email + label: __("contact.email.label"), + value: reply.contact.email }, tel: { - label: __("parent.tel.label"), - value: reply.parent.tel + label: __("contact.tel.label"), + value: reply.contact.tel }, contactPreference: { - label: __("parent.contactPreference.label"), - value: reply.parent.contactPreference + label: __("contact.contactPreference.label"), + value: reply.contact.contactPreference }, refusalReason: {}, refusalReasonDetails: {} diff --git a/app/views/reply/form/notify-parent.njk b/app/views/reply/form/notify-contact.njk similarity index 76% rename from app/views/reply/form/notify-parent.njk rename to app/views/reply/form/notify-contact.njk index 55272aee9..78fcf5c07 100644 --- a/app/views/reply/form/notify-parent.njk +++ b/app/views/reply/form/notify-contact.njk @@ -1,6 +1,6 @@ {% extends "_layouts/form.njk" %} -{% set title = __("parent.notify.title") %} +{% set title = __("contact.notify.title") %} {% block form %} {{ radios({ @@ -13,6 +13,6 @@ } }, items: getBooleanItems(), - decorate: "reply.parent.notify" + decorate: "reply.contact.notify" }) }} {% endblock %} diff --git a/app/views/reply/form/parent.njk b/app/views/reply/form/parent.njk deleted file mode 100644 index 750c7edc5..000000000 --- a/app/views/reply/form/parent.njk +++ /dev/null @@ -1,79 +0,0 @@ -{% extends "_layouts/form.njk" %} - -{% if reply.parent.fullName %} - {% set title = __("reply.parent.title.edit", { parent: reply.parent }) %} -{% else %} - {% set title = __("reply.parent.title.new") %} -{% endif %} - -{% block form %} - {{ appHeading({ - caption: patient.fullName, - title: title - }) }} - - {{ input({ - label: { text: __("parent.fullName.label") }, - decorate: "reply.parent.fullName" - }) }} - - {# `pop` removes `Other` and `Unknown` from ParentalRelationship array #} - {{ radios({ - fieldset: { - legend: { text: __("parent.relationship.label") } - }, - items: enumItems(ParentalRelationship) | pop | pop | push({ - text: ParentalRelationship.Other, - conditional: { - html: input({ - label: { text: __("parent.relationshipOther.label") }, - decorate: "reply.parent.relationshipOther" - }) + radios({ - fieldset: { - legend: { text: __("parent.hasParentalResponsibility.label") } - }, - hint: { text: __("parent.hasParentalResponsibility.hint") }, - items: [{ - text: "Yes" - }, { - text: "No" - }], - decorate: "reply.parent.hasParentalResponsibility" - }) - } - }), - decorate: "reply.parent.relationship" - }) }} - - {{ input({ - label: { text: __("parent.email.label") }, - decorate: "reply.parent.email" - }) }} - - {{ input({ - label: { text: __("parent.tel.label") + " (optional)" }, - decorate: "reply.parent.tel" - }) }} - - {{ radios({ - fieldset: { - legend: { - text: __("parent.contactPreference.label") - } - }, - items: [{ - text: __("parent.contactPreference.yes"), - value: true, - conditional: { - html: textarea({ - label: { text: __("parent.contactPreferenceDetails.label") }, - decorate: "reply.parent.contactPreferenceDetails" - }) - } - }, { - text: __("parent.contactPreference.no"), - value: false - }], - decorate: "reply.parent.contactPreference" - }) }} -{% endblock %} diff --git a/app/views/reply/form/programme.njk b/app/views/reply/form/programme.njk index 73ebc361d..0183f5440 100644 --- a/app/views/reply/form/programme.njk +++ b/app/views/reply/form/programme.njk @@ -3,7 +3,7 @@ {% if reply.selfConsent %} {% set title = __("reply.programme.title.Child") %} {% else %} - {% set title = __("reply.programme.title.Parent") %} + {% set title = __("reply.programme.title.Contact") %} {% endif %} {% block form %} diff --git a/app/views/reply/form/refusal-notification.njk b/app/views/reply/form/refusal-notification.njk index 91cda2424..e63bf1d99 100644 --- a/app/views/reply/form/refusal-notification.njk +++ b/app/views/reply/form/refusal-notification.njk @@ -1,6 +1,6 @@ {% extends "_layouts/form.njk" %} -{% set title = __("reply.parent.notify.title", { parent: reply.parent }) %} +{% set title = __("reply.contact.notify.title", { contact: reply.contact }) %} {% block form %} {{ radios({ @@ -14,6 +14,6 @@ } }, items: getBooleanItems(), - decorate: "reply.parent.notify" + decorate: "reply.contact.notify" }) }} {% endblock %} diff --git a/app/views/reply/show.njk b/app/views/reply/show.njk index 78d84744e..5ee1b4b61 100644 --- a/app/views/reply/show.njk +++ b/app/views/reply/show.njk @@ -67,28 +67,28 @@ {{ summaryList({ card: { - heading: __("parent.label"), + heading: __("contact.label"), headingSize: "m" }, lastRowBorder: false, - rows: summaryRows(reply.parent, { + rows: summaryRows(reply.contact, { fullName: { - href: reply.uri + "/edit/parent" + href: reply.uri + "/edit/contact" }, relationship: { - href: reply.uri + "/edit/parent" + href: reply.uri + "/edit/contact" }, hasParentalResponsibility: {}, email: { - href: reply.uri + "/edit/parent" + href: reply.uri + "/edit/contact" }, tel: { - href: reply.uri + "/edit/parent" + href: reply.uri + "/edit/contact" }, sms: {}, contactPreference: {} }) - }) if reply.parent }} + }) if reply.contact }} {{ summaryList({ card: { diff --git a/app/views/session/reminders.njk b/app/views/session/reminders.njk index 2c756aafc..4a2409906 100644 --- a/app/views/session/reminders.njk +++ b/app/views/session/reminders.njk @@ -51,7 +51,7 @@ level: 3, size: "s", title: __("session.reminders.activity", { - parents: session.tally(session.programmes[0].id, PatientStatus.Consent), + contacts: session.tally(session.programmes[0].id, PatientStatus.Consent), patients: session.tally(session.programmes[0].id) }) }) }} diff --git a/lib/create-data.js b/lib/create-data.js index 01b89657e..a29671441 100644 --- a/lib/create-data.js +++ b/lib/create-data.js @@ -31,9 +31,9 @@ import { generateClinicAppointment } from '../app/generators/clinic-appointment. import { generateEmptyClinicBooking } from '../app/generators/clinic-booking.js' import { generateClinicVaccinationPeriods } from '../app/generators/clinic-vaccination-periods.js' import { generateConsent } from '../app/generators/consent.js' +import { generateContact } from '../app/generators/contact.js' import { generateInstruction } from '../app/generators/instruction.js' import { generateNotice } from '../app/generators/notice.js' -import { generateParent } from '../app/generators/parent.js' import { generatePatient } from '../app/generators/patient.js' import { generatePDSRecord } from '../app/generators/pds-record.js' import { generateSession } from '../app/generators/session.js' @@ -127,23 +127,23 @@ Array.from([...range(0, totalBatches)]).forEach(() => { context.batches[batch.id] = batch }) -// Parents -context.parents = {} +// Contacts +context.contacts = {} // Patients context.patients = {} Array.from([...range(0, totalPatients)]).forEach(() => { const patient = generatePatient() - // Parents - const parent1 = generateParent(patient, true) - context.parents[parent1.uuid] = parent1 - patient.parent_uuids.push(parent1.uuid) + // Contacts + const contact1 = generateContact(patient, true) + context.contacts[contact1.uuid] = contact1 + patient.contact_uuids.push(contact1.uuid) if (faker.datatype.boolean(0.5)) { - const parent2 = generateParent(patient) - context.parents[parent2.uuid] = parent2 - patient.parent_uuids.push(parent2.uuid) + const contact2 = generateContact(patient) + context.contacts[contact2.uuid] = contact2 + patient.contact_uuids.push(contact2.uuid) } context.patients[patient.uuid] = patient @@ -154,15 +154,15 @@ context.pdsRecords = {} Array.from([...range(0, 20)]).forEach(() => { const pdsRecord = generatePDSRecord() - // Parents - const parent1 = generateParent(pdsRecord, true) - context.parents[parent1.uuid] = parent1 - pdsRecord.parent_uuids.push(parent1.uuid) + // Contacts + const contact1 = generateContact(pdsRecord, true) + context.contacts[contact1.uuid] = contact1 + pdsRecord.contact_uuids.push(contact1.uuid) if (faker.datatype.boolean(0.5)) { - const parent2 = generateParent(pdsRecord) - context.parents[parent2.uuid] = parent2 - pdsRecord.parent_uuids.push(parent2.uuid) + const contact2 = generateContact(pdsRecord) + context.contacts[contact2.uuid] = contact2 + pdsRecord.contact_uuids.push(contact2.uuid) } context.pdsRecords[pdsRecord.uuid] = pdsRecord @@ -324,23 +324,23 @@ for (const patientSession of Object.values(context.patientSessions)) { Array.from([...range(0, maxReplies)]).forEach((_, index) => { let lastConsentCreatedAt - const parent = generateParent(patient, index === 0) + const contact = generateContact(patient, index === 0) // If telephone number provided, sometimes add a communication need - if (parent.tel && faker.datatype.boolean(0.2)) { - parent.contactPreference = true - parent.contactPreferenceDetails = + if (contact.tel && faker.datatype.boolean(0.2)) { + contact.contactPreference = true + contact.contactPreferenceDetails = 'I sometimes have difficulty hearing phone calls, so it’s best to send me a text message.' } - context.parents[parent.uuid] = parent + context.contacts[contact.uuid] = contact for (programme of session.programmes) { const consent = generateConsent( programme, session, patientSession, - parent, + contact, lastConsentCreatedAt ) @@ -728,10 +728,10 @@ if (vaccinatedPatient) { generateDataFile('.data/batches.json', context.batches) generateDataFile('.data/clinic-bookings.json', context.clinicBookings) generateDataFile('.data/clinics.json', context.clinics) +generateDataFile('.data/contacts.json', context.contacts) generateDataFile('.data/instructions.json', context.instructions) generateDataFile('.data/moves.json', context.moves) generateDataFile('.data/notices.json', context.notices) -generateDataFile('.data/parents.json', context.parents) generateDataFile('.data/patients.json', context.patients) generateDataFile('.data/patient-sessions.json', context.patientSessions) generateDataFile('.data/pds-records.json', context.pdsRecords) From 23c2c7340d2e589ecb75664dd086de2e9093671e Mon Sep 17 00:00:00 2001 From: Paul Robert Lloyd Date: Thu, 14 May 2026 16:10:49 +0100 Subject: [PATCH 8/8] Add comment about purpose of Reply.contact_ property --- app/models/reply.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/models/reply.js b/app/models/reply.js index 71916197b..983b1f304 100644 --- a/app/models/reply.js +++ b/app/models/reply.js @@ -85,12 +85,16 @@ export class Reply { this.method = options?.method this.selfConsent = options?.selfConsent this.note = options?.note || '' - this.contact_ = options?.contact_ && new Contact(options.contact_) this.contact_uuid = options?.contact_uuid this.patient_uuid = options?.patient_uuid this.programme_id = options?.programme_id this.session_id = options?.session_id + // For reasons of simplicity, we use `contact_` this to store contact + // details in the parental consent journey. + // TODO: Find out why contact() setter doesn’t work for this purpose + this.contact_ = options?.contact_ && new Contact(options.contact_) + // Some values only valid if the consent request was received if (this.delivered) { this.decision =