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

Create match and allocate service #660

Merged
merged 13 commits into from
Jan 17, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* Match and allocate licences to returns for a two-part tariff bill run for the given billing periods
* @module MatchAndAllocateService
*/

const AllocateReturnsToChargeElementService = require('./allocate-returns-to-charge-element.service.js')
const FetchLicencesService = require('./fetch-licences.service.js')
const MatchReturnsToChargeElementService = require('./match-returns-to-charge-element.service.js')
const PrepareChargeVersionService = require('./prepare-charge-version.service.js')
const PrepareReturnLogsService = require('./prepare-return-logs.service.js')
const PersistAllocatedLicenceToResultsService = require('./persist-allocated-licence-to-results.service.js')

/**
* Performs the two-part tariff matching and allocating
*
* This function initiates the processing of matching and allocating by fetching licenses within the specified region and billing period.
* Each license undergoes individual processing, including fetching and preparing return logs, charge versions, and
* charge references. The allocated quantity for each charge reference is set to 0, and matching return logs are allocated
* to the corresponding charge elements.
*
* After processing each license, the results are persisted using PersistAllocatedLicenceToResultsService.
*
* @param {module:BillRunModel} billRun - The bill run object containing billing information
* @param {Object[]} billingPeriods - An array of billing periods each containing a `startDate` and `endDate`
*
* @returns {Array} - An array of processed licences associated with the bill run
*/
async function go (billRun, billingPeriods) {
const startTime = process.hrtime.bigint()

const licences = await FetchLicencesService.go(billRun.regionId, billingPeriods[0])

if (licences.length > 0) {
await _process(licences, billingPeriods, billRun)
}

_calculateAndLogTime(startTime)

return licences
}

function _calculateAndLogTime (startTime) {
const endTime = process.hrtime.bigint()
const timeTakenNs = endTime - startTime
const timeTakenMs = timeTakenNs / 1000000n

global.GlobalNotifier.omg('Two part tariff matching complete', { timeTakenMs })
}

async function _process (licences, billingPeriods, billRun) {
for (const licence of licences) {
await PrepareReturnLogsService.go(licence, billingPeriods[0])

const { chargeVersions, returnLogs } = licence
chargeVersions.forEach((chargeVersion) => {
PrepareChargeVersionService.go(chargeVersion, billingPeriods[0])

const { chargeReferences } = chargeVersion
chargeReferences.forEach((chargeReference) => {
chargeReference.allocatedQuantity = 0

const { chargeElements } = chargeReference

chargeElements.forEach((chargeElement) => {
const matchingReturns = MatchReturnsToChargeElementService.go(chargeElement, returnLogs)

if (matchingReturns.length > 0) {
AllocateReturnsToChargeElementService.go(chargeElement, matchingReturns, chargeVersion.chargePeriod, chargeReference)
}
})
})
})

await PersistAllocatedLicenceToResultsService.go(billRun.billingBatchId, licence)
}
}

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

// Test framework dependencies
const Lab = require('@hapi/lab')
const Code = require('@hapi/code')
const Sinon = require('sinon')

const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script()
const { expect } = Code

// Test helpers

// Things we need to stub
const AllocateReturnsToChargeElementService = require('../../../../app/services/bill-runs/two-part-tariff/allocate-returns-to-charge-element.service.js')
const FetchLicencesService = require('../../../../app/services/bill-runs/two-part-tariff/fetch-licences.service.js')
const MatchReturnsToChargeElementService = require('../../../../app/services/bill-runs/two-part-tariff/match-returns-to-charge-element.service.js')
const PrepareChargeVersionService = require('../../../../app/services/bill-runs/two-part-tariff/prepare-charge-version.service.js')
const PrepareReturnLogsService = require('../../../../app/services/bill-runs/two-part-tariff/prepare-return-logs.service.js')
const PersistAllocatedLicenceToResultsService = require('../../../../app/services/bill-runs/two-part-tariff/persist-allocated-licence-to-results.service.js')

// Thing under test
const MatchAndAllocateService = require('../../../../app/services/bill-runs/two-part-tariff/match-and-allocate.service.js')

