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

Pharmacies

href: "/pharmacies/select" }) }} - - -
Pharmacies added ({{ organisations | length }})
- {% if (organisations | length) > 10 %} -
- - -
- {% endif %} - - - - - - - - - - - - {% for organisation in organisations %} - - - - - - + {% if (organisations | length) > 0 %} +
- Name - - Vaccines - - Users - - Status - - Actions -
- {{ organisation.name }} ({{ organisation.id}}) - - {% set vaccinesEnabled = [] %} - {% for vaccine in organisation.vaccines %} - {% if vaccine.status == "enabled" %} - {% set vaccinesEnabled = (vaccinesEnabled.push(vaccine.name), vaccinesEnabled) %} - {% endif %} - {% endfor %} - - {{ (vaccinesEnabled | sort | join(", ")) | capitaliseFirstLetter }} - - {{ organisationUserCounts[organisation.id] }} - - {{ organisation.status }} - - Manage -
+ +
Pharmacies added ({{ organisations | length }})
+ {% if (organisations | length) > 10 %} +
+ + +
+ {% endif %} + + + + + + + + - {% endfor %} + + + {% for organisation in organisations %} + + + + + + + + {% endfor %} - -
+ Name + + Vaccines + + Users + + Status + + Actions +
+ {{ organisation.name }} ({{ organisation.id}}) + + {% set vaccinesEnabled = [] %} + {% for vaccine in organisation.vaccines %} + {% if vaccine.status == "enabled" %} + {% set vaccinesEnabled = (vaccinesEnabled.push(vaccine.name), vaccinesEnabled) %} + {% endif %} + {% endfor %} + + {{ (vaccinesEnabled | sort | join(", ")) | capitaliseFirstLetter }} + + {{ organisationUserCounts[organisation.id] }} + + {{ organisation.status }} + + Manage +
+ + + {% endif %} {% if closedOrganisations.length > 0 %}
From 2ca114bbaabd36d9e627b0c3cd92056df85d9c2e Mon Sep 17 00:00:00 2001 From: Caitlin Roach Date: Thu, 9 Jul 2026 13:35:04 +0100 Subject: [PATCH 2/6] Updates to allow access to ind pharmacies for group admins --- app/locals.js | 32 ++- app/routes/auth.js | 202 ++++++++++++++++++ app/routes/pharmacies.js | 159 ++++++++++++-- app/routes/user-admin.js | 8 + app/views/includes/header.html | 10 +- .../pharmacies/add-user-permission-level.html | 52 ++++- app/views/pharmacies/add-user.html | 21 ++ app/views/pharmacies/pharmacy.html | 8 + 8 files changed, 473 insertions(+), 19 deletions(-) diff --git a/app/locals.js b/app/locals.js index 8a0697d7..b3388b0d 100644 --- a/app/locals.js +++ b/app/locals.js @@ -1,5 +1,18 @@ module.exports = function(req, res, next) { + const buildSwitchedPharmacyOrganisationSetting = (sessionData, currentOrganisationId) => { + if (!sessionData.previousOrganisationId || !currentOrganisationId) { + return null + } + + return { + id: currentOrganisationId, + status: 'Active', + permissionLevel: 'Lead administrator', + vaccinator: false + } + } + const monthNames = [ 'January', 'February', @@ -25,7 +38,24 @@ module.exports = function(req, res, next) { // Set currentUser for convenience if (req.session.data.currentUserId) { - res.locals.currentUser = req.session.data.users.find((user) => user.id === req.session.data.currentUserId) + const sessionUser = req.session.data.users.find((user) => user.id === req.session.data.currentUserId) + const switchedOrganisationSetting = buildSwitchedPharmacyOrganisationSetting( + req.session.data, + req.session.data.currentOrganisationId + ) + + if (sessionUser && switchedOrganisationSetting) { + const userOrganisations = (sessionUser.organisations || []) + .filter((organisation) => organisation.id !== switchedOrganisationSetting.id) + .map((organisation) => ({ ...organisation })) + + res.locals.currentUser = { + ...sessionUser, + organisations: [...userOrganisations, switchedOrganisationSetting] + } + } else { + res.locals.currentUser = sessionUser + } } else { res.locals.currentUser = null } diff --git a/app/routes/auth.js b/app/routes/auth.js index cb3fb725..859d7b93 100644 --- a/app/routes/auth.js +++ b/app/routes/auth.js @@ -1,5 +1,13 @@ module.exports = router => { + const asArray = (value) => { + if (value === undefined || value === null) { + return [] + } + + return Array.isArray(value) ? value : [value] + } + const getDefaultLandingPath = (data, organisationId) => { const organisation = (data.organisations || []).find((item) => item.id === organisationId) @@ -22,6 +30,109 @@ module.exports = router => { return '/home' } + const getCurrentUserFromSessionData = (data) => { + if (!data) { + return null + } + + if (data.currentUserId) { + + router.get('/auth/restore-organisation', (req, res) => { + const data = req.session.data + const previousOrganisationId = data.previousOrganisationId + + if (!previousOrganisationId) { + return res.redirect('/auth/select-organisation') + } + + data.currentOrganisationId = previousOrganisationId + delete data.previousOrganisationId + + res.redirect(getDefaultLandingPath(data, previousOrganisationId)) + }) + return (data.users || []).find((user) => user.id === data.currentUserId) || null + } + + if (data.email) { + return (data.users || []).find((user) => user.email === data.email) || null + } + + return null + } + + const isTemporaryAccessInactive = (addedOnIsoDate, deactivatedOnIsoDate) => { + if (deactivatedOnIsoDate) { + return true + } + + if (!addedOnIsoDate) { + return false + } + + const expiryDate = new Date(`${addedOnIsoDate}T12:00:00`) + expiryDate.setDate(expiryDate.getDate() + 7) + + const today = new Date() + today.setHours(0, 0, 0, 0) + + return expiryDate < today + } + + const getGroupAdminSwitchContext = (data, user) => { + if (!data || !user) { + return { + isEligible: false, + groupOrganisation: null, + temporaryPharmacies: [] + } + } + + const activeGroupAdminOrganisationIds = (user.organisations || []) + .filter((organisation) => organisation.permissionLevel === 'Group administrator' && organisation.status === 'Active') + .map((organisation) => organisation.id) + + const groupOrganisation = (data.organisations || []).find((organisation) => { + return organisation.type === 'Pharmacy HQ' && activeGroupAdminOrganisationIds.includes(organisation.id) + }) + + if (!groupOrganisation) { + return { + isEligible: false, + groupOrganisation: null, + temporaryPharmacies: [] + } + } + + const temporaryPharmacyIds = [...new Set(asArray(data.groupAdminTemporaryPharmacyIds))] + const temporaryPharmacyAddedDates = data.groupAdminTemporaryPharmacyAddedDates || {} + const temporaryPharmacyDeactivatedDates = data.groupAdminTemporaryPharmacyDeactivatedDates || {} + + const temporaryPharmacies = temporaryPharmacyIds + .map((pharmacyId) => { + const organisation = (data.organisations || []).find((item) => item.id === pharmacyId) + + return { + organisation, + addedOn: temporaryPharmacyAddedDates[pharmacyId], + deactivatedOn: temporaryPharmacyDeactivatedDates[pharmacyId] + } + }) + .filter((item) => item.organisation) + .filter((organisation) => { + return organisation.organisation.status === 'Active' && organisation.organisation.companyId === groupOrganisation.id + }) + .filter((item) => { + return !isTemporaryAccessInactive(item.addedOn, item.deactivatedOn) + }) + .map((item) => item.organisation) + + return { + isEligible: temporaryPharmacies.length > 1, + groupOrganisation, + temporaryPharmacies + } + } + router.post('/auth/sign-in', (req, res) => { const data = req.session.data @@ -119,6 +230,97 @@ module.exports = router => { } }) + router.get('/auth/switch-organisation/work-type', (req, res) => { + const data = req.session.data + const user = getCurrentUserFromSessionData(data) + const context = getGroupAdminSwitchContext(data, user) + + if (!user || !context.isEligible) { + return res.redirect('/auth/select-organisation') + } + + res.render('auth/switch-work-type', { + groupOrganisation: context.groupOrganisation + }) + }) + + router.post('/auth/switch-organisation/work-type', (req, res) => { + const data = req.session.data + const user = getCurrentUserFromSessionData(data) + const context = getGroupAdminSwitchContext(data, user) + const switchOrganisationWorkType = req.body.switchOrganisationWorkType + + if (!user || !context.isEligible) { + return res.redirect('/auth/select-organisation') + } + + if (!switchOrganisationWorkType) { + return res.render('auth/switch-work-type', { + groupOrganisation: context.groupOrganisation, + error: { + text: 'Select the type of work you want to do' + } + }) + } + + if (switchOrganisationWorkType === 'group-admin') { + data.currentOrganisationId = context.groupOrganisation.id + return res.redirect(getDefaultLandingPath(data, context.groupOrganisation.id)) + } + + if (switchOrganisationWorkType === 'individual-pharmacy') { + return res.redirect('/auth/switch-organisation/select-pharmacy') + } + + res.render('auth/switch-work-type', { + groupOrganisation: context.groupOrganisation, + error: { + text: 'Select the type of work you want to do' + } + }) + }) + + router.get('/auth/switch-organisation/select-pharmacy', (req, res) => { + const data = req.session.data + const user = getCurrentUserFromSessionData(data) + const context = getGroupAdminSwitchContext(data, user) + + if (!user || !context.isEligible) { + return res.redirect('/auth/select-organisation') + } + + res.render('auth/select-pharmacy-for-access', { + groupOrganisation: context.groupOrganisation, + pharmacies: context.temporaryPharmacies + }) + }) + + router.post('/auth/switch-organisation/select-pharmacy', (req, res) => { + const data = req.session.data + const user = getCurrentUserFromSessionData(data) + const context = getGroupAdminSwitchContext(data, user) + const switchOrganisationPharmacyId = req.body.switchOrganisationPharmacyId + + if (!user || !context.isEligible) { + return res.redirect('/auth/select-organisation') + } + + const isValidSelection = context.temporaryPharmacies.some((pharmacy) => pharmacy.id === switchOrganisationPharmacyId) + + if (!switchOrganisationPharmacyId || !isValidSelection) { + return res.render('auth/select-pharmacy-for-access', { + groupOrganisation: context.groupOrganisation, + pharmacies: context.temporaryPharmacies, + error: { + text: 'Select a pharmacy to access' + } + }) + } + + data.currentOrganisationId = switchOrganisationPharmacyId + res.redirect(getDefaultLandingPath(data, switchOrganisationPharmacyId)) + }) + router.get('/sign-out', (req, res) => { req.session.data.currentUserId = null req.session.data.currentOrganisationId = null diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index f098f672..c61a8f65 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -135,6 +135,28 @@ const buildDefaultScenarioUsersForPharmacy = (organisation) => { ] } +const isGroupAdminUser = (user) => { + return (user?.organisations || []).some((organisation) => { + return organisation.permissionLevel === 'Group administrator' && organisation.status !== 'Deactivated' + }) +} + +const renderAddUserSelectionPage = (res, organisation, users, userIdError) => { + res.render('pharmacies/add-user', { + users, + organisation, + userIdError + }) +} + +const renderAddUserPermissionLevelPage = (res, organisation, existingUser, errors = {}) => { + res.render('pharmacies/add-user-permission-level', { + organisation, + existingUser, + ...errors + }) +} + module.exports = router => { router.get('/pharmacies', (req, res) => { @@ -513,6 +535,25 @@ module.exports = router => { res.redirect('/pharmacies/users?added=true') }) + router.get('/pharmacies/:id/view-as', (req, res) => { + const data = req.session.data + const id = req.params.id + const organisation = data.organisations.find(o => o.id === id) + + if (!organisation || organisation.status === 'Deactivated') { + return res.redirect(`/pharmacies/${id}`) + } + + req.session.data.previousOrganisationId = req.session.data.currentOrganisationId + req.session.data.currentOrganisationId = id + + const landingPath = organisation.appointmentsInterfaceEnabled !== false + ? '/appointments' + : '/record-vaccinations' + + res.redirect(landingPath) + }) + router.get('/pharmacies/:id/deactivate',(req, res) => { const data = req.session.data const id = req.params.id @@ -581,10 +622,29 @@ module.exports = router => { const id = req.params.id const organisation = data.organisations.find((organisation) => organisation.id === id) - res.render('pharmacies/add-user', { - users, - organisation - }) + renderAddUserSelectionPage(res, organisation, users) + }) + + router.post('/pharmacies/:id/add-user-permission-level', (req, res) => { + const data = req.session.data + const users = data.users.slice(10, 30) + const id = req.params.id + const organisation = data.organisations.find((item) => item.id === id) + const selectedUserId = req.body.userId || data.userId + const selectedUser = selectedUserId && selectedUserId !== 'add-new' + ? data.users.find((user) => user.id === selectedUserId) + : null + + if (selectedUser && isGroupAdminUser(selectedUser)) { + return renderAddUserSelectionPage( + res, + organisation, + users, + 'You cannot add a group administrator as an individual pharmacy user' + ) + } + + res.redirect(`/pharmacies/${id}/add-user-permission-level`) }) router.get('/pharmacies/:id/add-user-permission-level',(req, res) => { @@ -597,10 +657,67 @@ module.exports = router => { existingUser = data.users.find((user) => user.id === data.userId) } - res.render('pharmacies/add-user-permission-level', { - organisation, - existingUser - }) + renderAddUserPermissionLevelPage(res, organisation, existingUser) + }) + + router.post('/pharmacies/:id/add-user-check', (req, res) => { + const data = req.session.data + const id = req.params.id + const organisation = data.organisations.find((item) => item.id === id) + const selectedUserId = req.body.userId || data.userId + const submittedFirstName = req.body.firstName || data.firstName + const submittedLastName = req.body.lastName || data.lastName + const submittedEmail = (req.body.email || data.email || '').trim() + const submittedPermissionLevel = req.body.permissionLevel || data.permissionLevel + const submittedVaccinator = req.body.vaccinator || data.vaccinator + const existingUser = selectedUserId ? data.users.find((user) => user.id === selectedUserId) : null + const existingUserWithSameEmail = submittedEmail + ? data.users.find((user) => user.email.toLowerCase() === submittedEmail.toLowerCase()) + : null + + let firstNameError + let lastNameError + let emailError + let permissionLevelError + let vaccinatorError + + if (!existingUser) { + if (!submittedFirstName || submittedFirstName === '') { + firstNameError = 'Enter a first name' + } + + if (!submittedLastName || submittedLastName === '') { + lastNameError = 'Enter a last name' + } + + if (!submittedEmail || submittedEmail === '') { + emailError = 'Enter an email address' + } else if (!(submittedEmail.toLowerCase().endsWith('nhs.net') || submittedEmail.toLowerCase().endsWith('.nhs.uk'))) { + emailError = 'Enter an allowed email address' + } else if (existingUserWithSameEmail && isGroupAdminUser(existingUserWithSameEmail)) { + emailError = 'You cannot add a group administrator as an individual pharmacy user' + } + } + + if (!submittedPermissionLevel || submittedPermissionLevel === '') { + permissionLevelError = 'Select a permission level' + } + + if (!submittedVaccinator || submittedVaccinator === '') { + vaccinatorError = 'Select if they’re a vaccinator' + } + + if (firstNameError || lastNameError || emailError || permissionLevelError || vaccinatorError) { + return renderAddUserPermissionLevelPage(res, organisation, existingUser, { + firstNameError, + lastNameError, + emailError, + permissionLevelError, + vaccinatorError + }) + } + + res.redirect(`/pharmacies/${id}/add-user-check`) }) router.get('/pharmacies/:id/add-user-check',(req, res) => { @@ -619,13 +736,24 @@ module.exports = router => { }) }) - router.get('/pharmacies/:id/user-added',(req, res) => { + router.post('/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) - const existingUser = data.users.find((user) => user.id === data.userId) + const selectedUserId = req.body.userId || data.userId + const submittedEmail = (req.body.email || data.email || '').trim() + const existingUser = selectedUserId ? data.users.find((user) => user.id === selectedUserId) : null + const existingUserWithSameEmail = submittedEmail + ? data.users.find((user) => user.email.toLowerCase() === submittedEmail.toLowerCase()) + : null let addedUserId + if ((existingUser && isGroupAdminUser(existingUser)) || (!existingUser && existingUserWithSameEmail && isGroupAdminUser(existingUserWithSameEmail))) { + return renderAddUserPermissionLevelPage(res, organisation, existingUser, { + emailError: 'You cannot add a group administrator as an individual pharmacy user' + }) + } + if (existingUser) { existingUser.organisations ||= [] @@ -1103,9 +1231,14 @@ module.exports = router => { ? data.users : [...data.users, ...defaultScenarioUsers.filter((user) => !existingUserIds.has(user.id))] - const usersForOrganisation = users.filter((user) => (user.organisations || []) - .find((orgPermission) => orgPermission.id === organisation.id) - ) + const usersForOrganisation = users.filter((user) => { + if (data.previousOrganisationId && user.id === data.currentUserId) { + return false + } + + return (user.organisations || []) + .find((orgPermission) => orgPermission.id === organisation.id) + }) const usersByStatus = { invited: [], diff --git a/app/routes/user-admin.js b/app/routes/user-admin.js index adff57a0..8fa8ba56 100644 --- a/app/routes/user-admin.js +++ b/app/routes/user-admin.js @@ -1,3 +1,9 @@ +const isGroupAdminUser = (user) => { + return (user?.organisations || []).some((organisation) => { + return organisation.permissionLevel === 'Group administrator' && organisation.status !== 'Deactivated' + }) +} + module.exports = (router) => { router.get('/user-admin', (req, res) => { @@ -176,6 +182,8 @@ module.exports = (router) => { emailError = 'Enter an email address' } else if (!(email.endsWith('nhs.net') || email.endsWith('.nhs.uk'))) { emailError = 'Enter an allowed email address' + } else if (existingUserWithSameEmail && isGroupAdminUser(existingUserWithSameEmail)) { + emailError = 'Group administrators cannot be added to individual pharmacies' } else if (existingUserWithSameEmail && existingUserWithSameEmail.status !== 'Deactivated') { emailError = 'This email address has already been added' } diff --git a/app/views/includes/header.html b/app/views/includes/header.html index 2f48b59a..5f28111f 100644 --- a/app/views/includes/header.html +++ b/app/views/includes/header.html @@ -130,9 +130,15 @@ {% endif %} +{% set organisationLabelLimit = 20 if data.previousOrganisationId else 30 %} +{% set organisationCodeSuffix = " (" + currentOrganisation.id + ")" if currentOrganisation and currentOrganisation.id else "" %} +{% set organisationNameLimit = organisationLabelLimit - (organisationCodeSuffix | length) %} + {% set organisationHtml %} - {{ currentOrganisation.name | truncate(30) }} - {% if currentUser and currentOrganisation and ((currentUser.organisations | length) > 1) %} + {{ currentOrganisation.name | truncate(organisationNameLimit if organisationNameLimit > 1 else 1) }}{{ organisationCodeSuffix }} + {% if currentUser and currentOrganisation and data.previousOrganisationId %} + Return to organisation + {% elif currentUser and currentOrganisation and ((currentUser.organisations | length) > 1) %} Change organisation {% endif %} {% endset %} diff --git a/app/views/pharmacies/add-user-permission-level.html b/app/views/pharmacies/add-user-permission-level.html index 0c991cd0..3a8f2b6e 100644 --- a/app/views/pharmacies/add-user-permission-level.html +++ b/app/views/pharmacies/add-user-permission-level.html @@ -18,6 +18,34 @@ {% block content %}
+ {% if firstNameError or lastNameError or emailError or permissionLevelError or vaccinatorError %} + {{ errorSummary({ + titleText: "There is a problem", + errorList: [ + { + text: firstNameError, + href: "#first-name" + } if firstNameError, + { + text: lastNameError, + href: "#last-name" + } if lastNameError, + { + text: emailError, + href: "#email" + } if emailError, + { + text: vaccinatorError, + href: "#vaccinator" + } if vaccinatorError, + { + text: permissionLevelError, + href: "#permission-level-1" + } if permissionLevelError + ] + }) }} + {% endif %} +

{% if existingUser %} {{ existingUser.firstName }} {{ existingUser.lastName }}'s role at {{ organisation.name }} ({{ organisation.id }}) @@ -37,27 +65,39 @@

label: { text: "First name" }, + id: "first-name", name: "firstName", classes: "nhsuk-input--width-20", - value: data.firstName + 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 + value: data.lastName, + errorMessage: { + text: lastNameError + } if lastNameError }) }} {{ input({ label: { text: "Email address" }, + id: "email", name: "email", type: "email", - value: data.email + value: data.email, + errorMessage: { + text: emailError + } if emailError }) }} {% endif %} @@ -73,6 +113,9 @@

