From 2a8c48a18445588dcfbbdaa02ab2b52cebc2a658 Mon Sep 17 00:00:00 2001 From: Caitlin Roach Date: Tue, 16 Jun 2026 16:14:41 +0100 Subject: [PATCH 1/4] Add empty state for no pharmacies --- app/routes/prototype-admin.js | 14 +++ .../includes/_preset-scenarios-table.html | 8 ++ app/views/pharmacies/index.html | 116 +++++++++--------- 3 files changed, 81 insertions(+), 57 deletions(-) diff --git a/app/routes/prototype-admin.js b/app/routes/prototype-admin.js index b2401748..2bd6777f 100644 --- a/app/routes/prototype-admin.js +++ b/app/routes/prototype-admin.js @@ -564,6 +564,20 @@ module.exports = (router) => { res.redirect('/home') }) + // ---------------------------------------------------------------- + // Preset: Pharmacy HQ, no data (Amanda White, P15951) + // ---------------------------------------------------------------- + + router.get('/prototype-setup/preset/pharmacy-hq-no-data', (req, res) => { + resetSession(req) + const data = req.session.data + data.currentUserId = '6424325235325' + data.currentOrganisationId = 'P15951' + // Remove all pharmacies belonging to P15951 to start with a clean slate + data.organisations = data.organisations.filter(org => org.companyId !== 'P15951' || org.type === 'Pharmacy HQ') + res.redirect('/home') + }) + // ---------------------------------------------------------------- // Preset: Recorder, single organisation (Ocean Merritt, FR4V56) // ---------------------------------------------------------------- diff --git a/app/views/includes/_preset-scenarios-table.html b/app/views/includes/_preset-scenarios-table.html index 3d9bd63b..05a98cf7 100644 --- a/app/views/includes/_preset-scenarios-table.html +++ b/app/views/includes/_preset-scenarios-table.html @@ -71,6 +71,14 @@ role: "Group administrator", scenarioDetails: "Group admin for a large pharmacy chain (same-name pharmacies)." }, + { + presetLabel: "Group admin, pharmacy HQ (no data)", + presetPath: "/prototype-setup/preset/pharmacy-hq-no-data", + userName: "Amanda White", + userEmail: "amanda.white@nhs.net", + role: "Group administrator", + scenarioDetails: "Group admin for a pharmacy HQ with no pre-loaded data, pharmacies or users." + }, { presetLabel: "Regional lead", presetPath: "/prototype-setup/preset/regions", diff --git a/app/views/pharmacies/index.html b/app/views/pharmacies/index.html index b033a9bf..ac9ab6ff 100644 --- a/app/views/pharmacies/index.html +++ b/app/views/pharmacies/index.html @@ -43,65 +43,67 @@

Pharmacies

