-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into fix-reissue-great-rename-errors
- Loading branch information
Showing
23 changed files
with
1,569 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
'use strict' | ||
|
||
/** | ||
* Model for licence_document_headers (crm.document_header) | ||
* @module LicenceDocumentHeaderModel | ||
*/ | ||
|
||
const { Model } = require('objection') | ||
|
||
const BaseModel = require('./base.model.js') | ||
|
||
/** | ||
* Represents an instance of a licence document header record | ||
* | ||
* For reference, the licence document header record is a 'nothing' record! It doesn't hold anything not already stored | ||
* in other licence tables. We only need to obtain the licence holder name which matches what the legacy UI displays. | ||
* | ||
* We think the reason for the table being there is because of the original ambition to have a generic permit | ||
* repository that could be used for all types of permits. Along with that a 'tactical' CRM that interfaced with the | ||
* permit repository was built. Though the permit repository referred to them as licences, the CRM chose to refer to | ||
* them as 'documents' hence the `crm.document_header` table. | ||
* | ||
* The previous team then tried to refactor the CRM schema but never completed it. Hence we have the `crm_v2` schema | ||
* and more licence duplication. Finally, at a later date the previous team then opted to create a `licences` table in | ||
* the `water` schema we think to support charging. | ||
* | ||
* So, if you see the phrase 'Document' you can assume the instance is one of these older copies of a licence. | ||
* `water.licences` is the primary licence record. But we often have to dip into this older tables for other bits of | ||
* information, for example, the licence holder name currently displayed in the legacy UI. This is why we have models | ||
* like this one. | ||
* | ||
* Welcome to dealing with the legacy database schema! ¯\_(ツ)_/¯ | ||
*/ | ||
class LicenceDocumentHeaderModel extends BaseModel { | ||
static get tableName () { | ||
return 'licenceDocumentHeaders' | ||
} | ||
|
||
// Defining which fields contain json allows us to insert an object without needing to stringify it first | ||
static get jsonAttributes () { | ||
return [ | ||
'metadata' | ||
] | ||
} | ||
|
||
static get relationMappings () { | ||
return { | ||
licence: { | ||
relation: Model.BelongsToOneRelation, | ||
modelClass: 'licence.model', | ||
join: { | ||
from: 'licenceDocumentHeaders.licenceRef', | ||
to: 'licences.licenceRef' | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
module.exports = LicenceDocumentHeaderModel |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
'use strict' | ||
|
||
/** | ||
* Formats data for the view `/licences/{id}/` page | ||
* @module ViewLicencePresenter | ||
*/ | ||
|
||
const { formatLongDate } = require('../base.presenter.js') | ||
|
||
/** | ||
* Formats data for the `/licences/{id}/` page | ||
* | ||
* @param {module:LicenceModel} licence - The licence where the data will be extracted for from | ||
* | ||
* @returns {Object} The data formatted for the view template | ||
*/ | ||
function go (licence) { | ||
const { expiredDate, id, licenceRef, region, startDate } = licence | ||
|
||
return { | ||
id, | ||
endDate: _endDate(expiredDate), | ||
licenceRef, | ||
region: region.displayName, | ||
startDate: formatLongDate(startDate) | ||
} | ||
} | ||
|
||
/** | ||
* Formats the expired date of the licence as the end date for the view | ||
* | ||
* @module ViewLicencePresenter | ||
*/ | ||
function _endDate (expiredDate) { | ||
if (!expiredDate || expiredDate < Date.now()) { | ||
return null | ||
} | ||
|
||
return formatLongDate(expiredDate) | ||
} | ||
|
||
module.exports = { | ||
go | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
103 changes: 103 additions & 0 deletions
103
app/services/bill-runs/two-part-tariff/allocate-returns-to-charge-element.service.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
'use strict' | ||
|
||
/** | ||
* Allocates where applicable the abstracted volumes on the return log with the appropriate charge element | ||
* @module AllocateReturnsToChargeElementService | ||
*/ | ||
|
||
const { periodsOverlap } = require('../../../lib/general.lib.js') | ||
|
||
/** | ||
* For a chargeElement with matching returns any abstracted volume recorded on the return log will be allocated to the | ||
* charge element up to a maximum of the charge elements authorised volume, or the remaining authorised volume on the | ||
* charge reference, whichever is lower. | ||
* | ||
* @param {module:ChargeElementModel} chargeElement - The charge element to allocate return logs against | ||
* @param {module:ReturnLogModel[]} matchingReturns - Return logs that matched to the charge element | ||
* @param {module:ChargeVersionModel} chargePeriod - The charge period taken from the charge version | ||
* @param {module:ChargeReferenceModel} chargeReference - The charge reference the element belongs to | ||
*/ | ||
function go (chargeElement, matchingReturns, chargePeriod, chargeReference) { | ||
matchingReturns.forEach((matchedReturn, i) => { | ||
// We don't allocate returns with issues | ||
if (matchedReturn.issues) { | ||
return | ||
} | ||
|
||
// We can only allocate up to the authorised volume on the charge reference, even if there is charge elements | ||
// unallocated and returns to be allocated | ||
if (chargeReference.allocatedQuantity >= chargeReference.volume) { | ||
return | ||
} | ||
|
||
// Finally, we can only allocate to the charge element if there is unallocated volume left! | ||
if (chargeElement.allocatedQuantity >= chargeElement.authorisedAnnualQuantity) { | ||
return | ||
} | ||
|
||
const matchedLines = _matchLines(chargeElement, matchedReturn.returnSubmissions[0].returnSubmissionLines) | ||
|
||
if (matchedLines.length > 0) { | ||
_allocateReturns(chargeElement, matchedReturn, chargePeriod, chargeReference, i, matchedLines) | ||
} | ||
}) | ||
} | ||
|
||
function _allocateReturns (chargeElement, matchedReturn, chargePeriod, chargeReference, i, matchedLines) { | ||
matchedLines.forEach((matchedLine) => { | ||
const remainingAllocation = chargeElement.authorisedAnnualQuantity - chargeElement.allocatedQuantity | ||
if (remainingAllocation > 0) { | ||
// We default how much to allocate to what is unallocated on the line i.e. remaining >= line.unallocated | ||
let qtyToAllocate = matchedLine.unallocated | ||
|
||
// If what remains is actually less than the line we instead set qtyToAllocate to what remains. We check this | ||
// on both the chargeReference and the element | ||
const chargeReferenceRemainingAllocation = chargeReference.volume - chargeReference.allocatedQuantity | ||
|
||
if (qtyToAllocate > chargeReferenceRemainingAllocation) { | ||
qtyToAllocate = chargeReferenceRemainingAllocation | ||
} else if (remainingAllocation < matchedLine.unallocated) { | ||
qtyToAllocate = remainingAllocation | ||
} | ||
|
||
// We do this check to prevent overwriting the value with false once it's been set to true as it only requires | ||
// a single `matchedLine` to overlap the charge period | ||
if (!chargeElement.chargeDatesOverlap) { | ||
chargeElement.chargeDatesOverlap = _chargeDatesOverlap(matchedLine, chargePeriod) | ||
} | ||
|
||
chargeElement.allocatedQuantity += qtyToAllocate | ||
chargeElement.returnLogs[i].allocatedQuantity += qtyToAllocate | ||
|
||
matchedLine.unallocated -= qtyToAllocate | ||
matchedReturn.allocatedQuantity += qtyToAllocate | ||
chargeReference.allocatedQuantity += qtyToAllocate | ||
} | ||
}) | ||
} | ||
|
||
function _chargeDatesOverlap (matchedLine, chargePeriod) { | ||
const { startDate: chargePeriodStartDate, endDate: chargePeriodEndDate } = chargePeriod | ||
const { startDate: lineStartDate, endDate: lineEndDate } = matchedLine | ||
|
||
if (lineStartDate < chargePeriodEndDate && lineEndDate > chargePeriodEndDate) { | ||
return true | ||
} | ||
|
||
return (lineStartDate < chargePeriodStartDate && lineEndDate > chargePeriodStartDate) | ||
} | ||
|
||
function _matchLines (chargeElement, returnSubmissionLines) { | ||
return returnSubmissionLines.filter((returnSubmissionLine) => { | ||
if (returnSubmissionLine.unallocated === 0) { | ||
return false | ||
} | ||
|
||
const { startDate, endDate } = returnSubmissionLine | ||
return periodsOverlap(chargeElement.abstractionPeriods, [{ startDate, endDate }]) | ||
}) | ||
} | ||
|
||
module.exports = { | ||
go | ||
} |
80 changes: 80 additions & 0 deletions
80
app/services/bill-runs/two-part-tariff/match-and-allocate.service.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.