Skip to content

Commit

Permalink
Create review an individual licence for 2PT bill run (#704)
Browse files Browse the repository at this point in the history
https://eaflood.atlassian.net/browse/WATER-4191

As part of our two-part tariff bill run work, we've developed a review page displaying a summary of all licences in a bill run. This PR allows you to click on a specific licence from the review page and review its details within the bill run.
  • Loading branch information
Beckyrose200 authored Feb 9, 2024
1 parent 779b151 commit d290c0e
Show file tree
Hide file tree
Showing 16 changed files with 1,124 additions and 9 deletions.
14 changes: 14 additions & 0 deletions app/controllers/bill-runs.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const CreateBillRunValidator = require('../validators/create-bill-run.validator.
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')
const ReviewLicenceService = require('../services/bill-runs/two-part-tariff/review-licence.service.js')

async function create (request, h) {
const validatedData = CreateBillRunValidator.go(request.payload)
Expand Down Expand Up @@ -41,6 +42,18 @@ async function review (request, h) {
})
}

async function reviewLicence (request, h) {
const { id: billRunId, licenceId } = request.params

const pageData = await ReviewLicenceService.go(billRunId, licenceId)

return h.view('bill-runs/review-licence.njk', {
pageTitle: `Licence ${pageData.licenceRef}`,
activeNavBar: 'bill-runs',
...pageData
})
}

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

Expand Down Expand Up @@ -69,5 +82,6 @@ function _formattedInitiateBillRunError (error) {
module.exports = {
create,
review,
reviewLicence,
view
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ function _prepareLicences (licences) {
}

preparedLicences.push({
id: licence.id,
licenceRef: licence.licenceRef,
licenceHolder: licence.licenceHolder,
status: licence.status,
Expand Down
112 changes: 112 additions & 0 deletions app/presenters/bill-runs/two-part-tariff/review-licence.presenter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
'use strict'

/**
* Formats the review licence data ready for presenting in the review licence page
* @module ReviewLicencePresenter
*/

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

/**
* Prepares and processes bill run and review licence data for presentation
*
* @param {module:ReviewReturnResultModel} matchedReturns matched return logs for an individual licence
* @param {module:ReviewReturnResultModel} unmatchedReturns unmatched return logs for an individual licence
* @param {Object[]} chargePeriods chargePeriods with start and end date properties
* @param {module:BillRunModel} billRun the data from the bill run
* @param {String} licenceRef the reference for the licence
*
* @returns {Object} the prepared bill run and licence data to be passed to the review licence page
*/
function go (matchedReturns, unmatchedReturns, chargePeriods, billRun, licenceRef) {
return {
licenceRef,
billRunId: billRun.id,
status: 'Review',
region: billRun.region.displayName,
matchedReturns: _prepareMatchedReturns(matchedReturns),
unmatchedReturns: _prepareUnmatchedReturns(unmatchedReturns),
chargePeriodDates: _prepareLicenceChargePeriods(chargePeriods)
}
}

function _prepareLicenceChargePeriods (chargePeriods) {
return chargePeriods.map((chargePeriod) => {
const { startDate, endDate } = chargePeriod

return _prepareDate(startDate, endDate)
})
}

function _prepareUnmatchedReturns (unmatchedReturns) {
return unmatchedReturns.map((unmatchedReturn) => {
const { returnReference, status, description, purposes, allocated, quantity, startDate, endDate } = unmatchedReturn.reviewReturnResults

return {
reference: returnReference,
dates: _prepareDate(startDate, endDate),
status,
description,
purpose: purposes[0].tertiary.description,
total: `${allocated} ML / ${quantity} ML`
}
})
}

function _prepareMatchedReturns (matchedReturns) {
return matchedReturns.map((matchedReturn) => {
const { returnStatus, total, allocated } = _checkStatusAndReturnTotal(matchedReturn)
const { returnReference, description, purposes, startDate, endDate } = matchedReturn.reviewReturnResults

return {
reference: returnReference,
dates: _prepareDate(startDate, endDate),
status: returnStatus,
description,
purpose: purposes[0].tertiary.description,
total,
allocated
}
})
}

function _checkStatusAndReturnTotal (returnLog) {
const { status, allocated, quantity, underQuery } = returnLog.reviewReturnResults

let allocatedStatus
let total
let returnStatus = underQuery ? 'query' : status

if (status === 'void' || status === 'received') {
total = '/'
allocatedStatus = 'Not processed'
} else if (status === 'due') {
returnStatus = 'overdue'
total = '/'
allocatedStatus = 'Not processed'
} else {
total = `${allocated} ML / ${quantity} ML`
allocatedStatus = _allocated(quantity, allocated)
}

return { returnStatus, total, allocated: allocatedStatus }
}

function _allocated (quantity, allocated) {
if (quantity > allocated) {
return 'Over abstraction'
} else {
return 'Fully allocated'
}
}

function _prepareDate (startDate, endDate) {
const preparedStartDate = formatLongDate(startDate)
const preparedEndDate = formatLongDate(endDate)

return `${preparedStartDate} to ${preparedEndDate}`
}

module.exports = {
go
}
13 changes: 13 additions & 0 deletions app/routes/bill-runs.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ const routes = [
},
description: 'Review two-part tariff match and allocation results'
}
},
{
method: 'GET',
path: '/bill-runs/{id}/review/{licenceId}',
handler: BillRunsController.reviewLicence,
options: {
auth: {
access: {
scope: ['billing']
}
},
description: 'Review a two-part tariff licence'
}
}
]

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
'use strict'

/**
* Fetches the review licence results and bill run data for a two-part tariff bill run
* @module FetchReviewLicenceResultsService
*/

const BillRunModel = require('../../../models/bill-run.model.js')
const ReviewResultModel = require('../../../models/review-result.model.js')

/**
* Fetches the review return results data for an individual licence in the bill run and the bill run data
*
* @param {String} billRunId UUID of the bill run
* @param {String} licenceId UUID of the licence
*
* @returns {Promise<Object[]>} Contains an array of bill run data and review licence data
*/
async function go (billRunId, licenceId) {
const billRun = await _fetchBillRun(billRunId)
const reviewReturnResults = await _fetchReviewReturnResults(billRunId, licenceId)

return { reviewReturnResults, billRun }
}

async function _fetchBillRun (billRunId) {
return BillRunModel.query()
.findById(billRunId)
.select([
'id',
'batchType'
])
.withGraphFetched('region')
.modifyGraph('region', (builder) => {
builder.select([
'id',
'displayName'
])
})
}

async function _fetchReviewReturnResults (billRunId, licenceId) {
return ReviewResultModel.query()
.where({ billRunId, licenceId })
.whereNotNull('reviewReturnResultId')
.select([
'reviewReturnResultId',
'reviewChargeElementResultId',
'chargeVersionId',
'chargePeriodStartDate',
'chargePeriodEndDate'])
.withGraphFetched('reviewReturnResults')
.modifyGraph('reviewReturnResults', (builder) => {
builder.select([
'id',
'returnId',
'return_reference',
'startDate',
'endDate',
'dueDate',
'receivedDate',
'status',
'underQuery',
'nilReturn',
'description',
'purposes',
'quantity',
'allocated',
'abstractionOutsidePeriod'
])
})
.withGraphFetched('licence')
.modifyGraph('licence', (builder) => {
builder.select([
'id',
'licenceRef'
])
})
}

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

/**
* Prepares the review return logs ready for presenting in the review page
* @module PrepareReviewLicenceResultsService
*/

/**
* Prepares the given review return logs, deduplicates them, and extracts matched and unmatched returns along with their
* corresponding charge periods for the licence being reviewed
*
* @param {module:ReviewReturnResultModel} reviewReturnResults All the review return logs associated with the licence being reviewed
*
* @returns {Object[]} matched and unmatched return logs and the charge periods for that licence
*/
function go (reviewReturnResults) {
const licenceRef = reviewReturnResults[0].licence.licenceRef
const uniqueReturnLogs = _dedupeReturnLogs(reviewReturnResults)

const { matchedReturns, unmatchedReturns } = _splitReturns(uniqueReturnLogs)

// Only matched returns have a charge version and therefore chargePeriods
const chargePeriods = _fetchChargePeriods(matchedReturns)

return { matchedReturns, unmatchedReturns, chargePeriods, licenceRef }
}

function _dedupeReturnLogs (reviewReturnResults) {
const uniqueReturnIds = new Set()
const uniqueReturnLogs = []

reviewReturnResults.forEach((returnLog) => {
const id = returnLog.reviewReturnResultId

if (!uniqueReturnIds.has(id)) {
uniqueReturnIds.add(id)
uniqueReturnLogs.push(returnLog)
}
})

return uniqueReturnLogs
}

// To generate a list of charge periods from the return logs, we need to eliminate duplicate charge versions and extract
// unique charge periods based on their start and end dates.
function _fetchChargePeriods (matchedReturns) {
const uniqueChargeVersionIds = new Set()
const chargePeriods = []

for (const returnLog of matchedReturns) {
const id = returnLog.chargeVersionId

if (!uniqueChargeVersionIds.has(id)) {
uniqueChargeVersionIds.add(id)

chargePeriods.push({
startDate: returnLog.chargePeriodStartDate,
endDate: returnLog.chargePeriodEndDate
})
}
}

return chargePeriods
}

function _splitReturns (uniqueReturnLogs) {
// Filters the return logs to only return the ones where reviewChargeElementResultId exists (ie the return log
// matches to a charge element)
const matchedReturns = uniqueReturnLogs.filter((returnLog) => {
return returnLog.reviewChargeElementResultId !== null
})

// Filters the return logs to only return the ones where reviewChargeElementResultId is null (ie the return log
// does not match to a charge element)
const unmatchedReturns = uniqueReturnLogs.filter((returnLog) => {
return returnLog.reviewChargeElementResultId === null
})

return { matchedReturns, unmatchedReturns }
}

module.exports = {
go
}
34 changes: 34 additions & 0 deletions app/services/bill-runs/two-part-tariff/review-licence.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use strict'

/**
* Orchestrates fetching and presenting the data needed for the licence review page
* @module ReviewLicenceService
*/

const FetchReviewLicenceResultsService = require('./fetch-review-licence-results.service.js')
const ReviewLicencePresenter = require('../../../presenters/bill-runs/two-part-tariff/review-licence.presenter.js')
const PrepareReviewLicenceResultsService = require('./prepare-review-licence-results.service.js')

/**
* Orchestrated fetching and presenting the data needed for the licence review page
*
* @param {*} billRunId The UUID for the bill run
* @param {*} licenceId The UUID of the licence that is being reviewed
* @param {*} status The current overall status of the licence
*
* @returns {Object} an object representing the 'pageData' needed to review the individual licence. It contains the
* licence matched and unmatched returns and the licence charge data
*/
async function go (billRunId, licenceId) {
const { reviewReturnResults, billRun } = await FetchReviewLicenceResultsService.go(billRunId, licenceId)

const { matchedReturns, unmatchedReturns, chargePeriods, licenceRef } = PrepareReviewLicenceResultsService.go(reviewReturnResults)

const pageData = ReviewLicencePresenter.go(matchedReturns, unmatchedReturns, chargePeriods, billRun, licenceRef)

return pageData
}

module.exports = {
go
}
Loading

0 comments on commit d290c0e

Please sign in to comment.