text: "Vaccination records include the name of the person who gave the vaccination" }, value: data.vaccinator, + errorMessage: { + text: vaccinatorError + } if vaccinatorError, "items": [ { "value": "yes", @@ -96,6 +139,9 @@

} }, value: data.permissionLevel, + errorMessage: { + text: permissionLevelError + } if permissionLevelError, items: [ { value: "Recorder", diff --git a/app/views/pharmacies/add-user.html b/app/views/pharmacies/add-user.html index ce6462c2..c05d68db 100644 --- a/app/views/pharmacies/add-user.html +++ b/app/views/pharmacies/add-user.html @@ -12,6 +12,18 @@ {% block content %}
+ {% if userIdError %} + {{ errorSummary({ + titleText: "There is a problem", + errorList: [ + { + text: userIdError, + href: "#user-id" + } + ] + }) }} + {% endif %} +

{{ pageName }}

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

@@ -145,13 +157,18 @@

{{ pageName }}

{% if (users | length) > 0 %}
{{ radios({ + idPrefix: "user-id", name: "userId", value: data.userId, + errorMessage: { + text: userIdError + } if userIdError, items: items }) }}
{{ radios({ + idPrefix: "user-id-alternative", name: "userId", value: data.userId, items: [ @@ -170,8 +187,12 @@

{{ pageName }}

}) }} {% else %} {{ radios({ + idPrefix: "user-id", name: "userId", value: data.userId, + errorMessage: { + text: userIdError + } if userIdError, items: [ { text: "Add a new user", diff --git a/app/views/pharmacies/pharmacy.html b/app/views/pharmacies/pharmacy.html index 6cbf6dea..3e2f7268 100644 --- a/app/views/pharmacies/pharmacy.html +++ b/app/views/pharmacies/pharmacy.html @@ -112,6 +112,14 @@

{{ pageName }}

] }) }} + {% if organisation.status != 'Deactivated' %} + {{ button({ + href: "/pharmacies/" + organisation.id + "/view-as", + text: "Access this pharmacy", + classes: "nhsuk-button--secondary" + }) }} + {% endif %} + {% if organisation.status == 'Deactivated' %}

This pharmacy has been deactivated.

From f7883f347d9f5ababb6d55c5d3b784aa4142d5e1 Mon Sep 17 00:00:00 2001 From: Caitlin Roach Date: Thu, 9 Jul 2026 14:00:00 +0100 Subject: [PATCH 3/6] Fixes --- app/routes/auth.js | 33 ++++++++++++++----------- app/routes/pharmacies.js | 38 +++++++++++++++++++++++++++++ app/views/pharmacies/users/new.html | 5 +++- 3 files changed, 61 insertions(+), 15 deletions(-) diff --git a/app/routes/auth.js b/app/routes/auth.js index 859d7b93..d3fd4045 100644 --- a/app/routes/auth.js +++ b/app/routes/auth.js @@ -36,20 +36,6 @@ module.exports = router => { } if (data.currentUserId) { - - router.get('/auth/restore-organisation', (req, res) => { - const data = req.session.data - const previousOrganisationId = data.previousOrganisationId - - if (!previousOrganisationId) { - return res.redirect('/auth/select-organisation') - } - - data.currentOrganisationId = previousOrganisationId - delete data.previousOrganisationId - - res.redirect(getDefaultLandingPath(data, previousOrganisationId)) - }) return (data.users || []).find((user) => user.id === data.currentUserId) || null } @@ -60,6 +46,25 @@ module.exports = router => { return null } + router.get('/auth/restore-organisation', (req, res) => { + const data = req.session.data + const previousOrganisationId = data.previousOrganisationId + const previousOrganisation = (data.organisations || []).find((organisation) => organisation.id === previousOrganisationId) + + if (!previousOrganisationId) { + return res.redirect('/auth/select-organisation') + } + + data.currentOrganisationId = previousOrganisationId + delete data.previousOrganisationId + + if (previousOrganisation && previousOrganisation.type === 'Pharmacy HQ') { + return res.redirect('/pharmacies') + } + + res.redirect(getDefaultLandingPath(data, previousOrganisationId)) + }) + const isTemporaryAccessInactive = (addedOnIsoDate, deactivatedOnIsoDate) => { if (deactivatedOnIsoDate) { return true diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index c61a8f65..e93863bc 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -398,6 +398,44 @@ module.exports = router => { router.post('/pharmacies/users/new-answer',(req, res) => { const data = req.session.data const groupAdministrator = data.groupAdministrator + const firstName = (data.firstName || '').trim() + const lastName = (data.lastName || '').trim() + const email = (data.email || '').trim() + const existingUserWithSameEmail = email + ? data.users.find((user) => user.email.toLowerCase() === email.toLowerCase()) + : null + + let firstNameError + let lastNameError + let emailError + + if (!firstName) { + firstNameError = 'Enter a first name' + } + + if (!lastName) { + lastNameError = 'Enter a last name' + } + + if (!email) { + emailError = 'Enter an email address' + } else if (!(email.toLowerCase().endsWith('nhs.net') || email.toLowerCase().endsWith('.nhs.uk'))) { + emailError = 'Enter an allowed email address' + } else if (groupAdministrator === 'no' && existingUserWithSameEmail && isGroupAdminUser(existingUserWithSameEmail)) { + emailError = 'You cannot add a group administrator as an individual pharmacy user' + } + + data.firstName = firstName + data.lastName = lastName + data.email = email + + if (firstNameError || lastNameError || emailError) { + return res.render('pharmacies/users/new', { + firstNameError, + lastNameError, + emailError + }) + } if (groupAdministrator === "yes") { res.redirect('/pharmacies/users/check') diff --git a/app/views/pharmacies/users/new.html b/app/views/pharmacies/users/new.html index c835c3fc..75e7cbde 100644 --- a/app/views/pharmacies/users/new.html +++ b/app/views/pharmacies/users/new.html @@ -49,7 +49,10 @@

Add user

"id": "email", "name": "email", type: "email", - value: data.email + value: data.email, + "errorMessage": { + "text": emailError + } if emailError }) }} {{ radios({ From 1a89f1344b88c809da13e547edc3ca6f30c0e15a Mon Sep 17 00:00:00 2001 From: Anna-Sutton Date: Thu, 9 Jul 2026 14:55:55 +0100 Subject: [PATCH 4/6] Update link text for returning to organisation --- 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 5f28111f..9c9fafd6 100644 --- a/app/views/includes/header.html +++ b/app/views/includes/header.html @@ -137,7 +137,7 @@ {% set organisationHtml %} {{ currentOrganisation.name | truncate(organisationNameLimit if organisationNameLimit > 1 else 1) }}{{ organisationCodeSuffix }} {% if currentUser and currentOrganisation and data.previousOrganisationId %} - Return to organisation + Back to Pharmacies {% elif currentUser and currentOrganisation and ((currentUser.organisations | length) > 1) %} Change organisation {% endif %} From d51af40d7ba75601e18511c618118f4d1bf880df Mon Sep 17 00:00:00 2001 From: Anna-Sutton Date: Thu, 9 Jul 2026 15:00:36 +0100 Subject: [PATCH 5/6] Update email error message for pharmacy user --- app/routes/pharmacies.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index e93863bc..5fa7c53c 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -422,7 +422,7 @@ module.exports = router => { } else if (!(email.toLowerCase().endsWith('nhs.net') || email.toLowerCase().endsWith('.nhs.uk'))) { emailError = 'Enter an allowed email address' } else if (groupAdministrator === 'no' && existingUserWithSameEmail && isGroupAdminUser(existingUserWithSameEmail)) { - emailError = 'You cannot add a group administrator as an individual pharmacy user' + emailError = 'You cannot add a group administrator to an individual pharmacy' } data.firstName = firstName From cd837099269e2c64061fb9f5804bff942ef4f2c5 Mon Sep 17 00:00:00 2001 From: Anna-Sutton Date: Fri, 10 Jul 2026 09:45:10 +0100 Subject: [PATCH 6/6] Update error messages for group administrator restrictions --- app/routes/pharmacies.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index 5fa7c53c..0dbe4bc2 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -678,7 +678,7 @@ module.exports = router => { res, organisation, users, - 'You cannot add a group administrator as an individual pharmacy user' + 'You cannot add a group administrator to an individual pharmacy' ) } @@ -733,7 +733,7 @@ module.exports = router => { } else if (!(submittedEmail.toLowerCase().endsWith('nhs.net') || submittedEmail.toLowerCase().endsWith('.nhs.uk'))) { emailError = 'Enter an allowed email address' } else if (existingUserWithSameEmail && isGroupAdminUser(existingUserWithSameEmail)) { - emailError = 'You cannot add a group administrator as an individual pharmacy user' + emailError = 'You cannot add a group administrator to an individual pharmacy' } } @@ -788,7 +788,7 @@ module.exports = router => { if ((existingUser && isGroupAdminUser(existingUser)) || (!existingUser && existingUserWithSameEmail && isGroupAdminUser(existingUserWithSameEmail))) { return renderAddUserPermissionLevelPage(res, organisation, existingUser, { - emailError: 'You cannot add a group administrator as an individual pharmacy user' + emailError: 'You cannot add a group administrator to an individual pharmacy' }) }