diff --git a/app/controllers/bill-runs/bill-runs.controller.js b/app/controllers/bill-runs/bill-runs.controller.js index ea94cf85ee..536d22e56f 100644 --- a/app/controllers/bill-runs/bill-runs.controller.js +++ b/app/controllers/bill-runs/bill-runs.controller.js @@ -8,7 +8,7 @@ const Boom = require('@hapi/boom') const CreateBillRunValidator = require('../../validators/bill-runs/create-bill-run.validator.js') -const InitiateBillingBatchService = require('../../services/supplementary-billing/initiate-billing-batch.service.js') +const ProcessBillingBatchService = require('../../services/supplementary-billing/process-billing-batch.service.js') async function create (request, h) { const validatedData = CreateBillRunValidator.go(request.payload) @@ -18,7 +18,9 @@ async function create (request, h) { } try { - const result = await InitiateBillingBatchService.go(validatedData.value) + const { region, user } = validatedData.value + const result = await ProcessBillingBatchService.go(region, user) + return h.response(result).code(200) } catch (error) { return _formattedInitiateBillingBatchError(error) 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 592d2a84f8..456865587c 100644 --- a/app/services/supplementary-billing/check-live-bill-run.service.js +++ b/app/services/supplementary-billing/check-live-bill-run.service.js @@ -15,16 +15,16 @@ const LIVE_STATUSES = ['processing', 'ready', 'review', 'queued'] * We define "live" as having the status `processing`, `ready`, `review` or `queued` * * @param {String} regionId The id of the region to be checked - * @param {Number} financialYear The financial year to be checked + * @param {Number} toFinancialYearEnding The financial year to be checked * * @returns {Boolean} Whether a "live" bill run exists */ -async function go (regionId, financialYear) { +async function go (regionId, toFinancialYearEnding) { const numberOfLiveBillRuns = await BillingBatchModel.query() .select(1) .where({ regionId, - toFinancialYearEnding: financialYear, + toFinancialYearEnding, scheme: 'sroc', batchType: 'supplementary' }) diff --git a/app/services/supplementary-billing/create-billing-batch.service.js b/app/services/supplementary-billing/create-billing-batch.service.js index d9f67dbb72..47b6ab1e96 100644 --- a/app/services/supplementary-billing/create-billing-batch.service.js +++ b/app/services/supplementary-billing/create-billing-batch.service.js @@ -11,7 +11,7 @@ const BillingBatchModel = require('../../models/water/billing-batch.model.js') * Create a new billing batch * * @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} financialYearEndings Object that contains the from and to financial year endings * @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' @@ -23,14 +23,15 @@ const BillingBatchModel = require('../../models/water/billing-batch.model.js') * * @returns {module:BillingBatchModel} The newly created billing batch instance with the `.region` property populated */ -async function go (regionId, billingPeriod, options) { - const optionsData = optionsDefaults(options) +async function go (regionId, financialYearEndings, options) { + const { fromFinancialYearEnding, toFinancialYearEnding } = financialYearEndings + const optionsData = _defaultOptions(options) const billingBatch = await BillingBatchModel.query() .insert({ regionId, - fromFinancialYearEnding: billingPeriod.endDate.getFullYear(), - toFinancialYearEnding: billingPeriod.endDate.getFullYear(), + fromFinancialYearEnding, + toFinancialYearEnding, ...optionsData }) .returning('*') @@ -39,7 +40,7 @@ async function go (regionId, billingPeriod, options) { return billingBatch } -function optionsDefaults (data) { +function _defaultOptions (option) { const defaults = { batchType: 'supplementary', scheme: 'sroc', @@ -51,7 +52,7 @@ function optionsDefaults (data) { return { ...defaults, - ...data + ...option } } diff --git a/app/services/supplementary-billing/initiate-billing-batch.service.js b/app/services/supplementary-billing/initiate-billing-batch.service.js index 8be52f5382..4936ff3501 100644 --- a/app/services/supplementary-billing/initiate-billing-batch.service.js +++ b/app/services/supplementary-billing/initiate-billing-batch.service.js @@ -6,13 +6,11 @@ */ const BillingBatchModel = require('../../models/water/billing-batch.model.js') -const BillingPeriodsService = require('./billing-periods.service.js') + const ChargingModuleCreateBillRunService = require('../charging-module/create-bill-run.service.js') const CheckLiveBillRunService = require('./check-live-bill-run.service.js') -const CreateBillingBatchPresenter = require('../../presenters/supplementary-billing/create-billing-batch.presenter.js') const CreateBillingBatchService = require('./create-billing-batch.service.js') const CreateBillingBatchEventService = require('./create-billing-batch-event.service.js') -const ProcessBillingBatchService = require('./process-billing-batch.service.js') /** * Initiate a new billing batch @@ -20,42 +18,32 @@ const ProcessBillingBatchService = require('./process-billing-batch.service.js') * Initiating a new billing batch means creating both the `billing_batch` and `event` record with the appropriate data, * along with a bill run record in the SROC Charging Module API. * - * @param {Object} billRunRequestData Validated version of the data sent in the request to create the new billing batch + * @param {String} regionId Id of the region the bill run is for + * @param {String} user Email address of the user who initiated the bill run * - * @returns {Object} Details of the newly created billing batch record + * @returns {module:BillingBatchModel} The newly created billing batch instance */ -async function go (billRunRequestData) { - // NOTE: It will be required in the future that we cater for a range of billing periods, as changes can be back dated - // up to 5 years. For now though, our delivery scope is only for the 2022-2023 billing period so the final record is - // extracted from the `billingPeriods` array which will currently always be for the 2022-2023 billing period. - const billingPeriods = BillingPeriodsService.go() - const billingPeriod = billingPeriods[billingPeriods.length - 1] - - const { region, scheme, type, user } = billRunRequestData - - const financialYear = billingPeriod.endDate.getFullYear() - const liveBillRunExists = await CheckLiveBillRunService.go(region, financialYear) +async function go (financialYearEndings, regionId, user) { + const liveBillRunExists = await CheckLiveBillRunService.go(regionId, financialYearEndings.toFinancialYearEnding) if (liveBillRunExists) { - throw Error(`Batch already live for region ${region}`) + throw Error(`Batch already live for region ${regionId}`) } - const chargingModuleResult = await ChargingModuleCreateBillRunService.go(region, 'sroc') + const chargingModuleResult = await ChargingModuleCreateBillRunService.go(regionId, 'sroc') - const billingBatchOptions = _billingBatchOptions(type, scheme, chargingModuleResult) - const billingBatch = await CreateBillingBatchService.go(region, billingPeriod, billingBatchOptions) + const billingBatchOptions = _billingBatchOptions(chargingModuleResult) + const billingBatch = await CreateBillingBatchService.go(regionId, financialYearEndings, billingBatchOptions) await CreateBillingBatchEventService.go(billingBatch, user) - ProcessBillingBatchService.go(billingBatch, billingPeriod) - - return _response(billingBatch) + return billingBatch } -function _billingBatchOptions (type, scheme, chargingModuleResult) { +function _billingBatchOptions (chargingModuleResult) { const options = { - scheme, - batchType: type + scheme: 'sroc', + batchType: 'supplementary' } if (chargingModuleResult.succeeded) { @@ -71,10 +59,6 @@ function _billingBatchOptions (type, scheme, chargingModuleResult) { return options } -function _response (billingBatch) { - return CreateBillingBatchPresenter.go(billingBatch) -} - module.exports = { go } diff --git a/app/services/supplementary-billing/process-billing-batch.service.js b/app/services/supplementary-billing/process-billing-batch.service.js index 2319212280..ed340f7762 100644 --- a/app/services/supplementary-billing/process-billing-batch.service.js +++ b/app/services/supplementary-billing/process-billing-batch.service.js @@ -1,476 +1,39 @@ 'use strict' -/** - * Processes a new billing batch - * @module ProcessBillingBatchService - */ +const BillingPeriodsService = require('./billing-periods.service.js') +const CreateBillingBatchPresenter = require('../../presenters/supplementary-billing/create-billing-batch.presenter.js') +const InitiateBillingBatchService = require('./initiate-billing-batch.service.js') +const ProcessBillingPeriodService = require('./process-billing-period.service.js') -const BillingBatchModel = require('../../models/water/billing-batch.model.js') -const BillingInvoiceModel = require('../../models/water/billing-invoice.model.js') -const BillingInvoiceLicenceModel = require('../../models/water/billing-invoice-licence.model.js') -const BillingTransactionModel = require('../../models/water/billing-transaction.model.js') -const ChargingModuleCreateTransactionService = require('../charging-module/create-transaction.service.js') -const ChargingModuleGenerateService = require('..//charging-module/generate-bill-run.service.js') -const ChargingModuleCreateTransactionPresenter = require('../../presenters/charging-module/create-transaction.presenter.js') -const DetermineChargePeriodService = require('./determine-charge-period.service.js') -const DetermineMinimumChargeService = require('./determine-minimum-charge.service.js') -const FetchChargeVersionsService = require('./fetch-charge-versions.service.js') -const FetchInvoiceAccountNumbersService = require('./fetch-invoice-account-numbers.service.js') -const GenerateBillingTransactionsService = require('./generate-billing-transactions.service.js') -const GenerateBillingInvoiceService = require('./generate-billing-invoice.service.js') -const GenerateBillingInvoiceLicenceService = require('./generate-billing-invoice-licence.service.js') -const HandleErroredBillingBatchService = require('./handle-errored-billing-batch.service.js') -const LegacyRequestLib = require('../../lib/legacy-request.lib.js') -const ProcessBillingTransactionsService = require('./process-billing-transactions.service.js') -const UnflagUnbilledLicencesService = require('./unflag-unbilled-licences.service.js') +async function go (region, user) { + // NOTE: It will be required in the future that we cater for a range of billing periods, as changes can be back dated + // up to 5 years. For now though, our delivery scope is only for the 2022-2023 billing period so the final record is + // extracted from the `billingPeriods` array which will currently always be for the 2022-2023 billing period. + const billingPeriods = BillingPeriodsService.go() + const financialYearEndings = _financialYearEndings(billingPeriods) -/** - * Creates the invoices and transactions in both WRLS and the Charging Module API - * - * 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 - */ -async function go (billingBatch, billingPeriod) { - const { billingBatchId } = billingBatch + const billingBatch = await InitiateBillingBatchService.go(financialYearEndings, region, user) - try { - // Mark the start time for later logging - const startTime = process.hrtime.bigint() + _process(billingBatch, billingPeriods) - await _updateStatus(billingBatchId, 'processing') - - const fetchedData = await _fetchData(billingBatch, billingPeriod) - const preGeneratedData = _preGenerateData(fetchedData, billingBatchId, billingPeriod) - - const billingData = _buildBillingDataWithTransactions(fetchedData, preGeneratedData, billingPeriod, billingBatchId) - const dataToPersist = await _buildDataToPersist(billingData, billingPeriod, billingBatch) - - await _persistData(dataToPersist, billingBatch) - - await _finaliseBillingBatch(billingBatch, fetchedData, dataToPersist.billingInvoiceLicences) - - // Log how long the process took - _calculateAndLogTime(billingBatchId, startTime) - } catch (error) { - _logError(billingBatch, error) - } -} - -/** - * Pre-generates the empty billing invoices and billing invoice licences we will be populating - */ -function _preGenerateData (fetchedData, billingBatchId, billingPeriod) { - const { chargeVersions, invoiceAccounts } = fetchedData - - const billingInvoices = _preGenerateBillingInvoices(invoiceAccounts, billingBatchId, billingPeriod) - const billingInvoiceLicences = _preGenerateBillingInvoiceLicences(chargeVersions, billingInvoices, billingBatchId) - - return { billingInvoices, billingInvoiceLicences } -} - -/** - * Fetch the charge versions and invoice accounts data we need from the db - */ -async function _fetchData (billingBatch, billingPeriod) { - const chargeVersions = await _fetchChargeVersions(billingBatch, billingPeriod) - const invoiceAccounts = await _fetchInvoiceAccounts(chargeVersions, billingBatch) - - return { invoiceAccounts, chargeVersions } -} - -/** - * Iterates over the populated billing data and builds an object of data to be persisted. This process includes sending - * "create transaction" requests to the Charging Module as this data is needed to fully create our transaction records - */ -async function _buildDataToPersist (billingData, billingPeriod, billingBatch) { - const dataToPersist = { - transactions: [], - billingInvoices: new Set(), - billingInvoiceLicences: [] - } - - for (const currentBillingData of Object.values(billingData)) { - const cleansedTransactions = await _cleanseTransactions(currentBillingData, billingPeriod, billingBatch) - - if (cleansedTransactions.length !== 0) { - const billingTransactions = await _generateBillingTransactions( - currentBillingData, - billingBatch, - cleansedTransactions, - billingPeriod - ) - - dataToPersist.transactions.push(...billingTransactions) - // Note that Sets use add rather than push - dataToPersist.billingInvoices.add(currentBillingData.billingInvoice) - dataToPersist.billingInvoiceLicences.push(currentBillingData.billingInvoiceLicence) - } - } - - return dataToPersist -} - -/** - * Processes each charge version and and returns an object where each key is a billing invoice id which exists in one or - * more charge versions and the key's value is an object containing the associated licence, billing invoice and billing - * invoice licence, along with any required transactions, eg: - * - * { - * 'billing-invoice-licence-id-1': { - * billingInvoiceLicence: '...', // instance of the billing invoice licence - * licence: '...', // instance of the licence for this billing invoice licence - * billingInvoice: '...', // instance of the billing invoice for this billing invoice licence - * transactions: [] // array of calculated transactions for this billing invoice licence - * }, - * 'billing-invoice-licence-id-2': { - * // Same object structure as above - * } - * } - */ -function _buildBillingDataWithTransactions (fetchedData, preGeneratedData, billingPeriod, billingBatchId) { - const { chargeVersions } = fetchedData - - // We use reduce to build up the object as this allows us to start with an empty object and populate it with each - // charge version. - return chargeVersions.reduce((acc, chargeVersion) => { - // We only need to handle charge versions with a status of `current` - if (chargeVersion.status !== 'current') { - return acc - } - - const { billingInvoiceLicence, billingInvoice } = _retrievePreGeneratedData(preGeneratedData, chargeVersion) - const { billingInvoiceLicenceId } = billingInvoiceLicence - - if (!acc[billingInvoiceLicenceId]) { - acc[billingInvoiceLicenceId] = _initialBillingData(chargeVersion, billingInvoice, billingInvoiceLicence) - } - - const calculatedTransactions = _generateCalculatedTransactions(billingPeriod, chargeVersion, billingBatchId) - acc[billingInvoiceLicenceId].calculatedTransactions.push(...calculatedTransactions) - - return acc - }, {}) -} - -/** - * Persists the transaction, invoice and invoice licence records in the db - */ -async function _persistData (dataToPersist, billingBatch) { - try { - if (dataToPersist.transactions.length !== 0) { - await BillingTransactionModel.query().insert(dataToPersist.transactions) - } - - // Note that Sets have a size property rather than length - if (dataToPersist.billingInvoices.size !== 0) { - // We need to spread the Set into an array for Objection to accept it - await BillingInvoiceModel.query().insert([...dataToPersist.billingInvoices]) - } - - if (dataToPersist.billingInvoiceLicences.length !== 0) { - await BillingInvoiceLicenceModel.query().insert(dataToPersist.billingInvoiceLicences) - } - } catch (error) { - HandleErroredBillingBatchService.go(billingBatch.billingBatchId) - - throw error - } -} - -function _retrievePreGeneratedData (preGeneratedData, chargeVersion) { - const { billingInvoices, billingInvoiceLicences } = preGeneratedData - - const billingInvoice = billingInvoices[chargeVersion.invoiceAccountId] - - const billingInvoiceLicenceKey = _billingInvoiceLicenceKey( - billingInvoice.billingInvoiceId, - chargeVersion.licence.licenceId - ) - const billingInvoiceLicence = billingInvoiceLicences[billingInvoiceLicenceKey] - - return { billingInvoice, billingInvoiceLicence } -} - -async function _fetchInvoiceAccounts (chargeVersions, billingBatch) { - try { - const invoiceAccounts = await FetchInvoiceAccountNumbersService.go(chargeVersions) - - return invoiceAccounts - } catch (error) { - HandleErroredBillingBatchService.go(billingBatch.billingBatchId) - - throw error - } -} - -/** - * We pre-generate billing invoice licences for every combination of billing invoice and licence in the charge versions - * so that we don't need to fetch any data from the db during the main charge version processing loop. This function - * generates the required billing invoice licences and returns an object where each key is a concatenated billing - * invoice id and licence id, and each value is the billing invoice licence for that combination of billing invoice and - * licence, ie: - * - * { - * 'key-1': { billingInvoiceLicenceId: 'billing-invoice-licence-1', ... }, - * 'key-2': { billingInvoiceLicenceId: 'billing-invoice-licence-2', ... } - * } - */ -function _preGenerateBillingInvoiceLicences (chargeVersions, billingInvoices, billingBatchId) { - try { - const keyedBillingInvoiceLicences = chargeVersions.reduce((acc, chargeVersion) => { - const { billingInvoiceId } = billingInvoices[chargeVersion.invoiceAccountId] - const { licence } = chargeVersion - - const key = _billingInvoiceLicenceKey(billingInvoiceId, licence.licenceId) - - // The charge versions may contain a combination of billing invoice and licence multiple times, so we check to see - // if this combination has already had a billing invoice licence generated for it and return early if so - if (acc.key) { - return acc - } - - return { - ...acc, - [key]: GenerateBillingInvoiceLicenceService.go(billingInvoiceId, licence) - } - }, {}) - - return keyedBillingInvoiceLicences - } catch (error) { - HandleErroredBillingBatchService.go(billingBatchId) - - throw error - } + return _response(billingBatch) } -function _billingInvoiceLicenceKey (billingInvoiceId, licenceId) { - return `${billingInvoiceId}-${licenceId}` -} - -/** - * We pre-generate billing invoices for every invoice account so that we don't need to fetch any data from the db - * during the main charge version processing loop. This function generates the required billing invoice licences and - * returns an object where each key is the invoice account id, and each value is the billing invoice, ie: - * - * { - * 'uuid-1': { invoiceAccountId: 'uuid-1', ... }, - * 'uuid-2': { invoiceAccountId: 'uuid-2', ... } - * } - */ -function _preGenerateBillingInvoices (invoiceAccounts, billingBatchId, billingPeriod) { - try { - const keyedBillingInvoices = invoiceAccounts.reduce((acc, invoiceAccount) => { - // Note that the array of invoice accounts will already have been deduped so we don't need to check whether a - // billing invoice licence already exists in the object before generating one - return { - ...acc, - [invoiceAccount.invoiceAccountId]: GenerateBillingInvoiceService.go( - invoiceAccount, - billingBatchId, - billingPeriod.endDate.getFullYear() - ) - } - }, {}) - - return keyedBillingInvoices - } catch (error) { - HandleErroredBillingBatchService.go(billingBatchId) - - throw error - } -} - -function _initialBillingData (chargeVersion, billingInvoice, billingInvoiceLicence) { +function _financialYearEndings (billingPeriods) { return { - licence: chargeVersion.licence, - billingInvoice, - billingInvoiceLicence, - calculatedTransactions: [] + fromFinancialYearEnding: billingPeriods[billingPeriods.length - 1].endDate.getFullYear(), + toFinancialYearEnding: billingPeriods[0].endDate.getFullYear() } } -/** - * 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 - const timeTakenMs = timeTakenNs / 1000000n - - global.GlobalNotifier.omg(`Time taken to process billing batch ${billingBatchId}: ${timeTakenMs}ms`) -} - -async function _generateBillingTransactions (currentBillingData, billingBatch, billingTransactions, billingPeriod) { - const { licence, billingInvoice, billingInvoiceLicence } = currentBillingData +async function _process (billingBatch, billingPeriods) { + const currentBillingPeriod = billingPeriods[billingPeriods.length - 1] - try { - const generatedTransactions = [] - - for (const transaction of billingTransactions) { - const chargingModuleRequest = ChargingModuleCreateTransactionPresenter.go( - transaction, - billingPeriod, - billingInvoice.invoiceAccountNumber, - licence - ) - - const chargingModuleResponse = await ChargingModuleCreateTransactionService.go( - billingBatch.externalId, - chargingModuleRequest - ) - - transaction.status = 'charge_created' - transaction.externalId = chargingModuleResponse.response.body.transaction.id - transaction.billingInvoiceLicenceId = billingInvoiceLicence.billingInvoiceLicenceId - - generatedTransactions.push(transaction) - } - - return generatedTransactions - } catch (error) { - HandleErroredBillingBatchService.go( - billingBatch.billingBatchId, - BillingBatchModel.errorCodes.failedToCreateCharge - ) - - throw error - } -} - -async function _fetchChargeVersions (billingBatch, billingPeriod) { - try { - // 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(billingBatch.regionId, billingPeriod) - - // We don't just `return FetchChargeVersionsService.go()` as we need to call HandleErroredBillingBatchService if it - // fails - return chargeVersions - } catch (error) { - HandleErroredBillingBatchService.go( - billingBatch.billingBatchId, - BillingBatchModel.errorCodes.failedToProcessChargeVersions - ) - - throw error - } + await ProcessBillingPeriodService.go(billingBatch, currentBillingPeriod) } -/** - * Finalises the billing batch by unflagging all unbilled licences, requesting the Charging Module run its generate - * process, and refreshes the billing batch locally. However if there were no resulting invoice licences then we simply - * unflag the unbilled licences and mark the billing batch with `empty` status - */ -async function _finaliseBillingBatch (billingBatch, fetchedData, billingInvoiceLicences) { - try { - await UnflagUnbilledLicencesService.go(billingBatch.billingBatchId, fetchedData.chargeVersions) - - // If there are no billing invoice licences then the bill run is considered empty. We just need to set the status to - // indicate this in the UI - if (billingInvoiceLicences.length === 0) { - await _updateStatus(billingBatch.billingBatchId, 'empty') - - return - } - - // We then need to tell the Charging Module to run its generate process. This is where the Charging module finalises - // the debit and credit amounts, and adds any additional transactions needed, for example, minimum charge - await ChargingModuleGenerateService.go(billingBatch.externalId) - - await LegacyRequestLib.post('water', `billing/batches/${billingBatch.billingBatchId}/refresh`) - } catch (error) { - HandleErroredBillingBatchService.go(billingBatch.billingBatchId) - - throw error - } -} - -async function _cleanseTransactions (currentBillingData, billingPeriod, billingBatch) { - try { - // Guard clause which is most likely to hit in the event that no charge versions were 'fetched' to be billed in the - // first place - if (!currentBillingData.billingInvoice) { - return [] - } - - const cleansedTransactions = await ProcessBillingTransactionsService.go( - currentBillingData.calculatedTransactions, - currentBillingData.billingInvoice, - currentBillingData.billingInvoiceLicence, - billingPeriod - ) - - return cleansedTransactions - } catch (error) { - HandleErroredBillingBatchService.go(billingBatch.billingBatchId) - - throw error - } -} - -function _generateCalculatedTransactions (billingPeriod, chargeVersion, billingBatchId) { - try { - const financialYearEnding = billingPeriod.endDate.getFullYear() - const chargePeriod = DetermineChargePeriodService.go(chargeVersion, financialYearEnding) - const isNewLicence = DetermineMinimumChargeService.go(chargeVersion, financialYearEnding) - const isWaterUndertaker = chargeVersion.licence.isWaterUndertaker - - // We use flatMap as GenerateBillingTransactionsService returns an array of transactions - const transactions = chargeVersion.chargeElements.flatMap((chargeElement) => { - return GenerateBillingTransactionsService.go( - chargeElement, - billingPeriod, - chargePeriod, - isNewLicence, - isWaterUndertaker - ) - }) - - return transactions - } catch (error) { - HandleErroredBillingBatchService.go( - billingBatchId, - BillingBatchModel.errorCodes.failedToPrepareTransactions - ) - - throw error - } -} - -function _logError (billingBatch, error) { - global.GlobalNotifier.omfg( - 'Billing Batch process errored', - { - billingBatch, - error: { - name: error.name, - message: error.message, - stack: error.stack - } - }) -} - -async function _updateStatus (billingBatchId, status) { - try { - await BillingBatchModel.query() - .findById(billingBatchId) - .patch({ status }) - } catch (error) { - HandleErroredBillingBatchService.go(billingBatchId) - - throw error - } +function _response (billingBatch) { + return CreateBillingBatchPresenter.go(billingBatch) } module.exports = { diff --git a/app/services/supplementary-billing/process-billing-period.service.js b/app/services/supplementary-billing/process-billing-period.service.js new file mode 100644 index 0000000000..567701f90d --- /dev/null +++ b/app/services/supplementary-billing/process-billing-period.service.js @@ -0,0 +1,478 @@ +'use strict' + +/** + * Processes the charge versions for a given billing period + * @module ProcessBillingPeriodService + */ + +const BillingBatchModel = require('../../models/water/billing-batch.model.js') +const BillingInvoiceModel = require('../../models/water/billing-invoice.model.js') +const BillingInvoiceLicenceModel = require('../../models/water/billing-invoice-licence.model.js') +const BillingTransactionModel = require('../../models/water/billing-transaction.model.js') +const ChargingModuleCreateTransactionService = require('../charging-module/create-transaction.service.js') +const ChargingModuleGenerateService = require('..//charging-module/generate-bill-run.service.js') +const ChargingModuleCreateTransactionPresenter = require('../../presenters/charging-module/create-transaction.presenter.js') +const DetermineChargePeriodService = require('./determine-charge-period.service.js') +const DetermineMinimumChargeService = require('./determine-minimum-charge.service.js') +const FetchChargeVersionsService = require('./fetch-charge-versions.service.js') +const FetchInvoiceAccountNumbersService = require('./fetch-invoice-account-numbers.service.js') +const GenerateBillingTransactionsService = require('./generate-billing-transactions.service.js') +const GenerateBillingInvoiceService = require('./generate-billing-invoice.service.js') +const GenerateBillingInvoiceLicenceService = require('./generate-billing-invoice-licence.service.js') +const HandleErroredBillingBatchService = require('./handle-errored-billing-batch.service.js') +const LegacyRequestLib = require('../../lib/legacy-request.lib.js') +const ProcessBillingTransactionsService = require('./process-billing-transactions.service.js') +const UnflagUnbilledLicencesService = require('./unflag-unbilled-licences.service.js') + +/** + * Creates the invoices and transactions in both WRLS and the Charging Module API + * + * 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 + */ +async function go (billingBatch, billingPeriod) { + const { billingBatchId } = billingBatch + + try { + // Mark the start time for later logging + const startTime = process.hrtime.bigint() + + await _updateStatus(billingBatchId, 'processing') + + const fetchedData = await _fetchData(billingBatch, billingPeriod) + const preGeneratedData = _preGenerateData(fetchedData, billingBatchId, billingPeriod) + + const billingData = _buildBillingDataWithTransactions(fetchedData, preGeneratedData, billingPeriod, billingBatchId) + const dataToPersist = await _buildDataToPersist(billingData, billingPeriod, billingBatch) + + await _persistData(dataToPersist, billingBatch) + + await _finaliseBillingBatch(billingBatch, fetchedData, dataToPersist.billingInvoiceLicences) + + // Log how long the process took + _calculateAndLogTime(billingBatchId, startTime) + } catch (error) { + _logError(billingBatch, error) + } +} + +/** + * Pre-generates the empty billing invoices and billing invoice licences we will be populating + */ +function _preGenerateData (fetchedData, billingBatchId, billingPeriod) { + const { chargeVersions, invoiceAccounts } = fetchedData + + const billingInvoices = _preGenerateBillingInvoices(invoiceAccounts, billingBatchId, billingPeriod) + const billingInvoiceLicences = _preGenerateBillingInvoiceLicences(chargeVersions, billingInvoices, billingBatchId) + + return { billingInvoices, billingInvoiceLicences } +} + +/** + * Fetch the charge versions and invoice accounts data we need from the db + */ +async function _fetchData (billingBatch, billingPeriod) { + const chargeVersions = await _fetchChargeVersions(billingBatch, billingPeriod) + const invoiceAccounts = await _fetchInvoiceAccounts(chargeVersions, billingBatch) + + return { invoiceAccounts, chargeVersions } +} + +/** + * Iterates over the populated billing data and builds an object of data to be persisted. This process includes sending + * "create transaction" requests to the Charging Module as this data is needed to fully create our transaction records + */ +async function _buildDataToPersist (billingData, billingPeriod, billingBatch) { + const dataToPersist = { + transactions: [], + billingInvoices: new Set(), + billingInvoiceLicences: [] + } + + for (const currentBillingData of Object.values(billingData)) { + const cleansedTransactions = await _cleanseTransactions(currentBillingData, billingPeriod, billingBatch) + + if (cleansedTransactions.length !== 0) { + const billingTransactions = await _generateBillingTransactions( + currentBillingData, + billingBatch, + cleansedTransactions, + billingPeriod + ) + + dataToPersist.transactions.push(...billingTransactions) + // Note that Sets use add rather than push + dataToPersist.billingInvoices.add(currentBillingData.billingInvoice) + dataToPersist.billingInvoiceLicences.push(currentBillingData.billingInvoiceLicence) + } + } + + return dataToPersist +} + +/** + * Processes each charge version and and returns an object where each key is a billing invoice id which exists in one or + * more charge versions and the key's value is an object containing the associated licence, billing invoice and billing + * invoice licence, along with any required transactions, eg: + * + * { + * 'billing-invoice-licence-id-1': { + * billingInvoiceLicence: '...', // instance of the billing invoice licence + * licence: '...', // instance of the licence for this billing invoice licence + * billingInvoice: '...', // instance of the billing invoice for this billing invoice licence + * transactions: [] // array of calculated transactions for this billing invoice licence + * }, + * 'billing-invoice-licence-id-2': { + * // Same object structure as above + * } + * } + */ +function _buildBillingDataWithTransactions (fetchedData, preGeneratedData, billingPeriod, billingBatchId) { + const { chargeVersions } = fetchedData + + // We use reduce to build up the object as this allows us to start with an empty object and populate it with each + // charge version. + return chargeVersions.reduce((acc, chargeVersion) => { + // We only need to handle charge versions with a status of `current` + if (chargeVersion.status !== 'current') { + return acc + } + + const { billingInvoiceLicence, billingInvoice } = _retrievePreGeneratedData(preGeneratedData, chargeVersion) + const { billingInvoiceLicenceId } = billingInvoiceLicence + + if (!acc[billingInvoiceLicenceId]) { + acc[billingInvoiceLicenceId] = _initialBillingData(chargeVersion, billingInvoice, billingInvoiceLicence) + } + + const calculatedTransactions = _generateCalculatedTransactions(billingPeriod, chargeVersion, billingBatchId) + acc[billingInvoiceLicenceId].calculatedTransactions.push(...calculatedTransactions) + + return acc + }, {}) +} + +/** + * Persists the transaction, invoice and invoice licence records in the db + */ +async function _persistData (dataToPersist, billingBatch) { + try { + if (dataToPersist.transactions.length !== 0) { + await BillingTransactionModel.query().insert(dataToPersist.transactions) + } + + // Note that Sets have a size property rather than length + if (dataToPersist.billingInvoices.size !== 0) { + // We need to spread the Set into an array for Objection to accept it + await BillingInvoiceModel.query().insert([...dataToPersist.billingInvoices]) + } + + if (dataToPersist.billingInvoiceLicences.length !== 0) { + await BillingInvoiceLicenceModel.query().insert(dataToPersist.billingInvoiceLicences) + } + } catch (error) { + HandleErroredBillingBatchService.go(billingBatch.billingBatchId) + + throw error + } +} + +function _retrievePreGeneratedData (preGeneratedData, chargeVersion) { + const { billingInvoices, billingInvoiceLicences } = preGeneratedData + + const billingInvoice = billingInvoices[chargeVersion.invoiceAccountId] + + const billingInvoiceLicenceKey = _billingInvoiceLicenceKey( + billingInvoice.billingInvoiceId, + chargeVersion.licence.licenceId + ) + const billingInvoiceLicence = billingInvoiceLicences[billingInvoiceLicenceKey] + + return { billingInvoice, billingInvoiceLicence } +} + +async function _fetchInvoiceAccounts (chargeVersions, billingBatch) { + try { + const invoiceAccounts = await FetchInvoiceAccountNumbersService.go(chargeVersions) + + return invoiceAccounts + } catch (error) { + HandleErroredBillingBatchService.go(billingBatch.billingBatchId) + + throw error + } +} + +/** + * We pre-generate billing invoice licences for every combination of billing invoice and licence in the charge versions + * so that we don't need to fetch any data from the db during the main charge version processing loop. This function + * generates the required billing invoice licences and returns an object where each key is a concatenated billing + * invoice id and licence id, and each value is the billing invoice licence for that combination of billing invoice and + * licence, ie: + * + * { + * 'key-1': { billingInvoiceLicenceId: 'billing-invoice-licence-1', ... }, + * 'key-2': { billingInvoiceLicenceId: 'billing-invoice-licence-2', ... } + * } + */ +function _preGenerateBillingInvoiceLicences (chargeVersions, billingInvoices, billingBatchId) { + try { + const keyedBillingInvoiceLicences = chargeVersions.reduce((acc, chargeVersion) => { + const { billingInvoiceId } = billingInvoices[chargeVersion.invoiceAccountId] + const { licence } = chargeVersion + + const key = _billingInvoiceLicenceKey(billingInvoiceId, licence.licenceId) + + // The charge versions may contain a combination of billing invoice and licence multiple times, so we check to see + // if this combination has already had a billing invoice licence generated for it and return early if so + if (acc.key) { + return acc + } + + return { + ...acc, + [key]: GenerateBillingInvoiceLicenceService.go(billingInvoiceId, licence) + } + }, {}) + + return keyedBillingInvoiceLicences + } catch (error) { + HandleErroredBillingBatchService.go(billingBatchId) + + throw error + } +} + +function _billingInvoiceLicenceKey (billingInvoiceId, licenceId) { + return `${billingInvoiceId}-${licenceId}` +} + +/** + * We pre-generate billing invoices for every invoice account so that we don't need to fetch any data from the db + * during the main charge version processing loop. This function generates the required billing invoice licences and + * returns an object where each key is the invoice account id, and each value is the billing invoice, ie: + * + * { + * 'uuid-1': { invoiceAccountId: 'uuid-1', ... }, + * 'uuid-2': { invoiceAccountId: 'uuid-2', ... } + * } + */ +function _preGenerateBillingInvoices (invoiceAccounts, billingBatchId, billingPeriod) { + try { + const keyedBillingInvoices = invoiceAccounts.reduce((acc, invoiceAccount) => { + // Note that the array of invoice accounts will already have been deduped so we don't need to check whether a + // billing invoice licence already exists in the object before generating one + return { + ...acc, + [invoiceAccount.invoiceAccountId]: GenerateBillingInvoiceService.go( + invoiceAccount, + billingBatchId, + billingPeriod.endDate.getFullYear() + ) + } + }, {}) + + return keyedBillingInvoices + } catch (error) { + HandleErroredBillingBatchService.go(billingBatchId) + + throw error + } +} + +function _initialBillingData (chargeVersion, billingInvoice, billingInvoiceLicence) { + return { + licence: chargeVersion.licence, + billingInvoice, + billingInvoiceLicence, + calculatedTransactions: [] + } +} + +/** + * 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 + const timeTakenMs = timeTakenNs / 1000000n + + global.GlobalNotifier.omg(`Time taken to process billing batch ${billingBatchId}: ${timeTakenMs}ms`) +} + +async function _generateBillingTransactions (currentBillingData, billingBatch, billingTransactions, billingPeriod) { + const { licence, billingInvoice, billingInvoiceLicence } = currentBillingData + + try { + const generatedTransactions = [] + + for (const transaction of billingTransactions) { + const chargingModuleRequest = ChargingModuleCreateTransactionPresenter.go( + transaction, + billingPeriod, + billingInvoice.invoiceAccountNumber, + licence + ) + + const chargingModuleResponse = await ChargingModuleCreateTransactionService.go( + billingBatch.externalId, + chargingModuleRequest + ) + + transaction.status = 'charge_created' + transaction.externalId = chargingModuleResponse.response.body.transaction.id + transaction.billingInvoiceLicenceId = billingInvoiceLicence.billingInvoiceLicenceId + + generatedTransactions.push(transaction) + } + + return generatedTransactions + } catch (error) { + HandleErroredBillingBatchService.go( + billingBatch.billingBatchId, + BillingBatchModel.errorCodes.failedToCreateCharge + ) + + throw error + } +} + +async function _fetchChargeVersions (billingBatch, billingPeriod) { + try { + // 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(billingBatch.regionId, billingPeriod) + + // We don't just `return FetchChargeVersionsService.go()` as we need to call HandleErroredBillingBatchService if it + // fails + return chargeVersions + } catch (error) { + HandleErroredBillingBatchService.go( + billingBatch.billingBatchId, + BillingBatchModel.errorCodes.failedToProcessChargeVersions + ) + + throw error + } +} + +/** + * Finalises the billing batch by unflagging all unbilled licences, requesting the Charging Module run its generate + * process, and refreshes the billing batch locally. However if there were no resulting invoice licences then we simply + * unflag the unbilled licences and mark the billing batch with `empty` status + */ +async function _finaliseBillingBatch (billingBatch, fetchedData, billingInvoiceLicences) { + try { + await UnflagUnbilledLicencesService.go(billingBatch.billingBatchId, fetchedData.chargeVersions) + + // If there are no billing invoice licences then the bill run is considered empty. We just need to set the status to + // indicate this in the UI + if (billingInvoiceLicences.length === 0) { + await _updateStatus(billingBatch.billingBatchId, 'empty') + + return + } + + // We then need to tell the Charging Module to run its generate process. This is where the Charging module finalises + // the debit and credit amounts, and adds any additional transactions needed, for example, minimum charge + await ChargingModuleGenerateService.go(billingBatch.externalId) + + await LegacyRequestLib.post('water', `billing/batches/${billingBatch.billingBatchId}/refresh`) + } catch (error) { + HandleErroredBillingBatchService.go(billingBatch.billingBatchId) + + throw error + } +} + +async function _cleanseTransactions (currentBillingData, billingPeriod, billingBatch) { + try { + // Guard clause which is most likely to hit in the event that no charge versions were 'fetched' to be billed in the + // first place + if (!currentBillingData.billingInvoice) { + return [] + } + + const cleansedTransactions = await ProcessBillingTransactionsService.go( + currentBillingData.calculatedTransactions, + currentBillingData.billingInvoice, + currentBillingData.billingInvoiceLicence, + billingPeriod + ) + + return cleansedTransactions + } catch (error) { + HandleErroredBillingBatchService.go(billingBatch.billingBatchId) + + throw error + } +} + +function _generateCalculatedTransactions (billingPeriod, chargeVersion, billingBatchId) { + try { + const financialYearEnding = billingPeriod.endDate.getFullYear() + const chargePeriod = DetermineChargePeriodService.go(chargeVersion, financialYearEnding) + const isNewLicence = DetermineMinimumChargeService.go(chargeVersion, financialYearEnding) + const isWaterUndertaker = chargeVersion.licence.isWaterUndertaker + + // We use flatMap as GenerateBillingTransactionsService returns an array of transactions + const transactions = chargeVersion.chargeElements.flatMap((chargeElement) => { + return GenerateBillingTransactionsService.go( + chargeElement, + billingPeriod, + chargePeriod, + isNewLicence, + isWaterUndertaker + ) + }) + + return transactions + } catch (error) { + HandleErroredBillingBatchService.go( + billingBatchId, + BillingBatchModel.errorCodes.failedToPrepareTransactions + ) + + throw error + } +} + +function _logError (billingBatch, error) { + global.GlobalNotifier.omfg( + 'Billing Batch process errored', + { + billingBatch, + error: { + name: error.name, + message: error.message, + stack: error.stack + } + }) +} + +async function _updateStatus (billingBatchId, status) { + try { + await BillingBatchModel.query() + .findById(billingBatchId) + .patch({ status }) + } catch (error) { + HandleErroredBillingBatchService.go(billingBatchId) + + throw error + } +} + +module.exports = { + go +} diff --git a/test/controllers/bill-runs/bill-runs.controller.test.js b/test/controllers/bill-runs/bill-runs.controller.test.js index 7bfbaed8ec..e2c55ba17a 100644 --- a/test/controllers/bill-runs/bill-runs.controller.test.js +++ b/test/controllers/bill-runs/bill-runs.controller.test.js @@ -9,7 +9,7 @@ const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() const { expect } = Code // Things we need to stub -const InitiateBillingBatchService = require('../../../app/services/supplementary-billing/initiate-billing-batch.service.js') +const ProcessBillingBatchService = require('../../../app/services/supplementary-billing/process-billing-batch.service.js') const Boom = require('@hapi/boom') // For running our service @@ -58,7 +58,7 @@ describe('Bill Runs controller', () => { } beforeEach(async () => { - Sinon.stub(InitiateBillingBatchService, 'go').resolves(validResponse) + Sinon.stub(ProcessBillingBatchService, 'go').resolves(validResponse) }) it('returns a 200 response including details of the new billing batch', async () => { @@ -84,7 +84,7 @@ describe('Bill Runs controller', () => { describe('because the billing batch could not be initiated', () => { beforeEach(async () => { Sinon.stub(Boom, 'badImplementation').returns(new Boom.Boom('Bang', { statusCode: 500 })) - Sinon.stub(InitiateBillingBatchService, 'go').rejects() + Sinon.stub(ProcessBillingBatchService, 'go').rejects() }) it('returns an error response', async () => { diff --git a/test/services/supplementary-billing/create-billing-batch.service.test.js b/test/services/supplementary-billing/create-billing-batch.service.test.js index 22de668a3b..68eb89d3bf 100644 --- a/test/services/supplementary-billing/create-billing-batch.service.test.js +++ b/test/services/supplementary-billing/create-billing-batch.service.test.js @@ -17,7 +17,7 @@ const RegionModel = require('../../../app/models/water/region.model.js') const CreateBillingBatchService = require('../../../app/services/supplementary-billing/create-billing-batch.service.js') describe('Create Billing Batch service', () => { - const billingPeriod = { startDate: new Date('2022-04-01'), endDate: new Date('2023-03-31') } + const financialYearEndings = { fromFinancialYearEnding: 2023, toFinancialYearEnding: 2024 } let region beforeEach(async () => { @@ -28,12 +28,12 @@ describe('Create Billing Batch service', () => { describe('when the defaults are not overridden', () => { it('returns the new billing batch instance containing the defaults', async () => { - const result = await CreateBillingBatchService.go(region.regionId, billingPeriod) + const result = await CreateBillingBatchService.go(region.regionId, financialYearEndings) expect(result).to.be.an.instanceOf(BillingBatchModel) expect(result.fromFinancialYearEnding).to.equal(2023) - expect(result.toFinancialYearEnding).to.equal(2023) + expect(result.toFinancialYearEnding).to.equal(2024) expect(result.batchType).to.equal('supplementary') expect(result.scheme).to.equal('sroc') expect(result.source).to.equal('wrls') @@ -55,12 +55,12 @@ describe('Create Billing Batch service', () => { const errorCode = 50 it('returns the new billing batch instance containing the provided values', async () => { - const result = await CreateBillingBatchService.go(region.regionId, billingPeriod, { batchType, scheme, source, externalId, status, errorCode }) + const result = await CreateBillingBatchService.go(region.regionId, financialYearEndings, { batchType, scheme, source, externalId, status, errorCode }) expect(result).to.be.an.instanceOf(BillingBatchModel) expect(result.fromFinancialYearEnding).to.equal(2023) - expect(result.toFinancialYearEnding).to.equal(2023) + expect(result.toFinancialYearEnding).to.equal(2024) expect(result.batchType).to.equal(batchType) expect(result.scheme).to.equal(scheme) expect(result.source).to.equal(source) @@ -78,12 +78,12 @@ describe('Create Billing Batch service', () => { const status = 'error' it('returns the new billing batch instance containing the provided values', async () => { - const result = await CreateBillingBatchService.go(region.regionId, billingPeriod, { externalId, status }) + const result = await CreateBillingBatchService.go(region.regionId, financialYearEndings, { externalId, status }) expect(result).to.be.an.instanceOf(BillingBatchModel) expect(result.fromFinancialYearEnding).to.equal(2023) - expect(result.toFinancialYearEnding).to.equal(2023) + expect(result.toFinancialYearEnding).to.equal(2024) expect(result.batchType).to.equal('supplementary') expect(result.scheme).to.equal('sroc') expect(result.source).to.equal('wrls') diff --git a/test/services/supplementary-billing/initiate-billing-batch.service.test.js b/test/services/supplementary-billing/initiate-billing-batch.service.test.js index 76a6ef202e..a22e620b0f 100644 --- a/test/services/supplementary-billing/initiate-billing-batch.service.test.js +++ b/test/services/supplementary-billing/initiate-billing-batch.service.test.js @@ -15,7 +15,6 @@ const EventModel = require('../../../app/models/water/event.model.js') const RegionHelper = require('../../support/helpers/water/region.helper.js') // Things we need to stub -const BillingPeriodsService = require('../../../app/services/supplementary-billing/billing-periods.service.js') const ChargingModuleCreateBillRunService = require('../../../app/services/charging-module/create-bill-run.service.js') const CheckLiveBillRunService = require('../../../app/services/supplementary-billing/check-live-bill-run.service.js') const ProcessBillingBatchService = require('../../../app/services/supplementary-billing/process-billing-batch.service.js') @@ -24,24 +23,16 @@ const ProcessBillingBatchService = require('../../../app/services/supplementary- const InitiateBillingBatchService = require('../../../app//services/supplementary-billing/initiate-billing-batch.service.js') describe('Initiate Billing Batch service', () => { - const currentBillingPeriod = { - startDate: new Date('2022-04-01'), - endDate: new Date('2023-03-31') - } - let validatedRequestData + const financialYearEndings = { fromFinancialYearEnding: 2023, toFinancialYearEnding: 2024 } + const user = 'test.user@defra.gov.uk' + let regionId beforeEach(async () => { await DatabaseHelper.clean() const region = await RegionHelper.add() - validatedRequestData = { - type: 'supplementary', - scheme: 'sroc', - region: region.regionId, - user: 'test.user@defra.gov.uk' - } + regionId = region.regionId - Sinon.stub(BillingPeriodsService, 'go').returns([currentBillingPeriod]) Sinon.stub(CheckLiveBillRunService, 'go').resolves(false) // The InitiateBillingBatch service does not await the call to the ProcessBillingBatchService. It is intended to @@ -76,7 +67,7 @@ describe('Initiate Billing Batch service', () => { }) it('creates a new billing batch record', async () => { - await InitiateBillingBatchService.go(validatedRequestData) + await InitiateBillingBatchService.go(financialYearEndings, regionId, user) const result = await BillingBatchModel.query().limit(1).first() @@ -85,24 +76,24 @@ describe('Initiate Billing Batch service', () => { }) it('creates a new event record', async () => { - await InitiateBillingBatchService.go(validatedRequestData) + await InitiateBillingBatchService.go(financialYearEndings, regionId, user) const count = await EventModel.query().resultSize() expect(count).to.equal(1) }) - it('returns a response', async () => { - const result = await InitiateBillingBatchService.go(validatedRequestData) + it('returns the new billing batch', async () => { + const result = await InitiateBillingBatchService.go(financialYearEndings, regionId, user) const billingBatch = await BillingBatchModel.query().first() - expect(result.id).to.equal(billingBatch.billingBatchId) - expect(result.region).to.equal(billingBatch.regionId) + expect(result.billingBatchId).to.equal(billingBatch.billingBatchId) + expect(result.regionId).to.equal(billingBatch.regionId) expect(result.scheme).to.equal('sroc') expect(result.batchType).to.equal('supplementary') expect(result.status).to.equal('queued') - expect(result.errorCode).to.equal(null) + expect(result.errorCode).to.be.null() }) }) @@ -127,12 +118,12 @@ describe('Initiate Billing Batch service', () => { }) it('creates a bill run with `error` status and error code 50', async () => { - const result = await InitiateBillingBatchService.go(validatedRequestData) + const result = await InitiateBillingBatchService.go(financialYearEndings, regionId, user) const billingBatch = await BillingBatchModel.query().limit(1).first() - expect(result.id).to.equal(billingBatch.billingBatchId) - expect(result.region).to.equal(billingBatch.regionId) + expect(result.billingBatchId).to.equal(billingBatch.billingBatchId) + expect(result.regionId).to.equal(billingBatch.regionId) expect(result.scheme).to.equal('sroc') expect(result.batchType).to.equal('supplementary') expect(result.status).to.equal('error') @@ -146,10 +137,10 @@ describe('Initiate Billing Batch service', () => { }) it('rejects with an appropriate error', async () => { - const err = await expect(InitiateBillingBatchService.go(validatedRequestData)).to.reject() + const err = await expect(InitiateBillingBatchService.go(financialYearEndings, regionId, user)).to.reject() expect(err).to.be.an.error() - expect(err.message).to.equal(`Batch already live for region ${validatedRequestData.region}`) + expect(err.message).to.equal(`Batch already live for region ${regionId}`) }) }) }) diff --git a/test/services/supplementary-billing/process-billing-batch.service.test.js b/test/services/supplementary-billing/process-billing-period.service.test.js similarity index 91% rename from test/services/supplementary-billing/process-billing-batch.service.test.js rename to test/services/supplementary-billing/process-billing-period.service.test.js index 1781c2d4ee..8e8f549263 100644 --- a/test/services/supplementary-billing/process-billing-batch.service.test.js +++ b/test/services/supplementary-billing/process-billing-period.service.test.js @@ -32,9 +32,9 @@ const GenerateBillingTransactionsService = require('../../../app/services/supple const HandleErroredBillingBatchService = require('../../../app/services/supplementary-billing/handle-errored-billing-batch.service.js') // Thing under test -const ProcessBillingBatchService = require('../../../app/services/supplementary-billing/process-billing-batch.service.js') +const ProcessBillingPeriodService = require('../../../app/services/supplementary-billing/process-billing-period.service.js') -describe('Process billing batch service', () => { +describe('Process billing period service', () => { const billingPeriod = { startDate: new Date('2022-04-01'), endDate: new Date('2023-03-31') @@ -79,7 +79,7 @@ describe('Process billing batch service', () => { }) it('sets the Billing Batch status to empty', async () => { - await ProcessBillingBatchService.go(billingBatch, billingPeriod) + await ProcessBillingPeriodService.go(billingBatch, billingPeriod) const result = await BillingBatchModel.query().findById(billingBatch.billingBatchId) @@ -124,7 +124,7 @@ describe('Process billing batch service', () => { }) it('sets the Billing Batch status to processing', async () => { - await ProcessBillingBatchService.go(billingBatch, billingPeriod) + await ProcessBillingPeriodService.go(billingBatch, billingPeriod) const result = await BillingBatchModel.query().findById(billingBatch.billingBatchId) @@ -157,7 +157,7 @@ describe('Process billing batch service', () => { describe('and there are no previous billed transactions', () => { it('sets the Billing Batch status to empty', async () => { - await ProcessBillingBatchService.go(billingBatch, billingPeriod) + await ProcessBillingPeriodService.go(billingBatch, billingPeriod) const result = await BillingBatchModel.query().findById(billingBatch.billingBatchId) @@ -165,7 +165,7 @@ describe('Process billing batch service', () => { }) it('sets the includeInSrocSupplementaryBilling flag to false', async () => { - await ProcessBillingBatchService.go(billingBatch, billingPeriod) + await ProcessBillingPeriodService.go(billingBatch, billingPeriod) const result = await LicenceModel.query().findById(licence.licenceId) @@ -199,7 +199,7 @@ describe('Process billing batch service', () => { }) it('sets the Billing Batch status to empty', async () => { - await ProcessBillingBatchService.go(billingBatch, billingPeriod) + await ProcessBillingPeriodService.go(billingBatch, billingPeriod) const result = await BillingBatchModel.query().findById(billingBatch.billingBatchId) @@ -207,7 +207,7 @@ describe('Process billing batch service', () => { }) it('sets the includeInSrocSupplementaryBilling flag to false', async () => { - await ProcessBillingBatchService.go(billingBatch, billingPeriod) + await ProcessBillingPeriodService.go(billingBatch, billingPeriod) const result = await LicenceModel.query().findById(licence.licenceId) @@ -219,7 +219,7 @@ describe('Process billing batch service', () => { }) it('logs the time taken to process the billing batch', async () => { - await ProcessBillingBatchService.go(billingBatch, billingPeriod) + await ProcessBillingPeriodService.go(billingBatch, billingPeriod) const logMessage = notifierStub.omg.firstCall.args[0] @@ -236,7 +236,7 @@ describe('Process billing batch service', () => { }) it('sets the appropriate error code', async () => { - await ProcessBillingBatchService.go(billingBatch, billingPeriod) + await ProcessBillingPeriodService.go(billingBatch, billingPeriod) const handlerArgs = handleErroredBillingBatchStub.firstCall.args @@ -250,7 +250,7 @@ describe('Process billing batch service', () => { }) it('sets no error code', async () => { - await ProcessBillingBatchService.go(billingBatch, billingPeriod) + await ProcessBillingPeriodService.go(billingBatch, billingPeriod) const handlerArgs = handleErroredBillingBatchStub.firstCall.args @@ -275,7 +275,7 @@ describe('Process billing batch service', () => { }) it('sets the appropriate error code', async () => { - await ProcessBillingBatchService.go(billingBatch, billingPeriod) + await ProcessBillingPeriodService.go(billingBatch, billingPeriod) const handlerArgs = handleErroredBillingBatchStub.firstCall.args @@ -299,7 +299,7 @@ describe('Process billing batch service', () => { }) it('sets the appropriate error code', async () => { - await ProcessBillingBatchService.go(billingBatch, billingPeriod) + await ProcessBillingPeriodService.go(billingBatch, billingPeriod) const handlerArgs = handleErroredBillingBatchStub.firstCall.args @@ -315,7 +315,7 @@ describe('Process billing batch service', () => { }) it('sets no error code', async () => { - await ProcessBillingBatchService.go(billingBatch, billingPeriod) + await ProcessBillingPeriodService.go(billingBatch, billingPeriod) const handlerArgs = handleErroredBillingBatchStub.firstCall.args @@ -347,7 +347,7 @@ describe('Process billing batch service', () => { }) it('sets no error code', async () => { - await ProcessBillingBatchService.go(billingBatch, billingPeriod) + await ProcessBillingPeriodService.go(billingBatch, billingPeriod) const handlerArgs = handleErroredBillingBatchStub.firstCall.args @@ -381,7 +381,7 @@ describe('Process billing batch service', () => { }) it('sets no error code', async () => { - await ProcessBillingBatchService.go(billingBatch, billingPeriod) + await ProcessBillingPeriodService.go(billingBatch, billingPeriod) const handlerArgs = handleErroredBillingBatchStub.firstCall.args @@ -396,11 +396,11 @@ describe('Process billing batch service', () => { }) it('handles the error', async () => { - await expect(ProcessBillingBatchService.go(billingBatch, billingPeriod)).not.to.reject() + await expect(ProcessBillingPeriodService.go(billingBatch, billingPeriod)).not.to.reject() }) it('sets the Billing Batch status to errored', async () => { - await ProcessBillingBatchService.go(billingBatch, billingPeriod) + await ProcessBillingPeriodService.go(billingBatch, billingPeriod) const handlerArgs = handleErroredBillingBatchStub.firstCall.args @@ -408,7 +408,7 @@ describe('Process billing batch service', () => { }) it('logs the error', async () => { - await ProcessBillingBatchService.go(billingBatch, billingPeriod) + await ProcessBillingPeriodService.go(billingBatch, billingPeriod) const logDataArg = notifierStub.omfg.firstCall.args[1]