diff --git a/app/routes/record-vaccinations.js b/app/routes/record-vaccinations.js index 1e20ad7f..5ad39340 100644 --- a/app/routes/record-vaccinations.js +++ b/app/routes/record-vaccinations.js @@ -846,7 +846,7 @@ module.exports = router => { return (expiryDate > dateToday) }) - .filter((batch) => !batch.depletedDate) + .filter((batch) => !batch.deactivatedDate) if (req.query.showError === 'yes') { diff --git a/app/routes/vaccines.js b/app/routes/vaccines.js index 1ed8a0f0..666477df 100644 --- a/app/routes/vaccines.js +++ b/app/routes/vaccines.js @@ -3,6 +3,12 @@ module.exports = (router) => { router.get('/vaccines', (req, res) => { const currentOrganisation = res.locals.currentOrganisation const data = req.session.data + const deactivatedSite = req.query.deactivatedSite === 'true' + const reactivatedSite = req.query.reactivatedSite === 'true' + const siteName = req.query.siteName + const siteTab = (req.query.siteTab || 'active').toLowerCase() + const validSiteTabs = ['active', 'deactivated'] + const currentSiteTab = validSiteTabs.includes(siteTab) ? siteTab : 'active' const organisationVaccines = res.locals.currentOrganisation.vaccines || [] @@ -18,10 +24,20 @@ module.exports = (router) => { .map((vaccine) => vaccine.name) const vaccineStock = data.vaccineStock.filter((vaccine) => vaccine.organisationId === currentOrganisation.id) + const siteIdsInUse = [...new Set(vaccineStock.map((vaccine) => vaccine.siteId))] + const sitesInUse = (currentOrganisation.sites || []).filter((site) => siteIdsInUse.includes(site.id)) + const activeSitesCount = sitesInUse.filter((site) => site.status !== 'closed').length + const deactivatedSitesCount = sitesInUse.filter((site) => site.status === 'closed').length res.render('vaccines/index', { vaccineStock, - vaccinesThatCanBeRequested + vaccinesThatCanBeRequested, + deactivatedSite, + reactivatedSite, + siteName, + currentSiteTab, + activeSitesCount, + deactivatedSitesCount }) }) @@ -140,7 +156,6 @@ module.exports = (router) => { // Viewing check answers page router.get('/vaccines/check', (req, res) => { - const data = req.session.data const siteId = data.siteId @@ -153,6 +168,50 @@ module.exports = (router) => { }) }) + // View page to deactivate a site + router.get('/vaccines/sites/:siteId/deactivate', (req, res) => { + const currentOrganisationSites = res.locals.currentOrganisation.sites || [] + const site = currentOrganisationSites.find((item) => item.id === req.params.siteId) + if (!site) { res.redirect('/vaccines'); return } + + res.render('vaccines/deactivate-site', { + site + }) + }) + + // Mark site as closed + router.post('/vaccines/sites/:siteId/deactivated', (req, res) => { + const currentOrganisationSites = res.locals.currentOrganisation.sites || [] + const site = currentOrganisationSites.find((item) => item.id === req.params.siteId) + if (!site) { res.redirect('/vaccines'); return } + + site.status = 'closed' + + res.redirect(`/vaccines?siteTab=deactivated&deactivatedSite=true&siteName=${encodeURIComponent(site.name)}`) + }) + + // View page to reactivate a site + router.get('/vaccines/sites/:siteId/reactivate', (req, res) => { + const currentOrganisationSites = res.locals.currentOrganisation.sites || [] + const site = currentOrganisationSites.find((item) => item.id === req.params.siteId) + if (!site) { res.redirect('/vaccines'); return } + + res.render('vaccines/reactivate-site', { + site + }) + }) + + // Mark site as active + router.post('/vaccines/sites/:siteId/reactivated', (req, res) => { + const currentOrganisationSites = res.locals.currentOrganisation.sites || [] + const site = currentOrganisationSites.find((item) => item.id === req.params.siteId) + if (!site) { res.redirect('/vaccines'); return } + + delete site.status + + res.redirect(`/vaccines?siteTab=active&reactivatedSite=true&siteName=${encodeURIComponent(site.name)}`) + }) + // Adding a batch to an existing vaccine at a site router.post('/vaccines/:id/added', (req, res) => { @@ -164,6 +223,15 @@ module.exports = (router) => { const expiryDate = new Date(data.batchExpiryDate.year, (parseInt(data.batchExpiryDate.month) - 1), data.batchExpiryDate.day, 12).toISOString().substring(0,10) + const matchingDeactivatedBatch = vaccine.batches.find((batch) => { + return batch.batchNumber === data.batchNumber && batch.expiryDate === expiryDate && batch.deactivatedDate + }) + + if (matchingDeactivatedBatch) { + res.redirect(`/vaccines/${vaccine.id}/add-batch-check`) + return + } + vaccine.batches.push({ id: generatedId, batchNumber: data.batchNumber, @@ -184,7 +252,11 @@ module.exports = (router) => { router.get('/vaccines/:id', (req, res) => { const data = req.session.data const perPage = 20; // Max number of users to show per page - const page = parseInt(req.query.page) || 1 ; // Current page, default to 1 + const queryPage = parseInt(req.query.page) || 1 ; // Current page, default to 1 + const tab = (req.query.tab || 'active').toLowerCase() + const deactivated = req.query.deactivated === 'true' + const reactivated = req.query.reactivated === 'true' + const updatedBatchNumber = req.query.batchNumber const vaccine = data.vaccineStock.find((vaccine) => vaccine.id === req.params.id) if (!vaccine) { res.redirect('/vaccines'); return } @@ -194,6 +266,8 @@ module.exports = (router) => { const site = currentOrganisation.sites.find((site) => site.id === vaccine.siteId) const today = new Date().toISOString().substring(0,10) + const validTabs = ['active', 'inactive'] + const currentTab = validTabs.includes(tab) ? tab : 'active' const allBatches = vaccine.batches.sort((a, b) => { const expiryA = a.expiryDate @@ -207,11 +281,21 @@ module.exports = (router) => { return 0; }) - const totalBatches = allBatches.length - const indexStartFrom = (page - 1) * perPage - const totalPages = Math.ceil(totalBatches / perPage) + const isActiveBatch = (batch) => { + return !batch.deactivatedDate && batch.expiryDate >= today + } - const batches = allBatches.slice(indexStartFrom, indexStartFrom + perPage) + const batchesByStatus = { + active: allBatches.filter((batch) => isActiveBatch(batch)), + inactive: allBatches.filter((batch) => !isActiveBatch(batch)) + } + + const filteredBatches = batchesByStatus[currentTab] + const totalBatches = filteredBatches.length + const totalPages = Math.ceil(totalBatches / perPage) + const page = totalPages > 0 ? Math.min(queryPage, totalPages) : 1 + const indexStartFrom = (page - 1) * perPage + const batches = filteredBatches.slice(indexStartFrom, indexStartFrom + perPage) res.render('vaccines/product-page', { @@ -221,7 +305,13 @@ module.exports = (router) => { today, totalPages, totalBatches, - page + page, + currentTab, + activeBatchesCount: batchesByStatus.active.length, + inactiveBatchesCount: batchesByStatus.inactive.length, + deactivated, + reactivated, + updatedBatchNumber }) }) @@ -251,9 +341,21 @@ module.exports = (router) => { const site = currentOrganisationSites.find((site) => site.id == vaccine.siteId) + let matchingDeactivatedBatch + const { day, month, year } = data.batchExpiryDate || {} + + if (data.batchNumber && day && month && year) { + const expiryDate = new Date(year, (parseInt(month) - 1), day, 12).toISOString().substring(0,10) + + matchingDeactivatedBatch = vaccine.batches.find((batch) => { + return batch.batchNumber === data.batchNumber && batch.expiryDate === expiryDate && batch.deactivatedDate + }) + } + res.render('vaccines/add-batch-to-site-check', { vaccine, site, + matchingDeactivatedBatch, }) }) @@ -301,8 +403,8 @@ module.exports = (router) => { res.redirect('/vaccines/' + vaccine.id) }) - // View page to deplete a batch - router.get('/vaccines/:vaccineId/:batchNumber/deplete', (req, res) => { + // View page to deactivate a batch + router.get('/vaccines/:vaccineId/:batchNumber/deactivate', (req, res) => { const data = req.session.data const vaccine = data.vaccineStock.find((vaccine) => vaccine.id === req.params.vaccineId) if (!vaccine) { res.redirect('/vaccines'); return } @@ -313,25 +415,25 @@ module.exports = (router) => { const batch = vaccine.batches.find((batch) => batch.batchNumber === req.params.batchNumber) if (!batch) { res.redirect(`/vaccines/${vaccine.id}`); return } - res.render('vaccines/deplete-batch', { + res.render('vaccines/deactivate-batch', { vaccine, site, batch }) }) - // Mark batch as depleted - router.post('/vaccines/:vaccineId/:batchNumber/depleted', (req, res) => { + // Mark batch as deactivated + router.post('/vaccines/:vaccineId/:batchNumber/deactivated', (req, res) => { const data = req.session.data const vaccine = data.vaccineStock.find((vaccine) => vaccine.id === req.params.vaccineId) if (!vaccine) { res.redirect('/vaccines'); return } const batch = vaccine.batches.find((batch) => batch.batchNumber === req.params.batchNumber) - let depletedDate = new Date() + let deactivatedDate = new Date() - batch.depletedDate = depletedDate.toISOString().substring(0,10) + batch.deactivatedDate = deactivatedDate.toISOString().substring(0,10) - res.redirect('/vaccines/' + vaccine.id) + res.redirect(`/vaccines/${vaccine.id}?tab=inactive&deactivated=true&batchNumber=${encodeURIComponent(batch.batchNumber)}`) }) @@ -362,8 +464,8 @@ module.exports = (router) => { if (!vaccine) { res.redirect('/vaccines'); return } const batch = vaccine.batches.find((batch) => batch.batchNumber === req.params.batchNumber) - batch.depletedDate = null + batch.deactivatedDate = null - res.redirect('/vaccines/' + vaccine.id) + res.redirect(`/vaccines/${vaccine.id}?tab=active&reactivated=true&batchNumber=${encodeURIComponent(batch.batchNumber)}`) }) } diff --git a/app/views/vaccines/add-batch-to-site-check.html b/app/views/vaccines/add-batch-to-site-check.html index 0d0650d4..410d9868 100644 --- a/app/views/vaccines/add-batch-to-site-check.html +++ b/app/views/vaccines/add-batch-to-site-check.html @@ -18,6 +18,18 @@

Check and confirm

+ {% if matchingDeactivatedBatch %} + {% set html %} +

Batch already exists

+

Batch {{ matchingDeactivatedBatch.batchNumber }} with expiry date {{ matchingDeactivatedBatch.expiryDate | govukDate }} already exists and is deactivated.

+

You can reactivate this batch instead of adding it again.

+ {% endset %} + + {{ notificationBanner({ + html: html + }) }} + {% endif %} + {{ summaryList({ classes: "nhsuk-u-margin-bottom-2", @@ -101,15 +113,26 @@

Check and confirm

] }) }} -

Edit batch

+ {% if not matchingDeactivatedBatch %} +

Edit batch

+ {% endif %} -
- {{ button({ - "text": "Confirm" - }) }} -
+ {% if matchingDeactivatedBatch %} +
+ {{ button({ + text: "Reactivate batch", + classes: "nhsuk-u-margin-top-6" + }) }} +
+ {% else %} +
+ {{ button({ + "text": "Confirm" + }) }} +
+ {% endif %} diff --git a/app/views/vaccines/deplete-batch.html b/app/views/vaccines/deactivate-batch.html similarity index 78% rename from app/views/vaccines/deplete-batch.html rename to app/views/vaccines/deactivate-batch.html index 869f1021..eaf55e67 100644 --- a/app/views/vaccines/deplete-batch.html +++ b/app/views/vaccines/deactivate-batch.html @@ -1,6 +1,6 @@ {% extends 'layout.html' %} -{% set pageName = "Deplete batch" %} +{% set pageName = "Deactivate batch" %} {% set currentSection = "vaccines" %} @@ -34,13 +34,13 @@

{{ pageName }}

} ] }) }} -

