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

Calculate and display expected charge during 2PT review - Part 2 #1046

Merged
merged 33 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
07bacd9
Calculate and display expected charge during 2PT review - Part 2
Jozzey May 23, 2024
c4e9eb4
Add URL to button in view
Jozzey May 23, 2024
eefdddf
Get the routes working
Jozzey May 24, 2024
fcf46d7
Put new route in the correct order
Jozzey May 24, 2024
5f50d8e
Merge remote-tracking branch 'origin/main' into calc-tpt-expected-cha…
Jozzey May 24, 2024
271b0bc
Add new service to controller
Jozzey May 28, 2024
fce375e
Add new function `formatShortDate` to base presenter
Jozzey May 28, 2024
7fd997e
Add banner to view
Jozzey May 28, 2024
689e23a
Create service to calculate charge
Jozzey May 28, 2024
72d7db4
Update charge ref service to add new banner
Jozzey May 28, 2024
71fcd84
Merge branch 'main' into calc-tpt-expected-charge-pt2
Jozzey May 28, 2024
3c3613e
Tidy up
Jozzey May 28, 2024
1f431a9
Add controller tests
Jozzey May 29, 2024
341ac43
Add test for `formatShortDate` to base presenter tests
Jozzey May 29, 2024
763a7b9
Update helper defaults to more realistic values
Jozzey May 29, 2024
5c32cf0
Create unit tests for calculate charge service
Jozzey May 29, 2024
e711edb
Sort out line length
Jozzey May 29, 2024
41c6337
Update charge ref service test
Jozzey May 29, 2024
ccac1d3
Tidy up
Jozzey May 29, 2024
ea25583
Merge branch 'main' into calc-tpt-expected-charge-pt2
Jozzey May 29, 2024
b594692
Tidy up calculate charge test
Jozzey May 30, 2024
fc3e2fc
Update `formatShortDate` comment
Jozzey May 30, 2024
f0bf4bd
Use `formatMoney` rather than `formatPounds` to format the charge
Jozzey May 30, 2024
7a74e1c
Merge branch 'main' into calc-tpt-expected-charge-pt2
Jozzey May 30, 2024
91ef420
Updated comment
Jozzey May 30, 2024
391845d
Fix typo
Jozzey May 30, 2024
7e6615f
Use `formatChargingModuleDate` as per PR comment
Jozzey May 30, 2024
b43939d
Remove `formatShortDate` from base presenter
Jozzey May 30, 2024
e585164
Fix typo
Jozzey May 30, 2024
624bebe
Update helper comment
Jozzey May 30, 2024
94d1f98
Update `section127Agreement` and `section130Agreement` as per PR comment
Jozzey May 30, 2024
9a18e47
Merge branch 'main' into calc-tpt-expected-charge-pt2
Jozzey May 30, 2024
20f716b
Merge branch 'main' into calc-tpt-expected-charge-pt2
Jozzey May 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 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 AmendAdjustmentFactorService = require('../services/bill-runs/two-part-tariff/amend-adjustment-factor.service.js')
const AmendAuthorisedVolumeService = require('../services/bill-runs/two-part-tariff/amend-authorised-volume.service.js')
const AmendBillableReturnsService = require('../services/bill-runs/two-part-tariff/amend-billable-returns.service.js')
const CalculateChargeService = require('../services/bill-runs/two-part-tariff/calculate-charge.service.js')
const CancelBillRunService = require('../services/bill-runs/cancel-bill-run.service.js')
const ChargeReferenceDetailsService = require('../services/bill-runs/two-part-tariff/charge-reference-details.service.js')
const CreateBillRunValidator = require('../validators/create-bill-run.validator.js')
Expand Down Expand Up @@ -130,6 +131,14 @@ async function matchDetails (request, h) {
})
}

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

await CalculateChargeService.go(licenceId, reviewChargeReferenceId, request.yar)

return h.redirect(`/system/bill-runs/${billRunId}/review/${licenceId}/charge-reference-details/${reviewChargeReferenceId}`)
}

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

