diff --git a/app/services/supplementary-billing/fetch-previous-billing-transactions.service.js b/app/services/supplementary-billing/fetch-previous-billing-transactions.service.js index b24cb33b28..6e82c8f025 100644 --- a/app/services/supplementary-billing/fetch-previous-billing-transactions.service.js +++ b/app/services/supplementary-billing/fetch-previous-billing-transactions.service.js @@ -1,7 +1,8 @@ 'use strict' /** - * Fetches the previously billed transactions that match the invoice, licence and year provided + * Fetches the previously billed transactions that match the invoice, licence and year provided, removing any debits + * which are cancelled out by previous credits. * @module FetchPreviousBillingTransactionsService */ @@ -14,7 +15,29 @@ async function go (billingInvoice, billingInvoiceLicence, financialYearEnding) { financialYearEnding ) - return billingTransactions + return _cleanse(billingTransactions) +} + +/** + * Cleanse the billing transactions by cancelling out matching pairs of debits and credits, and return the remaining + * debits. We judge a pair of credits and debits to be matching if they have the same number of billable days and the + * same charge type. + */ +function _cleanse (billingTransactions) { + const credits = billingTransactions.filter((transaction) => transaction.isCredit) + const debits = billingTransactions.filter((transaction) => !transaction.isCredit) + + for (const credit of credits) { + const debitIndex = debits.findIndex((debit) => { + return debit.billableDays === credit.billableDays && debit.chargeType === credit.chargeType + }) + + if (debitIndex > -1) { + debits.splice(debitIndex, 1) + } + } + + return debits } async function _fetch (licenceId, invoiceAccountId, financialYearEnding) { @@ -70,14 +93,9 @@ async function _fetch (licenceId, invoiceAccountId, financialYearEnding) { 'bb.status': 'sent', 'bb.scheme': 'sroc' }) - .orderBy('bb.dateCreated', 'desc') - .limit(1) .as('validBillingInvoices'), 'bt.billingInvoiceLicenceId', 'validBillingInvoices.billingInvoiceLicenceId' ) - .where({ - 'bt.isCredit': false - }) } module.exports = { diff --git a/test/services/supplementary-billing/fetch-previous-billing-transactions.service.test.js b/test/services/supplementary-billing/fetch-previous-billing-transactions.service.test.js index 19faeddcc8..bdfcb7e720 100644 --- a/test/services/supplementary-billing/fetch-previous-billing-transactions.service.test.js +++ b/test/services/supplementary-billing/fetch-previous-billing-transactions.service.test.js @@ -41,13 +41,7 @@ describe('Fetch Previous Billing Transactions service', () => { describe('when there is a bill run', () => { describe('for the same licence and invoice account', () => { beforeEach(async () => { - const { billingBatchId } = await BillingBatchHelper.add({ status: 'sent' }) - const { billingInvoiceId } = await BillingInvoiceHelper.add({ invoiceAccountId }, { billingBatchId }) - const { billingInvoiceLicenceId } = await BillingInvoiceLicenceHelper.add( - {}, - { licenceId }, - { billingInvoiceId } - ) + const billingInvoiceLicenceId = await _createBillingBatchInvoiceAndLicence(invoiceAccountId, licenceId) await BillingTransactionHelper.add({ billingInvoiceLicenceId }) }) @@ -63,14 +57,35 @@ describe('Fetch Previous Billing Transactions service', () => { describe('followed by another run which credits the previous', () => { beforeEach(async () => { - const { billingBatchId } = await BillingBatchHelper.add({ status: 'sent' }) - const { billingInvoiceId } = await BillingInvoiceHelper.add({ invoiceAccountId }, { billingBatchId }) - const { billingInvoiceLicenceId } = await BillingInvoiceLicenceHelper.add( - {}, + const billingInvoiceLicenceId = await _createBillingBatchInvoiceAndLicence(invoiceAccountId, licenceId) + await BillingTransactionHelper.add({ billingInvoiceLicenceId, isCredit: true }) + }) + + it('returns no results', async () => { + const result = await FetchPreviousBillingTransactionsService.go( + { invoiceAccountId }, { licenceId }, - { billingInvoiceId } + financialYearEnding ) - await BillingTransactionHelper.add({ billingInvoiceLicenceId, isCredit: true }) + + expect(result).to.be.empty() + }) + }) + + describe('followed by more runs with equal credits and debits', () => { + beforeEach(async () => { + const creditsBillingInvoiceLicenceId = await _createBillingBatchInvoiceAndLicence(invoiceAccountId, licenceId) + await BillingTransactionHelper.add({ + billingInvoiceLicenceId: creditsBillingInvoiceLicenceId, + isCredit: true + }) + await BillingTransactionHelper.add({ + billingInvoiceLicenceId: creditsBillingInvoiceLicenceId, + isCredit: true + }) + + const debitBillingInvoiceLicenceId = await _createBillingBatchInvoiceAndLicence(invoiceAccountId, licenceId) + await BillingTransactionHelper.add({ billingInvoiceLicenceId: debitBillingInvoiceLicenceId }) }) it('returns no results', async () => { @@ -83,17 +98,42 @@ describe('Fetch Previous Billing Transactions service', () => { expect(result).to.be.empty() }) }) + + describe('followed by more runs with unequal credits and debits', () => { + beforeEach(async () => { + const unmatchedBillingInvoiceLicenceId = await _createBillingBatchInvoiceAndLicence( + invoiceAccountId, + licenceId + ) + await BillingTransactionHelper.add({ + billingInvoiceLicenceId: unmatchedBillingInvoiceLicenceId, + billableDays: 30, + isCredit: true + }) + + const matchedBillingInvoiceLicenceId = await _createBillingBatchInvoiceAndLicence(invoiceAccountId, licenceId) + await BillingTransactionHelper.add({ + billingInvoiceLicenceId: matchedBillingInvoiceLicenceId, + isCredit: true + }) + await BillingTransactionHelper.add({ billingInvoiceLicenceId: matchedBillingInvoiceLicenceId }) + }) + + it('returns results', async () => { + const result = await FetchPreviousBillingTransactionsService.go( + { invoiceAccountId }, + { licenceId }, + financialYearEnding + ) + + expect(result).to.have.length(1) + }) + }) }) describe('but for a different licence', () => { beforeEach(async () => { - const { billingBatchId } = await BillingBatchHelper.add({ status: 'sent' }) - const { billingInvoiceId } = await BillingInvoiceHelper.add({ invoiceAccountId }, { billingBatchId }) - const { billingInvoiceLicenceId } = await BillingInvoiceLicenceHelper.add( - {}, - { licenceId: '66498337-e6a6-4a2a-9fb7-e39f43410f80' }, - { billingInvoiceId } - ) + const billingInvoiceLicenceId = await _createBillingBatchInvoiceAndLicence(invoiceAccountId, '66498337-e6a6-4a2a-9fb7-e39f43410f80') await BillingTransactionHelper.add({ billingInvoiceLicenceId }) }) @@ -110,15 +150,9 @@ describe('Fetch Previous Billing Transactions service', () => { describe('but for a different invoice account', () => { beforeEach(async () => { - const { billingBatchId } = await BillingBatchHelper.add({ status: 'sent' }) - const { billingInvoiceId } = await BillingInvoiceHelper.add( - { invoiceAccountId: 'b0b75e7a-e80a-4c28-9ac9-33b3a850722b' }, - { billingBatchId } - ) - const { billingInvoiceLicenceId } = await BillingInvoiceLicenceHelper.add( - {}, - { licenceId }, - { billingInvoiceId } + const billingInvoiceLicenceId = await _createBillingBatchInvoiceAndLicence( + 'b0b75e7a-e80a-4c28-9ac9-33b3a850722b', + licenceId ) await BillingTransactionHelper.add({ billingInvoiceLicenceId }) }) @@ -135,3 +169,15 @@ describe('Fetch Previous Billing Transactions service', () => { }) }) }) + +async function _createBillingBatchInvoiceAndLicence (invoiceAccountId, licenceId) { + const { billingBatchId } = await BillingBatchHelper.add({ status: 'sent' }) + const { billingInvoiceId } = await BillingInvoiceHelper.add({ invoiceAccountId }, { billingBatchId }) + const { billingInvoiceLicenceId } = await BillingInvoiceLicenceHelper.add( + {}, + { licenceId }, + { billingInvoiceId } + ) + + return billingInvoiceLicenceId +}