Depleted batches will be made unavailable.

+

Deactivated batches will be made unavailable immediately. Batches can be reactivated if needed.

-
+ {{ button({ - "text": "Confirm deplete batch" + "text": "Confirm deactivate batch" }) }}
diff --git a/app/views/vaccines/deactivate-site.html b/app/views/vaccines/deactivate-site.html new file mode 100644 index 00000000..96ab6fb3 --- /dev/null +++ b/app/views/vaccines/deactivate-site.html @@ -0,0 +1,28 @@ +{% extends 'layout.html' %} + +{% set pageName = "Deactivate " + site.name %} + +{% set currentSection = "vaccines" %} + +{% block beforeContent %} + {{ backLink({ href: "/vaccines", text: "Back" }) }} +{% endblock %} + +{% block content %} +
+
+ +

{{ pageName }}

+ +

Are you sure you want to deactivate this site?

+ +
+ {{ button({ + "text": "Confirm deactivate site", + "classes": "nhsuk-button--warning" + }) }} +
+ +
+
+{% endblock %} diff --git a/app/views/vaccines/index.html b/app/views/vaccines/index.html index 86fcbfdb..3a823f8c 100644 --- a/app/views/vaccines/index.html +++ b/app/views/vaccines/index.html @@ -11,12 +11,54 @@ {% endif %} {% endfor %} +{% set filteredSites = [] %} +{% for siteId in sites %} + {% set thisSite = currentOrganisation.sites | findById(siteId) %} + {% if currentSiteTab == "deactivated" %} + {% if thisSite.status == "closed" %} + {% set filteredSites = (filteredSites.push(siteId), filteredSites) %} + {% endif %} + {% else %} + {% if thisSite.status != "closed" %} + {% set filteredSites = (filteredSites.push(siteId), filteredSites) %} + {% endif %} + {% endif %} +{% endfor %} + {% block content %}
{% include "includes/notification.html" %} + {% if deactivatedSite %} + {% set html %} +

