From eeeab75e173cb60332d57b32d519bd58f4154328 Mon Sep 17 00:00:00 2001 From: Rebecca Ransome Date: Thu, 23 Nov 2023 15:20:55 +0000 Subject: [PATCH 1/5] Adding a periods overlap helper https://eaflood.atlassian.net/browse/WATER-4188 This PR is focused on adding a helper method for the two-part-tariff work. The helper method is used to determine if two dates it has been given overlap or not. From f58a97d019d664f86ec57d4f371780d8c831d47c Mon Sep 17 00:00:00 2001 From: Rebecca Ransome Date: Thu, 23 Nov 2023 16:21:11 +0000 Subject: [PATCH 2/5] Adding periodsOverlap helper --- app/lib/general.lib.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/app/lib/general.lib.js b/app/lib/general.lib.js index 195174711b..b44524d612 100644 --- a/app/lib/general.lib.js +++ b/app/lib/general.lib.js @@ -27,6 +27,30 @@ function generateUUID () { return randomUUID({ disableEntropyCache: true }) } +/** + * Checks if any of the periods in checkPeriods overlap with any period in referencePeriods + * @param {*} referencePeriods Array of objects representing periods with startDate and endDate properties + * @param {*} checkPeriods Array of objects representing periods to check with startDate and endDate properties + * @returns Returns true if there is an overlap, otherwise false + */ +function periodsOverlap (referencePeriods, checkPeriods) { + for (const referencePeriod of referencePeriods) { + const overLappingPeriods = checkPeriods.filter((checkPeriod) => { + if (checkPeriod.startDate > referencePeriod.endDate || referencePeriod.startDate > checkPeriod.endDate) { + return false + } + + return true + }) + + if (overLappingPeriods.length) { + return true + } + } + + return false +} + /** * Returns the current date and time as an ISO string * @@ -44,5 +68,6 @@ function timestampForPostgres () { module.exports = { generateUUID, + periodsOverlap, timestampForPostgres } From 6ac5d327b041cd74f5c5d792f0487d7f65acfff7 Mon Sep 17 00:00:00 2001 From: Rebecca Ransome Date: Thu, 23 Nov 2023 16:30:59 +0000 Subject: [PATCH 3/5] Adding unit tests for overlap date checker --- test/lib/general.lib.test.js | 44 ++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/lib/general.lib.test.js b/test/lib/general.lib.test.js index ba58b40656..dd3ed33a4c 100644 --- a/test/lib/general.lib.test.js +++ b/test/lib/general.lib.test.js @@ -48,4 +48,48 @@ describe('RequestLib', () => { expect(result).to.equal('2015-10-21T20:31:57.000Z') }) }) + + describe.only('#periodsOverlap', () => { + let referencePeriod + let checkPeriod + describe('when given dates that do not overlap', () => { + beforeEach(() => { + referencePeriod = [{ + startDate: new Date('2023-02-01'), + endDate: new Date('2023-02-02') + }] + + checkPeriod = [{ + startDate: new Date('2023-01-01'), + endDate: new Date('2023-01-31') + }] + }) + + it('returns false', () => { + const result = GeneralLib.periodsOverlap(referencePeriod, checkPeriod) + + expect(result).to.equal(false) + }) + }) + + describe('when given dates that do overlap', () => { + beforeEach(() => { + referencePeriod = [{ + startDate: new Date('2023-01-01'), + endDate: new Date('2023-01-02') + }] + + checkPeriod = [{ + startDate: new Date('2023-01-01'), + endDate: new Date('2023-01-31') + }] + }) + + it('returns true', () => { + const result = GeneralLib.periodsOverlap(referencePeriod, checkPeriod) + + expect(result).to.equal(true) + }) + }) + }) }) From 2108496cfd0aa3fab20fc3f4daad88d2770498ae Mon Sep 17 00:00:00 2001 From: Rebecca Ransome Date: Thu, 23 Nov 2023 16:33:38 +0000 Subject: [PATCH 4/5] Removing .only --- test/lib/general.lib.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/lib/general.lib.test.js b/test/lib/general.lib.test.js index dd3ed33a4c..1659552b2d 100644 --- a/test/lib/general.lib.test.js +++ b/test/lib/general.lib.test.js @@ -49,9 +49,10 @@ describe('RequestLib', () => { }) }) - describe.only('#periodsOverlap', () => { + describe('#periodsOverlap', () => { let referencePeriod let checkPeriod + describe('when given dates that do not overlap', () => { beforeEach(() => { referencePeriod = [{ From fc12d95a932af3d9797bcc17ba8385776a721754 Mon Sep 17 00:00:00 2001 From: Rebecca Ransome Date: Fri, 24 Nov 2023 09:26:00 +0000 Subject: [PATCH 5/5] Refactor period overlap comments and test --- app/lib/general.lib.js | 31 +++++++++++-- test/lib/general.lib.test.js | 88 ++++++++++++++++++++++++++++++++++-- 2 files changed, 111 insertions(+), 8 deletions(-) diff --git a/app/lib/general.lib.js b/app/lib/general.lib.js index b44524d612..bf7ed1de70 100644 --- a/app/lib/general.lib.js +++ b/app/lib/general.lib.js @@ -28,10 +28,33 @@ function generateUUID () { } /** - * Checks if any of the periods in checkPeriods overlap with any period in referencePeriods - * @param {*} referencePeriods Array of objects representing periods with startDate and endDate properties - * @param {*} checkPeriods Array of objects representing periods to check with startDate and endDate properties - * @returns Returns true if there is an overlap, otherwise false + * Tests if one set of periods (represented by a start and end date) overlaps with another + * + * Added as part of two-part tariff and the need to match returns and lines to charge elements. A common complication in + * WRLS is the need to convert an abstract period, for example '1 Nov to 31 Mar' to a concrete period (2023-11-01 to + * 2024-03-31). It gets even more complex when the period crosses over another, for example the start or end of a + * billing period. Then the only way to represent the abstract period in a usable way is as 2 separate periods. Hence + * this service deals with arrays of periods. + * + * > See the comments for `DetermineAbstractionPeriodService` to better understand the complexity of going from + * > abstract to concrete periods + * + * Then there are times we need to test if the periods of one thing overlap with another. In two-part tariff that's the + * abstraction periods of a charge element with those of a return. If _any_ of the periods overlap then the return is + * 'matched' and can be allocated to the charge element. + * + * This method iterates through the `referencePeriods`. It then filters the `checkPeriods` by testing if any of them + * overlap with the `referencePeriod`. If any do `checkPeriods.filter()` will return a non-empty array at which point + * `periodsOverlap()` will return `true`. + * + * Else, having compared all the `checkPeriods` against each `referencePeriod` and finding no overlaps the function will + * return false. + * + * @param {Object[]} referencePeriods Each period is an object containing a `startDate` and `endDate` property + * @param {Object[]} checkPeriods Each period is an object containing a `startDate` and `endDate` property. These + * periods will be checked against the `referencePeriods for any overlaps + * + * @returns {boolean} Returns true if there _any_ check period overlaps with a reference period, else false */ function periodsOverlap (referencePeriods, checkPeriods) { for (const referencePeriod of referencePeriods) { diff --git a/test/lib/general.lib.test.js b/test/lib/general.lib.test.js index 1659552b2d..42d6baa88d 100644 --- a/test/lib/general.lib.test.js +++ b/test/lib/general.lib.test.js @@ -53,7 +53,7 @@ describe('RequestLib', () => { let referencePeriod let checkPeriod - describe('when given dates that do not overlap', () => { + describe('when given periods that do not overlap', () => { beforeEach(() => { referencePeriod = [{ startDate: new Date('2023-02-01'), @@ -73,17 +73,97 @@ describe('RequestLib', () => { }) }) - describe('when given dates that do overlap', () => { + describe('when a check period overlaps the start of a reference period', () => { beforeEach(() => { referencePeriod = [{ - startDate: new Date('2023-01-01'), - endDate: new Date('2023-01-02') + startDate: new Date('2023-02-01'), + endDate: new Date('2023-02-28') }] checkPeriod = [{ + startDate: new Date('2023-01-15'), + endDate: new Date('2023-02-15') + }] + }) + + it('returns true', () => { + const result = GeneralLib.periodsOverlap(referencePeriod, checkPeriod) + + expect(result).to.equal(true) + }) + }) + + describe('when a check period overlaps the end of a reference period', () => { + beforeEach(() => { + referencePeriod = [{ startDate: new Date('2023-01-01'), endDate: new Date('2023-01-31') }] + + checkPeriod = [{ + startDate: new Date('2023-01-15'), + endDate: new Date('2023-02-15') + }] + }) + + it('returns true', () => { + const result = GeneralLib.periodsOverlap(referencePeriod, checkPeriod) + + expect(result).to.equal(true) + }) + }) + + describe('when a reference period is completely inside a check period', () => { + beforeEach(() => { + referencePeriod = [{ + startDate: new Date('2023-02-01'), + endDate: new Date('2023-02-15') + }] + + checkPeriod = [{ + startDate: new Date('2023-01-01'), + endDate: new Date('2023-02-28') + }] + }) + + it('returns true', () => { + const result = GeneralLib.periodsOverlap(referencePeriod, checkPeriod) + + expect(result).to.equal(true) + }) + }) + + describe('when a check period is completely inside a reference period', () => { + beforeEach(() => { + referencePeriod = [{ + startDate: new Date('2023-01-01'), + endDate: new Date('2023-02-28') + }] + + checkPeriod = [{ + startDate: new Date('2023-02-01'), + endDate: new Date('2023-02-15') + }] + }) + + it('returns true', () => { + const result = GeneralLib.periodsOverlap(referencePeriod, checkPeriod) + + expect(result).to.equal(true) + }) + }) + + describe('when the periods are the same', () => { + beforeEach(() => { + referencePeriod = [{ + startDate: new Date('2023-02-01'), + endDate: new Date('2023-02-28') + }] + + checkPeriod = [{ + startDate: new Date('2023-02-01'), + endDate: new Date('2023-02-28') + }] }) it('returns true', () => {