diff --git a/app/controllers/check/supplementary.controller.js b/app/controllers/check/supplementary.controller.js deleted file mode 100644 index 9799a05bb2..0000000000 --- a/app/controllers/check/supplementary.controller.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict' - -/** - * Controller for /test/supplementary endpoints - * @module SupplementaryController - */ - -const SupplementaryDataService = require('../../services/check/supplementary-data.service.js') - -async function index (request, h) { - const result = await SupplementaryDataService.go(request.query.region) - - return h.response(result).code(200) -} - -module.exports = { - index -} diff --git a/app/plugins/router.plugin.js b/app/plugins/router.plugin.js index fe0929b4d3..6fdc1364e7 100644 --- a/app/plugins/router.plugin.js +++ b/app/plugins/router.plugin.js @@ -13,7 +13,6 @@ const AssetRoutes = require('../routes/assets.routes.js') const BillRunRoutes = require('../routes/bill-runs.routes') -const CheckRoutes = require('../routes/check.routes.js') const FilterRoutesService = require('../services/plugins/filter-routes.service.js') const HealthRoutes = require('../routes/health.routes.js') const RootRoutes = require('../routes/root.routes.js') @@ -24,7 +23,6 @@ const routes = [ ...RootRoutes, ...AssetRoutes, ...HealthRoutes, - ...CheckRoutes, ...BillRunRoutes ] diff --git a/app/presenters/check/supplementary-data.presenter.js b/app/presenters/check/supplementary-data.presenter.js deleted file mode 100644 index b85fbcba3f..0000000000 --- a/app/presenters/check/supplementary-data.presenter.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict' - -/** - * Formats responses from the `SupplementaryDataService` - * @module SupplementaryDataPresenter - */ - -function go (data) { - const chargeVersions = data.chargeVersions - - return { - billingPeriods: data.billingPeriods, - chargeVersions - } -} - -module.exports = { - go -} diff --git a/app/routes/check.routes.js b/app/routes/check.routes.js deleted file mode 100644 index 8114d6205d..0000000000 --- a/app/routes/check.routes.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict' - -const SupplementaryController = require('../controllers/check/supplementary.controller.js') - -const routes = [ - { - method: 'GET', - path: '/check/supplementary', - handler: SupplementaryController.index, - options: { - description: 'Used by the delivery team to check the SROC supplementary billing logic' - } - } -] - -module.exports = routes diff --git a/app/services/check/supplementary-data.service.js b/app/services/check/supplementary-data.service.js deleted file mode 100644 index fa87895546..0000000000 --- a/app/services/check/supplementary-data.service.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict' - -/** - * Confirms the billing periods, licences and charge versions used to generate an SROC supplementary bill run - * @module SupplementaryDataService - */ - -const BillingPeriodService = require('../supplementary-billing/billing-period.service.js') -const FetchChargeVersionsService = require('../supplementary-billing/fetch-charge-versions.service.js') -const FetchRegionService = require('../supplementary-billing/fetch-region.service.js') -const SupplementaryDataPresenter = require('../../presenters/check/supplementary-data.presenter.js') - -async function go (naldRegionId) { - const region = await FetchRegionService.go(naldRegionId) - const billingPeriods = BillingPeriodService.go() - - // We know in the future we will be calculating multiple billing periods and so will have to iterate through each, - // generating bill runs and reviewing if there is anything to bill. For now, whilst our knowledge of the process - // is low we are focusing on just the current financial year, and intending to ship a working version for just it. - // This is why we are only passing through the first billing period; we know there is only one! - const chargeVersions = await FetchChargeVersionsService.go(region.regionId, billingPeriods[0]) - - return _response({ billingPeriods, chargeVersions }) -} - -function _response (data) { - return SupplementaryDataPresenter.go(data) -} - -module.exports = { - go -} diff --git a/app/services/supplementary-billing/billing-period.service.js b/app/services/supplementary-billing/billing-period.service.js index 4533a46fb0..df16a2c7fb 100644 --- a/app/services/supplementary-billing/billing-period.service.js +++ b/app/services/supplementary-billing/billing-period.service.js @@ -12,13 +12,13 @@ * **IMPORTANT!** This service currently only handles SROC billing periods and only the 'current year' * * Using the current date at the time the service is called, it calculates the billing periods to use. We permit - * changes to charge versions to be retroactively applied up to 5 years. So, a bill run generated in 2022 would need - * to consider every financial year back to 2017. + * changes to charge versions to be retroactively applied up to 5 years. So, a bill run generated in 2022 would need to + * consider every financial year back to 2017. * * The exception to that is the change in charge scheme that happened in 2022, when we moved from ALCS (or PRESROC) to * SROC. Changes prior to 2022 would only apply to a ALCS bill run and vice versa. * - * @returns {Object[]} an array of billing periods each containing a `startDate` and `endDate`. + * @returns {Object[]} An array of billing periods each containing a `startDate` and `endDate`. */ function go () { // TODO: We have hardcoded the billing period this service returns to be 2022-23 the first year SROC went live. This diff --git a/app/services/supplementary-billing/calculate-authorised-and-billable-days.service.js b/app/services/supplementary-billing/calculate-authorised-and-billable-days.service.js index 815dd6e08c..daf83990ef 100644 --- a/app/services/supplementary-billing/calculate-authorised-and-billable-days.service.js +++ b/app/services/supplementary-billing/calculate-authorised-and-billable-days.service.js @@ -7,6 +7,8 @@ const ConsolidateDateRangesService = require('./consolidate-date-ranges.service.js') +const ONE_DAY_IN_MILLISECONDS = 24 * 60 * 60 * 1000 + /** * Returns the authorised and billable days for a given charge element based on its abstraction periods * @@ -15,9 +17,9 @@ const ConsolidateDateRangesService = require('./consolidate-date-ranges.service. * start and end day and month, for example 1 Apr to 31 Oct. They do not have years because the intent is they are the * same period no matter what year it is. * - * **Authorised** days is how much of the billing period overlaps with the abstraction period. **Billable** days is - * how much of the charge period overlaps with the abstraction period (see params for explanations of billable and - * charge periods). + * **Authorised** days is how much of the billing period overlaps with the abstraction period. **Billable** days is how + * much of the charge period overlaps with the abstraction period (see params for explanations of billable and charge + * periods). * * Calculating these values is complicated by the fact a charge element may have multiple abstraction periods. Added to * that abstraction periods do not intersect nicely with billing or charge periods. The abstraction period might start @@ -28,34 +30,30 @@ const ConsolidateDateRangesService = require('./consolidate-date-ranges.service. * - **1 Jan 2023 to 30 Jun 2023** intersects as 1 Jan 2023 to 31 Mar 2023 * * The number of days the abstraction period intersects either the billing or charge period is where we get our 'days' - * from. The final complication is we cannot double count. If 2 charge purposes have abstraction periods that overlap - * we must only count one of them. For example + * from. The final complication is we cannot double count. If 2 charge purposes have abstraction periods that overlap we + * must only count one of them. For example * * - charge purpose 1 has **1 Jan to 30 Jun** * - charge purpose 2 has **1 May to 31 Oct** * - * They overlap 1 May to 30 Jun. To get our days we summate the result for each abstraction period and have to ensure - * we only count this once. + * They overlap 1 May to 30 Jun. To get our days we summate the result for each abstraction period and have to ensure we + * only count this once. * * So, a charge purpose's abstraction dates might result in 2 relevant abstraction periods. A charge element might have * multiple charge purposes. But we must return a single **Authorised** and **Billable** days calculation. This problem * is what this service tackles. * - * @param {Object} chargePeriod charge period is determined as the overlap between a charge version's start and end - * dates, and the billing period's (financial year) start and end dates. So, when the charge version and billing period - * are compared the charge period's start date is the latest of the two, and the end date is the earliest of their end - * dates - * @param {Date} chargePeriod.startDate - * @param {Date} chargePeriod.endDate - * @param {Object} billingPeriod the period a billing batch is being calculated for. Currently, this always equates to - * a financial year, for example, 2022-04-01 to 2023-03-31 - * @param {Date} billingPeriod.startDate - * @param {Date} billingPeriod.endDate - * @param {module:ChargeElementModel} chargeElement referred to as the 'charge reference' in the UI, for example, - * 4.1.10. A charge version can have multiple charge elements, though each will have a different reference. Each element - * can have multiple charge purposes and it's these that hold the abstraction period data - * - * @returns {Object} an object containing an `authorisedDays` and `billableDays` property + * @param {{startDate: Date, endDate: Date}} chargePeriod Charge period is determined as the overlap between a charge + * version's start and end dates, and the billing period's (financial year) start and end dates. So, when the charge + * version and billing period are compared the charge period's start date is the latest of the two, and the end date is + * the earliest of their end dates + * @param {{startDate: Date, endDate: Date}} billingPeriod The period a billing batch is being calculated for. + * Currently, this always equates to a financial year, for example, 2022-04-01 to 2023-03-31 + * @param {module:ChargeElementModel} chargeElement Referred to as the 'charge reference' in the UI, for example, + * 4.1.10. A charge version can have multiple charge elements, though each will have a different reference. Each + * element can have multiple charge purposes and it's these that hold the abstraction period data + * + * @returns {Object} An object containing an `authorisedDays` and `billableDays` property */ function go (chargePeriod, billingPeriod, chargeElement) { const { chargePurposes } = chargeElement @@ -166,21 +164,18 @@ function _abstractionPeriods (referencePeriod, chargePurpose) { * @returns {number} the length of the period in days (inclusive) */ function _calculateDays (abstractionOverlapPeriod) { - const DAY_IN_MILLISECONDS = (24 * 60 * 60 * 1000) // (24 hrs * 60 mins * 60 secs * 1000 msecs) - - // difference in msecs const difference = abstractionOverlapPeriod.endDate.getTime() - abstractionOverlapPeriod.startDate.getTime() // ceil() always rounds up, even if the result is 1.1 (rounds to 2). We add 1 to make the calculation inclusive of // the last day - return Math.ceil(difference / DAY_IN_MILLISECONDS) + 1 + return Math.ceil(difference / ONE_DAY_IN_MILLISECONDS) + 1 } /** * Calculates the period that an abstraction period overlaps the reference period * - * Before we can work out either the authorised or billable days, we first need to work out what part of the - * abstraction period overlaps. + * Before we can work out either the authorised or billable days, we first need to work out what part of the abstraction + * period overlaps. * * The simplest way to look at this is that the overlapping period is the latest start date and the earliest end date. * @@ -193,10 +188,11 @@ function _calculateDays (abstractionOverlapPeriod) { * - 01-NOV-2022 to 31-MAY-2023 - overlap is 01-NOV-2022 to 31-MAR-2023 * * @param {Object} referencePeriod either the billing period or charge period - * @param {*} abstractionPeriod a start and end date representing the abstraction period relative to the reference + * @param {Object} abstractionPeriod an object containing `startDate` and `endDate` date representing the abstraction + * period relative to the reference * * @returns {Object} an object with a start and end date representing the part of the abstraction period that overlaps - * the reference period + * the reference period */ function _calculateAbstractionOverlapPeriod (referencePeriod, abstractionPeriod) { const latestStartDateTimestamp = Math.max(abstractionPeriod.startDate, referencePeriod.startDate) diff --git a/app/services/supplementary-billing/check-live-bill-run.service.js b/app/services/supplementary-billing/check-live-bill-run.service.js index 671a56edd0..592d2a84f8 100644 --- a/app/services/supplementary-billing/check-live-bill-run.service.js +++ b/app/services/supplementary-billing/check-live-bill-run.service.js @@ -14,14 +14,14 @@ const LIVE_STATUSES = ['processing', 'ready', 'review', 'queued'] * * We define "live" as having the status `processing`, `ready`, `review` or `queued` * - * @param {*} regionId The id of the region to be checked - * @param {*} financialYear The financial year to be checked + * @param {String} regionId The id of the region to be checked + * @param {Number} financialYear The financial year to be checked * * @returns {Boolean} Whether a "live" bill run exists */ async function go (regionId, financialYear) { const numberOfLiveBillRuns = await BillingBatchModel.query() - .select('billing_batch_id') + .select(1) .where({ regionId, toFinancialYearEnding: financialYear, diff --git a/app/services/supplementary-billing/consolidate-date-ranges.service.js b/app/services/supplementary-billing/consolidate-date-ranges.service.js index 89efa39211..28c4c8d85b 100644 --- a/app/services/supplementary-billing/consolidate-date-ranges.service.js +++ b/app/services/supplementary-billing/consolidate-date-ranges.service.js @@ -44,10 +44,9 @@ * { startDate: 2023-11-01, endDate: 2023-12-01 } // Range 4 unchanged * ] * - * @param {Array.<{startDate: Date, endDate: Date}>} dateRanges Array containing a series of date ranges to be - * consolidated, each of which is an Object containing startDate and endDate, both of which are Dates + * @param {{startDate: Date, endDate: Date}[]} dateRanges Array containing a series of date ranges to be consolidated. * - * @returns {Array.<{startDate: Date, endDate: Date}>} An array of the consolidated date ranges + * @returns {{startDate: Date, endDate: Date}[]} An array of the consolidated date ranges */ function go (dateRanges) { // We sort the date ranges by start date from earliest to latest to make life easier when consolidating them @@ -92,9 +91,9 @@ function _consolidateDates (dateRanges) { return [...acc, previousRange] } - // If the current range's start date is on or earlier than the previous end date then the current range overlaps (starting - // the same day as the previous one ends counts as overlapping) so we add a new date range to our ongoing acc array, - // starting when the previous range starts and ending when the current range end + // If the current range's start date is on or earlier than the previous end date then the current range overlaps + // (starting the same day as the previous one ends counts as overlapping) so we add a new date range to our ongoing + // acc array, starting when the previous range starts and ending when the current range ends if (currentRange.startDate <= previousRange.endDate) { return [...acc, { startDate: previousRange.startDate, endDate: currentRange.endDate }] } diff --git a/app/services/supplementary-billing/create-billing-batch-event.service.js b/app/services/supplementary-billing/create-billing-batch-event.service.js index 5e2547d228..26d5056bfb 100644 --- a/app/services/supplementary-billing/create-billing-batch-event.service.js +++ b/app/services/supplementary-billing/create-billing-batch-event.service.js @@ -12,9 +12,9 @@ const GeneralLib = require('../../lib/general.lib.js') /** * Create an event for when a new bill run is initialised * - * @param {module:BillingBatchModel} [billingBatch] An instance of `BillingBatchModel` representing the initialised - * billing batch - * @param {String} [issuer] The email address of the user triggering the event + * @param {module:BillingBatchModel} billingBatch An instance of `BillingBatchModel` representing the initialised + * billing batch + * @param {String} issuer The email address of the user triggering the event * * @returns {Object} The newly created event record */ diff --git a/app/services/supplementary-billing/create-billing-batch.service.js b/app/services/supplementary-billing/create-billing-batch.service.js index 62c138726c..d9f67dbb72 100644 --- a/app/services/supplementary-billing/create-billing-batch.service.js +++ b/app/services/supplementary-billing/create-billing-batch.service.js @@ -13,13 +13,13 @@ const BillingBatchModel = require('../../models/water/billing-batch.model.js') * @param {Object} regionId The regionId for the selected region * @param {Object} billingPeriod The billing period in the format { startDate: 2022-04-01, endDate: 2023-03-31 } * @param {Object} options Optional params to be overridden - * @param {string} [options.batchType=supplementary] The type of billing batch to create. Defaults to 'supplementary' - * @param {string} [options.scheme=sroc] The applicable charging scheme. Defaults to 'sroc' - * @param {string} [options.source=wrls] Where the billing batch originated from. Records imported from NALD have the + * @param {String} [options.batchType=supplementary] The type of billing batch to create. Defaults to 'supplementary' + * @param {String} [options.scheme=sroc] The applicable charging scheme. Defaults to 'sroc' + * @param {String} [options.source=wrls] Where the billing batch originated from. Records imported from NALD have the * source 'nald'. Those created in the service use 'wrls'. Defaults to 'wrls' - * @param {string} [options.externalId=null] The id of the bill run as created in the Charging Module - * @param {string} [options.status=queued] The status that the bill run should be created with - * @param {number} [options.errorCode=null] Numeric error code + * @param {String} [options.externalId=null] The id of the bill run as created in the Charging Module + * @param {String} [options.status=queued] The status that the bill run should be created with + * @param {Number} [options.errorCode=null] Numeric error code * * @returns {module:BillingBatchModel} The newly created billing batch instance with the `.region` property populated */ diff --git a/app/services/supplementary-billing/determine-charge-period.service.js b/app/services/supplementary-billing/determine-charge-period.service.js index 61b25a5a9f..5c8ad5ba9b 100644 --- a/app/services/supplementary-billing/determine-charge-period.service.js +++ b/app/services/supplementary-billing/determine-charge-period.service.js @@ -14,10 +14,10 @@ * * > Charge versions may not have an end date; in this case, we simply use the financial year end date. * - * @param {module:ChargeVersionModel} chargeVersion the charge version being processed for billing - * @param {number} financialYearEnding the year the financial billing period ends + * @param {module:ChargeVersionModel} chargeVersion The charge version being processed for billing + * @param {Number} financialYearEnding The year the financial billing period ends * - * @returns {Object} the start and end date of the calculated charge period + * @returns {{startDate: Date, endDate: Date}} The start and end date of the calculated charge period */ function go (chargeVersion, financialYearEnding) { const financialYearStartDate = new Date(financialYearEnding - 1, 3, 1) diff --git a/app/services/supplementary-billing/determine-minimum-charge.service.js b/app/services/supplementary-billing/determine-minimum-charge.service.js index 015eae0f23..fd744a1751 100644 --- a/app/services/supplementary-billing/determine-minimum-charge.service.js +++ b/app/services/supplementary-billing/determine-minimum-charge.service.js @@ -10,15 +10,15 @@ const DetermineChargePeriodService = require('./determine-charge-period.service. /** * Checks if minimum charge applies to a charge version for the given billing period * - * If the charge version's start date matches the start of the charging period, and the reason for its creation - * is flagged as triggering a minimum charge this service will return true. + * If the charge version's start date matches the start of the charging period, and the reason for its creation is + * flagged as triggering a minimum charge this service will return true. * * If either of those tests is false then the service will return false. * - * @param {module:ChargeVersionModel} chargeVersion the charge version being checked for minimum charge - * @param {number} financialYearEnding the year the financial billing period ends + * @param {module:ChargeVersionModel} chargeVersion The charge version being checked for minimum charge + * @param {Number} financialYearEnding The year the financial billing period ends * - * @returns {boolean} true if minimum charge applies else false + * @returns {Boolean} true if minimum charge applies else false */ function go (chargeVersion, financialYearEnding) { const chargePeriod = DetermineChargePeriodService.go(chargeVersion, financialYearEnding) @@ -30,7 +30,6 @@ function go (chargeVersion, financialYearEnding) { const triggersMinimumCharge = chargeVersion.changeReason?.triggersMinimumCharge ?? false const isFirstChargeOnNewLicence = isSharedStartDate && triggersMinimumCharge - // TODO: confirm whether legacy code is correct when it says return true if charge period starts on first april return isFirstChargeOnNewLicence } diff --git a/app/services/supplementary-billing/fetch-charge-versions.service.js b/app/services/supplementary-billing/fetch-charge-versions.service.js index b936998bcd..512abb32ee 100644 --- a/app/services/supplementary-billing/fetch-charge-versions.service.js +++ b/app/services/supplementary-billing/fetch-charge-versions.service.js @@ -14,10 +14,10 @@ const ChargeVersionWorkflow = require('../../models/water/charge-version-workflo * Fetch all SROC charge versions linked to licences flagged for supplementary billing that are in the period being * billed * - * @param {string} regionId UUID of the region being billed that the licences must be linked to + * @param {String} regionId UUID of the region being billed that the licences must be linked to * @param {Object} billingPeriod Object with a `startDate` and `endDate` property representing the period being billed * - * @returns {Object[]} an array of matching charge versions + * @returns {Object[]} An array of matching charge versions */ async function go (regionId, billingPeriod) { const chargeVersions = await _fetch(regionId, billingPeriod) diff --git a/app/services/supplementary-billing/fetch-previous-billing-transactions.service.js b/app/services/supplementary-billing/fetch-previous-billing-transactions.service.js index 6e82c8f025..f63f6a6c77 100644 --- a/app/services/supplementary-billing/fetch-previous-billing-transactions.service.js +++ b/app/services/supplementary-billing/fetch-previous-billing-transactions.service.js @@ -8,6 +8,18 @@ const { db } = require('../../../db/db.js') +/** + * Fetches the previously billed transactions that match the invoice, licence and year provided, removing any debits + * which are cancelled out by previous credits. + * + * @param {Object} billingInvoice A generated billing invoice that identifies the invoice account ID we need to match + * against + * @param {Object} billingInvoiceLicence A generated billing invoice licence that identifies the licence we need to + * match against + * @param {Number} financialYearEnding The year the financial billing period ends that we need to match against + * + * @returns {Object} The resulting matched billing transactions + */ async function go (billingInvoice, billingInvoiceLicence, financialYearEnding) { const billingTransactions = await _fetch( billingInvoiceLicence.licenceId, diff --git a/app/services/supplementary-billing/fetch-region.service.js b/app/services/supplementary-billing/fetch-region.service.js index 0af8a81f95..2a09ac7197 100644 --- a/app/services/supplementary-billing/fetch-region.service.js +++ b/app/services/supplementary-billing/fetch-region.service.js @@ -13,8 +13,8 @@ const RegionModel = require('../../models/water/region.model.js') * This is a temporary service to help us confirm we are selecting the correct data to use when creating a * supplementary bill run. Its primary aim is to meet the acceptance criteria defined in WATER-3787. * - * @param {string} naldRegionId The NALD region ID (a number between 1 to 9, 9 being the test region) for the region - * to find + * @param {String} naldRegionId The NALD region ID (a number between 1 to 9, 9 being the test region) for the region + * to find * * @returns {Object} Instance of `RegionModel` with the matching NALD Region ID */ diff --git a/app/services/supplementary-billing/generate-billing-invoice-licence.service.js b/app/services/supplementary-billing/generate-billing-invoice-licence.service.js index 144d57701b..458c00fd27 100644 --- a/app/services/supplementary-billing/generate-billing-invoice-licence.service.js +++ b/app/services/supplementary-billing/generate-billing-invoice-licence.service.js @@ -10,16 +10,14 @@ const { randomUUID } = require('crypto') /** * Return either a new billing invoice licence object ready for persisting or an existing one if it exists * - * This first checks whether a billing invoice licence with the same invoice and licence ID exists in - * `generatedBillingInvoiceLicences`. The calling service is expected to provide and keep track of this variable - * between calls. If it does, it returns that instance along with the original array unchanged. + * This first checks whether the billing invoice id and licence id of `currentBillingInvoiceLicence` match the ones + * passed to this service. If they do, we return that instance. * - * If it doesn't, we generate a new instance and create a new array, based on the one provided plus our new instance. - * We then return the instance and the new array as the result. + * If they don't, we generate a new instance and return it. * - * For context, this is all to avoid creating `billing_invoice` and `billing_invoice_licence` records unnecessarily. - * The legacy service will create them first, then determine if there are any transactions to be billed. If there - * aren't, it then has to go back and delete the records it created. + * For context, this is all to avoid creating `billing_invoice` and `billing_invoice_licence` records unnecessarily. The + * legacy service will create them first, then determine if there are any transactions to be billed. If there aren't, it + * then has to go back and delete the records it created. * * Our intent is to only call the DB when we have records that need persisting. So, we start at the transaction level * and only persist `billing_invoice` and `billing_invoice_licence` records that are linked to billable transactions. @@ -27,13 +25,12 @@ const { randomUUID } = require('crypto') * licence data in memory along with ID's, and use this service to provide the right record when persisting the * transaction. * - * @param {Object[]} generatedBillingInvoiceLicences An array of previously generated billing invoice licence objects - * @param {string} billingInvoiceId UUID of the billing invoice this billing invoice licence will be linked to if - * persisted - * @param {module:LicenceModel} licence the licence this billing invoice licence will be linked to + * @param {module:billingInvoiceLicence} currentBillingInvoiceLicence A billing invoice licence object + * @param {String} billingInvoiceId UUID of the billing invoice this billing invoice licence will be linked to if + * persisted + * @param {module:LicenceModel} licence The licence this billing invoice licence will be linked to * - * @returns {Object} A result object containing either the found or generated billing invoice licence object, and an - * array of generated billing invoice licences which includes the one being returned + * @returns {Object} The current or newly-generated billing invoice licence object */ function go (currentBillingInvoiceLicence, billingInvoiceId, licence) { if ( diff --git a/app/services/supplementary-billing/generate-billing-invoice.service.js b/app/services/supplementary-billing/generate-billing-invoice.service.js index c74f6e1251..f41f96fa57 100644 --- a/app/services/supplementary-billing/generate-billing-invoice.service.js +++ b/app/services/supplementary-billing/generate-billing-invoice.service.js @@ -12,12 +12,10 @@ const InvoiceAccountModel = require('../../models/crm-v2/invoice-account.model.j /** * Return either a new billing invoice object ready for persisting or an existing one if it exists * - * This first checks whether a billing invoice with the same invoice account ID exists in - * `generatedBillingInvoices`. The calling service is expected to provide and keep track of this variable between - * between calls. If it does, it returns that instance along with the original array unchanged. + * This first checks whether the invoice account ID of `currentBillingInvoice` matches the one passed to this service. + * If it does, we return that instance. * - * If it doesn't, we generate a new instance and create a new array, based on the one provided plus our new instance. - * We then return the instance and the new array as the result. + * If it doesn't, we generate a new instance and return it. * * For context, this is all to avoid creating `billing_invoice` and `billing_invoice_licence` records unnecessarily. * The legacy service will create them first, then determine if there are any transactions to be billed. If there @@ -29,13 +27,12 @@ const InvoiceAccountModel = require('../../models/crm-v2/invoice-account.model.j * licence data in memory along with ID's, and use this service to provide the right record when persisting the * transaction. * - * @param {Object[]} generatedBillingInvoices An array of previously generated billing invoice objects - * @param {*} invoiceAccountId UUID of the invoice account this billing invoice will be linked to if persisted - * @param {*} billingBatchId UUID of the billing batch this billing invoice will be linked to if persisted - * @param {*} financialYearEnding a value that must exist in the persisted record + * @param {module:BillingInvoiceModel} currentBillingInvoice A billing invoice object + * @param {String} invoiceAccountId UUID of the invoice account this billing invoice will be linked to if persisted + * @param {String} billingBatchId UUID of the billing batch this billing invoice will be linked to if persisted + * @param {Number} financialYearEnding A value that must exist in the persisted record * - * @returns {Object} A result object containing either the found or generated billing invoice object, and an array of - * generated billing invoices which includes the one being returned + * @returns {Object} The current or newly-generated billing invoice object */ async function go (currentBillingInvoice, invoiceAccountId, billingBatchId, financialYearEnding) { if (currentBillingInvoice?.invoiceAccountId === invoiceAccountId) { diff --git a/app/services/supplementary-billing/generate-billing-transactions.service.js b/app/services/supplementary-billing/generate-billing-transactions.service.js index 605a1f3bb6..6989904925 100644 --- a/app/services/supplementary-billing/generate-billing-transactions.service.js +++ b/app/services/supplementary-billing/generate-billing-transactions.service.js @@ -24,11 +24,11 @@ const CalculateAuthorisedAndBillableDaysServiceService = require('./calculate-au * They will then be returned in an array for further processing before being persisted to the DB as * `billing_transactions`. * - * @param {Object} chargeElement the charge element the transaction generated from - * @param {Object} billingPeriod a start and end date representing the billing period for the billing batch - * @param {Object} chargePeriod a start and end date representing the charge period for the charge version - * @param {boolean} isNewLicence whether the charge version is linked to a new licence - * @param {boolean} isWaterUndertaker whether the charge version is linked to a water undertaker licence + * @param {Object} chargeElement The charge element the transaction generated from + * @param {Object} billingPeriod A start and end date representing the billing period for the billing batch + * @param {Object} chargePeriod A start and end date representing the charge period for the charge version + * @param {Boolean} isNewLicence Whether the charge version is linked to a new licence + * @param {Boolean} isWaterUndertaker Whether the charge version is linked to a water undertaker licence * * @returns {Object[]} an array of 0, 1 or 2 transaction objects */ @@ -64,6 +64,10 @@ function go (chargeElement, billingPeriod, chargePeriod, isNewLicence, isWaterUn return billingTransactions } +/** + * Generates a compensation transaction by taking a standard transaction and overwriting it with the supplied billing id + * and the correct charge type and description for a compensation charge. + */ function _compensationTransaction (billingTransactionId, standardTransaction) { return { ...standardTransaction, @@ -103,6 +107,9 @@ function _generatePurposes (chargeElement) { return JSON.stringify(jsonChargePurposes) } +/** + * Generates a standard transaction based on the supplied data, along with some default fields (eg. status) + */ function _standardTransaction ( billingTransactionId, authorisedDays, diff --git a/app/services/supplementary-billing/handle-errored-billing-batch.service.js b/app/services/supplementary-billing/handle-errored-billing-batch.service.js index dd394d3165..b481f1a3ce 100644 --- a/app/services/supplementary-billing/handle-errored-billing-batch.service.js +++ b/app/services/supplementary-billing/handle-errored-billing-batch.service.js @@ -16,8 +16,8 @@ const BillingBatchModel = require('../../models/water/billing-batch.model.js') * Note that although this is async we would generally not call it asyncronously as the intent is you can call it and * continue with whatever error logging is required * - * @param {string} billingBatchId UUID of the billing batch to be marked with `error` status - * @param {number} [errorCode] Numeric error code as defined in BillingBatchModel. Defaults to `null` + * @param {String} billingBatchId UUID of the billing batch to be marked with `error` status + * @param {Number} [errorCode] Numeric error code as defined in BillingBatchModel. Defaults to `null` */ async function go (billingBatchId, errorCode = null) { try { diff --git a/app/services/supplementary-billing/process-billing-batch.service.js b/app/services/supplementary-billing/process-billing-batch.service.js index 7150eda33d..77fadcf983 100644 --- a/app/services/supplementary-billing/process-billing-batch.service.js +++ b/app/services/supplementary-billing/process-billing-batch.service.js @@ -29,7 +29,7 @@ const ProcessBillingTransactionsService = require('./process-billing-transaction * TODO: Currently a placeholder service. Proper implementation is coming * * @param {module:BillingBatchModel} billingBatch The newly created bill batch we need to process - * @param {Object} billingPeriod an object representing the financial year the transaction is for + * @param {Object} billingPeriod An object representing the financial year the transaction is for */ async function go (billingBatch, billingPeriod) { const { billingBatchId } = billingBatch @@ -94,15 +94,15 @@ async function go (billingBatch, billingPeriod) { } /** - * Log the time taken to process the billing batch - * - * If `notifier` is not set then it will do nothing. If it is set this will get the current time and then calculate the - * difference from `startTime`. This and the `billRunId` are then used to generate a log message. - * - * @param {string} billingBatchId Id of the billing batch currently being 'processed' - * @param {BigInt} startTime The time the generate process kicked off. It is expected to be the result of a call to - * `process.hrtime.bigint()` - */ + * Log the time taken to process the billing batch + * + * If `notifier` is not set then it will do nothing. If it is set this will get the current time and then calculate the + * difference from `startTime`. This and the `billRunId` are then used to generate a log message. + * + * @param {string} billingBatchId Id of the billing batch currently being 'processed' + * @param {BigInt} startTime The time the generate process kicked off. It is expected to be the result of a call to + * `process.hrtime.bigint()` + */ function _calculateAndLogTime (billingBatchId, startTime) { const endTime = process.hrtime.bigint() const timeTakenNs = endTime - startTime diff --git a/app/services/supplementary-billing/process-billing-transactions.service.js b/app/services/supplementary-billing/process-billing-transactions.service.js index e193431ade..ef0db3ad20 100644 --- a/app/services/supplementary-billing/process-billing-transactions.service.js +++ b/app/services/supplementary-billing/process-billing-transactions.service.js @@ -1,8 +1,31 @@ 'use strict' +/** + * Fetches the matching debit billing transactions from a previous billing batch and reverses them as credits; removes + * any which would be cancelled out by the supplied calculated debit transactions; combines the remaining transactions + * and returns them all + * @module ProcessBillingTransactionsService + */ + const FetchPreviousBillingTransactionsService = require('./fetch-previous-billing-transactions.service.js') const ReverseBillingTransactionsService = require('./reverse-billing-transactions.service.js') +/** + * Fetches debit-only billing transactions from the previous billing batch for the invoice account and licence provided + * and reverses them as credits. These credits are compared with the supplied calculated debit transactions (ie. debit + * transactions which are to be sent to the Charging Module) and any matching pairs of transactions which would cancel + * each other out are removed. Any remaining reversed credits and calculated debits are returned. + * + * @param {Object[]} calculatedTransactions The calculated transactions to be processed + * @param {Object} billingInvoice A generated billing invoice that identifies the invoice account ID we need to match + * against + * @param {Object} billingInvoiceLicence A generated billing invoice licence that identifies the licence we need to + * match against + * @param {Object} billingPeriod Object with a `startDate` and `endDate` property representing the period being billed + * + * @returns {Object[]} An array of the remaining calculated transactions (ie. those which were not cancelled out by a + * previous matching credit) + */ async function go (calculatedTransactions, billingInvoice, billingInvoiceLicence, billingPeriod) { const previousTransactions = await _fetchPreviousTransactions(billingInvoice, billingInvoiceLicence, billingPeriod) @@ -15,12 +38,19 @@ async function go (calculatedTransactions, billingInvoice, billingInvoiceLicence return _cleanseTransactions(calculatedTransactions, reversedTransactions) } +/** + * Takes a single calculated debit transaction and checks to see if the provided array of reversed (credit) transactions + * contains a transaction that will cancel it out, returning `true` or `false` to indicate if it does or doesn't. Since + * the calculated debit transactions have not yet been sent to the Charging Module, we look at `chargeType`, + * `chargeCategoryCode` and `billableDays` as any given combination of these will always result in the same value coming + * back from the Charging Module. + * + * NOTE: This function will mutate the provided array of reversed transactions if one of the transactions in it will + * cancel the calculated transaction; in this case, we remove the reversed transaction from the array as it can only + * cancel one calculated transaction. + */ function _cancelCalculatedTransaction (calculatedTransaction, reversedTransactions) { const result = reversedTransactions.findIndex((reversedTransaction) => { - // Example of the things we are comparing - // - chargeType - standard or compensation - // - chargeCategory - 4.10.1 - // - billableDays - 215 return reversedTransaction.chargeType === calculatedTransaction.chargeType && reversedTransaction.chargeCategoryCode === calculatedTransaction.chargeCategoryCode && reversedTransaction.billableDays === calculatedTransaction.billableDays @@ -38,17 +68,23 @@ function _cancelCalculatedTransaction (calculatedTransaction, reversedTransactio /** * Remove any "cancelling pairs" of transaction lines. We define a "cancelling pair" as a pair of transactions belonging * to the same billing invoice licence which would send the same data to the Charging Module (and therefore return the - * same values) but with opposing credit flags -- in other words, a credit and a debit which cancel each other out. + * same values) but with opposing credit flags -- in other words, a credit and a debit which cancel each other out. All + * remaining transactions (both calculated transactions and reverse transactions) are returned. */ function _cleanseTransactions (calculatedTransactions, reverseTransactions) { const cleansedTransactionLines = [] + // Iterate over each calculated transaction to see if a transaction in the reverse transactions would form a + // "cancelling pair" with it. If not then add the unpaired calculated transaction to our array of cleansed transaction + // lines. Note that `reverseTransactions` will be mutated to remove any reverse transactions which form a cancelling + // pair. for (const calculatedTransactionLine of calculatedTransactions) { if (!_cancelCalculatedTransaction(calculatedTransactionLine, reverseTransactions)) { cleansedTransactionLines.push(calculatedTransactionLine) } } + // Add the remaining reverse transactions (ie. those which didn't form a cancelling pair) cleansedTransactionLines.push(...reverseTransactions) return cleansedTransactionLines diff --git a/app/services/supplementary-billing/process-previous-billing-transactions.service.js b/app/services/supplementary-billing/process-previous-billing-transactions.service.js deleted file mode 100644 index 14bd906d43..0000000000 --- a/app/services/supplementary-billing/process-previous-billing-transactions.service.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict' - -/** - * Fetches the matching debit billing transactions from a previous billing batch and reverses them as credits - * @module ProcessPreviousBillingTransactionsService - */ - -const FetchPreviousBillingTransactionsService = require('./fetch-previous-billing-transactions.service.js') -const ReverseBillingTransactionsService = require('./reverse-billing-transactions.service.js') - -/** - * Fetches debit-only billing transactions from the previous billing batch for the invoice account and licence provided - * then reverses them as credits - * - * @param {Object} billingInvoice A generated billing invoice that identifies the invoice account ID we need to match - * against - * @param {Object} billingInvoiceLicence A generated billing invoice licence that identifies the licence we need to - * match against. Also, has the billing invoice licence ID we'll be linked our reversed transactions to - * @param {Object} billingPeriod Object with a `startDate` and `endDate` property representing the period being billed - * - * @returns {Object[]} an array of matching Billing Transaction objects with new transaction IDs, the billing invoice - * licence ID set to that passed in, and the `isCredit:` reversed to true - */ -async function go (billingInvoice, billingInvoiceLicence, billingPeriod) { - const previousTransactions = await _fetchPreviousTransactions(billingInvoice, billingInvoiceLicence, billingPeriod) - - const reversedTransactions = ReverseBillingTransactionsService.go(previousTransactions, billingInvoiceLicence) - - return reversedTransactions -} - -async function _fetchPreviousTransactions (billingInvoice, billingInvoiceLicence, billingPeriod) { - const financialYearEnding = billingPeriod.endDate.getFullYear() - - const transactions = await FetchPreviousBillingTransactionsService.go(billingInvoice, billingInvoiceLicence, financialYearEnding) - - return transactions -} - -module.exports = { - go -} diff --git a/app/services/supplementary-billing/reverse-billing-transactions.service.js b/app/services/supplementary-billing/reverse-billing-transactions.service.js index 4f9d9c807c..6b3dd417ae 100644 --- a/app/services/supplementary-billing/reverse-billing-transactions.service.js +++ b/app/services/supplementary-billing/reverse-billing-transactions.service.js @@ -15,11 +15,11 @@ const { randomUUID } = require('crypto') * will reverse the original transactions, with their billing invoice licence id set to the id of the supplied billing * invoice licence. * - * @param {Array[module:BillingTransactionModel]} transactions Array of transactions to be reversed + * @param {module:BillingTransactionModel[]} transactions Array of transactions to be reversed * @param {module:BillingInvoiceLicenceModel} billingInvoiceLicence The billing invoice licence these transactions are * intended to be added to * - * @returns {Array[Object]} Array of reversing transactions with `billingInvoiceLicenceId` set to the id of the supplied + * @returns {Object[]} Array of reversing transactions with `billingInvoiceLicenceId` set to the id of the supplied * `billingInvoiceLicence` */ function go (transactions, billingInvoiceLicence) { diff --git a/test/controllers/check/supplementary.controller.test.js b/test/controllers/check/supplementary.controller.test.js deleted file mode 100644 index 15a63a0bc7..0000000000 --- a/test/controllers/check/supplementary.controller.test.js +++ /dev/null @@ -1,48 +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, after } = exports.lab = Lab.script() -const { expect } = Code - -// Things we need to stub -const SupplementaryDataService = require('../../../app/services/check/supplementary-data.service.js') - -// For running our service -const { init } = require('../../../app/server.js') - -describe('Supplementary controller', () => { - let server - - beforeEach(async () => { - server = await init() - }) - - after(() => { - Sinon.restore() - }) - - describe('GET /check/supplementary', () => { - const options = { - method: 'GET', - url: '/check/supplementary?region=9' - } - - let response - - beforeEach(async () => { - Sinon.stub(SupplementaryDataService, 'go').resolves({ billingPeriods: [], licences: [], chargeVersions: [] }) - - response = await server.inject(options) - }) - - describe('when the request is valid', () => { - it('returns success status 200', async () => { - expect(response.statusCode).to.equal(200) - }) - }) - }) -}) diff --git a/test/presenters/check/supplementary-data.presenter.test.js b/test/presenters/check/supplementary-data.presenter.test.js deleted file mode 100644 index 28966ae11b..0000000000 --- a/test/presenters/check/supplementary-data.presenter.test.js +++ /dev/null @@ -1,83 +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 SupplementaryDataPresenter = require('../../../app/presenters/check/supplementary-data.presenter.js') - -describe('Supplementary presenter', () => { - let data - - describe('when there are results', () => { - beforeEach(() => { - data = { - billingPeriods: [ - { - startDate: new Date(2022, 3, 1), // 2022-04-01 - Months are zero indexed - endDate: new Date(2023, 2, 31) - } - ], - chargeVersions: [ - { - chargeVersionId: '6a472535-145c-4170-ab59-f555783fa6e7', - scheme: 'sroc', - startDate: new Date(2022, 4, 1), - endDate: null, - licence: { - licenceId: 'f1288f6c-8503-4dc1-b114-75c408a14bd0', - licenceRef: 'AT/SROC/SUPB/01' - }, - chargeElements: [ - { - chargeElementId: '0382824f-2b17-4294-aa57-c5fe5749960f', - chargePurposes: [ - { - chargePurposeId: 'ffcb7d57-6148-4ee7-bc95-9de23c0cdc39', - abstractionPeriodStartDay: 1, - abstractionPeriodStartMonth: 4, - abstractionPeriodEndDay: 31, - abstractionPeriodEndMonth: 3 - } - ], - billingChargeCategory: { - reference: '4.2.1' - } - } - ] - } - ] - } - }) - - it('correctly presents the data', () => { - const result = SupplementaryDataPresenter.go(data) - - expect(result.billingPeriods).to.have.length(1) - expect(result.billingPeriods[0]).to.equal(data.billingPeriods[0]) - - expect(result.chargeVersions).to.have.length(1) - expect(result.chargeVersions[0]).to.equal(data.chargeVersions[0]) - }) - }) - - describe('when there are no results', () => { - beforeEach(() => { - data = { - billingPeriods: [], - chargeVersions: [] - } - }) - - it('correctly presents the data', () => { - const result = SupplementaryDataPresenter.go(data) - - expect(result.billingPeriods).to.be.empty() - expect(result.chargeVersions).to.be.empty() - }) - }) -}) diff --git a/test/services/check/supplementary-data.service.test.js b/test/services/check/supplementary-data.service.test.js deleted file mode 100644 index c220f486ee..0000000000 --- a/test/services/check/supplementary-data.service.test.js +++ /dev/null @@ -1,82 +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 BillingPeriodService = require('../../../app/services/supplementary-billing/billing-period.service.js') -const FetchChargeVersionsService = require('../../../app/services/supplementary-billing/fetch-charge-versions.service.js') -const FetchRegionService = require('../../../app/services/supplementary-billing/fetch-region.service.js') - -// Thing under test -const SupplementaryDataService = require('../../../app/services/check/supplementary-data.service.js') - -describe('Supplementary service', () => { - const naldRegionId = 9 - const currentBillingPeriod = { - startDate: new Date('2022-04-01'), - endDate: new Date('2023-03-31') - } - - beforeEach(async () => { - Sinon.stub(BillingPeriodService, 'go').returns([currentBillingPeriod]) - Sinon.stub(FetchRegionService, 'go').resolves({ regionId: 'bd114474-790f-4470-8ba4-7b0cc9c225d7' }) - }) - - afterEach(() => { - Sinon.restore() - }) - - describe('the response for billing periods', () => { - beforeEach(async () => { - Sinon.stub(FetchChargeVersionsService, 'go').resolves([]) - }) - - it('always includes the current billing period', async () => { - const result = await SupplementaryDataService.go(naldRegionId) - - expect(result.billingPeriods).to.have.length(1) - expect(result.billingPeriods[0]).to.equal(currentBillingPeriod) - }) - }) - - describe('the response for charge versions', () => { - describe('when there are charge versions for supplementary billing', () => { - const testRecords = [{ - chargeVersionId: '4b5cbe04-a0e2-468c-909e-1e2d93810ba8', - scheme: 'sroc', - endDate: null, - licenceId: '2627a306-23a3-432f-9c71-a71663888285', - licenceRef: 'AT/SROC/SUPB/01' - }] - - beforeEach(async () => { - Sinon.stub(FetchChargeVersionsService, 'go').resolves(testRecords) - }) - - it('returns the matching charge versions', async () => { - const result = await SupplementaryDataService.go(naldRegionId) - - expect(result.chargeVersions).to.have.length(1) - expect(result.chargeVersions[0].chargeVersionId).to.equal(testRecords[0].chargeVersionId) - }) - }) - - describe('When there are no charge versions for supplementary billing', () => { - beforeEach(async () => { - Sinon.stub(FetchChargeVersionsService, 'go').resolves([]) - }) - - it('returns no results', async () => { - const result = await SupplementaryDataService.go(naldRegionId) - - expect(result.chargeVersions).to.be.empty() - }) - }) - }) -}) diff --git a/test/services/supplementary-billing/process-previous-billing-transactions.service.test.js b/test/services/supplementary-billing/process-previous-billing-transactions.service.test.js deleted file mode 100644 index 02f133a64e..0000000000 --- a/test/services/supplementary-billing/process-previous-billing-transactions.service.test.js +++ /dev/null @@ -1,76 +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 FetchPreviousBillingTransactionsService = require('../../../app/services/supplementary-billing/fetch-previous-billing-transactions.service.js') - -// Thing under test -const ProcessPreviousBillingTransactionsService = require('../../../app/services/supplementary-billing/process-previous-billing-transactions.service.js') - -describe('Process previous billing transactions service', () => { - const billingInvoice = { billingInvoiceId: 'a56ef6d9-370a-4224-b6ec-0fca8bfa4d1f' } - const billingInvoiceLicence = { billingInvoiceLicenceId: '110ab2e2-6076-4d5a-a56f-b17a048eb269' } - - const billingPeriod = { - startDate: new Date('2022-04-01'), - endDate: new Date('2023-03-31') - } - - const previousTransaction = { - billingTransactionId: '63742bee-cb1b-44f1-86f6-c7d546f59c88', - billingInvoiceLicenceId: '110ab2e2-6076-4d5a-a56f-b17a048eb269', - isCredit: false, - status: 'candidate', - chargeType: 'standard', - chargeCategoryCode: '5.11.2', - billableDays: 265, - purposes: [] - } - - afterEach(() => { - Sinon.restore() - }) - - describe('when the billing invoice, licence and period', () => { - describe('match to transactions on a previous billing batch', () => { - beforeEach(() => { - Sinon.stub(FetchPreviousBillingTransactionsService, 'go').resolves([previousTransaction]) - }) - - it('returns the debits reversed as credits with new Ids', async () => { - const result = await ProcessPreviousBillingTransactionsService.go( - billingInvoice, - billingInvoiceLicence, - billingPeriod - ) - - expect(result).to.have.length(1) - expect(result[0].billingTransactionId).not.to.equal(previousTransaction.billingTransactionId) - expect(result[0].isCredit).not.to.equal(previousTransaction.isCredit) - }) - }) - - describe('do not match to transactions on a previous billing batch', () => { - beforeEach(() => { - Sinon.stub(FetchPreviousBillingTransactionsService, 'go').resolves([]) - }) - - it('returns an empty array', async () => { - const result = await ProcessPreviousBillingTransactionsService.go( - billingInvoice, - billingInvoiceLicence, - billingPeriod - ) - - expect(result).to.be.empty() - }) - }) - }) -})