+ Site deactivated +

+

{{ (siteName + " has been deactivated.") if siteName else "Site has been deactivated." }}

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

+ Site reactivated +

+

{{ (siteName + " has been reactivated.") if siteName else "Site has been reactivated." }}

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

Vaccines

@@ -47,14 +89,29 @@

Vaccines

{% from 'select/macro.njk' import select %} - {% if (sites | length) > 4 %} +
+ {{ appSecondaryNavigation({ + visuallyHiddenTitle: "Sites by status", + items: [{ + text: "Active (" + activeSitesCount + ")", + href: "/vaccines?siteTab=active", + current: (currentSiteTab == "active") + }, { + text: "Deactivated (" + deactivatedSitesCount + ")", + href: "/vaccines?siteTab=deactivated", + current: (currentSiteTab == "deactivated") + }] + }) }} +
+ + {% if (filteredSites | length) > 4 %} {% set filterItems = [{ text: "All sites", value: "", selected: true }] %} - {% for site in sites %} + {% for site in filteredSites %} {% set filterItems = (filterItems.push({text: currentOrganisation.sites[site].name, value: site}), filterItems) %} {% endfor %} @@ -74,11 +131,15 @@

Vaccines

- {% for siteId in sites %} + {% if (filteredSites | length) == 0 %} +

