From 09a6abbb3addd72a43e3214e1de77f5037ffdd9c Mon Sep 17 00:00:00 2001 From: Alan Cruikshanks Date: Fri, 1 Mar 2024 14:35:29 +0000 Subject: [PATCH 1/6] Add cancel bill run page https://eaflood.atlassian.net/browse/WATER-4387 We need to add this to support our work to improve how bill runs are cancelled. Basically, replace the crummy legacy one! Rather than invest time in updating the existing cancel bill run page to point to what will be our new cancel endpoint we instead are investing in rebuilding it. There are already changes we'll need to make to support work on [cancelling a two-part tariff bill run](https://eaflood.atlassian.net/browse/WATER-4189) so we may as well focus our efforts into building a replacement page. This changes adds the route, controller, presenter, service and view for a new cancel bill run page. From 048c0ce663a174458c482bb848b10ced4d90b5b8 Mon Sep 17 00:00:00 2001 From: Alan Cruikshanks Date: Fri, 1 Mar 2024 15:09:15 +0000 Subject: [PATCH 2/6] Add the presenter --- .../bill-runs/cancel-bill-run.presenter.js | 81 ++++++++ .../cancel-bill-run.presenter.test.js | 176 ++++++++++++++++++ 2 files changed, 257 insertions(+) create mode 100644 app/presenters/bill-runs/cancel-bill-run.presenter.js create mode 100644 test/presenters/bill-runs/cancel-bill-run.presenter.test.js diff --git a/app/presenters/bill-runs/cancel-bill-run.presenter.js b/app/presenters/bill-runs/cancel-bill-run.presenter.js new file mode 100644 index 0000000000..ebf17d38a3 --- /dev/null +++ b/app/presenters/bill-runs/cancel-bill-run.presenter.js @@ -0,0 +1,81 @@ +'use strict' + +/** + * Formats the bill run data ready for presenting in the cancel bill run confirmation page + * @module CancelBillRunPresenter + */ + +const { capitalize, formatLongDate } = require('../base.presenter.js') + +/** + * Prepares and processes bill run data for presentation + * + * @param {module:BillRunModel} billRun - an instance of `BillRunModel` + * + * @returns {Object} - the prepared bill run data to be passed to the cancel bill run confirmation page + */ +function go (billRun) { + const { + batchType, + billRunNumber, + createdAt, + id, + region, + scheme, + status, + summer, + toFinancialYearEnding + } = billRun + + return { + backLink: _backLink(id, status), + billRunId: id, + billRunNumber, + billRunStatus: status, + billRunType: _billRunType(batchType, summer, scheme), + chargeScheme: _chargeScheme(scheme), + dateCreated: formatLongDate(createdAt), + financialYear: _financialYear(toFinancialYearEnding), + region: capitalize(region.displayName) + } +} + +function _backLink (id, status) { + if (status === 'review') { + return `/system/bill-runs/${id}/review` + } + + return `/system/bill-runs/${id}` +} + +function _billRunType (batchType, summer, scheme) { + if (batchType !== 'two_part_tariff') { + return capitalize(batchType) + } + + if (scheme === 'sroc') { + return 'Two-part tariff' + } + + if (summer) { + return 'Two-part tariff summer' + } + + return 'Two-part tariff winter and all year' +} + +function _chargeScheme (scheme) { + if (scheme === 'sroc') { + return 'Current' + } + + return 'Old' +} + +function _financialYear (financialYearEnding) { + return `${financialYearEnding - 1} to ${financialYearEnding}` +} + +module.exports = { + go +} diff --git a/test/presenters/bill-runs/cancel-bill-run.presenter.test.js b/test/presenters/bill-runs/cancel-bill-run.presenter.test.js new file mode 100644 index 0000000000..27c0755f99 --- /dev/null +++ b/test/presenters/bill-runs/cancel-bill-run.presenter.test.js @@ -0,0 +1,176 @@ +'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 + +// Thing under test +const CancelBillRunPresenter = require('../../../app/presenters/bill-runs/cancel-bill-run.presenter.js') + +describe('Cancel Bill Run presenter', () => { + let billRun + + describe('when provided with a populated bill run', () => { + beforeEach(() => { + billRun = _testBillRun() + }) + + it('correctly presents the data', () => { + const result = CancelBillRunPresenter.go(billRun) + + expect(result).to.equal({ + backLink: '/system/bill-runs/420e948f-1992-437e-8a47-74c0066cb017', + billRunId: '420e948f-1992-437e-8a47-74c0066cb017', + billRunNumber: 10010, + billRunStatus: 'ready', + billRunType: 'Supplementary', + chargeScheme: 'Current', + dateCreated: '1 November 2023', + financialYear: '2023 to 2024', + region: 'Wales' + }) + }) + + describe("the 'backLink' property", () => { + describe('when the bill run status is review', () => { + beforeEach(() => { + billRun.status = 'review' + }) + + it('returns a link to the review page', () => { + const result = CancelBillRunPresenter.go(billRun) + + expect(result.backLink).to.equal('/system/bill-runs/420e948f-1992-437e-8a47-74c0066cb017/review') + }) + }) + + describe('when the bill run status is not review', () => { + it('returns a link to the bill run page', () => { + const result = CancelBillRunPresenter.go(billRun) + + expect(result.backLink).to.equal('/system/bill-runs/420e948f-1992-437e-8a47-74c0066cb017') + }) + }) + }) + + describe("the 'billRunType' property", () => { + describe('when the bill run is annual', () => { + beforeEach(() => { + billRun.batchType = 'annual' + }) + + it('returns Annual', () => { + const result = CancelBillRunPresenter.go(billRun) + + expect(result.billRunType).to.equal('Annual') + }) + }) + + describe('when the bill run is supplementary', () => { + it('returns Supplementary', () => { + const result = CancelBillRunPresenter.go(billRun) + + expect(result.billRunType).to.equal('Supplementary') + }) + }) + + describe('when the bill run is two_part_tariff', () => { + beforeEach(() => { + billRun.batchType = 'two_part_tariff' + }) + + describe('and the scheme is sroc', () => { + it('returns Supplementary', () => { + const result = CancelBillRunPresenter.go(billRun) + + expect(result.billRunType).to.equal('Two-part tariff') + }) + }) + + describe('and the scheme is alcs', () => { + beforeEach(() => { + billRun.scheme = 'alcs' + }) + + describe('and it is not summer only', () => { + it('returns Supplementary', () => { + const result = CancelBillRunPresenter.go(billRun) + + expect(result.billRunType).to.equal('Two-part tariff winter and all year') + }) + }) + + describe('and it is for summer only', () => { + beforeEach(() => { + billRun.summer = true + }) + + it('returns Supplementary', () => { + const result = CancelBillRunPresenter.go(billRun) + + expect(result.billRunType).to.equal('Two-part tariff summer') + }) + }) + }) + }) + }) + + describe("the 'chargeScheme' property", () => { + describe('when the bill run is sroc', () => { + it('returns Current', () => { + const result = CancelBillRunPresenter.go(billRun) + + expect(result.chargeScheme).to.equal('Current') + }) + }) + + describe('when the bill run is alcs', () => { + beforeEach(() => { + billRun.scheme = 'alcs' + }) + + it('returns Old', () => { + const result = CancelBillRunPresenter.go(billRun) + + expect(result.chargeScheme).to.equal('Old') + }) + }) + }) + + describe("the 'financialYear' property", () => { + it('returns the to and from financial year (2023 to 2024)', () => { + const result = CancelBillRunPresenter.go(billRun) + + expect(result.financialYear).to.equal('2023 to 2024') + }) + }) + + describe("the 'region' property", () => { + it("returns the bill run's region display name capitalized (Wales)", () => { + const result = CancelBillRunPresenter.go(billRun) + + expect(result.region).to.equal('Wales') + }) + }) + }) +}) + +function _testBillRun () { + return { + id: '420e948f-1992-437e-8a47-74c0066cb017', + batchType: 'supplementary', + billRunNumber: 10010, + summer: false, + scheme: 'sroc', + status: 'ready', + toFinancialYearEnding: 2024, + createdAt: new Date('2023-11-01'), + region: { + id: 'f6c4699f-9a80-419a-82e7-f785ece727e1', + displayName: 'Wales' + } + } +} From 016cbac3bbaaec7a242ef1238d733222e0daeeaf Mon Sep 17 00:00:00 2001 From: Alan Cruikshanks Date: Fri, 1 Mar 2024 15:11:45 +0000 Subject: [PATCH 3/6] Add service --- .../bill-runs/cancel-bill-run.service.js | 51 ++++++++++++++++ .../bill-runs/cancel-bill-run.service.test.js | 61 +++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 app/services/bill-runs/cancel-bill-run.service.js create mode 100644 test/services/bill-runs/cancel-bill-run.service.test.js diff --git a/app/services/bill-runs/cancel-bill-run.service.js b/app/services/bill-runs/cancel-bill-run.service.js new file mode 100644 index 0000000000..7e37a4500b --- /dev/null +++ b/app/services/bill-runs/cancel-bill-run.service.js @@ -0,0 +1,51 @@ +'use strict' + +/** + * Orchestrates fetching and presenting the data needed for the cancel bill run confirmation page + * @module CancelBillRunService + */ + +const BillRunModel = require('../../models/bill-run.model.js') +const CancelBillRunPresenter = require('../../presenters/bill-runs/cancel-bill-run.presenter.js') + +/** + * Orchestrates fetching and presenting the data needed for the cancel bill run confirmation page + * + * @param {string} id - The UUID of the bill run to cancel + * + * @returns {Promise an object representing the `pageData` needed by the cancel bill run template. It contains + * details of the bill run. + */ +async function go (id) { + const billRun = await _fetchBillRun(id) + + const pageData = CancelBillRunPresenter.go(billRun) + + return pageData +} + +async function _fetchBillRun (id) { + return BillRunModel.query() + .findById(id) + .select([ + 'id', + 'batchType', + 'billRunNumber', + 'createdAt', + 'scheme', + 'status', + 'summer', + 'toFinancialYearEnding' + ]) + .withGraphFetched('region') + .modifyGraph('region', (builder) => { + builder.select([ + 'id', + 'displayName' + ]) + }) +} + +module.exports = { + go +} diff --git a/test/services/bill-runs/cancel-bill-run.service.test.js b/test/services/bill-runs/cancel-bill-run.service.test.js new file mode 100644 index 0000000000..e35af2426c --- /dev/null +++ b/test/services/bill-runs/cancel-bill-run.service.test.js @@ -0,0 +1,61 @@ +'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 BillRunHelper = require('../../support/helpers/bill-run.helper.js') +const DatabaseHelper = require('../../support/helpers/database.helper.js') +const RegionHelper = require('../../support/helpers/region.helper.js') + +// Thing under test +const CancelBillRunService = require('../../../app/services/bill-runs/cancel-bill-run.service.js') + +describe('Cancel Bill Run service', () => { + let testBillRunId + + beforeEach(async () => { + await DatabaseHelper.clean() + + const { id: regionId } = await RegionHelper.add() + const billRun = await BillRunHelper.add({ + billRunNumber: 10101, + createdAt: new Date('2024-02-28'), + externalId: 'f54e53f0-37a0-400f-9f0e-bf8575c17668', + regionId, + status: 'ready' + }) + + testBillRunId = billRun.id + }) + + describe('when a bill with a matching ID exists', () => { + it('will fetch the data and format it for use in the cancel bill run page', async () => { + const result = await CancelBillRunService.go(testBillRunId) + + expect(result).to.equal({ + backLink: `/system/bill-runs/${testBillRunId}`, + billRunId: testBillRunId, + billRunNumber: 10101, + billRunStatus: 'ready', + billRunType: 'Supplementary', + chargeScheme: 'Current', + dateCreated: '28 February 2024', + financialYear: '2022 to 2023', + region: 'Avalon' + }) + }) + }) + + describe('when a bill run with a matching ID does not exist', () => { + it('throws an exception', async () => { + await expect(CancelBillRunService.go('testId')) + .to + .reject() + }) + }) +}) From 44a107c1a5a59942087f417db38d06e8673014bc Mon Sep 17 00:00:00 2001 From: Alan Cruikshanks Date: Fri, 1 Mar 2024 15:12:40 +0000 Subject: [PATCH 4/6] Add the view --- app/views/bill-runs/cancel.njk | 84 ++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 app/views/bill-runs/cancel.njk diff --git a/app/views/bill-runs/cancel.njk b/app/views/bill-runs/cancel.njk new file mode 100644 index 0000000000..c4a028ccb6 --- /dev/null +++ b/app/views/bill-runs/cancel.njk @@ -0,0 +1,84 @@ +{% extends 'layout.njk' %} +{% from "govuk/components/back-link/macro.njk" import govukBackLink %} +{% from "govuk/components/button/macro.njk" import govukButton %} +{% from "govuk/components/details/macro.njk" import govukDetails %} +{% from "govuk/components/summary-list/macro.njk" import govukSummaryList %} +{% from "govuk/components/table/macro.njk" import govukTable %} + +{% from "macros/badge.njk" import badge %} + +{% block breadcrumbs %} + {# Back link #} + {{ + govukBackLink({ + text: 'Back', + href: backLink + }) + }} +{% endblock %} + +{% block content %} + {# Main heading #} +
+

