diff --git a/app/services/import/determine-supplementary-billing-flags.service.js b/app/services/import/determine-supplementary-billing-flags.service.js new file mode 100644 index 0000000000..ad5338eb64 --- /dev/null +++ b/app/services/import/determine-supplementary-billing-flags.service.js @@ -0,0 +1,91 @@ +'use strict' + +/** + * Determines if an imported licence has a new end date + * @module DetermineSupplementaryBillingFlagsService + */ + +const LicenceModel = require('../../models/licence.model.js') +const ProcessImportedLicenceService = require('../licences/supplementary/process-imported-licence.service.js') + +/** + * Determines if an imported licence has a new end date. + * + * This service is responsible for determining whether a licence imported has a new end day and therefore should be + * flagged for supplementary billing. + * + * It compares the licences end dates (such as lapsed, revoked or expired dates) between WRLS licence and the imported + * data, and if there is a change in the dates allows the licence to go on to determining the flags. + * + * @param {object} importedLicence - The imported licence + * @param {string} licenceId - The UUID of the licence being updated by the import + * + * @returns {Promise} A promise is returned but it does not resolve to anything we expect the caller to use + */ +async function go (importedLicence, licenceId) { + try { + const licenceChanged = await _licenceChanged(importedLicence, licenceId) + + if (!licenceChanged) { + return + } + + return ProcessImportedLicenceService.go(importedLicence, licenceId) + } catch (error) { + global.GlobalNotifier.omfg('Determine supplementary billing flags on import failed ', { licenceId }, error) + } +} + +async function _licenceChanged (importedLicence, licenceId) { + const query = LicenceModel.query() + .select(['id']) + .where('id', licenceId) + + _whereClauses(query, importedLicence) + + const result = await query + + return result.length === 0 +} + +/** + * Adds where clauses to compare the end dates (expired, revoked, lapsed) of the imported licence with those stored + * in the database. It handles where the end dates can be null. + * + * In SQL, comparing `null` values using a regular `where` clause does not work as expected because + * `null` represents the absence of a value and `null = null` returns false. To address this, we use + * `whereNull` to explicitly check for null values in the database. + * + * If an end date is present on the imported licence, the query uses a standard `where` clause to check + * for a match. If the end date is null, the query uses `whereNull` to compare against the null values. + * + * This ensures that value types (dates and null) can be correctly compared, allowing us to detect changes + * between the imported licence and the existing WRLS licence data. + * + * @private + */ +function _whereClauses (query, importedLicence) { + const { expiredDate, lapsedDate, revokedDate } = importedLicence + + if (expiredDate) { + query.where('expiredDate', expiredDate) + } else { + query.whereNull('expiredDate') + } + + if (revokedDate) { + query.where('revokedDate', revokedDate) + } else { + query.whereNull('revokedDate') + } + + if (lapsedDate) { + query.where('lapsedDate', lapsedDate) + } else { + query.whereNull('lapsedDate') + } +} + +module.exports = { + go +} diff --git a/app/services/import/legacy/process-licence.service.js b/app/services/import/legacy/process-licence.service.js index 37fefc1700..81b3321d41 100644 --- a/app/services/import/legacy/process-licence.service.js +++ b/app/services/import/legacy/process-licence.service.js @@ -5,6 +5,7 @@ * @module ImportLegacyProcessLicenceService */ +const DetermineSupplementaryBillingFlagsService = require('../determine-supplementary-billing-flags.service.js') const LicenceStructureValidator = require('../../../validators/import/licence-structure.validator.js') const PersistImportService = require('../persist-import.service.js') const ProcessLicenceReturnLogsService = require('../../jobs/return-logs/process-licence-return-logs.service.js') @@ -35,6 +36,13 @@ async function go (licenceRef) { const { naldLicenceId, regionCode, transformedLicence, wrlsLicenceId } = await TransformLicenceService.go(licenceRef) + // We have other services that need to know when a licence has been imported. However, they only care about changes + // to existing licences. So, if wrlsLicenceId is populated it means the import is updating an existing licence. + if (wrlsLicenceId) { + DetermineSupplementaryBillingFlagsService.go(transformedLicence, wrlsLicenceId) + await ProcessLicenceReturnLogsService.go(wrlsLicenceId) + } + // Pass the transformed licence through each transformation step, building the licence as we go await TransformLicenceVersionsService.go(regionCode, naldLicenceId, transformedLicence) await TransformLicenceVersionPurposesService.go(regionCode, naldLicenceId, transformedLicence) @@ -58,10 +66,6 @@ async function go (licenceRef) { // Either insert or update the licence in WRLS const licenceId = await PersistImportService.go(transformedLicence, transformedCompanies) - if (wrlsLicenceId) { - await ProcessLicenceReturnLogsService.go(wrlsLicenceId) - } - calculateAndLogTimeTaken(startTime, 'Legacy licence import complete', { licenceId, licenceRef }) } catch (error) { global.GlobalNotifier.omfg('Legacy licence import errored', { licenceRef }, error) diff --git a/app/services/licences/supplementary/fetch-existing-licence-details.service.js b/app/services/licences/supplementary/fetch-existing-licence-details.service.js new file mode 100644 index 0000000000..f28421c92d --- /dev/null +++ b/app/services/licences/supplementary/fetch-existing-licence-details.service.js @@ -0,0 +1,89 @@ +'use strict' + +/** + * Fetches existing supplementary details about a licence being updated during import + * @module FetchExistingLicenceDetailsService + */ + +const { db } = require('../../../../db/db.js') + +/** + * Fetches existing supplementary details about a licence being updated during import + * + * We need to get the existing end dates so we can compare with those being imported to determine which have been + * changed (it could be more than one). + * + * The query also determines what relevant charge versions exist for the licence. When processing the licence we have + * to work out whether to set the include in presroc, include in sroc, and include in two-part tariff (by way of + * supplementary billing years) flags on the licence. + * + * We can skip those steps if we know the licence being updated doesn't have the relevant charge versions. If it doesn't + * have them, then it could never have been previously billed for them! + * + * @param {string} licenceId - The UUID of the licence details being fetched + * + * @returns {Promise} - The data needed to determine which supplementary flags the licence needs + */ +async function go (licenceId) { + const query = _query() + + const { rows: [row] } = await db.raw(query, [licenceId]) + + return row +} + +function _query () { + return ` + SELECT + l.id, + l.expired_date, + l.lapsed_date, + l.revoked_date, + (CASE l.include_in_presroc_billing + WHEN 'yes' THEN TRUE + ELSE FALSE + END) AS flagged_for_presroc, + l.include_in_sroc_billing AS flagged_for_sroc, + EXISTS( + SELECT + 1 + FROM + public.charge_versions cv + WHERE + cv.licence_id = l.id + AND cv.start_date < '2022-04-01' + ) AS pre_sroc_charge_versions, + EXISTS( + SELECT + 1 + FROM + public.charge_versions cv + WHERE + cv.licence_id = l.id + AND cv.start_date > '2022-03-31' + ) AS sroc_charge_versions, + EXISTS( + SELECT + 1 + FROM + public.charge_versions cv + INNER JOIN + public.charge_references cr ON cr.charge_version_id = cv.id + INNER JOIN + public.charge_elements ce ON ce.charge_reference_id = cr.id + WHERE + cv.licence_id = l.id + AND cv.start_date > '2022-03-31' + AND cr.adjustments->>'s127' = 'true' + AND ce.section_127_Agreement = TRUE + ) AS two_part_tariff_charge_versions + FROM + public.licences l + WHERE + l.id = ?; + ` +} + +module.exports = { + go +} diff --git a/app/services/licences/supplementary/persist-supplementary-billing-flags.service.js b/app/services/licences/supplementary/persist-supplementary-billing-flags.service.js new file mode 100644 index 0000000000..38822658ce --- /dev/null +++ b/app/services/licences/supplementary/persist-supplementary-billing-flags.service.js @@ -0,0 +1,59 @@ +'use strict' + +/** + * Persists the supplementary billing flags for a licence + * @module PersistSupplementaryBillingFlagsService + */ + +const CreateLicenceSupplementaryYearService = require('./create-licence-supplementary-year.service.js') +const LicenceModel = require('../../../models/licence.model.js') + +/** + * Persists the supplementary billing flags for a licence + * + * Updates the licences includeInPresrocBilling and includeInSrocBilling flags. + * Adds financial years related to two-part tariff billing into the LicenceSupplementaryYears table. + * + * NOTE: Due to the column data type of the includeInPresrocBilling & includeInSrocBilling, one is a string value and + * one is a boolean. + * + * @param {object[]} twoPartTariffBillingYears - The years that need persisting in the LicenceSupplementaryYears table + * @param {boolean} flagForPreSrocSupplementary - `true` or `false` depending on if the licence needs to be flagged + * for pre sroc billing + * @param {boolean} flagForSrocSupplementary - `true` or `false` depending on if the licence needs to be flagged for + * sroc billing + * @param {string} licenceId - The UUID of the licence that needs the flags persisting for + * + * @returns {Promise} - Resolves with the result of persisting two-part tariff billing years, + */ +async function go (twoPartTariffBillingYears, flagForPreSrocSupplementary, flagForSrocSupplementary, licenceId) { + const includeInPresrocBilling = flagForPreSrocSupplementary ? 'yes' : 'no' + + await _updateLicenceFlags(includeInPresrocBilling, flagForSrocSupplementary, licenceId) + + return _flagForLicenceSupplementaryYears(twoPartTariffBillingYears, licenceId) +} + +/** + * Persists two-part tariff financial years in the LicenceSupplementaryYears table. + * @private + */ +async function _flagForLicenceSupplementaryYears (twoPartTariffBillingYears, licenceId) { + if (twoPartTariffBillingYears.length === 0) { + return + } + + const twoPartTariff = true + + return CreateLicenceSupplementaryYearService.go(licenceId, twoPartTariffBillingYears, twoPartTariff) +} + +async function _updateLicenceFlags (includeInPresrocBilling, flagForSrocSupplementary, licenceId) { + return LicenceModel.query() + .patch({ includeInPresrocBilling, includeInSrocBilling: flagForSrocSupplementary }) + .where('id', licenceId) +} + +module.exports = { + go +} diff --git a/app/services/licences/supplementary/process-imported-licence.service.js b/app/services/licences/supplementary/process-imported-licence.service.js new file mode 100644 index 0000000000..96adc292b4 --- /dev/null +++ b/app/services/licences/supplementary/process-imported-licence.service.js @@ -0,0 +1,126 @@ +'use strict' + +/** + * Processes a licence that has been imported with at least one changed 'end' date (expired, lapsed, or revoked) + * @module ProcessImportedLicenceService + */ + +const DetermineBillingYearsService = require('./determine-billing-years.service.js') +const FetchExistingLicenceDetailsService = require('./fetch-existing-licence-details.service.js') +const { determineCurrentFinancialYear } = require('../../../lib/general.lib.js') +const PersistSupplementaryBillingFlagsService = require('./persist-supplementary-billing-flags.service.js') + +const SROC_START_DATE = new Date('2022-04-01') + +/** + * Processes a licence that has been imported with at least one changed 'end' date (expired, lapsed, or revoked) + * + * @param {object} importedLicence - Object representing the updated licence being imported + * @param {string} licenceId - The UUID of the licence being imported + * + * @returns {Promise} A promise is returned but it does not resolve to anything we expect the caller to use + */ +async function go (importedLicence, licenceId) { + const existingLicenceDetails = await FetchExistingLicenceDetailsService.go(licenceId) + const earliestChangedDate = _earliestChangedDate(importedLicence, existingLicenceDetails) + + // If not set it means none of the dates changed were before the current financial year end so there is no reason + // to change anything on the flags + if (!earliestChangedDate) { + return + } + + const flagForPreSrocSupplementary = _flagForPresrocSupplementary(existingLicenceDetails, earliestChangedDate) + const flagForSrocSupplementary = _flagForSrocSupplementary(existingLicenceDetails) + const twoPartTariffBillingYears = _flagForTwoPartTariffSupplementary(existingLicenceDetails, earliestChangedDate) + + return PersistSupplementaryBillingFlagsService.go( + twoPartTariffBillingYears, + flagForPreSrocSupplementary, + flagForSrocSupplementary, + licenceId + ) +} + +function _earliestChangedDate (importedLicence, existingLicenceDetails) { + const { endDate: currentFinancialYearEndDate } = determineCurrentFinancialYear() + const changedDates = [] + + let date + + // NOTE: In JavaScript, comparing date objects directly can lead to incorrect results, as two date objects, even with + // the same date and time, are treated as different objects. To avoid this, we convert the dates to strings for + // comparison. Normally, you might use getTime() to compare dates, but since any of these values can be null, calling + // getTime() on a null value would result in an error. Using strings safely handles null values. + if (String(importedLicence.expiredDate) !== String(existingLicenceDetails.expired_date)) { + date = importedLicence.expiredDate ?? existingLicenceDetails.expired_date + changedDates.push(date) + } + + if (String(importedLicence.lapsedDate) !== String(existingLicenceDetails.lapsed_date)) { + date = importedLicence.lapsedDate ?? existingLicenceDetails.lapsed_date + changedDates.push(date) + } + + if (String(importedLicence.revokedDate) !== String(existingLicenceDetails.revoked_date)) { + date = importedLicence.revokedDate ?? existingLicenceDetails.revoked_date + changedDates.push(date) + } + + // Filter out those greater than the current financial year end date + const filteredDates = changedDates.filter((changedDate) => { + return (changedDate < currentFinancialYearEndDate) + }) + + // Now work out the earliest end date from those that have changed + return filteredDates.length > 0 ? new Date(Math.min(...filteredDates)) : null +} + +function _flagForPresrocSupplementary (existingLicenceDetails, earliestChangedDate) { + const { flagged_for_presroc: flagged, pre_sroc_charge_versions: chargeVersions } = existingLicenceDetails + + // If the licence has no presroc charge versions return false. We check this before the existing flag, because this + // is an opportunity to remove the PRESROC supplementary billing flag from a licence that won't ever be picked up by + // the PRESROC billing engine (so the flag will never get removed) + if (!chargeVersions) { + return false + } + + // If it is already flagged then we don't want to change the flag so return true + if (flagged) { + return true + } + + // Else return the result of checking if the earliest changed date was before PRESROC billing regime ended + return earliestChangedDate < SROC_START_DATE +} + +function _flagForSrocSupplementary (existingLicenceDetails) { + const { sroc_charge_versions: chargeVersions } = existingLicenceDetails + + // If the licence has no SROC charge versions, return false. We check because it's an opportunity + // to remove the SROC supplementary billing flag from a licence that won't be picked up by the SROC billing engine + // (so the flag will never get removed). If charge versions exist, we return true to flag for SROC billing. + + return !!chargeVersions +} + +function _flagForTwoPartTariffSupplementary (existingLicenceDetails, earliestChangedDate) { + const { two_part_tariff_charge_versions: chargeVersions } = existingLicenceDetails + const billingYears = [] + + // If the licence has no 2PT charge versions return the empty billing years + if (!chargeVersions) { + return billingYears + } + + // If the earliest date is before the SROC charging scheme started default to the SROC START DATE + const startDate = earliestChangedDate < SROC_START_DATE ? SROC_START_DATE : earliestChangedDate + const { endDate } = determineCurrentFinancialYear() + + return DetermineBillingYearsService.go(startDate, endDate) +} + +module.exports = { + go +} diff --git a/test/services/import/determine-supplementary-billing-flags.service.test.js b/test/services/import/determine-supplementary-billing-flags.service.test.js new file mode 100644 index 0000000000..66805d9e55 --- /dev/null +++ b/test/services/import/determine-supplementary-billing-flags.service.test.js @@ -0,0 +1,135 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, before, beforeEach, afterEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const LicenceHelper = require('../../support/helpers/licence.helper.js') + +// Things we need to stub +const ProcessImportedLicenceService = require('../../../app/services/licences/supplementary/process-imported-licence.service.js') + +// Thing under test +const DetermineSupplementaryBillingFlagsService = require('../../../app/services/import/determine-supplementary-billing-flags.service.js') + +describe('Determine Supplementary Billing Flags Service', () => { + const lapsedDate = new Date('2023-01-01') + const revokedDate = new Date('2023-01-01') + const expiredDate = new Date('2023-01-01') + + let existingLicenceNullDates + let existingLicencePopulatedDates + let notifierStub + let importedLicence + + before(async () => { + existingLicenceNullDates = await LicenceHelper.add() + existingLicencePopulatedDates = await LicenceHelper.add({ expiredDate, lapsedDate, revokedDate }) + }) + + beforeEach(() => { + // The service depends on GlobalNotifier to have been set. This happens in app/plugins/global-notifier.plugin.js + // when the app starts up and the plugin is registered. As we're not creating an instance of Hapi server in this + // test we recreate the condition by setting it directly with our own stub + notifierStub = { omg: Sinon.stub(), omfg: Sinon.stub() } + global.GlobalNotifier = notifierStub + + Sinon.stub(ProcessImportedLicenceService, 'go').resolves() + }) + + afterEach(async () => { + Sinon.restore() + }) + + describe('when the existing version of the licence', () => { + describe('matches the imported version of the licence', () => { + describe('because all the dates are null', () => { + before(() => { + importedLicence = { expiredDate: null, lapsedDate: null, revokedDate: null } + }) + + it('does not call ProcessImportedLicenceService', async () => { + await DetermineSupplementaryBillingFlagsService.go(importedLicence, existingLicenceNullDates.id) + + expect(ProcessImportedLicenceService.go.called).to.be.false() + }) + }) + + describe('because all the dates match', () => { + before(() => { + importedLicence = { expiredDate, lapsedDate, revokedDate } + }) + + it('does not call ProcessImportedLicenceService', async () => { + await DetermineSupplementaryBillingFlagsService.go(importedLicence, existingLicencePopulatedDates.id) + + expect(ProcessImportedLicenceService.go.called).to.be.false() + }) + }) + }) + + describe('does not match the imported version of the licence', () => { + describe('because the imported version has an end date where the existing version has null', () => { + before(() => { + importedLicence = { expiredDate, lapsedDate: null, revokedDate: null } + }) + + it('calls ProcessImportedLicenceService to handle what supplementary flags are needed', async () => { + await DetermineSupplementaryBillingFlagsService.go(importedLicence, existingLicenceNullDates.id) + + expect(ProcessImportedLicenceService.go.called).to.be.true() + }) + }) + + describe('because the imported version has a null end date where the existing version has one', () => { + before(() => { + importedLicence = { expiredDate, lapsedDate: null, revokedDate } + }) + + it('calls ProcessImportedLicenceService to handle what supplementary flags are needed', async () => { + await DetermineSupplementaryBillingFlagsService.go(importedLicence, existingLicencePopulatedDates.id) + + expect(ProcessImportedLicenceService.go.called).to.be.true() + }) + }) + + describe('because the imported version has a different end date to the existing version', () => { + before(() => { + importedLicence = { expiredDate, lapsedDate, revokedDate: new Date('2023-02-02') } + }) + + it('calls ProcessImportedLicenceService to handle what supplementary flags are needed', async () => { + await DetermineSupplementaryBillingFlagsService.go(importedLicence, existingLicencePopulatedDates.id) + + expect(ProcessImportedLicenceService.go.called).to.be.true() + }) + }) + }) + }) + + describe('when there is an error', () => { + let licenceId + + before(() => { + // To make the service fail we pass it an invalid licence id + licenceId = '1234' + + importedLicence = { expiredDate: null, lapsedDate: null, revokedDate: null } + }) + + it('handles the error', async () => { + await DetermineSupplementaryBillingFlagsService.go(importedLicence, licenceId) + + const args = notifierStub.omfg.firstCall.args + + expect(args[0]).to.equal('Determine supplementary billing flags on import failed ') + expect(args[1].licenceId).to.equal(licenceId) + expect(args[2]).to.be.an.error() + }) + }) +}) diff --git a/test/services/import/legacy/process-licence.service.test.js b/test/services/import/legacy/process-licence.service.test.js index 2fe375d341..1dee9b3e93 100644 --- a/test/services/import/legacy/process-licence.service.test.js +++ b/test/services/import/legacy/process-licence.service.test.js @@ -13,6 +13,7 @@ const { generateUUID } = require('../../../../app/lib/general.lib.js') const { generateLicenceRef } = require('../../../support/helpers/licence.helper.js') // Things to stub +const DetermineSupplementaryBillingFlagsService = require('../../../../app/services/import/determine-supplementary-billing-flags.service.js') const PersistImportService = require('../../../../app/services/import/persist-import.service.js') const ProcessLicenceReturnLogsService = require('../../../../app/services/jobs/return-logs/process-licence-return-logs.service.js') const TransformAddressesService = require('../../../../app/services/import/legacy/transform-addresses.service.js') @@ -48,6 +49,7 @@ describe('Import Legacy Process Licence service', () => { transformedLicence = _transformedLicence(licenceRef) + Sinon.stub(DetermineSupplementaryBillingFlagsService, 'go').resolves() Sinon.stub(TransformLicenceVersionsService, 'go').resolves() Sinon.stub(TransformLicenceVersionPurposesService, 'go').resolves(transformedLicence) Sinon.stub(TransformLicenceVersionPurposeConditionsService, 'go').resolves(transformedLicence) diff --git a/test/services/licences/supplementary/fetch-existing-licence-details.service.test.js b/test/services/licences/supplementary/fetch-existing-licence-details.service.test.js new file mode 100644 index 0000000000..b3d4e68154 --- /dev/null +++ b/test/services/licences/supplementary/fetch-existing-licence-details.service.test.js @@ -0,0 +1,91 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') + +const { describe, it, before } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const ChargeElementHelper = require('../../../support/helpers/charge-element.helper.js') +const ChargeReferenceHelper = require('../../../support/helpers/charge-reference.helper.js') +const ChargeVersionHelper = require('../../../support/helpers/charge-version.helper.js') +const LicenceHelper = require('../../../support/helpers/licence.helper.js') + +// Thing under test +const FetchExistingLicenceDetailsService = require('../../../../app/services/licences/supplementary/fetch-existing-licence-details.service.js') + +describe('Fetch Existing Licence Details Service', () => { + describe('when passed a licence ID for a licence that exists', () => { + let licence + + describe('and the licence has no charge versions', () => { + before(async () => { + licence = await LicenceHelper.add({ + revokedDate: new Date('2024-08-01'), + lapsedDate: new Date('2024-06-01'), + expiredDate: new Date('2024-04-01'), + includeInSrocBilling: true, + includeInPresrocBilling: 'yes' + }) + }) + + it('fetches the licence data', async () => { + const result = await FetchExistingLicenceDetailsService.go(licence.id) + + expect(result.id).to.equal(licence.id) + expect(result.revoked_date).to.equal(new Date('2024-08-01')) + expect(result.lapsed_date).to.equal(new Date('2024-06-01')) + expect(result.expired_date).to.equal(new Date('2024-04-01')) + expect(result.flagged_for_presroc).to.equal(true) + expect(result.flagged_for_sroc).to.equal(true) + }) + + it('outlines which charge versions the licence does not have', async () => { + const result = await FetchExistingLicenceDetailsService.go(licence.id) + + expect(result.pre_sroc_charge_versions).to.equal(false) + expect(result.sroc_charge_versions).to.equal(false) + expect(result.two_part_tariff_charge_versions).to.equal(false) + }) + }) + + describe('and the licence has charge versions', () => { + before(async () => { + licence = await LicenceHelper.add() + // pre sroc charge versions + await ChargeVersionHelper.add({ licenceId: licence.id, startDate: new Date('2018-04-01') }) + + // sroc charge version + const chargeVersion = await ChargeVersionHelper.add({ licenceId: licence.id }) + + // two-part tariff indicators + const chargeReference = await ChargeReferenceHelper.add( + { chargeVersionId: chargeVersion.id, adjustments: { s127: true } } + ) + + await ChargeElementHelper.add({ chargeReferenceId: chargeReference.id }) + }) + + it('fetches the licence data', async () => { + const result = await FetchExistingLicenceDetailsService.go(licence.id) + + expect(result.id).to.equal(licence.id) + expect(result.revoked_date).to.equal(null) + expect(result.lapsed_date).to.equal(null) + expect(result.expired_date).to.equal(null) + expect(result.flagged_for_presroc).to.equal(false) + expect(result.flagged_for_sroc).to.equal(false) + }) + + it('outlines which charge versions the licence has', async () => { + const result = await FetchExistingLicenceDetailsService.go(licence.id) + + expect(result.pre_sroc_charge_versions).to.equal(true) + expect(result.sroc_charge_versions).to.equal(true) + expect(result.two_part_tariff_charge_versions).to.equal(true) + }) + }) + }) +}) diff --git a/test/services/licences/supplementary/persist-supplementary-billing-flags.service.test.js b/test/services/licences/supplementary/persist-supplementary-billing-flags.service.test.js new file mode 100644 index 0000000000..a6bfa2c86c --- /dev/null +++ b/test/services/licences/supplementary/persist-supplementary-billing-flags.service.test.js @@ -0,0 +1,98 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, before, beforeEach, afterEach } = exports.lab = Lab.script() +const { expect } = Code + +// Test helpers +const LicenceHelper = require('../../../support/helpers/licence.helper.js') +const LicenceModel = require('../../../../app/models/licence.model.js') + +// Things we need to stub +const CreateLicenceSupplementaryYearService = require('../../../../app/services/licences/supplementary/create-licence-supplementary-year.service.js') + +// Thing under test +const PersistSupplementaryBillingFlagsService = require('../../../../app/services/licences/supplementary/persist-supplementary-billing-flags.service.js') + +describe('Persist Supplementary Billing Flags Service', () => { + beforeEach(async () => { + Sinon.stub(CreateLicenceSupplementaryYearService, 'go').resolves() + }) + + afterEach(() => { + Sinon.restore() + }) + + describe('when called with a licence id', () => { + let testLicence + let preSrocFlag + let srocFlag + let twoPartTariffFinancialYears + + before(async () => { + testLicence = await LicenceHelper.add() + }) + + describe('and supplementary billing flags', () => { + describe('and two-part tariff financial years', () => { + before(() => { + preSrocFlag = true + srocFlag = true + twoPartTariffFinancialYears = [2022, 2023] + }) + + it('persists the flags on the licence', async () => { + await PersistSupplementaryBillingFlagsService.go( + twoPartTariffFinancialYears, preSrocFlag, srocFlag, testLicence.id + ) + + const licence = await LicenceModel.query().findById(testLicence.id) + + expect(licence.id).to.equal(testLicence.id) + expect(licence.includeInPresrocBilling).to.equal('yes') + expect(licence.includeInSrocBilling).to.equal(true) + }) + + it('calls `CreateLicenceSupplementaryYearsService` to handle persisting the financial years', async () => { + await PersistSupplementaryBillingFlagsService.go( + twoPartTariffFinancialYears, preSrocFlag, srocFlag, testLicence.id + ) + + expect(CreateLicenceSupplementaryYearService.go.called).to.be.true() + }) + }) + + describe('but no two-part tariff financial years', () => { + before(() => { + preSrocFlag = false + srocFlag = false + twoPartTariffFinancialYears = [] + }) + + it('persists the flags on the licence', async () => { + await PersistSupplementaryBillingFlagsService.go( + twoPartTariffFinancialYears, preSrocFlag, srocFlag, testLicence.id + ) + + const licence = await LicenceModel.query().findById(testLicence.id) + + expect(licence.id).to.equal(testLicence.id) + expect(licence.includeInPresrocBilling).to.equal('no') + expect(licence.includeInSrocBilling).to.equal(false) + }) + + it('does not call `CreateLicenceSupplementaryYearsService`', async () => { + await PersistSupplementaryBillingFlagsService.go( + twoPartTariffFinancialYears, preSrocFlag, srocFlag, testLicence.id + ) + + expect(CreateLicenceSupplementaryYearService.go.called).to.be.false() + }) + }) + }) + }) +}) diff --git a/test/services/licences/supplementary/process-imported-licence.service.test.js b/test/services/licences/supplementary/process-imported-licence.service.test.js new file mode 100644 index 0000000000..cbccfd9370 --- /dev/null +++ b/test/services/licences/supplementary/process-imported-licence.service.test.js @@ -0,0 +1,271 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() +const { expect } = Code + +// Things we need to stub +const FetchExistingLicenceDetailsService = require('../../../../app/services/licences/supplementary/fetch-existing-licence-details.service.js') +const PersistSupplementaryBillingFlagsService = require('../../../../app/services/licences/supplementary/persist-supplementary-billing-flags.service.js') + +// Thing under test +const ProcessImportedLicenceService = require('../../../../app/services/licences/supplementary/process-imported-licence.service.js') + +describe('Process Imported Licence Service', () => { + let clock + let testDate + let PersistSupplementaryBillingFlagsServiceStub + + beforeEach(() => { + testDate = new Date('2024-04-01') + clock = Sinon.useFakeTimers(testDate) + PersistSupplementaryBillingFlagsServiceStub = Sinon.stub(PersistSupplementaryBillingFlagsService, 'go').resolves() + }) + + afterEach(() => { + Sinon.restore() + clock.restore() + }) + + describe('when given a valid licenceId', () => { + let licence + let importedLicence + + beforeEach(async () => { + licence = '16b9f590-a231-4a38-b69b-7dde3ff907f2' + + importedLicence = { + expiredDate: null, + lapsedDate: null, + revokedDate: null + } + }) + + describe('and an imported licence', () => { + describe('with null expired date and pre sroc charge versions', () => { + beforeEach(() => { + Sinon.stub(FetchExistingLicenceDetailsService, 'go').resolves(_preSrocLicence(licence.id)) + }) + + it('flags the licence for pre sroc supplementary billing', async () => { + await ProcessImportedLicenceService.go(importedLicence, licence.id) + + expect(PersistSupplementaryBillingFlagsServiceStub.called).to.be.true() + expect(PersistSupplementaryBillingFlagsServiceStub.calledWith( + [], + true, + false, + licence.id + )).to.be.true() + }) + }) + + describe('with pre-sroc expired date and pre-sroc charge versions', () => { + beforeEach(() => { + importedLicence.expiredDate = new Date('2020-04-01') + Sinon.stub(FetchExistingLicenceDetailsService, 'go').resolves(_flaggedPreSrocLicence(licence.id)) + }) + + it('flags the licence for pre sroc supplementary billing', async () => { + await ProcessImportedLicenceService.go(importedLicence, licence.id) + + expect(PersistSupplementaryBillingFlagsServiceStub.called).to.be.true() + expect(PersistSupplementaryBillingFlagsServiceStub.calledWith( + [], + true, + false, + licence.id + )).to.be.true() + }) + }) + + describe('with pre-sroc expired date and two-part tariff charge versions', () => { + beforeEach(() => { + importedLicence.expiredDate = new Date('2020-04-01') + Sinon.stub(FetchExistingLicenceDetailsService, 'go').resolves(_preSrocTwoPartTariffLicence(licence.id)) + }) + + it('flags the licence for sroc supplementary billing', async () => { + await ProcessImportedLicenceService.go(importedLicence, licence.id) + + expect(PersistSupplementaryBillingFlagsServiceStub.called).to.be.true() + expect(PersistSupplementaryBillingFlagsServiceStub.calledWith( + [2023, 2024, 2025], + false, + true, + licence.id + )).to.be.true() + }) + }) + + describe('with null lapsed date and sroc charge versions', () => { + beforeEach(() => { + Sinon.stub(FetchExistingLicenceDetailsService, 'go').resolves(_srocLicence(licence.id, '2024-04-01')) + }) + + it('flags the licence for sroc supplementary billing', async () => { + await ProcessImportedLicenceService.go(importedLicence, licence.id) + + expect(PersistSupplementaryBillingFlagsServiceStub.called).to.be.true() + expect(PersistSupplementaryBillingFlagsServiceStub.calledWith( + [], + false, + true, + licence.id + )).to.be.true() + }) + }) + + describe('with sroc lapsed date and sroc charge versions', () => { + beforeEach(() => { + importedLicence.lapsedDate = new Date('2024-04-01') + Sinon.stub(FetchExistingLicenceDetailsService, 'go').resolves(_srocLicence(licence.id, null)) + }) + + it('flags the licence for sroc supplementary billing', async () => { + await ProcessImportedLicenceService.go(importedLicence, licence.id) + + expect(PersistSupplementaryBillingFlagsServiceStub.called).to.be.true() + expect(PersistSupplementaryBillingFlagsServiceStub.calledWith( + [], + false, + true, + licence.id + )).to.be.true() + }) + }) + + describe('with null revoked date and two-part tariff charge versions', () => { + beforeEach(() => { + Sinon.stub(FetchExistingLicenceDetailsService, 'go').resolves(_srocTwoPartTariffLicence(licence.id, '2024-04-01')) + }) + + it('flags the licence for sroc supplementary billing', async () => { + await ProcessImportedLicenceService.go(importedLicence, licence.id) + + expect(PersistSupplementaryBillingFlagsServiceStub.called).to.be.true() + expect(PersistSupplementaryBillingFlagsServiceStub.calledWith( + [2025], + false, + true, + licence.id + )).to.be.true() + }) + }) + + describe('with sroc revoked date and two-part tariff charge versions', () => { + beforeEach(() => { + importedLicence.revokedDate = new Date('2024-04-01') + Sinon.stub(FetchExistingLicenceDetailsService, 'go').resolves(_srocTwoPartTariffLicence(licence.id, null)) + }) + + it('flags the licence for sroc supplementary billing', async () => { + await ProcessImportedLicenceService.go(importedLicence, licence.id) + + expect(PersistSupplementaryBillingFlagsServiceStub.called).to.be.true() + expect(PersistSupplementaryBillingFlagsServiceStub.calledWith( + [2025], + false, + true, + licence.id + )).to.be.true() + }) + }) + + describe('with a revoked date thats set in the future', () => { + beforeEach(() => { + importedLicence.revokedDate = new Date('2025-04-01') + Sinon.stub(FetchExistingLicenceDetailsService, 'go').resolves(_futureRevokedDate()) + }) + + it('should not flag any supplementary billing', async () => { + await ProcessImportedLicenceService.go(importedLicence, licence.id) + + expect(PersistSupplementaryBillingFlagsService.go.called).to.be.false() + }) + }) + }) + }) +}) + +function _flaggedPreSrocLicence (id) { + return { + id, + expired_date: null, + lapsed_date: null, + revoked_date: null, + flagged_for_presroc: true, + flagged_for_sroc: false, + pre_sroc_charge_versions: true, + sroc_charge_versions: false, + two_part_tariff_charge_versions: false + } +} + +function _futureRevokedDate () { + return { + expired_date: null, + lapsed_date: null, + revoked_date: new Date('2024-04-01') + } +} + +function _preSrocLicence (id) { + return { + id, + expired_date: new Date('2021-04-01'), + lapsed_date: null, + revoked_date: null, + flagged_for_presroc: false, + flagged_for_sroc: false, + pre_sroc_charge_versions: true, + sroc_charge_versions: false, + two_part_tariff_charge_versions: false + } +} + +function _preSrocTwoPartTariffLicence (id) { + return { + id, + expired_date: null, + lapsed_date: null, + revoked_date: null, + flagged_for_presroc: false, + flagged_for_sroc: false, + pre_sroc_charge_versions: false, + sroc_charge_versions: true, + two_part_tariff_charge_versions: true + } +} + +function _srocLicence (id, date) { + return { + id, + expired_date: null, + lapsed_date: date ? new Date(date) : null, + revoked_date: null, + flagged_for_presroc: false, + flagged_for_sroc: false, + pre_sroc_charge_versions: false, + sroc_charge_versions: true, + two_part_tariff_charge_versions: false + } +} + +function _srocTwoPartTariffLicence (id, date) { + return { + id, + expired_date: null, + lapsed_date: null, + revoked_date: date ? new Date(date) : null, + flagged_for_presroc: false, + flagged_for_sroc: false, + pre_sroc_charge_versions: false, + sroc_charge_versions: true, + two_part_tariff_charge_versions: true + } +}