@zeniai/client-epic-state 5.0.85-betaAR2 → 5.0.85-betaAR3
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.
|
@@ -28,28 +28,57 @@ export const TRANSACTION_FILTER_CATEGORIES = [
|
|
|
28
28
|
{ value: 'class', type: 'dropdown' },
|
|
29
29
|
{ value: 'amount', type: 'numberRange' },
|
|
30
30
|
];
|
|
31
|
+
/**
|
|
32
|
+
* Per-line amounts for a transaction, preserving the canonical source
|
|
33
|
+
* precedence:
|
|
34
|
+
*
|
|
35
|
+
* 1. `transactionLocalData.transactionReviewLocalData.lineItemById` —
|
|
36
|
+
* the locally-edited line items the user sees in the grid. Reflects
|
|
37
|
+
* unsaved edits so the filter matches what the user actually has on
|
|
38
|
+
* screen, not the pre-edit server state.
|
|
39
|
+
* 2. `transaction.transaction.lines` — persisted lines from the
|
|
40
|
+
* transaction payload.
|
|
41
|
+
* 3. `[transaction.amount.amount]` — single-element fallback for
|
|
42
|
+
* transactions that don't have a `lines` array at all (treat the
|
|
43
|
+
* transaction-level amount as a one-line transaction).
|
|
44
|
+
*
|
|
45
|
+
* Returned as an array so the amount filter in `transactionMatchesCategory`
|
|
46
|
+
* can OR-combine per-line `matchAmount` results: a transaction is included
|
|
47
|
+
* if ANY of its lines satisfies the operator, and the full row (with all
|
|
48
|
+
* lines, matching or not) renders downstream. Before the per-line change
|
|
49
|
+
* this helper's predecessor in `getCategoryValueForTransaction` summed
|
|
50
|
+
* these values into a single aggregate and compared the sum.
|
|
51
|
+
*/
|
|
52
|
+
const getLineAmountsForTransaction = (transaction) => {
|
|
53
|
+
const localData = transaction.transactionLocalData?.transactionReviewLocalData;
|
|
54
|
+
if (localData?.lineItemById != null) {
|
|
55
|
+
const lineItems = Object.values(localData.lineItemById);
|
|
56
|
+
if (lineItems.length > 0) {
|
|
57
|
+
return lineItems.map((lineItem) => lineItem.amount?.amount ?? 0);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (transaction.transaction.lines != null &&
|
|
61
|
+
transaction.transaction.lines.length > 0) {
|
|
62
|
+
return transaction.transaction.lines.map((line) => line.amount?.amount ?? 0);
|
|
63
|
+
}
|
|
64
|
+
return [transaction.amount.amount];
|
|
65
|
+
};
|
|
31
66
|
/**
|
|
32
67
|
* Resolve the comparable value for a transaction against a given filter field.
|
|
33
68
|
* Returns undefined when the transaction doesn't carry the field — callers
|
|
34
69
|
* decide whether absence counts as a match (for 'not_equal' it does).
|
|
70
|
+
*
|
|
71
|
+
* NOTE: the `'amount'` field is NOT handled here. Amount matching is
|
|
72
|
+
* per-line (see `getLineAmountsForTransaction` + the early-return in
|
|
73
|
+
* `transactionMatchesCategory`) and would otherwise need to return a
|
|
74
|
+
* non-scalar value that doesn't fit this helper's contract.
|
|
35
75
|
*/
|
|
36
76
|
const getCategoryValueForTransaction = (key, transaction) => {
|
|
37
77
|
switch (key) {
|
|
38
78
|
case 'amount': {
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
if (localDataForAmount?.lineItemById) {
|
|
43
|
-
const lineItems = Object.values(localDataForAmount.lineItemById);
|
|
44
|
-
if (lineItems.length > 0) {
|
|
45
|
-
return lineItems.reduce((sum, lineItem) => sum + (lineItem.amount?.amount ?? 0), 0);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
if (transaction.transaction.lines &&
|
|
49
|
-
transaction.transaction.lines.length > 0) {
|
|
50
|
-
return transaction.transaction.lines.reduce((sum, line) => sum + (line.amount?.amount ?? 0), 0);
|
|
51
|
-
}
|
|
52
|
-
return transaction.amount.amount;
|
|
79
|
+
// Handled per-line in `transactionMatchesCategory` — see the note on
|
|
80
|
+
// this function's JSDoc and `getLineAmountsForTransaction` above.
|
|
81
|
+
return undefined;
|
|
53
82
|
}
|
|
54
83
|
case 'payee': {
|
|
55
84
|
if (isNonEmptyString(transaction.vendorName)) {
|
|
@@ -187,6 +216,27 @@ const matchAmount = (amount, category) => {
|
|
|
187
216
|
return op === 'not_equal' ? !matched : matched;
|
|
188
217
|
};
|
|
189
218
|
const transactionMatchesCategory = (transaction, category) => {
|
|
219
|
+
if (category.field === 'amount') {
|
|
220
|
+
// Per-line matching: the transaction is included if ANY of its lines
|
|
221
|
+
// satisfies the operator. This is what the user expects when a
|
|
222
|
+
// multi-line transaction contains lines at different amounts — they
|
|
223
|
+
// want the row to surface if even one line falls in the filter's
|
|
224
|
+
// range. The full row (with non-matching lines too) renders
|
|
225
|
+
// downstream; this matcher only decides row inclusion. Same
|
|
226
|
+
// operator + sign semantics as the aggregate version, just applied
|
|
227
|
+
// per line.
|
|
228
|
+
//
|
|
229
|
+
// `not_equal` deserves a sanity-check: `matchAmount` already inverts
|
|
230
|
+
// internally for that operator, so for a line whose amount is NOT
|
|
231
|
+
// in the filter's `values`, matchAmount returns true; for a line
|
|
232
|
+
// whose amount IS in the values, it returns false. `.some()` over
|
|
233
|
+
// lines therefore means "the transaction has at least one line that
|
|
234
|
+
// is not equal to any of the filter values" — which is the right
|
|
235
|
+
// match-any-line interpretation of `not_equal` (a transaction
|
|
236
|
+
// matches if it has a line that satisfies the negation).
|
|
237
|
+
const lineAmounts = getLineAmountsForTransaction(transaction);
|
|
238
|
+
return lineAmounts.some((lineAmount) => matchAmount(lineAmount, category));
|
|
239
|
+
}
|
|
190
240
|
const value = getCategoryValueForTransaction(category.field, transaction);
|
|
191
241
|
if (value == null) {
|
|
192
242
|
// Absent-value semantics — pinning these down explicitly so the
|
|
@@ -206,12 +256,10 @@ const transactionMatchesCategory = (transaction, category) => {
|
|
|
206
256
|
// are filtered out.
|
|
207
257
|
//
|
|
208
258
|
// Only the `not_equal` branch returns `true` here; everything else
|
|
209
|
-
// falls through to the `false` below.
|
|
259
|
+
// falls through to the `false` below. Note: 'amount' never lands here
|
|
260
|
+
// because it short-circuits to the per-line path above.
|
|
210
261
|
return category.matchingOperator === 'not_equal';
|
|
211
262
|
}
|
|
212
|
-
if (category.field === 'amount') {
|
|
213
|
-
return matchAmount(Number(value), category);
|
|
214
|
-
}
|
|
215
263
|
const valueStr = value.toString();
|
|
216
264
|
const valueInList = category.values.some((v) => v.toString() === valueStr);
|
|
217
265
|
return category.matchingOperator === 'not_equal' ? !valueInList : valueInList;
|
|
@@ -31,28 +31,57 @@ exports.TRANSACTION_FILTER_CATEGORIES = [
|
|
|
31
31
|
{ value: 'class', type: 'dropdown' },
|
|
32
32
|
{ value: 'amount', type: 'numberRange' },
|
|
33
33
|
];
|
|
34
|
+
/**
|
|
35
|
+
* Per-line amounts for a transaction, preserving the canonical source
|
|
36
|
+
* precedence:
|
|
37
|
+
*
|
|
38
|
+
* 1. `transactionLocalData.transactionReviewLocalData.lineItemById` —
|
|
39
|
+
* the locally-edited line items the user sees in the grid. Reflects
|
|
40
|
+
* unsaved edits so the filter matches what the user actually has on
|
|
41
|
+
* screen, not the pre-edit server state.
|
|
42
|
+
* 2. `transaction.transaction.lines` — persisted lines from the
|
|
43
|
+
* transaction payload.
|
|
44
|
+
* 3. `[transaction.amount.amount]` — single-element fallback for
|
|
45
|
+
* transactions that don't have a `lines` array at all (treat the
|
|
46
|
+
* transaction-level amount as a one-line transaction).
|
|
47
|
+
*
|
|
48
|
+
* Returned as an array so the amount filter in `transactionMatchesCategory`
|
|
49
|
+
* can OR-combine per-line `matchAmount` results: a transaction is included
|
|
50
|
+
* if ANY of its lines satisfies the operator, and the full row (with all
|
|
51
|
+
* lines, matching or not) renders downstream. Before the per-line change
|
|
52
|
+
* this helper's predecessor in `getCategoryValueForTransaction` summed
|
|
53
|
+
* these values into a single aggregate and compared the sum.
|
|
54
|
+
*/
|
|
55
|
+
const getLineAmountsForTransaction = (transaction) => {
|
|
56
|
+
const localData = transaction.transactionLocalData?.transactionReviewLocalData;
|
|
57
|
+
if (localData?.lineItemById != null) {
|
|
58
|
+
const lineItems = Object.values(localData.lineItemById);
|
|
59
|
+
if (lineItems.length > 0) {
|
|
60
|
+
return lineItems.map((lineItem) => lineItem.amount?.amount ?? 0);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (transaction.transaction.lines != null &&
|
|
64
|
+
transaction.transaction.lines.length > 0) {
|
|
65
|
+
return transaction.transaction.lines.map((line) => line.amount?.amount ?? 0);
|
|
66
|
+
}
|
|
67
|
+
return [transaction.amount.amount];
|
|
68
|
+
};
|
|
34
69
|
/**
|
|
35
70
|
* Resolve the comparable value for a transaction against a given filter field.
|
|
36
71
|
* Returns undefined when the transaction doesn't carry the field — callers
|
|
37
72
|
* decide whether absence counts as a match (for 'not_equal' it does).
|
|
73
|
+
*
|
|
74
|
+
* NOTE: the `'amount'` field is NOT handled here. Amount matching is
|
|
75
|
+
* per-line (see `getLineAmountsForTransaction` + the early-return in
|
|
76
|
+
* `transactionMatchesCategory`) and would otherwise need to return a
|
|
77
|
+
* non-scalar value that doesn't fit this helper's contract.
|
|
38
78
|
*/
|
|
39
79
|
const getCategoryValueForTransaction = (key, transaction) => {
|
|
40
80
|
switch (key) {
|
|
41
81
|
case 'amount': {
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
if (localDataForAmount?.lineItemById) {
|
|
46
|
-
const lineItems = Object.values(localDataForAmount.lineItemById);
|
|
47
|
-
if (lineItems.length > 0) {
|
|
48
|
-
return lineItems.reduce((sum, lineItem) => sum + (lineItem.amount?.amount ?? 0), 0);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (transaction.transaction.lines &&
|
|
52
|
-
transaction.transaction.lines.length > 0) {
|
|
53
|
-
return transaction.transaction.lines.reduce((sum, line) => sum + (line.amount?.amount ?? 0), 0);
|
|
54
|
-
}
|
|
55
|
-
return transaction.amount.amount;
|
|
82
|
+
// Handled per-line in `transactionMatchesCategory` — see the note on
|
|
83
|
+
// this function's JSDoc and `getLineAmountsForTransaction` above.
|
|
84
|
+
return undefined;
|
|
56
85
|
}
|
|
57
86
|
case 'payee': {
|
|
58
87
|
if (isNonEmptyString(transaction.vendorName)) {
|
|
@@ -190,6 +219,27 @@ const matchAmount = (amount, category) => {
|
|
|
190
219
|
return op === 'not_equal' ? !matched : matched;
|
|
191
220
|
};
|
|
192
221
|
const transactionMatchesCategory = (transaction, category) => {
|
|
222
|
+
if (category.field === 'amount') {
|
|
223
|
+
// Per-line matching: the transaction is included if ANY of its lines
|
|
224
|
+
// satisfies the operator. This is what the user expects when a
|
|
225
|
+
// multi-line transaction contains lines at different amounts — they
|
|
226
|
+
// want the row to surface if even one line falls in the filter's
|
|
227
|
+
// range. The full row (with non-matching lines too) renders
|
|
228
|
+
// downstream; this matcher only decides row inclusion. Same
|
|
229
|
+
// operator + sign semantics as the aggregate version, just applied
|
|
230
|
+
// per line.
|
|
231
|
+
//
|
|
232
|
+
// `not_equal` deserves a sanity-check: `matchAmount` already inverts
|
|
233
|
+
// internally for that operator, so for a line whose amount is NOT
|
|
234
|
+
// in the filter's `values`, matchAmount returns true; for a line
|
|
235
|
+
// whose amount IS in the values, it returns false. `.some()` over
|
|
236
|
+
// lines therefore means "the transaction has at least one line that
|
|
237
|
+
// is not equal to any of the filter values" — which is the right
|
|
238
|
+
// match-any-line interpretation of `not_equal` (a transaction
|
|
239
|
+
// matches if it has a line that satisfies the negation).
|
|
240
|
+
const lineAmounts = getLineAmountsForTransaction(transaction);
|
|
241
|
+
return lineAmounts.some((lineAmount) => matchAmount(lineAmount, category));
|
|
242
|
+
}
|
|
193
243
|
const value = getCategoryValueForTransaction(category.field, transaction);
|
|
194
244
|
if (value == null) {
|
|
195
245
|
// Absent-value semantics — pinning these down explicitly so the
|
|
@@ -209,12 +259,10 @@ const transactionMatchesCategory = (transaction, category) => {
|
|
|
209
259
|
// are filtered out.
|
|
210
260
|
//
|
|
211
261
|
// Only the `not_equal` branch returns `true` here; everything else
|
|
212
|
-
// falls through to the `false` below.
|
|
262
|
+
// falls through to the `false` below. Note: 'amount' never lands here
|
|
263
|
+
// because it short-circuits to the per-line path above.
|
|
213
264
|
return category.matchingOperator === 'not_equal';
|
|
214
265
|
}
|
|
215
|
-
if (category.field === 'amount') {
|
|
216
|
-
return matchAmount(Number(value), category);
|
|
217
|
-
}
|
|
218
266
|
const valueStr = value.toString();
|
|
219
267
|
const valueInList = category.values.some((v) => v.toString() === valueStr);
|
|
220
268
|
return category.matchingOperator === 'not_equal' ? !valueInList : valueInList;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeniai/client-epic-state",
|
|
3
|
-
"version": "5.0.85-
|
|
3
|
+
"version": "5.0.85-betaAR3",
|
|
4
4
|
"description": "Shared module between Web & Mobile containing required abstractions for state management, async network communication. ",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "lib/esm/index.js",
|