From 8f40e2a1c64d59f7b9ffded2d6db3493af4104c5 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 24 Apr 2026 13:19:23 +0100 Subject: [PATCH 01/68] Initial work on pharmacy chain mode --- app/routes.js | 1 + app/routes/pharmacies.js | 55 +++++++++ app/views/home/index.html | 2 +- app/views/includes/header.html | 15 +++ app/views/pharmacies/index.html | 58 +++++++++ app/views/pharmacies/pharmacy.html | 23 ++++ app/views/pharmacies/select.html | 186 +++++++++++++++++++++++++++++ 7 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 app/routes/pharmacies.js create mode 100644 app/views/pharmacies/index.html create mode 100644 app/views/pharmacies/pharmacy.html create mode 100644 app/views/pharmacies/select.html diff --git a/app/routes.js b/app/routes.js index db5607e4..c794b938 100644 --- a/app/routes.js +++ b/app/routes.js @@ -51,6 +51,7 @@ require('./routes/user-profile')(router) require('./routes/vaccines')(router) require('./routes/reports')(router) require('./routes/records')(router) +require('./routes/pharmacies')(router) require('./routes/prototype-admin')(router) require('./routes/lists')(router) require('./routes/support')(router) diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js new file mode 100644 index 00000000..e9375d92 --- /dev/null +++ b/app/routes/pharmacies.js @@ -0,0 +1,55 @@ +const { getPharmaciesBelongingToOrganisation, getPharmacyChains, getOrganisation } = require('../lib/ods'); + +const sortByNameThenPostcode = (getPostcode = (item) => item.postcode) => (a, b) => { + if (a.name < b.name) return -1 + if (a.name > b.name) return 1 + const postcodeA = getPostcode(a) + const postcodeB = getPostcode(b) + if (postcodeA < postcodeB) return -1 + return 1 +} + +module.exports = router => { + + router.get('/pharmacies', (req, res) => { + const data = req.session.data + const currentUser = res.locals.currentUser + + const userOrganisationIds = currentUser.organisations.map((organisation) => organisation.id) + + const organisations = data.organisations.filter((organisation) => userOrganisationIds.includes(organisation.id) ) + + + res.render('pharmacies/index', { + organisations + }) + }) + + router.get('/pharmacies/select', async (req, res) => { + const data = req.session.data + const id = req.params.id + + let pharmacies = await getPharmaciesBelongingToOrganisation("P15J") + + pharmacies = pharmacies.sort(sortByNameThenPostcode((item) => item.address.postcode)) + + + res.render('pharmacies/select', { + pharmacies + }) + }) + + + router.get('/pharmacies/:id', (req, res) => { + const data = req.session.data + const id = req.params.id + + const organisation = data.organisations.find((organisation) => organisation.id === id) + + res.render('pharmacies/pharmacy', { + organisation + }) + }) + + +} diff --git a/app/views/home/index.html b/app/views/home/index.html index fad8e48b..662883cd 100644 --- a/app/views/home/index.html +++ b/app/views/home/index.html @@ -13,7 +13,7 @@ {% include "includes/notification.html" %} -

{% if currentOrganisation %}{{ currentOrganisation.name }}{% else %}Overview{% endif %}

+

{% if currentOrganisation %}{{ currentOrganisation.name }}{% else %}Peak pharmacies{% endif %}

{% if currentOrganisation and totalVaccinationsRecorded == 0 %} diff --git a/app/views/includes/header.html b/app/views/includes/header.html index 7b92876d..8881592f 100644 --- a/app/views/includes/header.html +++ b/app/views/includes/header.html @@ -11,6 +11,21 @@ active: (currentSection == "home") }), navigationItems) %} + + {% if data.currentMode == "reports" %} + {% set navigationItems = (navigationItems.push({ + href: "/pharmacies", + text: "Pharmacies", + active: (currentSection == "pharmacies") + }), navigationItems) %} + + {% set navigationItems = (navigationItems.push({ + href: "/users", + text: "Manage users", + active: (currentSection == "user-admin") + }), navigationItems) %} + {% endif %} + {% if currentOrganisation %} {% set navigationItems = (navigationItems.push({ href: "/record-vaccinations", diff --git a/app/views/pharmacies/index.html b/app/views/pharmacies/index.html new file mode 100644 index 00000000..1fe6d32d --- /dev/null +++ b/app/views/pharmacies/index.html @@ -0,0 +1,58 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies" %} + +{% block content %} +
+
+ +

Pharmacies

+ +

Add a new pharmacy or manage an existing pharmacy.

+ + {{ button({ + text: "Add pharmacy", + href: "/pharmacies/select" + }) }} + + + + + + + + + + + + + + {% for organisation in organisations %} + + + + + + + {% endfor %} + + +
Current pharmacies
+ Name + + Vaccines + + Users +
+ {{ organisation.name }} ({{ organisation.id}}) + + COVID-19 + + 1 + + Deactivate +
+
+
+ +{% endblock %} diff --git a/app/views/pharmacies/pharmacy.html b/app/views/pharmacies/pharmacy.html new file mode 100644 index 00000000..a50639f2 --- /dev/null +++ b/app/views/pharmacies/pharmacy.html @@ -0,0 +1,23 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies" %} + +{% set pageName = organisation.name %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies" + }) }} +{% endblock %} + +{% block content %} +
+
+ +

{{ pageName }}

+ + +
+
+ +{% endblock %} diff --git a/app/views/pharmacies/select.html b/app/views/pharmacies/select.html new file mode 100644 index 00000000..0ff4eb6d --- /dev/null +++ b/app/views/pharmacies/select.html @@ -0,0 +1,186 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies" + }) }} +{% endblock %} + +{% block content %} +
+
+
+ + {% set items = [] %} + + {% for pharmacy in pharmacies %} + {% set items = (items.push({ + text: pharmacy.name + ", " + pharmacy.address.postcode + " (" + pharmacy.id + ")", + value: pharmacy.id + }), items) %} + {% endfor %} + + + {% call fieldset({ + legend: { + text: "Select pharmacies", + size: "l", + classes: "nhsuk-u-margin-bottom-5" + } + }) %} + + {% if (pharmacies | length) > 1 %} + {{ checkboxes({ + idPrefix: "pharmacy-select-all", + name: "pharmacyIds", + values: data.pharmacyIds, + formGroup: { + classes: "nhsuk-u-margin-bottom-2" + }, + items: [ + { + text: "Select all " + (pharmacies | length), + value: "", + attributes: { + "data-select-all": "true" + } + }, + { + divider: "or" + } + ] + }) }} + {% endif %} + + {% if (pharmacies | length) > 20 %} + {{ input({ + id: "pharmacy-search", + name: "pharmacySearch", + type: "search", + label: { + text: "Search" + }, + classes: "nhsuk-input--width-20", + attributes: { + "data-module": "app-filter-checkboxes" + }, + formGroup: { + classes: "nhsuk-u-margin-bottom-4" + } + }) }} + {% endif %} + + +
+ {{ checkboxes({ + id: "pharmacy-ids", + name: "pharmacyIds", + values: data.pharmacyIds, + items: items + }) }} +
+ + {% endcall %} + + + {{ button({ + text: "Continue" + }) }} + +
+
+
+ + + + +{% endblock %} From 41a628b6992d688ed36b570d9ddb4bbb3dd658d9 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 24 Apr 2026 14:01:28 +0100 Subject: [PATCH 02/68] More work in progress --- app/data/session-data-defaults.js | 5 +- app/routes/pharmacies.js | 24 ++++++ app/views/includes/header.html | 4 +- app/views/pharmacies/check-selection.html | 58 ++++++++++++++ app/views/pharmacies/pharmacy.html | 65 +++++++++++++++ app/views/pharmacies/users/index.html | 52 ++++++++++++ app/views/pharmacies/users/new.html | 98 +++++++++++++++++++++++ 7 files changed, 302 insertions(+), 4 deletions(-) create mode 100644 app/views/pharmacies/check-selection.html create mode 100644 app/views/pharmacies/users/index.html create mode 100644 app/views/pharmacies/users/new.html diff --git a/app/data/session-data-defaults.js b/app/data/session-data-defaults.js index c0027bf0..e06dba75 100644 --- a/app/data/session-data-defaults.js +++ b/app/data/session-data-defaults.js @@ -15,8 +15,9 @@ module.exports = { vaccineStock: vaccineStock, lists: [], nhsNumberKnown: "yes", - currentUserId: "2387441662601", - currentOrganisationId: "RW3", + currentUserId: "6424325235325", + currentOrganisationId: null, + currentMode: "reports", vaccinationsRecorded: vaccinationsRecorded, // These are the options for extracting CSV reports diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index e9375d92..f71ba910 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -39,6 +39,30 @@ module.exports = router => { }) }) + router.get('/pharmacies/check-selection', async (req, res) => { + const data = req.session.data + + let pharmacies = await getPharmaciesBelongingToOrganisation("P15J") + + pharmacies = pharmacies.filter((pharmacy) => { + return data.pharmacyIds.includes(pharmacy.id) + }).sort(sortByNameThenPostcode()) + + + res.render('pharmacies/check-selection', { + pharmacies + }) + }) + + router.get('/pharmacies/users',(req, res) => { + const data = req.session.data + const users = data.users.slice(10, 20) + + res.render('pharmacies/users/index', { + users + }) + }) + router.get('/pharmacies/:id', (req, res) => { const data = req.session.data diff --git a/app/views/includes/header.html b/app/views/includes/header.html index 8881592f..58e9a25a 100644 --- a/app/views/includes/header.html +++ b/app/views/includes/header.html @@ -20,9 +20,9 @@ }), navigationItems) %} {% set navigationItems = (navigationItems.push({ - href: "/users", + href: "/pharmacies/users", text: "Manage users", - active: (currentSection == "user-admin") + active: (currentSection == "pharmacies-users") }), navigationItems) %} {% endif %} diff --git a/app/views/pharmacies/check-selection.html b/app/views/pharmacies/check-selection.html new file mode 100644 index 00000000..e1596c75 --- /dev/null +++ b/app/views/pharmacies/check-selection.html @@ -0,0 +1,58 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies" %} +{% set pageName = "Check your list of pharmacies" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/select" + }) }} +{% endblock %} + +{% block content %} +
+
+ +

{{ pageName }}

+ +

You have selected {{ pharmacies | length | plural("pharmacy") }}.

+ + + + + + + + + + + {% for pharmacy in pharmacies %} + + + + + {% endfor %} + + +
Pharmacy
{{ pharmacy.name }}, {{ pharmacy.address.postcode }} ({{ pharmacy.id }}) + {% if pharmacies | length > 1 %} +
+ + {{ button({ + text: "Remove", + classes: "nhsuk-button--small nhsuk-button--secondary nhsuk-u-margin-bottom-0" + }) }} +
+ {% endif %} +
+ +
+ {{ button({ + text: "Continue" + }) }} +
+ +
+
+ +{% endblock %} diff --git a/app/views/pharmacies/pharmacy.html b/app/views/pharmacies/pharmacy.html index a50639f2..7ce47a11 100644 --- a/app/views/pharmacies/pharmacy.html +++ b/app/views/pharmacies/pharmacy.html @@ -16,6 +16,71 @@

{{ pageName }}

+ {{ summaryList({ + rows: [ + { + key: { + text: "ODS code" + }, + value: { + text: organisation.id + } + }, + { + key: { + text: "Address" + }, + value: { + text: organisation.address.line1 + ", " + organisation.address.town + ", " + organisation.address.postcode + } + } + ] + }) }} + +

Users

+ + {{ button({ + text: "Add user" + }) }} + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Permission level + + Vaccinator + + Status +
+ Jane Smith + + Lead administrator + + Yes + + Active + +
diff --git a/app/views/pharmacies/users/index.html b/app/views/pharmacies/users/index.html new file mode 100644 index 00000000..bd940b52 --- /dev/null +++ b/app/views/pharmacies/users/index.html @@ -0,0 +1,52 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% block content %} +
+
+ +

Users

+ +

Add a new user or manage an existing pharmacy.

+ + {{ button({ + text: "Add user", + href: "/pharmacies/users/new" + }) }} + + + + + + + + + + + + + {% for user in users %} + + + + + + {% endfor %} + + +
Current users
+ Name + + Email +
+ {{ user.firstName }} {{ user.lastName }} + + {{ user.email }} + + Deactivate +
+
+
+ +{% endblock %} diff --git a/app/views/pharmacies/users/new.html b/app/views/pharmacies/users/new.html new file mode 100644 index 00000000..c6ebdf50 --- /dev/null +++ b/app/views/pharmacies/users/new.html @@ -0,0 +1,98 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users" + }) }} +{% endblock %} + +{% block content %} +
+
+ +

Add user

+ +
+ + {{ input({ + "label": { + "text": "First name" + }, + "id": "first-name", + "name": "firstName", + classes: "nhsuk-input--width-20", + value: data.firstName, + "errorMessage": { + "text": firstNameError + } if firstNameError + }) }} + + {{ input({ + "label": { + "text": "Last name" + }, + "id": "last-name", + "name": "lastName", + classes: "nhsuk-input--width-20", + value: data.lastName, + "errorMessage": { + "text": lastNameError + } if lastNameError + }) }} + + {{ input({ + "label": { + "text": "Email address", + size: "s" + }, + "id": "email", + "name": "email", + type: "email", + value: data.email + }) }} + + {{ checkboxes({ + idPrefix: "example", + name: "example", + fieldset: { + legend: { + text: "Which pharmacy do you want to add them to?", + size: "s", + isPageHeading: true + } + }, + items: [ + { + value: "all", + text: "All – add them as a super admin for Peak Pharmacy" + }, + { + divider: "or" + }, + { + text: "Peak Pharmacy (P141)" + }, + { + text: "Peak Pharmacy (P931)" + }, + { + text: "Peak Pharmacy (P291)" + }, + { + text: "etc" + } + ] + }) }} + + + {{ button({ + "text": "Continue" + }) }} +
+ +
+
+ +{% endblock %} From d319388ce83a1c4301e3c4ea05c9b0d3daf276ee Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 24 Apr 2026 14:13:12 +0100 Subject: [PATCH 03/68] More work in progress --- app/routes/pharmacies.js | 10 +++ app/views/pharmacies/pharmacy.html | 2 +- app/views/pharmacies/users/index.html | 2 +- app/views/pharmacies/users/user.html | 100 ++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 app/views/pharmacies/users/user.html diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index f71ba910..bd6d3173 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -63,6 +63,16 @@ module.exports = router => { }) }) + router.get('/pharmacies/users/:id',(req, res) => { + const data = req.session.data + const id = req.params.id + const user = data.users.find((user) => user.id === id) + + res.render('pharmacies/users/user', { + user + }) + }) + router.get('/pharmacies/:id', (req, res) => { const data = req.session.data diff --git a/app/views/pharmacies/pharmacy.html b/app/views/pharmacies/pharmacy.html index 7ce47a11..4769b4a0 100644 --- a/app/views/pharmacies/pharmacy.html +++ b/app/views/pharmacies/pharmacy.html @@ -65,7 +65,7 @@

Users

- Jane Smith + Jane Smith Lead administrator diff --git a/app/views/pharmacies/users/index.html b/app/views/pharmacies/users/index.html index bd940b52..0c530fb2 100644 --- a/app/views/pharmacies/users/index.html +++ b/app/views/pharmacies/users/index.html @@ -33,7 +33,7 @@

Users

{% for user in users %} - {{ user.firstName }} {{ user.lastName }} + {{ user.firstName }} {{ user.lastName }} {{ user.email }} diff --git a/app/views/pharmacies/users/user.html b/app/views/pharmacies/users/user.html new file mode 100644 index 00000000..6c008800 --- /dev/null +++ b/app/views/pharmacies/users/user.html @@ -0,0 +1,100 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% set pageName = user.firstName + " " + user.lastName %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users" + }) }} +{% endblock %} + +{% block content %} +
+
+ +

{{ pageName }}

+ + {{ summaryList({ + rows: [ + { + key: { + text: "Email" + }, + value: { + text: user.email + } + } + ] + }) }} + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Pharmacies
+ Name + + Permission level + + Vaccinator + + Status +
+ Peak Pharmacy (P112) + + Lead administrator + + Yes + + Active + + Remove +
+ Peak Pharmacy (P9241) + + Recorder + + Yes + + Active + + Remove +
+ +

Deactivate user from all pharmacies

+ +
+
+ +{% endblock %} From 2c13df8d800a65f9f828d1e67e194a0fb0ae137a Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 24 Apr 2026 14:28:55 +0100 Subject: [PATCH 04/68] Add lead administrators page --- app/routes/pharmacies.js | 9 ++++ app/views/pharmacies/add-lead-admins.html | 60 +++++++++++++++++++++++ app/views/pharmacies/check-selection.html | 1 + 3 files changed, 70 insertions(+) create mode 100644 app/views/pharmacies/add-lead-admins.html diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index bd6d3173..408bb722 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -63,6 +63,15 @@ module.exports = router => { }) }) + router.get('/pharmacies/add-lead-admins',(req, res) => { + const data = req.session.data + const users = data.users.slice(10, 20) + + res.render('pharmacies/add-lead-admins', { + users + }) + }) + router.get('/pharmacies/users/:id',(req, res) => { const data = req.session.data const id = req.params.id diff --git a/app/views/pharmacies/add-lead-admins.html b/app/views/pharmacies/add-lead-admins.html new file mode 100644 index 00000000..269a35b0 --- /dev/null +++ b/app/views/pharmacies/add-lead-admins.html @@ -0,0 +1,60 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies" %} +{% set pageName = "Add lead administrators" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/check-selection" + }) }} +{% endblock %} + +{% block content %} +
+
+

{{ pageName }}

+ +

Who do you want to get be a lead administrator on RAVS for all {{ data.pharmacyIds | length }} pharmacies?

+ + {% set items = [] %} + + {% set items = (items.push({ + text: "Me (" + currentUser.firstName + " " + currentUser.lastName + ")", + value: user.id + }), items) %} + + {% for user in (users | sort(false, false, "firstName")) %} + {% set items = (items.push({ + text: user.firstName + " " + user.lastName, + value: user.id + }), items) %} + {% endfor %} + + {% set items = (items.push({ + divider: "or" + }), items) %} + + {% set items = (items.push({ + text: "No one, I’ll add lead administrators later", + value: "", + exclusive: true + }), items) %} + + {{ checkboxes({ + fieldset: { + legend: { + text: "Users", + size: "m" + } + }, + items: items + }) }} + + {{ button({ + text: "Continue" + }) }} + + +
+
+{% endblock %} diff --git a/app/views/pharmacies/check-selection.html b/app/views/pharmacies/check-selection.html index e1596c75..9d99d841 100644 --- a/app/views/pharmacies/check-selection.html +++ b/app/views/pharmacies/check-selection.html @@ -48,6 +48,7 @@

{{ pageName }}

{{ button({ + href: "/pharmacies/add-lead-admins", text: "Continue" }) }}
From 93e50b2632e332fa933ca7219ae8d502f2b1a710 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 24 Apr 2026 15:25:19 +0100 Subject: [PATCH 05/68] Some more progress --- app/views/home/index.html | 2 +- app/views/pharmacies/add-lead-admins.html | 153 ++++++++++++++++++++-- app/views/pharmacies/index.html | 14 +- app/views/pharmacies/pharmacy.html | 1 + app/views/pharmacies/select.html | 10 +- app/views/pharmacies/users/index.html | 4 - app/views/pharmacies/users/user.html | 13 +- 7 files changed, 167 insertions(+), 30 deletions(-) diff --git a/app/views/home/index.html b/app/views/home/index.html index 662883cd..a5a5e58f 100644 --- a/app/views/home/index.html +++ b/app/views/home/index.html @@ -13,7 +13,7 @@ {% include "includes/notification.html" %} -

{% if currentOrganisation %}{{ currentOrganisation.name }}{% else %}Peak pharmacies{% endif %}

+

{% if currentOrganisation %}{{ currentOrganisation.name }}{% else %}PCT Healthcare{% endif %}

{% if currentOrganisation and totalVaccinationsRecorded == 0 %} diff --git a/app/views/pharmacies/add-lead-admins.html b/app/views/pharmacies/add-lead-admins.html index 269a35b0..e555f8d6 100644 --- a/app/views/pharmacies/add-lead-admins.html +++ b/app/views/pharmacies/add-lead-admins.html @@ -14,13 +14,69 @@

{{ pageName }}

-

Who do you want to get be a lead administrator on RAVS for all {{ data.pharmacyIds | length }} pharmacies?

+
+ +

Who do you want to assign as lead administrators for all {{ data.pharmacyIds | length }} pharmacies?

+ + {% call details({ + summaryText: "What is a lead administrator?", + classes: "nhsuk-expander" + }) %} +

Lead administrators are responsible for setting up the pharmacy's users and vaccines.

+ +

They will get a Welcome email telling them how to log into RAVS. Once logged in at ABC pharmacy, they will be able to:

+ +
    +
  • add more users, with different permission levels
  • +
  • add vaccines
  • +
  • create reports
  • +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Permission levelRecord and edit vaccinationsAdd and manage vaccinesCreate reportsAdd and manage users
Lead administratorYesYesYesYes
AdministratorYesYesYesNo
RecorderYesNoNoNo
+ {% endcall %} {% set items = [] %} {% set items = (items.push({ text: "Me (" + currentUser.firstName + " " + currentUser.lastName + ")", - value: user.id + value: user.id, + hint: { + text: "Do not add yourself if you do not need to record vaccinations at these pharmacies" + } }), items) %} {% for user in (users | sort(false, false, "firstName")) %} @@ -30,30 +86,101 @@

{{ pageName }}

}), items) %} {% endfor %} - {% set items = (items.push({ - divider: "or" - }), items) %} - - {% set items = (items.push({ - text: "No one, I’ll add lead administrators later", - value: "", - exclusive: true - }), items) %} - {{ checkboxes({ fieldset: { legend: { - text: "Users", + text: "Existing lead administrators", size: "m" } }, items: items }) }} +
+ {{ button({ + id: "add-lead-admin", + text: "Add a new lead administrator", + type: "button", + classes: "nhsuk-button--secondary", + attributes: { + "data-add-another-add": "true" + } + }) }} +
+ + + {{ button({ text: "Continue" }) }} +
+
diff --git a/app/views/pharmacies/index.html b/app/views/pharmacies/index.html index 1fe6d32d..ecf76961 100644 --- a/app/views/pharmacies/index.html +++ b/app/views/pharmacies/index.html @@ -8,15 +8,15 @@

Pharmacies

-

Add a new pharmacy or manage an existing pharmacy.

+

Add new pharmacies or manage an existing pharmacy.

{{ button({ - text: "Add pharmacy", + text: "Add pharmacies", href: "/pharmacies/select" }) }} - + - + @@ -39,14 +39,14 @@