No {{ currentSiteTab }} sites found.

+ {% endif %} + + {% for siteId in filteredSites %} {% set site = currentOrganisation.sites | findById(siteId) %} - +
{{ site.name }} {% if site.status == "closed" %} (closed) @@ -152,6 +213,14 @@

Vaccines

{% endfor %}
+ + {% if site.status != "closed" %} +
+

No longer using this site? Deactivate {{ site.name }}

+ {% else %} +
+

Using this site again? Reactivate {{ site.name }}

+ {% endif %} {% endfor %} diff --git a/app/views/vaccines/product-page.html b/app/views/vaccines/product-page.html index ad970ffe..118fd69a 100644 --- a/app/views/vaccines/product-page.html +++ b/app/views/vaccines/product-page.html @@ -16,6 +16,34 @@
+ {% if deactivated %} + {% set html %} +

+ Batch deactivated +

+

{{ ("Batch " + updatedBatchNumber + " has been deactivated.") if updatedBatchNumber else "Batch has been deactivated." }}

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

+ Batch reactivated +

+

{{ ("Batch " + updatedBatchNumber + " has been reactivated.") if updatedBatchNumber else "Batch has been reactivated." }}

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

{{ vaccine.vaccineProduct }}

{{ summaryList({ @@ -60,9 +88,37 @@

{{ vaccine.vaccineProduct }}

+

Batches

+ + {% set activeCount = activeBatchesCount | default(0) %} + {% set inactiveCount = inactiveBatchesCount | default(0) %} + + {{ appSecondaryNavigation({ + visuallyHiddenTitle: "Batches by status", + items: [{ + text: "Active (" + activeCount + ")", + href: "/vaccines/" + vaccine.id + "?tab=active", + current: (currentTab == "active") + }, { + text: "Inactive (" + inactiveCount + ")", + href: "/vaccines/" + vaccine.id + "?tab=inactive", + current: (currentTab == "inactive") + }] + }) }} - - + {% set columns = 2 %} + {% if currentTab == "inactive" %} + {% set columns = columns + 2 %} + {% endif %} + {% if vaccine.vaccineProduct == "Spikevax JN.1" %} + {% set columns = columns + 1 %} + {% endif %} + {% if site.status != "closed" %} + {% set columns = columns + 1 %} + {% endif %} + + +
Batches
- - - - + {% if currentTab == "inactive" %} + + + {% endif %} {% if site.status != "closed" %} @@ -93,48 +148,62 @@

{{ vaccine.vaccineProduct }}

- {% for batch in batches %} + {% if (batches | length) == 0 %} - - {% if vaccine.vaccineProduct == "Spikevax JN.1" %} + + {% else %} + {% for batch in batches %} + - {% endif %} - - - {% endif %} - - {% if site.status != "closed" %} - - {% endif %} + {% if currentTab == "inactive" %} + + + {% endif %} + {% if site.status != "closed" %} + + {% endif %} - - {% endfor %} + + {% endfor %} + {% endif %}
@@ -76,15 +132,14 @@

{{ vaccine.vaccineProduct }}

Expiry date - Status - - Depleted date - - + Date deactivated + + Reason deactivated +
- {{ batch.batchNumber }} + + No {{ currentTab }} batches found.
- {{ batch.packType }} + {{ batch.batchNumber }} - {{ batch.expiryDate | govukDate }} - - {% if batch.depletedDate %} - {{ tag({text: "Depleted", classes: "nhsuk-tag--grey" }) }} - {% elif (batch.expiryDate | daysAgo) > 0 %} - {{ tag({text: "Expired", classes: "nhsuk-tag--grey" }) }} - {% else %} - {{ tag({text: "Active" }) }} - {% endif %} - - {% if batch.depletedDate %} - {{ batch.depletedDate | govukDate }} + {% if vaccine.vaccineProduct == "Spikevax JN.1" %} + + {{ batch.packType }} + {% if (batch.expiryDate | daysAgo) > 0 %} {% else %} - Edit{{ batch.batchNumber }} -   - {% if batch.depletedDate %} - Reactivate{{ batch.batchNumber }} - {% else %} - Deplete{{ batch.batchNumber }} - {% endif %} - {% endif %} + + {{ batch.expiryDate | govukDate }} + {% if batch.deactivatedDate %} + {{ batch.deactivatedDate | govukDate }} + {% else %} + {{ batch.expiryDate | govukDate }} + {% endif %} + + {% if batch.deactivatedDate %} + Deactivated by user + {% elif (batch.expiryDate | daysAgo) > 0 %} + Expired + {% endif %} + + {% if currentTab == "inactive" %} + {% if batch.deactivatedDate %} + Reactivate{{ batch.batchNumber }} + {% endif %} + {% else %} + {% if (batch.expiryDate | daysAgo) > 0 %} + {% else %} + Edit{{ batch.batchNumber }} +   + Deactivate{{ batch.batchNumber }} + {% endif %} + {% endif %} +
@@ -143,19 +212,19 @@

{{ vaccine.vaccineProduct }}

{% for i in range(1, totalPages + 1) -%} {% set items = (items.push({ number: i, - href: "/vaccines/" + vaccine.id + "?page=" + i, + href: "/vaccines/" + vaccine.id + "?tab=" + currentTab + "&page=" + i, current: (i === page) }), items) %} {%- endfor %} - {% if totalPages > 0 %} + {% if totalBatches > 20 %} {{ pagination({ previous: { - href: "/vaccines/" + vaccine.id + "?page=" + (page - 1) if page != 1 + href: "/vaccines/" + vaccine.id + "?tab=" + currentTab + "&page=" + (page - 1) if page != 1 }, next: { - href: "/vaccines/" + vaccine.id + "?page=" + (page + 1) if page != totalPages + href: "/vaccines/" + vaccine.id + "?tab=" + currentTab + "&page=" + (page + 1) if page != totalPages }, items: items }) }} diff --git a/app/views/vaccines/reactivate-site.html b/app/views/vaccines/reactivate-site.html new file mode 100644 index 00000000..7b9e0607 --- /dev/null +++ b/app/views/vaccines/reactivate-site.html @@ -0,0 +1,27 @@ +{% extends 'layout.html' %} + +{% set pageName = "Reactivate " + site.name %} + +{% set currentSection = "vaccines" %} + +{% block beforeContent %} + {{ backLink({ href: "/vaccines?siteTab=deactivated", text: "Back" }) }} +{% endblock %} + +{% block content %} +
+
+ +

{{ pageName }}

+ +

Are you sure you want to reactivate this site?

+ +
+ {{ button({ + "text": "Confirm reactivate site" + }) }} +
+ +
+
+{% endblock %} diff --git a/app/views/vaccines/reactivate.html b/app/views/vaccines/reactivate.html index 72c54fdc..507b7737 100644 --- a/app/views/vaccines/reactivate.html +++ b/app/views/vaccines/reactivate.html @@ -1,6 +1,6 @@ {% extends 'layout.html' %} -{% set pageName = "Reactivate batch " + batch.batchNumber %} +{% set pageName = "Reactivate batch" %} {% set currentSection = "vaccines" %} @@ -14,12 +14,33 @@

{{ pageName }}

-

This batch will become ‘active’ immediately.

+ {{ summaryList({ + rows: [ + { + key: { + text: "Batch number" + }, + value: { + text: batch.batchNumber + } + }, + { + key: { + text: "Expiry date" + }, + value: { + text: batch.expiryDate | govukDate + } + } + ] + }) }} + +

Reactivated batches will become available immediately.

{{ button({ - text: "Reactivate" + text: "Confirm reactivate batch" })}}