Skip to content

Commit

Permalink
Add new fetch bill service (#495)
Browse files Browse the repository at this point in the history
https://eaflood.atlassian.net/browse/WATER-4155

We are building our first proper page which will replace the bill summary page the [water-abstraction-ui](https://github.com/DEFRA/water-abstraction-ui) currently provides. Rather than try to show all licences _and_ all their transactions for a bill on one page we'll instead list the licences. Users can then see the transaction details by selecting a licence.

We've already made several other changes to support this. This change adds a new service to fetch the bill and summaries for each licence linked to it.
  • Loading branch information
Cruikshanks authored Nov 3, 2023
1 parent 1e6b0d4 commit 525d5ff
Show file tree
Hide file tree
Showing 2 changed files with 221 additions and 0 deletions.
103 changes: 103 additions & 0 deletions app/services/bills/fetch-bill-service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
'use strict'

/**
* Fetches data needed for the bill page which includes a summary for each licence linked to the bill
* @module FetchBillService
*/

const BillModel = require('../../models/water/bill.model.js')
const { db } = require('../../../db/db.js')

/**
* Fetch the matching Bill plus a summary for each licence linked to it
*
* Was built to provide the data needed for the '/bills/{id}' page
*
* @param {string} id The UUID for the bill to fetch
*
* @returns {Object} the matching instance of BillModel plus a summary (ID, reference, and total net amount) for each
* licence linked to the bill
*/
async function go (id) {
const bill = await _fetchBill(id)
const licenceSummaries = await _fetchLicenceSummaries(id)

return {
bill,
licenceSummaries
}
}

async function _fetchBill (id) {
const result = BillModel.query()
.findById(id)
.select([
'billingInvoiceId',
'creditNoteValue',
'dateCreated',
'invoiceAccountId',
'invoiceNumber',
'invoiceValue',
'isCredit',
'isFlaggedForRebilling',
'netAmount',
'rebillingState',
'isDeMinimis'
])
.withGraphFetched('billRun')
.modifyGraph('billRun', (builder) => {
builder.select([
'billingBatchId',
'batchType',
'dateCreated',
'fromFinancialYearEnding',
'toFinancialYearEnding',
'status',
'billRunNumber',
'transactionFileReference',
'scheme',
'isSummer',
'source'
])
})
.withGraphFetched('billRun.region')
.modifyGraph('billRun.region', (builder) => {
builder.select([
'regionId',
'displayName'
])
})

return result
}

/**
* Query the DB and generate a distinct summarised result for each licence
*
* Licences are linked to a bill (billing_invoice) via bill licences (billing_invoice_licences). But the bill licence
* doesn't contain any data, for example, the total for all transactions for the linked licence.
*
* The page requires us to show the total for each licence which means we need to calculate this by totalling the
* net amount on each linked transaction.
*
* To get the query to return a single line for each licence we need to use DISTINCT. So, due to the complexity of the
* query needed we've had to drop back down to Knex and generate the query ourselves rather than going through
* Objection.js.
*/
async function _fetchLicenceSummaries (id) {
const results = await db
.distinct(['bil.billingInvoiceLicenceId', 'bil.licenceRef'])
.sum('bt.net_amount AS total')
.from('water.billingInvoiceLicences AS bil')
.innerJoin('water.billing_transactions AS bt', 'bil.billingInvoiceLicenceId', 'bt.billingInvoiceLicenceId')
.where('bil.billingInvoiceId', id)
.where('bt.chargeType', 'standard')
.groupBy('bil.billingInvoiceLicenceId', 'bil.licenceRef')
.orderBy('bil.licenceRef')

return results
}

module.exports = {
go
}
118 changes: 118 additions & 0 deletions test/services/bills/fetch-bill.service.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
'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 BillHelper = require('../../support/helpers/water/bill.helper.js')
const BillModel = require('../../../app/models/water/bill.model.js')
const BillRunHelper = require('../../support/helpers/water/bill-run.helper.js')
const BillRunModel = require('../../../app/models/water/bill-run.model.js')
const BillLicenceHelper = require('../../support/helpers/water/bill-licence.helper.js')
const DatabaseHelper = require('../../support/helpers/database.helper.js')
const RegionHelper = require('../../support/helpers/water/region.helper.js')
const RegionModel = require('../../../app/models/water/region.model.js')
const TransactionHelper = require('../../support/helpers/water/transaction.helper.js')

// Thing under test
const FetchBillService = require('../../../app/services/bills/fetch-bill-service.js')

describe('Fetch Bill service', () => {
let linkedBillLicences
let linkedBillRun
let linkedRegion
let testBill
let unlinkedBillLicence

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

linkedRegion = await RegionHelper.add()
linkedBillRun = await BillRunHelper.add({ regionId: linkedRegion.regionId })

testBill = await BillHelper.add({ billingBatchId: linkedBillRun.billingBatchId })

linkedBillLicences = await Promise.all([
BillLicenceHelper.add({ billingInvoiceId: testBill.billingInvoiceId }),
BillLicenceHelper.add({ billingInvoiceId: testBill.billingInvoiceId })
])

await Promise.all([
TransactionHelper.add({ billingInvoiceLicenceId: linkedBillLicences[0].billingInvoiceLicenceId, netAmount: 10 }),
TransactionHelper.add({ billingInvoiceLicenceId: linkedBillLicences[0].billingInvoiceLicenceId, netAmount: 20 }),
TransactionHelper.add({ billingInvoiceLicenceId: linkedBillLicences[1].billingInvoiceLicenceId, netAmount: 30 }),
TransactionHelper.add({ billingInvoiceLicenceId: linkedBillLicences[1].billingInvoiceLicenceId, netAmount: 40 })
])

// Add an unlinked BillLicence and Transaction to demonstrate only linked ones are returned
unlinkedBillLicence = await BillLicenceHelper.add()
await TransactionHelper.add({ billingInvoiceLicenceId: unlinkedBillLicence.billingInvoiceLicenceId, netAmount: 50 })
})

describe('when a bill with a matching ID exists', () => {
it('returns the matching instance of BillModel', async () => {
const { bill: result } = await FetchBillService.go(testBill.billingInvoiceId)

expect(result.billingInvoiceId).to.equal(testBill.billingInvoiceId)
expect(result).to.be.an.instanceOf(BillModel)
})

it('returns the matching bill including the linked bill run', async () => {
const { bill: result } = await FetchBillService.go(testBill.billingInvoiceId)
const { billRun: returnedBillRun } = result

expect(result.billingInvoiceId).to.equal(testBill.billingInvoiceId)
expect(result).to.be.an.instanceOf(BillModel)

expect(returnedBillRun.billingBatchId).to.equal(linkedBillRun.billingBatchId)
expect(returnedBillRun).to.be.an.instanceOf(BillRunModel)
})

it('returns the matching bill including the linked bill run and the region it is linked to', async () => {
const { bill: result } = await FetchBillService.go(testBill.billingInvoiceId)
const { region: returnedRegion } = result.billRun

expect(result.billingInvoiceId).to.equal(testBill.billingInvoiceId)
expect(result).to.be.an.instanceOf(BillModel)

expect(returnedRegion.regionId).to.equal(linkedRegion.regionId)
expect(returnedRegion).to.be.an.instanceOf(RegionModel)
})

it('returns a transaction summary for each licence linked to the bill', async () => {
const { licenceSummaries: result } = await FetchBillService.go(testBill.billingInvoiceId)

expect(result).to.have.length(2)
expect(result).to.include({
billingInvoiceLicenceId: linkedBillLicences[0].billingInvoiceLicenceId,
licenceRef: linkedBillLicences[0].licenceRef,
total: 30
})
expect(result).to.include({
billingInvoiceLicenceId: linkedBillLicences[1].billingInvoiceLicenceId,
licenceRef: linkedBillLicences[1].licenceRef,
total: 70
})

expect(result).not.to.include({
billingInvoiceLicenceId: unlinkedBillLicence.billingInvoiceLicenceId,
licenceRef: unlinkedBillLicence.licenceRef,
total: 50
})
})
})

describe('when a bill with a matching ID does not exist', () => {
it('returns a result with no values set', async () => {
const result = await FetchBillService.go('93112100-152b-4860-abea-2adee11dcd69')

expect(result).to.exist()
expect(result.bill).to.equal(undefined)
expect(result.licenceSummaries).to.equal([])
})
})
})

0 comments on commit 525d5ff

Please sign in to comment.