-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add Backend.getNextTransactionDateConstraints helper
- Loading branch information
Showing
3 changed files
with
152 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import type * as Core from '../core'; | ||
import type * as Rels from './Rels'; | ||
|
||
import { getTimeFromFrequency } from './getTimeFromFrequency.js'; | ||
import jsonata from 'jsonata'; | ||
|
||
type Rules = Core.Resource<Rels.CustomerPortalSettings>['subscriptions']['allowNextDateModification']; | ||
type Subscription = Omit<Core.Resource<Rels.Subscription>, '_links' | '_embedded'>; | ||
|
||
export type Constraints = { | ||
min?: string; | ||
max?: string; | ||
disallowedDates?: string[]; | ||
allowedDaysOfWeek?: number[]; | ||
allowedDaysOfMonth?: number[]; | ||
}; | ||
|
||
/** | ||
* Finds which next transaction date modification rules are applicable to | ||
* the given subscription and merges them together. | ||
* | ||
* @param data Subscription to generate constraints for. | ||
* @param rules Next date modification config from the customer portal settings. | ||
* | ||
* @returns | ||
* Returns true if all modifications are allowed, false if next date can't | ||
* be changed by the customer, object with constraints in any other case. | ||
*/ | ||
export function getNextTransactionDateConstraints(data: Subscription, rules: Rules): Constraints | boolean { | ||
if (typeof rules === 'boolean') return rules; | ||
|
||
const combinedRule = rules | ||
.filter(rule => Boolean(jsonata(rule.jsonataQuery).evaluate(data))) | ||
.reduce((result, rule) => { | ||
if (rule.min) { | ||
const currentTime = result.min ? getTimeFromFrequency(result.min) : Infinity; | ||
const proposedTime = getTimeFromFrequency(rule.min); | ||
if (proposedTime < currentTime) result.min = rule.min; | ||
} | ||
|
||
if (rule.max) { | ||
const currentTime = result.max ? getTimeFromFrequency(result.max) : -Infinity; | ||
const proposedTime = getTimeFromFrequency(rule.max); | ||
if (proposedTime > currentTime) result.max = rule.max; | ||
} | ||
|
||
if (rule.allowedDays?.type === 'day') { | ||
const previousSet = result.allowedDaysOfWeek ?? []; | ||
const expandedSet = [...previousSet, ...rule.allowedDays.days]; | ||
result.allowedDaysOfWeek = Array.from(new Set(expandedSet)); | ||
} | ||
|
||
if (rule.allowedDays?.type === 'month') { | ||
const previousSet = result.allowedDaysOfMonth ?? []; | ||
const expandedSet = [...previousSet, ...rule.allowedDays.days]; | ||
result.allowedDaysOfMonth = Array.from(new Set(expandedSet)); | ||
} | ||
|
||
if (rule.disallowedDates) { | ||
const previousSet = result.disallowedDates ?? []; | ||
const expandedSet = [...previousSet, ...rule.disallowedDates]; | ||
result.disallowedDates = Array.from(new Set(expandedSet)); | ||
} | ||
|
||
return result; | ||
}, {} as Constraints); | ||
|
||
return Object.keys(combinedRule).length === 0 ? false : combinedRule; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import { getNextTransactionDateConstraints as getConstraints } from '../../src/backend'; | ||
|
||
const sampleData = { | ||
date_created: '2013-08-19T10:58:39-0700', | ||
date_modified: '2013-08-19T10:58:39-0700', | ||
end_date: null, | ||
error_message: '', | ||
first_failed_transaction_date: null, | ||
frequency: '1m', | ||
is_active: false, | ||
next_transaction_date: '2014-05-01T00:00:00-0700', | ||
past_due_amount: 0, | ||
start_date: '2010-09-15T00:00:00-0700', | ||
third_party_id: '', | ||
}; | ||
|
||
describe('Backend', () => { | ||
describe('getNextTransactionDateConstraints', () => { | ||
it('returns false if rules argument is false', () => { | ||
expect(getConstraints(sampleData, false)).toBe(false); | ||
}); | ||
|
||
it('returns false if none of the rules apply', () => { | ||
const rules = [{ jsonataQuery: '$contains(frequency, "d")' }, { jsonataQuery: '$contains(frequency, "d")' }]; | ||
expect(getConstraints(sampleData, rules)).toBe(false); | ||
}); | ||
|
||
it('returns true if rules argument is true', () => { | ||
expect(getConstraints(sampleData, true)).toBe(true); | ||
}); | ||
|
||
it('picks the smallest of the applicable min properties', () => { | ||
const rules = [ | ||
{ jsonataQuery: '$contains(frequency, "m")', min: '4y' }, | ||
{ jsonataQuery: '$contains(frequency, "m")', min: '2w' }, | ||
{ jsonataQuery: '$contains(frequency, "d")', min: '1d' }, | ||
]; | ||
|
||
expect(getConstraints(sampleData, rules)).toHaveProperty('min', '2w'); | ||
}); | ||
|
||
it('picks the greatest of the applicable max properties', () => { | ||
const rules = [ | ||
{ jsonataQuery: '$contains(frequency, "m")', max: '2w' }, | ||
{ jsonataQuery: '$contains(frequency, "m")', max: '4m' }, | ||
{ jsonataQuery: '$contains(frequency, "d")', max: '7y' }, | ||
]; | ||
|
||
expect(getConstraints(sampleData, rules)).toHaveProperty('max', '4m'); | ||
}); | ||
|
||
it('combines and dedupes all applicable disallowed dates', () => { | ||
const rules = [ | ||
{ disallowedDates: ['2020-03-14'], jsonataQuery: '$contains(frequency, "m")' }, | ||
{ disallowedDates: ['2020-03-14', '2021-02-18'], jsonataQuery: '$contains(frequency, "m")' }, | ||
{ disallowedDates: ['2019-03-26'], jsonataQuery: '$contains(frequency, "d")' }, | ||
]; | ||
|
||
expect(getConstraints(sampleData, rules)).toHaveProperty('disallowedDates', ['2020-03-14', '2021-02-18']); | ||
}); | ||
|
||
it('combines and dedupes all applicable allowed days of week', () => { | ||
const rules = [ | ||
{ allowedDays: { days: [1], type: 'day' as const }, jsonataQuery: '$contains(frequency, "m")' }, | ||
{ allowedDays: { days: [1, 2], type: 'day' as const }, jsonataQuery: '$contains(frequency, "m")' }, | ||
{ allowedDays: { days: [3], type: 'day' as const }, jsonataQuery: '$contains(frequency, "d")' }, | ||
]; | ||
|
||
expect(getConstraints(sampleData, rules)).toHaveProperty('allowedDaysOfWeek', [1, 2]); | ||
}); | ||
|
||
it('combines and dedupes all applicable allowed days of month', () => { | ||
const rules = [ | ||
{ allowedDays: { days: [23], type: 'month' as const }, jsonataQuery: '$contains(frequency, "m")' }, | ||
{ allowedDays: { days: [23, 31], type: 'month' as const }, jsonataQuery: '$contains(frequency, "m")' }, | ||
{ allowedDays: { days: [10], type: 'month' as const }, jsonataQuery: '$contains(frequency, "d")' }, | ||
]; | ||
|
||
expect(getConstraints(sampleData, rules)).toHaveProperty('allowedDaysOfMonth', [23, 31]); | ||
}); | ||
}); | ||
}); |