Skip to content

Commit

Permalink
Create error bill run when CM fails (#104)
Browse files Browse the repository at this point in the history
https://eaflood.atlassian.net/browse/WATER-3833

When we currently initiate a new bill run, a fail response from the Charging Module means we throw an error and no bill run is created. We want to revise this so that it _does_ create a new bill run but with the status `error` and with `errorCode` set to `50`.

As part of this we update `BillingBatchModel` with a static getter `errorCodes`, which allows us to consistently use the correct error codes by referring to (for example) `BillingBatchModel.errorCodes.failedToCreateBillRun`.
  • Loading branch information
StuAA78 authored Jan 31, 2023
1 parent acb4fb9 commit 57c8373
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 56 deletions.
14 changes: 14 additions & 0 deletions app/models/water/billing-batch.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@ class BillingBatchModel extends WaterBaseModel {
}
}
}

static get errorCodes () {
return {
failedToPopulateChargeVersions: 10,
failedToProcessChargeVersions: 20,
failedToPrepareTransactions: 30,
failedToCreateCharge: 40,
failedToCreateBillRun: 50,
failedToDeleteInvoice: 60,
failedToProcessTwoPartTariff: 70,
failedToGetChargeModuleBillRunSummary: 80,
failedToProcessRebilling: 90
}
}
}

module.exports = BillingBatchModel
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ function go (billingBatch) {
scheme,
batchType,
status,
externalId
externalId,
errorCode
} = billingBatch

return {
Expand All @@ -21,7 +22,8 @@ function go (billingBatch) {
scheme,
batchType,
status,
externalId
externalId,
errorCode
}
}

