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/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/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', 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/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 %} 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' %}
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' + } + } +} 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() + }) + }) +})