Skip to content

Commit

Permalink
Review Result of 2PT Matching for selected Region (#663)
Browse files Browse the repository at this point in the history
https://eaflood.atlassian.net/browse/WATER-4188

Create a screen to display the overview of the results of the 2PT matching process.  It will be displayed at the end of the matching process, and when a 2PT Bill Run is clicked from the Bill runs page and is in the REVIEW state.
  • Loading branch information
Jozzey authored Jan 31, 2024
1 parent 24ea36e commit 69e13b5
Show file tree
Hide file tree
Showing 21 changed files with 1,244 additions and 17 deletions.
12 changes: 9 additions & 3 deletions app/controllers/bill-runs.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const Boom = require('@hapi/boom')
const CreateBillRunValidator = require('../validators/create-bill-run.validator.js')
const StartBillRunProcessService = require('../services/bill-runs/start-bill-run-process.service.js')
const ViewBillRunService = require('../services/bill-runs/view-bill-run.service.js')
const ReviewBillRunService = require('../services/bill-runs/two-part-tariff/review-bill-run.service.js')

async function create (request, h) {
const validatedData = CreateBillRunValidator.go(request.payload)
Expand All @@ -28,10 +29,15 @@ async function create (request, h) {
}
}

async function review (_request, h) {
async function review (request, h) {
const { id } = request.params

const pageData = await ReviewBillRunService.go(id)

return h.view('bill-runs/review.njk', {
pageTitle: 'Review Two Part Tariff SROC',
activeNavBar: 'bill-runs'
pageTitle: 'Review licences',
activeNavBar: 'bill-runs',
...pageData
})
}

Expand Down
8 changes: 8 additions & 0 deletions app/models/licence.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ class LicenceModel extends BaseModel {
to: 'regions.id'
}
},
reviewResults: {
relation: Model.HasManyRelation,
modelClass: 'review-result.model',
join: {
from: 'licences.id',
to: 'reviewResults.licenceId'
}
},
workflows: {
relation: Model.HasManyRelation,
modelClass: 'workflow.model',
Expand Down
8 changes: 8 additions & 0 deletions app/models/review-result.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ class ReviewResultModel extends BaseModel {
from: 'reviewResults.reviewReturnResultId',
to: 'reviewReturnResults.id'
}
},
licence: {
relation: Model.BelongsToOneRelation,
modelClass: 'licence.model',
join: {
from: 'reviewResults.licenceId',
to: 'licences.id'
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
'use strict'

/**
* Formats the two part tariff review data ready for presenting in the review page
* @module ReviewBillRunPresenter
*/

const { formatLongDate } = require('../../base.presenter.js')

/**
* Prepares and processes bill run and licence data for presentation
*
* @param {module:BillRunModel} billRun the data from the bill run
* @param {module:LicenceModel} licences the licences data asociated with the bill run
*
* @returns {Object} the prepared bill run and licence data to be passed to the review page
*/
function go (billRun, licences) {
const { licencesToReviewCount, preparedLicences } = _prepareLicences(licences)

const preparedBillRun = _prepareBillRun(billRun, preparedLicences, licencesToReviewCount)

return { ...preparedBillRun, preparedLicences }
}

function _prepareLicences (licences) {
let licencesToReviewCount = 0
const preparedLicences = []

for (const licence of licences) {
if (licence.status === 'review') {
licencesToReviewCount++
}

preparedLicences.push({
licenceRef: licence.licenceRef,
licenceHolder: licence.licenceHolder,
status: licence.status,
issue: _getIssueOnLicence(licence.issues)
})
}

return { preparedLicences, licencesToReviewCount }
}

function _prepareBillRun (billRun, billRunLicences, licencesToReviewCount) {
return {
region: billRun.region.displayName,
status: billRun.status,
dateCreated: formatLongDate(billRun.createdAt),
financialYear: _financialYear(billRun.toFinancialYearEnding),
billRunType: 'two-part tariff',
numberOfLicences: billRunLicences.length,
licencesToReviewCount
}
}

function _financialYear (financialYearEnding) {
const startYear = financialYearEnding - 1
const endYear = financialYearEnding

return `${startYear} to ${endYear}`
}

function _getIssueOnLicence (issues) {
if (issues.length > 1) {
return 'Multiple Issues'
} else if (issues.length === 1) {
return issues[0]
} else {
return ''
}
}

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

/**
* Determines the issues on the licences for a two-part tariff bill run
* @module DetermineBillRunIssuesService
*/

const FetchReviewResultsService = require('./fetch-review-results.service.js')

// A list of issues that would put a licence into a status of 'review'
const REVIEW_STATUSES = [
'Aggregate factor', 'Checking query', 'Overlap of charge dates', 'Returns received but not processed',
'Returns split over charge references', 'Unable to match returns'
]

/**
* Fetches the review data on a licence and determines all the issues and licence status
*
* @param {module:LicenceModel} licences the two-part tariff licence included in the bill run
*/
async function go (licences) {
for (const licence of licences) {
const licenceReviewResults = await FetchReviewResultsService.go(licence.id)
const { issues, status } = _determineIssues(licenceReviewResults)

licence.issues = issues
licence.status = status
}
}

function _abstractionOutsidePeriod (issues, licenceReviewResults) {
const abstractionOutsidePeriod = licenceReviewResults.some((licenceReviewResult) => {
return licenceReviewResult.reviewReturnResults?.abstractionOutsidePeriod
})

if (abstractionOutsidePeriod) {
issues.push('Abstraction outside period')
}
}

function _determineIssues (licenceReviewResults) {
const issues = []

_abstractionOutsidePeriod(issues, licenceReviewResults)

_hasAggregate(issues, licenceReviewResults)

_underQuery(issues, licenceReviewResults)

_noReturnsReceived(issues, licenceReviewResults)

_overAbstracted(issues, licenceReviewResults)

_hasChargeDatesOverlap(issues, licenceReviewResults)

_notProcessed(issues, licenceReviewResults)

_receivedLate(issues, licenceReviewResults)

_returnsSplitOverChargeReference(issues, licenceReviewResults)

_returnsNotReceived(issues, licenceReviewResults)

_matchingReturns(issues, licenceReviewResults)

const status = _determineIssueStatus(issues)

return { issues, status }
}

function _determineIssueStatus (issues) {
const hasReviewIssue = issues.some((issue) => {
return REVIEW_STATUSES.includes(issue)
})

if (hasReviewIssue) {
return 'review'
}

return 'ready'
}

function _hasAggregate (issues, licenceReviewResults) {
const hasAggregate = licenceReviewResults.some((licenceReviewResult) => {
return licenceReviewResult.reviewChargeElementResults?.aggregate !== 1
})

if (hasAggregate) {
issues.push('Aggregate factor')
}
}

function _hasChargeDatesOverlap (issues, licenceReviewResults) {
const hasChargeDatesOverlap = licenceReviewResults.some((licenceReviewResult) => {
return licenceReviewResult.reviewChargeElementResults?.chargeDatesOverlap
})

if (hasChargeDatesOverlap) {
issues.push('Overlap of charge dates')
}
}

function _matchingReturns (issues, licenceReviewResults) {
const matchingReturns = licenceReviewResults.some((licenceReviewResult) => {
return !(licenceReviewResult?.reviewChargeElementResultId && licenceReviewResult?.reviewReturnResultId)
})

if (matchingReturns) {
issues.push('Unable to match returns')
}
}

function _notProcessed (issues, licenceReviewResults) {
const notProcessed = licenceReviewResults.some((licenceReviewResult) => {
return licenceReviewResult.reviewReturnResults?.status === 'received'
})

if (notProcessed) {
issues.push('Returns received but not processed')
}
}

function _noReturnsReceived (issues, licenceReviewResults) {
const noReturnsReceived = licenceReviewResults.some((licenceReviewResult) => {
return licenceReviewResult.reviewReturnResults?.status === 'due' || licenceReviewResult.reviewReturnResults?.status === 'overdue'
})

if (noReturnsReceived) {
issues.push('No returns received')
}
}

function _overAbstracted (issues, licenceReviewResults) {
const overAbstracted = licenceReviewResults.some((licenceReviewResult) => {
return licenceReviewResult.reviewReturnResults?.quantity > licenceReviewResult.reviewReturnResults?.allocated
})

if (overAbstracted) {
issues.push('Over abstraction')
}
}

function _receivedLate (issues, licenceReviewResults) {
const receivedLate = licenceReviewResults.some((licenceReviewResult) => {
return licenceReviewResult.reviewReturnResults?.receivedDate > licenceReviewResult.reviewReturnResults?.dueDate
})

if (receivedLate) {
issues.push('Returns received late')
}
}

function _underQuery (issues, licenceReviewResults) {
const underQuery = licenceReviewResults.some((licenceReviewResult) => {
return licenceReviewResult.reviewReturnResults?.underQuery
})

if (underQuery) {
issues.push('Checking query')
}
}

/**
* Checks if any of the licence review results indicate that a charge element has not received its returns
*/
function _returnsNotReceived (issues, licenceReviewResults) {
const returnsNotReceived = licenceReviewResults.some((licenceReviewResult) => {
return (licenceReviewResult.reviewReturnResultId &&
licenceReviewResult.reviewChargeElementResultId &&
(licenceReviewResult.reviewReturnResults.status === 'due' ||
licenceReviewResult.reviewReturnResults.status === 'overdue')
)
})

if (returnsNotReceived) {
issues.push('Some returns not received')
}
}

/**
* Determines if there are multiple charge references associated with a matched return
*/
function _returnsSplitOverChargeReference (issues, licenceReviewResults) {
const seenReviewReturnResults = {}
let returnsSplitOverChargeReference

for (const result of licenceReviewResults) {
const { chargeReferenceId, reviewReturnResultId } = result

if (seenReviewReturnResults[reviewReturnResultId]) {
if (seenReviewReturnResults[reviewReturnResultId] !== chargeReferenceId) {
returnsSplitOverChargeReference = true
}
} else {
seenReviewReturnResults[reviewReturnResultId] = chargeReferenceId
}
}

if (returnsSplitOverChargeReference) {
issues.push('Returns split over charge references')
}
}

module.exports = {
go
}
Loading

0 comments on commit 69e13b5

Please sign in to comment.