Expand Down Expand Up @@ -306,6 +315,7 @@ module.exports = {
create,
index,
matchDetails,
previewCharge,
removeLicence,
review,
reviewLicence,
Expand Down
12 changes: 12 additions & 0 deletions app/presenters/base.presenter.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,17 @@ function formatPounds (valueInPence) {
return valueInPounds.toFixed(2)
}

/**
* Formats a date into a DD/MM/YYYY string, for example, '01/04/2022'
*
* @param {Date} date The date to be formatted
*
* @returns {string} The date formatted as a 'DD/MM/YYYY' string
*/
function formatShortDate (date) {
Jozzey marked this conversation as resolved.
Show resolved Hide resolved
return date.toLocaleDateString('en-GB', { year: 'numeric', month: '2-digit', day: '2-digit' })
}

/**
* Pads a number to a given length with leading zeroes and returns the result as a string
*
Expand Down Expand Up @@ -274,6 +285,7 @@ module.exports = {
formatLongDateTime,
formatMoney,
formatPounds,
formatShortDate,
leftPadZeroes,
sentenceCase,
titleCase
Expand Down
13 changes: 13 additions & 0 deletions app/routes/bill-runs.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,19 @@ const routes = [
description: 'Submit the amended billable return volumes on a charge element'
}
},
{
method: 'GET',
path: '/bill-runs/{id}/review/{licenceId}/preview-charge/{reviewChargeReferenceId}',
handler: BillRunsController.previewCharge,
options: {
auth: {
access: {
scope: ['billing']
}
},
description: 'Sends a request to the Charging Module to calculate the charge for a charge reference'
}
},
{
method: 'GET',
path: '/bill-runs/{id}/send',
Expand Down
125 changes: 125 additions & 0 deletions app/services/bill-runs/two-part-tariff/calculate-charge.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
'use strict'

/**
* Calculates the charge for a charge reference to display in a banner to the user
* @module CalculateChargeService
*/

const { ref } = require('objection')

const CalculateChargeRequest = require('../../../requests/charging-module/calculate-charge.request.js')
const { formatMoney, formatShortDate } = require('../../../presenters/base.presenter.js')
const LicenceModel = require('../../../models/licence.model.js')
const ReviewChargeReferenceModel = require('../../../models/review-charge-reference.model.js')

/**
* Calculates the charge for a charge reference to display in a banner to the user
*
* It does this by sending a transaction based on the selected charge reference to the charging module so that it can
* calculate the charge. The charge amount is then added to to a flash message which will be displayed to the user.
Jozzey marked this conversation as resolved.
Show resolved Hide resolved
*
* @param {String} licenceId - The UUID of the licence related to the charge
* @param {String} reviewChargeReferenceId - The UUID of the charge reference review data to calculate the charge on
* @param {Object} yar - The Hapi `request.yar` session manager passed on by the controller
*/
async function go (licenceId, reviewChargeReferenceId, yar) {
const reviewChargeReference = await _fetchReviewChargeReference(reviewChargeReferenceId)
Jozzey marked this conversation as resolved.
Show resolved Hide resolved
const waterUndertaker = await _fetchWaterUndertaker(licenceId)
const calculatedCharge = await _calculateCharge(reviewChargeReference, waterUndertaker)

if (calculatedCharge) {
yar.flash('charge', `Based on this information the example charge is ${formatMoney(calculatedCharge)}.`)
}
}

function _calculateActualVolume (reviewChargeElements) {
return reviewChargeElements.reduce((total, reviewChargeElement) => {
total += reviewChargeElement.amendedAllocated

return total
}, 0)
}

async function _calculateCharge (reviewChargeReference, waterUndertaker) {
const transaction = {
abatementFactor: reviewChargeReference.abatementAgreement,
actualVolume: _calculateActualVolume(reviewChargeReference.reviewChargeElements),
adjustmentFactor: reviewChargeReference.amendedChargeAdjustment,
aggregateProportion: reviewChargeReference.amendedAggregate,
authorisedDays: 0, // 2PT uses volumes in the calculations rather than days so this can be set to 0
authorisedVolume: reviewChargeReference.amendedAuthorisedVolume,
billableDays: 0, // 2PT uses volumes in the calculations rather than days so this can be set to 0
chargeCategoryCode: reviewChargeReference.chargeReference.chargeCategory.reference,
compensationCharge: false, // Always false for the two-part tariff annual
credit: false,
loss: reviewChargeReference.chargeReference.loss,
periodStart: formatShortDate(reviewChargeReference.reviewChargeVersion.chargePeriodStartDate),
periodEnd: formatShortDate(reviewChargeReference.reviewChargeVersion.chargePeriodEndDate),
ruleset: 'sroc',
section127Agreement: reviewChargeReference.chargeReference.section127Agreement,
Jozzey marked this conversation as resolved.
Show resolved Hide resolved
section130Agreement: reviewChargeReference.chargeReference.section130Agreement,
Jozzey marked this conversation as resolved.
Show resolved Hide resolved
supportedSource: reviewChargeReference.chargeReference.supportedSourceName !== null,
// If `supportedSource` is `true` then `supportedSourceName` must be present
supportedSourceName: reviewChargeReference.chargeReference.supportedSourceName,
// If `twoPartTariff` is `true` then `section127Agreement` must also be `true`
twoPartTariff: reviewChargeReference.twoPartTariffAgreement,
waterCompanyCharge: reviewChargeReference.chargeReference.waterCompanyCharge !== null,
waterUndertaker,
winterOnly: reviewChargeReference.winterDiscount
}

const calculatedCharge = await CalculateChargeRequest.send(transaction)

if (calculatedCharge.succeeded) {
return calculatedCharge.response.body.calculation.chargeValue
} else {
return null
}
}

async function _fetchReviewChargeReference (reviewChargeReferenceId) {
return ReviewChargeReferenceModel.query()
.findById(reviewChargeReferenceId)
.select(
'abatementAgreement',
'amendedAggregate',
'amendedAuthorisedVolume',
'amendedChargeAdjustment',
'twoPartTariffAgreement',
'winterDiscount'
)
.withGraphFetched('chargeReference')
.modifyGraph('chargeReference', (builder) => {
builder.select(
'loss',
'section127Agreement',
Jozzey marked this conversation as resolved.
Show resolved Hide resolved
'section130Agreement',
ref('chargeReferences.additionalCharges:supportedSource.name').castText().as('supportedSourceName'),
ref('chargeReferences.additionalCharges:isSupplyPublicWater').castText().as('waterCompanyCharge')
)
})
.withGraphFetched('chargeReference.chargeCategory')
.modifyGraph('chargeReference.chargeCategory', (builder) => {
builder.select('reference')
})
.withGraphFetched('reviewChargeVersion')
.modifyGraph('reviewChargeVersion', (builder) => {
builder.select('chargePeriodStartDate', 'chargePeriodEndDate')
})
.withGraphFetched('reviewChargeElements')
.modifyGraph('reviewChargeElements', (builder) => {
builder.select('amendedAllocated')
})
}

async function _fetchWaterUndertaker (licenceId) {
const licence = await LicenceModel.query()
.findById(licenceId)
.select('waterUndertaker')

return licence.waterUndertaker
}

module.exports = {
go
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ async function go (billRunId, licenceId, reviewChargeReferenceId, yar) {
} = await FetchReviewChargeReferenceService.go(billRunId, reviewChargeReferenceId)

const [bannerMessage] = yar.flash('banner')
const [chargeMessage] = yar.flash('charge')
const pageData = ChargeReferenceDetailsPresenter.go(billRun, reviewChargeReference, licenceId)

return {
bannerMessage,
chargeMessage,
...pageData
}
}
Expand Down
11 changes: 10 additions & 1 deletion app/views/bill-runs/charge-reference-details.njk
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@
}) }}
{% endif %}

