@zeniai/client-epic-state 5.1.51-beta0RR → 5.1.51-beta1RR

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.
@@ -36,9 +36,11 @@ export const markTransactionAsNotMiscategorizedEpic = (actions$, state$, zeniAPI
36
36
  actions.push(updateTransactionIsMiscategorized(transactionKey, false));
37
37
  const { expenseAutomationViewState, expenseAutomationTransactionsViewState, } = state$.value;
38
38
  const { transactionCategorizationView, selectedTransactionCategorizationTab, } = expenseAutomationTransactionsViewState;
39
- const currentTenant = getCurrentTenant(state$.value);
39
+ const currentTenantId = getCurrentTenant(state$.value)?.tenantId;
40
40
  const { selectedPeriodByTenantId } = expenseAutomationViewState;
41
- const selectedPeriod = selectedPeriodByTenantId[currentTenant?.tenantId ?? ''];
41
+ const selectedPeriod = currentTenantId != null
42
+ ? selectedPeriodByTenantId[currentTenantId]
43
+ : undefined;
42
44
  const autoTabLineItems = transactionCategorizationView.autoCategorized
43
45
  .transactionReviewLocalDataById[txnId]
44
46
  ?.transactionReviewLocalData.tabSpecificLineItems
@@ -11,6 +11,7 @@ import { injectCOTFlagsIntoTransactionPayload, toTransactionPayload, } from '../
11
11
  import { toVendorTransactionPayload, toVendorTransactionWithCustomerPayload, } from '../../../entity/transaction/payloadTypes/vendorTransactionPayload';
12
12
  import { isCustomerTransaction, } from '../../../entity/transaction/stateTypes/customerTransaction';
13
13
  import { isTransferTransaction, } from '../../../entity/transaction/stateTypes/otherTransaction';
14
+ import { toJEAndDepositTransactionTypeStrict } from '../../../entity/transaction/stateTypes/transactionType';
14
15
  import { isVendorTransaction, } from '../../../entity/transaction/stateTypes/vendorTransaction';
15
16
  import toSupportedTransaction from '../../../entity/transaction/toSupportedTransaction';
16
17
  import { getTransactionWithCOT } from '../../../entity/transaction/transactionHelper';
@@ -50,7 +51,7 @@ export const updateTransactionDetailEpic = (actions$, state$, zeniAPI) => action
50
51
  const uncategorizedIncomeExpense = getUncategorizedAccounts(state$.value.accountState, state$.value.accountListState, 'accountList');
51
52
  const transaction = toSupportedTransaction(response.data.transaction);
52
53
  const isAccountingClassesEnabled = getIsAccountingClassesEnabled(state$.value);
53
- const eligibleLineIds = getLineIdsEligibleForReview(transaction, uncategorizedIncomeExpense, isAccountingClassesEnabled);
54
+ const eligibleLineIds = getLineIdsEligibleForReview(transaction, uncategorizedIncomeExpense, isAccountingClassesEnabled, action.payload.isExpenseAutomationEnabled);
54
55
  if (action.payload.transactionSaveUpdates.markAsMiscatUpdates ===
55
56
  true &&
56
57
  eligibleLineIds.length > 0) {
@@ -264,7 +265,34 @@ export const toTransactionUpdatePayload = (updates, detail, cotTransactionTracki
264
265
  // Inject COT flags into the payload
265
266
  return injectCOTFlagsIntoTransactionPayload(basePayload, transaction);
266
267
  };
267
- const getLineIdsEligibleForReview = (transaction, uncategorizedIncomeExpense, isAccountingClassesEnabled) => {
268
+ const getLineIdsEligibleForReview = (transaction, uncategorizedIncomeExpense, isAccountingClassesEnabled, isExpenseAutomationEnabled) => {
269
+ // For non-EA, any line missing a required field (class when enabled, or entity on JE/Deposit)
270
+ // suppresses the entire call. EA keeps the per-line model, returning only eligible line IDs.
271
+ if (!isExpenseAutomationEnabled) {
272
+ const isApplicableType = isVendorTransaction(transaction) ||
273
+ toJEAndDepositTransactionTypeStrict(transaction.type) != null;
274
+ if (isApplicableType) {
275
+ const lines = (transaction.lines ?? []).filter((line) => line.type === 'transaction_with_account_and_class_line' ||
276
+ line.type === 'journal_entry_transaction_line' ||
277
+ line.type === 'transaction_with_product_or_service_line');
278
+ if (isAccountingClassesEnabled) {
279
+ const anyLineMissingClass = lines.some((line) => (line.class?.classId ??
280
+ '') === '');
281
+ if (anyLineMissingClass) {
282
+ return [];
283
+ }
284
+ }
285
+ if (toJEAndDepositTransactionTypeStrict(transaction.type) != null) {
286
+ const anyLineMissingEntity = lines.some((line) => {
287
+ const tl = line;
288
+ return tl.vendor == null && tl.customer == null;
289
+ });
290
+ if (anyLineMissingEntity) {
291
+ return [];
292
+ }
293
+ }
294
+ }
295
+ }
268
296
  return (transaction.lines ?? [])
269
297
  .filter((line) => {
270
298
  if (line.type !== 'transaction_with_account_and_class_line' &&
@@ -274,9 +302,15 @@ const getLineIdsEligibleForReview = (transaction, uncategorizedIncomeExpense, is
274
302
  }
275
303
  const typedLine = line;
276
304
  const hasCategory = !isAccountUncategorized(uncategorizedIncomeExpense, typedLine.account?.qboId);
277
- const hasClass = !isAccountingClassesEnabled || typedLine.class != null;
305
+ // class is always a ClassBase object (never null) because the payload mapper
306
+ // calls mapClassBasePayloadToClassBase(payload ?? {}) which returns {} with classId:''.
307
+ // A real class is indicated by a non-empty classId.
308
+ const hasClass = !isAccountingClassesEnabled || (typedLine.class?.classId ?? '') !== '';
278
309
  const hasAmount = (typedLine.amount?.amount ?? 0) !== 0;
279
- const hasEntity = line.type !== 'journal_entry_transaction_line' ||
310
+ // JE lines and Deposit transactions require vendor or customer before being eligible for review.
311
+ const isJEOrDepositContext = line.type === 'journal_entry_transaction_line' ||
312
+ toJEAndDepositTransactionTypeStrict(transaction.type) != null;
313
+ const hasEntity = !isJEOrDepositContext ||
280
314
  typedLine.vendor != null ||
281
315
  typedLine.customer != null;
282
316
  return hasCategory && hasClass && hasAmount && hasEntity;
@@ -39,9 +39,11 @@ const markTransactionAsNotMiscategorizedEpic = (actions$, state$, zeniAPI) => ac
39
39
  actions.push((0, transactionReducer_1.updateTransactionIsMiscategorized)(transactionKey, false));
40
40
  const { expenseAutomationViewState, expenseAutomationTransactionsViewState, } = state$.value;
41
41
  const { transactionCategorizationView, selectedTransactionCategorizationTab, } = expenseAutomationTransactionsViewState;
42
- const currentTenant = (0, tenantSelector_1.getCurrentTenant)(state$.value);
42
+ const currentTenantId = (0, tenantSelector_1.getCurrentTenant)(state$.value)?.tenantId;
43
43
  const { selectedPeriodByTenantId } = expenseAutomationViewState;
44
- const selectedPeriod = selectedPeriodByTenantId[currentTenant?.tenantId ?? ''];
44
+ const selectedPeriod = currentTenantId != null
45
+ ? selectedPeriodByTenantId[currentTenantId]
46
+ : undefined;
45
47
  const autoTabLineItems = transactionCategorizationView.autoCategorized
46
48
  .transactionReviewLocalDataById[txnId]
47
49
  ?.transactionReviewLocalData.tabSpecificLineItems
@@ -17,6 +17,7 @@ const transactionPayload_1 = require("../../../entity/transaction/payloadTypes/t
17
17
  const vendorTransactionPayload_1 = require("../../../entity/transaction/payloadTypes/vendorTransactionPayload");
18
18
  const customerTransaction_1 = require("../../../entity/transaction/stateTypes/customerTransaction");
19
19
  const otherTransaction_1 = require("../../../entity/transaction/stateTypes/otherTransaction");
20
+ const transactionType_1 = require("../../../entity/transaction/stateTypes/transactionType");
20
21
  const vendorTransaction_1 = require("../../../entity/transaction/stateTypes/vendorTransaction");
21
22
  const toSupportedTransaction_1 = __importDefault(require("../../../entity/transaction/toSupportedTransaction"));
22
23
  const transactionHelper_1 = require("../../../entity/transaction/transactionHelper");
@@ -56,7 +57,7 @@ const updateTransactionDetailEpic = (actions$, state$, zeniAPI) => actions$.pipe
56
57
  const uncategorizedIncomeExpense = (0, accountListSelector_1.getUncategorizedAccounts)(state$.value.accountState, state$.value.accountListState, 'accountList');
57
58
  const transaction = (0, toSupportedTransaction_1.default)(response.data.transaction);
58
59
  const isAccountingClassesEnabled = (0, tenantSelector_1.getIsAccountingClassesEnabled)(state$.value);
59
- const eligibleLineIds = getLineIdsEligibleForReview(transaction, uncategorizedIncomeExpense, isAccountingClassesEnabled);
60
+ const eligibleLineIds = getLineIdsEligibleForReview(transaction, uncategorizedIncomeExpense, isAccountingClassesEnabled, action.payload.isExpenseAutomationEnabled);
60
61
  if (action.payload.transactionSaveUpdates.markAsMiscatUpdates ===
61
62
  true &&
62
63
  eligibleLineIds.length > 0) {
@@ -272,7 +273,34 @@ const toTransactionUpdatePayload = (updates, detail, cotTransactionTracking, isA
272
273
  return (0, transactionPayload_1.injectCOTFlagsIntoTransactionPayload)(basePayload, transaction);
273
274
  };
274
275
  exports.toTransactionUpdatePayload = toTransactionUpdatePayload;
275
- const getLineIdsEligibleForReview = (transaction, uncategorizedIncomeExpense, isAccountingClassesEnabled) => {
276
+ const getLineIdsEligibleForReview = (transaction, uncategorizedIncomeExpense, isAccountingClassesEnabled, isExpenseAutomationEnabled) => {
277
+ // For non-EA, any line missing a required field (class when enabled, or entity on JE/Deposit)
278
+ // suppresses the entire call. EA keeps the per-line model, returning only eligible line IDs.
279
+ if (!isExpenseAutomationEnabled) {
280
+ const isApplicableType = (0, vendorTransaction_1.isVendorTransaction)(transaction) ||
281
+ (0, transactionType_1.toJEAndDepositTransactionTypeStrict)(transaction.type) != null;
282
+ if (isApplicableType) {
283
+ const lines = (transaction.lines ?? []).filter((line) => line.type === 'transaction_with_account_and_class_line' ||
284
+ line.type === 'journal_entry_transaction_line' ||
285
+ line.type === 'transaction_with_product_or_service_line');
286
+ if (isAccountingClassesEnabled) {
287
+ const anyLineMissingClass = lines.some((line) => (line.class?.classId ??
288
+ '') === '');
289
+ if (anyLineMissingClass) {
290
+ return [];
291
+ }
292
+ }
293
+ if ((0, transactionType_1.toJEAndDepositTransactionTypeStrict)(transaction.type) != null) {
294
+ const anyLineMissingEntity = lines.some((line) => {
295
+ const tl = line;
296
+ return tl.vendor == null && tl.customer == null;
297
+ });
298
+ if (anyLineMissingEntity) {
299
+ return [];
300
+ }
301
+ }
302
+ }
303
+ }
276
304
  return (transaction.lines ?? [])
277
305
  .filter((line) => {
278
306
  if (line.type !== 'transaction_with_account_and_class_line' &&
@@ -282,9 +310,15 @@ const getLineIdsEligibleForReview = (transaction, uncategorizedIncomeExpense, is
282
310
  }
283
311
  const typedLine = line;
284
312
  const hasCategory = !(0, transactionDetailLocalDataHelper_1.isAccountUncategorized)(uncategorizedIncomeExpense, typedLine.account?.qboId);
285
- const hasClass = !isAccountingClassesEnabled || typedLine.class != null;
313
+ // class is always a ClassBase object (never null) because the payload mapper
314
+ // calls mapClassBasePayloadToClassBase(payload ?? {}) which returns {} with classId:''.
315
+ // A real class is indicated by a non-empty classId.
316
+ const hasClass = !isAccountingClassesEnabled || (typedLine.class?.classId ?? '') !== '';
286
317
  const hasAmount = (typedLine.amount?.amount ?? 0) !== 0;
287
- const hasEntity = line.type !== 'journal_entry_transaction_line' ||
318
+ // JE lines and Deposit transactions require vendor or customer before being eligible for review.
319
+ const isJEOrDepositContext = line.type === 'journal_entry_transaction_line' ||
320
+ (0, transactionType_1.toJEAndDepositTransactionTypeStrict)(transaction.type) != null;
321
+ const hasEntity = !isJEOrDepositContext ||
288
322
  typedLine.vendor != null ||
289
323
  typedLine.customer != null;
290
324
  return hasCategory && hasClass && hasAmount && hasEntity;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "5.1.51-beta0RR",
3
+ "version": "5.1.51-beta1RR",
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",