+ Bill run {{ billRunNumber }}{{ pageTitle }} +

+
+ +
+
+ + {# Status badge #} + {% if billRunStatus === 'ready' %} + {% set badgeType = 'info' %} + {% elif billRunStatus === 'sent' %} + {% set badgeType = 'success' %} + {% endif %} +

+ {{ badge(billRunStatus, badgeType) }} +

+ + {# Bill run meta-data #} + {# + GOV.UK summary lists only allow us to assign attributes at the top level and not to each row. This means we + can't assign our data-test attribute using the component. Our solution is to use the html option for each row + instead of text and wrap each value in a . That way we can manually assign our data-test attribute to the + span. + #} + {{ + govukSummaryList({ + classes: 'govuk-summary-list--no-border', + attributes: { + 'data-test': 'meta-data' + }, + rows: [ + { + key: { text: "Date created", classes: "meta-data__label" }, + value: { html: '' + dateCreated + '', classes: "meta-data__value" } + }, + { + key: { text: "Region", classes: "meta-data__label" }, + value: { html: '' + region + '', classes: "meta-data__value" } + }, + { + key: { text: "Bill run type", classes: "meta-data__label" }, + value: { html: '' + billRunType + '', classes: "meta-data__value" } + }, + { + key: { text: "Charge scheme", classes: "meta-data__label" }, + value: { html: '' + chargeScheme + '', classes: "meta-data__value" } + }, + { + key: { text: "Financial year", classes: "meta-data__label" }, + value: { html: '' + financialYear + '', classes: "meta-data__value" } + } + ] + }) + }} + +
+ {{ govukButton({ text: "Cancel bill run" }) }} +
+
+
+{% endblock %} From 9463b03eb5694870916c31e301003c84927bbc83 Mon Sep 17 00:00:00 2001 From: Alan Cruikshanks Date: Fri, 1 Mar 2024 15:13:52 +0000 Subject: [PATCH 5/6] Update controller and routes --- app/controllers/bill-runs.controller.js | 14 ++++++++++++++ app/routes/bill-runs.routes.js | 13 +++++++++++++ 2 files changed, 27 insertions(+) diff --git a/app/controllers/bill-runs.controller.js b/app/controllers/bill-runs.controller.js index a48d200762..be6f7974a3 100644 --- a/app/controllers/bill-runs.controller.js +++ b/app/controllers/bill-runs.controller.js @@ -7,12 +7,25 @@ const Boom = require('@hapi/boom') +const CancelBillRunService = require('../services/bill-runs/cancel-bill-run.service.js') const CreateBillRunValidator = require('../validators/create-bill-run.validator.js') const StartBillRunProcessService = require('../services/bill-runs/start-bill-run-process.service.js') const ViewBillRunService = require('../services/bill-runs/view-bill-run.service.js') const ReviewBillRunService = require('../services/bill-runs/two-part-tariff/review-bill-run.service.js') const ReviewLicenceService = require('../services/bill-runs/two-part-tariff/review-licence.service.js') +async function cancel (request, h) { + const { id } = request.params + + const pageData = await CancelBillRunService.go(id) + + return h.view('bill-runs/cancel.njk', { + pageTitle: "You're about to cancel this bill run", + activeNavBar: 'bill-runs', + ...pageData + }) +} + async function create (request, h) { const validatedData = CreateBillRunValidator.go(request.payload) @@ -80,6 +93,7 @@ function _formattedInitiateBillRunError (error) { } module.exports = { + cancel, create, review, reviewLicence, diff --git a/app/routes/bill-runs.routes.js b/app/routes/bill-runs.routes.js index 022b6ba554..c6be26b7c4 100644 --- a/app/routes/bill-runs.routes.js +++ b/app/routes/bill-runs.routes.js @@ -32,6 +32,19 @@ const routes = [ description: 'View a bill run' } }, + { + method: 'GET', + path: '/bill-runs/{id}/cancel', + handler: BillRunsController.cancel, + options: { + auth: { + access: { + scope: ['billing'] + } + }, + description: 'Confirm cancel a bill run' + } + }, { method: 'GET', path: '/bill-runs/{id}/review', From e993fbd855e35a54838e71fb76a05d2d26ef8f1e Mon Sep 17 00:00:00 2001 From: Alan Cruikshanks Date: Fri, 1 Mar 2024 15:15:04 +0000 Subject: [PATCH 6/6] Update bill runs page to use our new page --- app/views/bill-runs/view.njk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/bill-runs/view.njk b/app/views/bill-runs/view.njk index f972bf8b25..33efba17e7 100644 --- a/app/views/bill-runs/view.njk +++ b/app/views/bill-runs/view.njk @@ -116,7 +116,7 @@ {# Confirm and cancel buttons #} {% if billRunStatus === 'ready' %} - {% set cancelBillRunLink = '/billing/batch/' + billRunId + '/cancel' %} + {% set cancelBillRunLink = '/system/bill-runs/' + billRunId + '/cancel' %} {% set confirmBillRunLink = '/billing/batch/' + billRunId + '/confirm' %}