Skip to content

Commit

Permalink
Update Process Batch service for debit only (#133)
Browse files Browse the repository at this point in the history
https://eaflood.atlassian.net/browse/WATER-3906

We have created the individual 'tools' needed to generate an SROC supplementary bill-run. By this, we mean the `billing_batch`, `billing_invoice`, `billing_invoice_licence` and `billing_transaction` records. Also, (though we're still not sure where it's used!) the event record.

We have also worked to identify the charge versions to consider in an SROC supplementary bill-run, and how to communicate with the charging module API. Now it's time to pull it all together and create a complete bill-run, one that can be confirmed and sent in the WRLS service UI.

The work to credit a previous bill run is still to be done. We also need to properly handle when a bill run is empty (no valid transactions were found) or the process errors. But we have everything we need to get us to a place where we folks can start testing and verifying debit-only bill-runs.

So, we've 'spiked' a solution and this change updates `ProcessBillingBatchService` with it. There are no unit tests because we're still trying to decide on our final approach. But this change means testing can begin.
  • Loading branch information
Cruikshanks authored Mar 6, 2023
1 parent 4b70f2b commit 3c753c4
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,24 @@ const FetchInvoiceAccountService = require('./fetch-invoice-account.service.js')
* @returns {Object} The newly-created billing invoice record
*/
async function go (chargeVersion, billingPeriod, billingBatchId) {
// TODO: REFACTOR THIS TO UPSERT INSTEAD OF RETRIEVE AND RETURN EXISTING
const retrievedBillingInvoice = await BillingInvoiceModel.query()
.where('invoiceAccountId', chargeVersion.invoiceAccountId)
.where('billingBatchId', billingBatchId)
.first()

if (retrievedBillingInvoice) {
return retrievedBillingInvoice
}

const billingInvoice = await BillingInvoiceModel.query()
.insert({
invoiceAccountId: chargeVersion.invoiceAccountId,
address: {}, // Address is set to an empty object for SROC billing invoices
invoiceAccountNumber: await _getInvoiceAccountNumber(chargeVersion.invoiceAccountId),
billingBatchId,
financialYearEnding: billingPeriod.endDate.getFullYear()
financialYearEnding: billingPeriod.endDate.getFullYear(),
isCredit: false
})
.returning('*')

Expand Down
148 changes: 141 additions & 7 deletions app/services/supplementary-billing/process-billing-batch.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,22 @@

/**
* Processes a new billing batch
* @module InitiateBillingBatchService
* @module ProcessBillingBatchService
*/

const ChargingModuleRequestLib = require('../../lib/charging-module-request.lib.js')
const { randomUUID } = require('crypto')

const BillingBatchModel = require('../../models/water/billing-batch.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 CreateBillingInvoiceService = require('./create-billing-invoice.service.js')
const CreateBillingInvoiceLicenceService = require('./create-billing-invoice-licence.service.js')
const CreateBillingTransactionService = require('./create-billing-transaction.service.js')
const DetermineMinimumChargeService = require('./determine-minimum-charge.service.js')
const FetchChargeVersionsService = require('./fetch-charge-versions.service.js')
const FormatSrocTransactionLineService = require('./format-sroc-transaction-line.service.js')
const LegacyRequestLib = require('../../lib/legacy-request.lib.js')

/**
* Creates the invoices and transactions in both WRLS and the Charging Module API
Expand All @@ -16,11 +28,133 @@ const ChargingModuleRequestLib = require('../../lib/charging-module-request.lib.
* @param {Object} billingPeriod an object representing the financial year the transaction is for
*/
async function go (billingBatch, billingPeriod) {
// TODO: Remove this call. We'e just using this to demonstrate that we can make asynchronous calls and perform
// actions in here all whilst the InitiateBillingBatchService (which calls this service) has moved on and responded
// to the original request
const result = await ChargingModuleRequestLib.get('status')
console.log(result, billingBatch, billingPeriod)
const { billingBatchId } = billingBatch

await _updateStatus(billingBatchId, 'processing')

// 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)

// TODO: Handle an empty billing invoice
for (const chargeVersion of chargeVersions) {
const billingInvoice = await CreateBillingInvoiceService.go(chargeVersion, billingPeriod, billingBatchId)
const billingInvoiceLicence = await CreateBillingInvoiceLicenceService.go(billingInvoice, chargeVersion.licence)

await _processTransactionLines(
billingBatch.externalId,
billingPeriod,
chargeVersion,
billingInvoice.invoiceAccountNumber,
billingInvoiceLicence.billingInvoiceLicenceId
)
}

await ChargingModuleGenerateService.go(billingBatch.externalId)

// NOTE: Retaining this as a candidate for updating the bill run status if the process errors or the bill run is empty
// await UpdateBillingBatchStatusService.go(billingData.id, 'ready')
await LegacyRequestLib.post('water', `billing/batches/${billingBatchId}/refresh`)
}

async function _updateStatus (billingBatchId, status) {
await BillingBatchModel.query()
.findById(billingBatchId)
.patch({ status })
}

async function _processTransactionLines (cmBillRunId, billingPeriod, chargeVersion, invoiceAccountNumber, billingInvoiceLicenceId) {
const financialYearEnding = billingPeriod.endDate.getFullYear()

if (chargeVersion.chargeElements) {
for (const chargeElement of chargeVersion.chargeElements) {
const options = {
isTwoPartSecondPartCharge: false,
isWaterUndertaker: chargeVersion.licence.isWaterUndertaker,
isNewLicence: DetermineMinimumChargeService.go(chargeVersion, financialYearEnding),
isCompensationCharge: false
}

let transaction = await _processTransactionLine(
chargeElement,
chargeVersion,
billingPeriod,
cmBillRunId,
billingInvoiceLicenceId,
invoiceAccountNumber,
options
)

if (transaction.billableDays === 0) {
return
}

// Compensation charge line
// First, we look to see if there is anything to bill. If there is we then determine if a compensation charge line
// is needed. From looking at the legacy code this is based purely on whether the licence is flagged as a
// water undertaker
if (!options.isWaterUndertaker) {
options.isCompensationCharge = true
transaction = await _processTransactionLine(
chargeElement,
chargeVersion,
billingPeriod,
cmBillRunId,
billingInvoiceLicenceId,
invoiceAccountNumber,
options
)
}
}
}
}

async function _processTransactionLine (
chargeElement,
chargeVersion,
billingPeriod,
cmBillRunId,
billingInvoiceLicenceId,
invoiceAccountNumber,
options
) {
const financialYearEnding = billingPeriod.endDate.getFullYear()

const transaction = {
...FormatSrocTransactionLineService.go(chargeElement, chargeVersion, financialYearEnding, options),
billingInvoiceLicenceId
}

// No point carrying on if there is nothing to bill
if (transaction.billableDays === 0) {
return transaction
}

// We set `disableEntropyCache` to `false` as normally, for performance reasons node caches enough random data to
// generate up to 128 UUIDs. We disable this as we may need to generate more than this and the performance hit in
// disabling this cache is a rounding error in comparison to the rest of the process.
//
// https://nodejs.org/api/crypto.html#cryptorandomuuidoptions
transaction.billingTransactionId = randomUUID({ disableEntropyCache: true })

const chargingModuleRequest = ChargingModuleCreateTransactionPresenter.go(
transaction,
billingPeriod,
invoiceAccountNumber,
chargeVersion.licence
)

const chargingModuleResponse = await ChargingModuleCreateTransactionService.go(cmBillRunId, chargingModuleRequest)

// TODO: Handle a failed request
transaction.status = 'charge_created'
transaction.externalId = chargingModuleResponse.response.body.transaction.id

await CreateBillingTransactionService.go(transaction)

return transaction
}

module.exports = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,26 @@ describe('Create Billing Invoice service', () => {
chargeVersion = { invoiceAccountId: invoiceAccount.invoiceAccountId }
})

it('returns the new billing invoice instance containing the correct data', async () => {
const result = await CreateBillingInvoiceService.go(chargeVersion, billingPeriod, billingBatchId)
describe('when no existing billing invoice exists', () => {
it('returns the new billing invoice instance containing the correct data', async () => {
const result = await CreateBillingInvoiceService.go(chargeVersion, billingPeriod, billingBatchId)

expect(result).to.be.an.instanceOf(BillingInvoiceModel)

expect(result.invoiceAccountId).to.equal(invoiceAccount.invoiceAccountId)
expect(result.address).to.equal({})
expect(result.invoiceAccountNumber).to.equal(invoiceAccount.invoiceAccountNumber)
expect(result.billingBatchId).to.equal(billingBatchId)
expect(result.financialYearEnding).to.equal(2023)
})
})

expect(result).to.be.an.instanceOf(BillingInvoiceModel)
describe('when an existing billing invoice exists', () => {
it('returns an existing billing invoice instance containing the correct data', async () => {
const result1 = await CreateBillingInvoiceService.go(chargeVersion, billingPeriod, billingBatchId)
const result2 = await CreateBillingInvoiceService.go(chargeVersion, billingPeriod, billingBatchId)

expect(result.invoiceAccountId).to.equal(invoiceAccount.invoiceAccountId)
expect(result.address).to.equal({})
expect(result.invoiceAccountNumber).to.equal(invoiceAccount.invoiceAccountNumber)
expect(result.billingBatchId).to.equal(billingBatchId)
expect(result.financialYearEnding).to.equal(2023)
expect(result1.billingInvoiceId).to.equal(result2.billingInvoiceId)
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,10 @@ const { expect } = Code
// Test helpers
const BillingBatchHelper = require('../../support/helpers/water/billing-batch.helper.js')

// Things we need to stub
const ChargingModuleRequestLib = require('../../../app/lib/charging-module-request.lib.js')

// Thing under test
const ProcessBillingBatchService = require('../../../app/services/supplementary-billing/process-billing-batch.service.js')

describe('Process billing batch service', () => {
describe.skip('Process billing batch service', () => {
const billingPeriod = {
startDate: new Date('2022-04-01'),
endDate: new Date('2023-03-31')
Expand All @@ -31,20 +28,6 @@ describe('Process billing batch service', () => {
describe('when the service is called', () => {
beforeEach(async () => {
billingBatch = await BillingBatchHelper.add()

Sinon.stub(ChargingModuleRequestLib, 'get').resolves({
succeeded: true,
response: {
info: {
gitCommit: '273604040a47e0977b0579a0fef0f09726d95e39',
dockerTag: 'ghcr.io/defra/sroc-charging-module-api:v0.19.0'
},
statusCode: 200,
body: {
status: 'alive'
}
}
})
})

it('does something temporarily as it is just a placeholder at the moment', async () => {
Expand Down

0 comments on commit 3c753c4

Please sign in to comment.