diff --git a/app/controllers/bill-runs-review.controller.js b/app/controllers/bill-runs-review.controller.js new file mode 100644 index 0000000000..ecc06fa0f8 --- /dev/null +++ b/app/controllers/bill-runs-review.controller.js @@ -0,0 +1,210 @@ +'use strict' + +/** + * Controller for /bill-runs/review endpoints + * @module BillRunsReviewController + */ + +const AuthorisedService = require('../services/bill-runs/review/authorised.service.js') +const FactorsService = require('../services/bill-runs/review/factors.service.js') +const EditService = require('../services/bill-runs/review/edit.service.js') +const PreviewService = require('../services/bill-runs/review/preview.service.js') +const RemoveService = require('../services/bill-runs/review/remove.service.js') +const ReviewChargeElementService = require('../services/bill-runs/review/review-charge-element.service.js') +const ReviewChargeReferenceService = require('../services/bill-runs/review/review-charge-reference.service.js') +const ReviewBillRunService = require('../services/bill-runs/review/review-bill-run.service.js') +const ReviewLicenceService = require('../services/bill-runs/review/review-licence.service.js') +const SubmitAuthorisedService = require('../services/bill-runs/review/submit-authorised.service.js') +const SubmitEditService = require('..//services/bill-runs/review/submit-edit.service.js') +const SubmitFactorsService = require('../services/bill-runs/review/submit-factors.service.js') +const SubmitRemoveService = require('../services/bill-runs/review/submit-remove.service.js') +const SubmitReviewBillRunService = require('../services/bill-runs/review/submit-review-bill-run.service.js') +const SubmitReviewLicenceService = require('../services/bill-runs/review/submit-review-licence.service.js') + +async function authorised (request, h) { + const { reviewChargeReferenceId } = request.params + + const pageData = await AuthorisedService.go(reviewChargeReferenceId) + + return h.view('bill-runs/review/authorised.njk', { + activeNavBar: 'bill-runs', + ...pageData + }) +} + +async function edit (request, h) { + const { elementIndex, reviewChargeElementId } = request.params + + const pageData = await EditService.go(reviewChargeElementId, elementIndex) + + return h.view('bill-runs/review/edit.njk', { + activeNavBar: 'bill-runs', + ...pageData + }) +} + +async function factors (request, h) { + const { reviewChargeReferenceId } = request.params + + const pageData = await FactorsService.go(reviewChargeReferenceId) + + return h.view('bill-runs/review/factors.njk', { + activeNavBar: 'bill-runs', + ...pageData + }) +} + +async function preview (request, h) { + const { reviewChargeReferenceId } = request.params + + await PreviewService.go(reviewChargeReferenceId, request.yar) + + return h.redirect(`/system/bill-runs/review/charge-reference/${reviewChargeReferenceId}`) +} + +async function remove (request, h) { + const { reviewLicenceId } = request.params + + const pageData = await RemoveService.go(reviewLicenceId) + + return h.view('bill-runs/review/remove.njk', { + activeNavBar: 'bill-runs', + ...pageData + }) +} + +async function reviewBillRun (request, h) { + const { billRunId } = request.params + const { page } = request.query + + const pageData = await ReviewBillRunService.go(billRunId, page, request.yar) + + return h.view('bill-runs/review/review.njk', { + activeNavBar: 'bill-runs', + ...pageData + }) +} + +async function reviewChargeElement (request, h) { + const { elementIndex, reviewChargeElementId } = request.params + + const pageData = await ReviewChargeElementService.go(reviewChargeElementId, elementIndex, request.yar) + + return h.view('bill-runs/review/review-charge-element.njk', { + activeNavBar: 'bill-runs', + ...pageData + }) +} + +async function reviewChargeReference (request, h) { + const { reviewChargeReferenceId } = request.params + + const pageData = await ReviewChargeReferenceService.go(reviewChargeReferenceId, request.yar) + + return h.view('bill-runs/review/review-charge-reference.njk', { + activeNavBar: 'bill-runs', + ...pageData + }) +} + +async function reviewLicence (request, h) { + const { reviewLicenceId } = request.params + + const pageData = await ReviewLicenceService.go(reviewLicenceId, request.yar) + + return h.view('bill-runs/review/review-licence.njk', { + activeNavBar: 'bill-runs', + ...pageData + }) +} + +async function submitAuthorised (request, h) { + const { reviewChargeReferenceId } = request.params + const pageData = await SubmitAuthorisedService.go(reviewChargeReferenceId, request.yar, request.payload) + + if (pageData.error) { + return h.view('bill-runs/review/authorised.njk', { + activeNavBar: 'bill-runs', + ...pageData + }) + } + + return h.redirect(`/system/bill-runs/review/charge-reference/${reviewChargeReferenceId}`) +} + +async function submitEdit (request, h) { + const { elementIndex, reviewChargeElementId } = request.params + + const pageData = await SubmitEditService.go( + reviewChargeElementId, elementIndex, request.yar, request.payload + ) + + if (pageData.error) { + return h.view('bill-runs/review/edit.njk', { + activeNavBar: 'bill-runs', + ...pageData + }) + } + + return h.redirect(`/system/bill-runs/review/charge-element/${reviewChargeElementId}/${elementIndex}`) +} + +async function submitFactors (request, h) { + const { reviewChargeReferenceId } = request.params + const pageData = await SubmitFactorsService.go(reviewChargeReferenceId, request.yar, request.payload) + + if (pageData.error) { + return h.view('bill-runs/review/factors.njk', { + activeNavBar: 'bill-runs', + ...pageData + }) + } + + return h.redirect(`/system/bill-runs/review/charge-reference/${reviewChargeReferenceId}`) +} + +async function submitRemove (request, h) { + const { reviewLicenceId } = request.params + + const result = await SubmitRemoveService.go(reviewLicenceId, request.yar) + + if (result.empty) { + return h.redirect('/system/bill-runs') + } + + return h.redirect(`/system/bill-runs/review/${result.billRunId}`) +} + +async function submitReviewBillRun (request, h) { + const { billRunId } = request.params + + await SubmitReviewBillRunService.go(billRunId, request.payload, request.yar) + + return h.redirect(`/system/bill-runs/review/${billRunId}`) +} + +async function submitReviewLicence (request, h) { + const { reviewLicenceId } = request.params + + await SubmitReviewLicenceService.go(reviewLicenceId, request.yar, request.payload) + + return h.redirect(`/system/bill-runs/review/licence/${reviewLicenceId}`) +} + +module.exports = { + authorised, + edit, + factors, + preview, + remove, + reviewBillRun, + reviewChargeElement, + reviewChargeReference, + reviewLicence, + submitAuthorised, + submitEdit, + submitFactors, + submitRemove, + submitReviewBillRun, + submitReviewLicence +} diff --git a/app/controllers/bill-runs.controller.js b/app/controllers/bill-runs.controller.js index 9f62a49676..91b2fb7e56 100644 --- a/app/controllers/bill-runs.controller.js +++ b/app/controllers/bill-runs.controller.js @@ -7,65 +7,14 @@ const Boom = require('@hapi/boom') -const AmendAdjustmentFactorService = require('../services/bill-runs/two-part-tariff/amend-adjustment-factor.service.js') -const AmendAuthorisedVolumeService = require('../services/bill-runs/two-part-tariff/amend-authorised-volume.service.js') -const AmendBillableReturnsService = require('../services/bill-runs/two-part-tariff/amend-billable-returns.service.js') -const CalculateChargeService = require('../services/bill-runs/two-part-tariff/calculate-charge.service.js') const CancelBillRunService = require('../services/bill-runs/cancel-bill-run.service.js') -const ChargeReferenceDetailsService = require('../services/bill-runs/two-part-tariff/charge-reference-details.service.js') const GenerateBillRunService = require('../services/bill-runs/two-part-tariff/generate-bill-run.service.js') const IndexBillRunsService = require('../services/bill-runs/index-bill-runs.service.js') -const MatchDetailsService = require('../services/bill-runs/two-part-tariff/match-details.service.js') -const RemoveBillRunLicenceService = require('../services/bill-runs/two-part-tariff/remove-bill-run-licence.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') const SendBillRunService = require('../services/bill-runs/send-bill-run.service.js') -const SubmitAmendedAdjustmentFactorService = require('../services/bill-runs/two-part-tariff/submit-amended-adjustment-factor.service.js') -const SubmitAmendedAuthorisedVolumeService = require('../services/bill-runs/two-part-tariff/submit-amended-authorised-volume.service.js') -const SubmitAmendedBillableReturnsService = require('..//services/bill-runs/two-part-tariff/submit-amended-billable-returns.service.js') const SubmitCancelBillRunService = require('../services/bill-runs/submit-cancel-bill-run.service.js') -const SubmitRemoveBillRunLicenceService = require('../services/bill-runs/two-part-tariff/submit-remove-bill-run-licence.service.js') -const SubmitReviewBillRunService = require('../services/bill-runs/two-part-tariff/submit-review-bill-run.service.js') -const SubmitReviewLicenceService = require('../services/bill-runs/two-part-tariff/submit-review-licence.service.js') const SubmitSendBillRunService = require('../services/bill-runs/submit-send-bill-run.service.js') const ViewBillRunService = require('../services/bill-runs/view-bill-run.service.js') -async function amendAdjustmentFactor (request, h) { - const { id: billRunId, licenceId, reviewChargeReferenceId } = request.params - - const pageData = await AmendAdjustmentFactorService.go(billRunId, licenceId, reviewChargeReferenceId) - - return h.view('bill-runs/amend-adjustment-factor.njk', { - pageTitle: 'Set the adjustment factors', - activeNavBar: 'bill-runs', - ...pageData - }) -} - -async function amendAuthorisedVolume (request, h) { - const { id: billRunId, licenceId, reviewChargeReferenceId } = request.params - - const pageData = await AmendAuthorisedVolumeService.go(billRunId, licenceId, reviewChargeReferenceId) - - return h.view('bill-runs/amend-authorised-volume.njk', { - pageTitle: 'Set the authorised volume', - activeNavBar: 'bill-runs', - ...pageData - }) -} - -async function amendBillableReturns (request, h) { - const { id: billRunId, licenceId, reviewChargeElementId } = request.params - - const pageData = await AmendBillableReturnsService.go(billRunId, licenceId, reviewChargeElementId) - - return h.view('bill-runs/amend-billable-returns.njk', { - pageTitle: 'Set the billable returns quantity for this bill run', - activeNavBar: 'bill-runs', - ...pageData - }) -} - async function cancel (request, h) { const { id } = request.params @@ -78,18 +27,6 @@ async function cancel (request, h) { }) } -async function chargeReferenceDetails (request, h) { - const { id: billRunId, licenceId, reviewChargeReferenceId } = request.params - - const pageData = await ChargeReferenceDetailsService.go(billRunId, licenceId, reviewChargeReferenceId, request.yar) - - return h.view('bill-runs/charge-reference-details.njk', { - pageTitle: 'Charge reference details', - activeNavBar: 'bill-runs', - ...pageData - }) -} - async function index (request, h) { const { page } = request.query @@ -101,61 +38,6 @@ async function index (request, h) { }) } -async function matchDetails (request, h) { - const { id: billRunId, licenceId, reviewChargeElementId } = request.params - - const pageData = await MatchDetailsService.go(billRunId, licenceId, reviewChargeElementId, request.yar) - - return h.view('bill-runs/match-details.njk', { - pageTitle: 'View match details', - activeNavBar: 'bill-runs', - ...pageData - }) -} - -async function previewCharge (request, h) { - const { id: billRunId, licenceId, reviewChargeReferenceId } = request.params - - await CalculateChargeService.go(licenceId, reviewChargeReferenceId, request.yar) - - return h.redirect(`/system/bill-runs/${billRunId}/review/${licenceId}/charge-reference-details/${reviewChargeReferenceId}`) -} - -async function removeLicence (request, h) { - const { id: billRunId, licenceId } = request.params - - const pageData = await RemoveBillRunLicenceService.go(billRunId, licenceId) - - return h.view('bill-runs/remove-licence.njk', { - activeNavBar: 'bill-runs', - ...pageData - }) -} - -async function review (request, h) { - const { id } = request.params - const { page } = request.query - - const pageData = await ReviewBillRunService.go(id, page, request.yar) - - return h.view('bill-runs/review.njk', { - activeNavBar: 'bill-runs', - ...pageData - }) -} - -async function reviewLicence (request, h) { - const { id: billRunId, licenceId } = request.params - - const pageData = await ReviewLicenceService.go(billRunId, licenceId, request.yar) - - return h.view('bill-runs/review-licence.njk', { - pageTitle: `Licence ${pageData.licence.licenceRef}`, - activeNavBar: 'bill-runs', - ...pageData - }) -} - async function send (request, h) { const { id } = request.params @@ -168,58 +50,6 @@ async function send (request, h) { }) } -async function submitAmendedAdjustmentFactor (request, h) { - const { id: billRunId, licenceId, reviewChargeReferenceId } = request.params - const pageData = await SubmitAmendedAdjustmentFactorService.go( - billRunId, - licenceId, - reviewChargeReferenceId, - request.payload, - request.yar - ) - - if (pageData.error) { - return h.view('bill-runs/amend-adjustment-factor.njk', pageData) - } - - return h.redirect(`/system/bill-runs/${billRunId}/review/${licenceId}/charge-reference-details/${reviewChargeReferenceId}`) -} - -async function submitAmendedAuthorisedVolume (request, h) { - const { id: billRunId, licenceId, reviewChargeReferenceId } = request.params - const pageData = await SubmitAmendedAuthorisedVolumeService.go( - billRunId, - licenceId, - reviewChargeReferenceId, - request.payload, - request.yar - ) - - if (pageData.error) { - return h.view('bill-runs/amend-authorised-volume.njk', pageData) - } - - return h.redirect(`/system/bill-runs/${billRunId}/review/${licenceId}/charge-reference-details/${reviewChargeReferenceId}`) -} - -async function submitAmendedBillableReturns (request, h) { - const { id: billRunId, licenceId, reviewChargeElementId } = request.params - - const pageData = await SubmitAmendedBillableReturnsService.go( - billRunId, - licenceId, - reviewChargeElementId, - request.payload, - request.yar - ) - - if (pageData.error) { - return h.view('bill-runs/amend-billable-returns.njk', pageData) - } - - return h.redirect(`/system/bill-runs/${billRunId}/review/${licenceId}/match-details/${reviewChargeElementId}`) -} - async function submitCancel (request, h) { const { id } = request.params @@ -234,34 +64,6 @@ async function submitCancel (request, h) { } } -async function submitRemoveLicence (request, h) { - const { id: billRunId, licenceId } = request.params - - const allLicencesRemoved = await SubmitRemoveBillRunLicenceService.go(billRunId, licenceId, request.yar) - - if (allLicencesRemoved) { - return h.redirect('/system/bill-runs') - } - - return h.redirect(`/system/bill-runs/${billRunId}/review`) -} - -async function submitReview (request, h) { - const { id } = request.params - - await SubmitReviewBillRunService.go(id, request.payload, request.yar) - - return h.redirect(`/system/bill-runs/${id}/review`) -} - -async function submitReviewLicence (request, h) { - const { id: billRunId, licenceId } = request.params - - await SubmitReviewLicenceService.go(billRunId, licenceId, request.payload, request.yar) - - return h.redirect(`/system/bill-runs/${billRunId}/review/${licenceId}`) -} - async function submitSend (request, h) { const { id } = request.params @@ -304,25 +106,10 @@ async function view (request, h) { } module.exports = { - amendAdjustmentFactor, - amendAuthorisedVolume, - amendBillableReturns, cancel, - chargeReferenceDetails, index, - matchDetails, - previewCharge, - removeLicence, - review, - reviewLicence, send, - submitAmendedAdjustmentFactor, - submitAmendedAuthorisedVolume, - submitAmendedBillableReturns, submitCancel, - submitReview, - submitRemoveLicence, - submitReviewLicence, submitSend, twoPartTariff, view diff --git a/app/plugins/router.plugin.js b/app/plugins/router.plugin.js index 14cf4e1576..ce4a98cb7f 100644 --- a/app/plugins/router.plugin.js +++ b/app/plugins/router.plugin.js @@ -15,6 +15,7 @@ const AssetRoutes = require('../routes/assets.routes.js') const BillLicences = require('../routes/bill-licences.routes.js') const BillRoutes = require('../routes/bills.routes.js') const BillRunRoutes = require('../routes/bill-runs.routes.js') +const BillRunReviewRoutes = require('../routes/bill-runs-review.routes.js') const BillRunSetupRoutes = require('../routes/bill-runs-setup.routes.js') const BillingAccountRoutes = require('../routes/billing-accounts.routes.js') const CheckRoutes = require('../routes/check.routes.js') @@ -38,6 +39,7 @@ const routes = [ ...BillLicences, ...BillRoutes, ...BillRunRoutes, + ...BillRunReviewRoutes, ...BillRunSetupRoutes, ...BillingAccountRoutes, ...CheckRoutes, diff --git a/app/presenters/bill-runs/cancel-bill-run.presenter.js b/app/presenters/bill-runs/cancel-bill-run.presenter.js index e5c8c28397..35eed3da99 100644 --- a/app/presenters/bill-runs/cancel-bill-run.presenter.js +++ b/app/presenters/bill-runs/cancel-bill-run.presenter.js @@ -52,7 +52,7 @@ function _backLink (id, scheme, status) { return `/billing/batch/${id}/two-part-tariff-review` } - return `/system/bill-runs/${id}/review` + return `/system/bill-runs/review/${id}` } return `/system/bill-runs/${id}` diff --git a/app/presenters/bill-runs/index-bill-runs.presenter.js b/app/presenters/bill-runs/index-bill-runs.presenter.js index 2f1e503276..1dcd561ed2 100644 --- a/app/presenters/bill-runs/index-bill-runs.presenter.js +++ b/app/presenters/bill-runs/index-bill-runs.presenter.js @@ -68,7 +68,7 @@ function _link (billRunId, status, scheme) { return `/billing/batch/${billRunId}/two-part-tariff-review` } - return `/system/bill-runs/${billRunId}/review` + return `/system/bill-runs/review/${billRunId}` } return `/system/bill-runs/${billRunId}` diff --git a/app/presenters/bill-runs/review/authorised.presenter.js b/app/presenters/bill-runs/review/authorised.presenter.js new file mode 100644 index 0000000000..e3abf13869 --- /dev/null +++ b/app/presenters/bill-runs/review/authorised.presenter.js @@ -0,0 +1,40 @@ +'use strict' + +/** + * Formats the review charge reference data ready for presenting in the review charge reference authorised page + * @module AuthorisedPresenter + */ + +const { formatFinancialYear } = require('../../base.presenter.js') +const { calculateTotalBillableReturns, formatChargePeriod } = require('./base-review.presenter.js') + +/** + * Formats the review charge reference data ready for presenting in the review charge reference authorised page + * + * @param {module:ReviewChargeReference} reviewChargeReference - instance of the `ReviewChargeReferenceModel` + * returned from `FetchReviewChargeReferenceService` + * + * @returns {object} page date needed for the review charge reference factors page + */ +function go (reviewChargeReference) { + const { + amendedAuthorisedVolume, + chargeReference, + reviewChargeElements, + reviewChargeVersion, + id: reviewChargeReferenceId + } = reviewChargeReference + + return { + amendedAuthorisedVolume, + chargeDescription: chargeReference.chargeCategory.shortDescription, + chargePeriod: formatChargePeriod(reviewChargeVersion), + financialPeriod: formatFinancialYear(reviewChargeVersion.reviewLicence.billRun.toFinancialYearEnding), + reviewChargeReferenceId, + totalBillableReturns: calculateTotalBillableReturns(reviewChargeElements) + } +} + +module.exports = { + go +} diff --git a/app/presenters/bill-runs/review/base-review.presenter.js b/app/presenters/bill-runs/review/base-review.presenter.js new file mode 100644 index 0000000000..b72507bc36 --- /dev/null +++ b/app/presenters/bill-runs/review/base-review.presenter.js @@ -0,0 +1,264 @@ +'use strict' + +const Big = require('big.js') + +const { formatLongDate } = require('../../base.presenter.js') +const DetermineAbstractionPeriodService = require('../../../services/bill-runs/determine-abstraction-periods.service.js') + +/** + * Calculates the total allocated volume across all review change elements + * + * @param {module:ReviewChargeElementModel[]} reviewChargeElements - array of `ReviewChargeElementModel` instances + * + * @returns {string} the sum of allocated volume against all review charge elements without loss of precision + */ +function calculateTotalBillableReturns (reviewChargeElements) { + return reviewChargeElements.reduce((total, reviewChargeElement) => { + const { amendedAllocated } = reviewChargeElement + + return Big(total).plus(amendedAllocated).toNumber() + }, 0) +} + +/** + * Determine the link for a return, for example, should it go to the edit or view page? + * + * @param {module:ReviewReturnModel} reviewReturn - instance of `ReviewReturn` to determine the link for + * + * @returns {string} the relative URL the view template should use to link to the return + */ +function determineReturnLink (reviewReturn) { + const { returnId, returnStatus } = reviewReturn + + if (['due', 'received'].includes(returnStatus)) { + return `/return/internal?returnId=${returnId}` + } + + return `/returns/return?id=${returnId}` +} + +/** + * Extract and format the additional charges from a charge reference for display + * + * If the charge reference has both a supported source and is a public water company then the following is returned. + * + * ```javascript + * const additionalCharges = [ + * 'Supported source foo', + * 'Public Water Supply' + * ] + * ``` + * + * If it has known an empty array is returned + * + * @param {object} chargeReference - an object representing as 'Charge Reference' that has the properties + * `supportedSourceName` and `waterCompanyCharge`, taken from the charge reference's `additionalCharges` field + * + * @returns {string[]} the additional charges (if present) formatted as a string for display + */ +function formatAdditionalCharges (chargeReference) { + const { supportedSourceName, waterCompanyCharge } = chargeReference + + const additionalCharges = [] + + if (supportedSourceName) { + additionalCharges.push(`Supported source ${supportedSourceName}`) + } + + if (waterCompanyCharge) { + additionalCharges.push('Public Water Supply') + } + + return additionalCharges +} + +/** + * Extract and format the adjustments (excluding aggregate and charge adjustment) from a charge reference for display + * + * If the charge reference has adjustments, for example, an abatement and two-part tariff agreement then the following + * is returned. + * + * ```javascript + * const agreements = [ + * 'Abatement agreement 0.8', + * 'Two part tariff agreement' + * ] + * ``` + * + * We exclude aggregate and charge adjustment because the review screens have functionality to allow users to edit these + * values when they have been applied to a charge reference. Therefore they are handled separately. + * + * @param {module:ReviewChargeReferenceModel} reviewChargeReference - instance of `ReviewChargeReferenceModel` to format + * the adjustments for + * + * @returns {string[]} the adjustments (if present) formatted as a string for display + */ +function formatAdjustments (reviewChargeReference) { + const adjustments = [] + + if (reviewChargeReference.abatementAgreement && reviewChargeReference.abatementAgreement !== 1) { + adjustments.push(`Abatement agreement (${reviewChargeReference.abatementAgreement})`) + } + + if (reviewChargeReference.winterDiscount) { + adjustments.push('Winter discount') + } + + if (reviewChargeReference.twoPartTariffAgreement) { + adjustments.push('Two part tariff agreement') + } + + if (reviewChargeReference.canalAndRiverTrustAgreement) { + adjustments.push('Canal and River trust agreement') + } + + return adjustments +} + +/** + * Formats the charge period into its string variant, for example, '1 April 2023 to 10 October 2023' + * + * @param {module:ReviewChargeVersionModel} reviewChargeVersion - instance of `ReviewChargeVersionModel` to format the + * charge period for + * + * @returns {string} The review charge version's charge period formatted as a 'DD MMMM YYYY to DD MMMM YYYY' string + */ +function formatChargePeriod (reviewChargeVersion) { + const chargePeriod = _chargePeriod(reviewChargeVersion) + + return `${formatLongDate(chargePeriod.startDate)} to ${formatLongDate(chargePeriod.endDate)}` +} + +/** + * Determine the charge periods for a `ReviewChargeElementModel` and format them for display + * + * If the charge period is known, for example, the review licence presenter determines first and then passes it to + * various functions, then this can be provided. Else the function will assume the `ReviewChargeElementModel` instance + * contains a `reviewChargeReference` property that then contains a `reviewChargeVersion`. The charge period will be + * determined by extracting the start and end date from it. + * + * With the abstraction details and charge period determined, it calls `DetermineAbstractionPeriodService` to calculate + * the charge periods for the _element_. An element can have 1 or 2 charge periods, due to the way abstraction periods + * and financial years cross-over. + * + * With these calculated, they are passed through `formatLongDate()` and return for display in the UI. + * + * @param {module:ReviewChargeElementModel} reviewChargeElement - instance of `ReviewChargeElementModel` containing at + * least a `chargeElement` property populated with abstraction days and months + * @param {object} [chargePeriod] - The start and end date of the calculated charge period + * + * @returns {string[]} an array containing the review charge element's charge period(s) formatted as 'DD MMMM YYYY to DD + * MMMM YYYY' + */ +function formatChargePeriods (reviewChargeElement, chargePeriod = null) { + const { chargeElement, reviewChargeReference } = reviewChargeElement + + const { + abstractionPeriodStartDay, + abstractionPeriodStartMonth, + abstractionPeriodEndDay, + abstractionPeriodEndMonth + } = chargeElement + + if (!chargePeriod) { + chargePeriod = _chargePeriod(reviewChargeReference.reviewChargeVersion) + } + + const abstractionPeriods = DetermineAbstractionPeriodService.go( + chargePeriod, + abstractionPeriodStartDay, + abstractionPeriodStartMonth, + abstractionPeriodEndDay, + abstractionPeriodEndMonth + ) + + return abstractionPeriods.map((abstractionPeriod) => { + const { endDate, startDate } = abstractionPeriod + + return `${formatLongDate(startDate)} to ${formatLongDate(endDate)}` + }) +} + +/** + * Formats the issues held against review charge elements and returns for display + * + * TODO: Use NULL for no issues not an empty string + * + * An oversight when setting up the table/logic means 'issues' in `review_returns` and `review_charge_elements` is never + * null. If there are no issues we are setting it to an empty string not NULL. This means we always get a string back + * from the DB. + * + * @param {string} issues - the issues from the review charge element or return as a comma separated string + * + * @returns {string[]} the issues as a string array, else an empty array if issues is equal to '' + */ +function formatIssues (issues) { + if (issues === '') { + return [] + } + + return issues.split(', ') +} + +/** + * Format the status for a review return, for example, 'overdue' + * + * We cannot just return the status from the DB for a return because of the disparity between what we show and how the + * status is stored. For example, the status field in the DB holds completed, due, received, and void. When the review + * screens display the return we can assume anything with a status of `due` is overdue, hence the first disparity. + * + * The other is that if a return is under query this is not reflected in the status. Instead, a flag is set in a + * different field. + * + * @param {module:ReviewReturnModel} reviewReturn - instance of `ReviewReturn` to format the status for + * + * @returns {string} the return's status formatted for display + */ +function formatReturnStatus (reviewReturn) { + const { returnStatus, underQuery } = reviewReturn + + if (returnStatus === 'due') { + return 'overdue' + } + + if (underQuery) { + return 'query' + } + + return reviewReturn.returnStatus +} + +/** + * Format the total allocated vs quantity for a review return, for example, '15.5 ML / 20 ML' + * + * @param {module:ReviewReturnModel} reviewReturn - instance of `ReviewReturn` to format the totals for + * + * @returns the return's totals formatted for display + */ +function formatReturnTotals (reviewReturn) { + const { allocated, quantity, returnStatus } = reviewReturn + + if (['due', 'received'].includes(returnStatus)) { + return '/' + } + + return `${allocated} ML / ${quantity} ML` +} + +function _chargePeriod (reviewChargeVersion) { + const { chargePeriodStartDate, chargePeriodEndDate } = reviewChargeVersion + + return { startDate: chargePeriodStartDate, endDate: chargePeriodEndDate } +} + +module.exports = { + calculateTotalBillableReturns, + determineReturnLink, + formatAdditionalCharges, + formatAdjustments, + formatChargePeriod, + formatChargePeriods, + formatIssues, + formatReturnStatus, + formatReturnTotals +} diff --git a/app/presenters/bill-runs/review/edit.presenter.js b/app/presenters/bill-runs/review/edit.presenter.js new file mode 100644 index 0000000000..f3684a6dbc --- /dev/null +++ b/app/presenters/bill-runs/review/edit.presenter.js @@ -0,0 +1,58 @@ +'use strict' + +/** + * Formats the review charge element data ready for presenting in the review charge element edit page + * @module EditPresenter + */ + +const { formatFinancialYear } = require('../../base.presenter.js') +const { formatChargePeriod, formatChargePeriods } = require('./base-review.presenter.js') + +/** + * Prepares and processes bill run and review charge element data for presenting + * + * @param {module:ReviewChargeElement} reviewChargeElement - instance of the `ReviewChargeElementModel` + * returned from `FetchReviewChargeElementService` + * @param {number} elementIndex - the index of the element within all charge elements for the charge reference. This + * helps users relate which element they are reviewing to the one they selected on the review licence screen + * + * @returns {object} the prepared bill run and charge element data to be passed to the edit billable returns page + */ +function go (reviewChargeElement, elementIndex) { + const { + amendedAllocated: billableReturns, + chargeElement, + id: reviewChargeElementId, + reviewChargeReference + } = reviewChargeElement + + return { + authorisedQuantity: _authorisedQuantity(reviewChargeElement), + billableReturns, + chargeDescription: chargeElement.description, + chargePeriod: formatChargePeriod(reviewChargeReference.reviewChargeVersion), + chargePeriods: formatChargePeriods(reviewChargeElement), + elementIndex, + financialPeriod: formatFinancialYear( + reviewChargeReference.reviewChargeVersion.reviewLicence.billRun.toFinancialYearEnding + ), + reviewChargeElementId + } +} + +/** + * The user can only enter a volume on the billable returns that is less than the authorised volume. The authorised + * volume is either the authorised volume on the charge element or the authorised volume on the charge reference. + * Whichever is lower. + * + * @private + */ +function _authorisedQuantity (reviewChargeElement) { + const { chargeElement, reviewChargeReference } = reviewChargeElement + + return Math.min(chargeElement.authorisedAnnualQuantity, reviewChargeReference.amendedAuthorisedVolume) +} + +module.exports = { + go +} diff --git a/app/presenters/bill-runs/review/factors.presenter.js b/app/presenters/bill-runs/review/factors.presenter.js new file mode 100644 index 0000000000..5a560c9484 --- /dev/null +++ b/app/presenters/bill-runs/review/factors.presenter.js @@ -0,0 +1,44 @@ +'use strict' + +/** + * Formats the review charge reference data ready for presenting in the review charge reference factors page + * @module FactorsPresenter + */ + +const { formatFinancialYear } = require('../../base.presenter.js') +const { formatAdditionalCharges, formatChargePeriod, formatAdjustments } = require('./base-review.presenter.js') + +/** + * Formats the review charge reference data ready for presenting in the review charge reference factors page + * + * @param {module:ReviewChargeReference} reviewChargeReference - instance of the `ReviewChargeReferenceModel` + * returned from `FetchReviewChargeReferenceService` + * + * @returns {object} page date needed for the review charge reference factors page + */ +function go (reviewChargeReference) { + const { + amendedAggregate, + amendedChargeAdjustment, + chargeReference, + reviewChargeVersion, + id: reviewChargeReferenceId + } = reviewChargeReference + + const additionalCharges = formatAdditionalCharges(chargeReference) + const adjustments = formatAdjustments(reviewChargeReference) + + return { + amendedAggregate, + amendedChargeAdjustment, + chargeDescription: chargeReference.chargeCategory.shortDescription, + chargePeriod: formatChargePeriod(reviewChargeVersion), + financialPeriod: formatFinancialYear(reviewChargeVersion.reviewLicence.billRun.toFinancialYearEnding), + otherAdjustments: [...additionalCharges, ...adjustments], + reviewChargeReferenceId + } +} + +module.exports = { + go +} diff --git a/app/presenters/bill-runs/review/remove.presenter.js b/app/presenters/bill-runs/review/remove.presenter.js new file mode 100644 index 0000000000..c44e9800bb --- /dev/null +++ b/app/presenters/bill-runs/review/remove.presenter.js @@ -0,0 +1,35 @@ +'use strict' + +/** + * Formats the review licence data ready for presenting in the remove review licence confirmation page + * @module RemovePresenter + */ + +const { formatFinancialYear, formatLongDate } = require('../../base.presenter.js') + +/** + * Formats the review licence data ready for presenting in the remove review licence confirmation page + * + * @param {module:ReviewLicenceModel} reviewLicence - instance of the `ReviewLicenceModel` returned from + * `FetchRemoveReviewLicenceService` + * + * @returns {object} page date needed for the remove review licence confirmation page + */ +function go (reviewLicence) { + const { billRun, id: reviewLicenceId, licenceRef } = reviewLicence + const { billRunNumber, createdAt, region, status, toFinancialYearEnding } = billRun + + return { + billRunNumber, + billRunStatus: status, + dateCreated: formatLongDate(createdAt), + financialYearPeriod: formatFinancialYear(toFinancialYearEnding), + pageTitle: `You're about to remove ${licenceRef} from the bill run`, + region: region.displayName, + reviewLicenceId + } +} + +module.exports = { + go +} diff --git a/app/presenters/bill-runs/two-part-tariff/review-bill-run.presenter.js b/app/presenters/bill-runs/review/review-bill-run.presenter.js similarity index 99% rename from app/presenters/bill-runs/two-part-tariff/review-bill-run.presenter.js rename to app/presenters/bill-runs/review/review-bill-run.presenter.js index ef5b0b1bdc..811da853dd 100644 --- a/app/presenters/bill-runs/two-part-tariff/review-bill-run.presenter.js +++ b/app/presenters/bill-runs/review/review-bill-run.presenter.js @@ -73,7 +73,7 @@ function _prepareLicences (licences) { for (const licence of licences) { preparedLicences.push({ - id: licence.licenceId, + id: licence.id, licenceRef: licence.licenceRef, licenceHolder: licence.licenceHolder, issue: _getIssueOnLicence(licence.issues), diff --git a/app/presenters/bill-runs/review/review-charge-element.presenter.js b/app/presenters/bill-runs/review/review-charge-element.presenter.js new file mode 100644 index 0000000000..7aad97b289 --- /dev/null +++ b/app/presenters/bill-runs/review/review-charge-element.presenter.js @@ -0,0 +1,80 @@ +'use strict' + +/** + * Formats the review charge element data ready for presenting in the review charge element page + * @module ReviewChargeElementPresenter + */ + +const { formatAbstractionPeriod, formatFinancialYear, formatLongDate } = require('../../base.presenter.js') +const { + determineReturnLink, + formatChargePeriod, + formatChargePeriods, + formatIssues, + formatReturnStatus, + formatReturnTotals +} = require('./base-review.presenter.js') + +/** + * Formats the review charge element data ready for presenting in the review charge element page + * + * @param {module:ReviewChargeElement} reviewChargeElement - instance of the `ReviewChargeElementModel` + * returned from `FetchReviewChargeElementService` + * @param {number} elementIndex - the index of the element within all charge elements for the charge reference. This + * helps users relate which element they are reviewing to the one they selected on the review licence screen + * + * @returns {object} page date needed for the review charge element page + */ +function go (reviewChargeElement, elementIndex) { + const { + amendedAllocated: billableReturns, + chargeElement, + id: reviewChargeElementId, + issues, + reviewChargeReference, + status + } = reviewChargeElement + + return { + authorisedVolume: chargeElement.authorisedAnnualQuantity, + billableReturns, + chargeDescription: chargeElement.description, + chargePeriod: formatChargePeriod(reviewChargeReference.reviewChargeVersion), + chargePeriods: formatChargePeriods(reviewChargeElement), + elementCount: reviewChargeReference.reviewChargeElements.length, + elementIndex, + financialPeriod: formatFinancialYear( + reviewChargeReference.reviewChargeVersion.reviewLicence.billRun.toFinancialYearEnding + ), + issues: formatIssues(issues), + licenceId: reviewChargeReference.reviewChargeVersion.reviewLicence.licenceId, + matchedReturns: _matchedReturns(reviewChargeElement.reviewReturns), + reviewChargeElementId, + reviewLicenceId: reviewChargeReference.reviewChargeVersion.reviewLicence.id, + status + } +} + +function _matchedReturns (reviewReturns) { + return reviewReturns.map((reviewReturn) => { + const { description, endDate, issues, purposes, returnLog, returnId, returnReference, startDate } = reviewReturn + const { periodStartDay, periodStartMonth, periodEndDay, periodEndMonth } = returnLog + + return { + abstractionPeriod: formatAbstractionPeriod(periodStartDay, periodStartMonth, periodEndDay, periodEndMonth), + description, + issues: formatIssues(issues), + purpose: purposes[0].tertiary.description, + reference: returnReference, + returnId, + returnLink: determineReturnLink(reviewReturn), + returnPeriod: `${formatLongDate(startDate)} to ${formatLongDate(endDate)}`, + returnStatus: formatReturnStatus(reviewReturn), + returnTotal: formatReturnTotals(reviewReturn) + } + }) +} + +module.exports = { + go +} diff --git a/app/presenters/bill-runs/review/review-charge-reference.presenter.js b/app/presenters/bill-runs/review/review-charge-reference.presenter.js new file mode 100644 index 0000000000..42691310df --- /dev/null +++ b/app/presenters/bill-runs/review/review-charge-reference.presenter.js @@ -0,0 +1,78 @@ +'use strict' + +/** + * Formats the review charge reference data ready for presenting in the review charge reference page + * @module ReviewChargeReferencePresenter + */ + +const { formatFinancialYear } = require('../../base.presenter.js') +const { + calculateTotalBillableReturns, + formatAdditionalCharges, + formatAdjustments, + formatChargePeriod +} = require('./base-review.presenter.js') + +/** + * Formats the review charge reference data ready for presenting in the review charge reference page + * + * @param {module:ReviewChargeReferenceModel} reviewChargeReference - instance of the `ReviewChargeReferenceModel` + * returned from `FetchReviewChargeReferenceService` + * + * @returns {object} page date needed for the review charge reference page + */ +function go (reviewChargeReference) { + const { + amendedAuthorisedVolume, + chargeReference, + reviewChargeElements, + reviewChargeVersion, + id: reviewChargeReferenceId + } = reviewChargeReference + + const canAmend = _canAmend(reviewChargeReference) + const adjustments = formatAdjustments(reviewChargeReference) + const factors = _factors(reviewChargeReference, canAmend) + + return { + additionalCharges: formatAdditionalCharges(chargeReference).join(', '), + adjustments: [...factors, ...adjustments], + amendedAuthorisedVolume, + canAmend, + chargeCategory: chargeReference.chargeCategory.reference, + chargeDescription: chargeReference.chargeCategory.shortDescription, + chargePeriod: formatChargePeriod(reviewChargeVersion), + financialPeriod: formatFinancialYear(reviewChargeVersion.reviewLicence.billRun.toFinancialYearEnding), + reviewChargeReferenceId, + reviewLicenceId: reviewChargeVersion.reviewLicence.id, + totalBillableReturns: calculateTotalBillableReturns(reviewChargeElements) + } +} + +function _factors (reviewChargeReference, canAmend) { + const { + aggregate, + amendedAggregate, + amendedChargeAdjustment, + chargeAdjustment + } = reviewChargeReference + + const factors = [] + + if (canAmend) { + factors.push(`Aggregate factor (${amendedAggregate} / ${aggregate})`) + factors.push(`Charge adjustment (${amendedChargeAdjustment} / ${chargeAdjustment})`) + } + + return factors +} + +function _canAmend (reviewChargeReference) { + const { aggregate, chargeAdjustment } = reviewChargeReference + + return (aggregate !== 1 || chargeAdjustment !== 1) +} + +module.exports = { + go +} diff --git a/app/presenters/bill-runs/review/review-licence.presenter.js b/app/presenters/bill-runs/review/review-licence.presenter.js new file mode 100644 index 0000000000..bcd06cc9ae --- /dev/null +++ b/app/presenters/bill-runs/review/review-licence.presenter.js @@ -0,0 +1,204 @@ +'use strict' + +/** + * Formats the review licence data ready for presenting in the review licence page + * @module ReviewLicencePresenter + */ + +const { formatAbstractionPeriod, formatFinancialYear, formatLongDate } = require('../../base.presenter.js') +const { + calculateTotalBillableReturns, + determineReturnLink, + formatChargePeriod, + formatChargePeriods, + formatIssues, + formatReturnStatus, + formatReturnTotals +} = require('./base-review.presenter.js') + +/** + * Formats the review licence data ready for presenting in the review licence page + * + * @param {module:ReviewLicenceModel} reviewLicence - instance of the `ReviewLicenceModel` returned from + * `FetchReviewLicenceService` + * + * @returns {object} page date needed for the review licence page + */ +function go (reviewLicence) { + const { + billRun, + id: reviewLicenceId, + licenceHolder, + licenceId, + licenceRef, + progress, + reviewChargeVersions, + reviewReturns, + status + } = reviewLicence + const { matchedReturns, unmatchedReturns } = _formatReviewReturns(reviewReturns) + + return { + billRunId: billRun.id, + chargeVersions: _chargeVersions(reviewChargeVersions, billRun.toFinancialYearEnding), + elementsInReview: _elementsInReview(reviewChargeVersions), + licenceHolder, + licenceId, + licenceRef, + matchedReturns, + pageTitle: `Licence ${licenceRef}`, + progress, + region: billRun.region.displayName, + reviewLicenceId, + status, + unmatchedReturns + } +} + +function _billingAccountDetails (billingAccount) { + return { + billingAccountId: billingAccount.id, + accountNumber: billingAccount.accountNumber, + accountName: billingAccount.$accountName(), + contactName: billingAccount.$contactName(), + addressLines: billingAccount.$addressLines() + } +} + +function _chargeElements (reviewChargeElements, chargePeriod) { + const numberOfElements = reviewChargeElements.length + + return reviewChargeElements.map((reviewChargeElement, index) => { + const { amendedAllocated, chargeElement, id, issues, reviewReturns, status } = reviewChargeElement + + return { + billableReturns: `${amendedAllocated} ML / ${chargeElement.authorisedAnnualQuantity} ML`, + chargePeriods: formatChargePeriods(reviewChargeElement, chargePeriod), + returnVolumes: _chargeElementReturnVolumes(reviewReturns), + description: chargeElement.description, + elementCount: numberOfElements, + elementIndex: index + 1, + status, + id, + issues: formatIssues(issues), + purpose: chargeElement.purpose.description + } + }) +} + +function _chargeElementReturnVolumes (reviewReturns) { + return reviewReturns.map((reviewReturn) => { + const { quantity, returnReference, returnStatus } = reviewReturn + + if (returnStatus === 'due') { + return `overdue (${returnReference})` + } + + return `${quantity} ML (${returnReference})` + }) +} + +function _chargeReferences (reviewChargeReferences, chargePeriod) { + return reviewChargeReferences.map((reviewChargeReference) => { + const { amendedAuthorisedVolume, chargeReference, reviewChargeElements, id } = reviewChargeReference + const totalAllocated = calculateTotalBillableReturns(reviewChargeElements) + + return { + billableReturnsWarning: totalAllocated > amendedAuthorisedVolume, + chargeCategory: `Charge reference ${chargeReference.chargeCategory.reference}`, + chargeDescription: chargeReference.chargeCategory.shortDescription, + id, + chargeElements: _chargeElements(reviewChargeElements, chargePeriod), + chargeReferenceLinkTitle: _chargeReferenceLinkTitle(reviewChargeReference), + totalBillableReturns: `${totalAllocated} ML / ${amendedAuthorisedVolume} ML` + } + }) +} + +function _chargeReferenceLinkTitle (reviewChargeReference) { + const { aggregate, chargeAdjustment } = reviewChargeReference + + if (aggregate !== 1 || chargeAdjustment !== 1) { + return 'Change details' + } + + return 'View details' +} + +function _chargeVersionDescription (reviewChargeReferences) { + const referenceCount = reviewChargeReferences.length + const elementCount = reviewChargeReferences.reduce((total, reviewChargeReference) => { + return total + reviewChargeReference.reviewChargeElements.length + }, 0) + + const referenceText = referenceCount > 1 ? 'references' : 'reference' + const elementText = elementCount > 1 ? 'elements' : 'element' + + return `${referenceCount} charge ${referenceText} with ${elementCount} two-part tariff charge ${elementText}` +} + +function _chargeVersions (reviewChargeVersions, toFinancialYearEnding) { + return reviewChargeVersions.map((reviewChargeVersion) => { + const { chargePeriodStartDate, chargePeriodEndDate, chargeVersion, reviewChargeReferences } = reviewChargeVersion + const chargePeriod = { startDate: chargePeriodStartDate, endDate: chargePeriodEndDate } + + return { + billingAccountDetails: _billingAccountDetails(chargeVersion.billingAccount), + chargePeriod: formatChargePeriod(reviewChargeVersion), + chargeReferences: _chargeReferences(reviewChargeReferences, chargePeriod), + description: _chargeVersionDescription(reviewChargeReferences), + financialPeriod: formatFinancialYear(toFinancialYearEnding) + } + }) +} + +function _elementsInReview (reviewChargeVersions) { + // If the licence we are reviewing is linked to at least one charge element (via charge version -> charge reference -> + // charge element) that has a status of 'review' then the licence is said to have a status of 'REVIEW' + return reviewChargeVersions.some((reviewChargeVersion) => { + const { reviewChargeReferences } = reviewChargeVersion + + return reviewChargeReferences.some((reviewChargeReference) => { + const { reviewChargeElements } = reviewChargeReference + + return reviewChargeElements.some((reviewChargeElement) => { + return reviewChargeElement.status === 'review' + }) + }) + }) +} + +function _formatReviewReturns (reviewReturns) { + const matchedReturns = [] + const unmatchedReturns = [] + + reviewReturns.forEach((reviewReturn) => { + const { description, endDate, issues, purposes, returnLog, returnId, returnReference, startDate } = reviewReturn + const { periodStartDay, periodStartMonth, periodEndDay, periodEndMonth } = returnLog + + const formattedReviewReturn = { + abstractionPeriod: formatAbstractionPeriod(periodStartDay, periodStartMonth, periodEndDay, periodEndMonth), + description, + issues: formatIssues(issues), + purpose: purposes[0].tertiary.description, + reference: returnReference, + returnId, + returnLink: determineReturnLink(reviewReturn), + returnPeriod: `${formatLongDate(startDate)} to ${formatLongDate(endDate)}`, + returnStatus: formatReturnStatus(reviewReturn), + returnTotal: formatReturnTotals(reviewReturn) + } + + if (reviewReturn.reviewChargeElements.length > 0) { + matchedReturns.push(formattedReviewReturn) + } else { + unmatchedReturns.push(formattedReviewReturn) + } + }) + + return { matchedReturns, unmatchedReturns } +} + +module.exports = { + go +} diff --git a/app/presenters/bill-runs/two-part-tariff/amend-adjustment-factor.presenter.js b/app/presenters/bill-runs/two-part-tariff/amend-adjustment-factor.presenter.js deleted file mode 100644 index 8ba3294158..0000000000 --- a/app/presenters/bill-runs/two-part-tariff/amend-adjustment-factor.presenter.js +++ /dev/null @@ -1,79 +0,0 @@ -'use strict' - -/** - * Formats the two part tariff review data ready for presenting in the amend adjustment factor page - * @module AmendAdjustmentFactorPresenter - */ - -const { formatLongDate, formatFinancialYear } = require('../../base.presenter.js') - -/** - * Prepares and processes bill run and review charge reference data for presenting - * - * @param {module:BillRunModel} billRun - the data from the bill run - * @param {module:ReviewChargeReference} reviewChargeReference - the data from the review charge reference - * @param {string} licenceId - the UUID of the licence being reviewed - * - * @returns {object} the prepared bill run and charge reference data to be passed to the amend adjustment factor page - */ -function go (billRun, reviewChargeReference, licenceId) { - return { - billRunId: billRun.id, - licenceId, - financialYear: formatFinancialYear(billRun.toFinancialYearEnding), - chargePeriod: _prepareDate( - reviewChargeReference.reviewChargeVersion.chargePeriodStartDate, - reviewChargeReference.reviewChargeVersion.chargePeriodEndDate - ), - chargeReference: { - id: reviewChargeReference.id, - description: reviewChargeReference.chargeReference.chargeCategory.shortDescription, - aggregateFactor: reviewChargeReference.amendedAggregate === 0 ? '0' : reviewChargeReference.amendedAggregate, - chargeAdjustment: reviewChargeReference.amendedChargeAdjustment === 0 ? '0' : reviewChargeReference.amendedChargeAdjustment, - otherAdjustments: _otherAdjustments(reviewChargeReference) - } - } -} - -function _otherAdjustments (reviewChargeReference) { - const { supportedSourceName, waterCompanyCharge } = reviewChargeReference.chargeReference - - const adjustments = [] - - if (supportedSourceName) { - adjustments.push(`Supported source ${supportedSourceName}`) - } - - if (waterCompanyCharge) { - adjustments.push('Public Water Supply') - } - - if (reviewChargeReference.abatementAgreement !== 1) { - adjustments.push(`Abatement agreement (${reviewChargeReference.abatementAgreement})`) - } - - if (reviewChargeReference.winterDiscount) { - adjustments.push('Winter discount') - } - - if (reviewChargeReference.twoPartTariffAgreement) { - adjustments.push('Two part tariff agreement') - } - - if (reviewChargeReference.canalAndRiverTrustAgreement) { - adjustments.push('Canal and River trust agreement') - } - - return adjustments -} - -function _prepareDate (startDate, endDate) { - const preparedStartDate = formatLongDate(startDate) - const preparedEndDate = formatLongDate(endDate) - - return `${preparedStartDate} to ${preparedEndDate}` -} - -module.exports = { - go -} diff --git a/app/presenters/bill-runs/two-part-tariff/amend-authorised-volume.presenter.js b/app/presenters/bill-runs/two-part-tariff/amend-authorised-volume.presenter.js deleted file mode 100644 index 9cb18051f6..0000000000 --- a/app/presenters/bill-runs/two-part-tariff/amend-authorised-volume.presenter.js +++ /dev/null @@ -1,58 +0,0 @@ -'use strict' - -/** - * Formats the two part tariff review data ready for presenting in the amend authorised volume page - * @module AmendAuthorisedVolumePresenter - */ - -const { formatLongDate, formatFinancialYear } = require('../../base.presenter.js') - -/** - * Prepares and processes bill run and review charge reference data for presenting - * - * @param {module:BillRunModel} billRun - the data from the bill run - * @param {module:ReviewChargeReference} reviewChargeReference - the data from the review charge reference - * @param {string} licenceId - the UUID of the licence being reviewed - * - * @returns {object} the prepared bill run and charge reference data to be passed to the amend authorised volume page - */ -function go (billRun, reviewChargeReference, licenceId) { - return { - billRunId: billRun.id, - licenceId, - financialYear: formatFinancialYear(billRun.toFinancialYearEnding), - chargePeriod: _prepareDate( - reviewChargeReference.reviewChargeVersion.chargePeriodStartDate, - reviewChargeReference.reviewChargeVersion.chargePeriodEndDate - ), - chargeReference: { - id: reviewChargeReference.id, - description: reviewChargeReference.chargeReference.chargeCategory.shortDescription, - authorisedVolume: reviewChargeReference.amendedAuthorisedVolume, - totalBillableReturns: _totalBillableReturns(reviewChargeReference.reviewChargeElements) - }, - chargeCategory: { - minVolume: reviewChargeReference.chargeReference.chargeCategory.minVolume, - maxVolume: reviewChargeReference.chargeReference.chargeCategory.maxVolume - } - } -} - -function _prepareDate (startDate, endDate) { - const preparedStartDate = formatLongDate(startDate) - const preparedEndDate = formatLongDate(endDate) - - return `${preparedStartDate} to ${preparedEndDate}` -} - -function _totalBillableReturns (reviewChargeElements) { - return reviewChargeElements.reduce((total, reviewChargeElement) => { - total += reviewChargeElement.amendedAllocated - - return total - }, 0) -} - -module.exports = { - go -} diff --git a/app/presenters/bill-runs/two-part-tariff/amend-billable-returns.presenter.js b/app/presenters/bill-runs/two-part-tariff/amend-billable-returns.presenter.js deleted file mode 100644 index a725a738d8..0000000000 --- a/app/presenters/bill-runs/two-part-tariff/amend-billable-returns.presenter.js +++ /dev/null @@ -1,105 +0,0 @@ -'use strict' - -/** - * Formats the two part tariff review data ready for presenting in the edit billable returns page - * @module AmendBillableReturnsPresenter - */ - -const DetermineAbstractionPeriodService = require('../../../services/bill-runs/determine-abstraction-periods.service.js') -const { formatLongDate } = require('../../base.presenter.js') - -/** - * Prepares and processes bill run and review charge element data for presenting - * - * @param {module:BillRunModel} billRun - the data from the bill run - * @param {module:ReviewChargeElement} reviewChargeElement - the data from the review charge element - * @param {string} licenceId - the UUID of the licence being reviewed - * - * @returns {object} the prepared bill run and charge element data to be passed to the edit billable returns page - */ -function go (billRun, reviewChargeElement, licenceId) { - return { - chargeElement: { - description: reviewChargeElement.chargeElement.description, - dates: _prepareChargeElementDates( - reviewChargeElement.chargeElement, - reviewChargeElement.reviewChargeReference.reviewChargeVersion - ), - reviewChargeElementId: reviewChargeElement.id - }, - billRun: { - id: billRun.id, - financialYear: _financialYear(billRun.toFinancialYearEnding) - }, - chargeVersion: { - chargePeriod: _prepareDate( - reviewChargeElement.reviewChargeReference.reviewChargeVersion.chargePeriodStartDate, - reviewChargeElement.reviewChargeReference.reviewChargeVersion.chargePeriodEndDate - ) - }, - licenceId, - authorisedQuantity: _authorisedQuantity(reviewChargeElement) - } -} - -/** - * The user can only enter a volume on the billable returns that is less than the authorised volume. The authorised - * volume is either the authorised volume on the charge element or the authorised volume on the charge reference. - * Whichever is lower. - * - * @private - */ -function _authorisedQuantity (reviewChargeElement) { - const { chargeElement, reviewChargeReference } = reviewChargeElement - - return Math.min(chargeElement.authorisedAnnualQuantity, reviewChargeReference.amendedAuthorisedVolume) -} - -function _financialYear (financialYearEnding) { - const startYear = financialYearEnding - 1 - const endYear = financialYearEnding - - return `${startYear} to ${endYear}` -} - -function _prepareChargeElementDates (chargeElement, chargeVersion) { - const chargePeriod = { - startDate: chargeVersion.chargePeriodStartDate, - endDate: chargeVersion.chargePeriodEndDate - } - - const { - abstractionPeriodStartDay, - abstractionPeriodStartMonth, - abstractionPeriodEndDay, - abstractionPeriodEndMonth - } = chargeElement - - const abstractionPeriods = DetermineAbstractionPeriodService.go( - chargePeriod, - abstractionPeriodStartDay, - abstractionPeriodStartMonth, - abstractionPeriodEndDay, - abstractionPeriodEndMonth - ) - - const dates = [] - - // NOTE: There can be more than 1 abstraction period for an element, hence why we loop through them - abstractionPeriods.forEach((abstractionPeriod) => { - dates.push(_prepareDate(abstractionPeriod.startDate, abstractionPeriod.endDate)) - }) - - return dates -} - -function _prepareDate (startDate, endDate) { - const preparedStartDate = formatLongDate(startDate) - const preparedEndDate = formatLongDate(endDate) - - return `${preparedStartDate} to ${preparedEndDate}` -} - -module.exports = { - go -} diff --git a/app/presenters/bill-runs/two-part-tariff/charge-reference-details.presenter.js b/app/presenters/bill-runs/two-part-tariff/charge-reference-details.presenter.js deleted file mode 100644 index 6c1ce00ff5..0000000000 --- a/app/presenters/bill-runs/two-part-tariff/charge-reference-details.presenter.js +++ /dev/null @@ -1,126 +0,0 @@ -'use strict' - -/** - * Formats the review charge reference data ready for presenting in the charge reference details page - * @module ChargeReferenceDetailsPresenter - */ - -const Big = require('big.js') - -const { formatLongDate, formatFinancialYear } = require('../../base.presenter.js') - -/** - * Prepares and processes review charge reference data for presentation - * - * @param {module:BillRunModel} billRun - the data from the bill run - * @param {module:ReviewChargeReference} reviewChargeReference - the data from the review charge reference - * @param {string} licenceId - the UUID of the licence the charge reference is linked to - * - * @returns {object} the prepared bill run and charge reference data to be passed to the charge reference details page - */ -function go (billRun, reviewChargeReference, licenceId) { - const { hasAggregateOrChargeFactor, adjustments } = _adjustments(reviewChargeReference) - - return { - billRunId: billRun.id, - financialYear: formatFinancialYear(billRun.toFinancialYearEnding), - chargePeriod: _prepareDate( - reviewChargeReference.reviewChargeVersion.chargePeriodStartDate, - reviewChargeReference.reviewChargeVersion.chargePeriodEndDate - ), - chargeReference: { - id: reviewChargeReference.id, - reference: reviewChargeReference.chargeReference.chargeCategory.reference, - description: reviewChargeReference.chargeReference.chargeCategory.shortDescription, - totalBillableReturns: _totalBillableReturns(reviewChargeReference.reviewChargeElements), - authorisedVolume: reviewChargeReference.amendedAuthorisedVolume, - adjustments, - additionalCharges: _additionalCharges(reviewChargeReference.chargeReference) - }, - licenceId, - hasAggregateOrChargeFactor - } -} - -function _additionalCharges (chargeReference) { - const { supportedSourceName, waterCompanyCharge } = chargeReference - - const charges = [] - - if (supportedSourceName) { - charges.push(`Supported source ${supportedSourceName}`) - } - - if (waterCompanyCharge) { - charges.push('Public Water Supply') - } - - return charges.join(', ') -} - -function _adjustments (reviewChargeReference) { - const { - aggregate, - amendedAggregate, - chargeAdjustment, - amendedChargeAdjustment, - abatementAgreement, - winterDiscount, - twoPartTariffAgreement, - canalAndRiverTrustAgreement - } = reviewChargeReference - - const adjustments = [] - let hasAggregateOrChargeFactor = false - - if (amendedAggregate !== 1) { - adjustments.push(`Aggregate factor (${amendedAggregate})`) - hasAggregateOrChargeFactor = true - } - - if (amendedChargeAdjustment !== 1) { - adjustments.push(`Charge adjustment (${amendedChargeAdjustment})`) - hasAggregateOrChargeFactor = true - } - - if (abatementAgreement !== 1) { - adjustments.push(`Abatement agreement (${abatementAgreement})`) - } - - if (winterDiscount) { - adjustments.push('Winter discount') - } - - if (twoPartTariffAgreement) { - adjustments.push('Two part tariff agreement') - } - - if (canalAndRiverTrustAgreement) { - adjustments.push('Canal and River trust agreement') - } - - if (aggregate !== amendedAggregate || chargeAdjustment !== amendedChargeAdjustment) { - hasAggregateOrChargeFactor = true - } - - return { adjustments, hasAggregateOrChargeFactor } -} - -function _prepareDate (startDate, endDate) { - const preparedStartDate = formatLongDate(startDate) - const preparedEndDate = formatLongDate(endDate) - - return `${preparedStartDate} to ${preparedEndDate}` -} - -function _totalBillableReturns (reviewChargeElements) { - return reviewChargeElements.reduce((total, reviewChargeElement) => { - total = Big(total).plus(reviewChargeElement.amendedAllocated).toNumber() - - return total - }, 0) -} - -module.exports = { - go -} diff --git a/app/presenters/bill-runs/two-part-tariff/match-details.presenter.js b/app/presenters/bill-runs/two-part-tariff/match-details.presenter.js deleted file mode 100644 index 6b29fde8eb..0000000000 --- a/app/presenters/bill-runs/two-part-tariff/match-details.presenter.js +++ /dev/null @@ -1,143 +0,0 @@ -'use strict' - -/** - * Formats the charge element and returns data ready for presenting in the match details page - * @module MatchDetailsPresenter - */ - -const DetermineAbstractionPeriodService = require('../../../services/bill-runs/determine-abstraction-periods.service.js') -const { formatAbstractionPeriod, formatLongDate } = require('../../base.presenter.js') - -/** - * Prepares and processes bill run and review charge element and returns data for presentation - * - * @param {module:BillRunModel} billRun - the data from the bill run - * @param {module:ReviewChargeElement} reviewChargeElement - the data from the review charge element - * @param {string} licenceId - the UUID of the licence the charge element is linked to - * - * @returns {object} the prepared bill run and charge element data to be passed to the match details page - */ -function go (billRun, reviewChargeElement, licenceId) { - return { - billRunId: billRun.id, - financialYear: _financialYear(billRun.toFinancialYearEnding), - chargePeriod: _prepareDate( - reviewChargeElement.reviewChargeReference.reviewChargeVersion.chargePeriodStartDate, - reviewChargeElement.reviewChargeReference.reviewChargeVersion.chargePeriodEndDate - ), - licenceId, - chargeElement: { - chargeElementId: reviewChargeElement.id, - description: reviewChargeElement.chargeElement.description, - dates: _prepareChargeElementDates( - reviewChargeElement.chargeElement, - reviewChargeElement.reviewChargeReference.reviewChargeVersion - ), - status: reviewChargeElement.status, - billableVolume: reviewChargeElement.amendedAllocated, - authorisedVolume: reviewChargeElement.chargeElement.authorisedAnnualQuantity, - issues: reviewChargeElement.issues?.length > 0 ? reviewChargeElement.issues.split(', ') : [] - }, - matchedReturns: _matchedReturns(reviewChargeElement.reviewReturns) - } -} - -function _financialYear (financialYearEnding) { - const startYear = financialYearEnding - 1 - const endYear = financialYearEnding - - return `${startYear} to ${endYear}` -} - -function _prepareAbsPeriod (returnLog) { - const { periodStartDay, periodStartMonth, periodEndDay, periodEndMonth } = returnLog - - return formatAbstractionPeriod(periodStartDay, periodStartMonth, periodEndDay, periodEndMonth) -} - -function _matchedReturns (reviewReturns) { - const matchedReturns = [] - - reviewReturns.forEach((returnLog) => { - const { returnLink, returnTotal } = _returnLinkAndTotal(returnLog) - - matchedReturns.push({ - returnId: returnLog.returnId, - reference: returnLog.returnReference, - dates: _prepareDate(returnLog.startDate, returnLog.endDate), - purpose: returnLog.purposes[0]?.tertiary.description, - description: returnLog.description, - returnStatus: _returnStatus(returnLog), - returnTotal, - issues: returnLog.issues?.length > 0 ? returnLog.issues.split(', ') : [''], - returnLink, - absPeriod: _prepareAbsPeriod(returnLog.returnLog) - }) - }) - - return matchedReturns -} - -function _prepareChargeElementDates (chargeElement, chargeVersion) { - const chargePeriod = { - startDate: chargeVersion.chargePeriodStartDate, - endDate: chargeVersion.chargePeriodEndDate - } - - const { - abstractionPeriodStartDay, - abstractionPeriodStartMonth, - abstractionPeriodEndDay, - abstractionPeriodEndMonth - } = chargeElement - - const abstractionPeriods = DetermineAbstractionPeriodService.go( - chargePeriod, - abstractionPeriodStartDay, - abstractionPeriodStartMonth, - abstractionPeriodEndDay, - abstractionPeriodEndMonth - ) - - const dates = [] - - // NOTE: There can be more than 1 abstraction period for an element, hence why we loop through them - abstractionPeriods.forEach((abstractionPeriod) => { - dates.push(_prepareDate(abstractionPeriod.startDate, abstractionPeriod.endDate)) - }) - - return dates -} - -function _prepareDate (startDate, endDate) { - const preparedStartDate = formatLongDate(startDate) - const preparedEndDate = formatLongDate(endDate) - - return `${preparedStartDate} to ${preparedEndDate}` -} - -function _returnLinkAndTotal (returnLog) { - const { returnStatus, allocated, quantity } = returnLog - - if (['due', 'received'].includes(returnStatus)) { - return { returnTotal: '/', returnLink: `/return/internal?returnId=${returnLog.returnId}` } - } - - return { returnTotal: `${allocated} ML / ${quantity} ML`, returnLink: `/returns/return?id=${returnLog.returnId}` } -} - -function _returnStatus (returnLog) { - if (returnLog.returnStatus === 'due') { - return 'overdue' - } - - if (returnLog.underQuery) { - return 'query' - } - - return returnLog.returnStatus -} - -module.exports = { - go -} diff --git a/app/presenters/bill-runs/two-part-tariff/remove-bill-run-licence.presenter.js b/app/presenters/bill-runs/two-part-tariff/remove-bill-run-licence.presenter.js deleted file mode 100644 index 2586b0de55..0000000000 --- a/app/presenters/bill-runs/two-part-tariff/remove-bill-run-licence.presenter.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict' - -/** - * Formats the data ready for presenting in the remove bill run licence confirmation page - * @module RemoveBillRunLicencePresenter - */ - -const { formatFinancialYear, formatLongDate } = require('../../base.presenter.js') - -/** - * Formats the data ready for presenting in the remove bill run licence confirmation page - * - * @param {module:BillRunModel} billRun - an instance of `BillRunModel` - * @param {string} licenceId - UUID of the licence to remove from the bill run - * @param {string} licenceRef - the licence reference of the licence to remove from the bill run - * - * @returns {object} - the prepared data to be passed to the remove licence template - */ -function go (billRun, licenceId, licenceRef) { - const { billRunNumber, createdAt, region, status, toFinancialYearEnding } = billRun - - return { - pageTitle: `You're about to remove ${licenceRef} from the bill run`, - backLink: `../review/${licenceId}`, - billRunNumber, - billRunStatus: status, - dateCreated: formatLongDate(createdAt), - financialYear: formatFinancialYear(toFinancialYearEnding), - region - } -} - -module.exports = { - go -} diff --git a/app/presenters/bill-runs/two-part-tariff/review-licence.presenter.js b/app/presenters/bill-runs/two-part-tariff/review-licence.presenter.js deleted file mode 100644 index c2cce71743..0000000000 --- a/app/presenters/bill-runs/two-part-tariff/review-licence.presenter.js +++ /dev/null @@ -1,292 +0,0 @@ -'use strict' - -/** - * Formats the review licence data ready for presenting in the review licence page - * @module ReviewLicencePresenter - */ - -const Big = require('big.js') - -const DetermineAbstractionPeriodService = require('../../../services/bill-runs/determine-abstraction-periods.service.js') -const { formatAbstractionPeriod, formatLongDate } = require('../../base.presenter.js') - -/** - * Formats the review licence data ready for presenting in the review licence page - * - * @param {module:BillRunModel} billRun - the data from the bill run - * @param {module:ReviewLicenceModel} licence - the data from review licence - * - * @returns {object} the prepared bill run and licence data to be passed to the review licence page - */ -function go (billRun, licence) { - return { - billRunId: billRun.id, - region: billRun.region.displayName, - licence: { - licenceId: licence[0].licenceId, - licenceRef: licence[0].licenceRef, - status: licence[0].status, - licenceHolder: licence[0].licenceHolder, - progress: licence[0].progress - }, - elementsInReview: licence[0].hasReviewStatus, - matchedReturns: _matchedReturns(licence[0].reviewReturns), - unmatchedReturns: _unmatchedReturns(licence[0].reviewReturns), - chargeData: _prepareChargeData(licence, billRun) - } -} - -function _billingAccountDetails (billingAccount) { - return { - billingAccountId: billingAccount.id, - accountNumber: billingAccount.accountNumber, - accountName: billingAccount.$accountName(), - contactName: billingAccount.$contactName(), - addressLines: billingAccount.$addressLines() - } -} - -function _chargeElementCount (reviewChargeVersion) { - const { reviewChargeReferences } = reviewChargeVersion - - const chargeElementCount = reviewChargeReferences.reduce((total, reviewChargeReference) => { - return total + reviewChargeReference.reviewChargeElements.length - }, 0) - - return chargeElementCount -} - -function _chargeElementDetails (reviewChargeReference, chargePeriod) { - const { reviewChargeElements } = reviewChargeReference - - const chargeElements = reviewChargeElements.map((reviewChargeElement, index) => { - return { - reviewChargeElementId: reviewChargeElement.id, - elementNumber: `Element ${index + 1} of ${reviewChargeElements.length}`, - elementStatus: reviewChargeElement.status, - elementDescription: reviewChargeElement.chargeElement.description, - dates: _prepareChargeElementDates(reviewChargeElement.chargeElement, chargePeriod), - purpose: reviewChargeElement.chargeElement.purpose.description, - issues: reviewChargeElement.issues.length > 0 ? reviewChargeElement.issues.split(', ') : [''], - billableReturns: `${reviewChargeElement.amendedAllocated} ML / ${reviewChargeElement.chargeElement.authorisedAnnualQuantity} ML`, - returnVolume: _prepareReturnVolume(reviewChargeElement) - } - }) - - return chargeElements -} - -function _chargeReferenceDetails (reviewChargeVersion, chargePeriod) { - const chargeReference = [] - - const { reviewChargeReferences } = reviewChargeVersion - - reviewChargeReferences.forEach((reviewChargeReference) => { - const totalBillableReturns = _totalBillableReturns(reviewChargeReference) - - chargeReference.push({ - id: reviewChargeReference.id, - chargeCategory: `Charge reference ${reviewChargeReference.chargeReference.chargeCategory.reference}`, - chargeDescription: reviewChargeReference.chargeReference.chargeCategory.shortDescription, - totalBillableReturns: `${totalBillableReturns} ML / ${reviewChargeReference.amendedAuthorisedVolume} ML`, - billableReturnsWarning: totalBillableReturns > reviewChargeReference.amendedAuthorisedVolume, - chargeReferenceLink: _chargeReferenceLink(reviewChargeReference), - chargeElements: _chargeElementDetails(reviewChargeReference, chargePeriod) - }) - }) - - return chargeReference -} - -function _chargeReferenceLink (reviewChargeReference) { - const { chargeAdjustment, aggregate } = reviewChargeReference - - if (chargeAdjustment !== 1 || aggregate !== 1) { - return { linkName: 'Change details' } - } - - return { linkName: 'View details' } -} - -function _financialYear (financialYearEnding) { - const startYear = financialYearEnding - 1 - const endYear = financialYearEnding - - return `${startYear} to ${endYear}` -} - -function _matchedReturns (returnLogs) { - const matchedReturns = [] - - returnLogs.forEach((returnLog) => { - if (returnLog.reviewChargeElements.length > 0) { - matchedReturns.push( - { - returnId: returnLog.returnId, - reference: returnLog.returnReference, - dates: _prepareDate(returnLog.startDate, returnLog.endDate), - returnStatus: _returnStatus(returnLog), - description: returnLog.description, - purpose: returnLog.purposes[0].tertiary.description, - returnTotal: _returnTotal(returnLog), - issues: returnLog.issues.length > 0 ? returnLog.issues.split(', ') : [''], - returnLink: _returnLink(returnLog), - absPeriod: _prepareAbsPeriod(returnLog.returnLog) - } - ) - } - }) - - return matchedReturns -} - -function _prepareAbsPeriod (returnLog) { - const { periodStartDay, periodStartMonth, periodEndDay, periodEndMonth } = returnLog - - return formatAbstractionPeriod(periodStartDay, periodStartMonth, periodEndDay, periodEndMonth) -} - -function _prepareChargeData (licence, billRun) { - const chargeData = [] - - licence[0].reviewChargeVersions.forEach((reviewChargeVersion) => { - const chargePeriod = { - startDate: reviewChargeVersion.chargePeriodStartDate, - endDate: reviewChargeVersion.chargePeriodEndDate - } - - chargeData.push({ - financialYear: _financialYear(billRun.toFinancialYearEnding), - chargePeriodDate: _prepareDate( - reviewChargeVersion.chargePeriodStartDate, - reviewChargeVersion.chargePeriodEndDate - ), - chargeElementCount: _chargeElementCount(reviewChargeVersion), - billingAccountDetails: _billingAccountDetails(reviewChargeVersion.billingAccountDetails), - chargeReferences: _chargeReferenceDetails(reviewChargeVersion, chargePeriod) - }) - }) - - return chargeData -} - -function _prepareChargeElementDates (chargeElement, chargePeriod) { - const { - abstractionPeriodStartDay, - abstractionPeriodStartMonth, - abstractionPeriodEndDay, - abstractionPeriodEndMonth - } = chargeElement - - const abstractionPeriods = DetermineAbstractionPeriodService.go( - chargePeriod, - abstractionPeriodStartDay, - abstractionPeriodStartMonth, - abstractionPeriodEndDay, - abstractionPeriodEndMonth - ) - - const dates = [] - - // NOTE: There can be more than 1 abstraction period for an element, hence why we loop through them - abstractionPeriods.forEach((abstractionPeriod) => { - dates.push(_prepareDate(abstractionPeriod.startDate, abstractionPeriod.endDate)) - }) - - return dates -} - -function _prepareDate (startDate, endDate) { - const preparedStartDate = formatLongDate(startDate) - const preparedEndDate = formatLongDate(endDate) - - return `${preparedStartDate} to ${preparedEndDate}` -} - -function _prepareReturnVolume (reviewChargeElement) { - const { reviewReturns } = reviewChargeElement - const returnVolumes = [] - - if (reviewReturns) { - reviewReturns.forEach((reviewReturn) => { - if (reviewReturn.returnStatus === 'due') { - returnVolumes.push(`overdue (${reviewReturn.returnReference})`) - } else { - returnVolumes.push(`${reviewReturn.quantity} ML (${reviewReturn.returnReference})`) - } - }) - } - - return returnVolumes -} - -function _returnLink (returnLog) { - if (['due', 'received'].includes(returnLog.returnStatus)) { - return `/return/internal?returnId=${returnLog.returnId}` - } - - return `/returns/return?id=${returnLog.returnId}` -} - -function _returnStatus (returnLog) { - if (returnLog.returnStatus === 'due') { - return 'overdue' - } - - if (returnLog.underQuery) { - return 'query' - } - - return returnLog.returnStatus -} - -function _returnTotal (returnLog) { - const { returnStatus, allocated, quantity } = returnLog - - if (['due', 'received'].includes(returnStatus)) { - return '/' - } - - return `${allocated} ML / ${quantity} ML` -} - -function _totalBillableReturns (reviewChargeReference) { - let totalBillableReturns = 0 - - reviewChargeReference.reviewChargeElements.forEach((reviewChargeElement) => { - totalBillableReturns = Big(totalBillableReturns).plus(reviewChargeElement.amendedAllocated).toNumber() - }) - - return totalBillableReturns -} - -function _unmatchedReturns (returnLogs) { - const unmatchedReturns = [] - - returnLogs.forEach((returnLog) => { - // If the reviewChargeElement length is less than 1 it means the return did not match to a charge element and - // therefore belongs in the unmatchedReturns section - if (returnLog.reviewChargeElements.length < 1) { - unmatchedReturns.push( - { - returnId: returnLog.returnId, - reference: returnLog.returnReference, - dates: _prepareDate(returnLog.startDate, returnLog.endDate), - returnStatus: _returnStatus(returnLog), - description: returnLog.description, - purpose: returnLog.purposes[0].tertiary.description, - returnTotal: `${returnLog.allocated} / ${returnLog.quantity} ML`, - issues: returnLog.issues.length > 0 ? returnLog.issues.split(', ') : [''], - returnLink: _returnLink(returnLog), - absPeriod: _prepareAbsPeriod(returnLog.returnLog) - } - ) - } - }) - - return unmatchedReturns -} - -module.exports = { - go -} diff --git a/app/routes/bill-runs-review.routes.js b/app/routes/bill-runs-review.routes.js new file mode 100644 index 0000000000..c1a47bf9b6 --- /dev/null +++ b/app/routes/bill-runs-review.routes.js @@ -0,0 +1,188 @@ +'use strict' + +const BillRunsReviewController = require('../controllers/bill-runs-review.controller.js') + +const routes = [ + { + method: 'GET', + path: '/bill-runs/review/{billRunId}', + options: { + handler: BillRunsReviewController.reviewBillRun, + auth: { + access: { + scope: ['billing'] + } + } + } + }, + { + method: 'POST', + path: '/bill-runs/review/{billRunId}', + options: { + handler: BillRunsReviewController.submitReviewBillRun, + auth: { + access: { + scope: ['billing'] + } + } + } + }, + { + method: 'GET', + path: '/bill-runs/review/charge-element/{reviewChargeElementId}/{elementIndex}', + options: { + handler: BillRunsReviewController.reviewChargeElement, + auth: { + access: { + scope: ['billing'] + } + } + } + }, + { + method: 'GET', + path: '/bill-runs/review/charge-element/{reviewChargeElementId}/{elementIndex}/edit', + options: { + handler: BillRunsReviewController.edit, + auth: { + access: { + scope: ['billing'] + } + } + } + }, + { + method: 'POST', + path: '/bill-runs/review/charge-element/{reviewChargeElementId}/{elementIndex}/edit', + options: { + handler: BillRunsReviewController.submitEdit, + auth: { + access: { + scope: ['billing'] + } + } + } + }, + { + method: 'GET', + path: '/bill-runs/review/charge-reference/{reviewChargeReferenceId}', + options: { + handler: BillRunsReviewController.reviewChargeReference, + auth: { + access: { + scope: ['billing'] + } + } + } + }, + { + method: 'GET', + path: '/bill-runs/review/charge-reference/{reviewChargeReferenceId}/authorised', + options: { + handler: BillRunsReviewController.authorised, + auth: { + access: { + scope: ['billing'] + } + } + } + }, + { + method: 'POST', + path: '/bill-runs/review/charge-reference/{reviewChargeReferenceId}/authorised', + options: { + handler: BillRunsReviewController.submitAuthorised, + auth: { + access: { + scope: ['billing'] + } + } + } + }, + { + method: 'GET', + path: '/bill-runs/review/charge-reference/{reviewChargeReferenceId}/factors', + options: { + handler: BillRunsReviewController.factors, + auth: { + access: { + scope: ['billing'] + } + } + } + }, + { + method: 'POST', + path: '/bill-runs/review/charge-reference/{reviewChargeReferenceId}/factors', + options: { + handler: BillRunsReviewController.submitFactors, + auth: { + access: { + scope: ['billing'] + } + } + } + }, + { + method: 'GET', + path: '/bill-runs/review/charge-reference/{reviewChargeReferenceId}/preview', + options: { + handler: BillRunsReviewController.preview, + auth: { + access: { + scope: ['billing'] + } + } + } + }, + { + method: 'GET', + path: '/bill-runs/review/licence/{reviewLicenceId}', + options: { + handler: BillRunsReviewController.reviewLicence, + auth: { + access: { + scope: ['billing'] + } + } + } + }, + { + method: 'POST', + path: '/bill-runs/review/licence/{reviewLicenceId}', + options: { + handler: BillRunsReviewController.submitReviewLicence, + auth: { + access: { + scope: ['billing'] + } + } + } + }, + { + method: 'GET', + path: '/bill-runs/review/licence/{reviewLicenceId}/remove', + options: { + handler: BillRunsReviewController.remove, + auth: { + access: { + scope: ['billing'] + } + } + } + }, + { + method: 'POST', + path: '/bill-runs/review/licence/{reviewLicenceId}/remove', + options: { + handler: BillRunsReviewController.submitRemove, + auth: { + access: { + scope: ['billing'] + } + } + } + } +] + +module.exports = routes diff --git a/app/routes/bill-runs.routes.js b/app/routes/bill-runs.routes.js index 598e9f7e9d..0822eaa584 100644 --- a/app/routes/bill-runs.routes.js +++ b/app/routes/bill-runs.routes.js @@ -54,186 +54,6 @@ const routes = [ } } }, - { - method: 'GET', - path: '/bill-runs/{id}/remove/{licenceId}', - options: { - handler: BillRunsController.removeLicence, - auth: { - access: { - scope: ['billing'] - } - } - } - }, - { - method: 'POST', - path: '/bill-runs/{id}/remove/{licenceId}', - options: { - handler: BillRunsController.submitRemoveLicence, - auth: { - access: { - scope: ['billing'] - } - } - } - }, - { - method: 'GET', - path: '/bill-runs/{id}/review', - options: { - handler: BillRunsController.review, - auth: { - access: { - scope: ['billing'] - } - } - } - }, - { - method: 'POST', - path: '/bill-runs/{id}/review', - options: { - handler: BillRunsController.submitReview, - auth: { - access: { - scope: ['billing'] - } - } - } - }, - { - method: 'GET', - path: '/bill-runs/{id}/review/{licenceId}', - options: { - handler: BillRunsController.reviewLicence, - auth: { - access: { - scope: ['billing'] - } - } - } - }, - { - method: 'POST', - path: '/bill-runs/{id}/review/{licenceId}', - options: { - handler: BillRunsController.submitReviewLicence, - auth: { - access: { - scope: ['billing'] - } - } - } - }, - { - method: 'GET', - path: '/bill-runs/{id}/review/{licenceId}/charge-reference-details/{reviewChargeReferenceId}', - options: { - handler: BillRunsController.chargeReferenceDetails, - auth: { - access: { - scope: ['billing'] - } - } - } - }, - { - method: 'GET', - path: '/bill-runs/{id}/review/{licenceId}/charge-reference-details/{reviewChargeReferenceId}/amend-authorised-volume', - options: { - handler: BillRunsController.amendAuthorisedVolume, - auth: { - access: { - scope: ['billing'] - } - } - } - }, - { - method: 'POST', - path: '/bill-runs/{id}/review/{licenceId}/charge-reference-details/{reviewChargeReferenceId}/amend-authorised-volume', - options: { - handler: BillRunsController.submitAmendedAuthorisedVolume, - auth: { - access: { - scope: ['billing'] - } - } - } - }, - { - method: 'GET', - path: '/bill-runs/{id}/review/{licenceId}/charge-reference-details/{reviewChargeReferenceId}/amend-adjustment-factor', - options: { - handler: BillRunsController.amendAdjustmentFactor, - auth: { - access: { - scope: ['billing'] - } - } - } - }, - { - method: 'POST', - path: '/bill-runs/{id}/review/{licenceId}/charge-reference-details/{reviewChargeReferenceId}/amend-adjustment-factor', - options: { - handler: BillRunsController.submitAmendedAdjustmentFactor, - auth: { - access: { - scope: ['billing'] - } - } - } - }, - { - method: 'GET', - path: '/bill-runs/{id}/review/{licenceId}/match-details/{reviewChargeElementId}', - options: { - handler: BillRunsController.matchDetails, - auth: { - access: { - scope: ['billing'] - } - } - } - }, - { - method: 'GET', - path: '/bill-runs/{id}/review/{licenceId}/match-details/{reviewChargeElementId}/amend-billable-returns', - options: { - handler: BillRunsController.amendBillableReturns, - auth: { - access: { - scope: ['billing'] - } - } - } - }, - { - method: 'POST', - path: '/bill-runs/{id}/review/{licenceId}/match-details/{reviewChargeElementId}/amend-billable-returns', - options: { - handler: BillRunsController.submitAmendedBillableReturns, - auth: { - access: { - scope: ['billing'] - } - } - } - }, - { - method: 'GET', - path: '/bill-runs/{id}/review/{licenceId}/preview-charge/{reviewChargeReferenceId}', - options: { - handler: BillRunsController.previewCharge, - auth: { - access: { - scope: ['billing'] - } - } - } - }, { method: 'GET', path: '/bill-runs/{id}/send', diff --git a/app/services/bill-runs/review/authorised.service.js b/app/services/bill-runs/review/authorised.service.js new file mode 100644 index 0000000000..c762837a17 --- /dev/null +++ b/app/services/bill-runs/review/authorised.service.js @@ -0,0 +1,32 @@ +'use strict' + +/** + * Orchestrates fetching and presenting the data needed for the review charge reference authorised page + * @module AuthorisedService + */ + +const AuthorisedPresenter = require('../../../presenters/bill-runs/review/authorised.presenter.js') +const FetchReviewChargeReferenceService = require('./fetch-review-charge-reference.service.js') + +/** + * Orchestrates fetching and presenting the data needed for the review charge reference authorised page + * + * @param {string} reviewChargeReferenceId - The UUID of the review charge reference whose authorised volume is being + * amended + * + * @returns {Promise} the 'pageData' needed to view the amend authorised volume page + */ +async function go (reviewChargeReferenceId) { + const reviewChargeReference = await FetchReviewChargeReferenceService.go(reviewChargeReferenceId) + + const pageData = AuthorisedPresenter.go(reviewChargeReference) + + return { + pageTitle: 'Set the authorised volume', + ...pageData + } +} + +module.exports = { + go +} diff --git a/app/services/bill-runs/review/edit.service.js b/app/services/bill-runs/review/edit.service.js new file mode 100644 index 0000000000..6ee8b3b0da --- /dev/null +++ b/app/services/bill-runs/review/edit.service.js @@ -0,0 +1,33 @@ +'use strict' + +/** + * Orchestrates fetching and presenting the data needed for the amend billable returns page + * @module EditService + */ + +const EditPresenter = require('../../../presenters/bill-runs/review/edit.presenter.js') +const FetchReviewChargeElementService = require('./fetch-review-charge-element.service.js') + +/** + * Orchestrates fetching and presenting the data needed for the amend billable returns page + * + * @param {string} reviewChargeElementId - The UUID of the charge element being reviewed + * @param {number} elementIndex - the index of the element within all charge elements for the charge reference. This + * helps users relate which element they are editing to the one they selected on the review licence screen + * + * @returns {Promise} the 'pageData' needed to view the edit billable return volumes page + */ +async function go (reviewChargeElementId, elementIndex) { + const reviewChargeElement = await FetchReviewChargeElementService.go(reviewChargeElementId) + + const pageData = EditPresenter.go(reviewChargeElement, elementIndex) + + return { + pageTitle: 'Set the billable returns quantity for this bill run', + ...pageData + } +} + +module.exports = { + go +} diff --git a/app/services/bill-runs/review/factors.service.js b/app/services/bill-runs/review/factors.service.js new file mode 100644 index 0000000000..6ff2d33a77 --- /dev/null +++ b/app/services/bill-runs/review/factors.service.js @@ -0,0 +1,31 @@ +'use strict' + +/** + * Orchestrates fetching and presenting the data needed for the review charge reference factors page + * @module FactorsService + */ + +const FactorsPresenter = require('../../../presenters/bill-runs/review/factors.presenter.js') +const FetchReviewChargeReferenceService = require('./fetch-review-charge-reference.service.js') + +/** + * Orchestrates fetching and presenting the data needed for the review charge reference factors page + * + * @param {string} reviewChargeReferenceId - The UUID of the review charge reference whose factors are being amended + * + * @returns {Promise} the 'pageData' needed for the review charge reference factors page + */ +async function go (reviewChargeReferenceId) { + const reviewChargeReference = await FetchReviewChargeReferenceService.go(reviewChargeReferenceId) + + const pageData = FactorsPresenter.go(reviewChargeReference) + + return { + pageTitle: 'Set the adjustment factors', + ...pageData + } +} + +module.exports = { + go +} diff --git a/app/services/bill-runs/two-part-tariff/fetch-bill-run-licences.service.js b/app/services/bill-runs/review/fetch-bill-run-licences.service.js similarity index 98% rename from app/services/bill-runs/two-part-tariff/fetch-bill-run-licences.service.js rename to app/services/bill-runs/review/fetch-bill-run-licences.service.js index d3269580fe..049f35bcf8 100644 --- a/app/services/bill-runs/two-part-tariff/fetch-bill-run-licences.service.js +++ b/app/services/bill-runs/review/fetch-bill-run-licences.service.js @@ -90,7 +90,7 @@ async function _fetchBillRunLicences ( id, filterIssues, filterLicenceHolderNumber, filterLicenceStatus, filterProgress, page = 1 ) { const reviewLicenceQuery = ReviewLicenceModel.query() - .select('licenceId', 'licenceRef', 'licenceHolder', 'issues', 'progress', 'status') + .select('id', 'licenceId', 'licenceRef', 'licenceHolder', 'issues', 'progress', 'status') .where('billRunId', id) .orderBy([ { column: 'status', order: 'desc' }, diff --git a/app/services/bill-runs/review/fetch-remove-review-licence.service.js b/app/services/bill-runs/review/fetch-remove-review-licence.service.js new file mode 100644 index 0000000000..21fe8d167a --- /dev/null +++ b/app/services/bill-runs/review/fetch-remove-review-licence.service.js @@ -0,0 +1,52 @@ +'use strict' + +/** + * Fetches the selected review licence instance and related data for the two-part tariff remove review licence page + * @module FetchRemoveReviewLicenceService + */ + +const ReviewLicenceModel = require('../../../models/review-licence.model.js') + +/** + * Fetches the selected review licence instance and related data for the two-part tariff remove review licence page + * + * @param {string} reviewLicenceId - the UUID of the selected review licence + * + * @returns {module:ReviewLicenceModel} the matching `ReviewLicenceModel` instance and related data needed for the + * two-part tariff remove review licence page + */ +async function go (reviewLicenceId) { + return _fetch(reviewLicenceId) +} + +async function _fetch (reviewLicenceId) { + return ReviewLicenceModel.query() + .findById(reviewLicenceId) + .select([ + 'id', + 'licenceId', + 'licenceRef' + ]) + .withGraphFetched('billRun') + .modifyGraph('billRun', (billRunBuilder) => { + billRunBuilder + .select([ + 'id', + 'billRunNumber', + 'createdAt', + 'status', + 'toFinancialYearEnding' + ]) + .withGraphFetched('region') + .modifyGraph('region', (regionBuilder) => { + regionBuilder.select([ + 'id', + 'displayName' + ]) + }) + }) +} + +module.exports = { + go +} diff --git a/app/services/bill-runs/review/fetch-review-charge-element.service.js b/app/services/bill-runs/review/fetch-review-charge-element.service.js new file mode 100644 index 0000000000..547b5e435f --- /dev/null +++ b/app/services/bill-runs/review/fetch-review-charge-element.service.js @@ -0,0 +1,119 @@ +'use strict' + +/** + * Fetches the selected review charge element instance and related data for the 2PT review charge element pages + * @module FetchReviewChargeReferenceService + */ + +const { ref } = require('objection') + +const ReviewChargeElementModel = require('../../../models/review-charge-element.model.js') + +/** + * Fetches the selected review charge element instance and related data for the 2PT review charge element pages + * + * This fetch service fetches the data needed for the main review charge element page, but also the edit page/service. + * + * @param {string} reviewChargeElementId - The UUID of the review charge element being viewed + * + * @returns {Promise} the matching `ReviewChargeElementModel` instance and related data needed for the + * two-part tariff review charge element page + */ +async function go (reviewChargeElementId) { + return _fetch(reviewChargeElementId) +} + +async function _fetch (reviewChargeElementId) { + return ReviewChargeElementModel.query() + .findById(reviewChargeElementId) + .select([ + 'id', + 'amendedAllocated', + 'issues', + 'status' + ]) + .withGraphFetched('chargeElement') + .modifyGraph('chargeElement', (chargeElementBuilder) => { + chargeElementBuilder + .select([ + 'id', + 'abstractionPeriodStartDay', + 'abstractionPeriodStartMonth', + 'abstractionPeriodEndDay', + 'abstractionPeriodEndMonth', + 'authorisedAnnualQuantity', + 'description' + ]) + }) + .withGraphFetched('reviewChargeReference') + .modifyGraph('reviewChargeReference', (reviewChargeReferenceBuilder) => { + reviewChargeReferenceBuilder + .select([ + 'id', + 'amendedAuthorisedVolume' + ]) + .withGraphFetched('reviewChargeElements') + .modifyGraph('reviewChargeElements', (reviewChargeElementsBuilder) => { + reviewChargeElementsBuilder + .select(['id']) + }) + .withGraphFetched('reviewChargeVersion') + .modifyGraph('reviewChargeVersion', (reviewChargeVersionBuilder) => { + reviewChargeVersionBuilder + .select([ + 'id', + 'chargePeriodStartDate', + 'chargePeriodEndDate' + ]).withGraphFetched('reviewLicence') + .modifyGraph('reviewLicence', (reviewLicenceBuilder) => { + reviewLicenceBuilder + .select([ + 'id', + 'licenceId' + ]) + .withGraphFetched('billRun') + .modifyGraph('billRun', (billRunBuilder) => { + billRunBuilder + .select([ + 'id', + 'toFinancialYearEnding' + ]) + }) + }) + }) + }) + .withGraphFetched('reviewReturns') + .modifyGraph('reviewReturns', (reviewReturnsBuilder) => { + reviewReturnsBuilder + .select([ + 'reviewReturns.id', + 'reviewReturns.allocated', + 'reviewReturns.description', + 'reviewReturns.endDate', + 'reviewReturns.issues', + 'reviewReturns.quantity', + 'reviewReturns.purposes', + 'reviewReturns.returnId', + 'reviewReturns.returnReference', + 'reviewReturns.returnStatus', + 'reviewReturns.startDate', + 'reviewReturns.underQuery' + ]) + .orderBy('reviewReturns.startDate', 'asc') + .withGraphFetched('returnLog') + .modifyGraph('returnLog', (returnLogBuilder) => { + returnLogBuilder + .select([ + 'id', + ref('metadata:nald.periodStartDay').castInt().as('periodStartDay'), + ref('metadata:nald.periodStartMonth').castInt().as('periodStartMonth'), + ref('metadata:nald.periodEndDay').castInt().as('periodEndDay'), + ref('metadata:nald.periodEndMonth').castInt().as('periodEndMonth') + ]) + }) + }) +} + +module.exports = { + go +} diff --git a/app/services/bill-runs/review/fetch-review-charge-reference.service.js b/app/services/bill-runs/review/fetch-review-charge-reference.service.js new file mode 100644 index 0000000000..1f107be829 --- /dev/null +++ b/app/services/bill-runs/review/fetch-review-charge-reference.service.js @@ -0,0 +1,106 @@ +'use strict' + +/** + * Fetches the selected review charge reference instance and related data for the 2PT review charge reference pages + * @module FetchReviewChargeReferenceService + */ + +const { ref } = require('objection') + +const ReviewChargeReferenceModel = require('../../../models/review-charge-reference.model.js') + +/** + * Fetches the selected review charge reference instance and related data for the 2PT review charge reference pages + * + * This fetch service fetches the data needed for the main review charge reference page, but also the authorised, + * factors and preview charge pages/services. + * + * @param {string} reviewChargeReferenceId - the UUID of the selected review charge reference + * + * @returns {module:ReviewChargeReferenceModel} the matching `ReviewChargeReferenceModel` instance and related data + * needed for the two-part tariff review charge reference page and sub-pages + */ +async function go (reviewChargeReferenceId) { + return _fetch(reviewChargeReferenceId) +} + +async function _fetch (reviewChargeReferenceId) { + return ReviewChargeReferenceModel.query() + .findById(reviewChargeReferenceId) + .select([ + 'id', + 'abatementAgreement', + 'aggregate', + 'amendedAggregate', + 'amendedAuthorisedVolume', + 'amendedChargeAdjustment', + 'canalAndRiverTrustAgreement', + 'chargeAdjustment', + 'twoPartTariffAgreement', + 'winterDiscount' + ]) + .withGraphFetched('reviewChargeVersion') + .modifyGraph('reviewChargeVersion', (reviewChargeVersionBuilder) => { + reviewChargeVersionBuilder + .select([ + 'id', + 'chargePeriodStartDate', + 'chargePeriodEndDate' + ]) + .withGraphFetched('reviewLicence') + .modifyGraph('reviewLicence', (reviewLicenceBuilder) => { + reviewLicenceBuilder + .select([ + 'id' + ]) + .withGraphFetched('billRun') + .modifyGraph('billRun', (billRunBuilder) => { + billRunBuilder + .select([ + 'id', + 'toFinancialYearEnding' + ]) + }) + .withGraphFetched('licence') + .modifyGraph('licence', (licenceBuilder) => { + licenceBuilder + .select([ + 'id', + 'waterUndertaker' + ]) + }) + }) + }) + .withGraphFetched('reviewChargeElements') + .modifyGraph('reviewChargeElements', (reviewChargeElementsBuilder) => { + reviewChargeElementsBuilder + .select([ + 'id', + 'amendedAllocated' + ]) + }) + .withGraphFetched('chargeReference') + .modifyGraph('chargeReference', (chargeReferenceBuilder) => { + chargeReferenceBuilder + .select([ + 'id', + 'volume', + 'loss', + ref('chargeReferences.additionalCharges:supportedSource.name').castText().as('supportedSourceName'), + ref('chargeReferences.additionalCharges:isSupplyPublicWater').castText().as('waterCompanyCharge') + ]) + .withGraphFetched('chargeCategory') + .modifyGraph('chargeCategory', (chargeCategoryBuilder) => { + chargeCategoryBuilder + .select([ + 'id', + 'reference', + 'shortDescription' + ]) + }) + }) +} + +module.exports = { + go +} diff --git a/app/services/bill-runs/review/fetch-review-licence.service.js b/app/services/bill-runs/review/fetch-review-licence.service.js new file mode 100644 index 0000000000..31161ab0fc --- /dev/null +++ b/app/services/bill-runs/review/fetch-review-licence.service.js @@ -0,0 +1,182 @@ +'use strict' + +/** + * Fetches the selected review licence instance and related data for the two-part tariff review licence page + * @module FetchReviewLicenceService + */ + +const { ref } = require('objection') + +const ReviewLicenceModel = require('../../../models/review-licence.model.js') + +/** + * Fetches the selected review licence instance and associated data for the two-part tariff review licence page + * + * @param {string} reviewLicenceId - the UUID of the selected review licence + * + * @returns {module:ReviewLicenceModel} the matching `ReviewLicenceModel` instance and related data needed for the + * two-part tariff review licence page + */ +async function go (reviewLicenceId) { + return _fetch(reviewLicenceId) +} + +async function _fetch (reviewLicenceId) { + return ReviewLicenceModel.query() + .findById(reviewLicenceId) + .select([ + 'id', + 'billRunId', + 'licenceId', + 'licenceRef', + 'licenceHolder', + 'status', + 'progress' + ]) + .withGraphFetched('billRun') + .modifyGraph('billRun', (billRunBuilder) => { + billRunBuilder + .select([ + 'id', + 'toFinancialYearEnding' + ]) + .withGraphFetched('region') + .modifyGraph('region', (regionBuilder) => { + regionBuilder.select([ + 'id', + 'displayName' + ]) + }) + }) + .withGraphFetched('reviewReturns') + .modifyGraph('reviewReturns', (reviewReturnsBuilder) => { + reviewReturnsBuilder + .select([ + 'id', + 'allocated', + 'description', + 'endDate', + 'issues', + 'quantity', + 'purposes', + 'returnId', + 'returnReference', + 'returnStatus', + 'startDate', + 'underQuery' + ]) + .orderBy('reviewReturns.startDate', 'asc') + .withGraphFetched('returnLog') + .modifyGraph('returnLog', (returnLogBuilder) => { + returnLogBuilder + .select([ + 'id', + ref('metadata:nald.periodStartDay').castInt().as('periodStartDay'), + ref('metadata:nald.periodStartMonth').castInt().as('periodStartMonth'), + ref('metadata:nald.periodEndDay').castInt().as('periodEndDay'), + ref('metadata:nald.periodEndMonth').castInt().as('periodEndMonth') + ]) + }) + .withGraphFetched('reviewChargeElements') + .modifyGraph('reviewChargeElements', (reviewChargeElementsBuilder) => { + reviewChargeElementsBuilder + .select([ + 'reviewChargeElements.id' + ]) + }) + }) + .withGraphFetched('reviewChargeVersions') + .modifyGraph('reviewChargeVersions', (reviewChargeVersions) => { + reviewChargeVersions + .select([ + 'id', + 'chargePeriodEndDate', + 'chargePeriodStartDate' + ]) + .orderBy('chargePeriodStartDate', 'asc') + .withGraphFetched('reviewChargeReferences') + .modifyGraph('reviewChargeReferences', (reviewChargeReferencesBuilder) => { + reviewChargeReferencesBuilder + .select([ + 'id', + 'aggregate', + 'amendedAuthorisedVolume', + 'chargeAdjustment' + ]) + .withGraphFetched('chargeReference') + .modifyGraph('chargeReference', (chargeReferenceBuilder) => { + chargeReferenceBuilder + .select([ + 'id' + ]) + .withGraphFetched('chargeCategory') + .modifyGraph('chargeCategory', (chargeCategoryBuilder) => { + chargeCategoryBuilder + .select([ + 'id', + 'reference', + 'shortDescription' + ]) + }) + }) + .withGraphFetched('reviewChargeElements') + .modifyGraph('reviewChargeElements', (reviewChargeElementsBuilder) => { + reviewChargeElementsBuilder + .select([ + 'reviewChargeElements.id', + 'reviewChargeElements.amendedAllocated', + 'reviewChargeElements.issues', + 'reviewChargeElements.status' + ]) + .join('chargeElements', 'reviewChargeElements.chargeElementId', 'chargeElements.id') + .orderBy('chargeElements.authorisedAnnualQuantity', 'desc') + .withGraphFetched('chargeElement') + .modifyGraph('chargeElement', (chargeElementBuilder) => { + chargeElementBuilder + .select([ + 'id', + 'abstractionPeriodStartDay', + 'abstractionPeriodStartMonth', + 'abstractionPeriodEndDay', + 'abstractionPeriodEndMonth', + 'authorisedAnnualQuantity', + 'description' + ]) + .withGraphFetched('purpose') + .modifyGraph('purpose', (purposeBuilder) => { + purposeBuilder + .select([ + 'id', + 'description' + ]) + }) + }) + .withGraphFetched('reviewReturns') + .modifyGraph('reviewReturns', (reviewReturnsBuilder) => { + reviewReturnsBuilder + .select([ + 'reviewReturns.id', + 'reviewReturns.quantity', + 'reviewReturns.returnReference', + 'reviewReturns.returnStatus' + ]) + }) + }) + }) + .withGraphFetched('chargeVersion') + .modifyGraph('chargeVersion', (chargeVersionBuilder) => { + chargeVersionBuilder + .select([ + 'id' + ]) + .withGraphFetched('billingAccount') + .modifyGraph('billingAccount', (billingAccountBuilder) => { + billingAccountBuilder.modify('contactDetails') + }) + }) + }) +} + +module.exports = { + go +} diff --git a/app/services/bill-runs/review/preview.service.js b/app/services/bill-runs/review/preview.service.js new file mode 100644 index 0000000000..315c7c6200 --- /dev/null +++ b/app/services/bill-runs/review/preview.service.js @@ -0,0 +1,106 @@ +'use strict' + +/** + * Calculates the charge for a charge reference for preview by a user on the review charge reference page + * @module PreviewService + */ + +const { formatChargingModuleDate, formatMoney } = require('../../../presenters/base.presenter.js') +const CalculateChargeRequest = require('../../../requests/charging-module/calculate-charge.request.js') +const FetchReviewChargeReferenceService = require('./fetch-review-charge-reference.service.js') + +/** + * Calculates the charge for a charge reference for preview by a user on the review charge reference page + * + * It does this by sending a transaction based on the selected charge reference to the charging module so that it can + * calculate the charge. The charge amount is then added to a flash message which will be displayed to the user. + * + * If nothing has been allocated to the review charge reference (total billable returns is 0) then the service skips + * sending the request and just returns £0.00. + * + * If the request is sent but does not succeed, we display the message returned in order to help us diagnose what the + * issue could be. + * + * @param {string} reviewChargeReferenceId - The UUID of the charge reference review data to calculate the charge for + * @param {object} yar - The Hapi `request.yar` session manager passed on by the controller + */ +async function go (reviewChargeReferenceId, yar) { + const reviewChargeReference = await FetchReviewChargeReferenceService.go(reviewChargeReferenceId) + const transaction = _transaction(reviewChargeReference) + + const result = await _calculateCharge(transaction) + + if (result.charge || result.charge === 0) { + yar.flash('charge', `Based on this information the example charge is ${formatMoney(result.charge)}`) + } + + yar.flash('charge', `Could not calculate a charge. ${result.message}.`) +} + +function _actualVolume (reviewChargeElements) { + return reviewChargeElements.reduce((total, reviewChargeElement) => { + total += reviewChargeElement.amendedAllocated + + return total + }, 0) +} + +async function _calculateCharge (transaction) { + if (transaction.actualVolume === 0) { + return { charge: 0 } + } + + const result = await CalculateChargeRequest.send(transaction) + + if (result.succeeded) { + return { charge: result.response.body.calculation.chargeValue } + } + + return { message: result.response.body.message } +} + +function _transaction (reviewChargeReference) { + const { + abatementAgreement: abatementFactor, + amendedAggregate: aggregateProportion, + amendedAuthorisedVolume: authorisedVolume, + amendedChargeAdjustment: adjustmentFactor, + canalAndRiverTrustAgreement: section130Agreement, + chargeReference, + reviewChargeElements, + reviewChargeVersion, + twoPartTariffAgreement: section127Agreement, + winterDiscount: winterOnly + } = reviewChargeReference + + return { + abatementFactor, + actualVolume: _actualVolume(reviewChargeElements), + adjustmentFactor, + aggregateProportion, + authorisedDays: 0, // 2PT uses volumes in the calculations rather than days so this can be set to 0 + authorisedVolume, + billableDays: 0, // 2PT uses volumes in the calculations rather than days so this can be set to 0 + chargeCategoryCode: chargeReference.chargeCategory.reference, + compensationCharge: false, // Always false for the two-part tariff annual + credit: false, + loss: chargeReference.loss, + periodStart: formatChargingModuleDate(reviewChargeVersion.chargePeriodStartDate), + periodEnd: formatChargingModuleDate(reviewChargeVersion.chargePeriodEndDate), + ruleset: 'sroc', + section127Agreement, + section130Agreement, + supportedSource: chargeReference.supportedSourceName !== null, + // If `supportedSource` is `true` then `supportedSourceName` must be present + supportedSourceName: chargeReference.supportedSourceName, + // If `twoPartTariff` is `true` then `section127Agreement` must also be `true` + twoPartTariff: section127Agreement, + waterCompanyCharge: chargeReference.waterCompanyCharge !== null, + waterUndertaker: reviewChargeVersion.reviewLicence.licence.waterUndertaker, + winterOnly + } +} + +module.exports = { + go +} diff --git a/app/services/bill-runs/review/process-bill-run-post-remove.service.js b/app/services/bill-runs/review/process-bill-run-post-remove.service.js new file mode 100644 index 0000000000..63335aaf96 --- /dev/null +++ b/app/services/bill-runs/review/process-bill-run-post-remove.service.js @@ -0,0 +1,41 @@ +'use strict' + +/** + * Determines if a two-part tariff bill run is now empty (all licences removed) and if so sets its status to empty + * @module ProcessBillRunPostRemove + */ + +const BillRunModel = require('../../../models/bill-run.model.js') +const ReviewLicenceModel = require('../../../models/review-licence.model.js') + +/** + * Determines if a two-part tariff bill run is now empty (all licences removed) and if so sets its status to empty + * + * @param {string} billRunId - UUID of the two-part tariff bill run being processed post a review licence being removed + * + * @returns {Promise} true if it was the last review licence in the bill run, so the bill run is now empty, + * else false + */ +async function go (billRunId) { + const empty = await _empty(billRunId) + + if (empty) { + await _updateStatus(billRunId) + } + + return empty +} + +async function _empty (billRunId) { + const resultSize = await ReviewLicenceModel.query().select('id').where('billRunId', billRunId).resultSize() + + return resultSize === 0 +} + +async function _updateStatus (billRunId) { + return BillRunModel.query().findById(billRunId).patch({ status: 'empty' }) +} + +module.exports = { + go +} diff --git a/app/services/bill-runs/review/remove-review-licence.service.js b/app/services/bill-runs/review/remove-review-licence.service.js new file mode 100644 index 0000000000..774682f4db --- /dev/null +++ b/app/services/bill-runs/review/remove-review-licence.service.js @@ -0,0 +1,82 @@ +'use strict' + +/** + * Deletes all data relating to a review licence from the review tables + * @module RemoveReviewLicenceService + */ + +const { db } = require('../../../../db/db.js') + +/** + * Deletes all data relating to a review licence from the review tables + * + * @param {string} reviewLicenceId - The UUID of the review licence that is being removed from the bill run + * + * @returns {Promise} the promise returned is not intended to resolve to any particular value + */ +async function go (reviewLicenceId) { + await _removeChargeElementReturns(reviewLicenceId) + await _removeReturns(reviewLicenceId) + await _removeChargeElements(reviewLicenceId) + await _removeChargeReferences(reviewLicenceId) + await _removeChargeVersions(reviewLicenceId) + + return _removeLicence(reviewLicenceId) +} + +async function _removeChargeElements (reviewLicenceId) { + return db + .del() + .from('reviewChargeElements AS rce') + .innerJoin('reviewChargeReferences AS rcr', 'rce.reviewChargeReferenceId', 'rcr.id') + .innerJoin('reviewChargeVersions AS rcv', 'rcr.reviewChargeVersionId', 'rcv.id') + .innerJoin('reviewLicences AS rl', 'rcv.reviewLicenceId', 'rl.id') + .where('rl.id', reviewLicenceId) +} + +async function _removeChargeElementReturns (reviewLicenceId) { + return db + .del() + .from('reviewChargeElementsReturns AS rcer') + .innerJoin('reviewChargeElements AS rce', 'rcer.reviewChargeElementId', 'rce.id') + .innerJoin('reviewChargeReferences AS rcr', 'rce.reviewChargeReferenceId', 'rcr.id') + .innerJoin('reviewChargeVersions AS rcv', 'rcr.reviewChargeVersionId', 'rcv.id') + .innerJoin('reviewLicences AS rl', 'rcv.reviewLicenceId', 'rl.id') + .where('rl.id', reviewLicenceId) +} + +async function _removeChargeReferences (reviewLicenceId) { + return db + .del() + .from('reviewChargeReferences AS rcr') + .innerJoin('reviewChargeVersions AS rcv', 'rcr.reviewChargeVersionId', 'rcv.id') + .innerJoin('reviewLicences AS rl', 'rcv.reviewLicenceId', 'rl.id') + .where('rl.id', reviewLicenceId) +} + +async function _removeChargeVersions (reviewLicenceId) { + return db + .del() + .from('reviewChargeVersions AS rcv') + .innerJoin('reviewLicences AS rl', 'rcv.reviewLicenceId', 'rl.id') + .where('rl.id', reviewLicenceId) +} + +async function _removeLicence (reviewLicenceId) { + return db + .del() + .from('reviewLicences') + .where('id', reviewLicenceId) +} + +async function _removeReturns (reviewLicenceId) { + return db + .del() + .from('reviewReturns AS rr') + .innerJoin('reviewLicences AS rl', 'rr.reviewLicenceId', 'rl.id') + .where('rl.id', reviewLicenceId) +} + +module.exports = { + go +} diff --git a/app/services/bill-runs/review/remove.service.js b/app/services/bill-runs/review/remove.service.js new file mode 100644 index 0000000000..aa6c5dae88 --- /dev/null +++ b/app/services/bill-runs/review/remove.service.js @@ -0,0 +1,27 @@ +'use strict' + +/** + * Orchestrates fetching and presenting the data needed for the remove review licence confirmation page + * @module RemoveService + */ + +const FetchRemoveReviewLicenceService = require('./fetch-remove-review-licence.service.js') +const RemovePresenter = require('../../../presenters/bill-runs/review/remove.presenter.js') + +/** + * Orchestrates fetching and presenting the data needed for the remove bill run licence confirmation page + * + * @param {string} reviewLicenceId - The UUID of the licence that is being removed from the bill run + * + * @returns {Promise} an object representing the `pageData` needed by the remove licence template. It contains + * details of the bill run & the licence reference. + */ +async function go (reviewLicenceId) { + const reviewLicence = await FetchRemoveReviewLicenceService.go(reviewLicenceId) + + return RemovePresenter.go(reviewLicence) +} + +module.exports = { + go +} diff --git a/app/services/bill-runs/two-part-tariff/review-bill-run.service.js b/app/services/bill-runs/review/review-bill-run.service.js similarity index 95% rename from app/services/bill-runs/two-part-tariff/review-bill-run.service.js rename to app/services/bill-runs/review/review-bill-run.service.js index 6129e76173..6c518574e0 100644 --- a/app/services/bill-runs/two-part-tariff/review-bill-run.service.js +++ b/app/services/bill-runs/review/review-bill-run.service.js @@ -7,7 +7,7 @@ const FetchBillRunLicencesService = require('./fetch-bill-run-licences.service.js') const PaginatorPresenter = require('../../../presenters/paginator.presenter.js') -const ReviewBillRunPresenter = require('../../../presenters/bill-runs/two-part-tariff/review-bill-run.presenter.js') +const ReviewBillRunPresenter = require('../../../presenters/bill-runs/review/review-bill-run.presenter.js') /** * Orchestrates fetching and presenting the data needed for the review bill run page @@ -44,7 +44,7 @@ async function go (id, page, yar) { licences.results ) - const pagination = PaginatorPresenter.go(licences.total, selectedPageNumber, `/system/bill-runs/${id}/review`) + const pagination = PaginatorPresenter.go(licences.total, selectedPageNumber, `/system/bill-runs/review/${id}`) const pageTitle = _pageTitle(pagination.numberOfPages, selectedPageNumber) diff --git a/app/services/bill-runs/review/review-charge-element.service.js b/app/services/bill-runs/review/review-charge-element.service.js new file mode 100644 index 0000000000..c5bd75a7ed --- /dev/null +++ b/app/services/bill-runs/review/review-charge-element.service.js @@ -0,0 +1,37 @@ +'use strict' + +/** + * Orchestrates fetching and presenting the data needed for the review charge element page + * @module ReviewChargeElementService + */ + +const FetchReviewChargeElementService = require('./fetch-review-charge-element.service.js') +const ReviewChargeElementPresenter = require('../../../presenters/bill-runs/review/review-charge-element.presenter.js') + +/** + * Orchestrates fetching and presenting the data needed for the review charge element page + * + * @param {string} reviewChargeElementId - The UUID of the charge element being reviewed + * @param {number} elementIndex - the index of the element within all charge elements for the charge reference. This + * helps users relate which element they are reviewing to the one they selected on the review licence screen + * @param {object} yar - The Hapi `request.yar` session manager passed on by the controller + * + * @returns {Promise} the 'pageData' needed for the review charge element page + */ +async function go (reviewChargeElementId, elementIndex, yar) { + const reviewChargeElement = await FetchReviewChargeElementService.go(reviewChargeElementId) + + const [bannerMessage] = yar.flash('banner') + + const pageData = ReviewChargeElementPresenter.go(reviewChargeElement, elementIndex) + + return { + bannerMessage, + pageTitle: 'Review charge element', + ...pageData + } +} + +module.exports = { + go +} diff --git a/app/services/bill-runs/two-part-tariff/charge-reference-details.service.js b/app/services/bill-runs/review/review-charge-reference.service.js similarity index 52% rename from app/services/bill-runs/two-part-tariff/charge-reference-details.service.js rename to app/services/bill-runs/review/review-charge-reference.service.js index f57976fe18..62e0890164 100644 --- a/app/services/bill-runs/two-part-tariff/charge-reference-details.service.js +++ b/app/services/bill-runs/review/review-charge-reference.service.js @@ -1,37 +1,32 @@ 'use strict' /** - * Orchestrates fetching and presenting the data needed for the charge reference details page in a two-part tariff bill - * run - * @module ChargeReferenceDetailsService + * Orchestrates fetching and presenting the data needed for the review charge reference page + * @module ReviewChargeReferenceService */ -const ChargeReferenceDetailsPresenter = require('../../../presenters/bill-runs/two-part-tariff/charge-reference-details.presenter.js') const FetchReviewChargeReferenceService = require('./fetch-review-charge-reference.service.js') +const ReviewChargeReferencePresenter = require('../../../presenters/bill-runs/review/review-charge-reference.presenter.js') /** - * Orchestrates fetching and presenting the data needed for the charge reference details page in a two-part tariff bill - * run + * Orchestrates fetching and presenting the data needed for the review charge reference page * - * @param {string} billRunId - The UUID for the bill run - * @param {string} licenceId - The UUID of the licence that is being reviewed * @param {string} reviewChargeReferenceId - The UUID of the charge reference being reviewed * @param {object} yar - The Hapi `request.yar` session manager passed on by the controller * * @returns {Promise} the 'pageData' needed for the review charge reference page. It contains details of the * bill run, charge reference and the charge adjustments */ -async function go (billRunId, licenceId, reviewChargeReferenceId, yar) { - const { - billRun, - reviewChargeReference - } = await FetchReviewChargeReferenceService.go(billRunId, reviewChargeReferenceId) +async function go (reviewChargeReferenceId, yar) { + const reviewChargeReference = await FetchReviewChargeReferenceService.go(reviewChargeReferenceId) const [bannerMessage] = yar.flash('banner') const [chargeMessage] = yar.flash('charge') - const pageData = ChargeReferenceDetailsPresenter.go(billRun, reviewChargeReference, licenceId) + + const pageData = ReviewChargeReferencePresenter.go(reviewChargeReference) return { + pageTitle: 'Review charge reference', bannerMessage, chargeMessage, ...pageData diff --git a/app/services/bill-runs/two-part-tariff/review-licence.service.js b/app/services/bill-runs/review/review-licence.service.js similarity index 51% rename from app/services/bill-runs/two-part-tariff/review-licence.service.js rename to app/services/bill-runs/review/review-licence.service.js index c03412dc6e..71e32ce152 100644 --- a/app/services/bill-runs/two-part-tariff/review-licence.service.js +++ b/app/services/bill-runs/review/review-licence.service.js @@ -1,28 +1,27 @@ 'use strict' /** - * Orchestrates fetching and presenting the data needed for the licence review page in a two-part tariff bill run + * Orchestrates fetching and presenting the data needed for the two-part tariff review licence page * @module ReviewLicenceService */ -const FetchReviewLicenceResultsService = require('./fetch-review-licence-results.service.js') -const ReviewLicencePresenter = require('../../../presenters/bill-runs/two-part-tariff/review-licence.presenter.js') +const FetchReviewLicenceService = require('./fetch-review-licence.service.js') +const ReviewLicencePresenter = require('../../../presenters/bill-runs/review/review-licence.presenter.js') /** - * Orchestrates fetching and presenting the data needed for the licence review page in a two-part tariff bill run + * Orchestrates fetching and presenting the data needed for the two-part tariff review licence page * - * @param {module:BillRunModel} billRunId - The UUID for the bill run - * @param {module:LicenceModel} licenceId - The UUID of the licence that is being reviewed + * @param {string} reviewLicenceId - The UUID of the licence that is being reviewed * @param {object} yar - The Hapi `request.yar` session manager passed on by the controller * * @returns {Promise} the 'pageData' needed for the review licence page. It contains the licence, bill run, * matched and unmatched returns and the licence charge data */ -async function go (billRunId, licenceId, yar) { - const { billRun, licence } = await FetchReviewLicenceResultsService.go(billRunId, licenceId) +async function go (reviewLicenceId, yar) { + const reviewLicence = await FetchReviewLicenceService.go(reviewLicenceId) const [bannerMessage] = yar.flash('banner') - const pageData = ReviewLicencePresenter.go(billRun, licence) + const pageData = ReviewLicencePresenter.go(reviewLicence) return { bannerMessage, diff --git a/app/services/bill-runs/review/submit-authorised.service.js b/app/services/bill-runs/review/submit-authorised.service.js new file mode 100644 index 0000000000..a1ec47b74f --- /dev/null +++ b/app/services/bill-runs/review/submit-authorised.service.js @@ -0,0 +1,66 @@ +'use strict' + +/** + * Handles user submission for the review charge reference authorised page + * @module SubmitAuthorisedService + */ + +const AuthorisedPresenter = require('../../../presenters/bill-runs/review/authorised.presenter.js') +const AuthorisedValidator = require('../../../validators/bill-runs/review/authorised.validator.js') +const FetchReviewChargeReferenceService = require('./fetch-review-charge-reference.service.js') +const ReviewChargeReferenceModel = require('../../../models/review-charge-reference.model.js') + +/** + * Orchestrates validating the data for the amend authorised volume page and patching the db value + * + * @param {string} reviewChargeReferenceId - The UUID of the review charge reference being updated + * @param {object} yar - The Hapi `request.yar` session manager passed on by the controller + * @param {object} payload - The submitted form data + * + * @returns {Promise} An empty object if there are no errors else the page data for the page including the + * validation error details + */ +async function go (reviewChargeReferenceId, yar, payload) { + const validationResult = _validate(payload) + + if (!validationResult) { + await _save(reviewChargeReferenceId, payload) + yar.flash('banner', 'The authorised volume for this licence have been updated') + + return {} + } + + const reviewChargeReference = await FetchReviewChargeReferenceService.go(reviewChargeReferenceId) + const pageData = AuthorisedPresenter.go(reviewChargeReference) + + return { + amendedAuthorisedVolume: payload.amendedAuthorisedVolume, + error: validationResult, + pageTitle: 'Set the authorised volume', + ...pageData + } +} + +async function _save (reviewChargeReferenceId, payload) { + return ReviewChargeReferenceModel.query() + .findById(reviewChargeReferenceId) + .patch({ amendedAuthorisedVolume: payload.amendedAuthorisedVolume }) +} + +function _validate (payload) { + const validation = AuthorisedValidator.go(payload) + + if (!validation.error) { + return null + } + + const { message } = validation.error.details[0] + + return { + text: message + } +} + +module.exports = { + go +} diff --git a/app/services/bill-runs/review/submit-edit.service.js b/app/services/bill-runs/review/submit-edit.service.js new file mode 100644 index 0000000000..d7c35da9a1 --- /dev/null +++ b/app/services/bill-runs/review/submit-edit.service.js @@ -0,0 +1,78 @@ +'use strict' + +/** + * Orchestrates validating and patching the data for the amend billable returns page + * @module SubmitEditService + */ + +const EditPresenter = require('../../../presenters/bill-runs/review/edit.presenter.js') +const EditValidator = require('../../../validators/bill-runs/review/edit.validator.js') +const FetchReviewChargeElementService = require('./fetch-review-charge-element.service.js') +const ReviewChargeElementModel = require('../../../models/review-charge-element.model.js') + +/** + * Orchestrates validating the data for the amend billable returns page and patching the db value + * + * @param {string} reviewChargeElementId - The UUID of the charge element being updated + * @param {number} elementIndex - the index of the element within all charge elements for the charge reference. This + * helps users relate which element they are editing to the one they selected on the review licence screen + * @param {object} yar - The Hapi `request.yar` session manager passed on by the controller + * @param {object} payload - The submitted form data + * + * @returns {Promise} The updated value for the billable returns + */ +async function go (reviewChargeElementId, elementIndex, yar, payload) { + const validationResult = _validate(payload) + + if (!validationResult) { + await _save(reviewChargeElementId, payload) + yar.flash('banner', 'The billable returns for this licence have been updated') + + return {} + } + + const reviewChargeElement = await FetchReviewChargeElementService.go(reviewChargeElementId) + const pageData = EditPresenter.go(reviewChargeElement, elementIndex) + + return { + customQuantitySelected: payload.quantityOptions === 'customQuantity', + customQuantityValue: payload.customQuantity, + error: validationResult, + pageTitle: 'Set the billable returns quantity for this bill run', + ...pageData + } +} + +function _save (reviewChargeElementId, payload) { + const volume = payload.quantityOptions === 'customQuantity' ? payload.customQuantity : payload.quantityOptions + + return ReviewChargeElementModel.query() + .findById(reviewChargeElementId) + .patch({ amendedAllocated: volume }) +} + +function _validate (payload) { + const validation = EditValidator.go(payload) + + if (!validation.error) { + return null + } + + const { message } = validation.error.details[0] + + if (payload.quantityOptions === 'customQuantity') { + return { + errorList: [{ href: '#custom-quantity', text: message }], + customQuantityErrorMessage: { text: message } + } + } + + return { + errorList: [{ href: '#quantityOptions-error', text: message }], + quantityOptionsErrorMessage: { text: message } + } +} + +module.exports = { + go +} diff --git a/app/services/bill-runs/review/submit-factors.service.js b/app/services/bill-runs/review/submit-factors.service.js new file mode 100644 index 0000000000..9415757ad9 --- /dev/null +++ b/app/services/bill-runs/review/submit-factors.service.js @@ -0,0 +1,87 @@ +'use strict' + +/** + * Handles user submission for the review charge reference factors page + * @module SubmitFactorsService + */ + +const FactorsPresenter = require('../../../presenters/bill-runs/review/factors.presenter.js') +const FactorsValidator = require('../../../validators/bill-runs/review/factors.validator.js') +const FetchReviewChargeReferenceService = require('./fetch-review-charge-reference.service.js') +const ReviewChargeReferenceModel = require('../../../models/review-charge-reference.model.js') + +/** + * Handles user submission for the review charge reference factors page + * + * It first validates the payload of the submitted request. + * + * If there is no validation error it will save the selected value to the review charge reference then return an empty + * object. This will indicate to the controller that the submission was successful triggering it to redirect back to the + * review charge reference page. + * + * If there is a validation error it is combined with the output of the presenter to generate the page data needed to + * re-render the view with an error message. + * + * @param {string} reviewChargeReferenceId - The UUID of the review charge reference being updated + * @param {object} yar - The Hapi `request.yar` session manager passed on by the controller + * @param {object} payload - The submitted form data + * + * @returns {Promise} An empty object if there are no errors else the page data for the page including the + * validation error details + */ +async function go (reviewChargeReferenceId, yar, payload) { + const validationResult = _validate(payload) + + if (!validationResult) { + await _save(reviewChargeReferenceId, payload) + yar.flash('banner', 'The adjustment factors for this licence have been updated') + + return {} + } + + const reviewChargeReference = await FetchReviewChargeReferenceService.go(reviewChargeReferenceId) + const pageData = FactorsPresenter.go(reviewChargeReference) + + return { + amendedAggregate: payload.amendedAggregate, + amendedChargeAdjustment: payload.amendedChargeAdjustment, + error: validationResult, + pageTitle: 'Set the adjustment factors', + ...pageData + } +} + +async function _save (reviewChargeReferenceId, payload) { + return ReviewChargeReferenceModel.query() + .findById(reviewChargeReferenceId) + .patch(payload) +} + +function _validate (payload) { + const validation = FactorsValidator.go(payload) + + if (!validation.error) { + return null + } + + const result = { + errorList: [] + } + + validation.error.details.forEach((detail) => { + const href = detail.context.key === 'amendedAggregate' ? '#amended-aggregate' : '#amended-charge-adjustment' + + result.errorList.push({ + href, + text: detail.message + }) + + result[detail.context.key] = { message: detail.message } + }) + + return result +} + +module.exports = { + go +} diff --git a/app/services/bill-runs/review/submit-remove.service.js b/app/services/bill-runs/review/submit-remove.service.js new file mode 100644 index 0000000000..c36f53c1a4 --- /dev/null +++ b/app/services/bill-runs/review/submit-remove.service.js @@ -0,0 +1,64 @@ +'use strict' + +/** + * Orchestrates removing a review licence from a two-part tariff bill run whilst it is at the review stage + * @module SubmitRemoveService + */ + +const CreateLicenceSupplementaryYearService = require('../../licences/supplementary/create-licence-supplementary-year.service.js') +const FetchRemoveReviewLicenceModel = require('./fetch-remove-review-licence.service.js') +const ProcessBillRunPostRemove = require('./process-bill-run-post-remove.service.js') +const RemoveReviewLicenceService = require('./remove-review-licence.service.js') + +/** + * Orchestrates removing a review licence from a two-part tariff bill run whilst it is at the review stage + * + * It does this by deleting all of the persisted data relating to the licence from the review tables. The licence will + * then be flagged for 2PT supplementary billing. + * + * If after removing the review licence the bill run is empty, the bill run status will be set to `empty`. We also let + * the controller know so that the user is redirected back to the Bill runs page rather than Review bill run page. + * + * @param {string} reviewLicenceId - The UUID of the review licence that is being removed from the bill run + * @param {object} yar - The Hapi `request.yar` session manager passed on by the controller + * + * @returns {Promise} an object containing the bill run ID plus a boolean flag that indicates whether this was + * the last licence in the bill run (bill run is now empty) + */ +async function go (reviewLicenceId, yar) { + const reviewLicence = await FetchRemoveReviewLicenceModel.go(reviewLicenceId) + + await RemoveReviewLicenceService.go(reviewLicenceId) + + await _flagForSupplementaryBilling(reviewLicence) + + const empty = await _empty(reviewLicence) + + if (!empty) { + // NOTE: The banner message is only set if licences remain in the bill run. This is because if there are no longer + // any licences remaining in the bill run the user is redirected to the "Bill runs" page instead of "Review + // licences". As the banner isn't displayed on the "Bill runs" page the message would remain in the cookie. + yar.flash('banner', `Licence ${reviewLicence.licenceRef} removed from the bill run.`) + } + + return { + billRunId: reviewLicence.billRun.id, + empty + } +} + +async function _empty (reviewLicence) { + const { billRun } = reviewLicence + + return ProcessBillRunPostRemove.go(billRun.id) +} + +async function _flagForSupplementaryBilling (reviewLicence) { + const { billRun, licenceId } = reviewLicence + + return CreateLicenceSupplementaryYearService.go(licenceId, [billRun.toFinancialYearEnding], true) +} + +module.exports = { + go +} diff --git a/app/services/bill-runs/two-part-tariff/submit-review-bill-run.service.js b/app/services/bill-runs/review/submit-review-bill-run.service.js similarity index 91% rename from app/services/bill-runs/two-part-tariff/submit-review-bill-run.service.js rename to app/services/bill-runs/review/submit-review-bill-run.service.js index f5ac10a68f..15a183870a 100644 --- a/app/services/bill-runs/two-part-tariff/submit-review-bill-run.service.js +++ b/app/services/bill-runs/review/submit-review-bill-run.service.js @@ -11,6 +11,8 @@ * @param {string} billRunId - The UUID of the bill run * @param {object} payload - The `request.payload` containing the filter data. * @param {object} yar - The Hapi `request.yar` session manager passed on by the controller + * + * @returns {Promise} the promise returned is not intended to resolve to any particular value */ async function go (billRunId, payload, yar) { const clearFilters = payload?.clearFilters diff --git a/app/services/bill-runs/two-part-tariff/submit-review-licence.service.js b/app/services/bill-runs/review/submit-review-licence.service.js similarity index 83% rename from app/services/bill-runs/two-part-tariff/submit-review-licence.service.js rename to app/services/bill-runs/review/submit-review-licence.service.js index e0cef342a0..24eb4627d2 100644 --- a/app/services/bill-runs/two-part-tariff/submit-review-licence.service.js +++ b/app/services/bill-runs/review/submit-review-licence.service.js @@ -18,20 +18,19 @@ const ReviewLicenceModel = require('../../../models/review-licence.model.js') * redirected to the `GET` it knows to display a notification banner to confirm that the progress or status has changed * to the user. * - * @param {module:BillRunModel} billRunId - The UUID for the bill run - * @param {module:LicenceModel} licenceId - The UUID of the licence that is being reviewed - * @param {object} payload - The Hapi `request.payload` object passed on by the controller + * @param {string} reviewLicenceId - The UUID of the licence that is being reviewed * @param {object} yar - The Hapi `request.yar` session manager passed on by the controller + * @param {object} payload - The Hapi `request.payload` object passed on by the controller * * @returns {Promise} resolves to the result of the update query. Not intended to be used */ -async function go (billRunId, licenceId, payload, yar) { +async function go (reviewLicenceId, yar, payload) { const parsedPayload = _parsePayload(payload) // NOTE: The YarPlugin decorates the Hapi request object with a yar property. Yar is a session manager _bannerMessage(yar, parsedPayload) - return _update(billRunId, licenceId, parsedPayload) + return _update(reviewLicenceId, parsedPayload) } function _bannerMessage (yar, parsedPayload) { @@ -62,7 +61,7 @@ function _parsePayload (payload) { } } -async function _update (billRunId, licenceId, parsedPayload) { +async function _update (reviewLicenceId, parsedPayload) { const { progress, status } = parsedPayload const patch = {} @@ -73,9 +72,8 @@ async function _update (billRunId, licenceId, parsedPayload) { } return ReviewLicenceModel.query() + .findById(reviewLicenceId) .patch(patch) - .where('billRunId', billRunId) - .andWhere('licenceId', licenceId) } module.exports = { diff --git a/app/services/bill-runs/two-part-tariff/amend-adjustment-factor.service.js b/app/services/bill-runs/two-part-tariff/amend-adjustment-factor.service.js deleted file mode 100644 index c2ebd95a71..0000000000 --- a/app/services/bill-runs/two-part-tariff/amend-adjustment-factor.service.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict' - -/** - * Orchestrates fetching and presenting the data needed for the amend adjustment factor page - * @module AmendAdjustmentFactorService - */ - -const AmendAdjustmentFactorPresenter = require('../../../presenters/bill-runs/two-part-tariff/amend-adjustment-factor.presenter.js') -const FetchReviewChargeReferenceService = require('./fetch-review-charge-reference.service.js') - -/** - * Orchestrates fetching and presenting the data needed for the amend adjustment factor page - * - * @param {string} billRunId - The UUID for the bill run - * @param {string} licenceId - The UUID of the licence that is being reviewed - * @param {string} reviewChargeReferenceId - The UUID of the review charge reference being viewed - * - * @returns {Promise} the 'pageData' needed to view the amend adjustment factor page - */ -async function go (billRunId, licenceId, reviewChargeReferenceId) { - const { - billRun, - reviewChargeReference - } = await FetchReviewChargeReferenceService.go(billRunId, reviewChargeReferenceId) - - const pageData = AmendAdjustmentFactorPresenter.go(billRun, reviewChargeReference, licenceId) - - return pageData -} - -module.exports = { - go -} diff --git a/app/services/bill-runs/two-part-tariff/amend-authorised-volume.service.js b/app/services/bill-runs/two-part-tariff/amend-authorised-volume.service.js deleted file mode 100644 index cf4f14d484..0000000000 --- a/app/services/bill-runs/two-part-tariff/amend-authorised-volume.service.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict' - -/** - * Orchestrates fetching and presenting the data needed for the amend authorised volume page - * @module AmendAuthorisedVolumeService - */ - -const AmendAuthorisedVolumePresenter = require('../../../presenters/bill-runs/two-part-tariff/amend-authorised-volume.presenter.js') -const FetchAuthorisedVolumeService = require('./fetch-authorised-volume.service.js') - -/** - * Orchestrates fetching and presenting the data needed for the amend authorised volume page - * - * @param {string} billRunId - The UUID for the bill run - * @param {string} licenceId - The UUID of the licence that is being reviewed - * @param {string} reviewChargeReferenceId - The UUID of the review charge reference being viewed - * - * @returns {Promise} the 'pageData' needed to view the amend authorised volume page - */ -async function go (billRunId, licenceId, reviewChargeReferenceId) { - const { billRun, reviewChargeReference } = await FetchAuthorisedVolumeService.go(billRunId, reviewChargeReferenceId) - - const pageData = AmendAuthorisedVolumePresenter.go(billRun, reviewChargeReference, licenceId) - - return pageData -} - -module.exports = { - go -} diff --git a/app/services/bill-runs/two-part-tariff/amend-billable-returns.service.js b/app/services/bill-runs/two-part-tariff/amend-billable-returns.service.js deleted file mode 100644 index ad8dd6f6ee..0000000000 --- a/app/services/bill-runs/two-part-tariff/amend-billable-returns.service.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict' - -/** - * Orchestrates fetching and presenting the data needed for the amend billable returns page - * @module AmendBillableReturnsService - */ - -const AmendBillableReturnsPresenter = require('../../../presenters/bill-runs/two-part-tariff/amend-billable-returns.presenter.js') -const FetchMatchDetailsService = require('./fetch-match-details.service.js') - -/** - * Orchestrates fetching and presenting the data needed for the amend billable returns page - * - * @param {string} billRunId - The UUID for the bill run - * @param {string} licenceId - The UUID of the licence that is being reviewed - * @param {string} reviewChargeElementId - The UUID of the review charge element being viewed - * - * @returns {Promise} the 'pageData' needed to view the edit billable return volumes page - */ -async function go (billRunId, licenceId, reviewChargeElementId) { - const { billRun, reviewChargeElement } = await FetchMatchDetailsService.go(billRunId, reviewChargeElementId) - - const pageData = AmendBillableReturnsPresenter.go(billRun, reviewChargeElement, licenceId) - - return pageData -} - -module.exports = { - go -} diff --git a/app/services/bill-runs/two-part-tariff/calculate-charge.service.js b/app/services/bill-runs/two-part-tariff/calculate-charge.service.js deleted file mode 100644 index f43c643465..0000000000 --- a/app/services/bill-runs/two-part-tariff/calculate-charge.service.js +++ /dev/null @@ -1,124 +0,0 @@ -'use strict' - -/** - * Calculates the charge for a charge reference to display in a banner to the user - * @module CalculateChargeService - */ - -const { ref } = require('objection') - -const CalculateChargeRequest = require('../../../requests/charging-module/calculate-charge.request.js') -const { formatChargingModuleDate, formatMoney } = require('../../../presenters/base.presenter.js') -const LicenceModel = require('../../../models/licence.model.js') -const ReviewChargeReferenceModel = require('../../../models/review-charge-reference.model.js') - -/** - * Calculates the charge for a charge reference to display in a banner to the user - * - * It does this by sending a transaction based on the selected charge reference to the charging module so that it can - * calculate the charge. The charge amount is then added to a flash message which will be displayed to the user. - * - * @param {string} licenceId - The UUID of the licence related to the charge - * @param {string} reviewChargeReferenceId - The UUID of the charge reference review data to calculate the charge on - * @param {object} yar - The Hapi `request.yar` session manager passed on by the controller - */ -async function go (licenceId, reviewChargeReferenceId, yar) { - const reviewChargeReference = await _fetchReviewChargeReference(reviewChargeReferenceId) - const waterUndertaker = await _fetchWaterUndertaker(licenceId) - const calculatedCharge = await _calculateCharge(reviewChargeReference, waterUndertaker) - - if (calculatedCharge) { - yar.flash('charge', `Based on this information the example charge is ${formatMoney(calculatedCharge)}.`) - } -} - -function _calculateActualVolume (reviewChargeElements) { - return reviewChargeElements.reduce((total, reviewChargeElement) => { - total += reviewChargeElement.amendedAllocated - - return total - }, 0) -} - -async function _calculateCharge (reviewChargeReference, waterUndertaker) { - const transaction = { - abatementFactor: reviewChargeReference.abatementAgreement, - actualVolume: _calculateActualVolume(reviewChargeReference.reviewChargeElements), - adjustmentFactor: reviewChargeReference.amendedChargeAdjustment, - aggregateProportion: reviewChargeReference.amendedAggregate, - authorisedDays: 0, // 2PT uses volumes in the calculations rather than days so this can be set to 0 - authorisedVolume: reviewChargeReference.amendedAuthorisedVolume, - billableDays: 0, // 2PT uses volumes in the calculations rather than days so this can be set to 0 - chargeCategoryCode: reviewChargeReference.chargeReference.chargeCategory.reference, - compensationCharge: false, // Always false for the two-part tariff annual - credit: false, - loss: reviewChargeReference.chargeReference.loss, - periodStart: formatChargingModuleDate(reviewChargeReference.reviewChargeVersion.chargePeriodStartDate), - periodEnd: formatChargingModuleDate(reviewChargeReference.reviewChargeVersion.chargePeriodEndDate), - ruleset: 'sroc', - section127Agreement: reviewChargeReference.twoPartTariffAgreement, - section130Agreement: reviewChargeReference.canalAndRiverTrustAgreement, - supportedSource: reviewChargeReference.chargeReference.supportedSourceName !== null, - // If `supportedSource` is `true` then `supportedSourceName` must be present - supportedSourceName: reviewChargeReference.chargeReference.supportedSourceName, - // If `twoPartTariff` is `true` then `section127Agreement` must also be `true` - twoPartTariff: reviewChargeReference.twoPartTariffAgreement, - waterCompanyCharge: reviewChargeReference.chargeReference.waterCompanyCharge !== null, - waterUndertaker, - winterOnly: reviewChargeReference.winterDiscount - } - - const calculatedCharge = await CalculateChargeRequest.send(transaction) - - if (calculatedCharge.succeeded) { - return calculatedCharge.response.body.calculation.chargeValue - } else { - return null - } -} - -async function _fetchReviewChargeReference (reviewChargeReferenceId) { - return ReviewChargeReferenceModel.query() - .findById(reviewChargeReferenceId) - .select( - 'abatementAgreement', - 'amendedAggregate', - 'amendedAuthorisedVolume', - 'amendedChargeAdjustment', - 'canalAndRiverTrustAgreement', - 'twoPartTariffAgreement', - 'winterDiscount' - ) - .withGraphFetched('chargeReference') - .modifyGraph('chargeReference', (builder) => { - builder.select( - 'loss', - ref('chargeReferences.additionalCharges:supportedSource.name').castText().as('supportedSourceName'), - ref('chargeReferences.additionalCharges:isSupplyPublicWater').castText().as('waterCompanyCharge') - ) - }) - .withGraphFetched('chargeReference.chargeCategory') - .modifyGraph('chargeReference.chargeCategory', (builder) => { - builder.select('reference') - }) - .withGraphFetched('reviewChargeVersion') - .modifyGraph('reviewChargeVersion', (builder) => { - builder.select('chargePeriodStartDate', 'chargePeriodEndDate') - }) - .withGraphFetched('reviewChargeElements') - .modifyGraph('reviewChargeElements', (builder) => { - builder.select('amendedAllocated') - }) -} - -async function _fetchWaterUndertaker (licenceId) { - const licence = await LicenceModel.query() - .findById(licenceId) - .select('waterUndertaker') - - return licence.waterUndertaker -} - -module.exports = { - go -} diff --git a/app/services/bill-runs/two-part-tariff/fetch-authorised-volume.service.js b/app/services/bill-runs/two-part-tariff/fetch-authorised-volume.service.js deleted file mode 100644 index 89eced1f18..0000000000 --- a/app/services/bill-runs/two-part-tariff/fetch-authorised-volume.service.js +++ /dev/null @@ -1,70 +0,0 @@ -'use strict' - -/** - * Fetches the individual charge reference details when the authorised volume is being amended for a two-part tariff - * bill run - * @module FetchAuthorisedVolumeService - */ - -const BillRunModel = require('../../../models/bill-run.model.js') -const ReviewChargeReferenceModel = require('../../../models/review-charge-reference.model.js') - -/** - * Fetches the charge reference details for an individual licence - * - * @param {string} billRunId - UUID of the bill run - * @param {string} reviewChargeReferenceId - The UUID of the review charge reference being viewed - * - * @returns {Promise} An object containing the bill run and review charge reference instances - */ -async function go (billRunId, reviewChargeReferenceId) { - const billRun = await _fetchBillRun(billRunId) - const reviewChargeReference = await _fetchReviewChargeReference(reviewChargeReferenceId) - - return { billRun, reviewChargeReference } -} - -async function _fetchBillRun (billRunId) { - return BillRunModel.query() - .findById(billRunId) - .select( - 'id', - 'toFinancialYearEnding') -} - -async function _fetchReviewChargeReference (reviewChargeReferenceId) { - return ReviewChargeReferenceModel.query() - .findById(reviewChargeReferenceId) - .select('id', 'amendedAuthorisedVolume') - .withGraphFetched('chargeReference') - .modifyGraph('chargeReference', (builder) => { - builder.select([ - 'chargeCategoryId' - ]) - }) - .withGraphFetched('chargeReference.chargeCategory') - .modifyGraph('chargeReference.chargeCategory', (builder) => { - builder.select([ - 'shortDescription', - 'minVolume', - 'maxVolume' - ]) - }) - .withGraphFetched('reviewChargeVersion') - .modifyGraph('reviewChargeVersion', (builder) => { - builder.select([ - 'chargePeriodStartDate', - 'chargePeriodEndDate' - ]) - }) - .withGraphFetched('reviewChargeElements') - .modifyGraph('reviewChargeElements', (builder) => { - builder.select([ - 'amendedAllocated' - ]) - }) -} - -module.exports = { - go -} diff --git a/app/services/bill-runs/two-part-tariff/fetch-match-details.service.js b/app/services/bill-runs/two-part-tariff/fetch-match-details.service.js deleted file mode 100644 index e5b396fe83..0000000000 --- a/app/services/bill-runs/two-part-tariff/fetch-match-details.service.js +++ /dev/null @@ -1,78 +0,0 @@ -'use strict' - -/** - * Fetches the individual charge element match details during the review stage of a two-part tariff bill run - * @module FetchMatchDetailsService - */ - -const { ref } = require('objection') - -const BillRunModel = require('../../../models/bill-run.model.js') -const ReviewChargeElementModel = require('../../../models/review-charge-element.model.js') - -/** - * Fetches the match details for an individual charge element - * - * @param {string} billRunId - UUID of the bill run - * @param {string} reviewChargeElementId - The UUID of the review charge element being viewed - * - * @returns {Promise} An object containing the bill run and review charge element instances - */ -async function go (billRunId, reviewChargeElementId) { - const billRun = await _fetchBillRun(billRunId) - const reviewChargeElement = await _fetchReviewChargeElement(reviewChargeElementId) - - return { billRun, reviewChargeElement } -} - -async function _fetchBillRun (billRunId) { - return BillRunModel.query() - .findById(billRunId) - .select( - 'id', - 'fromFinancialYearEnding', - 'toFinancialYearEnding') -} - -async function _fetchReviewChargeElement (reviewChargeElementId) { - return ReviewChargeElementModel.query() - .findById(reviewChargeElementId) - .withGraphFetched('reviewReturns') - .withGraphFetched('reviewReturns.returnLog') - .modifyGraph('reviewReturns.returnLog', (builder) => { - builder.select([ - ref('metadata:nald.periodStartDay').castInt().as('periodStartDay'), - ref('metadata:nald.periodStartMonth').castInt().as('periodStartMonth'), - ref('metadata:nald.periodEndDay').castInt().as('periodEndDay'), - ref('metadata:nald.periodEndMonth').castInt().as('periodEndMonth')]) - }) - .withGraphFetched('chargeElement') - .modifyGraph('chargeElement', (builder) => { - builder.select([ - 'description', - 'abstractionPeriodStartDay', - 'abstractionPeriodStartMonth', - 'abstractionPeriodEndDay', - 'abstractionPeriodEndMonth', - 'authorisedAnnualQuantity' - ]) - }) - .withGraphFetched('reviewChargeReference') - .modifyGraph('reviewChargeReference', (builder) => { - builder.select([ - 'id', - 'amendedAuthorisedVolume' - ]) - }) - .withGraphFetched('reviewChargeReference.reviewChargeVersion') - .modifyGraph('reviewChargeReference.reviewChargeVersion', (builder) => { - builder.select([ - 'chargePeriodStartDate', - 'chargePeriodEndDate' - ]) - }) -} - -module.exports = { - go -} diff --git a/app/services/bill-runs/two-part-tariff/fetch-review-charge-reference.service.js b/app/services/bill-runs/two-part-tariff/fetch-review-charge-reference.service.js deleted file mode 100644 index 5c938cd9ca..0000000000 --- a/app/services/bill-runs/two-part-tariff/fetch-review-charge-reference.service.js +++ /dev/null @@ -1,72 +0,0 @@ -'use strict' - -/** - * Fetches the individual charge reference details during the review stage of a two-part tariff bill run - * @module FetchReviewChargeReferenceService - */ - -const { ref } = require('objection') - -const BillRunModel = require('../../../models/bill-run.model.js') -const ReviewChargeReferenceModel = require('../../../models/review-charge-reference.model.js') - -/** - * Fetches the charge reference details for an individual licence - * - * @param {string} billRunId - UUID of the bill run - * @param {string} reviewChargeReferenceId - The UUID of the review charge reference being viewed - * - * @returns {Promise} An object containing the bill run and review charge reference instances - */ -async function go (billRunId, reviewChargeReferenceId) { - const billRun = await _fetchBillRun(billRunId) - const reviewChargeReference = await _fetchReviewChargeReference(reviewChargeReferenceId) - - return { billRun, reviewChargeReference } -} - -async function _fetchBillRun (billRunId) { - return BillRunModel.query() - .findById(billRunId) - .select( - 'id', - 'toFinancialYearEnding') -} - -async function _fetchReviewChargeReference (reviewChargeReferenceId) { - return ReviewChargeReferenceModel.query() - .findById(reviewChargeReferenceId) - .withGraphFetched('chargeReference') - .modifyGraph('chargeReference', (builder) => { - builder.select([ - 'volume', - 'chargeCategoryId', - ref('chargeReferences.additionalCharges:supportedSource.name').castText().as('supportedSourceName'), - ref('chargeReferences.additionalCharges:isSupplyPublicWater').castText().as('waterCompanyCharge') - ]) - }) - .withGraphFetched('chargeReference.chargeCategory') - .modifyGraph('chargeReference.chargeCategory', (builder) => { - builder.select([ - 'reference', - 'shortDescription' - ]) - }) - .withGraphFetched('reviewChargeVersion') - .modifyGraph('reviewChargeVersion', (builder) => { - builder.select([ - 'chargePeriodStartDate', - 'chargePeriodEndDate' - ]) - }) - .withGraphFetched('reviewChargeElements') - .modifyGraph('reviewChargeElements', (builder) => { - builder.select([ - 'amendedAllocated' - ]) - }) -} - -module.exports = { - go -} diff --git a/app/services/bill-runs/two-part-tariff/fetch-review-licence-results.service.js b/app/services/bill-runs/two-part-tariff/fetch-review-licence-results.service.js deleted file mode 100644 index f348a3d5f4..0000000000 --- a/app/services/bill-runs/two-part-tariff/fetch-review-licence-results.service.js +++ /dev/null @@ -1,148 +0,0 @@ -'use strict' - -/** - * Fetches the individual review licence data for a two-part tariff bill run - * @module FetchReviewLicenceResultsService - */ - -const { ref } = require('objection') - -const BillingAccountModel = require('../../../models/billing-account.model.js') -const BillRunModel = require('../../../models/bill-run.model.js') -const ReviewLicenceModel = require('../../../models/review-licence.model.js') - -/** - * Fetches the bill run and an individual licences review data for a two-part tariff bill run - * - * @param {module:BillRunModel} billRunId - UUID of the bill run - * @param {module:LicenceModel} licenceId - UUID of the individual licence to review - * - * @returns {Promise} Contains an array of bill run data and review licence data - */ -async function go (billRunId, licenceId) { - const billRun = await _fetchBillRun(billRunId) - const licence = await _fetchReviewLicence(licenceId, billRunId) - - await _fetchBillingAccountDetails(licence[0].reviewChargeVersions) - - return { billRun, licence } -} - -async function _fetchBillingAccount (billingAccountId) { - return BillingAccountModel.query().findById(billingAccountId).modify('contactDetails') -} - -async function _fetchBillingAccountDetails (reviewChargeVersions) { - for (const reviewChargeVersion of reviewChargeVersions) { - reviewChargeVersion.billingAccountDetails = await _fetchBillingAccount( - reviewChargeVersion.chargeVersion.billingAccountId - ) - } -} - -async function _fetchBillRun (billRunId) { - return BillRunModel.query() - .findById(billRunId) - .select( - 'id', - 'fromFinancialYearEnding', - 'toFinancialYearEnding') - .withGraphFetched('region') - .modifyGraph('region', (builder) => { - builder.select('displayName') - }) -} - -async function _fetchReviewLicence (licenceId, billRunId) { - return ReviewLicenceModel.query() - .select( - 'id', - 'billRunId', - 'licenceId', - 'licenceRef', - 'licenceHolder', - 'issues', - 'status', - 'progress', - ReviewLicenceModel.raw(` - EXISTS (SELECT 1 - FROM review_charge_elements rce - INNER JOIN review_charge_references rcr ON rce.review_charge_reference_id = rcr.id - INNER JOIN review_charge_versions rcv ON rcr.review_charge_version_id = rcv.id - WHERE rce.status = 'review' - AND rcv.review_licence_id = review_licences.id) AS has_review_status - `) - ) - .where('licenceId', licenceId) - .where('billRunId', billRunId) - .withGraphFetched('reviewReturns.reviewChargeElements') - .modifyGraph('reviewReturns', (builder) => { - builder.orderBy('reviewReturns.startDate', 'asc') - }) - .withGraphFetched('reviewReturns.returnLog') - .modifyGraph('reviewReturns.returnLog', (builder) => { - builder.select([ - ref('metadata:nald.periodStartDay').castInt().as('periodStartDay'), - ref('metadata:nald.periodStartMonth').castInt().as('periodStartMonth'), - ref('metadata:nald.periodEndDay').castInt().as('periodEndDay'), - ref('metadata:nald.periodEndMonth').castInt().as('periodEndMonth')]) - }) - .withGraphFetched('reviewChargeVersions') - .modifyGraph('reviewChargeVersions', (builder) => { - builder - .join('chargeVersions', 'reviewChargeVersions.chargeVersionId', 'chargeVersions.id') - .orderBy('chargeVersions.startDate', 'asc') - }) - .withGraphFetched('reviewChargeVersions.chargeVersion') - .modifyGraph('reviewChargeVersions.chargeVersion', (builder) => { - builder.select([ - 'billingAccountId' - ]) - }) - .withGraphFetched('reviewChargeVersions.reviewChargeReferences') - .modifyGraph('reviewChargeVersions.reviewChargeReferences', (builder) => { - builder - .join('chargeReferences', 'reviewChargeReferences.chargeReferenceId', 'chargeReferences.id') - .join('chargeCategories', 'chargeReferences.chargeCategoryId', 'chargeCategories.id') - .orderBy('chargeCategories.subsistenceCharge', 'desc') - }) - .withGraphFetched('reviewChargeVersions.reviewChargeReferences.chargeReference') - .modifyGraph('reviewChargeVersions.reviewChargeReferences.chargeReference', (builder) => { - builder.select([ - 'chargeCategoryId' - ]) - }) - .withGraphFetched('reviewChargeVersions.reviewChargeReferences.chargeReference.chargeCategory') - .modifyGraph('reviewChargeVersions.reviewChargeReferences.chargeReference.chargeCategory', (builder) => { - builder.select([ - 'reference', - 'shortDescription' - ]) - }) - .withGraphFetched('reviewChargeVersions.reviewChargeReferences.reviewChargeElements') - .modifyGraph('reviewChargeVersions.reviewChargeReferences.reviewChargeElements', (builder) => { - builder - .join('chargeElements', 'reviewChargeElements.chargeElementId', 'chargeElements.id') - .orderBy('chargeElements.authorisedAnnualQuantity', 'desc') - }) - .withGraphFetched('reviewChargeVersions.reviewChargeReferences.reviewChargeElements.chargeElement') - .modifyGraph('reviewChargeVersions.reviewChargeReferences.reviewChargeElements.chargeElement', (builder) => { - builder.select([ - 'description', - 'abstractionPeriodStartDay', - 'abstractionPeriodStartMonth', - 'abstractionPeriodEndDay', - 'abstractionPeriodEndMonth', - 'authorisedAnnualQuantity' - ]) - }) - .withGraphFetched('reviewChargeVersions.reviewChargeReferences.reviewChargeElements.chargeElement.purpose') - .modifyGraph('reviewChargeVersions.reviewChargeReferences.reviewChargeElements.chargeElement.purpose', (builder) => { - builder.select(['description']) - }) - .withGraphFetched('reviewChargeVersions.reviewChargeReferences.reviewChargeElements.reviewReturns') -} - -module.exports = { - go -} diff --git a/app/services/bill-runs/two-part-tariff/match-details.service.js b/app/services/bill-runs/two-part-tariff/match-details.service.js deleted file mode 100644 index ba4147c3b9..0000000000 --- a/app/services/bill-runs/two-part-tariff/match-details.service.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict' - -/** - * Orchestrates fetching and presenting the data needed for the match details page on a charge element in a - * two-part tariff bill run - * @module MatchDetailsService - */ - -const FetchMatchDetailsService = require('./fetch-match-details.service.js') -const MatchDetailsPresenter = require('../../../presenters/bill-runs/two-part-tariff/match-details.presenter.js') - -/** - * Orchestrates fetching and presenting the data needed for the view match details page for a charge element - * - * @param {string} billRunId - The UUID for the bill run - * @param {string} licenceId - The UUID of the licence that is being reviewed - * @param {string} reviewChargeElementId - The UUID of the review charge element being viewed - * @param {object} yar - The Hapi `request.yar` session manager passed on by the controller - * - * @returns {Promise} the 'pageData' needed to view the match details of an individual charge - */ -async function go (billRunId, licenceId, reviewChargeElementId, yar) { - const { billRun, reviewChargeElement } = await FetchMatchDetailsService.go(billRunId, reviewChargeElementId) - - const [bannerMessage] = yar.flash('banner') - const pageData = MatchDetailsPresenter.go(billRun, reviewChargeElement, licenceId) - - return { - bannerMessage, - ...pageData - } -} - -module.exports = { - go -} diff --git a/app/services/bill-runs/two-part-tariff/remove-bill-run-licence.service.js b/app/services/bill-runs/two-part-tariff/remove-bill-run-licence.service.js deleted file mode 100644 index faecdfe314..0000000000 --- a/app/services/bill-runs/two-part-tariff/remove-bill-run-licence.service.js +++ /dev/null @@ -1,53 +0,0 @@ -'use strict' - -/** - * Orchestrates fetching and presenting the data needed for the remove bill run licence confirmation page - * @module RemoveBillRunLicenceService - */ - -const BillRunModel = require('../../../models/bill-run.model.js') -const LicenceModel = require('../../../models/licence.model.js') -const RemoveBillRunLicencePresenter = require('../../../presenters/bill-runs/two-part-tariff/remove-bill-run-licence.presenter.js') - -/** - * Orchestrates fetching and presenting the data needed for the remove bill run licence confirmation page - * - * @param {string} billRunId - The UUID of the bill run that the licence is in - * @param {string} licenceId - UUID of the licence to remove from the bill run - * - * @returns {Promise} an object representing the `pageData` needed by the remove licence template. It contains - * details of the bill run & the licence reference. - */ -async function go (billRunId, licenceId) { - const billRun = await _fetchBillRun(billRunId) - const licenceRef = await _fetchLicenceRef(licenceId) - - const pageData = RemoveBillRunLicencePresenter.go(billRun, licenceId, licenceRef) - - return pageData -} - -async function _fetchBillRun (billRunId) { - return BillRunModel.query() - .findById(billRunId) - .select( - 'billRuns.billRunNumber', - 'billRuns.createdAt', - 'billRuns.status', - 'billRuns.toFinancialYearEnding', - 'region.displayName as region' - ) - .innerJoinRelated('region') -} - -async function _fetchLicenceRef (licenceId) { - const licence = await LicenceModel.query() - .findById(licenceId) - .select('licenceRef') - - return licence.licenceRef -} - -module.exports = { - go -} diff --git a/app/services/bill-runs/two-part-tariff/remove-review-data.service.js b/app/services/bill-runs/two-part-tariff/remove-review-data.service.js deleted file mode 100644 index 390dabc253..0000000000 --- a/app/services/bill-runs/two-part-tariff/remove-review-data.service.js +++ /dev/null @@ -1,86 +0,0 @@ -'use strict' - -/** - * Deletes all of the persisted data relating to the bill and licence from the review tables. - * @module RemoveReviewDataService - */ - -const { db } = require('../../../../db/db.js') - -/** - * Deletes all of the persisted data relating to the bill and licence from the review tables - * - * @param {string} billRunId - The UUID of the bill run that the licence is in - * @param {string} licenceId - UUID of the licence to remove from the review tables - */ -async function go (billRunId, licenceId) { - await _removeChargeElementReturns(billRunId, licenceId) - await _removeReturns(billRunId, licenceId) - await _removeChargeElements(billRunId, licenceId) - await _removeChargeReferences(billRunId, licenceId) - await _removeChargeVersions(billRunId, licenceId) - await _removeLicence(billRunId, licenceId) -} - -async function _removeChargeElements (billRunId, licenceId) { - return db - .del() - .from('reviewChargeElements AS rce') - .innerJoin('reviewChargeReferences AS rcr', 'rce.reviewChargeReferenceId', 'rcr.id') - .innerJoin('reviewChargeVersions AS rcv', 'rcr.reviewChargeVersionId', 'rcv.id') - .innerJoin('reviewLicences AS rl', 'rcv.reviewLicenceId', 'rl.id') - .where('rl.billRunId', billRunId) - .andWhere('rl.licenceId', licenceId) -} - -async function _removeChargeElementReturns (billRunId, licenceId) { - return db - .del() - .from('reviewChargeElementsReturns AS rcer') - .innerJoin('reviewChargeElements AS rce', 'rcer.reviewChargeElementId', 'rce.id') - .innerJoin('reviewChargeReferences AS rcr', 'rce.reviewChargeReferenceId', 'rcr.id') - .innerJoin('reviewChargeVersions AS rcv', 'rcr.reviewChargeVersionId', 'rcv.id') - .innerJoin('reviewLicences AS rl', 'rcv.reviewLicenceId', 'rl.id') - .where('rl.billRunId', billRunId) - .andWhere('rl.licenceId', licenceId) -} - -async function _removeChargeReferences (billRunId, licenceId) { - return db - .del() - .from('reviewChargeReferences AS rcr') - .innerJoin('reviewChargeVersions AS rcv', 'rcr.reviewChargeVersionId', 'rcv.id') - .innerJoin('reviewLicences AS rl', 'rcv.reviewLicenceId', 'rl.id') - .where('rl.billRunId', billRunId) - .andWhere('rl.licenceId', licenceId) -} - -async function _removeChargeVersions (billRunId, licenceId) { - return db - .del() - .from('reviewChargeVersions AS rcv') - .innerJoin('reviewLicences AS rl', 'rcv.reviewLicenceId', 'rl.id') - .where('rl.billRunId', billRunId) - .andWhere('rl.licenceId', licenceId) -} - -async function _removeLicence (billRunId, licenceId) { - return db - .del() - .from('reviewLicences') - .where('billRunId', billRunId) - .andWhere('licenceId', licenceId) -} - -async function _removeReturns (billRunId, licenceId) { - return db - .del() - .from('reviewReturns AS rr') - .innerJoin('reviewLicences AS rl', 'rr.reviewLicenceId', 'rl.id') - .where('rl.billRunId', billRunId) - .andWhere('rl.licenceId', licenceId) -} - -module.exports = { - go -} diff --git a/app/services/bill-runs/two-part-tariff/submit-amended-adjustment-factor.service.js b/app/services/bill-runs/two-part-tariff/submit-amended-adjustment-factor.service.js deleted file mode 100644 index 2ace5e35a9..0000000000 --- a/app/services/bill-runs/two-part-tariff/submit-amended-adjustment-factor.service.js +++ /dev/null @@ -1,91 +0,0 @@ -'use strict' - -/** - * Orchestrates validating and patching the data for the amend adjustment factors page - * @module SubmitAmendedAdjustmentFactorService -*/ - -const AdjustmentFactorValidator = require('../../../validators/bill-runs/two-part-tariff/adjustment-factor.validator.js') -const AmendAdjustmentFactorPresenter = require('../../../presenters/bill-runs/two-part-tariff/amend-adjustment-factor.presenter.js') -const FetchReviewChargeReferenceService = require('./fetch-review-charge-reference.service.js') -const ReviewChargeReferenceModel = require('../../../models/review-charge-reference.model.js') - -/** - * Orchestrates validating the data for the amend adjustment factor page and patching the db value - - * @param {string} billRunId - The UUID for the bill run - * @param {string} licenceId - The UUID of the licence that is being reviewed - * @param {string} reviewChargeReferenceId - The UUID of the review charge reference being updated - * @param {object} payload - The submitted form data - * @param {object} yar - The Hapi `request.yar` session manager passed on by the controller - * - * @returns {Promise} The updated value for the adjustment factor - */ -async function go (billRunId, licenceId, reviewChargeReferenceId, payload, yar) { - const validationResult = _validate(payload) - - if (!validationResult) { - await _persistAmendedAdjustmentFactor(reviewChargeReferenceId, payload) - yar.flash('banner', 'The adjustment factors for this licence have been updated') - - return { error: null } - } - - const pageData = await _getPageData(billRunId, reviewChargeReferenceId, licenceId) - - return { - activeNavBar: 'search', - pageTitle: 'Set the adjustment factors', - error: validationResult, - inputtedAggregateValue: payload.amendedAggregateFactor, - inputtedChargeValue: payload.amendedChargeAdjustment, - ...pageData - } -} - -async function _getPageData (billRunId, reviewChargeReferenceId, licenceId) { - const { - billRun, - reviewChargeReference - } = await FetchReviewChargeReferenceService.go(billRunId, reviewChargeReferenceId) - - return AmendAdjustmentFactorPresenter.go(billRun, reviewChargeReference, licenceId) -} - -async function _persistAmendedAdjustmentFactor (reviewChargeReferenceId, payload) { - await ReviewChargeReferenceModel.query() - .findById(reviewChargeReferenceId) - .patch({ amendedAggregate: payload.amendedAggregateFactor }) - - await ReviewChargeReferenceModel.query() - .findById(reviewChargeReferenceId) - .patch({ amendedChargeAdjustment: payload.amendedChargeAdjustment }) -} - -function _validate (payload) { - const maxDecimalsForAggregateValue = 15 - const maxDecimalsForChargeAdjustmentValue = 15 - - const aggregateValidation = AdjustmentFactorValidator.go( - payload.amendedAggregateFactor, - maxDecimalsForAggregateValue, - 'aggregate') - - const chargeValidation = AdjustmentFactorValidator.go( - payload.amendedChargeAdjustment, - maxDecimalsForChargeAdjustmentValue, - 'charge') - - if (!aggregateValidation.error && !chargeValidation.error) { - return null - } - - return { - aggregateFactorElement: aggregateValidation.error ? { text: aggregateValidation.error.details[0].message } : null, - chargeAdjustmentElement: chargeValidation.error ? { text: chargeValidation.error.details[0].message } : null - } -} - -module.exports = { - go -} diff --git a/app/services/bill-runs/two-part-tariff/submit-amended-authorised-volume.service.js b/app/services/bill-runs/two-part-tariff/submit-amended-authorised-volume.service.js deleted file mode 100644 index 1db2db604e..0000000000 --- a/app/services/bill-runs/two-part-tariff/submit-amended-authorised-volume.service.js +++ /dev/null @@ -1,68 +0,0 @@ -'use strict' - -/** - * Orchestrates validating and patching the data for the amend authorised volume page - * @module SubmitAmendedAuthorisedVolumeService -*/ - -const AmendAuthorisedVolumePresenter = require('../../../presenters/bill-runs/two-part-tariff/amend-authorised-volume.presenter.js') -const AuthorisedVolumeValidator = require('../../../validators/bill-runs/two-part-tariff/authorised-volume.validator.js') -const FetchAuthorisedVolumeService = require('./fetch-authorised-volume.service.js') -const ReviewChargeReferenceModel = require('../../../models/review-charge-reference.model.js') - -/** - * Orchestrates validating the data for the amend authorised volume page and patching the db value - * - * @param {string} billRunId - The UUID for the bill run - * @param {string} licenceId - The UUID of the licence that is being reviewed - * @param {string} reviewChargeReferenceId - The UUID of the review charge reference being updated - * @param {object} payload - The submitted form data - * @param {object} yar - The Hapi `request.yar` session manager passed on by the controller - * - * @returns {Promise} The updated value for the authorised volume - */ -async function go (billRunId, licenceId, reviewChargeReferenceId, payload, yar) { - const validationResult = _validate(payload) - - if (!validationResult) { - await _persistAuthorisedVolume(reviewChargeReferenceId, payload) - yar.flash('banner', 'The authorised volume for this licence have been updated') - - return { error: null } - } - - const { billRun, reviewChargeReference } = await FetchAuthorisedVolumeService.go(billRunId, reviewChargeReferenceId) - - const pageData = AmendAuthorisedVolumePresenter.go(billRun, reviewChargeReference, licenceId) - - return { - activeNavBar: 'search', - pageTitle: 'Set the authorised volume', - error: validationResult, - ...pageData - } -} - -async function _persistAuthorisedVolume (reviewChargeReferenceId, payload) { - return ReviewChargeReferenceModel.query() - .findById(reviewChargeReferenceId) - .patch({ amendedAuthorisedVolume: payload.authorisedVolume }) -} - -function _validate (payload) { - const validation = AuthorisedVolumeValidator.go(payload) - - if (!validation.error) { - return null - } - - const authorisedVolume = validation.error.details[0].message - - return { - authorisedVolume - } -} - -module.exports = { - go -} diff --git a/app/services/bill-runs/two-part-tariff/submit-amended-billable-returns.service.js b/app/services/bill-runs/two-part-tariff/submit-amended-billable-returns.service.js deleted file mode 100644 index ef0651abe3..0000000000 --- a/app/services/bill-runs/two-part-tariff/submit-amended-billable-returns.service.js +++ /dev/null @@ -1,76 +0,0 @@ -'use strict' - -/** - * Orchestrates validating and patching the data for the amend billable returns page - * @module SubmitAmendedBillableReturnsService -*/ - -const AmendBillableReturnsPresenter = require('../../../presenters/bill-runs/two-part-tariff/amend-billable-returns.presenter.js') -const BillableReturnsValidator = require('../../../validators/bill-runs/two-part-tariff/billable-returns.validator.js') -const FetchMatchDetailsService = require('./fetch-match-details.service.js') -const ReviewChargeElementModel = require('../../../models/review-charge-element.model.js') - -/** - * Orchestrates validating the data for the amend billable returns page and patching the db value - * - * @param {string} billRunId - The UUID for the bill run - * @param {string} licenceId - The UUID of the licence that is being reviewed - * @param {string} reviewChargeElementId - The UUID of the review charge element being updated - * @param {object} payload - The submitted form data - * @param {object} yar - The Hapi `request.yar` session manager passed on by the controller - * - * @returns {Promise} The updated value for the billable returns - */ -async function go (billRunId, licenceId, reviewChargeElementId, payload, yar) { - const validationResult = _validate(payload) - - if (!validationResult) { - await _persistAmendedBillableReturns(reviewChargeElementId, payload) - yar.flash('banner', 'The billable returns for this licence have been updated') - - return { error: null } - } - - const { billRun, reviewChargeElement } = await FetchMatchDetailsService.go(billRunId, reviewChargeElementId) - const pageData = AmendBillableReturnsPresenter.go(billRun, reviewChargeElement, licenceId) - - return { - activeNavBar: 'search', - pageTitle: 'Set the billable returns quantity for this bill run', - error: validationResult, - customQuantitySelected: payload['quantity-options'] === 'customQuantity', - customQuantityValue: payload.customQuantity, - ...pageData - } -} - -function _persistAmendedBillableReturns (reviewChargeElementId, payload) { - const volume = payload['quantity-options'] === 'customQuantity' ? payload.customQuantity : payload['quantity-options'] - - return ReviewChargeElementModel.query() - .findById(reviewChargeElementId) - .patch({ amendedAllocated: volume }) -} - -function _validate (payload) { - const validation = BillableReturnsValidator.go(payload) - - if (!validation.error) { - return null - } - - const { message } = validation.error.details[0] - - const radioFormElement = payload['quantity-options'] === 'customQuantity' ? null : { text: message } - const customQuantityInputFormElement = payload['quantity-options'] === 'customQuantity' ? { text: message } : null - - return { - message, - radioFormElement, - customQuantityInputFormElement - } -} - -module.exports = { - go -} diff --git a/app/services/bill-runs/two-part-tariff/submit-remove-bill-run-licence.service.js b/app/services/bill-runs/two-part-tariff/submit-remove-bill-run-licence.service.js deleted file mode 100644 index 78d2e4ba3a..0000000000 --- a/app/services/bill-runs/two-part-tariff/submit-remove-bill-run-licence.service.js +++ /dev/null @@ -1,69 +0,0 @@ -'use strict' - -/** - * Orchestrates removing a licence from a bill run whilst it is at the review stage - * @module SubmitRemoveBillRunLicenceService - */ - -const BillRunModel = require('../../../models/bill-run.model.js') -const CreateLicenceSupplementaryYearService = require('../../licences/supplementary/create-licence-supplementary-year.service.js') -const LicenceModel = require('../../../models/licence.model.js') -const RemoveReviewDataService = require('./remove-review-data.service.js') -const ReviewLicenceModel = require('../../../models/review-licence.model.js') - -/** - * Orchestrates removing a licence from a bill run whilst it is at the review stage - * - * It does this by deleting all of the persisted data relating to the licence from the review tables. The licence will - * then be flagged for 2PT supplementary billing. If after removing a licence the bill run is empty, the bill run status - * will be set to `empty` and `true` returned so that the user is redirected back to the Bill runs page rather - * than Review bill run. - * - * @param {string} billRunId - The UUID of the bill run that the licence is in - * @param {string} licenceId - UUID of the licence to remove from the bill run - * @param {object} yar - The Hapi `request.yar` session manager passed on by the controller - * - * @returns {Promise} true if all the licences have been removed from the bill run else false - */ -async function go (billRunId, licenceId, yar) { - await RemoveReviewDataService.go(billRunId, licenceId) - - await _flagForSupplementaryBilling(licenceId, billRunId) - - const allLicencesRemoved = await _allLicencesRemoved(billRunId) - - if (!allLicencesRemoved) { - const { licenceRef } = await LicenceModel.query().findById(licenceId) - - // NOTE: The banner message is only set if licences remain in the bill run. This is because if there are no longer - // any licences remaining in the bill run the user is redirected to the "Bill runs" page instead of - // "Review licences". As the banner isn't displayed on the "Bill runs" page the message would remain in the cookie. - yar.flash('banner', `Licence ${licenceRef} removed from the bill run.`) - } - - return allLicencesRemoved -} - -async function _allLicencesRemoved (billRunId) { - const count = await ReviewLicenceModel.query().where('billRunId', billRunId).resultSize() - - if (count === 0) { - await BillRunModel.query().findById(billRunId).patch({ status: 'empty' }) - - return true - } - - return false -} - -async function _flagForSupplementaryBilling (licenceId, billRunId) { - const twoPartTariff = true - const { toFinancialYearEnding } = await BillRunModel.query() - .findById(billRunId) - - return CreateLicenceSupplementaryYearService.go(licenceId, [toFinancialYearEnding], twoPartTariff) -} - -module.exports = { - go -} diff --git a/app/services/return-versions/setup/fetch-existing-requirements.js b/app/services/return-versions/setup/fetch-existing-requirements.service.js similarity index 100% rename from app/services/return-versions/setup/fetch-existing-requirements.js rename to app/services/return-versions/setup/fetch-existing-requirements.service.js diff --git a/app/services/return-versions/setup/generate-from-existing-requirements.service.js b/app/services/return-versions/setup/generate-from-existing-requirements.service.js index 63090764f6..ea74b46f3e 100644 --- a/app/services/return-versions/setup/generate-from-existing-requirements.service.js +++ b/app/services/return-versions/setup/generate-from-existing-requirements.service.js @@ -5,7 +5,7 @@ * @module GenerateFromExistingRequirementsService */ -const FetchExistingRequirementsService = require('./fetch-existing-requirements.js') +const FetchExistingRequirementsService = require('./fetch-existing-requirements.service.js') /** * Generates returns setup requirements from an existing return version diff --git a/app/validators/bill-runs/review/authorised.validator.js b/app/validators/bill-runs/review/authorised.validator.js new file mode 100644 index 0000000000..2a40c10e96 --- /dev/null +++ b/app/validators/bill-runs/review/authorised.validator.js @@ -0,0 +1,78 @@ +'use strict' + +/** + * Validates data submitted for the review charge reference authorised page + * @module AuthorisedValidator + */ + +const Joi = require('joi') + +/** + * Validates data submitted for the review charge reference authorised page + * + * When editing the authorised volume on the charge reference, the user input box is pre-populated with the current + * value. The user must overwrite this value with there own value to amend the authorised volume. + * The validation happening here is to ensure that the volume has been entered, it has a maximum 6 decimal places and + * is more than the totalBillableReturns. + * + * @param {object} payload - The payload from the request to be validated + * + * @returns {object} the result from calling Joi's schema.validate(). It will be an object with a `value:` property. If + * any errors are found the `error:` property will also exist detailing what the issues were + */ +function go (payload) { + const { amendedAuthorisedVolume, totalBillableReturns } = payload + + return _validate(amendedAuthorisedVolume, Number(totalBillableReturns)) +} + +function _validate (amendedAuthorisedVolume, totalBillableReturns) { + const schema = Joi + .number() + .min(totalBillableReturns) + .required() + .custom(_maxDecimals, 'Max decimals') + .messages({ + 'number.base': 'The authorised volume must be a number', + 'number.unsafe': 'The authorised volume must be a number or fewer than 17 digits long', + 'number.min': `The authorised volume must be greater than ${totalBillableReturns}`, + 'any.required': 'Enter an authorised volume', + 'any.invalid': 'The authorised volume must not have more than 6 decimal places' + }) + + return schema.validate(amendedAuthorisedVolume, { abortEarly: false }) +} + +/** + * Custom JOI validator to check a value does not have more than 6 decimal places + * + * We are limited to 6 decimals by the Rules Service that will eventually be called to calculate the charge. Due to + * limitations in Joi validation for decimals, achieving validation for numbers with more than 6 decimal places + * requires a custom approach. + * + * See {@link https://github.com/hapijs/joi/blob/master/API.md#anycustommethod-description | Joi custom validation}. + * + * @param {number} value - the value to be validated + * @param {object} helpers - a Joi object containing a numbers of helpers + * + * @returns {number|object} if valid the original value else a Joi 'any.invalid' error. Knowing we return this means + * you can assign what error message to use when a number has too many decimals. + */ +function _maxDecimals (value, helpers) { + // Guard clause to ensure we don't try and interact with a null or undefined value + if (!value) { + return value + } + + const parts = value.toString().split('.') + + if (parts.length === 1 || parts[1].length <= 6) { + return value + } + + return helpers.error('any.invalid') +} + +module.exports = { + go +} diff --git a/app/validators/bill-runs/review/edit.validator.js b/app/validators/bill-runs/review/edit.validator.js new file mode 100644 index 0000000000..f68d61d4ff --- /dev/null +++ b/app/validators/bill-runs/review/edit.validator.js @@ -0,0 +1,95 @@ +'use strict' + +/** + * Validates data submitted for the review charge element edit page + * @module EditValidator + */ + +const Joi = require('joi') + +/** + * Validates data submitted for the review charge element edit page + * + * When editing the charge element's billable volume the user must either choose the existing authorised volume or enter + * there own custom volume. The validation happening here is to ensure that a user selects either option and if its the + * custom one, that they enter a number above 0 but below the authorised volume and that the number is less than 6 + * decimal places. + * + * @param {object} payload - The payload from the request to be validated + * + * @returns {object} the result from calling Joi's schema.validate(). It will be an object with a `value:` property. If + * any errors are found the `error:` property will also exist detailing what the issues were + */ +function go (payload) { + const { quantityOptions } = payload + + if (quantityOptions === 'customQuantity') { + return _validateCustomQuantity(payload.customQuantity, Number(payload.authorisedVolume)) + } + + return _validateAuthorisedQuantity(quantityOptions) +} + +/** + * Custom JOI validator to check a value does not have more than 6 decimal places + * + * We are limited to 6 decimals by the Rules Service that will eventually be called to calculate the charge. Due to + * limitations in Joi validation for decimals, achieving validation for numbers with more than 6 decimal places + * requires a custom approach. + * + * See {@link https://github.com/hapijs/joi/blob/master/API.md#anycustommethod-description | Joi custom validation}. + * + * @param {number} value - the value to be validated + * @param {object} helpers - a Joi object containing a numbers of helpers + * + * @returns {number|object} if valid the original value else a Joi 'any.invalid' error. Knowing we return this means + * you can assign what error message to use when a number has too many decimals. + */ +function _maxDecimals (value, helpers) { + // Guard clause to ensure we don't try and interact with a null or undefined value + if (!value) { + return value + } + + const parts = value.toString().split('.') + + if (parts.length === 1 || parts[1].length <= 6) { + return value + } + + return helpers.error('any.invalid') +} + +function _validateCustomQuantity (customQuantity, authorisedVolume) { + const schema = Joi + .number() + .min(0) + .max(authorisedVolume) + .custom(_maxDecimals, 'Max decimals') + .required() + .messages({ + 'number.unsafe': 'The quantity must be a number', + 'number.base': 'The quantity must be a number', + 'number.min': 'The quantity must be zero or higher', + 'number.max': 'The quantity must be the same as or less than the authorised amount', + 'any.required': 'Enter the billable quantity', + 'any.invalid': 'The quantity must contain no more than 6 decimal places' + }) + + return schema.validate(customQuantity, { abortEarly: true }) +} + +function _validateAuthorisedQuantity (quantityOptions) { + const schema = Joi + .number() + .required() + .messages({ + 'any.required': 'Select the billable quantity' + }) + + return schema.validate(quantityOptions, { abortEarly: true }) +} + +module.exports = { + go +} diff --git a/app/validators/bill-runs/review/factors.validator.js b/app/validators/bill-runs/review/factors.validator.js new file mode 100644 index 0000000000..17203f48d9 --- /dev/null +++ b/app/validators/bill-runs/review/factors.validator.js @@ -0,0 +1,84 @@ +'use strict' + +/** + * Validates data submitted for the review charge reference factors page + * @module FactorsValidator + */ + +const Joi = require('joi') + +/** + * Validates data submitted for the review charge reference factors page + * + * There are two inputs on the page, both of which need to contain valid values. However, they are always pre-populated + * with existing data, so in theory would only both be invalid if the user has incorrectly updated both. + * + * @param {object} payload - The payload from the request to be validated + * + * @returns {object} The result from calling Joi's schema.validate(). If any errors are found the `error:` property will + * also exist detailing what the issue is. + */ +function go (payload) { + const schema = Joi.object({ + amendedAggregate: Joi + .number() + .min(0) + .required() + .custom(_maxDecimals, 'Max decimals') + .messages({ + 'number.base': 'The aggregate factor must be a number', + 'number.unsafe': 'The aggregate factor must be a number', + 'number.min': 'The aggregate factor must be greater than 0', + 'any.required': 'Enter an aggregate factor', + 'any.invalid': 'The aggregate factor must not have more than 15 decimal places' + }), + amendedChargeAdjustment: Joi + .number() + .min(0) + .required() + .custom(_maxDecimals, 'Max decimals') + .messages({ + 'number.base': 'The charge factor must be a number', + 'number.unsafe': 'The charge factor must be a number', + 'number.min': 'The charge factor must be greater than 0', + 'any.required': 'Enter a charge factor', + 'any.invalid': 'The charge factor must not have more than 15 decimal places' + }) + }) + + return schema.validate(payload, { abortEarly: false }) +} + +/** + * Custom JOI validator to check a value does not have more than 15 decimal places + * + * We are limited to 15 decimals by the Rules Service that will eventually be called to calculate the charge. Due to + * limitations in Joi validation for decimals, achieving validation for numbers with more than 15 decimal places + * requires a custom approach. + * + * See {@link https://github.com/hapijs/joi/blob/master/API.md#anycustommethod-description | Joi custom validation}. + * + * @param {number} value - the value to be validated + * @param {object} helpers - a Joi object containing a numbers of helpers + * + * @returns {number|object} if valid the original value else a Joi 'any.invalid' error. Knowing we return this means + * you can assign what error message to use when a number has too many decimals. + */ +function _maxDecimals (value, helpers) { + // Guard clause to ensure we don't try and interact with a null or undefined value + if (!value) { + return value + } + + const parts = value.toString().split('.') + + if (parts.length === 1 || parts[1].length <= 15) { + return value + } + + return helpers.error('any.invalid') +} + +module.exports = { + go +} diff --git a/app/validators/bill-runs/two-part-tariff/adjustment-factor.validator.js b/app/validators/bill-runs/two-part-tariff/adjustment-factor.validator.js deleted file mode 100644 index 0b390c436c..0000000000 --- a/app/validators/bill-runs/two-part-tariff/adjustment-factor.validator.js +++ /dev/null @@ -1,89 +0,0 @@ -'use strict' - -/** - * Validates data submitted for the `/bill-runs/{billRunId}/review/{licenceId}/charge-reference-details/ - * {reviewChargeReferenceId}/amend-adjustment-factor` page - * @module AdjustmentFactorValidator - */ - -const Joi = require('joi') - -/** - * Validates data submitted for the `/bill-runs/{billRunId}/review/{licenceId}/charge-reference-details/ - * {reviewChargeReferenceId}/amend-adjustment-factor` page - * - * When editing the aggregate factor or the charge adjustment on the charge reference, the user input boxes are - * pre-populated with the current value for both. The user must overwrite this value with there own value to amend the - * adjustments. - * The validation happening here is to ensure that the adjustments have been entered. Both have a - * minimum value of 0 and they both get validated to either 2 or 15 decimal places. - * - * @param {object} payload - The payload from the request to be validated - * @param {number} maxNumberOfDecimals - The maximum number of decimal places the factor can be validated to - * @param {string} validationType - The type of factor being validated, this is to add to the validation messages - * - * @returns {object} the result from calling Joi's schema.validate(). It will be an object with a `value:` property. If - * any errors are found the `error:` property will also exist detailing what the issues were - */ -function go (payload, maxNumberOfDecimals, validationType) { - return _validate(payload, maxNumberOfDecimals, validationType) -} - -/** - * Due to limitations in Joi validation for decimals, achieving validation for numbers with fewer than 2 or 15 decimal - * places requires a custom approach. First, convert the number into a string. Then split the string into an array using - * the decimal point (`.`) as the delimiter. This results in either one item in the array (if no decimal is present) or - * two items (if a decimal is present). The first item represents the part before the decimal, while the second item - * represents the part after. By assessing if the length of the second string is less than 3 or 16, we can validate if - * there the correct number of decimals. - * - * @private - */ -function _customValidation (quantity, helpers, maxNumberOfDecimals, validationType) { - const quantityParts = quantity.toString().split('.') - - if (quantityParts.length === 1 || quantityParts[1].length <= maxNumberOfDecimals) { - return quantity - } - - return helpers.message({ - custom: `The ${validationType} factor must not have more than ${maxNumberOfDecimals} decimal places` - }) -} - -function _validate (quantity, maxNumberOfDecimals, validationType) { - const schema = Joi.object({ - quantity: Joi - .number() - .min(0) - .required() - .messages({ - 'number.base': `The ${validationType} factor must be a number`, - 'number.unsafe': `The ${validationType} factor must be a number`, - 'number.min': `The ${validationType} factor must be greater than 0`, - 'any.required': `Enter a ${validationType} factor` - }) - }) - - const validation = schema.validate({ quantity }, { abortEarly: false }) - - // The first check we are doing is validating that a number has been inputted. If it is a number then we can move onto - // checking if there are a valid number of decimal places - if (!validation.error) { - return _validateDecimals(quantity, maxNumberOfDecimals, validationType) - } - - return validation -} - -function _validateDecimals (quantity, maxNumberOfDecimals, validationType) { - const decimalSchema = Joi.number().custom((value, helpers) => { - return _customValidation(value, helpers, maxNumberOfDecimals, validationType) - }) - - return decimalSchema.validate(quantity) -} - -module.exports = { - go -} diff --git a/app/validators/bill-runs/two-part-tariff/authorised-volume.validator.js b/app/validators/bill-runs/two-part-tariff/authorised-volume.validator.js deleted file mode 100644 index bfe127b126..0000000000 --- a/app/validators/bill-runs/two-part-tariff/authorised-volume.validator.js +++ /dev/null @@ -1,85 +0,0 @@ -'use strict' - -/** - * Validates data submitted for the `/bill-runs/{billRunId}/review/{licenceId}/charge-reference-details/ - * {reviewChargeReferenceId}/amend-authorised-volume` page - * @module AuthorisedVolumeValidator - */ - -const Joi = require('joi') - -/** - * Validates data submitted for the `/bill-runs/{billRunId}/review/{licenceId}/charge-reference-details/ - * {reviewChargeReferenceId}/amend-authorised-volume` page - * - * When editing the authorised volume on the charge reference, the user input box is pre-populated with the current - * value. The user must overwrite this value with there own value to amend the authorised volume. - * The validation happening here is to ensure that the volume has been entered, it has a maximum 6 decimal places and - * is more than the totalBillableReturns. - * - * @param {object} payload - The payload from the request to be validated - * - * @returns {object} the result from calling Joi's schema.validate(). It will be an object with a `value:` property. If - * any errors are found the `error:` property will also exist detailing what the issues were - */ -function go (payload) { - const { authorisedVolume, totalBillableReturns } = payload - - const validation = _validate(authorisedVolume, Number(totalBillableReturns)) - - // The first check we are doing is validating that a number has been inputted within the correct range. If it has - // then we can move onto next validating the number of decimal places - if (!validation.error) { - const decimalSchema = Joi.number().custom(_customValidation, 'custom validation') - - return decimalSchema.validate(authorisedVolume) - } - - return validation -} - -/** - * Due to limitations in Joi validation for decimals, achieving validation for numbers with fewer than 6 decimal - * places requires a custom approach. First, convert the number into a string. Then split the string into an array using - * the decimal point (`.`) as the delimiter. This results in either one item in the array (if no decimal is present) or - * two items (if a decimal is present). The first item represents the part before the decimal, while the second item - * represents the part after. By assessing if the length of the second string is less than o equal to 6, we can validate - * if there are the correct number of decimals. - * - * @private - */ -function _customValidation (quantity, helpers) { - const maxNumberOfDecimals = 6 - const quantityParts = quantity.toString().split('.') - - if (quantityParts.length === 1 || quantityParts[1].length <= maxNumberOfDecimals) { - return quantity - } - - return helpers.message({ - custom: 'The authorised volume must not have more than 6 decimal places' - }) -} - -function _validate (authorisedVolume, totalBillableReturns) { - const schema = Joi.object({ - authorisedVolume: Joi - .number() - .min(totalBillableReturns) - .required() - .messages({ - 'number.base': 'The authorised volume must be a number', - 'number.unsafe': 'The authorised volume must be a number or fewer than 17 digits long', - 'number.min': `The authorised volume must be greater than ${totalBillableReturns}`, - 'any.required': 'Enter an authorised volume' - }) - }) - - const validation = schema.validate({ authorisedVolume }, { abortEarly: false }) - - return validation -} - -module.exports = { - go -} diff --git a/app/validators/bill-runs/two-part-tariff/billable-returns.validator.js b/app/validators/bill-runs/two-part-tariff/billable-returns.validator.js deleted file mode 100644 index 09f2531c7c..0000000000 --- a/app/validators/bill-runs/two-part-tariff/billable-returns.validator.js +++ /dev/null @@ -1,99 +0,0 @@ -'use strict' - -/** - * Validates data submitted for the `/bill-runs/{billRunId}/review/{licenceId}/match-details/{reviewChargeElementId} - * /amend-billable-returns` page - * @module BillableReturnsValidator - */ - -const Joi = require('joi') - -/** - * Validates data submitted for the `/bill-runs/{billRunId}/review/{licenceId}/match-details/{reviewChargeElementId} - * /amend-billable-returns` page - * - * When editing the charge elements billable volume the user must either choose the existing authorised volume or enter - * there own custom volume. The validation happening here is to ensure that a user selects either option and if its the - * custom one, that they enter a number above 0 but below the authorised volume and that the number is less than 6 - * decimal places. - * @param {object} payload - The payload from the request to be validated - * - * @returns {object} the result from calling Joi's schema.validate(). It will be an object with a `value:` property. If - * any errors are found the `error:` property will also exist detailing what the issues were - */ -function go (payload) { - const { 'quantity-options': selectedOption } = payload - - if (selectedOption === 'customQuantity') { - return _validateCustomQuantity(payload.customQuantity, Number(payload.authorisedVolume)) - } - - return _validateAuthorisedQuantity(selectedOption) -} - -/** - * Due to limitations in Joi validation for decimals, achieving validation for numbers with fewer than 6 decimal places - * requires a custom approach. First, convert the number into a string. Then split the string into an array using the - * decimal point (`.`) as the delimiter. This results in either one item in the array (if no decimal is present) or two - * items (if a decimal is present). The first item represents the part before the decimal, while the second item - * represents the part after. By assessing if the length of the second string is less than 7, we can validate if there - * the correct number of decimals. - * - * @private - */ -function customValidation (customQuantity, helpers) { - const maxNumberOfDecimals = 7 - const customQuantityParts = customQuantity.toString().split('.') - - if (customQuantityParts.length === 1 || customQuantityParts[1].length < maxNumberOfDecimals) { - return customQuantity - } - - return helpers.message({ custom: 'The quantity must contain no more than 6 decimal places' }) -} - -function _validateCustomQuantity (customQuantity, authorisedVolume) { - const schema = Joi.object({ - customQuantity: Joi - .number() - .min(0) - .max(authorisedVolume) - .required() - .messages({ - 'number.unsafe': 'The quantity must be a number', - 'number.base': 'The quantity must be a number', - 'number.min': 'The quantity must be zero or higher', - 'number.max': 'The quantity must be the same as or less than the authorised amount', - 'any.required': 'Enter the billable quantity' - }) - }) - - const validation = schema.validate({ customQuantity }, { abortEarly: true }) - - // The first check we are doing is validating that a number has been inputted. If it has then we can move onto our - // next check for if there are less than 7 decimal places. - if (!validation.error) { - const decimalSchema = Joi.number().custom(customValidation, 'custom validation') - - return decimalSchema.validate(customQuantity) - } - - return validation -} - -function _validateAuthorisedQuantity (selectedOption) { - const schema = Joi.object({ - selectedOption: Joi - .number() - .required() - .messages({ - 'any.required': 'Select the billable quantity' - }) - }) - - return schema.validate({ selectedOption }, { abortEarly: true }) -} - -module.exports = { - go -} diff --git a/app/views/bill-runs/amend-adjustment-factor.njk b/app/views/bill-runs/amend-adjustment-factor.njk deleted file mode 100644 index 761af487f9..0000000000 --- a/app/views/bill-runs/amend-adjustment-factor.njk +++ /dev/null @@ -1,105 +0,0 @@ -{% extends 'layout.njk' %} -{% from "govuk/components/back-link/macro.njk" import govukBackLink %} -{% from "govuk/components/inset-text/macro.njk" import govukInsetText %} -{% from "govuk/components/input/macro.njk" import govukInput %} -{% from "govuk/components/button/macro.njk" import govukButton %} -{% from 'govuk/components/error-summary/macro.njk' import govukErrorSummary %} - - -{% block breadcrumbs %} - {# Back link #} - {{ govukBackLink({ - text: 'Go back to licence', - href: '/system/bill-runs/' + billRunId + '/review/' + licenceId + '/charge-reference-details/' + chargeReference.id - }) }} -{% endblock %} - -{% block content %} -
-
- - {% set errorMessage %} -
{{error.aggregateFactorElement.text}}
-
{{error.chargeAdjustmentElement.text}}
- {% endset %} - - {% if error %} - {{ govukErrorSummary({ - titleText: 'There is a problem', - errorList: [ - { - html: errorMessage, - href: '#adjustment-factor-error' - } - ] - }) }} - {% endif %} - - {{chargeReference.description}} -

Set the adjustment factors

- - {% set insertText %} -

- Financial year - {{financialYear}} -

- -

- Charge period - {{chargePeriod}} -

-

Other adjustments apply:

- - {% for adjustment in chargeReference.otherAdjustments %} -
{{adjustment}}
- {% endfor %} - {% endset %} - - {{ govukInsetText({ - html: insertText - }) }} - - {% if inputtedAggregateValue %} - {% set aggregateValue = inputtedAggregateValue %} - {% else %} - {% set aggregateValue = chargeReference.aggregateFactor %} - {% endif %} - - {% if inputtedChargeValue %} - {% set chargeValue = inputtedChargeValue %} - {% else %} - {% set chargeValue = chargeReference.chargeAdjustment %} - {% endif %} - -
- - - {{ govukInput({ - label: { - text: "Aggregate factor", - classes: "govuk-label" - }, - id: "amended-aggregate-factor", - name: "amendedAggregateFactor", - classes: "govuk-input--width-10", - value: aggregateValue, - errorMessage: error.aggregateFactorElement - }) }} - - {{ govukInput({ - label: { - text: "Charge adjustment", - classes: "govuk-label" - }, - id: "amended-charge-adjustment", - name: "amendedChargeAdjustment", - classes: "govuk-input govuk-input--width-10", - value: chargeValue, - errorMessage: error.chargeAdjustmentElement - }) }} - - {{ govukButton({ text: 'Confirm', preventDoubleClick: true }) }} -
-
-
-{% endblock %} diff --git a/app/views/bill-runs/amend-billable-returns.njk b/app/views/bill-runs/amend-billable-returns.njk deleted file mode 100644 index 4aaf23b8bf..0000000000 --- a/app/views/bill-runs/amend-billable-returns.njk +++ /dev/null @@ -1,108 +0,0 @@ -{% extends 'layout.njk' %} -{% from "govuk/components/back-link/macro.njk" import govukBackLink %} -{% from "govuk/components/button/macro.njk" import govukButton %} -{% from "govuk/components/inset-text/macro.njk" import govukInsetText %} -{% from "govuk/components/radios/macro.njk" import govukRadios %} -{% from "govuk/components/input/macro.njk" import govukInput %} -{% from 'govuk/components/error-summary/macro.njk' import govukErrorSummary %} - - -{% block breadcrumbs %} - {# Back link #} - {{ govukBackLink({ - text: 'Go back to licence', - href: '/system/bill-runs/' + billRun.id + '/review/' + licenceId + '/match-details/' + chargeElement.reviewChargeElementId - }) }} -{% endblock %} - -{% set quantityInputHTML %} - {% if error.customQuantityInputFormElement %} - {% set errorClass = 'govuk-input--error' %} - {% endif %} - - {{ govukInput({ - id: "custom-quantity-input", - name: "customQuantity", - errorMessage: error.customQuantityInputFormElement, - classes: "govuk-!-width-one-third " + errorClass, - value: customQuantityValue, - label: { - text: "Billable returns quantity" - }, - hint: { - text: "Enter a number with no more than 6 decimal places. For example, 20.123456" - }, - suffix: { - text: "ML" - } - }) }} - -{% endset %} - -{% block content %} - {% if error %} - {{ govukErrorSummary({ - titleText: 'There is a problem', - errorList: [ - { - text: error.message, - href: '#billable-volumes-error' - } - ] - }) }} - {% endif %} - - {% set insertText %} -
- Financial year {{billRun.financialYear}} -
-
- Charge period {{chargeVersion.chargePeriod}} -
- {%endset%} - - {% set secondHeader %} - {{ govukInsetText({ - html: insertText - }) }} - {% endset %} - -
-
- - - {{ govukRadios({ - name: 'quantity-options', - errorMessage: error.radioFormElement, - fieldset: { - legend: { - html: '' + chargeElement.description + ' ' + chargeElement.dates + '' + '
' + 'Set the billable returns quantity for this bill run' + '
' + secondHeader, - isPageHeading: true, - classes: 'govuk-fieldset__legend--l' - } - }, - items: [ - { - id: 'authorised-quantity', - value: authorisedQuantity, - html: '
Authorised ' + authorisedQuantity + 'ML
' - }, - { - id: 'custom-quantity', - value: 'customQuantity', - text: 'Custom quantity', - checked: customQuantitySelected, - conditional: { - html: quantityInputHTML - } - } - ] - }) }} - - {# Hidden input for authorised volume #} - - - {{ govukButton({ text: 'Confirm', preventDoubleClick: true }) }} -
-
-{% endblock %} diff --git a/app/views/bill-runs/charge-reference-details.njk b/app/views/bill-runs/charge-reference-details.njk deleted file mode 100644 index 12e88c71c5..0000000000 --- a/app/views/bill-runs/charge-reference-details.njk +++ /dev/null @@ -1,156 +0,0 @@ -{% extends 'layout.njk' %} -{% from "govuk/components/back-link/macro.njk" import govukBackLink %} -{% from "govuk/components/button/macro.njk" import govukButton %} -{% from "govuk/components/summary-list/macro.njk" import govukSummaryList %} -{% from "govuk/components/notification-banner/macro.njk" import govukNotificationBanner %} - -{% block breadcrumbs %} - {# Back link #} - {{ govukBackLink({ - text: 'Go back to licence', - href: '/system/bill-runs/' + billRunId + '/review/' + licenceId - }) }} -{% endblock %} - -{% block content %} - - {# Adjustment factor ammended banner #} - {% if bannerMessage %} - {{ govukNotificationBanner({ - titleText: 'Adjustment updated', - text: bannerMessage - }) }} - {% endif %} - - {# Charge preview banner #} - {% if chargeMessage %} - {{ govukNotificationBanner({ - titleText: 'Information', - text: chargeMessage - }) }} - {% endif %} - -
-
-

- Charge reference {{chargeReference.reference}}{{chargeReference.description}} -

- Financial Year {{financialYear}} -

- Charge period {{chargePeriod}} -

-
-
- -
-
-
-

- {{chargeReference.totalBillableReturns}} ML -
- Total billable returns -
-

-
-
-

- / -

-
-
-

- {{chargeReference.authorisedVolume}} ML -
- Authorised volume -
-

-
-
-
- -
-
- {% if hasAggregateOrChargeFactor %} - - {{ govukButton({ - text: "Change the authorised volume", - classes: "govuk-button--secondary", - preventDoubleClick: true, - href: chargeReference.id + '/amend-authorised-volume' - }) }} - - {% endif %} - - - {{ govukButton({ - text: "Preview the charge", - classes: "govuk-button--secondary", - preventDoubleClick: true, - href: "../preview-charge/" + chargeReference.id - }) }} - -
-
- -{% if chargeReference.additionalCharges or chargeReference.adjustments.length > 0 %} -
-
-

- Reference details -

- - {% set adjustmentsValue %} - {% for adjustment in chargeReference.adjustments %} - {% set adjustmentsIndex = loop.index0 %} -
{{adjustment}}
- {% endfor %} - {% endset %} - - {% if hasAggregateOrChargeFactor %} - {% set showLink = { - items: [ - { - href: chargeReference.id + "/amend-adjustment-factor", - text: "Change factors" - } - ] - }%} - {% endif %} - - {% set tableRows = [] %} - {% if chargeReference.additionalCharges %} - {% set tableRow = { - key: { - text: "Additional charges" - }, - value: { - html: '
' + chargeReference.additionalCharges + '
' - } - } - %} - - {% set tableRows = (tableRows.push(tableRow), tableRows) %} - {% endif %} - - {% if chargeReference.adjustments %} - {% set tableRow = { - key: { - text: "Adjustments" - }, - value: { - html: adjustmentsValue - }, - actions: showLink - } - %} - - {% set tableRows = (tableRows.push(tableRow), tableRows) %} - {% endif %} - - {{ govukSummaryList({ - rows: tableRows - }) }} -

-
-{% endif %} -{% endblock %} diff --git a/app/views/bill-runs/match-details.njk b/app/views/bill-runs/match-details.njk deleted file mode 100644 index 95a47ddf29..0000000000 --- a/app/views/bill-runs/match-details.njk +++ /dev/null @@ -1,230 +0,0 @@ -{% extends 'layout.njk' %} -{% from "govuk/components/back-link/macro.njk" import govukBackLink %} -{% from "govuk/components/tag/macro.njk" import govukTag %} -{% from "govuk/components/summary-list/macro.njk" import govukSummaryList %} -{% from "govuk/components/table/macro.njk" import govukTable %} -{% from "govuk/components/button/macro.njk" import govukButton %} -{% from "macros/review-status-tag.njk" import statusTag %} -{% from "govuk/components/notification-banner/macro.njk" import govukNotificationBanner %} - -{% block breadcrumbs %} - {# Back link #} - {{ govukBackLink({ - text: 'Go back to licence', - href: '/system/bill-runs/' + billRunId + '/review/' + licenceId - }) }} -{% endblock %} - -{% block content %} - {# Billable returns ammended banner #} - {% if bannerMessage %} - {{ govukNotificationBanner({ - titleText: 'Element updated', - text: bannerMessage - }) }} - {% endif %} - -
-
-

- {{ chargeElement.description }} -
{{ chargeElement.dates }}
-

- -

- {{ statusTag(chargeElement.status) }} -

- - {# Licence nav bars #} - -
-
- -
Financial year {{ financialYear }}
-

Charge period {{ chargePeriod }}

- - {# Billable returns and volume #} -
-
-
-

-
- {{chargeElement.billableVolume}}ML -
-
- Billable returns -
-

-
-
-

-
- / -
-

-
-
-

-
- {{chargeElement.authorisedVolume}}ML -
-
- Authorised volume -
-

-
-
-

- {% if chargeElement.issues.length > 0 %} -
-

- Issue -

- {% for issue in chargeElement.issues %} - {% set issuesIndex = loop.index0 %} -
{{ issue }}
- {% endfor %} -
- {% endif %} -

-
-
-
- - {# Billable returns button #} -
-
- {{ govukButton({ - text: "Edit the billable returns", - href: "/system/bill-runs/" + billRunId + "/review/" + licenceId + "/match-details/" + chargeElement.chargeElementId + "/amend-billable-returns", - classes: "govuk-button--secondary", - preventDoubleClick: true - }) }} -
-
- - - {# Matched Returns #} - {% if matchedReturns.length > 0 %} - {% set tableRows = [] %} - {% for return in matchedReturns %} - {% set matchedReturnIndex = loop.index0 %} - - {% set action %} - {{ return.reference }} -
{{ return.dates }}
-
{{return.absPeriod}}
- {% endset %} - - {% set returnSummary %} -
- {{ return.purpose }} -
- {{ return.description }} - {% endset %} - - {% set tag %} - {{ statusTag(return.returnStatus) }} - {% endset %} - - {% set issues = '' %} - {% for issue in return.issues %} - {% set issues = issues + '
' + issue + '
' %} - {% endfor %} - - {% set tableRow = [ - { - html: '
' + action + '
', - classes: 'govuk-body-s' - }, - { - html: '
' + returnSummary + '
', - classes: 'govuk-body-s' - }, - { - html: '
' + tag + '
', - classes: 'govuk-body-s' - }, - { - html: '
' + return.returnTotal + '
' + issues + '
' , - classes: "govuk-body-s govuk-!-text-align-right" - }] - %} - - {% set tableRows = (tableRows.push(tableRow), tableRows) %} - {% endfor %} - - {# Table displaying details of the matched returns #} - {{ govukTable({ - caption: "Matched returns", - captionClasses: "govuk-table__caption--m", - attributes: { 'data-test': 'matched-returns' }, - firstCellIsHeader: false, - head: [ - { - text: 'Return reference and periods' - }, - { - text: 'Purpose and description' - }, - { - text: 'Status' - }, - { - text: 'Return totals Allocated/Total', - format: 'numeric', - classes: 'width-one-tenth' - } - ], - rows: tableRows - }) }} - {% endif %} - - {# No Returns #} - {% if matchedReturns == 0 %} -

No two-part tariff returns

- {% endif %} -{% endblock %} diff --git a/app/views/bill-runs/amend-authorised-volume.njk b/app/views/bill-runs/review/authorised.njk similarity index 52% rename from app/views/bill-runs/amend-authorised-volume.njk rename to app/views/bill-runs/review/authorised.njk index f394ec601c..6d444c531b 100644 --- a/app/views/bill-runs/amend-authorised-volume.njk +++ b/app/views/bill-runs/review/authorised.njk @@ -8,42 +8,45 @@ {% block breadcrumbs %} {# Back link #} {{ govukBackLink({ - text: 'Go back to licence', - href: '/system/bill-runs/' + billRunId + '/review/' + licenceId + '/charge-reference-details/' + chargeReference.id + text: 'Go back to review charge reference', + href: '/system/bill-runs/review/charge-reference/' + reviewChargeReferenceId }) }} {% endblock %} {% block content %} - {% if error %} - {{ govukErrorSummary({ - titleText: 'There is a problem', - errorList: [ - { - text: error.authorisedVolume, - href: '#authorised-volume-error' - } - ] - }) }} - {% endif %} -
- {{chargeReference.description}} -

Set the authorised volume

+ {% if error %} + {{ govukErrorSummary({ + titleText: 'There is a problem', + errorList: [ + { + text: error.text, + href: '#amended-authorised-volume' + } + ] + }) }} + {% endif %} + + {# Title #} +

+ {{chargeDescription}} + {{pageTitle}} +

+ + {# Periods #} + Financial Year {{financialPeriod}} +

+ Charge period {{chargePeriod}} +

+ {# Billable returns inset #} {% set insertText %}

- Financial year - {{financialYear}} + Total billable returns + {{totalBillableReturns}} ML

-

- Charge period - {{chargePeriod}} -

-

Total billable returns

-

{{chargeReference.totalBillableReturns}}

{% endset %} - {{ govukInsetText({ html: insertText }) @@ -57,18 +60,18 @@ text: "Authorised volume", classes: "govuk-label" }, - id: "authorised-volume", - name: "authorisedVolume", + id: "amended-authorised-volume", + name: "amendedAuthorisedVolume", classes: "govuk-input--width-10", - value: chargeReference.authorisedVolume, - errorMessage: error.authorisedVolume, + value: amendedAuthorisedVolume, suffix: { text: "ML" - } + }, + errorMessage: error }) }} {# Hidden input for authorised volume #} - + {{ govukButton({ text: 'Confirm', preventDoubleClick: true }) }} diff --git a/app/views/bill-runs/review/edit.njk b/app/views/bill-runs/review/edit.njk new file mode 100644 index 0000000000..5df36d940c --- /dev/null +++ b/app/views/bill-runs/review/edit.njk @@ -0,0 +1,112 @@ +{% extends 'layout.njk' %} +{% from "govuk/components/back-link/macro.njk" import govukBackLink %} +{% from "govuk/components/button/macro.njk" import govukButton %} +{% from "govuk/components/inset-text/macro.njk" import govukInsetText %} +{% from "govuk/components/radios/macro.njk" import govukRadios %} +{% from "govuk/components/input/macro.njk" import govukInput %} +{% from 'govuk/components/error-summary/macro.njk' import govukErrorSummary %} + +{% block breadcrumbs %} + {# Back link #} + {{ govukBackLink({ + text: 'Go back to review charge element', + href: '/system/bill-runs/review/charge-element/' + reviewChargeElementId + '/' + elementIndex + }) }} +{% endblock %} + +{% block content %} + {# Main heading #} +
+
+ {% if error %} + {{ govukErrorSummary({ + titleText: 'There is a problem', + errorList: error.errorList + }) }} + {% endif %} + {{ chargeDescription }} +

{{pageTitle}}

+ + {% for chargePeriod in chargePeriods %} + {% set chargePeriodIndex = loop.index0 %} +

{{ chargePeriod }}

+ {% endfor %} +
+
+ + {# Periods #} +
+
+ Financial year {{financialPeriod}} +

Charge period {{chargePeriod}}

+ + {# Billable returns inset #} + {% set insertText %} +

+ Billable returns + {{billableReturns}} ML +

+ {% endset %} + {{ govukInsetText({ + html: insertText + }) + }} +
+
+ +
+
+ + + {% set quantityInputHTML %} + {% if error.customQuantityErrorMessage %} + {% set errorClass = 'govuk-input--error' %} + {% endif %} + + {{ govukInput({ + id: "custom-quantity", + name: "customQuantity", + classes: "govuk-!-width-one-third " + errorClass, + value: customQuantityValue, + label: { + text: "Billable returns quantity" + }, + hint: { + text: "Enter a number with no more than 6 decimal places. For example, 20.123456" + }, + suffix: { + text: "ML" + }, + errorMessage: error.customQuantityErrorMessage + }) }} + {% endset %} + + {{ govukRadios({ + id: 'quantity-options', + name: 'quantityOptions', + items: [ + { + id: 'authorised-quantity', + value: authorisedQuantity, + html: '
Authorised ' + authorisedQuantity + 'ML
' + }, + { + id: 'custom-quantity-selector', + value: 'customQuantity', + text: 'Custom quantity', + checked: customQuantitySelected, + conditional: { + html: quantityInputHTML + } + } + ], + errorMessage: error.quantityOptionsErrorMessage + }) }} + + {# Hidden input for authorised volume #} + + + {{ govukButton({ text: 'Confirm', preventDoubleClick: true }) }} +
+
+{% endblock %} diff --git a/app/views/bill-runs/review/factors.njk b/app/views/bill-runs/review/factors.njk new file mode 100644 index 0000000000..4d15f4780d --- /dev/null +++ b/app/views/bill-runs/review/factors.njk @@ -0,0 +1,85 @@ +{% extends 'layout.njk' %} +{% from "govuk/components/back-link/macro.njk" import govukBackLink %} +{% from "govuk/components/inset-text/macro.njk" import govukInsetText %} +{% from "govuk/components/input/macro.njk" import govukInput %} +{% from "govuk/components/button/macro.njk" import govukButton %} +{% from 'govuk/components/error-summary/macro.njk' import govukErrorSummary %} + +{% block breadcrumbs %} + {# Back link #} + {{ govukBackLink({ + text: 'Go back to review charge reference', + href: '/system/bill-runs/review/charge-reference/' + reviewChargeReferenceId + }) }} +{% endblock %} + +{% block content %} +
+
+ {% if error %} + {{ govukErrorSummary({ + titleText: 'There is a problem', + errorList: error.errorList + }) }} + {% endif %} + + {# Title #} +

+ {{chargeDescription}} + {{pageTitle}} +

+ Financial Year {{financialPeriod}} +

Charge period {{chargePeriod}}

+ + {# Inset section #} + {% if otherAdjustments.length > 0 %} + {% set insertText %} +

Other adjustments apply:

+ {% for otherAdjustment in otherAdjustments %} + {% set adjustmentIndex = loop.index0 %} +
{{otherAdjustment}}
+ {% endfor %} + {% endset %} + + {{ govukInsetText({ + html: insertText + }) }} + {% endif %} + + {# Input fields #} +
+ + + {{ govukInput({ + label: { + text: "Aggregate factor", + classes: "govuk-label" + }, + id: "amended-aggregate", + name: "amendedAggregate", + classes: "govuk-input--width-10", + value: amendedAggregate, + errorMessage: { + text: error.amendedAggregate.message + } if error.amendedAggregate + }) }} + + {{ govukInput({ + label: { + text: "Charge adjustment", + classes: "govuk-label" + }, + id: "amended-charge-adjustment", + name: "amendedChargeAdjustment", + classes: "govuk-input--width-10", + value: amendedChargeAdjustment, + errorMessage: { + text: error.amendedChargeAdjustment.message + } if error.amendedChargeAdjustment + }) }} + + {{ govukButton({ text: 'Confirm', preventDoubleClick: true }) }} +
+
+
+{% endblock %} diff --git a/app/views/bill-runs/remove-licence.njk b/app/views/bill-runs/review/remove.njk similarity index 94% rename from app/views/bill-runs/remove-licence.njk rename to app/views/bill-runs/review/remove.njk index 7fd8dfb15b..cef03db39c 100644 --- a/app/views/bill-runs/remove-licence.njk +++ b/app/views/bill-runs/review/remove.njk @@ -10,8 +10,8 @@ {# Back link #} {{ govukBackLink({ - text: 'Go back to licence', - href: backLink + text: 'Go back to review licence', + href: '/system/bill-runs/review/licence/' + reviewLicenceId }) }} {% endblock %} @@ -68,7 +68,7 @@ }, { key: { text: "Financial year", classes: "meta-data__label" }, - value: { html: '' + financialYear + '', classes: "meta-data__value" } + value: { html: '' + financialYearPeriod + '', classes: "meta-data__value" } } ] }) diff --git a/app/views/bill-runs/review/review-charge-element.njk b/app/views/bill-runs/review/review-charge-element.njk new file mode 100644 index 0000000000..1ca1f56d72 --- /dev/null +++ b/app/views/bill-runs/review/review-charge-element.njk @@ -0,0 +1,212 @@ +{% extends 'layout.njk' %} +{% from "govuk/components/back-link/macro.njk" import govukBackLink %} +{% from "govuk/components/tag/macro.njk" import govukTag %} +{% from "govuk/components/summary-list/macro.njk" import govukSummaryList %} +{% from "govuk/components/table/macro.njk" import govukTable %} +{% from "govuk/components/button/macro.njk" import govukButton %} +{% from "macros/review-status-tag.njk" import statusTag %} +{% from "govuk/components/notification-banner/macro.njk" import govukNotificationBanner %} + +{% block breadcrumbs %} + {# Back link #} + {{ govukBackLink({ + text: 'Go back to review licence', + href: '/system/bill-runs/review/licence/' + reviewLicenceId + }) }} +{% endblock %} + +{% block content %} + {# Billable returns ammended banner #} + {% if bannerMessage %} + {{ govukNotificationBanner({ + titleText: 'Element updated', + text: bannerMessage + }) }} + {% endif %} + + {# Main heading #} +
+
+ Element {{elementIndex}} of {{elementCount}} +

{{ chargeDescription }}

+ + {% for chargePeriod in chargePeriods %} + {% set chargePeriodIndex = loop.index0 %} +

{{ chargePeriod }}

+ {% endfor %} +
+
+ +
+
+ {{ statusTag(status) }} +
+
+ + {# Licence nav bars #} + + + {# Periods #} + Financial year {{financialPeriod}} +

Charge period {{chargePeriod}}

+ + {# Billable returns, authorised volume and issues #} +
+
+
+

+ {{billableReturns}} ML +
+ Billable returns +
+

+
+
+

+ / +

+
+
+

+ {{authorisedVolume}} ML +
+ Authorised volume +
+

+
+ + {% if issues.length > 0 %} +
+

+
+

Issues

+ {% for issue in issues %} + {% set issuesIndex = loop.index0 %} +
{{issue}}
+ {% endfor %} +
+

+
+ {% endif %} +
+
+ + {# Billable returns button #} +
+
+ {{ govukButton({ + text: 'Edit the billable returns', + href: '/system/bill-runs/review/charge-element/' + reviewChargeElementId + '/' + elementIndex + '/edit', + classes: 'govuk-button--secondary', + preventDoubleClick: true + }) }} +
+
+ + + {# Matched Returns #} + {% if matchedReturns.length > 0 %} + {% set tableRows = [] %} + {% for return in matchedReturns %} + {% set returnIndex = loop.index0 %} + + {% set action %} + {{ return.reference }} +
{{return.returnPeriod}}
+
{{return.abstractionPeriod}}
+ {% endset %} + + {% set returnSummary %} +
{{ return.purpose }}
+ {{ return.description }} + {% endset %} + + {% set tag %} + {{ statusTag(return.returnStatus) }} + {% endset %} + + {% set issues = '' %} + {% for issue in return.issues %} + {% set issues = issues + '
' + issue + '
' %} + {% endfor %} + + {% set tableRow = [ + { + html: action, + classes: 'govuk-body-s', + attributes: { 'data-test': 'matched-return-action-' + returnIndex } + }, + { + html: returnSummary, + classes: 'govuk-body-s', + attributes: { 'data-test': 'matched-return-summary-' + returnIndex } + }, + { + html: tag, + classes: 'govuk-body-s', + attributes: { 'data-test': 'matched-return-status-' + returnIndex } + }, + { + html: '
' + return.returnTotal + '
' + issues, + classes: "govuk-body-s govuk-!-text-align-right", + attributes: { 'data-test': 'matched-return-total-' + returnIndex } + }] + %} + + {% set tableRows = (tableRows.push(tableRow), tableRows) %} + {% endfor %} + + {# Table displaying details of the matched returns #} + {{ govukTable({ + caption: "Matched returns", + captionClasses: "govuk-table__caption--m", + attributes: { 'data-test': 'matched-returns' }, + firstCellIsHeader: false, + head: [ + { + text: 'Return reference and periods' + }, + { + text: 'Purpose and description' + }, + { + text: 'Status' + }, + { + text: 'Return totals Allocated/Total', + format: 'numeric', + classes: 'width-one-tenth' + } + ], + rows: tableRows + }) }} + {% else %} +

No matching two-part tariff returns

+ {% endif %} +{% endblock %} diff --git a/app/views/bill-runs/review/review-charge-reference.njk b/app/views/bill-runs/review/review-charge-reference.njk new file mode 100644 index 0000000000..77d4e3af7c --- /dev/null +++ b/app/views/bill-runs/review/review-charge-reference.njk @@ -0,0 +1,156 @@ +{% extends 'layout.njk' %} +{% from "govuk/components/back-link/macro.njk" import govukBackLink %} +{% from "govuk/components/button/macro.njk" import govukButton %} +{% from "govuk/components/summary-list/macro.njk" import govukSummaryList %} +{% from "govuk/components/notification-banner/macro.njk" import govukNotificationBanner %} + +{% block breadcrumbs %} + {# Back link #} + {{ govukBackLink({ + text: 'Go back to review licence', + href: '/system/bill-runs/review/licence/' + reviewLicenceId + }) }} +{% endblock %} + +{% block content %} + {# Adjustment factor ammended banner #} + {% if bannerMessage %} + {{ govukNotificationBanner({ + titleText: 'Adjustment updated', + text: bannerMessage + }) }} + {% endif %} + + {# Charge preview banner #} + {% if chargeMessage %} + {{ govukNotificationBanner({ + titleText: 'Information', + text: chargeMessage + }) }} + {% endif %} + + {# Main heading #} +
+
+

+ Charge reference {{chargeCategory}}{{chargeDescription}} +

+ Financial Year {{financialPeriod}} +

+ Charge period {{chargePeriod}} +

+
+
+ + {# Return numbers ML #} +
+
+
+

+ {{totalBillableReturns}} ML +
+ Total billable returns +
+

+
+
+

+ / +

+
+
+

+ {{amendedAuthorisedVolume}} ML +
+ Authorised volume +
+

+
+
+
+ + {# Buttons #} +
+
+ {% if canAmend %} + + {{ govukButton({ + text: "Change the authorised volume", + classes: "govuk-button--secondary", + preventDoubleClick: true, + href: "/system/bill-runs/review/charge-reference/" + reviewChargeReferenceId + "/authorised" + }) }} + + {% endif %} + + + {{ govukButton({ + text: "Preview the charge", + classes: "govuk-button--secondary", + preventDoubleClick: true, + href: "/system/bill-runs/review/charge-reference/" + reviewChargeReferenceId + "/preview" + }) }} + +
+
+ + {# Reference details #} + {% if additionalCharges or adjustments.length > 0 %} +
+
+

Reference details

+ + {% set adjustmentsValue %} + {% for adjustment in adjustments %} + {% set adjustmentsIndex = loop.index0 %} +
{{adjustment}}
+ {% endfor %} + {% endset %} + + {% if canAmend %} + {% set showLink = { + items: [{ + href: "/system/bill-runs/review/charge-reference/" + reviewChargeReferenceId + "/factors", + text: "Change factors" + }] + } + %} + {% endif %} + + {% set tableRows = [] %} + {% if additionalCharges %} + {% set tableRow = { + key: { + text: "Additional charges" + }, + value: { + html: '
' + additionalCharges + '
' + } + } + %} + + {% set tableRows = (tableRows.push(tableRow), tableRows) %} + {% endif %} + + {% if adjustments %} + {% set tableRow = { + key: { + text: "Adjustments" + }, + value: { + html: adjustmentsValue + }, + actions: showLink + } + %} + + {% set tableRows = (tableRows.push(tableRow), tableRows) %} + {% endif %} + + {{ govukSummaryList({ + rows: tableRows + }) }} +

+
+ {% endif %} +{% endblock %} diff --git a/app/views/bill-runs/review-licence.njk b/app/views/bill-runs/review/review-licence.njk similarity index 50% rename from app/views/bill-runs/review-licence.njk rename to app/views/bill-runs/review/review-licence.njk index 3b058a61a9..c7a44402a0 100644 --- a/app/views/bill-runs/review-licence.njk +++ b/app/views/bill-runs/review/review-licence.njk @@ -9,13 +9,99 @@ {% from "govuk/components/inset-text/macro.njk" import govukInsetText %} {% from "govuk/components/warning-text/macro.njk" import govukWarningText %} +{# Helper for generating both the matched and unmatched returns tables #} +{% macro returnsTable(matchType, returns) %} + {% set caption %} + {% if matchType == 'matched' %} + Matched returns + {% else %} + Unmatched returns + {% endif %} + {% endset %} + + {% set tableRows = [] %} + {% for return in returns %} + {% set returnIndex = loop.index0 %} + + {% set action %} + {{ return.reference }} +
{{return.returnPeriod}}
+
{{return.abstractionPeriod}}
+ {% endset %} + + {% set returnSummary %} +
{{ return.purpose }}
+ {{ return.description }} + {% endset %} + + {% set tag %} + {{ statusTag(return.returnStatus) }} + {% endset %} + + {% set issues = '' %} + {% for issue in return.issues %} + {% set issueIndex = loop.index0 %} + {% set issues = issues + '
' + issue + '
' %} + {% endfor %} + + {% set tableRow = [ + { + html: action, + classes: 'govuk-body-s', + attributes: { 'data-test': matchType + '-return-action-' + returnIndex } + }, + { + html: returnSummary, + classes: 'govuk-body-s', + attributes: { 'data-test': matchType + '-return-summary-' + returnIndex } + }, + { + html: tag, + classes: 'govuk-body-s', + attributes: { 'data-test': matchType + '-return-status-' + returnIndex } + }, + { + html: '
' + return.returnTotal + '
' + issues, + classes: "govuk-body-s govuk-!-text-align-right", + attributes: { 'data-test': matchType + '-return-total-' + returnIndex } + }] + %} + + {% set tableRows = (tableRows.push(tableRow), tableRows) %} + {% endfor %} + + {# Table displaying details of the returns #} + {{ govukTable({ + caption: caption, + captionClasses: "govuk-table__caption--m", + attributes: { 'data-test': matchType + '-returns' }, + firstCellIsHeader: false, + head: [ + { + text: 'Return reference and periods' + }, + { + text: 'Purpose and description' + }, + { + text: 'Status' + }, + { + text: 'Return totals Allocated/Total', + format: 'numeric', + classes: 'width-one-tenth' + } + ], + rows: tableRows + }) }} +{% endmacro %} {% block breadcrumbs %} {# Back link #} {{ govukBackLink({ text: 'Go back to review licences', - href: '/system/bill-runs/' + billRunId + '/review' + href: '/system/bill-runs/review/' + billRunId }) }} {% endblock %} @@ -36,62 +122,35 @@

{{pageTitle}}

- Licence holder {{ licence.licenceHolder }} -

{{ statusTag(licence.status) }}
+ {{ licenceHolder }} +
{{ statusTag(status) }}

{# Licence nav bars #} {# Licence in review text #} @@ -101,64 +160,65 @@ }) }} {% endif %} - {# Change status #} - {% if licence.status === 'ready' %} - {% set statusButtonText = 'Put licence into review' %} - {% set statusButtonValue = 'review' %} - {% set statusButtonClass = "govuk-button--secondary" %} - {% else %} - {% set statusButtonText = 'Confirm licence is ready' %} - {% set statusButtonValue = 'ready' %} - {% set statusButtonClass = "govuk-button--primary" %} - {% endif %} - - {# Mark progress #} - {% if licence.progress %} - {% set progressButtonText = 'Remove progress mark' %} - {% set progressButtonValue = 'unmark' %} - {% else %} - {% set progressButtonText = 'Mark progress' %} - {% set progressButtonValue = 'mark' %} - {% endif %} -
- {{ govukButton({ - text: statusButtonText, - classes: statusButtonClass, - name: "licence-status", - value: statusButtonValue, - preventDoubleClick: true - }) }} + {# Change status button #} + {% if status === 'ready' %} + {% set statusButtonText = 'Put licence into review' %} + {% set statusButtonValue = 'review' %} + {% set statusButtonClass = "govuk-button--secondary" %} + {% else %} + {% set statusButtonText = 'Confirm licence is ready' %} + {% set statusButtonValue = 'ready' %} + {% set statusButtonClass = "govuk-button--primary" %} + {% endif %} + + {{ govukButton({ + text: statusButtonText, + classes: statusButtonClass, + name: "licence-status", + value: statusButtonValue, + preventDoubleClick: true + }) }} - {{ govukButton({ - text: progressButtonText, - classes: "govuk-button--secondary", - name: "mark-progress", - value: progressButtonValue, - preventDoubleClick: true - }) }} + {# Mark progress button #} + {% if progress %} + {% set progressButtonText = 'Remove progress mark' %} + {% set progressButtonValue = 'unmark' %} + {% else %} + {% set progressButtonText = 'Mark progress' %} + {% set progressButtonValue = 'mark' %} + {% endif %} + + {{ govukButton({ + text: progressButtonText, + classes: "govuk-button--secondary", + name: "mark-progress", + value: progressButtonValue, + preventDoubleClick: true + }) }} - {{ govukButton({ - text: 'Remove from bill run', - classes: "govuk-button--secondary", - href: "../remove/" + licence.licenceId, - preventDoubleClick: true - }) }} + {# Remove from bill run button #} + {{ govukButton({ + text: 'Remove from bill run', + classes: "govuk-button--secondary", + href: '/system/bill-runs/review/licence/' + reviewLicenceId + '/remove', + preventDoubleClick: true + }) }}
- {# Charge Periods #} + {# Charge Periods (bookmarks) #}

Charge periods

@@ -174,187 +234,35 @@
- {# Matched Returns #} - {% if matchedReturns.length > 0 %} - {% set tableRows = [] %} - {% for return in matchedReturns %} - {% set matchedReturnIndex = loop.index0 %} - - {% set action %} - {{ return.reference }} -
{{return.dates}}
-
{{return.absPeriod}}
- {% endset %} - - {% set returnSummary %} -
- {{ return.purpose }} -
- {{ return.description }} - {% endset %} - - {% set tag %} - {{ statusTag(return.returnStatus) }} - {% endset %} - - {% set issues = '' %} - {% for issue in return.issues %} - {% set issues = issues + '
' + issue + '
' %} - {% endfor %} - - {% set tableRow = [ - { - html: action, - classes: 'govuk-body-s', - attributes: { 'data-test': 'matched-return-action-' + matchedReturnIndex } - }, - { - html: returnSummary, - classes: 'govuk-body-s', - attributes: { 'data-test': 'matched-return-summary-' + matchedReturnIndex } - }, - { - html: tag, - classes: 'govuk-body-s', - attributes: { 'data-test': 'matched-return-status-' + matchedReturnIndex } - }, - { - html: "
" + return.returnTotal + "
" + issues, - classes: "govuk-body-s govuk-!-text-align-right", - attributes: { 'data-test': 'matched-return-total-' + matchedReturnIndex} - }] - %} - - {% set tableRows = (tableRows.push(tableRow), tableRows) %} - {% endfor %} - - {# Table displaying details of the matched returns #} - {{ govukTable({ - caption: "Matched returns", - captionClasses: "govuk-table__caption--m", - attributes: { 'data-test': 'matched-returns' }, - firstCellIsHeader: false, - head: [ - { - text: 'Return reference and periods' - }, - { - text: 'Purpose and description' - }, - { - text: 'Status' - }, - { - text: 'Return totals Allocated/Total', - format: 'numeric', - classes: 'width-one-tenth' - } - ], - rows: tableRows - }) }} - {% endif %} - - {# Unmatched Returns #} - {% if unmatchedReturns.length > 0 %} - {% set tableRows = [] %} - {% for return in unmatchedReturns %} - {% set unmatchedReturnIndex = loop.index0 %} - - {% set action %} - {{ return.reference }} -
{{return.dates}}
-
{{return.absPeriod}}
- {% endset %} - - {% set returnSummary %} -
- {{ return.purpose }} -
- {{ return.description }} - {% endset %} - - {% set tag %} - {{ statusTag(return.returnStatus) }} - {% endset %} - - {% set issues = '' %} - {% for issue in return.issues %} - {% set issues = issues + '
' + issue + '
' %} - {% endfor %} - - {% set tableRow = [ - { - html: action, - classes: 'govuk-body-s', - attributes: { 'data-test': 'unmatched-return-action-' + unmatchedReturnIndex } - }, - { - html: returnSummary, - classes: 'govuk-body-s', - attributes: { 'data-test': 'unmatched-return-summary-' + unmatchedReturnIndex } - }, - { - html: tag, - classes: 'govuk-body-s', - attributes: { 'data-test': 'unmatched-return-status-' + unmatchedReturnIndex } - }, - { - html: "
" + return.returnTotal + "
" + issues, - classes: "govuk-body-s govuk-!-text-align-right", - attributes: { 'data-test': 'unmatched-return-total-' + unmatchedReturnIndex } - } - ] - %} - - {% set tableRows = (tableRows.push(tableRow), tableRows) %} - {% endfor %} - - {# Table displaying details of the unmatched returns #} - {{ govukTable({ - caption: "Unmatched returns", - captionClasses: "govuk-table__caption--m", - attributes: { 'data-test': 'unmatched-returns' }, - firstCellIsHeader: false, - head: [ - { - text: 'Return reference and periods' - }, - { - text: 'Purpose and description' - }, - { - text: 'Status' - }, - { - text: 'Return totals', - format: 'numeric' - } - ], - rows: tableRows - }) }} - {% endif %} - - {# No Returns #} {% if matchedReturns == 0 and unmatchedReturns == 0 %} -

No two-part tariff returns

+ {# No Returns #} +

No two-part tariff returns

+ {% else %} + {# Matched Returns #} + {% if matchedReturns.length > 0 %} + {{ returnsTable('matched', matchedReturns) }} + {% endif %} + + {# Unmatched Returns #} + {% if unmatchedReturns.length > 0 %} + {{ returnsTable('unmatched', unmatchedReturns) }} + {% endif %} {% endif %}
- {# Loop through Charge Versions #} + {# Charge Periods (proper) #}
- {% for chargeVersion in chargeData %} - {% set chargeVersionIndex = loop.index0 %} -
Financial year {{chargeVersion.financialYear}}
+ {% for chargeVersion in chargeVersions %} + {% set chargeVersionIndex = loop.index0 %} + +
Financial year {{chargeVersion.financialPeriod}}
-

Charge periods {{chargeVersion.chargePeriodDate}}

+

Charge periods {{chargeVersion.chargePeriod}}

- {% set chargeReferenceText = "charge references " if chargeVersion.chargeReferences.length > 1 else "charge reference " %} - {% set chargeElementText = "charge elements " if chargeVersion.chargeElementCount > 1 else "charge element " %} - -
{{chargeVersion.chargeReferences.length}} {{chargeReferenceText}} with {{chargeVersion.chargeElementCount}} two-part tariff {{chargeElementText}}
+
{{ chargeVersion.description }}
{# Billing account details #} {% set billingAccountLink = '/billing-accounts/' + chargeVersion.billingAccountDetails.billingAccountId %} @@ -389,16 +297,19 @@
-

{{chargeReference.chargeCategory}}
{{chargeReference.chargeDescription}}

+

+ {{chargeReference.chargeCategory}} +
{{chargeReference.chargeDescription}}
+

- {% if chargeReference.billableReturnsWarning %} - {{ govukWarningText({ - text: "The total billable return volume exceeds the total authorised volume", - iconFallbackText: "Warning" - }) }} - {% endif %} + {% if chargeReference.billableReturnsWarning %} + {{ govukWarningText({ + text: "The total billable return volume exceeds the total authorised volume", + iconFallbackText: "Warning" + }) }} + {% endif %} {% set rows = [] %} {% set row = { @@ -412,8 +323,8 @@ actions: { items: [ { - href: licence.licenceId + '/charge-reference-details/' + chargeReference.id, - text: chargeReference.chargeReferenceLink.linkName, + href: '/system/bill-runs/review/charge-reference/' + chargeReference.id, + text: chargeReference.chargeReferenceLinkTitle, attributes: { 'data-test': 'charge-version-' + chargeVersionIndex + '-charge-reference-link-' + chargeReferenceIndex } } ] @@ -437,14 +348,14 @@ {% set elementRows = [] %}
- {{chargeElement.elementNumber}} + Element {{chargeElement.elementIndex}} of {{chargeElement.elementCount}} - {{ statusTag(chargeElement.elementStatus) }} + {{ statusTag(chargeElement.status) }} -

{{chargeElement.elementDescription}} +

{{chargeElement.description}}
- {% for chargeElementDate in chargeElement.dates %} -
{{ chargeElementDate }}
+ {% for chargePeriod in chargeElement.chargePeriods %} +
{{ chargePeriod }}
{% endfor %}
{{ chargeElement.purpose }} @@ -456,7 +367,7 @@ {% endfor %} {% set returnVolumes = '' %} - {% for returnVolume in chargeElement.returnVolume %} + {% for returnVolume in chargeElement.returnVolumes %} {% set returnVolumes = returnVolumes + '
' + returnVolume + '
' %} {% endfor %} @@ -479,7 +390,7 @@ actions: { items: [ { - href: licence.licenceId + '/match-details/' + chargeElement.reviewChargeElementId, + href: '/system/bill-runs/review/charge-element/' + chargeElement.id + '/' + chargeElement.elementIndex, html: '
View match details
' } ]} diff --git a/app/views/bill-runs/review.njk b/app/views/bill-runs/review/review.njk similarity index 97% rename from app/views/bill-runs/review.njk rename to app/views/bill-runs/review/review.njk index ab6ce74944..8023ab5ea9 100644 --- a/app/views/bill-runs/review.njk +++ b/app/views/bill-runs/review/review.njk @@ -120,7 +120,7 @@ {{ govukButton({ classes: "govuk-button--secondary govuk-!-margin-bottom-0", text: "Cancel bill run", - href: "cancel", + href: "/system/bill-runs/" + billRunId + "/cancel", preventDoubleClick: true }) }} @@ -130,7 +130,7 @@ {% set filtersForm %}

Filter by

-
+ {# Filter by licence holder or licence number #} @@ -322,7 +322,7 @@ {% endset %} {% set action %} - {{ licence.licenceRef }}View licence matching for licence {{ licence.licenceRef }} + {{ licence.licenceRef }}View licence matching for licence {{ licence.licenceRef }} {% endset %} {% set tableRow = [ diff --git a/test/controllers/bill-runs-review.controller.test.js b/test/controllers/bill-runs-review.controller.test.js new file mode 100644 index 0000000000..99789c2898 --- /dev/null +++ b/test/controllers/bill-runs-review.controller.test.js @@ -0,0 +1,719 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, before, beforeEach, afterEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const { postRequestOptions } = require('../support/general.js') + +// Things we need to stub +const AuthorisedService = require('../../app/services/bill-runs/review/authorised.service.js') +const EditService = require('../../app/services/bill-runs/review/edit.service.js') +const FactorsService = require('../../app/services/bill-runs/review/factors.service.js') +const PreviewService = require('../../app/services/bill-runs/review/preview.service.js') +const ReviewBillRunService = require('../../app/services/bill-runs/review/review-bill-run.service.js') +const ReviewChargeElementService = require('../../app/services/bill-runs/review/review-charge-element.service.js') +const ReviewChargeReferenceService = require('../../app/services/bill-runs/review/review-charge-reference.service.js') +const ReviewLicenceService = require('../../app/services/bill-runs/review/review-licence.service.js') +const SubmitAuthorisedService = require('../../app/services/bill-runs/review/submit-authorised.service.js') +const SubmitEditService = require('../../app/services/bill-runs/review/submit-edit.service.js') +const SubmitFactorsService = require('../../app/services/bill-runs/review/submit-factors.service.js') +const SubmitRemoveService = require('../../app/services/bill-runs/review/submit-remove.service.js') +const SubmitReviewBillRunService = require('../../app/services/bill-runs/review/submit-review-bill-run.service.js') +const RemoveService = require('../../app/services/bill-runs/review/remove.service.js') + +// For running our service +const { init } = require('../../app/server.js') + +describe('Bill Runs Review controller', () => { + let options + let path + let server + + // Create server before running the tests + before(async () => { + server = await init() + }) + + beforeEach(async () => { + // server = await init() + + // We silence any calls to server.logger.error made in the plugin to try and keep the test output as clean as + // possible + Sinon.stub(server.logger, 'error') + + // We silence sending a notification to our Errbit instance using Airbrake + Sinon.stub(server.app.airbrake, 'notify').resolvesThis() + }) + + afterEach(() => { + Sinon.restore() + }) + + describe('/bill-runs/review/{billRunId}', () => { + beforeEach(() => { + path = '97db1a27-8308-4aba-b463-8a6af2558b28' + }) + + describe('GET', () => { + beforeEach(() => { + Sinon.stub(ReviewBillRunService, 'go').resolves({ + region: 'Southern (Test replica)', + status: 'review', + dateCreated: '6 November 2023', + financialYear: '2021 to 2022', + billRunType: 'two-part tariff', + numberOfLicencesDisplayed: 2, + numberOfLicencesToReview: 1, + totalNumberOfLicences: 2, + preparedLicences: [ + { + licenceId: 'cc4bbb18-0d6a-4254-ac2c-7409de814d7e', + licenceRef: '1/11/11/*1/1111', + licenceHolder: 'Big Farm Ltd', + status: 'review', + issue: 'Multiple Issues' + }, + { + licenceId: '9442527a-64f3-471a-a3e4-fa0683a3d505', + licenceRef: '2/22/22/*2/2222', + licenceHolder: 'Small Farm Ltd', + status: 'ready', + issue: 'Multiple Issues' + } + ], + filter: { + issues: undefined, + licenceHolder: undefined, + licenceStatus: undefined, + openFilter: false + } + }) + }) + + describe('when a request is valid', () => { + describe('with no pagination', () => { + beforeEach(() => { + options = _getRequestOptions(path) + }) + + it('returns a 200 response', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(200) + expect(response.payload).to.contain('Southern (Test replica) two-part tariff bill run') + expect(response.payload).to.contain('Showing all 2 licences') + }) + }) + + describe('with pagination', () => { + beforeEach(() => { + options = _getRequestOptions(path, 'page=2') + }) + + it('returns a 200 response', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(200) + expect(response.payload).to.contain('Southern (Test replica) two-part tariff bill run') + expect(response.payload).to.contain('Showing all 2 licences') + }) + }) + }) + }) + + describe('POST', () => { + beforeEach(async () => { + options = _postRequestOptions(path) + + Sinon.stub(SubmitReviewBillRunService, 'go').resolves() + }) + + describe('when a request is valid', () => { + it('redirects to the review licences page', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(302) + expect(response.headers.location).to.equal( + '/system/bill-runs/review/97db1a27-8308-4aba-b463-8a6af2558b28' + ) + }) + }) + }) + }) + + describe('/bill-runs/review/charge-element/{reviewChargeElementId}/{elementIndex}', () => { + beforeEach(() => { + path = 'charge-element/a1840523-a04c-4c64-bff7-4a515e8ba1c1/1' + }) + + describe('GET', () => { + beforeEach(async () => { + options = _getRequestOptions(path) + + Sinon.stub(ReviewChargeElementService, 'go').resolves({ + authorisedVolume: 9.092, + billableReturns: 0, + chargeDescription: 'Spray Irrigation - Direct', + chargePeriod: '1 April 2023 to 31 March 2024', + chargePeriods: ['1 April 2023 to 30 September 2023'], + elementCount: 1, + elementIndex: '1', + financialPeriod: '2023 to 2024', + issues: ['Aggregate'], + licenceId: '7c8a248c-b71e-463c-bea8-bc5e0a5d95e2', + matchedReturns: [ + { + abstractionPeriod: '1 April to 30 September', + description: 'Borehole in top field', + issues: [Array], + purpose: 'Spray Irrigation - Direct', + reference: '11142960', + returnId: 'v1:5:1/11/10/*S/0084:11142960:2022-11-01:2023-10-31', + returnLink: '/returns/return?id=v1:5:1/11/10/*S/0084:11142960:2022-11-01:2023-10-31', + returnPeriod: '1 November 2022 to 31 October 2023', + returnStatus: 'completed', + returnTotal: '0 ML / 0 ML' + } + ], + reviewChargeElementId: 'a1840523-a04c-4c64-bff7-4a515e8ba1c1', + reviewLicenceId: 'deaffa60-6488-4e54-a402-485d43aca1af', + status: 'review' + }) + }) + + describe('when a request is valid', () => { + it('returns a 200 response', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(200) + expect(response.payload).to.contain('Spray Irrigation - Direct') + expect(response.payload).to.contain('Element 1 of 1') + expect(response.payload).to.contain('Matched returns') + }) + }) + }) + }) + + describe('/bill-runs/review/charge-element/{reviewChargeElementId}/{elementIndex}/edit', () => { + beforeEach(() => { + path = 'charge-element/a1840523-a04c-4c64-bff7-4a515e8ba1c1/1/edit' + }) + + describe('GET', () => { + beforeEach(async () => { + options = _getRequestOptions(path) + }) + + describe('when a request is valid', () => { + beforeEach(async () => { + Sinon.stub(EditService, 'go').resolves({ + pageTitle: 'Set the billable returns quantity for this bill run', + authorisedQuantity: 9.092, + billableReturns: 0, + chargeDescription: 'Spray Irrigation - Direct', + chargePeriod: '1 April 2023 to 31 March 2024', + chargePeriods: ['1 April 2023 to 30 September 2023'], + elementIndex: '1', + financialPeriod: '2023 to 2024', + reviewChargeElementId: 'a1840523-a04c-4c64-bff7-4a515e8ba1c1' + }) + }) + + it('returns a 200 response', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(200) + expect(response.payload).to.contain('Set the billable returns quantity for this bill run') + expect(response.payload).to.contain('Spray Irrigation - Direct') + expect(response.payload).to.contain('Billable returns') + }) + }) + }) + + describe('POST', () => { + beforeEach(async () => { + options = _postRequestOptions(path) + }) + + describe('when a request is valid', () => { + beforeEach(async () => { + Sinon.stub(SubmitEditService, 'go').resolves({}) + }) + + it('redirects to the Review charge element page', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(302) + expect(response.headers.location).to.equal( + '/system/bill-runs/review/charge-element/a1840523-a04c-4c64-bff7-4a515e8ba1c1/1' + ) + }) + }) + + describe('when a request is invalid', () => { + beforeEach(async () => { + Sinon.stub(SubmitEditService, 'go').resolves({ + customQuantitySelected: false, + customQuantityValue: undefined, + error: { + errorList: [{ href: '#quantityOptions-error', text: 'Select the billable quantity' }], + quantityOptionsErrorMessage: { text: 'Select the billable quantity' } + }, + pageTitle: 'Set the billable returns quantity for this bill run', + authorisedQuantity: 9.092, + billableReturns: 0, + chargeDescription: 'Spray Irrigation - Direct', + chargePeriod: '1 April 2023 to 31 March 2024', + chargePeriods: ['1 April 2023 to 30 September 2023'], + elementIndex: '1', + financialPeriod: '2023 to 2024', + reviewChargeElementId: 'a1840523-a04c-4c64-bff7-4a515e8ba1c1' + }) + }) + + it('re-renders the page with an error message', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(200) + expect(response.payload).to.contain('Set the billable returns quantity for this bill ru') + expect(response.payload).to.contain('There is a problem') + expect(response.payload).to.contain('Select the billable quantity') + }) + }) + }) + }) + + describe('/bill-runs/review/charge-reference/{reviewChargeReferenceId}', () => { + beforeEach(() => { + path = 'charge-reference/7c09753d-f606-4deb-a929-4bc8aa7acb8d' + }) + + describe('GET', () => { + beforeEach(async () => { + options = _getRequestOptions(path) + + Sinon.stub(ReviewChargeReferenceService, 'go').resolves({ + additionalCharges: '', + adjustments: ['Aggregate factor (0.333333333)', 'Two part tariff agreement'], + amendedAuthorisedVolume: 9.092, + canAmend: true, + chargeCategory: '4.6.5', + chargeDescription: 'High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model', + chargePeriod: '1 April 2023 to 31 March 2024', + financialPeriod: '2023 to 2024', + reviewChargeReferenceId: '7c09753d-f606-4deb-a929-4bc8aa7acb8d', + reviewLicenceId: 'deaffa60-6488-4e54-a402-485d43aca1af', + totalBillableReturns: 0 + }) + }) + + describe('when a request is valid', () => { + it('returns a 200 response', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(200) + expect(response.payload).to.contain('Charge reference 4.6.5') + expect(response.payload).to.contain('High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model') + expect(response.payload).to.contain('Reference details') + }) + }) + }) + }) + + describe('/bill-runs/review/charge-reference/{reviewChargeReferenceId}/authorised', () => { + beforeEach(() => { + path = 'charge-reference/7c09753d-f606-4deb-a929-4bc8aa7acb8d/authorised' + }) + + describe('GET', () => { + beforeEach(async () => { + options = _getRequestOptions(path) + }) + + describe('when a request is valid', () => { + beforeEach(async () => { + Sinon.stub(AuthorisedService, 'go').resolves({ + amendedAuthorisedVolume: 9.092, + chargeDescription: 'High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model', + chargePeriod: '1 April 2023 to 31 March 2024', + financialPeriod: '2023 to 2024', + reviewChargeReferenceId: '7c09753d-f606-4deb-a929-4bc8aa7acb8d', + totalBillableReturns: 0, + pageTitle: 'Set the authorised volume' + }) + }) + + it('returns a 200 response', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(200) + expect(response.payload).to.contain('Set the authorised volume') + expect(response.payload).to.contain('High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model') + expect(response.payload).to.contain('Total billable returns') + }) + }) + }) + + describe('POST', () => { + beforeEach(async () => { + options = _postRequestOptions(path) + }) + + describe('when a request is valid', () => { + beforeEach(async () => { + Sinon.stub(SubmitAuthorisedService, 'go').resolves({}) + }) + + it('redirects to the Review charge reference page', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(302) + expect(response.headers.location).to.equal( + '/system/bill-runs/review/charge-reference/7c09753d-f606-4deb-a929-4bc8aa7acb8d' + ) + }) + }) + + describe('when a request is invalid', () => { + beforeEach(async () => { + Sinon.stub(SubmitAuthorisedService, 'go').resolves({ + amendedAuthorisedVolume: 'foo', + error: { text: 'The authorised volume must be a number' }, + pageTitle: 'Set the authorised volume', + chargeDescription: 'High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model', + chargePeriod: '1 April 2023 to 31 March 2024', + financialPeriod: '2023 to 2024', + reviewChargeReferenceId: '7c09753d-f606-4deb-a929-4bc8aa7acb8d', + totalBillableReturns: 0 + }) + }) + + it('re-renders the page with an error message', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(200) + expect(response.payload).to.contain('Set the authorised volume') + expect(response.payload).to.contain('There is a problem') + expect(response.payload).to.contain('The authorised volume must be a number') + }) + }) + }) + }) + + describe('/bill-runs/review/charge-reference/{reviewChargeReferenceId}/factors', () => { + beforeEach(() => { + path = 'charge-reference/7c09753d-f606-4deb-a929-4bc8aa7acb8d/factors' + }) + + describe('GET', () => { + beforeEach(async () => { + options = _getRequestOptions(path) + }) + + describe('when a request is valid', () => { + beforeEach(async () => { + Sinon.stub(FactorsService, 'go').resolves({ + amendedAggregate: 0.333333333, + amendedChargeAdjustment: 1, + chargeDescription: 'High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model', + chargePeriod: '1 April 2023 to 31 March 2024', + financialPeriod: '2023 to 2024', + otherAdjustments: ['Two part tariff agreement'], + reviewChargeReferenceId: '7c09753d-f606-4deb-a929-4bc8aa7acb8d', + pageTitle: 'Set the adjustment factors' + }) + }) + + it('returns a 200 response', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(200) + expect(response.payload).to.contain('Set the adjustment factors') + expect(response.payload).to.contain('High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model') + }) + }) + }) + + describe('POST', () => { + beforeEach(async () => { + options = _postRequestOptions(path) + }) + + describe('when a request is valid', () => { + beforeEach(async () => { + Sinon.stub(SubmitFactorsService, 'go').resolves({}) + }) + + it('redirects to the Review charge reference page', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(302) + expect(response.headers.location).to.equal( + '/system/bill-runs/review/charge-reference/7c09753d-f606-4deb-a929-4bc8aa7acb8d' + ) + }) + }) + + describe('when a request is invalid', () => { + beforeEach(async () => { + Sinon.stub(SubmitFactorsService, 'go').resolves({ + amendedAggregate: 'foo', + amendedChargeAdjustment: '1', + error: { + errorList: [{ href: '#amended-aggregate', text: 'The aggregate factor must be a number' }], + amendedAggregate: { message: 'The aggregate factor must be a number' } + }, + pageTitle: 'Set the adjustment factors', + chargeDescription: 'High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model', + chargePeriod: '1 April 2023 to 31 March 2024', + financialPeriod: '2023 to 2024', + otherAdjustments: ['Two part tariff agreement'], + reviewChargeReferenceId: '7c09753d-f606-4deb-a929-4bc8aa7acb8d' + }) + }) + + it('re-renders the page with an error message', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(200) + expect(response.payload).to.contain('Set the adjustment factors') + expect(response.payload).to.contain('There is a problem') + expect(response.payload).to.contain('The aggregate factor must be a number') + }) + }) + }) + }) + + describe('/bill-runs/review/charge-reference/{reviewChargeReferenceId}/preview', () => { + beforeEach(() => { + path = 'charge-reference/7c09753d-f606-4deb-a929-4bc8aa7acb8d/preview' + }) + + describe('GET', () => { + beforeEach(async () => { + options = _getRequestOptions(path) + + Sinon.stub(PreviewService, 'go').resolves() + }) + + describe('when a request is valid', () => { + it('redirects to the Review charge reference page', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(302) + expect(response.headers.location).to.equal( + '/system/bill-runs/review/charge-reference/7c09753d-f606-4deb-a929-4bc8aa7acb8d' + ) + }) + }) + }) + }) + + describe('/bill-runs/review/licence/{reviewLicenceId}', () => { + beforeEach(() => { + path = 'licence/deaffa60-6488-4e54-a402-485d43aca1af' + }) + + describe('GET', () => { + beforeEach(async () => { + options = _getRequestOptions(path) + + Sinon.stub(ReviewLicenceService, 'go').resolves({ + billRunId: '97db1a27-8308-4aba-b463-8a6af2558b28', + chargeVersions: [{ + billingAccountDetails: [{ + billingAccountId: 'ee3f5562-26ad-4d58-9b59-5c388a13d7d0', + accountNumber: 'E99999999A', + accountName: 'Mr B Blobby', + contactName: null, + addressLines: [ + 'C/O Noel Edmonds', + 'Crinkley Bottom', + 'Cricket St Thomas', + 'Somerset', + 'TA20 1KL' + ] + }], + chargePeriod: '1 April 2023 to 31 March 2024', + chargeReferences: [{ + billableReturnsWarning: false, + chargeCategory: 'Charge reference 4.6.5', + chargeDescription: 'High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model', + id: '7c09753d-f606-4deb-a929-4bc8aa7acb8d', + chargeElements: [{ + billableReturns: '0 ML / 9.092 ML', + chargePeriods: ['1 April 2023 to 30 September 2023'], + returnVolumes: ['0 ML (11142960)'], + description: 'Spray Irrigation - Direct', + elementCount: 1, + elementIndex: 1, + status: 'review', + id: 'a1840523-a04c-4c64-bff7-4a515e8ba1c1', + issues: ['Aggregate'], + purpose: 'Spray Irrigation - Direct' + }], + chargeReferenceLinkTitle: 'Change details', + totalBillableReturns: '0 ML / 9.092 ML' + }], + description: '1 charge reference with 1 two-part tariff charge element', + financialPeriod: '2023 to 2024' + }], + elementsInReview: true, + licenceHolder: 'Licence Holder Ltd', + licenceId: '7c8a248c-b71e-463c-bea8-bc5e0a5d95e2', + licenceRef: '1/11/10/*S/0084', + matchedReturns: [{ + abstractionPeriod: '1 April to 30 September', + description: 'Test Road. Points 1 and 2.', + issues: ['Returns received late'], + purpose: 'Spray Irrigation - Direct', + reference: '11142960', + returnId: 'v1:5:1/11/10/*S/0084:11142960:2022-11-01:2023-10-31', + returnLink: '/returns/return?id=v1:5:1/11/10/*S/0084:11142960:2022-11-01:2023-10-31', + returnPeriod: '1 November 2022 to 31 October 2023', + returnStatus: 'completed', + returnTotal: '0 ML / 0 ML' + }], + pageTitle: 'Licence 13/43/028/S/045', + progress: false, + region: 'Southern (Test replica)', + reviewLicenceId: 'deaffa60-6488-4e54-a402-485d43aca1af', + status: 'review', + unmatchedReturns: [] + }) + }) + + describe('when a request is valid', () => { + it('returns a 200 response', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(200) + expect(response.payload).to.contain('1/11/10/*S/0084') + expect(response.payload).to.contain('two-part tariff') + expect(response.payload).to.contain('Test Road. Points 1 and 2.') + }) + }) + }) + + describe('POST', () => { + beforeEach(async () => { + options = _postRequestOptions(path) + }) + + describe('when a request is valid', () => { + it('redirects to the Review licence page', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(302) + expect(response.headers.location).to.equal('/system/bill-runs/review/licence/deaffa60-6488-4e54-a402-485d43aca1af') + }) + }) + }) + }) + + describe('/bill-runs/review/licence/{reviewLicenceId}/remove', () => { + beforeEach(() => { + path = 'licence/deaffa60-6488-4e54-a402-485d43aca1af/remove' + }) + + describe('GET', () => { + beforeEach(() => { + options = _getRequestOptions(path) + + Sinon.stub(RemoveService, 'go').resolves({ + billRunNumber: 12345, + billRunStatus: 'review', + dateCreated: '3 May 2024', + financialYearPeriod: '2022 to 2023', + licenceRef: '01/123/ABC', + pageTitle: "You're about to remove 01/123/ABC from the bill run", + region: 'Test region', + reviewLicenceId: '70703eac-cab6-4eff-906d-2832fe27b646' + }) + }) + + describe('when a request is valid', () => { + it('returns a 200 response', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(200) + expect(response.payload).to.contain('You're about to remove 01/123/ABC from the bill run') + }) + }) + }) + + describe('POST', () => { + beforeEach(() => { + options = _postRequestOptions(path) + }) + + describe('when there are still licences to review in the bill run', () => { + beforeEach(() => { + options = _postRequestOptions(path) + + Sinon.stub(SubmitRemoveService, 'go').resolves({ + billRunId: '97db1a27-8308-4aba-b463-8a6af2558b28', + empty: false + }) + }) + + describe('when a request is valid', () => { + it('redirects to the Review bill run page', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(302) + expect(response.headers.location).to.equal('/system/bill-runs/review/97db1a27-8308-4aba-b463-8a6af2558b28') + }) + }) + }) + + describe('when there are no licences left to review in the bill run', () => { + beforeEach(() => { + options = _postRequestOptions(path) + + Sinon.stub(SubmitRemoveService, 'go').resolves({ + billRunId: '97db1a27-8308-4aba-b463-8a6af2558b28', + empty: true + }) + }) + + describe('when a request is valid', () => { + it('redirects to the Bill runs page', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(302) + expect(response.headers.location).to.equal('/system/bill-runs') + }) + }) + }) + }) + }) +}) + +function _getRequestOptions (path, query = null) { + const root = '/bill-runs/review' + const rootPath = path ? `${root}/${path}` : root + const url = query ? `${rootPath}?${query}` : rootPath + + return { + method: 'GET', + url, + auth: { + strategy: 'session', + credentials: { scope: ['billing'] } + } + } +} + +function _postRequestOptions (path) { + const root = '/bill-runs/review' + const rootPath = path ? `${root}/${path}` : root + + return postRequestOptions(rootPath) +} diff --git a/test/controllers/bill-runs.controller.test.js b/test/controllers/bill-runs.controller.test.js index 83d0b38394..34c6be786a 100644 --- a/test/controllers/bill-runs.controller.test.js +++ b/test/controllers/bill-runs.controller.test.js @@ -5,34 +5,19 @@ const Lab = require('@hapi/lab') const Code = require('@hapi/code') const Sinon = require('sinon') -const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() +const { describe, it, before, beforeEach, afterEach } = exports.lab = Lab.script() const { expect } = Code // Test helpers const { postRequestOptions } = require('../support/general.js') // Things we need to stub -const AmendAdjustmentFactorService = require('../../app/services/bill-runs/two-part-tariff/amend-adjustment-factor.service.js') -const AmendAuthorisedVolumeService = require('../../app/services/bill-runs/two-part-tariff/amend-authorised-volume.service.js') -const AmendBillableReturnsService = require('../../app/services/bill-runs/two-part-tariff/amend-billable-returns.service.js') const Boom = require('@hapi/boom') -const CalculateChargeService = require('../../app/services/bill-runs/two-part-tariff/calculate-charge.service.js') const CancelBillRunService = require('../../app/services/bill-runs/cancel-bill-run.service.js') -const ChargeReferenceDetailsService = require('../../app/services/bill-runs/two-part-tariff/charge-reference-details.service.js') const GenerateBillRunService = require('../../app/services/bill-runs/two-part-tariff/generate-bill-run.service.js') const IndexBillRunsService = require('../../app/services/bill-runs/index-bill-runs.service.js') -const MatchDetailsService = require('../../app/services/bill-runs/two-part-tariff/match-details.service.js') -const RemoveBillRunLicenceService = require('../../app/services/bill-runs/two-part-tariff/remove-bill-run-licence.service.js') -const ReviewBillRunService = require('../../app/services/bill-runs/two-part-tariff/review-bill-run.service.js') -const ReviewLicenceService = require('../../app/services/bill-runs/two-part-tariff/review-licence.service.js') const SendBillRunService = require('../../app/services/bill-runs/send-bill-run.service.js') -const SubmitAmendedAdjustmentFactorService = require('../../app/services/bill-runs/two-part-tariff/submit-amended-adjustment-factor.service.js') -const SubmitAmendedAuthorisedVolumeService = require('../../app/services/bill-runs/two-part-tariff/submit-amended-authorised-volume.service.js') -const SubmitAmendedBillableReturnsService = require('../../app/services/bill-runs/two-part-tariff/submit-amended-billable-returns.service.js') const SubmitCancelBillRunService = require('../../app/services/bill-runs/submit-cancel-bill-run.service.js') -const SubmitRemoveBillRunLicenceService = require('../../app/services/bill-runs/two-part-tariff/submit-remove-bill-run-licence.service.js') -const SubmitReviewBillRunService = require('../../app/services/bill-runs/two-part-tariff/submit-review-bill-run.service.js') -const SubmitReviewLicenceService = require('../../app/services/bill-runs/two-part-tariff/submit-review-licence.service.js') const SubmitSendBillRunService = require('../../app/services/bill-runs/submit-send-bill-run.service.js') const ViewBillRunService = require('../../app/services/bill-runs/view-bill-run.service.js') @@ -43,10 +28,12 @@ describe('Bill Runs controller', () => { let options let server - // Create server before each test - beforeEach(async () => { + // Create server before running the tests + before(async () => { server = await init() + }) + beforeEach(async () => { // We silence any calls to server.logger.error made in the plugin to try and keep the test output as clean as // possible Sinon.stub(server.logger, 'error') @@ -218,491 +205,6 @@ describe('Bill Runs controller', () => { }) }) - describe('/bill-runs/{id}/remove/{licenceId}', () => { - const licenceId = '949aab20-d3fc-4726-aace-6bd2def6a146' - - describe('GET /bill-runs/{id}/remove/{licenceId}', () => { - beforeEach(async () => { - options = _getRequestOptions(`remove/${licenceId}`) - }) - - describe('when a request is valid', () => { - beforeEach(() => { - Sinon.stub(RemoveBillRunLicenceService, 'go').resolves({ - pageTitle: "You're about to remove 01/123/ABC from the bill run", - backLink: `../review/${licenceId}`, - billRunNumber: 12345, - billRunStatus: 'review', - dateCreated: '3 May 2024', - financialYear: '2022 to 2023', - licenceRef: '01/123/ABC', - region: 'Test region' - }) - }) - - it('returns a 200 response', async () => { - const response = await server.inject(options) - - expect(response.statusCode).to.equal(200) - expect(response.payload).to.contain('You're about to remove 01/123/ABC from the bill run') - expect(response.payload).to.contain(`../review/${licenceId}`) - expect(response.payload).to.contain('12345') - expect(response.payload).to.contain('review') - expect(response.payload).to.contain('3 May 2024') - expect(response.payload).to.contain('2022 to 2023') - expect(response.payload).to.contain('01/123/ABC') - expect(response.payload).to.contain('Test region') - }) - }) - }) - - describe('POST /bill-runs/{id}/remove/{licenceId}', () => { - beforeEach(() => { - options = _postRequestOptions(`remove/${licenceId}`) - }) - - describe('when a request is valid and licences still remain in the bill run', () => { - beforeEach(() => { - Sinon.stub(SubmitRemoveBillRunLicenceService, 'go').resolves(false) - }) - - it('redirects to the Review licences page', async () => { - const response = await server.inject(options) - - expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal('/system/bill-runs/97db1a27-8308-4aba-b463-8a6af2558b28/review') - }) - }) - - describe('when a request is valid and NO licences remain in the bill run', () => { - beforeEach(() => { - Sinon.stub(SubmitRemoveBillRunLicenceService, 'go').resolves(true) - }) - - it('redirects to the Bill runs page', async () => { - const response = await server.inject(options) - - expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal('/system/bill-runs') - }) - }) - }) - }) - - describe('/bill-runs/{id}/review', () => { - describe('GET', () => { - let ReviewBillRunServiceStub - - describe('when a request is valid with no pagination', () => { - beforeEach(() => { - options = _getRequestOptions('review') - ReviewBillRunServiceStub = Sinon.stub(ReviewBillRunService, 'go').resolves(_reviewBillRunData()) - }) - - it('returns a 200 response', async () => { - const response = await server.inject(options) - const ReviewBillRunServiceArgs = ReviewBillRunServiceStub.args[0] - - expect(response.statusCode).to.equal(200) - expect(ReviewBillRunServiceArgs[0]).to.equal('97db1a27-8308-4aba-b463-8a6af2558b28') - expect(ReviewBillRunServiceArgs[1]).to.equal(undefined) - expect(response.payload).to.contain('two-part tariff') - expect(response.payload).to.contain('Southern (Test replica)') - expect(response.payload).to.contain('Showing all 2 licences') - }) - }) - - describe('when a request is valid with pagination', () => { - beforeEach(() => { - options = _getRequestOptions('review?page=2') - ReviewBillRunServiceStub = Sinon.stub(ReviewBillRunService, 'go').resolves(_reviewBillRunData()) - }) - - it('returns a 200 response', async () => { - const response = await server.inject(options) - const ReviewBillRunServiceArgs = ReviewBillRunServiceStub.args[0] - - expect(response.statusCode).to.equal(200) - expect(ReviewBillRunServiceArgs[0]).to.equal('97db1a27-8308-4aba-b463-8a6af2558b28') - expect(ReviewBillRunServiceArgs[1]).to.equal('2') - expect(response.payload).to.contain('two-part tariff') - expect(response.payload).to.contain('Southern (Test replica)') - expect(response.payload).to.contain('Showing all 2 licences') - }) - }) - }) - - describe('POST', () => { - beforeEach(async () => { - options = _postRequestOptions('review') - }) - - describe('when a request is valid', () => { - beforeEach(() => { - Sinon.stub(SubmitReviewBillRunService, 'go').resolves() - }) - - it('redirects to the review licences page', async () => { - const response = await server.inject(options) - - expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal( - '/system/bill-runs/97db1a27-8308-4aba-b463-8a6af2558b28/review' - ) - }) - }) - }) - }) - - describe('/bill-runs/{id}/review/{licenceId}', () => { - describe('GET', () => { - beforeEach(async () => { - options = _getRequestOptions('review/cc4bbb18-0d6a-4254-ac2c-7409de814d7e') - }) - - describe('when a request is valid', () => { - beforeEach(() => { - Sinon.stub(ReviewLicenceService, 'go').resolves(_licenceReviewData()) - }) - - it('returns a 200 response', async () => { - const response = await server.inject(options) - - expect(response.statusCode).to.equal(200) - expect(response.payload).to.contain('1/11/10/*S/0084') - expect(response.payload).to.contain('two-part tariff') - expect(response.payload).to.contain('Test Road. Points 1 and 2.') - }) - }) - }) - - describe('POST', () => { - beforeEach(async () => { - options = _postRequestOptions('review/cc4bbb18-0d6a-4254-ac2c-7409de814d7e') - }) - - describe('when a request is valid', () => { - beforeEach(() => { - Sinon.stub(SubmitReviewLicenceService, 'go').resolves() - }) - - it('redirects to the review licence page', async () => { - const response = await server.inject(options) - - expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal('/system/bill-runs/97db1a27-8308-4aba-b463-8a6af2558b28/review/cc4bbb18-0d6a-4254-ac2c-7409de814d7e') - }) - }) - }) - }) - - describe('/bill-runs/{id}/review/{licenceId}/charge-reference-details/{reviewChargeReferenceId}', () => { - describe('GET', () => { - beforeEach(async () => { - options = _getRequestOptions( - 'review/cc4bbb18-0d6a-4254-ac2c-7409de814d7e/charge-reference-details/9a8a148d-b71e-463c-bea8-bc5e0a5d95e2') - }) - - describe('when a request is valid', () => { - beforeEach(() => { - Sinon.stub(ChargeReferenceDetailsService, 'go').resolves(_chargeReferenceData()) - }) - - it('returns a 200 response', async () => { - const response = await server.inject(options) - - expect(response.statusCode).to.equal(200) - expect(response.payload).to.contain('4.6.24') - expect(response.payload).to.contain('Total billable returns') - expect(response.payload).to.contain('Aggregate factor (0.5)') - }) - }) - }) - }) - - describe('/bill-runs/{id}/review/{licenceId}/charge-reference-details/{reviewChargeReferenceId}/amend-adjustment-factor', () => { - describe('GET', () => { - beforeEach(async () => { - options = _getRequestOptions( - 'review/cc4bbb18-0d6a-4254-ac2c-7409de814d7e/charge-reference-details/9a8a148d-b71e-463c-bea8-bc5e0a5d95e2/amend-adjustment-factor') - }) - - describe('when a request is valid', () => { - beforeEach(() => { - Sinon.stub(AmendAdjustmentFactorService, 'go').resolves(_chargeReferenceData()) - }) - - it('returns a 200 response', async () => { - const response = await server.inject(options) - - expect(response.statusCode).to.equal(200) - expect(response.payload).to.contain('High loss, non-tidal, restricted water, greater than 85 up to and including 120 ML/yr') - expect(response.payload).to.contain('Set the adjustment factors') - expect(response.payload).to.contain('Aggregate factor') - }) - }) - }) - - describe('POST', () => { - beforeEach(() => { - options = _postRequestOptions( - 'review/cc4bbb18-0d6a-4254-ac2c-7409de814d7e/charge-reference-details/9a8a148d-b71e-463c-bea8-bc5e0a5d95e2/amend-adjustment-factor' - ) - }) - - describe('when a request is valid', () => { - beforeEach(async () => { - Sinon.stub(SubmitAmendedAdjustmentFactorService, 'go').resolves(_chargeReferenceData()) - }) - - it('redirects to the charge reference details page', async () => { - const response = await server.inject(options) - - expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal( - '/system/bill-runs/97db1a27-8308-4aba-b463-8a6af2558b28/review/cc4bbb18-0d6a-4254-ac2c-7409de814d7e/charge-reference-details/9a8a148d-b71e-463c-bea8-bc5e0a5d95e2' - ) - }) - }) - - describe('when a request is invalid', () => { - beforeEach(async () => { - Sinon.stub(SubmitAmendedAdjustmentFactorService, 'go').resolves(_chargeReferenceData(true)) - }) - - it('re-renders the page with an error message', async () => { - const response = await server.inject(options) - - expect(response.statusCode).to.equal(200) - expect(response.payload).to.contain('The aggregate factor must be a number') - expect(response.payload).to.contain('There is a problem') - }) - }) - - describe('when the request fails', () => { - describe('because the sending service threw an error', () => { - beforeEach(async () => { - Sinon.stub(Boom, 'badImplementation').returns(new Boom.Boom('Bang', { statusCode: 500 })) - Sinon.stub(SubmitAmendedAdjustmentFactorService, 'go').rejects() - }) - - it('returns the error page', async () => { - const response = await server.inject(options) - - expect(response.statusCode).to.equal(200) - expect(response.payload).to.contain('Sorry, there is a problem with the service') - }) - }) - }) - }) - }) - - describe('/bill-runs/{id}/review/{licenceId}/charge-reference-details/{reviewChargeReferenceId}/amend-authorised-volume', () => { - describe('GET', () => { - beforeEach(async () => { - options = _getRequestOptions( - 'review/cc4bbb18-0d6a-4254-ac2c-7409de814d7e/charge-reference-details/9a8a148d-b71e-463c-bea8-bc5e0a5d95e2/amend-authorised-volume') - }) - - describe('when a request is valid', () => { - beforeEach(() => { - Sinon.stub(AmendAuthorisedVolumeService, 'go').resolves(_authorisedVolumeData()) - }) - - it('returns a 200 response', async () => { - const response = await server.inject(options) - - expect(response.statusCode).to.equal(200) - expect(response.payload).to.contain('High loss, non-tidal, restricted water, greater than 85 up to and including 120 ML/yr') - expect(response.payload).to.contain('Set the authorised volume') - expect(response.payload).to.contain('Total billable returns') - }) - }) - }) - - describe('POST', () => { - beforeEach(() => { - options = _postRequestOptions( - 'review/cc4bbb18-0d6a-4254-ac2c-7409de814d7e/charge-reference-details/9a8a148d-b71e-463c-bea8-bc5e0a5d95e2/amend-authorised-volume' - ) - }) - - describe('when a request is valid', () => { - beforeEach(async () => { - Sinon.stub(SubmitAmendedAuthorisedVolumeService, 'go').resolves(_chargeReferenceData()) - }) - - it('redirects to the charge reference details page', async () => { - const response = await server.inject(options) - - expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal( - '/system/bill-runs/97db1a27-8308-4aba-b463-8a6af2558b28/review/cc4bbb18-0d6a-4254-ac2c-7409de814d7e/charge-reference-details/9a8a148d-b71e-463c-bea8-bc5e0a5d95e2' - ) - }) - }) - - describe('when a request is invalid', () => { - beforeEach(async () => { - Sinon.stub(SubmitAmendedAuthorisedVolumeService, 'go').resolves(_authorisedVolumeData(true)) - }) - - it('re-renders the page with an error message', async () => { - const response = await server.inject(options) - - expect(response.statusCode).to.equal(200) - expect(response.payload).to.contain('The authorised volume must be a number') - expect(response.payload).to.contain('There is a problem') - }) - }) - - describe('when the request fails', () => { - describe('because the sending service threw an error', () => { - beforeEach(async () => { - Sinon.stub(Boom, 'badImplementation').returns(new Boom.Boom('Bang', { statusCode: 500 })) - Sinon.stub(SubmitAmendedAuthorisedVolumeService, 'go').rejects() - }) - - it('returns the error page', async () => { - const response = await server.inject(options) - - expect(response.statusCode).to.equal(200) - expect(response.payload).to.contain('Sorry, there is a problem with the service') - }) - }) - }) - }) - }) - - describe('/bill-runs/{id}/review/{licenceId}/match-details/{reviewChargeElementId}', () => { - describe('GET', () => { - beforeEach(async () => { - options = _getRequestOptions( - 'review/cc4bbb18-0d6a-4254-ac2c-7409de814d7e/match-details/9a8a148d-b71e-463c-bea8-bc5e0a5d95e2' - ) - }) - - describe('when a request is valid', () => { - beforeEach(() => { - Sinon.stub(MatchDetailsService, 'go').resolves(_chargeElementDetails()) - }) - - it('returns a 200 response', async () => { - const response = await server.inject(options) - - expect(response.statusCode).to.equal(200) - expect(response.payload).to.contain('Charge period 1 April 2022 to 31 March 2023') - expect(response.payload).to.contain('Financial year 2022 to 2023') - expect(response.payload).to.contain('River Test and tributaries near Fullerton Grange, Andover') - }) - }) - }) - }) - - describe('/bill-runs/{id}/review/{licenceId}/match-details/{reviewChargeElementId}/amend-billable-returns', () => { - describe('GET', () => { - beforeEach(async () => { - options = _getRequestOptions( - 'review/cc4bbb18-0d6a-4254-ac2c-7409de814d7e/match-details/9a8a148d-b71e-463c-bea8-bc5e0a5d95e2/amend-billable-returns' - ) - }) - - describe('when a request is valid', () => { - beforeEach(() => { - Sinon.stub(AmendBillableReturnsService, 'go').resolves(_billableReturnData()) - }) - - it('returns a 200 response', async () => { - const response = await server.inject(options) - - expect(response.statusCode).to.equal(200) - expect(response.payload).to.contain('Spray irrigation - storage, Abstraction from borehole at Chipping Norton') - expect(response.payload).to.contain('Financial year 2022 to 2023') - expect(response.payload).to.contain('Authorised 40ML') - }) - }) - }) - - describe('POST', () => { - beforeEach(() => { - options = _postRequestOptions( - 'review/cc4bbb18-0d6a-4254-ac2c-7409de814d7e/match-details/9a8a148d-b71e-463c-bea8-bc5e0a5d95e2/amend-billable-returns' - ) - }) - - describe('when a request is valid', () => { - beforeEach(async () => { - Sinon.stub(SubmitAmendedBillableReturnsService, 'go').resolves(_matchDetailsData()) - }) - - it('redirects to the match details page', async () => { - const response = await server.inject(options) - - expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal( - '/system/bill-runs/97db1a27-8308-4aba-b463-8a6af2558b28/review/cc4bbb18-0d6a-4254-ac2c-7409de814d7e/match-details/9a8a148d-b71e-463c-bea8-bc5e0a5d95e2' - ) - }) - }) - - describe('when a request is invalid', () => { - beforeEach(async () => { - Sinon.stub(SubmitAmendedBillableReturnsService, 'go').resolves(_billableReturnData(true)) - }) - - it('re-renders the page with an error message', async () => { - const response = await server.inject(options) - - expect(response.statusCode).to.equal(200) - expect(response.payload).to.contain('Select the billable quantity') - expect(response.payload).to.contain('There is a problem') - }) - }) - - describe('when the request fails', () => { - describe('because the sending service threw an error', () => { - beforeEach(async () => { - Sinon.stub(Boom, 'badImplementation').returns(new Boom.Boom('Bang', { statusCode: 500 })) - Sinon.stub(SubmitAmendedBillableReturnsService, 'go').rejects() - }) - - it('returns the error page', async () => { - const response = await server.inject(options) - - expect(response.statusCode).to.equal(200) - expect(response.payload).to.contain('Sorry, there is a problem with the service') - }) - }) - }) - }) - }) - - describe('/bill-runs/{id}/review/{licenceId}/preview-charge/{reviewChargeReferenceId}', () => { - describe('GET', () => { - const licenceId = '87cb11cf-1e5e-448d-8050-29f4e681b416' - const reviewChargeReferenceId = '7c09753d-f606-4deb-a929-4bc8aa7acb8d' - - beforeEach(async () => { - options = _getRequestOptions(`review/${licenceId}/preview-charge/${reviewChargeReferenceId}`) - }) - - describe('when a request is valid', () => { - beforeEach(() => { - Sinon.stub(CalculateChargeService, 'go').resolves() - }) - - it('redirects to the review charge reference details page', async () => { - const response = await server.inject(options) - - expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal( - `/system/bill-runs/97db1a27-8308-4aba-b463-8a6af2558b28/review/${licenceId}/charge-reference-details/${reviewChargeReferenceId}` - ) - }) - }) - }) - }) - describe('/bill-runs/{id}/send', () => { describe('GET', () => { beforeEach(async () => { @@ -802,97 +304,6 @@ describe('Bill Runs controller', () => { }) }) -function _authorisedVolumeData (error = false) { - const pageData = { - billRunId: '97db1a27-8308-4aba-b463-8a6af2558b28', - financialYear: '2022 to 2023', - chargePeriod: '1 April 2022 to 31 March 2023', - chargeReference: { - id: '9a8a148d-b71e-463c-bea8-bc5e0a5d95e2', - description: 'High loss, non-tidal, restricted water, greater than 85 up to and including 120 ML/yr', - authorisedVolume: 150, - totalBillableReturns: 140 - }, - chargeCategory: { - minVolume: 10, - maxVolume: 170 - } - } - - if (error) { - pageData.error = { authorisedVolume: 'The authorised volume must be a number' } - } - - return pageData -} - -function _billableReturnData (error = false) { - const pageDate = { - chargeElement: { - description: 'Spray irrigation - storage, Abstraction from borehole at Chipping Norton', - dates: '25 July 2022 to 29 December 2022' - }, - billRun: { - financialYear: '2022 to 2023' - }, - chargeVersion: { - chargePeriod: '1 April 2022 to 31 March 2023' - }, - authorisedQuantity: 40 - } - - if (error) { - pageDate.error = { message: 'Select the billable quantity' } - } - - return pageDate -} - -function _chargeElementDetails () { - return { - billRunId: '97db1a27-8308-4aba-b463-8a6af2558b28', - financialYear: '2022 to 2023', - chargePeriod: '1 April 2022 to 31 March 2023', - chargeElement: { - chargeElementId: '9a8a148d-b71e-463c-bea8-bc5e0a5d95e2', - description: 'River Test and tributaries near Fullerton Grange, Andover', - dates: '1 April 2022 to 31 October 2022', - status: 'Ready', - billableVolume: 10, - authorisedVolume: 10, - issues: [] - }, - matchedReturns: {} - } -} - -function _chargeReferenceData (error = false) { - const pageData = { - billRunId: '97db1a27-8308-4aba-b463-8a6af2558b28', - financialYear: '2022 to 2023', - chargePeriod: '1 April 2022 to 31 March 2023', - chargeReference: { - reference: '4.6.24', - description: 'High loss, non-tidal, restricted water, greater than 85 up to and including 120 ML/yr', - totalBillableReturns: 5, - authorisedVolume: 10, - adjustments: ['Aggregate factor (0.5)'] - } - } - - if (error) { - pageData.error = { - aggregateFactorElement: { - text: 'The aggregate factor must be a number' - }, - chargeAdjustmentElement: null - } - pageData.inputtedAggregateValue = '10' - } - - return pageData -} - function _getRequestOptions (path) { const root = '/bill-runs/97db1a27-8308-4aba-b463-8a6af2558b28' const url = path ? `${root}/${path}` : root @@ -914,54 +325,6 @@ function _postRequestOptions (path) { return postRequestOptions(url) } -function _licenceReviewData () { - return { - billRunId: '97db1a27-8308-4aba-b463-8a6af2558b28', - region: 'Southern (Test replica)', - licence: { - licenceId: '7c8a248c-b71e-463c-bea8-bc5e0a5d95e2', - licenceRef: '1/11/10/*S/0084', - status: 'review', - licenceHolder: 'Licence Holder Ltd' - }, - chargePeriodDates: ['1 April 2022 to 31 March 2023'], - matchedReturns: [ - { - returnId: '9a8a148d-b71e-463c-bea8-bc5e0a5d95e2', - reference: '11142960', - dates: '1 November 2021 to 31 October 2022', - status: 'completed', - description: 'Test Road. Points 1 and 2.', - purpose: 'Spray Irrigation - Anti Frost', - total: '0 ML / 0 ML', - issues: ['Returns received late'] - } - ], - unmatchedReturns: [], - chargeData: [] - } -} - -function _matchDetailsData () { - return { - billRunId: '6620135b-0ecf-4fd4-924e-371f950c0526', - financialYear: '2022 to 2023', - chargePeriod: '1 April 2022 to 5 June 2022', - licenceId: '5aa8e752-1a5c-4b01-9112-d92a543b70d1', - showBanner: true, - chargeElement: { - chargeElementId: 'b4d70c89-de1b-4f68-a47f-832b338ac044', - description: 'Trickle Irrigation - Direct', - dates: ['1 April 2022 to 5 June 2022'], - status: 'ready', - billableVolume: 0, - authorisedVolume: 200, - issues: [] - }, - matchedReturns: [] - } -} - function _multiGroupBillRun () { return { billsCount: '2 Annual bills', @@ -1014,41 +377,6 @@ function _multiGroupBillRun () { } } -function _reviewBillRunData () { - return { - region: 'Southern (Test replica)', - status: 'review', - dateCreated: '6 November 2023', - financialYear: '2021 to 2022', - billRunType: 'two-part tariff', - numberOfLicencesDisplayed: 2, - numberOfLicencesToReview: 1, - totalNumberOfLicences: 2, - preparedLicences: [ - { - licenceId: 'cc4bbb18-0d6a-4254-ac2c-7409de814d7e', - licenceRef: '1/11/11/*1/1111', - licenceHolder: 'Big Farm Ltd', - status: 'review', - issue: 'Multiple Issues' - }, - { - licenceId: '9442527a-64f3-471a-a3e4-fa0683a3d505', - licenceRef: '2/22/22/*2/2222', - licenceHolder: 'Small Farm Ltd', - status: 'ready', - issue: 'Multiple Issues' - } - ], - filter: { - issues: undefined, - licenceHolder: undefined, - licenceStatus: undefined, - openFilter: false - } - } -} - function _singleGroupBillRun () { return { billsCount: '1 Annual bill', diff --git a/test/fixtures/bill-runs-review.fixture.js b/test/fixtures/bill-runs-review.fixture.js new file mode 100644 index 0000000000..d8151ff39b --- /dev/null +++ b/test/fixtures/bill-runs-review.fixture.js @@ -0,0 +1,339 @@ +'use strict' + +const BillingAccountModel = require('../../app/models/billing-account.model.js') + +/** + * Represents a complete response from `FetchRemoveReviewLicenceService` + * + * @returns {object} + */ +function removeReviewLicence () { + return { + id: 'bb779166-0576-4581-b504-edbc0227d763', + licenceId: '32416c67-f755-4c3f-8816-ecde0ee596bd', + licenceRef: '1/11/11/*11/1111', + billRun: { + id: '287aeb25-cf11-429d-8c6f-f98f06db021d', + billRunNumber: 10001, + createdAt: new Date('2024-10-22'), + status: 'review', + toFinancialYearEnding: 2024, + region: { + id: '4ccf3c5b-ab4e-48e1-afa8-3b18b5d07fab', + displayName: 'Test Region' + } + } + } +} + +/** + * Represents a complete response from `FetchReviewChargeElementService` + * + * @returns {object} + */ +function reviewChargeElement () { + return { + id: 'a1840523-a04c-4c64-bff7-4a515e8ba1c1', + amendedAllocated: 0, + issues: 'Aggregate', + status: 'review', + chargeElement: { + id: 'dd050414-9c58-4a40-a114-77853f2fe6d2', + abstractionPeriodStartDay: 1, + abstractionPeriodStartMonth: 4, + abstractionPeriodEndDay: 30, + abstractionPeriodEndMonth: 9, + authorisedAnnualQuantity: 9.092, + description: 'Spray Irrigation - Direct' + }, + reviewChargeReference: { + id: '6c70461b-3f83-47b1-9538-8305e82b34eb', + amendedAuthorisedVolume: 9.092, + reviewChargeElements: [ + { id: 'a1840523-a04c-4c64-bff7-4a515e8ba1c1' } + ], + reviewChargeVersion: { + id: 'a71c386c-d9b8-4915-a508-74fb1508c071', + chargePeriodStartDate: new Date('2023-04-01'), + chargePeriodEndDate: new Date('2024-03-31'), + reviewLicence: { + id: 'bb779166-0576-4581-b504-edbc0227d763', + licenceId: '32416c67-f755-4c3f-8816-ecde0ee596bd', + billRun: { + id: '287aeb25-cf11-429d-8c6f-f98f06db021d', + toFinancialYearEnding: 2024 + } + } + } + }, + reviewReturns: [ + { + id: 'e3f64190-6a58-40af-8648-23c71ad1726f', + allocated: 0, + description: 'Test Road. Points 1 and 2.', + endDate: new Date('2023-10-31'), + issues: '', + quantity: 0, + purposes: [ + { + primary: { code: 'A', description: 'Agriculture' }, + tertiary: { code: '400', description: 'Spray Irrigation - Direct' }, + secondary: { code: 'AGR', description: 'General Agriculture' } + } + ], + returnId: 'v1:5:1/11/11/*11/1111:11142960:2022-11-01:2023-10-31', + returnReference: '11142960', + returnStatus: 'completed', + startDate: new Date('2022-11-01'), + underQuery: false, + returnLog: { + id: 'v1:5:1/11/11/*11/1111:11142960:2022-11-01:2023-10-31', + periodStartDay: 1, + periodStartMonth: 4, + periodEndDay: 30, + periodEndMonth: 9 + } + } + ] + } +} + +/** + * Represents a complete response from `FetchReviewChargeReferenceService` + * + * @returns {object} + */ +function reviewChargeReference () { + return { + id: '6b3d11f2-d361-4eaa-bce2-5561283bd023', + abatementAgreement: 1, + aggregate: 0.333333333, + amendedAggregate: 0.333333333, + amendedAuthorisedVolume: 9.092, + amendedChargeAdjustment: 1, + canalAndRiverTrustAgreement: false, + chargeAdjustment: 1, + twoPartTariffAgreement: true, + winterDiscount: false, + reviewChargeVersion: { + id: 'a71c386c-d9b8-4915-a508-74fb1508c071', + chargePeriodStartDate: new Date('2023-04-01'), + chargePeriodEndDate: new Date('2024-03-31'), + reviewLicence: { + id: 'bb779166-0576-4581-b504-edbc0227d763', + billRun: { + id: '287aeb25-cf11-429d-8c6f-f98f06db021d', + toFinancialYearEnding: 2024 + }, + licence: { + id: '32416c67-f755-4c3f-8816-ecde0ee596bd', + waterUndertaker: false + } + } + }, + reviewChargeElements: [ + { + id: 'a1840523-a04c-4c64-bff7-4a515e8ba1c1', + amendedAllocated: 0 + } + ], + chargeReference: { + id: 'fc493c81-5003-4dc0-9d48-4b5bf4af9c1e', + volume: 9.092, + chargeCategoryId: '9f194aa2-562d-4e89-a0ce-d4a31b5833b1', + loss: 'high', + supportedSourceName: null, + waterCompanyCharge: null, + chargeCategory: { + id: '9f194aa2-562d-4e89-a0ce-d4a31b5833b1', + reference: '4.6.5', + shortDescription: 'High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model' + } + } + } +} + +/** + * Represents a complete response from `FetchReviewLicenceService` + * + * @returns {object} + */ +function reviewLicence () { + return { + id: 'bb779166-0576-4581-b504-edbc0227d763', + billRunId: '287aeb25-cf11-429d-8c6f-f98f06db021d', + licenceId: '32416c67-f755-4c3f-8816-ecde0ee596bd', + licenceRef: '1/11/11/*11/1111', + licenceHolder: 'Licence Holder Ltd', + status: 'review', + progress: false, + billRun: { + id: '287aeb25-cf11-429d-8c6f-f98f06db021d', + toFinancialYearEnding: 2024, + region: { + id: '4ccf3c5b-ab4e-48e1-afa8-3b18b5d07fab', + displayName: 'South West' + } + }, + reviewReturns: [ + { + id: 'e3f64190-6a58-40af-8648-23c71ad1726f', + allocated: 0, + description: 'Test Road. Points 1 and 2.', + endDate: new Date('2023-10-31'), + issues: '', + quantity: 0, + purposes: [ + { + primary: { code: 'A', description: 'Agriculture' }, + tertiary: { code: '400', description: 'Spray Irrigation - Direct' }, + secondary: { code: 'AGR', description: 'General Agriculture' } + } + ], + returnId: 'v1:5:1/11/11/*11/1111:11142960:2022-11-01:2023-10-31', + returnReference: '11142960', + returnStatus: 'completed', + startDate: new Date('2022-11-01'), + underQuery: false, + returnLog: { + id: 'v1:5:1/11/11/*11/1111:11142960:2022-11-01:2023-10-31', + periodStartDay: 1, + periodStartMonth: 4, + periodEndDay: 30, + periodEndMonth: 9 + }, + reviewChargeElements: [ + { + id: 'a1840523-a04c-4c64-bff7-4a515e8ba1c1', + reviewChargeReferenceId: '6c70461b-3f83-47b1-9538-8305e82b34eb', + chargeElementId: 'dd050414-9c58-4a40-a114-77853f2fe6d2', + allocated: 0, + chargeDatesOverlap: false, + issues: 'Aggregate', + status: 'review', + createdAt: new Date('2024-10-22'), + updatedAt: new Date('2024-10-22'), + amendedAllocated: 0 + } + ] + }, + { + id: '4864f643-5c16-5ca9-8512-f63e1d4e58be', + allocated: 0, + description: 'Lost Road. Points 1 and 2.', + endDate: new Date('2023-10-31'), + issues: '', + quantity: 0, + purposes: [ + { + primary: { code: 'A', description: 'Agriculture' }, + tertiary: { code: '420', description: 'Spray Irrigation - Storage' }, + secondary: { code: 'AGR', description: 'General Agriculture' } + } + ], + returnId: 'v1:5:1/11/11/*11/1111:11142961:2022-11-01:2023-10-31', + returnReference: '11142961', + returnStatus: 'completed', + startDate: new Date('2022-11-01'), + underQuery: false, + returnLog: { + id: 'v1:5:1/11/11/*11/1111:11142961:2022-11-01:2023-10-31', + periodStartDay: 1, + periodStartMonth: 4, + periodEndDay: 30, + periodEndMonth: 9 + }, + reviewChargeElements: [] + } + ], + reviewChargeVersions: [ + { + id: 'a71c386c-d9b8-4915-a508-74fb1508c071', + chargePeriodEndDate: new Date('2024-03-31'), + chargePeriodStartDate: new Date('2023-04-01'), + reviewChargeReferences: [ + { + id: '6c70461b-3f83-47b1-9538-8305e82b34eb', + aggregate: 0.333333333, + amendedAuthorisedVolume: 9.092, + chargeAdjustment: 1, + chargeReference: { + id: 'fc493c81-5003-4dc0-9d48-4b5bf4af9c1e', + chargeCategory: { + id: '9f194aa2-562d-4e89-a0ce-d4a31b5833b1', + reference: '4.6.5', + shortDescription: 'High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model' + } + }, + reviewChargeElements: [ + { + id: 'a1840523-a04c-4c64-bff7-4a515e8ba1c1', + amendedAllocated: 0, + issues: 'Aggregate', + status: 'review', + chargeElement: { + id: 'dd050414-9c58-4a40-a114-77853f2fe6d2', + description: 'Spray Irrigation - Direct', + abstractionPeriodStartDay: 1, + abstractionPeriodStartMonth: 4, + abstractionPeriodEndDay: 30, + abstractionPeriodEndMonth: 9, + authorisedAnnualQuantity: 9.092, + purpose: { + id: '4ad11971-be6a-4da5-af04-563c76205b0e', + description: 'Spray Irrigation - Direct' + } + }, + reviewReturns: [ + { + id: 'e3f64190-6a58-40af-8648-23c71ad1726f', + quantity: 0, + returnReference: '10030495', + returnStatus: 'completed' + } + ] + } + ] + } + ], + chargeVersion: { + id: '281a6820-4074-4ba8-92f1-7d1bfe63b426', + billingAccount: BillingAccountModel.fromJson({ + id: 'f041c128-bb4d-4f67-8f97-e33d71d50842', + accountNumber: 'E99999999A', + company: { + id: '838c9770-87a8-47b6-89a4-549c8e08ed2f', + name: 'Mr B Blobby Ltd', + type: 'organisation' + }, + billingAccountAddresses: [ + { + id: 'c95c0144-db2d-48fc-9bc0-e60126f762db', + company: null, + contact: null, + address: { + id: 'da502c5b-0214-49d5-9125-83c4ea1359e5', + address1: 'C/O Noel Edmonds', + address2: 'Crinkley Bottom', + address3: 'Cricket St Thomas', + address4: null, + address5: null, + address6: 'Somerset', + postcode: 'TA20 1KL', + country: 'United Kingdom' + } + } + ] + }) + } + } + ] + } +} + +module.exports = { + removeReviewLicence, + reviewChargeElement, + reviewChargeReference, + reviewLicence +} diff --git a/test/presenters/bill-runs/cancel-bill-run.presenter.test.js b/test/presenters/bill-runs/cancel-bill-run.presenter.test.js index a481617b22..e8be48ee00 100644 --- a/test/presenters/bill-runs/cancel-bill-run.presenter.test.js +++ b/test/presenters/bill-runs/cancel-bill-run.presenter.test.js @@ -44,7 +44,7 @@ describe('Cancel Bill Run presenter', () => { it('returns a link to the SROC review page', () => { const result = CancelBillRunPresenter.go(billRun) - expect(result.backLink).to.equal('/system/bill-runs/420e948f-1992-437e-8a47-74c0066cb017/review') + expect(result.backLink).to.equal('/system/bill-runs/review/420e948f-1992-437e-8a47-74c0066cb017') }) }) diff --git a/test/presenters/bill-runs/index-bill-runs.presenter.test.js b/test/presenters/bill-runs/index-bill-runs.presenter.test.js index 0539c003ee..fef530e55c 100644 --- a/test/presenters/bill-runs/index-bill-runs.presenter.test.js +++ b/test/presenters/bill-runs/index-bill-runs.presenter.test.js @@ -142,7 +142,7 @@ describe('Index Bill Runs presenter', () => { it('generates the href needed to link to bill run review', () => { const results = IndexBillRunsPresenter.go(billRuns) - expect(results[0].link).to.equal('/system/bill-runs/31fec553-f2de-40cf-a8d7-a5fb65f5761b/review') + expect(results[0].link).to.equal('/system/bill-runs/review/31fec553-f2de-40cf-a8d7-a5fb65f5761b') expect(results[1].link).to.equal('/system/bill-runs/dfdde4c9-9a0e-440d-b297-7143903c6734') }) }) diff --git a/test/presenters/bill-runs/review/authorised.presenter.test.js b/test/presenters/bill-runs/review/authorised.presenter.test.js new file mode 100644 index 0000000000..725c3ac7e7 --- /dev/null +++ b/test/presenters/bill-runs/review/authorised.presenter.test.js @@ -0,0 +1,37 @@ +'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 BillRunsReviewFixture = require('../../../fixtures/bill-runs-review.fixture.js') + +// Thing under test +const AuthorisedPresenter = require('../../../../app/presenters/bill-runs/review/authorised.presenter.js') + +describe('Bill Runs Review - Authorised presenter', () => { + let reviewChargeReference + + beforeEach(() => { + reviewChargeReference = BillRunsReviewFixture.reviewChargeReference() + }) + + describe('when provided with the result of fetch review charge reference service', () => { + it('correctly presents the data', () => { + const result = AuthorisedPresenter.go(reviewChargeReference) + + expect(result).to.equal({ + amendedAuthorisedVolume: 9.092, + chargeDescription: 'High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model', + chargePeriod: '1 April 2023 to 31 March 2024', + financialPeriod: '2023 to 2024', + reviewChargeReferenceId: '6b3d11f2-d361-4eaa-bce2-5561283bd023', + totalBillableReturns: 0 + }) + }) + }) +}) diff --git a/test/presenters/bill-runs/review/base-review.presenter.test.js b/test/presenters/bill-runs/review/base-review.presenter.test.js new file mode 100644 index 0000000000..707588366a --- /dev/null +++ b/test/presenters/bill-runs/review/base-review.presenter.test.js @@ -0,0 +1,382 @@ +'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 ReturnLogHelper = require('../../../support/helpers/return-log.helper.js') + +// Thing under test +const BaseReviewPresenter = require('../../../../app/presenters/bill-runs/review/base-review.presenter.js') + +describe('Bill Runs Review - Base Review presenter', () => { + describe('#calculateTotalBillableReturns()', () => { + const reviewChargeElements = [ + { amendedAllocated: 11.513736 }, + { amendedAllocated: 1.92708 }, + { amendedAllocated: 23.022753 }, + { amendedAllocated: 0.636794 }, + { amendedAllocated: 26.400139 } + ] + + it('returns the sum of "amendedAllocated" for the review charge elements passed in', () => { + const result = BaseReviewPresenter.calculateTotalBillableReturns(reviewChargeElements) + + expect(result).to.equal(63.500502) + }) + }) + + describe('#determineReturnLink()', () => { + const returnId = ReturnLogHelper.generateReturnLogId() + + let reviewReturn + + describe('when the review return has a status of "due"', () => { + beforeEach(() => { + reviewReturn = { returnId, returnStatus: 'due' } + }) + + it('returns the link to edit the return', () => { + const result = BaseReviewPresenter.determineReturnLink(reviewReturn) + + expect(result).to.equal(`/return/internal?returnId=${returnId}`) + }) + }) + + describe('when the review return has a status of "received"', () => { + beforeEach(() => { + reviewReturn = { returnId, returnStatus: 'received' } + }) + + it('returns the link to edit the return', () => { + const result = BaseReviewPresenter.determineReturnLink(reviewReturn) + + expect(result).to.equal(`/return/internal?returnId=${returnId}`) + }) + }) + + describe('when the review return has any other status', () => { + beforeEach(() => { + reviewReturn = { returnId, returnStatus: 'completed' } + }) + + it('returns the link to view the return', () => { + const result = BaseReviewPresenter.determineReturnLink(reviewReturn) + + expect(result).to.equal(`/returns/return?id=${returnId}`) + }) + }) + }) + + describe('#formatAdditionalCharges()', () => { + let chargeReference + + describe('when the charge reference has no additional charges', () => { + beforeEach(() => { + chargeReference = { supportedSourceName: null, waterCompanyCharge: null } + }) + + it('returns an empty array', () => { + const result = BaseReviewPresenter.formatAdditionalCharges(chargeReference) + + expect(result).to.be.empty() + }) + }) + + describe('when the charge reference has a supported source name', () => { + beforeEach(() => { + chargeReference = { supportedSourceName: 'Foo source', waterCompanyCharge: null } + }) + + it('returns an array containing the charge formatted for display', () => { + const result = BaseReviewPresenter.formatAdditionalCharges(chargeReference) + + expect(result).to.equal(['Supported source Foo source']) + }) + }) + + describe('when the charge reference has a water company charge', () => { + beforeEach(() => { + chargeReference = { supportedSourceName: null, waterCompanyCharge: true } + }) + + it('returns an array containing the charge formatted for display', () => { + const result = BaseReviewPresenter.formatAdditionalCharges(chargeReference) + + expect(result).to.equal(['Public Water Supply']) + }) + }) + + describe('when the charge reference has both charges', () => { + beforeEach(() => { + chargeReference = { supportedSourceName: 'Foo source', waterCompanyCharge: true } + }) + + it('returns an array containing the charges formatted for display', () => { + const result = BaseReviewPresenter.formatAdditionalCharges(chargeReference) + + expect(result).to.equal(['Supported source Foo source', 'Public Water Supply']) + }) + }) + }) + + describe('#formatAdjustments()', () => { + let reviewChargeReference + + describe('when the review charge reference has no adjustments', () => { + beforeEach(() => { + reviewChargeReference = {} + }) + + it('returns an empty array', () => { + const result = BaseReviewPresenter.formatAdjustments(reviewChargeReference) + + expect(result).to.be.empty() + }) + }) + + describe('when the review charge reference has an abatement agreement', () => { + describe('that is set to a value other than 1', () => { + beforeEach(() => { + reviewChargeReference = { abatementAgreement: 0.3 } + }) + + it('includes it in the array returned', () => { + const result = BaseReviewPresenter.formatAdjustments(reviewChargeReference) + + expect(result).to.include('Abatement agreement (0.3)') + }) + }) + + describe('that is set to 1', () => { + beforeEach(() => { + reviewChargeReference = { abatementAgreement: 1 } + }) + + it('does not add include it in the array returned', () => { + const result = BaseReviewPresenter.formatAdjustments(reviewChargeReference) + + expect(result).to.not.include('Abatement agreement (1)') + }) + }) + }) + + describe('when the review charge reference has a winter discount', () => { + beforeEach(() => { + reviewChargeReference = { winterDiscount: true } + }) + + it('includes it in the array returned', () => { + const result = BaseReviewPresenter.formatAdjustments(reviewChargeReference) + + expect(result).to.include('Winter discount') + }) + }) + + describe('when the review charge reference has a two part tariff agreement', () => { + beforeEach(() => { + reviewChargeReference = { twoPartTariffAgreement: true } + }) + + it('includes it in the array returned', () => { + const result = BaseReviewPresenter.formatAdjustments(reviewChargeReference) + + expect(result).to.include('Two part tariff agreement') + }) + }) + + describe('when the review charge reference has a canal and river trust agreement', () => { + beforeEach(() => { + reviewChargeReference = { canalAndRiverTrustAgreement: true } + }) + + it('includes it in the array returned', () => { + const result = BaseReviewPresenter.formatAdjustments(reviewChargeReference) + + expect(result).to.include('Canal and River trust agreement') + }) + }) + }) + + describe('#formatChargePeriod()', () => { + const reviewChargeVersion = { + chargePeriodStartDate: new Date('2024-04-01'), + chargePeriodEndDate: new Date('2024-09-30') + } + + it("returns the review charge version's charge period formatted for display", () => { + const result = BaseReviewPresenter.formatChargePeriod(reviewChargeVersion) + + expect(result).to.equal('1 April 2024 to 30 September 2024') + }) + }) + + describe('#formatChargePeriods()', () => { + const chargeElement = { + abstractionPeriodStartDay: 1, + abstractionPeriodStartMonth: 1, + abstractionPeriodEndDay: 31, + abstractionPeriodEndMonth: 12 + } + + let reviewChargeElement + + describe('when no charge period is provided (it will be extracted via linked records)', () => { + beforeEach(() => { + reviewChargeElement = { + chargeElement, + reviewChargeReference: { + reviewChargeVersion: { + chargePeriodStartDate: new Date('2023-05-24'), + chargePeriodEndDate: new Date('2024-03-31') + } + } + } + }) + + it("will return the review element's charge periods formatted for display", () => { + const result = BaseReviewPresenter.formatChargePeriods(reviewChargeElement) + + expect(result).to.equal(['24 May 2023 to 31 December 2023', '1 January 2024 to 31 March 2024']) + }) + }) + + describe('when a charge period is provided', () => { + const chargePeriod = { startDate: new Date('2023-05-24'), endDate: new Date('2024-03-31') } + + beforeEach(() => { + reviewChargeElement = { chargeElement } + }) + + it("will return the review element's charge periods formatted for display", () => { + const result = BaseReviewPresenter.formatChargePeriods(reviewChargeElement, chargePeriod) + + expect(result).to.equal(['24 May 2023 to 31 December 2023', '1 January 2024 to 31 March 2024']) + }) + }) + }) + + describe('#formatIssues()', () => { + let issues + + describe('when "issues" is an empty string', () => { + beforeEach(() => { + issues = '' + }) + + it('returns an empty array', () => { + const result = BaseReviewPresenter.formatIssues(issues) + + expect(result).to.be.empty() + }) + }) + + describe('when "issues" is a single value', () => { + beforeEach(() => { + issues = 'Aggregate' + }) + + it('returns an array containing the one value', () => { + const result = BaseReviewPresenter.formatIssues(issues) + + expect(result).to.equal(['Aggregate']) + }) + }) + + describe('when "issues" contains multiple values', () => { + beforeEach(() => { + issues = 'Aggregate, No returns received' + }) + + it('returns an array containing all values', () => { + const result = BaseReviewPresenter.formatIssues(issues) + + expect(result).to.equal(['Aggregate', 'No returns received']) + }) + }) + }) + + describe('#formatReturnStatus()', () => { + let reviewReturn + + describe("when the review return's status is 'due'", () => { + beforeEach(() => { + reviewReturn = { returnStatus: 'due', underQuery: false } + }) + + it('returns "overdue"', () => { + const result = BaseReviewPresenter.formatReturnStatus(reviewReturn) + + expect(result).to.equal('overdue') + }) + }) + + describe("when the review return is 'under query'", () => { + beforeEach(() => { + reviewReturn = { returnStatus: 'completed', underQuery: true } + }) + + it('returns "query"', () => { + const result = BaseReviewPresenter.formatReturnStatus(reviewReturn) + + expect(result).to.equal('query') + }) + }) + + describe("when the review return is not 'under query' and the status is not 'due'", () => { + beforeEach(() => { + reviewReturn = { returnStatus: 'completed', underQuery: false } + }) + + it('returns whatever is the status of the review return', () => { + const result = BaseReviewPresenter.formatReturnStatus(reviewReturn) + + expect(result).to.equal('completed') + }) + }) + }) + + describe('#formatReturnTotals()', () => { + let reviewReturn + + describe("when the review return's status is 'due'", () => { + beforeEach(() => { + reviewReturn = { allocated: 10.1, quantity: 15, returnStatus: 'due' } + }) + + it('returns "/"', () => { + const result = BaseReviewPresenter.formatReturnTotals(reviewReturn) + + expect(result).to.equal('/') + }) + }) + + describe("when the review return's status is 'received'", () => { + beforeEach(() => { + reviewReturn = { allocated: 10.1, quantity: 15, returnStatus: 'received' } + }) + + it('returns "/"', () => { + const result = BaseReviewPresenter.formatReturnTotals(reviewReturn) + + expect(result).to.equal('/') + }) + }) + + describe('when the review return has any other status', () => { + beforeEach(() => { + reviewReturn = { allocated: 10.1, quantity: 15, returnStatus: 'completed' } + }) + + it('returns "10.1 ML / 15 ML"', () => { + const result = BaseReviewPresenter.formatReturnTotals(reviewReturn) + + expect(result).to.equal('10.1 ML / 15 ML') + }) + }) + }) +}) diff --git a/test/presenters/bill-runs/review/edit.presenter.test.js b/test/presenters/bill-runs/review/edit.presenter.test.js new file mode 100644 index 0000000000..9dc4b6907f --- /dev/null +++ b/test/presenters/bill-runs/review/edit.presenter.test.js @@ -0,0 +1,67 @@ +'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 BillRunsReviewFixture = require('../../../fixtures/bill-runs-review.fixture.js') + +// Thing under test +const EditPresenter = require('../../../../app/presenters/bill-runs/review/edit.presenter.js') + +describe('Bill Runs Review - Edit presenter', () => { + const elementIndex = 1 + + let reviewChargeElement + + beforeEach(() => { + reviewChargeElement = BillRunsReviewFixture.reviewChargeElement() + }) + + describe('when provided with the result of fetch review charge element service', () => { + it('correctly presents the data', () => { + const result = EditPresenter.go(reviewChargeElement, elementIndex) + + expect(result).to.equal({ + authorisedQuantity: 9.092, + billableReturns: 0, + chargeDescription: 'Spray Irrigation - Direct', + chargePeriod: '1 April 2023 to 31 March 2024', + chargePeriods: ['1 April 2023 to 30 September 2023'], + elementIndex: 1, + financialPeriod: '2023 to 2024', + reviewChargeElementId: 'a1840523-a04c-4c64-bff7-4a515e8ba1c1' + }) + }) + }) + + describe('the "authorisedQuantity" property', () => { + describe("when the linked review charge reference's authorised volume is less than the element's", () => { + beforeEach(() => { + reviewChargeElement.reviewChargeReference.amendedAuthorisedVolume = 5 + }) + + it("returns the charge reference's lower authorised volume", () => { + const result = EditPresenter.go(reviewChargeElement) + + expect(result.authorisedQuantity).to.equal(5) + }) + }) + + describe("when the linked review charge reference's authorised volume is greater than the element's", () => { + beforeEach(() => { + reviewChargeElement.reviewChargeReference.amendedAuthorisedVolume = 15 + }) + + it("returns the charge element's lower authorised volume", () => { + const result = EditPresenter.go(reviewChargeElement) + + expect(result.authorisedQuantity).to.equal(9.092) + }) + }) + }) +}) diff --git a/test/presenters/bill-runs/review/factors.presenter.test.js b/test/presenters/bill-runs/review/factors.presenter.test.js new file mode 100644 index 0000000000..10cc08c063 --- /dev/null +++ b/test/presenters/bill-runs/review/factors.presenter.test.js @@ -0,0 +1,97 @@ +'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 BillRunsReviewFixture = require('../../../fixtures/bill-runs-review.fixture.js') + +// Thing under test +const FactorsPresenter = require('../../../../app/presenters/bill-runs/review/factors.presenter.js') + +describe('Bill Runs Review - Factors presenter', () => { + let reviewChargeReference + + beforeEach(() => { + reviewChargeReference = BillRunsReviewFixture.reviewChargeReference() + }) + + describe('when provided with the result of fetch review charge reference service', () => { + it('correctly presents the data', () => { + const result = FactorsPresenter.go(reviewChargeReference) + + expect(result).to.equal({ + amendedAggregate: 0.333333333, + amendedChargeAdjustment: 1, + chargeDescription: 'High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model', + chargePeriod: '1 April 2023 to 31 March 2024', + financialPeriod: '2023 to 2024', + otherAdjustments: ['Two part tariff agreement'], + reviewChargeReferenceId: '6b3d11f2-d361-4eaa-bce2-5561283bd023' + }) + }) + }) + + // NOTE: otherAdjustments combines the results of `formatAdditionalCharges()` and `formatAdjustments()` from + // `base-review.presenter.js`. So, just to ensure the combining is working correctly we just test what we get back + // when we have one of each, and one from each type + describe('the "otherAdjustments" property', () => { + beforeEach(() => { + // Our fixture has this as true by default. We set it false so it doesn't interfere with the following tests + reviewChargeReference.twoPartTariffAgreement = false + }) + + describe('when the charge reference has only an adjustment', () => { + beforeEach(() => { + reviewChargeReference.abatementAgreement = 0.3 + }) + + it('the otherAdjustments property only contains the adjustment', () => { + const result = FactorsPresenter.go(reviewChargeReference) + + expect(result.otherAdjustments).to.equal(['Abatement agreement (0.3)']) + }) + }) + + describe('when the charge reference has only an additional charge', () => { + beforeEach(() => { + reviewChargeReference.chargeReference.waterCompanyCharge = true + }) + + it('the otherAdjustments property only contains the additional charge', () => { + const result = FactorsPresenter.go(reviewChargeReference) + + expect(result.otherAdjustments).to.equal(['Public Water Supply']) + }) + }) + + describe('when the charge reference has both an adjustment and an additional charge', () => { + beforeEach(() => { + reviewChargeReference.abatementAgreement = 0.3 + reviewChargeReference.chargeReference.waterCompanyCharge = true + }) + + it('the otherAdjustments property contains both', () => { + const result = FactorsPresenter.go(reviewChargeReference) + + expect(result.otherAdjustments).to.equal(['Public Water Supply', 'Abatement agreement (0.3)']) + }) + }) + + describe('when the charge reference has no adjustments or additional charges', () => { + beforeEach(() => { + reviewChargeReference.twoPartTariffAgreement = false + }) + + it('sets the otherAdjustments property as empty', () => { + const result = FactorsPresenter.go(reviewChargeReference) + + expect(result.otherAdjustments).to.equal([]) + }) + }) + }) +}) diff --git a/test/presenters/bill-runs/review/remove.presenter.test.js b/test/presenters/bill-runs/review/remove.presenter.test.js new file mode 100644 index 0000000000..0302481be8 --- /dev/null +++ b/test/presenters/bill-runs/review/remove.presenter.test.js @@ -0,0 +1,38 @@ +'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 BillRunsReviewFixture = require('../../../fixtures/bill-runs-review.fixture.js') + +// Thing under test +const RemovePresenter = require('../../../../app/presenters/bill-runs/review/remove.presenter.js') + +describe('Bill Runs Review - Remove presenter', () => { + let removeReviewLicence + + beforeEach(() => { + removeReviewLicence = BillRunsReviewFixture.removeReviewLicence() + }) + + describe('when provided with the result of fetch remove review licence service', () => { + it('correctly presents the data', () => { + const result = RemovePresenter.go(removeReviewLicence) + + expect(result).to.equal({ + billRunNumber: 10001, + billRunStatus: 'review', + dateCreated: '22 October 2024', + financialYearPeriod: '2023 to 2024', + pageTitle: "You're about to remove 1/11/11/*11/1111 from the bill run", + region: 'Test Region', + reviewLicenceId: 'bb779166-0576-4581-b504-edbc0227d763' + }) + }) + }) +}) diff --git a/test/presenters/bill-runs/two-part-tariff/review-bill-run.presenter.test.js b/test/presenters/bill-runs/review/review-bill-run.presenter.test.js similarity index 95% rename from test/presenters/bill-runs/two-part-tariff/review-bill-run.presenter.test.js rename to test/presenters/bill-runs/review/review-bill-run.presenter.test.js index 6e6cb34053..7d082f6f07 100644 --- a/test/presenters/bill-runs/two-part-tariff/review-bill-run.presenter.test.js +++ b/test/presenters/bill-runs/review/review-bill-run.presenter.test.js @@ -8,9 +8,9 @@ const { beforeEach, describe, it } = exports.lab = Lab.script() const { expect } = Code // Thing under test -const ReviewBillRunPresenter = require('../../../../app/presenters/bill-runs/two-part-tariff/review-bill-run.presenter.js') +const ReviewBillRunPresenter = require('../../../../app/presenters/bill-runs/review/review-bill-run.presenter.js') -describe('Review Bill Run presenter', () => { +describe('Bill Runs Review - Review Bill Run presenter', () => { describe('when there is data to be presented for review', () => { let filterIssues let filterLicenceHolderNumber @@ -192,7 +192,7 @@ function _testLicences () { return [ // Licence with no issues { - licenceId: 'cc4bbb18-0d6a-4254-ac2c-7409de814d7e', + id: 'cc4bbb18-0d6a-4254-ac2c-7409de814d7e', licenceRef: '1/11/11/*11/1111', licenceHolder: 'Big Farm Ltd', issues: '', @@ -201,7 +201,7 @@ function _testLicences () { }, // Licence with a single issue { - licenceId: '395bdc01-605b-44f5-9d90-5836cc013799', + id: '395bdc01-605b-44f5-9d90-5836cc013799', licenceRef: '2/22/22/*S2/2222', licenceHolder: 'Bob Bobbles', issues: 'Abstraction outside period', @@ -210,7 +210,7 @@ function _testLicences () { }, // Licence with multiple issues { - licenceId: 'fdae33da-9195-4b97-976a-9791bc4f6b66', + id: 'fdae33da-9195-4b97-976a-9791bc4f6b66', licenceRef: '3/33/33/*3/3333', licenceHolder: 'Farmer Palmer', issues: 'Abstraction outside period, Over abstraction, Overlap of charge dates', diff --git a/test/presenters/bill-runs/review/review-charge-element.presenter.test.js b/test/presenters/bill-runs/review/review-charge-element.presenter.test.js new file mode 100644 index 0000000000..54dd22a55c --- /dev/null +++ b/test/presenters/bill-runs/review/review-charge-element.presenter.test.js @@ -0,0 +1,70 @@ +'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 BillRunsReviewFixture = require('../../../fixtures/bill-runs-review.fixture.js') + +// Thing under test +const ReviewChargeElementPresenter = require('../../../../app/presenters/bill-runs/review/review-charge-element.presenter.js') + +describe('Bill Runs Review - Review Charge Element presenter', () => { + const elementIndex = 1 + + let reviewChargeElement + + beforeEach(() => { + reviewChargeElement = BillRunsReviewFixture.reviewChargeElement() + }) + + describe('when provided with a ReviewChargeElement', () => { + it('correctly presents the data', () => { + const result = ReviewChargeElementPresenter.go(reviewChargeElement, elementIndex) + + expect(result).to.equal({ + authorisedVolume: 9.092, + billableReturns: 0, + chargeDescription: 'Spray Irrigation - Direct', + chargePeriod: '1 April 2023 to 31 March 2024', + chargePeriods: ['1 April 2023 to 30 September 2023'], + elementCount: 1, + elementIndex: 1, + financialPeriod: '2023 to 2024', + issues: ['Aggregate'], + licenceId: '32416c67-f755-4c3f-8816-ecde0ee596bd', + matchedReturns: [ + { + abstractionPeriod: '1 April to 30 September', + description: 'Test Road. Points 1 and 2.', + issues: [], + purpose: 'Spray Irrigation - Direct', + reference: '11142960', + returnId: 'v1:5:1/11/11/*11/1111:11142960:2022-11-01:2023-10-31', + returnLink: '/returns/return?id=v1:5:1/11/11/*11/1111:11142960:2022-11-01:2023-10-31', + returnPeriod: '1 November 2022 to 31 October 2023', + returnStatus: 'completed', + returnTotal: '0 ML / 0 ML' + } + ], + reviewChargeElementId: 'a1840523-a04c-4c64-bff7-4a515e8ba1c1', + reviewLicenceId: 'bb779166-0576-4581-b504-edbc0227d763', + status: 'review' + }) + }) + }) + + describe('the "matchedReturns" property', () => { + describe('the "purpose" property', () => { + it("returns the matched return's tertiary purpose description", () => { + const result = ReviewChargeElementPresenter.go(reviewChargeElement, elementIndex) + + expect(result.matchedReturns[0].purpose).to.equal('Spray Irrigation - Direct') + }) + }) + }) +}) diff --git a/test/presenters/bill-runs/review/review-charge-reference.presenter.test.js b/test/presenters/bill-runs/review/review-charge-reference.presenter.test.js new file mode 100644 index 0000000000..daffa75b5c --- /dev/null +++ b/test/presenters/bill-runs/review/review-charge-reference.presenter.test.js @@ -0,0 +1,143 @@ +'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 BillRunsReviewFixture = require('../../../fixtures/bill-runs-review.fixture.js') + +// Thing under test +const ReviewChargeReferencePresenter = require('../../../../app/presenters/bill-runs/review/review-charge-reference.presenter.js') + +describe('Bill Runs Review - Review Charge Reference presenter', () => { + let reviewChargeReference + + beforeEach(() => { + reviewChargeReference = BillRunsReviewFixture.reviewChargeReference() + }) + + describe('when provided with the result of fetch review charge reference service', () => { + it('correctly presents the data', () => { + const result = ReviewChargeReferencePresenter.go(reviewChargeReference) + + expect(result).to.equal({ + additionalCharges: '', + adjustments: [ + 'Aggregate factor (0.333333333 / 0.333333333)', + 'Charge adjustment (1 / 1)', + 'Two part tariff agreement' + ], + amendedAuthorisedVolume: 9.092, + canAmend: true, + chargeCategory: '4.6.5', + chargeDescription: 'High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model', + chargePeriod: '1 April 2023 to 31 March 2024', + financialPeriod: '2023 to 2024', + reviewChargeReferenceId: '6b3d11f2-d361-4eaa-bce2-5561283bd023', + reviewLicenceId: 'bb779166-0576-4581-b504-edbc0227d763', + totalBillableReturns: 0 + }) + }) + }) + + // NOTE: adjustments combines the results of an internal `_factors()` method and `formatAdjustments()` from + // `base-review.presenter.js`. So, the tests focus on ensuring `_factors()` is working and combining as expected with + // the result of `formatAdjustments()`. + describe('the "adjustments" property', () => { + beforeEach(() => { + // Our fixture has these set by default. We set unset them so they don't interfere with the following tests + reviewChargeReference.aggregate = 1 + reviewChargeReference.amendedAggregate = 1 + reviewChargeReference.twoPartTariffAgreement = false + }) + + // NOTE: A value other than 1 or false means the charge reference has the 'adjustment' + describe('when the user can change the factors', () => { + describe('because the aggregate factor is not 1', () => { + beforeEach(() => { + reviewChargeReference.aggregate = 0.5 + reviewChargeReference.amendedAggregate = 0.6 + }) + + it('adds it to the ""adjustments" property and displays both amended and original values', () => { + const result = ReviewChargeReferencePresenter.go(reviewChargeReference) + + expect(result.adjustments).to.equal([ + 'Aggregate factor (0.6 / 0.5)', + 'Charge adjustment (1 / 1)' + ]) + }) + }) + + describe('because the charge adjustment factor is not 1', () => { + beforeEach(() => { + reviewChargeReference.amendedChargeAdjustment = 0.8 + reviewChargeReference.chargeAdjustment = 0.7 + }) + + it('adds it to the ""adjustments" property and displays both amended and original values', () => { + const result = ReviewChargeReferencePresenter.go(reviewChargeReference) + + expect(result.adjustments).to.equal([ + 'Aggregate factor (1 / 1)', + 'Charge adjustment (0.8 / 0.7)' + ]) + }) + }) + + describe('and there is also an adjustment', () => { + beforeEach(() => { + reviewChargeReference.aggregate = 0.9 + reviewChargeReference.amendedAggregate = 0.9 + reviewChargeReference.twoPartTariffAgreement = true + }) + + it('adds it to the ""adjustments" property along with the factors', () => { + const result = ReviewChargeReferencePresenter.go(reviewChargeReference) + + expect(result.adjustments).to.equal([ + 'Aggregate factor (0.9 / 0.9)', + 'Charge adjustment (1 / 1)', + 'Two part tariff agreement' + ]) + }) + }) + }) + + describe('when the user cannot change the factors', () => { + describe('because both the aggregate and charge adjustment factors are 1', () => { + beforeEach(() => { + reviewChargeReference.aggregate = 1 + reviewChargeReference.amendedAggregate = 1 + reviewChargeReference.amendedChargeAdjustment = 1 + reviewChargeReference.chargeAdjustment = 1 + }) + + it('does not add either factor to "adjustments"', () => { + const result = ReviewChargeReferencePresenter.go(reviewChargeReference) + + expect(result.adjustments).to.not.include('Aggregate factor (1)') + expect(result.adjustments).to.not.include('Charge adjustment (1)') + }) + }) + + describe('but there is an adjustment', () => { + beforeEach(() => { + reviewChargeReference.aggregate = 1 + reviewChargeReference.amendedAggregate = 1 + reviewChargeReference.twoPartTariffAgreement = true + }) + + it('adds just the adjustment to the ""adjustments" property', () => { + const result = ReviewChargeReferencePresenter.go(reviewChargeReference) + + expect(result.adjustments).to.equal(['Two part tariff agreement']) + }) + }) + }) + }) +}) diff --git a/test/presenters/bill-runs/review/review-licence.presenter.test.js b/test/presenters/bill-runs/review/review-licence.presenter.test.js new file mode 100644 index 0000000000..919212c8b4 --- /dev/null +++ b/test/presenters/bill-runs/review/review-licence.presenter.test.js @@ -0,0 +1,201 @@ +'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 BillRunsReviewFixture = require('../../../fixtures/bill-runs-review.fixture.js') + +// Thing under test +const ReviewLicencePresenter = require('../../../../app/presenters/bill-runs/review/review-licence.presenter.js') + +describe('Bill Runs Review - Review Licence presenter', () => { + let reviewLicence + + beforeEach(() => { + reviewLicence = BillRunsReviewFixture.reviewLicence() + }) + + describe('when provided with the result of fetch review licence service', () => { + it('correctly presents the data', async () => { + const result = ReviewLicencePresenter.go(reviewLicence) + + expect(result).to.equal({ + billRunId: '287aeb25-cf11-429d-8c6f-f98f06db021d', + chargeVersions: [ + { + billingAccountDetails: { + billingAccountId: 'f041c128-bb4d-4f67-8f97-e33d71d50842', + accountNumber: 'E99999999A', + accountName: 'Mr B Blobby Ltd', + contactName: null, + addressLines: [ + 'C/O Noel Edmonds', + 'Crinkley Bottom', + 'Cricket St Thomas', + 'Somerset', + 'TA20 1KL', + 'United Kingdom' + ] + }, + chargePeriod: '1 April 2023 to 31 March 2024', + chargeReferences: [ + { + billableReturnsWarning: false, + chargeCategory: 'Charge reference 4.6.5', + chargeDescription: 'High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model', + id: '6c70461b-3f83-47b1-9538-8305e82b34eb', + chargeElements: [ + { + billableReturns: '0 ML / 9.092 ML', + chargePeriods: ['1 April 2023 to 30 September 2023'], + returnVolumes: ['0 ML (10030495)'], + description: 'Spray Irrigation - Direct', + elementCount: 1, + elementIndex: 1, + status: 'review', + id: 'a1840523-a04c-4c64-bff7-4a515e8ba1c1', + issues: ['Aggregate'], + purpose: 'Spray Irrigation - Direct' + } + ], + chargeReferenceLinkTitle: 'Change details', + totalBillableReturns: '0 ML / 9.092 ML' + } + ], + description: '1 charge reference with 1 two-part tariff charge element', + financialPeriod: '2023 to 2024' + } + ], + elementsInReview: true, + licenceHolder: 'Licence Holder Ltd', + licenceId: '32416c67-f755-4c3f-8816-ecde0ee596bd', + licenceRef: '1/11/11/*11/1111', + matchedReturns: [ + { + abstractionPeriod: '1 April to 30 September', + description: 'Test Road. Points 1 and 2.', + issues: [], + purpose: 'Spray Irrigation - Direct', + reference: '11142960', + returnId: 'v1:5:1/11/11/*11/1111:11142960:2022-11-01:2023-10-31', + returnLink: '/returns/return?id=v1:5:1/11/11/*11/1111:11142960:2022-11-01:2023-10-31', + returnPeriod: '1 November 2022 to 31 October 2023', + returnStatus: 'completed', + returnTotal: '0 ML / 0 ML' + } + ], + pageTitle: 'Licence 1/11/11/*11/1111', + progress: false, + region: 'South West', + reviewLicenceId: 'bb779166-0576-4581-b504-edbc0227d763', + status: 'review', + unmatchedReturns: [ + { + abstractionPeriod: '1 April to 30 September', + description: 'Lost Road. Points 1 and 2.', + issues: [], + purpose: 'Spray Irrigation - Storage', + reference: '11142961', + returnId: 'v1:5:1/11/11/*11/1111:11142961:2022-11-01:2023-10-31', + returnLink: '/returns/return?id=v1:5:1/11/11/*11/1111:11142961:2022-11-01:2023-10-31', + returnPeriod: '1 November 2022 to 31 October 2023', + returnStatus: 'completed', + returnTotal: '0 ML / 0 ML' + } + ] + }) + }) + + describe('the "chargeVersions" property', () => { + describe('the "chargeReferences" property', () => { + describe('the "chargeReferenceLinkTitle" property', () => { + describe('when a review charge reference has an aggregate (not equal to 1)', () => { + beforeEach(() => { + reviewLicence.reviewChargeVersions[0].reviewChargeReferences[0].aggregate = 0.5 + reviewLicence.reviewChargeVersions[0].reviewChargeReferences[0].chargeAdjustment = 1 + }) + + it('returns "Change details"', () => { + const result = ReviewLicencePresenter.go(reviewLicence) + + expect(result.chargeVersions[0].chargeReferences[0].chargeReferenceLinkTitle).to.equal('Change details') + }) + }) + + describe('when a review charge reference has a chargeAdjustment (not equal to 1)', () => { + beforeEach(() => { + reviewLicence.reviewChargeVersions[0].reviewChargeReferences[0].aggregate = 1 + reviewLicence.reviewChargeVersions[0].reviewChargeReferences[0].chargeAdjustment = 0.5 + }) + + it('returns "Change details"', () => { + const result = ReviewLicencePresenter.go(reviewLicence) + + expect(result.chargeVersions[0].chargeReferences[0].chargeReferenceLinkTitle).to.equal('Change details') + }) + }) + + describe('when a review charge reference neither an aggregate or charge adjustment (both equal 1)', () => { + beforeEach(() => { + reviewLicence.reviewChargeVersions[0].reviewChargeReferences[0].aggregate = 1 + reviewLicence.reviewChargeVersions[0].reviewChargeReferences[0].chargeAdjustment = 1 + }) + + it('returns "View details"', () => { + const result = ReviewLicencePresenter.go(reviewLicence) + + expect(result.chargeVersions[0].chargeReferences[0].chargeReferenceLinkTitle).to.equal('View details') + }) + }) + }) + + describe('the "billableReturnsWarning" property', () => { + describe("when the sum allocated to a charge reference's charge elements is less than its authorised volume", () => { + it('returns false', () => { + const result = ReviewLicencePresenter.go(reviewLicence) + + expect(result.chargeVersions[0].chargeReferences[0].billableReturnsWarning).to.equal(false) + }) + }) + + describe("when the sum allocated to a charge reference's charge elements equal to its authorised volume", () => { + beforeEach(() => { + reviewLicence + .reviewChargeVersions[0] + .reviewChargeReferences[0] + .reviewChargeElements[0] + .amendedAllocated = 9.092 + }) + + it('returns false', () => { + const result = ReviewLicencePresenter.go(reviewLicence) + + expect(result.chargeVersions[0].chargeReferences[0].billableReturnsWarning).to.equal(false) + }) + }) + + describe("when the sum allocated to a charge reference's charge elements is greater than its authorised volume", () => { + beforeEach(() => { + reviewLicence + .reviewChargeVersions[0] + .reviewChargeReferences[0] + .reviewChargeElements[0] + .amendedAllocated = 10 + }) + + it('returns true', () => { + const result = ReviewLicencePresenter.go(reviewLicence) + + expect(result.chargeVersions[0].chargeReferences[0].billableReturnsWarning).to.equal(true) + }) + }) + }) + }) + }) + }) +}) diff --git a/test/presenters/bill-runs/two-part-tariff/amend-adjustment-factor.presenter.test.js b/test/presenters/bill-runs/two-part-tariff/amend-adjustment-factor.presenter.test.js deleted file mode 100644 index 9e8824f4be..0000000000 --- a/test/presenters/bill-runs/two-part-tariff/amend-adjustment-factor.presenter.test.js +++ /dev/null @@ -1,167 +0,0 @@ -'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 AmendAdjustmentFactorPresenter = require('../../../../app/presenters/bill-runs/two-part-tariff/amend-adjustment-factor.presenter.js') - -describe('Amend Adjustment Factor presenter', () => { - const licenceId = '5aa8e752-1a5c-4b01-9112-d92a543b70d1' - let reviewChargeReference - let billRun - - describe('when there is data to be presented for the amend adjustment factor page', () => { - beforeEach(() => { - billRun = _billRun() - reviewChargeReference = _reviewChargeReference() - }) - - it('correctly presents the data', () => { - const result = AmendAdjustmentFactorPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result).to.equal({ - billRunId: '6620135b-0ecf-4fd4-924e-371f950c0526', - licenceId: '5aa8e752-1a5c-4b01-9112-d92a543b70d1', - financialYear: '2022 to 2023', - chargePeriod: '1 September 2022 to 31 March 2023', - chargeReference: { - id: 'e93fa3c6-195f-48eb-a03f-f87db7218f2d', - description: 'High loss, non-tidal, greater than 50 up to and including 85 ML/yr', - aggregateFactor: 1, - chargeAdjustment: 1, - otherAdjustments: [] - } - }) - }) - - describe('the "otherAdjustments" property', () => { - describe('when the charge reference has a abatement agreement', () => { - beforeEach(() => { - reviewChargeReference.abatementAgreement = 0.3 - }) - - it('adds the abatement agreement to the otherAdjustments property', () => { - const result = AmendAdjustmentFactorPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.chargeReference.otherAdjustments).to.equal(['Abatement agreement (0.3)']) - }) - }) - - describe('when the charge reference has a winter discount', () => { - beforeEach(() => { - reviewChargeReference.winterDiscount = true - }) - - it('adds the winter discount to the otherAdjustments property', () => { - const result = AmendAdjustmentFactorPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.chargeReference.otherAdjustments).to.equal(['Winter discount']) - }) - }) - - describe('when the charge reference has a two part tariff agreement', () => { - beforeEach(() => { - reviewChargeReference.twoPartTariffAgreement = true - }) - - it('adds the two part tariff agreement to the otherAdjustments property', () => { - const result = AmendAdjustmentFactorPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.chargeReference.otherAdjustments).to.equal(['Two part tariff agreement']) - }) - }) - - describe('when the charge reference has a canal and river trust agreement', () => { - beforeEach(() => { - reviewChargeReference.canalAndRiverTrustAgreement = true - }) - - it('adds the canal and river trust agreement to the otherAdjustments property', () => { - const result = AmendAdjustmentFactorPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.chargeReference.otherAdjustments).to.equal(['Canal and River trust agreement']) - }) - }) - - describe('when the charge reference has a supported source', () => { - beforeEach(() => { - reviewChargeReference.chargeReference.supportedSourceName = 'Thames' - }) - - it('adds the supported source to the otherAdjustments property', () => { - const result = AmendAdjustmentFactorPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.chargeReference.otherAdjustments).to.equal(['Supported source Thames']) - }) - }) - - describe('when the charge reference has a public water supply', () => { - beforeEach(() => { - reviewChargeReference.chargeReference.waterCompanyCharge = true - }) - - it('adds the public water supply to the otherAdjustments property', () => { - const result = AmendAdjustmentFactorPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.chargeReference.otherAdjustments).to.equal(['Public Water Supply']) - }) - }) - - describe('when the charge reference has no other adjustments', () => { - it('sets the otherAdjustments property as empty', () => { - const result = AmendAdjustmentFactorPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.chargeReference.otherAdjustments).to.equal([]) - }) - }) - }) - }) -}) - -function _billRun () { - return { - id: '6620135b-0ecf-4fd4-924e-371f950c0526', - fromFinancialYearEnding: 2023, - toFinancialYearEnding: 2023 - } -} - -function _reviewChargeReference () { - return { - id: 'e93fa3c6-195f-48eb-a03f-f87db7218f2d', - reviewChargeVersionId: 'ad4d0543-e129-429b-ab36-39d0804637b7', - chargeReferenceId: 'c61dfa06-e8e9-413e-93d8-0aedbf4d8638', - aggregate: 1.25, - createdAt: new Date('2024-05-02'), - updatedAt: new Date('2024-05-02'), - amendedAggregate: 1, - chargeAdjustment: 1, - amendedChargeAdjustment: 1, - abatementAgreement: 1, - winterDiscount: false, - twoPartTariffAgreement: false, - canalAndRiverTrustAgreement: false, - authorisedVolume: 60, - amendedAuthorisedVolume: 60, - reviewChargeVersion: { - chargePeriodStartDate: new Date('2022-09-01'), - chargePeriodEndDate: new Date('2023-03-31') - }, - chargeReference: { - volume: 60, - chargeCategoryId: '65e80b7f-13fb-4d27-b739-20f0adf36b54', - supportedSourceName: null, - waterCompanyCharge: null, - chargeCategory: { - reference: '4.6.13', - shortDescription: 'High loss, non-tidal, greater than 50 up to and including 85 ML/yr' - } - }, - reviewChargeElements: [] - } -} diff --git a/test/presenters/bill-runs/two-part-tariff/amend-authorised-volume.presenter.test.js b/test/presenters/bill-runs/two-part-tariff/amend-authorised-volume.presenter.test.js deleted file mode 100644 index 58bd524134..0000000000 --- a/test/presenters/bill-runs/two-part-tariff/amend-authorised-volume.presenter.test.js +++ /dev/null @@ -1,102 +0,0 @@ -'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 AmendAuthorisedVolumePresenter = require('../../../../app/presenters/bill-runs/two-part-tariff/amend-authorised-volume.presenter.js') - -describe('Amend Authorised Volume presenter', () => { - const licenceId = '5aa8e752-1a5c-4b01-9112-d92a543b70d1' - let reviewChargeReference - let billRun - - describe('when there is data to be presented for the amend authorised volume page', () => { - beforeEach(() => { - billRun = _billRun() - reviewChargeReference = _reviewChargeReference() - }) - - it('correctly presents the data', () => { - const result = AmendAuthorisedVolumePresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result).to.equal({ - billRunId: '6620135b-0ecf-4fd4-924e-371f950c0526', - licenceId: '5aa8e752-1a5c-4b01-9112-d92a543b70d1', - financialYear: '2022 to 2023', - chargePeriod: '1 September 2022 to 31 March 2023', - chargeReference: { - id: '6b3d11f2-d361-4eaa-bce2-5561283bd023', - description: 'Medium loss, non-tidal, greater than 83 up to and including 142 ML/yr', - authorisedVolume: 25.5, - totalBillableReturns: 15 - }, - chargeCategory: { - minVolume: 83, - maxVolume: 142 - } - }) - }) - - describe('the "totalBillableReturns" property', () => { - describe('when there are multiple reviewChargeElements', () => { - beforeEach(() => { - reviewChargeReference.reviewChargeElements.push({ - amendedAllocated: 17 - }) - }) - - it('sums the amendedAllocated volumes up', () => { - const result = AmendAuthorisedVolumePresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.chargeReference.totalBillableReturns).to.equal(32) - }) - }) - - describe('when there are no reviewChargeElements', () => { - beforeEach(() => { - reviewChargeReference.reviewChargeElements = [] - }) - - it('returns a total of 0', () => { - const result = AmendAuthorisedVolumePresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.chargeReference.totalBillableReturns).to.equal(0) - }) - }) - }) - }) -}) - -function _billRun () { - return { - id: '6620135b-0ecf-4fd4-924e-371f950c0526', - toFinancialYearEnding: 2023 - } -} - -function _reviewChargeReference () { - return { - id: '6b3d11f2-d361-4eaa-bce2-5561283bd023', - amendedAuthorisedVolume: 25.5, - chargeReference: { - chargeCategoryId: 'b4354db6-6699-4987-b4c8-d53ac2bf2250', - chargeCategory: { - shortDescription: 'Medium loss, non-tidal, greater than 83 up to and including 142 ML/yr', - minVolume: 83, - maxVolume: 142 - } - }, - reviewChargeElements: [{ - amendedAllocated: 15 - }], - reviewChargeVersion: { - chargePeriodStartDate: new Date('2022-09-01'), - chargePeriodEndDate: new Date('2023-03-31') - } - } -} diff --git a/test/presenters/bill-runs/two-part-tariff/amend-billable-returns.presenter.test.js b/test/presenters/bill-runs/two-part-tariff/amend-billable-returns.presenter.test.js deleted file mode 100644 index 17e354ff4c..0000000000 --- a/test/presenters/bill-runs/two-part-tariff/amend-billable-returns.presenter.test.js +++ /dev/null @@ -1,93 +0,0 @@ -'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 AmendBillableReturnsPresenter = require('../../../../app/presenters/bill-runs/two-part-tariff/amend-billable-returns.presenter.js') - -describe('Amend Billable Returns presenter', () => { - const licenceId = '5aa8e752-1a5c-4b01-9112-d92a543b70d1' - let reviewChargeElement - let billRun - - describe('when there is data to be presented for the amend billable returns page', () => { - beforeEach(() => { - billRun = _billRun() - reviewChargeElement = _reviewChargeElementData() - }) - - it('correctly presents the data', async () => { - const result = AmendBillableReturnsPresenter.go(billRun, reviewChargeElement, licenceId) - - expect(result).to.equal({ - chargeElement: { - description: 'Trickle Irrigation - Direct', - dates: ['1 April 2022 to 5 June 2022'], - reviewChargeElementId: 'b4d70c89-de1b-4f68-a47f-832b338ac044' - }, - billRun: { - id: '6620135b-0ecf-4fd4-924e-371f950c0526', - financialYear: '2022 to 2023' - }, - chargeVersion: { - chargePeriod: '1 April 2022 to 5 June 2022' - }, - licenceId: '5aa8e752-1a5c-4b01-9112-d92a543b70d1', - authorisedQuantity: 200 - }) - }) - - describe('when the charge reference has a lower authorised volume than the element', () => { - beforeEach(() => { - reviewChargeElement.reviewChargeReference.amendedAuthorisedVolume = 150 - }) - - it('displays the lower volume from the two', () => { - const result = AmendBillableReturnsPresenter.go(billRun, reviewChargeElement, licenceId) - - expect(result.authorisedQuantity).to.equal(150) - }) - }) - }) -}) - -function _billRun () { - return { - id: '6620135b-0ecf-4fd4-924e-371f950c0526', - fromFinancialYearEnding: 2023, - toFinancialYearEnding: 2023 - } -} - -function _reviewChargeElementData () { - return { - id: 'b4d70c89-de1b-4f68-a47f-832b338ac044', - reviewChargeReferenceId: '9e5d87d7-073e-420e-b12d-73ca220dd8ef', - chargeElementId: 'b345f1f1-496b-4049-a647-6bcd123dcf68', - allocated: 0, - status: 'ready', - createdAt: new Date('2024-04-02'), - updatedAt: new Date('2024-04-02'), - chargeElement: { - description: 'Trickle Irrigation - Direct', - abstractionPeriodStartDay: 1, - abstractionPeriodStartMonth: 4, - abstractionPeriodEndDay: 31, - abstractionPeriodEndMonth: 3, - authorisedAnnualQuantity: 200 - }, - reviewChargeReference: { - id: '9e5d87d7-073e-420e-b12d-73ca220dd8ef', - amendedAuthorisedVolume: 250, - reviewChargeVersion: { - chargePeriodStartDate: new Date('2022-04-01'), - chargePeriodEndDate: new Date('2022-06-05') - } - } - } -} diff --git a/test/presenters/bill-runs/two-part-tariff/charge-reference-details.presenter.test.js b/test/presenters/bill-runs/two-part-tariff/charge-reference-details.presenter.test.js deleted file mode 100644 index d125ee494d..0000000000 --- a/test/presenters/bill-runs/two-part-tariff/charge-reference-details.presenter.test.js +++ /dev/null @@ -1,276 +0,0 @@ -'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 ChargeReferenceDetailsPresenter = require('../../../../app/presenters/bill-runs/two-part-tariff/charge-reference-details.presenter.js') - -describe('Charge Reference Details presenter', () => { - describe('when there is data to be presented for the charge reference details page', () => { - const billRun = _billRun() - const licenceId = '5aa8e752-1a5c-4b01-9112-d92a543b70d1' - - let reviewChargeReference - - beforeEach(() => { - reviewChargeReference = _reviewChargeReferenceData() - }) - - it('correctly presents the data', async () => { - const result = ChargeReferenceDetailsPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result).to.equal({ - billRunId: '6620135b-0ecf-4fd4-924e-371f950c0526', - financialYear: '2022 to 2023', - chargePeriod: '1 April 2022 to 31 March 2023', - licenceId: '5aa8e752-1a5c-4b01-9112-d92a543b70d1', - chargeReference: { - id: '89eebffa-28a2-489d-b93a-c0f02a2bdbdd', - reference: '4.6.12', - description: 'High loss, non-tidal, restricted water, greater than 15 up to and including 50 ML/yr, Tier 2 model', - totalBillableReturns: 0.00018, - authorisedVolume: 32, - adjustments: [], - additionalCharges: '' - }, - hasAggregateOrChargeFactor: false - }) - }) - - describe('the "adjustments" property', () => { - describe('when the charge reference has an aggregate factor', () => { - beforeEach(() => { - reviewChargeReference.amendedAggregate = 0.5 - reviewChargeReference.aggregate = 0.5 - }) - - it('adds the aggregate factor to the adjustments property', () => { - const result = ChargeReferenceDetailsPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.chargeReference.adjustments).to.equal(['Aggregate factor (0.5)']) - }) - - it('sets the "hasAggregateOrChargeFactor" property to true', () => { - const result = ChargeReferenceDetailsPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.hasAggregateOrChargeFactor).to.equal(true) - }) - - describe('when the source aggregate factor is different to the amendedAggregate factor', () => { - beforeEach(() => { - reviewChargeReference.aggregate = 0.5 - reviewChargeReference.amendedAggregate = 1 - }) - - it('sets the "hasAggregateOrChargeFactor" property to true', () => { - const result = ChargeReferenceDetailsPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.hasAggregateOrChargeFactor).to.equal(true) - }) - }) - }) - - describe('when the charge reference has a charge adjustment factor', () => { - beforeEach(() => { - reviewChargeReference.amendedChargeAdjustment = 0.7 - reviewChargeReference.chargeAdjustment = 0.7 - }) - - it('adds the charge adjustment factor to the adjustments property', () => { - const result = ChargeReferenceDetailsPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.chargeReference.adjustments).to.equal(['Charge adjustment (0.7)']) - }) - - it('sets the "hasAggregateOrChargeFactor" property to true', () => { - const result = ChargeReferenceDetailsPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.hasAggregateOrChargeFactor).to.equal(true) - }) - - describe('when the source charge adjustment factor is different to the amendedChargeAdjustment factor', () => { - beforeEach(() => { - reviewChargeReference.chargeAdjustment = 0.5 - reviewChargeReference.amendedChargeAdjustment = 1 - }) - - it('sets the "hasAggregateOrChargeFactor" property to true', () => { - const result = ChargeReferenceDetailsPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.hasAggregateOrChargeFactor).to.equal(true) - }) - }) - }) - - describe('when the charge reference has a abatement agreement', () => { - beforeEach(() => { - reviewChargeReference.abatementAgreement = 0.3 - }) - - it('adds the abatement agreement to the adjustments property', () => { - const result = ChargeReferenceDetailsPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.chargeReference.adjustments).to.equal(['Abatement agreement (0.3)']) - }) - }) - - describe('when the charge reference has a winter discount', () => { - beforeEach(() => { - reviewChargeReference.winterDiscount = true - }) - - it('adds the winter discount to the adjustments property', () => { - const result = ChargeReferenceDetailsPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.chargeReference.adjustments).to.equal(['Winter discount']) - }) - }) - - describe('when the charge reference has a two part tariff agreement', () => { - beforeEach(() => { - reviewChargeReference.twoPartTariffAgreement = true - }) - - it('adds the two part tariff agreement to the adjustments property', () => { - const result = ChargeReferenceDetailsPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.chargeReference.adjustments).to.equal(['Two part tariff agreement']) - }) - }) - - describe('when the charge reference has a canal and river trust agreement', () => { - beforeEach(() => { - reviewChargeReference.canalAndRiverTrustAgreement = true - }) - - it('adds the canal and river trust agreement to the adjustments property', () => { - const result = ChargeReferenceDetailsPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.chargeReference.adjustments).to.equal(['Canal and River trust agreement']) - }) - }) - - describe('when the charge reference has no adjustments', () => { - it('sets the adjustments property as empty', () => { - const result = ChargeReferenceDetailsPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.chargeReference.adjustments).to.equal([]) - }) - - it('sets "hasAggregateOrChargeFactor" property to false', () => { - const result = ChargeReferenceDetailsPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.hasAggregateOrChargeFactor).to.equal(false) - }) - }) - }) - - describe('the "additionalCharges" property', () => { - describe('when the charge reference has a support source charge', () => { - beforeEach(() => { - reviewChargeReference.chargeReference.supportedSourceName = 'Thames' - }) - - it('adds the supported source charge to the "additionalCharges" property', () => { - const result = ChargeReferenceDetailsPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.chargeReference.additionalCharges).to.equal('Supported source Thames') - }) - }) - - describe('when the charge reference has a water company charge', () => { - beforeEach(() => { - reviewChargeReference.chargeReference.waterCompanyCharge = true - }) - - it('adds the water company charge to the "additionalCharges" property', () => { - const result = ChargeReferenceDetailsPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.chargeReference.additionalCharges).to.equal('Public Water Supply') - }) - - describe('and a support source charge', () => { - beforeEach(() => { - reviewChargeReference.chargeReference.supportedSourceName = 'Thames' - }) - - it('adds the both charges to the "additionalCharges" property', () => { - const result = ChargeReferenceDetailsPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.chargeReference.additionalCharges).to.equal('Supported source Thames, Public Water Supply') - }) - }) - }) - - describe('when the charge reference has no extra charges', () => { - it('sets the "additionalCharges" property to empty', () => { - const result = ChargeReferenceDetailsPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.chargeReference.additionalCharges).to.equal('') - }) - }) - }) - - describe('the "totalBillableReturns" property', () => { - describe('when there are multiple charge elements on the charge reference', () => { - beforeEach(() => { - reviewChargeReference.reviewChargeElements = [{ amendedAllocated: 1 }, { amendedAllocated: 2 }] - }) - - it('adds the "amendedAllocated" property on the charge elements', () => { - const result = ChargeReferenceDetailsPresenter.go(billRun, reviewChargeReference, licenceId) - - expect(result.chargeReference.totalBillableReturns).to.equal(3) - }) - }) - }) - }) -}) - -function _billRun () { - return { - id: '6620135b-0ecf-4fd4-924e-371f950c0526', - fromFinancialYearEnding: 2023, - toFinancialYearEnding: 2023 - } -} - -function _reviewChargeReferenceData () { - return { - id: '89eebffa-28a2-489d-b93a-c0f02a2bdbdd', - reviewChargeVersionId: '4940eaca-8cf1-410b-8a89-faf1faa8081b', - chargeReferenceId: '4e7f1824-3680-4df0-806f-c6d651ba4771', - aggregate: 1, - createdAt: new Date('2022-03-31'), - updatedAt: new Date('2024-05-01'), - amendedAggregate: 1, - chargeAdjustment: 1, - amendedChargeAdjustment: 1, - abatementAgreement: 1, - winterDiscount: false, - twoPartTariffAgreement: false, - canalAndRiverTrustAgreement: false, - authorisedVolume: 32, - amendedAuthorisedVolume: 32, - reviewChargeVersion: { - chargePeriodStartDate: new Date('2022-04-01'), - chargePeriodEndDate: new Date('2023-03-31') - }, - reviewChargeElements: [{ amendedAllocated: 0.00018 }], - chargeReference: { - volume: 32, - chargeCategoryId: 'c037ad9a-d3b4-4b1b-8ac9-1cd2b46d152f', - supportedSourceName: null, - waterCompanyCharge: null, - chargeCategory: { - reference: '4.6.12', - shortDescription: 'High loss, non-tidal, restricted water, greater than 15 up to and including 50 ML/yr, Tier 2 model' - } - } - } -} diff --git a/test/presenters/bill-runs/two-part-tariff/match-details.presenter.test.js b/test/presenters/bill-runs/two-part-tariff/match-details.presenter.test.js deleted file mode 100644 index bae41f8c6e..0000000000 --- a/test/presenters/bill-runs/two-part-tariff/match-details.presenter.test.js +++ /dev/null @@ -1,122 +0,0 @@ -'use strict' - -// Test framework dependencies -const Lab = require('@hapi/lab') -const Code = require('@hapi/code') - -const { describe, it } = exports.lab = Lab.script() -const { expect } = Code - -// Thing under test -const MatchDetailsPresenter = require('../../../../app/presenters/bill-runs/two-part-tariff/match-details.presenter.js') - -describe('Match Details presenter', () => { - describe('when there is data to be presented for the view match details page', () => { - const billRun = _billRun() - const reviewChargeElement = _reviewChargeElementData() - const licenceId = '5aa8e752-1a5c-4b01-9112-d92a543b70d1' - - it('correctly presents the data', async () => { - const result = MatchDetailsPresenter.go(billRun, reviewChargeElement, licenceId) - - expect(result).to.equal({ - billRunId: '6620135b-0ecf-4fd4-924e-371f950c0526', - financialYear: '2022 to 2023', - chargePeriod: '1 April 2022 to 5 June 2022', - licenceId: '5aa8e752-1a5c-4b01-9112-d92a543b70d1', - chargeElement: { - chargeElementId: 'b4d70c89-de1b-4f68-a47f-832b338ac044', - description: 'Trickle Irrigation - Direct', - dates: ['1 April 2022 to 5 June 2022'], - status: 'ready', - billableVolume: 0, - authorisedVolume: 200, - issues: [] - }, - matchedReturns: [ - { - returnId: 'v1:1:01/57/14/1646:15584914:2022-04-01:2023-03-31', - reference: '10031343', - dates: '1 April 2022 to 6 May 2022', - purpose: 'Spray Irrigation - Direct', - description: 'Lands at Mosshayne Farm, Exeter & Broadclyst', - returnStatus: 'completed', - returnTotal: '0 ML / 0 ML', - issues: [''], - returnLink: '/returns/return?id=v1:1:01/57/14/1646:15584914:2022-04-01:2023-03-31', - absPeriod: '1 April to 31 March' - } - ] - }) - }) - }) -}) - -function _billRun () { - return { - id: '6620135b-0ecf-4fd4-924e-371f950c0526', - fromFinancialYearEnding: 2023, - toFinancialYearEnding: 2023 - } -} - -function _reviewChargeElementData () { - return { - id: 'b4d70c89-de1b-4f68-a47f-832b338ac044', - reviewChargeReferenceId: '9e5d87d7-073e-420e-b12d-73ca220dd8ef', - chargeElementId: 'b345f1f1-496b-4049-a647-6bcd123dcf68', - allocated: 10, - amendedAllocated: 0, - chargeDatesOverlap: false, - issues: null, - status: 'ready', - createdAt: new Date('2024-04-02'), - updatedAt: new Date('2024-04-02'), - reviewReturns: [ - { - id: 'c4cdbfa9-4528-4776-b62f-fa667b797717', - reviewLicenceId: '674ffa02-51be-4caa-b25e-cc1fea1ac057', - returnId: 'v1:1:01/57/14/1646:15584914:2022-04-01:2023-03-31', - returnReference: '10031343', - quantity: 0, - allocated: 0, - underQuery: false, - returnStatus: 'completed', - nilReturn: false, - abstractionOutsidePeriod: false, - receivedDate: new Date('2022-06-03'), - dueDate: new Date('2022-06-03'), - purposes: [{ - tertiary: { code: '400', description: 'Spray Irrigation - Direct' } - }], - description: 'Lands at Mosshayne Farm, Exeter & Broadclyst', - startDate: new Date('2022-04-01'), - endDate: new Date('2022-05-06'), - issues: null, - createdAt: new Date('2024-04-02'), - updatedAt: new Date('2024-04-02'), - returnLog: { - periodEndDay: 31, - periodEndMonth: 3, - periodStartDay: 1, - periodStartMonth: 4 - } - } - ], - chargeElement: { - description: 'Trickle Irrigation - Direct', - abstractionPeriodStartDay: 1, - abstractionPeriodStartMonth: 4, - abstractionPeriodEndDay: 31, - abstractionPeriodEndMonth: 3, - authorisedAnnualQuantity: 200 - }, - reviewChargeReference: { - id: '9e5d87d7-073e-420e-b12d-73ca220dd8ef', - reviewChargeVersion: { - chargePeriodStartDate: new Date('2022-04-01'), - chargePeriodEndDate: new Date('2022-06-05') - } - } - } -} diff --git a/test/presenters/bill-runs/two-part-tariff/remove-bill-run-licence.presenter.test.js b/test/presenters/bill-runs/two-part-tariff/remove-bill-run-licence.presenter.test.js deleted file mode 100644 index 75b2e5ae82..0000000000 --- a/test/presenters/bill-runs/two-part-tariff/remove-bill-run-licence.presenter.test.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict' - -// Test framework dependencies -const Lab = require('@hapi/lab') -const Code = require('@hapi/code') - -const { describe, it } = exports.lab = Lab.script() -const { expect } = Code - -// Thing under test -const RemoveBillRunLicencePresenter = require('../../../../app/presenters/bill-runs/two-part-tariff/remove-bill-run-licence.presenter.js') - -describe('Cancel Bill Run presenter', () => { - describe('when provided with bill run and licence data', () => { - const billRun = { - billRunNumber: 12345, - createdAt: new Date('2024-05-03'), - region: 'Test Region', - status: 'review', - toFinancialYearEnding: 2023 - } - const licenceId = '85a8e2d7-b73f-45a1-b5fd-ba5632b43442' - const licenceRef = '01/123/ABC' - - it('correctly presents the data', () => { - const result = RemoveBillRunLicencePresenter.go(billRun, licenceId, licenceRef) - - expect(result).to.equal({ - pageTitle: "You're about to remove 01/123/ABC from the bill run", - backLink: `../review/${licenceId}`, - billRunNumber: billRun.billRunNumber, - billRunStatus: billRun.status, - dateCreated: '3 May 2024', - financialYear: '2022 to 2023', - region: billRun.region - }) - }) - }) -}) diff --git a/test/presenters/bill-runs/two-part-tariff/review-licence.presenter.test.js b/test/presenters/bill-runs/two-part-tariff/review-licence.presenter.test.js deleted file mode 100644 index 87fef7ee9c..0000000000 --- a/test/presenters/bill-runs/two-part-tariff/review-licence.presenter.test.js +++ /dev/null @@ -1,467 +0,0 @@ -'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 BillingAccountModel = require('../../../../app/models/billing-account.model.js') - -// Thing under test -const ReviewLicencePresenter = require('../../../../app/presenters/bill-runs/two-part-tariff/review-licence.presenter.js') - -describe('Review Licence presenter', () => { - let billRun - let licence - - describe('when there is data to be presented for the review licence page', () => { - beforeEach(() => { - billRun = _billRun() - licence = _licenceData() - }) - - it('correctly presents the data', async () => { - const result = ReviewLicencePresenter.go(billRun, licence) - - expect(result).to.equal({ - billRunId: '6620135b-0ecf-4fd4-924e-371f950c0526', - region: 'Anglian', - licence: { - licenceId: '786f0d83-eaf7-43c3-9de5-ec59e3de05ee', - licenceRef: '01/49/80/4608', - progress: false, - status: 'ready', - licenceHolder: 'Licence Holder Ltd' - }, - elementsInReview: false, - matchedReturns: [ - { - returnId: 'v1:1:01/60/28/3437:17061181:2022-04-01:2023-03-31', - reference: '10031343', - dates: '1 April 2022 to 6 May 2022', - returnStatus: 'completed', - description: 'Lands at Mosshayne Farm, Exeter & Broadclyst', - purpose: 'Site description', - returnTotal: '0 ML / 0 ML', - issues: [''], - returnLink: '/returns/return?id=v1:1:01/60/28/3437:17061181:2022-04-01:2023-03-31', - absPeriod: '1 April to 31 March' - } - ], - unmatchedReturns: [ - { - dates: '1 April 2022 to 6 May 2022', - description: 'Lands at Mosshayne Farm, Exeter & Broadclyst', - issues: [''], - purpose: 'Site description', - reference: '10031343', - returnId: 'v2:1:01/60/28/3437:17061181:2022-04-01:2023-03-31', - returnLink: '/returns/return?id=v2:1:01/60/28/3437:17061181:2022-04-01:2023-03-31', - returnStatus: 'completed', - returnTotal: '0 / 0 ML', - absPeriod: '1 April to 31 March' - } - ], - chargeData: [ - { - financialYear: '2022 to 2023', - chargePeriodDate: '1 April 2022 to 5 June 2022', - chargeElementCount: 1, - billingAccountDetails: { - billingAccountId: 'a17ae69b-8074-4d27-80bf-074f4c79a05a', - accountNumber: 'E88896464A', - accountName: 'Furland Farm', - contactName: null, - addressLines: [ - 'Furland Farm', - 'Furland', - 'Crewkerne', - 'Somerset', - 'TA18 7TT', - 'England' - ] - }, - chargeReferences: [ - { - chargeCategory: 'Charge reference 4.6.7', - chargeDescription: 'High loss, non-tidal, greater than 15 up to and including 50 ML/yr', - totalBillableReturns: '0 ML / 200 ML', - billableReturnsWarning: false, - id: 'b2af5935-4b65-4dce-9f75-9073798f6375', - chargeReferenceLink: { linkName: 'View details' }, - chargeElements: [ - { - elementNumber: 'Element 1 of 1', - elementStatus: 'ready', - elementDescription: 'Trickle Irrigation - Direct', - dates: ['1 April 2022 to 5 June 2022'], - purpose: 'Make-up or top up water', - issues: [''], - billableReturns: '0 ML / 200 ML', - returnVolume: ['0 ML (10031343)'], - reviewChargeElementId: '8bc0cd32-400e-4a45-9dd7-fbce3d486031' - } - ] - } - ] - } - ] - }) - }) - - describe('the "issues" property', () => { - describe('when the charge element has multiple issues', () => { - beforeEach(() => { - licence[0].reviewChargeVersions[0].reviewChargeReferences[0].reviewChargeElements[0].issues = 'Over abstraction, no returns' - }) - - it('correctly splits the issues into an array', () => { - const result = ReviewLicencePresenter.go(billRun, licence) - - expect(result.chargeData[0].chargeReferences[0].chargeElements[0].issues).to.equal(['Over abstraction', 'no returns']) - }) - }) - - describe('when the matched returns has multiple issues', () => { - beforeEach(() => { - licence[0].reviewReturns[0].issues = 'Over abstraction, no returns' - }) - - it('correctly splits the issues into an array', () => { - const result = ReviewLicencePresenter.go(billRun, licence) - - expect(result.matchedReturns[0].issues).to.equal(['Over abstraction', 'no returns']) - }) - }) - - describe('when the unmatched returns has multiple issues', () => { - beforeEach(() => { - licence[0].reviewReturns[1].issues = 'Over abstraction, no returns' - }) - - it('correctly splits the issues into an array', () => { - const result = ReviewLicencePresenter.go(billRun, licence) - - expect(result.unmatchedReturns[0].issues).to.equal(['Over abstraction', 'no returns']) - }) - }) - }) - - describe('the "returnStatus" property', () => { - describe('when a return has a status of "due"', () => { - beforeEach(() => { - licence[0].reviewReturns[0].returnStatus = 'due' - licence[0].reviewChargeVersions[0].reviewChargeReferences[0].reviewChargeElements[0].reviewReturns[0].returnStatus = 'due' - }) - - it('changes the status text to "overdue"', () => { - const result = ReviewLicencePresenter.go(billRun, licence) - - expect(result.matchedReturns[0].returnStatus).to.equal('overdue') - }) - - it('formats the returns total correctly', () => { - const result = ReviewLicencePresenter.go(billRun, licence) - - expect(result.matchedReturns[0].returnTotal).to.equal('/') - }) - - it('formats the charge elements return total correctly', () => { - const result = ReviewLicencePresenter.go(billRun, licence) - - expect(result.chargeData[0].chargeReferences[0].chargeElements[0].returnVolume).to.equal(['overdue (10031343)']) - }) - - it('formats the returns link correctly', () => { - const result = ReviewLicencePresenter.go(billRun, licence) - - expect(result.matchedReturns[0].returnLink).to.equal('/return/internal?returnId=v1:1:01/60/28/3437:17061181:2022-04-01:2023-03-31') - }) - }) - }) - - describe('the "underQuery" property', () => { - describe('when a return is under query', () => { - beforeEach(() => { - licence[0].reviewReturns[0].underQuery = true - }) - - it('changes the returns status to be "under query"', () => { - const result = ReviewLicencePresenter.go(billRun, licence) - - expect(result.matchedReturns[0].returnStatus).to.equal('query') - }) - }) - }) - - describe('the "adjustment" properties', () => { - describe('when a review charge reference has an aggregate', () => { - beforeEach(() => { - licence[0].reviewChargeVersions[0].reviewChargeReferences[0].aggregate = 0.5 - }) - - it('changes the chargeReferenceLink to "Change details"', () => { - const result = ReviewLicencePresenter.go(billRun, licence) - - expect(result.chargeData[0].chargeReferences[0].chargeReferenceLink.linkName).to.equal('Change details') - }) - }) - - describe('when a review charge reference has a charge factor adjustment', () => { - beforeEach(() => { - licence[0].reviewChargeVersions[0].reviewChargeReferences[0].chargeAdjustment = 0.5 - }) - - it('changes the chargeReferenceLink to "Change details"', () => { - const result = ReviewLicencePresenter.go(billRun, licence) - - expect(result.chargeData[0].chargeReferences[0].chargeReferenceLink.linkName).to.equal('Change details') - }) - }) - - describe('when a review charge reference has an aggregate and charge factor adjustment', () => { - beforeEach(() => { - licence[0].reviewChargeVersions[0].reviewChargeReferences[0].aggregate = 0.5 - licence[0].reviewChargeVersions[0].reviewChargeReferences[0].chargeAdjustment = 0.5 - }) - - it('changes the chargeReferenceLink to "Change details"', () => { - const result = ReviewLicencePresenter.go(billRun, licence) - - expect(result.chargeData[0].chargeReferences[0].chargeReferenceLink.linkName).to.equal('Change details') - }) - }) - - describe('when a review charge reference does not have an aggregate or charge factor adjustment', () => { - it('changes the chargeReferenceLink to "View details"', () => { - const result = ReviewLicencePresenter.go(billRun, licence) - - expect(result.chargeData[0].chargeReferences[0].chargeReferenceLink.linkName).to.equal('View details') - }) - }) - }) - - describe('the "billableReturnsWarning" property', () => { - describe('when a review charge reference has an authorised volume greater than the sum of its charge element quantities', () => { - it('sets the "billableReturnsWarning" to false', () => { - const result = ReviewLicencePresenter.go(billRun, licence) - - expect(result.chargeData[0].chargeReferences[0].billableReturnsWarning).to.equal(false) - }) - }) - - describe('when a review charge reference has an authorised volume less than the sum of its charge element quantities', () => { - beforeEach(() => { - licence[0].reviewChargeVersions[0].reviewChargeReferences[0].amendedAuthorisedVolume = 10 - licence[0].reviewChargeVersions[0].reviewChargeReferences[0].reviewChargeElements[0].amendedAllocated = 50 - }) - - it('sets the "billableReturnsWarning" to true', () => { - const result = ReviewLicencePresenter.go(billRun, licence) - - expect(result.chargeData[0].chargeReferences[0].billableReturnsWarning).to.equal(true) - }) - }) - }) - }) -}) - -function _billRun () { - return { - id: '6620135b-0ecf-4fd4-924e-371f950c0526', - fromFinancialYearEnding: 2023, - toFinancialYearEnding: 2023, - region: { - displayName: 'Anglian' - } - } -} - -function _licenceData () { - const billingAccountDetails = BillingAccountModel.fromJson({ - id: 'a17ae69b-8074-4d27-80bf-074f4c79a05a', - accountNumber: 'E88896464A', - company: { - id: 'e44491db-2b33-4473-9c3a-b57aceabb6e8', - name: 'Furland Farm', - type: 'organisation' - }, - billingAccountAddresses: [ - { - id: 'eb5cb54a-0b51-4e4a-8472-dab993eb6157', - billingAccountId: 'a17ae69b-8074-4d27-80bf-074f4c79a05a', - addressId: 'cc32fefd-7f3e-4581-b437-78a3fae66d4b', - startDate: new Date('2016-05-20'), - endDate: null, - companyId: null, - contactId: null, - company: null, - contact: null, - address: { - id: 'cc32fefd-7f3e-4581-b437-78a3fae66d4b', - address1: 'Furland Farm', - address2: 'Furland', - address3: null, - address4: null, - address5: 'Crewkerne', - address6: 'Somerset', - postcode: 'TA18 7TT', - country: 'England' - } - } - ] - }) - - return [{ - id: '5aa8e752-1a5c-4b01-9112-d92a543b70d1', - billRunId: '82772a06-c8ce-45f7-8504-dd20ea8824e4', - licenceId: '786f0d83-eaf7-43c3-9de5-ec59e3de05ee', - licenceRef: '01/49/80/4608', - licenceHolder: 'Licence Holder Ltd', - issues: '', - status: 'ready', - progress: false, - hasReviewStatus: false, - reviewReturns: [{ - id: '2264f443-5c16-4ca9-8522-f63e2d4e38be', - reviewLicenceId: '78a99c1c-26d3-4163-ab58-084cd78594ab', - returnId: 'v1:1:01/60/28/3437:17061181:2022-04-01:2023-03-31', - returnReference: '10031343', - quantity: 0, - allocated: 0, - underQuery: false, - returnStatus: 'completed', - nilReturn: false, - abstractionOutsidePeriod: false, - receivedDate: new Date('2022-06-03'), - dueDate: new Date('2022-06-03'), - purposes: [{ - tertiary: { - description: 'Site description' - } - }], - description: 'Lands at Mosshayne Farm, Exeter & Broadclyst', - startDate: new Date(' 2022-04-01'), - endDate: new Date('2022-05-06'), - issues: '', - reviewChargeElements: [{ - id: 'e840f418-ca6b-4d96-9f36-bf684c78590f', - reviewChargeReferenceId: '7759e0f9-5763-4b94-8d45-0621aea3edc1', - chargeElementId: 'b1cd4f98-ad96-4901-9e21-4432f032492a', - allocated: 0, - chargeDatesOverlap: false, - issues: '', - status: 'ready' - }], - returnLog: { - periodStartDay: 1, - periodStartMonth: 4, - periodEndDay: 31, - periodEndMonth: 3 - } - }, - { - id: '4864f643-5c16-5ca9-8512-f63e1d4e58be', - reviewLicenceId: '78a99c1c-26d3-4163-ab58-084cd78594ab', - returnId: 'v2:1:01/60/28/3437:17061181:2022-04-01:2023-03-31', - returnReference: '10031343', - quantity: 0, - allocated: 0, - underQuery: false, - returnStatus: 'completed', - nilReturn: false, - abstractionOutsidePeriod: false, - receivedDate: new Date('2022-06-03'), - dueDate: new Date('2022-06-03'), - purposes: [{ - tertiary: { - description: 'Site description' - } - }], - description: 'Lands at Mosshayne Farm, Exeter & Broadclyst', - startDate: new Date(' 2022-04-01'), - endDate: new Date('2022-05-06'), - issues: '', - reviewChargeElements: [], - returnLog: { - periodStartDay: 1, - periodStartMonth: 4, - periodEndDay: 31, - periodEndMonth: 3 - } - }], - reviewChargeVersions: [{ - id: '3de5634a-da26-4241-87e9-7248a4b83a69', - reviewLicenceId: 'd9e78306-bf65-4020-b279-5ae471cea4e6', - chargeVersionId: 'd103bb54-1819-4e77-b3d9-bc8913454e06', - changeReason: 'Strategic review of charges (SRoC)', - chargePeriodStartDate: new Date('2022-04-01'), - chargePeriodEndDate: new Date('2022-06-05'), - reviewChargeReferences: [{ - id: 'b2af5935-4b65-4dce-9f75-9073798f6375', - reviewChargeVersionId: 'bd16e7b0-c2a3-4258-b873-b965fd74cdf5', - chargeReferenceId: '82ce8695-5841-41b0-a1e7-d016407adad4', - aggregate: 1, - authorisedVolume: 200, - amendedAuthorisedVolume: 200, - chargeAdjustment: 1, - createdAt: new Date('2024-03-18'), - updatedAt: new Date('2024-03-18'), - chargeReference: { - chargeCategoryId: 'f100dc23-c6a7-4efa-af4f-80618260b32e', - chargeCategory: { - reference: '4.6.7', - shortDescription: 'High loss, non-tidal, greater than 15 up to and including 50 ML/yr' - } - }, - reviewChargeElements: [{ - id: '8bc0cd32-400e-4a45-9dd7-fbce3d486031', - reviewChargeReferenceId: '2210bb45-1efc-4e69-85cb-c8cc6e75c4fd', - chargeElementId: 'b1001716-cfb4-43c6-91f0-1863f4529223', - allocated: 10, - amendedAllocated: 0, - chargeDatesOverlap: false, - issues: '', - status: 'ready', - chargeElement: { - description: 'Trickle Irrigation - Direct', - abstractionPeriodStartDay: 1, - abstractionPeriodStartMonth: 4, - abstractionPeriodEndDay: 31, - abstractionPeriodEndMonth: 3, - authorisedAnnualQuantity: 200, - purpose: { - description: 'Make-up or top up water' - } - }, - reviewReturns: [{ - id: '2264f443-5c16-4ca9-8522-f63e2d4e38be', - reviewLicenceId: '78a99c1c-26d3-4163-ab58-084cd78594ab', - returnId: 'v1:1:01/60/28/3437:17061181:2022-04-01:2023-03-31', - returnReference: '10031343', - quantity: 0, - allocated: 0, - underQuery: false, - returnStatus: 'completed', - nilReturn: false, - abstractionOutsidePeriod: false, - receivedDate: new Date('2022-06-03'), - dueDate: new Date('2022-06-03'), - purposes: {}, - description: 'Lands at Mosshayne Farm, Exeter & Broadclyst', - startDate: new Date(' 2022-04-01'), - endDate: new Date('2022-05-06'), - issues: '' - }] - }] - }], - chargeVersion: { - billingAccountId: '67d7cacb-5d10-4a08-b7f8-e6ce98cbf4c8' - }, - billingAccountDetails - }] - }] -} diff --git a/test/services/bill-runs/review/authorised.service.test.js b/test/services/bill-runs/review/authorised.service.test.js new file mode 100644 index 0000000000..8e02639e90 --- /dev/null +++ b/test/services/bill-runs/review/authorised.service.test.js @@ -0,0 +1,48 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const BillRunsReviewFixture = require('../../../fixtures/bill-runs-review.fixture.js') + +// Things we need to stub +const FetchReviewChargeReferenceService = require('../../../../app/services/bill-runs/review/fetch-review-charge-reference.service.js') + +// Thing under test +const AuthorisedService = require('../../../../app/services/bill-runs/review/authorised.service.js') + +describe('Bill Runs Review - Authorised Service', () => { + let reviewChargeReference + + beforeEach(() => { + reviewChargeReference = BillRunsReviewFixture.reviewChargeReference() + + Sinon.stub(FetchReviewChargeReferenceService, 'go').resolves(reviewChargeReference) + }) + + afterEach(() => { + Sinon.restore() + }) + + describe('when called', () => { + it('returns page data for the view', async () => { + const result = await AuthorisedService.go(reviewChargeReference.id) + + expect(result).to.equal({ + pageTitle: 'Set the authorised volume', + amendedAuthorisedVolume: 9.092, + chargeDescription: 'High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model', + chargePeriod: '1 April 2023 to 31 March 2024', + financialPeriod: '2023 to 2024', + reviewChargeReferenceId: '6b3d11f2-d361-4eaa-bce2-5561283bd023', + totalBillableReturns: 0 + }) + }) + }) +}) diff --git a/test/services/bill-runs/review/edit.service.test.js b/test/services/bill-runs/review/edit.service.test.js new file mode 100644 index 0000000000..91f7f8f98d --- /dev/null +++ b/test/services/bill-runs/review/edit.service.test.js @@ -0,0 +1,52 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const BillRunsReviewFixture = require('../../../fixtures/bill-runs-review.fixture.js') + +// Things we need to stub +const FetchReviewChargeElementService = require('../../../../app/services/bill-runs/review/fetch-review-charge-element.service.js') + +// Thing under test +const EditService = require('../../../../app/services/bill-runs/review/edit.service.js') + +describe('Bill Runs Review - Edit Service', () => { + const elementIndex = 1 + + let reviewChargeElement + + beforeEach(() => { + reviewChargeElement = BillRunsReviewFixture.reviewChargeElement() + + Sinon.stub(FetchReviewChargeElementService, 'go').resolves(reviewChargeElement) + }) + + afterEach(() => { + Sinon.restore() + }) + + describe('when called', () => { + it('returns page data for the view', async () => { + const result = await EditService.go(reviewChargeElement.id, elementIndex) + + expect(result).to.equal({ + pageTitle: 'Set the billable returns quantity for this bill run', + authorisedQuantity: 9.092, + billableReturns: 0, + chargeDescription: 'Spray Irrigation - Direct', + chargePeriod: '1 April 2023 to 31 March 2024', + chargePeriods: ['1 April 2023 to 30 September 2023'], + elementIndex: 1, + financialPeriod: '2023 to 2024', + reviewChargeElementId: 'a1840523-a04c-4c64-bff7-4a515e8ba1c1' + }) + }) + }) +}) diff --git a/test/services/bill-runs/review/factors.service.test.js b/test/services/bill-runs/review/factors.service.test.js new file mode 100644 index 0000000000..6d611995f6 --- /dev/null +++ b/test/services/bill-runs/review/factors.service.test.js @@ -0,0 +1,49 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const BillRunsReviewFixture = require('../../../fixtures/bill-runs-review.fixture.js') + +// Things we need to stub +const FetchReviewChargeReferenceService = require('../../../../app/services/bill-runs/review/fetch-review-charge-reference.service.js') + +// Thing under test +const FactorsService = require('../../../../app/services/bill-runs/review/factors.service.js') + +describe('Bill Runs Review - Factors Service', () => { + let reviewChargeReference + + beforeEach(() => { + reviewChargeReference = BillRunsReviewFixture.reviewChargeReference() + + Sinon.stub(FetchReviewChargeReferenceService, 'go').resolves(reviewChargeReference) + }) + + afterEach(() => { + Sinon.restore() + }) + + describe('when called', () => { + it('returns page data for the view', async () => { + const result = await FactorsService.go(reviewChargeReference.id) + + expect(result).to.equal({ + pageTitle: 'Set the adjustment factors', + amendedAggregate: 0.333333333, + amendedChargeAdjustment: 1, + chargeDescription: 'High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model', + chargePeriod: '1 April 2023 to 31 March 2024', + financialPeriod: '2023 to 2024', + otherAdjustments: ['Two part tariff agreement'], + reviewChargeReferenceId: '6b3d11f2-d361-4eaa-bce2-5561283bd023' + }) + }) + }) +}) diff --git a/test/services/bill-runs/two-part-tariff/fetch-bill-run-licences.service.test.js b/test/services/bill-runs/review/fetch-bill-run-licences.service.test.js similarity index 99% rename from test/services/bill-runs/two-part-tariff/fetch-bill-run-licences.service.test.js rename to test/services/bill-runs/review/fetch-bill-run-licences.service.test.js index 60ae9ba9bc..4e07006003 100644 --- a/test/services/bill-runs/two-part-tariff/fetch-bill-run-licences.service.test.js +++ b/test/services/bill-runs/review/fetch-bill-run-licences.service.test.js @@ -15,9 +15,9 @@ const RegionHelper = require('../../../support/helpers/region.helper.js') const ReviewLicenceHelper = require('../../../support/helpers/review-licence.helper.js') // Thing under test -const FetchBillRunLicencesService = require('../../../../app/services/bill-runs/two-part-tariff/fetch-bill-run-licences.service.js') +const FetchBillRunLicencesService = require('../../../../app/services/bill-runs/review/fetch-bill-run-licences.service.js') -describe('Fetch Bill Run Licences service', () => { +describe('Bill Runs Review - Fetch Bill Run Licences service', () => { let filterIssues let filterLicenceHolderNumber let filterLicenceStatus diff --git a/test/services/bill-runs/review/fetch-remove-review-licence.service.test.js b/test/services/bill-runs/review/fetch-remove-review-licence.service.test.js new file mode 100644 index 0000000000..387fbb15ef --- /dev/null +++ b/test/services/bill-runs/review/fetch-remove-review-licence.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, before } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const BillRunHelper = require('../../../support/helpers/bill-run.helper.js') +const RegionHelper = require('../../../support/helpers/region.helper.js') +const ReviewLicenceHelper = require('../../../support/helpers/review-licence.helper.js') + +// Thing under test +const FetchRemoveReviewLicenceService = require('../../../../app/services/bill-runs/review/fetch-remove-review-licence.service.js') + +describe('Bill Runs Review - Fetch Remove Review Licence service', () => { + let billRun + let region + let reviewLicence + + before(async () => { + region = RegionHelper.select() + + billRun = await BillRunHelper.add({ batchType: 'two_part_tariff', regionId: region.id, status: 'review' }) + + reviewLicence = await ReviewLicenceHelper.add({ billRunId: billRun.id }) + }) + + describe('when a matching review licence exists', () => { + it('returns the match', async () => { + const result = await FetchRemoveReviewLicenceService.go(reviewLicence.id) + + expect(result).to.equal({ + id: reviewLicence.id, + licenceId: reviewLicence.licenceId, + licenceRef: reviewLicence.licenceRef, + billRun: { + id: billRun.id, + billRunNumber: billRun.billRunNumber, + createdAt: billRun.createdAt, + status: 'review', + toFinancialYearEnding: billRun.toFinancialYearEnding, + region: { + id: region.id, + displayName: region.displayName + } + } + }) + }) + }) + + describe('when no matching review licence exists', () => { + it('returns nothing', async () => { + const result = await FetchRemoveReviewLicenceService.go('dfa47d48-0c98-4707-a5b8-820eb16c1dfd') + + expect(result).to.be.undefined() + }) + }) +}) diff --git a/test/services/bill-runs/review/fetch-review-charge-element.service.test.js b/test/services/bill-runs/review/fetch-review-charge-element.service.test.js new file mode 100644 index 0000000000..61f0a0f795 --- /dev/null +++ b/test/services/bill-runs/review/fetch-review-charge-element.service.test.js @@ -0,0 +1,146 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') + +const { describe, it, before } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const BillRunHelper = require('../../../support/helpers/bill-run.helper.js') +const ChargeElementHelper = require('../../../support/helpers/charge-element.helper.js') +const RegionHelper = require('../../../support/helpers/region.helper.js') +const ReturnLogHelper = require('../../../support/helpers/return-log.helper.js') +const ReviewChargeElementHelper = require('../../../support/helpers/review-charge-element.helper.js') +const ReviewChargeElementReturnHelper = require('../../../support/helpers/review-charge-element-return.helper.js') +const ReviewChargeReferenceHelper = require('../../../support/helpers/review-charge-reference.helper.js') +const ReviewChargeVersionHelper = require('../../../support/helpers/review-charge-version.helper.js') +const ReviewLicenceHelper = require('../../../support/helpers/review-licence.helper.js') +const ReviewReturnHelper = require('../../../support/helpers/review-return.helper.js') + +// Thing under test +const FetchReviewChargeElementService = require('../../../../app/services/bill-runs/review/fetch-review-charge-element.service.js') + +describe('Bill Runs Review - Fetch Review Charge Element service', () => { + let billRun + let chargeElement + let returnLog + let reviewChargeElement + let reviewChargeVersion + let reviewChargeReference + let reviewLicence + let reviewReturn + + before(async () => { + const region = RegionHelper.select() + + billRun = await BillRunHelper.add({ batchType: 'two_part_tariff', regionId: region.id, status: 'review' }) + + returnLog = await ReturnLogHelper.add() + chargeElement = await ChargeElementHelper.add() + + reviewLicence = await ReviewLicenceHelper.add({ billRunId: billRun.id }) + reviewReturn = await ReviewReturnHelper.add({ + purposes: [{ + primary: { code: 'A', description: 'Agriculture' }, + tertiary: { code: '400', description: 'Spray Irrigation - Direct' }, + secondary: { code: 'AGR', description: 'General Agriculture' } + }], + returnId: returnLog.id, + returnReference: returnLog.returnReference, + reviewLicenceId: reviewLicence.id + }) + + reviewChargeVersion = await ReviewChargeVersionHelper.add({ reviewLicenceId: reviewLicence.id }) + reviewChargeReference = await ReviewChargeReferenceHelper.add({ reviewChargeVersionId: reviewChargeVersion.id }) + reviewChargeElement = await ReviewChargeElementHelper.add({ + chargeElementId: chargeElement.id, reviewChargeReferenceId: reviewChargeReference.id + }) + + await ReviewChargeElementReturnHelper.add({ + reviewChargeElementId: reviewChargeElement.id, reviewReturnId: reviewReturn.id + }) + }) + + describe('when a matching review charge element exists', () => { + it('returns the match', async () => { + const result = await FetchReviewChargeElementService.go(reviewChargeElement.id) + + expect(result).to.equal({ + id: reviewChargeElement.id, + amendedAllocated: 0, + issues: '', + status: 'ready', + chargeElement: { + id: chargeElement.id, + abstractionPeriodStartDay: 1, + abstractionPeriodStartMonth: 4, + abstractionPeriodEndDay: 31, + abstractionPeriodEndMonth: 3, + authorisedAnnualQuantity: 200, + description: 'Trickle Irrigation - Direct' + }, + reviewChargeReference: { + id: reviewChargeReference.id, + amendedAuthorisedVolume: 50, + reviewChargeElements: [ + { + id: reviewChargeElement.id + } + ], + reviewChargeVersion: { + id: reviewChargeVersion.id, + chargePeriodStartDate: new Date('2022-04-01'), + chargePeriodEndDate: new Date('2022-06-05'), + reviewLicence: { + id: reviewLicence.id, + licenceId: reviewLicence.licenceId, + billRun: { + id: billRun.id, + toFinancialYearEnding: 2023 + } + } + } + }, + reviewReturns: [ + { + id: reviewReturn.id, + allocated: 0, + description: 'Lands at Mosshayne Farm, Exeter & Broadclyst', + endDate: new Date('2022-05-06'), + issues: '', + quantity: 0, + purposes: [ + { + primary: { code: 'A', description: 'Agriculture' }, + tertiary: { code: '400', description: 'Spray Irrigation - Direct' }, + secondary: { code: 'AGR', description: 'General Agriculture' } + } + ], + returnId: reviewReturn.returnId, + returnReference: reviewReturn.returnReference, + returnStatus: 'completed', + startDate: new Date('2022-04-01'), + underQuery: false, + returnLog: { + id: returnLog.id, + periodStartDay: 1, + periodStartMonth: 4, + periodEndDay: 28, + periodEndMonth: 4 + } + } + ] + }) + }) + }) + + describe('when no matching review charge element exists', () => { + it('returns nothing', async () => { + const result = await FetchReviewChargeElementService.go('dfa47d48-0c98-4707-a5b8-820eb16c1dfd') + + expect(result).to.be.undefined() + }) + }) +}) diff --git a/test/services/bill-runs/review/fetch-review-charge-reference.test.js b/test/services/bill-runs/review/fetch-review-charge-reference.test.js new file mode 100644 index 0000000000..fe623d9f79 --- /dev/null +++ b/test/services/bill-runs/review/fetch-review-charge-reference.test.js @@ -0,0 +1,123 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') + +const { describe, it, before } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const BillRunHelper = require('../../../support/helpers/bill-run.helper.js') +const ChargeCategoryHelper = require('../../../support/helpers/charge-category.helper.js') +const ChargeElementHelper = require('../../../support/helpers/charge-element.helper.js') +const ChargeReferenceHelper = require('../../../support/helpers/charge-reference.helper.js') +const LicenceHelper = require('../../../support/helpers/licence.helper.js') +const RegionHelper = require('../../../support/helpers/region.helper.js') +const ReviewChargeElementHelper = require('../../../support/helpers/review-charge-element.helper.js') +const ReviewChargeReferenceHelper = require('../../../support/helpers/review-charge-reference.helper.js') +const ReviewChargeVersionHelper = require('../../../support/helpers/review-charge-version.helper.js') +const ReviewLicenceHelper = require('../../../support/helpers/review-licence.helper.js') + +// Thing under test +const FetchReviewChargeReferenceService = require('../../../../app/services/bill-runs/review/fetch-review-charge-reference.service.js') + +describe('Bill Runs Review - Fetch Review Charge Reference service', () => { + let billRun + let chargeCategory + let chargeElement + let chargeReference + let licence + let reviewChargeElement + let reviewChargeVersion + let reviewChargeReference + let reviewLicence + + before(async () => { + const region = RegionHelper.select() + + billRun = await BillRunHelper.add({ batchType: 'two_part_tariff', regionId: region.id, status: 'review' }) + + chargeCategory = ChargeCategoryHelper.select() + chargeReference = await ChargeReferenceHelper.add({ + additionalCharges: { isSupplyPublicWater: true, supportedSource: { name: 'Foo source' } }, + chargeCategoryId: chargeCategory.id + }) + chargeElement = await ChargeElementHelper.add({ chargeReferenceId: chargeReference.id }) + licence = await LicenceHelper.add() + + reviewLicence = await ReviewLicenceHelper.add({ + billRunId: billRun.id, licenceId: licence.id, licenceRef: licence.licenceRef + }) + + reviewChargeVersion = await ReviewChargeVersionHelper.add({ reviewLicenceId: reviewLicence.id }) + reviewChargeReference = await ReviewChargeReferenceHelper.add({ + chargeReferenceId: chargeReference.id, + reviewChargeVersionId: reviewChargeVersion.id + }) + reviewChargeElement = await ReviewChargeElementHelper.add({ + chargeElementId: chargeElement.id, reviewChargeReferenceId: reviewChargeReference.id + }) + }) + + describe('when a matching review charge reference exists', () => { + it('returns the match', async () => { + const result = await FetchReviewChargeReferenceService.go(reviewChargeReference.id) + + expect(result).to.equal({ + id: reviewChargeReference.id, + abatementAgreement: 1, + aggregate: 1, + amendedAggregate: 1, + amendedAuthorisedVolume: 50, + amendedChargeAdjustment: 1, + canalAndRiverTrustAgreement: false, + chargeAdjustment: 1, + twoPartTariffAgreement: true, + winterDiscount: false, + reviewChargeVersion: { + id: reviewChargeVersion.id, + chargePeriodStartDate: new Date('2022-04-01'), + chargePeriodEndDate: new Date('2022-06-05'), + reviewLicence: { + id: reviewLicence.id, + billRun: { + id: billRun.id, + toFinancialYearEnding: 2023 + }, + licence: { + id: licence.id, + waterUndertaker: false + } + } + }, + reviewChargeElements: [ + { + id: reviewChargeElement.id, + amendedAllocated: 0 + } + ], + chargeReference: { + id: chargeReference.id, + volume: 6.819, + loss: 'low', + supportedSourceName: 'Foo source', + waterCompanyCharge: 'true', + chargeCategory: { + id: chargeCategory.id, + reference: chargeCategory.reference, + shortDescription: chargeCategory.shortDescription + } + } + }) + }) + }) + + describe('when no matching review charge reference exists', () => { + it('returns nothing', async () => { + const result = await FetchReviewChargeReferenceService.go('dfa47d48-0c98-4707-a5b8-820eb16c1dfd') + + expect(result).to.be.undefined() + }) + }) +}) diff --git a/test/services/bill-runs/review/fetch-review-licence.service.test.js b/test/services/bill-runs/review/fetch-review-licence.service.test.js new file mode 100644 index 0000000000..72e7765a55 --- /dev/null +++ b/test/services/bill-runs/review/fetch-review-licence.service.test.js @@ -0,0 +1,278 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') + +const { describe, it, before } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const AddressHelper = require('../../../support/helpers/address.helper.js') +const BillingAccountHelper = require('../../../support/helpers/billing-account.helper.js') +const BillingAccountAddressHelper = require('../../../support/helpers/billing-account-address.helper.js') +const BillRunHelper = require('../../../support/helpers/bill-run.helper.js') +const ChargeCategoryHelper = require('../../../support/helpers/charge-category.helper.js') +const ChargeElementHelper = require('../../../support/helpers/charge-element.helper.js') +const ChargeReferenceHelper = require('../../../support/helpers/charge-reference.helper.js') +const ChargeVersionHelper = require('../../../support/helpers/charge-version.helper.js') +const CompanyHelper = require('../../../support/helpers/company.helper.js') +const ContactHelper = require('../../../support/helpers/contact.helper.js') +const LicenceHelper = require('../../../support/helpers/licence.helper.js') +const PurposeHelper = require('../../../support/helpers/purpose.helper.js') +const RegionHelper = require('../../../support/helpers/region.helper.js') +const ReturnLogHelper = require('../../../support/helpers/return-log.helper.js') +const ReviewChargeElementHelper = require('../../../support/helpers/review-charge-element.helper.js') +const ReviewChargeElementReturnHelper = require('../../../support/helpers/review-charge-element-return.helper.js') +const ReviewChargeReferenceHelper = require('../../../support/helpers/review-charge-reference.helper.js') +const ReviewChargeVersionHelper = require('../../../support/helpers/review-charge-version.helper.js') +const ReviewLicenceHelper = require('../../../support/helpers/review-licence.helper.js') +const ReviewReturnHelper = require('../../../support/helpers/review-return.helper.js') + +// Thing under test +const FetchReviewLicenceService = require('../../../../app/services/bill-runs/review/fetch-review-licence.service.js') + +describe('Bill Runs Review - Fetch Review Licence service', () => { + let address + let billingAccount + let billingAccountAddress + let billRun + let chargeCategory + let chargeElement + let chargeReference + let chargeVersion + let company + let contact + let licence + let purpose + let region + let returnLog + let reviewChargeElement + let reviewChargeVersion + let reviewChargeReference + let reviewLicence + let reviewReturn + + before(async () => { + address = await AddressHelper.add() + company = await CompanyHelper.add() + contact = await ContactHelper.add() + billingAccount = await BillingAccountHelper.add({ companyId: company.id }) + billingAccountAddress = await BillingAccountAddressHelper.add({ + addressId: address.id, + billingAccountId: billingAccount.id, + companyId: company.id, + contactId: contact.id + }) + + region = RegionHelper.select() + + billRun = await BillRunHelper.add({ batchType: 'two_part_tariff', regionId: region.id, status: 'review' }) + + licence = await LicenceHelper.add() + returnLog = await ReturnLogHelper.add() + + chargeVersion = await ChargeVersionHelper.add({ + billingAccountId: billingAccount.id, licenceId: licence.id, licenceRef: licence.licenceRef + }) + chargeCategory = ChargeCategoryHelper.select() + chargeReference = await ChargeReferenceHelper.add({ + chargeCategoryId: chargeCategory.id, chargeVersionId: chargeVersion.id + }) + purpose = PurposeHelper.select() + chargeElement = await ChargeElementHelper.add({ chargeReferenceId: chargeReference.id, purposeId: purpose.id }) + + reviewLicence = await ReviewLicenceHelper.add({ + billRunId: billRun.id, licenceId: licence.id, licenceRef: licence.licenceRef + }) + reviewReturn = await ReviewReturnHelper.add({ + purposes: [{ + primary: { code: 'A', description: 'Agriculture' }, + tertiary: { code: '400', description: 'Spray Irrigation - Direct' }, + secondary: { code: 'AGR', description: 'General Agriculture' } + }], + returnId: returnLog.id, + reviewLicenceId: reviewLicence.id + }) + + reviewChargeVersion = await ReviewChargeVersionHelper.add({ + chargeVersionId: chargeVersion.id, + reviewLicenceId: reviewLicence.id + }) + reviewChargeReference = await ReviewChargeReferenceHelper.add({ + chargeReferenceId: chargeReference.id, + reviewChargeVersionId: reviewChargeVersion.id + }) + reviewChargeElement = await ReviewChargeElementHelper.add({ + chargeElementId: chargeElement.id, + reviewChargeReferenceId: reviewChargeReference.id + }) + + await ReviewChargeElementReturnHelper.add({ + reviewChargeElementId: reviewChargeElement.id, reviewReturnId: reviewReturn.id + }) + }) + + describe('when a matching review licence exists', () => { + it('returns the match', async () => { + const result = await FetchReviewLicenceService.go(reviewLicence.id) + + expect(result).to.equal({ + id: reviewLicence.id, + billRunId: reviewLicence.billRunId, + licenceId: reviewLicence.licenceId, + licenceRef: reviewLicence.licenceRef, + licenceHolder: 'Licence Holder Ltd', + status: 'ready', + progress: false, + billRun: { + id: billRun.id, + toFinancialYearEnding: 2023, + region: { + id: region.id, + displayName: region.displayName + } + }, + reviewReturns: [ + { + id: reviewReturn.id, + allocated: 0, + description: 'Lands at Mosshayne Farm, Exeter & Broadclyst', + endDate: new Date('2022-05-06'), + issues: '', + quantity: 0, + purposes: [ + { + primary: { code: 'A', description: 'Agriculture' }, + tertiary: { code: '400', description: 'Spray Irrigation - Direct' }, + secondary: { code: 'AGR', description: 'General Agriculture' } + } + ], + returnId: reviewReturn.returnId, + returnReference: reviewReturn.returnReference, + returnStatus: 'completed', + startDate: new Date('2022-04-01'), + underQuery: false, + returnLog: { + id: returnLog.id, + periodStartDay: 1, + periodStartMonth: 4, + periodEndDay: 28, + periodEndMonth: 4 + }, + reviewChargeElements: [ + { + id: reviewChargeElement.id + } + ] + } + ], + reviewChargeVersions: [ + { + id: reviewChargeVersion.id, + chargePeriodStartDate: new Date('2022-04-01'), + chargePeriodEndDate: new Date('2022-06-05'), + reviewChargeReferences: [ + { + id: reviewChargeReference.id, + aggregate: 1, + amendedAuthorisedVolume: 50, + chargeAdjustment: 1, + chargeReference: { + id: chargeReference.id, + chargeCategory: { + id: chargeCategory.id, + reference: chargeCategory.reference, + shortDescription: chargeCategory.shortDescription + } + }, + reviewChargeElements: [ + { + id: reviewChargeElement.id, + amendedAllocated: 0, + issues: '', + status: 'ready', + chargeElement: { + id: chargeElement.id, + abstractionPeriodStartDay: 1, + abstractionPeriodStartMonth: 4, + abstractionPeriodEndDay: 31, + abstractionPeriodEndMonth: 3, + authorisedAnnualQuantity: 200, + description: 'Trickle Irrigation - Direct', + purpose: { + id: purpose.id, + description: purpose.description + } + }, + reviewReturns: [ + { + id: reviewReturn.id, + quantity: 0, + returnReference: reviewReturn.returnReference, + returnStatus: 'completed' + } + ] + } + ] + } + ], + chargeVersion: { + id: chargeVersion.id, + billingAccount: { + id: billingAccount.id, + accountNumber: billingAccount.accountNumber, + billingAccountAddresses: [ + { + id: billingAccountAddress.id, + address: { + id: address.id, + address1: 'ENVIRONMENT AGENCY', + address2: 'HORIZON HOUSE', + address3: 'DEANERY ROAD', + address4: 'BRISTOL', + address5: null, + address6: null, + postcode: 'BS1 5AH', + country: 'United Kingdom' + }, + company: { + id: company.id, + name: 'Example Trading Ltd', + type: 'organisation' + }, + contact: { + id: contact.id, + contactType: 'person', + dataSource: 'wrls', + department: null, + firstName: 'Amara', + initials: null, + lastName: 'Gupta', + middleInitials: null, + salutation: null, + suffix: null + } + } + ], + company: { + id: company.id, + name: 'Example Trading Ltd', + type: 'organisation' + } + } + } + } + ] + }) + }) + }) + + describe('when no matching review licence exists', () => { + it('returns nothing', async () => { + const result = await FetchReviewLicenceService.go('dfa47d48-0c98-4707-a5b8-820eb16c1dfd') + + expect(result).to.be.undefined() + }) + }) +}) diff --git a/test/services/bill-runs/review/preview.service.test.js b/test/services/bill-runs/review/preview.service.test.js new file mode 100644 index 0000000000..d96565629f --- /dev/null +++ b/test/services/bill-runs/review/preview.service.test.js @@ -0,0 +1,130 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const BillRunsReviewFixture = require('../../../fixtures/bill-runs-review.fixture.js') + +// Things we need to stub +const CalculateChargeRequest = require('../../../../app/requests/charging-module/calculate-charge.request.js') +const FetchReviewChargeReferenceService = require('../../../../app/services/bill-runs/review/fetch-review-charge-reference.service.js') + +// Thing under test +const PreviewService = require('../../../../app/services/bill-runs/review/preview.service.js') + +describe('Bill Runs Review - Preview service', () => { + let reviewChargeReference + let yarStub + + beforeEach(() => { + reviewChargeReference = BillRunsReviewFixture.reviewChargeReference() + + yarStub = { flash: Sinon.stub() } + }) + + afterEach(() => { + Sinon.restore() + }) + + describe('when called', () => { + describe('for a review charge reference with a total allocation that is greater than zero', () => { + beforeEach(() => { + reviewChargeReference.reviewChargeElements[0].amendedAllocated = 9.092 + + Sinon.stub(FetchReviewChargeReferenceService, 'go').resolves(reviewChargeReference) + }) + + describe('and the request to the Charging Module API succeeds', () => { + beforeEach(async () => { + Sinon.stub(CalculateChargeRequest, 'send').resolves({ + succeeded: true, + response: { + info: { + gitCommit: '273604040a47e0977b0579a0fef0f09726d95e39', + dockerTag: 'ghcr.io/defra/sroc-charging-module-api:v0.19.0' + }, + statusCode: 200, + body: { + calculation: { + chargeValue: 2000, + baseCharge: 12000, + waterCompanyChargeValue: 0, + supportedSourceValue: 0, + winterOnlyFactor: null, + section130Factor: null, + section127Factor: 0.5, + compensationChargePercent: null + } + } + } + }) + }) + + it('adds a flash message stating the example charge returned by the Charging Module API', async () => { + await PreviewService.go(reviewChargeReference.id, yarStub) + + const [flashType, bannerMessage] = yarStub.flash.args[0] + + expect(yarStub.flash.called).to.be.true() + expect(flashType).to.equal('charge') + expect(bannerMessage).to.equal('Based on this information the example charge is £20.00') + }) + }) + + describe('and the request to the Charging Module API fails', () => { + beforeEach(async () => { + Sinon.stub(CalculateChargeRequest, 'send').resolves({ + succeeded: false, + response: { + info: { gitCommit: undefined, dockerTag: undefined }, + statusCode: 422, + body: { + statusCode: 422, + error: 'Unprocessable Entity', + message: '"section127Agreement" must be [true]' + } + } + }) + }) + + it('adds a flash message stating the charge could not be calculated', async () => { + await PreviewService.go(reviewChargeReference.id, yarStub) + + const [flashType, bannerMessage] = yarStub.flash.args[0] + + expect(yarStub.flash.called).to.be.true() + expect(flashType).to.equal('charge') + expect(bannerMessage).to.equal('Could not calculate a charge. "section127Agreement" must be [true].') + }) + }) + }) + + describe('for a review charge reference with a total allocation that is 0', () => { + let calculateChargeRequestStub + + beforeEach(() => { + Sinon.stub(FetchReviewChargeReferenceService, 'go').resolves(reviewChargeReference) + + calculateChargeRequestStub = Sinon.stub(CalculateChargeRequest, 'send').resolves() + }) + + it('adds a flash message stating the example charge is £0.00 and skips calling the Charging Module API', async () => { + await PreviewService.go(reviewChargeReference.id, yarStub) + + expect(calculateChargeRequestStub.called).to.be.false() + + const [flashType, bannerMessage] = yarStub.flash.args[0] + + expect(yarStub.flash.called).to.be.true() + expect(flashType).to.equal('charge') + expect(bannerMessage).to.equal('Based on this information the example charge is £0.00') + }) + }) + }) +}) diff --git a/test/services/bill-runs/review/process-bill-run-post-remove.service.test.js b/test/services/bill-runs/review/process-bill-run-post-remove.service.test.js new file mode 100644 index 0000000000..7bb65334c1 --- /dev/null +++ b/test/services/bill-runs/review/process-bill-run-post-remove.service.test.js @@ -0,0 +1,76 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() +const { expect } = Code + +// Things we need to stub +const BillRunModel = require('../../../../app/models/bill-run.model.js') +const ReviewLicenceModel = require('../../../../app/models/review-licence.model.js') + +// Thing under test +const ProcessBillRunPostRemoveService = require('../../../../app/services/bill-runs/review/process-bill-run-post-remove.service.js') + +describe('Bill Runs Review - Process Bill Run Post Remove service', () => { + const billRunId = 'd4b76592-8f98-4064-892c-399ff83928f7' + + let billRunPatchStub + + beforeEach(() => { + billRunPatchStub = Sinon.stub().resolves() + Sinon.stub(BillRunModel, 'query').returns({ + findById: Sinon.stub().withArgs(billRunId).returnsThis(), + patch: billRunPatchStub + }) + }) + + afterEach(() => { + Sinon.restore() + }) + + describe('when called', () => { + describe('and the bill run contains no licences (it is now empty)', () => { + beforeEach(() => { + Sinon.stub(ReviewLicenceModel, 'query').returns({ + select: Sinon.stub().withArgs('id').returnsThis(), + where: Sinon.stub().withArgs('billRunId', billRunId).returnsThis(), + resultSize: Sinon.stub().resolves(0) + }) + }) + + it('sets the status of the bill run to empty and returns "true"', async () => { + const result = await ProcessBillRunPostRemoveService.go(billRunId) + + expect(result).to.be.true() + + // Check we set the bill run status + const [patchObject] = billRunPatchStub.args[0] + + expect(patchObject).to.equal({ status: 'empty' }) + }) + }) + + describe('and the bill run still contains licences', () => { + beforeEach(() => { + Sinon.stub(ReviewLicenceModel, 'query').returns({ + select: Sinon.stub().withArgs('id').returnsThis(), + where: Sinon.stub().withArgs('billRunId', billRunId).returnsThis(), + resultSize: Sinon.stub().resolves(1) + }) + }) + + it('does not change the bill run status status and returns "false"', async () => { + const result = await ProcessBillRunPostRemoveService.go(billRunId) + + expect(result).to.be.false() + + // Check we not change the bill run status + expect(billRunPatchStub.called).to.be.false() + }) + }) + }) +}) diff --git a/test/services/bill-runs/two-part-tariff/remove-review-data.service.test.js b/test/services/bill-runs/review/remove-review-licence.service.test.js similarity index 79% rename from test/services/bill-runs/two-part-tariff/remove-review-data.service.test.js rename to test/services/bill-runs/review/remove-review-licence.service.test.js index cf32a98abb..f7d5ddfb7e 100644 --- a/test/services/bill-runs/two-part-tariff/remove-review-data.service.test.js +++ b/test/services/bill-runs/review/remove-review-licence.service.test.js @@ -22,25 +22,23 @@ const ReviewReturnHelper = require('../../../support/helpers/review-return.helpe const ReviewReturnModel = require('../../../../app/models/review-return.model.js') // Thing under test -const RemoveReviewDataService = require('../../../../app/services/bill-runs/two-part-tariff/remove-review-data.service.js') - -describe('Remove Review Data service', () => { - describe('when called with a valid billRunId & licenceId', () => { - const billRunId = 'f54005c6-66bc-43d7-a7e8-d162e6ebc317' - const licenceId = '41f1ad1d-0f25-4b2f-bc0a-4b38131db12a' +const RemoveReviewLicenceService = require('../../../../app/services/bill-runs/review/remove-review-licence.service.js') +describe('Bill Runs Review - Remove Review Licence service', () => { + describe('when called', () => { let reviewChargeElementId let reviewChargeElementReturnId let reviewChargeReferenceId let reviewChargeVersionId + let reviewLicence let reviewReturnId beforeEach(async () => { - const { id: reviewLicenceId } = await ReviewLicenceHelper.add({ billRunId, licenceId }) - const reviewReturn = await ReviewReturnHelper.add({ reviewLicenceId }) + reviewLicence = await ReviewLicenceHelper.add() + const reviewReturn = await ReviewReturnHelper.add({ reviewLicenceId: reviewLicence.id }) reviewReturnId = reviewReturn.id - const reviewChargeVersion = await ReviewChargeVersionHelper.add({ reviewLicenceId }) + const reviewChargeVersion = await ReviewChargeVersionHelper.add({ reviewLicenceId: reviewLicence.id }) reviewChargeVersionId = reviewChargeVersion.id const reviewChargeReference = await ReviewChargeReferenceHelper.add({ reviewChargeVersionId }) @@ -54,10 +52,10 @@ describe('Remove Review Data service', () => { reviewChargeElementReturnId = reviewChargeElementReturn.id }) - it('will remove the records relating to the licence from the review tables', async () => { - await RemoveReviewDataService.go(billRunId, licenceId) + it('will remove the records relating to the review licence from the review tables', async () => { + await RemoveReviewLicenceService.go(reviewLicence.id) - expect(await ReviewLicenceModel.query().where('licenceId', licenceId)).to.be.empty() + expect(await ReviewLicenceModel.query().findById(reviewLicence.id)).to.be.undefined() expect(await ReviewReturnModel.query().findById(reviewReturnId)).to.be.undefined() expect(await ReviewChargeVersionModel.query().findById(reviewChargeVersionId)).to.be.undefined() expect(await ReviewChargeReferenceModel.query().findById(reviewChargeReferenceId)).to.be.undefined() diff --git a/test/services/bill-runs/review/remove.service.test.js b/test/services/bill-runs/review/remove.service.test.js new file mode 100644 index 0000000000..79e50e65d1 --- /dev/null +++ b/test/services/bill-runs/review/remove.service.test.js @@ -0,0 +1,48 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const BillRunsReviewFixture = require('../../../fixtures/bill-runs-review.fixture.js') + +// Things we need to stub +const FetchRemoveReviewLicenceService = require('../../../../app/services/bill-runs/review/fetch-remove-review-licence.service.js') + +// Thing under test +const RemoveService = require('../../../../app/services/bill-runs/review/remove.service.js') + +describe('Bill Runs Review - Remove service', () => { + let removeReviewLicence + + beforeEach(() => { + removeReviewLicence = BillRunsReviewFixture.removeReviewLicence() + + Sinon.stub(FetchRemoveReviewLicenceService, 'go').resolves(removeReviewLicence) + }) + + afterEach(() => { + Sinon.restore() + }) + + describe('when called', () => { + it('returns page data for the view', async () => { + const result = await RemoveService.go(removeReviewLicence.id) + + expect(result).to.equal({ + billRunNumber: 10001, + billRunStatus: 'review', + dateCreated: '22 October 2024', + financialYearPeriod: '2023 to 2024', + pageTitle: "You're about to remove 1/11/11/*11/1111 from the bill run", + region: 'Test Region', + reviewLicenceId: 'bb779166-0576-4581-b504-edbc0227d763' + }) + }) + }) +}) diff --git a/test/services/bill-runs/two-part-tariff/review-bill-run.service.test.js b/test/services/bill-runs/review/review-bill-run.service.test.js similarity index 97% rename from test/services/bill-runs/two-part-tariff/review-bill-run.service.test.js rename to test/services/bill-runs/review/review-bill-run.service.test.js index bf9cc73f20..253e46dc5c 100644 --- a/test/services/bill-runs/two-part-tariff/review-bill-run.service.test.js +++ b/test/services/bill-runs/review/review-bill-run.service.test.js @@ -12,13 +12,14 @@ const { expect } = Code const DatabaseConfig = require('../../../../config/database.config.js') // Things we need to stub -const FetchBillRunLicencesService = require('../../../../app/services/bill-runs/two-part-tariff/fetch-bill-run-licences.service.js') -const ReviewBillRunPresenter = require('../../../../app/presenters/bill-runs/two-part-tariff/review-bill-run.presenter.js') +const FetchBillRunLicencesService = require('../../../../app/services/bill-runs/review/fetch-bill-run-licences.service.js') +// TODO: Stop stubbing the presenter +const ReviewBillRunPresenter = require('../../../../app/presenters/bill-runs/review/review-bill-run.presenter.js') // Thing under test -const ReviewBillRunService = require('../../../../app/services/bill-runs/two-part-tariff/review-bill-run.service.js') +const ReviewBillRunService = require('../../../../app/services/bill-runs/review/review-bill-run.service.js') -describe('Review Bill Run Service', () => { +describe('Bill Runs Review - Review Bill Run Service', () => { const billRunId = '2c80bd22-a005-4cf4-a2a2-73812a9861de' let page diff --git a/test/services/bill-runs/review/review-charge-element.service.test.js b/test/services/bill-runs/review/review-charge-element.service.test.js new file mode 100644 index 0000000000..634c890293 --- /dev/null +++ b/test/services/bill-runs/review/review-charge-element.service.test.js @@ -0,0 +1,121 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const BillRunsReviewFixture = require('../../../fixtures/bill-runs-review.fixture.js') + +// Things we need to stub +const FetchReviewChargeElementService = require('../../../../app/services/bill-runs/review/fetch-review-charge-element.service.js') + +// Thing under test +const ReviewChargeElementService = require('../../../../app/services/bill-runs/review/review-charge-element.service.js') + +describe('Bill Runs Review - Review Charge Element Service', () => { + const elementIndex = 1 + + let reviewChargeElement + let yarStub + + beforeEach(() => { + reviewChargeElement = BillRunsReviewFixture.reviewChargeElement() + + Sinon.stub(FetchReviewChargeElementService, 'go').resolves(reviewChargeElement) + }) + + afterEach(() => { + Sinon.restore() + }) + + describe('when called', () => { + describe('and there is a flash message to display', () => { + beforeEach(() => { + yarStub = { flash: Sinon.stub().withArgs('banner').returns(['The billable returns for this licence have been updated']) } + }) + + it('returns page data for the view', async () => { + const result = await ReviewChargeElementService.go(reviewChargeElement.id, elementIndex, yarStub) + + expect(result).to.equal({ + bannerMessage: 'The billable returns for this licence have been updated', + pageTitle: 'Review charge element', + authorisedVolume: 9.092, + billableReturns: 0, + chargeDescription: 'Spray Irrigation - Direct', + chargePeriod: '1 April 2023 to 31 March 2024', + chargePeriods: ['1 April 2023 to 30 September 2023'], + elementCount: 1, + elementIndex: 1, + financialPeriod: '2023 to 2024', + issues: ['Aggregate'], + licenceId: '32416c67-f755-4c3f-8816-ecde0ee596bd', + matchedReturns: [ + { + abstractionPeriod: '1 April to 30 September', + description: 'Test Road. Points 1 and 2.', + issues: [], + purpose: 'Spray Irrigation - Direct', + reference: '11142960', + returnId: 'v1:5:1/11/11/*11/1111:11142960:2022-11-01:2023-10-31', + returnLink: '/returns/return?id=v1:5:1/11/11/*11/1111:11142960:2022-11-01:2023-10-31', + returnPeriod: '1 November 2022 to 31 October 2023', + returnStatus: 'completed', + returnTotal: '0 ML / 0 ML' + } + ], + reviewChargeElementId: 'a1840523-a04c-4c64-bff7-4a515e8ba1c1', + reviewLicenceId: 'bb779166-0576-4581-b504-edbc0227d763', + status: 'review' + }) + }) + }) + + describe('and there is no flash message to display', () => { + beforeEach(() => { + yarStub = { flash: Sinon.stub().withArgs('banner').returns([undefined]) } + }) + + it('returns page data for the view', async () => { + const result = await ReviewChargeElementService.go(reviewChargeElement.id, elementIndex, yarStub) + + expect(result).to.equal({ + bannerMessage: undefined, + pageTitle: 'Review charge element', + authorisedVolume: 9.092, + billableReturns: 0, + chargeDescription: 'Spray Irrigation - Direct', + chargePeriod: '1 April 2023 to 31 March 2024', + chargePeriods: ['1 April 2023 to 30 September 2023'], + elementCount: 1, + elementIndex: 1, + financialPeriod: '2023 to 2024', + issues: ['Aggregate'], + licenceId: '32416c67-f755-4c3f-8816-ecde0ee596bd', + matchedReturns: [ + { + abstractionPeriod: '1 April to 30 September', + description: 'Test Road. Points 1 and 2.', + issues: [], + purpose: 'Spray Irrigation - Direct', + reference: '11142960', + returnId: 'v1:5:1/11/11/*11/1111:11142960:2022-11-01:2023-10-31', + returnLink: '/returns/return?id=v1:5:1/11/11/*11/1111:11142960:2022-11-01:2023-10-31', + returnPeriod: '1 November 2022 to 31 October 2023', + returnStatus: 'completed', + returnTotal: '0 ML / 0 ML' + } + ], + reviewChargeElementId: 'a1840523-a04c-4c64-bff7-4a515e8ba1c1', + reviewLicenceId: 'bb779166-0576-4581-b504-edbc0227d763', + status: 'review' + }) + }) + }) + }) +}) diff --git a/test/services/bill-runs/review/review-charge-reference.service.test.js b/test/services/bill-runs/review/review-charge-reference.service.test.js new file mode 100644 index 0000000000..0053d2b1eb --- /dev/null +++ b/test/services/bill-runs/review/review-charge-reference.service.test.js @@ -0,0 +1,143 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const BillRunsReviewFixture = require('../../../fixtures/bill-runs-review.fixture.js') + +// Things we need to stub +const FetchReviewChargeReferenceService = require('../../../../app/services/bill-runs/review/fetch-review-charge-reference.service.js') + +// Thing under test +const ReviewChargeReferenceService = require('../../../../app/services/bill-runs/review/review-charge-reference.service.js') + +describe('Bill Runs Review - Review Charge Reference Service', () => { + let reviewChargeReference + let yarStub + + beforeEach(() => { + reviewChargeReference = BillRunsReviewFixture.reviewChargeReference() + + Sinon.stub(FetchReviewChargeReferenceService, 'go').resolves(reviewChargeReference) + }) + + afterEach(() => { + Sinon.restore() + }) + + describe('when called', () => { + describe('and there is a banner flash message to display', () => { + beforeEach(() => { + const stub = Sinon.stub() + + stub.withArgs('banner').returns(['The authorised volume for this licence have been updated']) + stub.withArgs('charge').returns([undefined]) + + yarStub = { flash: stub } + }) + + it('returns page data for the view', async () => { + const result = await ReviewChargeReferenceService.go(reviewChargeReference.id, yarStub) + + expect(result).to.equal({ + pageTitle: 'Review charge reference', + bannerMessage: 'The authorised volume for this licence have been updated', + chargeMessage: undefined, + additionalCharges: '', + adjustments: [ + 'Aggregate factor (0.333333333 / 0.333333333)', + 'Charge adjustment (1 / 1)', + 'Two part tariff agreement' + ], + amendedAuthorisedVolume: 9.092, + canAmend: true, + chargeCategory: '4.6.5', + chargeDescription: 'High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model', + chargePeriod: '1 April 2023 to 31 March 2024', + financialPeriod: '2023 to 2024', + reviewChargeReferenceId: '6b3d11f2-d361-4eaa-bce2-5561283bd023', + reviewLicenceId: 'bb779166-0576-4581-b504-edbc0227d763', + totalBillableReturns: 0 + }) + }) + }) + + describe('and there is a charge flash message to display', () => { + beforeEach(() => { + const stub = Sinon.stub() + + stub.withArgs('banner').returns([undefined]) + stub.withArgs('charge').returns(['Based on this information the example charge is £256.48.']) + + yarStub = { flash: stub } + }) + + it('returns page data for the view', async () => { + const result = await ReviewChargeReferenceService.go(reviewChargeReference.id, yarStub) + + expect(result).to.equal({ + pageTitle: 'Review charge reference', + bannerMessage: undefined, + chargeMessage: 'Based on this information the example charge is £256.48.', + additionalCharges: '', + adjustments: [ + 'Aggregate factor (0.333333333 / 0.333333333)', + 'Charge adjustment (1 / 1)', + 'Two part tariff agreement' + ], + amendedAuthorisedVolume: 9.092, + canAmend: true, + chargeCategory: '4.6.5', + chargeDescription: 'High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model', + chargePeriod: '1 April 2023 to 31 March 2024', + financialPeriod: '2023 to 2024', + reviewChargeReferenceId: '6b3d11f2-d361-4eaa-bce2-5561283bd023', + reviewLicenceId: 'bb779166-0576-4581-b504-edbc0227d763', + totalBillableReturns: 0 + }) + }) + }) + + describe('and there is no flash message to display', () => { + beforeEach(() => { + const stub = Sinon.stub() + + stub.withArgs('banner').returns([undefined]) + stub.withArgs('charge').returns([undefined]) + + yarStub = { flash: stub } + }) + + it('returns page data for the view', async () => { + const result = await ReviewChargeReferenceService.go(reviewChargeReference.id, yarStub) + + expect(result).to.equal({ + pageTitle: 'Review charge reference', + bannerMessage: undefined, + chargeMessage: undefined, + additionalCharges: '', + adjustments: [ + 'Aggregate factor (0.333333333 / 0.333333333)', + 'Charge adjustment (1 / 1)', + 'Two part tariff agreement' + ], + amendedAuthorisedVolume: 9.092, + canAmend: true, + chargeCategory: '4.6.5', + chargeDescription: 'High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model', + chargePeriod: '1 April 2023 to 31 March 2024', + financialPeriod: '2023 to 2024', + reviewChargeReferenceId: '6b3d11f2-d361-4eaa-bce2-5561283bd023', + reviewLicenceId: 'bb779166-0576-4581-b504-edbc0227d763', + totalBillableReturns: 0 + }) + }) + }) + }) +}) diff --git a/test/services/bill-runs/review/review-licence.service.test.js b/test/services/bill-runs/review/review-licence.service.test.js new file mode 100644 index 0000000000..eb40b3aba5 --- /dev/null +++ b/test/services/bill-runs/review/review-licence.service.test.js @@ -0,0 +1,230 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const BillRunsReviewFixture = require('../../../fixtures/bill-runs-review.fixture.js') + +// Things we need to stub +const FetchReviewLicenceService = require('../../../../app/services/bill-runs/review/fetch-review-licence.service.js') + +// Thing under test +const ReviewLicenceService = require('../../../../app/services/bill-runs/review/review-licence.service.js') + +describe('Bill Runs Review - Review Licence Service', () => { + let reviewLicence + + let yarStub + + beforeEach(() => { + reviewLicence = BillRunsReviewFixture.reviewLicence() + + Sinon.stub(FetchReviewLicenceService, 'go').resolves(reviewLicence) + }) + + afterEach(() => { + Sinon.restore() + }) + + describe('when called', () => { + describe('and there is a flash message to display', () => { + beforeEach(() => { + yarStub = { flash: Sinon.stub().withArgs('banner').returns(['This licence has been marked.']) } + }) + + it('returns page data for the view', async () => { + const result = await ReviewLicenceService.go(reviewLicence.id, yarStub) + + expect(result).to.equal({ + bannerMessage: 'This licence has been marked.', + billRunId: '287aeb25-cf11-429d-8c6f-f98f06db021d', + chargeVersions: [ + { + billingAccountDetails: { + billingAccountId: 'f041c128-bb4d-4f67-8f97-e33d71d50842', + accountNumber: 'E99999999A', + accountName: 'Mr B Blobby Ltd', + contactName: null, + addressLines: [ + 'C/O Noel Edmonds', + 'Crinkley Bottom', + 'Cricket St Thomas', + 'Somerset', + 'TA20 1KL', + 'United Kingdom' + ] + }, + chargePeriod: '1 April 2023 to 31 March 2024', + chargeReferences: [ + { + billableReturnsWarning: false, + chargeCategory: 'Charge reference 4.6.5', + chargeDescription: 'High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model', + id: '6c70461b-3f83-47b1-9538-8305e82b34eb', + chargeElements: [ + { + billableReturns: '0 ML / 9.092 ML', + chargePeriods: ['1 April 2023 to 30 September 2023'], + returnVolumes: ['0 ML (10030495)'], + description: 'Spray Irrigation - Direct', + elementCount: 1, + elementIndex: 1, + status: 'review', + id: 'a1840523-a04c-4c64-bff7-4a515e8ba1c1', + issues: ['Aggregate'], + purpose: 'Spray Irrigation - Direct' + } + ], + chargeReferenceLinkTitle: 'Change details', + totalBillableReturns: '0 ML / 9.092 ML' + } + ], + description: '1 charge reference with 1 two-part tariff charge element', + financialPeriod: '2023 to 2024' + } + ], + elementsInReview: true, + licenceHolder: 'Licence Holder Ltd', + licenceId: '32416c67-f755-4c3f-8816-ecde0ee596bd', + licenceRef: '1/11/11/*11/1111', + matchedReturns: [ + { + abstractionPeriod: '1 April to 30 September', + description: 'Test Road. Points 1 and 2.', + issues: [], + purpose: 'Spray Irrigation - Direct', + reference: '11142960', + returnId: 'v1:5:1/11/11/*11/1111:11142960:2022-11-01:2023-10-31', + returnLink: '/returns/return?id=v1:5:1/11/11/*11/1111:11142960:2022-11-01:2023-10-31', + returnPeriod: '1 November 2022 to 31 October 2023', + returnStatus: 'completed', + returnTotal: '0 ML / 0 ML' + } + ], + pageTitle: 'Licence 1/11/11/*11/1111', + progress: false, + region: 'South West', + reviewLicenceId: 'bb779166-0576-4581-b504-edbc0227d763', + status: 'review', + unmatchedReturns: [ + { + abstractionPeriod: '1 April to 30 September', + description: 'Lost Road. Points 1 and 2.', + issues: [], + purpose: 'Spray Irrigation - Storage', + reference: '11142961', + returnId: 'v1:5:1/11/11/*11/1111:11142961:2022-11-01:2023-10-31', + returnLink: '/returns/return?id=v1:5:1/11/11/*11/1111:11142961:2022-11-01:2023-10-31', + returnPeriod: '1 November 2022 to 31 October 2023', + returnStatus: 'completed', + returnTotal: '0 ML / 0 ML' + } + ] + }) + }) + }) + + describe('and there is no flash message to display', () => { + beforeEach(() => { + yarStub = { flash: Sinon.stub().withArgs('banner').returns([undefined]) } + }) + + it('returns page data for the view', async () => { + const result = await ReviewLicenceService.go(reviewLicence.id, yarStub) + + expect(result).to.equal({ + bannerMessage: undefined, + billRunId: '287aeb25-cf11-429d-8c6f-f98f06db021d', + chargeVersions: [ + { + billingAccountDetails: { + billingAccountId: 'f041c128-bb4d-4f67-8f97-e33d71d50842', + accountNumber: 'E99999999A', + accountName: 'Mr B Blobby Ltd', + contactName: null, + addressLines: [ + 'C/O Noel Edmonds', + 'Crinkley Bottom', + 'Cricket St Thomas', + 'Somerset', + 'TA20 1KL', + 'United Kingdom' + ] + }, + chargePeriod: '1 April 2023 to 31 March 2024', + chargeReferences: [ + { + billableReturnsWarning: false, + chargeCategory: 'Charge reference 4.6.5', + chargeDescription: 'High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model', + id: '6c70461b-3f83-47b1-9538-8305e82b34eb', + chargeElements: [ + { + billableReturns: '0 ML / 9.092 ML', + chargePeriods: ['1 April 2023 to 30 September 2023'], + returnVolumes: ['0 ML (10030495)'], + description: 'Spray Irrigation - Direct', + elementCount: 1, + elementIndex: 1, + status: 'review', + id: 'a1840523-a04c-4c64-bff7-4a515e8ba1c1', + issues: ['Aggregate'], + purpose: 'Spray Irrigation - Direct' + } + ], + chargeReferenceLinkTitle: 'Change details', + totalBillableReturns: '0 ML / 9.092 ML' + } + ], + description: '1 charge reference with 1 two-part tariff charge element', + financialPeriod: '2023 to 2024' + } + ], + elementsInReview: true, + licenceHolder: 'Licence Holder Ltd', + licenceId: '32416c67-f755-4c3f-8816-ecde0ee596bd', + licenceRef: '1/11/11/*11/1111', + matchedReturns: [ + { + abstractionPeriod: '1 April to 30 September', + description: 'Test Road. Points 1 and 2.', + issues: [], + purpose: 'Spray Irrigation - Direct', + reference: '11142960', + returnId: 'v1:5:1/11/11/*11/1111:11142960:2022-11-01:2023-10-31', + returnLink: '/returns/return?id=v1:5:1/11/11/*11/1111:11142960:2022-11-01:2023-10-31', + returnPeriod: '1 November 2022 to 31 October 2023', + returnStatus: 'completed', + returnTotal: '0 ML / 0 ML' + } + ], + pageTitle: 'Licence 1/11/11/*11/1111', + progress: false, + region: 'South West', + reviewLicenceId: 'bb779166-0576-4581-b504-edbc0227d763', + status: 'review', + unmatchedReturns: [ + { + abstractionPeriod: '1 April to 30 September', + description: 'Lost Road. Points 1 and 2.', + issues: [], + purpose: 'Spray Irrigation - Storage', + reference: '11142961', + returnId: 'v1:5:1/11/11/*11/1111:11142961:2022-11-01:2023-10-31', + returnLink: '/returns/return?id=v1:5:1/11/11/*11/1111:11142961:2022-11-01:2023-10-31', + returnPeriod: '1 November 2022 to 31 October 2023', + returnStatus: 'completed', + returnTotal: '0 ML / 0 ML' + } + ] + }) + }) + }) + }) +}) diff --git a/test/services/bill-runs/review/submit-authorised.service.test.js b/test/services/bill-runs/review/submit-authorised.service.test.js new file mode 100644 index 0000000000..3d45221399 --- /dev/null +++ b/test/services/bill-runs/review/submit-authorised.service.test.js @@ -0,0 +1,98 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const BillRunsReviewFixture = require('../../../fixtures/bill-runs-review.fixture.js') + +// Things we need to stub +const FetchReviewChargeReferenceService = require('../../../../app/services/bill-runs/review/fetch-review-charge-reference.service.js') +const ReviewChargeReferenceModel = require('../../../../app/models/review-charge-reference.model.js') + +// Thing under test +const SubmitAuthorisedService = require('../../../../app/services/bill-runs/review/submit-authorised.service.js') + +describe('Bill Runs Review - Submit Authorised Service', () => { + let payload + let patchStub + let reviewChargeReference + let yarStub + + beforeEach(() => { + reviewChargeReference = BillRunsReviewFixture.reviewChargeReference() + + Sinon.stub(FetchReviewChargeReferenceService, 'go').resolves(reviewChargeReference) + + patchStub = Sinon.stub().resolves() + Sinon.stub(ReviewChargeReferenceModel, 'query').returns({ + findById: Sinon.stub().withArgs(reviewChargeReference.id).returnsThis(), + patch: patchStub + }) + + yarStub = { flash: Sinon.stub() } + }) + + afterEach(() => { + Sinon.restore() + }) + + describe('when called', () => { + describe('with a valid payload', () => { + beforeEach(async () => { + payload = { amendedAuthorisedVolume: '9.092', totalBillableReturns: '9.092' } + }) + + it('saves the submitted value, adds a flash message and returns an empty object', async () => { + const result = await SubmitAuthorisedService.go(reviewChargeReference.id, yarStub, payload) + + // Check we save the change + const [patchObject] = patchStub.args[0] + + expect(patchObject).to.equal({ amendedAuthorisedVolume: '9.092' }) + + // Check we add the flash message + const [flashType, bannerMessage] = yarStub.flash.args[0] + + expect(flashType).to.equal('banner') + expect(bannerMessage).to.equal('The authorised volume for this licence have been updated') + + // Check we return an empty object (controller knows POST was successful so redirects) + expect(result).to.equal({}) + }) + }) + + describe('with an invalid payload', () => { + beforeEach(async () => { + payload = { amendedAuthorisedVolume: '-1', totalBillableReturns: '9.092' } + }) + + it('does not save the submitted value or add a flash message, and returns the page data including an error', async () => { + const result = await SubmitAuthorisedService.go(reviewChargeReference.id, yarStub, payload) + + // Check we didn't save + expect(patchStub.called).to.be.false() + + // Check we didn't add the flash message + expect(yarStub.flash.called).to.be.false() + + // Check we return page data including error (controller knows POST failed so re-renders) + expect(result).to.equal({ + amendedAuthorisedVolume: 9.092, + error: { text: 'The authorised volume must be greater than 9.092' }, + pageTitle: 'Set the authorised volume', + chargeDescription: 'High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model', + chargePeriod: '1 April 2023 to 31 March 2024', + financialPeriod: '2023 to 2024', + reviewChargeReferenceId: '6b3d11f2-d361-4eaa-bce2-5561283bd023', + totalBillableReturns: 0 + }) + }) + }) + }) +}) diff --git a/test/services/bill-runs/review/submit-edit.service.test.js b/test/services/bill-runs/review/submit-edit.service.test.js new file mode 100644 index 0000000000..0db8ea9496 --- /dev/null +++ b/test/services/bill-runs/review/submit-edit.service.test.js @@ -0,0 +1,180 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const BillRunsReviewFixture = require('../../../fixtures/bill-runs-review.fixture.js') + +// Things we need to stub +const FetchReviewChargeElementService = require('../../../../app/services/bill-runs/review/fetch-review-charge-element.service.js') +const ReviewChargeElementModel = require('../../../../app/models/review-charge-element.model.js') + +// Thing under test +const SubmitEditService = require('../../../../app/services/bill-runs/review/submit-edit.service.js') + +describe('Bill Runs Review - Submit Edit Service', () => { + const elementIndex = 1 + + let payload + let patchStub + let reviewChargeElement + let yarStub + + beforeEach(() => { + reviewChargeElement = BillRunsReviewFixture.reviewChargeElement() + + Sinon.stub(FetchReviewChargeElementService, 'go').resolves(reviewChargeElement) + + patchStub = Sinon.stub().resolves() + Sinon.stub(ReviewChargeElementModel, 'query').returns({ + findById: Sinon.stub().withArgs(reviewChargeElement.id).returnsThis(), + patch: patchStub + }) + + yarStub = { flash: Sinon.stub() } + }) + + afterEach(() => { + Sinon.restore() + }) + + describe('when called', () => { + describe('with a valid payload', () => { + describe('where the user has selected the authorised quantity', () => { + beforeEach(async () => { + payload = { quantityOptions: 25, authorisedVolume: 30 } + }) + + it('saves the submitted value, adds a flash message and returns an empty object', async () => { + const result = await SubmitEditService.go(reviewChargeElement.id, elementIndex, yarStub, payload) + + // Check we save the change + const [patchObject] = patchStub.args[0] + + expect(patchObject).to.equal({ amendedAllocated: 25 }) + + // Check we add the flash message + const [flashType, bannerMessage] = yarStub.flash.args[0] + + expect(flashType).to.equal('banner') + expect(bannerMessage).to.equal('The billable returns for this licence have been updated') + + // Check we return an empty object (controller knows POST was successful so redirects) + expect(result).to.equal({}) + }) + }) + + describe('where the user has a custom quantity', () => { + beforeEach(async () => { + payload = { quantityOptions: 'customQuantity', customQuantity: 12, authorisedVolume: 30 } + }) + + it('saves the submitted value, adds a flash message and returns an empty object', async () => { + const result = await SubmitEditService.go(reviewChargeElement.id, elementIndex, yarStub, payload) + + // Check we save the change + const [patchObject] = patchStub.args[0] + + expect(patchObject).to.equal({ amendedAllocated: 12 }) + + // Check we add the flash message + const [flashType, bannerMessage] = yarStub.flash.args[0] + + expect(flashType).to.equal('banner') + expect(bannerMessage).to.equal('The billable returns for this licence have been updated') + + // Check we return an empty object (controller knows POST was successful so redirects) + expect(result).to.equal({}) + }) + }) + }) + + describe('with an invalid payload', () => { + describe('because the user did not select an option', () => { + beforeEach(async () => { + payload = { authorisedVolume: 30 } + }) + + it('does not save the submitted value or add a flash message, and returns the page data including an error', async () => { + const result = await SubmitEditService.go(reviewChargeElement.id, elementIndex, yarStub, payload) + + // Check we didn't save + expect(patchStub.called).to.be.false() + + // Check we didn't add the flash message + expect(yarStub.flash.called).to.be.false() + + // Check we return page data including error (controller knows POST failed so re-renders) + expect(result).to.equal({ + customQuantitySelected: false, + customQuantityValue: undefined, + error: { + errorList: [ + { + href: '#quantityOptions-error', + text: 'Select the billable quantity' + } + ], + quantityOptionsErrorMessage: { text: 'Select the billable quantity' } + }, + pageTitle: 'Set the billable returns quantity for this bill run', + authorisedQuantity: 9.092, + billableReturns: 0, + chargeDescription: 'Spray Irrigation - Direct', + chargePeriod: '1 April 2023 to 31 March 2024', + chargePeriods: ['1 April 2023 to 30 September 2023'], + elementIndex: 1, + financialPeriod: '2023 to 2024', + reviewChargeElementId: 'a1840523-a04c-4c64-bff7-4a515e8ba1c1' + }) + }) + }) + + describe('because the submitted an invalid custom quantity', () => { + beforeEach(async () => { + payload = { quantityOptions: 'customQuantity', customQuantity: -0.1, authorisedVolume: 25 } + }) + + it('does not save the submitted value or add a flash message, and returns the page data including an error', async () => { + const result = await SubmitEditService.go(reviewChargeElement.id, elementIndex, yarStub, payload) + + // Check we didn't save + expect(patchStub.called).to.be.false() + + // Check we didn't add the flash message + expect(yarStub.flash.called).to.be.false() + + // Check we return page data including error (controller knows POST failed so re-renders) + expect(result).to.equal({ + customQuantitySelected: true, + customQuantityValue: -0.1, + error: { + errorList: [ + { + href: '#custom-quantity', + text: 'The quantity must be zero or higher' + } + ], + customQuantityErrorMessage: { text: 'The quantity must be zero or higher' } + }, + pageTitle: 'Set the billable returns quantity for this bill run', + authorisedQuantity: 9.092, + billableReturns: 0, + chargeDescription: 'Spray Irrigation - Direct', + chargePeriod: '1 April 2023 to 31 March 2024', + chargePeriods: ['1 April 2023 to 30 September 2023'], + elementIndex: 1, + financialPeriod: '2023 to 2024', + reviewChargeElementId: 'a1840523-a04c-4c64-bff7-4a515e8ba1c1' + }) + }) + }) + }) + }) +}) diff --git a/test/services/bill-runs/review/submit-factors.service.test.js b/test/services/bill-runs/review/submit-factors.service.test.js new file mode 100644 index 0000000000..f70e88ca21 --- /dev/null +++ b/test/services/bill-runs/review/submit-factors.service.test.js @@ -0,0 +1,106 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const BillRunsReviewFixture = require('../../../fixtures/bill-runs-review.fixture.js') + +// Things we need to stub +const FetchReviewChargeReferenceService = require('../../../../app/services/bill-runs/review/fetch-review-charge-reference.service.js') +const ReviewChargeReferenceModel = require('../../../../app/models/review-charge-reference.model.js') + +// Thing under test +const SubmitFactorsService = require('../../../../app/services/bill-runs/review/submit-factors.service.js') + +describe('Bill Runs Review - Submit Factors Service', () => { + let payload + let patchStub + let reviewChargeReference + let yarStub + + beforeEach(() => { + reviewChargeReference = BillRunsReviewFixture.reviewChargeReference() + + Sinon.stub(FetchReviewChargeReferenceService, 'go').resolves(reviewChargeReference) + + patchStub = Sinon.stub().resolves() + Sinon.stub(ReviewChargeReferenceModel, 'query').returns({ + findById: Sinon.stub().withArgs(reviewChargeReference.id).returnsThis(), + patch: patchStub + }) + + yarStub = { flash: Sinon.stub() } + }) + + afterEach(() => { + Sinon.restore() + }) + + describe('when called', () => { + describe('with a valid payload', () => { + beforeEach(async () => { + payload = { amendedAggregate: 0.5, amendedChargeAdjustment: 0.5 } + }) + + it('saves the submitted value, adds a flash message and returns an empty object', async () => { + const result = await SubmitFactorsService.go(reviewChargeReference.id, yarStub, payload) + + // Check we save the change + const [patchObject] = patchStub.args[0] + + expect(patchObject).to.equal({ amendedAggregate: 0.5, amendedChargeAdjustment: 0.5 }) + + // Check we add the flash message + const [flashType, bannerMessage] = yarStub.flash.args[0] + + expect(flashType).to.equal('banner') + expect(bannerMessage).to.equal('The adjustment factors for this licence have been updated') + + // Check we return an empty object (controller knows POST was successful so redirects) + expect(result).to.equal({}) + }) + }) + + describe('with an invalid payload', () => { + beforeEach(async () => { + payload = {} + }) + + it('does not save the submitted value or add a flash message, and returns the page data including an error', async () => { + const result = await SubmitFactorsService.go(reviewChargeReference.id, yarStub, payload) + + // Check we didn't save + expect(patchStub.called).to.be.false() + + // Check we didn't add the flash message + expect(yarStub.flash.called).to.be.false() + + // Check we return page data including error (controller knows POST failed so re-renders) + expect(result).to.equal({ + amendedAggregate: 0.333333333, + amendedChargeAdjustment: 1, + error: { + errorList: [ + { href: '#amended-aggregate', text: 'Enter an aggregate factor' }, + { href: '#amended-charge-adjustment', text: 'Enter a charge factor' } + ], + amendedAggregate: { message: 'Enter an aggregate factor' }, + amendedChargeAdjustment: { message: 'Enter a charge factor' } + }, + pageTitle: 'Set the adjustment factors', + chargeDescription: 'High loss, non-tidal, restricted water, up to and including 15 ML/yr, Tier 1 model', + chargePeriod: '1 April 2023 to 31 March 2024', + financialPeriod: '2023 to 2024', + otherAdjustments: ['Two part tariff agreement'], + reviewChargeReferenceId: '6b3d11f2-d361-4eaa-bce2-5561283bd023' + }) + }) + }) + }) +}) diff --git a/test/services/bill-runs/review/submit-remove.service.test.js b/test/services/bill-runs/review/submit-remove.service.test.js new file mode 100644 index 0000000000..f1f0b0168d --- /dev/null +++ b/test/services/bill-runs/review/submit-remove.service.test.js @@ -0,0 +1,105 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const BillRunsReviewFixture = require('../../../fixtures/bill-runs-review.fixture.js') + +// Things we need to stub +const CreateLicenceSupplementaryYearService = require('../../../../app/services/licences/supplementary/create-licence-supplementary-year.service.js') +const FetchRemoveReviewLicenceService = require('../../../../app/services/bill-runs/review/fetch-remove-review-licence.service.js') +const ProcessBillRunPostRemove = require('../../../../app/services/bill-runs/review/process-bill-run-post-remove.service.js') +const RemoveReviewLicenceService = require('../../../../app/services/bill-runs/review/remove-review-licence.service.js') + +// Thing under test +const SubmitRemoveService = require('../../../../app/services/bill-runs/review/submit-remove.service.js') + +describe('Bill Runs Review - Submit Remove service', () => { + let createLicenceSupplementaryYearStub + let removeReviewLicence + let removeReviewLicenceStub + let yarStub + + beforeEach(() => { + removeReviewLicence = BillRunsReviewFixture.removeReviewLicence() + + Sinon.stub(FetchRemoveReviewLicenceService, 'go').resolves(removeReviewLicence) + + removeReviewLicenceStub = Sinon + .stub(RemoveReviewLicenceService, 'go') + .withArgs(removeReviewLicence.id) + .resolves() + + createLicenceSupplementaryYearStub = Sinon + .stub(CreateLicenceSupplementaryYearService, 'go') + .withArgs(removeReviewLicence.licenceId, [removeReviewLicence.billRun.toFinancialYearEnding], true) + .resolves() + + yarStub = { flash: Sinon.stub() } + }) + + afterEach(() => { + Sinon.restore() + }) + + describe('when called', () => { + describe('and this is the last licence in the bill run', () => { + beforeEach(() => { + Sinon.stub(ProcessBillRunPostRemove, 'go').withArgs(removeReviewLicence.billRun.id).resolves(true) + }) + + it('removes the review licence, flags the licence for supplementary billing, does not add a flash message, and returns `empty: true`', async () => { + const result = await SubmitRemoveService.go(removeReviewLicence.id, yarStub) + + // Confirm we called the remove review licence service with the correct ID + expect(removeReviewLicenceStub.called).to.be.true() + + // Confirm we flagged the licence for the next two-part tariff supplementary bill run + expect(createLicenceSupplementaryYearStub.called).to.be.true() + + // Check we didn't add the flash message + expect(yarStub.flash.called).to.be.false() + + // Check we return empty true in our result so the controller knows to redirect to the bill runs page + expect(result).to.equal({ + billRunId: '287aeb25-cf11-429d-8c6f-f98f06db021d', + empty: true + }) + }) + }) + + describe('and this is not the last licence in the bill run', () => { + beforeEach(() => { + Sinon.stub(ProcessBillRunPostRemove, 'go').withArgs(removeReviewLicence.billRun.id).resolves(false) + }) + + it('removes the review licence, flags the licence for supplementary billing, adds a flash message, and returns `empty: false`', async () => { + const result = await SubmitRemoveService.go(removeReviewLicence.id, yarStub) + + // Confirm we called the remove review licence service with the correct ID + expect(removeReviewLicenceStub.called).to.be.true() + + // Confirm we flagged the licence for the next two-part tariff supplementary bill run + expect(createLicenceSupplementaryYearStub.called).to.be.true() + + // Check we add the flash message + const [flashType, bannerMessage] = yarStub.flash.args[0] + + expect(flashType).to.equal('banner') + expect(bannerMessage).to.equal('Licence 1/11/11/*11/1111 removed from the bill run.') + + // Check we return empty true in our result so the controller knows to redirect to the bill runs page + expect(result).to.equal({ + billRunId: '287aeb25-cf11-429d-8c6f-f98f06db021d', + empty: false + }) + }) + }) + }) +}) diff --git a/test/services/bill-runs/two-part-tariff/submit-review-bill-run.service.test.js b/test/services/bill-runs/review/submit-review-bill-run.service.test.js similarity index 92% rename from test/services/bill-runs/two-part-tariff/submit-review-bill-run.service.test.js rename to test/services/bill-runs/review/submit-review-bill-run.service.test.js index bfa64cd4ea..985abf4380 100644 --- a/test/services/bill-runs/two-part-tariff/submit-review-bill-run.service.test.js +++ b/test/services/bill-runs/review/submit-review-bill-run.service.test.js @@ -9,9 +9,9 @@ const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() const { expect } = Code // Thing under test -const SubmitReviewBillRunService = require('../../../../app/services/bill-runs/two-part-tariff/submit-review-bill-run.service.js') +const SubmitReviewBillRunService = require('../../../../app/services/bill-runs/review/submit-review-bill-run.service.js') -describe('Submit Review Bill Run Service', () => { +describe('Bill Runs Review - Submit Review Bill Run Service', () => { const billRunId = '27dad88a-6b3c-438b-a25f-f1483e7e12a0' let yarStub diff --git a/test/services/bill-runs/review/submit-review-licence.service.test.js b/test/services/bill-runs/review/submit-review-licence.service.test.js new file mode 100644 index 0000000000..a3be9a2384 --- /dev/null +++ b/test/services/bill-runs/review/submit-review-licence.service.test.js @@ -0,0 +1,111 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const BillRunsReviewFixture = require('../../../fixtures/bill-runs-review.fixture.js') +const ReviewLicenceModel = require('../../../../app/models/review-licence.model.js') + +// Things we need to stub +const FetchReviewLicenceService = require('../../../../app/services/bill-runs/review/fetch-review-licence.service.js') + +// Thing under test +const SubmitReviewLicenceService = require('../../../../app/services/bill-runs/review/submit-review-licence.service.js') + +describe('Bill Runs Review - Submit Review Licence Service', () => { + let payload + let patchStub + let reviewLicence + let yarStub + + beforeEach(async () => { + reviewLicence = BillRunsReviewFixture.reviewLicence() + + Sinon.stub(FetchReviewLicenceService, 'go').resolves(reviewLicence) + + patchStub = Sinon.stub().resolves() + Sinon.stub(ReviewLicenceModel, 'query').returns({ + findById: Sinon.stub().withArgs(reviewLicence.id).returnsThis(), + patch: patchStub + }) + + yarStub = { flash: Sinon.stub() } + }) + + afterEach(() => { + Sinon.restore() + }) + + describe('when called', () => { + describe('and the user is updating the status', () => { + beforeEach(() => { + payload = { 'licence-status': 'ready' } + }) + + it('sets a flash message and updates the status of the review licence', async () => { + await SubmitReviewLicenceService.go(reviewLicence.id, yarStub, payload) + + // Check we save the status change + const [patchObject] = patchStub.args[0] + + expect(patchObject).to.equal({ status: 'ready' }) + + // Check we add the flash message + const [flashType, bannerMessage] = yarStub.flash.args[0] + + expect(flashType).to.equal('banner') + expect(bannerMessage).to.equal('Licence changed to ready.') + }) + }) + + describe('and the user is updating the progress', () => { + describe('to mark it as "in progress"', () => { + beforeEach(() => { + payload = { 'mark-progress': 'mark' } + }) + + it('sets a flash message and updates the progress of the review licence', async () => { + await SubmitReviewLicenceService.go(reviewLicence.id, yarStub, payload) + + // Check we save the status change + const [patchObject] = patchStub.args[0] + + expect(patchObject).to.equal({ progress: true }) + + // Check we add the flash message + const [flashType, bannerMessage] = yarStub.flash.args[0] + + expect(flashType).to.equal('banner') + expect(bannerMessage).to.equal('This licence has been marked.') + }) + }) + + describe('to unmark it', () => { + beforeEach(() => { + payload = { 'mark-progress': 'unmark' } + }) + + it('sets a flash message and updates the progress of the review licence', async () => { + await SubmitReviewLicenceService.go(reviewLicence.id, yarStub, payload) + + // Check we save the status change + const [patchObject] = patchStub.args[0] + + expect(patchObject).to.equal({ progress: false }) + + // Check we add the flash message + const [flashType, bannerMessage] = yarStub.flash.args[0] + + expect(flashType).to.equal('banner') + expect(bannerMessage).to.equal('The progress mark for this licence has been removed.') + }) + }) + }) + }) +}) diff --git a/test/services/bill-runs/two-part-tariff/amend-adjustment-factor.service.test.js b/test/services/bill-runs/two-part-tariff/amend-adjustment-factor.service.test.js deleted file mode 100644 index 2e8e7e7636..0000000000 --- a/test/services/bill-runs/two-part-tariff/amend-adjustment-factor.service.test.js +++ /dev/null @@ -1,91 +0,0 @@ -'use strict' - -// Test framework dependencies -const Lab = require('@hapi/lab') -const Code = require('@hapi/code') -const Sinon = require('sinon') - -const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() -const { expect } = Code - -// Things we need to stub -const FetchReviewChargeReferenceService = require('../../../../app/services/bill-runs/two-part-tariff/fetch-review-charge-reference.service.js') - -// Thing under test -const AmendAdjustmentFactorService = require('../../../../app/services/bill-runs/two-part-tariff/amend-adjustment-factor.service.js') - -describe('Amend Adjustment Factor Service', () => { - afterEach(() => { - Sinon.restore() - }) - - describe('when given a billRun, licence and a reviewChargeReferenceId', () => { - const reviewChargeReferenceId = '2c80bd22-a005-4cf4-a2a2-73812a9861de' - const billRunId = 'cc4bbb18-0d6a-4254-ac2c-7409de814d7e' - const licenceId = '9a8a148d-b71e-463c-bea8-bc5e0a5d95e2' - - beforeEach(() => { - Sinon.stub(FetchReviewChargeReferenceService, 'go').resolves({ - billRun: _billRun(), - reviewChargeReference: _reviewChargeReferenceData() - }) - }) - - it('will fetch the charge reference data and return it once formatted by the presenter', async () => { - const result = await AmendAdjustmentFactorService.go(billRunId, licenceId, reviewChargeReferenceId) - - // NOTE: The service mainly just regurgitates what the AmendAdjustmentFactorPresenter returns. So, we don't - // diligently check each property of the result because we know this will have been covered by the - // AmendAdjustmentFactorPresenter - expect(FetchReviewChargeReferenceService.go.called).to.be.true() - expect(result.billRunId).to.equal('cc4bbb18-0d6a-4254-ac2c-7409de814d7e') - expect(result.licenceId).to.equal('9a8a148d-b71e-463c-bea8-bc5e0a5d95e2') - expect(result.financialYear).to.equal('2022 to 2023') - expect(result.chargeReference.description).to.equal( - 'High loss, non-tidal, restricted water, greater than 15 up to and including 50 ML/yr, Tier 2 model' - ) - }) - }) -}) - -function _billRun () { - return { - id: 'cc4bbb18-0d6a-4254-ac2c-7409de814d7e', - toFinancialYearEnding: 2023 - } -} - -function _reviewChargeReferenceData () { - return { - id: '89eebffa-28a2-489d-b93a-c0f02a2bdbdd', - reviewChargeVersionId: '4940eaca-8cf1-410b-8a89-faf1faa8081b', - chargeReferenceId: '4e7f1824-3680-4df0-806f-c6d651ba4771', - aggregate: 1, - createdAt: new Date('2024-05-01'), - updatedAt: new Date('2024-05-01'), - amendedAggregate: 1, - chargeAdjustment: 1, - amendedChargeAdjustment: 1, - abatementAgreement: 1, - winterDiscount: false, - twoPartTariffAgreement: true, - canalAndRiverTrustAgreement: false, - authorisedVolume: 32, - amendedAuthorisedVolume: 32, - reviewChargeVersion: { - chargePeriodStartDate: new Date('2022-04-01'), - chargePeriodEndDate: new Date('2023-03-31') - }, - chargeReference: { - volume: 32, - chargeCategoryId: 'c037ad9a-d3b4-4b1b-8ac9-1cd2b46d152f', - supportedSourceName: null, - waterCompanyCharge: null, - chargeCategory: { - reference: '4.6.12', - shortDescription: 'High loss, non-tidal, restricted water, greater than 15 up to and including 50 ML/yr, Tier 2 model' - } - }, - reviewChargeElements: [{ amendedAllocated: 0.00018 }] - } -} diff --git a/test/services/bill-runs/two-part-tariff/amend-authorised-volume.service.test.js b/test/services/bill-runs/two-part-tariff/amend-authorised-volume.service.test.js deleted file mode 100644 index 623c99df66..0000000000 --- a/test/services/bill-runs/two-part-tariff/amend-authorised-volume.service.test.js +++ /dev/null @@ -1,78 +0,0 @@ -'use strict' - -// Test framework dependencies -const Lab = require('@hapi/lab') -const Code = require('@hapi/code') -const Sinon = require('sinon') - -const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() -const { expect } = Code - -// Things we need to stub -const FetchAuthorisedVolumeService = require('../../../../app/services/bill-runs/two-part-tariff/fetch-authorised-volume.service.js') - -// Thing under test -const AmendAuthorisedVolumeService = require('../../../../app/services/bill-runs/two-part-tariff/amend-authorised-volume.service.js') - -describe('Amend Authorised Volume Service', () => { - afterEach(() => { - Sinon.restore() - }) - - describe('when given a billRun, licence, and a reviewChargeReferenceId', () => { - const reviewChargeReferenceId = '2c80bd22-a005-4cf4-a2a2-73812a9861de' - const billRunId = 'cc4bbb18-0d6a-4254-ac2c-7409de814d7e' - const licenceId = '9a8a148d-b71e-463c-bea8-bc5e0a5d95e2' - - beforeEach(() => { - Sinon.stub(FetchAuthorisedVolumeService, 'go').resolves({ - billRun: _billRun(), - reviewChargeReference: _reviewChargeReferenceData() - }) - }) - - it('will fetch the charge reference data and return it once formatted by the presenter', async () => { - const result = await AmendAuthorisedVolumeService.go(billRunId, licenceId, reviewChargeReferenceId) - - // NOTE: The service mainly just regurgitates what the AmendAuthorisedVolumePresenter returns. So, we don't - // diligently check each property of the result because we know this will have been covered by the - // AmendAuthorisedVolumePresenter tests - expect(FetchAuthorisedVolumeService.go.called).to.be.true() - expect(result.billRunId).to.equal('cc4bbb18-0d6a-4254-ac2c-7409de814d7e') - expect(result.licenceId).to.equal('9a8a148d-b71e-463c-bea8-bc5e0a5d95e2') - expect(result.financialYear).to.equal('2022 to 2023') - expect(result.chargeReference.description).to.equal( - 'Medium loss, non-tidal, greater than 83 up to and including 142 ML/yr' - ) - }) - }) -}) - -function _billRun () { - return { - id: 'cc4bbb18-0d6a-4254-ac2c-7409de814d7e', - toFinancialYearEnding: 2023 - } -} - -function _reviewChargeReferenceData () { - return { - id: '6b3d11f2-d361-4eaa-bce2-5561283bd023', - amendedAuthorisedVolume: 25.5, - chargeReference: { - chargeCategoryId: 'b4354db6-6699-4987-b4c8-d53ac2bf2250', - chargeCategory: { - shortDescription: 'Medium loss, non-tidal, greater than 83 up to and including 142 ML/yr', - minVolume: 83, - maxVolume: 142 - } - }, - reviewChargeElements: [{ - amendedAllocated: 15 - }], - reviewChargeVersion: { - chargePeriodStartDate: new Date('2022-04-01'), - chargePeriodEndDate: new Date('2022-06-01') - } - } -} diff --git a/test/services/bill-runs/two-part-tariff/amend-billable-returns.service.test.js b/test/services/bill-runs/two-part-tariff/amend-billable-returns.service.test.js deleted file mode 100644 index e36f8aad72..0000000000 --- a/test/services/bill-runs/two-part-tariff/amend-billable-returns.service.test.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict' - -// Test framework dependencies -const Lab = require('@hapi/lab') -const Code = require('@hapi/code') -const Sinon = require('sinon') - -const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() -const { expect } = Code - -// Things we need to stub -const AmendBillableReturnsPresenter = require('../../../../app/presenters/bill-runs/two-part-tariff/amend-billable-returns.presenter.js') -const FetchMatchDetailsService = require('../../../../app/services/bill-runs/two-part-tariff/fetch-match-details.service.js') - -// Thing under test -const AmendBillableReturnsService = require('../../../../app/services/bill-runs/two-part-tariff/amend-billable-returns.service.js') - -describe('Amend Billable Returns Service', () => { - afterEach(() => { - Sinon.restore() - }) - - describe('when given a billRun, licence and chargeElement ID', () => { - const reviewChargeElementId = '2c80bd22-a005-4cf4-a2a2-73812a9861de' - const billRunId = 'cc4bbb18-0d6a-4254-ac2c-7409de814d7e' - const licenceId = '9a8a148d-b71e-463c-bea8-bc5e0a5d95e2' - - beforeEach(() => { - Sinon.stub(FetchMatchDetailsService, 'go').resolves({ - billRun: 'bill run data', - reviewChargeElement: 'charge element details' - }) - - Sinon.stub(AmendBillableReturnsPresenter, 'go').resolves('page data') - }) - - it('will fetch the charge element data and return it once formatted by the presenter', async () => { - const result = await AmendBillableReturnsService.go(billRunId, licenceId, reviewChargeElementId) - - expect(FetchMatchDetailsService.go.called).to.be.true() - expect(AmendBillableReturnsPresenter.go.called).to.be.true() - expect(result).to.equal('page data') - }) - }) -}) diff --git a/test/services/bill-runs/two-part-tariff/calculate-charge.service.test.js b/test/services/bill-runs/two-part-tariff/calculate-charge.service.test.js deleted file mode 100644 index 7cf73f9786..0000000000 --- a/test/services/bill-runs/two-part-tariff/calculate-charge.service.test.js +++ /dev/null @@ -1,181 +0,0 @@ -'use strict' - -// Test framework dependencies -const Lab = require('@hapi/lab') -const Code = require('@hapi/code') -const Sinon = require('sinon') - -const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() -const { expect } = Code - -// Test helpers -const ChargeCategoryHelper = require('../../../support/helpers/charge-category.helper.js') -const ChargeReferenceHelper = require('../../../support/helpers/charge-reference.helper.js') -const LicenceHelper = require('../../../support/helpers/licence.helper.js') -const ReviewChargeElementHelper = require('../../../support/helpers/review-charge-element.helper.js') -const ReviewChargeReferenceHelper = require('../../../support/helpers/review-charge-reference.helper.js') -const ReviewChargeVersionHelper = require('../../../support/helpers/review-charge-version.helper.js') - -// Things we need to stub -const CalculateChargeRequest = require('../../../../app/requests/charging-module/calculate-charge.request.js') - -// Thing under test -const CalculateChargeService = require('../../../../app/services/bill-runs/two-part-tariff/calculate-charge.service.js') - -describe('Calculate Charge service', () => { - let calculateChargeRequestStub - let chargeCategoryReference - let licenceId - let reviewChargeReferenceId - let yarStub - - beforeEach(async () => { - const testLicence = await LicenceHelper.add({ waterUndertaker: true }) - - licenceId = testLicence.id - - const testChargeCategory = ChargeCategoryHelper.select() - - chargeCategoryReference = testChargeCategory.reference - - const { id: chargeReferenceId } = await ChargeReferenceHelper.add({ - additionalCharges: { isSupplyPublicWater: true, supportedSource: { name: 'TestSource' } }, - chargeCategoryId: testChargeCategory.id - }) - - const { id: reviewChargeVersionId } = await ReviewChargeVersionHelper.add({ - chargePeriodStartDate: new Date('2022-04-01'), - chargePeriodEndDate: new Date('2023-03-31') - }) - - const testReviewChargeReference = await ReviewChargeReferenceHelper.add({ - reviewChargeVersionId, - chargeReferenceId - }) - - reviewChargeReferenceId = testReviewChargeReference.id - - await ReviewChargeElementHelper.add({ reviewChargeReferenceId, amendedAllocated: 25 }) - await ReviewChargeElementHelper.add({ reviewChargeReferenceId, amendedAllocated: 25 }) - - yarStub = { flash: Sinon.stub() } - }) - - afterEach(() => { - Sinon.restore() - }) - - describe('when the charge can be successfully calculated', () => { - beforeEach(async () => { - calculateChargeRequestStub = Sinon.stub(CalculateChargeRequest, 'send').resolves(_calculateChargeRequestStub()) - }) - - it('sends a valid transaction to the charging module to calculate the charge', async () => { - await CalculateChargeService.go(licenceId, reviewChargeReferenceId, yarStub) - - const [testTransactionData] = calculateChargeRequestStub.args[0] - - expect(testTransactionData).to.equal({ - abatementFactor: 1, - actualVolume: 50, - adjustmentFactor: 1, - aggregateProportion: 1, - authorisedDays: 0, - authorisedVolume: 50, - billableDays: 0, - chargeCategoryCode: chargeCategoryReference, - compensationCharge: false, - credit: false, - loss: 'low', - periodStart: '01-APR-2022', - periodEnd: '31-MAR-2023', - ruleset: 'sroc', - section127Agreement: true, - section130Agreement: false, - supportedSource: true, - supportedSourceName: 'TestSource', - twoPartTariff: true, - waterCompanyCharge: true, - waterUndertaker: true, - winterOnly: false - }) - }) - - it('will generate a banner message to display the example charge', async () => { - await CalculateChargeService.go(licenceId, reviewChargeReferenceId, yarStub) - const [flashType, bannerMessage] = yarStub.flash.args[0] - - expect(yarStub.flash.called).to.be.true() - expect(flashType).to.equal('charge') - expect(bannerMessage).to.equal('Based on this information the example charge is £256.48.') - }) - }) - - describe('when the charge is not calculated because the request to calculate the charge fails', () => { - beforeEach(async () => { - calculateChargeRequestStub = Sinon.stub(CalculateChargeRequest, 'send').resolves({ succeeded: false }) - }) - - it('sends a valid transaction to the charging module to calculate the charge', async () => { - await CalculateChargeService.go(licenceId, reviewChargeReferenceId, yarStub) - - const [testTransactionData] = calculateChargeRequestStub.args[0] - - expect(testTransactionData).to.equal({ - abatementFactor: 1, - actualVolume: 50, - adjustmentFactor: 1, - aggregateProportion: 1, - authorisedDays: 0, - authorisedVolume: 50, - billableDays: 0, - chargeCategoryCode: chargeCategoryReference, - compensationCharge: false, - credit: false, - loss: 'low', - periodStart: '01-APR-2022', - periodEnd: '31-MAR-2023', - ruleset: 'sroc', - section127Agreement: true, - section130Agreement: false, - supportedSource: true, - supportedSourceName: 'TestSource', - twoPartTariff: true, - waterCompanyCharge: true, - waterUndertaker: true, - winterOnly: false - }) - }) - - it('will not generate a banner message', async () => { - await CalculateChargeService.go(licenceId, reviewChargeReferenceId, yarStub) - - expect(yarStub.flash.called).to.be.false() - }) - }) -}) - -function _calculateChargeRequestStub () { - return { - succeeded: true, - response: { - info: { - gitCommit: '273604040a47e0977b0579a0fef0f09726d95e39', - dockerTag: 'ghcr.io/defra/sroc-charging-module-api:v0.19.0' - }, - statusCode: 200, - body: { - calculation: { - chargeValue: 25648, - baseCharge: 51300, - waterCompanyChargeValue: 800, - supportedSourceValue: 3500, - winterOnlyFactor: null, - section130Factor: null, - section127Factor: 0.5, - compensationChargePercent: null - } - } - } - } -} diff --git a/test/services/bill-runs/two-part-tariff/charge-reference-details.service.test.js b/test/services/bill-runs/two-part-tariff/charge-reference-details.service.test.js deleted file mode 100644 index d03372cdbd..0000000000 --- a/test/services/bill-runs/two-part-tariff/charge-reference-details.service.test.js +++ /dev/null @@ -1,99 +0,0 @@ -'use strict' - -// Test framework dependencies -const Lab = require('@hapi/lab') -const Code = require('@hapi/code') -const Sinon = require('sinon') - -const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() -const { expect } = Code - -// Things we need to stub -const FetchReviewChargeReferenceService = require('../../../../app/services/bill-runs/two-part-tariff/fetch-review-charge-reference.service.js') - -// Thing under test -const ChargeReferenceDetailsService = require('../../../../app/services/bill-runs/two-part-tariff/charge-reference-details.service.js') - -describe('Charge Reference Details Service', () => { - afterEach(() => { - Sinon.restore() - }) - - describe('when given a billRun, licence and chargeReference ID', () => { - const reviewChargeReferenceId = '2c80bd22-a005-4cf4-a2a2-73812a9861de' - const billRunId = 'cc4bbb18-0d6a-4254-ac2c-7409de814d7e' - const licenceId = '9a8a148d-b71e-463c-bea8-bc5e0a5d95e2' - - let yarStub - - beforeEach(() => { - Sinon.stub(FetchReviewChargeReferenceService, 'go').resolves({ - billRun: _billRun(), - reviewChargeReference: _reviewChargeReferenceData() - }) - - yarStub = { - flash: Sinon.stub() - .onFirstCall().returns(['Adjustment updated']) - .onSecondCall().returns(['Based on this information the example charge is £256.48.']) - } - }) - - it('will fetch the charge reference data and return it once formatted by the presenter', async () => { - const result = await ChargeReferenceDetailsService.go(billRunId, licenceId, reviewChargeReferenceId, yarStub) - - expect(result.bannerMessage).to.equal('Adjustment updated') - expect(result.chargeMessage).to.equal('Based on this information the example charge is £256.48.') - - // NOTE: The service mainly just regurgitates what the ChargeReferencePresenter returns. So, we don't diligently - // check each property of the result because we know this will have been covered by the ChargeReferencePresenter - expect(FetchReviewChargeReferenceService.go.called).to.be.true() - expect(result.billRunId).to.equal('cc4bbb18-0d6a-4254-ac2c-7409de814d7e') - expect(result.licenceId).to.equal('9a8a148d-b71e-463c-bea8-bc5e0a5d95e2') - expect(result.financialYear).to.equal('2022 to 2023') - expect(result.chargeReference.reference).to.equal('4.6.12') - }) - }) -}) - -function _billRun () { - return { - id: 'cc4bbb18-0d6a-4254-ac2c-7409de814d7e', - toFinancialYearEnding: 2023 - } -} - -function _reviewChargeReferenceData () { - return { - id: '89eebffa-28a2-489d-b93a-c0f02a2bdbdd', - reviewChargeVersionId: '4940eaca-8cf1-410b-8a89-faf1faa8081b', - chargeReferenceId: '4e7f1824-3680-4df0-806f-c6d651ba4771', - aggregate: 1, - createdAt: new Date('2024-05-01'), - updatedAt: new Date('2024-05-01'), - amendedAggregate: 1, - chargeAdjustment: 1, - amendedChargeAdjustment: 1, - abatementAgreement: 1, - winterDiscount: false, - twoPartTariffAgreement: true, - canalAndRiverTrustAgreement: false, - authorisedVolume: 32, - amendedAuthorisedVolume: 32, - reviewChargeVersion: { - chargePeriodStartDate: new Date('2022-04-01'), - chargePeriodEndDate: new Date('2023-03-31') - }, - chargeReference: { - volume: 32, - chargeCategoryId: 'c037ad9a-d3b4-4b1b-8ac9-1cd2b46d152f', - supportedSourceName: null, - waterCompanyCharge: null, - chargeCategory: { - reference: '4.6.12', - shortDescription: 'High loss, non-tidal, restricted water, greater than 15 up to and including 50 ML/yr, Tier 2 model' - } - }, - reviewChargeElements: [{ amendedAllocated: 0.00018 }] - } -} diff --git a/test/services/bill-runs/two-part-tariff/fetch-authorised-volume.service.test.js b/test/services/bill-runs/two-part-tariff/fetch-authorised-volume.service.test.js deleted file mode 100644 index 2b47648da8..0000000000 --- a/test/services/bill-runs/two-part-tariff/fetch-authorised-volume.service.test.js +++ /dev/null @@ -1,86 +0,0 @@ -'use strict' - -// Test framework dependencies -const Lab = require('@hapi/lab') -const Code = require('@hapi/code') -const Sinon = require('sinon') - -const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() -const { expect } = Code - -// Test helpers -const BillRunHelper = require('../../../support/helpers/bill-run.helper.js') -const ChargeCategoryHelper = require('../../../support/helpers/charge-category.helper.js') -const ChargeReferenceHelper = require('../../../support/helpers/charge-reference.helper.js') -const ReviewChargeElementHelper = require('../../../support/helpers/review-charge-element.helper.js') -const ReviewChargeReferenceHelper = require('../../../support/helpers/review-charge-reference.helper.js') -const ReviewChargeVersionHelper = require('../../../support/helpers/review-charge-version.helper.js') - -// Thing under test -const FetchAuthorisedVolumeService = require('../../../../app/services/bill-runs/two-part-tariff/fetch-authorised-volume.service.js') - -describe('Fetch Authorised Volume service', () => { - afterEach(() => { - Sinon.restore() - }) - - describe('when there is a valid bull run', () => { - let billRun - - beforeEach(async () => { - billRun = await BillRunHelper.add() - }) - - describe('and a valid review charge reference', () => { - let reviewChargeReference - let chargeReference - let reviewChargeVersion - let reviewChargeElement - let chargeCategory - - beforeEach(async () => { - reviewChargeVersion = await ReviewChargeVersionHelper.add() - chargeCategory = ChargeCategoryHelper.select() - chargeReference = await ChargeReferenceHelper.add({ chargeCategoryId: chargeCategory.id }) - reviewChargeReference = await ReviewChargeReferenceHelper.add({ - reviewChargeVersionId: reviewChargeVersion.id, - chargeReferenceId: chargeReference.id - }) - reviewChargeElement = await ReviewChargeElementHelper.add({ reviewChargeReferenceId: reviewChargeReference.id }) - }) - - it('returns details of the bill run', async () => { - const result = await FetchAuthorisedVolumeService.go(billRun.id, reviewChargeReference.id) - - expect(result.billRun).to.equal({ - id: billRun.id, - toFinancialYearEnding: billRun.toFinancialYearEnding - }) - }) - - it('returns details of the review charge reference', async () => { - const result = await FetchAuthorisedVolumeService.go(billRun.id, reviewChargeReference.id) - - expect(result.reviewChargeReference).to.equal({ - id: reviewChargeReference.id, - amendedAuthorisedVolume: reviewChargeReference.amendedAuthorisedVolume, - chargeReference: { - chargeCategoryId: chargeCategory.id, - chargeCategory: { - shortDescription: chargeCategory.shortDescription, - maxVolume: chargeCategory.maxVolume, - minVolume: chargeCategory.minVolume - } - }, - reviewChargeElements: [{ - amendedAllocated: reviewChargeElement.amendedAllocated - }], - reviewChargeVersion: { - chargePeriodStartDate: reviewChargeVersion.chargePeriodStartDate, - chargePeriodEndDate: reviewChargeVersion.chargePeriodEndDate - } - }) - }) - }) - }) -}) diff --git a/test/services/bill-runs/two-part-tariff/fetch-match-details.service.test.js b/test/services/bill-runs/two-part-tariff/fetch-match-details.service.test.js deleted file mode 100644 index 8ccc5d15af..0000000000 --- a/test/services/bill-runs/two-part-tariff/fetch-match-details.service.test.js +++ /dev/null @@ -1,190 +0,0 @@ -'use strict' - -// Test framework dependencies -const Lab = require('@hapi/lab') -const Code = require('@hapi/code') -const Sinon = require('sinon') - -const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() -const { expect } = Code - -// Test helpers -const BillRunHelper = require('../../../support/helpers/bill-run.helper.js') -const ChargeElementHelper = require('../../../support/helpers/charge-element.helper.js') -const ChargeReferenceHelper = require('../../../support/helpers/charge-reference.helper.js') -const ReturnLogHelper = require('../../../support/helpers/return-log.helper.js') -const ReviewChargeElementHelper = require('../../../support/helpers/review-charge-element.helper.js') -const ReviewChargeElementReturnHelper = require('../../../support/helpers/review-charge-element-return.helper.js') -const ReviewChargeReferenceHelper = require('../../../support/helpers/review-charge-reference.helper.js') -const ReviewChargeVersionHelper = require('../../../support/helpers/review-charge-version.helper.js') -const ReviewReturnHelper = require('../../../support/helpers/review-return.helper.js') - -// Thing under test -const FetchMatchDetailsService = require('../../../../app/services/bill-runs/two-part-tariff/fetch-match-details.service.js') - -describe('Fetch Match Details service', () => { - afterEach(() => { - Sinon.restore() - }) - - describe('when there is a valid bill run', () => { - let billRun - - beforeEach(async () => { - billRun = await BillRunHelper.add() - }) - - describe('and a valid review charge element', () => { - let reviewChargeElement - let chargeElement - let chargeReference - let reviewChargeVersion - let reviewChargeReference - let returnLog - - beforeEach(async () => { - reviewChargeVersion = await ReviewChargeVersionHelper.add() - chargeReference = await ChargeReferenceHelper.add() - reviewChargeReference = await ReviewChargeReferenceHelper.add( - { reviewChargeVersionId: reviewChargeVersion.id, chargeReferenceId: chargeReference.id } - ) - - chargeElement = await ChargeElementHelper.add({ chargeReferenceId: reviewChargeReference.chargeReferenceId }) - reviewChargeElement = await ReviewChargeElementHelper.add( - { reviewChargeReferenceId: reviewChargeReference.id, chargeElementId: chargeElement.id } - ) - }) - - describe('that has a matching return', () => { - let reviewReturn - - beforeEach(async () => { - const metadata = { - nald: { - periodEndDay: 30, - periodEndMonth: 9, - periodStartDay: 1, - periodStartMonth: 4 - } - } - - returnLog = await ReturnLogHelper.add({ metadata }) - reviewReturn = await ReviewReturnHelper.add({ returnId: returnLog.id }) - - await ReviewChargeElementReturnHelper.add({ - reviewChargeElementId: reviewChargeElement.id, - reviewReturnId: reviewReturn.id - }) - }) - - it('returns details of the bill run', async () => { - const result = await FetchMatchDetailsService.go(billRun.id, reviewChargeElement.id) - - expect(result.billRun).to.equal({ - id: billRun.id, - fromFinancialYearEnding: billRun.fromFinancialYearEnding, - toFinancialYearEnding: billRun.toFinancialYearEnding - }) - }) - - it('returns details of the charge element and its matched returns', async () => { - const result = await FetchMatchDetailsService.go(billRun.id, reviewChargeElement.id) - - expect(result.reviewChargeElement).to.equal({ - id: reviewChargeElement.id, - reviewChargeReferenceId: reviewChargeReference.id, - chargeElementId: chargeElement.id, - allocated: reviewChargeElement.allocated, - amendedAllocated: reviewChargeElement.amendedAllocated, - chargeDatesOverlap: reviewChargeElement.chargeDatesOverlap, - issues: reviewChargeElement.issues, - status: reviewChargeElement.status, - createdAt: reviewChargeElement.createdAt, - updatedAt: reviewChargeElement.updatedAt, - reviewReturns: [ - { - id: reviewReturn.id, - reviewLicenceId: reviewReturn.reviewLicenceId, - returnId: reviewReturn.returnId, - returnReference: reviewReturn.returnReference, - quantity: reviewReturn.quantity, - allocated: reviewReturn.allocated, - underQuery: reviewReturn.underQuery, - returnStatus: reviewReturn.returnStatus, - nilReturn: reviewReturn.nilReturn, - abstractionOutsidePeriod: reviewReturn.abstractionOutsidePeriod, - receivedDate: reviewReturn.receivedDate, - dueDate: reviewReturn.dueDate, - purposes: {}, - description: reviewReturn.description, - startDate: reviewReturn.startDate, - endDate: reviewReturn.endDate, - issues: reviewReturn.issues, - createdAt: reviewReturn.createdAt, - updatedAt: reviewReturn.updatedAt, - returnLog: { - periodEndDay: returnLog.metadata.nald.periodEndDay, - periodEndMonth: returnLog.metadata.nald.periodEndMonth, - periodStartDay: returnLog.metadata.nald.periodStartDay, - periodStartMonth: returnLog.metadata.nald.periodStartMonth - } - } - ], - chargeElement: { - description: chargeElement.description, - abstractionPeriodStartDay: chargeElement.abstractionPeriodStartDay, - abstractionPeriodStartMonth: chargeElement.abstractionPeriodStartMonth, - abstractionPeriodEndDay: chargeElement.abstractionPeriodEndDay, - abstractionPeriodEndMonth: chargeElement.abstractionPeriodEndMonth, - authorisedAnnualQuantity: chargeElement.authorisedAnnualQuantity - }, - reviewChargeReference: { - id: reviewChargeReference.id, - amendedAuthorisedVolume: reviewChargeReference.amendedAuthorisedVolume, - reviewChargeVersion: { - chargePeriodStartDate: reviewChargeVersion.chargePeriodStartDate, - chargePeriodEndDate: reviewChargeVersion.chargePeriodEndDate - } - } - }) - }) - }) - - describe('with a charge element that did not match to a return', () => { - it('does not return any matching return details', async () => { - const result = await FetchMatchDetailsService.go(billRun.id, reviewChargeElement.id) - - expect(result.reviewChargeElement).to.equal({ - id: reviewChargeElement.id, - reviewChargeReferenceId: reviewChargeReference.id, - chargeElementId: chargeElement.id, - allocated: reviewChargeElement.allocated, - amendedAllocated: reviewChargeElement.amendedAllocated, - chargeDatesOverlap: reviewChargeElement.chargeDatesOverlap, - issues: reviewChargeElement.issues, - status: reviewChargeElement.status, - createdAt: reviewChargeElement.createdAt, - updatedAt: reviewChargeElement.updatedAt, - reviewReturns: [], - chargeElement: { - description: chargeElement.description, - abstractionPeriodStartDay: chargeElement.abstractionPeriodStartDay, - abstractionPeriodStartMonth: chargeElement.abstractionPeriodStartMonth, - abstractionPeriodEndDay: chargeElement.abstractionPeriodEndDay, - abstractionPeriodEndMonth: chargeElement.abstractionPeriodEndMonth, - authorisedAnnualQuantity: chargeElement.authorisedAnnualQuantity - }, - reviewChargeReference: { - id: reviewChargeReference.id, - amendedAuthorisedVolume: reviewChargeReference.amendedAuthorisedVolume, - reviewChargeVersion: { - chargePeriodStartDate: reviewChargeVersion.chargePeriodStartDate, - chargePeriodEndDate: reviewChargeVersion.chargePeriodEndDate - } - } - }) - }) - }) - }) - }) -}) diff --git a/test/services/bill-runs/two-part-tariff/fetch-review-charge-reference.service.test.js b/test/services/bill-runs/two-part-tariff/fetch-review-charge-reference.service.test.js deleted file mode 100644 index b303c15461..0000000000 --- a/test/services/bill-runs/two-part-tariff/fetch-review-charge-reference.service.test.js +++ /dev/null @@ -1,109 +0,0 @@ -'use strict' - -// Test framework dependencies -const Lab = require('@hapi/lab') -const Code = require('@hapi/code') -const Sinon = require('sinon') - -const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() -const { expect } = Code - -// Test helpers -const BillRunHelper = require('../../../support/helpers/bill-run.helper.js') -const ChargeCategoryHelper = require('../../../support/helpers/charge-category.helper.js') -const ChargeReferenceHelper = require('../../../support/helpers/charge-reference.helper.js') -const ReviewChargeElementHelper = require('../../../support/helpers/review-charge-element.helper.js') -const ReviewChargeReferenceHelper = require('../../../support/helpers/review-charge-reference.helper.js') -const ReviewChargeVersionHelper = require('../../../support/helpers/review-charge-version.helper.js') - -// Thing under test -const FetchReviewChargeReferenceService = require('../../../../app/services/bill-runs/two-part-tariff/fetch-review-charge-reference.service.js') - -describe('Fetch Review Charge Reference service', () => { - afterEach(() => { - Sinon.restore() - }) - - describe('when there is a valid bill run', () => { - let billRun - - beforeEach(async () => { - billRun = await BillRunHelper.add() - }) - - describe('and a valid review charge reference', () => { - let reviewChargeReference - let chargeReference - let reviewChargeVersion - let reviewChargeElement - let chargeCategory - - beforeEach(async () => { - reviewChargeVersion = await ReviewChargeVersionHelper.add() - chargeCategory = ChargeCategoryHelper.select() - chargeReference = await ChargeReferenceHelper.add({ - chargeCategoryId: chargeCategory.id, - additionalCharges: { - isSupplyPublicWater: true, - supportedSource: { - name: 'Thames' - } - } - }) - reviewChargeReference = await ReviewChargeReferenceHelper.add({ - reviewChargeVersionId: reviewChargeVersion.id, - chargeReferenceId: chargeReference.id - }) - reviewChargeElement = await ReviewChargeElementHelper.add({ reviewChargeReferenceId: reviewChargeReference.id }) - }) - - it('returns details of the bill run', async () => { - const result = await FetchReviewChargeReferenceService.go(billRun.id, reviewChargeReference.id) - - expect(result.billRun).to.equal({ - id: billRun.id, - toFinancialYearEnding: billRun.toFinancialYearEnding - }) - }) - - it('returns details of the review charge reference', async () => { - const result = await FetchReviewChargeReferenceService.go(billRun.id, reviewChargeReference.id) - - expect(result.reviewChargeReference).to.equal({ - id: reviewChargeReference.id, - reviewChargeVersionId: reviewChargeVersion.id, - chargeReferenceId: chargeReference.id, - aggregate: reviewChargeReference.aggregate, - createdAt: reviewChargeReference.createdAt, - updatedAt: reviewChargeReference.updatedAt, - amendedAggregate: reviewChargeReference.amendedAggregate, - chargeAdjustment: reviewChargeReference.chargeAdjustment, - amendedChargeAdjustment: reviewChargeReference.amendedChargeAdjustment, - abatementAgreement: reviewChargeReference.abatementAgreement, - winterDiscount: reviewChargeReference.winterDiscount, - twoPartTariffAgreement: reviewChargeReference.twoPartTariffAgreement, - canalAndRiverTrustAgreement: reviewChargeReference.canalAndRiverTrustAgreement, - authorisedVolume: reviewChargeReference.authorisedVolume, - amendedAuthorisedVolume: reviewChargeReference.amendedAuthorisedVolume, - reviewChargeVersion: { - chargePeriodStartDate: reviewChargeVersion.chargePeriodStartDate, - chargePeriodEndDate: reviewChargeVersion.chargePeriodEndDate - }, - reviewChargeElements: [{ - amendedAllocated: reviewChargeElement.amendedAllocated - }], - chargeReference: { - volume: chargeReference.volume, - chargeCategoryId: chargeCategory.id, - supportedSourceName: chargeReference.additionalCharges.supportedSource.name, - waterCompanyCharge: `${chargeReference.additionalCharges.isSupplyPublicWater}`, - chargeCategory: { - reference: chargeCategory.reference, - shortDescription: chargeCategory.shortDescription - } - } - }) - }) - }) - }) -}) diff --git a/test/services/bill-runs/two-part-tariff/fetch-review-licence-results.service.test.js b/test/services/bill-runs/two-part-tariff/fetch-review-licence-results.service.test.js deleted file mode 100644 index 5afde19bb4..0000000000 --- a/test/services/bill-runs/two-part-tariff/fetch-review-licence-results.service.test.js +++ /dev/null @@ -1,263 +0,0 @@ -'use strict' - -// Test framework dependencies -const Lab = require('@hapi/lab') -const Code = require('@hapi/code') -const Sinon = require('sinon') - -const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() -const { expect } = Code - -// Test helpers -const BillRunHelper = require('../../../support/helpers/bill-run.helper.js') -const ChargeCategoryHelper = require('../../../support/helpers/charge-category.helper.js') -const ChargeElementHelper = require('../../../support/helpers/charge-element.helper.js') -const ChargeReferenceHelper = require('../../../support/helpers/charge-reference.helper.js') -const ChargeVersionHelper = require('../../../support/helpers/charge-version.helper.js') -const LicenceHelper = require('../../../support/helpers/licence.helper.js') -const PurposeHelper = require('../../../support/helpers/purpose.helper.js') -const RegionHelper = require('../../../support/helpers/region.helper.js') -const ReturnLogHelper = require('../../../support/helpers/return-log.helper.js') -const ReviewChargeElementHelper = require('../../../support/helpers/review-charge-element.helper.js') -const ReviewChargeElementReturnHelper = require('../../../support/helpers/review-charge-element-return.helper.js') -const ReviewChargeReferenceHelper = require('../../../support/helpers/review-charge-reference.helper.js') -const ReviewChargeVersionHelper = require('../../../support/helpers/review-charge-version.helper.js') -const ReviewLicenceHelper = require('../../../support/helpers/review-licence.helper.js') -const ReviewReturnHelper = require('../../../support/helpers/review-return.helper.js') - -// Things we need to stub -const BillingAccountModel = require('../../../../app/models/billing-account.model.js') - -// Thing under test -const FetchReviewLicenceResultsService = require('../../../../app/services/bill-runs/two-part-tariff/fetch-review-licence-results.service.js') - -describe('Fetch Review Licence Results Service', () => { - let billRun - let region - - afterEach(() => { - Sinon.restore() - }) - - describe('when there is a valid bill run', () => { - beforeEach(async () => { - region = RegionHelper.select() - billRun = await BillRunHelper.add({ regionId: region.id, batchType: 'two_part_tariff' }) - }) - - describe('and a valid licence that is included in the bill run', () => { - let licence - let reviewLicence - let chargeVersion - let reviewChargeVersion - let chargeReference - let reviewChargeReference - let chargeElement - let reviewChargeElement - let returnLog - let reviewReturn - let purpose - let chargeCategory - - beforeEach(async () => { - licence = await LicenceHelper.add() - reviewLicence = await ReviewLicenceHelper.add({ licenceId: licence.id, billRunId: billRun.id }) - - chargeVersion = await ChargeVersionHelper.add({ licenceId: licence.id, licenceRef: licence.licenceRef }) - reviewChargeVersion = await ReviewChargeVersionHelper.add({ - reviewLicenceId: reviewLicence.id, - chargeVersionId: chargeVersion.id - }) - - chargeCategory = ChargeCategoryHelper.select() - chargeReference = await ChargeReferenceHelper.add({ - chargeVersionId: chargeVersion.id, - chargeCategoryId: chargeCategory.id - }) - reviewChargeReference = await ReviewChargeReferenceHelper.add({ - reviewChargeVersionId: reviewChargeVersion.id, - chargeReferenceId: chargeReference.id - }) - - purpose = PurposeHelper.select() - chargeElement = await ChargeElementHelper.add({ chargeReferenceId: chargeReference.id, purposeId: purpose.id }) - reviewChargeElement = await ReviewChargeElementHelper.add({ - reviewChargeReferenceId: reviewChargeReference.id, - chargeElementId: chargeElement.id - }) - - const metadata = { - nald: { - periodEndDay: 30, - periodEndMonth: 9, - periodStartDay: 1, - periodStartMonth: 4 - } - } - - returnLog = await ReturnLogHelper.add({ licenceRef: licence.licenceRef, metadata }) - reviewReturn = await ReviewReturnHelper.add({ returnId: returnLog.id, reviewLicenceId: reviewLicence.id }) - - await ReviewChargeElementReturnHelper.add({ - reviewChargeElementId: reviewChargeElement.id, - reviewReturnId: reviewReturn.id - }) - - Sinon.stub(BillingAccountModel, 'query').returns({ - findById: Sinon.stub().returnsThis(), - modify: Sinon.stub().resolves([]) - }) - }) - - it('returns details of the bill run', async () => { - const result = await FetchReviewLicenceResultsService.go(billRun.id, licence.id) - - expect(result.billRun).to.equal({ - id: billRun.id, - fromFinancialYearEnding: 2023, - toFinancialYearEnding: 2023, - region: { - displayName: region.displayName - } - }) - }) - - it('returns the licence review data', async () => { - const result = await FetchReviewLicenceResultsService.go(billRun.id, licence.id) - - expect(result.licence).to.equal([{ - id: reviewLicence.id, - billRunId: billRun.id, - licenceId: licence.id, - licenceRef: reviewLicence.licenceRef, - licenceHolder: reviewLicence.licenceHolder, - issues: reviewLicence.issues, - status: reviewLicence.status, - progress: false, - hasReviewStatus: false, - reviewReturns: [{ - id: reviewReturn.id, - reviewLicenceId: reviewReturn.reviewLicenceId, - returnId: reviewReturn.returnId, - returnReference: reviewReturn.returnReference, - quantity: reviewReturn.quantity, - allocated: reviewReturn.allocated, - underQuery: reviewReturn.underQuery, - returnStatus: reviewReturn.returnStatus, - nilReturn: reviewReturn.nilReturn, - abstractionOutsidePeriod: reviewReturn.abstractionOutsidePeriod, - receivedDate: reviewReturn.receivedDate, - dueDate: reviewReturn.dueDate, - purposes: reviewReturn.purposes, - description: reviewReturn.description, - startDate: reviewReturn.startDate, - endDate: reviewReturn.endDate, - issues: reviewReturn.issues, - createdAt: reviewReturn.createdAt, - updatedAt: reviewReturn.updatedAt, - reviewChargeElements: [{ - id: reviewChargeElement.id, - reviewChargeReferenceId: reviewChargeElement.reviewChargeReferenceId, - chargeElementId: reviewChargeElement.chargeElementId, - allocated: reviewChargeElement.allocated, - amendedAllocated: reviewChargeElement.amendedAllocated, - chargeDatesOverlap: reviewChargeElement.chargeDatesOverlap, - issues: reviewChargeElement.issues, - status: reviewChargeElement.status, - createdAt: reviewChargeElement.createdAt, - updatedAt: reviewChargeElement.updatedAt - }], - returnLog: { - periodEndDay: returnLog.metadata.nald.periodEndDay, - periodEndMonth: returnLog.metadata.nald.periodEndMonth, - periodStartDay: returnLog.metadata.nald.periodStartDay, - periodStartMonth: returnLog.metadata.nald.periodStartMonth - } - }], - reviewChargeVersions: [{ - id: reviewChargeVersion.id, - reviewLicenceId: reviewLicence.id, - chargeVersionId: chargeVersion.id, - changeReason: reviewChargeVersion.changeReason, - chargePeriodStartDate: reviewChargeVersion.chargePeriodStartDate, - chargePeriodEndDate: reviewChargeVersion.chargePeriodEndDate, - createdAt: reviewChargeVersion.createdAt, - updatedAt: reviewChargeVersion.updatedAt, - reviewChargeReferences: [{ - id: reviewChargeReference.id, - reviewChargeVersionId: reviewChargeVersion.id, - chargeReferenceId: reviewChargeReference.chargeReferenceId, - aggregate: reviewChargeReference.aggregate, - amendedAggregate: reviewChargeReference.amendedAggregate, - chargeAdjustment: reviewChargeReference.chargeAdjustment, - amendedChargeAdjustment: reviewChargeReference.amendedChargeAdjustment, - canalAndRiverTrustAgreement: reviewChargeReference.canalAndRiverTrustAgreement, - abatementAgreement: reviewChargeReference.abatementAgreement, - winterDiscount: reviewChargeReference.winterDiscount, - twoPartTariffAgreement: reviewChargeReference.twoPartTariffAgreement, - authorisedVolume: reviewChargeReference.authorisedVolume, - amendedAuthorisedVolume: reviewChargeReference.amendedAuthorisedVolume, - createdAt: reviewChargeReference.createdAt, - updatedAt: reviewChargeReference.updatedAt, - chargeReference: { - chargeCategoryId: chargeReference.chargeCategoryId, - chargeCategory: { - reference: chargeCategory.reference, - shortDescription: chargeCategory.shortDescription - } - }, - reviewChargeElements: [{ - id: reviewChargeElement.id, - reviewChargeReferenceId: reviewChargeElement.reviewChargeReferenceId, - chargeElementId: reviewChargeElement.chargeElementId, - allocated: reviewChargeElement.allocated, - amendedAllocated: reviewChargeElement.amendedAllocated, - chargeDatesOverlap: reviewChargeElement.chargeDatesOverlap, - issues: reviewChargeElement.issues, - status: reviewChargeElement.status, - createdAt: reviewChargeElement.createdAt, - updatedAt: reviewChargeElement.updatedAt, - chargeElement: { - description: chargeElement.description, - abstractionPeriodStartDay: chargeElement.abstractionPeriodStartDay, - abstractionPeriodStartMonth: chargeElement.abstractionPeriodStartMonth, - abstractionPeriodEndDay: chargeElement.abstractionPeriodEndDay, - abstractionPeriodEndMonth: chargeElement.abstractionPeriodEndMonth, - authorisedAnnualQuantity: chargeElement.authorisedAnnualQuantity, - purpose: { - description: purpose.description - } - }, - reviewReturns: [{ - id: reviewReturn.id, - reviewLicenceId: reviewReturn.reviewLicenceId, - returnId: reviewReturn.returnId, - returnReference: reviewReturn.returnReference, - quantity: reviewReturn.quantity, - allocated: reviewReturn.allocated, - underQuery: reviewReturn.underQuery, - returnStatus: reviewReturn.returnStatus, - nilReturn: reviewReturn.nilReturn, - abstractionOutsidePeriod: reviewReturn.abstractionOutsidePeriod, - receivedDate: reviewReturn.receivedDate, - dueDate: reviewReturn.dueDate, - purposes: reviewReturn.purposes, - description: reviewReturn.description, - startDate: reviewReturn.startDate, - endDate: reviewReturn.endDate, - issues: reviewReturn.issues, - createdAt: reviewReturn.createdAt, - updatedAt: reviewReturn.updatedAt - }] - }] - }], - chargeVersion: { - billingAccountId: chargeVersion.billingAccountId - }, - billingAccountDetails: [] - }] - }]) - }) - }) - }) -}) diff --git a/test/services/bill-runs/two-part-tariff/match-details.service.test.js b/test/services/bill-runs/two-part-tariff/match-details.service.test.js deleted file mode 100644 index efae73e4db..0000000000 --- a/test/services/bill-runs/two-part-tariff/match-details.service.test.js +++ /dev/null @@ -1,122 +0,0 @@ -'use strict' - -// Test framework dependencies -const Lab = require('@hapi/lab') -const Code = require('@hapi/code') -const Sinon = require('sinon') - -const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() -const { expect } = Code - -// Things we need to stub -const FetchMatchDetailsService = require('../../../../app/services/bill-runs/two-part-tariff/fetch-match-details.service.js') - -// Thing under test -const MatchDetailsService = require('../../../../app/services/bill-runs/two-part-tariff/match-details.service.js') - -describe('Match Details Service', () => { - afterEach(() => { - Sinon.restore() - }) - - describe('when given a billRun, licence and chargeElement ID', () => { - const reviewChargeElementId = '2c80bd22-a005-4cf4-a2a2-73812a9861de' - const billRunId = 'cc4bbb18-0d6a-4254-ac2c-7409de814d7e' - const licenceId = '9a8a148d-b71e-463c-bea8-bc5e0a5d95e2' - - let yarStub - - beforeEach(() => { - Sinon.stub(FetchMatchDetailsService, 'go').resolves({ - billRun: _billRun(), - reviewChargeElement: _reviewChargeElementData() - }) - - yarStub = { flash: Sinon.stub().returns(['The billable returns for this licence have been updated']) } - }) - - it('will fetch the charge element data and return it once formatted by the presenter', async () => { - const result = await MatchDetailsService.go(billRunId, licenceId, reviewChargeElementId, yarStub) - - expect(result.bannerMessage).to.equal('The billable returns for this licence have been updated') - - // NOTE: The service mainly just regurgitates what the MatchDetailsPresenter returns. So, we don't diligently - // check each property of the result because we know this will have been covered by the MatchDetailsPresenter - - expect(FetchMatchDetailsService.go.called).to.be.true() - expect(result.billRunId).to.equal('6620135b-0ecf-4fd4-924e-371f950c0526') - expect(result.licenceId).to.equal('9a8a148d-b71e-463c-bea8-bc5e0a5d95e2') - expect(result.chargeElement.chargeElementId).to.equal('b4d70c89-de1b-4f68-a47f-832b338ac044') - expect(result.chargeElement.billableVolume).to.equal(0) - }) - }) -}) - -function _billRun () { - return { - id: '6620135b-0ecf-4fd4-924e-371f950c0526', - fromFinancialYearEnding: 2023, - toFinancialYearEnding: 2023 - } -} - -function _reviewChargeElementData () { - return { - id: 'b4d70c89-de1b-4f68-a47f-832b338ac044', - reviewChargeReferenceId: '9e5d87d7-073e-420e-b12d-73ca220dd8ef', - chargeElementId: 'b345f1f1-496b-4049-a647-6bcd123dcf68', - allocated: 10, - amendedAllocated: 0, - chargeDatesOverlap: false, - issues: null, - status: 'ready', - createdAt: new Date('2024-04-02'), - updatedAt: new Date('2024-04-02'), - reviewReturns: [ - { - id: 'c4cdbfa9-4528-4776-b62f-fa667b797717', - reviewLicenceId: '674ffa02-51be-4caa-b25e-cc1fea1ac057', - returnId: 'v1:1:01/57/14/1646:15584914:2022-04-01:2023-03-31', - returnReference: '10031343', - quantity: 0, - allocated: 0, - underQuery: false, - returnStatus: 'completed', - nilReturn: false, - abstractionOutsidePeriod: false, - receivedDate: new Date('2022-06-03'), - dueDate: new Date('2022-06-03'), - purposes: [{ - tertiary: { code: '400', description: 'Spray Irrigation - Direct' } - }], - description: 'Lands at Mosshayne Farm, Exeter & Broadclyst', - startDate: new Date('2022-04-01'), - endDate: new Date('2022-05-06'), - issues: null, - createdAt: new Date('2024-04-02'), - updatedAt: new Date('2024-04-02'), - returnLog: { - periodEndDay: 31, - periodEndMonth: 3, - periodStartDay: 1, - periodStartMonth: 4 - } - } - ], - chargeElement: { - description: 'Trickle Irrigation - Direct', - abstractionPeriodStartDay: 1, - abstractionPeriodStartMonth: 4, - abstractionPeriodEndDay: 31, - abstractionPeriodEndMonth: 3, - authorisedAnnualQuantity: 200 - }, - reviewChargeReference: { - id: '9e5d87d7-073e-420e-b12d-73ca220dd8ef', - reviewChargeVersion: { - chargePeriodStartDate: new Date('2022-04-01'), - chargePeriodEndDate: new Date('2022-06-05') - } - } - } -} diff --git a/test/services/bill-runs/two-part-tariff/remove-bill-run-licence.service.test.js b/test/services/bill-runs/two-part-tariff/remove-bill-run-licence.service.test.js deleted file mode 100644 index ac726bfd7e..0000000000 --- a/test/services/bill-runs/two-part-tariff/remove-bill-run-licence.service.test.js +++ /dev/null @@ -1,53 +0,0 @@ -'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 LicenceHelper = require('../../../support/helpers/licence.helper.js') -const RegionHelper = require('../../../support/helpers/region.helper.js') - -// Thing under test -const RemoveBillRunLicenceService = require('../../../../app/services/bill-runs/two-part-tariff/remove-bill-run-licence.service.js') - -describe('Remove Bill Run Licence service', () => { - let billRunId - let licence - let region - - beforeEach(async () => { - region = RegionHelper.select() - const billRun = await BillRunHelper.add({ - billRunNumber: 12345, - createdAt: new Date('2024-05-03'), - regionId: region.id, - status: 'review', - toFinancialYearEnding: 2023 - }) - - billRunId = billRun.id - - licence = await LicenceHelper.add() - }) - - describe('when called with a valid billRunId & licenceId', () => { - it('will fetch the data and format it for use in the Remove bill run licence confirmation page', async () => { - const result = await RemoveBillRunLicenceService.go(billRunId, licence.id) - - expect(result).to.equal({ - pageTitle: `You're about to remove ${licence.licenceRef} from the bill run`, - backLink: `../review/${licence.id}`, - billRunNumber: 12345, - billRunStatus: 'review', - dateCreated: '3 May 2024', - financialYear: '2022 to 2023', - region: region.displayName - }) - }) - }) -}) diff --git a/test/services/bill-runs/two-part-tariff/review-licence.service.test.js b/test/services/bill-runs/two-part-tariff/review-licence.service.test.js deleted file mode 100644 index 5803036faa..0000000000 --- a/test/services/bill-runs/two-part-tariff/review-licence.service.test.js +++ /dev/null @@ -1,248 +0,0 @@ -'use strict' - -// Test framework dependencies -const Lab = require('@hapi/lab') -const Code = require('@hapi/code') -const Sinon = require('sinon') - -const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() -const { expect } = Code - -// Test helpers -const BillingAccountModel = require('../../../../app/models/billing-account.model.js') - -// Things we need to stub -const FetchReviewLicenceResultsService = require('../../../../app/services/bill-runs/two-part-tariff/fetch-review-licence-results.service.js') - -// Thing under test -const ReviewLicenceService = require('../../../../app/services/bill-runs/two-part-tariff/review-licence.service.js') - -describe('Review Licence Service', () => { - const billRunId = '2c80bd22-a005-4cf4-a2a2-73812a9861de' - const licenceId = '082f528e-4ae4-4f41-ba64-b740a0a210ff' - - let yarStub - - beforeEach(async () => { - Sinon.stub(FetchReviewLicenceResultsService, 'go').resolves(_fetchReviewLicenceResults()) - - yarStub = { flash: Sinon.stub().returns(['This licence has been marked.']) } - }) - - afterEach(() => { - Sinon.restore() - }) - - describe('when called', () => { - it('returns page data for the view', async () => { - const result = await ReviewLicenceService.go(billRunId, licenceId, yarStub) - - expect(result.bannerMessage).to.equal('This licence has been marked.') - - // NOTE: The service mainly just regurgitates what the ReviewLicencePresenter returns. So, we don't diligently - // check each property of the result because we know this will have been covered by the ReviewLicencePresenter - expect(result.billRunId).to.equal('6620135b-0ecf-4fd4-924e-371f950c0526') - expect(result.region).to.equal('Anglian') - expect(result.licence.licenceId).to.equal('786f0d83-eaf7-43c3-9de5-ec59e3de05ee') - expect(result.licence.licenceHolder).to.equal('Licence Holder Ltd') - expect(result.matchedReturns[0].returnId).to.equal('v1:1:01/60/28/3437:17061181:2022-04-01:2023-03-31') - expect(result.unmatchedReturns[0].returnId).to.equal('v2:1:01/60/28/3437:17061181:2022-04-01:2023-03-31') - expect(result.chargeData[0].financialYear).to.equal('2022 to 2023') - expect(result.chargeData[0].billingAccountDetails.billingAccountId).to.equal('a17ae69b-8074-4d27-80bf-074f4c79a05a') - }) - }) -}) - -function _fetchReviewLicenceResults () { - const billingAccountDetails = BillingAccountModel.fromJson({ - id: 'a17ae69b-8074-4d27-80bf-074f4c79a05a', - accountNumber: 'E88896464A', - company: { - id: 'e44491db-2b33-4473-9c3a-b57aceabb6e8', - name: 'Furland Farm', - type: 'organisation' - }, - billingAccountAddresses: [ - { - id: 'eb5cb54a-0b51-4e4a-8472-dab993eb6157', - billingAccountId: 'a17ae69b-8074-4d27-80bf-074f4c79a05a', - addressId: 'cc32fefd-7f3e-4581-b437-78a3fae66d4b', - startDate: new Date('2016-05-20'), - endDate: null, - companyId: null, - contactId: null, - company: null, - contact: null, - address: { - id: 'cc32fefd-7f3e-4581-b437-78a3fae66d4b', - address1: 'Furland Farm', - address2: 'Furland', - address3: null, - address4: null, - address5: 'Crewkerne', - address6: 'Somerset', - postcode: 'TA18 7TT', - country: 'England' - } - } - ] - }) - - return { - billRun: { - id: '6620135b-0ecf-4fd4-924e-371f950c0526', - fromFinancialYearEnding: 2023, - toFinancialYearEnding: 2023, - region: { - displayName: 'Anglian' - } - }, - licence: [{ - id: '5aa8e752-1a5c-4b01-9112-d92a543b70d1', - billRunId: '82772a06-c8ce-45f7-8504-dd20ea8824e4', - licenceId: '786f0d83-eaf7-43c3-9de5-ec59e3de05ee', - licenceRef: '01/49/80/4608', - licenceHolder: 'Licence Holder Ltd', - issues: '', - status: 'ready', - progress: false, - hasReviewStatus: false, - reviewReturns: [{ - id: '2264f443-5c16-4ca9-8522-f63e2d4e38be', - reviewLicenceId: '78a99c1c-26d3-4163-ab58-084cd78594ab', - returnId: 'v1:1:01/60/28/3437:17061181:2022-04-01:2023-03-31', - returnReference: '10031343', - quantity: 0, - allocated: 0, - underQuery: false, - returnStatus: 'completed', - nilReturn: false, - abstractionOutsidePeriod: false, - receivedDate: new Date('2022-06-03'), - dueDate: new Date('2022-06-03'), - purposes: [{ - tertiary: { - description: 'Site description' - } - }], - description: 'Lands at Mosshayne Farm, Exeter & Broadclyst', - startDate: new Date(' 2022-04-01'), - endDate: new Date('2022-05-06'), - issues: '', - reviewChargeElements: [{ - id: 'e840f418-ca6b-4d96-9f36-bf684c78590f', - reviewChargeReferenceId: '7759e0f9-5763-4b94-8d45-0621aea3edc1', - chargeElementId: 'b1cd4f98-ad96-4901-9e21-4432f032492a', - allocated: 0, - amendedAllocated: 0, - chargeDatesOverlap: false, - issues: '', - status: 'ready' - }], - returnLog: { - periodEndDay: 31, - periodEndMonth: 3, - periodStartDay: 1, - periodStartMonth: 4 - } - }, - { - id: '4864f643-5c16-5ca9-8512-f63e1d4e58be', - reviewLicenceId: '78a99c1c-26d3-4163-ab58-084cd78594ab', - returnId: 'v2:1:01/60/28/3437:17061181:2022-04-01:2023-03-31', - returnReference: '10031343', - quantity: 0, - allocated: 0, - underQuery: false, - returnStatus: 'completed', - nilReturn: false, - abstractionOutsidePeriod: false, - receivedDate: new Date('2022-06-03'), - dueDate: new Date('2022-06-03'), - purposes: [{ - tertiary: { - description: 'Site description' - } - }], - description: 'Lands at Mosshayne Farm, Exeter & Broadclyst', - startDate: new Date(' 2022-04-01'), - endDate: new Date('2022-05-06'), - issues: '', - reviewChargeElements: [], - returnLog: { - periodEndDay: 31, - periodEndMonth: 3, - periodStartDay: 1, - periodStartMonth: 4 - } - }], - reviewChargeVersions: [{ - id: '3de5634a-da26-4241-87e9-7248a4b83a69', - reviewLicenceId: 'd9e78306-bf65-4020-b279-5ae471cea4e6', - chargeVersionId: 'd103bb54-1819-4e77-b3d9-bc8913454e06', - changeReason: 'Strategic review of charges (SRoC)', - chargePeriodStartDate: new Date('2022-04-01'), - chargePeriodEndDate: new Date('2022-06-05'), - reviewChargeReferences: [{ - id: 'b2af5935-4b65-4dce-9f75-9073798f6375', - reviewChargeVersionId: 'bd16e7b0-c2a3-4258-b873-b965fd74cdf5', - chargeReferenceId: '82ce8695-5841-41b0-a1e7-d016407adad4', - aggregate: 1, - createdAt: new Date('2024-03-18'), - updatedAt: new Date('2024-03-18'), - chargeReference: { - chargeCategoryId: 'f100dc23-c6a7-4efa-af4f-80618260b32e', - chargeCategory: { - reference: '4.6.7', - shortDescription: 'High loss, non-tidal, greater than 15 up to and including 50 ML/yr' - } - }, - reviewChargeElements: [{ - id: '8bc0cd32-400e-4a45-9dd7-fbce3d486031', - reviewChargeReferenceId: '2210bb45-1efc-4e69-85cb-c8cc6e75c4fd', - chargeElementId: 'b1001716-cfb4-43c6-91f0-1863f4529223', - allocated: 0, - amendedAllocated: 0, - chargeDatesOverlap: false, - issues: '', - status: 'ready', - chargeElement: { - description: 'Trickle Irrigation - Direct', - abstractionPeriodStartDay: 1, - abstractionPeriodStartMonth: 4, - abstractionPeriodEndDay: 31, - abstractionPeriodEndMonth: 3, - authorisedAnnualQuantity: 200, - purpose: { - description: 'Spray irrigation - direct' - } - }, - reviewReturns: [{ - id: '2264f443-5c16-4ca9-8522-f63e2d4e38be', - reviewLicenceId: '78a99c1c-26d3-4163-ab58-084cd78594ab', - returnId: 'v1:1:01/60/28/3437:17061181:2022-04-01:2023-03-31', - returnReference: '10031343', - quantity: 0, - allocated: 0, - underQuery: false, - returnStatus: 'completed', - nilReturn: false, - abstractionOutsidePeriod: false, - receivedDate: new Date('2022-06-03'), - dueDate: new Date('2022-06-03'), - purposes: {}, - description: 'Lands at Mosshayne Farm, Exeter & Broadclyst', - startDate: new Date(' 2022-04-01'), - endDate: new Date('2022-05-06'), - issues: '' - }] - }] - }], - chargeVersion: { - billingAccountId: '67d7cacb-5d10-4a08-b7f8-e6ce98cbf4c8' - }, - billingAccountDetails - }] - }] - } -} diff --git a/test/services/bill-runs/two-part-tariff/submit-amended-adjustment-factor.service.test.js b/test/services/bill-runs/two-part-tariff/submit-amended-adjustment-factor.service.test.js deleted file mode 100644 index 346fdd1dd0..0000000000 --- a/test/services/bill-runs/two-part-tariff/submit-amended-adjustment-factor.service.test.js +++ /dev/null @@ -1,269 +0,0 @@ -'use strict' - -// Test framework dependencies -const Lab = require('@hapi/lab') -const Code = require('@hapi/code') -const Sinon = require('sinon') - -const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() -const { expect } = Code - -// Test helpers -const AmendAdjustmentFactorPresenter = require('../../../../app/presenters/bill-runs/two-part-tariff/amend-adjustment-factor.presenter.js') -const FetchReviewChargeReferenceService = require('../../../../app/services/bill-runs/two-part-tariff/fetch-review-charge-reference.service.js') -const ReviewChargeReferenceHelper = require('../../../support/helpers/review-charge-reference.helper.js') -const ReviewChargeReferenceModel = require('../../../../app/models/review-charge-reference.model.js') - -// Thing under test -const SubmitAmendedAdjustmentFactorService = require('../../../../app/services/bill-runs/two-part-tariff/submit-amended-adjustment-factor.service.js') - -describe('Submit Amended Adjustment Factor Service', () => { - const billRunId = 'cc4bbb18-0d6a-4254-ac2c-7409de814d7e' - const licenceId = '9a8a148d-b71e-463c-bea8-bc5e0a5d95e2' - - let payload - let reviewChargeReference - let yarStub - - beforeEach(async () => { - yarStub = { flash: Sinon.stub() } - - reviewChargeReference = await ReviewChargeReferenceHelper.add() - }) - - afterEach(() => { - Sinon.restore() - }) - - describe('when called', () => { - describe('with a valid payload for aggregate factor', () => { - beforeEach(async () => { - // Note: Both values should always be present because the input box auto populates the original value before a - // user can change it. If either aren't then this leads to a validation error - payload = { - amendedAggregateFactor: 0.5, - amendedChargeAdjustment: 1 - } - }) - - it('saves the users entered value', async () => { - await SubmitAmendedAdjustmentFactorService.go(billRunId, licenceId, reviewChargeReference.id, payload, yarStub) - - const reviewChargeReferenceData = await _fetchReviewChargeReference(reviewChargeReference.id) - - expect(reviewChargeReferenceData.amendedAggregate).to.equal(0.5) - }) - - it('sets the banner message to "The adjustment factors for this licence have been updated"', async () => { - await SubmitAmendedAdjustmentFactorService.go(billRunId, licenceId, reviewChargeReference.id, payload, yarStub) - - const [flashType, bannerMessage] = yarStub.flash.args[0] - - expect(flashType).to.equal('banner') - expect(bannerMessage).to.equal('The adjustment factors for this licence have been updated') - }) - }) - - describe('with a valid payload for charge adjustment', () => { - beforeEach(async () => { - payload = { - amendedAggregateFactor: 1, - amendedChargeAdjustment: 0.7 - } - }) - - it('saves the users entered value', async () => { - await SubmitAmendedAdjustmentFactorService.go(billRunId, licenceId, reviewChargeReference.id, payload, yarStub) - - const reviewChargeReferenceData = await _fetchReviewChargeReference(reviewChargeReference.id) - - expect(reviewChargeReferenceData.amendedChargeAdjustment).to.equal(0.7) - }) - - it('sets the banner message to "The adjustment factors for this licence have been updated"', async () => { - await SubmitAmendedAdjustmentFactorService.go(billRunId, licenceId, reviewChargeReference.id, payload, yarStub) - - const [flashType, bannerMessage] = yarStub.flash.args[0] - - expect(flashType).to.equal('banner') - expect(bannerMessage).to.equal('The adjustment factors for this licence have been updated') - }) - }) - - describe('with a valid payload for both aggregate factor and charge adjustment', () => { - beforeEach(async () => { - payload = { - amendedAggregateFactor: 0.2, - amendedChargeAdjustment: 0.3 - } - }) - - it('saves the users entered value', async () => { - await SubmitAmendedAdjustmentFactorService.go(billRunId, licenceId, reviewChargeReference.id, payload, yarStub) - - const reviewChargeReferenceData = await _fetchReviewChargeReference(reviewChargeReference.id) - - expect(reviewChargeReferenceData.amendedAggregate).to.equal(0.2) - expect(reviewChargeReferenceData.amendedChargeAdjustment).to.equal(0.3) - }) - - it('sets the banner message to "The adjustment factors for this licence have been updated"', async () => { - await SubmitAmendedAdjustmentFactorService.go(billRunId, licenceId, reviewChargeReference.id, payload, yarStub) - - const [flashType, bannerMessage] = yarStub.flash.args[0] - - expect(flashType).to.equal('banner') - expect(bannerMessage).to.equal('The adjustment factors for this licence have been updated') - }) - }) - - describe('when an invalid payload', () => { - beforeEach(() => { - Sinon.stub(FetchReviewChargeReferenceService, 'go').resolves({ billRun: 'bill run', reviewChargeReference: 'charge reference' }) - Sinon.stub(AmendAdjustmentFactorPresenter, 'go').returns(_amendAdjustmentFactorData()) - }) - - describe('because the user left the aggregate factor input blank', () => { - beforeEach(() => { - payload = { - amendedChargeAdjustment: 0.3 - } - }) - - it('returns the page data for the view', async () => { - const result = await SubmitAmendedAdjustmentFactorService - .go(billRunId, licenceId, reviewChargeReference.id, payload, yarStub) - - expect(result).to.equal({ - activeNavBar: 'search', - pageTitle: 'Set the adjustment factors', - inputtedAggregateValue: undefined, - inputtedChargeValue: 0.3, - billRunId: 'cc4bbb18-0d6a-4254-ac2c-7409de814d7e', - licenceId: '9a8a148d-b71e-463c-bea8-bc5e0a5d95e2', - financialYear: '2022 to 2023', - chargePeriod: '1 September 2022 to 31 March 2023', - chargeReference: { - id: 'e93fa3c6-195f-48eb-a03f-f87db7218f2d', - description: 'High loss, non-tidal, greater than 50 up to and including 85 ML/yr', - aggregateFactor: 0.5, - chargeAdjustment: 0.5, - otherAdjustments: '' - } - }, { skip: ['error'] }) - }) - - it('returns the page data with an error for the aggregate factor input element', async () => { - const result = await SubmitAmendedAdjustmentFactorService - .go(billRunId, licenceId, reviewChargeReference.id, payload, yarStub) - - expect(result.error).to.equal({ - aggregateFactorElement: { text: 'Enter a aggregate factor' }, - chargeAdjustmentElement: null - }) - }) - }) - - describe('because the user left the charge factor input blank', () => { - beforeEach(() => { - payload = { - amendedAggregateFactor: 0.2 - } - }) - - it('returns the page data for the view', async () => { - const result = await SubmitAmendedAdjustmentFactorService - .go(billRunId, licenceId, reviewChargeReference.id, payload, yarStub) - - expect(result).to.equal({ - activeNavBar: 'search', - pageTitle: 'Set the adjustment factors', - inputtedAggregateValue: 0.2, - inputtedChargeValue: undefined, - billRunId: 'cc4bbb18-0d6a-4254-ac2c-7409de814d7e', - licenceId: '9a8a148d-b71e-463c-bea8-bc5e0a5d95e2', - financialYear: '2022 to 2023', - chargePeriod: '1 September 2022 to 31 March 2023', - chargeReference: { - id: 'e93fa3c6-195f-48eb-a03f-f87db7218f2d', - description: 'High loss, non-tidal, greater than 50 up to and including 85 ML/yr', - aggregateFactor: 0.5, - chargeAdjustment: 0.5, - otherAdjustments: '' - } - }, { skip: ['error'] }) - }) - - it('returns the page data with an error for the charge factor input element', async () => { - const result = await SubmitAmendedAdjustmentFactorService - .go(billRunId, licenceId, reviewChargeReference.id, payload, yarStub) - - expect(result.error).to.equal({ - aggregateFactorElement: null, - chargeAdjustmentElement: { text: 'Enter a charge factor' } - }) - }) - }) - - describe('because the user left both the aggregate and charge factor input blank', () => { - beforeEach(() => { - payload = {} - }) - - it('returns the page data for the view', async () => { - const result = await SubmitAmendedAdjustmentFactorService - .go(billRunId, licenceId, reviewChargeReference.id, payload, yarStub) - - expect(result).to.equal({ - activeNavBar: 'search', - pageTitle: 'Set the adjustment factors', - inputtedAggregateValue: undefined, - inputtedChargeValue: undefined, - billRunId: 'cc4bbb18-0d6a-4254-ac2c-7409de814d7e', - licenceId: '9a8a148d-b71e-463c-bea8-bc5e0a5d95e2', - financialYear: '2022 to 2023', - chargePeriod: '1 September 2022 to 31 March 2023', - chargeReference: { - id: 'e93fa3c6-195f-48eb-a03f-f87db7218f2d', - description: 'High loss, non-tidal, greater than 50 up to and including 85 ML/yr', - aggregateFactor: 0.5, - chargeAdjustment: 0.5, - otherAdjustments: '' - } - }, { skip: ['error'] }) - }) - - it('returns the page data with an error for the aggregate and charge factor input element', async () => { - const result = await SubmitAmendedAdjustmentFactorService - .go(billRunId, licenceId, reviewChargeReference.id, payload, yarStub) - - expect(result.error).to.equal({ - aggregateFactorElement: { text: 'Enter a aggregate factor' }, - chargeAdjustmentElement: { text: 'Enter a charge factor' } - }) - }) - }) - }) - }) -}) - -function _amendAdjustmentFactorData () { - return { - billRunId: 'cc4bbb18-0d6a-4254-ac2c-7409de814d7e', - licenceId: '9a8a148d-b71e-463c-bea8-bc5e0a5d95e2', - financialYear: '2022 to 2023', - chargePeriod: '1 September 2022 to 31 March 2023', - chargeReference: { - id: 'e93fa3c6-195f-48eb-a03f-f87db7218f2d', - description: 'High loss, non-tidal, greater than 50 up to and including 85 ML/yr', - aggregateFactor: 0.5, - chargeAdjustment: 0.5, - otherAdjustments: '' - } - } -} - -async function _fetchReviewChargeReference (id) { - return ReviewChargeReferenceModel.query() - .findById(id) -} diff --git a/test/services/bill-runs/two-part-tariff/submit-amended-authorised-volume.service.test.js b/test/services/bill-runs/two-part-tariff/submit-amended-authorised-volume.service.test.js deleted file mode 100644 index 1dfaac82ce..0000000000 --- a/test/services/bill-runs/two-part-tariff/submit-amended-authorised-volume.service.test.js +++ /dev/null @@ -1,147 +0,0 @@ -'use strict' - -// Test framework dependencies -const Lab = require('@hapi/lab') -const Code = require('@hapi/code') -const Sinon = require('sinon') - -const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() -const { expect } = Code - -// Test helpers -const AmendAuthorisedVolumePresenter = require('../../../../app/presenters/bill-runs/two-part-tariff/amend-authorised-volume.presenter.js') -const FetchAuthorisedVolumeService = require('../../../../app/services/bill-runs/two-part-tariff/fetch-authorised-volume.service.js') -const ReviewChargeReferenceHelper = require('../../../support/helpers/review-charge-reference.helper.js') -const ReviewChargeReferenceModel = require('../../../../app/models/review-charge-reference.model.js') - -// Thing under test -const SubmitAmendedAuthorisedVolumeService = require('../../../../app/services/bill-runs/two-part-tariff/submit-amended-authorised-volume.service.js') - -describe('Submit Amended Authorised Volume Service', () => { - const billRunId = 'cc4bbb18-0d6a-4254-ac2c-7409de814d7e' - const licenceId = '9a8a148d-b71e-463c-bea8-bc5e0a5d95e2' - - let payload - let reviewChargeReference - let yarStub - - beforeEach(async () => { - yarStub = { flash: Sinon.stub() } - - reviewChargeReference = await ReviewChargeReferenceHelper.add() - }) - - afterEach(() => { - Sinon.restore() - }) - - describe('when called', () => { - describe('with a valid payload for authorised volume', () => { - beforeEach(async () => { - payload = { - authorisedVolume: '10', - totalBillableReturns: '5', - minVolume: '5', - maxVolume: '20' - } - }) - - it('saves the users entered value', async () => { - await SubmitAmendedAuthorisedVolumeService.go(billRunId, licenceId, reviewChargeReference.id, payload, yarStub) - - const reviewChargeReferenceData = await _fetchReviewChargeReference(reviewChargeReference.id) - - expect(reviewChargeReferenceData.amendedAuthorisedVolume).to.equal(10) - }) - - it('sets the banner message to "The authorised volume for this licence have been updated"', async () => { - await SubmitAmendedAuthorisedVolumeService.go(billRunId, licenceId, reviewChargeReference.id, payload, yarStub) - - const [flashType, bannerMessage] = yarStub.flash.args[0] - - expect(flashType).to.equal('banner') - expect(bannerMessage).to.equal('The authorised volume for this licence have been updated') - }) - }) - - describe('with an invalid payload', () => { - beforeEach(() => { - Sinon.stub(FetchAuthorisedVolumeService, 'go').resolves({ billRun: 'bill run', reviewChargeReference: 'charge reference' }) - Sinon.stub(AmendAuthorisedVolumePresenter, 'go').returns(_amendAuthorisedVolumeData()) - }) - - describe('because the user left the authorised volume input blank', () => { - beforeEach(() => { - payload = { - totalBillableReturns: '5', - minVolume: '5', - maxVolume: '20' - } - }) - - it('returns the page data for the view', async () => { - const result = await SubmitAmendedAuthorisedVolumeService.go( - billRunId, licenceId, reviewChargeReference.id, payload, yarStub - ) - - expect(result).to.equal({ - activeNavBar: 'search', - pageTitle: 'Set the authorised volume', - id: '6b3d11f2-d361-4eaa-bce2-5561283bd023', - amendedAuthorisedVolume: 25.5, - chargeReference: { - chargeCategoryId: 'b4354db6-6699-4987-b4c8-d53ac2bf2250', - chargeCategory: { - shortDescription: 'Medium loss, non-tidal, greater than 83 up to and including 142 ML/yr', - minVolume: 83, - maxVolume: 142 - } - }, - reviewChargeElements: [{ amendedAllocated: 15 }], - reviewChargeVersion: { - chargePeriodStartDate: new Date('2022-04-01'), - chargePeriodEndDate: new Date('2022-06-01') - } - }, { skip: ['error'] }) - }) - - it('returns the page data with an error for the authorised volume input element', async () => { - const result = await SubmitAmendedAuthorisedVolumeService.go( - billRunId, licenceId, reviewChargeReference.id, payload, yarStub - ) - - expect(result.error).to.equal({ - authorisedVolume: 'Enter an authorised volume' - }) - }) - }) - }) - }) -}) - -function _amendAuthorisedVolumeData () { - return { - id: '6b3d11f2-d361-4eaa-bce2-5561283bd023', - amendedAuthorisedVolume: 25.5, - chargeReference: { - chargeCategoryId: 'b4354db6-6699-4987-b4c8-d53ac2bf2250', - chargeCategory: { - shortDescription: 'Medium loss, non-tidal, greater than 83 up to and including 142 ML/yr', - minVolume: 83, - maxVolume: 142 - } - }, - reviewChargeElements: [{ - amendedAllocated: 15 - }], - reviewChargeVersion: { - chargePeriodStartDate: new Date('2022-04-01'), - chargePeriodEndDate: new Date('2022-06-01') - } - } -} - -async function _fetchReviewChargeReference (id) { - return ReviewChargeReferenceModel.query() - .findById(id) -} diff --git a/test/services/bill-runs/two-part-tariff/submit-amended-billable-returns.service.test.js b/test/services/bill-runs/two-part-tariff/submit-amended-billable-returns.service.test.js deleted file mode 100644 index 4e260a60b0..0000000000 --- a/test/services/bill-runs/two-part-tariff/submit-amended-billable-returns.service.test.js +++ /dev/null @@ -1,235 +0,0 @@ -'use strict' - -// Test framework dependencies -const Lab = require('@hapi/lab') -const Code = require('@hapi/code') -const Sinon = require('sinon') - -const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() -const { expect } = Code - -// Test helpers -const ReviewChargeElementHelper = require('../../../support/helpers/review-charge-element.helper.js') -const ReviewChargeElementModel = require('../../../../app/models/review-charge-element.model.js') - -// Things we need to stub -const FetchMatchDetailsService = require('../../../../app/services/bill-runs/two-part-tariff/fetch-match-details.service.js') -const AmendBillableReturnsPresenter = require('../../../../app/presenters/bill-runs/two-part-tariff/amend-billable-returns.presenter.js') - -// Thing under test -const SubmitAmendedBillableReturnsService = require('../../../../app/services/bill-runs/two-part-tariff/submit-amended-billable-returns.service.js') - -describe('Submit Amended Billable Returns Service', () => { - const billRunId = 'cc4bbb18-0d6a-4254-ac2c-7409de814d7e' - const licenceId = '9a8a148d-b71e-463c-bea8-bc5e0a5d95e2' - let payload - let reviewChargeElement - let yarStub - - beforeEach(async () => { - yarStub = { flash: Sinon.stub() } - - reviewChargeElement = await ReviewChargeElementHelper.add() - }) - - afterEach(() => { - Sinon.restore() - }) - - describe('when called', () => { - describe('with a valid payload for quantityOptions', () => { - beforeEach(async () => { - payload = { - 'quantity-options': 10, - authorisedVolume: 11 - } - }) - - it('saves the submitted option', async () => { - await SubmitAmendedBillableReturnsService.go(billRunId, licenceId, reviewChargeElement.id, payload, yarStub) - - const reviewChargeElementData = await _fetchReviewChargeElement(reviewChargeElement.id) - - expect(reviewChargeElementData.amendedAllocated).to.equal(10) - }) - - it('sets the banner message to "The billable returns for this licence have been updated"', async () => { - await SubmitAmendedBillableReturnsService.go(billRunId, licenceId, reviewChargeElement.id, payload, yarStub) - - const [flashType, bannerMessage] = yarStub.flash.args[0] - - expect(flashType).to.equal('banner') - expect(bannerMessage).to.equal('The billable returns for this licence have been updated') - }) - }) - - describe('with a valid payload for customQuantity', () => { - beforeEach(async () => { - payload = { - 'quantity-options': 'customQuantity', - customQuantity: 20, - authorisedVolume: 25 - } - }) - - it('saves the submitted value', async () => { - await SubmitAmendedBillableReturnsService.go(billRunId, licenceId, reviewChargeElement.id, payload, yarStub) - - const reviewChargeElementData = await _fetchReviewChargeElement(reviewChargeElement.id) - - expect(reviewChargeElementData.amendedAllocated).to.equal(20) - }) - - it('sets the banner message to "The billable returns for this licence have been updated"', async () => { - await SubmitAmendedBillableReturnsService.go(billRunId, licenceId, reviewChargeElement.id, payload, yarStub) - - const [flashType, bannerMessage] = yarStub.flash.args[0] - - expect(flashType).to.equal('banner') - expect(bannerMessage).to.equal('The billable returns for this licence have been updated') - }) - }) - - describe('with an invalid payload', () => { - beforeEach(() => { - Sinon.stub(FetchMatchDetailsService, 'go').resolves({ billRun: 'bill run', reviewChargeElement: 'charge element' }) - Sinon.stub(AmendBillableReturnsPresenter, 'go').returns(_amendBillableReturnsData()) - }) - describe('because the user did not select anything', () => { - beforeEach(async () => { - payload = { - authorisedVolume: 11 - } - }) - - it('returns the page data for the view', async () => { - const result = await SubmitAmendedBillableReturnsService.go( - billRunId, - licenceId, - reviewChargeElement.id, - payload, - yarStub - ) - - expect(result).to.equal({ - activeNavBar: 'search', - pageTitle: 'Set the billable returns quantity for this bill run', - chargeElement: { - description: 'Trickle Irrigation - Direct', - dates: ['1 April 2022 to 5 June 2022'], - authorisedQuantity: 200, - reviewChargeElementId: 'b4d70c89-de1b-4f68-a47f-832b338ac044' - }, - billRun: { - id: '6620135b-0ecf-4fd4-924e-371f950c0526', - financialYear: '2022 to 2023' - }, - chargeVersion: { - chargePeriod: '1 April 2022 to 5 June 2022' - }, - licenceId: '5aa8e752-1a5c-4b01-9112-d92a543b70d1', - customQuantitySelected: false, - customQuantityValue: undefined - }, { skip: ['error'] }) - }) - - it('returns page data with an error for the radio form element', async () => { - const result = await SubmitAmendedBillableReturnsService.go( - billRunId, - licenceId, - reviewChargeElement.id, - payload, - yarStub - ) - - expect(result.error).to.equal({ - message: 'Select the billable quantity', - radioFormElement: { text: 'Select the billable quantity' }, - customQuantityInputFormElement: null - }) - }) - }) - - describe('because the user entered an invalid value', () => { - beforeEach(async () => { - payload = { - 'quantity-options': 'customQuantity', - customQuantity: 'Hello world', - authorisedVolume: 11 - } - }) - - it('returns the page data for the view', async () => { - const result = await SubmitAmendedBillableReturnsService.go( - billRunId, - licenceId, - reviewChargeElement.id, - payload, - yarStub - ) - - expect(result).to.equal({ - activeNavBar: 'search', - pageTitle: 'Set the billable returns quantity for this bill run', - chargeElement: { - description: 'Trickle Irrigation - Direct', - dates: ['1 April 2022 to 5 June 2022'], - authorisedQuantity: 200, - reviewChargeElementId: 'b4d70c89-de1b-4f68-a47f-832b338ac044' - }, - billRun: { - id: '6620135b-0ecf-4fd4-924e-371f950c0526', - financialYear: '2022 to 2023' - }, - chargeVersion: { - chargePeriod: '1 April 2022 to 5 June 2022' - }, - licenceId: '5aa8e752-1a5c-4b01-9112-d92a543b70d1', - customQuantitySelected: true, - customQuantityValue: 'Hello world' - }, { skip: ['error'] }) - }) - - it('returns page data with an error for the custom quantity input form element', async () => { - const result = await SubmitAmendedBillableReturnsService.go( - billRunId, - licenceId, - reviewChargeElement.id, - payload, - yarStub - ) - - expect(result.error).to.equal({ - message: 'The quantity must be a number', - radioFormElement: null, - customQuantityInputFormElement: { text: 'The quantity must be a number' } - }) - }) - }) - }) - }) -}) - -function _amendBillableReturnsData () { - return { - chargeElement: { - description: 'Trickle Irrigation - Direct', - dates: ['1 April 2022 to 5 June 2022'], - authorisedQuantity: 200, - reviewChargeElementId: 'b4d70c89-de1b-4f68-a47f-832b338ac044' - }, - billRun: { - id: '6620135b-0ecf-4fd4-924e-371f950c0526', - financialYear: '2022 to 2023' - }, - chargeVersion: { - chargePeriod: '1 April 2022 to 5 June 2022' - }, - licenceId: '5aa8e752-1a5c-4b01-9112-d92a543b70d1' - } -} - -async function _fetchReviewChargeElement (id) { - return ReviewChargeElementModel.query() - .findById(id) -} diff --git a/test/services/bill-runs/two-part-tariff/submit-remove-bill-run-licence.service.test.js b/test/services/bill-runs/two-part-tariff/submit-remove-bill-run-licence.service.test.js deleted file mode 100644 index 9e22bd9553..0000000000 --- a/test/services/bill-runs/two-part-tariff/submit-remove-bill-run-licence.service.test.js +++ /dev/null @@ -1,95 +0,0 @@ -'use strict' - -// Test framework dependencies -const Lab = require('@hapi/lab') -const Code = require('@hapi/code') -const Sinon = require('sinon') - -const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() -const { expect } = Code - -// Test helpers -const BillRunHelper = require('../../../support/helpers/bill-run.helper.js') -const BillRunModel = require('../../../../app/models/bill-run.model.js') -const LicenceHelper = require('../../../support/helpers/licence.helper.js') -const LicenceSupplementaryYearModel = require('../../../../app/models/licence-supplementary-year.model.js') -const ReviewLicenceHelper = require('../../../support/helpers/review-licence.helper.js') - -// Things we need to stub -const RemoveReviewDataService = require('../../../../app/services/bill-runs/two-part-tariff/remove-review-data.service.js') - -// Thing under test -const SubmitRemoveBillRunLicenceService = require('../../../../app/services/bill-runs/two-part-tariff/submit-remove-bill-run-licence.service.js') - -describe('Submit Remove Bill Run Licence service', () => { - let yarStub - - beforeEach(async () => { - Sinon.stub(RemoveReviewDataService, 'go').resolves() - yarStub = { flash: Sinon.stub() } - }) - - afterEach(() => { - Sinon.restore() - }) - - describe('when called with a valid billRunId & licenceId', () => { - let billRun - let licence - - beforeEach(async () => { - billRun = await BillRunHelper.add({ status: 'review' }) - - licence = await LicenceHelper.add() - }) - - describe('which has at least one licence remaining in the bill run after the licence is removed', () => { - beforeEach(async () => { - // add an extra record to the `reviewLicence` table so that it isn't empty when the licence is removed - await ReviewLicenceHelper.add({ billRunId: billRun.id }) - }) - - it('will return false as all licences are not removed and generate a banner message', async () => { - const result = await SubmitRemoveBillRunLicenceService.go(billRun.id, licence.id, yarStub) - const [flashType, bannerMessage] = yarStub.flash.args[0] - - expect(result).to.be.false() - - expect(yarStub.flash.called).to.be.true() - expect(flashType).to.equal('banner') - expect(bannerMessage).to.equal(`Licence ${licence.licenceRef} removed from the bill run.`) - }) - }) - - describe('which has NO licences remain in the bill run after the licence is removed', () => { - it('will return true as no licences remain in the bill run and NO banner message is generated', async () => { - const result = await SubmitRemoveBillRunLicenceService.go(billRun.id, licence.id, yarStub) - - expect(result).to.be.true() - expect(yarStub.flash.called).to.be.false() - }) - - it('will set the bill run status to empty', async () => { - await SubmitRemoveBillRunLicenceService.go(billRun.id, licence.id, yarStub) - const { status } = await BillRunModel.query().findById(billRun.id).select('status') - - expect(status).to.equal('empty') - }) - - it('will mark the licence for two-part tariff supplementary billing', async () => { - await SubmitRemoveBillRunLicenceService.go(billRun.id, licence.id, yarStub) - const includeInSrocTptBilling = await LicenceSupplementaryYearModel.query() - .select([ - 'licenceId', - 'twoPartTariff', - 'financialYearEnd' - ]) - .where('licenceId', licence.id) - - expect(includeInSrocTptBilling[0].licenceId).to.equal(licence.id) - expect(includeInSrocTptBilling[0].twoPartTariff).to.be.true() - expect(includeInSrocTptBilling[0].financialYearEnd).to.equal(billRun.toFinancialYearEnding) - }) - }) - }) -}) diff --git a/test/services/bill-runs/two-part-tariff/submit-review-licence.service.test.js b/test/services/bill-runs/two-part-tariff/submit-review-licence.service.test.js deleted file mode 100644 index e481dde7c3..0000000000 --- a/test/services/bill-runs/two-part-tariff/submit-review-licence.service.test.js +++ /dev/null @@ -1,136 +0,0 @@ -'use strict' - -// Test framework dependencies -const Lab = require('@hapi/lab') -const Code = require('@hapi/code') -const Sinon = require('sinon') - -const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() -const { expect } = Code - -// Test helpers -const ReviewLicenceHelper = require('../../../support/helpers/review-licence.helper.js') - -// Thing under test -const SubmitReviewLicenceService = require('../../../../app/services/bill-runs/two-part-tariff/submit-review-licence.service.js') - -describe('Submit Review Licence Service', () => { - const billRunId = '1760c5f9-b868-4d77-adb0-961dfc5f5c5d' - const licenceId = '7a0388cf-d5e8-4bec-ac5a-bc495d0106b2' - - let payload - let reviewLicence - let yarStub - - beforeEach(async () => { - yarStub = { flash: Sinon.stub() } - }) - - afterEach(() => { - Sinon.restore() - }) - - describe('when called by the status button', () => { - describe('to set the review licence status to "review"', () => { - beforeEach(async () => { - payload = { 'licence-status': 'review' } - - reviewLicence = await ReviewLicenceHelper.add({ billRunId, licenceId, status: 'ready' }) - }) - - it('updates the review licence record status to "review"', async () => { - await SubmitReviewLicenceService.go(billRunId, licenceId, payload, yarStub) - - const refreshedRecord = await reviewLicence.$query() - - expect(refreshedRecord.status).to.equal('review') - }) - - it('sets the banner message to "Licence changed to review."', async () => { - await SubmitReviewLicenceService.go(billRunId, licenceId, payload, yarStub) - - const [flashType, bannerMessage] = yarStub.flash.args[0] - - expect(flashType).to.equal('banner') - expect(bannerMessage).to.equal('Licence changed to review.') - }) - }) - - describe('to set the review licence status to "ready"', () => { - beforeEach(async () => { - payload = { 'licence-status': 'ready' } - - reviewLicence = await ReviewLicenceHelper.add({ billRunId, licenceId, status: 'review' }) - }) - - it('updates the review licence record status to "ready"', async () => { - await SubmitReviewLicenceService.go(billRunId, licenceId, payload, yarStub) - - const refreshedRecord = await reviewLicence.$query() - - expect(refreshedRecord.status).to.equal('ready') - }) - - it('sets the banner message to "Licence changed to ready."', async () => { - await SubmitReviewLicenceService.go(billRunId, licenceId, payload, yarStub) - - const [flashType, bannerMessage] = yarStub.flash.args[0] - - expect(flashType).to.equal('banner') - expect(bannerMessage).to.equal('Licence changed to ready.') - }) - }) - }) - - describe('when called by the progress button', () => { - describe('to mark the licence as in progress', () => { - beforeEach(async () => { - payload = { 'mark-progress': 'mark' } - - reviewLicence = await ReviewLicenceHelper.add({ billRunId, licenceId, progress: false }) - }) - - it('updates the review licence record progress to true', async () => { - await SubmitReviewLicenceService.go(billRunId, licenceId, payload, yarStub) - - const refreshedRecord = await reviewLicence.$query() - - expect(refreshedRecord.progress).to.be.true() - }) - - it('sets the banner message to "This licence has been marked."', async () => { - await SubmitReviewLicenceService.go(billRunId, licenceId, payload, yarStub) - - const [flashType, bannerMessage] = yarStub.flash.args[0] - - expect(flashType).to.equal('banner') - expect(bannerMessage).to.equal('This licence has been marked.') - }) - }) - - describe('to remove the progress mark from the licence', () => { - beforeEach(async () => { - payload = { 'mark-progress': 'unmark' } - - reviewLicence = await ReviewLicenceHelper.add({ billRunId, licenceId, progress: true }) - }) - - it('updates the review licence record progress to false', async () => { - await SubmitReviewLicenceService.go(billRunId, licenceId, payload, yarStub) - - const refreshedRecord = await reviewLicence.$query() - - expect(refreshedRecord.progress).to.be.false() - }) - - it('sets the banner message to "The progress mark for this licence has been removed."', async () => { - await SubmitReviewLicenceService.go(billRunId, licenceId, payload, yarStub) - - const [flashType, bannerMessage] = yarStub.flash.args[0] - - expect(flashType).to.equal('banner') - expect(bannerMessage).to.equal('The progress mark for this licence has been removed.') - }) - }) - }) -}) diff --git a/test/services/return-versions/setup/fetch-existing-requirements.test.js b/test/services/return-versions/setup/fetch-existing-requirements.service.test.js similarity index 98% rename from test/services/return-versions/setup/fetch-existing-requirements.test.js rename to test/services/return-versions/setup/fetch-existing-requirements.service.test.js index b6730530d4..64d47938bc 100644 --- a/test/services/return-versions/setup/fetch-existing-requirements.test.js +++ b/test/services/return-versions/setup/fetch-existing-requirements.service.test.js @@ -11,7 +11,7 @@ const { expect } = Code const RequirementsForReturnsSeeder = require('../../../support/seeders/requirements-for-returns.seeder.js') // Thing under test -const FetchExistingRequirementsService = require('../../../../app/services/return-versions/setup/fetch-existing-requirements.js') +const FetchExistingRequirementsService = require('../../../../app/services/return-versions/setup/fetch-existing-requirements.service.js') describe('Return Versions Setup - Fetch Existing Requirements service', () => { let returnVersion diff --git a/test/services/return-versions/setup/generate-from-existing-requirements.service.test.js b/test/services/return-versions/setup/generate-from-existing-requirements.service.test.js index ee817af11f..a967db449c 100644 --- a/test/services/return-versions/setup/generate-from-existing-requirements.service.test.js +++ b/test/services/return-versions/setup/generate-from-existing-requirements.service.test.js @@ -9,7 +9,7 @@ const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() const { expect } = Code // Things we need to stub -const FetchExistingRequirementsService = require('../../../../app/services/return-versions/setup/fetch-existing-requirements.js') +const FetchExistingRequirementsService = require('../../../../app/services/return-versions/setup/fetch-existing-requirements.service.js') // Thing under test const GenerateFromExistingRequirementsService = require('../../../../app/services/return-versions/setup/generate-from-existing-requirements.service.js') diff --git a/test/support/helpers/address.helper.js b/test/support/helpers/address.helper.js index 0f4bb90209..e6b80ea501 100644 --- a/test/support/helpers/address.helper.js +++ b/test/support/helpers/address.helper.js @@ -15,7 +15,7 @@ const { randomInteger } = require('../general.js') * - `address1` - ENVIRONMENT AGENCY * - `address2` - HORIZON HOUSE * - `address3` - DEANERY ROAD - * - `town` - BRISTOL + * - `address4` - BRISTOL * - `postcode` - BS1 5AH * - `country` - United Kingdom * - `dataSource` - wrls diff --git a/test/support/helpers/return-log.helper.js b/test/support/helpers/return-log.helper.js index a9e7fbe5be..f9657a0fef 100644 --- a/test/support/helpers/return-log.helper.js +++ b/test/support/helpers/return-log.helper.js @@ -5,9 +5,9 @@ */ const { generateLicenceRef } = require('./licence.helper.js') -const { randomInteger } = require('../general.js') const { timestampForPostgres } = require('../../../app/lib/general.lib.js') const ReturnLogModel = require('../../../app/models/return-log.model.js') +const { generateLegacyId } = require('./return-requirement.helper.js') /** * Add a new return log @@ -51,7 +51,7 @@ function add (data = {}) { */ function defaults (data = {}) { const licenceRef = data.licenceRef ? data.licenceRef : generateLicenceRef() - const returnReference = data.returnReference ? data.returnReference : randomInteger(10000000, 19999999) + const returnReference = data.returnReference ? data.returnReference : generateLegacyId() const timestamp = timestampForPostgres() const receivedDate = data.receivedDate ? data.receivedDate : null @@ -127,7 +127,7 @@ function generateReturnLogId ( } if (!returnReference) { - returnReference = randomInteger(10000000, 19999999) + returnReference = generateLegacyId() } return `v${version}:1:${licenceRef}:${returnReference}:${startDate}:${endDate}` diff --git a/test/support/helpers/return-requirement.helper.js b/test/support/helpers/return-requirement.helper.js index 614b6e3a1e..b6f95a8a2d 100644 --- a/test/support/helpers/return-requirement.helper.js +++ b/test/support/helpers/return-requirement.helper.js @@ -46,7 +46,7 @@ function add (data = {}) { * @returns {object} - Returns the set defaults with the override data spread */ function defaults (data = {}) { - const legacyId = data.legacyId ? data.legacyId : randomInteger(100, 99999) + const legacyId = data.legacyId ? data.legacyId : generateLegacyId() const defaults = { abstractionPeriodStartDay: 1, @@ -66,7 +66,17 @@ function defaults (data = {}) { } } +/** + * Generates a return requirement legacy ID (also known as return reference) + * + * @returns {number} + */ +function generateLegacyId () { + return randomInteger(100, 19999999) +} + module.exports = { add, - defaults + defaults, + generateLegacyId } diff --git a/test/support/helpers/review-charge-element.helper.js b/test/support/helpers/review-charge-element.helper.js index 90554b29bc..4ff40b608d 100644 --- a/test/support/helpers/review-charge-element.helper.js +++ b/test/support/helpers/review-charge-element.helper.js @@ -16,7 +16,7 @@ const ReviewChargeElementModel = require('../../../app/models/review-charge-elem * - `reviewChargeReferenceId` - [random UUID] * - `allocated` - 0 * - `chargeDatesOverlap` - false - * - `issues` - null + * - `issues` - '' * - `status` - ready * - `amendedAllocated` - 0 * @@ -48,7 +48,7 @@ function defaults (data = {}) { reviewChargeReferenceId: generateUUID(), allocated: 0, chargeDatesOverlap: false, - issues: null, + issues: '', status: 'ready', amendedAllocated: 0 } diff --git a/test/support/helpers/review-licence.helper.js b/test/support/helpers/review-licence.helper.js index bd493d8c1a..d5c7b4717b 100644 --- a/test/support/helpers/review-licence.helper.js +++ b/test/support/helpers/review-licence.helper.js @@ -16,10 +16,10 @@ const ReviewLicenceModel = require('../../../app/models/review-licence.model.js' * - `billRunId` - [random UUID] * - `licenceId` - [random UUID] * - `licenceRef` - [randomly generated - 01/123] - * - `licenceHolder` - A LicenceHolder + * - `licenceHolder` - Licence Holder Ltd * - `progress` - false * - `status` - ready - * - `issues` - null + * - `issues` - '' * * @param {object} [data] - Any data you want to use instead of the defaults used here or in the database * @@ -51,7 +51,7 @@ function defaults (data = {}) { licenceHolder: 'Licence Holder Ltd', progress: false, status: 'ready', - issues: null + issues: '' } return { diff --git a/test/support/helpers/review-return.helper.js b/test/support/helpers/review-return.helper.js index 071f17034d..2284bd4949 100644 --- a/test/support/helpers/review-return.helper.js +++ b/test/support/helpers/review-return.helper.js @@ -4,10 +4,10 @@ * @module ReviewReturnHelper */ -const { randomInteger } = require('../general.js') const { generateUUID } = require('../../../app/lib/general.lib.js') const { generateLicenceRef } = require('./licence.helper.js') const { generateReturnLogId } = require('./return-log.helper.js') +const { generateLegacyId } = require('./return-requirement.helper.js') const ReviewReturnModel = require('../../../app/models/review-return.model.js') /** @@ -17,7 +17,7 @@ const ReviewReturnModel = require('../../../app/models/review-return.model.js') * * - `reviewLicenceId` - [random UUID] * - `returnId` - v1:1:[the generated licenceRef]:[the generated returnReference]:2022-04-01:2023-03-31 - * - `returnReference` - `10031343` + * - `returnReference` - [randomly generated - 10000321] * - `quantity` - 0 * - `allocated` - 0 * - `underQuery` - false @@ -26,11 +26,11 @@ const ReviewReturnModel = require('../../../app/models/review-return.model.js') * - `abstractionOutsidePeriod` - false * - `receivedDate` - 2022-06-03 * - `dueDate` - 2022-06-03 - * - `purposes` - {} + * - `purposes` - [{}] * - `description` - Lands at Mosshayne Farm, Exeter & Broadclyst * - `startDate` - 2022-04-01 * - `endDate` - 2022-05-06 - * - `issues` - null + * - `issues` - '' * * @param {object} [data] - Any data you want to use instead of the defaults used here or in the database * @@ -56,12 +56,12 @@ function add (data = {}) { */ function defaults (data = {}) { const licenceRef = data.licenceRef ? data.licenceRef : generateLicenceRef() - const returnReference = data.returnReference ? data.returnReference : randomInteger(10000000, 19999999) + const returnReference = data.returnReference ? data.returnReference : generateLegacyId() const defaults = { reviewLicenceId: generateUUID(), returnId: generateReturnLogId('2022-04-01', '2023-03-31', 1, licenceRef, returnReference), - returnReference: '10031343', + returnReference, quantity: 0, allocated: 0, underQuery: false, @@ -74,7 +74,7 @@ function defaults (data = {}) { description: 'Lands at Mosshayne Farm, Exeter & Broadclyst', startDate: new Date('2022-04-01'), endDate: new Date('2022-05-06'), - issues: null + issues: '' } return { diff --git a/test/validators/bill-runs/two-part-tariff/authorised-volume.validator.test.js b/test/validators/bill-runs/review/authorised.validator.test.js similarity index 57% rename from test/validators/bill-runs/two-part-tariff/authorised-volume.validator.test.js rename to test/validators/bill-runs/review/authorised.validator.test.js index b8164521ce..1185d9ed42 100644 --- a/test/validators/bill-runs/two-part-tariff/authorised-volume.validator.test.js +++ b/test/validators/bill-runs/review/authorised.validator.test.js @@ -8,38 +8,32 @@ const { describe, it, beforeEach } = exports.lab = Lab.script() const { expect } = Code // Thing under test -const AuthorisedVolumeValidator = require('../../../../app/validators/bill-runs/two-part-tariff/authorised-volume.validator.js') +const AuthorisedValidator = require('../../../../app/validators/bill-runs/review/authorised.validator.js') -describe('Authorised Volume validator', () => { +describe('Bill Runs Review - Authorised validator', () => { let payload - describe('when a valid payload is provided', () => { - describe('because the user entered an authorised volume', () => { - beforeEach(() => { - payload = { - authorisedVolume: '10', - totalBillableReturns: '5' - } - }) + describe('when valid data is provided', () => { + beforeEach(() => { + payload = { amendedAuthorisedVolume: '10', totalBillableReturns: '5' } + }) - it('confirms the payload is valid', () => { - const result = AuthorisedVolumeValidator.go(payload) + it('confirms the data is valid', () => { + const result = AuthorisedValidator.go(payload) - expect(result.error).not.to.exist() - }) + expect(result.value).to.exist() + expect(result.error).not.to.exist() }) }) - describe('when an invalid payload is provided', () => { + describe('when invalid data is provided', () => { describe('because the user did not enter an authorised volume', () => { beforeEach(() => { - payload = { - totalBillableReturns: '5' - } + payload = { totalBillableReturns: '5' } }) it('fails the validation with the message "Enter an authorised volume"', () => { - const result = AuthorisedVolumeValidator.go(payload) + const result = AuthorisedValidator.go(payload) expect(result.error).to.exist() expect(result.error.details[0].message).to.equal('Enter an authorised volume') @@ -48,14 +42,11 @@ describe('Authorised Volume validator', () => { describe('because the user entered text', () => { beforeEach(() => { - payload = { - authorisedVolume: 'Hello World', - totalBillableReturns: '5' - } + payload = { amendedAuthorisedVolume: 'Hello World', totalBillableReturns: '5' } }) it('fails the validation with the message "The authorised volume must be a number"', () => { - const result = AuthorisedVolumeValidator.go(payload) + const result = AuthorisedValidator.go(payload) expect(result.error).to.exist() expect(result.error.details[0].message).to.equal('The authorised volume must be a number') @@ -64,14 +55,11 @@ describe('Authorised Volume validator', () => { describe('because the user entered a number less than the totalBillableReturns', () => { beforeEach(() => { - payload = { - authorisedVolume: '5', - totalBillableReturns: '6' - } + payload = { amendedAuthorisedVolume: '5', totalBillableReturns: '6' } }) it('fails the validation with the message "The authorised volume must be greater than 6"', () => { - const result = AuthorisedVolumeValidator.go(payload) + const result = AuthorisedValidator.go(payload) expect(result.error).to.exist() expect(result.error.details[0].message).to.equal('The authorised volume must be greater than 6') @@ -80,14 +68,11 @@ describe('Authorised Volume validator', () => { describe('because the user entered too many decimal places', () => { beforeEach(() => { - payload = { - authorisedVolume: '15.1234567', - totalBillableReturns: '5' - } + payload = { amendedAuthorisedVolume: '15.1234567', totalBillableReturns: '5' } }) it('fails the validation with the message "The authorised volume must not have more than 6 decimal places"', () => { - const result = AuthorisedVolumeValidator.go(payload) + const result = AuthorisedValidator.go(payload) expect(result.error).to.exist() expect(result.error.details[0].message).to.equal('The authorised volume must not have more than 6 decimal places') diff --git a/test/validators/bill-runs/two-part-tariff/billable-returns.validator.test.js b/test/validators/bill-runs/review/edit.validator.test.js similarity index 58% rename from test/validators/bill-runs/two-part-tariff/billable-returns.validator.test.js rename to test/validators/bill-runs/review/edit.validator.test.js index 0b9b71ec94..4bd022d983 100644 --- a/test/validators/bill-runs/two-part-tariff/billable-returns.validator.test.js +++ b/test/validators/bill-runs/review/edit.validator.test.js @@ -8,54 +8,46 @@ const { describe, it, beforeEach } = exports.lab = Lab.script() const { expect } = Code // Thing under test -const BillableReturnsValidator = require('../../../../app/validators/bill-runs/two-part-tariff/billable-returns.validator.js') +const EditValidator = require('../../../../app/validators/bill-runs/review/edit.validator.js') -describe('Billable Returns validator', () => { +describe('Bill Runs Review - Edit validator', () => { let payload - describe('when a valid payload is provided', () => { + describe('when valid data is provided', () => { describe('because the user selected the authorised volume option', () => { beforeEach(() => { - payload = { - 'quantity-options': 25, - authorisedVolume: 30 - } + payload = { quantityOptions: 25, authorisedVolume: 30 } }) - it('confirms the payload is valid', () => { - const result = BillableReturnsValidator.go(payload) + it('confirms the data is valid', () => { + const result = EditValidator.go(payload) + expect(result.value).to.exist() expect(result.error).not.to.exist() }) }) - describe('because the user entered a valid volume (less than the authorised volume but greater than 0)', () => { + describe('because the user entered a valid custom volume (less than the authorised volume but greater than 0)', () => { beforeEach(() => { - payload = { - 'quantity-options': 'customQuantity', - customQuantity: 12, - authorisedVolume: 30 - } + payload = { quantityOptions: 'customQuantity', customQuantity: 12, authorisedVolume: 30 } }) - it('confirms the payload is valid', () => { - const result = BillableReturnsValidator.go(payload) + it('confirms the data is valid', () => { + const result = EditValidator.go(payload) expect(result.error).not.to.exist() }) }) }) - describe('when an invalid payload is provided', () => { + describe('when invalid data is provided', () => { describe('because the user did not select an option', () => { beforeEach(() => { - payload = { - authorisedVolume: 30 - } + payload = { authorisedVolume: 30 } }) it('fails the validation with the message "Select the billable quantity"', () => { - const result = BillableReturnsValidator.go(payload) + const result = EditValidator.go(payload) expect(result.error).to.exist() expect(result.error.details[0].message).to.equal('Select the billable quantity') @@ -65,14 +57,11 @@ describe('Billable Returns validator', () => { describe('because the user selected to enter a custom quantity', () => { describe('but entered no value', () => { beforeEach(() => { - payload = { - 'quantity-options': 'customQuantity', - authorisedVolume: 25 - } + payload = { quantityOptions: 'customQuantity', authorisedVolume: 25 } }) it('fails validation with the message "Enter the billable quantity"', () => { - const result = BillableReturnsValidator.go(payload) + const result = EditValidator.go(payload) expect(result.error).to.exist() expect(result.error.details[0].message).to.equal('Enter the billable quantity') @@ -81,15 +70,11 @@ describe('Billable Returns validator', () => { describe('but entered text', () => { beforeEach(() => { - payload = { - 'quantity-options': 'customQuantity', - customQuantity: 'Hello world', - authorisedVolume: 25 - } + payload = { quantityOptions: 'customQuantity', customQuantity: 'Hello world', authorisedVolume: 25 } }) it('fails validation with the message "The quantity must be a number"', () => { - const result = BillableReturnsValidator.go(payload) + const result = EditValidator.go(payload) expect(result.error).to.exist() expect(result.error.details[0].message).to.equal('The quantity must be a number') @@ -98,15 +83,11 @@ describe('Billable Returns validator', () => { describe('but entered a number with more than 6 decimal places', () => { beforeEach(() => { - payload = { - 'quantity-options': 'customQuantity', - customQuantity: 12.3456789, - authorisedVolume: 25 - } + payload = { quantityOptions: 'customQuantity', customQuantity: 12.3456789, authorisedVolume: 25 } }) it('fails validation with the message "The quantity must contain no more than 6 decimal places"', () => { - const result = BillableReturnsValidator.go(payload) + const result = EditValidator.go(payload) expect(result.error).to.exist() expect(result.error.details[0].message).to.equal('The quantity must contain no more than 6 decimal places') @@ -115,15 +96,11 @@ describe('Billable Returns validator', () => { describe('but entered a number less than 0', () => { beforeEach(() => { - payload = { - 'quantity-options': 'customQuantity', - customQuantity: -0.1, - authorisedVolume: 25 - } + payload = { quantityOptions: 'customQuantity', customQuantity: -0.1, authorisedVolume: 25 } }) it('fails validation with the message "The quantity must be zero or higher"', () => { - const result = BillableReturnsValidator.go(payload) + const result = EditValidator.go(payload) expect(result.error).to.exist() expect(result.error.details[0].message).to.equal('The quantity must be zero or higher') @@ -132,15 +109,11 @@ describe('Billable Returns validator', () => { describe('but entered a number greater than the authorised annual quantity', () => { beforeEach(() => { - payload = { - 'quantity-options': 'customQuantity', - customQuantity: 40, - authorisedVolume: 25 - } + payload = { quantityOptions: 'customQuantity', customQuantity: 40, authorisedVolume: 25 } }) it('fails validation with the message "The quantity must be the same as or less than the authorised amount"', () => { - const result = BillableReturnsValidator.go(payload) + const result = EditValidator.go(payload) expect(result.error).to.exist() expect(result.error.details[0].message).to.equal('The quantity must be the same as or less than the authorised amount') diff --git a/test/validators/bill-runs/review/factors.validator.test.js b/test/validators/bill-runs/review/factors.validator.test.js new file mode 100644 index 0000000000..b563bbf378 --- /dev/null +++ b/test/validators/bill-runs/review/factors.validator.test.js @@ -0,0 +1,155 @@ +'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 FactorsValidator = require('../../../../app/validators/bill-runs/review/factors.validator.js') + +describe('Bill Runs Review - Factors validator', () => { + let payload + + describe('when valid data is provided', () => { + beforeEach(() => { + payload = { amendedAggregate: 0.5, amendedChargeAdjustment: 0.5 } + }) + + it('confirms the data is valid', () => { + const result = FactorsValidator.go(payload) + + expect(result.value).to.exist() + expect(result.error).not.to.exist() + }) + }) + + describe('when invalid data is provided', () => { + describe('because nothing was entered', () => { + beforeEach(() => { + // NOTE: Confirmed in manual testing. Payload mya have no properties, but it itself is never undefined + payload = {} + }) + + it('fails the validation with the messages "Enter an aggregate factor" and "Enter a charge factor"', () => { + const result = FactorsValidator.go(payload) + + expect(result.error).to.exist() + expect(result.error.details[0].message).to.equal('Enter an aggregate factor') + expect(result.error.details[1].message).to.equal('Enter a charge factor') + }) + }) + + describe('and the aggregate factor is the issue', () => { + beforeEach(() => { + payload = { amendedChargeAdjustment: 0.5 } + }) + + describe('because nothing was entered', () => { + it('fails the validation with the message "Enter a aggregate factor"', () => { + const result = FactorsValidator.go(payload) + + expect(result.error).to.exist() + expect(result.error.details[0].message).to.equal('Enter an aggregate factor') + }) + }) + + describe('because the user entered text', () => { + beforeEach(() => { + payload.amendedAggregate = 'Hello World' + }) + + it('fails the validation with the message "The aggregate factor must be a number"', () => { + const result = FactorsValidator.go(payload) + + expect(result.error).to.exist() + expect(result.error.details[0].message).to.equal('The aggregate factor must be a number') + }) + }) + + describe('because the user entered too many decimal places', () => { + beforeEach(() => { + payload.amendedAggregate = 0.1234567890123456 + }) + + it('fails the validation with the message "The aggregate factor must not have more than 15 decimal places"', () => { + const result = FactorsValidator.go(payload) + + expect(result.error).to.exist() + expect(result.error.details[0].message).to.equal( + 'The aggregate factor must not have more than 15 decimal places' + ) + }) + }) + + describe('because the user entered a value less than 0', () => { + beforeEach(() => { + payload.amendedAggregate = -1 + }) + + it('fails the validation with the message "The aggregate factor must be greater than 0"', () => { + const result = FactorsValidator.go(payload) + + expect(result.error).to.exist() + expect(result.error.details[0].message).to.equal('The aggregate factor must be greater than 0') + }) + }) + }) + + describe('and the charge factor is the issue', () => { + beforeEach(() => { + payload = { amendedAggregate: 0.5 } + }) + + describe('because nothing was entered', () => { + it('fails the validation with the message "Enter a charge factor"', () => { + const result = FactorsValidator.go(payload) + + expect(result.error).to.exist() + expect(result.error.details[0].message).to.equal('Enter a charge factor') + }) + }) + + describe('because the user entered text', () => { + beforeEach(() => { + payload.amendedChargeAdjustment = 'Hello World' + }) + + it('fails the validation with the message "The charge factor must be a number"', () => { + const result = FactorsValidator.go(payload) + + expect(result.error).to.exist() + expect(result.error.details[0].message).to.equal('The charge factor must be a number') + }) + }) + + describe('because the user entered too many decimal places', () => { + beforeEach(() => { + payload.amendedChargeAdjustment = 0.5555555555555555 + }) + + it('fails the validation with the message "The charge factor must not have more than 15 decimal places"', () => { + const result = FactorsValidator.go(payload) + + expect(result.error).to.exist() + expect(result.error.details[0].message).to.equal('The charge factor must not have more than 15 decimal places') + }) + }) + + describe('because the user entered a value less than 0', () => { + beforeEach(() => { + payload.amendedChargeAdjustment = -1 + }) + + it('fails the validation with the message "The charge factor must be greater than 0"', () => { + const result = FactorsValidator.go(payload) + + expect(result.error).to.exist() + expect(result.error.details[0].message).to.equal('The charge factor must be greater than 0') + }) + }) + }) + }) +}) diff --git a/test/validators/bill-runs/two-part-tariff/adjustment-factor.validator.test.js b/test/validators/bill-runs/two-part-tariff/adjustment-factor.validator.test.js deleted file mode 100644 index fbf7be2133..0000000000 --- a/test/validators/bill-runs/two-part-tariff/adjustment-factor.validator.test.js +++ /dev/null @@ -1,194 +0,0 @@ -'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 AdjustmentFactorValidator = require('../../../../app/validators/bill-runs/two-part-tariff/adjustment-factor.validator.js') - -describe('Adjustment Factor validator', () => { - const maxNumberOfDecimals = 15 - - let payload - let validationType - - describe('when a valid payload is provided', () => { - describe('because there is a valid aggregate factor', () => { - beforeEach(() => { - payload = { - amendedAggregateFactor: 0.5 - } - - validationType = 'aggregate' - }) - - it('confirms the payload is valid', () => { - const result = AdjustmentFactorValidator.go(payload.amendedAggregateFactor, maxNumberOfDecimals, validationType) - - expect(result.error).not.to.exist() - }) - }) - - describe('because there is a valid charge factor', () => { - beforeEach(() => { - payload = { - amendedChargeAdjustment: 0.5 - } - - validationType = 'charge' - }) - - it('confirms the payload is valid', () => { - const result = AdjustmentFactorValidator.go( - payload.amendedChargeAdjustment, - maxNumberOfDecimals, - validationType - ) - - expect(result.error).not.to.exist() - }) - }) - }) - - describe('when an invalid payload is provided for the aggregate factor', () => { - describe('because nothing was entered', () => { - beforeEach(() => { - payload = undefined - - validationType = 'aggregate' - }) - - it('fails the validation with the message "Enter a aggregate factor"', () => { - const result = AdjustmentFactorValidator.go(payload, maxNumberOfDecimals, validationType) - - expect(result.error).to.exist() - expect(result.error.details[0].message).to.equal('Enter a aggregate factor') - }) - }) - - describe('because the user entered text', () => { - beforeEach(() => { - payload = { - amendedAggregateFactor: 'Hello World' - } - - validationType = 'aggregate' - }) - - it('fails the validation with the message "The aggregate factor must be a number"', () => { - const result = AdjustmentFactorValidator.go(payload.amendedAggregateFactor, maxNumberOfDecimals, validationType) - - expect(result.error).to.exist() - expect(result.error.details[0].message).to.equal('The aggregate factor must be a number') - }) - }) - - describe('because the user entered too many decimal places', () => { - beforeEach(() => { - payload = { - amendedAggregateFactor: 0.1234567890123456 - } - - validationType = 'aggregate' - }) - - it('fails the validation with the message "The aggregate factor must not have more than 15 decimal places"', () => { - const result = AdjustmentFactorValidator.go(payload.amendedAggregateFactor, maxNumberOfDecimals, validationType) - - expect(result.error).to.exist() - expect(result.error.details[0].message).to.equal( - 'The aggregate factor must not have more than 15 decimal places' - ) - }) - }) - - describe('because the user entered a value less than 0', () => { - beforeEach(() => { - payload = { - amendedAggregateFactor: -1 - } - - validationType = 'aggregate' - }) - - it('fails the validation with the message "The aggregate factor must be greater than 0"', () => { - const result = AdjustmentFactorValidator.go(payload.amendedAggregateFactor, maxNumberOfDecimals, validationType) - - expect(result.error).to.exist() - expect(result.error.details[0].message).to.equal('The aggregate factor must be greater than 0') - }) - }) - }) - - describe('when an invalid payload is provided for the charge factor', () => { - describe('because nothing was entered', () => { - beforeEach(() => { - payload = undefined - - validationType = 'charge' - }) - - it('fails the validation with the message "Enter a charge factor"', () => { - const result = AdjustmentFactorValidator.go(payload, maxNumberOfDecimals, validationType) - - expect(result.error).to.exist() - expect(result.error.details[0].message).to.equal('Enter a charge factor') - }) - }) - - describe('because the user entered text', () => { - beforeEach(() => { - payload = { - amendedChargeFactor: 'Hello World' - } - - validationType = 'charge' - }) - - it('fails the validation with the message "The charge factor must be a number"', () => { - const result = AdjustmentFactorValidator.go(payload.amendedChargeFactor, maxNumberOfDecimals, validationType) - - expect(result.error).to.exist() - expect(result.error.details[0].message).to.equal('The charge factor must be a number') - }) - }) - - describe('because the user entered too many decimal places', () => { - beforeEach(() => { - payload = { - amendedChargeFactor: 0.5555555555555555 - } - - validationType = 'charge' - }) - - it('fails the validation with the message "The charge factor must not have more than 15 decimal places"', () => { - const result = AdjustmentFactorValidator.go(payload.amendedChargeFactor, maxNumberOfDecimals, validationType) - - expect(result.error).to.exist() - expect(result.error.details[0].message).to.equal('The charge factor must not have more than 15 decimal places') - }) - }) - - describe('because the user entered a value less than 0', () => { - beforeEach(() => { - payload = { - amendedChargeFactor: -1 - } - - validationType = 'charge' - }) - - it('fails the validation with the message "The charge factor must be greater than 0"', () => { - const result = AdjustmentFactorValidator.go(payload.amendedChargeFactor, maxNumberOfDecimals, validationType) - - expect(result.error).to.exist() - expect(result.error.details[0].message).to.equal('The charge factor must be greater than 0') - }) - }) - }) -})