Skip to content

Commit

Permalink
Move reusable two-part tariff billing services (#1765)
Browse files Browse the repository at this point in the history
https://eaflood.atlassian.net/browse/WATER-4201

> Part of the work to support two-part tariff supplementary bill runs

In [Move reusable supplementary billing services](#1760) we shifted some existing supplementary billing services to make them reusable by the two-part tariff supplementary billing engine we're building.

Our [spike](#1412) has shown there are some two-part tariff annual billing services we can also make reusable.
  • Loading branch information
Cruikshanks authored Mar 1, 2025
1 parent 7967091 commit 189506e
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 55 deletions.
4 changes: 2 additions & 2 deletions app/controllers/bill-runs.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

const Boom = require('@hapi/boom')

const GenerateBillRunService = require('../services/bill-runs/two-part-tariff/generate-bill-run.service.js')
const GenerateTwoPartTariffBillRunService = require('../services/bill-runs/generate-two-part-tariff-bill-run.service.js')
const IndexBillRunsService = require('../services/bill-runs/index-bill-runs.service.js')
const SubmitCancelBillRunService = require('../services/bill-runs/cancel/submit-cancel-bill-run.service.js')
const SubmitSendBillRunService = require('../services/bill-runs/send/submit-send-bill-run.service.js')
Expand Down Expand Up @@ -66,7 +66,7 @@ async function twoPartTariff(request, h) {
try {
// NOTE: What we are awaiting here is for the GenerateBillRunService to update the status of the bill run to
// `processing'.
await GenerateBillRunService.go(id)
await GenerateTwoPartTariffBillRunService.go(id)

// Redirect to the bill runs page
return h.redirect('/system/bill-runs')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,16 @@
* @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 FetchTwoPartTariffBillingAccountsService = require('../fetch-two-part-tariff-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')
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 FetchTwoPartTariffBillingAccountsService = require('./fetch-two-part-tariff-billing-accounts.service.js')
const HandleErroredBillRunService = require('./handle-errored-bill-run.service.js')
const LegacyRefreshBillRunRequest = require('../../requests/legacy/refresh-bill-run.request.js')
const ProcessAnnualBillingPeriodService = require('./two-part-tariff/process-billing-period.service.js')
const ProcessSupplementaryBillingPeriodService = require('./tpt-supplementary/process-billing-period.service.js')

/**
* Generates a two-part tariff bill run after the users have completed reviewing its match & allocate results
Expand Down Expand Up @@ -136,7 +133,12 @@ async function _processBillingPeriod(billingPeriod, billRun) {

const billingAccounts = await _fetchBillingAccounts(billRunId)

const billRunPopulated = await ProcessBillingPeriodService.go(billRun, billingPeriod, billingAccounts)
let billRunPopulated = false
if (billRun.batchType === 'two_part_tariff') {
billRunPopulated = await ProcessAnnualBillingPeriodService.go(billRun, billingPeriod, billingAccounts)
} else {
billRunPopulated = await ProcessSupplementaryBillingPeriodService.go(billRun, billingPeriod, billingAccounts)
}

await _finaliseBillRun(billRun, billRunPopulated)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

/**
* Generate a two-part tariff transaction data from the the charge reference and other information passed in
* @module GenerateTransactionService
* @module GenerateTwoPartTariffTransactionService
*/

const { generateUUID } = require('../../../lib/general.lib.js')
const { generateUUID } = require('../../lib/general.lib.js')

/**
* Generate a two-part tariff transaction data from the the charge reference and other information passed in
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict'

/**
* Process the billing accounts for a given billing period and creates their supplementary two-part tariff bills
* @module ProcessBillingPeriodService
*/

/**
* Process the billing accounts for a given billing period and creates their supplementary two-part tariff bills
*
* @param {module:BillRunModel} _billRun - The two-part tariff supplementary 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<boolean>} true if the bill run is not empty (there are transactions to bill) else false
*/
async function go(_billRun, _billingPeriod, _billingAccounts) {
throw Error('Not implemented')
}

module.exports = {
go
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ 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 GenerateTwoPartTariffTransactionService = require('../generate-two-part-tariff-transaction.service.js')
const SendTransactionsService = require('../send-transactions.service.js')
const TransactionModel = require('../../../models/transaction.model.js')

Expand All @@ -21,7 +21,7 @@ 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 {module:BillRunModel} billRun - The two-part tariff annual 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
*
Expand Down Expand Up @@ -227,7 +227,7 @@ function _generateTransactionData(billLicenceId, chargePeriod, chargeVersion) {
const transactions = []

chargeVersion.chargeReferences.forEach((chargeReference) => {
const transaction = GenerateTransactionService.go(
const transaction = GenerateTwoPartTariffTransactionService.go(
billLicenceId,
chargeReference,
chargePeriod,
Expand Down
6 changes: 3 additions & 3 deletions test/controllers/bill-runs.controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const { postRequestOptions } = require('../support/general.js')

// Things we need to stub
const Boom = require('@hapi/boom')
const GenerateBillRunService = require('../../app/services/bill-runs/two-part-tariff/generate-bill-run.service.js')
const GenerateTwoPartTariffBillRunService = require('../../app/services/bill-runs/generate-two-part-tariff-bill-run.service.js')
const IndexBillRunsService = require('../../app/services/bill-runs/index-bill-runs.service.js')
const SubmitCancelBillRunService = require('../../app/services/bill-runs/cancel/submit-cancel-bill-run.service.js')
const SubmitSendBillRunService = require('../../app/services/bill-runs/send/submit-send-bill-run.service.js')
Expand Down Expand Up @@ -276,7 +276,7 @@ describe('Bill Runs controller', () => {

describe('when a request is valid', () => {
beforeEach(() => {
Sinon.stub(GenerateBillRunService, 'go').resolves('97db1a27-8308-4aba-b463-8a6af2558b28')
Sinon.stub(GenerateTwoPartTariffBillRunService, 'go').resolves('97db1a27-8308-4aba-b463-8a6af2558b28')
})

it('redirects to the bill runs page', async () => {
Expand All @@ -291,7 +291,7 @@ describe('Bill Runs controller', () => {
describe('because the generate service threw an error', () => {
beforeEach(async () => {
Sinon.stub(Boom, 'badImplementation').returns(new Boom.Boom('Bang', { statusCode: 500 }))
Sinon.stub(GenerateBillRunService, 'go').rejects()
Sinon.stub(GenerateTwoPartTariffBillRunService, 'go').rejects()
})

it('returns the error page', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,20 @@ const { expect } = Code
// Test helpers
const { setTimeout } = require('timers/promises')

const BillRunError = require('../../../../app/errors/bill-run.error.js')
const ExpandedErrorError = require('../../../../app/errors/expanded.error.js')
const BillRunError = require('../../../app/errors/bill-run.error.js')
const ExpandedErrorError = require('../../../app/errors/expanded.error.js')

// Things we need to stub
const BillRunModel = require('../../../../app/models/bill-run.model.js')
const ChargingModuleGenerateRequest = require('../../../../app/requests/charging-module/generate-bill-run.request.js')
const FetchTwoPartTariffBillingAccountsService = require('../../../../app/services/bill-runs/fetch-two-part-tariff-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')
const BillRunModel = require('../../../app/models/bill-run.model.js')
const ChargingModuleGenerateRequest = require('../../../app/requests/charging-module/generate-bill-run.request.js')
const FetchTwoPartTariffBillingAccountsService = require('../../../app/services/bill-runs/fetch-two-part-tariff-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 ProcessAnnualBillingPeriodService = require('../../../app/services/bill-runs/two-part-tariff/process-billing-period.service.js')
const ProcessSupplementaryBillingPeriodService = require('../../../app/services/bill-runs/tpt-supplementary/process-billing-period.service.js')

// Thing under test
const GenerateBillRunService = require('../../../../app/services/bill-runs/two-part-tariff/generate-bill-run.service.js')
const GenerateTwoPartTariffBillRunService = require('../../../app/services/bill-runs/generate-two-part-tariff-bill-run.service.js')

describe('Bill Runs - Two Part Tariff - Generate Bill Run Service', () => {
// NOTE: introducing a delay in the tests is not ideal. But the service is written such that the generating happens in
Expand All @@ -41,6 +42,8 @@ describe('Bill Runs - Two Part Tariff - Generate Bill Run Service', () => {

let billRunPatchStub
let billRunSelectStub
let processAnnualStub
let processSupplementaryStub
let notifierStub

beforeEach(async () => {
Expand All @@ -53,6 +56,9 @@ describe('Bill Runs - Two Part Tariff - Generate Bill Run Service', () => {
select: billRunSelectStub
})

processAnnualStub = Sinon.stub(ProcessAnnualBillingPeriodService, 'go')
processSupplementaryStub = Sinon.stub(ProcessSupplementaryBillingPeriodService, 'go')

// 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
Expand All @@ -79,21 +85,56 @@ describe('Bill Runs - Two Part Tariff - Generate Bill Run Service', () => {
})

it('throws an error', async () => {
const error = await expect(GenerateBillRunService.go(billRunDetails.id)).to.reject()
const error = await expect(GenerateTwoPartTariffBillRunService.go(billRunDetails.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', () => {
describe('and is a "two part tariff annual"', () => {
beforeEach(async () => {
billRunSelectStub.resolves({ ...billRunDetails })
processAnnualStub.resolves(false)
processSupplementaryStub.resolves(false)
})

it('triggers the "process annual" service', async () => {
await GenerateTwoPartTariffBillRunService.go(billRunDetails.id)

await setTimeout(delay)

expect(processAnnualStub.calledOnce).to.be.true()
expect(processSupplementaryStub.called).to.be.false()
})
})

describe('and is a "two part tariff annual"', () => {
beforeEach(async () => {
billRunSelectStub.resolves({ ...billRunDetails, batchType: 'two_part_supplementary' })
processAnnualStub.resolves(false)
processSupplementaryStub.resolves(false)
})

it('triggers the "process supplementary" service', async () => {
await GenerateTwoPartTariffBillRunService.go(billRunDetails.id)

await setTimeout(delay)

expect(processSupplementaryStub.calledOnce).to.be.true()
expect(processAnnualStub.called).to.be.false()
})
})

describe('but there is nothing to bill', () => {
beforeEach(async () => {
billRunSelectStub.resolves({ ...billRunDetails })
processAnnualStub.resolves(false)
})

it('sets the bill run status first to "processing" and then to "empty"', async () => {
await GenerateBillRunService.go(billRunDetails.id)
await GenerateTwoPartTariffBillRunService.go(billRunDetails.id)

await setTimeout(delay)

Expand All @@ -113,26 +154,26 @@ describe('Bill Runs - Two Part Tariff - Generate Bill Run Service', () => {
chargingModuleGenerateRequestStub = Sinon.stub(ChargingModuleGenerateRequest, 'send')
legacyRefreshBillRunRequestStub = Sinon.stub(LegacyRefreshBillRunRequest, 'send')

Sinon.stub(ProcessBillingPeriodService, 'go').resolves(true)
processAnnualStub.resolves(true)
})

it('sets the bill run status to "processing"', async () => {
await GenerateBillRunService.go(billRunDetails.id)
await GenerateTwoPartTariffBillRunService.go(billRunDetails.id)

expect(billRunPatchStub.calledOnce).to.be.true()
expect(billRunPatchStub.firstCall.firstArg).to.equal({ status: 'processing' }, { skip: ['updatedAt'] })
})

it('tells the charging module API to "generate" the bill run', async () => {
await GenerateBillRunService.go(billRunDetails.id)
await GenerateTwoPartTariffBillRunService.go(billRunDetails.id)

await setTimeout(delay)

expect(chargingModuleGenerateRequestStub.called).to.be.true()
})

it('tells the legacy service to start its refresh job', async () => {
await GenerateBillRunService.go(billRunDetails.id)
await GenerateTwoPartTariffBillRunService.go(billRunDetails.id)

await setTimeout(delay)

Expand Down Expand Up @@ -160,7 +201,7 @@ describe('Bill Runs - Two Part Tariff - Generate Bill Run Service', () => {
})

it('calls HandleErroredBillRunService with appropriate error code', async () => {
await GenerateBillRunService.go(billRunDetails.id)
await GenerateTwoPartTariffBillRunService.go(billRunDetails.id)

await setTimeout(delay)

Expand All @@ -170,7 +211,7 @@ describe('Bill Runs - Two Part Tariff - Generate Bill Run Service', () => {
})

it('logs the error', async () => {
await GenerateBillRunService.go(billRunDetails.id)
await GenerateTwoPartTariffBillRunService.go(billRunDetails.id)

await setTimeout(delay)

Expand All @@ -191,11 +232,11 @@ describe('Bill Runs - Two Part Tariff - Generate Bill Run Service', () => {
thrownError = new BillRunError(new Error(), BillRunModel.errorCodes.failedToPrepareTransactions)

Sinon.stub(FetchTwoPartTariffBillingAccountsService, 'go').resolves([])
Sinon.stub(ProcessBillingPeriodService, 'go').rejects(thrownError)
processAnnualStub.rejects(thrownError)
})

it('calls HandleErroredBillRunService with appropriate error code', async () => {
await GenerateBillRunService.go(billRunDetails.id)
await GenerateTwoPartTariffBillRunService.go(billRunDetails.id)

await setTimeout(delay)

Expand All @@ -205,7 +246,7 @@ describe('Bill Runs - Two Part Tariff - Generate Bill Run Service', () => {
})

it('logs the error', async () => {
await GenerateBillRunService.go(billRunDetails.id)
await GenerateTwoPartTariffBillRunService.go(billRunDetails.id)

await setTimeout(delay)

Expand All @@ -226,12 +267,12 @@ describe('Bill Runs - Two Part Tariff - Generate Bill Run Service', () => {
thrownError = new Error('ERROR')

Sinon.stub(FetchTwoPartTariffBillingAccountsService, 'go').resolves([])
Sinon.stub(ProcessBillingPeriodService, 'go').resolves(true)
processAnnualStub.resolves(true)
Sinon.stub(ChargingModuleGenerateRequest, 'send').rejects(thrownError)
})

it('calls HandleErroredBillRunService with appropriate error code', async () => {
await GenerateBillRunService.go(billRunDetails.id)
await GenerateTwoPartTariffBillRunService.go(billRunDetails.id)

await setTimeout(delay)

Expand All @@ -241,7 +282,7 @@ describe('Bill Runs - Two Part Tariff - Generate Bill Run Service', () => {
})

it('logs the error', async () => {
await GenerateBillRunService.go(billRunDetails.id)
await GenerateTwoPartTariffBillRunService.go(billRunDetails.id)

await setTimeout(delay)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ 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')
const GenerateTwoPartTariffTransactionService = require('../../../app/services/bill-runs/generate-two-part-tariff-transaction.service.js')

describe('Generate Transaction service', () => {
describe('Bill Runs - Generate Two Part Tariff Transaction service', () => {
const billLicenceId = '5e2afb53-ca92-4515-ad71-36a7cefbcebb'

let chargePeriod
Expand All @@ -32,7 +32,7 @@ describe('Generate Transaction service', () => {
describe('when called', () => {
describe('with a charge reference that has volume to be billed', () => {
it('returns a two-part tariff transaction ready to be persisted', () => {
const result = GenerateTransactionService.go(
const result = GenerateTwoPartTariffTransactionService.go(
billLicenceId,
chargeReference,
chargePeriod,
Expand Down Expand Up @@ -90,7 +90,7 @@ describe('Generate Transaction service', () => {
})

it('returns the two-part tariff prefixed description', () => {
const result = GenerateTransactionService.go(
const result = GenerateTwoPartTariffTransactionService.go(
billLicenceId,
chargeReference,
chargePeriod,
Expand All @@ -112,7 +112,7 @@ describe('Generate Transaction service', () => {
})

it('returns null', () => {
const result = GenerateTransactionService.go(
const result = GenerateTwoPartTariffTransactionService.go(
billLicenceId,
chargeReference,
chargePeriod,
Expand Down
Loading

0 comments on commit 189506e

Please sign in to comment.