{# Charge preview banner #}
{% if chargeMessage %}
{{ govukNotificationBanner({
titleText: 'Information',
text: chargeMessage
}) }}
{% endif %}

<div class="govuk-grid-row">
<div class="govuk-grid-column-full">
<h1 class="govuk-heading-l">
Expand Down Expand Up @@ -76,8 +84,9 @@
<span class="inline">
{{ govukButton({
text: "Preview the charge",
classes: "govuk-button--secondary",
preventDoubleClick: true,
classes: "govuk-button--secondary"
href: "../preview-charge/" + chargeReference.id
}) }}
</span>
</div>
Expand Down
27 changes: 27 additions & 0 deletions test/controllers/bill-runs.controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const AmendAdjustmentFactorService = require('../../app/services/bill-runs/two-p
const AmendAuthorisedVolumeService = require('../../app/services/bill-runs/two-part-tariff/amend-authorised-volume.service.js')
const AmendBillableReturnsService = require('../../app/services/bill-runs/two-part-tariff/amend-billable-returns.service.js')
const Boom = require('@hapi/boom')
const CalculateCharge = require('../../app/services/bill-runs/two-part-tariff/calculate-charge.service.js')
Jozzey marked this conversation as resolved.
Show resolved Hide resolved
const CancelBillRunService = require('../../app/services/bill-runs/cancel-bill-run.service.js')
const ChargeReferenceDetailsService = require('../../app/services/bill-runs/two-part-tariff/charge-reference-details.service.js')
const IndexBillRunsService = require('../../app/services/bill-runs/index-bill-runs.service.js')
Expand Down Expand Up @@ -717,6 +718,32 @@ describe('Bill Runs controller', () => {
})
})

describe('/bill-runs/{id}/review/{licenceId}/preview-charge/{reviewChargeReferenceId}', () => {
describe('GET', () => {
const licenceId = '87cb11cf-1e5e-448d-8050-29f4e681b416'
const reviewChargeReferenceId = '7c09753d-f606-4deb-a929-4bc8aa7acb8d'

beforeEach(async () => {
options = _options('GET', `review/${licenceId}/preview-charge/${reviewChargeReferenceId}`)
})

describe('when a request is valid', () => {
beforeEach(() => {
Sinon.stub(CalculateCharge, 'go').resolves()
Jozzey marked this conversation as resolved.
Show resolved Hide resolved
})

it('redirects to the review charge reference details page', async () => {
const response = await server.inject(options)

expect(response.statusCode).to.equal(302)
expect(response.headers.location).to.equal(
`/system/bill-runs/97db1a27-8308-4aba-b463-8a6af2558b28/review/${licenceId}/charge-reference-details/${reviewChargeReferenceId}`
)
})
})
})
})

describe('/bill-runs/{id}/send', () => {
describe('GET', () => {
beforeEach(async () => {
Expand Down
8 changes: 8 additions & 0 deletions test/presenters/base.presenter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,14 @@ describe('Base presenter', () => {
})
})

describe('#formatShortDate()', () => {
it('correctly formats the given date, for example, 12/09/2021', async () => {
const result = BasePresenter.formatShortDate(new Date('2021-09-12T14:41:10.511Z'))

expect(result).to.equal('12/09/2021')
})
})

describe('#formatMoney()', () => {
let valueInPence

Expand Down
Loading
Loading