@oneblink/sdk-core 10.0.0 → 10.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/calculationService.d.ts +144 -0
- package/dist/calculationService.js +297 -0
- package/dist/calculationService.js.map +1 -0
- package/dist/conditionalLogicService/evaluateConditionalSubmissionTimestampPredicate.js +1 -1
- package/dist/conditionalLogicService/evaluateConditionalSubmissionTimestampPredicate.js.map +1 -1
- package/dist/conditionalLogicService/types.d.ts +5 -0
- package/dist/conditionalLogicService/types.js +5 -1
- package/dist/conditionalLogicService/types.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/paymentService/getPaymentProvider.d.ts +9 -0
- package/dist/paymentService/getPaymentProvider.js +20 -0
- package/dist/paymentService/getPaymentProvider.js.map +1 -0
- package/dist/paymentService/providers/bpoint.d.ts +7 -0
- package/dist/paymentService/providers/bpoint.js +76 -0
- package/dist/paymentService/providers/bpoint.js.map +1 -0
- package/dist/paymentService/providers/cpPay.d.ts +7 -0
- package/dist/paymentService/providers/cpPay.js +119 -0
- package/dist/paymentService/providers/cpPay.js.map +1 -0
- package/dist/paymentService/providers/nswGovPay.d.ts +7 -0
- package/dist/paymentService/providers/nswGovPay.js +90 -0
- package/dist/paymentService/providers/nswGovPay.js.map +1 -0
- package/dist/paymentService/providers/westpacQuickStream.d.ts +7 -0
- package/dist/paymentService/providers/westpacQuickStream.js +64 -0
- package/dist/paymentService/providers/westpacQuickStream.js.map +1 -0
- package/dist/paymentService/types.d.ts +25 -0
- package/dist/paymentService/types.js +2 -0
- package/dist/paymentService/types.js.map +1 -0
- package/dist/paymentService.d.ts +19 -15
- package/dist/paymentService.js +61 -296
- package/dist/paymentService.js.map +1 -1
- package/package.json +4 -1
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { FormTypes, SubmissionTypes } from '@oneblink/types';
|
|
2
|
+
import { type ParseDayOnlyDate } from './conditionalLogicService/types.js';
|
|
3
|
+
export type { ParseDayOnlyDate } from './conditionalLogicService/types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Evaluate a OneBlink calculation expression against form submission data.
|
|
6
|
+
*
|
|
7
|
+
* Day-only (`YYYY-MM-DD`) date parsing is injected via `parseDayOnlyDate` so
|
|
8
|
+
* callers control timezone-aware behaviour (client vs server), matching the
|
|
9
|
+
* pattern used by `conditionalLogicService`. Other date strings use
|
|
10
|
+
* `new Date(value)`.
|
|
11
|
+
*
|
|
12
|
+
* #### Expression syntax
|
|
13
|
+
*
|
|
14
|
+
* Expressions are mathematical / logical formulas. Form element values are
|
|
15
|
+
* referenced with `{ELEMENT:<elementName>}` tags (including nested paths via
|
|
16
|
+
* `|`, e.g. `{ELEMENT:Children|Age}`).
|
|
17
|
+
*
|
|
18
|
+
* ##### Literals
|
|
19
|
+
*
|
|
20
|
+
* - **Numbers** — e.g. `0`, `5`, `5.4`
|
|
21
|
+
* - **Strings** — single or double quoted, e.g. `"abc"`, `'abc'`
|
|
22
|
+
* - **Booleans** — `true`, `false`
|
|
23
|
+
*
|
|
24
|
+
* ##### Arithmetic operators
|
|
25
|
+
*
|
|
26
|
+
* | Operator | Description | Example |
|
|
27
|
+
* | -------- | ------------------------------------ | -------------- |
|
|
28
|
+
* | `+` | Addition (also unary plus) | `1 + 2` → `3` |
|
|
29
|
+
* | `-` | Subtraction (also unary minus) | `1 - 2` → `-1` |
|
|
30
|
+
* | `*` | Multiplication | `4 * 2` → `8` |
|
|
31
|
+
* | `/` | Division | `4 / 2` → `2` |
|
|
32
|
+
* | `%` | Remainder | `8 % 3` → `2` |
|
|
33
|
+
* | `++` | Unary increment | `++2` → `3` |
|
|
34
|
+
* | `--` | Unary decrement | `--2` → `1` |
|
|
35
|
+
*
|
|
36
|
+
* Standard operator precedence applies. Use parentheses to override it, e.g.
|
|
37
|
+
* `3 * (2 + 1)` → `9`.
|
|
38
|
+
*
|
|
39
|
+
* ##### Comparison operators
|
|
40
|
+
*
|
|
41
|
+
* | Operator | Description | Example |
|
|
42
|
+
* | -------- | --------------------- | ------------------- |
|
|
43
|
+
* | `==` | Equal | `2 == 2` → `true` |
|
|
44
|
+
* | `!=` | Not equal | `2 != 3` → `true` |
|
|
45
|
+
* | `>` | Greater than | `2 > 1` → `true` |
|
|
46
|
+
* | `>=` | Greater than or equal | `2 >= 2` → `true` |
|
|
47
|
+
* | `<` | Less than | `2 < 3` → `true` |
|
|
48
|
+
* | `<=` | Less than or equal | `2 <= 2` → `true` |
|
|
49
|
+
*
|
|
50
|
+
* ##### Logical operators
|
|
51
|
+
*
|
|
52
|
+
* | Operator | Description | Example |
|
|
53
|
+
* | -------- | ------------------------------------ | -------------------------- |
|
|
54
|
+
* | `&&` | Logical AND (returns last operand) | `true && false` → `false` |
|
|
55
|
+
* | `\|\|` | Logical OR (returns first truthy) | `false \|\| true` → `true` |
|
|
56
|
+
* | `!` | Logical NOT | `!false` → `true` |
|
|
57
|
+
*
|
|
58
|
+
* ##### Element references
|
|
59
|
+
*
|
|
60
|
+
* - `{ELEMENT:Number}` — value of the root element named `Number`
|
|
61
|
+
* - `{ELEMENT:Set\|Child}` — value of `Child` within repeatable set / nested
|
|
62
|
+
* form element `Set`
|
|
63
|
+
* - Nested paths may include multiple segments:
|
|
64
|
+
* `{ELEMENT:Parents\|Children\|Age}`
|
|
65
|
+
*
|
|
66
|
+
* How values are coerced for evaluation:
|
|
67
|
+
*
|
|
68
|
+
* - Numbers are used as-is
|
|
69
|
+
* - Day-only (`YYYY-MM-DD`) strings are parsed via `parseDayOnlyDate`; other
|
|
70
|
+
* non-numeric date strings use `new Date(value)`. Valid dates use
|
|
71
|
+
* `Date#getTime()`, otherwise the string is parsed as a float via
|
|
72
|
+
* `parseFloat`
|
|
73
|
+
* - Arrays of numeric strings (e.g. checkboxes) are summed
|
|
74
|
+
* - Repeatable set entries sum the referenced nested numeric values
|
|
75
|
+
* - Empty arrays yield `NaN` (calculation result becomes `undefined`)
|
|
76
|
+
* - Compliance element objects use their `value` property
|
|
77
|
+
*
|
|
78
|
+
* ##### Built-in functions
|
|
79
|
+
*
|
|
80
|
+
* | Function | Description | Example |
|
|
81
|
+
* | -------------------------------- | --------------------------------------------------------------------------- | ------------------------------- |
|
|
82
|
+
* | `ROUND(value, precision)` | Round `value` to `precision` decimal places (correct floating-point rounds) | `ROUND(1.255, 2)` → `1.26` |
|
|
83
|
+
* | `ROUND_DOWN(value)` | Round down to the nearest integer (`Math.floor`) | `ROUND_DOWN(1.9)` → `1` |
|
|
84
|
+
* | `ROUND_UP(value)` | Round up to the nearest integer (`Math.ceil`) | `ROUND_UP(1.1)` → `2` |
|
|
85
|
+
* | `ISNULL(value, defaultValue)` | If `value` is unentered (`null`/`undefined`/`""`), return `defaultValue` or `0`; otherwise return `value` | `ISNULL({ELEMENT:A}, 10)` |
|
|
86
|
+
*
|
|
87
|
+
* `ROUND`, `ROUND_DOWN`, and `ROUND_UP` return `null` when `value` is `NaN` or
|
|
88
|
+
* not finite.
|
|
89
|
+
*
|
|
90
|
+
* ##### Examples
|
|
91
|
+
*
|
|
92
|
+
* ```js
|
|
93
|
+
* const value = calculationService.evaluateExpression({
|
|
94
|
+
* expression: '{ELEMENT:Quantity} * {ELEMENT:Price}',
|
|
95
|
+
* submission: { Quantity: 3, Price: 12.5 },
|
|
96
|
+
* formElements: form.elements,
|
|
97
|
+
* // Client: local timezone. Server: organisation timezone.
|
|
98
|
+
* parseDayOnlyDate: (value) => new Date(`${value}T00:00:00.000Z`),
|
|
99
|
+
* })
|
|
100
|
+
* // value === 37.5
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* ```js
|
|
104
|
+
* calculationService.evaluateExpression({
|
|
105
|
+
* expression: 'ROUND({ELEMENT:Amount} * 1.1, 2)',
|
|
106
|
+
* submission: { Amount: 100 },
|
|
107
|
+
* formElements: form.elements,
|
|
108
|
+
* parseDayOnlyDate: (value) => new Date(`${value}T00:00:00.000Z`),
|
|
109
|
+
* })
|
|
110
|
+
* // 110
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
* ```js
|
|
114
|
+
* calculationService.evaluateExpression({
|
|
115
|
+
* expression: 'ISNULL({ELEMENT:Optional}, 0) + {ELEMENT:Required}',
|
|
116
|
+
* submission: { Required: 5 },
|
|
117
|
+
* formElements: form.elements,
|
|
118
|
+
* parseDayOnlyDate: (value) => new Date(`${value}T00:00:00.000Z`),
|
|
119
|
+
* })
|
|
120
|
+
* // 5
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* @param options
|
|
124
|
+
* @returns The evaluated numeric result, or `undefined` when evaluation does
|
|
125
|
+
* not yield a valid number (including `NaN`). Throws when the expression
|
|
126
|
+
* cannot be parsed.
|
|
127
|
+
*/
|
|
128
|
+
export declare function evaluateExpression({ expression, submission, formElements, parseDayOnlyDate, }: {
|
|
129
|
+
/** The calculation expression string to evaluate */
|
|
130
|
+
expression: string;
|
|
131
|
+
/** Form submission data used to resolve `{ELEMENT:...}` references */
|
|
132
|
+
submission: SubmissionTypes.S3SubmissionData['submission'];
|
|
133
|
+
/**
|
|
134
|
+
* Form elements definition. Used to resolve nested `form` element values
|
|
135
|
+
* referenced in the expression.
|
|
136
|
+
*/
|
|
137
|
+
formElements: FormTypes.FormElement[];
|
|
138
|
+
/**
|
|
139
|
+
* Parse `YYYY-MM-DD` strings when resolving date element values. sdk-core
|
|
140
|
+
* only calls this for day-only values; other date strings use
|
|
141
|
+
* `new Date(value)`.
|
|
142
|
+
*/
|
|
143
|
+
parseDayOnlyDate: ParseDayOnlyDate;
|
|
144
|
+
}): number | undefined;
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import ExpressionParser from 'morph-expressions';
|
|
2
|
+
import { findFormElement, matchElementsTagRegex, } from './formElementsService.js';
|
|
3
|
+
import { DATE_ONLY_PATTERN, } from './conditionalLogicService/types.js';
|
|
4
|
+
const isUnenteredValue = (value) => {
|
|
5
|
+
return !value && value !== 0;
|
|
6
|
+
};
|
|
7
|
+
const isObjectWithValue = (obj) => {
|
|
8
|
+
return typeof obj === 'object' && obj !== null && 'value' in obj;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Escape special RegExp characters in a string so it can be used as a literal
|
|
12
|
+
* pattern.
|
|
13
|
+
*/
|
|
14
|
+
function escapeRegExp(value) {
|
|
15
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Workaround for `.toFixed()` not rounding floating point numbers correctly.
|
|
19
|
+
*/
|
|
20
|
+
function roundToFixed(number, decimals) {
|
|
21
|
+
const multiplier = Math.pow(10, decimals);
|
|
22
|
+
const roundedNumber = Math.round(number * multiplier) / multiplier;
|
|
23
|
+
return roundedNumber.toFixed(decimals);
|
|
24
|
+
}
|
|
25
|
+
function resolveElementValue({ submission, nestedElementNames, formElements, parseDayOnlyDate, }) {
|
|
26
|
+
const defaultAccumulator = submission[nestedElementNames[0]];
|
|
27
|
+
return nestedElementNames.reduce((elementValue, elementName, index) => {
|
|
28
|
+
// Numbers can just be returned as is
|
|
29
|
+
if (typeof elementValue === 'number') {
|
|
30
|
+
return elementValue;
|
|
31
|
+
}
|
|
32
|
+
// Attempt to get a number from the element value as a string.
|
|
33
|
+
// NaN is accounted for in the calculation so we can return that from here.
|
|
34
|
+
if (typeof elementValue === 'string') {
|
|
35
|
+
// Day-only (`YYYY-MM-DD`) strings are parsed via parseDayOnlyDate so
|
|
36
|
+
// callers control timezone. Other date strings use `new Date(value)`.
|
|
37
|
+
// Numeric strings (e.g. `"10"`) fall through to parseFloat — `Date`
|
|
38
|
+
// can otherwise treat them as valid dates.
|
|
39
|
+
const isDayOnly = DATE_ONLY_PATTERN.test(elementValue);
|
|
40
|
+
if (isDayOnly) {
|
|
41
|
+
const parsedDate = parseDayOnlyDate(elementValue);
|
|
42
|
+
if (!Number.isNaN(parsedDate.getTime())) {
|
|
43
|
+
return parsedDate.getTime();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else if (Number.isNaN(Number(elementValue))) {
|
|
47
|
+
const parsedDate = new Date(elementValue);
|
|
48
|
+
if (!Number.isNaN(parsedDate.getTime())) {
|
|
49
|
+
return parsedDate.getTime();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return parseFloat(elementValue);
|
|
53
|
+
}
|
|
54
|
+
if (Array.isArray(elementValue)) {
|
|
55
|
+
// If there are no entries, return NaN to prevent the calculation from
|
|
56
|
+
// running.
|
|
57
|
+
if (!elementValue.length) {
|
|
58
|
+
return NaN;
|
|
59
|
+
}
|
|
60
|
+
// An array could be an element that allows multiple values e.g.
|
|
61
|
+
// checkboxes. If that is the case, add them all together and move on.
|
|
62
|
+
const elementValues = elementValue.map((entry) => parseFloat(entry));
|
|
63
|
+
if (elementValues.every((entry) => !Number.isNaN(entry))) {
|
|
64
|
+
return elementValues.reduce((number, entry) => number + entry, 0);
|
|
65
|
+
}
|
|
66
|
+
// Otherwise attempt to process it as a repeatable set. If we found
|
|
67
|
+
// another repeatable set to process, pass it to the next element name
|
|
68
|
+
// to iterate over the entries. If we are processing the entries in a
|
|
69
|
+
// repeatable set, we can sum the number elements in the entries.
|
|
70
|
+
const nextElementName = nestedElementNames[index + 1];
|
|
71
|
+
let isNestedRepeatableSet = false;
|
|
72
|
+
const nestedElementValues = elementValue.reduce((nestedElementValues, entry) => {
|
|
73
|
+
if (entry) {
|
|
74
|
+
const nextElementValue = entry[nextElementName];
|
|
75
|
+
if (Array.isArray(nextElementValue)) {
|
|
76
|
+
if (nextElementValue.length) {
|
|
77
|
+
nestedElementValues.push(...nextElementValue);
|
|
78
|
+
isNestedRepeatableSet = true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
nestedElementValues.push(nextElementValue);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return nestedElementValues;
|
|
86
|
+
}, []);
|
|
87
|
+
// If the nested element values are all arrays, pass them on to the
|
|
88
|
+
// next iteration.
|
|
89
|
+
if (isNestedRepeatableSet) {
|
|
90
|
+
return nestedElementValues;
|
|
91
|
+
}
|
|
92
|
+
return nestedElementValues.reduce((total, nestedElementValue) => {
|
|
93
|
+
if (Number.isNaN(total)) {
|
|
94
|
+
return NaN;
|
|
95
|
+
}
|
|
96
|
+
const value = parseFloat(nestedElementValue);
|
|
97
|
+
if (Number.isNaN(value)) {
|
|
98
|
+
return NaN;
|
|
99
|
+
}
|
|
100
|
+
return total + value;
|
|
101
|
+
}, 0);
|
|
102
|
+
}
|
|
103
|
+
// If the value is an object, take the element name and check to see if
|
|
104
|
+
// this element is a nested form element. If so, take the next nested
|
|
105
|
+
// element name, find its value in the object and return it for the next
|
|
106
|
+
// iteration to handle.
|
|
107
|
+
if (typeof elementValue === 'object') {
|
|
108
|
+
const formFormElement = findFormElement(formElements, (e) => e.type === 'form' && e.name === nestedElementNames[index]);
|
|
109
|
+
const nextElementName = nestedElementNames[index + 1];
|
|
110
|
+
if (formFormElement && nextElementName) {
|
|
111
|
+
return elementValue[nextElementName];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// "compliance" form element has an object value with a "value" property.
|
|
115
|
+
if (isObjectWithValue(elementValue) &&
|
|
116
|
+
typeof elementValue.value === 'string') {
|
|
117
|
+
return parseFloat(elementValue.value);
|
|
118
|
+
}
|
|
119
|
+
// We did not find a number value from the known elements, assume we are
|
|
120
|
+
// at the end of the line.
|
|
121
|
+
return NaN;
|
|
122
|
+
}, defaultAccumulator);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Evaluate a OneBlink calculation expression against form submission data.
|
|
126
|
+
*
|
|
127
|
+
* Day-only (`YYYY-MM-DD`) date parsing is injected via `parseDayOnlyDate` so
|
|
128
|
+
* callers control timezone-aware behaviour (client vs server), matching the
|
|
129
|
+
* pattern used by `conditionalLogicService`. Other date strings use
|
|
130
|
+
* `new Date(value)`.
|
|
131
|
+
*
|
|
132
|
+
* #### Expression syntax
|
|
133
|
+
*
|
|
134
|
+
* Expressions are mathematical / logical formulas. Form element values are
|
|
135
|
+
* referenced with `{ELEMENT:<elementName>}` tags (including nested paths via
|
|
136
|
+
* `|`, e.g. `{ELEMENT:Children|Age}`).
|
|
137
|
+
*
|
|
138
|
+
* ##### Literals
|
|
139
|
+
*
|
|
140
|
+
* - **Numbers** — e.g. `0`, `5`, `5.4`
|
|
141
|
+
* - **Strings** — single or double quoted, e.g. `"abc"`, `'abc'`
|
|
142
|
+
* - **Booleans** — `true`, `false`
|
|
143
|
+
*
|
|
144
|
+
* ##### Arithmetic operators
|
|
145
|
+
*
|
|
146
|
+
* | Operator | Description | Example |
|
|
147
|
+
* | -------- | ------------------------------------ | -------------- |
|
|
148
|
+
* | `+` | Addition (also unary plus) | `1 + 2` → `3` |
|
|
149
|
+
* | `-` | Subtraction (also unary minus) | `1 - 2` → `-1` |
|
|
150
|
+
* | `*` | Multiplication | `4 * 2` → `8` |
|
|
151
|
+
* | `/` | Division | `4 / 2` → `2` |
|
|
152
|
+
* | `%` | Remainder | `8 % 3` → `2` |
|
|
153
|
+
* | `++` | Unary increment | `++2` → `3` |
|
|
154
|
+
* | `--` | Unary decrement | `--2` → `1` |
|
|
155
|
+
*
|
|
156
|
+
* Standard operator precedence applies. Use parentheses to override it, e.g.
|
|
157
|
+
* `3 * (2 + 1)` → `9`.
|
|
158
|
+
*
|
|
159
|
+
* ##### Comparison operators
|
|
160
|
+
*
|
|
161
|
+
* | Operator | Description | Example |
|
|
162
|
+
* | -------- | --------------------- | ------------------- |
|
|
163
|
+
* | `==` | Equal | `2 == 2` → `true` |
|
|
164
|
+
* | `!=` | Not equal | `2 != 3` → `true` |
|
|
165
|
+
* | `>` | Greater than | `2 > 1` → `true` |
|
|
166
|
+
* | `>=` | Greater than or equal | `2 >= 2` → `true` |
|
|
167
|
+
* | `<` | Less than | `2 < 3` → `true` |
|
|
168
|
+
* | `<=` | Less than or equal | `2 <= 2` → `true` |
|
|
169
|
+
*
|
|
170
|
+
* ##### Logical operators
|
|
171
|
+
*
|
|
172
|
+
* | Operator | Description | Example |
|
|
173
|
+
* | -------- | ------------------------------------ | -------------------------- |
|
|
174
|
+
* | `&&` | Logical AND (returns last operand) | `true && false` → `false` |
|
|
175
|
+
* | `\|\|` | Logical OR (returns first truthy) | `false \|\| true` → `true` |
|
|
176
|
+
* | `!` | Logical NOT | `!false` → `true` |
|
|
177
|
+
*
|
|
178
|
+
* ##### Element references
|
|
179
|
+
*
|
|
180
|
+
* - `{ELEMENT:Number}` — value of the root element named `Number`
|
|
181
|
+
* - `{ELEMENT:Set\|Child}` — value of `Child` within repeatable set / nested
|
|
182
|
+
* form element `Set`
|
|
183
|
+
* - Nested paths may include multiple segments:
|
|
184
|
+
* `{ELEMENT:Parents\|Children\|Age}`
|
|
185
|
+
*
|
|
186
|
+
* How values are coerced for evaluation:
|
|
187
|
+
*
|
|
188
|
+
* - Numbers are used as-is
|
|
189
|
+
* - Day-only (`YYYY-MM-DD`) strings are parsed via `parseDayOnlyDate`; other
|
|
190
|
+
* non-numeric date strings use `new Date(value)`. Valid dates use
|
|
191
|
+
* `Date#getTime()`, otherwise the string is parsed as a float via
|
|
192
|
+
* `parseFloat`
|
|
193
|
+
* - Arrays of numeric strings (e.g. checkboxes) are summed
|
|
194
|
+
* - Repeatable set entries sum the referenced nested numeric values
|
|
195
|
+
* - Empty arrays yield `NaN` (calculation result becomes `undefined`)
|
|
196
|
+
* - Compliance element objects use their `value` property
|
|
197
|
+
*
|
|
198
|
+
* ##### Built-in functions
|
|
199
|
+
*
|
|
200
|
+
* | Function | Description | Example |
|
|
201
|
+
* | -------------------------------- | --------------------------------------------------------------------------- | ------------------------------- |
|
|
202
|
+
* | `ROUND(value, precision)` | Round `value` to `precision` decimal places (correct floating-point rounds) | `ROUND(1.255, 2)` → `1.26` |
|
|
203
|
+
* | `ROUND_DOWN(value)` | Round down to the nearest integer (`Math.floor`) | `ROUND_DOWN(1.9)` → `1` |
|
|
204
|
+
* | `ROUND_UP(value)` | Round up to the nearest integer (`Math.ceil`) | `ROUND_UP(1.1)` → `2` |
|
|
205
|
+
* | `ISNULL(value, defaultValue)` | If `value` is unentered (`null`/`undefined`/`""`), return `defaultValue` or `0`; otherwise return `value` | `ISNULL({ELEMENT:A}, 10)` |
|
|
206
|
+
*
|
|
207
|
+
* `ROUND`, `ROUND_DOWN`, and `ROUND_UP` return `null` when `value` is `NaN` or
|
|
208
|
+
* not finite.
|
|
209
|
+
*
|
|
210
|
+
* ##### Examples
|
|
211
|
+
*
|
|
212
|
+
* ```js
|
|
213
|
+
* const value = calculationService.evaluateExpression({
|
|
214
|
+
* expression: '{ELEMENT:Quantity} * {ELEMENT:Price}',
|
|
215
|
+
* submission: { Quantity: 3, Price: 12.5 },
|
|
216
|
+
* formElements: form.elements,
|
|
217
|
+
* // Client: local timezone. Server: organisation timezone.
|
|
218
|
+
* parseDayOnlyDate: (value) => new Date(`${value}T00:00:00.000Z`),
|
|
219
|
+
* })
|
|
220
|
+
* // value === 37.5
|
|
221
|
+
* ```
|
|
222
|
+
*
|
|
223
|
+
* ```js
|
|
224
|
+
* calculationService.evaluateExpression({
|
|
225
|
+
* expression: 'ROUND({ELEMENT:Amount} * 1.1, 2)',
|
|
226
|
+
* submission: { Amount: 100 },
|
|
227
|
+
* formElements: form.elements,
|
|
228
|
+
* parseDayOnlyDate: (value) => new Date(`${value}T00:00:00.000Z`),
|
|
229
|
+
* })
|
|
230
|
+
* // 110
|
|
231
|
+
* ```
|
|
232
|
+
*
|
|
233
|
+
* ```js
|
|
234
|
+
* calculationService.evaluateExpression({
|
|
235
|
+
* expression: 'ISNULL({ELEMENT:Optional}, 0) + {ELEMENT:Required}',
|
|
236
|
+
* submission: { Required: 5 },
|
|
237
|
+
* formElements: form.elements,
|
|
238
|
+
* parseDayOnlyDate: (value) => new Date(`${value}T00:00:00.000Z`),
|
|
239
|
+
* })
|
|
240
|
+
* // 5
|
|
241
|
+
* ```
|
|
242
|
+
*
|
|
243
|
+
* @param options
|
|
244
|
+
* @returns The evaluated numeric result, or `undefined` when evaluation does
|
|
245
|
+
* not yield a valid number (including `NaN`). Throws when the expression
|
|
246
|
+
* cannot be parsed.
|
|
247
|
+
*/
|
|
248
|
+
export function evaluateExpression({ expression, submission, formElements, parseDayOnlyDate, }) {
|
|
249
|
+
if (!expression) {
|
|
250
|
+
throw new Error('Expression is required.');
|
|
251
|
+
}
|
|
252
|
+
const exprParser = new ExpressionParser();
|
|
253
|
+
exprParser.registerFunction('ROUND', (value, precision) => {
|
|
254
|
+
if (!Number.isNaN(value) && Number.isFinite(value)) {
|
|
255
|
+
return parseFloat(roundToFixed(value, precision));
|
|
256
|
+
}
|
|
257
|
+
return null;
|
|
258
|
+
});
|
|
259
|
+
exprParser.registerFunction('ROUND_DOWN', (value) => {
|
|
260
|
+
if (!Number.isNaN(value) && Number.isFinite(value)) {
|
|
261
|
+
return Math.floor(value);
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
});
|
|
265
|
+
exprParser.registerFunction('ROUND_UP', (value) => {
|
|
266
|
+
if (!Number.isNaN(value) && Number.isFinite(value)) {
|
|
267
|
+
return Math.ceil(value);
|
|
268
|
+
}
|
|
269
|
+
return null;
|
|
270
|
+
});
|
|
271
|
+
exprParser.registerFunction('ISNULL', (value, defaultValue) => {
|
|
272
|
+
if (isUnenteredValue(value)) {
|
|
273
|
+
return defaultValue || 0;
|
|
274
|
+
}
|
|
275
|
+
return value;
|
|
276
|
+
});
|
|
277
|
+
const elementNames = [];
|
|
278
|
+
matchElementsTagRegex(expression, ({ elementName }) => {
|
|
279
|
+
elementNames.push(elementName);
|
|
280
|
+
});
|
|
281
|
+
const code = elementNames.reduce((code, elementName, index) => {
|
|
282
|
+
const regex = new RegExp(escapeRegExp(`{ELEMENT:${elementName}}`), 'g');
|
|
283
|
+
const replacement = `a${index}`;
|
|
284
|
+
exprParser.registerProperty(replacement, (submissionScope) => resolveElementValue({
|
|
285
|
+
submission: submissionScope,
|
|
286
|
+
nestedElementNames: elementName.split('|'),
|
|
287
|
+
formElements,
|
|
288
|
+
parseDayOnlyDate,
|
|
289
|
+
}));
|
|
290
|
+
return code.replace(regex, replacement);
|
|
291
|
+
}, expression);
|
|
292
|
+
const result = exprParser.parse(code.trim()).eval(submission);
|
|
293
|
+
if (typeof result === 'number' && !Number.isNaN(result)) {
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
//# sourceMappingURL=calculationService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calculationService.js","sourceRoot":"","sources":["../src/calculationService.ts"],"names":[],"mappings":"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","sourcesContent":["import ExpressionParser from 'morph-expressions'\nimport { FormTypes, SubmissionTypes } from '@oneblink/types'\nimport {\n findFormElement,\n matchElementsTagRegex,\n} from './formElementsService.js'\nimport {\n DATE_ONLY_PATTERN,\n type ParseDayOnlyDate,\n} from './conditionalLogicService/types.js'\n\nexport type { ParseDayOnlyDate } from './conditionalLogicService/types.js'\n\nconst isUnenteredValue = (value: unknown | undefined) => {\n return !value && value !== 0\n}\n\nconst isObjectWithValue = (obj: unknown): obj is { value: unknown } => {\n return typeof obj === 'object' && obj !== null && 'value' in obj\n}\n\n/**\n * Escape special RegExp characters in a string so it can be used as a literal\n * pattern.\n */\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * Workaround for `.toFixed()` not rounding floating point numbers correctly.\n */\nfunction roundToFixed(number: number, decimals: number) {\n const multiplier = Math.pow(10, decimals)\n const roundedNumber = Math.round(number * multiplier) / multiplier\n return roundedNumber.toFixed(decimals)\n}\n\nfunction resolveElementValue({\n submission,\n nestedElementNames,\n formElements,\n parseDayOnlyDate,\n}: {\n submission: SubmissionTypes.S3SubmissionData['submission']\n nestedElementNames: string[]\n formElements: FormTypes.FormElement[]\n parseDayOnlyDate: ParseDayOnlyDate\n}): unknown {\n const defaultAccumulator = submission[nestedElementNames[0]]\n return nestedElementNames.reduce(\n (elementValue: unknown | undefined, elementName: string, index: number) => {\n // Numbers can just be returned as is\n if (typeof elementValue === 'number') {\n return elementValue\n }\n\n // Attempt to get a number from the element value as a string.\n // NaN is accounted for in the calculation so we can return that from here.\n if (typeof elementValue === 'string') {\n // Day-only (`YYYY-MM-DD`) strings are parsed via parseDayOnlyDate so\n // callers control timezone. Other date strings use `new Date(value)`.\n // Numeric strings (e.g. `\"10\"`) fall through to parseFloat — `Date`\n // can otherwise treat them as valid dates.\n const isDayOnly = DATE_ONLY_PATTERN.test(elementValue)\n if (isDayOnly) {\n const parsedDate = parseDayOnlyDate(elementValue)\n if (!Number.isNaN(parsedDate.getTime())) {\n return parsedDate.getTime()\n }\n } else if (Number.isNaN(Number(elementValue))) {\n const parsedDate = new Date(elementValue)\n if (!Number.isNaN(parsedDate.getTime())) {\n return parsedDate.getTime()\n }\n }\n\n return parseFloat(elementValue)\n }\n\n if (Array.isArray(elementValue)) {\n // If there are no entries, return NaN to prevent the calculation from\n // running.\n if (!elementValue.length) {\n return NaN\n }\n\n // An array could be an element that allows multiple values e.g.\n // checkboxes. If that is the case, add them all together and move on.\n const elementValues = elementValue.map((entry) => parseFloat(entry))\n if (elementValues.every((entry) => !Number.isNaN(entry))) {\n return elementValues.reduce((number, entry) => number + entry, 0)\n }\n\n // Otherwise attempt to process it as a repeatable set. If we found\n // another repeatable set to process, pass it to the next element name\n // to iterate over the entries. If we are processing the entries in a\n // repeatable set, we can sum the number elements in the entries.\n const nextElementName = nestedElementNames[index + 1]\n\n let isNestedRepeatableSet = false\n const nestedElementValues = elementValue.reduce(\n (nestedElementValues, entry) => {\n if (entry) {\n const nextElementValue = entry[nextElementName]\n if (Array.isArray(nextElementValue)) {\n if (nextElementValue.length) {\n nestedElementValues.push(...nextElementValue)\n isNestedRepeatableSet = true\n }\n } else {\n nestedElementValues.push(nextElementValue)\n }\n }\n return nestedElementValues\n },\n [] as unknown[],\n )\n\n // If the nested element values are all arrays, pass them on to the\n // next iteration.\n if (isNestedRepeatableSet) {\n return nestedElementValues\n }\n\n return nestedElementValues.reduce(\n (total: number, nestedElementValue: unknown | undefined) => {\n if (Number.isNaN(total)) {\n return NaN\n }\n const value = parseFloat(nestedElementValue as string)\n if (Number.isNaN(value)) {\n return NaN\n }\n return total + value\n },\n 0,\n )\n }\n\n // If the value is an object, take the element name and check to see if\n // this element is a nested form element. If so, take the next nested\n // element name, find its value in the object and return it for the next\n // iteration to handle.\n if (typeof elementValue === 'object') {\n const formFormElement = findFormElement(\n formElements,\n (e) => e.type === 'form' && e.name === nestedElementNames[index],\n )\n const nextElementName = nestedElementNames[index + 1]\n if (formFormElement && nextElementName) {\n return (elementValue as Record<string, unknown>)[nextElementName]\n }\n }\n\n // \"compliance\" form element has an object value with a \"value\" property.\n if (\n isObjectWithValue(elementValue) &&\n typeof elementValue.value === 'string'\n ) {\n return parseFloat(elementValue.value)\n }\n\n // We did not find a number value from the known elements, assume we are\n // at the end of the line.\n return NaN\n },\n defaultAccumulator,\n )\n}\n\n/**\n * Evaluate a OneBlink calculation expression against form submission data.\n *\n * Day-only (`YYYY-MM-DD`) date parsing is injected via `parseDayOnlyDate` so\n * callers control timezone-aware behaviour (client vs server), matching the\n * pattern used by `conditionalLogicService`. Other date strings use\n * `new Date(value)`.\n *\n * #### Expression syntax\n *\n * Expressions are mathematical / logical formulas. Form element values are\n * referenced with `{ELEMENT:<elementName>}` tags (including nested paths via\n * `|`, e.g. `{ELEMENT:Children|Age}`).\n *\n * ##### Literals\n *\n * - **Numbers** — e.g. `0`, `5`, `5.4`\n * - **Strings** — single or double quoted, e.g. `\"abc\"`, `'abc'`\n * - **Booleans** — `true`, `false`\n *\n * ##### Arithmetic operators\n *\n * | Operator | Description | Example |\n * | -------- | ------------------------------------ | -------------- |\n * | `+` | Addition (also unary plus) | `1 + 2` → `3` |\n * | `-` | Subtraction (also unary minus) | `1 - 2` → `-1` |\n * | `*` | Multiplication | `4 * 2` → `8` |\n * | `/` | Division | `4 / 2` → `2` |\n * | `%` | Remainder | `8 % 3` → `2` |\n * | `++` | Unary increment | `++2` → `3` |\n * | `--` | Unary decrement | `--2` → `1` |\n *\n * Standard operator precedence applies. Use parentheses to override it, e.g.\n * `3 * (2 + 1)` → `9`.\n *\n * ##### Comparison operators\n *\n * | Operator | Description | Example |\n * | -------- | --------------------- | ------------------- |\n * | `==` | Equal | `2 == 2` → `true` |\n * | `!=` | Not equal | `2 != 3` → `true` |\n * | `>` | Greater than | `2 > 1` → `true` |\n * | `>=` | Greater than or equal | `2 >= 2` → `true` |\n * | `<` | Less than | `2 < 3` → `true` |\n * | `<=` | Less than or equal | `2 <= 2` → `true` |\n *\n * ##### Logical operators\n *\n * | Operator | Description | Example |\n * | -------- | ------------------------------------ | -------------------------- |\n * | `&&` | Logical AND (returns last operand) | `true && false` → `false` |\n * | `\\|\\|` | Logical OR (returns first truthy) | `false \\|\\| true` → `true` |\n * | `!` | Logical NOT | `!false` → `true` |\n *\n * ##### Element references\n *\n * - `{ELEMENT:Number}` — value of the root element named `Number`\n * - `{ELEMENT:Set\\|Child}` — value of `Child` within repeatable set / nested\n * form element `Set`\n * - Nested paths may include multiple segments:\n * `{ELEMENT:Parents\\|Children\\|Age}`\n *\n * How values are coerced for evaluation:\n *\n * - Numbers are used as-is\n * - Day-only (`YYYY-MM-DD`) strings are parsed via `parseDayOnlyDate`; other\n * non-numeric date strings use `new Date(value)`. Valid dates use\n * `Date#getTime()`, otherwise the string is parsed as a float via\n * `parseFloat`\n * - Arrays of numeric strings (e.g. checkboxes) are summed\n * - Repeatable set entries sum the referenced nested numeric values\n * - Empty arrays yield `NaN` (calculation result becomes `undefined`)\n * - Compliance element objects use their `value` property\n *\n * ##### Built-in functions\n *\n * | Function | Description | Example |\n * | -------------------------------- | --------------------------------------------------------------------------- | ------------------------------- |\n * | `ROUND(value, precision)` | Round `value` to `precision` decimal places (correct floating-point rounds) | `ROUND(1.255, 2)` → `1.26` |\n * | `ROUND_DOWN(value)` | Round down to the nearest integer (`Math.floor`) | `ROUND_DOWN(1.9)` → `1` |\n * | `ROUND_UP(value)` | Round up to the nearest integer (`Math.ceil`) | `ROUND_UP(1.1)` → `2` |\n * | `ISNULL(value, defaultValue)` | If `value` is unentered (`null`/`undefined`/`\"\"`), return `defaultValue` or `0`; otherwise return `value` | `ISNULL({ELEMENT:A}, 10)` |\n *\n * `ROUND`, `ROUND_DOWN`, and `ROUND_UP` return `null` when `value` is `NaN` or\n * not finite.\n *\n * ##### Examples\n *\n * ```js\n * const value = calculationService.evaluateExpression({\n * expression: '{ELEMENT:Quantity} * {ELEMENT:Price}',\n * submission: { Quantity: 3, Price: 12.5 },\n * formElements: form.elements,\n * // Client: local timezone. Server: organisation timezone.\n * parseDayOnlyDate: (value) => new Date(`${value}T00:00:00.000Z`),\n * })\n * // value === 37.5\n * ```\n *\n * ```js\n * calculationService.evaluateExpression({\n * expression: 'ROUND({ELEMENT:Amount} * 1.1, 2)',\n * submission: { Amount: 100 },\n * formElements: form.elements,\n * parseDayOnlyDate: (value) => new Date(`${value}T00:00:00.000Z`),\n * })\n * // 110\n * ```\n *\n * ```js\n * calculationService.evaluateExpression({\n * expression: 'ISNULL({ELEMENT:Optional}, 0) + {ELEMENT:Required}',\n * submission: { Required: 5 },\n * formElements: form.elements,\n * parseDayOnlyDate: (value) => new Date(`${value}T00:00:00.000Z`),\n * })\n * // 5\n * ```\n *\n * @param options\n * @returns The evaluated numeric result, or `undefined` when evaluation does\n * not yield a valid number (including `NaN`). Throws when the expression\n * cannot be parsed.\n */\nexport function evaluateExpression({\n expression,\n submission,\n formElements,\n parseDayOnlyDate,\n}: {\n /** The calculation expression string to evaluate */\n expression: string\n /** Form submission data used to resolve `{ELEMENT:...}` references */\n submission: SubmissionTypes.S3SubmissionData['submission']\n /**\n * Form elements definition. Used to resolve nested `form` element values\n * referenced in the expression.\n */\n formElements: FormTypes.FormElement[]\n /**\n * Parse `YYYY-MM-DD` strings when resolving date element values. sdk-core\n * only calls this for day-only values; other date strings use\n * `new Date(value)`.\n */\n parseDayOnlyDate: ParseDayOnlyDate\n}): number | undefined {\n if (!expression) {\n throw new Error('Expression is required.')\n }\n\n const exprParser = new ExpressionParser<\n SubmissionTypes.S3SubmissionData['submission']\n >()\n exprParser.registerFunction('ROUND', (value: number, precision: number) => {\n if (!Number.isNaN(value) && Number.isFinite(value)) {\n return parseFloat(roundToFixed(value, precision))\n }\n return null\n })\n exprParser.registerFunction('ROUND_DOWN', (value: number) => {\n if (!Number.isNaN(value) && Number.isFinite(value)) {\n return Math.floor(value)\n }\n return null\n })\n exprParser.registerFunction('ROUND_UP', (value: number) => {\n if (!Number.isNaN(value) && Number.isFinite(value)) {\n return Math.ceil(value)\n }\n return null\n })\n exprParser.registerFunction(\n 'ISNULL',\n (value: unknown | undefined, defaultValue: number) => {\n if (isUnenteredValue(value)) {\n return defaultValue || 0\n }\n return value\n },\n )\n\n const elementNames: string[] = []\n matchElementsTagRegex(expression, ({ elementName }) => {\n elementNames.push(elementName)\n })\n\n const code = elementNames.reduce((code, elementName, index) => {\n const regex = new RegExp(escapeRegExp(`{ELEMENT:${elementName}}`), 'g')\n const replacement = `a${index}`\n exprParser.registerProperty(\n replacement,\n (submissionScope: SubmissionTypes.S3SubmissionData['submission']) =>\n resolveElementValue({\n submission: submissionScope,\n nestedElementNames: elementName.split('|'),\n formElements,\n parseDayOnlyDate,\n }),\n )\n return code.replace(regex, replacement)\n }, expression)\n\n const result = exprParser.parse(code.trim()).eval(submission)\n if (typeof result === 'number' && !Number.isNaN(result)) {\n return result\n }\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { DATE_ONLY_PATTERN, } from './types.js';
|
|
1
2
|
import { getElementAndValue } from './evaluateFormElementConditionalPredicate.js';
|
|
2
|
-
const DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
3
3
|
function resolveDateValue(dateValue, formElementsCtrl, parseDayOnlyDate, addDaysToDate, startOfDay, endOfDay, dayBoundary) {
|
|
4
4
|
let dateString;
|
|
5
5
|
if (dateValue.compareWith === 'ELEMENT') {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evaluateConditionalSubmissionTimestampPredicate.js","sourceRoot":"","sources":["../../src/conditionalLogicService/evaluateConditionalSubmissionTimestampPredicate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"evaluateConditionalSubmissionTimestampPredicate.js","sourceRoot":"","sources":["../../src/conditionalLogicService/evaluateConditionalSubmissionTimestampPredicate.ts"],"names":[],"mappings":"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","sourcesContent":["import { ConditionTypes } from '@oneblink/types'\nimport {\n AddOffsetToDate,\n DATE_ONLY_PATTERN,\n EndOfDay,\n FormElementsCtrl,\n ParseDayOnlyDate,\n StartOfDay,\n} from './types.js'\nimport { getElementAndValue } from './evaluateFormElementConditionalPredicate.js'\n\ntype DayBoundary = 'start' | 'end'\n\nfunction resolveDateValue(\n dateValue: ConditionTypes.ConditionalPredicateDateValue,\n formElementsCtrl: FormElementsCtrl,\n parseDayOnlyDate: ParseDayOnlyDate,\n addDaysToDate: AddOffsetToDate,\n startOfDay: StartOfDay,\n endOfDay: EndOfDay,\n dayBoundary: DayBoundary,\n): Date | undefined {\n let dateString: string | undefined\n\n if (dateValue.compareWith === 'ELEMENT') {\n const elementAndValue = getElementAndValue(\n formElementsCtrl,\n dateValue.elementId,\n )\n if (typeof elementAndValue.value === 'string' && elementAndValue.value) {\n dateString = elementAndValue.value\n } else {\n return undefined\n }\n } else {\n dateString = dateValue.value\n }\n\n const isDayOnly = DATE_ONLY_PATTERN.test(dateString)\n const date = isDayOnly ? parseDayOnlyDate(dateString) : new Date(dateString)\n if (Number.isNaN(date.getTime())) {\n return undefined\n }\n\n const dateWithOffset =\n typeof dateValue.daysOffset !== 'number' ||\n Number.isNaN(dateValue.daysOffset)\n ? date\n : addDaysToDate(date, dateValue.daysOffset)\n\n if (isDayOnly) {\n if (dayBoundary === 'start') {\n return startOfDay(dateWithOffset)\n } else {\n return endOfDay(dateWithOffset)\n }\n }\n\n return dateWithOffset\n}\n\n/**\n * Evaluate a `SUBMISSION_TIMESTAMP` conditional predicate against the form\n * submission timestamp.\n *\n * - `AFTER` — submission timestamp is after (exclusive) the comparison date\n * - `BEFORE` — submission timestamp is before (exclusive) the comparison date\n * - `BETWEEN` — submission timestamp is between `min` and `max` (inclusive)\n *\n * When the comparison value is a day-only (`YYYY-MM-DD`) string, day\n * boundaries are applied via the injected helpers:\n *\n * - `AFTER` — end of the comparison day\n * - `BEFORE` — start of the comparison day\n * - `BETWEEN` — start of `min` day through end of `max` day\n *\n * Full ISO datetime values compare at the exact instant.\n *\n * Day-only (`YYYY-MM-DD`) strings are parsed via `parseDayOnlyDate`. Other\n * date strings use `new Date(value)`. Day offsets are applied via\n * `addDaysToDate(date, offset)`.\n */\nexport default function evaluateConditionalSubmissionTimestampPredicate({\n predicate,\n formElementsCtrl,\n submissionTimestamp,\n parseDayOnlyDate,\n addDaysToDate,\n startOfDay,\n endOfDay,\n}: {\n predicate: ConditionTypes.ConditionalPredicateSubmissionTimestamp\n formElementsCtrl: FormElementsCtrl\n submissionTimestamp: string\n parseDayOnlyDate: ParseDayOnlyDate\n addDaysToDate: AddOffsetToDate\n startOfDay: StartOfDay\n endOfDay: EndOfDay\n}): boolean {\n const submissionDate = new Date(submissionTimestamp)\n if (Number.isNaN(submissionDate.getTime())) {\n return false\n }\n\n const submissionTime = submissionDate.getTime()\n\n switch (predicate.operator) {\n case 'BETWEEN': {\n const min = resolveDateValue(\n predicate.min,\n formElementsCtrl,\n parseDayOnlyDate,\n addDaysToDate,\n startOfDay,\n endOfDay,\n 'start',\n )\n const max = resolveDateValue(\n predicate.max,\n formElementsCtrl,\n parseDayOnlyDate,\n addDaysToDate,\n startOfDay,\n endOfDay,\n 'end',\n )\n if (!min || !max) {\n return false\n }\n return submissionTime >= min.getTime() && submissionTime <= max.getTime()\n }\n default: {\n const dayBoundary: DayBoundary =\n predicate.operator === 'AFTER' ? 'end' : 'start'\n const compareDate = resolveDateValue(\n predicate,\n formElementsCtrl,\n parseDayOnlyDate,\n addDaysToDate,\n startOfDay,\n endOfDay,\n dayBoundary,\n )\n if (!compareDate) {\n return false\n }\n\n switch (predicate.operator) {\n case 'AFTER':\n return submissionTime > compareDate.getTime()\n case 'BEFORE':\n return submissionTime < compareDate.getTime()\n default: {\n const n: never = predicate\n console.warn('Unhandled predicate operator', n)\n return false\n }\n }\n }\n }\n}\n"]}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { FormTypes, SubmissionTypes } from '@oneblink/types';
|
|
2
|
+
/**
|
|
3
|
+
* Matches day-only (`YYYY-MM-DD`) date strings. Used to decide when to call
|
|
4
|
+
* `parseDayOnlyDate` instead of `new Date(value)`.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DATE_ONLY_PATTERN: RegExp;
|
|
2
7
|
/**
|
|
3
8
|
* Parse a day-only (`YYYY-MM-DD`) string into a `Date`. Callers supply
|
|
4
9
|
* timezone-aware parsing (e.g. start of day in an organisation timezone).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/conditionalLogicService/types.ts"],"names":[],"mappings":"","sourcesContent":["import { FormTypes, SubmissionTypes } from '@oneblink/types'\n\n/**\n * Parse a day-only (`YYYY-MM-DD`) string into a `Date`. Callers supply\n * timezone-aware parsing (e.g. start of day in an organisation timezone).\n * sdk-core only invokes this for values that match `YYYY-MM-DD`.\n */\nexport type ParseDayOnlyDate = (value: string) => Date\n\n/**\n * Add an offset to a date. Callers supply timezone-aware calendar arithmetic.\n */\nexport type AddOffsetToDate = (date: Date, offset: number) => Date\n\n/**\n * Move a date to the start of its calendar day. Callers supply timezone-aware\n * behaviour (e.g. organisation timezone on the server, local timezone in the\n * browser).\n */\nexport type StartOfDay = (date: Date) => Date\n\n/**\n * Move a date to the end of its calendar day (last millisecond). Callers supply\n * timezone-aware behaviour (e.g. organisation timezone on the server, local\n * timezone in the browser).\n */\nexport type EndOfDay = (date: Date) => Date\n\nexport type FormElementsCtrl = {\n model: SubmissionTypes.S3SubmissionData['submission'] | undefined\n flattenedElements: FormTypes.FormElement[]\n parentFormElementsCtrl?: FormElementsCtrl\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/conditionalLogicService/types.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,qBAAqB,CAAA","sourcesContent":["import { FormTypes, SubmissionTypes } from '@oneblink/types'\n\n/**\n * Matches day-only (`YYYY-MM-DD`) date strings. Used to decide when to call\n * `parseDayOnlyDate` instead of `new Date(value)`.\n */\nexport const DATE_ONLY_PATTERN = /^\\d{4}-\\d{2}-\\d{2}$/\n\n/**\n * Parse a day-only (`YYYY-MM-DD`) string into a `Date`. Callers supply\n * timezone-aware parsing (e.g. start of day in an organisation timezone).\n * sdk-core only invokes this for values that match `YYYY-MM-DD`.\n */\nexport type ParseDayOnlyDate = (value: string) => Date\n\n/**\n * Add an offset to a date. Callers supply timezone-aware calendar arithmetic.\n */\nexport type AddOffsetToDate = (date: Date, offset: number) => Date\n\n/**\n * Move a date to the start of its calendar day. Callers supply timezone-aware\n * behaviour (e.g. organisation timezone on the server, local timezone in the\n * browser).\n */\nexport type StartOfDay = (date: Date) => Date\n\n/**\n * Move a date to the end of its calendar day (last millisecond). Callers supply\n * timezone-aware behaviour (e.g. organisation timezone on the server, local\n * timezone in the browser).\n */\nexport type EndOfDay = (date: Date) => Date\n\nexport type FormElementsCtrl = {\n model: SubmissionTypes.S3SubmissionData['submission'] | undefined\n flattenedElements: FormTypes.FormElement[]\n parentFormElementsCtrl?: FormElementsCtrl\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,16 @@ export * as formElementsService from './formElementsService.js';
|
|
|
19
19
|
* ```
|
|
20
20
|
*/
|
|
21
21
|
export * as conditionalLogicService from './conditionalLogicService.js';
|
|
22
|
+
/**
|
|
23
|
+
* ## Calculation Service
|
|
24
|
+
*
|
|
25
|
+
* Helper functions for evaluating form calculation expressions
|
|
26
|
+
*
|
|
27
|
+
* ```js
|
|
28
|
+
* import { calculationService } from '@oneblink/sdk-core'
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export * as calculationService from './calculationService.js';
|
|
22
32
|
/**
|
|
23
33
|
* ## Submission Service
|
|
24
34
|
*
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,16 @@ export * as formElementsService from './formElementsService.js';
|
|
|
19
19
|
* ```
|
|
20
20
|
*/
|
|
21
21
|
export * as conditionalLogicService from './conditionalLogicService.js';
|
|
22
|
+
/**
|
|
23
|
+
* ## Calculation Service
|
|
24
|
+
*
|
|
25
|
+
* Helper functions for evaluating form calculation expressions
|
|
26
|
+
*
|
|
27
|
+
* ```js
|
|
28
|
+
* import { calculationService } from '@oneblink/sdk-core'
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export * as calculationService from './calculationService.js';
|
|
22
32
|
/**
|
|
23
33
|
* ## Submission Service
|
|
24
34
|
*
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,uBAAuB,MAAM,iCAAiC,CAAA;AAC1E;;;;;;;;GAQG;AACH,OAAO,KAAK,mBAAmB,MAAM,0BAA0B,CAAA;AAC/D;;;;;;;;GAQG;AACH,OAAO,KAAK,uBAAuB,MAAM,8BAA8B,CAAA;AACvE;;;;;;;;GAQG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAC3D;;;;;;;;GAQG;AACH,OAAO,KAAK,eAAe,MAAM,sBAAsB,CAAA;AACvD;;;;;;;;GAQG;AACH,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAA;AAC/C;;;;;;;;GAQG;AACH,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAC7C;;;;;;;;GAQG;AACH,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAA;AACrD;;;;;;;;GAQG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAC3D;;;;;;;;GAQG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAC3D;;;;;;;;GAQG;AACH,OAAO,KAAK,0BAA0B,MAAM,iCAAiC,CAAA;AAC7E;;;;;;;;GAQG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAE3D;;;;;;;;GAQG;AACH,OAAO,KAAK,gBAAgB,MAAM,uBAAuB,CAAA;AAEzD;;;;;;;;GAQG;AACH,OAAO,KAAK,eAAe,MAAM,sBAAsB,CAAA;AAEvD;;;;;;;;GAQG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA","sourcesContent":["export * as resourceDefinitionTypes from './types/resource-definitions.js'\n/**\n * ## Form Elements Service\n *\n * Helper functions for handling form elements\n *\n * ```js\n * import { formElementsService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as formElementsService from './formElementsService.js'\n/**\n * ## Conditional Logic Service\n *\n * Helper functions for handling conditional logic\n *\n * ```js\n * import { conditionalLogicService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as conditionalLogicService from './conditionalLogicService.js'\n/**\n * ## Submission Service\n *\n * Helper functions for handling form submission data\n *\n * ```js\n * import { submissionService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as submissionService from './submissionService.js'\n/**\n * ## Submission Service\n *\n * Helper functions for casting types\n *\n * ```js\n * import { typeCastService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as typeCastService from './typeCastService.js'\n/**\n * ## User Service\n *\n * Helper functions for handling users\n *\n * ```js\n * import { userService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as userService from './userService.js'\n/**\n * ## ABN Service\n *\n * Helper functions for handling ABN Records\n *\n * ```js\n * import { abnService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as abnService from './abnService.js'\n/**\n * ## Payment Service\n *\n * Helper functions for payments\n *\n * ```js\n * import { paymentService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as paymentService from './paymentService.js'\n/**\n * ## Scheduling Service\n *\n * Helper functions for scheduling\n *\n * ```js\n * import { schedulingService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as schedulingService from './schedulingService.js'\n/**\n * ## File Upload Service\n *\n * Helper functions for handling file uploads\n *\n * ```js\n * import { fileUploadService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as fileUploadService from './fileUploadService.js'\n/**\n * ## Webhook Subscription Service\n *\n * Helper functions for handling Webhook subscriptions\n *\n * ```js\n * import { webhookSubscriptionService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as webhookSubscriptionService from './webhookSubscriptionService.js'\n/**\n * ## SharePoint Service\n *\n * Helper functions for handling SharePoint\n *\n * ```js\n * import { sharepointService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as sharepointService from './sharepointService.js'\n\n/**\n * ## Freshdesk Service\n *\n * Helper functions for handling Freshdesk\n *\n * ```js\n * import { freshdeskService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as freshdeskService from './freshdeskService.js'\n\n/**\n * ## GoodToGo Service\n *\n * Helper functions for handling GoodToGo\n *\n * ```js\n * import { goodToGoService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as goodToGoService from './goodToGoService.js'\n\n/**\n * ## Salesforce Service\n *\n * Helper functions for handling Salesforce\n *\n * ```js\n * import { salesForceService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as salesforceService from './salesforceService.js'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,uBAAuB,MAAM,iCAAiC,CAAA;AAC1E;;;;;;;;GAQG;AACH,OAAO,KAAK,mBAAmB,MAAM,0BAA0B,CAAA;AAC/D;;;;;;;;GAQG;AACH,OAAO,KAAK,uBAAuB,MAAM,8BAA8B,CAAA;AACvE;;;;;;;;GAQG;AACH,OAAO,KAAK,kBAAkB,MAAM,yBAAyB,CAAA;AAC7D;;;;;;;;GAQG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAC3D;;;;;;;;GAQG;AACH,OAAO,KAAK,eAAe,MAAM,sBAAsB,CAAA;AACvD;;;;;;;;GAQG;AACH,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAA;AAC/C;;;;;;;;GAQG;AACH,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAC7C;;;;;;;;GAQG;AACH,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAA;AACrD;;;;;;;;GAQG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAC3D;;;;;;;;GAQG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAC3D;;;;;;;;GAQG;AACH,OAAO,KAAK,0BAA0B,MAAM,iCAAiC,CAAA;AAC7E;;;;;;;;GAQG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAE3D;;;;;;;;GAQG;AACH,OAAO,KAAK,gBAAgB,MAAM,uBAAuB,CAAA;AAEzD;;;;;;;;GAQG;AACH,OAAO,KAAK,eAAe,MAAM,sBAAsB,CAAA;AAEvD;;;;;;;;GAQG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA","sourcesContent":["export * as resourceDefinitionTypes from './types/resource-definitions.js'\n/**\n * ## Form Elements Service\n *\n * Helper functions for handling form elements\n *\n * ```js\n * import { formElementsService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as formElementsService from './formElementsService.js'\n/**\n * ## Conditional Logic Service\n *\n * Helper functions for handling conditional logic\n *\n * ```js\n * import { conditionalLogicService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as conditionalLogicService from './conditionalLogicService.js'\n/**\n * ## Calculation Service\n *\n * Helper functions for evaluating form calculation expressions\n *\n * ```js\n * import { calculationService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as calculationService from './calculationService.js'\n/**\n * ## Submission Service\n *\n * Helper functions for handling form submission data\n *\n * ```js\n * import { submissionService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as submissionService from './submissionService.js'\n/**\n * ## Submission Service\n *\n * Helper functions for casting types\n *\n * ```js\n * import { typeCastService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as typeCastService from './typeCastService.js'\n/**\n * ## User Service\n *\n * Helper functions for handling users\n *\n * ```js\n * import { userService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as userService from './userService.js'\n/**\n * ## ABN Service\n *\n * Helper functions for handling ABN Records\n *\n * ```js\n * import { abnService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as abnService from './abnService.js'\n/**\n * ## Payment Service\n *\n * Helper functions for payments\n *\n * ```js\n * import { paymentService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as paymentService from './paymentService.js'\n/**\n * ## Scheduling Service\n *\n * Helper functions for scheduling\n *\n * ```js\n * import { schedulingService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as schedulingService from './schedulingService.js'\n/**\n * ## File Upload Service\n *\n * Helper functions for handling file uploads\n *\n * ```js\n * import { fileUploadService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as fileUploadService from './fileUploadService.js'\n/**\n * ## Webhook Subscription Service\n *\n * Helper functions for handling Webhook subscriptions\n *\n * ```js\n * import { webhookSubscriptionService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as webhookSubscriptionService from './webhookSubscriptionService.js'\n/**\n * ## SharePoint Service\n *\n * Helper functions for handling SharePoint\n *\n * ```js\n * import { sharepointService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as sharepointService from './sharepointService.js'\n\n/**\n * ## Freshdesk Service\n *\n * Helper functions for handling Freshdesk\n *\n * ```js\n * import { freshdeskService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as freshdeskService from './freshdeskService.js'\n\n/**\n * ## GoodToGo Service\n *\n * Helper functions for handling GoodToGo\n *\n * ```js\n * import { goodToGoService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as goodToGoService from './goodToGoService.js'\n\n/**\n * ## Salesforce Service\n *\n * Helper functions for handling Salesforce\n *\n * ```js\n * import { salesForceService } from '@oneblink/sdk-core'\n * ```\n */\nexport * as salesforceService from './salesforceService.js'\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SubmissionTypes } from '@oneblink/types';
|
|
2
|
+
import { PaymentProvider } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the pure payment provider implementation for a payment type.
|
|
5
|
+
*
|
|
6
|
+
* @param type
|
|
7
|
+
* @returns
|
|
8
|
+
*/
|
|
9
|
+
export default function getPaymentProvider(type: SubmissionTypes.FormSubmissionPayment['type']): PaymentProvider<SubmissionTypes.FormSubmissionPayment>;
|