describe('Match And Allocate Service', () => {
let notifierStub
let licences

beforeEach(() => {
notifierStub = { omg: Sinon.stub() }
global.GlobalNotifier = notifierStub
})

afterEach(async () => {
delete global.GlobalNotifier
Sinon.restore()
})

describe('with a given billRun and billingPeriods', () => {
const billingPeriods = [
{ startDate: new Date('2023-04-01'), endDate: new Date('2024-03-31') },
{ startDate: new Date('2022-04-01'), endDate: new Date('2023-03-31') }
]

const billRun = {
regionId: 'ffea25c2-e577-4969-8667-b0eed899230d',
billingBatchId: '41be6d72-701b-4252-90d5-2d38614b6282'
}

describe('when there are licences to be processed', () => {
beforeEach(() => {
licences = _generateLicencesData()

Sinon.stub(FetchLicencesService, 'go').returns(licences)
Sinon.stub(PrepareReturnLogsService, 'go')
Sinon.stub(PrepareChargeVersionService, 'go')
Sinon.stub(AllocateReturnsToChargeElementService, 'go')
Sinon.stub(PersistAllocatedLicenceToResultsService, 'go')
})

describe('and the charge element has matching returns', () => {
beforeEach(() => {
const matchingReturns = _generateMatchingReturnsData()

Sinon.stub(MatchReturnsToChargeElementService, 'go').returns(matchingReturns)
})

it('processes the licence for matching allocating', async () => {
const result = await MatchAndAllocateService.go(billRun, billingPeriods)

expect(FetchLicencesService.go.called).to.be.true()
expect(PrepareReturnLogsService.go.called).to.be.true()
expect(PrepareChargeVersionService.go.called).to.be.true()

expect(result[0].chargeVersions[0].chargeReferences[0].allocatedQuantity).to.equal(0)
expect(MatchReturnsToChargeElementService.go.called).to.be.true()
expect(AllocateReturnsToChargeElementService.go.called).to.be.true()

expect(PersistAllocatedLicenceToResultsService.go.called).to.be.true()
})

it('returns the licences all processed', async () => {
const result = await MatchAndAllocateService.go(billRun, billingPeriods)

expect(result).to.equal(licences)
})
})

describe('and the charge element does not have matching returns', () => {
beforeEach(() => {
Sinon.stub(MatchReturnsToChargeElementService, 'go').returns([])
})

it('processes the licence for matching but does not allocate', async () => {
const result = await MatchAndAllocateService.go(billRun, billingPeriods)

expect(FetchLicencesService.go.called).to.be.true()
expect(PrepareReturnLogsService.go.called).to.be.true()
expect(PrepareChargeVersionService.go.called).to.be.true()

expect(result[0].chargeVersions[0].chargeReferences[0].allocatedQuantity).to.equal(0)
expect(MatchReturnsToChargeElementService.go.called).to.be.true()
expect(AllocateReturnsToChargeElementService.go.called).to.be.false()

expect(PersistAllocatedLicenceToResultsService.go.called).to.be.true()
})

it('returns the licences all processed', async () => {
const result = await MatchAndAllocateService.go(billRun, billingPeriods)

expect(result).to.equal(licences)
})
})
})

describe('when there are no licences to be processed', () => {
beforeEach(() => {
Sinon.stub(FetchLicencesService, 'go').returns([])
Sinon.stub(PrepareReturnLogsService, 'go')
Sinon.stub(PrepareChargeVersionService, 'go')
Sinon.stub(MatchReturnsToChargeElementService, 'go')
Sinon.stub(AllocateReturnsToChargeElementService, 'go')
Sinon.stub(PersistAllocatedLicenceToResultsService, 'go')
})

it('calls the fetchLicencesService', async () => {
await MatchAndAllocateService.go(billRun, billingPeriods)

expect(FetchLicencesService.go.called).to.be.true()
})

it('does not process the licences', async () => {
await MatchAndAllocateService.go(billRun, billingPeriods)

expect(PrepareReturnLogsService.go.called).to.be.false()
expect(PrepareChargeVersionService.go.called).to.be.false()
expect(MatchReturnsToChargeElementService.go.called).to.be.false()
expect(AllocateReturnsToChargeElementService.go.called).to.be.false()
expect(PersistAllocatedLicenceToResultsService.go.called).to.be.false()
})

it('does not return any licences', async () => {
const result = await MatchAndAllocateService.go(billRun, billingPeriods)

expect(result).to.equal([])
})
})
})
})

