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

Move determineChargePeriod() to a service #150

Merged
merged 7 commits into from
Mar 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use strict'

/**
* Determines what the charge period is for a charge version in a given billing period
* @module DetermineChargePeriodService
*/

/**
* Returns a charge period, which is an object comprising `startDate` and `endDate`
*
* The charge period is determined as the overlap between the charge version's start and end dates, and the financial
* year. So the charge period's start date is the later of the two's start dates, and the charge period end date is the
* earlier of the two's end dates.
*
* > Charge versions may not have an end date; in this case, we simply use the financial year end date.
*
* @param {module:ChargeVersionModel} chargeVersion the charge version being processed for billing
* @param {number} financialYearEnding the year the financial billing period ends
*
* @returns {Object} the start and end date of the calculated charge period
*/
function go (chargeVersion, financialYearEnding) {
const financialYearStartDate = new Date(financialYearEnding - 1, 3, 1)
const financialYearEndDate = new Date(financialYearEnding, 2, 31)

if (_periodIsInvalid(chargeVersion, financialYearStartDate, financialYearEndDate)) {
throw new Error(`Charge version is outside billing period ${financialYearEnding}`)
}

const chargeVersionStartDate = chargeVersion.startDate
const latestStartDateTimestamp = Math.max(financialYearStartDate, chargeVersionStartDate)

// If the charge version has no end date then use the financial year end date instead
const chargeVersionEndDate = chargeVersion.endDate || financialYearEndDate
const earliestEndDateTimestamp = Math.min(financialYearEndDate, chargeVersionEndDate)

return {
startDate: new Date(latestStartDateTimestamp),
endDate: new Date(earliestEndDateTimestamp)
}
}

/**
* Determine if the charge version starts after or ends before the billing period
*
* We never expect a charge version outside the financial period but we do this just to ensure we never return
* nonsense and all possible scenarios are covered in our tests 😁
*
* @param {Object} chargeVersion chargeVersion being billed
* @param {Date} financialYearStartDate billing period (financial year) start date
* @param {Date} financialYearEndDate billing period (financial year) end date
*
* @returns {boolean} true if invalid else false
*/
function _periodIsInvalid (chargeVersion, financialYearStartDate, financialYearEndDate) {
const chargeVersionStartsAfterFinancialYear = chargeVersion.startDate > financialYearEndDate
const chargeVersionEndsBeforeFinancialYear = chargeVersion.endDate && chargeVersion.endDate < financialYearStartDate

return chargeVersionStartsAfterFinancialYear || chargeVersionEndsBeforeFinancialYear
}

