From 09ed639191004168ea8e59e511995963c2538d47 Mon Sep 17 00:00:00 2001 From: Alan Cruikshanks Date: Sun, 24 Mar 2024 21:29:57 +0000 Subject: [PATCH] Move transaction matching logic to GeneralLib (#855) https://eaflood.atlassian.net/browse/WATER-4403 We are currently working on updating the supplementary billing engine to incorporate improvements we made to the process when implementing annual billing. One of the things we've been reminded of is that we have multiple services that use the same logic to determine if the charging information for 2 transactions is the same. It's a bit of a scary matching function which we flagged as a TODO to bring into a single place. Now seems a good time to do just that and help simplify the supplementary process. --- app/lib/general.lib.js | 58 +++- .../fetch-previous-transactions.service.js | 52 +-- .../process-transactions.service.js | 40 +-- test/lib/general.lib.test.js | 184 +++++++++++ ...etch-previous-transactions.service.test.js | 271 +--------------- .../process-transactions.service.test.js | 307 +----------------- 6 files changed, 262 insertions(+), 650 deletions(-) diff --git a/app/lib/general.lib.js b/app/lib/general.lib.js index c573002228..ab6351d644 100644 --- a/app/lib/general.lib.js +++ b/app/lib/general.lib.js @@ -180,11 +180,67 @@ function timestampForPostgres () { return new Date().toISOString() } +/** + * Compare key properties of 2 transactions and determine if they are a 'match' + * + * We compare those properties which determine the charge value calculated by the charging module. If the properties + * are the same we return true. Else we return false. + * + * This is used in the billing engines to determine 2 transactions within the same bill, often a debit and a credit, + * and whether they match. If they do we don't send either to the charge module or include them in the bill as they + * 'cancel' each other out. + * + * The key properties are charge type, category code, and billable days. But we also need to compare agreements and + * additional charges because if those have changed, we need to credit the previous transaction and calculate the + * new debit value. + * + * Because what we are checking does not match up to what you see in the UI we have this reference + * + * - Abatement agreement - section126Factor + * - Two-part tariff agreement - section127Agreement + * - Canal and River Trust agreement - section130Agreement + * - Aggregate - aggregateFactor + * - Charge Adjustment - adjustmentFactor + * - Winter discount - winterOnly + * + * - Additional charges - supportedSource + * - Additional charges - supportedSourceName + * - Additional charges - waterCompanyCharge + * + * @param {Object} left - First transaction to match + * @param {Object} right - Second transaction to match + * + * @returns {boolean} true if a match else false + */ +function transactionsMatch (left, right) { + // When we put together this matching logic our instincts were to try and do something 'better' than this long, + // chained `&&` statement. But whatever we came up with was + // + // - more complex + // - less performant + // + // We also believe this makes it easy to see what properties are being compared. Plus the moment something doesn't + // match we bail. So, much as it feels 'wrong', we are sticking with it! + return left.chargeType === right.chargeType && + left.chargeCategoryCode === right.chargeCategoryCode && + left.billableDays === right.billableDays && + left.section126Factor === right.section126Factor && + left.section127Agreement === right.section127Agreement && + left.section130Agreement === right.section130Agreement && + left.aggregateFactor === right.aggregateFactor && + left.adjustmentFactor === right.adjustmentFactor && + left.winterOnly === right.winterOnly && + left.supportedSource === right.supportedSource && + left.supportedSourceName === right.supportedSourceName && + left.waterCompanyCharge === right.waterCompanyCharge +} + module.exports = { calculateAndLogTimeTaken, currentTimeInNanoseconds, determineCurrentFinancialYear, generateUUID, periodsOverlap, - timestampForPostgres + timestampForPostgres, + transactionsMatch } diff --git a/app/services/bill-runs/supplementary/fetch-previous-transactions.service.js b/app/services/bill-runs/supplementary/fetch-previous-transactions.service.js index e46a4f3c00..73d138fffa 100644 --- a/app/services/bill-runs/supplementary/fetch-previous-transactions.service.js +++ b/app/services/bill-runs/supplementary/fetch-previous-transactions.service.js @@ -7,6 +7,7 @@ */ const { db } = require('../../../../db/db.js') +const { transactionsMatch } = require('../../../lib/general.lib.js') /** * Fetches the previously billed transactions that match the bill, licence and year provided, removing any debits @@ -42,7 +43,7 @@ function _cleanse (transactions) { credits.forEach((credit) => { const debitIndex = debits.findIndex((debit) => { - return _matchTransactions(debit, credit) + return transactionsMatch(debit, credit) }) if (debitIndex > -1) { @@ -53,55 +54,6 @@ function _cleanse (transactions) { return debits } -/** - * Compares a debit transaction to a credit transaction - * - * We compare those properties which determine the charge value calculated by the charging module. If the debit - * transaction's properties matches the credit's we return true. This will tell the calling method - * to remove the debit from the service's final results. - * - * The key properties are charge type, category code, and billable days. But we also need to compare agreements and - * additional charges because if those have changed, we'll need to credit the previous transaction and calculate the - * new debit value. Because what we are checking does not match up to what you see in the UI we have this reference - * - * - Abatement agreement - section126Factor - * - Two-part tariff agreement - section127Agreement - * - Canal and River Trust agreement - section130Agreement - * - Aggregate - aggregateFactor - * - Charge Adjustment - adjustmentFactor - * - Winter discount - winterOnly - * - * - Additional charges - supportedSource - * - Additional charges - supportedSourceName - * - Additional charges - waterCompanyCharge - */ -function _matchTransactions (debit, credit) { - // TODO: This logic is a duplicate of what we are doing in - // app/services/supplementary-billing/process-transactions.service.js. This also means we are running the - // same kind of unit tests on 2 places. We need to refactor this duplication in the code and the tests out. - - // When we put together this matching logic our instincts was to try and do something 'better' than this long, - // chained && statement. But whatever we came up with was - // - // - more complex - // - less performant - // - // We found this easy to see what properties are being compared. Plus the moment something doesn't match we bail. So, - // much as it feels 'wrong', we are sticking with it! - return debit.chargeType === credit.chargeType && - debit.chargeCategoryCode === credit.chargeCategoryCode && - debit.billableDays === credit.billableDays && - debit.section126Factor === credit.section126Factor && - debit.section127Agreement === credit.section127Agreement && - debit.section130Agreement === credit.section130Agreement && - debit.aggregateFactor === credit.aggregateFactor && - debit.adjustmentFactor === credit.adjustmentFactor && - debit.winterOnly === credit.winterOnly && - debit.supportedSource === credit.supportedSource && - debit.supportedSourceName === credit.supportedSourceName && - debit.waterCompanyCharge === credit.waterCompanyCharge -} - async function _fetch (licenceId, billingAccountId, financialYearEnding) { return db .select( diff --git a/app/services/bill-runs/supplementary/process-transactions.service.js b/app/services/bill-runs/supplementary/process-transactions.service.js index f8f8a04222..203653ac69 100644 --- a/app/services/bill-runs/supplementary/process-transactions.service.js +++ b/app/services/bill-runs/supplementary/process-transactions.service.js @@ -8,6 +8,7 @@ */ const FetchPreviousTransactionsService = require('./fetch-previous-transactions.service.js') +const { transactionsMatch } = require('../../../lib/general.lib.js') const ReverseTransactionsService = require('./reverse-transactions.service.js') /** @@ -44,50 +45,13 @@ async function go (calculatedTransactions, bill, billLicence, billingPeriod) { * Takes a single calculated debit transaction and checks to see if the provided array of reversed (credit) transactions * contains a transaction that will cancel it out, returning `true` or `false` to indicate if it does or doesn't. * - * We compare those properties which determine the charge value calculated by the charging module. If the calculated - * transaction's properties matches one in reversedTransactions we return true. This will tell the calling method - * to not include the calculated transaction in the bill run. We also remove the matched transaction from - * reversedTransactions. - * - * The key properties are charge type, category code, and billable days. But we also need to compare agreements and - * additional charges because if those have changed, we'll need to credit the previous transaction and calculate the - * new debit value. Because what we are checking does not match up to what you see in the UI we have this reference - * - * - Abatement agreement - section126Factor - * - Two-part tariff agreement - section127Agreement - * - Canal and River Trust agreement - section130Agreement - * - Aggregate - aggregateFactor - * - Charge Adjustment - adjustmentFactor - * - Winter discount - winterOnly - * - * - Additional charges - supportedSource - * - Additional charges - supportedSourceName - * - Additional charges - waterCompanyCharge - * * NOTE: This function will mutate the provided array of reversed transactions if one of the transactions in it will * cancel the calculated transaction; in this case, we remove the reversed transaction from the array as it can only * cancel one calculated transaction. */ function _cancelCalculatedTransaction (calculatedTransaction, reversedTransactions) { - // When we put together this matching logic our instincts were to try and do something 'better' than this long, - // chained && statement. But whatever we came up with was - // - more complex - // - less performant - // We found this easy to see what properties are being compared. Plus the moment something doesn't match we bail. So, - // much as it feels 'wrong', we are sticking with it! const result = reversedTransactions.findIndex((reversedTransaction) => { - return reversedTransaction.chargeType === calculatedTransaction.chargeType && - reversedTransaction.chargeCategoryCode === calculatedTransaction.chargeCategoryCode && - reversedTransaction.billableDays === calculatedTransaction.billableDays && - reversedTransaction.section126Factor === calculatedTransaction.section126Factor && - reversedTransaction.section127Agreement === calculatedTransaction.section127Agreement && - reversedTransaction.section130Agreement === calculatedTransaction.section130Agreement && - reversedTransaction.aggregateFactor === calculatedTransaction.aggregateFactor && - reversedTransaction.adjustmentFactor === calculatedTransaction.adjustmentFactor && - reversedTransaction.winterOnly === calculatedTransaction.winterOnly && - reversedTransaction.supportedSource === calculatedTransaction.supportedSource && - reversedTransaction.supportedSourceName === calculatedTransaction.supportedSourceName && - reversedTransaction.waterCompanyCharge === calculatedTransaction.waterCompanyCharge + return transactionsMatch(reversedTransaction, calculatedTransaction) }) if (result === -1) { diff --git a/test/lib/general.lib.test.js b/test/lib/general.lib.test.js index 4819eaa1e2..3dcb22b7f7 100644 --- a/test/lib/general.lib.test.js +++ b/test/lib/general.lib.test.js @@ -8,6 +8,9 @@ const Sinon = require('sinon') const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() const { expect } = Code +// Test helpers +const TransactionHelper = require('../support/helpers/transaction.helper.js') + // Thing under test const GeneralLib = require('../../app/lib/general.lib.js') @@ -277,4 +280,185 @@ describe('GeneralLib', () => { expect(result).to.equal('2015-10-21T20:31:57.000Z') }) }) + + describe('#transactionsMatch', () => { + let leftTransaction + let rightTransaction + + beforeEach(() => { + // NOTE: The properties the function is comparing are; chargeType, chargeCategoryCode, billableDays, + // section126Factor, section127Agreement, section130Agreement, aggregateFactor, adjustmentFactor, winterOnly, + // supportedSource, supportedSourceName, waterCompanyCharge. + // + // We add IDs just so we can tell them apart! + leftTransaction = { + ...TransactionHelper.defaults(), + id: 'cba29373-d9a2-423e-8f36-83c13b07d925', + adjustmentFactor: 1, + aggregateFactor: 1, + chargeCategoryCode: '4.3.2', + section126Factor: 1, + section127Agreement: false, + supportedSource: false, + supportedSourceName: 'Severn', + waterCompanyCharge: false, + winterOnly: false + } + rightTransaction = { ...leftTransaction, id: '164eb779-4d2d-4578-bfbb-f07347e68171' } + }) + + describe('when the transactions match', () => { + it('returns true', () => { + const result = GeneralLib.transactionsMatch(leftTransaction, rightTransaction) + + expect(result).to.be.true() + }) + }) + + describe('when the transactions do not match', () => { + describe('because the abatement agreement (section 126) is different', () => { + beforeEach(() => { + rightTransaction.section126Factor = 0.5 + }) + + it('returns false', () => { + const result = GeneralLib.transactionsMatch(leftTransaction, rightTransaction) + + expect(result).to.be.false() + }) + }) + + describe('because the aggregate is different', () => { + beforeEach(() => { + rightTransaction.aggregateFactor = 0.5 + }) + + it('returns false', () => { + const result = GeneralLib.transactionsMatch(leftTransaction, rightTransaction) + + expect(result).to.be.false() + }) + }) + + describe('because the billable days are different', () => { + beforeEach(() => { + rightTransaction.billableDays = 10 + }) + + it('returns false', () => { + const result = GeneralLib.transactionsMatch(leftTransaction, rightTransaction) + + expect(result).to.be.false() + }) + }) + + describe('because the canal and river trust agreement (section 130) is different', () => { + beforeEach(() => { + rightTransaction.section130Agreement = true + }) + + it('returns false', () => { + const result = GeneralLib.transactionsMatch(leftTransaction, rightTransaction) + + expect(result).to.be.false() + }) + }) + + describe('because the charge adjustment is different', () => { + beforeEach(() => { + rightTransaction.adjustmentFactor = 0.5 + }) + + it('returns false', () => { + const result = GeneralLib.transactionsMatch(leftTransaction, rightTransaction) + + expect(result).to.be.false() + }) + }) + + describe('because the charge category code is different', () => { + beforeEach(() => { + rightTransaction.chargeCategoryCode = '4.3.3' + }) + + it('returns false', () => { + const result = GeneralLib.transactionsMatch(leftTransaction, rightTransaction) + + expect(result).to.be.false() + }) + }) + + describe('because the charge type is different', () => { + beforeEach(() => { + rightTransaction.chargeType = 'compensation' + }) + + it('returns false', () => { + const result = GeneralLib.transactionsMatch(leftTransaction, rightTransaction) + + expect(result).to.be.false() + }) + }) + + describe('because the supported source differs (additional charge) is different', () => { + beforeEach(() => { + rightTransaction.supportedSource = true + }) + + it('returns false', () => { + const result = GeneralLib.transactionsMatch(leftTransaction, rightTransaction) + + expect(result).to.be.false() + }) + }) + + describe('because the supported source name differs (additional charge) is different', () => { + beforeEach(() => { + rightTransaction.supportedSourceName = 'source name' + }) + + it('returns false', () => { + const result = GeneralLib.transactionsMatch(leftTransaction, rightTransaction) + + expect(result).to.be.false() + }) + }) + + describe('because the two-part tariff agreement (section 127) is different', () => { + beforeEach(() => { + rightTransaction.section127Agreement = true + }) + + it('returns false', () => { + const result = GeneralLib.transactionsMatch(leftTransaction, rightTransaction) + + expect(result).to.be.false() + }) + }) + + describe('because the water company flag differs (additional charge) is different', () => { + beforeEach(() => { + rightTransaction.waterCompanyCharge = true + }) + + it('returns false', () => { + const result = GeneralLib.transactionsMatch(leftTransaction, rightTransaction) + + expect(result).to.be.false() + }) + }) + + describe('because the winter discount is different', () => { + beforeEach(() => { + rightTransaction.adjustmentFactor = true + }) + + it('returns false', () => { + const result = GeneralLib.transactionsMatch(leftTransaction, rightTransaction) + + expect(result).to.be.false() + }) + }) + }) + }) }) diff --git a/test/services/bill-runs/supplementary/fetch-previous-transactions.service.test.js b/test/services/bill-runs/supplementary/fetch-previous-transactions.service.test.js index eff20e0cf0..2d90261599 100644 --- a/test/services/bill-runs/supplementary/fetch-previous-transactions.service.test.js +++ b/test/services/bill-runs/supplementary/fetch-previous-transactions.service.test.js @@ -92,267 +92,24 @@ describe('Fetch Previous Transactions service', () => { }) describe("that does not match the first bill run's debit", () => { - describe('because the billable days are different', () => { - beforeEach(async () => { - await TransactionHelper.add({ - billLicenceId: followUpBillLicenceId, - billableDays: 30, - chargeCategoryCode, - credit: true - }) - }) - - it('returns the debits', async () => { - const results = await FetchPreviousTransactionsService.go( - { billingAccountId }, - { licenceId }, - financialYearEnding - ) - - expect(results).to.have.length(1) - expect(results[0].credit).to.be.false() - }) - }) - - describe('because the charge type is different', () => { - beforeEach(async () => { - await TransactionHelper.add({ - billLicenceId: followUpBillLicenceId, - chargeCategoryCode, - chargeType: 'compensation', - credit: true - }) - }) - - it('returns the debits', async () => { - const results = await FetchPreviousTransactionsService.go( - { billingAccountId }, - { licenceId }, - financialYearEnding - ) - - expect(results).to.have.length(1) - expect(results[0].credit).to.be.false() - }) - }) - - describe('because the charge category code is different', () => { - beforeEach(async () => { - await TransactionHelper.add({ - billLicenceId: followUpBillLicenceId, - chargeCategoryCode: '4.3.2', - credit: true - }) - }) - - it('returns the debits', async () => { - const results = await FetchPreviousTransactionsService.go( - { billingAccountId }, - { licenceId }, - financialYearEnding - ) - - expect(results).to.have.length(1) - expect(results[0].credit).to.be.false() - }) - }) - - describe('because the abatement agreement (section 126) is different', () => { - beforeEach(async () => { - await TransactionHelper.add({ - billLicenceId: followUpBillLicenceId, - chargeCategoryCode, - credit: true, - section126Factor: 0.5 - }) - }) - - it('returns the debits', async () => { - const results = await FetchPreviousTransactionsService.go( - { billingAccountId }, - { licenceId }, - financialYearEnding - ) - - expect(results).to.have.length(1) - expect(results[0].credit).to.be.false() - }) - }) - - describe('because the two-part tariff agreement (section 127) is different', () => { - beforeEach(async () => { - await TransactionHelper.add({ - billLicenceId: followUpBillLicenceId, - chargeCategoryCode, - credit: true, - section127Agreement: true - }) - }) - - it('returns the debits', async () => { - const results = await FetchPreviousTransactionsService.go( - { billingAccountId }, - { licenceId }, - financialYearEnding - ) - - expect(results).to.have.length(1) - expect(results[0].credit).to.be.false() - }) - }) - - describe('because the canal and river trust agreement (section 130) is different', () => { - beforeEach(async () => { - await TransactionHelper.add({ - billLicenceId: followUpBillLicenceId, - chargeCategoryCode, - credit: true, - section130Agreement: true - }) - }) - - it('returns the debits', async () => { - const results = await FetchPreviousTransactionsService.go( - { billingAccountId }, - { licenceId }, - financialYearEnding - ) - - expect(results).to.have.length(1) - expect(results[0].credit).to.be.false() - }) - }) - - describe('because the aggregate is different', () => { - beforeEach(async () => { - await TransactionHelper.add({ - billLicenceId: followUpBillLicenceId, - aggregateFactor: 0.5, - chargeCategoryCode, - credit: true - }) - }) - - it('returns the debits', async () => { - const results = await FetchPreviousTransactionsService.go( - { billingAccountId }, - { licenceId }, - financialYearEnding - ) - - expect(results).to.have.length(1) - expect(results[0].credit).to.be.false() - }) - }) - - describe('because the charge adjustment is different', () => { - beforeEach(async () => { - await TransactionHelper.add({ - billLicenceId: followUpBillLicenceId, - adjustmentFactor: 0.5, - chargeCategoryCode, - credit: true - }) - }) - - it('returns the debits', async () => { - const results = await FetchPreviousTransactionsService.go( - { billingAccountId }, - { licenceId }, - financialYearEnding - ) - - expect(results).to.have.length(1) - expect(results[0].credit).to.be.false() - }) - }) - - describe('because the winter discount is different', () => { - beforeEach(async () => { - await TransactionHelper.add({ - billLicenceId: followUpBillLicenceId, - chargeCategoryCode, - credit: true, - winterOnly: true - }) - }) - - it('returns the debits', async () => { - const results = await FetchPreviousTransactionsService.go( - { billingAccountId }, - { licenceId }, - financialYearEnding - ) - - expect(results).to.have.length(1) - expect(results[0].credit).to.be.false() - }) - }) - - describe('because the supported source differs (additional charge) is different', () => { - beforeEach(async () => { - await TransactionHelper.add({ - billLicenceId: followUpBillLicenceId, - chargeCategoryCode, - credit: true, - supportedSource: true - }) - }) - - it('returns the debits', async () => { - const results = await FetchPreviousTransactionsService.go( - { billingAccountId }, - { licenceId }, - financialYearEnding - ) - - expect(results).to.have.length(1) - expect(results[0].credit).to.be.false() - }) - }) - - describe('because the supported source name differs (additional charge) is different', () => { - beforeEach(async () => { - await TransactionHelper.add({ - billLicenceId: followUpBillLicenceId, - chargeCategoryCode, - credit: true, - supportedSourceName: 'source name' - }) - }) - - it('returns the debits', async () => { - const results = await FetchPreviousTransactionsService.go( - { billingAccountId }, - { licenceId }, - financialYearEnding - ) - - expect(results).to.have.length(1) - expect(results[0].credit).to.be.false() + beforeEach(async () => { + await TransactionHelper.add({ + billLicenceId: followUpBillLicenceId, + billableDays: 30, + chargeCategoryCode, + credit: true }) }) - describe('because the water company flag differs (additional charge) is different', () => { - beforeEach(async () => { - await TransactionHelper.add({ - billLicenceId: followUpBillLicenceId, - chargeCategoryCode, - credit: true, - waterCompanyCharge: true - }) - }) - - it('returns the debits', async () => { - const results = await FetchPreviousTransactionsService.go( - { billingAccountId }, - { licenceId }, - financialYearEnding - ) + it('returns the debits', async () => { + const results = await FetchPreviousTransactionsService.go( + { billingAccountId }, + { licenceId }, + financialYearEnding + ) - expect(results).to.have.length(1) - expect(results[0].credit).to.be.false() - }) + expect(results).to.have.length(1) + expect(results[0].credit).to.be.false() }) }) }) diff --git a/test/services/bill-runs/supplementary/process-transactions.service.test.js b/test/services/bill-runs/supplementary/process-transactions.service.test.js index 33dfda7175..8cc7a5bdd0 100644 --- a/test/services/bill-runs/supplementary/process-transactions.service.test.js +++ b/test/services/bill-runs/supplementary/process-transactions.service.test.js @@ -39,10 +39,10 @@ describe('Process Transactions service', () => { }) describe('match to transactions on a previous bill run', () => { - describe('and the calculated transactions provided', () => { + describe('and some of the calculated transactions provided', () => { let previousTransactions - describe('match all the previous transactions from the last bill run', () => { + describe('match to all the previous transactions from the last bill run', () => { beforeEach(() => { previousTransactions = [ _generatePreviousTransaction('4.10.1', 365, 'I_WILL_BE_REMOVED_1'), @@ -52,7 +52,7 @@ describe('Process Transactions service', () => { Sinon.stub(FetchPreviousTransactionsService, 'go').resolves(previousTransactions) }) - it('returns the matched calculated transactions', async () => { + it('returns the unmatched calculated transactions', async () => { const result = await ProcessTransactionsService.go( calculatedTransactions, bill, @@ -160,307 +160,6 @@ describe('Process Transactions service', () => { }) }) }) - - describe('the service matches calculated to previous transactions', () => { - let calculatedTransactions - - beforeEach(() => { - calculatedTransactions = [_generateCalculatedTransaction('4.10.1', 365, 'CALCULATED_TRANSACTION')] - }) - - describe('when the charge type differs', () => { - beforeEach(() => { - const previousTransactions = [ - _generatePreviousTransaction( - '4.10.1', 365, 'PREVIOUS_TRANSACTION', { chargeType: 'compensation' } - ) - ] - - Sinon.stub(FetchPreviousTransactionsService, 'go').resolves(previousTransactions) - }) - - it('does not match the transactions', async () => { - const result = await ProcessTransactionsService.go( - calculatedTransactions, - bill, - billLicence, - billingPeriod - ) - - expect(result).to.have.length(2) - expect(result[0].purposes).to.equal('CALCULATED_TRANSACTION') - expect(result[1].purposes).to.equal('PREVIOUS_TRANSACTION') - }) - }) - - describe('when the charge category code differs', () => { - beforeEach(() => { - const previousTransactions = [_generatePreviousTransaction('5.10.1', 365, 'PREVIOUS_TRANSACTION')] - - Sinon.stub(FetchPreviousTransactionsService, 'go').resolves(previousTransactions) - }) - - it('does not match the transactions', async () => { - const result = await ProcessTransactionsService.go( - calculatedTransactions, - bill, - billLicence, - billingPeriod - ) - - expect(result).to.have.length(2) - expect(result[0].purposes).to.equal('CALCULATED_TRANSACTION') - expect(result[1].purposes).to.equal('PREVIOUS_TRANSACTION') - }) - }) - - describe('when the billable days differ', () => { - beforeEach(() => { - const previousTransactions = [_generatePreviousTransaction('4.10.1', 5, 'PREVIOUS_TRANSACTION')] - - Sinon.stub(FetchPreviousTransactionsService, 'go').resolves(previousTransactions) - }) - - it('does not match the transactions', async () => { - const result = await ProcessTransactionsService.go( - calculatedTransactions, - bill, - billLicence, - billingPeriod - ) - - expect(result).to.have.length(2) - expect(result[0].purposes).to.equal('CALCULATED_TRANSACTION') - expect(result[1].purposes).to.equal('PREVIOUS_TRANSACTION') - }) - }) - - describe('when the abatement agreement (section 126) differs', () => { - beforeEach(() => { - const previousTransactions = [ - _generatePreviousTransaction('4.10.1', 365, 'PREVIOUS_TRANSACTION', { section126Factor: 0.5 }) - ] - - Sinon.stub(FetchPreviousTransactionsService, 'go').resolves(previousTransactions) - }) - - it('does not match the transactions', async () => { - const result = await ProcessTransactionsService.go( - calculatedTransactions, - bill, - billLicence, - billingPeriod - ) - - expect(result).to.have.length(2) - expect(result[0].purposes).to.equal('CALCULATED_TRANSACTION') - expect(result[1].purposes).to.equal('PREVIOUS_TRANSACTION') - }) - }) - - describe('when the two-part tariff agreement (section 127) differs', () => { - beforeEach(() => { - const previousTransactions = [ - _generatePreviousTransaction('4.10.1', 365, 'PREVIOUS_TRANSACTION', { section127Agreement: true }) - ] - - Sinon.stub(FetchPreviousTransactionsService, 'go').resolves(previousTransactions) - }) - - it('does not match the transactions', async () => { - const result = await ProcessTransactionsService.go( - calculatedTransactions, - bill, - billLicence, - billingPeriod - ) - - expect(result).to.have.length(2) - expect(result[0].purposes).to.equal('CALCULATED_TRANSACTION') - expect(result[1].purposes).to.equal('PREVIOUS_TRANSACTION') - }) - }) - - describe('when the canal and river trust agreement (section 130) differs', () => { - beforeEach(() => { - const previousTransactions = [ - _generatePreviousTransaction('4.10.1', 365, 'PREVIOUS_TRANSACTION', { section130Agreement: true }) - ] - - Sinon.stub(FetchPreviousTransactionsService, 'go').resolves(previousTransactions) - }) - - it('does not match the transactions', async () => { - const result = await ProcessTransactionsService.go( - calculatedTransactions, - bill, - billLicence, - billingPeriod - ) - - expect(result).to.have.length(2) - expect(result[0].purposes).to.equal('CALCULATED_TRANSACTION') - expect(result[1].purposes).to.equal('PREVIOUS_TRANSACTION') - }) - }) - - describe('when the aggregate differs', () => { - beforeEach(() => { - const previousTransactions = [ - _generatePreviousTransaction('4.10.1', 365, 'PREVIOUS_TRANSACTION', { aggregateFactor: 0.5 }) - ] - - Sinon.stub(FetchPreviousTransactionsService, 'go').resolves(previousTransactions) - }) - - it('does not match the transactions', async () => { - const result = await ProcessTransactionsService.go( - calculatedTransactions, - bill, - billLicence, - billingPeriod - ) - - expect(result).to.have.length(2) - expect(result[0].purposes).to.equal('CALCULATED_TRANSACTION') - expect(result[1].purposes).to.equal('PREVIOUS_TRANSACTION') - }) - }) - - describe('when the charge adjustment differs', () => { - beforeEach(() => { - const previousTransactions = [ - _generatePreviousTransaction('4.10.1', 365, 'PREVIOUS_TRANSACTION', { adjustmentFactor: 0.5 }) - ] - - Sinon.stub(FetchPreviousTransactionsService, 'go').resolves(previousTransactions) - }) - - it('does not match the transactions', async () => { - const result = await ProcessTransactionsService.go( - calculatedTransactions, - bill, - billLicence, - billingPeriod - ) - - expect(result).to.have.length(2) - expect(result[0].purposes).to.equal('CALCULATED_TRANSACTION') - expect(result[1].purposes).to.equal('PREVIOUS_TRANSACTION') - }) - }) - - describe('when the winter discount differs', () => { - beforeEach(() => { - const previousTransactions = [ - _generatePreviousTransaction('4.10.1', 365, 'PREVIOUS_TRANSACTION', { winterOnly: true }) - ] - - Sinon.stub(FetchPreviousTransactionsService, 'go').resolves(previousTransactions) - }) - - it('does not match the transactions', async () => { - const result = await ProcessTransactionsService.go( - calculatedTransactions, - bill, - billLicence, - billingPeriod - ) - - expect(result).to.have.length(2) - expect(result[0].purposes).to.equal('CALCULATED_TRANSACTION') - expect(result[1].purposes).to.equal('PREVIOUS_TRANSACTION') - }) - }) - - describe('when if it is a supported source differs (additional charge)', () => { - beforeEach(() => { - const previousTransactions = [ - _generatePreviousTransaction('4.10.1', 365, 'PREVIOUS_TRANSACTION', { supportedSource: true }) - ] - - Sinon.stub(FetchPreviousTransactionsService, 'go').resolves(previousTransactions) - }) - - it('does not match the transactions', async () => { - const result = await ProcessTransactionsService.go( - calculatedTransactions, - bill, - billLicence, - billingPeriod - ) - - expect(result).to.have.length(2) - expect(result[0].purposes).to.equal('CALCULATED_TRANSACTION') - expect(result[1].purposes).to.equal('PREVIOUS_TRANSACTION') - }) - }) - - describe('when the supported source name differs (additional charge)', () => { - beforeEach(() => { - const previousTransactions = [ - _generatePreviousTransaction('4.10.1', 365, 'PREVIOUS_TRANSACTION', { supportedSourceName: 'source name' }) - ] - - Sinon.stub(FetchPreviousTransactionsService, 'go').resolves(previousTransactions) - }) - - it('does not match the transactions', async () => { - const result = await ProcessTransactionsService.go( - calculatedTransactions, - bill, - billLicence, - billingPeriod - ) - - expect(result).to.have.length(2) - expect(result[0].purposes).to.equal('CALCULATED_TRANSACTION') - expect(result[1].purposes).to.equal('PREVIOUS_TRANSACTION') - }) - }) - - describe('when the water company flag differs (additional charge)', () => { - beforeEach(() => { - const previousTransactions = [ - _generatePreviousTransaction('4.10.1', 365, 'PREVIOUS_TRANSACTION', { waterCompanyCharge: true }) - ] - - Sinon.stub(FetchPreviousTransactionsService, 'go').resolves(previousTransactions) - }) - - it('does not match the transactions', async () => { - const result = await ProcessTransactionsService.go( - calculatedTransactions, - bill, - billLicence, - billingPeriod - ) - - expect(result).to.have.length(2) - expect(result[0].purposes).to.equal('CALCULATED_TRANSACTION') - expect(result[1].purposes).to.equal('PREVIOUS_TRANSACTION') - }) - }) - - describe('when nothing differs', () => { - beforeEach(() => { - const previousTransactions = [_generatePreviousTransaction('4.10.1', 365, 'PREVIOUS_TRANSACTION')] - - Sinon.stub(FetchPreviousTransactionsService, 'go').resolves(previousTransactions) - }) - - it('matches the transactions', async () => { - const result = await ProcessTransactionsService.go( - calculatedTransactions, - bill, - billLicence, - billingPeriod - ) - - expect(result).to.be.empty() - }) - }) - }) }) function _generateCalculatedTransaction (chargeCategoryCode, billableDays, testReference, changes = {}) {