diff --git a/app/locals.js b/app/locals.js index 8a0697d7..b3388b0d 100644 --- a/app/locals.js +++ b/app/locals.js @@ -1,5 +1,18 @@ module.exports = function(req, res, next) { + const buildSwitchedPharmacyOrganisationSetting = (sessionData, currentOrganisationId) => { + if (!sessionData.previousOrganisationId || !currentOrganisationId) { + return null + } + + return { + id: currentOrganisationId, + status: 'Active', + permissionLevel: 'Lead administrator', + vaccinator: false + } + } + const monthNames = [ 'January', 'February', @@ -25,7 +38,24 @@ module.exports = function(req, res, next) { // Set currentUser for convenience if (req.session.data.currentUserId) { - res.locals.currentUser = req.session.data.users.find((user) => user.id === req.session.data.currentUserId) + const sessionUser = req.session.data.users.find((user) => user.id === req.session.data.currentUserId) + const switchedOrganisationSetting = buildSwitchedPharmacyOrganisationSetting( + req.session.data, + req.session.data.currentOrganisationId + ) + + if (sessionUser && switchedOrganisationSetting) { + const userOrganisations = (sessionUser.organisations || []) + .filter((organisation) => organisation.id !== switchedOrganisationSetting.id) + .map((organisation) => ({ ...organisation })) + + res.locals.currentUser = { + ...sessionUser, + organisations: [...userOrganisations, switchedOrganisationSetting] + } + } else { + res.locals.currentUser = sessionUser + } } else { res.locals.currentUser = null } diff --git a/app/routes/auth.js b/app/routes/auth.js index cb3fb725..d3fd4045 100644 --- a/app/routes/auth.js +++ b/app/routes/auth.js @@ -1,5 +1,13 @@ module.exports = router => { + const asArray = (value) => { + if (value === undefined || value === null) { + return [] + } + + return Array.isArray(value) ? value : [value] + } + const getDefaultLandingPath = (data, organisationId) => { const organisation = (data.organisations || []).find((item) => item.id === organisationId) @@ -22,6 +30,114 @@ module.exports = router => { return '/home' } + const getCurrentUserFromSessionData = (data) => { + if (!data) { + return null + } + + if (data.currentUserId) { + return (data.users || []).find((user) => user.id === data.currentUserId) || null + } + + if (data.email) { + return (data.users || []).find((user) => user.email === data.email) || null + } + + return null + } + + router.get('/auth/restore-organisation', (req, res) => { + const data = req.session.data + const previousOrganisationId = data.previousOrganisationId + const previousOrganisation = (data.organisations || []).find((organisation) => organisation.id === previousOrganisationId) + + if (!previousOrganisationId) { + return res.redirect('/auth/select-organisation') + } + + data.currentOrganisationId = previousOrganisationId + delete data.previousOrganisationId + + if (previousOrganisation && previousOrganisation.type === 'Pharmacy HQ') { + return res.redirect('/pharmacies') + } + + res.redirect(getDefaultLandingPath(data, previousOrganisationId)) + }) + + const isTemporaryAccessInactive = (addedOnIsoDate, deactivatedOnIsoDate) => { + if (deactivatedOnIsoDate) { + return true + } + + if (!addedOnIsoDate) { + return false + } + + const expiryDate = new Date(`${addedOnIsoDate}T12:00:00`) + expiryDate.setDate(expiryDate.getDate() + 7) + + const today = new Date() + today.setHours(0, 0, 0, 0) + + return expiryDate < today + } + + const getGroupAdminSwitchContext = (data, user) => { + if (!data || !user) { + return { + isEligible: false, + groupOrganisation: null, + temporaryPharmacies: [] + } + } + + const activeGroupAdminOrganisationIds = (user.organisations || []) + .filter((organisation) => organisation.permissionLevel === 'Group administrator' && organisation.status === 'Active') + .map((organisation) => organisation.id) + + const groupOrganisation = (data.organisations || []).find((organisation) => { + return organisation.type === 'Pharmacy HQ' && activeGroupAdminOrganisationIds.includes(organisation.id) + }) + + if (!groupOrganisation) { + return { + isEligible: false, + groupOrganisation: null, + temporaryPharmacies: [] + } + } + + const temporaryPharmacyIds = [...new Set(asArray(data.groupAdminTemporaryPharmacyIds))] + const temporaryPharmacyAddedDates = data.groupAdminTemporaryPharmacyAddedDates || {} + const temporaryPharmacyDeactivatedDates = data.groupAdminTemporaryPharmacyDeactivatedDates || {} + + const temporaryPharmacies = temporaryPharmacyIds + .map((pharmacyId) => { + const organisation = (data.organisations || []).find((item) => item.id === pharmacyId) + + return { + organisation, + addedOn: temporaryPharmacyAddedDates[pharmacyId], + deactivatedOn: temporaryPharmacyDeactivatedDates[pharmacyId] + } + }) + .filter((item) => item.organisation) + .filter((organisation) => { + return organisation.organisation.status === 'Active' && organisation.organisation.companyId === groupOrganisation.id + }) + .filter((item) => { + return !isTemporaryAccessInactive(item.addedOn, item.deactivatedOn) + }) + .map((item) => item.organisation) + + return { + isEligible: temporaryPharmacies.length > 1, + groupOrganisation, + temporaryPharmacies + } + } + router.post('/auth/sign-in', (req, res) => { const data = req.session.data @@ -119,6 +235,97 @@ module.exports = router => { } }) + router.get('/auth/switch-organisation/work-type', (req, res) => { + const data = req.session.data + const user = getCurrentUserFromSessionData(data) + const context = getGroupAdminSwitchContext(data, user) + + if (!user || !context.isEligible) { + return res.redirect('/auth/select-organisation') + } + + res.render('auth/switch-work-type', { + groupOrganisation: context.groupOrganisation + }) + }) + + router.post('/auth/switch-organisation/work-type', (req, res) => { + const data = req.session.data + const user = getCurrentUserFromSessionData(data) + const context = getGroupAdminSwitchContext(data, user) + const switchOrganisationWorkType = req.body.switchOrganisationWorkType + + if (!user || !context.isEligible) { + return res.redirect('/auth/select-organisation') + } + + if (!switchOrganisationWorkType) { + return res.render('auth/switch-work-type', { + groupOrganisation: context.groupOrganisation, + error: { + text: 'Select the type of work you want to do' + } + }) + } + + if (switchOrganisationWorkType === 'group-admin') { + data.currentOrganisationId = context.groupOrganisation.id + return res.redirect(getDefaultLandingPath(data, context.groupOrganisation.id)) + } + + if (switchOrganisationWorkType === 'individual-pharmacy') { + return res.redirect('/auth/switch-organisation/select-pharmacy') + } + + res.render('auth/switch-work-type', { + groupOrganisation: context.groupOrganisation, + error: { + text: 'Select the type of work you want to do' + } + }) + }) + + router.get('/auth/switch-organisation/select-pharmacy', (req, res) => { + const data = req.session.data + const user = getCurrentUserFromSessionData(data) + const context = getGroupAdminSwitchContext(data, user) + + if (!user || !context.isEligible) { + return res.redirect('/auth/select-organisation') + } + + res.render('auth/select-pharmacy-for-access', { + groupOrganisation: context.groupOrganisation, + pharmacies: context.temporaryPharmacies + }) + }) + + router.post('/auth/switch-organisation/select-pharmacy', (req, res) => { + const data = req.session.data + const user = getCurrentUserFromSessionData(data) + const context = getGroupAdminSwitchContext(data, user) + const switchOrganisationPharmacyId = req.body.switchOrganisationPharmacyId + + if (!user || !context.isEligible) { + return res.redirect('/auth/select-organisation') + } + + const isValidSelection = context.temporaryPharmacies.some((pharmacy) => pharmacy.id === switchOrganisationPharmacyId) + + if (!switchOrganisationPharmacyId || !isValidSelection) { + return res.render('auth/select-pharmacy-for-access', { + groupOrganisation: context.groupOrganisation, + pharmacies: context.temporaryPharmacies, + error: { + text: 'Select a pharmacy to access' + } + }) + } + + data.currentOrganisationId = switchOrganisationPharmacyId + res.redirect(getDefaultLandingPath(data, switchOrganisationPharmacyId)) + }) + router.get('/sign-out', (req, res) => { req.session.data.currentUserId = null req.session.data.currentOrganisationId = null diff --git a/app/routes/pharmacies.js b/app/routes/pharmacies.js index f098f672..0dbe4bc2 100644 --- a/app/routes/pharmacies.js +++ b/app/routes/pharmacies.js @@ -135,6 +135,28 @@ const buildDefaultScenarioUsersForPharmacy = (organisation) => { ] } +const isGroupAdminUser = (user) => { + return (user?.organisations || []).some((organisation) => { + return organisation.permissionLevel === 'Group administrator' && organisation.status !== 'Deactivated' + }) +} + +const renderAddUserSelectionPage = (res, organisation, users, userIdError) => { + res.render('pharmacies/add-user', { + users, + organisation, + userIdError + }) +} + +const renderAddUserPermissionLevelPage = (res, organisation, existingUser, errors = {}) => { + res.render('pharmacies/add-user-permission-level', { + organisation, + existingUser, + ...errors + }) +} + module.exports = router => { router.get('/pharmacies', (req, res) => { @@ -376,6 +398,44 @@ module.exports = router => { router.post('/pharmacies/users/new-answer',(req, res) => { const data = req.session.data const groupAdministrator = data.groupAdministrator + const firstName = (data.firstName || '').trim() + const lastName = (data.lastName || '').trim() + const email = (data.email || '').trim() + const existingUserWithSameEmail = email + ? data.users.find((user) => user.email.toLowerCase() === email.toLowerCase()) + : null + + let firstNameError + let lastNameError + let emailError + + if (!firstName) { + firstNameError = 'Enter a first name' + } + + if (!lastName) { + lastNameError = 'Enter a last name' + } + + if (!email) { + emailError = 'Enter an email address' + } else if (!(email.toLowerCase().endsWith('nhs.net') || email.toLowerCase().endsWith('.nhs.uk'))) { + emailError = 'Enter an allowed email address' + } else if (groupAdministrator === 'no' && existingUserWithSameEmail && isGroupAdminUser(existingUserWithSameEmail)) { + emailError = 'You cannot add a group administrator to an individual pharmacy' + } + + data.firstName = firstName + data.lastName = lastName + data.email = email + + if (firstNameError || lastNameError || emailError) { + return res.render('pharmacies/users/new', { + firstNameError, + lastNameError, + emailError + }) + } if (groupAdministrator === "yes") { res.redirect('/pharmacies/users/check') @@ -513,6 +573,25 @@ module.exports = router => { res.redirect('/pharmacies/users?added=true') }) + router.get('/pharmacies/:id/view-as', (req, res) => { + const data = req.session.data + const id = req.params.id + const organisation = data.organisations.find(o => o.id === id) + + if (!organisation || organisation.status === 'Deactivated') { + return res.redirect(`/pharmacies/${id}`) + } + + req.session.data.previousOrganisationId = req.session.data.currentOrganisationId + req.session.data.currentOrganisationId = id + + const landingPath = organisation.appointmentsInterfaceEnabled !== false + ? '/appointments' + : '/record-vaccinations' + + res.redirect(landingPath) + }) + router.get('/pharmacies/:id/deactivate',(req, res) => { const data = req.session.data const id = req.params.id @@ -581,10 +660,29 @@ module.exports = router => { const id = req.params.id const organisation = data.organisations.find((organisation) => organisation.id === id) - res.render('pharmacies/add-user', { - users, - organisation - }) + renderAddUserSelectionPage(res, organisation, users) + }) + + router.post('/pharmacies/:id/add-user-permission-level', (req, res) => { + const data = req.session.data + const users = data.users.slice(10, 30) + const id = req.params.id + const organisation = data.organisations.find((item) => item.id === id) + const selectedUserId = req.body.userId || data.userId + const selectedUser = selectedUserId && selectedUserId !== 'add-new' + ? data.users.find((user) => user.id === selectedUserId) + : null + + if (selectedUser && isGroupAdminUser(selectedUser)) { + return renderAddUserSelectionPage( + res, + organisation, + users, + 'You cannot add a group administrator to an individual pharmacy' + ) + } + + res.redirect(`/pharmacies/${id}/add-user-permission-level`) }) router.get('/pharmacies/:id/add-user-permission-level',(req, res) => { @@ -597,10 +695,67 @@ module.exports = router => { existingUser = data.users.find((user) => user.id === data.userId) } - res.render('pharmacies/add-user-permission-level', { - organisation, - existingUser - }) + renderAddUserPermissionLevelPage(res, organisation, existingUser) + }) + + router.post('/pharmacies/:id/add-user-check', (req, res) => { + const data = req.session.data + const id = req.params.id + const organisation = data.organisations.find((item) => item.id === id) + const selectedUserId = req.body.userId || data.userId + const submittedFirstName = req.body.firstName || data.firstName + const submittedLastName = req.body.lastName || data.lastName + const submittedEmail = (req.body.email || data.email || '').trim() + const submittedPermissionLevel = req.body.permissionLevel || data.permissionLevel + const submittedVaccinator = req.body.vaccinator || data.vaccinator + const existingUser = selectedUserId ? data.users.find((user) => user.id === selectedUserId) : null + const existingUserWithSameEmail = submittedEmail + ? data.users.find((user) => user.email.toLowerCase() === submittedEmail.toLowerCase()) + : null + + let firstNameError + let lastNameError + let emailError + let permissionLevelError + let vaccinatorError + + if (!existingUser) { + if (!submittedFirstName || submittedFirstName === '') { + firstNameError = 'Enter a first name' + } + + if (!submittedLastName || submittedLastName === '') { + lastNameError = 'Enter a last name' + } + + if (!submittedEmail || submittedEmail === '') { + emailError = 'Enter an email address' + } else if (!(submittedEmail.toLowerCase().endsWith('nhs.net') || submittedEmail.toLowerCase().endsWith('.nhs.uk'))) { + emailError = 'Enter an allowed email address' + } else if (existingUserWithSameEmail && isGroupAdminUser(existingUserWithSameEmail)) { + emailError = 'You cannot add a group administrator to an individual pharmacy' + } + } + + if (!submittedPermissionLevel || submittedPermissionLevel === '') { + permissionLevelError = 'Select a permission level' + } + + if (!submittedVaccinator || submittedVaccinator === '') { + vaccinatorError = 'Select if they’re a vaccinator' + } + + if (firstNameError || lastNameError || emailError || permissionLevelError || vaccinatorError) { + return renderAddUserPermissionLevelPage(res, organisation, existingUser, { + firstNameError, + lastNameError, + emailError, + permissionLevelError, + vaccinatorError + }) + } + + res.redirect(`/pharmacies/${id}/add-user-check`) }) router.get('/pharmacies/:id/add-user-check',(req, res) => { @@ -619,13 +774,24 @@ module.exports = router => { }) }) - router.get('/pharmacies/:id/user-added',(req, res) => { + router.post('/pharmacies/:id/user-added',(req, res) => { const data = req.session.data const id = req.params.id const organisation = data.organisations.find((organisation) => organisation.id === id) - const existingUser = data.users.find((user) => user.id === data.userId) + const selectedUserId = req.body.userId || data.userId + const submittedEmail = (req.body.email || data.email || '').trim() + const existingUser = selectedUserId ? data.users.find((user) => user.id === selectedUserId) : null + const existingUserWithSameEmail = submittedEmail + ? data.users.find((user) => user.email.toLowerCase() === submittedEmail.toLowerCase()) + : null let addedUserId + if ((existingUser && isGroupAdminUser(existingUser)) || (!existingUser && existingUserWithSameEmail && isGroupAdminUser(existingUserWithSameEmail))) { + return renderAddUserPermissionLevelPage(res, organisation, existingUser, { + emailError: 'You cannot add a group administrator to an individual pharmacy' + }) + } + if (existingUser) { existingUser.organisations ||= [] @@ -1103,9 +1269,14 @@ module.exports = router => { ? data.users : [...data.users, ...defaultScenarioUsers.filter((user) => !existingUserIds.has(user.id))] - const usersForOrganisation = users.filter((user) => (user.organisations || []) - .find((orgPermission) => orgPermission.id === organisation.id) - ) + const usersForOrganisation = users.filter((user) => { + if (data.previousOrganisationId && user.id === data.currentUserId) { + return false + } + + return (user.organisations || []) + .find((orgPermission) => orgPermission.id === organisation.id) + }) const usersByStatus = { invited: [], diff --git a/app/routes/user-admin.js b/app/routes/user-admin.js index adff57a0..8fa8ba56 100644 --- a/app/routes/user-admin.js +++ b/app/routes/user-admin.js @@ -1,3 +1,9 @@ +const isGroupAdminUser = (user) => { + return (user?.organisations || []).some((organisation) => { + return organisation.permissionLevel === 'Group administrator' && organisation.status !== 'Deactivated' + }) +} + module.exports = (router) => { router.get('/user-admin', (req, res) => { @@ -176,6 +182,8 @@ module.exports = (router) => { emailError = 'Enter an email address' } else if (!(email.endsWith('nhs.net') || email.endsWith('.nhs.uk'))) { emailError = 'Enter an allowed email address' + } else if (existingUserWithSameEmail && isGroupAdminUser(existingUserWithSameEmail)) { + emailError = 'Group administrators cannot be added to individual pharmacies' } else if (existingUserWithSameEmail && existingUserWithSameEmail.status !== 'Deactivated') { emailError = 'This email address has already been added' } diff --git a/app/views/includes/header.html b/app/views/includes/header.html index 2f48b59a..9c9fafd6 100644 --- a/app/views/includes/header.html +++ b/app/views/includes/header.html @@ -130,9 +130,15 @@ {% endif %} +{% set organisationLabelLimit = 20 if data.previousOrganisationId else 30 %} +{% set organisationCodeSuffix = " (" + currentOrganisation.id + ")" if currentOrganisation and currentOrganisation.id else "" %} +{% set organisationNameLimit = organisationLabelLimit - (organisationCodeSuffix | length) %} + {% set organisationHtml %} - {{ currentOrganisation.name | truncate(30) }} - {% if currentUser and currentOrganisation and ((currentUser.organisations | length) > 1) %} + {{ currentOrganisation.name | truncate(organisationNameLimit if organisationNameLimit > 1 else 1) }}{{ organisationCodeSuffix }} + {% if currentUser and currentOrganisation and data.previousOrganisationId %} + Back to Pharmacies + {% elif currentUser and currentOrganisation and ((currentUser.organisations | length) > 1) %} Change organisation {% endif %} {% endset %} diff --git a/app/views/pharmacies/add-user-permission-level.html b/app/views/pharmacies/add-user-permission-level.html index 0c991cd0..3a8f2b6e 100644 --- a/app/views/pharmacies/add-user-permission-level.html +++ b/app/views/pharmacies/add-user-permission-level.html @@ -18,6 +18,34 @@ {% block content %}
You can add an existing user to the pharmacy, or invite a new user.
@@ -145,13 +157,18 @@This pharmacy has been deactivated.
diff --git a/app/views/pharmacies/users/new.html b/app/views/pharmacies/users/new.html index c835c3fc..75e7cbde 100644 --- a/app/views/pharmacies/users/new.html +++ b/app/views/pharmacies/users/new.html @@ -49,7 +49,10 @@