Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unflag all processed licences not in bill run #221

Merged
merged 7 commits into from
May 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ const GenerateBillingInvoiceService = require('./generate-billing-invoice.servic
const GenerateBillingInvoiceLicenceService = require('./generate-billing-invoice-licence.service.js')
const HandleErroredBillingBatchService = require('./handle-errored-billing-batch.service.js')
const LegacyRequestLib = require('../../lib/legacy-request.lib.js')
const LicenceModel = require('../../models/water/licence.model.js')
const ProcessBillingTransactionsService = require('./process-billing-transactions.service.js')
const UnflagUnbilledLicencesService = require('./unflag-unbilled-licences.service.js')

/**
* Creates the invoices and transactions in both WRLS and the Charging Module API
Expand Down Expand Up @@ -258,10 +258,11 @@ async function _fetchChargeVersions (billingBatch, billingPeriod) {

async function _finaliseBillingBatch (billingBatch, chargeVersions, isEmpty) {
try {
await UnflagUnbilledLicencesService.go(billingBatch.billingBatchId, chargeVersions)

// The bill run is considered empty. We just need to set the status to indicate this in the UI
if (isEmpty) {
await _updateStatus(billingBatch.billingBatchId, 'empty')
await _setSuppBillingFlagsToFalse(chargeVersions)

return
}
Expand Down Expand Up @@ -376,16 +377,6 @@ async function _updateStatus (billingBatchId, status) {
}
}

async function _setSuppBillingFlagsToFalse (chargeVersions) {
const licenceIds = chargeVersions.map((chargeVersion) => {
return chargeVersion.licence.licenceId
})

await LicenceModel.query()
.whereIn('licenceId', licenceIds)
.patch({ includeInSrocSupplementaryBilling: false })
}

module.exports = {
go
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use strict'

/**
* Unflag all licences in a bill run that did not result in a billing invoice (they are unbilled)
* @module UnflagUnbilledLicencesService
*/

const LicenceModel = require('../../models/water/licence.model.js')

/**
* Unflag any licences that were not billed as part of a bill run
*
* Some licences will not result in an invoice (`billing_invoice_licence`) being created. For example, you could add a
* new charge version that does nothing but change the description of the previous one. In isolation, this would result
* in an `EMPTY` bill run. If others are being processed at the same time, it would just mean no records are added to
* the bill run for this licence.
*
* If this is the case, we can remove the 'Include in SROC Supplementary Billing' flag from the licence now. Even if
* the bill run gets cancelled and re-run later the result will be the same.
*
* What it also means is we can be accurate with which licences get unflagged when the bill run is finally **SENT**. In
* the old logic they simply unflag any licence in a region where `date_updated` is less than the bill run's
* created date. But we know this is flawed because a licence can be updated after a bill run is created, for example
* the NALD import process updates them all. If this happens the flag remains on.
*
* The query we run during the **SEND** process unflags only those which are linked to the bill run being sent. We can
* do this because we know this service has handled anything that was unbilled and not represented.
*
* @param {*} billingBatchId The ID of the bill run (billing batch) being processed
* @param {module:ChargeVersionModel[]} chargeVersions All charge versions being processed in the bill run
* @returns {Number} count of records updated
*/
async function go (billingBatchId, chargeVersions) {
const allLicenceIds = _extractUniqueLicenceIds(chargeVersions)

return LicenceModel.query()
.patch({ includeInSrocSupplementaryBilling: false })
.whereIn('licenceId', allLicenceIds)
.whereNotExists(
LicenceModel.relatedQuery('billingInvoiceLicences')
.join('billingInvoices', 'billingInvoices.billingInvoiceId', '=', 'billingInvoiceLicences.billingInvoiceId')
.where('billingInvoices.billingBatchId', '=', billingBatchId)
)
}

function _extractUniqueLicenceIds (chargeVersions) {
const allLicenceIds = chargeVersions.map((chargeVersion) => {
return chargeVersion.licence.licenceId
})

// Creating a new set from allLicenceIds gives us just the unique ids. Objection does not accept sets in
// .findByIds() so we spread it into an array
return [...new Set(allLicenceIds)]
}

module.exports = {
go
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
'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

// Test helpers
const BillingInvoiceHelper = require('../../support/helpers/water/billing-invoice.helper.js')
const BillingInvoiceLicenceHelper = require('../../support/helpers/water/billing-invoice-licence.helper.js')
const LicenceHelper = require('../../support/helpers/water/licence.helper.js')
const LicenceModel = require('../../../app/models/water/licence.model.js')
const DatabaseHelper = require('../../support/helpers/database.helper.js')

// Thing under test
const UnflagUnbilledLicencesService = require('../../../app/services/supplementary-billing/unflag-unbilled-licences.service.js')

describe('Unflag unbilled licences service', () => {
const billingBatchId = '42e7a42b-8a9a-42b4-b527-2baaedf952f2'

beforeEach(async () => {
await DatabaseHelper.clean()
})

describe('when there are licences flagged for SROC supplementary billing', () => {
let chargeVersions
const licences = {
notInBillRun: null,
notBilledInBillRun: null,
billedInBillRun: null
}

beforeEach(async () => {
licences.notInBillRun = await LicenceHelper.add({ includeInSrocSupplementaryBilling: true })
licences.notBilledInBillRun = await LicenceHelper.add({ includeInSrocSupplementaryBilling: true })
licences.billedInBillRun = await LicenceHelper.add({ includeInSrocSupplementaryBilling: true })

chargeVersions = [
{ licence: { licenceId: licences.notBilledInBillRun.licenceId } },
{ licence: { licenceId: licences.billedInBillRun.licenceId } }
]
})

describe('those licences in the current bill run', () => {
describe('which were not billed', () => {
it('are unflagged (include_in_sroc_supplementary_billing set to false)', async () => {
await UnflagUnbilledLicencesService.go(billingBatchId, chargeVersions)

const licenceToBeChecked = await LicenceModel.query().findById(licences.notBilledInBillRun.licenceId)

expect(licenceToBeChecked.includeInSrocSupplementaryBilling).to.be.false()
})
})

describe('which were billed', () => {
beforeEach(async () => {
const { billingInvoiceId } = await BillingInvoiceHelper.add({ billingBatchId })
await BillingInvoiceLicenceHelper.add({ billingInvoiceId, licenceId: licences.billedInBillRun.licenceId })
})

it('are left flagged (include_in_sroc_supplementary_billing still true)', async () => {
await UnflagUnbilledLicencesService.go(billingBatchId, chargeVersions)

const licenceToBeChecked = await LicenceModel.query().findById(licences.billedInBillRun.licenceId)

expect(licenceToBeChecked.includeInSrocSupplementaryBilling).to.be.true()
})
})
})

describe('those licences not in the current bill run', () => {
it('leaves flagged (include_in_sroc_supplementary_billing still true)', async () => {
await UnflagUnbilledLicencesService.go(billingBatchId, chargeVersions)

const licenceToBeChecked = await LicenceModel.query().findById(licences.notInBillRun.licenceId)

expect(licenceToBeChecked.includeInSrocSupplementaryBilling).to.be.true()
})
})
})
})