Expand Down
38 changes: 28 additions & 10 deletions app/services/supplementary-billing/create-billing-batch.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,49 @@ const BillingBatchModel = require('../../models/water/billing-batch.model.js')
*
* @param {Object} regionId The regionId for the selected region
* @param {Object} billingPeriod The billing period in the format { startDate: 2022-04-01, endDate: 2023-03-31 }
* @param {string} [batchType=supplementary] The type of billing batch to create. Defaults to 'supplementary'
* @param {string} [scheme=sroc] The applicable charging scheme. Defaults to 'sroc'
* @param {string} [source=wrls] Where the billing batch originated from. Records imported from NALD have the source 'nald'. Those created in the service use 'wrls'. Defaults to 'wrls'
* @param {string} [externalId=null] The id of the bill run as created in the Charging Module
* @param {Object} options Optional params to be overridden
* @param {string} [options.batchType=supplementary] The type of billing batch to create. Defaults to 'supplementary'
* @param {string} [options.scheme=sroc] The applicable charging scheme. Defaults to 'sroc'
* @param {string} [options.source=wrls] Where the billing batch originated from. Records imported from NALD have the
* source 'nald'. Those created in the service use 'wrls'. Defaults to 'wrls'
* @param {string} [options.externalId=null] The id of the bill run as created in the Charging Module
* @param {string} [options.status=queued] The status that the bill run should be created with
* @param {number} [options.errorCode=null] Numeric error code
*
* @returns {module:BillingBatchModel} The newly created billing batch instance with the `.region` property populated
*/
async function go (regionId, billingPeriod, batchType = 'supplementary', scheme = 'sroc', source = 'wrls', externalId = null) {
async function go (regionId, billingPeriod, options) {
const optionsData = optionsDefaults(options)

const billingBatch = await BillingBatchModel.query()
.insert({
regionId,
batchType,
fromFinancialYearEnding: billingPeriod.endDate.getFullYear(),
toFinancialYearEnding: billingPeriod.endDate.getFullYear(),
status: 'queued',
scheme,
source,
externalId
...optionsData
})
.returning('*')
.withGraphFetched('region')

return billingBatch
}

function optionsDefaults (data) {
const defaults = {
batchType: 'supplementary',
scheme: 'sroc',
source: 'wrls',
externalId: null,
status: 'queued',
errorCode: null
}

return {
...defaults,
...data
}
}

module.exports = {
go
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* @module InitiateBillingBatchService
*/

const BillingBatchModel = require('../../models/water/billing-batch.model.js')
const BillingPeriodService = require('./billing-period.service.js')
const ChargingModuleCreateBillRunService = require('../charging-module/create-bill-run.service.js')
const CheckLiveBillRunService = require('./check-live-bill-run.service.js')
Expand All @@ -15,8 +16,8 @@ const CreateBillingBatchEventService = require('./create-billing-batch-event.ser
/**
* Initiate a new billing batch
*
* Initiating a new billing batch means creating both the `billing_batch` and `event` record with the appropriate data.
* In the future it will also encompass creating the bill run record in the SROC Charging Module API.
* 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
*
Expand All @@ -38,24 +39,27 @@ async function go (billRunRequestData) {

const chargingModuleBillRun = await ChargingModuleCreateBillRunService.go(region, 'sroc')

// A failed response will be due to a failed `RequestLib` request so format an error message accordingly and throw it
if (!chargingModuleBillRun.succeeded) {
// We always get the status code and error
const errorHead = `${chargingModuleBillRun.response.statusCode} ${chargingModuleBillRun.response.error}`
const billingBatchOptions = _billingBatchOptions(type, scheme, chargingModuleBillRun)
const billingBatch = await CreateBillingBatchService.go(region, billingPeriod, billingBatchOptions)

// We should always get an additional error messge but if not then simply throw the status code and error
if (!chargingModuleBillRun.response.message) {
throw Error(errorHead)
}
await CreateBillingBatchEventService.go(billingBatch, user)

throw Error(errorHead + ` - ${chargingModuleBillRun.response.message}`)
}
return _response(billingBatch)
}

const billingBatch = await CreateBillingBatchService.go(region, billingPeriod, type, scheme, undefined, chargingModuleBillRun.response.id)
function _billingBatchOptions (type, scheme, chargingModuleBillRun) {
const options = {
scheme,
batchType: type,
externalId: chargingModuleBillRun.response.id
}

await CreateBillingBatchEventService.go(billingBatch, user)
if (!chargingModuleBillRun.succeeded) {
options.status = 'error'
options.errorCode = BillingBatchModel.errorCodes.failedToCreateBillRun
}

return _response(billingBatch)
return options
}

function _response (billingBatch) {
Expand Down
21 changes: 21 additions & 0 deletions db/migrations/20230131152030_alter-water-billing-batches.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use strict'

const tableName = 'billing_batches'

exports.up = async function (knex) {
await knex
.schema
.withSchema('water')
.alterTable(tableName, table => {
table.integer('error_code')
})
}

exports.down = async function (knex) {
await knex
.schema
.withSchema('water')
.alterTable(tableName, table => {
table.dropColumn('error_code')
})
}
10 changes: 10 additions & 0 deletions test/models/water/billing-batch.model.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,14 @@ describe('Billing Batch model', () => {
})
})
})

describe('Static getters', () => {
describe('Error codes', () => {
it('returns the requested error code', async () => {
const result = BillingBatchModel.errorCodes.failedToCreateBillRun

expect(result).to.equal(50)
})
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ describe('Create Billing Batch presenter', () => {
regionId: '6a472535-145c-4170-ab59-f555783fa6e7',
scheme: 'sroc',
status: 'processing',
externalId: '2bbbe459-966e-4026-b5d2-2f10867bdddd'
externalId: '2bbbe459-966e-4026-b5d2-2f10867bdddd',
errorCode: 123
}
})

Expand All @@ -32,6 +33,7 @@ describe('Create Billing Batch presenter', () => {
expect(result.scheme).to.equal(data.scheme)
expect(result.status).to.equal(data.status)
expect(result.externalId).to.equal(data.externalId)
expect(result.errorCode).to.equal(data.errorCode)
})
})

Expand All @@ -42,7 +44,8 @@ describe('Create Billing Batch presenter', () => {
regionId: null,
scheme: null,
status: null,
externalId: null
externalId: null,
errorCode: null
}
})

Expand All @@ -54,6 +57,7 @@ describe('Create Billing Batch presenter', () => {
expect(result.scheme).to.be.null()
expect(result.status).to.be.null()
expect(result.externalId).to.be.null()
expect(result.errorCode).to.be.null()
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -32,37 +32,63 @@ describe('Create Billing Batch service', () => {

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

expect(result.status).to.equal('queued')
expect(result.fromFinancialYearEnding).to.equal(2023)
expect(result.toFinancialYearEnding).to.equal(2023)
expect(result.batchType).to.equal('supplementary')
expect(result.scheme).to.equal('sroc')
expect(result.source).to.equal('wrls')
expect(result.externalId).to.be.null()
expect(result.status).to.equal('queued')
expect(result.errorCode).to.be.null()

expect(result.region).to.be.an.instanceOf(RegionModel)
expect(result.region.regionId).to.equal(region.regionId)
})
})

describe('when the defaults are overridden', () => {
describe('when all defaults are overridden', () => {
const batchType = 'annual'
const scheme = 'wrls'
const source = 'nald'
const externalId = '2bbbe459-966e-4026-b5d2-2f10867bdddd'
const status = 'error'
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)
const result = await CreateBillingBatchService.go(region.regionId, billingPeriod, { batchType, scheme, source, externalId, status, errorCode })

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

expect(result.status).to.equal('queued')
expect(result.fromFinancialYearEnding).to.equal(2023)
expect(result.toFinancialYearEnding).to.equal(2023)
expect(result.batchType).to.equal(batchType)
expect(result.scheme).to.equal(scheme)
expect(result.source).to.equal(source)
expect(result.externalId).to.equal(externalId)
expect(result.status).to.equal('error')
expect(result.errorCode).to.equal(errorCode)

expect(result.region).to.be.an.instanceOf(RegionModel)
expect(result.region.regionId).to.equal(region.regionId)
})
})

describe('when some defaults are overridden', () => {
const externalId = '2bbbe459-966e-4026-b5d2-2f10867bdddd'
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 })

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

expect(result.fromFinancialYearEnding).to.equal(2023)
expect(result.toFinancialYearEnding).to.equal(2023)
expect(result.batchType).to.equal('supplementary')
expect(result.scheme).to.equal('sroc')
expect(result.source).to.equal('wrls')
expect(result.externalId).to.equal(externalId)
expect(result.status).to.equal('error')

expect(result.region).to.be.an.instanceOf(RegionModel)
expect(result.region.regionId).to.equal(region.regionId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ describe('Initiate Billing Batch service', () => {
expect(result.scheme).to.equal('sroc')
expect(result.batchType).to.equal('supplementary')
expect(result.status).to.equal('queued')
expect(result.errorCode).to.equal(null)
})
})

Expand All @@ -101,11 +102,17 @@ describe('Initiate Billing Batch service', () => {
})
})

it('rejects with an appropriate error', async () => {
const err = await expect(InitiateBillingBatchService.go(validatedRequestData)).to.reject()
it('creates a bill run with `error` status and error code 50', async () => {
const result = await InitiateBillingBatchService.go(validatedRequestData)

expect(err).to.be.an.error()
expect(err.message).to.equal("403 Forbidden - Unauthorised for regime 'wrls'")
const billingBatch = await BillingBatchModel.query().first()

expect(result.id).to.equal(billingBatch.billingBatchId)
expect(result.region).to.equal(billingBatch.regionId)
expect(result.scheme).to.equal('sroc')
expect(result.batchType).to.equal('supplementary')
expect(result.status).to.equal('error')
expect(result.errorCode).to.equal(50)
})
})

Expand All @@ -121,24 +128,5 @@ describe('Initiate Billing Batch service', () => {
expect(err.message).to.equal(`Batch already live for region ${validatedRequestData.region}`)
})
})

describe('and the error doesn\'t include a message', () => {
beforeEach(() => {
Sinon.stub(ChargingModuleCreateBillRunService, 'go').resolves({
succeeded: false,
response: {
statusCode: 403,
error: 'Forbidden'
}
})
})

it('rejects with an appropriate error', async () => {
const err = await expect(InitiateBillingBatchService.go(validatedRequestData)).to.reject()

expect(err).to.be.an.error()
expect(err.message).to.equal('403 Forbidden')
})
})
})
})

0 comments on commit 57c8373

Please sign in to comment.