Pharmacies

{{ organisation.name }} ({{ organisation.id}}) - {% endfor %} diff --git a/app/views/pharmacies/pharmacy.html b/app/views/pharmacies/pharmacy.html index 4769b4a0..23811652 100644 --- a/app/views/pharmacies/pharmacy.html +++ b/app/views/pharmacies/pharmacy.html @@ -82,6 +82,7 @@

Users

Current pharmaciesCurrent pharmacies ({{ organisations | length }})
@@ -28,7 +28,7 @@

Pharmacies

Users
- COVID-19 + COVID-19, flu 1 +
+

Deactivate this pharmacy

diff --git a/app/views/pharmacies/select.html b/app/views/pharmacies/select.html index 0ff4eb6d..11aa9abe 100644 --- a/app/views/pharmacies/select.html +++ b/app/views/pharmacies/select.html @@ -1,6 +1,7 @@ {% extends 'layout.html' %} {% set currentSection = "pharmacies" %} +{% set pageName = "Add pharmacies" %} {% block beforeContent %} {{ backLink({ @@ -13,6 +14,13 @@
+

{{ pageName }}

+ +

There are {{ (pharmacies | length) + 7 }} active pharmacies in your company according to the NHS Organisation Data Service (ODS).

+ +

{{ (pharmacies | length) }} of them are not yet using RAVS and can be added below.

+ + {% set items = [] %} {% for pharmacy in pharmacies %} @@ -26,7 +34,7 @@ {% call fieldset({ legend: { text: "Select pharmacies", - size: "l", + size: "m", classes: "nhsuk-u-margin-bottom-5" } }) %} diff --git a/app/views/pharmacies/users/index.html b/app/views/pharmacies/users/index.html index 0c530fb2..c7c66a3c 100644 --- a/app/views/pharmacies/users/index.html +++ b/app/views/pharmacies/users/index.html @@ -25,7 +25,6 @@

Users

Email - @@ -38,9 +37,6 @@

Users

{{ user.email }} - - Deactivate - {% endfor %} diff --git a/app/views/pharmacies/users/user.html b/app/views/pharmacies/users/user.html index 6c008800..50bfb9a3 100644 --- a/app/views/pharmacies/users/user.html +++ b/app/views/pharmacies/users/user.html @@ -35,11 +35,11 @@

{{ pageName }}

- + @@ -86,12 +86,17 @@

{{ pageName }}

Active
PharmaciesAccess and permission levels
- Name + Pharmacy Permission level @@ -69,7 +69,7 @@

{{ pageName }}

Active
- Remove + Deactivate
- Remove + Deactivate
+ {{ button({ + text: "Add to another pharmacy", + classes: "nhsuk-button--secondary" + }) }} +

Deactivate user from all pharmacies

From e4a8c67d65b2868541b35f04c0be197cf6e8842d Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 24 Apr 2026 18:07:37 +0100 Subject: [PATCH 06/68] Update add another so that none are shown by default --- app/assets/javascript/add-another.js | 37 ++++++++++++++++++----- app/views/pharmacies/add-lead-admins.html | 24 +++++---------- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/app/assets/javascript/add-another.js b/app/assets/javascript/add-another.js index cfb86296..23844906 100644 --- a/app/assets/javascript/add-another.js +++ b/app/assets/javascript/add-another.js @@ -14,6 +14,7 @@ import { Component } from 'nhsuk-frontend' * - Add `data-add-another-item="N"` to each item section (where N is the item index: 1, 2, 3, etc.) * - Add `data-add-another-add` to the "Add another" button (hidden by default) * - Add `data-add-another-remove="N"` to the "Remove" button within each section (hidden by default) + * - Optionally add `data-add-another-min="0"` to allow starting with no items visible (default is 1) * * @augments Component */ @@ -29,11 +30,13 @@ export class AddAnother extends Component { this.$items = Array.from(this.$root.querySelectorAll('[data-add-another-item]')) this.$addButton = this.$root.querySelector('[data-add-another-add]') this.$addButtonWrapper = this.$addButton?.closest('.nhsuk-button-group') + this.minItems = parseInt(this.$root.dataset.addAnotherMin ?? '1', 10) this.initializeItemVisibility() this.setupAddButton() this.setupRemoveButtons() this.updateAddButtonVisibility() + this.updateAddButtonText() this.updateRemoveButtonVisibility() } @@ -71,17 +74,18 @@ export class AddAnother extends Component { */ initializeItemVisibility() { // Find the last item with values - let lastFilledIndex = 0 + let lastFilledIndex = -1 this.$items.forEach(($item, index) => { if (this.hasInputValues($item)) { lastFilledIndex = index } }) - // Show items up to and including the last filled one (minimum 1) + // Show items up to and including the last filled one (respecting minItems) // Hide all items after that + const minVisibleIndex = this.minItems - 1 this.$items.forEach(($item, index) => { - if (index <= lastFilledIndex) { + if (index <= lastFilledIndex || index <= minVisibleIndex) { $item.hidden = false } else { $item.hidden = true @@ -152,6 +156,7 @@ export class AddAnother extends Component { } this.updateAddButtonVisibility() + this.updateAddButtonText() this.updateRemoveButtonVisibility() } @@ -163,8 +168,8 @@ export class AddAnother extends Component { removeItem(index) { const visibleItems = this.getVisibleItems() - // Don't remove if only one item is visible - if (visibleItems.length <= 1) { + // Don't remove if at minimum items + if (visibleItems.length <= this.minItems) { return } @@ -187,16 +192,34 @@ export class AddAnother extends Component { } this.updateAddButtonVisibility() + this.updateAddButtonText() this.updateRemoveButtonVisibility() } + /** + * Update the add button text based on number of visible items + * Uses data-add-another-text-first for the first item, + * data-add-another-text-another for subsequent items + */ + updateAddButtonText() { + if (!this.$addButton) return + + const firstText = this.$addButton.dataset.addAnotherTextFirst + const anotherText = this.$addButton.dataset.addAnotherTextAnother + + if (!firstText || !anotherText) return + + const visibleItems = this.getVisibleItems() + this.$addButton.textContent = visibleItems.length === 0 ? firstText : anotherText + } + /** * Update visibility of remove buttons based on number of visible items - * Remove buttons should only be visible when there are 2+ items + * Remove buttons should only be visible when there are more than minItems */ updateRemoveButtonVisibility() { const visibleItems = this.getVisibleItems() - const showRemoveButtons = visibleItems.length >= 2 + const showRemoveButtons = visibleItems.length > this.minItems this.$items.forEach($item => { const $removeButton = $item.querySelector('[data-add-another-remove]') diff --git a/app/views/pharmacies/add-lead-admins.html b/app/views/pharmacies/add-lead-admins.html index e555f8d6..c8e7da2c 100644 --- a/app/views/pharmacies/add-lead-admins.html +++ b/app/views/pharmacies/add-lead-admins.html @@ -14,7 +14,7 @@

{{ pageName }}

- +

Who do you want to assign as lead administrators for all {{ data.pharmacyIds | length }} pharmacies?

@@ -97,23 +97,13 @@

{{ pageName }}

}) }}
- {{ button({ - id: "add-lead-admin", - text: "Add a new lead administrator", - type: "button", - classes: "nhsuk-button--secondary", - attributes: { - "data-add-another-add": "true" - } - }) }} -
- +
{{ button({ From e08d33392a66fd0a0647d7f8885c596b6c70ec79 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 24 Apr 2026 18:15:07 +0100 Subject: [PATCH 07/68] Add a user to a pharmacy --- app/routes/pharmacies.js | 12 +++ app/views/pharmacies/add-users.html | 119 ++++++++++++++++++++++++++++ app/views/pharmacies/pharmacy.html | 3 +- 3 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 app/views/pharmacies/add-users.html diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index 408bb722..8e0ecfc6 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -83,6 +83,18 @@ module.exports = router => { }) + router.get('/pharmacies/:id/add-users',(req, res) => { + const data = req.session.data + const users = data.users.slice(10, 20) + const id = req.params.id + const organisation = data.organisations.find((organisation) => organisation.id === id) + + res.render('pharmacies/add-users', { + users, + organisation + }) + }) + router.get('/pharmacies/:id', (req, res) => { const data = req.session.data const id = req.params.id diff --git a/app/views/pharmacies/add-users.html b/app/views/pharmacies/add-users.html new file mode 100644 index 00000000..09ae6b96 --- /dev/null +++ b/app/views/pharmacies/add-users.html @@ -0,0 +1,119 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies" %} +{% set pageName = "Add user to " + organisation.name %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/" + organisation.id + }) }} +{% endblock %} + +{% block content %} +
+
+

{{ pageName }}

+ +

You can add an existing user to the pharmacy, or invite a new user.

+ + + + {% set items = [] %} + + {% for user in (users | sort(false, false, "firstName")) %} + {% set items = (items.push({ + text: user.firstName + " " + user.lastName, + value: user.id + }), items) %} + {% endfor %} + + {{ checkboxes({ + fieldset: { + legend: { + text: "Existing users", + size: "m" + } + }, + items: items + }) }} + +
+ + {# Ordinal suffixes for numbers 1-10 #} + + {% for index in range(0, 1) %} +
+

New user details

+ + + + {{ input({ + label: { + text: "First name" + }, + id: "first-name-" + (index + 1), + name: "firstName", + classes: "nhsuk-input--width-20", + value: data.firstName[index] + }) }} + + {{ input({ + label: { + text: "Last name" + }, + id: "last-name-" + (index + 1), + name: "lastName", + classes: "nhsuk-input--width-20", + value: data.lastName[index] + }) }} + + {{ input({ + label: { + text: "Email address" + }, + hint: { + html: "Use a personal nhs.net email, not a pharmacy email.
For example, joe.bloggs1@nhs.net" + }, + id: "email-" + (index + 1), + name: "email", + type: "email", + value: data.email[index] + }) }} +
+ {% endfor %} + + + + + {{ button({ + text: "Continue" + }) }} + + + + +
+
+{% endblock %} diff --git a/app/views/pharmacies/pharmacy.html b/app/views/pharmacies/pharmacy.html index 23811652..1610e168 100644 --- a/app/views/pharmacies/pharmacy.html +++ b/app/views/pharmacies/pharmacy.html @@ -40,7 +40,8 @@

{{ pageName }}

Users

{{ button({ - text: "Add user" + href: "/pharmacies/" + organisation.id + "/add-users", + text: "Add users" }) }} From 339a3a1681b2dd10c4f818d67bbfe30fc8c50b43 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Thu, 30 Apr 2026 11:13:20 +0100 Subject: [PATCH 08/68] Make more of the pages work --- app/data/users.js | 16 ++- app/routes/pharmacies.js | 108 ++++++++++++++- app/views/pharmacies/add-user-check.html | 125 ++++++++++++++++++ .../pharmacies/add-user-permission-level.html | 96 ++++++++++++++ app/views/pharmacies/add-user.html | 92 +++++++++++++ app/views/pharmacies/add-users.html | 119 ----------------- app/views/pharmacies/check-selection.html | 4 +- app/views/pharmacies/index.html | 7 +- app/views/pharmacies/pharmacy.html | 36 +++-- 9 files changed, 461 insertions(+), 142 deletions(-) create mode 100644 app/views/pharmacies/add-user-check.html create mode 100644 app/views/pharmacies/add-user-permission-level.html create mode 100644 app/views/pharmacies/add-user.html delete mode 100644 app/views/pharmacies/add-users.html diff --git a/app/data/users.js b/app/data/users.js index f3b4d98c..0ef9d0a6 100644 --- a/app/data/users.js +++ b/app/data/users.js @@ -316,12 +316,26 @@ module.exports = [ } ] }, + { + "id": "34634617277", + "email": "peter.orange@nhs.net", + "organisations": [ + { + "id": "RCY", + "permissionLevel": "Recorder", + "status": "Active", + "vaccinator": true + } + ], + "firstName": "Peter", + "lastName": "Orange" + }, { "id": "1394978032564", "email": "ocean.merritt@nhs.net", "organisations": [ { - "id": "RCY", + "id": "FR4V56", "permissionLevel": "Recorder", "status": "Active", "vaccinator": true diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index 8e0ecfc6..4014a1e0 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -19,9 +19,19 @@ module.exports = router => { const organisations = data.organisations.filter((organisation) => userOrganisationIds.includes(organisation.id) ) + let organisationUserCounts = {} + + for (const organisation of organisations) { + organisationUserCounts[organisation.id] = data.users + .filter((user) => (user.organisations || []) + .find((orgPermission) => orgPermission.id === organisation.id) + ).length + } + res.render('pharmacies/index', { - organisations + organisations, + organisationUserCounts }) }) @@ -83,28 +93,116 @@ module.exports = router => { }) - router.get('/pharmacies/:id/add-users',(req, res) => { + router.get('/pharmacies/:id/add-user',(req, res) => { const data = req.session.data const users = data.users.slice(10, 20) const id = req.params.id const organisation = data.organisations.find((organisation) => organisation.id === id) - res.render('pharmacies/add-users', { + res.render('pharmacies/add-user', { users, organisation }) }) + router.get('/pharmacies/:id/add-user-permission-level',(req, res) => { + const data = req.session.data + const id = req.params.id + const organisation = data.organisations.find((organisation) => organisation.id === id) + let existingUser + + if (data.userId) { + existingUser = data.users.find((user) => user.id === data.userId) + } + + res.render('pharmacies/add-user-permission-level', { + organisation, + existingUser + }) + }) + + router.get('/pharmacies/:id/add-user-check',(req, res) => { + const data = req.session.data + const id = req.params.id + const organisation = data.organisations.find((organisation) => organisation.id === id) + let existingUser + + if (data.userId) { + existingUser = data.users.find((user) => user.id === data.userId) + } + + res.render('pharmacies/add-user-check', { + organisation, + existingUser + }) + }) + + router.get('/pharmacies/:id/user-added',(req, res) => { + const data = req.session.data + const id = req.params.id + const organisation = data.organisations.find((organisation) => organisation.id === id) + let existingUser + + if (data.userId) { + existingUser = data.users.find((user) => user.id === data.userId) + + existingUser.organisations.push({ + id: organisation.id, + status: 'Active', + vaccinator: (data.vaccinator === 'yes'), + permissionLevel: data.permissionLevel + }) + } else { + + data.users.push({ + id: Math.floor(Math.random() * 10000000).toString(), + firstName: data.firstName, + lastName: data.lastName, + email: data.email, + organisations: [ + { + id: organisation.id, + status: 'Invited', + vaccinator: (data.vaccinator === 'yes'), + permissionLevel: data.permissionLevel + } + ] + }) + + } + + // Reset data + req.session.data.email = '' + req.session.data.firstName = '' + req.session.data.lastName = '' + req.session.data.permissionLevel = '' + req.session.data.vaccinator = '' + + res.redirect(`/pharmacies/${organisation.id}?added=true`) + }) + router.get('/pharmacies/:id', (req, res) => { const data = req.session.data const id = req.params.id const organisation = data.organisations.find((organisation) => organisation.id === id) + const userOrganisationPermissions = {} + + const users = data.users + .filter((user) => (user.organisations || []) + .find((orgPermission) => orgPermission.id === organisation.id) + ) + + for (const user of users) { + userOrganisationPermissions[user.id] = user.organisations.find((userOrganisation) => userOrganisation.id === organisation.id) + } + res.render('pharmacies/pharmacy', { - organisation + organisation, + users, + userOrganisationPermissions }) }) - } diff --git a/app/views/pharmacies/add-user-check.html b/app/views/pharmacies/add-user-check.html new file mode 100644 index 00000000..b7e8c577 --- /dev/null +++ b/app/views/pharmacies/add-user-check.html @@ -0,0 +1,125 @@ +{% extends 'layout.html' %} + +{% set pageName = "Check" %} +{% set currentSection = "pharmacies" %} + + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/" + organisation.id + "/add-user-permission-level", + text: "Back" + }) }} +{% endblock %} + +{% block content %} +
+
+ +

Check and {% if existingUserWithSameEmail %}reactivate{% else %}add{% endif %} user to {{ organisation.name }} ({{ organisation.id }})

+ + {% set nameText %} + {% if existingUser %} + {{ existingUser.firstName }} {{ existingUser.lastName }} + {% else %} + {{ data.firstName }} {{ data.lastName }} + {% endif %} + {% endset %} + + {{ summaryList({ + rows: [ + { + key: { + text: "Name" + }, + value: { + text: nameText + }, + actions: { + items: [ + { + href: "/pharmacies/" + organisation.id + "/add-user", + text: "Change", + visuallyHiddenText: "name" + } + ] + } + }, + { + key: { + text: "Email address" + }, + value: { + html: (existingUser.email if existingUser else data.email) + }, + actions: { + items: [ + { + href: "/pharmacies/" + organisation.id + "/add-user", + text: "Change", + visuallyHiddenText: "email address" + } + ] + } + }, + { + key: { + text: "Vaccinator" + }, + value: { + html: (data.vaccinator | capitalize) + }, + actions: { + items: [ + { + href: "/pharmacies/" + organisation.id + "/add-user-permission-level", + text: "Change", + visuallyHiddenText: "vaccinator status" + } + ] + } + }, + { + key: { + text: "Permission level" + }, + value: { + html: data.permissionLevel + }, + actions: { + items: [ + { + href: "/pharmacies/" + organisation.id + "/add-user-permission-level", + text: "Change", + visuallyHiddenText: "permission level" + } + ] + } + } + ] + }) }} + + + {% if existingUser %} +

{{ existingUser.firstName }} will be sent this email:

+ {% else %} +

{{ data.firstName }} will receive this welcome email with information about activating an account:

+ {% endif %} + +
+ {% if existingUserWithSameEmail %} + {% include "user-admin/_reactivation-email.html" %} + {% else %} + {% include "user-admin/_welcome-email.html" %} + {% endif %} +
+ +
+ {{ button({ + "text": "Confirm and send" + }) }} + + + +
+
+{% endblock %} diff --git a/app/views/pharmacies/add-user-permission-level.html b/app/views/pharmacies/add-user-permission-level.html new file mode 100644 index 00000000..bf0ee1cc --- /dev/null +++ b/app/views/pharmacies/add-user-permission-level.html @@ -0,0 +1,96 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies" %} +{% set pageName = "Adding user to " + organisation.name %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/" + organisation.id + "/add-user" + }) }} +{% endblock %} + +{% block content %} +
+
+

+ Adding + {% if existingUser %} + {{ existingUser.firstName }} {{ existingUser.lastName }} + {% else %} + {{ data.firstName }} {{ data.lastName }} + {% endif %} + to {{ organisation.name }} ({{ organisation.id }}) +

+ +
+ + {{ radios({ + name: "permissionLevel", + idPrefix: "permission-level", + fieldset: { + legend: { + text: "Permission level", + size: "s" + } + }, + value: data.permissionLevel, + items: [ + { + value: "Recorder", + text: "Recorder", + hint: { + text: "Record vaccinations only" + } + }, + { + value: "Administrator", + text: "Administrator", + hint: { + text: "Record vaccinations, create reports and manage vaccines" + } + }, + { + value: "Lead administrator", + text: "Lead administrator", + hint: { + text: "Record vaccinations, create reports, manage vaccines and users" + } + } + ] + }) }} + + {{ radios({ + "name": "vaccinator", + "fieldset": { + "legend": { + "text": "Are they a vaccinator?", + size: "s" + } + }, + hint: { + text: "Vaccination records include the name of the person who gave the vaccination" + }, + value: data.vaccinator, + "items": [ + { + "value": "yes", + "text": "Yes", + id: "vaccinator" + }, + { + "value": "no", + "text": "No" + } + ] + }) }} + + + {{ button({ + text: "Continue" + }) }} + + + +
+
+{% endblock %} diff --git a/app/views/pharmacies/add-user.html b/app/views/pharmacies/add-user.html new file mode 100644 index 00000000..547aa66b --- /dev/null +++ b/app/views/pharmacies/add-user.html @@ -0,0 +1,92 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies" %} +{% set pageName = "Add user to " + organisation.name + " (" + organisation.id + ")" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/" + organisation.id + }) }} +{% endblock %} + +{% block content %} +
+
+

{{ pageName }}

+ +

You can add an existing user to the pharmacy, or invite a new user.

+ +
+ + {% set items = [] %} + + {% for user in (users | sort(false, false, "firstName")) %} + {% set items = (items.push({ + text: user.firstName + " " + user.lastName, + value: user.id + }), items) %} + {% endfor %} + + {% set items = (items.push({ + divider: "or" + }), items) %} + + {% set newUserHtml %} + {{ input({ + label: { + text: "First name" + }, + name: "firstName", + classes: "nhsuk-input--width-20", + value: data.firstName + }) }} + + {{ input({ + label: { + text: "Last name" + }, + name: "lastName", + classes: "nhsuk-input--width-20", + value: data.lastName + }) }} + + {{ input({ + label: { + text: "Email address" + }, + name: "email", + type: "email", + value: data.email + }) }} + {% endset %} + + {% set items = (items.push({ + text: "Add a new user", + value: "add-new", + conditional: { + html: newUserHtml + } + }), items) %} + + {{ radios({ + name: "userId", + value: data.userId, + fieldset: { + legend: { + text: "User", + size: "m" + } + }, + items: items + }) }} + + {{ button({ + text: "Continue" + }) }} + + + + +
+
+{% endblock %} diff --git a/app/views/pharmacies/add-users.html b/app/views/pharmacies/add-users.html deleted file mode 100644 index 09ae6b96..00000000 --- a/app/views/pharmacies/add-users.html +++ /dev/null @@ -1,119 +0,0 @@ -{% extends 'layout.html' %} - -{% set currentSection = "pharmacies" %} -{% set pageName = "Add user to " + organisation.name %} - -{% block beforeContent %} - {{ backLink({ - href: "/pharmacies/" + organisation.id - }) }} -{% endblock %} - -{% block content %} -
-
-

{{ pageName }}

- -

You can add an existing user to the pharmacy, or invite a new user.

- -
- - {% set items = [] %} - - {% for user in (users | sort(false, false, "firstName")) %} - {% set items = (items.push({ - text: user.firstName + " " + user.lastName, - value: user.id - }), items) %} - {% endfor %} - - {{ checkboxes({ - fieldset: { - legend: { - text: "Existing users", - size: "m" - } - }, - items: items - }) }} - -
- - {# Ordinal suffixes for numbers 1-10 #} - - {% for index in range(0, 1) %} -
-

New user details

- - - - {{ input({ - label: { - text: "First name" - }, - id: "first-name-" + (index + 1), - name: "firstName", - classes: "nhsuk-input--width-20", - value: data.firstName[index] - }) }} - - {{ input({ - label: { - text: "Last name" - }, - id: "last-name-" + (index + 1), - name: "lastName", - classes: "nhsuk-input--width-20", - value: data.lastName[index] - }) }} - - {{ input({ - label: { - text: "Email address" - }, - hint: { - html: "Use a personal nhs.net email, not a pharmacy email.
For example, joe.bloggs1@nhs.net" - }, - id: "email-" + (index + 1), - name: "email", - type: "email", - value: data.email[index] - }) }} -
- {% endfor %} - - - - - {{ button({ - text: "Continue" - }) }} - - - - -
-
-{% endblock %} diff --git a/app/views/pharmacies/check-selection.html b/app/views/pharmacies/check-selection.html index 9d99d841..cefd8cd4 100644 --- a/app/views/pharmacies/check-selection.html +++ b/app/views/pharmacies/check-selection.html @@ -48,8 +48,8 @@

{{ pageName }}

{{ button({ - href: "/pharmacies/add-lead-admins", - text: "Continue" + href: "/pharmacies", + text: "Confirm and add" }) }} diff --git a/app/views/pharmacies/index.html b/app/views/pharmacies/index.html index ecf76961..8558a7d5 100644 --- a/app/views/pharmacies/index.html +++ b/app/views/pharmacies/index.html @@ -28,8 +28,6 @@

Pharmacies

- - @@ -42,11 +40,8 @@

Pharmacies

COVID-19, flu - {% endfor %} diff --git a/app/views/pharmacies/pharmacy.html b/app/views/pharmacies/pharmacy.html index 1610e168..01813d85 100644 --- a/app/views/pharmacies/pharmacy.html +++ b/app/views/pharmacies/pharmacy.html @@ -33,15 +33,27 @@

{{ pageName }}

value: { text: organisation.address.line1 + ", " + organisation.address.town + ", " + organisation.address.postcode } + }, + { + key: { + text: "Vaccines enabled" + }, + value: { + text: "COVID-19, flu" + } } ] }) }}

Users

+ + +
+
{{ button({ - href: "/pharmacies/" + organisation.id + "/add-users", - text: "Add users" + href: "/pharmacies/" + organisation.id + "/add-user", + text: "Add user" }) }}
Users
- 1 + {{ organisationUserCounts[organisation.id] }}
@@ -59,27 +71,33 @@

Users

- - + + {% for user in users %} - + {% endfor %}
Status + Actions +
- Jane Smith + + {{ user.firstName }} {{ user.lastName }} + - Lead administrator + {{ userOrganisationPermissions[user.id].permissionLevel }} - Yes + {{ "Yes" if userOrganisationPermissions[user.id].vaccinator else "No" }} - Active + {{ userOrganisationPermissions[user.id].status }} + + Changepermission level for {{ user.firstName }} {{ user.lastName }}
From 6916ff9b40e52dc71b5897595c29ff752a6752d5 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Thu, 30 Apr 2026 14:17:29 +0100 Subject: [PATCH 09/68] Add ability to add pharmacies --- app/data/organisations.js | 33 ++++++++++++++++++ app/data/users.js | 11 +++--- app/routes/pharmacies.js | 41 ++++++++++++++++++++++- app/views/pharmacies/check-selection.html | 3 +- 4 files changed, 79 insertions(+), 9 deletions(-) diff --git a/app/data/organisations.js b/app/data/organisations.js index 0427c632..962b698a 100644 --- a/app/data/organisations.js +++ b/app/data/organisations.js @@ -8484,9 +8484,22 @@ module.exports = [ postcode: "LS2 7UE" } }, + { + id: 'P15951', + name: 'MediCare Health Ltd', + address: { + line1: '28 High Street', + town: 'London', + postcode: 'N5 1PL' + }, + type: 'Pharmacy HQ', + status: 'Active', + region: "Y56" + }, { id: 'FX9141', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '28 High Street', town: 'London', @@ -8514,6 +8527,7 @@ module.exports = [ { id: 'FX4825', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '104 Bow Street', town: 'London', @@ -8541,6 +8555,7 @@ module.exports = [ { id: 'FX7314', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '99 Flowers Road', town: 'London', @@ -8568,6 +8583,7 @@ module.exports = [ { id: 'FX9151', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '12 Church Road', town: 'London', @@ -8595,6 +8611,7 @@ module.exports = [ { id: 'FQ2525', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '1 Granary Road', town: 'London', @@ -8622,6 +8639,7 @@ module.exports = [ { id: 'FW1941', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '8 Manchester Road', town: 'London', @@ -8649,6 +8667,7 @@ module.exports = [ { id: 'FP9824', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '12 John Robinson Road', town: 'London', @@ -8676,6 +8695,7 @@ module.exports = [ { id: 'FP1812', name: 'MediCare Pharmacy', + companyId: 'P15951', address: { line1: '18 Church Road', town: 'London', @@ -8703,6 +8723,7 @@ module.exports = [ { id: "FA7K23", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "45 High Street", town: "Manchester", @@ -8730,6 +8751,7 @@ module.exports = [ { id: "FG2R56", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "78 Queen Street", town: "Birmingham", @@ -8757,6 +8779,7 @@ module.exports = [ { id: "FH9P12", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "23 Station Road", town: "Leeds", @@ -8784,6 +8807,7 @@ module.exports = [ { id: "FJ4M89", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "156 Market Street", town: "Liverpool", @@ -8811,6 +8835,7 @@ module.exports = [ { id: "FK5N34", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "89 Park Lane", town: "Bristol", @@ -8838,6 +8863,7 @@ module.exports = [ { id: "FL7Q67", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "34 Castle Street", town: "Edinburgh", @@ -8865,6 +8891,7 @@ module.exports = [ { id: "FM8R23", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "67 Main Street", town: "Glasgow", @@ -8892,6 +8919,7 @@ module.exports = [ { id: "FN9S45", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "112 Church Road", town: "Cardiff", @@ -8919,6 +8947,7 @@ module.exports = [ { id: "FP2T78", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "45 Bridge Street", town: "Newcastle", @@ -8946,6 +8975,7 @@ module.exports = [ { id: "FQ3U12", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "28 Victoria Road", town: "Sheffield", @@ -8973,6 +9003,7 @@ module.exports = [ { id: "FR4V56", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "91 Oxford Street", town: "Nottingham", @@ -9000,6 +9031,7 @@ module.exports = [ { id: "FS5W89", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "15 King Street", town: "Leicester", @@ -9027,6 +9059,7 @@ module.exports = [ { id: "FT6X34", name: "MediCare Pharmacy", + companyId: 'P15951', address: { line1: "203 London Road", town: "Southampton", diff --git a/app/data/users.js b/app/data/users.js index 0ef9d0a6..673bec4c 100644 --- a/app/data/users.js +++ b/app/data/users.js @@ -174,8 +174,8 @@ module.exports = [ "firstName": "Jeremy", "lastName": "Blue" }, - // Amanda White is a lead admin for - // a chain of pharmacies + // Amanda White is a group administrator for the + // MediCare Health Ltd chain of pharmacies { "firstName": "Amanda", "lastName": "White", @@ -183,10 +183,9 @@ module.exports = [ "email": "amanda.white@nhs.net", "organisations": [ { - "id": "FX9141", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false + "id": "P15951", + "permissionLevel": "Group administrator", + "status": "Active" }, { "id": "FX4825", diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index 4014a1e0..d8c3ec12 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -15,9 +15,13 @@ module.exports = router => { const data = req.session.data const currentUser = res.locals.currentUser + // TODO: get this from the current login + // rather than hardcode it + const companyId = 'P15951' + const userOrganisationIds = currentUser.organisations.map((organisation) => organisation.id) - const organisations = data.organisations.filter((organisation) => userOrganisationIds.includes(organisation.id) ) + const organisations = data.organisations.filter((organisation) => organisation.companyId === companyId).sort(sortByNameThenPostcode()) let organisationUserCounts = {} @@ -64,6 +68,41 @@ module.exports = router => { }) }) + // Actually add the pharmacies + router.post('/pharmacies/added', async (req, res) => { + const data = req.session.data + + // TODO: get this from the current login + // rather than hardcode it + const companyId = 'P15951' + + let pharmacies = await getPharmaciesBelongingToOrganisation("P15J") + + pharmacies = pharmacies.filter((pharmacy) => { + return data.pharmacyIds.includes(pharmacy.id) + }).sort(sortByNameThenPostcode()) + + for (const pharmacy of pharmacies) { + + data.organisations.push({ + id: pharmacy.id, + name: pharmacy.name, + type: 'Community Pharmacy', + companyId: companyId, + status: 'Active', + vaccines: [], + sites: [ + { + id: pharmacy.id, + name: pharmacy.name + } + ] + }) + } + + res.redirect('/pharmacies?added=true') + }) + router.get('/pharmacies/users',(req, res) => { const data = req.session.data const users = data.users.slice(10, 20) diff --git a/app/views/pharmacies/check-selection.html b/app/views/pharmacies/check-selection.html index cefd8cd4..f95b8cd4 100644 --- a/app/views/pharmacies/check-selection.html +++ b/app/views/pharmacies/check-selection.html @@ -46,9 +46,8 @@

{{ pageName }}

-
+ {{ button({ - href: "/pharmacies", text: "Confirm and add" }) }}
From 0ff48306740db8035881b786d0de6addef3f374f Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Thu, 30 Apr 2026 14:19:03 +0100 Subject: [PATCH 10/68] Fix address --- app/routes/pharmacies.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index d8c3ec12..e02003c5 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -89,6 +89,7 @@ module.exports = router => { name: pharmacy.name, type: 'Community Pharmacy', companyId: companyId, + address: pharmacy.address, status: 'Active', vaccines: [], sites: [ From 388e6eb9392fa705c672a3aa27c66f26630076bb Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Thu, 30 Apr 2026 14:44:36 +0100 Subject: [PATCH 11/68] Fix adding users to pharmacies --- app/routes/pharmacies.js | 13 ++++++++----- app/views/pharmacies/index.html | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index e02003c5..4ebde354 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -14,6 +14,7 @@ module.exports = router => { router.get('/pharmacies', (req, res) => { const data = req.session.data const currentUser = res.locals.currentUser + const added = req.query.added // TODO: get this from the current login // rather than hardcode it @@ -35,7 +36,8 @@ module.exports = router => { res.render('pharmacies/index', { organisations, - organisationUserCounts + organisationUserCounts, + added }) }) @@ -101,7 +103,7 @@ module.exports = router => { }) } - res.redirect('/pharmacies?added=true') + res.redirect(`/pharmacies?added=${pharmacies.length}`) }) router.get('/pharmacies/users',(req, res) => { @@ -181,11 +183,11 @@ module.exports = router => { const data = req.session.data const id = req.params.id const organisation = data.organisations.find((organisation) => organisation.id === id) - let existingUser + const existingUser = data.users.find((user) => user.id === data.userId) - if (data.userId) { - existingUser = data.users.find((user) => user.id === data.userId) + if (existingUser) { + existingUser.organisations ||= [] existingUser.organisations.push({ id: organisation.id, status: 'Active', @@ -212,6 +214,7 @@ module.exports = router => { } // Reset data + req.session.data.userId = '' req.session.data.email = '' req.session.data.firstName = '' req.session.data.lastName = '' diff --git a/app/views/pharmacies/index.html b/app/views/pharmacies/index.html index 8558a7d5..d16445c5 100644 --- a/app/views/pharmacies/index.html +++ b/app/views/pharmacies/index.html @@ -6,6 +6,20 @@
+ {% if added %} + {% set html %} +

+ {{ added }} pharmacies added +

+

You can now add or assign users to these pharmacies.

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

Pharmacies

Add new pharmacies or manage an existing pharmacy.

From 9ece65934b4805a212df51128da39962101e6b4a Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Thu, 30 Apr 2026 15:05:44 +0100 Subject: [PATCH 12/68] Extract checkbox count javascript --- .../javascript/checkbox-selected-count.js | 61 +++++++++++++++++++ app/assets/javascript/main.js | 2 + app/views/pharmacies/select.html | 21 +------ 3 files changed, 65 insertions(+), 19 deletions(-) create mode 100644 app/assets/javascript/checkbox-selected-count.js diff --git a/app/assets/javascript/checkbox-selected-count.js b/app/assets/javascript/checkbox-selected-count.js new file mode 100644 index 00000000..b72b945c --- /dev/null +++ b/app/assets/javascript/checkbox-selected-count.js @@ -0,0 +1,61 @@ +import { Component } from 'nhsuk-frontend' + +/** + * Checkbox Selected Count component + * + * Displays a count of selected checkboxes within a container with pluralization support. + * + * Usage: + * - Add `data-module="app-checkboxes-count-select"` to a wrapper element + * - Add an element with pluralization attributes: + * `

` + * - Checkboxes with `data-select-all` attribute are excluded from the count + * + * @augments Component + */ +export class CheckboxSelectedCount extends Component { + static elementType = HTMLElement + + /** + * Name for the component used when initialising using data-module attributes + */ + static moduleName = 'app-checkboxes-count-select' + + /** + * @param {Element | null} $root - HTML element to use for component + */ + constructor($root) { + super($root) + + this.$countDisplay = this.$root.querySelector('[data-i18n-selected-count\\.one]') + this.$checkboxes = this.$root.querySelectorAll('input[type="checkbox"]') + + if (this.$countDisplay) { + this.singularLabel = this.$countDisplay.getAttribute('data-i18n-selected-count.one') || 'selected' + this.pluralLabel = this.$countDisplay.getAttribute('data-i18n-selected-count.many') || 'selected' + this.setupEventListeners() + this.updateCount() + } + } + + /** + * Set up change event listeners on all checkboxes + */ + setupEventListeners() { + this.$checkboxes.forEach(($checkbox) => { + $checkbox.addEventListener('change', () => this.updateCount()) + }) + } + + /** + * Update the count display with the number of selected checkboxes + */ + updateCount() { + const checkedCount = this.$root.querySelectorAll( + 'input[type="checkbox"]:checked:not([data-select-all])' + ).length + + const label = checkedCount === 1 ? this.singularLabel : this.pluralLabel + this.$countDisplay.textContent = `${checkedCount} ${label} selected` + } +} diff --git a/app/assets/javascript/main.js b/app/assets/javascript/main.js index d101693a..6ee6b810 100644 --- a/app/assets/javascript/main.js +++ b/app/assets/javascript/main.js @@ -5,11 +5,13 @@ import { import { AddAnother } from './add-another.js' import { Autocomplete } from './autocomplete.js' +import { CheckboxSelectedCount } from './checkbox-selected-count.js' // Initiate NHS.UK frontend components on page load document.addEventListener('DOMContentLoaded', () => { createAll(AddAnother) createAll(Autocomplete) + createAll(CheckboxSelectedCount) }) diff --git a/app/views/pharmacies/select.html b/app/views/pharmacies/select.html index 11aa9abe..9d2d71fe 100644 --- a/app/views/pharmacies/select.html +++ b/app/views/pharmacies/select.html @@ -12,7 +12,7 @@ {% block content %}
-
+

{{ pageName }}

@@ -92,6 +92,7 @@

{{ pageName }}

{% endcall %} +

{{ button({ text: "Continue" @@ -104,23 +105,6 @@

{{ pageName }}

- {% endblock %} diff --git a/app/views/pharmacies/select.html b/app/views/pharmacies/select.html index d665ca85..b95a88d3 100644 --- a/app/views/pharmacies/select.html +++ b/app/views/pharmacies/select.html @@ -55,6 +55,7 @@

{{ pageName }}

text: "Select all " + (pharmacies | length), value: "", attributes: { + "data-module": "app-checkbox-select-all", "data-select-all": "true" } }, @@ -105,37 +106,4 @@

{{ pageName }}

- - - {% endblock %} diff --git a/app/views/reports/choose-site.html b/app/views/reports/choose-site.html index aed4f930..42709b1a 100644 --- a/app/views/reports/choose-site.html +++ b/app/views/reports/choose-site.html @@ -25,6 +25,7 @@ text: "All " + ((sites|length) if sites else (organisations|length)) + " " + ("sites" if sites else "organisations"), value: "all", attributes: { + "data-module": "app-checkbox-select-all", "data-select-all": "true" } }), items) %} @@ -74,38 +75,5 @@
- - {% endblock %} From 04b0398b72b8ccfe6ca36073182fc4ce6e4d46ca Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 1 May 2026 15:07:35 +0100 Subject: [PATCH 20/68] Add radios filter --- app/assets/javascript/main.js | 2 + app/assets/javascript/radios-filter.js | 68 ++++++++++++++++ .../components/_scrollable-container.scss | 19 +++++ app/assets/sass/main.scss | 27 ++----- app/views/apply/pharmacies.html | 2 +- app/views/pharmacies/add-user.html | 77 ++++++++++++++----- app/views/pharmacies/select.html | 2 +- 7 files changed, 153 insertions(+), 44 deletions(-) create mode 100644 app/assets/javascript/radios-filter.js create mode 100644 app/assets/sass/components/_scrollable-container.scss diff --git a/app/assets/javascript/main.js b/app/assets/javascript/main.js index 32aa42e4..c7464192 100644 --- a/app/assets/javascript/main.js +++ b/app/assets/javascript/main.js @@ -8,6 +8,7 @@ import { Autocomplete } from './autocomplete.js' import { CheckboxFilter } from './checkbox-filter.js' import { CheckboxSelectAll } from './checkbox-select-all.js' import { CheckboxSelectedCount } from './checkbox-selected-count.js' +import { RadiosFilter } from './radios-filter.js' // Initiate NHS.UK frontend components on page load document.addEventListener('DOMContentLoaded', () => { @@ -16,6 +17,7 @@ document.addEventListener('DOMContentLoaded', () => { createAll(CheckboxFilter) createAll(CheckboxSelectAll) createAll(CheckboxSelectedCount) + createAll(RadiosFilter) }) diff --git a/app/assets/javascript/radios-filter.js b/app/assets/javascript/radios-filter.js new file mode 100644 index 00000000..e9dcf236 --- /dev/null +++ b/app/assets/javascript/radios-filter.js @@ -0,0 +1,68 @@ +import { Component } from 'nhsuk-frontend' + +/** + * Radios Filter component + * + * Filters a list of radio buttons based on search input. + * + * Usage: + * - Add `data-module="app-radios-filter"` to a search input element + * - The component will filter `.nhsuk-radios__item` elements within the same form or fieldset + * - Radio items with `data-no-filter` on their input are not filtered (e.g. "add new" options) + * + * @augments Component + */ +export class RadiosFilter extends Component { + static elementType = HTMLInputElement + + /** + * Name for the component used when initialising using data-module attributes + */ + static moduleName = 'app-radios-filter' + + /** + * @param {Element | null} $root - HTML input element to use for component + */ + constructor($root) { + super($root) + + this.$form = this.$root.closest('fieldset') || this.$root.closest('form') || document.body + this.$radioItems = this.$form.querySelectorAll('.nhsuk-radios__item') + + this.$root.addEventListener('input', () => this.filter()) + } + + /** + * Filter radio items based on the search input value + */ + filter() { + const searchTerm = this.$root.value.toLowerCase().trim().replace(/[.()]/g, '') + const searchWords = searchTerm.split(/\s+/).filter(word => word.length > 0) + + console.log('Filtering radios with search term:', searchTerm) + + console.log('Radio items:', this.$radioItems) + + this.$radioItems.forEach(($item) => { + const $label = $item.querySelector('.nhsuk-radios__label') + const $radio = $item.querySelector('.nhsuk-radios__input') + + if (!$label || !$radio) return + + // Skip items marked as excluded from filtering (e.g. "add new" options) + if ($radio.hasAttribute('data-no-filter')) return + + const labelText = $label.textContent.toLowerCase().replace(/[.()]/g, '') + const labelWords = labelText.split(/\s+/) + const matches = searchWords.length === 0 || searchWords.every(searchWord => + labelWords.some(labelWord => labelWord.startsWith(searchWord)) + ) + + if (matches) { + $item.removeAttribute('hidden') + } else { + $item.setAttribute('hidden', '') + } + }) + } +} diff --git a/app/assets/sass/components/_scrollable-container.scss b/app/assets/sass/components/_scrollable-container.scss new file mode 100644 index 00000000..81f21313 --- /dev/null +++ b/app/assets/sass/components/_scrollable-container.scss @@ -0,0 +1,19 @@ +.app-scrollable-container { + max-height: 500px; + overflow-y: scroll; + margin-bottom: nhsuk-spacing(4); + + // Force scrollbar to always be visible on WebKit browsers (Chrome, Safari) + &::-webkit-scrollbar { + width: $nhsuk-border-width-inset-text; + } + + &::-webkit-scrollbar-track { + background: $nhsuk-border-colour; + } + + &::-webkit-scrollbar-thumb { + background: $nhsuk-input-border-colour; + border-radius: 4px; + } +} diff --git a/app/assets/sass/main.scss b/app/assets/sass/main.scss index 5a8af5a1..7ea6cd22 100755 --- a/app/assets/sass/main.scss +++ b/app/assets/sass/main.scss @@ -24,6 +24,7 @@ @import 'components/tag'; @import 'components/numbered-heading'; @import 'components/inset-text'; +@import 'components/scrollable-container'; @import '../../components/secondary-navigation/_secondary-navigation'; @@ -31,11 +32,13 @@ // Add your custom CSS/Sass styles below... /////////////////////////////////////////// -// Ensure hidden attribute works on button groups (which have display: flex) -.nhsuk-button-group[hidden] { +// Ensure hidden attribute works on button groups and radios (which have display: flex) +.nhsuk-button-group[hidden], +.nhsuk-radios__item[hidden] { display: none; } + .nhsuk-header--left .nhsuk-header__navigation-list { justify-content: normal; } @@ -145,23 +148,3 @@ border-radius: 3px; .nhsuk-checkboxes__item[hidden] { display: none; } - -.app-checkboxes--scrollable-container { - max-height: 500px; - overflow-y: scroll; - margin-bottom: nhsuk-spacing(4); - - // Force scrollbar to always be visible on WebKit browsers (Chrome, Safari) - &::-webkit-scrollbar { - width: $nhsuk-border-width-inset-text; - } - - &::-webkit-scrollbar-track { - background: $nhsuk-border-colour; - } - - &::-webkit-scrollbar-thumb { - background: $nhsuk-input-border-colour; - border-radius: 4px; - } -} \ No newline at end of file diff --git a/app/views/apply/pharmacies.html b/app/views/apply/pharmacies.html index 156a4696..8d65fa40 100644 --- a/app/views/apply/pharmacies.html +++ b/app/views/apply/pharmacies.html @@ -90,7 +90,7 @@

Select pharmacies

}) }} {% endif %} -
+
{{ checkboxes({ id: "pharmacy-ids", name: "pharmacyIds", diff --git a/app/views/pharmacies/add-user.html b/app/views/pharmacies/add-user.html index 547aa66b..28c334a5 100644 --- a/app/views/pharmacies/add-user.html +++ b/app/views/pharmacies/add-user.html @@ -27,9 +27,7 @@

{{ pageName }}

}), items) %} {% endfor %} - {% set items = (items.push({ - divider: "or" - }), items) %} + {% set newUserHtml %} {{ input({ @@ -60,25 +58,64 @@

{{ pageName }}

}) }} {% endset %} - {% set items = (items.push({ - text: "Add a new user", - value: "add-new", - conditional: { - html: newUserHtml + + + {% call fieldset({ + legend: { + text: "User", + size: "m", + classes: "nhsuk-u-margin-bottom-5" } - }), items) %} - - {{ radios({ - name: "userId", - value: data.userId, - fieldset: { - legend: { - text: "User", - size: "m" + }) %} + + {% if (users | length) > 10 %} + {{ input({ + id: "user-search", + name: "userSearch", + type: "search", + label: { + text: "Search" + }, + classes: "nhsuk-input--width-20", + attributes: { + "data-module": "app-radios-filter" + }, + formGroup: { + classes: "nhsuk-u-margin-bottom-4" } - }, - items: items - }) }} + }) }} + {% endif %} + +
+ {{ radios({ + name: "userId", + value: data.userId, + items: items + }) }} +
+ + {{ radios({ + name: "userId", + value: data.userId, + items: [ + { + divider: "or" + }, + { + text: "Add a new user", + value: "add-new", + id: "add-new", + attributes: { + "data-no-filter": "" + }, + conditional: { + html: newUserHtml + } + } + ] + }) }} + + {% endcall %} {{ button({ text: "Continue" diff --git a/app/views/pharmacies/select.html b/app/views/pharmacies/select.html index b95a88d3..ed3b05e4 100644 --- a/app/views/pharmacies/select.html +++ b/app/views/pharmacies/select.html @@ -85,7 +85,7 @@

{{ pageName }}

{% endif %} -
+
{{ checkboxes({ id: "pharmacy-ids", name: "pharmacyIds", From e9e59fd6597eb62103da9b744ad97891b0868e61 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 1 May 2026 15:22:25 +0100 Subject: [PATCH 21/68] Add success notification banner --- app/routes/pharmacies.js | 5 ++++- app/views/pharmacies/pharmacy.html | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index 5e8cf0fa..f9e1b6e6 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -227,6 +227,8 @@ module.exports = router => { router.get('/pharmacies/:id', (req, res) => { const data = req.session.data const id = req.params.id + const added = req.query.added + const organisation = data.organisations.find((organisation) => organisation.id === id) @@ -244,7 +246,8 @@ module.exports = router => { res.render('pharmacies/pharmacy', { organisation, users, - userOrganisationPermissions + userOrganisationPermissions, + added }) }) diff --git a/app/views/pharmacies/pharmacy.html b/app/views/pharmacies/pharmacy.html index d28ef25b..ddb64ce5 100644 --- a/app/views/pharmacies/pharmacy.html +++ b/app/views/pharmacies/pharmacy.html @@ -14,6 +14,19 @@
+ {% if added %} + {% set html %} +

+ User added +

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

{{ pageName }}

{{ summaryList({ From 56666581d5519f8b369272fa105e4a936057cb57 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 1 May 2026 15:36:52 +0100 Subject: [PATCH 22/68] Add email addresses and make region fixed height --- app/assets/javascript/radios-filter.js | 13 +++++++----- .../components/_scrollable-container.scss | 4 ++++ app/routes/pharmacies.js | 7 ++++++- app/views/pharmacies/add-user.html | 7 +++++-- app/views/pharmacies/users/new.html | 21 +++++-------------- 5 files changed, 28 insertions(+), 24 deletions(-) diff --git a/app/assets/javascript/radios-filter.js b/app/assets/javascript/radios-filter.js index e9dcf236..b5540722 100644 --- a/app/assets/javascript/radios-filter.js +++ b/app/assets/javascript/radios-filter.js @@ -36,8 +36,8 @@ export class RadiosFilter extends Component { * Filter radio items based on the search input value */ filter() { - const searchTerm = this.$root.value.toLowerCase().trim().replace(/[.()]/g, '') - const searchWords = searchTerm.split(/\s+/).filter(word => word.length > 0) + const searchTerm = this.$root.value.toLowerCase().trim() + const searchWords = searchTerm.split(/[\s.@()]+/).filter(word => word.length > 0) console.log('Filtering radios with search term:', searchTerm) @@ -52,10 +52,13 @@ export class RadiosFilter extends Component { // Skip items marked as excluded from filtering (e.g. "add new" options) if ($radio.hasAttribute('data-no-filter')) return - const labelText = $label.textContent.toLowerCase().replace(/[.()]/g, '') - const labelWords = labelText.split(/\s+/) + const $hint = $item.querySelector('.nhsuk-radios__hint') + const labelText = $label.textContent.toLowerCase() + const hintText = $hint ? $hint.textContent.toLowerCase() : '' + const combinedText = `${labelText} ${hintText}` + const combinedWords = combinedText.split(/[\s.@()]+/).filter(word => word.length > 0) const matches = searchWords.length === 0 || searchWords.every(searchWord => - labelWords.some(labelWord => labelWord.startsWith(searchWord)) + combinedWords.some(word => word.startsWith(searchWord)) ) if (matches) { diff --git a/app/assets/sass/components/_scrollable-container.scss b/app/assets/sass/components/_scrollable-container.scss index 81f21313..e62f6b59 100644 --- a/app/assets/sass/components/_scrollable-container.scss +++ b/app/assets/sass/components/_scrollable-container.scss @@ -17,3 +17,7 @@ border-radius: 4px; } } + +.app-scrollable-container--fixed-height { + min-height: 500px; +} diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index f9e1b6e6..73063c72 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -108,13 +108,18 @@ module.exports = router => { router.get('/pharmacies/users',(req, res) => { const data = req.session.data - const users = data.users.slice(10, 20) + const users = data.users.slice(2, 30) res.render('pharmacies/users/index', { users }) }) + router.get('/pharmacies/users/new',(req, res) => { + + res.render('pharmacies/users/new') + }) + router.get('/pharmacies/add-lead-admins',(req, res) => { const data = req.session.data const users = data.users.slice(10, 20) diff --git a/app/views/pharmacies/add-user.html b/app/views/pharmacies/add-user.html index 28c334a5..9d5a3833 100644 --- a/app/views/pharmacies/add-user.html +++ b/app/views/pharmacies/add-user.html @@ -23,7 +23,10 @@

{{ pageName }}

{% for user in (users | sort(false, false, "firstName")) %} {% set items = (items.push({ text: user.firstName + " " + user.lastName, - value: user.id + value: user.id, + hint: { + text: user.email + } }), items) %} {% endfor %} @@ -86,7 +89,7 @@

{{ pageName }}

}) }} {% endif %} -
+
{{ radios({ name: "userId", value: data.userId, diff --git a/app/views/pharmacies/users/new.html b/app/views/pharmacies/users/new.html index c6ebdf50..e601611f 100644 --- a/app/views/pharmacies/users/new.html +++ b/app/views/pharmacies/users/new.html @@ -44,8 +44,7 @@

Add user

{{ input({ "label": { - "text": "Email address", - size: "s" + "text": "Email address" }, "id": "email", "name": "email", @@ -53,35 +52,25 @@

Add user

value: data.email }) }} - {{ checkboxes({ + {{ radios({ idPrefix: "example", name: "example", fieldset: { legend: { - text: "Which pharmacy do you want to add them to?", - size: "s", + text: "Permission level", isPageHeading: true } }, items: [ { value: "all", - text: "All – add them as a super admin for Peak Pharmacy" + text: "Group admin for Peak Pharmacy" }, { divider: "or" }, { - text: "Peak Pharmacy (P141)" - }, - { - text: "Peak Pharmacy (P931)" - }, - { - text: "Peak Pharmacy (P291)" - }, - { - text: "etc" + text: "I’ll add them to individual pharmacies" } ] }) }} From 499ae2c326ecd5c8d9f0f32320d43264bde6c0a9 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 8 May 2026 13:35:54 +0100 Subject: [PATCH 23/68] Rename tab to "By pharmacy" --- app/views/home/_by-organisation.html | 4 ++-- app/views/home/index.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/home/_by-organisation.html b/app/views/home/_by-organisation.html index 5096e321..442cee25 100644 --- a/app/views/home/_by-organisation.html +++ b/app/views/home/_by-organisation.html @@ -1,9 +1,9 @@ - + - {% for totalByOrganisation in (totalsByOrganisation | sort(false, false, "organisationName")) %} + {% for totalByOrganisation in (totalsByPharmacy | sort(false, false, "organisationName")) %} {% set organisation = data.organisations | findById(totalByOrganisation.organisationId) %} - + {% for organisation in user.organisations %} + {% set org = data.organisations | findById(organisation.id) %} - - - - - - - + {% endfor %}
By organisationBy pharmacy
- Organisation + Pharmacy Today diff --git a/app/views/home/index.html b/app/views/home/index.html index a5a5e58f..79c019f9 100644 --- a/app/views/home/index.html +++ b/app/views/home/index.html @@ -85,7 +85,7 @@

All vaccinations

} } if (totalsBySite | length) > 1, { - label: "By organisation", + label: "By pharmacy", id: "by-organisations", panel: { html: byOrganisationHtml From 31d8343022d107b8e5ac175c382ba6b84bd27d03 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 11 May 2026 11:44:08 +0100 Subject: [PATCH 24/68] Content changes --- app/routes/pharmacies.js | 5 +++++ app/views/pharmacies/index.html | 8 ++++++-- app/views/pharmacies/pharmacy.html | 5 +++-- app/views/pharmacies/users/new.html | 2 +- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index 73063c72..4c25cfa9 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -120,6 +120,11 @@ module.exports = router => { res.render('pharmacies/users/new') }) + router.post('/pharmacies/users/new-answer',(req, res) => { + + res.redirect('/pharmacies/users') + }) + router.get('/pharmacies/add-lead-admins',(req, res) => { const data = req.session.data const users = data.users.slice(10, 20) diff --git a/app/views/pharmacies/index.html b/app/views/pharmacies/index.html index d16445c5..591eedec 100644 --- a/app/views/pharmacies/index.html +++ b/app/views/pharmacies/index.html @@ -30,7 +30,7 @@

Pharmacies

}) }} - +
Current pharmacies ({{ organisations | length }})Pharmacies added ({{ organisations | length }})
@@ -51,7 +51,11 @@

Pharmacies

{{ organisation.name }} ({{ organisation.id}})
- COVID-19, flu + {% if loop.index0 == 0 %} + Not yet added + {% else %} + COVID-19, flu + {% endif %} {{ organisationUserCounts[organisation.id] }} diff --git a/app/views/pharmacies/pharmacy.html b/app/views/pharmacies/pharmacy.html index ddb64ce5..b817a217 100644 --- a/app/views/pharmacies/pharmacy.html +++ b/app/views/pharmacies/pharmacy.html @@ -49,7 +49,7 @@

{{ pageName }}

}, { key: { - text: "Vaccines enabled" + text: "Vaccines" }, value: { text: "COVID-19, flu" @@ -58,6 +58,8 @@

{{ pageName }}

] }) }} +

Deactivate this pharmacy

+

Users

@@ -117,7 +119,6 @@

Users

{% endif %} -

Deactivate this pharmacy

diff --git a/app/views/pharmacies/users/new.html b/app/views/pharmacies/users/new.html index e601611f..9987c923 100644 --- a/app/views/pharmacies/users/new.html +++ b/app/views/pharmacies/users/new.html @@ -14,7 +14,7 @@

Add user

- + {{ input({ "label": { From 9ffb77e84bd377d4b1a067859d1be93b0f169dd6 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 11 May 2026 11:49:53 +0100 Subject: [PATCH 25/68] Fix code style issues --- app/assets/javascript/autocomplete.js | 4 ---- app/filters.js | 17 ----------------- app/routes/pharmacies.js | 8 +------- 3 files changed, 1 insertion(+), 28 deletions(-) diff --git a/app/assets/javascript/autocomplete.js b/app/assets/javascript/autocomplete.js index 695a7cf2..ec0c1a85 100644 --- a/app/assets/javascript/autocomplete.js +++ b/app/assets/javascript/autocomplete.js @@ -67,10 +67,6 @@ export class Autocomplete extends Component { /** * Selected option - * - * @param {*} value - Current value - * @param {Array} options - Available options - * @returns {HTMLOptionElement} Selected option */ selectedOption(value, options) { return [].filter.call( diff --git a/app/filters.js b/app/filters.js index ce8b19fc..9d48df16 100644 --- a/app/filters.js +++ b/app/filters.js @@ -67,23 +67,6 @@ module.exports = function () { } } - /** - * Ensure a value is always returned as an array - * Useful for form fields with [] notation that may return a string if only one value - * - * @param {*} value - Value to convert to array - * @returns {Array} Value as an array - */ - filters.asArray = function(value) { - if (value === undefined || value === null) { - return [] - } - if (Array.isArray(value)) { - return value - } - return [value] - } - /* keep the following line to return your filters to the app */ return filters } diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index 4c25cfa9..e03f9526 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -1,4 +1,4 @@ -const { getPharmaciesBelongingToOrganisation, getPharmacyChains, getOrganisation } = require('../lib/ods'); +const { getPharmaciesBelongingToOrganisation } = require('../lib/ods'); const sortByNameThenPostcode = (getPostcode = (item) => item.postcode) => (a, b) => { if (a.name < b.name) return -1 @@ -13,15 +13,12 @@ module.exports = router => { router.get('/pharmacies', (req, res) => { const data = req.session.data - const currentUser = res.locals.currentUser const added = req.query.added // TODO: get this from the current login // rather than hardcode it const companyId = 'P15951' - const userOrganisationIds = currentUser.organisations.map((organisation) => organisation.id) - const organisations = data.organisations.filter((organisation) => organisation.companyId === companyId).sort(sortByNameThenPostcode()) let organisationUserCounts = {} @@ -42,14 +39,11 @@ module.exports = router => { }) router.get('/pharmacies/select', async (req, res) => { - const data = req.session.data - const id = req.params.id let pharmacies = await getPharmaciesBelongingToOrganisation("P15J") pharmacies = pharmacies.sort(sortByNameThenPostcode((item) => item.address.postcode)) - res.render('pharmacies/select', { pharmacies }) From 11ea5c76c81c9f0608c6920f118c2ddb05b40b75 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 11 May 2026 13:34:46 +0100 Subject: [PATCH 26/68] Refactor "By organisation" as "By pharmacy" --- app/data/session-data-defaults.js | 2 +- app/data/users.js | 126 ------------------ app/routes/home.js | 54 +++++--- ...by-organisation.html => _by-pharmacy.html} | 2 +- app/views/home/index.html | 12 +- 5 files changed, 41 insertions(+), 155 deletions(-) rename app/views/home/{_by-organisation.html => _by-pharmacy.html} (94%) diff --git a/app/data/session-data-defaults.js b/app/data/session-data-defaults.js index e06dba75..3737b555 100644 --- a/app/data/session-data-defaults.js +++ b/app/data/session-data-defaults.js @@ -16,7 +16,7 @@ module.exports = { lists: [], nhsNumberKnown: "yes", currentUserId: "6424325235325", - currentOrganisationId: null, + currentOrganisationId: "P15951", currentMode: "reports", vaccinationsRecorded: vaccinationsRecorded, diff --git a/app/data/users.js b/app/data/users.js index 673bec4c..623144a4 100644 --- a/app/data/users.js +++ b/app/data/users.js @@ -186,132 +186,6 @@ module.exports = [ "id": "P15951", "permissionLevel": "Group administrator", "status": "Active" - }, - { - "id": "FX4825", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FX7314", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FX9151", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FQ2525", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FW1941", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FP9824", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FP1812", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FA7K23", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FG2R56", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FH9P12", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FA7K23", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FJ4M89", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FK5N34", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FL7Q67", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FM8R23", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FN9S45", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FP2T78", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FQ3U12", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FR4V56", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FS5W89", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FT6X34", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false } ] }, diff --git a/app/routes/home.js b/app/routes/home.js index bc890a75..41768f5e 100644 --- a/app/routes/home.js +++ b/app/routes/home.js @@ -75,22 +75,36 @@ module.exports = router => { let vaccinationsRecorded = [] let sites = [] + let pharmacies = [] let organisations = [] if (currentOrganisation) { - // Showing all sites for now, for demo purposes - sites = currentOrganisation.sites - // Filter vaccinations to only those recorded by the current - // organisation - vaccinationsRecorded = allVaccinationsRecorded.filter((vaccination)=> vaccination.organisationId === currentOrganisation.id) + if (currentOrganisation.type == "Pharmacy HQ") { - if (!sites.length || sites.length === 0) { - sites = [currentOrganisation] - } + pharmacies = data.organisations.filter((organisation) => organisation.companyId === currentOrganisation.id) + + vaccinationsRecorded = allVaccinationsRecorded + + + + } else { + + // Showing all sites for now, for demo purposes + sites = currentOrganisation.sites || [] + // Filter vaccinations to only those recorded by the current + // organisation + vaccinationsRecorded = allVaccinationsRecorded.filter((vaccination)=> vaccination.organisationId === currentOrganisation.id) + + if (!sites.length || sites.length === 0) { + sites = [currentOrganisation] + } + } } else { + // TODO: remove all this. + // Include all organisations for now vaccinationsRecorded = allVaccinationsRecorded @@ -98,14 +112,11 @@ module.exports = router => { organisations = data.organisations.filter((organisation) => userOrganisationIds.includes(organisation.id) ) } - - let totalsBySite = [] - let totalsByOrganisation = [] + let totalsByPharmacy = [] let totalsByVaccine = [] let totalsByDay = [] - const totalVaccinationsRecorded = countVaccinations(vaccinationsRecorded) const totalVaccinationsRecordedToday = countVaccinations( @@ -190,28 +201,28 @@ module.exports = router => { } } - for (let organisation of organisations) { + for (let pharmacy of pharmacies) { const total = countVaccinations(vaccinationsRecorded, { - organisationId: organisation.id + organisationId: pharmacy.id }) if (total !== -1) { - totalsByOrganisation.push({ - organisationId: organisation.id, - organisationName: organisation.name, + totalsByPharmacy.push({ + organisationId: pharmacy.id, + organisationName: pharmacy.name, today: countVaccinations(vaccinationsRecorded, { date: dateToday, - organisationId: organisation.id + organisationId: pharmacy.id }), month:countVaccinations(vaccinationsRecorded, { month: dateToday, - organisationId: organisation.id + organisationId: pharmacy.id }), past7Days: countVaccinations(vaccinationsRecorded, { minDate: sevenDaysAgo, maxDate: dateToday, - organisationId: organisation.id + organisationId: pharmacy.id }), total: total }) @@ -220,6 +231,7 @@ module.exports = router => { res.render('home/index', { sites, + pharmacies, totalVaccinationsRecorded, totalVaccinationsRecordedToday, totalVaccinationsRecordedThisMonth, @@ -228,7 +240,7 @@ module.exports = router => { totalsBySite, totalsByVaccine, totalsByDay, - totalsByOrganisation + totalsByPharmacy }) }) } diff --git a/app/views/home/_by-organisation.html b/app/views/home/_by-pharmacy.html similarity index 94% rename from app/views/home/_by-organisation.html rename to app/views/home/_by-pharmacy.html index 442cee25..3aa613d0 100644 --- a/app/views/home/_by-organisation.html +++ b/app/views/home/_by-pharmacy.html @@ -20,7 +20,7 @@
diff --git a/app/views/home/index.html b/app/views/home/index.html index 79c019f9..542427ec 100644 --- a/app/views/home/index.html +++ b/app/views/home/index.html @@ -42,7 +42,6 @@

All vaccinations

{% include "home/_vaccination-totals.html" %} - {% if totalVaccinationsRecorded > 0 %} {% set byDayHtml %} @@ -57,10 +56,11 @@

All vaccinations

{% include "home/_by-site.html" %} {% endset %} - {% set byOrganisationHtml %} - {% include "home/_by-organisation.html" %} + {% set byPharmacyHtml %} + {% include "home/_by-pharmacy.html" %} {% endset %} + {{ tabs({ items: [ { @@ -86,11 +86,11 @@

All vaccinations

} if (totalsBySite | length) > 1, { label: "By pharmacy", - id: "by-organisations", + id: "by-pharmacies", panel: { - html: byOrganisationHtml + html: byPharmacyHtml } - } if (totalsByOrganisation | length) > 1 + } if (pharmacies | length) > 1 ] }) }} From e892dbfdd080a0bdc3eafce73ab3c4c1fe81e1e6 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 11 May 2026 13:39:18 +0100 Subject: [PATCH 27/68] Remove 'currentMode' This has now been replaced with "Pharmacy HQ" parent organisations. --- app/data/session-data-defaults.js | 1 - app/routes/auth.js | 2 -- app/views/includes/header.html | 31 ++++++++++++++++--------------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/app/data/session-data-defaults.js b/app/data/session-data-defaults.js index 3737b555..ace71cb2 100644 --- a/app/data/session-data-defaults.js +++ b/app/data/session-data-defaults.js @@ -17,7 +17,6 @@ module.exports = { nhsNumberKnown: "yes", currentUserId: "6424325235325", currentOrganisationId: "P15951", - currentMode: "reports", vaccinationsRecorded: vaccinationsRecorded, // These are the options for extracting CSV reports diff --git a/app/routes/auth.js b/app/routes/auth.js index 7959d8fb..82de9ef0 100644 --- a/app/routes/auth.js +++ b/app/routes/auth.js @@ -74,7 +74,6 @@ module.exports = router => { const email = data.email const user = data.users.find((user) => user.email === email) - req.session.data.currentMode = "reports" req.session.data.currentOrganisationId = null req.session.data.currentUserId = user.id @@ -122,7 +121,6 @@ module.exports = router => { router.get('/sign-out', (req, res) => { req.session.data.currentUserId = null req.session.data.currentOrganisationId = null - req.session.data.currentMode = null req.session.data.email = "" res.redirect('/product-page') diff --git a/app/views/includes/header.html b/app/views/includes/header.html index 58e9a25a..7036c24f 100644 --- a/app/views/includes/header.html +++ b/app/views/includes/header.html @@ -11,8 +11,8 @@ active: (currentSection == "home") }), navigationItems) %} - - {% if data.currentMode == "reports" %} + + {% if currentOrganisation.type === "Pharmacy HQ" %} {% set navigationItems = (navigationItems.push({ href: "/pharmacies", text: "Pharmacies", @@ -26,25 +26,26 @@ }), navigationItems) %} {% endif %} - {% if currentOrganisation %} + + {% if currentOrganisation and (not currentOrganisation.type == "Pharmacy HQ") %} {% set navigationItems = (navigationItems.push({ href: "/record-vaccinations", text: "Record vaccinations", active: (currentSection == "vaccinate") }), navigationItems) %} + {% endif %} - - {% if (["Lead administrator", "Administrator"] | arrayOrStringIncludes(organisationSetting.permissionLevel)) %} - {% set navigationItems = (navigationItems.push({ - href: "/vaccines", - text: "Vaccines", - active: (currentSection == "vaccines") - }), navigationItems) %} - {% endif %} + + {% if (["Lead administrator", "Administrator"] | arrayOrStringIncludes(organisationSetting.permissionLevel)) %} + {% set navigationItems = (navigationItems.push({ + href: "/vaccines", + text: "Vaccines", + active: (currentSection == "vaccines") + }), navigationItems) %} {% endif %} - - {% if currentOrganisation %} + + {% if currentOrganisation and (not currentOrganisation.type == "Pharmacy HQ") %} {% set navigationItems = (navigationItems.push({ href: "/records", text: "Records", @@ -52,8 +53,8 @@ }), navigationItems) %} {% endif %} - - {% if (["Lead administrator", "Administrator"] | arrayOrStringIncludes(organisationSetting.permissionLevel)) or data.currentMode == "reports" %} + + {% if (["Lead administrator", "Administrator"] | arrayOrStringIncludes(organisationSetting.permissionLevel)) or currentOrganisation.type === "Pharmacy HQ" %} {% set navigationItems = (navigationItems.push({ href: "/reports", text: "Reports", From 80aba372f84b4601abd669c71225a89de8bf8829 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 11 May 2026 13:56:31 +0100 Subject: [PATCH 28/68] Remove 'Select mode' screen This is no longer needed if we have the Pharmacy HQ screens. --- app/routes/auth.js | 27 --------------- app/views/auth/select-mode.html | 46 ------------------------- app/views/auth/select-organisation.html | 10 ------ app/views/pharmacies/users/index.html | 2 +- 4 files changed, 1 insertion(+), 84 deletions(-) delete mode 100644 app/views/auth/select-mode.html diff --git a/app/routes/auth.js b/app/routes/auth.js index 82de9ef0..3fd822a7 100644 --- a/app/routes/auth.js +++ b/app/routes/auth.js @@ -49,11 +49,6 @@ module.exports = router => { res.redirect('/regions') - } else if (organisationsUserIsAnAdminAt.length > 1) { - // They are an admin at 2 or more organisations, so - // ask them to select mode (single org or report mode) - res.redirect('/auth/select-mode') - } else { res.redirect('/auth/select-organisation') @@ -62,28 +57,6 @@ module.exports = router => { }) - - router.post('/auth/answer-select-mode', (req, res) => { - const data = req.session.data - const loginMode = data.loginMode - - if (loginMode === 'single') { - res.redirect('/auth/select-organisation?from=select-mode') - } else if (loginMode === 'create-reports') { - - const email = data.email - const user = data.users.find((user) => user.email === email) - - req.session.data.currentOrganisationId = null - req.session.data.currentUserId = user.id - - res.redirect('/home') - } else { - res.redirect('/auth/select-mode') - } - - }) - router.get('/auth/select-organisation', (req, res) => { const data = req.session.data diff --git a/app/views/auth/select-mode.html b/app/views/auth/select-mode.html deleted file mode 100644 index 65e270ab..00000000 --- a/app/views/auth/select-mode.html +++ /dev/null @@ -1,46 +0,0 @@ -{% extends 'layout.html' %} - -{% set pageName = "Sign in" %} - -{% block content %} -
-
- - - - {{ radios({ - name: "loginMode", - fieldset: { - legend: { - text: "What do you want to do?", - size: "l", - isPageHeading: true - } - }, - hint: { - text: "As an administrator at multiple pharmacies or organisations you have 2 options. To switch between these, log out and log back in again." - }, - items: [ - { - text: "Use the service at 1 organisation", - value: "single", - hint: { - text: "For example, to record vaccinations and manage vaccines" - } - }, - { - text: "Create a report across multiple pharmacies or organisations", - value: "create-reports" - } - ] - }) }} - - {{ button({ - text: "Continue" - }) }} - - -
-
- -{% endblock %} diff --git a/app/views/auth/select-organisation.html b/app/views/auth/select-organisation.html index eaa3304a..caae94ea 100644 --- a/app/views/auth/select-organisation.html +++ b/app/views/auth/select-organisation.html @@ -2,16 +2,6 @@ {% set pageName = "Choose your organisation" %} -{% block beforeContent %} - {% if from === "select-mode" %} - {{ backLink({ - text: "Back", - href: "/auth/select-mode" - }) }} - {% endif %} -{% endblock %} - - {% block content %}
diff --git a/app/views/pharmacies/users/index.html b/app/views/pharmacies/users/index.html index c7c66a3c..f0f12a29 100644 --- a/app/views/pharmacies/users/index.html +++ b/app/views/pharmacies/users/index.html @@ -8,7 +8,7 @@

Users

-

Add a new user or manage an existing pharmacy.

+

Add a new user or change the permissions of an existing user.

{{ button({ text: "Add user", From fa558eeefe115344f32ccca11fa00800be34fe0f Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 11 May 2026 16:30:11 +0100 Subject: [PATCH 29/68] Separate site and pharmacies in reports It's a bit clearer if these are separate pages. --- app/routes/reports.js | 61 ++++++++++++------- app/views/reports/check.html | 25 ++++++-- app/views/reports/choose-pharmacies.html | 76 ++++++++++++++++++++++++ app/views/reports/choose-site.html | 16 ++--- app/views/reports/choose-vaccines.html | 2 +- 5 files changed, 147 insertions(+), 33 deletions(-) create mode 100644 app/views/reports/choose-pharmacies.html diff --git a/app/routes/reports.js b/app/routes/reports.js index 234edeaa..2165795a 100644 --- a/app/routes/reports.js +++ b/app/routes/reports.js @@ -8,7 +8,18 @@ module.exports = (router) => { let vaccinationsRecordedCount if (currentOrganisation) { - vaccinationsRecordedCount = data.vaccinationsRecorded.filter((vaccination) => vaccination.organisationId === currentOrganisation.id).length + + if (currentOrganisation.type === "Pharmacy HQ") { + + // TODO count vaccinations recorded at any pharmacies within this group. + vaccinationsRecordedCount = 1 + + } else { + + vaccinationsRecordedCount = data.vaccinationsRecorded.filter((vaccination) => vaccination.organisationId === currentOrganisation.id).length + + } + } else { // TODO: count across all organisations you @@ -43,6 +54,16 @@ module.exports = (router) => { }) }) + router.post('/reports/choose-vaccines-answer', (req, res) => { + + if (res.locals.currentOrganisation.type === "Pharmacy HQ") { + res.redirect('/reports/choose-pharmacies') + } else { + res.redirect('/reports/choose-site') + } + + }) + router.get('/reports/choose-dates', (req, res) => { const data = req.session.data @@ -98,26 +119,22 @@ module.exports = (router) => { const currentOrganisation = res.locals.currentOrganisation const currentUser = res.locals.currentUser - let sites, organisations - - if (currentOrganisation) { - // Showing all sites for now, for demo purposes - sites = currentOrganisation.sites - - if (sites === []) { - sites = [currentOrganisation] - } + const sites = currentOrganisation.sites || [] + res.render('reports/choose-site', { + sites + }) + }) - } else { + router.get('/reports/choose-pharmacies', (req, res) => { + const data = req.session.data + const currentOrganisation = res.locals.currentOrganisation - const userOrganisationIds = currentUser.organisations.map((organisation) => organisation.id) - organisations = data.organisations.filter((organisation) => userOrganisationIds.includes(organisation.id) ) - } + const pharmacies = data.organisations.filter((organisation) => organisation.companyId === currentOrganisation.id) - res.render('reports/choose-site', { - sites, - organisations + res.render('reports/choose-pharmacies', { + pharmacies }) + }) @@ -184,11 +201,13 @@ module.exports = (router) => { const today = new Date() const days = 86400000 // number of milliseconds in a day - let sites, organisations + const pharmacyIdsToReport = data.pharmacyIdsToReport || [] + + let sites, pharmacies if (currentOrganisation) { - sites = currentOrganisation.sites + sites = (currentOrganisation.sites || []) .filter((site) => siteIds.includes(site.id)) } else { @@ -199,6 +218,8 @@ module.exports = (router) => { .filter((organisation) => siteIds.includes(organisation.id)) } + pharmacies = data.organisations.filter((pharmacy) => pharmacyIdsToReport.includes(pharmacy.id)) + const fromInput = data.from const toInput = data.to const dateOption = data.date @@ -236,7 +257,7 @@ module.exports = (router) => { res.render('reports/check', { sites, - organisations, + pharmacies, from, to }) diff --git a/app/views/reports/check.html b/app/views/reports/check.html index bd4e00ca..97546922 100644 --- a/app/views/reports/check.html +++ b/app/views/reports/check.html @@ -52,10 +52,10 @@

{{ pageName }}

{% endset %} - {% set organisationsHtml %} + {% set pharmaciesHtml %}
    - {% for organisation in organisations | sort(false, false, "name") %} -
  • {{ organisation.name }} ({{ organisation.id }})
  • + {% for pharmacy in pharmacies | sort(false, false, "name") %} +
  • {{ pharmacy.name }} ({{ pharmacy.id }})
  • {% endfor %}
{% endset %} @@ -132,7 +132,24 @@

{{ pageName }}

} ] } - } if sites, + } if (sites | length) > 0, + { + key: { + text: ("Pharmacies" if (data.pharmacyIdsToReport | length) > 1 else "Pharmacy") + }, + value: { + html: pharmaciesHtml + }, + actions: { + items: [ + { + href: "/reports/choose-pharmacies", + text: "Change", + visuallyHiddenText: "pharmacies" + } + ] + } + } if currentOrganisation.type == "Pharmacy HQ", { key: { text: ("Organisations" if (data.siteIdsToReport | length) > 1 else "Organisation") diff --git a/app/views/reports/choose-pharmacies.html b/app/views/reports/choose-pharmacies.html new file mode 100644 index 00000000..bbd552c4 --- /dev/null +++ b/app/views/reports/choose-pharmacies.html @@ -0,0 +1,76 @@ +{% extends 'layout.html' %} + +{% set pageName = "Choose pharmacies" %} + +{% set currentSection = "reports" %} + +{% block beforeContent %} + {{ backLink({ + href: "/reports/choose-vaccines", + text: "Back" + }) }} +{% endblock %} + +{% block content %} +
+
+ +
+ + {% set items = [] %} + + {% set items = (items.push({ + text: "All " + (pharmacies|length) + " pharmacies", + value: "all", + attributes: { + "data-module": "app-checkbox-select-all", + "data-select-all": "true" + } + }), items) %} + + {% set items = (items.push({ + divider: "or" + }), items) %} + + + {% for pharmacy in pharmacies | sort(false, false, "name") %} + + {% if pharmacy.address %} + {% set hint = { + text: pharmacy.address.line1 + ", " + pharmacy.address.town + ", " + pharmacy.address.postcode + } %} + {% endif %} + + {% set items = (items.push({ + value: pharmacy.id, + text: pharmacy.name + " (" + pharmacy.id + ")", + hint: hint + }), items) %} + {% endfor %} + + {{ checkboxes({ + idPrefix: "pharmacy-to-report", + name: "pharmacyIdsToReport", + fieldset: { + legend: { + text: pageName, + classes: "nhsuk-fieldset__legend--l", + isPageHeading: true + } + }, + values: data.pharmacyIdsToReport, + items: items + }) }} + + + {{ button({ + text: "Continue" + }) }} + +
+ +
+
+ +{% endblock %} + diff --git a/app/views/reports/choose-site.html b/app/views/reports/choose-site.html index 42709b1a..f95ff530 100644 --- a/app/views/reports/choose-site.html +++ b/app/views/reports/choose-site.html @@ -1,6 +1,6 @@ {% extends 'layout.html' %} -{% set pageName = ("Choose sites" if sites else "Choose organisations") %} +{% set pageName = "Choose sites" %} {% set currentSection = "reports" %} @@ -19,10 +19,10 @@ {% set items = [] %} - {% if ((sites | length) > 2) or ((organisations | length) > 2) %} + {% if ((sites | length) > 2) %} {% set items = (items.push({ - text: "All " + ((sites|length) if sites else (organisations|length)) + " " + ("sites" if sites else "organisations"), + text: "All " + (sites|length) + " " + ("sites" if sites else "site"), value: "all", attributes: { "data-module": "app-checkbox-select-all", @@ -36,17 +36,17 @@ {% endif %} - {% for siteOrOrg in ((sites if sites else organisations) | sort(false, false, "name")) %} + {% for site in (sites | sort(false, false, "name")) %} - {% if siteOrOrg.address %} + {% if site.address %} {% set hint = { - text: siteOrOrg.address.line1 + ", " + siteOrOrg.address.town + ", " + siteOrOrg.address.postcode + text: site.address.line1 + ", " + site.address.town + ", " + site.address.postcode } %} {% endif %} {% set items = (items.push({ - value: siteOrOrg.id, - text: siteOrOrg.name + " (" + siteOrOrg.id + ")", + value: site.id, + text: site.name + " (" + site.id + ")", hint: hint }), items) %} {% endfor %} diff --git a/app/views/reports/choose-vaccines.html b/app/views/reports/choose-vaccines.html index 8c60d5e4..c695571e 100644 --- a/app/views/reports/choose-vaccines.html +++ b/app/views/reports/choose-vaccines.html @@ -15,7 +15,7 @@ {% block content %}
-
+ {% set items = [] %} From 02beecd24d2f6a2869af0825735e9cdf1e382d20 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 11 May 2026 16:39:52 +0100 Subject: [PATCH 30/68] Fix choose data page --- app/views/reports/choose-data.html | 65 +++++++++++++++++------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/app/views/reports/choose-data.html b/app/views/reports/choose-data.html index 477c66a1..5076d211 100644 --- a/app/views/reports/choose-data.html +++ b/app/views/reports/choose-data.html @@ -240,52 +240,61 @@
From 21c795a7e8d7a8f73710419a36f20caed7513fec Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 11 May 2026 17:05:10 +0100 Subject: [PATCH 31/68] Update data model --- app/data/organisations.js | 16 ++++++++++++++++ app/data/users.js | 30 ++++++------------------------ 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/app/data/organisations.js b/app/data/organisations.js index 962b698a..b4fa4e0f 100644 --- a/app/data/organisations.js +++ b/app/data/organisations.js @@ -8261,6 +8261,7 @@ module.exports = [ { id: 'FA424', name: 'Pickfords Pharmacy', + companyId: "P0191N", sites: [ { id: "FA424X", @@ -8285,6 +8286,7 @@ module.exports = [ { id: 'FA02S', name: 'Addlestone Pharmacy', + companyId: "P0191N", address: { line1: '92a Station Road', town: 'Addlestone', @@ -8316,6 +8318,7 @@ module.exports = [ { id: 'FVJ99', name: 'Pharmacy 4U', + companyId: "P0191N", sites: [ { id: "123535", @@ -8340,6 +8343,7 @@ module.exports = [ }, { id: 'PDL93', + companyId: "P0191N", name: 'Silverfields Chemists', sites: [ { @@ -9110,5 +9114,17 @@ module.exports = [ } } ] + }, + { + id: "P0191N", + name: 'P.W. Pharmacies Ltd', + address: { + line1: '12 High Road', + town: 'Manchester', + postcode: 'M7 1LP' + }, + type: 'Pharmacy HQ', + status: 'Active', + region: "Y56" } ] diff --git a/app/data/users.js b/app/data/users.js index 623144a4..932f4df7 100644 --- a/app/data/users.js +++ b/app/data/users.js @@ -82,35 +82,17 @@ module.exports = [ "firstName": "Phoebe", "lastName": "Black" }, - // Paulina Sloan is a lead admin for - // a chain of pharmacies + // Paulina Sloan is a group admin for + // a chain of pharmacies called + // P.W. Pharmacies Ltd { "id": "9847489647892", "email": "paulina.sloan@nhs.net", "organisations": [ { - "id": "FA424", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FA02S", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "FVJ99", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false - }, - { - "id": "PDL93", - "permissionLevel": "Lead administrator", - "status": "Active", - "vaccinator": false + "id": "P0191N", + "permissionLevel": "Group administrator", + "status": "Active" } ], "firstName": "Paulina", From b4d36a1499ed9c28bf0f2ba3e97d62a5590d50cf Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 11 May 2026 17:05:15 +0100 Subject: [PATCH 32/68] Fix --- app/routes/pharmacies.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index e03f9526..727be355 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -15,9 +15,7 @@ module.exports = router => { const data = req.session.data const added = req.query.added - // TODO: get this from the current login - // rather than hardcode it - const companyId = 'P15951' + const companyId = res.locals.currentOrganisation.id const organisations = data.organisations.filter((organisation) => organisation.companyId === companyId).sort(sortByNameThenPostcode()) From c4e96611b5a65849e0229750bdcb6cbf82554d9b Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 11 May 2026 17:05:21 +0100 Subject: [PATCH 33/68] Fix header --- app/views/includes/header.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/includes/header.html b/app/views/includes/header.html index 7036c24f..a0f6a913 100644 --- a/app/views/includes/header.html +++ b/app/views/includes/header.html @@ -27,7 +27,7 @@ {% endif %} - {% if currentOrganisation and (not currentOrganisation.type == "Pharmacy HQ") %} + {% if currentOrganisation and (currentOrganisation.type != "Pharmacy HQ") %} {% set navigationItems = (navigationItems.push({ href: "/record-vaccinations", text: "Record vaccinations", From febab30dac286fd927e9fdbc561ef2cabc0aa1b2 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 11 May 2026 17:08:20 +0100 Subject: [PATCH 34/68] Tidy up the code --- app/routes/auth.js | 4 ---- app/routes/home.js | 12 ------------ app/routes/reports.js | 15 +-------------- 3 files changed, 1 insertion(+), 30 deletions(-) diff --git a/app/routes/auth.js b/app/routes/auth.js index 3fd822a7..4390d5d5 100644 --- a/app/routes/auth.js +++ b/app/routes/auth.js @@ -21,10 +21,6 @@ module.exports = router => { .filter((organisation) => organisation.status === "Active") .map((organisation) => organisation.id) - const organisationsUserIsAnAdminAt = (user.organisations || []) - .filter((organisation) => (organisation.status === "Active" && ["Lead administrator", "Administrator"].includes(organisation.permissionLevel))) - .map((organisation) => organisation.id) - const userRegionIds = (user.regions || []) .filter((organisation) => organisation.status === "Active") .map((organisation) => organisation.id) diff --git a/app/routes/home.js b/app/routes/home.js index 41768f5e..7bee500f 100644 --- a/app/routes/home.js +++ b/app/routes/home.js @@ -64,7 +64,6 @@ module.exports = router => { // Dashboard router.get('/home', (req, res) => { const currentOrganisation = res.locals.currentOrganisation - const currentUser = res.locals.currentUser const data = req.session.data const allVaccinationsRecorded = data.vaccinationsRecorded @@ -73,10 +72,8 @@ module.exports = router => { // Vaccinations to count let vaccinationsRecorded = [] - let sites = [] let pharmacies = [] - let organisations = [] if (currentOrganisation) { @@ -101,15 +98,6 @@ module.exports = router => { sites = [currentOrganisation] } } - } else { - - // TODO: remove all this. - - // Include all organisations for now - vaccinationsRecorded = allVaccinationsRecorded - - const userOrganisationIds = currentUser.organisations.map((organisation) => organisation.id) - organisations = data.organisations.filter((organisation) => userOrganisationIds.includes(organisation.id) ) } let totalsBySite = [] diff --git a/app/routes/reports.js b/app/routes/reports.js index 2165795a..3ce25df5 100644 --- a/app/routes/reports.js +++ b/app/routes/reports.js @@ -115,9 +115,7 @@ module.exports = (router) => { }) router.get('/reports/choose-site', (req, res) => { - const data = req.session.data const currentOrganisation = res.locals.currentOrganisation - const currentUser = res.locals.currentUser const sites = currentOrganisation.sites || [] res.render('reports/choose-site', { @@ -196,7 +194,6 @@ module.exports = (router) => { router.get('/reports/check', (req, res) => { const data = req.session.data const currentOrganisation = res.locals.currentOrganisation - const currentUser = res.locals.currentUser const siteIds = data.siteIdsToReport || [] const today = new Date() const days = 86400000 // number of milliseconds in a day @@ -205,19 +202,9 @@ module.exports = (router) => { let sites, pharmacies - if (currentOrganisation) { - - sites = (currentOrganisation.sites || []) + sites = (currentOrganisation.sites || []) .filter((site) => siteIds.includes(site.id)) - } else { - - const userOrganisationIds = currentUser.organisations.map((organisation) => organisation.id) - - organisations = data.organisations.filter((organisation) => userOrganisationIds.includes(organisation.id) ) - .filter((organisation) => siteIds.includes(organisation.id)) - } - pharmacies = data.organisations.filter((pharmacy) => pharmacyIdsToReport.includes(pharmacy.id)) const fromInput = data.from From 4e96faccc918933086ae721c3775bbc65144e289 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 11 May 2026 17:10:23 +0100 Subject: [PATCH 35/68] Simplify code --- app/routes/pharmacies.js | 4 +--- app/routes/reports.js | 19 +++++-------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index 727be355..203f76e2 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -66,9 +66,7 @@ module.exports = router => { router.post('/pharmacies/added', async (req, res) => { const data = req.session.data - // TODO: get this from the current login - // rather than hardcode it - const companyId = 'P15951' + const companyId = res.locals.currentOrganisation.id let pharmacies = await getPharmaciesBelongingToOrganisation("P15J") diff --git a/app/routes/reports.js b/app/routes/reports.js index 3ce25df5..230ac52a 100644 --- a/app/routes/reports.js +++ b/app/routes/reports.js @@ -7,24 +7,15 @@ module.exports = (router) => { const currentOrganisation = res.locals.currentOrganisation let vaccinationsRecordedCount - if (currentOrganisation) { + if (currentOrganisation.type === "Pharmacy HQ") { - if (currentOrganisation.type === "Pharmacy HQ") { - - // TODO count vaccinations recorded at any pharmacies within this group. - vaccinationsRecordedCount = 1 - - } else { - - vaccinationsRecordedCount = data.vaccinationsRecorded.filter((vaccination) => vaccination.organisationId === currentOrganisation.id).length - - } + // TODO count vaccinations recorded at any pharmacies within this group. + vaccinationsRecordedCount = 1 } else { - // TODO: count across all organisations you - // have access to - vaccinationsRecordedCount = 100 + vaccinationsRecordedCount = data.vaccinationsRecorded.filter((vaccination) => vaccination.organisationId === currentOrganisation.id).length + } res.render('reports/index', { From 13e57a0bbca5b92cd3703954c64df12d9c0c8cb4 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 11 May 2026 17:21:59 +0100 Subject: [PATCH 36/68] Revert default user --- app/data/session-data-defaults.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/data/session-data-defaults.js b/app/data/session-data-defaults.js index ace71cb2..c0027bf0 100644 --- a/app/data/session-data-defaults.js +++ b/app/data/session-data-defaults.js @@ -15,8 +15,8 @@ module.exports = { vaccineStock: vaccineStock, lists: [], nhsNumberKnown: "yes", - currentUserId: "6424325235325", - currentOrganisationId: "P15951", + currentUserId: "2387441662601", + currentOrganisationId: "RW3", vaccinationsRecorded: vaccinationsRecorded, // These are the options for extracting CSV reports From 1da9808df0b78869f7b3e7320c02ff76027d78d6 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 11 May 2026 17:45:31 +0100 Subject: [PATCH 37/68] Add check page --- app/routes/pharmacies.js | 22 ++++++- app/views/pharmacies/users/check.html | 88 +++++++++++++++++++++++++++ app/views/pharmacies/users/new.html | 11 ++-- 3 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 app/views/pharmacies/users/check.html diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index 203f76e2..f9da70a7 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -111,8 +111,15 @@ module.exports = router => { }) router.post('/pharmacies/users/new-answer',(req, res) => { + const data = req.session.data + const permissionLevel = data.permissionLevel + + if (permissionLevel === "Group administrator") { + res.redirect('/pharmacies/users/check') + } else { + res.redirect('/pharmacies/users') + } - res.redirect('/pharmacies/users') }) router.get('/pharmacies/add-lead-admins',(req, res) => { @@ -124,6 +131,19 @@ module.exports = router => { }) }) + router.get('/pharmacies/users/check',(req, res) => { + + res.render('pharmacies/users/check') + }) + + router.post('/pharmacies/users/check-answer',(req, res) => { + // TODO: add the user + + res.redirect('/pharmacies/users?added=true') + }) + + + router.get('/pharmacies/users/:id',(req, res) => { const data = req.session.data const id = req.params.id diff --git a/app/views/pharmacies/users/check.html b/app/views/pharmacies/users/check.html new file mode 100644 index 00000000..4495c722 --- /dev/null +++ b/app/views/pharmacies/users/check.html @@ -0,0 +1,88 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users/new", + text: "Back" + }) }} +{% endblock %} + +{% block content %} +
+
+ +

Check and add group administrator

+ + {{ summaryList({ + rows: [ + { + key: { + text: "Name" + }, + value: { + text: data.firstName + " " + data.lastName + }, + actions: { + items: [ + { + href: "/pharmacies/users/new", + text: "Change", + visuallyHiddenText: "name" + } + ] + } + }, + { + key: { + text: "Email address" + }, + value: { + html: data.email + }, + actions: { + items: [ + { + href: "/pharmacies/users/new", + text: "Change", + visuallyHiddenText: "email address" + } + ] + } + }, + { + key: { + text: "Permission level" + }, + value: { + html: data.permissionLevel + }, + actions: { + items: [ + { + href: "/pharmacies/users/new", + text: "Change", + visuallyHiddenText: "permission level" + } + ] + } + } + ] + }) }} + +

{{ data.firstName }} will receive this welcome email with information about activating an account:

+ +
+ {% include "user-admin/_welcome-email.html" %} +
+ + + {{ button({ + "text": "Confirm and send" + }) }} + + +
+
+{% endblock %} diff --git a/app/views/pharmacies/users/new.html b/app/views/pharmacies/users/new.html index 9987c923..91a3f1fc 100644 --- a/app/views/pharmacies/users/new.html +++ b/app/views/pharmacies/users/new.html @@ -53,8 +53,8 @@

Add user

}) }} {{ radios({ - idPrefix: "example", - name: "example", + idPrefix: "permission-level", + name: "permissionLevel", fieldset: { legend: { text: "Permission level", @@ -63,14 +63,15 @@

Add user

}, items: [ { - value: "all", - text: "Group admin for Peak Pharmacy" + value: "Group administrator", + text: "Group administrator for Peak Pharmacy" }, { divider: "or" }, { - text: "I’ll add them to individual pharmacies" + text: "I’ll add them to individual pharmacies later", + value: "none" } ] }) }} From 39a40bf97de35dfe87f265b07762f18e765414cc Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Tue, 12 May 2026 10:47:18 +0100 Subject: [PATCH 38/68] Fix names --- app/data/users.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/app/data/users.js b/app/data/users.js index 932f4df7..7453694c 100644 --- a/app/data/users.js +++ b/app/data/users.js @@ -113,10 +113,9 @@ module.exports = [ "firstName": "Jeremy", "lastName": "Blue" }, - { "id": "64746353", - "email": "jeremy.blue@nhs.net", + "email": "joseph.blue@nhs.net", "organisations": [ { "id": "FA02S", @@ -125,12 +124,12 @@ module.exports = [ "vaccinator": true } ], - "firstName": "Jeremy", - "lastName": "Blue" + "firstName": "Joseph", + "lastName": "White" }, { "id": "46436436436", - "email": "jeremy.blue@nhs.net", + "email": "jason.green@nhs.net", "organisations": [ { "id": "FVJ99", @@ -139,12 +138,12 @@ module.exports = [ "vaccinator": true } ], - "firstName": "Jeremy", - "lastName": "Blue" + "firstName": "Jason", + "lastName": "Green" }, { "id": "646436311", - "email": "jeremy.blue@nhs.net", + "email": "samantha.black@nhs.net", "organisations": [ { "id": "PDL93", @@ -153,8 +152,8 @@ module.exports = [ "vaccinator": true } ], - "firstName": "Jeremy", - "lastName": "Blue" + "firstName": "Samantha", + "lastName": "Black" }, // Amanda White is a group administrator for the // MediCare Health Ltd chain of pharmacies From 49bf1b9d355ccd7e77b62dab18a297766294a44f Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Tue, 12 May 2026 22:34:25 +0100 Subject: [PATCH 39/68] Add new user flow --- app/lib/utils/by-name.js | 15 +++ app/routes/pharmacies.js | 103 +++++++++++++++++- app/views/pharmacies/users/check.html | 48 +++++++- .../users/new-permission-level.html | 93 ++++++++++++++++ .../users/new-select-pharmacies-check.html | 55 ++++++++++ .../users/new-select-pharmacies.html | 72 ++++++++++++ app/views/pharmacies/users/new.html | 9 +- 7 files changed, 382 insertions(+), 13 deletions(-) create mode 100644 app/lib/utils/by-name.js create mode 100644 app/views/pharmacies/users/new-permission-level.html create mode 100644 app/views/pharmacies/users/new-select-pharmacies-check.html create mode 100644 app/views/pharmacies/users/new-select-pharmacies.html diff --git a/app/lib/utils/by-name.js b/app/lib/utils/by-name.js new file mode 100644 index 00000000..e0fa4539 --- /dev/null +++ b/app/lib/utils/by-name.js @@ -0,0 +1,15 @@ +// This function can be used to sort a list of +// users by first name and then last name +const byName = function (a, b) { + const nameA = a.firstName.toUpperCase(); // ignore upper and lowercase + const nameB = b.firstName.toUpperCase(); // ignore upper and lowercase + if (nameA < nameB) { + return -1; + } + if (nameA > nameB) { + return 1; + } + return 0; +} + +module.exports.byName = byName diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index f9da70a7..2b1c35ba 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -1,5 +1,8 @@ const { getPharmaciesBelongingToOrganisation } = require('../lib/ods'); +const { byName } = require('../lib/utils/by-name'); + + const sortByNameThenPostcode = (getPostcode = (item) => item.postcode) => (a, b) => { if (a.name < b.name) return -1 if (a.name > b.name) return 1 @@ -98,7 +101,23 @@ module.exports = router => { router.get('/pharmacies/users',(req, res) => { const data = req.session.data - const users = data.users.slice(2, 30) + const companyId = res.locals.currentOrganisation.id + const pharmacies = data.organisations.filter((organisation) => organisation.companyId === companyId) + + const pharmacyIds = pharmacies.map(pharmacy => pharmacy.id) + + let users = data.users.sort(byName) + + users = users.filter(function(user) { + // Get the IDs of all the organisations they have access to + const userOrganisationIds = (user.organisations || []).map((organisation) => organisation.id) + + // See whether any of those organisations are the pharmacies in + // this chain, or the head office company id + return userOrganisationIds.some(id => pharmacyIds.includes(id) || id === companyId) + }) + + res.render('pharmacies/users/index', { users @@ -112,14 +131,51 @@ module.exports = router => { router.post('/pharmacies/users/new-answer',(req, res) => { const data = req.session.data - const permissionLevel = data.permissionLevel + const groupAdministrator = data.groupAdministrator - if (permissionLevel === "Group administrator") { + if (groupAdministrator === "yes") { res.redirect('/pharmacies/users/check') } else { - res.redirect('/pharmacies/users') + res.redirect('/pharmacies/users/new-select-pharmacies') } + }) + + router.get('/pharmacies/users/new-select-pharmacies',(req, res) => { + const data = req.session.data + const companyId = res.locals.currentOrganisation.id + const pharmacies = data.organisations.filter((organisation) => organisation.companyId === companyId) + + res.render('pharmacies/users/new-select-pharmacies', { + pharmacies + }) + }) + + router.get('/pharmacies/users/new-select-pharmacies-check',(req, res) => { + const data = req.session.data + const pharmacyIds = data.pharmacyIds + const companyId = res.locals.currentOrganisation.id + + // Get pharmacies selected on previous page + const pharmacies = data.organisations.filter((organisation) => pharmacyIds.includes(organisation.id)) + + + res.render('pharmacies/users/new-select-pharmacies-check', { + pharmacies + }) + }) + + router.get('/pharmacies/users/new-permission-level',(req, res) => { + const data = req.session.data + const pharmacyIds = data.pharmacyIds || [] + const companyId = res.locals.currentOrganisation.id + // Get pharmacies selected on previous page + const pharmacies = data.organisations.filter((organisation) => pharmacyIds.includes(organisation.id)) + + + res.render('pharmacies/users/new-permission-level', { + pharmacies + }) }) router.get('/pharmacies/add-lead-admins',(req, res) => { @@ -132,12 +188,47 @@ module.exports = router => { }) router.get('/pharmacies/users/check',(req, res) => { + const data = req.session.data + const pharmacyIds = data.pharmacyIds || [] + const companyId = res.locals.currentOrganisation.id + + // Get pharmacies selected on previous page + const pharmacies = data.organisations.filter((organisation) => pharmacyIds.includes(organisation.id)) + + res.render('pharmacies/users/check', { + pharmacies + }) + - res.render('pharmacies/users/check') }) router.post('/pharmacies/users/check-answer',(req, res) => { - // TODO: add the user + const data = req.session.data + const groupAdministrator = data.groupAdministrator + + const user = { + id: Math.floor(Math.random() * 10000000).toString(), + firstName: data.firstName, + lastName: data.lastName, + email: data.email, + organisations: [] + } + + if (groupAdministrator === "yes") { + user.organisations.push({ + id: res.locals.currentOrganisation.id, + status: 'Invited', + permissionLevel: "Group administrator" + }) + } + + data.users.push(user) + + // Reset answers + data.firstName = '' + data.lastName = '' + data.email = '' + data.permissionLevel = '' res.redirect('/pharmacies/users?added=true') }) diff --git a/app/views/pharmacies/users/check.html b/app/views/pharmacies/users/check.html index 4495c722..93ab9861 100644 --- a/app/views/pharmacies/users/check.html +++ b/app/views/pharmacies/users/check.html @@ -13,7 +13,15 @@
-

Check and add group administrator

+

Check and add {{ "group administrator" if data.permissionLevel == "Group administrator" else "user" }}

+ + {% set pharmaciesHtml %} +
    + {% for pharmacy in pharmacies %} +
  • {{ pharmacy.name }}
  • + {% endfor %} +
+ {% endset %} {{ summaryList({ rows: [ @@ -51,12 +59,29 @@

Check and add group administrator

] } }, + { + key: { + text: "Vaccinator" + }, + value: { + html: data.vaccinator | capitalize + }, + actions: { + items: [ + { + href: "/pharmacies/users/new-permission-level", + text: "Change", + visuallyHiddenText: "vaccinator" + } + ] + } + } if data.groupAdministrator != "yes", { key: { text: "Permission level" }, value: { - html: data.permissionLevel + html: ("Group administrator" if data.groupAdministrator == "yes" else data.permissionLevel) }, actions: { items: [ @@ -67,7 +92,24 @@

Check and add group administrator

} ] } - } + }, + { + key: { + text: "Pharmacies" + }, + value: { + html: pharmaciesHtml + }, + actions: { + items: [ + { + href: "/pharmacies/users/new-select-pharmacies", + text: "Change", + visuallyHiddenText: "pharmacies" + } + ] + } + } if data.groupAdministrator != "yes" ] }) }} diff --git a/app/views/pharmacies/users/new-permission-level.html b/app/views/pharmacies/users/new-permission-level.html new file mode 100644 index 00000000..e01d527d --- /dev/null +++ b/app/views/pharmacies/users/new-permission-level.html @@ -0,0 +1,93 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users/new-select-pharmacies-check" + }) }} +{% endblock %} + +{% block content %} +
+
+ +
+ +

{{ data.firstName }} {{ data.lastName }}’s role

+ +

These roles will apply to the {{ data.pharmacyIds | length }} pharmacies.

+ + {{ radios({ + name: "vaccinator", + fieldset: { + legend: { + text: "Are they a vaccinator?", + size: "s" + } + }, + hint: { + text: "Vaccination records include the name of the person who gave the vaccination" + }, + value: data.vaccinator, + items: [ + { + value: "yes", + text: "Yes" + }, + { + value: "no", + text: "No" + } + ] + }) }} + + + {{ radios({ + name: "permissionLevel", + idPrefix: "permission-level", + value: data.permissionLevel, + fieldset: { + legend: { + text: "Permission level", + size: "s" + } + }, + items: [ + { + value: "Recorder", + text: "Recorder", + hint: { + text: "Record vaccinations only" + } + }, + { + "value": "Administrator", + "text": "Administrator", + "hint": { + "text": "Record vaccinations, create reports and manage vaccines" + } + }, + { + "value": "Lead administrator", + "text": "Lead administrator", + "hint": { + "text": "Record vaccinations, create reports, manage vaccines and users" + } + } + ], + "errorMessage": { + "text": permissionLevelError + } if permissionLevelError + }) }} + + + {{ button({ + "text": "Continue" + }) }} +
+ +
+
+ +{% endblock %} diff --git a/app/views/pharmacies/users/new-select-pharmacies-check.html b/app/views/pharmacies/users/new-select-pharmacies-check.html new file mode 100644 index 00000000..3a6c1461 --- /dev/null +++ b/app/views/pharmacies/users/new-select-pharmacies-check.html @@ -0,0 +1,55 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users/new-select-pharmacies" + }) }} +{% endblock %} + +{% block content %} +
+
+ +

Check pharmacies

+ +

These are the pharmacies you will give {{ data.firstName }} {{ data.lastName }} access to.

+ +
+ {% set rows = [] %} + + {% for pharmacy in pharmacies %} + + {% set removeHtml %} + {{ button({ + text: "Remove", + classes: "nhsuk-button--secondary nhsuk-button--small nhsuk-u-margin-bottom-0" + }) }} + {% endset %} + + {% set rows = (rows.push([ + { text: pharmacy.name + ", " + pharmacy.address.postcode + " (" + pharmacy.id + ")" }, + { html: removeHtml } + ]), rows) %} + {% endfor %} + + + {{ table({ + head: [ + { text: "Pharmacy" }, + { text: "" } + ], + rows: rows + }) }} + + + {{ button({ + "text": "Continue" + }) }} +
+ +
+
+ +{% endblock %} diff --git a/app/views/pharmacies/users/new-select-pharmacies.html b/app/views/pharmacies/users/new-select-pharmacies.html new file mode 100644 index 00000000..a1d0156f --- /dev/null +++ b/app/views/pharmacies/users/new-select-pharmacies.html @@ -0,0 +1,72 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users/new" + }) }} +{% endblock %} + +{% block content %} +
+
+ +

Select pharmacies

+ +

You can invite the new user to 1 or more of your pharmacies.

+ +
+ + {% set items = [] %} + + {% for pharmacy in pharmacies %} + {% set items = (items.push({ + text: pharmacy.name + ", " + pharmacy.address.postcode + " (" + pharmacy.id + ")", + value: pharmacy.id + }), items) %} + {% endfor %} + + {% call fieldset({ + legend: { + text: "Which pharmacies do you want to add " + data.firstName + " " + data.lastName + " to?", + size: "m" + } + }) %} + + {{ input({ + id: "pharmacy-search", + name: "pharmacySearch", + type: "search", + label: { + text: "Search" + }, + classes: "nhsuk-input--width-20", + attributes: { + "data-module": "app-checkbox-filter" + }, + formGroup: { + classes: "nhsuk-u-margin-bottom-4" + } + }) }} + +
+ {{ checkboxes({ + id: "pharmacy-ids", + name: "pharmacyIds", + values: data.pharmacyIds, + items: items + }) }} +
+ + {% endcall %} + + {{ button({ + "text": "Continue" + }) }} +
+ +
+
+ +{% endblock %} diff --git a/app/views/pharmacies/users/new.html b/app/views/pharmacies/users/new.html index 91a3f1fc..1be54372 100644 --- a/app/views/pharmacies/users/new.html +++ b/app/views/pharmacies/users/new.html @@ -53,8 +53,9 @@

Add user

}) }} {{ radios({ - idPrefix: "permission-level", - name: "permissionLevel", + idPrefix: "group-admin", + name: "groupAdministrator", + value: data.groupAdministrator, fieldset: { legend: { text: "Permission level", @@ -63,7 +64,7 @@

Add user

}, items: [ { - value: "Group administrator", + value: "yes", text: "Group administrator for Peak Pharmacy" }, { @@ -71,7 +72,7 @@

Add user

}, { text: "I’ll add them to individual pharmacies later", - value: "none" + value: "no" } ] }) }} From 39f364f8811128cd21796bf2680eb92a54089a67 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Tue, 12 May 2026 22:41:13 +0100 Subject: [PATCH 40/68] Make adding users work --- app/routes/pharmacies.js | 15 +++++++++ .../users/new-select-pharmacies-check.html | 2 +- app/views/pharmacies/users/user.html | 31 ++++++------------- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index 2b1c35ba..4b7ed9ce 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -205,6 +205,7 @@ module.exports = router => { router.post('/pharmacies/users/check-answer',(req, res) => { const data = req.session.data const groupAdministrator = data.groupAdministrator + const pharmacyIds = data.pharmacyIds || [] const user = { id: Math.floor(Math.random() * 10000000).toString(), @@ -222,6 +223,20 @@ module.exports = router => { }) } + if (pharmacyIds.length > 0) { + + for (pharmacyId of pharmacyIds) { + + user.organisations.push({ + id: pharmacyId, + status: 'Invited', + permissionLevel: data.permissionLevel, + vaccinator: data.vaccinator + }) + } + + } + data.users.push(user) // Reset answers diff --git a/app/views/pharmacies/users/new-select-pharmacies-check.html b/app/views/pharmacies/users/new-select-pharmacies-check.html index 3a6c1461..cff3e136 100644 --- a/app/views/pharmacies/users/new-select-pharmacies-check.html +++ b/app/views/pharmacies/users/new-select-pharmacies-check.html @@ -16,7 +16,7 @@

Check pharmacies

These are the pharmacies you will give {{ data.firstName }} {{ data.lastName }} access to.

-
+ {% set rows = [] %} {% for pharmacy in pharmacies %} diff --git a/app/views/pharmacies/users/user.html b/app/views/pharmacies/users/user.html index 50bfb9a3..00b831d2 100644 --- a/app/views/pharmacies/users/user.html +++ b/app/views/pharmacies/users/user.html @@ -51,44 +51,31 @@

{{ pageName }}

Status
- Peak Pharmacy (P112) - - Lead administrator - - Yes - - Active - - Deactivate -
- Peak Pharmacy (P9241) + + {{ org.name }} ({{ organisation.id }}) + - Recorder + {{ organisation.permissionLevel }} - Yes + {{ organisation.vaccinator }} - Active + {{ organisation.status }} Deactivate
From d495a206eb5fd349f73c6d664bdef0a855a1e521 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Wed, 13 May 2026 10:09:50 +0100 Subject: [PATCH 41/68] Tweaks --- app/views/pharmacies/pharmacy.html | 2 ++ app/views/pharmacies/users/user.html | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/app/views/pharmacies/pharmacy.html b/app/views/pharmacies/pharmacy.html index b817a217..8ec387ab 100644 --- a/app/views/pharmacies/pharmacy.html +++ b/app/views/pharmacies/pharmacy.html @@ -117,6 +117,8 @@

Users

{% endfor %} + {% else %} +

No users added yet.

{% endif %}
diff --git a/app/views/pharmacies/users/user.html b/app/views/pharmacies/users/user.html index 00b831d2..0a288a26 100644 --- a/app/views/pharmacies/users/user.html +++ b/app/views/pharmacies/users/user.html @@ -51,6 +51,7 @@

{{ pageName }}

Status + @@ -71,6 +72,9 @@

{{ pageName }}

{{ organisation.status }} + + Changepermission level at {{ organisation.name }} + Deactivate From 5aa1ab3450a727083b585e9b6257afbc85cda8f6 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Wed, 13 May 2026 13:00:00 +0100 Subject: [PATCH 42/68] Make it possible to edit roles --- ...dmin.html => _notification-user-admin.html | 0 app/routes/pharmacies.js | 34 +++++++ app/views/pharmacies/pharmacy.html | 2 +- .../pharmacies/users/change-user-role.html | 92 +++++++++++++++++++ app/views/pharmacies/users/user.html | 2 +- 5 files changed, 128 insertions(+), 2 deletions(-) rename app/views/_notification-user-admin.html => _notification-user-admin.html (100%) create mode 100644 app/views/pharmacies/users/change-user-role.html diff --git a/app/views/_notification-user-admin.html b/_notification-user-admin.html similarity index 100% rename from app/views/_notification-user-admin.html rename to _notification-user-admin.html diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index 4b7ed9ce..e43c4842 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -350,6 +350,40 @@ module.exports = router => { res.redirect(`/pharmacies/${organisation.id}?added=true`) }) + + router.get('/pharmacies/:pharmacyId/users/:userId/change',(req, res) => { + const data = req.session.data + const pharmacyId = req.params.pharmacyId + const userId = req.params.userId + + const user = data.users.find(user => user.id === userId) + const pharmacy = data.organisations.find(organisation => organisation.id === pharmacyId) + + const role = user.organisations.find(userOrg => userOrg.id === pharmacyId) + + res.render('pharmacies/users/change-user-role', { + user, + pharmacy, + role + }) + }) + + router.post('/pharmacies/:pharmacyId/users/:userId/change-answer',(req, res) => { + const data = req.session.data + const pharmacyId = req.params.pharmacyId + const userId = req.params.userId + + const user = data.users.find(user => user.id === userId) + const pharmacy = data.organisations.find(organisation => organisation.id === pharmacyId) + + const role = user.organisations.find(userOrg => userOrg.id === pharmacyId) + + role.permissionLevel = data.permissionLevel + role.vaccinator = (data.vaccinator === "yes") + + res.redirect(`/pharmacies/${pharmacy.id}`) + }) + router.get('/pharmacies/:id', (req, res) => { const data = req.session.data const id = req.params.id diff --git a/app/views/pharmacies/pharmacy.html b/app/views/pharmacies/pharmacy.html index 8ec387ab..ddc9298b 100644 --- a/app/views/pharmacies/pharmacy.html +++ b/app/views/pharmacies/pharmacy.html @@ -111,7 +111,7 @@

Users

{{ userOrganisationPermissions[user.id].status }} - Changepermission level for {{ user.firstName }} {{ user.lastName }} + Changepermission level for {{ user.firstName }} {{ user.lastName }} {% endfor %} diff --git a/app/views/pharmacies/users/change-user-role.html b/app/views/pharmacies/users/change-user-role.html new file mode 100644 index 00000000..e6d8de71 --- /dev/null +++ b/app/views/pharmacies/users/change-user-role.html @@ -0,0 +1,92 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% set pageName = "Add user to " + organisation.name + " (" + organisation.id + ")" %} + +{% block beforeContent %} + {% set backHref = "/pharmacies/users/" + user.id if data.from == "user" else "/pharmacies/" + pharmacy.id %} + {{ backLink({ + href: backHref + }) }} +{% endblock %} + +{% block content %} +
+
+ +

Change role for {{ user.firstName }} {{ user.lastName }} at {{ pharmacy.name }}

+ + + + {{ radios({ + name: "permissionLevel", + idPrefix: "permission-level", + fieldset: { + legend: { + text: "Permission level", + size: "s" + } + }, + value: role.permissionLevel, + items: [ + { + value: "Recorder", + text: "Recorder", + hint: { + text: "Record vaccinations only" + } + }, + { + value: "Administrator", + text: "Administrator", + hint: { + text: "Record vaccinations, create reports and manage vaccines" + } + }, + { + value: "Lead administrator", + text: "Lead administrator", + hint: { + text: "Record vaccinations, create reports, manage vaccines and users" + } + } + ] + }) }} + + {{ radios({ + "name": "vaccinator", + "fieldset": { + "legend": { + "text": "Are they a vaccinator?", + size: "s" + } + }, + hint: { + text: "Vaccination records include the name of the person who gave the vaccination" + }, + value: ("yes" if role.vaccinator else "no"), + "items": [ + { + "value": "yes", + "text": "Yes", + id: "vaccinator" + }, + { + "value": "no", + "text": "No" + } + ] + }) }} + + + {{ button({ + text: "Save" + }) }} + + + + +
+
+{% endblock %} diff --git a/app/views/pharmacies/users/user.html b/app/views/pharmacies/users/user.html index 0a288a26..4fc878c8 100644 --- a/app/views/pharmacies/users/user.html +++ b/app/views/pharmacies/users/user.html @@ -73,7 +73,7 @@

{{ pageName }}

{{ organisation.status }} - Changepermission level at {{ organisation.name }} + Changepermission level at {{ organisation.name }} Deactivate From f3ec313ef221256613efd62a6d07506ec9449bce Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Wed, 13 May 2026 13:23:40 +0100 Subject: [PATCH 43/68] Show role --- app/views/pharmacies/users/index.html | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/views/pharmacies/users/index.html b/app/views/pharmacies/users/index.html index f0f12a29..ed4857e8 100644 --- a/app/views/pharmacies/users/index.html +++ b/app/views/pharmacies/users/index.html @@ -25,11 +25,17 @@

Users

Email + + Role + {% for user in users %} + {# See if they are a Group administrator #} + {% set groupRole = user.organisations | findById(currentOrganisation.id) %} + {{ user.firstName }} {{ user.lastName }} @@ -37,6 +43,13 @@

Users

{{ user.email }} + + {% if groupRole %} + Group administrator + {% else %} + {{ user.organisations | length | plural("pharmacy") }} + {% endif %} + {% endfor %} From ccfe6dfd1d835eb52573eb96631f478a37d4b8f2 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Wed, 13 May 2026 13:45:10 +0100 Subject: [PATCH 44/68] Only list pharmacies --- app/routes/pharmacies.js | 5 +- app/views/pharmacies/users/user.html | 106 ++++++++++++++------------- 2 files changed, 58 insertions(+), 53 deletions(-) diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index e43c4842..6b7dd40e 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -255,8 +255,11 @@ module.exports = router => { const id = req.params.id const user = data.users.find((user) => user.id === id) + const pharmacyRoles = (user.organisations || []).filter(role => role.permissionLevel !== "Group administrator") + res.render('pharmacies/users/user', { - user + user, + pharmacyRoles }) }) diff --git a/app/views/pharmacies/users/user.html b/app/views/pharmacies/users/user.html index 4fc878c8..710d565f 100644 --- a/app/views/pharmacies/users/user.html +++ b/app/views/pharmacies/users/user.html @@ -34,61 +34,63 @@

{{ pageName }}

- - - - - - - - - - - - - - {% for organisation in user.organisations %} - {% set org = data.organisations | findById(organisation.id) %} - - - - - - - + {% if (pharmacyRoles | length) > 0 %} +
Access and permission levels
- Pharmacy - - Permission level - - Vaccinator - - Status -
- - {{ org.name }} ({{ organisation.id }}) - - - {{ organisation.permissionLevel }} - - {{ organisation.vaccinator }} - - {{ organisation.status }} - - Changepermission level at {{ organisation.name }} - - Deactivate -
+ + + + + + + + + - {% endfor %} - -
Access and permission levels
+ Pharmacy + + Permission level + + Vaccinator + + Status +
+ + + {% for organisation in pharmacyRoles %} + {% set org = data.organisations | findById(organisation.id) %} + + + + {{ org.name }} ({{ organisation.id }}) + + + + {{ organisation.permissionLevel }} + + + {{ organisation.vaccinator }} + + + {{ organisation.status }} + + + Changepermission level at {{ organisation.name }} + + + Deactivate + + + {% endfor %} + + - {{ button({ - text: "Add to another pharmacy", - classes: "nhsuk-button--secondary" - }) }} + {{ button({ + text: "Add to another pharmacy", + classes: "nhsuk-button--secondary" + }) }} -

Deactivate user from all pharmacies

+

Deactivate user from all pharmacies

+ {% endif %}
From 7c461e06ba29a3434b3b4f183ab1a619225375c9 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Wed, 13 May 2026 13:46:33 +0100 Subject: [PATCH 45/68] Remove word --- app/views/pharmacies/users/new.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/pharmacies/users/new.html b/app/views/pharmacies/users/new.html index 1be54372..19586b5c 100644 --- a/app/views/pharmacies/users/new.html +++ b/app/views/pharmacies/users/new.html @@ -71,7 +71,7 @@

Add user

divider: "or" }, { - text: "I’ll add them to individual pharmacies later", + text: "I’ll add them to individual pharmacies", value: "no" } ] From 158397e4453f18675968ef84e8d4a1fca858e676 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Wed, 13 May 2026 16:55:16 +0100 Subject: [PATCH 46/68] Spit out Group admins into a separate table --- app/routes/pharmacies.js | 8 +++++- app/views/pharmacies/users/index.html | 40 +++++++++++++++++++++------ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index 6b7dd40e..e31917f1 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -117,10 +117,16 @@ module.exports = router => { return userOrganisationIds.some(id => pharmacyIds.includes(id) || id === companyId) }) + groupAdministrators = users.filter(function(user) { + return user.organisations.find(org => org.permissionLevel === "Group administrator") + }) + // Filter out group admins from the general user list + users = users.filter((user) => !groupAdministrators.includes(user)) res.render('pharmacies/users/index', { - users + users, + groupAdministrators }) }) diff --git a/app/views/pharmacies/users/index.html b/app/views/pharmacies/users/index.html index ed4857e8..4f9f4a29 100644 --- a/app/views/pharmacies/users/index.html +++ b/app/views/pharmacies/users/index.html @@ -15,8 +15,37 @@

Users

href: "/pharmacies/users/new" }) }} +

Group administrators

+ + + + + + + + + + {% for user in groupAdministrators %} + + + + + + {% endfor %} + + +
+ Name + + Email +
+ {{ user.firstName }} {{ user.lastName }} + + {{ user.email }} +
+ - + - @@ -44,11 +72,7 @@

Users

{{ user.email }} {% endfor %} From 33fd511e6d4263dae8e950648c7d929371771328 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Wed, 13 May 2026 17:15:56 +0100 Subject: [PATCH 47/68] Code style fix --- app/routes/pharmacies.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index e31917f1..2fab5d9f 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -1,5 +1,4 @@ const { getPharmaciesBelongingToOrganisation } = require('../lib/ods'); - const { byName } = require('../lib/utils/by-name'); From 948f4507bd071bc16ebaac6828195cfb5115a2f1 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Wed, 13 May 2026 17:19:44 +0100 Subject: [PATCH 48/68] Code style fixes --- app/routes/pharmacies.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index 2fab5d9f..d0d7d49d 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -116,7 +116,7 @@ module.exports = router => { return userOrganisationIds.some(id => pharmacyIds.includes(id) || id === companyId) }) - groupAdministrators = users.filter(function(user) { + const groupAdministrators = users.filter(function(user) { return user.organisations.find(org => org.permissionLevel === "Group administrator") }) @@ -158,7 +158,6 @@ module.exports = router => { router.get('/pharmacies/users/new-select-pharmacies-check',(req, res) => { const data = req.session.data const pharmacyIds = data.pharmacyIds - const companyId = res.locals.currentOrganisation.id // Get pharmacies selected on previous page const pharmacies = data.organisations.filter((organisation) => pharmacyIds.includes(organisation.id)) @@ -172,7 +171,6 @@ module.exports = router => { router.get('/pharmacies/users/new-permission-level',(req, res) => { const data = req.session.data const pharmacyIds = data.pharmacyIds || [] - const companyId = res.locals.currentOrganisation.id // Get pharmacies selected on previous page const pharmacies = data.organisations.filter((organisation) => pharmacyIds.includes(organisation.id)) @@ -195,7 +193,6 @@ module.exports = router => { router.get('/pharmacies/users/check',(req, res) => { const data = req.session.data const pharmacyIds = data.pharmacyIds || [] - const companyId = res.locals.currentOrganisation.id // Get pharmacies selected on previous page const pharmacies = data.organisations.filter((organisation) => pharmacyIds.includes(organisation.id)) @@ -230,7 +227,7 @@ module.exports = router => { if (pharmacyIds.length > 0) { - for (pharmacyId of pharmacyIds) { + for (const pharmacyId of pharmacyIds) { user.organisations.push({ id: pharmacyId, From 84d007e18e77a74234e2a10de7ebd815325c7e84 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 15 May 2026 13:44:38 +0100 Subject: [PATCH 49/68] Update header to be 'Users' in pharmacy HQ nav --- app/views/includes/header.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/includes/header.html b/app/views/includes/header.html index a0f6a913..516a2f3c 100644 --- a/app/views/includes/header.html +++ b/app/views/includes/header.html @@ -21,7 +21,7 @@ {% set navigationItems = (navigationItems.push({ href: "/pharmacies/users", - text: "Manage users", + text: "Users", active: (currentSection == "pharmacies-users") }), navigationItems) %} {% endif %} From 3866df8832d0814d7d61f6eb207a58d8763607a0 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 15 May 2026 13:53:06 +0100 Subject: [PATCH 50/68] Change intro para --- app/views/pharmacies/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/pharmacies/index.html b/app/views/pharmacies/index.html index 591eedec..2937559c 100644 --- a/app/views/pharmacies/index.html +++ b/app/views/pharmacies/index.html @@ -22,7 +22,7 @@

Pharmacies

-

Add new pharmacies or manage an existing pharmacy.

+

Add new pharmacies, or select an existing pharmacy to add or manage users.

{{ button({ text: "Add pharmacies", From 8f7dada50f1335a02a1b491df03ffba979a1321f Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 15 May 2026 13:57:29 +0100 Subject: [PATCH 51/68] Display actual list of vaccines enabled --- app/views/pharmacies/index.html | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/views/pharmacies/index.html b/app/views/pharmacies/index.html index 2937559c..6cac5ce9 100644 --- a/app/views/pharmacies/index.html +++ b/app/views/pharmacies/index.html @@ -51,11 +51,14 @@

Pharmacies

{{ organisation.name }} ({{ organisation.id}})
{% endfor %} diff --git a/app/views/pharmacies/users/add-to-check.html b/app/views/pharmacies/users/add-to-check.html new file mode 100644 index 00000000..254d626f --- /dev/null +++ b/app/views/pharmacies/users/add-to-check.html @@ -0,0 +1,104 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users/new", + text: "Back" + }) }} +{% endblock %} + +{% block content %} +
+
+ +

Check and add user to pharmacy

+ + {% set pharmaciesHtml %} +
    + {% for pharmacy in pharmacies %} +
  • {{ pharmacy.name }}
  • + {% endfor %} +
+ {% endset %} + + {{ summaryList({ + rows: [ + { + key: { + text: "User" + }, + value: { + html: user.firstName + " " + user.lastName + "
" + user.email + } + }, + { + key: { + text: "Pharmacy" + }, + value: { + html: pharmacy.name + " (" + pharmacy.id + ")" + }, + actions: { + items: [ + { + href: "/pharmacies/users/" + user.id + "/add-to", + text: "Change", + visuallyHiddenText: "pharmacy" + } + ] + } + } if data.groupAdministrator != "yes", + { + key: { + text: "Vaccinator" + }, + value: { + html: data.vaccinator | capitalize + }, + actions: { + items: [ + { + href: "/pharmacies/users/" + user.id + "/add-to-permission-level", + text: "Change", + visuallyHiddenText: "vaccinator" + } + ] + } + }, + { + key: { + text: "Permission level" + }, + value: { + html: data.permissionLevel + }, + actions: { + items: [ + { + href: "/pharmacies/users/" + user.id + "/add-to-permission-level", + text: "Change", + visuallyHiddenText: "permission level" + } + ] + } + } + ] + }) }} + +

{{ user.firstName }} will receive this email telling them they now have access to the pharmacy:

+ +
+ {% include "user-admin/_welcome-email.html" %} +
+ + + {{ button({ + "text": "Confirm and send" + }) }} + + +
+
+{% endblock %} diff --git a/app/views/pharmacies/users/add-to-permission-level.html b/app/views/pharmacies/users/add-to-permission-level.html new file mode 100644 index 00000000..971049f7 --- /dev/null +++ b/app/views/pharmacies/users/add-to-permission-level.html @@ -0,0 +1,88 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% set pageName = "Add pharmacies" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users/" + user.id + "/add-to" + }) }} +{% endblock %} + +{% block content %} +
+
+
+ +

What should {{ user.firstName }} {{ user.lastName}}'s role at {{ pharmacy.name }} be?

+ + {{ radios({ + name: "permissionLevel", + idPrefix: "permission-level", + fieldset: { + legend: { + text: "Permission level", + size: "s" + } + }, + value: data.permissionLevel, + items: [ + { + value: "Recorder", + text: "Recorder", + hint: { + text: "Record vaccinations only" + } + }, + { + value: "Administrator", + text: "Administrator", + hint: { + text: "Record vaccinations, create reports and manage vaccines" + } + }, + { + value: "Lead administrator", + text: "Lead administrator", + hint: { + text: "Record vaccinations, create reports, manage vaccines and users" + } + } + ] + }) }} + + {{ radios({ + name: "vaccinator", + fieldset: { + legend: { + text: "Are they a vaccinator?", + size: "s" + } + }, + hint: { + text: "Vaccination records include the name of the person who gave the vaccination" + }, + value: data.vaccinator, + items: [ + { + value: "yes", + text: "Yes", + id: "vaccinator" + }, + { + value: "no", + text: "No" + } + ] + }) }} + + {{ button({ + text: "Continue" + }) }} + + +
+
+ +{% endblock %} diff --git a/app/views/pharmacies/users/add-to.html b/app/views/pharmacies/users/add-to.html new file mode 100644 index 00000000..b14ecbbb --- /dev/null +++ b/app/views/pharmacies/users/add-to.html @@ -0,0 +1,48 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies-users" %} + +{% set pageName = "Add pharmacies" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users/" + user.id + }) }} +{% endblock %} + +{% block content %} +
+
+
+ + {% set items = [] %} + + {% for pharmacy in pharmacies %} + {% set items = (items.push({ + text: pharmacy.name + ", " + pharmacy.address.postcode + " (" + pharmacy.id + ")", + value: pharmacy.id + }), items) %} + {% endfor %} + + {{ radios({ + id: "pharmacy-id", + name: "pharmacyId", + value: data.pharmacyId, + fieldset: { + legend: { + text: "Which pharmacy would you like to add " + user.firstName + " " + user.lastName + " to?", + size: "l" + } + }, + items: items + }) }} + + {{ button({ + text: "Continue" + }) }} + + +
+
+ +{% endblock %} diff --git a/app/views/pharmacies/users/user.html b/app/views/pharmacies/users/user.html index 710d565f..52387be3 100644 --- a/app/views/pharmacies/users/user.html +++ b/app/views/pharmacies/users/user.html @@ -14,6 +14,21 @@
+ {% if addedToPharmacy %} + {% set html %} +

+ Added to pharmacy +

+

The user can now log in to {{ addedToPharmacy.name }}

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

{{ pageName }}

{{ summaryList({ @@ -68,7 +83,7 @@

{{ pageName }}

{{ organisation.permissionLevel }}
Current usersUsers at individual pharmacies
@@ -26,9 +55,8 @@

Users

Email
- Role + Pharmacies
- {% if groupRole %} - Group administrator - {% else %} - {{ user.organisations | length | plural("pharmacy") }} - {% endif %} + {{ user.organisations | length }}
- {% if loop.index0 == 0 %} - Not yet added - {% else %} - COVID-19, flu - {% endif %} + {% set vaccinesEnabled = [] %} + {% for vaccine in organisation.vaccines %} + {% if vaccine.status == "enabled" %} + {% set vaccinesEnabled = (vaccinesEnabled.push(vaccine.name), vaccinesEnabled) %} + {% endif %} + {% endfor %} + + {{ vaccinesEnabled | sort | join(", ") }} {{ organisationUserCounts[organisation.id] }} From 2e7ed5c9fe65231baa29285bd70a11db98668fb9 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 15 May 2026 13:57:38 +0100 Subject: [PATCH 52/68] Add enabled vaccines for pharmacy --- app/data/organisations.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/data/organisations.js b/app/data/organisations.js index b4fa4e0f..a7296645 100644 --- a/app/data/organisations.js +++ b/app/data/organisations.js @@ -8339,7 +8339,11 @@ module.exports = [ lastName: "Brown", email: "james.brown@nhs.net", phone: "01234 567890" - } + }, + vaccines: [ + {name: "COVID-19", status: "enabled"}, + {name: "flu", status: "enabled"} + ] }, { id: 'PDL93', From 7335a553c707cda6cec4c5c1faee12318df7567c Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 15 May 2026 13:58:46 +0100 Subject: [PATCH 53/68] Enable flu by default --- app/routes/pharmacies.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index d0d7d49d..aeb7decf 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -85,7 +85,9 @@ module.exports = router => { companyId: companyId, address: pharmacy.address, status: 'Active', - vaccines: [], + vaccines: [ + {name: 'flu', status: 'enabled'} + ], sites: [ { id: pharmacy.id, From 606d5d05f44b95e89182981fcd351ffe1bd21888 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Fri, 15 May 2026 14:04:26 +0100 Subject: [PATCH 54/68] Fix pluralisation --- app/views/pharmacies/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/pharmacies/index.html b/app/views/pharmacies/index.html index 6cac5ce9..e7e388d3 100644 --- a/app/views/pharmacies/index.html +++ b/app/views/pharmacies/index.html @@ -9,7 +9,7 @@ {% if added %} {% set html %}

- {{ added }} pharmacies added + {{ added | int | plural('pharmacy') }} added

You can now add or assign users to these pharmacies.

{% endset %} From 13a6818b0e00827369d9811ee769f092986a8b70 Mon Sep 17 00:00:00 2001 From: Anna-Sutton Date: Fri, 15 May 2026 17:34:25 +0100 Subject: [PATCH 55/68] Create welcome-email-group-admin.html --- .../users/welcome-email-group-admin.html | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 app/views/pharmacies/users/welcome-email-group-admin.html diff --git a/app/views/pharmacies/users/welcome-email-group-admin.html b/app/views/pharmacies/users/welcome-email-group-admin.html new file mode 100644 index 00000000..6563e6ea --- /dev/null +++ b/app/views/pharmacies/users/welcome-email-group-admin.html @@ -0,0 +1,39 @@ +

From: NHS Record a vaccination service (RAVS)
+ Subject: Start using Record a vaccination at Example Pharmacy +

+
+ +

Dear Example User,

+ +

You’ve been invited to use the NHS Record a vaccination service at Example Pharmacy.

+ +

Get started

+ +

We’ve created an Okta account for you to securely access the service.

+ +

Here’s what you need to do:

+ +

1. Activate your Okta account

+

You’ll receive a 'Welcome to Okta' email (from noreply@okta.com).

+ +

Activate the link within 7 days.

+ +

If you cannot find the email, check your spam or junk.

+ +

2. Log in to Record a vaccination

+ +

Once you've activated your Okta account, log in to www.ravs.england.nhs.uk using your Okta username and password.

+ +

You can also access the service through your Okta account by selecting 'RAVS (PROD) app'.

+ +

Kind regards,
+ NHS Record a vaccination

+ +

User guide and training webinars

+

Visit www.guide.ravs.england.nhs.uk or book a webinar.

+ +

Helpdesk

+ +

Email: ravs.support@england.nhs.uk
+ Phone: 0333 038 4268 (select option 3)
+ Monday to Friday: 8am to 6pm

From abbb74ee0089a84da2729b08e6cd78ae150fa267 Mon Sep 17 00:00:00 2001 From: Anna-Sutton Date: Fri, 15 May 2026 17:38:57 +0100 Subject: [PATCH 56/68] Added content for welcome-email-group-admin.html --- .../users/welcome-email-group-admin.html | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/app/views/pharmacies/users/welcome-email-group-admin.html b/app/views/pharmacies/users/welcome-email-group-admin.html index 6563e6ea..fda6d57c 100644 --- a/app/views/pharmacies/users/welcome-email-group-admin.html +++ b/app/views/pharmacies/users/welcome-email-group-admin.html @@ -5,22 +5,21 @@

Dear Example User,

-

You’ve been invited to use the NHS Record a vaccination service at Example Pharmacy.

- -

Get started

+

You’ve been invited to use the NHS Record a vaccination service as a group administrator for (company name).

-

We’ve created an Okta account for you to securely access the service.

- -

Here’s what you need to do:

+

Here’s what you need to do next:

1. Activate your Okta account

+ +

We’ve created an Okta account for you to securely access the service.

+

You’ll receive a 'Welcome to Okta' email (from noreply@okta.com).

Activate the link within 7 days.

If you cannot find the email, check your spam or junk.

-

2. Log in to Record a vaccination

+

2. Log in to the service

Once you've activated your Okta account, log in to www.ravs.england.nhs.uk using your Okta username and password.

@@ -29,11 +28,4 @@

Kind regards,
NHS Record a vaccination

-

User guide and training webinars

-

Visit www.guide.ravs.england.nhs.uk or book a webinar.

- -

Helpdesk

- -

Email: ravs.support@england.nhs.uk
- Phone: 0333 038 4268 (select option 3)
- Monday to Friday: 8am to 6pm

+ From 56e4b7388773f30e1c4f45368dbda8f9aa0cff49 Mon Sep 17 00:00:00 2001 From: Anna-Sutton Date: Sat, 16 May 2026 15:50:05 +0100 Subject: [PATCH 57/68] Added link to Welcome email for group admin to Check and add user --- app/views/pharmacies/users/check.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/pharmacies/users/check.html b/app/views/pharmacies/users/check.html index 93ab9861..3d1e0625 100644 --- a/app/views/pharmacies/users/check.html +++ b/app/views/pharmacies/users/check.html @@ -116,7 +116,7 @@

Check and add {{ "group administrator" if data.permi

{{ data.firstName }} will receive this welcome email with information about activating an account:

- {% include "user-admin/_welcome-email.html" %} + {% include "welcome-email-group-admin.html" %}
From 965b6b8bd25751f4e92ab61efbcc5f64fe15d5d8 Mon Sep 17 00:00:00 2001 From: Anna-Sutton Date: Sat, 16 May 2026 15:55:28 +0100 Subject: [PATCH 58/68] Update check.html --- app/views/pharmacies/users/check.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/pharmacies/users/check.html b/app/views/pharmacies/users/check.html index 3d1e0625..60d6d61e 100644 --- a/app/views/pharmacies/users/check.html +++ b/app/views/pharmacies/users/check.html @@ -116,7 +116,7 @@

Check and add {{ "group administrator" if data.permi

{{ data.firstName }} will receive this welcome email with information about activating an account:

- {% include "welcome-email-group-admin.html" %} + {% include "/welcome-email-group-admin.html" %}
From d479d9ac80eefebd216df9a4f3a3cb1c19c12d35 Mon Sep 17 00:00:00 2001 From: Anna-Sutton Date: Sat, 16 May 2026 15:59:43 +0100 Subject: [PATCH 59/68] Update check.html --- app/views/pharmacies/users/check.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/pharmacies/users/check.html b/app/views/pharmacies/users/check.html index 60d6d61e..a9165b28 100644 --- a/app/views/pharmacies/users/check.html +++ b/app/views/pharmacies/users/check.html @@ -116,7 +116,7 @@

Check and add {{ "group administrator" if data.permi

{{ data.firstName }} will receive this welcome email with information about activating an account:

- {% include "/welcome-email-group-admin.html" %} + {% include "/pharmacies/users/welcome-email-group-admin.html" %}
From 31847193bcd6413981824448f1d923b06c25bc2a Mon Sep 17 00:00:00 2001 From: Anna-Sutton Date: Sat, 16 May 2026 16:07:23 +0100 Subject: [PATCH 60/68] Update check.html --- app/views/pharmacies/users/check.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/pharmacies/users/check.html b/app/views/pharmacies/users/check.html index a9165b28..52d443a4 100644 --- a/app/views/pharmacies/users/check.html +++ b/app/views/pharmacies/users/check.html @@ -116,7 +116,7 @@

Check and add {{ "group administrator" if data.permi

{{ data.firstName }} will receive this welcome email with information about activating an account:

- {% include "/pharmacies/users/welcome-email-group-admin.html" %} + {% include "pharmacies/users/welcome-email-group-admin.html" %}
From 8d0628d71314891d834978843a95be24eb89fab2 Mon Sep 17 00:00:00 2001 From: Anna-Sutton Date: Sat, 16 May 2026 16:15:15 +0100 Subject: [PATCH 61/68] Update welcome-email-group-admin.html Changed the subject line to include the name of the pharmacy company. --- app/views/pharmacies/users/welcome-email-group-admin.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/pharmacies/users/welcome-email-group-admin.html b/app/views/pharmacies/users/welcome-email-group-admin.html index fda6d57c..53643e02 100644 --- a/app/views/pharmacies/users/welcome-email-group-admin.html +++ b/app/views/pharmacies/users/welcome-email-group-admin.html @@ -1,9 +1,9 @@

From: NHS Record a vaccination service (RAVS)
- Subject: Start using Record a vaccination at Example Pharmacy + Subject: Start using Record a vaccination at (company name)


-

Dear Example User,

+

Dear (first name) (last name),

You’ve been invited to use the NHS Record a vaccination service as a group administrator for (company name).

From f619409255d36715ad3482f9cea86060c4f1a886 Mon Sep 17 00:00:00 2001 From: Anna-Sutton Date: Sat, 16 May 2026 16:17:48 +0100 Subject: [PATCH 62/68] Updated the line introducing the email. --- app/views/pharmacies/users/check.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/pharmacies/users/check.html b/app/views/pharmacies/users/check.html index 52d443a4..d914dcbe 100644 --- a/app/views/pharmacies/users/check.html +++ b/app/views/pharmacies/users/check.html @@ -113,10 +113,10 @@

Check and add {{ "group administrator" if data.permi ] }) }} -

{{ data.firstName }} will receive this welcome email with information about activating an account:

+

{{ data.firstName }} will receive this welcome email telling them how to access the service:

- {% include "pharmacies/users/welcome-email-group-admin.html" %} + {% include "user-admin/_welcome-email.html" %}
From 96ed4ef9cd19d070ed49d00dae04ae5a7db58e4d Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 18 May 2026 15:20:03 +0100 Subject: [PATCH 63/68] Add ability to add user to more pharmacies --- app/routes/pharmacies.js | 114 +++++++++++++++--- app/views/pharmacies/pharmacy.html | 2 +- app/views/pharmacies/users/add-to-check.html | 104 ++++++++++++++++ .../users/add-to-permission-level.html | 88 ++++++++++++++ app/views/pharmacies/users/add-to.html | 48 ++++++++ app/views/pharmacies/users/user.html | 28 ++++- 6 files changed, 364 insertions(+), 20 deletions(-) create mode 100644 app/views/pharmacies/users/add-to-check.html create mode 100644 app/views/pharmacies/users/add-to-permission-level.html create mode 100644 app/views/pharmacies/users/add-to.html diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index aeb7decf..ec5a3477 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -254,19 +254,6 @@ module.exports = router => { - router.get('/pharmacies/users/:id',(req, res) => { - const data = req.session.data - const id = req.params.id - const user = data.users.find((user) => user.id === id) - - const pharmacyRoles = (user.organisations || []).filter(role => role.permissionLevel !== "Group administrator") - - res.render('pharmacies/users/user', { - user, - pharmacyRoles - }) - }) - router.get('/pharmacies/:id/add-user',(req, res) => { const data = req.session.data @@ -379,6 +366,7 @@ module.exports = router => { const data = req.session.data const pharmacyId = req.params.pharmacyId const userId = req.params.userId + const from = data.from const user = data.users.find(user => user.id === userId) const pharmacy = data.organisations.find(organisation => organisation.id === pharmacyId) @@ -388,9 +376,81 @@ module.exports = router => { role.permissionLevel = data.permissionLevel role.vaccinator = (data.vaccinator === "yes") - res.redirect(`/pharmacies/${pharmacy.id}`) + if (from === "user") { + res.redirect(`/pharmacies/users/${user.id}`) + } else { + res.redirect(`/pharmacies/${pharmacy.id}`) + } + }) + router.get('/pharmacies/users/:id/add-to',(req, res) => { + const data = req.session.data + const userId = req.params.id + const user = data.users.find(user => user.id === userId) + const companyId = res.locals.currentOrganisation.id + + const pharmacyIdsThatUserAlreadyHasAccessTo = user.organisations.map(organisation => organisation.id) + + const pharmacies = data.organisations.filter((organisation) => (organisation.companyId === companyId) && !pharmacyIdsThatUserAlreadyHasAccessTo.includes(organisation.id)) + + + res.render('pharmacies/users/add-to', { + user, + pharmacies + }) + }) + + router.get('/pharmacies/users/:id/add-to-permission-level',(req, res) => { + const data = req.session.data + const userId = req.params.id + const user = data.users.find(user => user.id === userId) + + const pharmacy = data.organisations.find(organisation => organisation.id === data.pharmacyId) + + + res.render('pharmacies/users/add-to-permission-level', { + user, + pharmacy + }) + }) + + router.get('/pharmacies/users/:id/add-to-check',(req, res) => { + const data = req.session.data + const userId = req.params.id + const user = data.users.find(user => user.id === userId) + + const pharmacy = data.organisations.find(organisation => organisation.id === data.pharmacyId) + + + res.render('pharmacies/users/add-to-check', { + user, + pharmacy + }) + }) + + router.post('/pharmacies/users/:id/add-to-check-answer',(req, res) => { + const data = req.session.data + const id = req.params.id + const user = data.users.find(user => user.id === id) + const pharmacy = data.organisations.find(organisation => organisation.id === data.pharmacyId) + + user.organisations.push({ + id: pharmacy.id, + status: 'Active', + permissionLevel: data.permissionLevel, + vaccinator: (data.vaccinator === 'yes') + }) + + // Reset answers + data.permissionLevel = '' + data.vaccinator = '' + data.pharmacyId = '' + + res.redirect(`/pharmacies/users/${id}?addedToPharmacyId=${pharmacy.id}`) + }) + + router.get('/pharmacies/:id', (req, res) => { const data = req.session.data const id = req.params.id @@ -418,4 +478,30 @@ module.exports = router => { }) }) + + router.get('/pharmacies/users/:id',(req, res) => { + const data = req.session.data + const id = req.params.id + const user = data.users.find((user) => user.id === id) + const companyId = res.locals.currentOrganisation.id + + const addedToPharmacyId = req.query.addedToPharmacyId + let addedToPharmacy + + if (addedToPharmacyId) { + addedToPharmacy = data.organisations.find(organisation => organisation.id === addedToPharmacyId) + } + + const totalPharmaciesAtOrganisation = data.organisations.filter(organisation => organisation.companyId === companyId).length + + const pharmacyRoles = (user.organisations || []).filter(role => role.permissionLevel !== "Group administrator") + + res.render('pharmacies/users/user', { + user, + pharmacyRoles, + addedToPharmacy, + totalPharmaciesAtOrganisation + }) + }) + } diff --git a/app/views/pharmacies/pharmacy.html b/app/views/pharmacies/pharmacy.html index ddc9298b..3142a138 100644 --- a/app/views/pharmacies/pharmacy.html +++ b/app/views/pharmacies/pharmacy.html @@ -111,7 +111,7 @@

Users

{{ userOrganisationPermissions[user.id].status }}

- Changepermission level for {{ user.firstName }} {{ user.lastName }} + Changepermission level for {{ user.firstName }} {{ user.lastName }}
- {{ organisation.vaccinator }} + {{ "Yes" if organisation.vaccinator else "No" }} {{ organisation.status }} @@ -84,10 +99,13 @@

{{ pageName }}

- {{ button({ - text: "Add to another pharmacy", - classes: "nhsuk-button--secondary" - }) }} + {% if totalPharmaciesAtOrganisation > (user.organisations | length) %} + {{ button({ + text: "Add to another pharmacy", + classes: "nhsuk-button--secondary", + href: "/pharmacies/users/" + user.id + "/add-to" + }) }} + {% endif %}

Deactivate user from all pharmacies

{% endif %} From 68021565c1f118b13eabf5d5c2a30009711628a7 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 18 May 2026 15:23:12 +0100 Subject: [PATCH 64/68] Update content --- app/views/pharmacies/users/new.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/pharmacies/users/new.html b/app/views/pharmacies/users/new.html index 19586b5c..c835c3fc 100644 --- a/app/views/pharmacies/users/new.html +++ b/app/views/pharmacies/users/new.html @@ -71,7 +71,7 @@

Add user

divider: "or" }, { - text: "I’ll add them to individual pharmacies", + text: "I want to add them to individual pharmacies", value: "no" } ] From 83065d65a55664bb266860c4a4ab13ace23cc714 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Tue, 19 May 2026 09:45:21 +0100 Subject: [PATCH 65/68] Add deactivation flow --- app/routes/pharmacies.js | 39 ++++++++++++++++++- .../users/deactivate-from-pharmacy.html | 38 ++++++++++++++++++ app/views/pharmacies/users/user.html | 18 ++++++++- 3 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 app/views/pharmacies/users/deactivate-from-pharmacy.html diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index ec5a3477..333fe60d 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -362,6 +362,37 @@ module.exports = router => { }) }) + router.get('/pharmacies/:pharmacyId/users/:userId/deactivate-from-pharmacy',(req, res) => { + const data = req.session.data + const pharmacyId = req.params.pharmacyId + const userId = req.params.userId + + const user = data.users.find(user => user.id === userId) + const pharmacy = data.organisations.find(organisation => organisation.id === pharmacyId) + + res.render('pharmacies/users/deactivate-from-pharmacy', { + user, + pharmacy + }) + }) + + router.post('/pharmacies/:pharmacyId/users/:userId/deactivate-from-pharmacy-answer',(req, res) => { + const data = req.session.data + const pharmacyId = req.params.pharmacyId + const userId = req.params.userId + + const user = data.users.find(user => user.id === userId) + const pharmacy = data.organisations.find(organisation => organisation.id === pharmacyId) + + const role = user.organisations.find(role => role.id === pharmacyId) + + role.status = 'Deactivated' + + res.redirect(`/pharmacies/users/${user.id}?deactivatedFromPharmacyId=${pharmacy.id}`) + + }) + + router.post('/pharmacies/:pharmacyId/users/:userId/change-answer',(req, res) => { const data = req.session.data const pharmacyId = req.params.pharmacyId @@ -486,11 +517,16 @@ module.exports = router => { const companyId = res.locals.currentOrganisation.id const addedToPharmacyId = req.query.addedToPharmacyId - let addedToPharmacy + const deactivatedFromPharmacyId = req.query.deactivatedFromPharmacyId + + let addedToPharmacy, deactivatedFromPharmacy if (addedToPharmacyId) { addedToPharmacy = data.organisations.find(organisation => organisation.id === addedToPharmacyId) } + if (deactivatedFromPharmacyId) { + deactivatedFromPharmacy = data.organisations.find(organisation => organisation.id === deactivatedFromPharmacyId) + } const totalPharmaciesAtOrganisation = data.organisations.filter(organisation => organisation.companyId === companyId).length @@ -500,6 +536,7 @@ module.exports = router => { user, pharmacyRoles, addedToPharmacy, + deactivatedFromPharmacy, totalPharmaciesAtOrganisation }) }) diff --git a/app/views/pharmacies/users/deactivate-from-pharmacy.html b/app/views/pharmacies/users/deactivate-from-pharmacy.html new file mode 100644 index 00000000..8f0c5461 --- /dev/null +++ b/app/views/pharmacies/users/deactivate-from-pharmacy.html @@ -0,0 +1,38 @@ +{% extends 'layout.html' %} + +{% set pageName = "Deactivate account" %} + +{% set currentSection = "pharmacies-users" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/users/" + user.id, + text: "Back" + }) }} +{% endblock %} + +{% block content %} +
+
+ + +

Deactivate {{ user.firstName }} {{ user.lastName }} from {{ pharmacy.name }}

+ +

Once you deactivate {{ user.firstName }} {{ user.lastName }} ({{ user.email}}), they cannot sign in and use NHS Record a vaccination at this pharmacy. They’ll receive an email to confirm their account has been deactivated.

+ +

Their Okta account will remain active, so they can continue to access other services.

+ +

You can reactivate their account anytime.

+ +
+ {{ button({ + "text": "Deactivate", + classes: "nhsuk-button--warning" + }) }} +
+ +
+
+ + +{% endblock %} diff --git a/app/views/pharmacies/users/user.html b/app/views/pharmacies/users/user.html index 52387be3..d5f4acdd 100644 --- a/app/views/pharmacies/users/user.html +++ b/app/views/pharmacies/users/user.html @@ -28,6 +28,20 @@

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

+ Deactivated from pharmacy +

+

The user has been deactivated from {{ deactivatedFromPharmacy.name }}

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

{{ pageName }}

@@ -92,7 +106,7 @@

{{ pageName }}

Changepermission level at {{ organisation.name }} - Deactivate + Deactivate {% endfor %} @@ -107,7 +121,7 @@

{{ pageName }}

}) }} {% endif %} -

Deactivate user from all pharmacies

+

Deactivate user from all pharmacies

{% endif %}
From 774a5f6f5b684f1502593cc56f8a5cbeed5569a7 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Tue, 19 May 2026 09:47:18 +0100 Subject: [PATCH 66/68] Add visually-hidden text --- app/views/pharmacies/users/user.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/pharmacies/users/user.html b/app/views/pharmacies/users/user.html index d5f4acdd..973e3e2a 100644 --- a/app/views/pharmacies/users/user.html +++ b/app/views/pharmacies/users/user.html @@ -106,7 +106,7 @@

{{ pageName }}

Changepermission level at {{ organisation.name }} - Deactivate + Deactivate from {{ organisation.name }} {% endfor %} @@ -121,7 +121,7 @@

{{ pageName }}

}) }} {% endif %} -

Deactivate user from all pharmacies

+ {% endif %}
From df4736395aeb4b494138378c9aa03eaf97030ee3 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Tue, 19 May 2026 10:02:27 +0100 Subject: [PATCH 67/68] Add ability to deactivate pharmacy --- app/routes/pharmacies.js | 17 ++++++++++++++ app/views/pharmacies/deactivate.html | 33 ++++++++++++++++++++++++++++ app/views/pharmacies/pharmacy.html | 19 +++++++++++----- 3 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 app/views/pharmacies/deactivate.html diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index 333fe60d..be88e31d 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -252,8 +252,25 @@ module.exports = router => { res.redirect('/pharmacies/users?added=true') }) + router.get('/pharmacies/:id/deactivate',(req, res) => { + const data = req.session.data + const id = req.params.id + const pharmacy = data.organisations.find(organisation => organisation.id === id) + + res.render('pharmacies/deactivate', { + pharmacy + }) + }) + router.post('/pharmacies/:id/deactivate-answer',(req, res) => { + const data = req.session.data + const id = req.params.id + const pharmacy = data.organisations.find(organisation => organisation.id === id) + pharmacy.status = 'Deactivated' + + res.redirect(`/pharmacies/${id}`) + }) router.get('/pharmacies/:id/add-user',(req, res) => { const data = req.session.data diff --git a/app/views/pharmacies/deactivate.html b/app/views/pharmacies/deactivate.html new file mode 100644 index 00000000..87864833 --- /dev/null +++ b/app/views/pharmacies/deactivate.html @@ -0,0 +1,33 @@ +{% extends 'layout.html' %} + +{% set currentSection = "pharmacies" %} + +{% set pageName = "Deactivate " + pharmacy.name + " (" + pharmacy.id + ")" %} + +{% block beforeContent %} + {{ backLink({ + href: "/pharmacies/" + pharmacy.id + }) }} +{% endblock %} + +{% block content %} +
+
+ +

{{ pageName }}

+ +

Once this pharmacy has been deactivated, users will no longer be able to record vaccinations for this pharmacy.

+ +

For 90 days they will still be able to edit records and create reports. After 90 days the pharmacy will be closed and users will no longer have access to the Record a vaccination service for the pharmacy.

+ +
+ {{ button({ + "text": "Confirm and deactivate", + classes: "nhsuk-button--warning" + }) }} +
+ +
+
+ +{% endblock %} diff --git a/app/views/pharmacies/pharmacy.html b/app/views/pharmacies/pharmacy.html index 3142a138..98003614 100644 --- a/app/views/pharmacies/pharmacy.html +++ b/app/views/pharmacies/pharmacy.html @@ -58,7 +58,14 @@

{{ pageName }}

] }) }} -

Deactivate this pharmacy

+ {% if organisation.status == 'Deactivated' %} +

This pharmacy has been deactivated.

+ +

It will be closed in 90 days.

+ {% else %} +

Deactivate this pharmacy

+ + {% endif %}

Users

@@ -66,10 +73,12 @@

Users

- {{ button({ - href: "/pharmacies/" + organisation.id + "/add-user", - text: "Add user" - }) }} + {% if organisation.status != 'Deactivated' %} + {{ button({ + href: "/pharmacies/" + organisation.id + "/add-user", + text: "Add user" + }) }} + {% endif %} {% if (users | length) > 0 %} From 27d0652f0eb44ddc1c4ff2ee91eb52a82d3fae03 Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Thu, 21 May 2026 13:09:40 +0100 Subject: [PATCH 68/68] Update wording --- app/views/auth/medicare-sign-in.html | 4 ++-- app/views/auth/okta-sign-in.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/auth/medicare-sign-in.html b/app/views/auth/medicare-sign-in.html index 479a431b..7def5e0f 100644 --- a/app/views/auth/medicare-sign-in.html +++ b/app/views/auth/medicare-sign-in.html @@ -99,8 +99,8 @@

Testing area

Log in as:

diff --git a/app/views/auth/okta-sign-in.html b/app/views/auth/okta-sign-in.html index 0b913e3a..bb0a9173 100644 --- a/app/views/auth/okta-sign-in.html +++ b/app/views/auth/okta-sign-in.html @@ -38,8 +38,8 @@

Testing area

  • Recorder for an NHS trust
  • Recorder for multiple NHS trusts
  • Admin at 2 NHS trusts
  • -
  • Lead admin at a small chain of pharmacies
  • -
  • Lead admin for a large chain of pharmacies with the same name
  • +
  • Group admin at a small chain of pharmacies
  • +
  • Group admin for a large chain of pharmacies with the same name
  • Regional lead
  • Support user
  • Lead admin at a trust with all vaccines enabled