href: "/pharmacies/select" }) }} - - -
Pharmacies added ({{ organisations | length }})
- {% if (organisations | length) > 10 %} -
- - -
- {% endif %} - - - - - - - - - - - - {% for organisation in organisations %} - - - - - - + {% if (organisations | length) > 0 %} +
- Name - - Vaccines - - Users - - Status - - Actions -
- {{ organisation.name }} ({{ organisation.id}}) - - {% set vaccinesEnabled = [] %} - {% for vaccine in organisation.vaccines %} - {% if vaccine.status == "enabled" %} - {% set vaccinesEnabled = (vaccinesEnabled.push(vaccine.name), vaccinesEnabled) %} - {% endif %} - {% endfor %} - - {{ (vaccinesEnabled | sort | join(", ")) | capitaliseFirstLetter }} - - {{ organisationUserCounts[organisation.id] }} - - {{ organisation.status }} - - Manage -
+ +
Pharmacies added ({{ organisations | length }})
+ {% if (organisations | length) > 10 %} +
+ + +
+ {% endif %} + + + + + + + + - {% endfor %} + + + {% for organisation in organisations %} + + + + + + + + {% endfor %} - -
+ Name + + Vaccines + + Users + + Status + + Actions +
+ {{ organisation.name }} ({{ organisation.id}}) + + {% set vaccinesEnabled = [] %} + {% for vaccine in organisation.vaccines %} + {% if vaccine.status == "enabled" %} + {% set vaccinesEnabled = (vaccinesEnabled.push(vaccine.name), vaccinesEnabled) %} + {% endif %} + {% endfor %} + + {{ (vaccinesEnabled | sort | join(", ")) | capitaliseFirstLetter }} + + {{ organisationUserCounts[organisation.id] }} + + {{ organisation.status }} + + Manage +
+ + + {% endif %} {% if closedOrganisations.length > 0 %}
From 7d21a4028c3182a081a1d89cce48be0ddf18a1b2 Mon Sep 17 00:00:00 2001 From: Caitlin Roach Date: Wed, 9 Sep 2026 13:39:53 +0100 Subject: [PATCH 2/4] vaccs records added --- app/data/vaccinations-recorded.js | 264 +++++++++++++++++++++ app/routes/vaccines.js | 49 +++- app/views/vaccines/edit-batch.html | 4 +- app/views/vaccines/product-page.html | 19 ++ app/views/vaccines/update-batch-check.html | 73 ++++++ 5 files changed, 405 insertions(+), 4 deletions(-) create mode 100644 app/views/vaccines/update-batch-check.html diff --git a/app/data/vaccinations-recorded.js b/app/data/vaccinations-recorded.js index c5de4ae2..76a4257b 100644 --- a/app/data/vaccinations-recorded.js +++ b/app/data/vaccinations-recorded.js @@ -142,6 +142,270 @@ module.exports = [ injectionSite: "Left arm", notes: "", editable: true + }, + + // Additional records below ensure every batch at Jane Smith's organisation + // (Central Manchester Trust, RW3) has at least 2 vaccination records + + { + id: "700001", + date: { day: "24", month: "11", year: "2025" }, + vaccine: "COVID-19", + vaccineProduct: "Comirnaty 3 LP.8.1", + patient: { + name: "Priya Patel", + nhsNumber: "9111222333" + }, + batchNumber: "8535345", // Batch AB2514 + batchExpiryDate: "2028-03-06", + organisationId: "RW3", // Central Manchester Trust + siteId: "RW3NM", // North Manchester General Hospital + vaccinatorId: "2387441662601", // Jane Smith + eligibility: "Based on age", + consent: "patient", + injectionSite: "Right arm", + notes: "", + editable: true + }, + { + id: "700002", + date: { day: "24", month: "11", year: "2025" }, + vaccine: "flu", + vaccineProduct: "Adjuvanted Trivalent Influenza Vaccine (aTIV)", + patient: { + name: "Michael Osei", + nhsNumber: "9111222334" + }, + batchNumber: "634643636", // Batch 914-151 + batchExpiryDate: "2028-02-19", + organisationId: "RW3", // Central Manchester Trust + siteId: "RW3NM", // North Manchester General Hospital + vaccinatorId: "2387441662601", // Jane Smith + eligibility: "Based on age", + consent: "patient", + injectionSite: "Left arm", + notes: "", + editable: true + }, + { + id: "700003", + date: { day: "23", month: "11", year: "2025" }, + vaccine: "flu", + vaccineProduct: "Adjuvanted Trivalent Influenza Vaccine (aTIV)", + patient: { + name: "Sarah Kowalski", + nhsNumber: "9111222335" + }, + batchNumber: "634643636", // Batch 914-151 + batchExpiryDate: "2028-02-19", + organisationId: "RW3", // Central Manchester Trust + siteId: "RW3NM", // North Manchester General Hospital + vaccinatorId: "2387441662601", // Jane Smith + eligibility: "Based on age", + consent: "patient", + injectionSite: "Right arm", + notes: "", + editable: true + }, + { + id: "700004", + date: { day: "22", month: "11", year: "2025" }, + vaccine: "COVID-19", + vaccineProduct: "Comirnaty 3 LP.8.1", + patient: { + name: "Liam O'Connor", + nhsNumber: "9111222336" + }, + batchNumber: "343643636", // Batch NT2524 + batchExpiryDate: "2028-03-06", + organisationId: "RW3", // Central Manchester Trust + siteId: "RW3HT", // Heaton Moor Health Centre + vaccinatorId: "2387441662601", // Jane Smith + eligibility: "Based on age", + consent: "patient", + injectionSite: "Left arm", + notes: "", + editable: true + }, + { + id: "700005", + date: { day: "21", month: "11", year: "2025" }, + vaccine: "COVID-19", + vaccineProduct: "Comirnaty 3 LP.8.1", + patient: { + name: "Grace Nakamura", + nhsNumber: "9111222337" + }, + batchNumber: "343643636", // Batch NT2524 + batchExpiryDate: "2028-03-06", + organisationId: "RW3", // Central Manchester Trust + siteId: "RW3HT", // Heaton Moor Health Centre + vaccinatorId: "2387441662601", // Jane Smith + eligibility: "Based on age", + consent: "patient", + injectionSite: "Right arm", + notes: "", + editable: true + }, + { + id: "700006", + date: { day: "20", month: "11", year: "2025" }, + vaccine: "pertussis", + vaccineProduct: "Adacel vaccine suspension", + patient: { + name: "Daniel Wojcik", + nhsNumber: "9111222338" + }, + batchNumber: "46464363", // Batch M8-25-14 + batchExpiryDate: "2027-11-19", + organisationId: "RW3", // Central Manchester Trust + siteId: "RW3HT", // Heaton Moor Health Centre + vaccinatorId: "2387441662601", // Jane Smith + eligibility: "Based on age", + consent: "patient", + injectionSite: "Left arm", + notes: "", + editable: true + }, + { + id: "700007", + date: { day: "19", month: "11", year: "2025" }, + vaccine: "pertussis", + vaccineProduct: "Adacel vaccine suspension", + patient: { + name: "Freya Thomsen", + nhsNumber: "9111222339" + }, + batchNumber: "46464363", // Batch M8-25-14 + batchExpiryDate: "2027-11-19", + organisationId: "RW3", // Central Manchester Trust + siteId: "RW3HT", // Heaton Moor Health Centre + vaccinatorId: "2387441662601", // Jane Smith + eligibility: "Based on age", + consent: "patient", + injectionSite: "Right arm", + notes: "", + editable: true + }, + { + id: "700008", + date: { day: "18", month: "11", year: "2025" }, + vaccine: "flu", + vaccineProduct: "Adjuvanted Trivalent Influenza Vaccine (aTIV)", + patient: { + name: "Tomasz Nowak", + nhsNumber: "9111222340" + }, + batchNumber: "25523523", // Batch 194-151 + batchExpiryDate: "2027-12-04", + organisationId: "RW3", // Central Manchester Trust + siteId: "RW3HT", // Heaton Moor Health Centre + vaccinatorId: "2387441662601", // Jane Smith + eligibility: "Based on age", + consent: "patient", + injectionSite: "Left arm", + notes: "", + editable: true + }, + { + id: "700009", + date: { day: "17", month: "11", year: "2025" }, + vaccine: "flu", + vaccineProduct: "Adjuvanted Trivalent Influenza Vaccine (aTIV)", + patient: { + name: "Amara Okafor", + nhsNumber: "9111222341" + }, + batchNumber: "25523523", // Batch 194-151 + batchExpiryDate: "2027-12-04", + organisationId: "RW3", // Central Manchester Trust + siteId: "RW3HT", // Heaton Moor Health Centre + vaccinatorId: "2387441662601", // Jane Smith + eligibility: "Based on age", + consent: "patient", + injectionSite: "Right arm", + notes: "", + editable: true + }, + { + id: "700010", + date: { day: "16", month: "11", year: "2025" }, + vaccine: "RSV", + vaccineProduct: "Abrysvo", + patient: { + name: "Harold Fitzgerald", + nhsNumber: "9111222342" + }, + batchNumber: "46436346", // Batch ANEUEC-91 + batchExpiryDate: "2027-11-23", + organisationId: "RW3", // Central Manchester Trust + siteId: "RW3HT", // Heaton Moor Health Centre + vaccinatorId: "2387441662601", // Jane Smith + eligibility: "Based on age", + consent: "patient", + injectionSite: "Left arm", + notes: "", + editable: true + }, + { + id: "700011", + date: { day: "15", month: "11", year: "2025" }, + vaccine: "RSV", + vaccineProduct: "Abrysvo", + patient: { + name: "Beatrice Lindqvist", + nhsNumber: "9111222343" + }, + batchNumber: "46436346", // Batch ANEUEC-91 + batchExpiryDate: "2027-11-23", + organisationId: "RW3", // Central Manchester Trust + siteId: "RW3HT", // Heaton Moor Health Centre + vaccinatorId: "2387441662601", // Jane Smith + eligibility: "Based on age", + consent: "patient", + injectionSite: "Right arm", + notes: "", + editable: true + }, + { + id: "700012", + date: { day: "14", month: "11", year: "2025" }, + vaccine: "COVID-19", + vaccineProduct: "Comirnaty 3 LP.8.1", + patient: { + name: "Ellis Marsh", + nhsNumber: "9111222344" + }, + batchNumber: "325252145", // Batch PN8471 + batchExpiryDate: "2024-02-06", + organisationId: "RW3", // Central Manchester Trust + siteId: "RW07E", // Central Manchester Medical Centre + vaccinatorId: "2387441662601", // Jane Smith + eligibility: "Based on age", + consent: "patient", + injectionSite: "Left arm", + notes: "", + editable: true + }, + { + id: "700013", + date: { day: "13", month: "11", year: "2025" }, + vaccine: "COVID-19", + vaccineProduct: "Comirnaty 3 LP.8.1", + patient: { + name: "Nadia Hussain", + nhsNumber: "9111222345" + }, + batchNumber: "325252145", // Batch PN8471 + batchExpiryDate: "2024-02-06", + organisationId: "RW3", // Central Manchester Trust + siteId: "RW07E", // Central Manchester Medical Centre + vaccinatorId: "2387441662601", // Jane Smith + eligibility: "Based on age", + consent: "patient", + injectionSite: "Right arm", + notes: "", + editable: true } ] diff --git a/app/routes/vaccines.js b/app/routes/vaccines.js index a799cb13..01d15ec2 100644 --- a/app/routes/vaccines.js +++ b/app/routes/vaccines.js @@ -309,6 +309,8 @@ module.exports = (router) => { const addedType = req.query.addedType const deactivated = req.query.deactivated === 'true' const reactivated = req.query.reactivated === 'true' + const updated = req.query.updated === 'true' + const updatedRecordsCount = parseInt(req.query.updatedRecordsCount) || 0 const updatedBatchNumber = req.query.batchNumber const vaccine = data.vaccineStock.find((vaccine) => vaccine.id === req.params.id) @@ -370,6 +372,8 @@ module.exports = (router) => { addedType, deactivated, reactivated, + updated, + updatedRecordsCount, updatedBatchNumber }) }) @@ -450,6 +454,30 @@ module.exports = (router) => { }) }) + // Checking how many records will be affected by an update to a batch + router.post('/vaccines/:vaccineId/:batchNumber/update-check', (req, res) => { + + const data = req.session.data + const vaccine = data.vaccineStock.find((vaccine) => vaccine.id === req.params.vaccineId) + if (!vaccine) { res.redirect('/vaccines'); return } + const batch = vaccine.batches.find((batch) => batch.batchNumber === req.params.batchNumber) + if (!batch) { res.redirect(`/vaccines/${vaccine.id}`); return } + + const affectedRecordsCount = (data.vaccinationsRecorded || []).filter((record) => { + return record.organisationId === vaccine.organisationId && + record.siteId === vaccine.siteId && + record.vaccine === vaccine.vaccine && + record.vaccineProduct === vaccine.vaccineProduct && + record.batchNumber === batch.id + }).length + + res.render('vaccines/update-batch-check', { + vaccine, + batch, + affectedRecordsCount + }) + }) + // Updating a batch router.post('/vaccines/:vaccineId/:batchNumber/update', (req, res) => { @@ -461,10 +489,29 @@ module.exports = (router) => { const expiryDate = new Date(data.batchExpiryDate.year, (parseInt(data.batchExpiryDate.month) - 1), data.batchExpiryDate.day, 12).toISOString().substring(0,10) + let updatedRecordsCount = 0 + + if (data.updateExistingRecords === 'yes') { + for (const record of (data.vaccinationsRecorded || [])) { + if (record.organisationId === vaccine.organisationId && + record.siteId === vaccine.siteId && + record.vaccine === vaccine.vaccine && + record.vaccineProduct === vaccine.vaccineProduct && + record.batchNumber === batch.id) { + record.batchExpiryDate = expiryDate + updatedRecordsCount++ + } + } + } + + const updatedBatchNumber = data.batchNumber + batch.batchNumber = data.batchNumber; batch.expiryDate = expiryDate; - res.redirect('/vaccines/' + vaccine.id) + req.session.data.updateExistingRecords = '' + + res.redirect(`/vaccines/${vaccine.id}?updated=true&batchNumber=${encodeURIComponent(updatedBatchNumber)}&updatedRecordsCount=${updatedRecordsCount}`) }) // View page to deactivate a batch diff --git a/app/views/vaccines/edit-batch.html b/app/views/vaccines/edit-batch.html index 44f417cb..4db56248 100644 --- a/app/views/vaccines/edit-batch.html +++ b/app/views/vaccines/edit-batch.html @@ -14,9 +14,7 @@

