diff --git a/app/presenters/bill-runs/two-part-tariff/review-bill-run.presenter.js b/app/presenters/bill-runs/two-part-tariff/review-bill-run.presenter.js index b4e17811bc..8a4768f6fc 100644 --- a/app/presenters/bill-runs/two-part-tariff/review-bill-run.presenter.js +++ b/app/presenters/bill-runs/two-part-tariff/review-bill-run.presenter.js @@ -92,6 +92,7 @@ function _prepareBillRun (billRun, preparedLicences) { financialYear: _financialYear(billRun.toFinancialYearEnding), billRunType: 'two-part tariff', numberOfLicencesDisplayed: preparedLicences.length, + numberOfLicencesToReview: billRun.reviewLicences[0].numberOfLicencesToReview, reviewMessage: _prepareReviewMessage(billRun.reviewLicences[0].numberOfLicencesToReview), totalNumberOfLicences: billRun.reviewLicences[0].totalNumberOfLicences } diff --git a/app/services/bill-runs/two-part-tariff/fetch-billing-accounts.service.js b/app/services/bill-runs/two-part-tariff/fetch-billing-accounts.service.js index eff893b5b2..11ca0ed0ef 100644 --- a/app/services/bill-runs/two-part-tariff/fetch-billing-accounts.service.js +++ b/app/services/bill-runs/two-part-tariff/fetch-billing-accounts.service.js @@ -74,6 +74,13 @@ async function go (billRunId) { 'revokedDate' ]) }) + .withGraphFetched('chargeVersions.licence.region') + .modifyGraph('chargeVersions.licence.region', (builder) => { + builder.select([ + 'id', + 'chargeRegionId' + ]) + }) .withGraphFetched('chargeVersions.chargeReferences') .modifyGraph('chargeVersions.chargeReferences', (builder) => { builder.select([ diff --git a/app/services/bill-runs/two-part-tariff/generate-bill-run.service.js b/app/services/bill-runs/two-part-tariff/generate-bill-run.service.js index 1bbd2aac04..de4700a930 100644 --- a/app/services/bill-runs/two-part-tariff/generate-bill-run.service.js +++ b/app/services/bill-runs/two-part-tariff/generate-bill-run.service.js @@ -5,17 +5,156 @@ * @module GenerateBillRunService */ +const BillRunError = require('../../../errors/bill-run.error.js') +const BillRunModel = require('../../../models/bill-run.model.js') +const ChargingModuleGenerateBillRunRequest = require('../../../requests/charging-module/generate-bill-run.request.js') +const ExpandedError = require('../../../errors/expanded.error.js') +const { + calculateAndLogTimeTaken, + currentTimeInNanoseconds, + timestampForPostgres +} = require('../../../lib/general.lib.js') +const FetchBillingAccountsService = require('./fetch-billing-accounts.service.js') +const HandleErroredBillRunService = require('../handle-errored-bill-run.service.js') +const LegacyRefreshBillRunRequest = require('../../../requests/legacy/refresh-bill-run.request.js') +const ProcessBillingPeriodService = require('./process-billing-period.service.js') + /** * Generates a two-part tariff bill run after the users have completed reviewing its match & allocate results * - * > This is currently a shell that that we intend to expand in subsequent commits + * In this case, "generate" means that we create the required bills and transactions for them in both this service and + * the Charging Module. + * + * > In the other bill run types this would be the `ProcessBillRunService` but that has already been used to handle + * > the match and allocate process in two-part tariff + * + * We first fetch all the billing accounts applicable to this bill run and their charge information. We pass these to + * `ProcessBillingPeriodService` which will generate the bill for each billing account both in WRLS and the + * {@link https://github.com/DEFRA/sroc-charging-module-api | Charging Module API (CHA)}. + * + * Once `ProcessBillingPeriodService` is complete we tell the CHA to generate the bill run (this calculates final + * values for each bill and the bill run overall). + * + * The final step is to ping the legacy + * {@link https://github.com/DEFRA/water-abstraction-service | water-abstraction-service} 'refresh' endpoint. That + * service will extract the final values from the CHA and update the records on our side, finally marking the bill run + * as **Ready**. * - * @param {string} billRunId - The UUID of the two-part tariff bill run in review + * @param {module:BillRunModel} billRunId - The UUID of the two-part tariff bill run that has been reviewed and is ready + * for generating * * @returns {Promise} the promise returned is not intended to resolve to any particular value */ async function go (billRunId) { - return billRunId + const billRun = await _fetchBillRun(billRunId) + + if (billRun.status !== 'review') { + throw new ExpandedError('Cannot process a two-part tariff bill run that is not in review', { billRunId }) + } + + await _updateStatus(billRunId, 'processing') + + _generateBillRun(billRun) +} + +/** + * Unlike other bill runs where the bill run itself and the bills are generated in one process, two-part tariff is + * split. The first part which matches and allocates charge information to returns create's the bill run itself. This + * service handles the second part where we create the bills using the match and allocate data. + * + * This means we've already determined the billing period and recorded it against the bill run. So, we retrieve it from + * the bill run rather than pass it into the service. + */ +function _billingPeriod (billRun) { + const { toFinancialYearEnding } = billRun + + return { + startDate: new Date(`${toFinancialYearEnding - 1}-04-01`), + endDate: new Date(`${toFinancialYearEnding}-03-31`) + } +} + +async function _fetchBillingAccounts (billRunId) { + try { + // We don't just `return FetchBillingDataService.go()` as we need to call HandleErroredBillRunService if it + // fails + const billingAccounts = await FetchBillingAccountsService.go(billRunId) + + return billingAccounts + } catch (error) { + // We know we're saying we failed to process charge versions. But we're stuck with the legacy error codes and this + // is the closest one related to what stage we're at in the process + throw new BillRunError(error, BillRunModel.errorCodes.failedToProcessChargeVersions) + } +} + +async function _fetchBillRun (billRunId) { + return BillRunModel.query() + .findById(billRunId) + .select([ + 'id', + 'batchType', + 'createdAt', + 'externalId', + 'regionId', + 'scheme', + 'status', + 'toFinancialYearEnding' + ]) +} + +async function _finaliseBillRun (billRun, billRunPopulated) { + // If there are no bill licences then the bill run is considered empty. We just need to set the status to indicate + // this in the UI + if (!billRunPopulated) { + await _updateStatus(billRun.id, 'empty') + + return + } + + // We now 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 ChargingModuleGenerateBillRunRequest.send(billRun.externalId) + + await LegacyRefreshBillRunRequest.send(billRun.id) +} + +/** + * The go() has to deal with updating the status of the bill run and then passing a response back to the request to + * avoid the user seeing a timeout in their browser. So, this is where we actually generate the bills and record the + * time taken. + */ +async function _generateBillRun (billRun) { + const { id: billRunId } = billRun + + try { + const startTime = currentTimeInNanoseconds() + + const billingPeriod = _billingPeriod(billRun) + + await _processBillingPeriod(billingPeriod, billRun) + + calculateAndLogTimeTaken(startTime, 'Process bill run complete', { billRunId }) + } catch (error) { + await HandleErroredBillRunService.go(billRunId, error.code) + global.GlobalNotifier.omfg('Bill run process errored', { billRun }, error) + } +} + +async function _processBillingPeriod (billingPeriod, billRun) { + const { id: billRunId } = billRun + + const billingAccounts = await _fetchBillingAccounts(billRunId) + + const billRunPopulated = await ProcessBillingPeriodService.go(billRun, billingPeriod, billingAccounts) + + await _finaliseBillRun(billRun, billRunPopulated) +} + +async function _updateStatus (billRunId, status) { + return BillRunModel.query() + .findById(billRunId) + .patch({ status, updatedAt: timestampForPostgres() }) } module.exports = { diff --git a/app/services/bill-runs/two-part-tariff/generate-transaction.service.js b/app/services/bill-runs/two-part-tariff/generate-transaction.service.js new file mode 100644 index 0000000000..05a8002583 --- /dev/null +++ b/app/services/bill-runs/two-part-tariff/generate-transaction.service.js @@ -0,0 +1,128 @@ +'use strict' + +/** + * Generate a two-part tariff transaction data from the the charge reference and other information passed in + * @module GenerateTransactionService + */ + +const { generateUUID } = require('../../../lib/general.lib.js') + +/** + * Generate a two-part tariff transaction data from the the charge reference and other information passed in + * + * Unlike a standard transaction, we don't have to calculate the billing days for the transaction. Instead, two-part + * tariff transactions focus on volume. This information comes from the review data that results after the match & + * allocate results have been checked and amended by users. + * + * As well as allocated volume, users can override in the review + * + * - the authorised volume + * - the aggregate + * - the charge adjustment + * + * So, we have to grab those values as well. Finally, because the standard annual bill run will have handled the + * compensation charge we don't have to generate an additional transaction alongside our two-part tariff one. + * + * @param {String} billLicenceId - The UUID of the bill licence the transaction will be linked to + * @param {module:ChargeReferenceModel} chargeReference - The charge reference the transaction generated will be + * generated from + * @param {Object} chargePeriod - A start and end date representing the charge period for the transaction + * @param {Boolean} newLicence - Whether the charge reference is linked to a new licence + * @param {Boolean} waterUndertaker - Whether the charge reference is linked to a water undertaker licence + * + * @returns {Object} the two-part tariff transaction + */ +function go (billLicenceId, chargeReference, chargePeriod, newLicence, waterUndertaker) { + const billableQuantity = _billableQuantity(chargeReference.chargeElements) + + return _standardTransaction( + billLicenceId, + billableQuantity, + chargeReference, + chargePeriod, + newLicence, + waterUndertaker + ) +} + +function _billableQuantity (chargeElements) { + return chargeElements.reduce((total, chargeElement) => { + total += chargeElement.reviewChargeElements[0].amendedAllocated + + return total + }, 0) +} + +function _description (chargeReference) { + // If the value is false, undefined, null or simply doesn't exist we return the standard description + if (!chargeReference.adjustments.s127) { + return `Water abstraction charge: ${chargeReference.description}` + } + + return `Two-part tariff basic water abstraction charge: ${chargeReference.description}` +} + +/** + * Returns a json representation of all charge elements in a charge reference + */ +function _generateElements (chargeReference) { + const jsonChargeElements = chargeReference.chargeElements.map((chargeElement) => { + delete chargeElement.reviewChargeElements + + return chargeElement.toJSON() + }) + + return JSON.stringify(jsonChargeElements) +} + +/** + * Generates a standard transaction based on the supplied data, along with some default fields (eg. status) + */ +function _standardTransaction ( + billLicenceId, + billableQuantity, + chargeReference, + chargePeriod, + newLicence, + waterUndertaker +) { + return { + id: generateUUID(), + billLicenceId, + authorisedDays: 0, + billableDays: 0, + newLicence, + waterUndertaker, + chargeReferenceId: chargeReference.id, + startDate: chargePeriod.startDate, + endDate: chargePeriod.endDate, + source: chargeReference.source, + season: 'all year', + loss: chargeReference.loss, + credit: false, + chargeType: 'standard', + authorisedQuantity: chargeReference.reviewChargeReferences[0].amendedAuthorisedVolume, + billableQuantity, + status: 'candidate', + description: _description(chargeReference), + volume: chargeReference.volume, + section126Factor: Number(chargeReference.adjustments.s126) || 1, + section127Agreement: !!chargeReference.adjustments.s127, + section130Agreement: !!chargeReference.adjustments.s130, + secondPartCharge: true, + scheme: 'sroc', + aggregateFactor: chargeReference.reviewChargeReferences[0].amendedAggregate, + adjustmentFactor: chargeReference.reviewChargeReferences[0].amendedChargeAdjustment, + chargeCategoryCode: chargeReference.chargeCategory.reference, + chargeCategoryDescription: chargeReference.chargeCategory.shortDescription, + supportedSource: !!chargeReference.additionalCharges?.supportedSource?.name, + supportedSourceName: chargeReference.additionalCharges?.supportedSource?.name || null, + waterCompanyCharge: !!chargeReference.additionalCharges?.isSupplyPublicWater, + winterOnly: !!chargeReference.adjustments.winter, + purposes: _generateElements(chargeReference) + } +} + +module.exports = { + go +} diff --git a/app/services/bill-runs/two-part-tariff/process-billing-period.service.js b/app/services/bill-runs/two-part-tariff/process-billing-period.service.js new file mode 100644 index 0000000000..45b5d272e9 --- /dev/null +++ b/app/services/bill-runs/two-part-tariff/process-billing-period.service.js @@ -0,0 +1,278 @@ +'use strict' + +/** + * Process the billing accounts for a given billing period and creates their annual two-part tariff bills + * @module ProcessBillingPeriodService + */ + +const BillRunError = require('../../../errors/bill-run.error.js') +const BillRunModel = require('../../../models/bill-run.model.js') +const BillModel = require('../../../models/bill.model.js') +const BillLicenceModel = require('../../../models/bill-licence.model.js') +const DetermineChargePeriodService = require('../determine-charge-period.service.js') +const DetermineMinimumChargeService = require('../determine-minimum-charge.service.js') +const { generateUUID } = require('../../../lib/general.lib.js') +const GenerateTransactionService = require('./generate-transaction.service.js') +const SendTransactionsService = require('../send-transactions.service.js') +const TransactionModel = require('../../../models/transaction.model.js') + +const BillingConfig = require('../../../../config/billing.config.js') + +/** + * Process the billing accounts for a given billing period and creates their annual two-part tariff bills + * + * @param {module:BillRunModel} billRun - The two-part tariff bill run we need to process + * @param {Object} billingPeriod - An object representing the financial year the bills will be for + * @param {module:BillingAccountModel[]} billingAccounts - The billing accounts to create bills for + * + * @returns {Promise} true if the bill run is not empty (there are transactions to bill) else false + */ +async function go (billRun, billingPeriod, billingAccounts) { + let billRunIsPopulated = false + + if (billingAccounts.length === 0) { + return billRunIsPopulated + } + + // We set the batch size and number of billing accounts here rather than determine them for every iteration of the + // loop. It's a very minor node towards performance. + const batchSize = BillingConfig.annual.batchSize + const billingAccountsCount = billingAccounts.length + + // Loop through the billing accounts to be processed by the size of the batch. For example, if we have 100 billing + // accounts to process and the batch size is 10, we'll make 10 iterations of the loop + for (let i = 0; i < billingAccountsCount; i += batchSize) { + // Use slice(start, end) to extract the next batch of billing accounts to process. For example, if we have 100 + // billing accounts, a batch size of 10 then + // + // - 1st pass: slice(0, 10) will return billingAccounts[0] to billingAccounts[9] + // - 2nd pass: slice(10, 20) will return billingAccounts[10] to billingAccounts[19] + // + // Both the start and end params are zero-based indexes for the array being sliced. The bit that might confuse is + // end is not inclusive! + const accountsToProcess = billingAccounts.slice(i, i + batchSize) + + // NOTE: we purposefully loop through each billing account in the batch without awaiting them to be processed. This + // is for performance purposes. If our batch size is 10 we'll start processing one after the other. We then wait for + // all 10 to complete. The overall process time will only be that of the one that takes the longest. If we await + // instead the overall time will be the sum of the time to process each one. + const processes = accountsToProcess.map((accountToProcess) => { + return _processBillingAccount(accountToProcess, billRun, billingPeriod) + }) + + // _processBillingAccount() will return true (bill was created) else false (no bill created) for each billing + // account processed. Promise.all() will return these results as an array. Only one of them has to be true for us to + // mark the bill run as populated. The complication is we're not doing this once but multiple times as we iterate + // the billing accounts. As soon as we have flagged the bill run as populated we can stop checking. And TBH, unlike + // supplementary it is extremely unlikely an annual bill run would generate no bills. But we play it safe and don't + // make that assumption. + const results = await Promise.all(processes) + + if (!billRunIsPopulated) { + billRunIsPopulated = results.some((result) => { + return result + }) + } + } + + return billRunIsPopulated +} + +/** + * Create the bill licence and transaction records for a bill + * + * For each billing account we need to create a bill (1-to-1). Linked to the bill will be multiple charge versions + * which is what we actually use to calculate a bill. A charge version can have multiple charge references and for each + * one we need to generate a transaction line. + * + * The complication is we group transactions by licence (via the bill licence) not charge version. So, as we iterate + * the charge versions we have to determine if its for a licence that we have already generated a bill licence for, or + * we have to create a new one. + */ +async function _createBillLicencesAndTransactions (billId, billingAccount, billRunExternalId, billingPeriod) { + const allBillLicences = [] + const transactions = [] + + for (const chargeVersion of billingAccount.chargeVersions) { + const billLicence = _findOrCreateBillLicence(allBillLicences, chargeVersion.licence, billId) + + const createdTransactions = await _createTransactions( + billLicence.id, + billingPeriod, + chargeVersion, + billRunExternalId, + billingAccount.accountNumber + ) + + if (createdTransactions.length > 0) { + billLicence.billable = true + transactions.push(...createdTransactions) + } + } + + const billLicences = _extractBillableLicences(allBillLicences) + + return { billLicences, transactions } +} + +/** + * Handles generating the transaction data for a given charge version and then sending it to the Charging Module API. + */ +async function _createTransactions (billLicenceId, billingPeriod, chargeVersion, billRunExternalId, accountNumber) { + const chargePeriod = DetermineChargePeriodService.go(chargeVersion, billingPeriod) + + if (!chargePeriod.startDate) { + return [] + } + + const generatedTransactions = _generateTransactionData(billLicenceId, chargePeriod, chargeVersion) + + return SendTransactionsService.go(generatedTransactions, billRunExternalId, accountNumber, chargeVersion.licence) +} + +/** + * Intended to be used in conjunction with _createBillLicencesAndTransactions() it extracts only those bill licences + * where we generated transactions. This avoids us persisting a bill licence record with no transaction records. + * + * A billing account can be linked to multiple licences but not all of them may be billable. We add a flag to each + * one that denotes if transactions were generated so we can easily filter the billable ones out. But we also need + * to remove that flag because it doesn't exist in the DB and will cause issues if we try and persist the object. + */ +function _extractBillableLicences (allBillLicences) { + const billableBillLicences = [] + + allBillLicences.forEach((billLicence) => { + const { id, billId, licenceId, licenceRef, billable } = billLicence + + if (billable) { + billableBillLicences.push({ id, billId, licenceId, licenceRef }) + } + }) + + return billableBillLicences +} + +/** + * Use to either find or create the bill licence for a given bill (billing account) and licence + * + * For each billing account being processed the engine will create a bill. But the transactions within a bill are + * grouped by licence because the details of the licence will effect the charge against the transactions. + * + * So, transactions are linked to a bill by a 'bill licence' record. Things are further complicated by the fact we don't + * directly work with the licences against a billing account. The link is via charge versions, and more than one charge + * version can link to the same licence. + * + * > The information needed for billing is held in charge versions and their associated records. A licence will have at + * > least one of these but may have more should changes have been made, for example, a change in the amount that can be + * > abstracted. It's the charge version that is directly linked to the billing account, not the licence. + * + * Because we're processing the charge versions for a billing account the same licence might appear more than once. This + * means we need to find the existing bill licence record or if one doesn't exist create it. + * + * @param {Object[]} billLicences - The existing bill licences created for the bill being generated + * @param {Object} licence - the licence we're looking for an existing bill licence record + * @param {String} billId - the ID of the bill we're creating + * + * @return {Object} returns either an existing bill licence or a new one for the licence and bill being generated + */ +function _findOrCreateBillLicence (billLicences, licence, billId) { + const { id: licenceId, licenceRef } = licence + + let billLicence = billLicences.find((existingBillLicence) => { + return existingBillLicence.licenceId === licenceId + }) + + if (!billLicence) { + billLicence = { + id: generateUUID(), + billId, + licenceId, + licenceRef, + billable: false + } + + billLicences.push(billLicence) + } + + return billLicence +} + +/** + * Generate the transaction data for a charge version + * + * For each charge reference linked to the charge version we have to generate a + * transaction record. One of the things we need to know is if the charge version is the first charge on a new licence. + * This information needs to be passed to the Charging Module API as it affects the calculation. + * + * This function iterates the charge references generating a transaction for each one. + */ +function _generateTransactionData (billLicenceId, chargePeriod, chargeVersion) { + try { + const firstChargeOnNewLicence = DetermineMinimumChargeService.go(chargeVersion, chargePeriod) + + const transactions = [] + + chargeVersion.chargeReferences.forEach((chargeReference) => { + const transaction = GenerateTransactionService.go( + billLicenceId, + chargeReference, + chargePeriod, + firstChargeOnNewLicence, + chargeVersion.licence.waterUndertaker + ) + + transactions.push(transaction) + }) + + return transactions + } catch (error) { + throw new BillRunError(error, BillRunModel.errorCodes.failedToPrepareTransactions) + } +} + +/** + * Processes the billing account + * + * We create a bill object that will eventually be persisted for the billing account. We then call + * `_createBillLicencesAndTransactions()` which handles generating the bill licences and transactions that will form + * our bill. + * + * Once everything has been generated we persist the results to the DB. + */ +async function _processBillingAccount (billingAccount, billRun, billingPeriod) { + const { id: billingAccountId, accountNumber } = billingAccount + const { id: billRunId, externalId: billRunExternalId } = billRun + + const bill = { + id: generateUUID(), + accountNumber, + address: {}, // Address is set to an empty object for SROC billing invoices + billingAccountId, + billRunId, + credit: false, + financialYearEnding: billingPeriod.endDate.getFullYear() + } + + const billData = await _createBillLicencesAndTransactions(bill.id, billingAccount, billRunExternalId, billingPeriod) + + const { billLicences, transactions } = billData + + // No transactions were generated so there is nothing to bill. Do not persist anything! + if (transactions.length === 0) { + return false + } + + return _persistBillData(bill, billLicences, transactions) +} + +async function _persistBillData (bill, billLicences, transactions) { + await BillModel.query().insert(bill) + await BillLicenceModel.query().insert(billLicences) + await TransactionModel.query().insert(transactions) + + return true +} + +module.exports = { + go +} diff --git a/test/presenters/bill-runs/two-part-tariff/review-bill-run.presenter.test.js b/test/presenters/bill-runs/two-part-tariff/review-bill-run.presenter.test.js index 7734f0faae..6e6cb34053 100644 --- a/test/presenters/bill-runs/two-part-tariff/review-bill-run.presenter.test.js +++ b/test/presenters/bill-runs/two-part-tariff/review-bill-run.presenter.test.js @@ -50,6 +50,7 @@ describe('Review Bill Run presenter', () => { financialYear: '2022 to 2023', billRunType: 'two-part tariff', numberOfLicencesDisplayed: 3, + numberOfLicencesToReview: 1, reviewMessage: 'You need to review 1 licence with returns data issues. You can then continue and send the bill run.', totalNumberOfLicences: 3, preparedLicences: [ diff --git a/test/services/bill-runs/two-part-tariff/fetch-billing-accounts.service.test.js b/test/services/bill-runs/two-part-tariff/fetch-billing-accounts.service.test.js index ade374e01d..eae9b343ed 100644 --- a/test/services/bill-runs/two-part-tariff/fetch-billing-accounts.service.test.js +++ b/test/services/bill-runs/two-part-tariff/fetch-billing-accounts.service.test.js @@ -16,6 +16,7 @@ const ChargeElementHelper = require('../../../support/helpers/charge-element.hel const ChargeReferenceHelper = require('../../../support/helpers/charge-reference.helper.js') const ChargeVersionHelper = require('../../../support/helpers/charge-version.helper.js') const LicenceHelper = require('../../../support/helpers/licence.helper.js') +const RegionHelper = require('../../../support/helpers/region.helper.js') const ReviewChargeElementHelper = require('../../../support/helpers/review-charge-element.helper.js') const ReviewChargeReferenceHelper = require('../../../support/helpers/review-charge-reference.helper.js') const ReviewChargeVersionHelper = require('../../../support/helpers/review-charge-version.helper.js') @@ -33,15 +34,16 @@ describe('Fetch Billing Accounts service', () => { let chargeReference let chargeVersion let licence - + let region let reviewChargeElement let reviewChargeReference let reviewChargeVersion before(async () => { - billRun = await BillRunHelper.add() + region = await RegionHelper.add() + billRun = await BillRunHelper.add({ regionId: region.id }) - licence = await LicenceHelper.add() + licence = await LicenceHelper.add({ regionId: region.id }) billingAccount = await BillingAccountHelper.add() billingAccountNotInBillRun = await BillingAccountHelper.add() @@ -112,6 +114,10 @@ describe('Fetch Billing Accounts service', () => { expect(licence.waterUndertaker).to.equal(false) expect(licence.historicalAreaCode).to.equal('SAAR') expect(licence.regionalChargeArea).to.equal('Southern') + expect(licence.region).to.equal({ + id: region.id, + chargeRegionId: region.chargeRegionId + }) }) it('includes the applicable charge references', async () => { diff --git a/test/services/bill-runs/two-part-tariff/generate-bill-run.service.test.js b/test/services/bill-runs/two-part-tariff/generate-bill-run.service.test.js index f879cdb4c4..724308c0cf 100644 --- a/test/services/bill-runs/two-part-tariff/generate-bill-run.service.test.js +++ b/test/services/bill-runs/two-part-tariff/generate-bill-run.service.test.js @@ -3,21 +3,245 @@ // Test framework dependencies const Lab = require('@hapi/lab') const Code = require('@hapi/code') +const Sinon = require('sinon') -const { describe, it } = exports.lab = Lab.script() +const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() const { expect } = Code +// Test helpers +const { setTimeout } = require('timers/promises') + +const BillRunError = require('../../../../app/errors/bill-run.error.js') +const BillRunHelper = require('../../../support/helpers/bill-run.helper.js') +const BillRunModel = require('../../../../app/models/bill-run.model.js') +const ExpandedErrorError = require('../../../../app/errors/expanded.error.js') + +// Things we need to stub +const ChargingModuleGenerateRequest = require('../../../../app/requests/charging-module/generate-bill-run.request.js') +const FetchBillingAccountsService = require('../../../../app/services/bill-runs/two-part-tariff/fetch-billing-accounts.service.js') +const HandleErroredBillRunService = require('../../../../app/services/bill-runs/handle-errored-bill-run.service.js') +const LegacyRefreshBillRunRequest = require('../../../../app/requests/legacy/refresh-bill-run.request.js') +const ProcessBillingPeriodService = require('../../../../app/services/bill-runs/two-part-tariff/process-billing-period.service.js') + // Thing under test const GenerateBillRunService = require('../../../../app/services/bill-runs/two-part-tariff/generate-bill-run.service.js') describe('Generate Bill Run Service', () => { - const billRunId = 'efe4d4b3-cca6-47ba-bcf6-9b848ffb535c' + // NOTE: introducing a delay in the tests is not ideal. But the service is written such that the generating happens in + // the background and is not awaited. We want to confirm things like the status of the bill run at certain points. But + // the only way to do so is to give the background process time to complete. + const delay = 500 + + const billRunDetails = { + batchType: 'two_part_tariff', fromFinancialYearEnding: 2023, toFinancialYearEnding: 2023 + } + + let billRun + let notifierStub + + beforeEach(async () => { + // BaseRequest depends on the GlobalNotifier to have been set. This happens in app/plugins/global-notifier.plugin.js + // when the app starts up and the plugin is registered. As we're not creating an instance of Hapi server in this + // test we recreate the condition by setting it directly with our own stub + notifierStub = { omg: Sinon.stub(), omfg: Sinon.stub() } + global.GlobalNotifier = notifierStub + }) + + afterEach(() => { + Sinon.restore() + delete global.GlobalNotifier + }) describe('when called', () => { - it('returns the bill run ID passed to it', async () => { - const result = await GenerateBillRunService.go(billRunId) + beforeEach(() => { + // We stub FetchBillingAccountsService to return no results in all scenarios because it is the result of + // ProcessBillingPeriodService which determines if there is anything to bill. We change the stub of that service + // to dictate the scenario we're trying to test. + Sinon.stub(FetchBillingAccountsService, 'go').resolves([]) + }) + + describe('and the bill run is not in review', () => { + beforeEach(async () => { + billRun = await BillRunHelper.add({ ...billRunDetails, status: 'ready' }) + }) + + it('throws an error', async () => { + const error = await expect(GenerateBillRunService.go(billRun.id)) + .to + .reject() + + expect(error).to.be.an.instanceOf(ExpandedErrorError) + expect(error.message).to.equal('Cannot process a two-part tariff bill run that is not in review') + }) + }) + + describe('and the bill run is in review', () => { + beforeEach(async () => { + billRun = await BillRunHelper.add({ ...billRunDetails, status: 'review' }) + }) + + describe('but there is nothing to bill', () => { + it('sets the bill run status to "empty"', async () => { + await GenerateBillRunService.go(billRun.id) + + await setTimeout(delay) + + const result = await BillRunModel.query().findById(billRun.id) + + expect(result.status).to.equal('empty') + }) + }) + + describe('and something is billed', () => { + let chargingModuleGenerateRequestStub + let legacyRefreshBillRunRequestStub + + beforeEach(() => { + chargingModuleGenerateRequestStub = Sinon.stub(ChargingModuleGenerateRequest, 'send') + legacyRefreshBillRunRequestStub = Sinon.stub(LegacyRefreshBillRunRequest, 'send') + + Sinon.stub(ProcessBillingPeriodService, 'go').resolves(true) + }) + + it('sets the bill run status to "processing"', async () => { + await GenerateBillRunService.go(billRun.id) + + const result = await BillRunModel.query().findById(billRun.id) + + expect(result.status).to.equal('processing') + }) + + it('tells the charging module API to "generate" the bill run', async () => { + await GenerateBillRunService.go(billRun.id) + + await setTimeout(delay) + + expect(chargingModuleGenerateRequestStub.called).to.be.true() + }) + + it('tells the legacy service to start its refresh job', async () => { + await GenerateBillRunService.go(billRun.id) + + await setTimeout(delay) + + expect(legacyRefreshBillRunRequestStub.called).to.be.true() + }) + }) + }) + }) + + describe('when the service errors', () => { + let handleErroredBillRunStub + let thrownError + + beforeEach(async () => { + billRun = await BillRunHelper.add({ ...billRunDetails, status: 'review' }) + + handleErroredBillRunStub = Sinon.stub(HandleErroredBillRunService, 'go') + }) + + describe('because fetching the billing accounts fails', () => { + beforeEach(() => { + thrownError = new Error('ERROR') + + Sinon.stub(FetchBillingAccountsService, 'go').rejects(thrownError) + }) + + it('calls HandleErroredBillRunService with appropriate error code', async () => { + await GenerateBillRunService.go(billRun.id) + + await setTimeout(delay) + + const handlerArgs = handleErroredBillRunStub.firstCall.args + + expect(handlerArgs[1]).to.equal(BillRunModel.errorCodes.failedToProcessChargeVersions) + }) + + it('logs the error', async () => { + await GenerateBillRunService.go(billRun.id) + + await setTimeout(delay) + + const args = notifierStub.omfg.firstCall.args + + expect(args[0]).to.equal('Bill run process errored') + expect(args[1].billRun.id).to.equal(billRun.id) + expect(args[2]).to.be.an.error() + expect(args[2].name).to.equal(thrownError.name) + expect(args[2].message).to.equal(`Error: ${thrownError.message}`) + expect(args[2].code).to.equal(BillRunModel.errorCodes.failedToProcessChargeVersions) + }) + }) + + describe('because the process billing period service fails', () => { + describe('and the error thrown has an error code', () => { + beforeEach(() => { + thrownError = new BillRunError(new Error(), BillRunModel.errorCodes.failedToPrepareTransactions) + + Sinon.stub(FetchBillingAccountsService, 'go').resolves([]) + Sinon.stub(ProcessBillingPeriodService, 'go').rejects(thrownError) + }) + + it('calls HandleErroredBillRunService with appropriate error code', async () => { + await GenerateBillRunService.go(billRun.id) + + await setTimeout(delay) + + const handlerArgs = handleErroredBillRunStub.firstCall.args + + expect(handlerArgs[1]).to.equal(BillRunModel.errorCodes.failedToPrepareTransactions) + }) + + it('logs the error', async () => { + await GenerateBillRunService.go(billRun.id) + + await setTimeout(delay) + + const args = notifierStub.omfg.firstCall.args + + expect(args[0]).to.equal('Bill run process errored') + expect(args[1].billRun.id).to.equal(billRun.id) + expect(args[2]).to.be.an.error() + expect(args[2].name).to.equal(thrownError.name) + expect(args[2].message).to.equal(thrownError.message) + expect(args[2].code).to.equal(BillRunModel.errorCodes.failedToPrepareTransactions) + }) + }) + }) + + describe('because finalising the bill run fails', () => { + beforeEach(() => { + thrownError = new Error('ERROR') + + Sinon.stub(FetchBillingAccountsService, 'go').resolves([]) + Sinon.stub(ProcessBillingPeriodService, 'go').resolves(true) + Sinon.stub(ChargingModuleGenerateRequest, 'send').rejects(thrownError) + }) + + it('calls HandleErroredBillRunService with appropriate error code', async () => { + await GenerateBillRunService.go(billRun.id) + + await setTimeout(delay) + + const handlerArgs = handleErroredBillRunStub.firstCall.args + + expect(handlerArgs[1]).to.be.undefined() + }) + + it('logs the error', async () => { + await GenerateBillRunService.go(billRun.id) + + await setTimeout(delay) + + const args = notifierStub.omfg.firstCall.args - expect(result).to.equal(billRunId) + expect(args[0]).to.equal('Bill run process errored') + expect(args[1].billRun.id).to.equal(billRun.id) + expect(args[2]).to.be.an.error() + expect(args[2].name).to.equal(thrownError.name) + expect(args[2].message).to.equal(thrownError.message) + expect(args[2].code).to.be.undefined() + }) }) }) }) diff --git a/test/services/bill-runs/two-part-tariff/generate-transaction.service.test.js b/test/services/bill-runs/two-part-tariff/generate-transaction.service.test.js new file mode 100644 index 0000000000..d3a6d94b3e --- /dev/null +++ b/test/services/bill-runs/two-part-tariff/generate-transaction.service.test.js @@ -0,0 +1,142 @@ +'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 GenerateTransactionService = require('../../../../app/services/bill-runs/two-part-tariff/generate-transaction.service.js') + +describe('Generate Transaction service', () => { + const billLicenceId = '5e2afb53-ca92-4515-ad71-36a7cefbcebb' + + let chargePeriod + let chargeReference + let newLicence + let waterUndertaker + + beforeEach(async () => { + chargeReference = _chargeReference(billLicenceId) + + chargePeriod = { + startDate: new Date('2022-04-01'), + endDate: new Date('2022-10-31') + } + newLicence = false + waterUndertaker = false + }) + + describe('when called', () => { + it('returns a two-part tariff transaction ready to be persisted', () => { + const result = GenerateTransactionService.go( + billLicenceId, chargeReference, chargePeriod, newLicence, waterUndertaker + ) + + expect(result).to.equal({ + billLicenceId, + authorisedDays: 0, + billableDays: 0, + newLicence, + waterUndertaker, + chargeReferenceId: '89450220-0f7f-4280-a946-1fdbe9b789c1', + startDate: chargePeriod.startDate, + endDate: chargePeriod.endDate, + source: 'non-tidal', + season: 'all year', + loss: 'low', + credit: false, + chargeType: 'standard', + authorisedQuantity: 20, + billableQuantity: 15, + status: 'candidate', + description: 'Water abstraction charge: Lower Queenstown - Pittisham', + volume: 15, + section126Factor: 1, + section127Agreement: false, + section130Agreement: false, + secondPartCharge: true, + scheme: 'sroc', + aggregateFactor: 0.75, + adjustmentFactor: 0.6, + chargeCategoryCode: '4.4.5', + chargeCategoryDescription: 'Low loss, non-tidal, restricted water, up to and including 5,000 ML/yr, Tier 1 model', + supportedSource: false, + supportedSourceName: null, + waterCompanyCharge: false, + winterOnly: false + }, + { + // We skip the ID because it is a UUID generated by the service. We skip purposes because it is the result of + // calling `toJSON()` on the charge references child ChargeElementModel instances. Including it would just be us + // testing Objection + skip: ['id', 'purposes'] + }) + }) + + describe('and the charge reference has a two-part tariff agreement (section 127)', () => { + beforeEach(() => { + chargeReference.adjustments.s127 = true + }) + + it('returns the two-part tariff prefixed description', () => { + const result = GenerateTransactionService.go( + billLicenceId, chargeReference, chargePeriod, newLicence, waterUndertaker + ) + + expect(result.description).to.equal('Two-part tariff basic water abstraction charge: Lower Queenstown - Pittisham') + }) + }) + }) +}) + +function _chargeReference () { + // NOTE: We are faking an Objection model which comes with a toJSON() method that gets called as part + // of processing the billing account. + const toJSON = () => { return '{}' } + + return { + id: '89450220-0f7f-4280-a946-1fdbe9b789c1', + additionalCharges: { isSupplyPublicWater: false }, + adjustments: { + s126: null, s127: false, s130: false, charge: null, winter: false, aggregate: '0.562114443' + }, + chargeCategory: { + id: 'b270718a-12c0-4fca-884b-3f8612dbe2f5', + reference: '4.4.5', + shortDescription: 'Low loss, non-tidal, restricted water, up to and including 5,000 ML/yr, Tier 1 model' + }, + chargeElements: [ + { + id: 'e6b98712-227a-40c2-b93a-c05e9047be8c', + abstractionPeriodStartDay: 1, + abstractionPeriodStartMonth: 4, + abstractionPeriodEndDay: 31, + abstractionPeriodEndMonth: 3, + reviewChargeElements: [{ id: '1d9050b2-09c8-4570-8173-7f55921437cc', amendedAllocated: 5 }], + toJSON + }, + { + id: '9e6f3f64-78d5-441b-80fc-e01711b2f766', + abstractionPeriodStartDay: 1, + abstractionPeriodStartMonth: 4, + abstractionPeriodEndDay: 31, + abstractionPeriodEndMonth: 3, + reviewChargeElements: [{ id: '17f0c41e-e894-41d2-8a68-69dd2b39e9f9', amendedAllocated: 10 }], + toJSON + } + ], + description: 'Lower Queenstown - Pittisham', + loss: 'low', + reviewChargeReferences: [{ + id: '3dd04348-2c06-4559-9343-dd7dd76276ef', + amendedAggregate: 0.75, + amendedAuthorisedVolume: 20, + amendedChargeAdjustment: 0.6 + }], + source: 'non-tidal', + volume: 15 + } +} diff --git a/test/services/bill-runs/two-part-tariff/process-billing-period.service.test.js b/test/services/bill-runs/two-part-tariff/process-billing-period.service.test.js new file mode 100644 index 0000000000..2dfbd6781a --- /dev/null +++ b/test/services/bill-runs/two-part-tariff/process-billing-period.service.test.js @@ -0,0 +1,328 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const BillModel = require('../../../../app/models/bill.model.js') +const { generateAccountNumber } = require('../../../support/helpers/billing-account.helper.js') +const { generateUUID } = require('../../../../app/lib/general.lib.js') +const { generateLicenceRef } = require('../../../support/helpers/licence.helper.js') +const { generateChargeRegionId } = require('../../../support/helpers/region.helper.js') + +// Things we need to stub +const BillRunError = require('../../../../app/errors/bill-run.error.js') +const BillRunModel = require('../../../../app/models/bill-run.model.js') +const ChargingModuleCreateTransactionRequest = require('../../../../app/requests/charging-module/create-transaction.request.js') +const GenerateTransactionService = require('../../../../app/services/bill-runs/two-part-tariff/generate-transaction.service.js') + +// Thing under test +const ProcessBillingPeriodService = require('../../../../app/services/bill-runs/two-part-tariff/process-billing-period.service.js') + +describe('Two-part Tariff Process Billing Period service', () => { + const billingPeriod = { + startDate: new Date('2022-04-01'), + endDate: new Date('2023-03-31') + } + + let billRun + let billingAccount + let chargingModuleCreateTransactionRequestStub + let licence + + beforeEach(async () => { + billRun = { + id: generateUUID(), + externalId: generateUUID() + } + + billingAccount = _billingAccount() + + licence = _licence() + + chargingModuleCreateTransactionRequestStub = Sinon.stub(ChargingModuleCreateTransactionRequest, 'send') + }) + + afterEach(() => { + Sinon.restore() + }) + + describe('when the service is called', () => { + describe('and there are no billing accounts to process', () => { + it('returns false (bill run is empty)', async () => { + const result = await ProcessBillingPeriodService.go(billRun, billingPeriod, []) + + expect(result).to.be.false() + }) + }) + + describe('and there are billing accounts to process', () => { + beforeEach(async () => { + chargingModuleCreateTransactionRequestStub.onFirstCall().resolves({ + ..._chargingModuleResponse('7e752fa6-a19c-4779-b28c-6e536f028795') + }) + + chargingModuleCreateTransactionRequestStub.onSecondCall().resolves({ + ..._chargingModuleResponse('a2086da4-e3b6-4b83-afe1-0e2e5255efaf') + }) + }) + + describe('and they are billable', () => { + beforeEach(async () => { + // We want to ensure there is coverage of the functionality that finds an existing bill licence or creates a + // new one when processing a billing account. To to that we need a billing account with 2 charge versions + // linked to the same licence + billingAccount.chargeVersions = [ + _chargeVersion(billingAccount.id, licence), + _chargeVersion(billingAccount.id, licence) + ] + }) + + it('returns true (bill run is not empty) and persists the generated bills', async () => { + const result = await ProcessBillingPeriodService.go(billRun, billingPeriod, [billingAccount]) + + expect(result).to.be.true() + + const bills = await _fetchPersistedBill(billRun.id) + + expect(bills).to.have.length(1) + expect(bills[0]).to.equal({ + accountNumber: billingAccount.accountNumber, + address: {}, // Address is set to an empty object for SROC billing invoices + billingAccountId: billingAccount.id, + credit: false, + financialYearEnding: billingPeriod.endDate.getFullYear() + }, { skip: ['billLicences'] }) + + expect(bills[0].billLicences).to.have.length(1) + expect(bills[0].billLicences[0]).to.equal({ + licenceId: licence.id, + licenceRef: licence.licenceRef + }, { skip: ['transactions'] }) + + expect(bills[0].billLicences[0].transactions).to.have.length(2) + }) + }) + + describe('and they are partially billable (a bill licence generates 0 transactions)', () => { + beforeEach(() => { + // Create a licence that has a revoked date before the billing period and then link it to a charge version + // that is also linked to our billing account. The engine will determine that the charge period for the charge + // version is invalid so won't attempt to generate a transaction. If we did try, the Charging Module would + // only reject it. + const unbillableLicence = _licence() + + unbillableLicence.revokedDate = new Date('2019-01-01') + + const unbillableChargeVersion = _chargeVersion(billingAccount.id, unbillableLicence) + + billingAccount.chargeVersions = [ + _chargeVersion(billingAccount.id, licence), + _chargeVersion(billingAccount.id, licence), + unbillableChargeVersion + ] + }) + + it('returns true (bill run is not empty) and only persists the bill licences with transactions', async () => { + const result = await ProcessBillingPeriodService.go(billRun, billingPeriod, [billingAccount]) + + expect(result).to.be.true() + + const bills = await _fetchPersistedBill(billRun.id) + + expect(bills).to.have.length(1) + expect(bills[0]).to.equal({ + accountNumber: billingAccount.accountNumber, + address: {}, // Address is set to an empty object for SROC billing invoices + billingAccountId: billingAccount.id, + credit: false, + financialYearEnding: billingPeriod.endDate.getFullYear() + }, { skip: ['billLicences'] }) + + expect(bills[0].billLicences).to.have.length(1) + expect(bills[0].billLicences[0]).to.equal({ + licenceId: licence.id, + licenceRef: licence.licenceRef + }, { skip: ['transactions'] }) + + expect(bills[0].billLicences[0].transactions).to.have.length(2) + }) + }) + + describe('but they are not billable', () => { + beforeEach(() => { + // Create a licence that has a revoked date before the billing period and then link it to a charge version + // that is also linked to our billing account. The engine will determine that the charge period for the charge + // version is invalid so won't attempt to generate a transaction. If we did try, the Charging Module would + // only reject it. + const unbillableLicence = _licence() + + unbillableLicence.revokedDate = new Date('2019-01-01') + + const unbillableChargeVersion = _chargeVersion(billingAccount.id, unbillableLicence) + + billingAccount.chargeVersions = [unbillableChargeVersion] + }) + + it('returns false (bill run is empty) and persists nothing', async () => { + const result = await ProcessBillingPeriodService.go(billRun, billingPeriod, [billingAccount]) + + expect(result).to.be.false() + + const bills = await _fetchPersistedBill(billRun.id) + + expect(bills).to.be.empty() + }) + }) + }) + }) + + describe('when the service errors', () => { + beforeEach(async () => { + billingAccount.chargeVersions = [_chargeVersion(billingAccount.id, licence)] + }) + + describe('because generating the calculated transaction fails', () => { + beforeEach(async () => { + Sinon.stub(GenerateTransactionService, 'go').throws() + }) + + it('throws a BillRunError with the correct code', async () => { + const error = await expect(ProcessBillingPeriodService.go(billRun, billingPeriod, [billingAccount])) + .to + .reject() + + expect(error).to.be.an.instanceOf(BillRunError) + expect(error.code).to.equal(BillRunModel.errorCodes.failedToPrepareTransactions) + }) + }) + + describe('because sending the transactions fails', () => { + beforeEach(async () => { + chargingModuleCreateTransactionRequestStub.rejects() + }) + + it('throws a BillRunError with the correct code', async () => { + const error = await expect(ProcessBillingPeriodService.go(billRun, billingPeriod, [billingAccount])) + .to + .reject() + + expect(error).to.be.an.instanceOf(BillRunError) + expect(error.code).to.equal(BillRunModel.errorCodes.failedToCreateCharge) + }) + }) + }) +}) + +function _billingAccount () { + return { + id: generateUUID(), + accountNumber: generateAccountNumber() + } +} + +function _chargingModuleResponse (transactionId) { + return { + succeeded: true, + response: { + body: { transaction: { id: transactionId } } + } + } +} + +function _chargeVersion (billingAccountId, licence) { + // NOTE: We are faking an Objection model which comes with a toJSON() method that gets called as part + // of processing the billing account. + const toJSON = () => { return '{}' } + + return { + id: generateUUID(), + scheme: 'sroc', + startDate: new Date('2022-04-01'), + endDate: null, + billingAccountId, + status: 'current', + licence, + chargeReferences: [ + { + id: generateUUID(), + additionalCharges: { isSupplyPublicWater: false }, + adjustments: { + s126: null, s127: false, s130: false, charge: null, winter: false, aggregate: '0.562114443' + }, + chargeCategory: { + id: 'b270718a-12c0-4fca-884b-3f8612dbe2f5', + reference: '4.4.5', + shortDescription: 'Low loss, non-tidal, restricted water, up to and including 5,000 ML/yr, Tier 1 model' + }, + chargeElements: [ + { + id: 'e6b98712-227a-40c2-b93a-c05e9047be8c', + abstractionPeriodStartDay: 1, + abstractionPeriodStartMonth: 4, + abstractionPeriodEndDay: 31, + abstractionPeriodEndMonth: 3, + reviewChargeElements: [{ id: '1d9050b2-09c8-4570-8173-7f55921437cc', amendedAllocated: 5 }], + toJSON + }, + { + id: '9e6f3f64-78d5-441b-80fc-e01711b2f766', + abstractionPeriodStartDay: 1, + abstractionPeriodStartMonth: 4, + abstractionPeriodEndDay: 31, + abstractionPeriodEndMonth: 3, + reviewChargeElements: [{ id: '17f0c41e-e894-41d2-8a68-69dd2b39e9f9', amendedAllocated: 10 }], + toJSON + } + ], + description: 'Lower Queenstown - Pittisham', + loss: 'low', + reviewChargeReferences: [{ + id: '3dd04348-2c06-4559-9343-dd7dd76276ef', + amendedAggregate: 0.75, + amendedAuthorisedVolume: 20, + amendedChargeAdjustment: 0.6 + }], + source: 'non-tidal', + volume: 15 + } + ] + } +} + +async function _fetchPersistedBill (billRunId) { + return BillModel.query() + .select(['accountNumber', 'address', 'billingAccountId', 'credit', 'financialYearEnding']) + .where('billRunId', billRunId) + .withGraphFetched('billLicences') + .modifyGraph('billLicences', (builder) => { + builder.select(['licenceId', 'licenceRef']) + }) + .withGraphFetched('billLicences.transactions') + .modifyGraph('billLicences.transactions', (builder) => { + builder.select(['id']) + }) +} + +function _licence () { + return { + id: generateUUID(), + licenceRef: generateLicenceRef(), + waterUndertaker: true, + historicalAreaCode: 'SAAR', + regionalChargeArea: 'Southern', + startDate: new Date('2022-01-01'), + expiredDate: null, + lapsedDate: null, + revokedDate: null, + region: { + id: generateUUID(), + chargeRegionId: generateChargeRegionId() + } + } +} diff --git a/test/support/helpers/region.helper.js b/test/support/helpers/region.helper.js index fabc01b94e..ad0b27443a 100644 --- a/test/support/helpers/region.helper.js +++ b/test/support/helpers/region.helper.js @@ -40,7 +40,7 @@ function add (data = {}) { function defaults (data = {}) { const naldRegionId = randomInteger(1, 8) const defaults = { - chargeRegionId: _chargeRegionId(naldRegionId), + chargeRegionId: generateChargeRegionId(naldRegionId), naldRegionId, name: 'Kingdom of Avalon', displayName: 'Avalon' @@ -52,7 +52,11 @@ function defaults (data = {}) { } } -function _chargeRegionId (naldRegionId) { +function generateChargeRegionId (naldRegionId = null) { + if (!naldRegionId) { + naldRegionId = randomInteger(1, 8) + } + const chargeRegionIds = ['A', 'B', 'Y', 'N', 'E', 'S', 'T', 'W'] return chargeRegionIds[naldRegionId - 1] @@ -60,5 +64,6 @@ function _chargeRegionId (naldRegionId) { module.exports = { add, - defaults + defaults, + generateChargeRegionId }