@zeniai/client-epic-state 5.0.90-betaAR8 → 5.0.90-betaAR9
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.
|
@@ -224,82 +224,86 @@ const resolveLineLevelPayee = (vendor, customer) => {
|
|
|
224
224
|
return undefined;
|
|
225
225
|
};
|
|
226
226
|
/**
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
* carries vendor/customer per line) put the payee on each line, while
|
|
230
|
-
* Bills, Expenses, and Checks carry it at the transaction level. The
|
|
231
|
-
* single-scalar predecessor only consulted the transaction level, so
|
|
232
|
-
* JE rows whose vendor lived on the line silently missed every payee
|
|
233
|
-
* filter even though the row visibly displayed the vendor name (the
|
|
234
|
-
* row's `TransactionCategorizationListRow` already reads
|
|
235
|
-
* `line.vendor?.name` for non-vendor-typed transactions).
|
|
227
|
+
* All payee names that could legitimately identify a transaction, across
|
|
228
|
+
* every place TC stores payee data:
|
|
236
229
|
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
* element array so the rest of the matcher stays per-line shaped.
|
|
230
|
+
* • localData `lineItemById[*].vendor` / `.customer` — per-line
|
|
231
|
+
* vendor/customer the user has tagged in the grid. Populated for
|
|
232
|
+
* JEs (where the row reads per-line vendor) and sometimes for
|
|
233
|
+
* vendor-typed transactions too (e.g. when an AI vendor
|
|
234
|
+
* recommendation lands on a line).
|
|
235
|
+
* • Persisted `transaction.lines[*].vendor` / `.customer` — same
|
|
236
|
+
* fields on the server snapshot of each line.
|
|
237
|
+
* • localData transaction-level `vendor` / `customer` — what the row
|
|
238
|
+
* displays for vendor-typed transactions (Bills / Expenses /
|
|
239
|
+
* Checks).
|
|
240
|
+
* • Top-level `transaction.vendorName` / `customerName` — pre-edit
|
|
241
|
+
* server snapshot.
|
|
250
242
|
*
|
|
251
|
-
*
|
|
243
|
+
* The function collects names from ALL of these into a single set
|
|
244
|
+
* rather than picking exclusively from the highest-priority source.
|
|
245
|
+
* Two reasons:
|
|
246
|
+
*
|
|
247
|
+
* 1. Different transaction types put the displayed payee in different
|
|
248
|
+
* places. JEs put it on the line; Bills put it at the transaction
|
|
249
|
+
* level. A strict-precedence rule that returns early on the first
|
|
250
|
+
* non-empty source ends up missing the OTHER place when both have
|
|
251
|
+
* data with different values — which is exactly the
|
|
252
|
+
* production failure mode that motivated this fix (a Bill whose
|
|
253
|
+
* localData line item carried an AI-recommended vendor name was
|
|
254
|
+
* excluded because the recommendation won priority over the
|
|
255
|
+
* tx-level "Amazon" the row actually displayed).
|
|
256
|
+
* 2. The single-scalar predecessor missed JE rows entirely because
|
|
257
|
+
* it only looked at the tx level. Unioning the line-level sources
|
|
258
|
+
* in fixes that without re-introducing the "first source wins"
|
|
259
|
+
* brittleness.
|
|
260
|
+
*
|
|
261
|
+
* False positives are bounded: a transaction matches `payee = X` only
|
|
262
|
+
* if `X` literally appears in one of its stored payee fields. Stale
|
|
263
|
+
* recommendation data being included means the filter is slightly more
|
|
264
|
+
* lenient than what the row displays, not arbitrary.
|
|
265
|
+
*
|
|
266
|
+
* Returns `[]` when no source carries any name; the caller falls back
|
|
252
267
|
* to the absent-value contract (only `not_equal` matches; every other
|
|
253
268
|
* operator excludes).
|
|
254
269
|
*/
|
|
255
270
|
const getLinePayeesForTransaction = (transaction) => {
|
|
256
271
|
const localData = transaction?.transactionLocalData?.transactionReviewLocalData;
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
return linePayees;
|
|
266
|
-
}
|
|
272
|
+
// Set used to dedupe — the same vendor name often appears in multiple
|
|
273
|
+
// sources (e.g. localData mirror of a persisted line) and we don't
|
|
274
|
+
// want it to be over-counted in the match list. Iteration order is
|
|
275
|
+
// insertion-order; `.some()` doesn't depend on order so this is fine.
|
|
276
|
+
const payees = new Set();
|
|
277
|
+
const addIfPresent = (name) => {
|
|
278
|
+
if (isNonEmptyString(name)) {
|
|
279
|
+
payees.add(name);
|
|
267
280
|
}
|
|
281
|
+
};
|
|
282
|
+
// localData per-line vendor / customer
|
|
283
|
+
if (localData?.lineItemById != null) {
|
|
284
|
+
Object.values(localData.lineItemById).forEach((lineItem) => {
|
|
285
|
+
addIfPresent(resolveLineLevelPayee(lineItem?.vendor, lineItem?.customer));
|
|
286
|
+
});
|
|
268
287
|
}
|
|
269
|
-
//
|
|
288
|
+
// Persisted per-line vendor / customer (only on the three line
|
|
289
|
+
// shapes that actually carry them — see `isAccountAndClassLine`).
|
|
270
290
|
if (transaction?.transaction?.lines != null &&
|
|
271
291
|
transaction.transaction.lines.length > 0) {
|
|
272
|
-
|
|
273
|
-
.map((line) => {
|
|
292
|
+
transaction.transaction.lines.forEach((line) => {
|
|
274
293
|
if (!isAccountAndClassLine(line)) {
|
|
275
|
-
return
|
|
294
|
+
return;
|
|
276
295
|
}
|
|
277
296
|
const typedLine = line;
|
|
278
|
-
|
|
279
|
-
})
|
|
280
|
-
.filter(isNonEmptyString);
|
|
281
|
-
if (linePayees.length > 0) {
|
|
282
|
-
return linePayees;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
// Priority 3: transaction-level fallback. localData wins over the
|
|
286
|
-
// top-level fields to keep the existing precedence intact: unsaved
|
|
287
|
-
// grid edits should drive the filter, not the pre-edit server state.
|
|
288
|
-
const localVendorName = localData?.vendor?.name;
|
|
289
|
-
if (isNonEmptyString(localVendorName)) {
|
|
290
|
-
return [localVendorName];
|
|
291
|
-
}
|
|
292
|
-
const localCustomerName = localData?.customer?.name;
|
|
293
|
-
if (isNonEmptyString(localCustomerName)) {
|
|
294
|
-
return [localCustomerName];
|
|
295
|
-
}
|
|
296
|
-
if (isNonEmptyString(transaction.vendorName)) {
|
|
297
|
-
return [transaction.vendorName];
|
|
298
|
-
}
|
|
299
|
-
if (isNonEmptyString(transaction.customerName)) {
|
|
300
|
-
return [transaction.customerName];
|
|
297
|
+
addIfPresent(resolveLineLevelPayee(typedLine.vendor, typedLine.customer));
|
|
298
|
+
});
|
|
301
299
|
}
|
|
302
|
-
|
|
300
|
+
// Transaction-level: localData first so unsaved grid edits make it
|
|
301
|
+
// into the match set, then the pre-edit server fields.
|
|
302
|
+
addIfPresent(localData?.vendor?.name);
|
|
303
|
+
addIfPresent(localData?.customer?.name);
|
|
304
|
+
addIfPresent(transaction?.vendorName);
|
|
305
|
+
addIfPresent(transaction?.customerName);
|
|
306
|
+
return Array.from(payees);
|
|
303
307
|
};
|
|
304
308
|
/**
|
|
305
309
|
* Resolve the comparable value for a transaction against a given filter field.
|
|
@@ -227,82 +227,86 @@ const resolveLineLevelPayee = (vendor, customer) => {
|
|
|
227
227
|
return undefined;
|
|
228
228
|
};
|
|
229
229
|
/**
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
* carries vendor/customer per line) put the payee on each line, while
|
|
233
|
-
* Bills, Expenses, and Checks carry it at the transaction level. The
|
|
234
|
-
* single-scalar predecessor only consulted the transaction level, so
|
|
235
|
-
* JE rows whose vendor lived on the line silently missed every payee
|
|
236
|
-
* filter even though the row visibly displayed the vendor name (the
|
|
237
|
-
* row's `TransactionCategorizationListRow` already reads
|
|
238
|
-
* `line.vendor?.name` for non-vendor-typed transactions).
|
|
230
|
+
* All payee names that could legitimately identify a transaction, across
|
|
231
|
+
* every place TC stores payee data:
|
|
239
232
|
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
* element array so the rest of the matcher stays per-line shaped.
|
|
233
|
+
* • localData `lineItemById[*].vendor` / `.customer` — per-line
|
|
234
|
+
* vendor/customer the user has tagged in the grid. Populated for
|
|
235
|
+
* JEs (where the row reads per-line vendor) and sometimes for
|
|
236
|
+
* vendor-typed transactions too (e.g. when an AI vendor
|
|
237
|
+
* recommendation lands on a line).
|
|
238
|
+
* • Persisted `transaction.lines[*].vendor` / `.customer` — same
|
|
239
|
+
* fields on the server snapshot of each line.
|
|
240
|
+
* • localData transaction-level `vendor` / `customer` — what the row
|
|
241
|
+
* displays for vendor-typed transactions (Bills / Expenses /
|
|
242
|
+
* Checks).
|
|
243
|
+
* • Top-level `transaction.vendorName` / `customerName` — pre-edit
|
|
244
|
+
* server snapshot.
|
|
253
245
|
*
|
|
254
|
-
*
|
|
246
|
+
* The function collects names from ALL of these into a single set
|
|
247
|
+
* rather than picking exclusively from the highest-priority source.
|
|
248
|
+
* Two reasons:
|
|
249
|
+
*
|
|
250
|
+
* 1. Different transaction types put the displayed payee in different
|
|
251
|
+
* places. JEs put it on the line; Bills put it at the transaction
|
|
252
|
+
* level. A strict-precedence rule that returns early on the first
|
|
253
|
+
* non-empty source ends up missing the OTHER place when both have
|
|
254
|
+
* data with different values — which is exactly the
|
|
255
|
+
* production failure mode that motivated this fix (a Bill whose
|
|
256
|
+
* localData line item carried an AI-recommended vendor name was
|
|
257
|
+
* excluded because the recommendation won priority over the
|
|
258
|
+
* tx-level "Amazon" the row actually displayed).
|
|
259
|
+
* 2. The single-scalar predecessor missed JE rows entirely because
|
|
260
|
+
* it only looked at the tx level. Unioning the line-level sources
|
|
261
|
+
* in fixes that without re-introducing the "first source wins"
|
|
262
|
+
* brittleness.
|
|
263
|
+
*
|
|
264
|
+
* False positives are bounded: a transaction matches `payee = X` only
|
|
265
|
+
* if `X` literally appears in one of its stored payee fields. Stale
|
|
266
|
+
* recommendation data being included means the filter is slightly more
|
|
267
|
+
* lenient than what the row displays, not arbitrary.
|
|
268
|
+
*
|
|
269
|
+
* Returns `[]` when no source carries any name; the caller falls back
|
|
255
270
|
* to the absent-value contract (only `not_equal` matches; every other
|
|
256
271
|
* operator excludes).
|
|
257
272
|
*/
|
|
258
273
|
const getLinePayeesForTransaction = (transaction) => {
|
|
259
274
|
const localData = transaction?.transactionLocalData?.transactionReviewLocalData;
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
return linePayees;
|
|
269
|
-
}
|
|
275
|
+
// Set used to dedupe — the same vendor name often appears in multiple
|
|
276
|
+
// sources (e.g. localData mirror of a persisted line) and we don't
|
|
277
|
+
// want it to be over-counted in the match list. Iteration order is
|
|
278
|
+
// insertion-order; `.some()` doesn't depend on order so this is fine.
|
|
279
|
+
const payees = new Set();
|
|
280
|
+
const addIfPresent = (name) => {
|
|
281
|
+
if (isNonEmptyString(name)) {
|
|
282
|
+
payees.add(name);
|
|
270
283
|
}
|
|
284
|
+
};
|
|
285
|
+
// localData per-line vendor / customer
|
|
286
|
+
if (localData?.lineItemById != null) {
|
|
287
|
+
Object.values(localData.lineItemById).forEach((lineItem) => {
|
|
288
|
+
addIfPresent(resolveLineLevelPayee(lineItem?.vendor, lineItem?.customer));
|
|
289
|
+
});
|
|
271
290
|
}
|
|
272
|
-
//
|
|
291
|
+
// Persisted per-line vendor / customer (only on the three line
|
|
292
|
+
// shapes that actually carry them — see `isAccountAndClassLine`).
|
|
273
293
|
if (transaction?.transaction?.lines != null &&
|
|
274
294
|
transaction.transaction.lines.length > 0) {
|
|
275
|
-
|
|
276
|
-
.map((line) => {
|
|
295
|
+
transaction.transaction.lines.forEach((line) => {
|
|
277
296
|
if (!isAccountAndClassLine(line)) {
|
|
278
|
-
return
|
|
297
|
+
return;
|
|
279
298
|
}
|
|
280
299
|
const typedLine = line;
|
|
281
|
-
|
|
282
|
-
})
|
|
283
|
-
.filter(isNonEmptyString);
|
|
284
|
-
if (linePayees.length > 0) {
|
|
285
|
-
return linePayees;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
// Priority 3: transaction-level fallback. localData wins over the
|
|
289
|
-
// top-level fields to keep the existing precedence intact: unsaved
|
|
290
|
-
// grid edits should drive the filter, not the pre-edit server state.
|
|
291
|
-
const localVendorName = localData?.vendor?.name;
|
|
292
|
-
if (isNonEmptyString(localVendorName)) {
|
|
293
|
-
return [localVendorName];
|
|
294
|
-
}
|
|
295
|
-
const localCustomerName = localData?.customer?.name;
|
|
296
|
-
if (isNonEmptyString(localCustomerName)) {
|
|
297
|
-
return [localCustomerName];
|
|
298
|
-
}
|
|
299
|
-
if (isNonEmptyString(transaction.vendorName)) {
|
|
300
|
-
return [transaction.vendorName];
|
|
301
|
-
}
|
|
302
|
-
if (isNonEmptyString(transaction.customerName)) {
|
|
303
|
-
return [transaction.customerName];
|
|
300
|
+
addIfPresent(resolveLineLevelPayee(typedLine.vendor, typedLine.customer));
|
|
301
|
+
});
|
|
304
302
|
}
|
|
305
|
-
|
|
303
|
+
// Transaction-level: localData first so unsaved grid edits make it
|
|
304
|
+
// into the match set, then the pre-edit server fields.
|
|
305
|
+
addIfPresent(localData?.vendor?.name);
|
|
306
|
+
addIfPresent(localData?.customer?.name);
|
|
307
|
+
addIfPresent(transaction?.vendorName);
|
|
308
|
+
addIfPresent(transaction?.customerName);
|
|
309
|
+
return Array.from(payees);
|
|
306
310
|
};
|
|
307
311
|
/**
|
|
308
312
|
* Resolve the comparable value for a transaction against a given filter field.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeniai/client-epic-state",
|
|
3
|
-
"version": "5.0.90-
|
|
3
|
+
"version": "5.0.90-betaAR9",
|
|
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",
|