module.exports = {
go
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

const AbstractionBillingPeriodService = require('./abstraction-billing-period.service.js')
const ConsolidateDateRangesService = require('./consolidate-date-ranges.service.js')
const DetermineChargePeriodService = require('./determine-charge-period.service.js')

/**
* Takes a charge element, charge version and financial year and returns an object representing an sroc transaction
Expand All @@ -27,7 +28,7 @@ const ConsolidateDateRangesService = require('./consolidate-date-ranges.service.
function go (chargeElement, chargeVersion, financialYearEnding, options) {
const optionsData = _optionsDefaults(options)

const chargePeriod = _determineChargePeriod(chargeVersion, financialYearEnding)
const chargePeriod = DetermineChargePeriodService.go(chargeVersion, financialYearEnding)

return {
chargeElementId: chargeElement.chargeElementId,
Expand Down Expand Up @@ -78,31 +79,6 @@ function _optionsDefaults (options) {
}
}

/**
* Returns a charge period, which is an object comprising `startDate` and `endDate`
*
* The charge period is determined as the overlap between the charge version's start and end dates, and the financial
* year. So the charge period's start date is the later of the two's start dates, and the charge period end date is the
* earlier of the two's end dates.
*
* Note that charge versions may not have an end date; in this case, we simply use the financial year end date.
*/
function _determineChargePeriod (chargeVersion, financialYearEnding) {
const financialYearStartDate = new Date(financialYearEnding - 1, 3, 1)
const chargeVersionStartDate = chargeVersion.startDate
const latestStartDateTimestamp = Math.max(financialYearStartDate, chargeVersionStartDate)

const financialYearEndDate = new Date(financialYearEnding, 2, 31)
// If the charge version has no end date then use the financial year end date instead
const chargeVersionEndDate = chargeVersion.endDate || financialYearEndDate
const earliestEndDateTimestamp = Math.min(financialYearEndDate, chargeVersionEndDate)

return {
startDate: new Date(latestStartDateTimestamp),
endDate: new Date(earliestEndDateTimestamp)
}
}

/**
* Calculates the number of authorised days, ie. the number of overlapping days of the financial year and the charge
* element's abstraction periods
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
'use strict'

// Test framework dependencies
const Lab = require('@hapi/lab')
const Code = require('@hapi/code')

const { describe, it, beforeEach } = exports.lab = Lab.script()
const { expect } = Code

// Thing under test
const DetermineChargePeriodService = require('../../../app/services/supplementary-billing/determine-charge-period.service.js')

describe('Determine charge period service', () => {
const financialYear = {
startDate: new Date('2022-04-01'),
endDate: new Date('2023-03-31'),
yearEnding: 2023
}
let chargeVersion

describe('charge version starts inside the financial period', () => {
beforeEach(() => {
chargeVersion = {
startDate: new Date('2022-05-01'),
endDate: null
}
})

describe('and the charge version has an end date', () => {
beforeEach(() => {
chargeVersion.endDate = new Date('2022-05-31')
})

it('returns the charge version start and end dates', () => {
const result = DetermineChargePeriodService.go(chargeVersion, financialYear.yearEnding)

expect(result.startDate).to.equal(chargeVersion.startDate)
expect(result.endDate).to.equal(chargeVersion.endDate)
})
})

describe('and the charge version does not have an end date', () => {
it('returns the charge version start date and financial year end date', () => {
const result = DetermineChargePeriodService.go(chargeVersion, financialYear.yearEnding)

expect(result.startDate).to.equal(chargeVersion.startDate)
expect(result.endDate).to.equal(financialYear.endDate)
})
})
})

describe('financial period starts inside the charge version', () => {
beforeEach(() => {
chargeVersion = {
startDate: new Date('2022-02-01'),
endDate: null
}
})

describe('and the charge version has an end date', () => {
beforeEach(() => {
chargeVersion.endDate = new Date('2023-05-31')
})

it('returns the financial start and end dates', () => {
const result = DetermineChargePeriodService.go(chargeVersion, financialYear.yearEnding)

expect(result.startDate).to.equal(financialYear.startDate)
expect(result.endDate).to.equal(financialYear.endDate)
})
})

describe('and the charge version does not have an end date', () => {
it('returns the financial start and end dates', () => {
const result = DetermineChargePeriodService.go(chargeVersion, financialYear.yearEnding)

expect(result.startDate).to.equal(financialYear.startDate)
expect(result.endDate).to.equal(financialYear.endDate)
})
})
})

describe('charge version starts before the financial period', () => {
beforeEach(() => {
chargeVersion = {
startDate: new Date('2022-02-01'),
endDate: null
}
})

describe('and the charge version has an end date that is inside the financial period', () => {
beforeEach(() => {
chargeVersion.endDate = new Date('2022-05-31')
})

it('returns the financial start and charge version end date', () => {
const result = DetermineChargePeriodService.go(chargeVersion, financialYear.yearEnding)

expect(result.startDate).to.equal(financialYear.startDate)
expect(result.endDate).to.equal(chargeVersion.endDate)
})
})

describe('and the charge version does not have an end date', () => {
it('returns the financial start and end dates', () => {
const result = DetermineChargePeriodService.go(chargeVersion, financialYear.yearEnding)

expect(result.startDate).to.equal(financialYear.startDate)
expect(result.endDate).to.equal(financialYear.endDate)
})
})
})

describe('financial period starts before the charge version', () => {
beforeEach(() => {
chargeVersion = {
startDate: new Date('2022-05-01'),
endDate: null
}
})

describe('and the charge version has an end date that is outside the financial period', () => {
beforeEach(() => {
chargeVersion.endDate = new Date('2023-05-31')
})

it('returns the charge version start date and financial period end date', () => {
const result = DetermineChargePeriodService.go(chargeVersion, financialYear.yearEnding)

expect(result.startDate).to.equal(chargeVersion.startDate)
expect(result.endDate).to.equal(financialYear.endDate)
})
})

describe('and the charge version does not have an end date', () => {
it('returns the charge version start date and financial period end date', () => {
const result = DetermineChargePeriodService.go(chargeVersion, financialYear.yearEnding)

expect(result.startDate).to.equal(chargeVersion.startDate)
expect(result.endDate).to.equal(financialYear.endDate)
})
})
})

describe('neither period overlaps', () => {
describe('because the charge version start date is after the financial period', () => {
beforeEach(() => {
chargeVersion = {
startDate: new Date('2023-05-01'),
endDate: null
}
})

it('throws an error', () => {
expect(() => DetermineChargePeriodService.go(chargeVersion, financialYear.yearEnding)).to.throw()
})
})

describe('because the charge version end date is before the financial period', () => {
beforeEach(() => {
chargeVersion = {
startDate: new Date('2021-05-01'),
endDate: new Date('2021-05-31')
}
})

it('throws an error', () => {
expect(() => DetermineChargePeriodService.go(chargeVersion, financialYear.yearEnding)).to.throw()
})
})
})
})