From d1ec9fa467a53cf46fa5eac1002fdde6f9ca24e1 Mon Sep 17 00:00:00 2001 From: Roble Date: Thu, 16 May 2024 13:08:51 +0100 Subject: [PATCH] Adding multiple return requirements to the session (#1014) * Adding multiple return requirements to the session This PR is focused on allowing users to add another return requirement to the current session once they have reached the check page in the journey. Users will be redirected to the purpose page for the new requirement. --- .../return-requirements.controller.js | 10 ++ .../return-requirements/check.presenter.js | 19 ++ .../return-requirements/purpose.presenter.js | 15 +- app/routes/return-requirement.routes.js | 14 ++ .../return-requirements/add.service.js | 38 ++++ app/views/return-requirements/check.njk | 170 ++++++++++-------- .../check.presenter.test.js | 1 + .../return-requirements/add.service.test.js | 58 ++++++ .../return-requirements/check.service.test.js | 1 + 9 files changed, 249 insertions(+), 77 deletions(-) create mode 100644 app/services/return-requirements/add.service.js create mode 100644 test/services/return-requirements/add.service.test.js diff --git a/app/controllers/return-requirements.controller.js b/app/controllers/return-requirements.controller.js index 4426f90673..f42e327551 100644 --- a/app/controllers/return-requirements.controller.js +++ b/app/controllers/return-requirements.controller.js @@ -6,6 +6,7 @@ */ const AbstractionPeriodService = require('../services/return-requirements/abstraction-period.service.js') +const AddService = require('../services/return-requirements/add.service.js') const AgreementsExceptionsService = require('../services/return-requirements/agreements-exceptions.service.js') const CancelService = require('../services/return-requirements/cancel.service.js') const CheckService = require('../services/return-requirements/check.service.js') @@ -50,6 +51,14 @@ async function abstractionPeriod (request, h) { }) } +async function add (request, h) { + const { sessionId } = request.params + + const requirementIndex = await AddService.go(sessionId) + + return h.redirect(`/system/return-requirements/${sessionId}/purpose/${requirementIndex}`) +} + async function agreementsExceptions (request, h) { const { requirementIndex, sessionId } = request.params @@ -458,6 +467,7 @@ async function submitStartDate (request, h) { module.exports = { abstractionPeriod, + add, agreementsExceptions, approved, cancel, diff --git a/app/presenters/return-requirements/check.presenter.js b/app/presenters/return-requirements/check.presenter.js index ce3f4c5929..c0deeeff03 100644 --- a/app/presenters/return-requirements/check.presenter.js +++ b/app/presenters/return-requirements/check.presenter.js @@ -18,6 +18,7 @@ function go (session) { pageTitle: `Check the return requirements for ${licence.licenceHolder}`, reason: returnRequirementReasons[reason], reasonLink: _reasonLink(sessionId, journey), + requirements: _requirements(session), sessionId, startDate: _startDate(session), userEmail: note ? note.userEmail : 'No notes added' @@ -32,6 +33,24 @@ function _reasonLink (sessionId, journey) { return `/system/return-requirements/${sessionId}/no-returns-required` } +function _requirements (session) { + const { requirements } = session + + const completedRequirements = [] + + for (const requirement of requirements) { + const { siteDescription, agreementsExceptions } = requirement + + // NOTE: We determine a requirement is complete because agreement exceptions is populated and it is the last step in + // the journey + if (agreementsExceptions) { + completedRequirements.push({ siteDescription }) + } + } + + return completedRequirements +} + function _startDate (session) { const { licence, startDateOptions, startDateDay, startDateMonth, startDateYear } = session diff --git a/app/presenters/return-requirements/purpose.presenter.js b/app/presenters/return-requirements/purpose.presenter.js index 5a955fb129..9a05afb576 100644 --- a/app/presenters/return-requirements/purpose.presenter.js +++ b/app/presenters/return-requirements/purpose.presenter.js @@ -29,9 +29,18 @@ function go (session, requirementIndex, purposesData) { } function _backLink (session) { - const { checkPageVisited, id } = session - - if (checkPageVisited) { + const { checkPageVisited, id, requirements } = session + + // NOTE: Purpose is the first page in the manual setup journey. So, when a user first comes through, we want to allow + // them to go back to `/setup`. Once they've got to the `/check` page they may return because they clicked the + // 'Change' link for the purpose. When this happens, `checkPageVisited` will be true and 'Back' needs to take them + // back there. + // + // But if they click 'Add requirement' on the `/check` page they'll also be directed here. When that happens + // `checkPageVisited` will have been reset to false to allow the user to progress through the setup journey. In this + // scenario 'Back' also needs to take them back to `/check`. Hence, the logic is different in this presenter when + // compared with the other setup pages. + if (checkPageVisited || requirements.length > 1) { return `/system/return-requirements/${id}/check` } diff --git a/app/routes/return-requirement.routes.js b/app/routes/return-requirement.routes.js index c7671689be..94b2674e83 100644 --- a/app/routes/return-requirement.routes.js +++ b/app/routes/return-requirement.routes.js @@ -29,6 +29,20 @@ const routes = [ description: 'Submit the abstraction period for the return requirement' } }, + { + method: 'POST', + path: '/return-requirements/{sessionId}/add', + handler: ReturnRequirementsController.add, + options: { + auth: { + access: { + scope: ['billing'] + } + }, + description: 'Adds another object to the requirements array in the session' + } + + }, { method: 'GET', path: '/return-requirements/{sessionId}/agreements-exceptions/{requirementIndex}', diff --git a/app/services/return-requirements/add.service.js b/app/services/return-requirements/add.service.js new file mode 100644 index 0000000000..e3c90d715e --- /dev/null +++ b/app/services/return-requirements/add.service.js @@ -0,0 +1,38 @@ +'use strict' + +/** + * Orchestrates adding an empty object to the requirements array in the session + * @module AddService +*/ + +const SessionModel = require('../../models/session.model.js') + +/** + * Orchestrates adding an empty object to the requirements array in the session + * + * Supports adding another object to the requirements array in the session when a user hits the 'Add another + * requirement' button. + * + * @param {string} sessionId - The UUID of the current session + * + * @returns {number} - The index of the new requirement. Needed by the setup pages so they know which requirement to + * display and update +*/ +async function go (sessionId) { + const session = await SessionModel.query().findById(sessionId) + + await _save(session) + + return session.requirements.length - 1 +} + +async function _save (session) { + session.requirements.push({}) + + session.checkPageVisited = false + + return session.$update() +} +module.exports = { + go +} diff --git a/app/views/return-requirements/check.njk b/app/views/return-requirements/check.njk index 1ae9b30885..a63271bba9 100644 --- a/app/views/return-requirements/check.njk +++ b/app/views/return-requirements/check.njk @@ -19,86 +19,108 @@ -
-
-
- {{ govukSummaryList({ - classes: 'govuk-!-margin-bottom-2', - rows: [ - { - classes: 'govuk-summary-list govuk-summary-list__row--no-border', - key: { - text: "Start date" - }, - value: { - text: startDate - }, - actions: { - items: [ - { - href: "/system/return-requirements/" + sessionId + "/start-date", - text: "Change", - visuallyHiddenText: "the start date for the return requirement" - } - ] - } +
+
+ {{ govukSummaryList({ + classes: 'govuk-!-margin-bottom-2', + rows: [ + { + classes: 'govuk-summary-list govuk-summary-list__row--no-border', + key: { + text: "Start date" + }, + value: { + text: startDate }, - { - classes: 'govuk-summary-list govuk-summary-list__row--no-border', - key: { - text: "Reason" - }, - value: { - text: reason - }, - actions: { - items: [ - { - href: reasonLink, - text: "Change", - visuallyHiddenText: "the reason for the return requirement" - } - ] - } + actions: { + items: [ + { + href: "/system/return-requirements/" + sessionId + "/start-date", + text: "Change", + visuallyHiddenText: "the start date for the return requirement" + } + ] } - ] - }) }} -
-
+ }, + { + classes: 'govuk-summary-list govuk-summary-list__row--no-border', + key: { + text: "Reason" + }, + value: { + text: reason + }, + actions: { + items: [ + { + href: reasonLink, + text: "Change", + visuallyHiddenText: "the reason for the return requirement" + } + ] + } + } + ] + }) }} +
+
-
-

Notes

-
- {{ govukSummaryList({ - classes: 'govuk-!-margin-bottom-2', - rows: [ - { - classes: 'govuk-summary-list govuk-summary-list__row--no-border', - key: { - text: userEmail, - classes: "govuk-body govuk-!-font-weight-regular" - }, - value: { - text: note | escape | nl2br - }, - actions: { - items: [ - { - href: "note", - text: "Change" if note else "Add a note" - }, - { - href: "delete-note", - text: "Delete" - } if note - ] - } +
+

Notes

+
+ {{ govukSummaryList({ + classes: 'govuk-!-margin-bottom-2', + rows: [ + { + classes: 'govuk-summary-list govuk-summary-list__row--no-border', + key: { + text: userEmail, + classes: "govuk-body govuk-!-font-weight-regular" + }, + value: { + text: note | escape | nl2br + }, + actions: { + items: [ + { + href: "note", + text: "Change" if note else "Add a note" + }, + { + href: "delete-note", + text: "Delete" + } if note + ] } - ] - }) }} -
+ } + ] + }) }} +
+
+ + {% if journey == 'returns-required' %} +
+

Requirements for returns

+ + {{ govukButton({ + text: "Add another requirement", + classes: "govuk-button--secondary", + preventDoubleClick: true + }) }} + + + {% for requirement in requirements %} + {# Set an easier to use index #} + {% set rowIndex = loop.index0 %} +
+

{{requirement.siteDescription}}

+

Remove {{rowIndex}}

+
+ {% endfor %}
+ {% endif %} +
{% if journey == 'no-returns-required' %}

Returns are not required for this licence

diff --git a/test/presenters/return-requirements/check.presenter.test.js b/test/presenters/return-requirements/check.presenter.test.js index b422a33ee3..81b73087ae 100644 --- a/test/presenters/return-requirements/check.presenter.test.js +++ b/test/presenters/return-requirements/check.presenter.test.js @@ -43,6 +43,7 @@ describe('Return Requirements - Check presenter', () => { pageTitle: 'Check the return requirements for Turbo Kid', reason: 'Major change', reasonLink: '/system/return-requirements/61e07498-f309-4829-96a9-72084a54996d/reason', + requirements: [], sessionId: '61e07498-f309-4829-96a9-72084a54996d', startDate: '1 January 2023', userEmail: 'No notes added' diff --git a/test/services/return-requirements/add.service.test.js b/test/services/return-requirements/add.service.test.js new file mode 100644 index 0000000000..ff1e54a4c4 --- /dev/null +++ b/test/services/return-requirements/add.service.test.js @@ -0,0 +1,58 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') + +const { describe, it, beforeEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const DatabaseSupport = require('../../support/database.js') +const SessionHelper = require('../../support/helpers/session.helper.js') + +// Thing under test +const AddService = require('../../../app/services/return-requirements/add.service.js') + +describe('Return Requirements - Add service', () => { + let session + + beforeEach(async () => { + await DatabaseSupport.clean() + + session = await SessionHelper.add({ + data: { + checkPageVisited: false, + licence: { + id: '8b7f78ba-f3ad-4cb6-a058-78abc4d1383d', + currentVersionStartDate: '2023-01-01T00:00:00.000Z', + endDate: null, + licenceRef: '01/ABC', + licenceHolder: 'Turbo Kid', + startDate: '2022-04-01T00:00:00.000Z' + }, + journey: 'returns-required', + requirements: [{}], + startDateOptions: 'licenceStartDate', + reason: 'major-change' + } + }) + }) + + describe('when called', () => { + it('adds another empty object to the requirement array in the current setup session record', async () => { + await AddService.go(session.id) + + const refreshedSession = await session.$query() + + expect(refreshedSession.data.requirements.length).to.equal(2) + expect(refreshedSession.data.requirements).to.equal([{}, {}]) + }) + + it('returns the index of the new requirement', async () => { + const result = await AddService.go(session.id) + + expect(result).to.equal(1) + }) + }) +}) diff --git a/test/services/return-requirements/check.service.test.js b/test/services/return-requirements/check.service.test.js index f6320bdd43..47d9f8b172 100644 --- a/test/services/return-requirements/check.service.test.js +++ b/test/services/return-requirements/check.service.test.js @@ -66,6 +66,7 @@ describe('Return Requirements - Check service', () => { pageTitle: 'Check the return requirements for Turbo Kid', reason: 'Major change', reasonLink: `/system/return-requirements/${session.id}/reason`, + requirements: [], startDate: '1 January 2023', userEmail: 'No notes added' }, { skip: ['sessionId'] })