function _generateMatchingReturnsData () {
return [
{
id: 'v1:1:5/31/14/*S/0116A:10021668:2022-04-01:2023-03-31',
returnRequirement: '10021668',
description: 'DRAINS ETC-DEEPING FEN AND OTHER LINKED SITES',
startDate: new Date('2022-04-01'),
endDate: new Date('2023-03-31'),
receivedDate: new Date('2024-01-10'),
dueDate: new Date('2023-04-28'),
status: 'completed',
underQuery: false,
periodStartDay: 1,
periodStartMonth: 3,
periodEndDay: 31,
periodEndMonth: 10,
purposes: [
{
alias: 'Spray Irrigation - Direct',
primary: { code: 'A', description: 'Agriculture' },
tertiary: { code: '400', description: 'Spray Irrigation - Direct' },
secondary: { code: 'AGR', description: 'General Agriculture' }
}
],
returnSubmissions: [
{
id: '1313d4f1-0fe8-4dfa-b18d-3faddedcc18f',
nilReturn: false,
returnSubmissionLines: [
{
id: 'e828b761-0fe0-4d57-8fcd-fa892ecc213e',
startDate: new Date('2022-04-01'),
endDate: new Date('2022-04-30'),
quantity: 0.025,
unallocated: 0.000025
}
]
}
],
nilReturn: false,
quantity: 0.000263,
allocatedQuantity: 0,
abstractionPeriods: [
{
startDate: new Date('2022-04-01'),
endDate: new Date('2022-10-31')
},
{
startDate: new Date('2023-03-01'),
endDate: new Date('2023-03-31')
}
],
abstractionOutsidePeriod: true,
matched: true,
issues: false
}
]
}

function _generateLicencesData () {
return [
{
id: 'fdae33da-9195-4b97-976a-9791bc4f6b66',
licenceRef: '5/31/14/*S/0116A',
startDate: new Date('1966-02-01'),
expiredDate: null,
lapsedDate: null,
revokedDate: null,
chargeVersions: [
{
id: 'aad7de5b-d684-4980-bcb7-e3b631d3036f',
startDate: new Date('2022-04-01'),
endDate: null,
status: 'current',
changeReason: {
description: 'Strategic review of charges (SRoC)'
},
licence: {
id: 'fdae33da-9195-4b97-976a-9791bc4f6b66',
licenceRef: '5/31/14/*S/0116A',
startDate: new Date('1966-02-01'),
expiredDate: null,
lapsedDate: null,
revokedDate: null
},
chargeReferences: [
{
id: '4e7f1824-3680-4df0-806f-c6d651ba4771',
volume: 32,
description: 'Example 1',
aggregate: null,
s127: 'true',
chargeCategory: {
reference: '4.6.12',
shortDescription: 'High loss, non-tidal, restricted water, greater than 15 up to and including 50 ML/yr, Tier 2 model',
subsistenceCharge: 68400
},
chargeElements: [
{
id: '8eac5976-d16c-4818-8bc8-384d958ce863',
description: 'Spray irrigation at Welland and Deepings Internal Drainage Board drains and the River Glen at West Pinchbeck',
abstractionPeriodStartDay: 1,
abstractionPeriodStartMonth: 3,
abstractionPeriodEndDay: 31,
abstractionPeriodEndMonth: 10,
authorisedAnnualQuantity: 32,
purpose: {
id: 'f3872a42-b91b-4c58-887a-ef09dda686fd',
legacyId: '400',
description: 'Spray Irrigation - Direct'
}
}
]
}
]
}
]
}
]
}
Loading