From 1d67a91749e6259f5698c281c8d1e0c908eebb05 Mon Sep 17 00:00:00 2001 From: rivalee Date: Mon, 10 Aug 2026 13:47:29 +0100 Subject: [PATCH 01/65] Update button content to authorise --- app/assets/javascript/expandable-sections.js | 6 +++--- app/views/appointments/review-medical-information.html | 4 ++-- tests/e2e/appointment.spec.js | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/assets/javascript/expandable-sections.js b/app/assets/javascript/expandable-sections.js index ea2ba2d7d..31ffa0b0c 100644 --- a/app/assets/javascript/expandable-sections.js +++ b/app/assets/javascript/expandable-sections.js @@ -123,7 +123,7 @@ document.addEventListener('DOMContentLoaded', function () { // Initialize progress updateProgress(sections, completedSections) - // Handle "Complete all and continue" button + // Handle "Authorise" button const completeAllButtons = document.querySelectorAll( '.js-complete-all-sections' ) @@ -322,7 +322,7 @@ function openNextIncompleteSection(currentIndex, sections, completedSections) { // Function to highlight the completion button when all sections are done function highlightCompletionButton() { const completeButton = document.querySelector( - 'button:contains("Complete all and continue")' + 'button:contains("Authorise")' ) if (!completeButton) { @@ -330,7 +330,7 @@ function highlightCompletionButton() { const buttons = document.querySelectorAll('button') buttons.forEach((btn) => { - if (btn.textContent.includes('Complete all and continue')) { + if (btn.textContent.includes('Authorise')) { btn.classList.add('nhsuk-button--green') // Highlight in green btn.style.animation = 'pulse 2s infinite' // Add a subtle animation } diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index b61b10a50..95668f08c 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -32,7 +32,7 @@

{{ button({ - text: "Complete all and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", + text: "Authorise and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", classes: "nhsuk-u-margin-bottom-0 nhsuk-button js-complete-all-sections" }) }}
@@ -44,7 +44,7 @@

{{ button({ - text: "Complete all and continue", + text: "Authorise and continue", classes: "js-complete-all-sections" }) }} diff --git a/tests/e2e/appointment.spec.js b/tests/e2e/appointment.spec.js index 68e2d5f12..56452935a 100644 --- a/tests/e2e/appointment.spec.js +++ b/tests/e2e/appointment.spec.js @@ -132,7 +132,7 @@ test.describe('Screening appointment', () => { await expect(page.getByText('Lump').first()).toBeVisible() await page - .getByRole('button', { name: 'Complete all and continue' }) + .getByRole('button', { name: 'Authorise' }) .first() .click() @@ -162,7 +162,7 @@ test.describe('Screening appointment', () => { page.getByRole('heading', { name: 'Review medical information' }) ).toBeVisible() await page - .getByRole('button', { name: 'Complete all and continue' }) + .getByRole('button', { name: 'Authorise' }) .first() .click() From ac1c972812e1dcaad1736bd40629c449a4c5f1a3 Mon Sep 17 00:00:00 2001 From: rivalee Date: Mon, 10 Aug 2026 14:55:37 +0100 Subject: [PATCH 02/65] add line of text with mammographer name --- app/views/appointments/review-medical-information.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 95668f08c..95d7c0689 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -35,6 +35,7 @@

text: "Authorise and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", classes: "nhsuk-u-margin-bottom-0 nhsuk-button js-complete-all-sections" }) }} +

This will authorise imaging by {{ data.currentUser.firstName }} {{ data.currentUser.lastName }}

@@ -45,8 +46,9 @@

{{ button({ text: "Authorise and continue", - classes: "js-complete-all-sections" + classes: "js-complete-all-sections nhsuk-u-margin-bottom-0" }) }} +

This will authorise imaging by {{ data.currentUser.firstName }} {{ data.currentUser.lastName }}

{% include "screening-cannot-proceed-link.njk" %}
From 4d181826bb2e9c444fd38038881176acd0e1e780 Mon Sep 17 00:00:00 2001 From: rivalee Date: Wed, 12 Aug 2026 10:55:17 +0100 Subject: [PATCH 03/65] fix bug --- app/routes/clinics.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/routes/clinics.js b/app/routes/clinics.js index faad42005..491a80e76 100644 --- a/app/routes/clinics.js +++ b/app/routes/clinics.js @@ -182,6 +182,9 @@ module.exports = (router) => { } const clinicData = getClinicData(req.session.data, req.params.id) + if (!clinicData) { + return res.redirect('/clinics') + } let remainingCount = filterAppointmentsByStatus( clinicData.appointments, 'remaining' From f959888c2bd4613ac086f19096882ef152e43f81 Mon Sep 17 00:00:00 2001 From: rivalee Date: Tue, 18 Aug 2026 14:55:28 +0100 Subject: [PATCH 04/65] Update content when breast implants added --- .../review-medical-information.html | 138 ++++++++++++++++-- 1 file changed, 126 insertions(+), 12 deletions(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 95d7c0689..dc8190365 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -13,6 +13,19 @@ {# Display format for medical information sections #} {% set displayFormat = "expander" %} +{# Check if participant has active (non-removed) breast implants #} +{% set hasActiveImplants = false %} +{% set implantRecords = appointment.medicalInformation.medicalHistory.breastImplantsAugmentation %} +{% if implantRecords | length %} + {% for implant in implantRecords %} + {% if not implant.hasBeenRemoved %} + {% set hasActiveImplants = true %} + {% endif %} + {% endfor %} +{% endif %} + +{% set defaultAuthoriser = "J. Wyatt" %} + {% block pageContent %}

@@ -29,28 +42,129 @@

value: appointment.workflowStatus['review-breast-features-after-imaging'] }) }} -
-
+ {% if hasActiveImplants and appointment.workflowStatus['review-medical-information'] != 'completed' %} + {{ insetText({ + html: "

You do not have permission to authorise some views.

" + }) }} +
{{ button({ - text: "Authorise and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", - classes: "nhsuk-u-margin-bottom-0 nhsuk-button js-complete-all-sections" + text: "Authorise with " + defaultAuthoriser + " and continue", + classes: "nhsuk-u-margin-bottom-0 js-complete-all-sections js-irmer-authorise-button" }) }} -

This will authorise imaging by {{ data.currentUser.firstName }} {{ data.currentUser.lastName }}

+ Choose alternative authoriser
-
+ {% else %} +
+
+ {{ button({ + text: "Authorise and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", + classes: "nhsuk-u-margin-bottom-0 nhsuk-button js-complete-all-sections" + }) }} +

This will authorise imaging by {{ data.currentUser.firstName }} {{ data.currentUser.lastName }}

+
+
+ {% endif %}
{% include "_includes/medical-information/index.njk" %}
- {{ button({ - text: "Authorise and continue", - classes: "js-complete-all-sections nhsuk-u-margin-bottom-0" - }) }} -

This will authorise imaging by {{ data.currentUser.firstName }} {{ data.currentUser.lastName }}

+ {% if hasActiveImplants and appointment.workflowStatus['review-medical-information'] != 'completed' %} + {{ insetText({ + html: "

You do not have permission to authorise some views.

" + }) }} +
+ {{ button({ + text: "Authorise with " + defaultAuthoriser + " and continue", + classes: "nhsuk-u-margin-bottom-0 js-complete-all-sections js-irmer-authorise-button" + }) }} + Choose alternative authoriser +
+ {% else %} + {{ button({ + text: "Authorise and continue", + classes: "js-complete-all-sections nhsuk-u-margin-bottom-0" + }) }} +

This will authorise imaging by {{ data.currentUser.firstName }} {{ data.currentUser.lastName }}

+ {% endif %} - {% include "screening-cannot-proceed-link.njk" %} +
+ {% include "screening-cannot-proceed-link.njk" %} +
+ {# Alternative authoriser modal #} + {% if hasActiveImplants %} + {% set authoriserModalContent %} + {{ radios({ + name: "irmerAuthoriser", + fieldset: { + legend: { + text: "Select an alternative authoriser" + } + }, + items: [ + { value: "S. Patel", text: "S. Patel" }, + { value: "R. Thompson", text: "R. Thompson" }, + { value: "L. Okonkwo", text: "L. Okonkwo" }, + { value: "H. Chambers", text: "H. Chambers" }, + { value: "F. Kaur", text: "F. Kaur" } + ] + }) }} + {% endset %} + + {{ appModal({ + id: "choose-authoriser-modal", + title: "Choose alternative authoriser", + showCloseButton: true, + content: authoriserModalContent, + actions: [ + { + text: "Save", + action: "close", + classes: "js-select-authoriser" + }, + { + text: "Cancel", + element: "link", + action: "close" + } + ] + }) }} + {% endif %} + +{% endblock %} + +{% if hasActiveImplants %} +{% block pageScripts %} + {% endblock %} +{% endif %} From dd44623cd4778c4d3847c818a50ee8cdbb97a538 Mon Sep 17 00:00:00 2001 From: rivalee Date: Thu, 20 Aug 2026 15:25:05 +0100 Subject: [PATCH 05/65] Update IRMER auth button to choose authoriser --- .../review-medical-information.html | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index dc8190365..a2b44e127 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -24,8 +24,6 @@ {% endfor %} {% endif %} -{% set defaultAuthoriser = "J. Wyatt" %} - {% block pageContent %}

@@ -48,10 +46,11 @@

}) }}
{{ button({ - text: "Authorise with " + defaultAuthoriser + " and continue", - classes: "nhsuk-u-margin-bottom-0 js-complete-all-sections js-irmer-authorise-button" + text: "Choose an authoriser", + classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", + attributes: { type: "button" } }) }} - Choose alternative authoriser +
{% else %}
@@ -76,10 +75,11 @@

}) }}
{{ button({ - text: "Authorise with " + defaultAuthoriser + " and continue", - classes: "nhsuk-u-margin-bottom-0 js-complete-all-sections js-irmer-authorise-button" + text: "Choose an authoriser", + classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", + attributes: { type: "button" } }) }} - Choose alternative authoriser +
{% else %} {{ button({ @@ -101,7 +101,7 @@

name: "irmerAuthoriser", fieldset: { legend: { - text: "Select an alternative authoriser" + text: "Select an authoriser" } }, items: [ @@ -116,7 +116,7 @@

{{ appModal({ id: "choose-authoriser-modal", - title: "Choose alternative authoriser", + title: "Choose an authoriser", showCloseButton: true, content: authoriserModalContent, actions: [ @@ -129,6 +129,12 @@

text: "Cancel", element: "link", action: "close" + }, + { + text: "Appointment cannot proceed", + element: "link", + href: "/clinics/" + clinicId + "/appointments/" + appointmentId + "/attended-not-screened-reason", + action: "close" } ] }) }} @@ -144,7 +150,7 @@

const modal = document.getElementById("choose-authoriser-modal") if (!modal) return - // Open modal when "Choose alternative authoriser" is clicked + // Open modal when authoriser button is clicked document.querySelectorAll(".js-choose-authoriser-link").forEach((link) => { link.addEventListener("click", (event) => { event.preventDefault() @@ -152,7 +158,7 @@

}) }) - // Update all authorise buttons when an authoriser is selected + // Update buttons and enable form submission when an authoriser is selected const selectButton = modal.querySelector(".js-select-authoriser") if (selectButton) { selectButton.addEventListener("click", () => { @@ -160,6 +166,13 @@

if (selected) { document.querySelectorAll(".js-irmer-authorise-button").forEach((btn) => { btn.textContent = "Authorise with " + selected.value + " and continue" + btn.removeAttribute("type") + btn.classList.add("js-complete-all-sections") + btn.classList.remove("js-choose-authoriser-link") + }) + // Show the alternative authoriser links + document.querySelectorAll(".js-alternative-authoriser-link").forEach((link) => { + link.style.display = "" }) } }) From 46f63994e243dc098b75a26a4cbf21a343c4212f Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:35:59 +0100 Subject: [PATCH 06/65] Update app/assets/javascript/expandable-sections.js Co-authored-by: Danny Chadburn --- app/assets/javascript/expandable-sections.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascript/expandable-sections.js b/app/assets/javascript/expandable-sections.js index 31ffa0b0c..6b0e08daf 100644 --- a/app/assets/javascript/expandable-sections.js +++ b/app/assets/javascript/expandable-sections.js @@ -123,7 +123,7 @@ document.addEventListener('DOMContentLoaded', function () { // Initialize progress updateProgress(sections, completedSections) - // Handle "Authorise" button + // Handle "Complete all and continue" button const completeAllButtons = document.querySelectorAll( '.js-complete-all-sections' ) From bf1c588c08f554ce4ab8d73da86b7d2f2120aeb9 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:36:51 +0100 Subject: [PATCH 07/65] Update app/assets/javascript/expandable-sections.js Co-authored-by: Danny Chadburn --- app/assets/javascript/expandable-sections.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascript/expandable-sections.js b/app/assets/javascript/expandable-sections.js index 6b0e08daf..6178bc6b9 100644 --- a/app/assets/javascript/expandable-sections.js +++ b/app/assets/javascript/expandable-sections.js @@ -322,7 +322,7 @@ function openNextIncompleteSection(currentIndex, sections, completedSections) { // Function to highlight the completion button when all sections are done function highlightCompletionButton() { const completeButton = document.querySelector( - 'button:contains("Authorise")' + 'button:contains("Complete all and continue")' ) if (!completeButton) { From 853fa0a5fea800ee8a84ea5a1270e76fd70ddccf Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:37:00 +0100 Subject: [PATCH 08/65] Update app/assets/javascript/expandable-sections.js Co-authored-by: Danny Chadburn --- app/assets/javascript/expandable-sections.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascript/expandable-sections.js b/app/assets/javascript/expandable-sections.js index 6178bc6b9..ea2ba2d7d 100644 --- a/app/assets/javascript/expandable-sections.js +++ b/app/assets/javascript/expandable-sections.js @@ -330,7 +330,7 @@ function highlightCompletionButton() { const buttons = document.querySelectorAll('button') buttons.forEach((btn) => { - if (btn.textContent.includes('Authorise')) { + if (btn.textContent.includes('Complete all and continue')) { btn.classList.add('nhsuk-button--green') // Highlight in green btn.style.animation = 'pulse 2s infinite' // Add a subtle animation } From d98dd93767cf7629b55940216d2729afaa4c8f6e Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:37:09 +0100 Subject: [PATCH 09/65] Update tests/e2e/appointment.spec.js Co-authored-by: Danny Chadburn --- tests/e2e/appointment.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/appointment.spec.js b/tests/e2e/appointment.spec.js index 56452935a..62ff74c66 100644 --- a/tests/e2e/appointment.spec.js +++ b/tests/e2e/appointment.spec.js @@ -132,7 +132,7 @@ test.describe('Screening appointment', () => { await expect(page.getByText('Lump').first()).toBeVisible() await page - .getByRole('button', { name: 'Authorise' }) + .getByRole('button', { name: 'Complete all and continue' }) .first() .click() From 6827f6c320297643f5654c13075dc2181aafdb8f Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:37:24 +0100 Subject: [PATCH 10/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index a2b44e127..a3bc24d27 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -59,7 +59,7 @@

text: "Authorise and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", classes: "nhsuk-u-margin-bottom-0 nhsuk-button js-complete-all-sections" }) }} -

This will authorise imaging by {{ data.currentUser.firstName }} {{ data.currentUser.lastName }}

+

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images ()

{% endif %} From c4f26a232e194cde5f3f3e9d943bce2c2999728a Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:37:30 +0100 Subject: [PATCH 11/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index a3bc24d27..955b9ec23 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -86,7 +86,7 @@

text: "Authorise and continue", classes: "js-complete-all-sections nhsuk-u-margin-bottom-0" }) }} -

This will authorise imaging by {{ data.currentUser.firstName }} {{ data.currentUser.lastName }}

+

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images ()

{% endif %}
From 4c40d50344f8942c4a79a1aa8892e3f05e114488 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:37:40 +0100 Subject: [PATCH 12/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 955b9ec23..3c56d4f5f 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -101,7 +101,7 @@

name: "irmerAuthoriser", fieldset: { legend: { - text: "Select an authoriser" + text: "Who is taking images for this appointment?" } }, items: [ From f7ed4a8fe5979299a9bff06671168ae43d728a66 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:37:50 +0100 Subject: [PATCH 13/65] Update tests/e2e/appointment.spec.js Co-authored-by: Danny Chadburn --- tests/e2e/appointment.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/appointment.spec.js b/tests/e2e/appointment.spec.js index 62ff74c66..68e2d5f12 100644 --- a/tests/e2e/appointment.spec.js +++ b/tests/e2e/appointment.spec.js @@ -162,7 +162,7 @@ test.describe('Screening appointment', () => { page.getByRole('heading', { name: 'Review medical information' }) ).toBeVisible() await page - .getByRole('button', { name: 'Authorise' }) + .getByRole('button', { name: 'Complete all and continue' }) .first() .click() From a216bcb6360f5153495c3da33b060f17b7cdab2d Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:38:10 +0100 Subject: [PATCH 14/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 3c56d4f5f..9ab687265 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -46,7 +46,7 @@

}) }}
{{ button({ - text: "Choose an authoriser", + text: "Nominate a mammographer", classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", attributes: { type: "button" } }) }} From 378f66d55590b03c6864e583ab4df70f4d8cd337 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:38:17 +0100 Subject: [PATCH 15/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 9ab687265..7b5bdd901 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -83,7 +83,7 @@

{% else %} {{ button({ - text: "Authorise and continue", + text: "Complete all and continue", classes: "js-complete-all-sections nhsuk-u-margin-bottom-0" }) }}

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images ()

From 1b6ff3c8800c156f4657675cc6e27c3387f19b1d Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:38:58 +0100 Subject: [PATCH 16/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 7b5bdd901..bf9498b44 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -116,7 +116,7 @@

{{ appModal({ id: "choose-authoriser-modal", - title: "Choose an authoriser", + title: "Nominate a mammographer", showCloseButton: true, content: authoriserModalContent, actions: [ From 96909be0055a9b2250581e21e2b9fa6f043b4ec3 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:39:08 +0100 Subject: [PATCH 17/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index bf9498b44..71981c570 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -165,7 +165,7 @@

const selected = modal.querySelector("input[name='irmerAuthoriser']:checked") if (selected) { document.querySelectorAll(".js-irmer-authorise-button").forEach((btn) => { - btn.textContent = "Authorise with " + selected.value + " and continue" + btn.textContent = "Complete all and continue" btn.removeAttribute("type") btn.classList.add("js-complete-all-sections") btn.classList.remove("js-choose-authoriser-link") From b7baa8c901f220f6f2b7c454926cbe4aa8a4c9c7 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:39:15 +0100 Subject: [PATCH 18/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 71981c570..0c9490d55 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -170,6 +170,11 @@

btn.classList.add("js-complete-all-sections") btn.classList.remove("js-choose-authoriser-link") }) + +document.querySelectorAll(".js-authoriser-confirmation").forEach((p) => { + p.textContent = `Continuing will authorise ${selected.value} to take all images for this appointment`; +}); + // Show the alternative authoriser links document.querySelectorAll(".js-alternative-authoriser-link").forEach((link) => { link.style.display = "" From 4a1bfcf103ae591ff48dc4988a070efaf9bc4ffe Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:39:30 +0100 Subject: [PATCH 19/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 0c9490d55..1e2e42272 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -171,6 +171,11 @@

btn.classList.remove("js-choose-authoriser-link") }) +document.querySelectorAll(".js-authoriser-confirmation").forEach((p) => { + p.textContent = `Continuing will authorise ${selected.value} to take all images for this appointment`; +}); + + // Show the alternative authoriser links document.querySelectorAll(".js-authoriser-confirmation").forEach((p) => { p.textContent = `Continuing will authorise ${selected.value} to take all images for this appointment`; }); From d260ca03327016107e22a9a0ce6cb51296a7c5c0 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:39:43 +0100 Subject: [PATCH 20/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 1e2e42272..249d45e90 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -42,7 +42,7 @@

{% if hasActiveImplants and appointment.workflowStatus['review-medical-information'] != 'completed' %} {{ insetText({ - html: "

You do not have permission to authorise some views.

" + html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" }) }}
{{ button({ From 0d225cbe8e44a07f30a07c8e70de6ee269cca6fb Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:39:55 +0100 Subject: [PATCH 21/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 249d45e90..548182adc 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -50,7 +50,7 @@

classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", attributes: { type: "button" } }) }} - +

()

{% else %}
From d6650df3e85b6a7be7d46cce8efe4b3b61c7f739 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:40:09 +0100 Subject: [PATCH 22/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 548182adc..bb76c217c 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -56,7 +56,7 @@

{{ button({ - text: "Authorise and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", + text: "Complete all and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", classes: "nhsuk-u-margin-bottom-0 nhsuk-button js-complete-all-sections" }) }}

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images ()

From c4ab3e5f4ea3115d6f1706cf6b5fe7557a2c4b44 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:40:19 +0100 Subject: [PATCH 23/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index bb76c217c..5fddf04fc 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -71,7 +71,7 @@

{% if hasActiveImplants and appointment.workflowStatus['review-medical-information'] != 'completed' %} {{ insetText({ - html: "

You do not have permission to authorise some views.

" + html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" }) }}
{{ button({ From 60ecf30b0b73b5d6d580217f0fb598ef69a4f100 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:40:32 +0100 Subject: [PATCH 24/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 5fddf04fc..bd95816a7 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -75,7 +75,7 @@

}) }}
{{ button({ - text: "Choose an authoriser", + text: "Nominate a mammographer", classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", attributes: { type: "button" } }) }} From 6c929ab09edc58a45c2590b007b55ccbe9d3bced Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:40:43 +0100 Subject: [PATCH 25/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index bd95816a7..96b28d8fb 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -79,7 +79,7 @@

classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", attributes: { type: "button" } }) }} - +

()

{% else %} {{ button({ From 63e01eb4edd62e1ca5ddc2720e44ff37ed828f50 Mon Sep 17 00:00:00 2001 From: rivalee Date: Tue, 8 Sep 2026 16:09:22 +0100 Subject: [PATCH 26/65] Update content --- .../review-medical-information.html | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 96b28d8fb..903d1fb01 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -42,7 +42,7 @@

{% if hasActiveImplants and appointment.workflowStatus['review-medical-information'] != 'completed' %} {{ insetText({ - html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" + html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" }) }}
{{ button({ @@ -50,7 +50,7 @@

classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", attributes: { type: "button" } }) }} -

()

+

{% else %}
@@ -59,7 +59,7 @@

text: "Complete all and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", classes: "nhsuk-u-margin-bottom-0 nhsuk-button js-complete-all-sections" }) }} -

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images ()

+

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images (change mammographer)

{% endif %} @@ -71,7 +71,7 @@

{% if hasActiveImplants and appointment.workflowStatus['review-medical-information'] != 'completed' %} {{ insetText({ - html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" + html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" }) }}
{{ button({ @@ -79,14 +79,14 @@

classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", attributes: { type: "button" } }) }} -

()

+

{% else %} {{ button({ text: "Complete all and continue", classes: "js-complete-all-sections nhsuk-u-margin-bottom-0" }) }} -

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images ()

+

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images (change mammographer)

{% endif %}
@@ -95,8 +95,7 @@

{# Alternative authoriser modal #} - {% if hasActiveImplants %} - {% set authoriserModalContent %} + {% set authoriserModalContent %} {{ radios({ name: "irmerAuthoriser", fieldset: { @@ -112,6 +111,19 @@

{ value: "F. Kaur", text: "F. Kaur" } ] }) }} + + {{ radios({ + name: "irmerImagesScope", + fieldset: { + legend: { + text: "Which images are they taking?" + } + }, + items: [ + { value: "all-views", text: "All views" }, + { value: "additional-views", text: "Just the additional views" } + ] + }) }} {% endset %} {{ appModal({ @@ -138,11 +150,9 @@

} ] }) }} - {% endif %} {% endblock %} -{% if hasActiveImplants %} {% block pageScripts %} {% endblock %} -{% endif %} From d61b226b8982046ffd5e44fa4daa8f481b9292be Mon Sep 17 00:00:00 2001 From: rivalee Date: Thu, 10 Sep 2026 12:25:28 +0100 Subject: [PATCH 27/65] edits --- .../review-medical-information.html | 117 +++++++++++------- 1 file changed, 72 insertions(+), 45 deletions(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 903d1fb01..b347a7310 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -24,6 +24,20 @@ {% endfor %} {% endif %} +{# Check if participant has active (non-removed) implanted medical devices #} +{% set hasActiveMedicalDevices = false %} +{% set medicalDeviceRecords = appointment.medicalInformation.medicalHistory.implantedMedicalDevice %} +{% if medicalDeviceRecords | length %} + {% for device in medicalDeviceRecords %} + {% if not device.deviceRemoved %} + {% set hasActiveMedicalDevices = true %} + {% endif %} + {% endfor %} +{% endif %} + +{# Nominating an alternative mammographer is only relevant when implants or devices require authorisation #} +{% set requiresAuthorisation = hasActiveImplants or hasActiveMedicalDevices %} + {% block pageContent %}

@@ -40,17 +54,23 @@

value: appointment.workflowStatus['review-breast-features-after-imaging'] }) }} - {% if hasActiveImplants and appointment.workflowStatus['review-medical-information'] != 'completed' %} - {{ insetText({ - html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" - }) }} -
- {{ button({ - text: "Nominate a mammographer", - classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", - attributes: { type: "button" } + {% if requiresAuthorisation and appointment.workflowStatus['review-medical-information'] != 'completed' %} +
+ {{ insetText({ + html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" }) }} - +
+
+
+
+ {{ button({ + text: "Nominate a mammographer", + classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", + attributes: { type: "button" } + }) }} + +
+
{% else %}
@@ -59,7 +79,9 @@

text: "Complete all and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", classes: "nhsuk-u-margin-bottom-0 nhsuk-button js-complete-all-sections" }) }} -

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images (change mammographer)

+ {% if requiresAuthorisation %} +

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images (change mammographer)

+ {% endif %}

{% endif %} @@ -69,24 +91,36 @@

{% include "_includes/medical-information/index.njk" %}
- {% if hasActiveImplants and appointment.workflowStatus['review-medical-information'] != 'completed' %} - {{ insetText({ - html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" - }) }} -
- {{ button({ - text: "Nominate a mammographer", - classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", - attributes: { type: "button" } + {% if requiresAuthorisation and appointment.workflowStatus['review-medical-information'] != 'completed' %} +
+ {{ insetText({ + html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" }) }} - +
+
+
+
+ {{ button({ + text: "Nominate a mammographer", + classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", + attributes: { type: "button" } + }) }} + +
+
{% else %} - {{ button({ - text: "Complete all and continue", - classes: "js-complete-all-sections nhsuk-u-margin-bottom-0" - }) }} -

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images (change mammographer)

+
+
+ {{ button({ + text: "Complete all and continue", + classes: "js-complete-all-sections nhsuk-u-margin-bottom-0" + }) }} + {% if requiresAuthorisation %} +

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images (change mammographer)

+ {% endif %} +
+
{% endif %}
@@ -111,19 +145,6 @@

{ value: "F. Kaur", text: "F. Kaur" } ] }) }} - - {{ radios({ - name: "irmerImagesScope", - fieldset: { - legend: { - text: "Which images are they taking?" - } - }, - items: [ - { value: "all-views", text: "All views" }, - { value: "additional-views", text: "Just the additional views" } - ] - }) }} {% endset %} {{ appModal({ @@ -160,12 +181,13 @@

const modal = document.getElementById("choose-authoriser-modal") if (!modal) return - // Open modal when authoriser button is clicked - document.querySelectorAll(".js-choose-authoriser-link").forEach((link) => { - link.addEventListener("click", (event) => { - event.preventDefault() - window.openModal("choose-authoriser-modal") - }) + // Open modal when authoriser button is clicked - checked at click time so + // buttons that have been converted to complete-and-continue buttons submit normally + document.addEventListener("click", (event) => { + const link = event.target.closest(".js-choose-authoriser-link") + if (!link) return + event.preventDefault() + window.openModal("choose-authoriser-modal") }) // Update buttons and enable form submission when an authoriser is selected @@ -181,6 +203,11 @@

btn.classList.remove("js-choose-authoriser-link") }) + // Hide the permission notice now that a mammographer has been nominated + document.querySelectorAll(".js-irmer-permission-notice").forEach((notice) => { + notice.style.display = "none" + }) + document.querySelectorAll(".js-authoriser-confirmation-text").forEach((span) => { span.textContent = `Continuing will authorise ${selected.value} to take all images for this appointment` }) From d22db115ecdb1100c9756a15e1eec7ccbd72e33d Mon Sep 17 00:00:00 2001 From: rivalee Date: Thu, 10 Sep 2026 16:03:55 +0100 Subject: [PATCH 28/65] update content --- .../medical-information/mammogram-image-data.njk | 10 ++++++++++ app/views/appointments/images-manual.html | 15 +++++++++++++-- .../appointments/review-medical-information.html | 15 +++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk b/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk index 83fa8a232..548b63f24 100644 --- a/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk +++ b/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk @@ -250,6 +250,16 @@ } if allowEdits }) %} +{# Operator - defaults to the logged in user, unless an alternative mammographer was nominated #} +{% set summaryRows = summaryRows | push({ + key: { + text: "Operator" + }, + value: { + text: appointment.operator + } +}) %} + {# OLD Views taken (split into separate rows) - keeping for reference #} {# {% set summaryRows = summaryRows | push({ key: { diff --git a/app/views/appointments/images-manual.html b/app/views/appointments/images-manual.html index 742612618..5b3f10b43 100644 --- a/app/views/appointments/images-manual.html +++ b/app/views/appointments/images-manual.html @@ -18,6 +18,10 @@

{{ pageHeading }}

{% set mammogramSource = appointment.mammogramDataTemp or appointment.mammogramData %} + {# Determine whether a different mammographer was nominated to take the images #} + {% set currentUserName = data.currentUser.firstName + " " + data.currentUser.lastName %} + {% set operatorChanged = appointment.operator and appointment.operator != currentUserName %} + {# Determine current room name #} {% set currentRoomName = "" %} {% set isMobileClinic = (clinic.location.type == 'mobile_unit') %} @@ -38,10 +42,17 @@

{{ pageHeading }}

Mammogram location: {{ currentRoomName }}. {% if not isMobileClinic %} -
Change room or machine details + (change) {% endif %}

+ {% if operatorChanged %} +

+ Authorised operator: {{ appointment.operator }} + (change) +

+ {% endif %} + {{ appHiddenInput({ name: "appointment[mammogramDataTemp][machineRoom]", value: currentRoomName @@ -148,7 +159,7 @@

Manually add participant details

name: "appointment[mammogramDataTemp][isStandardSet]", fieldset: { legend: { - text: "Have you taken a standard set of images?", + text: "Were a standard set of images taken?", size: "m", isPageHeading: false } diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index b347a7310..a06e2512e 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -38,6 +38,9 @@ {# Nominating an alternative mammographer is only relevant when implants or devices require authorisation #} {% set requiresAuthorisation = hasActiveImplants or hasActiveMedicalDevices %} +{# Operator defaults to the logged in user, unless an alternative mammographer has been nominated #} +{% set operatorName = appointment.operator or (data.currentUser.firstName + " " + data.currentUser.lastName) %} + {% block pageContent %}

@@ -54,6 +57,12 @@

value: appointment.workflowStatus['review-breast-features-after-imaging'] }) }} + {{ appHiddenInput({ + id: "irmer-operator-input", + name: "appointment[operator]", + value: operatorName + }) }} + {% if requiresAuthorisation and appointment.workflowStatus['review-medical-information'] != 'completed' %}
{{ insetText({ @@ -212,6 +221,12 @@

span.textContent = `Continuing will authorise ${selected.value} to take all images for this appointment` }) + // Persist the nominated mammographer as the operator for this appointment + const operatorInput = document.getElementById("irmer-operator-input") + if (operatorInput) { + operatorInput.value = selected.value + } + // Show the confirmation message now that a mammographer has been nominated document.querySelectorAll(".js-authoriser-confirmation").forEach((p) => { p.style.display = "" From 2c0bf9d656caae503077083893d8bedd50f58817 Mon Sep 17 00:00:00 2001 From: rivalee Date: Mon, 10 Aug 2026 13:47:29 +0100 Subject: [PATCH 29/65] Update button content to authorise --- app/assets/javascript/expandable-sections.js | 6 +++--- app/views/appointments/review-medical-information.html | 4 ++-- tests/e2e/appointment.spec.js | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/assets/javascript/expandable-sections.js b/app/assets/javascript/expandable-sections.js index ea2ba2d7d..31ffa0b0c 100644 --- a/app/assets/javascript/expandable-sections.js +++ b/app/assets/javascript/expandable-sections.js @@ -123,7 +123,7 @@ document.addEventListener('DOMContentLoaded', function () { // Initialize progress updateProgress(sections, completedSections) - // Handle "Complete all and continue" button + // Handle "Authorise" button const completeAllButtons = document.querySelectorAll( '.js-complete-all-sections' ) @@ -322,7 +322,7 @@ function openNextIncompleteSection(currentIndex, sections, completedSections) { // Function to highlight the completion button when all sections are done function highlightCompletionButton() { const completeButton = document.querySelector( - 'button:contains("Complete all and continue")' + 'button:contains("Authorise")' ) if (!completeButton) { @@ -330,7 +330,7 @@ function highlightCompletionButton() { const buttons = document.querySelectorAll('button') buttons.forEach((btn) => { - if (btn.textContent.includes('Complete all and continue')) { + if (btn.textContent.includes('Authorise')) { btn.classList.add('nhsuk-button--green') // Highlight in green btn.style.animation = 'pulse 2s infinite' // Add a subtle animation } diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index b61b10a50..95668f08c 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -32,7 +32,7 @@

{{ button({ - text: "Complete all and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", + text: "Authorise and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", classes: "nhsuk-u-margin-bottom-0 nhsuk-button js-complete-all-sections" }) }}
@@ -44,7 +44,7 @@

{{ button({ - text: "Complete all and continue", + text: "Authorise and continue", classes: "js-complete-all-sections" }) }} diff --git a/tests/e2e/appointment.spec.js b/tests/e2e/appointment.spec.js index 4a448f75a..340b1fb2a 100644 --- a/tests/e2e/appointment.spec.js +++ b/tests/e2e/appointment.spec.js @@ -134,7 +134,7 @@ test.describe('Screening appointment', () => { await expect(page.getByText('Lump').first()).toBeVisible() await page - .getByRole('button', { name: 'Complete all and continue' }) + .getByRole('button', { name: 'Authorise' }) .first() .click() @@ -164,7 +164,7 @@ test.describe('Screening appointment', () => { page.getByRole('heading', { name: 'Review medical information' }) ).toBeVisible() await page - .getByRole('button', { name: 'Complete all and continue' }) + .getByRole('button', { name: 'Authorise' }) .first() .click() From 6dc395d9583940a9de04f8602bcccc117021bd71 Mon Sep 17 00:00:00 2001 From: rivalee Date: Mon, 10 Aug 2026 14:55:37 +0100 Subject: [PATCH 30/65] add line of text with mammographer name --- app/views/appointments/review-medical-information.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 95668f08c..95d7c0689 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -35,6 +35,7 @@

text: "Authorise and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", classes: "nhsuk-u-margin-bottom-0 nhsuk-button js-complete-all-sections" }) }} +

This will authorise imaging by {{ data.currentUser.firstName }} {{ data.currentUser.lastName }}

@@ -45,8 +46,9 @@

{{ button({ text: "Authorise and continue", - classes: "js-complete-all-sections" + classes: "js-complete-all-sections nhsuk-u-margin-bottom-0" }) }} +

This will authorise imaging by {{ data.currentUser.firstName }} {{ data.currentUser.lastName }}

{% include "screening-cannot-proceed-link.njk" %}
From 8b38b9819d58a4071ecb546e239aa6cc6a2e4374 Mon Sep 17 00:00:00 2001 From: rivalee Date: Wed, 12 Aug 2026 10:55:17 +0100 Subject: [PATCH 31/65] fix bug --- app/routes/clinics.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/routes/clinics.js b/app/routes/clinics.js index 380519127..4751d62f2 100644 --- a/app/routes/clinics.js +++ b/app/routes/clinics.js @@ -482,6 +482,9 @@ module.exports = (router) => { } const clinicData = getClinicData(req.session.data, req.params.id) + if (!clinicData) { + return res.redirect('/clinics') + } let remainingCount = filterAppointmentsByStatus( clinicData.appointments, 'remaining' From ea70647e20574912ee6090d7604a53af062eba0d Mon Sep 17 00:00:00 2001 From: rivalee Date: Tue, 18 Aug 2026 14:55:28 +0100 Subject: [PATCH 32/65] Update content when breast implants added --- .../review-medical-information.html | 138 ++++++++++++++++-- 1 file changed, 126 insertions(+), 12 deletions(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 95d7c0689..dc8190365 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -13,6 +13,19 @@ {# Display format for medical information sections #} {% set displayFormat = "expander" %} +{# Check if participant has active (non-removed) breast implants #} +{% set hasActiveImplants = false %} +{% set implantRecords = appointment.medicalInformation.medicalHistory.breastImplantsAugmentation %} +{% if implantRecords | length %} + {% for implant in implantRecords %} + {% if not implant.hasBeenRemoved %} + {% set hasActiveImplants = true %} + {% endif %} + {% endfor %} +{% endif %} + +{% set defaultAuthoriser = "J. Wyatt" %} + {% block pageContent %}

@@ -29,28 +42,129 @@

value: appointment.workflowStatus['review-breast-features-after-imaging'] }) }} -
-
+ {% if hasActiveImplants and appointment.workflowStatus['review-medical-information'] != 'completed' %} + {{ insetText({ + html: "

You do not have permission to authorise some views.

" + }) }} +
{{ button({ - text: "Authorise and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", - classes: "nhsuk-u-margin-bottom-0 nhsuk-button js-complete-all-sections" + text: "Authorise with " + defaultAuthoriser + " and continue", + classes: "nhsuk-u-margin-bottom-0 js-complete-all-sections js-irmer-authorise-button" }) }} -

This will authorise imaging by {{ data.currentUser.firstName }} {{ data.currentUser.lastName }}

+ Choose alternative authoriser
-
+ {% else %} +
+
+ {{ button({ + text: "Authorise and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", + classes: "nhsuk-u-margin-bottom-0 nhsuk-button js-complete-all-sections" + }) }} +

This will authorise imaging by {{ data.currentUser.firstName }} {{ data.currentUser.lastName }}

+
+
+ {% endif %}
{% include "_includes/medical-information/index.njk" %}
- {{ button({ - text: "Authorise and continue", - classes: "js-complete-all-sections nhsuk-u-margin-bottom-0" - }) }} -

This will authorise imaging by {{ data.currentUser.firstName }} {{ data.currentUser.lastName }}

+ {% if hasActiveImplants and appointment.workflowStatus['review-medical-information'] != 'completed' %} + {{ insetText({ + html: "

You do not have permission to authorise some views.

" + }) }} +
+ {{ button({ + text: "Authorise with " + defaultAuthoriser + " and continue", + classes: "nhsuk-u-margin-bottom-0 js-complete-all-sections js-irmer-authorise-button" + }) }} + Choose alternative authoriser +
+ {% else %} + {{ button({ + text: "Authorise and continue", + classes: "js-complete-all-sections nhsuk-u-margin-bottom-0" + }) }} +

This will authorise imaging by {{ data.currentUser.firstName }} {{ data.currentUser.lastName }}

+ {% endif %} - {% include "screening-cannot-proceed-link.njk" %} +
+ {% include "screening-cannot-proceed-link.njk" %} +
+ {# Alternative authoriser modal #} + {% if hasActiveImplants %} + {% set authoriserModalContent %} + {{ radios({ + name: "irmerAuthoriser", + fieldset: { + legend: { + text: "Select an alternative authoriser" + } + }, + items: [ + { value: "S. Patel", text: "S. Patel" }, + { value: "R. Thompson", text: "R. Thompson" }, + { value: "L. Okonkwo", text: "L. Okonkwo" }, + { value: "H. Chambers", text: "H. Chambers" }, + { value: "F. Kaur", text: "F. Kaur" } + ] + }) }} + {% endset %} + + {{ appModal({ + id: "choose-authoriser-modal", + title: "Choose alternative authoriser", + showCloseButton: true, + content: authoriserModalContent, + actions: [ + { + text: "Save", + action: "close", + classes: "js-select-authoriser" + }, + { + text: "Cancel", + element: "link", + action: "close" + } + ] + }) }} + {% endif %} + +{% endblock %} + +{% if hasActiveImplants %} +{% block pageScripts %} + {% endblock %} +{% endif %} From 62fcccd9671e0b897342e461b9394caa0c2a6df9 Mon Sep 17 00:00:00 2001 From: rivalee Date: Thu, 20 Aug 2026 15:25:05 +0100 Subject: [PATCH 33/65] Update IRMER auth button to choose authoriser --- .../review-medical-information.html | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index dc8190365..a2b44e127 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -24,8 +24,6 @@ {% endfor %} {% endif %} -{% set defaultAuthoriser = "J. Wyatt" %} - {% block pageContent %}

@@ -48,10 +46,11 @@

}) }}
{{ button({ - text: "Authorise with " + defaultAuthoriser + " and continue", - classes: "nhsuk-u-margin-bottom-0 js-complete-all-sections js-irmer-authorise-button" + text: "Choose an authoriser", + classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", + attributes: { type: "button" } }) }} - Choose alternative authoriser +
{% else %}
@@ -76,10 +75,11 @@

}) }}
{{ button({ - text: "Authorise with " + defaultAuthoriser + " and continue", - classes: "nhsuk-u-margin-bottom-0 js-complete-all-sections js-irmer-authorise-button" + text: "Choose an authoriser", + classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", + attributes: { type: "button" } }) }} - Choose alternative authoriser +
{% else %} {{ button({ @@ -101,7 +101,7 @@

name: "irmerAuthoriser", fieldset: { legend: { - text: "Select an alternative authoriser" + text: "Select an authoriser" } }, items: [ @@ -116,7 +116,7 @@

{{ appModal({ id: "choose-authoriser-modal", - title: "Choose alternative authoriser", + title: "Choose an authoriser", showCloseButton: true, content: authoriserModalContent, actions: [ @@ -129,6 +129,12 @@

text: "Cancel", element: "link", action: "close" + }, + { + text: "Appointment cannot proceed", + element: "link", + href: "/clinics/" + clinicId + "/appointments/" + appointmentId + "/attended-not-screened-reason", + action: "close" } ] }) }} @@ -144,7 +150,7 @@

const modal = document.getElementById("choose-authoriser-modal") if (!modal) return - // Open modal when "Choose alternative authoriser" is clicked + // Open modal when authoriser button is clicked document.querySelectorAll(".js-choose-authoriser-link").forEach((link) => { link.addEventListener("click", (event) => { event.preventDefault() @@ -152,7 +158,7 @@

}) }) - // Update all authorise buttons when an authoriser is selected + // Update buttons and enable form submission when an authoriser is selected const selectButton = modal.querySelector(".js-select-authoriser") if (selectButton) { selectButton.addEventListener("click", () => { @@ -160,6 +166,13 @@

if (selected) { document.querySelectorAll(".js-irmer-authorise-button").forEach((btn) => { btn.textContent = "Authorise with " + selected.value + " and continue" + btn.removeAttribute("type") + btn.classList.add("js-complete-all-sections") + btn.classList.remove("js-choose-authoriser-link") + }) + // Show the alternative authoriser links + document.querySelectorAll(".js-alternative-authoriser-link").forEach((link) => { + link.style.display = "" }) } }) From 03b31062a271e1c0599aa2a9d8e7a9a39ab16381 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:35:59 +0100 Subject: [PATCH 34/65] Update app/assets/javascript/expandable-sections.js Co-authored-by: Danny Chadburn --- app/assets/javascript/expandable-sections.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascript/expandable-sections.js b/app/assets/javascript/expandable-sections.js index 31ffa0b0c..6b0e08daf 100644 --- a/app/assets/javascript/expandable-sections.js +++ b/app/assets/javascript/expandable-sections.js @@ -123,7 +123,7 @@ document.addEventListener('DOMContentLoaded', function () { // Initialize progress updateProgress(sections, completedSections) - // Handle "Authorise" button + // Handle "Complete all and continue" button const completeAllButtons = document.querySelectorAll( '.js-complete-all-sections' ) From b6cbcf05a1f6e0767beccffb5a9f0d64719eb902 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:36:51 +0100 Subject: [PATCH 35/65] Update app/assets/javascript/expandable-sections.js Co-authored-by: Danny Chadburn --- app/assets/javascript/expandable-sections.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascript/expandable-sections.js b/app/assets/javascript/expandable-sections.js index 6b0e08daf..6178bc6b9 100644 --- a/app/assets/javascript/expandable-sections.js +++ b/app/assets/javascript/expandable-sections.js @@ -322,7 +322,7 @@ function openNextIncompleteSection(currentIndex, sections, completedSections) { // Function to highlight the completion button when all sections are done function highlightCompletionButton() { const completeButton = document.querySelector( - 'button:contains("Authorise")' + 'button:contains("Complete all and continue")' ) if (!completeButton) { From bf0b2ac90e72e69aec2cfbaf25f52b6d779c9336 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:37:00 +0100 Subject: [PATCH 36/65] Update app/assets/javascript/expandable-sections.js Co-authored-by: Danny Chadburn --- app/assets/javascript/expandable-sections.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascript/expandable-sections.js b/app/assets/javascript/expandable-sections.js index 6178bc6b9..ea2ba2d7d 100644 --- a/app/assets/javascript/expandable-sections.js +++ b/app/assets/javascript/expandable-sections.js @@ -330,7 +330,7 @@ function highlightCompletionButton() { const buttons = document.querySelectorAll('button') buttons.forEach((btn) => { - if (btn.textContent.includes('Authorise')) { + if (btn.textContent.includes('Complete all and continue')) { btn.classList.add('nhsuk-button--green') // Highlight in green btn.style.animation = 'pulse 2s infinite' // Add a subtle animation } From 685decc6789b7483ab284d22faabb62c54b059e5 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:37:09 +0100 Subject: [PATCH 37/65] Update tests/e2e/appointment.spec.js Co-authored-by: Danny Chadburn --- tests/e2e/appointment.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/appointment.spec.js b/tests/e2e/appointment.spec.js index 340b1fb2a..325b0871a 100644 --- a/tests/e2e/appointment.spec.js +++ b/tests/e2e/appointment.spec.js @@ -134,7 +134,7 @@ test.describe('Screening appointment', () => { await expect(page.getByText('Lump').first()).toBeVisible() await page - .getByRole('button', { name: 'Authorise' }) + .getByRole('button', { name: 'Complete all and continue' }) .first() .click() From 13aa87978b7ee834c76f8ff933e4d0cea2128a2d Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:37:24 +0100 Subject: [PATCH 38/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index a2b44e127..a3bc24d27 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -59,7 +59,7 @@

text: "Authorise and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", classes: "nhsuk-u-margin-bottom-0 nhsuk-button js-complete-all-sections" }) }} -

This will authorise imaging by {{ data.currentUser.firstName }} {{ data.currentUser.lastName }}

+

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images ()

{% endif %} From 63b87537a0ce4e7edf9402858c01173ffa090012 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:37:30 +0100 Subject: [PATCH 39/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index a3bc24d27..955b9ec23 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -86,7 +86,7 @@

text: "Authorise and continue", classes: "js-complete-all-sections nhsuk-u-margin-bottom-0" }) }} -

This will authorise imaging by {{ data.currentUser.firstName }} {{ data.currentUser.lastName }}

+

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images ()

{% endif %}
From 09b6efe89a6c897bccb97c88ca21c280c2a41a6d Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:37:40 +0100 Subject: [PATCH 40/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 955b9ec23..3c56d4f5f 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -101,7 +101,7 @@

name: "irmerAuthoriser", fieldset: { legend: { - text: "Select an authoriser" + text: "Who is taking images for this appointment?" } }, items: [ From ada64e485a1f9424d8cfdecc4dca9f4d977a2000 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:37:50 +0100 Subject: [PATCH 41/65] Update tests/e2e/appointment.spec.js Co-authored-by: Danny Chadburn --- tests/e2e/appointment.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/appointment.spec.js b/tests/e2e/appointment.spec.js index 325b0871a..4a448f75a 100644 --- a/tests/e2e/appointment.spec.js +++ b/tests/e2e/appointment.spec.js @@ -164,7 +164,7 @@ test.describe('Screening appointment', () => { page.getByRole('heading', { name: 'Review medical information' }) ).toBeVisible() await page - .getByRole('button', { name: 'Authorise' }) + .getByRole('button', { name: 'Complete all and continue' }) .first() .click() From 7478ec760dcd4656a06fad835e7118bcc5a19721 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:38:10 +0100 Subject: [PATCH 42/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 3c56d4f5f..9ab687265 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -46,7 +46,7 @@

}) }}
{{ button({ - text: "Choose an authoriser", + text: "Nominate a mammographer", classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", attributes: { type: "button" } }) }} From 1ecca17339a50670a07d17dab0587f665adbb505 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:38:17 +0100 Subject: [PATCH 43/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 9ab687265..7b5bdd901 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -83,7 +83,7 @@

{% else %} {{ button({ - text: "Authorise and continue", + text: "Complete all and continue", classes: "js-complete-all-sections nhsuk-u-margin-bottom-0" }) }}

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images ()

From 8a2dd2a43541069d6f2c34c87e5d03a5813394b9 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:38:58 +0100 Subject: [PATCH 44/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 7b5bdd901..bf9498b44 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -116,7 +116,7 @@

{{ appModal({ id: "choose-authoriser-modal", - title: "Choose an authoriser", + title: "Nominate a mammographer", showCloseButton: true, content: authoriserModalContent, actions: [ From 1f581e8c2a3daea7ab4c2d8beb651755af12f39b Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:39:08 +0100 Subject: [PATCH 45/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index bf9498b44..71981c570 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -165,7 +165,7 @@

const selected = modal.querySelector("input[name='irmerAuthoriser']:checked") if (selected) { document.querySelectorAll(".js-irmer-authorise-button").forEach((btn) => { - btn.textContent = "Authorise with " + selected.value + " and continue" + btn.textContent = "Complete all and continue" btn.removeAttribute("type") btn.classList.add("js-complete-all-sections") btn.classList.remove("js-choose-authoriser-link") From 9d03405b7cc1e122e3d0ee754aba5d476e00eee3 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:39:15 +0100 Subject: [PATCH 46/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 71981c570..0c9490d55 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -170,6 +170,11 @@

btn.classList.add("js-complete-all-sections") btn.classList.remove("js-choose-authoriser-link") }) + +document.querySelectorAll(".js-authoriser-confirmation").forEach((p) => { + p.textContent = `Continuing will authorise ${selected.value} to take all images for this appointment`; +}); + // Show the alternative authoriser links document.querySelectorAll(".js-alternative-authoriser-link").forEach((link) => { link.style.display = "" From 8c208293ad11ffac0e91714ac9ab0080667eb4c3 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:39:30 +0100 Subject: [PATCH 47/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 0c9490d55..1e2e42272 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -171,6 +171,11 @@

btn.classList.remove("js-choose-authoriser-link") }) +document.querySelectorAll(".js-authoriser-confirmation").forEach((p) => { + p.textContent = `Continuing will authorise ${selected.value} to take all images for this appointment`; +}); + + // Show the alternative authoriser links document.querySelectorAll(".js-authoriser-confirmation").forEach((p) => { p.textContent = `Continuing will authorise ${selected.value} to take all images for this appointment`; }); From bb83d6ae70195b6a9ec6715c69346c52003f651f Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:39:43 +0100 Subject: [PATCH 48/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 1e2e42272..249d45e90 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -42,7 +42,7 @@

{% if hasActiveImplants and appointment.workflowStatus['review-medical-information'] != 'completed' %} {{ insetText({ - html: "

You do not have permission to authorise some views.

" + html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" }) }}
{{ button({ From 4901a0ff6105545611af88470d657b439d83d8b6 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:39:55 +0100 Subject: [PATCH 49/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 249d45e90..548182adc 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -50,7 +50,7 @@

classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", attributes: { type: "button" } }) }} - +

()

{% else %}
From 0aaa5b61e1ee2f36f4676d549f6cd1a9160d857f Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:40:09 +0100 Subject: [PATCH 50/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 548182adc..bb76c217c 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -56,7 +56,7 @@

{{ button({ - text: "Authorise and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", + text: "Complete all and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", classes: "nhsuk-u-margin-bottom-0 nhsuk-button js-complete-all-sections" }) }}

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images ()

From c56a997e0e5fbc2c92758d8296e467487a74d2af Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:40:19 +0100 Subject: [PATCH 51/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index bb76c217c..5fddf04fc 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -71,7 +71,7 @@

{% if hasActiveImplants and appointment.workflowStatus['review-medical-information'] != 'completed' %} {{ insetText({ - html: "

You do not have permission to authorise some views.

" + html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" }) }}
{{ button({ From 80edd4b998134523a1058dad45a2630507618ec3 Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:40:32 +0100 Subject: [PATCH 52/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 5fddf04fc..bd95816a7 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -75,7 +75,7 @@

}) }}
{{ button({ - text: "Choose an authoriser", + text: "Nominate a mammographer", classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", attributes: { type: "button" } }) }} From b9b249aada66527e510c8460c7fd9156e8d2533b Mon Sep 17 00:00:00 2001 From: Rebecca Cottrell Date: Tue, 8 Sep 2026 13:40:43 +0100 Subject: [PATCH 53/65] Update app/views/appointments/review-medical-information.html Co-authored-by: Danny Chadburn --- app/views/appointments/review-medical-information.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index bd95816a7..96b28d8fb 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -79,7 +79,7 @@

classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", attributes: { type: "button" } }) }} - +

()

{% else %} {{ button({ From abe02638aa6f57892d8c1721d5d47a5ecdacd104 Mon Sep 17 00:00:00 2001 From: rivalee Date: Tue, 8 Sep 2026 16:09:22 +0100 Subject: [PATCH 54/65] Update content --- .../review-medical-information.html | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 96b28d8fb..903d1fb01 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -42,7 +42,7 @@

{% if hasActiveImplants and appointment.workflowStatus['review-medical-information'] != 'completed' %} {{ insetText({ - html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" + html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" }) }}
{{ button({ @@ -50,7 +50,7 @@

classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", attributes: { type: "button" } }) }} -

()

+

{% else %}
@@ -59,7 +59,7 @@

text: "Complete all and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", classes: "nhsuk-u-margin-bottom-0 nhsuk-button js-complete-all-sections" }) }} -

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images ()

+

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images (change mammographer)

{% endif %} @@ -71,7 +71,7 @@

{% if hasActiveImplants and appointment.workflowStatus['review-medical-information'] != 'completed' %} {{ insetText({ - html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" + html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" }) }}
{{ button({ @@ -79,14 +79,14 @@

classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", attributes: { type: "button" } }) }} -

()

+

{% else %} {{ button({ text: "Complete all and continue", classes: "js-complete-all-sections nhsuk-u-margin-bottom-0" }) }} -

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images ()

+

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images (change mammographer)

{% endif %}
@@ -95,8 +95,7 @@

{# Alternative authoriser modal #} - {% if hasActiveImplants %} - {% set authoriserModalContent %} + {% set authoriserModalContent %} {{ radios({ name: "irmerAuthoriser", fieldset: { @@ -112,6 +111,19 @@

{ value: "F. Kaur", text: "F. Kaur" } ] }) }} + + {{ radios({ + name: "irmerImagesScope", + fieldset: { + legend: { + text: "Which images are they taking?" + } + }, + items: [ + { value: "all-views", text: "All views" }, + { value: "additional-views", text: "Just the additional views" } + ] + }) }} {% endset %} {{ appModal({ @@ -138,11 +150,9 @@

} ] }) }} - {% endif %} {% endblock %} -{% if hasActiveImplants %} {% block pageScripts %} {% endblock %} -{% endif %} From 8b7d0eaa7764ec57797b56fad5398bd68d1100d6 Mon Sep 17 00:00:00 2001 From: rivalee Date: Thu, 10 Sep 2026 12:25:28 +0100 Subject: [PATCH 55/65] edits --- .../review-medical-information.html | 117 +++++++++++------- 1 file changed, 72 insertions(+), 45 deletions(-) diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 903d1fb01..b347a7310 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -24,6 +24,20 @@ {% endfor %} {% endif %} +{# Check if participant has active (non-removed) implanted medical devices #} +{% set hasActiveMedicalDevices = false %} +{% set medicalDeviceRecords = appointment.medicalInformation.medicalHistory.implantedMedicalDevice %} +{% if medicalDeviceRecords | length %} + {% for device in medicalDeviceRecords %} + {% if not device.deviceRemoved %} + {% set hasActiveMedicalDevices = true %} + {% endif %} + {% endfor %} +{% endif %} + +{# Nominating an alternative mammographer is only relevant when implants or devices require authorisation #} +{% set requiresAuthorisation = hasActiveImplants or hasActiveMedicalDevices %} + {% block pageContent %}

@@ -40,17 +54,23 @@

value: appointment.workflowStatus['review-breast-features-after-imaging'] }) }} - {% if hasActiveImplants and appointment.workflowStatus['review-medical-information'] != 'completed' %} - {{ insetText({ - html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" - }) }} -
- {{ button({ - text: "Nominate a mammographer", - classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", - attributes: { type: "button" } + {% if requiresAuthorisation and appointment.workflowStatus['review-medical-information'] != 'completed' %} +
+ {{ insetText({ + html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" }) }} - +
+
+
+
+ {{ button({ + text: "Nominate a mammographer", + classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", + attributes: { type: "button" } + }) }} + +
+
{% else %}
@@ -59,7 +79,9 @@

text: "Complete all and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", classes: "nhsuk-u-margin-bottom-0 nhsuk-button js-complete-all-sections" }) }} -

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images (change mammographer)

+ {% if requiresAuthorisation %} +

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images (change mammographer)

+ {% endif %}

{% endif %} @@ -69,24 +91,36 @@

{% include "_includes/medical-information/index.njk" %}
- {% if hasActiveImplants and appointment.workflowStatus['review-medical-information'] != 'completed' %} - {{ insetText({ - html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" - }) }} -
- {{ button({ - text: "Nominate a mammographer", - classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", - attributes: { type: "button" } + {% if requiresAuthorisation and appointment.workflowStatus['review-medical-information'] != 'completed' %} +
+ {{ insetText({ + html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" }) }} - +
+
+
+
+ {{ button({ + text: "Nominate a mammographer", + classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", + attributes: { type: "button" } + }) }} + +
+
{% else %} - {{ button({ - text: "Complete all and continue", - classes: "js-complete-all-sections nhsuk-u-margin-bottom-0" - }) }} -

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images (change mammographer)

+
+
+ {{ button({ + text: "Complete all and continue", + classes: "js-complete-all-sections nhsuk-u-margin-bottom-0" + }) }} + {% if requiresAuthorisation %} +

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images (change mammographer)

+ {% endif %} +
+
{% endif %}
@@ -111,19 +145,6 @@

{ value: "F. Kaur", text: "F. Kaur" } ] }) }} - - {{ radios({ - name: "irmerImagesScope", - fieldset: { - legend: { - text: "Which images are they taking?" - } - }, - items: [ - { value: "all-views", text: "All views" }, - { value: "additional-views", text: "Just the additional views" } - ] - }) }} {% endset %} {{ appModal({ @@ -160,12 +181,13 @@

const modal = document.getElementById("choose-authoriser-modal") if (!modal) return - // Open modal when authoriser button is clicked - document.querySelectorAll(".js-choose-authoriser-link").forEach((link) => { - link.addEventListener("click", (event) => { - event.preventDefault() - window.openModal("choose-authoriser-modal") - }) + // Open modal when authoriser button is clicked - checked at click time so + // buttons that have been converted to complete-and-continue buttons submit normally + document.addEventListener("click", (event) => { + const link = event.target.closest(".js-choose-authoriser-link") + if (!link) return + event.preventDefault() + window.openModal("choose-authoriser-modal") }) // Update buttons and enable form submission when an authoriser is selected @@ -181,6 +203,11 @@

btn.classList.remove("js-choose-authoriser-link") }) + // Hide the permission notice now that a mammographer has been nominated + document.querySelectorAll(".js-irmer-permission-notice").forEach((notice) => { + notice.style.display = "none" + }) + document.querySelectorAll(".js-authoriser-confirmation-text").forEach((span) => { span.textContent = `Continuing will authorise ${selected.value} to take all images for this appointment` }) From 147ac655919611f62dc05efb81a92fb5b3e227fc Mon Sep 17 00:00:00 2001 From: rivalee Date: Thu, 10 Sep 2026 16:03:55 +0100 Subject: [PATCH 56/65] update content --- .../medical-information/mammogram-image-data.njk | 10 ++++++++++ app/views/appointments/images-manual.html | 15 +++++++++++++-- .../appointments/review-medical-information.html | 15 +++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk b/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk index 0364ae4ca..6060d10e8 100644 --- a/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk +++ b/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk @@ -250,6 +250,16 @@ } if allowEdits }) %} +{# Operator - defaults to the logged in user, unless an alternative mammographer was nominated #} +{% set summaryRows = summaryRows | push({ + key: { + text: "Operator" + }, + value: { + text: appointment.operator + } +}) %} + {# OLD Views taken (split into separate rows) - keeping for reference #} {# {% set summaryRows = summaryRows | push({ key: { diff --git a/app/views/appointments/images-manual.html b/app/views/appointments/images-manual.html index 742612618..5b3f10b43 100644 --- a/app/views/appointments/images-manual.html +++ b/app/views/appointments/images-manual.html @@ -18,6 +18,10 @@

{{ pageHeading }}

{% set mammogramSource = appointment.mammogramDataTemp or appointment.mammogramData %} + {# Determine whether a different mammographer was nominated to take the images #} + {% set currentUserName = data.currentUser.firstName + " " + data.currentUser.lastName %} + {% set operatorChanged = appointment.operator and appointment.operator != currentUserName %} + {# Determine current room name #} {% set currentRoomName = "" %} {% set isMobileClinic = (clinic.location.type == 'mobile_unit') %} @@ -38,10 +42,17 @@

{{ pageHeading }}

Mammogram location: {{ currentRoomName }}. {% if not isMobileClinic %} -
Change room or machine details + (change) {% endif %}

+ {% if operatorChanged %} +

+ Authorised operator: {{ appointment.operator }} + (change) +

+ {% endif %} + {{ appHiddenInput({ name: "appointment[mammogramDataTemp][machineRoom]", value: currentRoomName @@ -148,7 +159,7 @@

Manually add participant details

name: "appointment[mammogramDataTemp][isStandardSet]", fieldset: { legend: { - text: "Have you taken a standard set of images?", + text: "Were a standard set of images taken?", size: "m", isPageHeading: false } diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index b347a7310..a06e2512e 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -38,6 +38,9 @@ {# Nominating an alternative mammographer is only relevant when implants or devices require authorisation #} {% set requiresAuthorisation = hasActiveImplants or hasActiveMedicalDevices %} +{# Operator defaults to the logged in user, unless an alternative mammographer has been nominated #} +{% set operatorName = appointment.operator or (data.currentUser.firstName + " " + data.currentUser.lastName) %} + {% block pageContent %}

@@ -54,6 +57,12 @@

value: appointment.workflowStatus['review-breast-features-after-imaging'] }) }} + {{ appHiddenInput({ + id: "irmer-operator-input", + name: "appointment[operator]", + value: operatorName + }) }} + {% if requiresAuthorisation and appointment.workflowStatus['review-medical-information'] != 'completed' %}
{{ insetText({ @@ -212,6 +221,12 @@

span.textContent = `Continuing will authorise ${selected.value} to take all images for this appointment` }) + // Persist the nominated mammographer as the operator for this appointment + const operatorInput = document.getElementById("irmer-operator-input") + if (operatorInput) { + operatorInput.value = selected.value + } + // Show the confirmation message now that a mammographer has been nominated document.querySelectorAll(".js-authoriser-confirmation").forEach((p) => { p.style.display = "" From d903c8157c4cd82fcca594765597e3a5b4ddc9e0 Mon Sep 17 00:00:00 2001 From: rivalee Date: Mon, 14 Sep 2026 14:28:38 +0100 Subject: [PATCH 57/65] update content --- .../mammogram-image-data.njk | 2 +- app/views/appointments/check-information.html | 1 + app/views/appointments/images-manual.html | 2 +- .../review-medical-information.html | 56 +++++++++++++++---- 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk b/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk index 6060d10e8..c25df2322 100644 --- a/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk +++ b/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk @@ -253,7 +253,7 @@ {# Operator - defaults to the logged in user, unless an alternative mammographer was nominated #} {% set summaryRows = summaryRows | push({ key: { - text: "Operator" + text: operatorLabel | default("Operator") }, value: { text: appointment.operator diff --git a/app/views/appointments/check-information.html b/app/views/appointments/check-information.html index b7462ff7a..00ee0ee6b 100644 --- a/app/views/appointments/check-information.html +++ b/app/views/appointments/check-information.html @@ -22,6 +22,7 @@ {% set activeTab = 'review' %} {% set showReviewAfterImagingReminder = appointment.workflowStatus['review-breast-features-after-imaging'] == 'yes' %} +{% set operatorLabel = "Authorised mammographer" %} diff --git a/app/views/appointments/images-manual.html b/app/views/appointments/images-manual.html index 5b3f10b43..bd31cf488 100644 --- a/app/views/appointments/images-manual.html +++ b/app/views/appointments/images-manual.html @@ -48,7 +48,7 @@

{{ pageHeading }}

{% if operatorChanged %}

- Authorised operator: {{ appointment.operator }} + Authorised mammographer: {{ appointment.operator }} (change)

{% endif %} diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index a06e2512e..5f3b32618 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -18,7 +18,7 @@ {% set implantRecords = appointment.medicalInformation.medicalHistory.breastImplantsAugmentation %} {% if implantRecords | length %} {% for implant in implantRecords %} - {% if not implant.hasBeenRemoved %} + {% if not (implant | isMedicalHistoryItemRemoved) %} {% set hasActiveImplants = true %} {% endif %} {% endfor %} @@ -29,12 +29,21 @@ {% set medicalDeviceRecords = appointment.medicalInformation.medicalHistory.implantedMedicalDevice %} {% if medicalDeviceRecords | length %} {% for device in medicalDeviceRecords %} - {% if not device.deviceRemoved %} + {% if not (device | isMedicalHistoryItemRemoved) %} {% set hasActiveMedicalDevices = true %} {% endif %} {% endfor %} {% endif %} +{% set permissionReason = "" %} +{% if hasActiveImplants and hasActiveMedicalDevices %} + {% set permissionReason = "breast implants and an implanted medical device" %} +{% elseif hasActiveImplants %} + {% set permissionReason = "breast implants" %} +{% elseif hasActiveMedicalDevices %} + {% set permissionReason = "an implanted medical device" %} +{% endif %} + {# Nominating an alternative mammographer is only relevant when implants or devices require authorisation #} {% set requiresAuthorisation = hasActiveImplants or hasActiveMedicalDevices %} @@ -66,18 +75,18 @@

{% if requiresAuthorisation and appointment.workflowStatus['review-medical-information'] != 'completed' %}
{{ insetText({ - html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" + html: "

Due to " + permissionReason + " you do not have the relevant permissions to take all the mammograms required for this appointment. Change the mammographer to someone authorised to take these images or exit the appointment.

" }) }}
{{ button({ - text: "Nominate a mammographer", + text: "Change mammographer", classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", attributes: { type: "button" } }) }} - +
@@ -103,18 +112,18 @@

{% if requiresAuthorisation and appointment.workflowStatus['review-medical-information'] != 'completed' %}
{{ insetText({ - html: "

You do not have permission to take all the required views for this participant. Nominate an authorised mammographer to take these images, or exit the appointment.

" + html: "

Due to " + permissionReason + " you do not have the relevant permissions to take all the mammograms required for this appointment. Change the mammographer to someone authorised to take these images or exit the appointment.

" }) }}
{{ button({ - text: "Nominate a mammographer", + text: "Change mammographer", classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", attributes: { type: "button" } }) }} - +
@@ -139,6 +148,9 @@

{# Alternative authoriser modal #} {% set authoriserModalContent %} + {{ insetText({ + text: "Ensure the person selected is also logged into the mammogram machine before images are taken" + }) }} {{ radios({ name: "irmerAuthoriser", fieldset: { @@ -151,14 +163,29 @@

{ value: "R. Thompson", text: "R. Thompson" }, { value: "L. Okonkwo", text: "L. Okonkwo" }, { value: "H. Chambers", text: "H. Chambers" }, - { value: "F. Kaur", text: "F. Kaur" } + { value: "F. Kaur", text: "F. Kaur" }, + { divider: "or" }, + { + value: "Other", + text: "Other", + conditional: { + html: input({ + id: "irmer-authoriser-other", + name: "irmerAuthoriserOther", + label: { + text: "Enter the mammographer's full name" + }, + autocomplete: "off" + }) + } + } ] }) }} {% endset %} {{ appModal({ id: "choose-authoriser-modal", - title: "Nominate a mammographer", + title: "Change mammographer", showCloseButton: true, content: authoriserModalContent, actions: [ @@ -205,6 +232,11 @@

selectButton.addEventListener("click", () => { const selected = modal.querySelector("input[name='irmerAuthoriser']:checked") if (selected) { + const otherName = modal.querySelector("input[name='irmerAuthoriserOther']") + const selectedName = selected.value === "Other" && otherName?.value.trim() + ? otherName.value.trim() + : selected.value + document.querySelectorAll(".js-irmer-authorise-button").forEach((btn) => { btn.textContent = "Complete all and continue" btn.removeAttribute("type") @@ -218,13 +250,13 @@

}) document.querySelectorAll(".js-authoriser-confirmation-text").forEach((span) => { - span.textContent = `Continuing will authorise ${selected.value} to take all images for this appointment` + span.textContent = `Continuing will authorise ${selectedName} to take all images for this appointment` }) // Persist the nominated mammographer as the operator for this appointment const operatorInput = document.getElementById("irmer-operator-input") if (operatorInput) { - operatorInput.value = selected.value + operatorInput.value = selectedName } // Show the confirmation message now that a mammographer has been nominated From addd899b616940eeaa28e58cb858e21ccb8e392f Mon Sep 17 00:00:00 2001 From: rivalee Date: Tue, 15 Sep 2026 14:32:08 +0100 Subject: [PATCH 58/65] get success messages working, content edits --- app/routes/appointments/lifecycle.js | 41 +++++ .../mammogram-image-data.njk | 43 +++-- .../appointments/change-mammographer.html | 58 +++++++ app/views/appointments/check-information.html | 8 + app/views/appointments/images-automatic.html | 15 ++ app/views/appointments/images-manual.html | 20 ++- .../review-medical-information.html | 155 ++---------------- 7 files changed, 181 insertions(+), 159 deletions(-) create mode 100644 app/views/appointments/change-mammographer.html diff --git a/app/routes/appointments/lifecycle.js b/app/routes/appointments/lifecycle.js index d22896382..8ba764698 100644 --- a/app/routes/appointments/lifecycle.js +++ b/app/routes/appointments/lifecycle.js @@ -337,6 +337,47 @@ module.exports = (router) => { } ) + // Change the authorised mammographer from the in-page modal, then return to + // the page it was opened from with a success banner if it actually changed + router.post( + '/clinics/:clinicId/appointments/:appointmentId/change-mammographer-answer', + (req, res) => { + const { clinicId, appointmentId } = req.params + const data = req.session.data + const currentUserName = `${data.currentUser.firstName} ${data.currentUser.lastName}` + + const selected = data.irmerAuthoriser + const otherName = (data.irmerAuthoriserOther || '').toString().trim() + const newOperator = + selected === 'Other' && otherName ? otherName : selected + + // The hidden operator field was bound to the appointment before this + // route ran, so it still holds the previous value to compare against + const previousOperator = data.appointment?.operator || currentUserName + + if (newOperator && data.appointment) { + data.appointment.operator = newOperator + // Records that an authorised mammographer has been explicitly chosen, + // so the review step can show "Complete all and continue" + data.appointment.mammographerNominated = true + if (newOperator !== previousOperator) { + req.flash('success', `Authorised mammographer updated to ${newOperator}`) + } + } + + // Clear the transient modal fields so they don't leak into other forms + delete data.irmerAuthoriser + delete data.irmerAuthoriserOther + + const returnPath = req.query.returnPath || 'check-information' + const returnUrl = getReturnUrl( + `/clinics/${clinicId}/appointments/${appointmentId}/${returnPath}`, + req.query.referrerChain + ) + res.redirect(modalBreakout(returnUrl)) + } + ) + // Appointment within clinic context router.get('/clinics/:clinicId/appointments/:appointmentId', (req, res) => { const { clinicId, appointmentId } = req.params diff --git a/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk b/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk index c25df2322..0224c5214 100644 --- a/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk +++ b/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk @@ -137,7 +137,7 @@ }) %} {% endif %} -{# Machine room #} +{# Mammogram location #} {% if appointment.mammogramData.machineRoom %} {# Check if this is a mobile clinic #} {% set isMobileClinic = (clinic.location.type == 'mobile_unit') %} @@ -148,14 +148,14 @@ { href: "./images-room-selection" | urlWithReferrer(referrerChain, scrollTo), text: "Change", - visuallyHiddenText: "machine room" + visuallyHiddenText: "mammogram location" } ] %} {% endif %} {% set summaryRows = summaryRows | push({ key: { - text: "Machine room" + text: "Mammogram location" }, value: { text: appointment.mammogramData.machineRoom @@ -166,6 +166,33 @@ }) %} {% endif %} +{# Authorised mammographer - defaults to the logged in user, unless an alternative mammographer was nominated #} +{% set operatorDisplayName = appointment.operator or (data.currentUser.firstName + " " + data.currentUser.lastName) %} +{% set operatorActions = [] %} +{% if allowEdits and operatorChangeHref %} + {% set operatorActions = [ + ({ + href: operatorChangeHref, + text: "Change", + visuallyHiddenText: "authorised mammographer" + } | openInModal) + ] %} +{% endif %} + +{% set operatorValueHtml %}{{ operatorDisplayName }}{% endset %} + +{% set summaryRows = summaryRows | push({ + key: { + text: operatorLabel | default("Operator") + }, + value: { + html: operatorValueHtml + }, + actions: { + items: operatorActions + } +}) %} + {# Combined views taken (showing all view information in one row) #} {# Sort views by side and standard order before displaying #} {% set sortedViews = [] %} @@ -250,16 +277,6 @@ } if allowEdits }) %} -{# Operator - defaults to the logged in user, unless an alternative mammographer was nominated #} -{% set summaryRows = summaryRows | push({ - key: { - text: operatorLabel | default("Operator") - }, - value: { - text: appointment.operator - } -}) %} - {# OLD Views taken (split into separate rows) - keeping for reference #} {# {% set summaryRows = summaryRows | push({ key: { diff --git a/app/views/appointments/change-mammographer.html b/app/views/appointments/change-mammographer.html new file mode 100644 index 000000000..a55ebe0b3 --- /dev/null +++ b/app/views/appointments/change-mammographer.html @@ -0,0 +1,58 @@ +{# app/views/appointments/change-mammographer.html #} + +{% extends parentLayout or '_templates/layout-appointment.html' %} + +{# Wording differs depending on whether images have already been taken #} +{% set imagesTaken = query.context == 'check-information' %} + +{% set pageHeading = "Change mammographer" %} +{% set formAction = ("./change-mammographer-answer?returnPath=" + (query.returnPath or 'check-information')) | urlWithReferrer(referrerChain) %} + +{% set legendText = "Who took the images for this appointment?" if imagesTaken else "Who is taking images for this appointment?" %} + +{% block pageContent %} + +

{{ pageHeading }}

+ + {% if not imagesTaken %} + {{ insetText({ + text: "Ensure the person selected is also logged into the mammogram machine before images are taken" + }) }} + {% endif %} + + {{ radios({ + name: "irmerAuthoriser", + fieldset: { + legend: { + text: legendText + } + }, + items: [ + { value: "S. Patel", text: "S. Patel" }, + { value: "R. Thompson", text: "R. Thompson" }, + { value: "L. Okonkwo", text: "L. Okonkwo" }, + { value: "H. Chambers", text: "H. Chambers" }, + { value: "F. Kaur", text: "F. Kaur" }, + { divider: "or" }, + { + value: "Other", + text: "Other", + conditional: { + html: input({ + id: "irmer-authoriser-other", + name: "irmerAuthoriserOther", + label: { + text: "Enter the mammographer's full name" + }, + autocomplete: "off" + }) + } + } + ] + }) }} + + {{ button({ + text: "Save" + }) }} + +{% endblock %} diff --git a/app/views/appointments/check-information.html b/app/views/appointments/check-information.html index 00ee0ee6b..ed020cf03 100644 --- a/app/views/appointments/check-information.html +++ b/app/views/appointments/check-information.html @@ -23,6 +23,8 @@ {% set activeTab = 'review' %} {% set showReviewAfterImagingReminder = appointment.workflowStatus['review-breast-features-after-imaging'] == 'yes' %} {% set operatorLabel = "Authorised mammographer" %} +{% set operatorName = appointment.operator or (data.currentUser.firstName + " " + data.currentUser.lastName) %} +{% set operatorChangeHref = (appointmentUrl + "/change-mammographer?context=check-information&returnPath=check-information") | urlWithReferrer(currentUrl) %} @@ -292,6 +294,12 @@

{{ pageHeading }}

{# Finish #} + {{ appHiddenInput({ + id: "irmer-operator-input", + name: "appointment[operator]", + value: operatorName + }) }} + {{ button({ text: "Complete screening and return to clinic" }) }} diff --git a/app/views/appointments/images-automatic.html b/app/views/appointments/images-automatic.html index 7f17a9522..fa52c6b8e 100644 --- a/app/views/appointments/images-automatic.html +++ b/app/views/appointments/images-automatic.html @@ -50,6 +50,21 @@

{{ pageHeading }}

+ {# Operator defaults to the logged in user, unless an alternative mammographer has been nominated #} + {% set operatorName = appointment.operator or (data.currentUser.firstName + " " + data.currentUser.lastName) %} + {% set changeMammographerHref = (appointmentUrl + "/change-mammographer?context=take-images&returnPath=take-images") | urlWithReferrer(currentUrl) %} + +

+ Authorised mammographer: {{ operatorName }} + ({{ appLink({ text: "change", href: changeMammographerHref } | openInModal) | trim | safe }}) +

+ + {{ appHiddenInput({ + id: "irmer-operator-input", + name: "appointment[operator]", + value: operatorName + }) }} +

Observations during mammogram

diff --git a/app/views/appointments/images-manual.html b/app/views/appointments/images-manual.html index bd31cf488..ca439e12e 100644 --- a/app/views/appointments/images-manual.html +++ b/app/views/appointments/images-manual.html @@ -18,9 +18,9 @@

{{ pageHeading }}

{% set mammogramSource = appointment.mammogramDataTemp or appointment.mammogramData %} - {# Determine whether a different mammographer was nominated to take the images #} + {# Operator defaults to the logged in user, unless an alternative mammographer has been nominated #} {% set currentUserName = data.currentUser.firstName + " " + data.currentUser.lastName %} - {% set operatorChanged = appointment.operator and appointment.operator != currentUserName %} + {% set operatorName = appointment.operator or currentUserName %} {# Determine current room name #} {% set currentRoomName = "" %} @@ -46,12 +46,16 @@

{{ pageHeading }}

{% endif %}

- {% if operatorChanged %} -

- Authorised mammographer: {{ appointment.operator }} - (change) -

- {% endif %} +

+ Authorised mammographer: {{ operatorName }} + ({{ appLink({ text: "change", href: (appointmentUrl + "/change-mammographer?context=take-images&returnPath=take-images") | urlWithReferrer(currentUrl) } | openInModal) | trim | safe }}) +

+ + {{ appHiddenInput({ + id: "irmer-operator-input", + name: "appointment[operator]", + value: operatorName + }) }} {{ appHiddenInput({ name: "appointment[mammogramDataTemp][machineRoom]", diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 5f3b32618..91c304fac 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -9,6 +9,7 @@ {% set hideBackLink = true %} {% set formAction = "./take-images" | getReturnUrl(referrerChain) %} +{% set exitAppointmentUrl = ("/clinics/" + clinicId + "/appointments/" + appointmentId + "/exit-appointment") | urlWithReferrer(referrerChain) %} {# Display format for medical information sections #} {% set displayFormat = "expander" %} @@ -49,6 +50,8 @@ {# Operator defaults to the logged in user, unless an alternative mammographer has been nominated #} {% set operatorName = appointment.operator or (data.currentUser.firstName + " " + data.currentUser.lastName) %} +{% set mammographerNominated = appointment.mammographerNominated %} +{% set changeMammographerHref = (appointmentUrl + "/change-mammographer?context=take-images&returnPath=review-medical-information") | urlWithReferrer(currentUrl) %} {% block pageContent %} @@ -72,10 +75,10 @@

value: operatorName }) }} - {% if requiresAuthorisation and appointment.workflowStatus['review-medical-information'] != 'completed' %} -
+ {% if requiresAuthorisation and appointment.workflowStatus['review-medical-information'] != 'completed' and not mammographerNominated %} +
{{ insetText({ - html: "

Due to " + permissionReason + " you do not have the relevant permissions to take all the mammograms required for this appointment. Change the mammographer to someone authorised to take these images or exit the appointment.

" + html: "

Due to " + permissionReason + " you do not have the relevant permissions to take all the mammograms required for this appointment. Change the mammographer to someone authorised to take these images or exit the appointment.

" }) }}
@@ -83,10 +86,9 @@

{{ button({ text: "Change mammographer", - classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", - attributes: { type: "button" } - }) }} - + href: changeMammographerHref, + classes: "nhsuk-u-margin-bottom-0" + } | openInModal) }}

@@ -98,7 +100,7 @@

classes: "nhsuk-u-margin-bottom-0 nhsuk-button js-complete-all-sections" }) }} {% if requiresAuthorisation %} -

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images (change mammographer)

+

Continuing will authorise {{ operatorName }} to take all appointment images ({{ appLink({ text: "change mammographer", href: changeMammographerHref, classes: "nhsuk-link--no-visited-state" } | openInModal) | trim | safe }})

{% endif %}

@@ -109,10 +111,10 @@

{% include "_includes/medical-information/index.njk" %}
- {% if requiresAuthorisation and appointment.workflowStatus['review-medical-information'] != 'completed' %} -
+ {% if requiresAuthorisation and appointment.workflowStatus['review-medical-information'] != 'completed' and not mammographerNominated %} +
{{ insetText({ - html: "

Due to " + permissionReason + " you do not have the relevant permissions to take all the mammograms required for this appointment. Change the mammographer to someone authorised to take these images or exit the appointment.

" + html: "

Due to " + permissionReason + " you do not have the relevant permissions to take all the mammograms required for this appointment. Change the mammographer to someone authorised to take these images or exit the appointment.

" }) }}
@@ -120,10 +122,9 @@

{{ button({ text: "Change mammographer", - classes: "nhsuk-u-margin-bottom-0 js-irmer-authorise-button js-choose-authoriser-link", - attributes: { type: "button" } - }) }} - + href: changeMammographerHref, + classes: "nhsuk-u-margin-bottom-0" + } | openInModal) }}

@@ -135,7 +136,7 @@

classes: "js-complete-all-sections nhsuk-u-margin-bottom-0" }) }} {% if requiresAuthorisation %} -

Continuing will authorise you ({{ data.currentUser.firstName }} {{ data.currentUser.lastName }}) to take all appointment images (change mammographer)

+

Continuing will authorise {{ operatorName }} to take all appointment images ({{ appLink({ text: "change mammographer", href: changeMammographerHref, classes: "nhsuk-link--no-visited-state" } | openInModal) | trim | safe }})

{% endif %}

@@ -146,126 +147,4 @@

- {# Alternative authoriser modal #} - {% set authoriserModalContent %} - {{ insetText({ - text: "Ensure the person selected is also logged into the mammogram machine before images are taken" - }) }} - {{ radios({ - name: "irmerAuthoriser", - fieldset: { - legend: { - text: "Who is taking images for this appointment?" - } - }, - items: [ - { value: "S. Patel", text: "S. Patel" }, - { value: "R. Thompson", text: "R. Thompson" }, - { value: "L. Okonkwo", text: "L. Okonkwo" }, - { value: "H. Chambers", text: "H. Chambers" }, - { value: "F. Kaur", text: "F. Kaur" }, - { divider: "or" }, - { - value: "Other", - text: "Other", - conditional: { - html: input({ - id: "irmer-authoriser-other", - name: "irmerAuthoriserOther", - label: { - text: "Enter the mammographer's full name" - }, - autocomplete: "off" - }) - } - } - ] - }) }} - {% endset %} - - {{ appModal({ - id: "choose-authoriser-modal", - title: "Change mammographer", - showCloseButton: true, - content: authoriserModalContent, - actions: [ - { - text: "Save", - action: "close", - classes: "js-select-authoriser" - }, - { - text: "Cancel", - element: "link", - action: "close" - }, - { - text: "Appointment cannot proceed", - element: "link", - href: "/clinics/" + clinicId + "/appointments/" + appointmentId + "/attended-not-screened-reason", - action: "close" - } - ] - }) }} - -{% endblock %} - -{% block pageScripts %} - {% endblock %} From 399d07e0335359953f027cdb2cdeb574fecd470b Mon Sep 17 00:00:00 2001 From: rivalee Date: Tue, 15 Sep 2026 15:43:29 +0100 Subject: [PATCH 59/65] make it possible to switch back to me --- .../appointments/change-mammographer.html | 20 ++++++++++++++++--- .../review-medical-information.html | 13 +++++++----- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/app/views/appointments/change-mammographer.html b/app/views/appointments/change-mammographer.html index a55ebe0b3..a9027dd0f 100644 --- a/app/views/appointments/change-mammographer.html +++ b/app/views/appointments/change-mammographer.html @@ -20,14 +20,20 @@

{{ pageHeading }}

}) }} {% endif %} + {# Current user sits at the top, pre-selected unless another mammographer was already nominated #} + {% set currentUserShort = (data.currentUser.firstName | first) + ". " + data.currentUser.lastName %} + {% set selectedOperator = appointment.operator if appointment.mammographerNominated else currentUserShort %} + {{ radios({ name: "irmerAuthoriser", + value: selectedOperator, fieldset: { legend: { text: legendText } }, items: [ + { value: currentUserShort, text: currentUserShort + " (you)" }, { value: "S. Patel", text: "S. Patel" }, { value: "R. Thompson", text: "R. Thompson" }, { value: "L. Okonkwo", text: "L. Okonkwo" }, @@ -51,8 +57,16 @@

{{ pageHeading }}

] }) }} - {{ button({ - text: "Save" - }) }} + {% set returnPageHref = (appointmentUrl + "/" + (query.returnPath or "check-information")) | urlWithReferrer(referrerChain) %} + +
+ {{ button({ + text: "Save" + }) }} + {{ appLink({ text: "Cancel", href: returnPageHref, attributes: { "data-modal-action": "close" } }) | trim | safe }} + {% if not imagesTaken %} + {{ appLink({ text: "Exit appointment", href: (appointmentUrl + "/exit-appointment") | urlWithReferrer(referrerChain) }) | trim | safe }} + {% endif %} +
{% endblock %} diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 91c304fac..d0885c351 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -50,7 +50,10 @@ {# Operator defaults to the logged in user, unless an alternative mammographer has been nominated #} {% set operatorName = appointment.operator or (data.currentUser.firstName + " " + data.currentUser.lastName) %} -{% set mammographerNominated = appointment.mammographerNominated %} +{# The logged-in user lacks implant-imaging permission, so the appointment can only proceed once a different (authorised) mammographer is set #} +{% set currentUserFull = data.currentUser.firstName + " " + data.currentUser.lastName %} +{% set currentUserShort = (data.currentUser.firstName | first) + ". " + data.currentUser.lastName %} +{% set hasAuthorisedMammographer = appointment.operator and appointment.operator != currentUserFull and appointment.operator != currentUserShort %} {% set changeMammographerHref = (appointmentUrl + "/change-mammographer?context=take-images&returnPath=review-medical-information") | urlWithReferrer(currentUrl) %} {% block pageContent %} @@ -75,7 +78,7 @@

value: operatorName }) }} - {% if requiresAuthorisation and appointment.workflowStatus['review-medical-information'] != 'completed' and not mammographerNominated %} + {% if requiresAuthorisation and appointment.workflowStatus['review-medical-information'] != 'completed' and not hasAuthorisedMammographer %}
{{ insetText({ html: "

Due to " + permissionReason + " you do not have the relevant permissions to take all the mammograms required for this appointment. Change the mammographer to someone authorised to take these images or exit the appointment.

" @@ -100,7 +103,7 @@

classes: "nhsuk-u-margin-bottom-0 nhsuk-button js-complete-all-sections" }) }} {% if requiresAuthorisation %} -

Continuing will authorise {{ operatorName }} to take all appointment images ({{ appLink({ text: "change mammographer", href: changeMammographerHref, classes: "nhsuk-link--no-visited-state" } | openInModal) | trim | safe }})

+

Authorised mammographer: {{ operatorName }} ({{ appLink({ text: "change", href: changeMammographerHref } | openInModal) | trim | safe }})

{% endif %}

@@ -111,7 +114,7 @@

{% include "_includes/medical-information/index.njk" %}
- {% if requiresAuthorisation and appointment.workflowStatus['review-medical-information'] != 'completed' and not mammographerNominated %} + {% if requiresAuthorisation and appointment.workflowStatus['review-medical-information'] != 'completed' and not hasAuthorisedMammographer %}
{{ insetText({ html: "

Due to " + permissionReason + " you do not have the relevant permissions to take all the mammograms required for this appointment. Change the mammographer to someone authorised to take these images or exit the appointment.

" @@ -136,7 +139,7 @@

classes: "js-complete-all-sections nhsuk-u-margin-bottom-0" }) }} {% if requiresAuthorisation %} -

Continuing will authorise {{ operatorName }} to take all appointment images ({{ appLink({ text: "change mammographer", href: changeMammographerHref, classes: "nhsuk-link--no-visited-state" } | openInModal) | trim | safe }})

+

Authorised mammographer: {{ operatorName }} ({{ appLink({ text: "change", href: changeMammographerHref } | openInModal) | trim | safe }})

{% endif %}

From 37dc4da93c7a63e5e288e24339a17c078406fb56 Mon Sep 17 00:00:00 2001 From: rivalee Date: Wed, 16 Sep 2026 09:49:35 +0100 Subject: [PATCH 60/65] final tweaks --- app/views/appointments/appointment.html | 2 +- app/views/appointments/images.html | 1 + app/views/appointments/review-medical-information.html | 8 ++------ 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/app/views/appointments/appointment.html b/app/views/appointments/appointment.html index 04c54ae7a..cb46e6dc6 100644 --- a/app/views/appointments/appointment.html +++ b/app/views/appointments/appointment.html @@ -229,7 +229,7 @@ }, { key: { - text: "Screened by" + text: "Run by" }, value: { html: (appointment.sessionDetails.endedBy | getUsername) + diff --git a/app/views/appointments/images.html b/app/views/appointments/images.html index 1318f95d7..1e55d83c2 100644 --- a/app/views/appointments/images.html +++ b/app/views/appointments/images.html @@ -22,6 +22,7 @@ {% endif %} {# Summary of mammogram data #} + {% set operatorLabel = "Authorised mammographer" %} {% set mammogramDetailsHtml %} {% include "_includes/summary-lists/medical-information/mammogram-image-data.njk" %} {% endset %} diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index d0885c351..8ea4ffd73 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -102,9 +102,7 @@

text: "Complete all and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", classes: "nhsuk-u-margin-bottom-0 nhsuk-button js-complete-all-sections" }) }} - {% if requiresAuthorisation %} -

Authorised mammographer: {{ operatorName }} ({{ appLink({ text: "change", href: changeMammographerHref } | openInModal) | trim | safe }})

- {% endif %} +

Authorised mammographer: {{ operatorName }} ({{ appLink({ text: "change", href: changeMammographerHref } | openInModal) | trim | safe }})

{% endif %} @@ -138,9 +136,7 @@

text: "Complete all and continue", classes: "js-complete-all-sections nhsuk-u-margin-bottom-0" }) }} - {% if requiresAuthorisation %} -

Authorised mammographer: {{ operatorName }} ({{ appLink({ text: "change", href: changeMammographerHref } | openInModal) | trim | safe }})

- {% endif %} +

Authorised mammographer: {{ operatorName }} ({{ appLink({ text: "change", href: changeMammographerHref } | openInModal) | trim | safe }})

{% endif %} From d432635201d553ec3dfaa59cd5d19185a142ca0e Mon Sep 17 00:00:00 2001 From: Ed Horsford Date: Wed, 16 Sep 2026 11:56:19 +0100 Subject: [PATCH 61/65] Rework authorised mammographer nomination to use permissions and user ids Gate on canUserScreenAppointment rather than assuming the viewer lacks permission. Nominees come from users with implantImaging and are stored as operatorId (or operatorOtherName for free text), rendered via getUsername. Modal fields live on appointment.operatorTemp, validation added, bespoke returnPath/context params replaced with the referrer chain, and the duplicated block on review-medical-information extracted to an include. --- app/lib/utils/roles-and-permissions.js | 96 +++++++++++++-- app/routes/appointments/lifecycle.js | 86 ++++++++++---- .../_includes/authorised-mammographer.njk | 56 +++++++++ .../mammogram-image-data.njk | 55 +++++---- .../appointments/change-mammographer.html | 91 ++++++++++----- app/views/appointments/check-information.html | 9 +- app/views/appointments/images-automatic.html | 12 +- app/views/appointments/images-manual.html | 13 +-- .../review-medical-information.html | 109 +----------------- 9 files changed, 306 insertions(+), 221 deletions(-) create mode 100644 app/views/_includes/authorised-mammographer.njk diff --git a/app/lib/utils/roles-and-permissions.js b/app/lib/utils/roles-and-permissions.js index 367d5065e..88e7c6562 100644 --- a/app/lib/utils/roles-and-permissions.js +++ b/app/lib/utils/roles-and-permissions.js @@ -1,6 +1,7 @@ // app/lib/utils/roles-and-permissions.js const { isMedicalHistoryItemRemoved } = require('./medical-information') +const { getUsername } = require('../../filters/nunjucks') // Implanted devices that need a mammographer with implant imaging training. // The prototype treats every device type as needing it - narrow this list if @@ -79,17 +80,9 @@ const hasAnyPermission = (user, permissions) => { return permissions.some((permission) => hasPermission(user, permission)) } -/** - * Check whether an appointment needs a user with implant imaging training - * - * Breast implants and implanted medical devices both need it, but only while - * they are still in place - once removed, any clinician can image the - * participant. - * - * @param {object} appointment - Appointment object - * @returns {boolean} Whether implant imaging training is needed - */ -const requiresImplantImaging = (appointment) => { +// Breakdown of what on an appointment needs implant imaging training. Kept +// unexported so it doesn't become a Nunjucks filter. +const getImplantImagingNeeds = (appointment) => { const medicalHistory = appointment?.medicalInformation?.medicalHistory || {} const hasActiveBreastImplants = ( @@ -104,9 +97,86 @@ const requiresImplantImaging = (appointment) => { } ) + return { hasActiveBreastImplants, hasActiveDevice } +} + +/** + * Check whether an appointment needs a user with implant imaging training + * + * Breast implants and implanted medical devices both need it, but only while + * they are still in place - once removed, any clinician can image the + * participant. + * + * @param {object} appointment - Appointment object + * @returns {boolean} Whether implant imaging training is needed + */ +const requiresImplantImaging = (appointment) => { + const { hasActiveBreastImplants, hasActiveDevice } = + getImplantImagingNeeds(appointment) + return hasActiveBreastImplants || hasActiveDevice } +/** + * Describe why an appointment needs a mammographer with implant imaging + * training, for use in page content + * + * @param {object} appointment - Appointment object + * @returns {string} Reason text, or '' when implant imaging is not needed + */ +const getImplantImagingReason = (appointment) => { + const { hasActiveBreastImplants, hasActiveDevice } = + getImplantImagingNeeds(appointment) + + if (hasActiveBreastImplants && hasActiveDevice) { + return 'breast implants and an implanted medical device' + } + if (hasActiveBreastImplants) return 'breast implants' + if (hasActiveDevice) return 'an implanted medical device' + return '' +} + +/** + * Get the clinicians who can image participants with implants or devices + * + * @param {object[]} users - Array of user objects + * @returns {object[]} Clinicians holding the implantImaging permission + */ +const getImplantImagingUsers = (users) => { + if (!Array.isArray(users)) return [] + return users.filter( + (user) => isClinician(user) && hasPermission(user, 'implantImaging') + ) +} + +/** + * Check whether a mammographer other than the current user has been nominated + * to take the images for an appointment + * + * @param {object} appointment - Appointment object + * @returns {boolean} Whether a mammographer has been nominated + */ +const hasNominatedOperator = (appointment) => { + return Boolean(appointment?.operatorId || appointment?.operatorOtherName) +} + +/** + * Get the display name of the mammographer nominated for an appointment + * + * Returns '' when nobody has been nominated, so callers can decide their own + * fallback. + * + * @param {object} appointment - Appointment object + * @param {object} [options] - Display options, as accepted by getUsername + * @returns {string} Nominated mammographer's name, or '' + */ +const getAppointmentOperatorName = function (appointment, options = {}) { + if (appointment?.operatorId) { + return getUsername.call(this, appointment.operatorId, options) + } + return appointment?.operatorOtherName || '' +} + /** * Check whether a user is able to screen a given appointment * @@ -208,6 +278,10 @@ module.exports = { hasPermission, hasAnyPermission, requiresImplantImaging, + getImplantImagingReason, + getImplantImagingUsers, + hasNominatedOperator, + getAppointmentOperatorName, canUserScreenAppointment, isClinician, isAdministrative, diff --git a/app/routes/appointments/lifecycle.js b/app/routes/appointments/lifecycle.js index 8ba764698..fa28d63bf 100644 --- a/app/routes/appointments/lifecycle.js +++ b/app/routes/appointments/lifecycle.js @@ -344,34 +344,74 @@ module.exports = (router) => { (req, res) => { const { clinicId, appointmentId } = req.params const data = req.session.data - const currentUserName = `${data.currentUser.firstName} ${data.currentUser.lastName}` - - const selected = data.irmerAuthoriser - const otherName = (data.irmerAuthoriserOther || '').toString().trim() - const newOperator = - selected === 'Other' && otherName ? otherName : selected - - // The hidden operator field was bound to the appointment before this - // route ran, so it still holds the previous value to compare against - const previousOperator = data.appointment?.operator || currentUserName - - if (newOperator && data.appointment) { - data.appointment.operator = newOperator - // Records that an authorised mammographer has been explicitly chosen, - // so the review step can show "Complete all and continue" - data.appointment.mammographerNominated = true - if (newOperator !== previousOperator) { - req.flash('success', `Authorised mammographer updated to ${newOperator}`) - } + const appointmentUrl = `/clinics/${clinicId}/appointments/${appointmentId}` + + const operatorTemp = data.appointment?.operatorTemp || {} + const selectedUserId = operatorTemp.userId + const otherName = (operatorTemp.otherName || '').toString().trim() + + const errors = [] + if (!selectedUserId) { + errors.push({ + text: 'Select who is taking the images', + name: 'appointment[operatorTemp][userId]', + href: '#operatorUserId' + }) + } else if (selectedUserId === 'other' && !otherName) { + errors.push({ + text: "Enter the mammographer's full name", + name: 'appointment[operatorTemp][otherName]', + href: '#operatorOtherName' + }) + } + + if (errors.length) { + errors.forEach((error) => req.flash('error', error)) + // Back to the modal page - _modal is threaded by the modal middleware, + // the referrer chain has to be carried by hand + return res.redirect( + urlWithReferrer( + `${appointmentUrl}/change-mammographer`, + req.query.referrerChain + ) + ) + } + + const previousOperatorId = data.appointment?.operatorId + const previousOtherName = data.appointment?.operatorOtherName + + if (selectedUserId === 'other') { + data.appointment.operatorId = null + data.appointment.operatorOtherName = otherName + } else { + // Nominating yourself is the same as no nomination at all + data.appointment.operatorId = + selectedUserId === data.currentUser?.id ? null : selectedUserId + data.appointment.operatorOtherName = null } // Clear the transient modal fields so they don't leak into other forms - delete data.irmerAuthoriser - delete data.irmerAuthoriserOther + delete data.appointment.operatorTemp + + const hasChanged = + data.appointment.operatorId !== (previousOperatorId || null) || + data.appointment.operatorOtherName !== (previousOtherName || null) + + if (hasChanged) { + const selectedUser = (data.users || []).find( + (user) => user.id === selectedUserId + ) + const newOperatorName = selectedUser + ? `${selectedUser.firstName} ${selectedUser.lastName}` + : otherName + req.flash( + 'success', + `Authorised mammographer updated to ${newOperatorName}` + ) + } - const returnPath = req.query.returnPath || 'check-information' const returnUrl = getReturnUrl( - `/clinics/${clinicId}/appointments/${appointmentId}/${returnPath}`, + `${appointmentUrl}/check-information`, req.query.referrerChain ) res.redirect(modalBreakout(returnUrl)) diff --git a/app/views/_includes/authorised-mammographer.njk b/app/views/_includes/authorised-mammographer.njk new file mode 100644 index 000000000..ee66ff844 --- /dev/null +++ b/app/views/_includes/authorised-mammographer.njk @@ -0,0 +1,56 @@ +{# app/views/_includes/authorised-mammographer.njk #} + +{# + The authorised mammographer for an in-progress appointment, alongside the + button that continues the workflow. + + Where the current user cannot image this participant themselves and nobody + else has been nominated, the workflow is blocked until a mammographer with + implant imaging training is chosen. + + Set continueButtonText before including. +#} + +{% set changeMammographerHref = (appointmentUrl + "/change-mammographer") | urlWithReferrer(referrerChain | appendReferrer(currentUrl)) %} + +{% set needsNominatedMammographer = appointment.workflowStatus['review-medical-information'] != 'completed' + and not (currentUser | canUserScreenAppointment(appointment)) + and not (appointment | hasNominatedOperator) %} + +{% if needsNominatedMammographer %} + + {% set insetTextHtml %} +

Due to {{ appointment | getImplantImagingReason }} you do not have the relevant permissions to take all the mammograms required for this appointment. Change the mammographer to someone authorised to take these images or {{ appLink({ text: "exit the appointment", href: (appointmentUrl + "/exit-appointment") | urlWithReferrer(referrerChain) }) | trim | safe }}.

+ {% endset %} + + {{ insetText({ + html: insetTextHtml + }) }} + +
+
+
+ {{ button({ + text: "Change mammographer", + href: changeMammographerHref, + classes: "nhsuk-u-margin-bottom-0" + } | openInModal) }} +
+
+
+ +{% else %} + + {% set operatorDisplayName = (appointment | getAppointmentOperatorName) or (currentUser.id | getUsername) %} + +
+
+ {{ button({ + text: continueButtonText, + classes: "nhsuk-u-margin-bottom-0 js-complete-all-sections" + }) }} +

Authorised mammographer: {{ operatorDisplayName }} ({{ appLink({ text: "change", href: changeMammographerHref } | openInModal) | trim | safe }})

+
+
+ +{% endif %} diff --git a/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk b/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk index 0224c5214..b45f9e00b 100644 --- a/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk +++ b/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk @@ -166,32 +166,41 @@ }) %} {% endif %} -{# Authorised mammographer - defaults to the logged in user, unless an alternative mammographer was nominated #} -{% set operatorDisplayName = appointment.operator or (data.currentUser.firstName + " " + data.currentUser.lastName) %} -{% set operatorActions = [] %} -{% if allowEdits and operatorChangeHref %} - {% set operatorActions = [ - ({ - href: operatorChangeHref, - text: "Change", - visuallyHiddenText: "authorised mammographer" - } | openInModal) - ] %} +{# Authorised mammographer - whoever was nominated to take the images, falling + back to whoever ran the appointment #} +{% set operatorDisplayName = appointment | getAppointmentOperatorName %} +{% if not operatorDisplayName %} + {% if appointment.sessionDetails.endedBy %} + {% set operatorDisplayName = appointment.sessionDetails.endedBy | getUsername %} + {% else %} + {% set operatorDisplayName = appointment.sessionDetails.startedBy | getUsername %} + {% endif %} {% endif %} -{% set operatorValueHtml %}{{ operatorDisplayName }}{% endset %} +{% if operatorDisplayName %} + {% set operatorActions = [] %} + {% if allowEdits and operatorChangeHref %} + {% set operatorActions = [ + ({ + href: operatorChangeHref, + text: "Change", + visuallyHiddenText: "authorised mammographer" + } | openInModal) + ] %} + {% endif %} -{% set summaryRows = summaryRows | push({ - key: { - text: operatorLabel | default("Operator") - }, - value: { - html: operatorValueHtml - }, - actions: { - items: operatorActions - } -}) %} + {% set summaryRows = summaryRows | push({ + key: { + text: operatorLabel | default("Operator") + }, + value: { + text: operatorDisplayName + }, + actions: { + items: operatorActions + } + }) %} +{% endif %} {# Combined views taken (showing all view information in one row) #} {# Sort views by side and standard order before displaying #} diff --git a/app/views/appointments/change-mammographer.html b/app/views/appointments/change-mammographer.html index a9027dd0f..0c747d6d6 100644 --- a/app/views/appointments/change-mammographer.html +++ b/app/views/appointments/change-mammographer.html @@ -3,13 +3,16 @@ {% extends parentLayout or '_templates/layout-appointment.html' %} {# Wording differs depending on whether images have already been taken #} -{% set imagesTaken = query.context == 'check-information' %} +{% set imagesTaken = appointment.workflowStatus['take-images'] == 'completed' %} {% set pageHeading = "Change mammographer" %} -{% set formAction = ("./change-mammographer-answer?returnPath=" + (query.returnPath or 'check-information')) | urlWithReferrer(referrerChain) %} +{% set formAction = "./change-mammographer-answer" | urlWithReferrer(referrerChain) %} {% set legendText = "Who took the images for this appointment?" if imagesTaken else "Who is taking images for this appointment?" %} +{# Where Save and Cancel return to - the page the modal was opened from #} +{% set returnPageHref = (appointmentUrl + "/check-information") | getReturnUrl(referrerChain) %} + {% block pageContent %}

{{ pageHeading }}

@@ -20,44 +23,68 @@

{{ pageHeading }}

}) }} {% endif %} - {# Current user sits at the top, pre-selected unless another mammographer was already nominated #} - {% set currentUserShort = (data.currentUser.firstName | first) + ". " + data.currentUser.lastName %} - {% set selectedOperator = appointment.operator if appointment.mammographerNominated else currentUserShort %} + {# Current user sits at the top, followed by the other mammographers trained + to image participants with implants #} + {% set operatorItems = [ + { + value: data.currentUser.id, + text: data.currentUser.id | getUsername({ format: "short", identifyCurrentUser: true }) + } + ] %} + + {% for user in data.users | getImplantImagingUsers %} + {% if user.id != data.currentUser.id %} + {% set operatorItems = operatorItems | push({ + value: user.id, + text: user.id | getUsername({ format: "short" }) + }) %} + {% endif %} + {% endfor %} + + {% set otherNameHtml %} + {{ input({ + id: "operatorOtherName", + name: "appointment[operatorTemp][otherName]", + value: appointment.operatorTemp.otherName or appointment.operatorOtherName, + label: { + text: "Enter the mammographer's full name" + }, + autocomplete: "off" + } | populateErrors) }} + {% endset %} + + {% set operatorItems = operatorItems | push({ divider: "or" }) %} + {% set operatorItems = operatorItems | push({ + value: "other", + text: "Other", + conditional: { + html: otherNameHtml + } + }) %} + + {# Nominating yourself is stored as no nomination, so default to the current user #} + {% set selectedOperator = data.currentUser.id %} + {% if appointment.operatorOtherName %} + {% set selectedOperator = "other" %} + {% endif %} + {% if appointment.operatorId %} + {% set selectedOperator = appointment.operatorId %} + {% endif %} + {% if appointment.operatorTemp.userId %} + {% set selectedOperator = appointment.operatorTemp.userId %} + {% endif %} {{ radios({ - name: "irmerAuthoriser", + idPrefix: "operatorUserId", + name: "appointment[operatorTemp][userId]", value: selectedOperator, fieldset: { legend: { text: legendText } }, - items: [ - { value: currentUserShort, text: currentUserShort + " (you)" }, - { value: "S. Patel", text: "S. Patel" }, - { value: "R. Thompson", text: "R. Thompson" }, - { value: "L. Okonkwo", text: "L. Okonkwo" }, - { value: "H. Chambers", text: "H. Chambers" }, - { value: "F. Kaur", text: "F. Kaur" }, - { divider: "or" }, - { - value: "Other", - text: "Other", - conditional: { - html: input({ - id: "irmer-authoriser-other", - name: "irmerAuthoriserOther", - label: { - text: "Enter the mammographer's full name" - }, - autocomplete: "off" - }) - } - } - ] - }) }} - - {% set returnPageHref = (appointmentUrl + "/" + (query.returnPath or "check-information")) | urlWithReferrer(referrerChain) %} + items: operatorItems + } | populateErrors) }}
{{ button({ diff --git a/app/views/appointments/check-information.html b/app/views/appointments/check-information.html index ed020cf03..45252c56d 100644 --- a/app/views/appointments/check-information.html +++ b/app/views/appointments/check-information.html @@ -23,8 +23,7 @@ {% set activeTab = 'review' %} {% set showReviewAfterImagingReminder = appointment.workflowStatus['review-breast-features-after-imaging'] == 'yes' %} {% set operatorLabel = "Authorised mammographer" %} -{% set operatorName = appointment.operator or (data.currentUser.firstName + " " + data.currentUser.lastName) %} -{% set operatorChangeHref = (appointmentUrl + "/change-mammographer?context=check-information&returnPath=check-information") | urlWithReferrer(currentUrl) %} +{% set operatorChangeHref = (appointmentUrl + "/change-mammographer") | urlWithReferrer(referrerChain | appendReferrer(currentUrl)) %} @@ -294,12 +293,6 @@

{{ pageHeading }}

{# Finish #} - {{ appHiddenInput({ - id: "irmer-operator-input", - name: "appointment[operator]", - value: operatorName - }) }} - {{ button({ text: "Complete screening and return to clinic" }) }} diff --git a/app/views/appointments/images-automatic.html b/app/views/appointments/images-automatic.html index fa52c6b8e..016b822a1 100644 --- a/app/views/appointments/images-automatic.html +++ b/app/views/appointments/images-automatic.html @@ -51,20 +51,14 @@

{{ pageHeading }}

{# Operator defaults to the logged in user, unless an alternative mammographer has been nominated #} - {% set operatorName = appointment.operator or (data.currentUser.firstName + " " + data.currentUser.lastName) %} - {% set changeMammographerHref = (appointmentUrl + "/change-mammographer?context=take-images&returnPath=take-images") | urlWithReferrer(currentUrl) %} + {% set operatorDisplayName = (appointment | getAppointmentOperatorName) or (currentUser.id | getUsername) %} + {% set changeMammographerHref = (appointmentUrl + "/change-mammographer") | urlWithReferrer(referrerChain | appendReferrer(currentUrl)) %}

- Authorised mammographer: {{ operatorName }} + Authorised mammographer: {{ operatorDisplayName }} ({{ appLink({ text: "change", href: changeMammographerHref } | openInModal) | trim | safe }})

- {{ appHiddenInput({ - id: "irmer-operator-input", - name: "appointment[operator]", - value: operatorName - }) }} -

Observations during mammogram

diff --git a/app/views/appointments/images-manual.html b/app/views/appointments/images-manual.html index ca439e12e..84ccf56fe 100644 --- a/app/views/appointments/images-manual.html +++ b/app/views/appointments/images-manual.html @@ -19,8 +19,7 @@

{{ pageHeading }}

{% set mammogramSource = appointment.mammogramDataTemp or appointment.mammogramData %} {# Operator defaults to the logged in user, unless an alternative mammographer has been nominated #} - {% set currentUserName = data.currentUser.firstName + " " + data.currentUser.lastName %} - {% set operatorName = appointment.operator or currentUserName %} + {% set operatorDisplayName = (appointment | getAppointmentOperatorName) or (currentUser.id | getUsername) %} {# Determine current room name #} {% set currentRoomName = "" %} @@ -47,16 +46,10 @@

{{ pageHeading }}

- Authorised mammographer: {{ operatorName }} - ({{ appLink({ text: "change", href: (appointmentUrl + "/change-mammographer?context=take-images&returnPath=take-images") | urlWithReferrer(currentUrl) } | openInModal) | trim | safe }}) + Authorised mammographer: {{ operatorDisplayName }} + ({{ appLink({ text: "change", href: (appointmentUrl + "/change-mammographer") | urlWithReferrer(referrerChain | appendReferrer(currentUrl)) } | openInModal) | trim | safe }})

- {{ appHiddenInput({ - id: "irmer-operator-input", - name: "appointment[operator]", - value: operatorName - }) }} - {{ appHiddenInput({ name: "appointment[mammogramDataTemp][machineRoom]", value: currentRoomName diff --git a/app/views/appointments/review-medical-information.html b/app/views/appointments/review-medical-information.html index 8ea4ffd73..e49d75f16 100644 --- a/app/views/appointments/review-medical-information.html +++ b/app/views/appointments/review-medical-information.html @@ -9,53 +9,10 @@ {% set hideBackLink = true %} {% set formAction = "./take-images" | getReturnUrl(referrerChain) %} -{% set exitAppointmentUrl = ("/clinics/" + clinicId + "/appointments/" + appointmentId + "/exit-appointment") | urlWithReferrer(referrerChain) %} {# Display format for medical information sections #} {% set displayFormat = "expander" %} -{# Check if participant has active (non-removed) breast implants #} -{% set hasActiveImplants = false %} -{% set implantRecords = appointment.medicalInformation.medicalHistory.breastImplantsAugmentation %} -{% if implantRecords | length %} - {% for implant in implantRecords %} - {% if not (implant | isMedicalHistoryItemRemoved) %} - {% set hasActiveImplants = true %} - {% endif %} - {% endfor %} -{% endif %} - -{# Check if participant has active (non-removed) implanted medical devices #} -{% set hasActiveMedicalDevices = false %} -{% set medicalDeviceRecords = appointment.medicalInformation.medicalHistory.implantedMedicalDevice %} -{% if medicalDeviceRecords | length %} - {% for device in medicalDeviceRecords %} - {% if not (device | isMedicalHistoryItemRemoved) %} - {% set hasActiveMedicalDevices = true %} - {% endif %} - {% endfor %} -{% endif %} - -{% set permissionReason = "" %} -{% if hasActiveImplants and hasActiveMedicalDevices %} - {% set permissionReason = "breast implants and an implanted medical device" %} -{% elseif hasActiveImplants %} - {% set permissionReason = "breast implants" %} -{% elseif hasActiveMedicalDevices %} - {% set permissionReason = "an implanted medical device" %} -{% endif %} - -{# Nominating an alternative mammographer is only relevant when implants or devices require authorisation #} -{% set requiresAuthorisation = hasActiveImplants or hasActiveMedicalDevices %} - -{# Operator defaults to the logged in user, unless an alternative mammographer has been nominated #} -{% set operatorName = appointment.operator or (data.currentUser.firstName + " " + data.currentUser.lastName) %} -{# The logged-in user lacks implant-imaging permission, so the appointment can only proceed once a different (authorised) mammographer is set #} -{% set currentUserFull = data.currentUser.firstName + " " + data.currentUser.lastName %} -{% set currentUserShort = (data.currentUser.firstName | first) + ". " + data.currentUser.lastName %} -{% set hasAuthorisedMammographer = appointment.operator and appointment.operator != currentUserFull and appointment.operator != currentUserShort %} -{% set changeMammographerHref = (appointmentUrl + "/change-mammographer?context=take-images&returnPath=review-medical-information") | urlWithReferrer(currentUrl) %} - {% block pageContent %}

@@ -72,74 +29,16 @@

value: appointment.workflowStatus['review-breast-features-after-imaging'] }) }} - {{ appHiddenInput({ - id: "irmer-operator-input", - name: "appointment[operator]", - value: operatorName - }) }} - - {% if requiresAuthorisation and appointment.workflowStatus['review-medical-information'] != 'completed' and not hasAuthorisedMammographer %} -
- {{ insetText({ - html: "

Due to " + permissionReason + " you do not have the relevant permissions to take all the mammograms required for this appointment. Change the mammographer to someone authorised to take these images or exit the appointment.

" - }) }} -
-
-
-
- {{ button({ - text: "Change mammographer", - href: changeMammographerHref, - classes: "nhsuk-u-margin-bottom-0" - } | openInModal) }} -
-
-
- {% else %} -
-
- {{ button({ - text: "Complete all and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section", - classes: "nhsuk-u-margin-bottom-0 nhsuk-button js-complete-all-sections" - }) }} -

Authorised mammographer: {{ operatorName }} ({{ appLink({ text: "change", href: changeMammographerHref } | openInModal) | trim | safe }})

-
-
- {% endif %} + {% set continueButtonText = "Complete all and continue" if appointment.workflowStatus['review-medical-information'] != 'completed' else "Next section" %} + {% include "_includes/authorised-mammographer.njk" %}
{% include "_includes/medical-information/index.njk" %}
- {% if requiresAuthorisation and appointment.workflowStatus['review-medical-information'] != 'completed' and not hasAuthorisedMammographer %} -
- {{ insetText({ - html: "

Due to " + permissionReason + " you do not have the relevant permissions to take all the mammograms required for this appointment. Change the mammographer to someone authorised to take these images or exit the appointment.

" - }) }} -
-
-
-
- {{ button({ - text: "Change mammographer", - href: changeMammographerHref, - classes: "nhsuk-u-margin-bottom-0" - } | openInModal) }} -
-
-
- {% else %} -
-
- {{ button({ - text: "Complete all and continue", - classes: "js-complete-all-sections nhsuk-u-margin-bottom-0" - }) }} -

Authorised mammographer: {{ operatorName }} ({{ appLink({ text: "change", href: changeMammographerHref } | openInModal) | trim | safe }})

-
-
- {% endif %} + {% set continueButtonText = "Complete all and continue" %} + {% include "_includes/authorised-mammographer.njk" %}
{% include "screening-cannot-proceed-link.njk" %} From a4ac1c596be2f70c6def01bbf34f71908ad02408 Mon Sep 17 00:00:00 2001 From: Ed Horsford Date: Wed, 16 Sep 2026 11:59:09 +0100 Subject: [PATCH 62/65] Sort implant imaging mammographers by surname --- app/lib/utils/roles-and-permissions.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/lib/utils/roles-and-permissions.js b/app/lib/utils/roles-and-permissions.js index 88e7c6562..40d301e20 100644 --- a/app/lib/utils/roles-and-permissions.js +++ b/app/lib/utils/roles-and-permissions.js @@ -137,16 +137,23 @@ const getImplantImagingReason = (appointment) => { } /** - * Get the clinicians who can image participants with implants or devices + * Get the clinicians who can image participants with implants or devices, + * sorted by surname then first name * * @param {object[]} users - Array of user objects * @returns {object[]} Clinicians holding the implantImaging permission */ const getImplantImagingUsers = (users) => { if (!Array.isArray(users)) return [] - return users.filter( - (user) => isClinician(user) && hasPermission(user, 'implantImaging') - ) + return users + .filter( + (user) => isClinician(user) && hasPermission(user, 'implantImaging') + ) + .sort( + (userA, userB) => + userA.lastName.localeCompare(userB.lastName) || + userA.firstName.localeCompare(userB.firstName) + ) } /** From 5b7977aa04e12f3e1981676fb753b25654170602 Mon Sep 17 00:00:00 2001 From: Ed Horsford Date: Wed, 16 Sep 2026 12:06:06 +0100 Subject: [PATCH 63/65] List every clinician able to screen the appointment, and show permissions on choose user --- app/lib/utils/roles-and-permissions.js | 21 ------------------- .../appointments/change-mammographer.html | 9 ++++---- app/views/choose-user.html | 9 ++++++++ 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/app/lib/utils/roles-and-permissions.js b/app/lib/utils/roles-and-permissions.js index 40d301e20..f8a7cbdd8 100644 --- a/app/lib/utils/roles-and-permissions.js +++ b/app/lib/utils/roles-and-permissions.js @@ -136,26 +136,6 @@ const getImplantImagingReason = (appointment) => { return '' } -/** - * Get the clinicians who can image participants with implants or devices, - * sorted by surname then first name - * - * @param {object[]} users - Array of user objects - * @returns {object[]} Clinicians holding the implantImaging permission - */ -const getImplantImagingUsers = (users) => { - if (!Array.isArray(users)) return [] - return users - .filter( - (user) => isClinician(user) && hasPermission(user, 'implantImaging') - ) - .sort( - (userA, userB) => - userA.lastName.localeCompare(userB.lastName) || - userA.firstName.localeCompare(userB.firstName) - ) -} - /** * Check whether a mammographer other than the current user has been nominated * to take the images for an appointment @@ -286,7 +266,6 @@ module.exports = { hasAnyPermission, requiresImplantImaging, getImplantImagingReason, - getImplantImagingUsers, hasNominatedOperator, getAppointmentOperatorName, canUserScreenAppointment, diff --git a/app/views/appointments/change-mammographer.html b/app/views/appointments/change-mammographer.html index 0c747d6d6..642ec45e2 100644 --- a/app/views/appointments/change-mammographer.html +++ b/app/views/appointments/change-mammographer.html @@ -23,8 +23,9 @@

{{ pageHeading }}

}) }} {% endif %} - {# Current user sits at the top, followed by the other mammographers trained - to image participants with implants #} + {# Current user sits at the top, followed by every other clinician able to + screen this appointment - only those with implant imaging training when + the participant has implants or a device #} {% set operatorItems = [ { value: data.currentUser.id, @@ -32,8 +33,8 @@

{{ pageHeading }}

} ] %} - {% for user in data.users | getImplantImagingUsers %} - {% if user.id != data.currentUser.id %} + {% for user in data.users | sort(false, false, "lastName") %} + {% if user.id != data.currentUser.id and (user | canUserScreenAppointment(appointment)) %} {% set operatorItems = operatorItems | push({ value: user.id, text: user.id | getUsername({ format: "short" }) diff --git a/app/views/choose-user.html b/app/views/choose-user.html index 81cee64f5..270b3eb67 100644 --- a/app/views/choose-user.html +++ b/app/views/choose-user.html @@ -32,6 +32,15 @@

{{ role | sentenceCase -}} {% if not loop.last %}, {% endif %} {% endfor %} + {% if user.permissions | length %} +
+ Permissions: + {% for permission in user.permissions %} + {{ permission | kebabCase | formatWords("-") | sentenceCase -}} + {% if not loop.last %}, {% endif %} + {% endfor %} + + {% endif %}

From e4e663a3a85409b9a0de695f45a2c6bfe4ddf807 Mon Sep 17 00:00:00 2001 From: Ed Horsford Date: Wed, 16 Sep 2026 12:30:49 +0100 Subject: [PATCH 64/65] Use authorised mammographer consistently in code, data keys and routes --- app/lib/utils/roles-and-permissions.js | 30 +++++++----- app/routes/appointments/lifecycle.js | 47 +++++++++++-------- .../_includes/authorised-mammographer.njk | 18 +++---- .../mammogram-image-data.njk | 24 +++++----- ...ml => change-authorised-mammographer.html} | 40 ++++++++-------- app/views/appointments/check-information.html | 3 +- app/views/appointments/images-automatic.html | 10 ++-- app/views/appointments/images-manual.html | 8 ++-- app/views/appointments/images.html | 1 - 9 files changed, 95 insertions(+), 86 deletions(-) rename app/views/appointments/{change-mammographer.html => change-authorised-mammographer.html} (64%) diff --git a/app/lib/utils/roles-and-permissions.js b/app/lib/utils/roles-and-permissions.js index f8a7cbdd8..6345af477 100644 --- a/app/lib/utils/roles-and-permissions.js +++ b/app/lib/utils/roles-and-permissions.js @@ -137,31 +137,35 @@ const getImplantImagingReason = (appointment) => { } /** - * Check whether a mammographer other than the current user has been nominated - * to take the images for an appointment + * Check whether an authorised mammographer other than the current user has + * been nominated to take the images for an appointment * * @param {object} appointment - Appointment object - * @returns {boolean} Whether a mammographer has been nominated + * @returns {boolean} Whether an authorised mammographer has been nominated */ -const hasNominatedOperator = (appointment) => { - return Boolean(appointment?.operatorId || appointment?.operatorOtherName) +const hasNominatedAuthorisedMammographer = (appointment) => { + return Boolean( + appointment?.authorisedMammographerId || + appointment?.authorisedMammographerOtherName + ) } /** - * Get the display name of the mammographer nominated for an appointment + * Get the display name of the authorised mammographer nominated for an + * appointment * * Returns '' when nobody has been nominated, so callers can decide their own * fallback. * * @param {object} appointment - Appointment object * @param {object} [options] - Display options, as accepted by getUsername - * @returns {string} Nominated mammographer's name, or '' + * @returns {string} Nominated authorised mammographer's name, or '' */ -const getAppointmentOperatorName = function (appointment, options = {}) { - if (appointment?.operatorId) { - return getUsername.call(this, appointment.operatorId, options) +const getAuthorisedMammographerName = function (appointment, options = {}) { + if (appointment?.authorisedMammographerId) { + return getUsername.call(this, appointment.authorisedMammographerId, options) } - return appointment?.operatorOtherName || '' + return appointment?.authorisedMammographerOtherName || '' } /** @@ -266,8 +270,8 @@ module.exports = { hasAnyPermission, requiresImplantImaging, getImplantImagingReason, - hasNominatedOperator, - getAppointmentOperatorName, + hasNominatedAuthorisedMammographer, + getAuthorisedMammographerName, canUserScreenAppointment, isClinician, isAdministrative, diff --git a/app/routes/appointments/lifecycle.js b/app/routes/appointments/lifecycle.js index fa28d63bf..14520212a 100644 --- a/app/routes/appointments/lifecycle.js +++ b/app/routes/appointments/lifecycle.js @@ -340,28 +340,31 @@ module.exports = (router) => { // Change the authorised mammographer from the in-page modal, then return to // the page it was opened from with a success banner if it actually changed router.post( - '/clinics/:clinicId/appointments/:appointmentId/change-mammographer-answer', + '/clinics/:clinicId/appointments/:appointmentId/change-authorised-mammographer-answer', (req, res) => { const { clinicId, appointmentId } = req.params const data = req.session.data const appointmentUrl = `/clinics/${clinicId}/appointments/${appointmentId}` - const operatorTemp = data.appointment?.operatorTemp || {} - const selectedUserId = operatorTemp.userId - const otherName = (operatorTemp.otherName || '').toString().trim() + const authorisedMammographerTemp = + data.appointment?.authorisedMammographerTemp || {} + const selectedUserId = authorisedMammographerTemp.userId + const otherName = (authorisedMammographerTemp.otherName || '') + .toString() + .trim() const errors = [] if (!selectedUserId) { errors.push({ text: 'Select who is taking the images', - name: 'appointment[operatorTemp][userId]', - href: '#operatorUserId' + name: 'appointment[authorisedMammographerTemp][userId]', + href: '#authorisedMammographerUserId' }) } else if (selectedUserId === 'other' && !otherName) { errors.push({ text: "Enter the mammographer's full name", - name: 'appointment[operatorTemp][otherName]', - href: '#operatorOtherName' + name: 'appointment[authorisedMammographerTemp][otherName]', + href: '#authorisedMammographerOtherName' }) } @@ -371,42 +374,46 @@ module.exports = (router) => { // the referrer chain has to be carried by hand return res.redirect( urlWithReferrer( - `${appointmentUrl}/change-mammographer`, + `${appointmentUrl}/change-authorised-mammographer`, req.query.referrerChain ) ) } - const previousOperatorId = data.appointment?.operatorId - const previousOtherName = data.appointment?.operatorOtherName + const previousAuthorisedMammographerId = + data.appointment?.authorisedMammographerId + const previousAuthorisedMammographerOtherName = + data.appointment?.authorisedMammographerOtherName if (selectedUserId === 'other') { - data.appointment.operatorId = null - data.appointment.operatorOtherName = otherName + data.appointment.authorisedMammographerId = null + data.appointment.authorisedMammographerOtherName = otherName } else { // Nominating yourself is the same as no nomination at all - data.appointment.operatorId = + data.appointment.authorisedMammographerId = selectedUserId === data.currentUser?.id ? null : selectedUserId - data.appointment.operatorOtherName = null + data.appointment.authorisedMammographerOtherName = null } // Clear the transient modal fields so they don't leak into other forms - delete data.appointment.operatorTemp + delete data.appointment.authorisedMammographerTemp const hasChanged = - data.appointment.operatorId !== (previousOperatorId || null) || - data.appointment.operatorOtherName !== (previousOtherName || null) + data.appointment.authorisedMammographerId !== + (previousAuthorisedMammographerId || null) || + data.appointment.authorisedMammographerOtherName !== + (previousAuthorisedMammographerOtherName || null) if (hasChanged) { const selectedUser = (data.users || []).find( (user) => user.id === selectedUserId ) - const newOperatorName = selectedUser + const newAuthorisedMammographerName = selectedUser ? `${selectedUser.firstName} ${selectedUser.lastName}` : otherName req.flash( 'success', - `Authorised mammographer updated to ${newOperatorName}` + `Authorised mammographer updated to ${newAuthorisedMammographerName}` ) } diff --git a/app/views/_includes/authorised-mammographer.njk b/app/views/_includes/authorised-mammographer.njk index ee66ff844..8af28046e 100644 --- a/app/views/_includes/authorised-mammographer.njk +++ b/app/views/_includes/authorised-mammographer.njk @@ -5,19 +5,19 @@ button that continues the workflow. Where the current user cannot image this participant themselves and nobody - else has been nominated, the workflow is blocked until a mammographer with - implant imaging training is chosen. + else has been nominated, the workflow is blocked until an authorised + mammographer with implant imaging training is chosen. Set continueButtonText before including. #} -{% set changeMammographerHref = (appointmentUrl + "/change-mammographer") | urlWithReferrer(referrerChain | appendReferrer(currentUrl)) %} +{% set authorisedMammographerChangeHref = (appointmentUrl + "/change-authorised-mammographer") | urlWithReferrer(referrerChain | appendReferrer(currentUrl)) %} -{% set needsNominatedMammographer = appointment.workflowStatus['review-medical-information'] != 'completed' +{% set needsNominatedAuthorisedMammographer = appointment.workflowStatus['review-medical-information'] != 'completed' and not (currentUser | canUserScreenAppointment(appointment)) - and not (appointment | hasNominatedOperator) %} + and not (appointment | hasNominatedAuthorisedMammographer) %} -{% if needsNominatedMammographer %} +{% if needsNominatedAuthorisedMammographer %} {% set insetTextHtml %}

Due to {{ appointment | getImplantImagingReason }} you do not have the relevant permissions to take all the mammograms required for this appointment. Change the mammographer to someone authorised to take these images or {{ appLink({ text: "exit the appointment", href: (appointmentUrl + "/exit-appointment") | urlWithReferrer(referrerChain) }) | trim | safe }}.

@@ -32,7 +32,7 @@
{{ button({ text: "Change mammographer", - href: changeMammographerHref, + href: authorisedMammographerChangeHref, classes: "nhsuk-u-margin-bottom-0" } | openInModal) }}
@@ -41,7 +41,7 @@ {% else %} - {% set operatorDisplayName = (appointment | getAppointmentOperatorName) or (currentUser.id | getUsername) %} + {% set authorisedMammographerName = (appointment | getAuthorisedMammographerName) or (currentUser.id | getUsername) %}
@@ -49,7 +49,7 @@ text: continueButtonText, classes: "nhsuk-u-margin-bottom-0 js-complete-all-sections" }) }} -

Authorised mammographer: {{ operatorDisplayName }} ({{ appLink({ text: "change", href: changeMammographerHref } | openInModal) | trim | safe }})

+

Authorised mammographer: {{ authorisedMammographerName }} ({{ appLink({ text: "change", href: authorisedMammographerChangeHref } | openInModal) | trim | safe }})

diff --git a/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk b/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk index b45f9e00b..3cfaaab71 100644 --- a/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk +++ b/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk @@ -168,21 +168,21 @@ {# Authorised mammographer - whoever was nominated to take the images, falling back to whoever ran the appointment #} -{% set operatorDisplayName = appointment | getAppointmentOperatorName %} -{% if not operatorDisplayName %} +{% set authorisedMammographerName = appointment | getAuthorisedMammographerName %} +{% if not authorisedMammographerName %} {% if appointment.sessionDetails.endedBy %} - {% set operatorDisplayName = appointment.sessionDetails.endedBy | getUsername %} + {% set authorisedMammographerName = appointment.sessionDetails.endedBy | getUsername %} {% else %} - {% set operatorDisplayName = appointment.sessionDetails.startedBy | getUsername %} + {% set authorisedMammographerName = appointment.sessionDetails.startedBy | getUsername %} {% endif %} {% endif %} -{% if operatorDisplayName %} - {% set operatorActions = [] %} - {% if allowEdits and operatorChangeHref %} - {% set operatorActions = [ +{% if authorisedMammographerName %} + {% set authorisedMammographerActions = [] %} + {% if allowEdits and authorisedMammographerChangeHref %} + {% set authorisedMammographerActions = [ ({ - href: operatorChangeHref, + href: authorisedMammographerChangeHref, text: "Change", visuallyHiddenText: "authorised mammographer" } | openInModal) @@ -191,13 +191,13 @@ {% set summaryRows = summaryRows | push({ key: { - text: operatorLabel | default("Operator") + text: "Authorised mammographer" }, value: { - text: operatorDisplayName + text: authorisedMammographerName }, actions: { - items: operatorActions + items: authorisedMammographerActions } }) %} {% endif %} diff --git a/app/views/appointments/change-mammographer.html b/app/views/appointments/change-authorised-mammographer.html similarity index 64% rename from app/views/appointments/change-mammographer.html rename to app/views/appointments/change-authorised-mammographer.html index 642ec45e2..ed593aca6 100644 --- a/app/views/appointments/change-mammographer.html +++ b/app/views/appointments/change-authorised-mammographer.html @@ -1,4 +1,4 @@ -{# app/views/appointments/change-mammographer.html #} +{# app/views/appointments/change-authorised-mammographer.html #} {% extends parentLayout or '_templates/layout-appointment.html' %} @@ -6,7 +6,7 @@ {% set imagesTaken = appointment.workflowStatus['take-images'] == 'completed' %} {% set pageHeading = "Change mammographer" %} -{% set formAction = "./change-mammographer-answer" | urlWithReferrer(referrerChain) %} +{% set formAction = "./change-authorised-mammographer-answer" | urlWithReferrer(referrerChain) %} {% set legendText = "Who took the images for this appointment?" if imagesTaken else "Who is taking images for this appointment?" %} @@ -26,7 +26,7 @@

{{ pageHeading }}

{# Current user sits at the top, followed by every other clinician able to screen this appointment - only those with implant imaging training when the participant has implants or a device #} - {% set operatorItems = [ + {% set authorisedMammographerItems = [ { value: data.currentUser.id, text: data.currentUser.id | getUsername({ format: "short", identifyCurrentUser: true }) @@ -35,7 +35,7 @@

{{ pageHeading }}

{% for user in data.users | sort(false, false, "lastName") %} {% if user.id != data.currentUser.id and (user | canUserScreenAppointment(appointment)) %} - {% set operatorItems = operatorItems | push({ + {% set authorisedMammographerItems = authorisedMammographerItems | push({ value: user.id, text: user.id | getUsername({ format: "short" }) }) %} @@ -44,9 +44,9 @@

{{ pageHeading }}

{% set otherNameHtml %} {{ input({ - id: "operatorOtherName", - name: "appointment[operatorTemp][otherName]", - value: appointment.operatorTemp.otherName or appointment.operatorOtherName, + id: "authorisedMammographerOtherName", + name: "appointment[authorisedMammographerTemp][otherName]", + value: appointment.authorisedMammographerTemp.otherName or appointment.authorisedMammographerOtherName, label: { text: "Enter the mammographer's full name" }, @@ -54,8 +54,8 @@

{{ pageHeading }}

} | populateErrors) }} {% endset %} - {% set operatorItems = operatorItems | push({ divider: "or" }) %} - {% set operatorItems = operatorItems | push({ + {% set authorisedMammographerItems = authorisedMammographerItems | push({ divider: "or" }) %} + {% set authorisedMammographerItems = authorisedMammographerItems | push({ value: "other", text: "Other", conditional: { @@ -64,27 +64,27 @@

{{ pageHeading }}

}) %} {# Nominating yourself is stored as no nomination, so default to the current user #} - {% set selectedOperator = data.currentUser.id %} - {% if appointment.operatorOtherName %} - {% set selectedOperator = "other" %} + {% set selectedAuthorisedMammographer = data.currentUser.id %} + {% if appointment.authorisedMammographerOtherName %} + {% set selectedAuthorisedMammographer = "other" %} {% endif %} - {% if appointment.operatorId %} - {% set selectedOperator = appointment.operatorId %} + {% if appointment.authorisedMammographerId %} + {% set selectedAuthorisedMammographer = appointment.authorisedMammographerId %} {% endif %} - {% if appointment.operatorTemp.userId %} - {% set selectedOperator = appointment.operatorTemp.userId %} + {% if appointment.authorisedMammographerTemp.userId %} + {% set selectedAuthorisedMammographer = appointment.authorisedMammographerTemp.userId %} {% endif %} {{ radios({ - idPrefix: "operatorUserId", - name: "appointment[operatorTemp][userId]", - value: selectedOperator, + idPrefix: "authorisedMammographerUserId", + name: "appointment[authorisedMammographerTemp][userId]", + value: selectedAuthorisedMammographer, fieldset: { legend: { text: legendText } }, - items: operatorItems + items: authorisedMammographerItems } | populateErrors) }}
diff --git a/app/views/appointments/check-information.html b/app/views/appointments/check-information.html index 45252c56d..9cc3eccc8 100644 --- a/app/views/appointments/check-information.html +++ b/app/views/appointments/check-information.html @@ -22,8 +22,7 @@ {% set activeTab = 'review' %} {% set showReviewAfterImagingReminder = appointment.workflowStatus['review-breast-features-after-imaging'] == 'yes' %} -{% set operatorLabel = "Authorised mammographer" %} -{% set operatorChangeHref = (appointmentUrl + "/change-mammographer") | urlWithReferrer(referrerChain | appendReferrer(currentUrl)) %} +{% set authorisedMammographerChangeHref = (appointmentUrl + "/change-authorised-mammographer") | urlWithReferrer(referrerChain | appendReferrer(currentUrl)) %} diff --git a/app/views/appointments/images-automatic.html b/app/views/appointments/images-automatic.html index 016b822a1..f88767435 100644 --- a/app/views/appointments/images-automatic.html +++ b/app/views/appointments/images-automatic.html @@ -50,13 +50,13 @@

{{ pageHeading }}

- {# Operator defaults to the logged in user, unless an alternative mammographer has been nominated #} - {% set operatorDisplayName = (appointment | getAppointmentOperatorName) or (currentUser.id | getUsername) %} - {% set changeMammographerHref = (appointmentUrl + "/change-mammographer") | urlWithReferrer(referrerChain | appendReferrer(currentUrl)) %} + {# The authorised mammographer defaults to the logged in user, unless an alternative has been nominated #} + {% set authorisedMammographerName = (appointment | getAuthorisedMammographerName) or (currentUser.id | getUsername) %} + {% set authorisedMammographerChangeHref = (appointmentUrl + "/change-authorised-mammographer") | urlWithReferrer(referrerChain | appendReferrer(currentUrl)) %}

- Authorised mammographer: {{ operatorDisplayName }} - ({{ appLink({ text: "change", href: changeMammographerHref } | openInModal) | trim | safe }}) + Authorised mammographer: {{ authorisedMammographerName }} + ({{ appLink({ text: "change", href: authorisedMammographerChangeHref } | openInModal) | trim | safe }})

diff --git a/app/views/appointments/images-manual.html b/app/views/appointments/images-manual.html index 84ccf56fe..27a4544a4 100644 --- a/app/views/appointments/images-manual.html +++ b/app/views/appointments/images-manual.html @@ -18,8 +18,8 @@

{{ pageHeading }}

{% set mammogramSource = appointment.mammogramDataTemp or appointment.mammogramData %} - {# Operator defaults to the logged in user, unless an alternative mammographer has been nominated #} - {% set operatorDisplayName = (appointment | getAppointmentOperatorName) or (currentUser.id | getUsername) %} + {# The authorised mammographer defaults to the logged in user, unless an alternative has been nominated #} + {% set authorisedMammographerName = (appointment | getAuthorisedMammographerName) or (currentUser.id | getUsername) %} {# Determine current room name #} {% set currentRoomName = "" %} @@ -46,8 +46,8 @@

{{ pageHeading }}

- Authorised mammographer: {{ operatorDisplayName }} - ({{ appLink({ text: "change", href: (appointmentUrl + "/change-mammographer") | urlWithReferrer(referrerChain | appendReferrer(currentUrl)) } | openInModal) | trim | safe }}) + Authorised mammographer: {{ authorisedMammographerName }} + ({{ appLink({ text: "change", href: (appointmentUrl + "/change-authorised-mammographer") | urlWithReferrer(referrerChain | appendReferrer(currentUrl)) } | openInModal) | trim | safe }})

{{ appHiddenInput({ diff --git a/app/views/appointments/images.html b/app/views/appointments/images.html index 1e55d83c2..1318f95d7 100644 --- a/app/views/appointments/images.html +++ b/app/views/appointments/images.html @@ -22,7 +22,6 @@ {% endif %} {# Summary of mammogram data #} - {% set operatorLabel = "Authorised mammographer" %} {% set mammogramDetailsHtml %} {% include "_includes/summary-lists/medical-information/mammogram-image-data.njk" %} {% endset %} From 4bd41d0c5ef49e42966272d7d68ec7054cf24094 Mon Sep 17 00:00:00 2001 From: rivalee Date: Wed, 16 Sep 2026 13:38:59 +0100 Subject: [PATCH 65/65] change formatting of name --- app/filters/nunjucks.js | 5 ++++- app/routes/appointments/lifecycle.js | 2 +- app/views/_includes/authorised-mammographer.njk | 2 +- .../medical-information/mammogram-image-data.njk | 6 +++--- app/views/appointments/appointment.html | 15 +++++++++++++-- .../change-authorised-mammographer.html | 4 ++-- app/views/appointments/images-automatic.html | 2 +- app/views/appointments/images-manual.html | 2 +- 8 files changed, 26 insertions(+), 12 deletions(-) diff --git a/app/filters/nunjucks.js b/app/filters/nunjucks.js index 28780fa9b..11b74e2c3 100644 --- a/app/filters/nunjucks.js +++ b/app/filters/nunjucks.js @@ -110,7 +110,7 @@ const getUsername = function (userId, options = {}) { const user = users.find((u) => u.id === userId) if (!user) return userId - // Format options: full (default), short (initial + surname), initial (just initials) + // Format options: full (default), short (initial + surname), initial (just initials), reversed (SURNAME, First) const format = options.format || 'full' let formattedName @@ -121,6 +121,9 @@ const getUsername = function (userId, options = {}) { case 'initial': formattedName = `${user.firstName.charAt(0)}${user.lastName.charAt(0)}` break + case 'reversed': + formattedName = `${user.lastName.toUpperCase()}, ${user.firstName}` + break case 'full': default: formattedName = `${user.firstName} ${user.lastName}` diff --git a/app/routes/appointments/lifecycle.js b/app/routes/appointments/lifecycle.js index 14520212a..dae3a227e 100644 --- a/app/routes/appointments/lifecycle.js +++ b/app/routes/appointments/lifecycle.js @@ -409,7 +409,7 @@ module.exports = (router) => { (user) => user.id === selectedUserId ) const newAuthorisedMammographerName = selectedUser - ? `${selectedUser.firstName} ${selectedUser.lastName}` + ? `${selectedUser.lastName.toUpperCase()}, ${selectedUser.firstName}` : otherName req.flash( 'success', diff --git a/app/views/_includes/authorised-mammographer.njk b/app/views/_includes/authorised-mammographer.njk index 8af28046e..6e2d4b918 100644 --- a/app/views/_includes/authorised-mammographer.njk +++ b/app/views/_includes/authorised-mammographer.njk @@ -41,7 +41,7 @@ {% else %} - {% set authorisedMammographerName = (appointment | getAuthorisedMammographerName) or (currentUser.id | getUsername) %} + {% set authorisedMammographerName = (appointment | getAuthorisedMammographerName({ format: "reversed" })) or (currentUser.id | getUsername({ format: "reversed" })) %}
diff --git a/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk b/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk index 3cfaaab71..15afd0cba 100644 --- a/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk +++ b/app/views/_includes/summary-lists/medical-information/mammogram-image-data.njk @@ -168,12 +168,12 @@ {# Authorised mammographer - whoever was nominated to take the images, falling back to whoever ran the appointment #} -{% set authorisedMammographerName = appointment | getAuthorisedMammographerName %} +{% set authorisedMammographerName = appointment | getAuthorisedMammographerName({ format: "reversed" }) %} {% if not authorisedMammographerName %} {% if appointment.sessionDetails.endedBy %} - {% set authorisedMammographerName = appointment.sessionDetails.endedBy | getUsername %} + {% set authorisedMammographerName = appointment.sessionDetails.endedBy | getUsername({ format: "reversed" }) %} {% else %} - {% set authorisedMammographerName = appointment.sessionDetails.startedBy | getUsername %} + {% set authorisedMammographerName = appointment.sessionDetails.startedBy | getUsername({ format: "reversed" }) %} {% endif %} {% endif %} diff --git a/app/views/appointments/appointment.html b/app/views/appointments/appointment.html index cb46e6dc6..29a39c0df 100644 --- a/app/views/appointments/appointment.html +++ b/app/views/appointments/appointment.html @@ -232,8 +232,8 @@ text: "Run by" }, value: { - html: (appointment.sessionDetails.endedBy | getUsername) + - ((" and " + (appointment.sessionDetails.authors | last).userId | getUsername) + html: (appointment.sessionDetails.endedBy | getUsername({ format: "reversed" })) + + ((" and " + ((appointment.sessionDetails.authors | last).userId | getUsername({ format: "reversed" }))) if appointment.sessionDetails.authors and appointment.sessionDetails.authors.length > 0 and (appointment.sessionDetails.authors | last).userId != appointment.sessionDetails.endedBy else "") @@ -242,6 +242,17 @@ items: [] } } if appointment.status == "complete" and appointment.sessionDetails.endedBy else {}, + { + key: { + text: "Authorised mammographer" + }, + value: { + html: (appointment | getAuthorisedMammographerName({ format: "reversed" })) or (appointment.sessionDetails.endedBy | getUsername({ format: "reversed" })) or (appointment.sessionDetails.startedBy | getUsername({ format: "reversed" })) + }, + actions: { + items: [] + } + } if appointment.status == "complete" and appointment.sessionDetails.endedBy else {}, { key: { text: "Special appointment" diff --git a/app/views/appointments/change-authorised-mammographer.html b/app/views/appointments/change-authorised-mammographer.html index ed593aca6..b3c8b03f0 100644 --- a/app/views/appointments/change-authorised-mammographer.html +++ b/app/views/appointments/change-authorised-mammographer.html @@ -29,7 +29,7 @@

{{ pageHeading }}

{% set authorisedMammographerItems = [ { value: data.currentUser.id, - text: data.currentUser.id | getUsername({ format: "short", identifyCurrentUser: true }) + text: data.currentUser.id | getUsername({ format: "reversed", identifyCurrentUser: true }) } ] %} @@ -37,7 +37,7 @@

{{ pageHeading }}

{% if user.id != data.currentUser.id and (user | canUserScreenAppointment(appointment)) %} {% set authorisedMammographerItems = authorisedMammographerItems | push({ value: user.id, - text: user.id | getUsername({ format: "short" }) + text: user.id | getUsername({ format: "reversed" }) }) %} {% endif %} {% endfor %} diff --git a/app/views/appointments/images-automatic.html b/app/views/appointments/images-automatic.html index f88767435..34f1a4db9 100644 --- a/app/views/appointments/images-automatic.html +++ b/app/views/appointments/images-automatic.html @@ -51,7 +51,7 @@

{{ pageHeading }}

{# The authorised mammographer defaults to the logged in user, unless an alternative has been nominated #} - {% set authorisedMammographerName = (appointment | getAuthorisedMammographerName) or (currentUser.id | getUsername) %} + {% set authorisedMammographerName = (appointment | getAuthorisedMammographerName({ format: "reversed" })) or (currentUser.id | getUsername({ format: "reversed" })) %} {% set authorisedMammographerChangeHref = (appointmentUrl + "/change-authorised-mammographer") | urlWithReferrer(referrerChain | appendReferrer(currentUrl)) %}

diff --git a/app/views/appointments/images-manual.html b/app/views/appointments/images-manual.html index 27a4544a4..37dd76ea0 100644 --- a/app/views/appointments/images-manual.html +++ b/app/views/appointments/images-manual.html @@ -19,7 +19,7 @@

{{ pageHeading }}

{% set mammogramSource = appointment.mammogramDataTemp or appointment.mammogramData %} {# The authorised mammographer defaults to the logged in user, unless an alternative has been nominated #} - {% set authorisedMammographerName = (appointment | getAuthorisedMammographerName) or (currentUser.id | getUsername) %} + {% set authorisedMammographerName = (appointment | getAuthorisedMammographerName({ format: "reversed" })) or (currentUser.id | getUsername({ format: "reversed" })) %} {# Determine current room name #} {% set currentRoomName = "" %}