Skip to content

Commit

Permalink
Merge branch 'main' into licence-holder-filter
Browse files Browse the repository at this point in the history
  • Loading branch information
Jozzey authored Mar 15, 2024
2 parents ed37c49 + 8165edd commit 5930b7b
Show file tree
Hide file tree
Showing 41 changed files with 2,227 additions and 38 deletions.
14 changes: 10 additions & 4 deletions app/controllers/return-requirements.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
*/

const NoReturnsRequiredService = require('../services/return-requirements/no-returns-required.service.js')
const SelectPurposeService = require('../services/return-requirements/purpose.service.js')
const SelectReasonService = require('../services/return-requirements/reason.service.js')
const SessionModel = require('../models/session.model.js')
const SetupService = require('../services/return-requirements/setup.service.js')
const SiteDescriptionService = require('../services/return-requirements/site-description.service.js')
const StartDateService = require('../services/return-requirements/start-date.service.js')
const SubmitNoReturnsRequiredService = require('../services/return-requirements/submit-no-returns-required.service.js')
const SubmitPurposeService = require('../services/return-requirements/submit-purpose.service.js')
const SubmitReasonService = require('../services/return-requirements/submit-reason.service.js')
const SubmitSetupService = require('../services/return-requirements/submit-setup.service.js')
const SubmitSiteDescriptionService = require('../services/return-requirements/submit-site-description.service.js')
Expand Down Expand Up @@ -136,12 +138,10 @@ async function points (request, h) {
async function purpose (request, h) {
const { sessionId } = request.params

const session = await SessionModel.query().findById(sessionId)
const pageData = await SelectPurposeService.go(sessionId)

return h.view('return-requirements/purpose.njk', {
activeNavBar: 'search',
pageTitle: 'Select the purpose for the return requirement',
...session
...pageData
})
}

Expand Down Expand Up @@ -264,6 +264,12 @@ async function submitPoints (request, h) {
async function submitPurpose (request, h) {
const { sessionId } = request.params

const pageData = await SubmitPurposeService.go(sessionId, request.payload)

if (pageData.error) {
return h.view('return-requirements/purpose.njk', pageData)
}

return h.redirect(`/system/return-requirements/${sessionId}/points`)
}

Expand Down
31 changes: 31 additions & 0 deletions app/models/licence-version-purpose-condition-type.model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict'

/**
* Model for LicenceVersionPurposeConditionType (water.licence_version_purpose_condition_types)
* @module LicenceVersionPurposeConditionTypes
*/

const { Model } = require('objection')

const BaseModel = require('./base.model.js')

class LicenceVersionPurposeConditionTypes extends BaseModel {
static get tableName () {
return 'licenceVersionPurposeConditionTypes'
}

static get relationMappings () {
return {
licenceVersionPurposeConditions: {
relation: Model.HasManyRelation,
modelClass: 'licence-version-purpose-condition.model',
join: {
from: 'licenceVersionPurposeConditionTypes.id',
to: 'licenceVersionPurposeConditions.licenceVersionPurposeConditionTypeId'
}
}
}
}
}

module.exports = LicenceVersionPurposeConditionTypes
31 changes: 31 additions & 0 deletions app/models/licence-version-purpose-condition.model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict'

/**
* Model for LicenceVersionPurposeCondition (water.licence_version_purpose_conditions)
* @module LicenceVersionPurposeConditions
*/

const { Model } = require('objection')

const BaseModel = require('./base.model.js')

class LicenceVersionPurposeConditions extends BaseModel {
static get tableName () {
return 'licenceVersionPurposeConditions'
}

static get relationMappings () {
return {
licenceVersionPurposeConditionTypes: {
relation: Model.HasManyRelation,
modelClass: 'licence-version-purpose-condition-type.model',
join: {
from: 'licenceVersionPurposeConditions.licenceVersionPurposeConditionTypeId',
to: 'licenceVersionPurposeConditionTypes.id'
}
}
}
}
}

module.exports = LicenceVersionPurposeConditions
8 changes: 8 additions & 0 deletions app/models/licence-version-purpose.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ class LicenceVersionPurposes extends BaseModel {
from: 'licenceVersionPurposes.licenceVersionId',
to: 'licenceVersions.id'
}
},
purpose: {
relation: Model.BelongsToOneRelation,
modelClass: 'purpose.model.js',
join: {
from: 'licenceVersionPurposes.purposeId',
to: 'purposes.id'
}
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions app/models/purpose.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ class PurposeModel extends BaseModel {
from: 'purposes.id',
to: 'chargeReferences.purposeId'
}
},
licenceVersionPurposes: {
relation: Model.HasManyRelation,
modelClass: 'licence-version-purpose.model',
join: {
from: 'purposes.id',
to: 'licenceVersionPurposes.purposeId'
}
}
}
}
Expand Down
25 changes: 24 additions & 1 deletion app/presenters/licences/view-licence.presenter.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const { formatLongDate } = require('../base.presenter.js')
*
* @returns {Object} The data formatted for the view template
*/
function go (licence) {
function go (licence, licenceVersionPurposeConditionData) {
const {
ends,
expiredDate,
Expand Down Expand Up @@ -45,8 +45,11 @@ function go (licence) {
const pointDetails = _parseAbstractionsAndSourceOfSupply(permitLicence)
const monitoringStationDetails = _generateMonitoringStation(licenceGaugingStations)

const abstractionConditions = _generateAbstractionConditions(licenceVersionPurposeConditionData)

return {
id,
abstractionConditions,
abstractionPeriods,
abstractionPeriodsAndPurposesLinkText,
abstractionPoints: pointDetails.abstractionPoints,
Expand Down Expand Up @@ -77,6 +80,26 @@ function _endDate (expiredDate) {
return formatLongDate(expiredDate)
}

function _generateAbstractionConditions (conditionsData) {
if (!conditionsData ||
conditionsData?.abstractionConditions === undefined ||
conditionsData.abstractionConditions.length === 0) {
return {
caption: 'Abstraction condition',
linkText: 'View details of the abstraction condition',
conditions: []
}
}

return {
caption: conditionsData.abstractionConditions.length > 1 ? 'Abstraction conditions' : 'Abstraction condition',
linkText: conditionsData.abstractionConditions.length > 1
? 'View details of the abstraction conditions'
: 'View details of the abstraction condition',
conditions: conditionsData.abstractionConditions
}
}

function _generateAbstractionPeriods (licenceVersions) {
if (!licenceVersions ||
licenceVersions.length === 0 ||
Expand Down
45 changes: 45 additions & 0 deletions app/presenters/return-requirements/purpose.presenter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use strict'

/**
* Formats data for the `/return-requirements/{sessionId}/purpose` page
* @module PurposePresenter
*/

/**
* Formats data for the `/return-requirements/{sessionId}/purpose` page
*
* @param {module:SessionModel} session - The returns requirements session instance
* @param {Object} [purposesData] - The purposes for the licence
* @param {Object} [payload] - The payload from the request
*
* @returns {Object} - The data formatted for the view template
*/
function go (session, purposesData, payload = {}) {
const data = {
id: session.id,
licenceId: session.data.licence.id,
licenceRef: session.data.licence.licenceRef,
licencePurposes: _licencePurposes(purposesData, payload)
}

return data
}

function _licencePurposes (purposesData, payload) {
// NOTE: 'purposes' is the payload value that tells us whether the user selected any purposes
// for the return requirement.
// If it is not set then it is because the presenter has been called from 'PurposeService' and it's the first
// load. Else it has been called by the 'SubmitPurposeService' and the user has not inputted a site description.
// Either way, we use it to tell us wether there is anything in the payload worth transforming.
const purposes = payload.purposes

if (!purposes) {
return purposesData
}

return purposes
}

module.exports = {
go
}
2 changes: 1 addition & 1 deletion app/routes/return-requirement.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ const routes = [
scope: ['billing']
}
},
description: 'Select the purpose for the return requirement'
description: 'Select the purpose for the requirement for returns'
}
},
{
Expand Down
140 changes: 140 additions & 0 deletions app/services/bill-runs/setup/determine-blocking-bill-run.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
'use strict'

/**
* Determines if an existing bill run will block the one a user is trying to setup
* @module ExistsService
*/

const FetchLiveBillRunsService = require('./fetch-live-bill-runs.service.js')
const FetchMatchingBillRunService = require('./fetch-matching-bill-run.service.js')

/**
* Determines if an existing bill run will block the one a user is trying to setup
*
* This service is used in conjunction with `ExistsService` to determine if an existing bill run will block the user
* from creating the one they have setup.
*
* The first check is to deal with those where the bill run already exists. For example, a user is trying to create
* an annual bill run but one has already been created for that region and year.
*
* If no match is found it then moves on to checking if _any_ bill runs are in progress, regardless of type. If neither
* match is found the user is free to create the bill run.
*
* Where things get complex is supplementary billing. Because we are always having to manage two, PRESROC and SROC, we
* have to deal with there only being one of them present. If that is the case we also need to let the user create the
* bill run, but only the supplementary that doesn't exist.
*
* @param {string} regionId - UUID of the region to fetch live bill runs for
* @param {string} batchType - The type of bill run
* @param {number} financialEndYear - The end year of the financial period to look at
* @param {string} [season] - Applies only to PRESROC two-part tariff. Whether the bill run is summer or winter
* all-year
*
* @returns {Promise<Object[]>} The matching bill run(s) if any. For annual and two-part tariff only one bill run
* instance will be returned. For supplementary 2 bill runs may be returned depending on whether there is both an SROC
* and PRESROC match.
*/
async function go (regionId, batchType, financialEndYear, season = null) {
const supplementary = batchType === 'supplementary'

const matchingBillRuns = await _fetchMatchingBillRuns(regionId, batchType, financialEndYear, season)
const liveBillRuns = await _fetchLiveBillRuns(matchingBillRuns, regionId, supplementary, financialEndYear)

return _processFetchResults(matchingBillRuns, liveBillRuns, supplementary, financialEndYear)
}

async function _fetchLiveBillRuns (matchingBillRuns, regionId, supplementary, financialEndYear) {
// We don't need to check live bill runs if we already have a match for a non-supplementary bill run
if (!supplementary && matchingBillRuns.length > 0) {
return []
}

// We are making an assumption here for supplementary. If we have 2 results then FetchMatchingBillRun must have
// matched a supplementary in both the current financial year and 2022 (last year of PRESROC). This is because
// FetchMatchingBillRun drops the filter by year when checking for supplementary so looks across all years. Our
// working understanding is you can't have 2 bill runs in progress for the same financial year (our and the legacy
// engine won't allow it). So, it _must_ be because we have previously created a supplementary bill run for the
// same region which triggered an SROC and PRESROC which are both sat in 'ready'
if (supplementary && matchingBillRuns.length > 1) {
return []
}

// No matches or only 1 match for supplementary means we need to check for any live bill runs
return FetchLiveBillRunsService.go(regionId, financialEndYear, supplementary)
}

async function _fetchMatchingBillRuns (regionId, batchType, financialEndYear, season) {
const summer = season === 'summer'

return FetchMatchingBillRunService.go(regionId, batchType, financialEndYear, summer)
}

function _processFetchResults (matchingBillRuns, liveBillRuns, supplementary, financialEndYear) {
// First, a shortcut! If we have no results to process we have no results to return!
if (matchingBillRuns.length === 0 && liveBillRuns.length === 0) {
return []
}

if (!supplementary) {
return _processNonSupplementaryResults(matchingBillRuns, liveBillRuns)
}

return _processSupplementaryResults(matchingBillRuns, liveBillRuns, financialEndYear)
}

function _processNonSupplementaryResults (matchingBillRuns, liveBillRuns) {
// If there was a match we return that as the result to display to the user as it will be more applicable
if (matchingBillRuns.length > 0) {
return [matchingBillRuns[0]]
}

// Else if there is a live bill run we display the first result. Any live bill run will block a new one
return [liveBillRuns[0]]
}

function _processSupplementaryResults (matchingBillRuns, liveBillRuns, financialEndYear) {
// If we are here we are dealing with a supplementary. What we want to know when it comes to supplementary is
//
// - does it have any matches/live bill runs for the current financial year?
// - does it have any matches/live bill runs for the last PRESROC year (2022)?
//
// If it is yes to both we have to block the new one. If it yes to just one, the other can go ahead. If it is no to
// both then both can be triggered
const results = []

let currentMatch = false
let presrocMatch = false

// NOTE: This needs explaining. First, for supplementary we only care about 'live' bill runs. Supplementary ignores
// completed ones because you can have as many completed supplementary bill runs in a year as you like. So, if
// matchingBillRuns is empty it means there are no live _supplementary_ bill runs. So, we only need to check the
// results in liveBillRuns.
//
// If matchingBillRuns contained 2 results we wouldn't be here (see notes in _fetchLiveBillRuns()).
//
// But when matchingBillRuns contains just 1 match it means we have a live supplementary in either the current year or
// 2022. So, we needed to call FetchLiveBillRunsService to check for all live bill runs in both years. That means
// liveBillRuns will contain the same live supplementary in matchingBillRuns. Hence, we can just process liveBillRuns
// for our match results.
const billRunsToProcess = liveBillRuns.length > 0 ? liveBillRuns : matchingBillRuns

for (const billRun of billRunsToProcess) {
if (billRun.toFinancialYearEnding === financialEndYear) {
results.push(billRun)
currentMatch = true
} else {
results.push(billRun)
presrocMatch = true
}

if (currentMatch && presrocMatch) {
break
}
}

return results
}

module.exports = {
go
}
Loading

0 comments on commit 5930b7b

Please sign in to comment.