Edit batch

-

Your changes will only apply to vaccination records saved in future.

- -
+ {{ input({ "label": { diff --git a/app/views/vaccines/product-page.html b/app/views/vaccines/product-page.html index 30e234ed..7091d327 100644 --- a/app/views/vaccines/product-page.html +++ b/app/views/vaccines/product-page.html @@ -64,6 +64,25 @@

}) }} {% endif %} + {% if updated %} + {% set html %} +

+ Batch edited +

+

+ {{ ("Batch " + updatedBatchNumber + " has been edited.") if updatedBatchNumber else "Batch has been edited." }} + {% if updatedRecordsCount > 0 %} + {{ updatedRecordsCount }} vaccination record{{ "s" if updatedRecordsCount != 1 }} {{ "was" if updatedRecordsCount == 1 else "were" }} also updated to use the new batch details. + {% endif %} +

+ {% endset %} + + {{ notificationBanner({ + html: html, + type: "success" + }) }} + {% endif %} +

{{ vaccine.vaccineProduct }}

{{ summaryList({ diff --git a/app/views/vaccines/update-batch-check.html b/app/views/vaccines/update-batch-check.html new file mode 100644 index 00000000..95e3895c --- /dev/null +++ b/app/views/vaccines/update-batch-check.html @@ -0,0 +1,73 @@ +{% extends 'layout.html' %} + +{% set pageName = "Update existing records for this batch?" %} + +{% set currentSection = "vaccines" %} + +{% block beforeContent %} + {{ backLink({ href: "/vaccines/" + vaccine.id + "/" + batch.batchNumber, text: "Back" }) }} +{% endblock %} + +{% block content %} +
+
+ +

{{ pageName }}

+ + {% if affectedRecordsCount > 0 %} + +

+ {{ affectedRecordsCount }} existing vaccination record{{ "s" if affectedRecordsCount != 1 }} + {{ "uses" if affectedRecordsCount == 1 else "use" }} batch {{ batch.batchNumber }}. +

+ + + + {{ radios({ + "idPrefix": "update-existing-records", + "name": "updateExistingRecords", + "fieldset": { + "legend": { + "text": pageName, + classes: "nhsuk-u-visually-hidden" + } + }, + "items": [ + { + "value": "yes", + "text": "Update " + affectedRecordsCount + " record" + ("s" if affectedRecordsCount != 1) + " to use " + data.batchNumber, + hint: { + text: "This will change the batch number and expiry date shown on each existing record." + } + }, + { + "value": "no", + "text": "Leave " + affectedRecordsCount + " record" + ("s" if affectedRecordsCount != 1) + " using batch " + batch.batchNumber, + hint: { + text: "The existing records will keep showing the old batch number and expiry date. You can update the records individually through the records tab." + } + } + ] + }) }} + + {{ button({ + "text": "Continue" + }) }} + + + {% else %} + +

No vaccination records have been saved using batch {{ batch.batchNumber }}.

+ +
+ {{ button({ + "text": "Continue" + }) }} +
+ + {% endif %} + +
+
+ +{% endblock %} From 75b90678726a6cccdae6d6872f0dccee80bc8954 Mon Sep 17 00:00:00 2001 From: Caitlin Roach Date: Wed, 9 Sep 2026 13:46:41 +0100 Subject: [PATCH 3/4] Button text change and details component --- app/views/vaccines/update-batch-check.html | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/views/vaccines/update-batch-check.html b/app/views/vaccines/update-batch-check.html index 95e3895c..444ef612 100644 --- a/app/views/vaccines/update-batch-check.html +++ b/app/views/vaccines/update-batch-check.html @@ -20,6 +20,7 @@

{{ pageName }}

{{ affectedRecordsCount }} existing vaccination record{{ "s" if affectedRecordsCount != 1 }} {{ "uses" if affectedRecordsCount == 1 else "use" }} batch {{ batch.batchNumber }}.

+
@@ -51,10 +52,15 @@

{{ pageName }}

}) }} {{ button({ - "text": "Continue" + "text": "Confirm update" }) }}
+ {{ details({ + summaryText: "Where will these updates show?", + html: "

Updating this batch will also update the batch number and expiry date on any existing vaccination records that use it. It will update in the PDS and in the patient's GP record.

" + }) }} + {% else %}

No vaccination records have been saved using batch {{ batch.batchNumber }}.

From fc7f567b9c8776121c79347007c2faae57579a14 Mon Sep 17 00:00:00 2001 From: Caitlin Roach Date: Wed, 9 Sep 2026 16:15:05 +0100 Subject: [PATCH 4/4] update --- app/routes/vaccines.js | 1 + app/views/vaccines/update-batch-check.html | 81 ++++++++++++++++++++-- 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/app/routes/vaccines.js b/app/routes/vaccines.js index 01d15ec2..88367ad0 100644 --- a/app/routes/vaccines.js +++ b/app/routes/vaccines.js @@ -472,6 +472,7 @@ module.exports = (router) => { }).length res.render('vaccines/update-batch-check', { + data, vaccine, batch, affectedRecordsCount diff --git a/app/views/vaccines/update-batch-check.html b/app/views/vaccines/update-batch-check.html index 444ef612..1709e428 100644 --- a/app/views/vaccines/update-batch-check.html +++ b/app/views/vaccines/update-batch-check.html @@ -1,6 +1,6 @@ {% extends 'layout.html' %} -{% set pageName = "Update existing records for this batch?" %} +{% set pageName = "Check and confirm batch changes" %} {% set currentSection = "vaccines" %} @@ -14,13 +14,80 @@

{{ pageName }}

- {% if affectedRecordsCount > 0 %} + {% set summaryRows = [] %} + {% set newExpiryDate = data.batchExpiryDate | isoDateFromDateInput %} + + {% if data.batchNumber == batch.batchNumber %} + {% set summaryRows = (summaryRows.push({ + key: { + text: "Batch number" + }, + value: { + html: batch.batchNumber + } + }), summaryRows) %} + {% else %} + {% set summaryRows = (summaryRows.push({ + key: { + text: "Previous batch number" + }, + value: { + html: batch.batchNumber + } + }), summaryRows) %} + {% set summaryRows = (summaryRows.push({ + key: { + text: "New batch number" + }, + value: { + html: data.batchNumber + } + }), summaryRows) %} + {% endif %} -

- {{ affectedRecordsCount }} existing vaccination record{{ "s" if affectedRecordsCount != 1 }} - {{ "uses" if affectedRecordsCount == 1 else "use" }} batch {{ batch.batchNumber }}. -

- + {% if newExpiryDate == batch.expiryDate %} + {% set summaryRows = (summaryRows.push({ + key: { + text: "Expiry date" + }, + value: { + html: (batch.expiryDate | govukDate) + } + }), summaryRows) %} + {% else %} + {% set summaryRows = (summaryRows.push({ + key: { + text: "Previous expiry date" + }, + value: { + html: (batch.expiryDate | govukDate) + } + }), summaryRows) %} + {% set summaryRows = (summaryRows.push({ + key: { + text: "New expiry date" + }, + value: { + html: (newExpiryDate | govukDate) + } + }), summaryRows) %} + {% endif %} + + {% set summaryRows = (summaryRows.push({ + key: { + text: "Number of records affected" + }, + value: { + html: affectedRecordsCount + } + }), summaryRows) %} + + {{ summaryList({ + classes: "nhsuk-u-margin-bottom-6", + rows: summaryRows + }) }} + + {% if affectedRecordsCount > 0 %}