@zeniai/client-epic-state 5.0.54 → 5.0.55-betaAR2

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.
Files changed (25) hide show
  1. package/lib/entity/account/accountSelector.d.ts +20 -1
  2. package/lib/entity/account/accountSelector.js +35 -1
  3. package/lib/entity/account/accountState.d.ts +5 -0
  4. package/lib/entity/account/accountState.js +11 -4
  5. package/lib/entity/class/classSelector.d.ts +5 -0
  6. package/lib/entity/class/classSelector.js +8 -0
  7. package/lib/esm/entity/account/accountSelector.js +33 -1
  8. package/lib/esm/entity/account/accountState.js +7 -1
  9. package/lib/esm/entity/class/classSelector.js +7 -0
  10. package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +11 -1
  11. package/lib/esm/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +57 -8
  12. package/lib/esm/view/spendManagement/spendManagementFilterHelpers.js +185 -15
  13. package/lib/esm/view/spendManagement/zeniAccounts/depositAccountDetail/depositAccountDetailSelector.js +1 -1
  14. package/lib/esm/view/spendManagement/zeniAccounts/depositAccountDetail/fetchDepositAccountDetailEpic.js +7 -2
  15. package/lib/tsconfig.typecheck.tsbuildinfo +1 -1
  16. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.d.ts +5 -1
  17. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +12 -2
  18. package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.d.ts +4 -1
  19. package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +59 -8
  20. package/lib/view/expenseAutomationView/types/transactionsViewState.d.ts +2 -0
  21. package/lib/view/spendManagement/spendManagementFilterHelpers.d.ts +45 -4
  22. package/lib/view/spendManagement/spendManagementFilterHelpers.js +186 -16
  23. package/lib/view/spendManagement/zeniAccounts/depositAccountDetail/depositAccountDetailSelector.js +1 -1
  24. package/lib/view/spendManagement/zeniAccounts/depositAccountDetail/fetchDepositAccountDetailEpic.js +7 -2
  25. package/package.json +1 -1
@@ -1,9 +1,9 @@
1
1
  import { AccountsViewParentID } from '../../commonStateTypes/accountView/nestedAccountID';
2
2
  import { ClassesViewParentID } from '../../commonStateTypes/classesView/nestedClassID';
3
- import { ProjectsViewParentID } from '../../commonStateTypes/projectView/projectViewParentID';
4
3
  import { AdditionalBalancesOptions } from '../../commonStateTypes/coaBalance/additionalBalances/getAdditionalBalances';
5
4
  import { COABalancesFilter } from '../../commonStateTypes/coaBalance/coaBalancesFilter';
6
5
  import { ID } from '../../commonStateTypes/common';
6
+ import { ProjectsViewParentID } from '../../commonStateTypes/projectView/projectViewParentID';
7
7
  import { EntityOrder } from '../../commonStateTypes/selectorTypes/selectorTypes';
8
8
  import { ReportIDPlusForecastID } from '../../commonStateTypes/viewAndReport/viewAndReport';
9
9
  import { Account, AccountBase, AccountState, AccountType } from './accountState';
@@ -123,3 +123,22 @@ export declare const getAccountsOrdered: (filter: COABalancesFilter, accountStat
123
123
  export declare const getAccountsByType: (accountState: AccountState, accountTypes: AccountType[]) => Partial<Record<AccountType, Account[]>>;
124
124
  export declare const getAccountIdsForTypes: (accountState: AccountState, accountTypes: AccountType[]) => ID[];
125
125
  export declare const getAccountIdsForLabels: (accountState: AccountState, labels: string[]) => ID[];
126
+ /**
127
+ * Returns all accounts currently in state (single pass over accountsByKey).
128
+ * Use for dropdowns that need account names and types; map to
129
+ * { accountId, accountName, accountType } as needed.
130
+ */
131
+ export declare const getAllAccounts: (accountState: AccountState) => Account[];
132
+ export interface AccountFilterOption {
133
+ accountId: ID;
134
+ accountName: string;
135
+ accountType: AccountType;
136
+ }
137
+ /**
138
+ * Partitions accounts for transaction filter dropdowns: bank/credit_card → paymentAccountNames,
139
+ * all other account types → categoryOptions. Use for payment_account_name and category filter options.
140
+ */
141
+ export declare const getTransactionFilterAccountOptions: (accountState: AccountState) => {
142
+ categoryOptions: AccountFilterOption[];
143
+ paymentAccountNames: AccountFilterOption[];
144
+ };
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getAccountIdsForLabels = exports.getAccountIdsForTypes = exports.getAccountsByType = exports.getAccountsOrdered = exports.getAccountBase = void 0;
6
+ exports.getTransactionFilterAccountOptions = exports.getAllAccounts = exports.getAccountIdsForLabels = exports.getAccountIdsForTypes = exports.getAccountsByType = exports.getAccountsOrdered = exports.getAccountBase = void 0;
7
7
  exports.getAccount = getAccount;
8
8
  exports.getAccountWithBalance = getAccountWithBalance;
9
9
  const flatMap_1 = __importDefault(require("lodash/flatMap"));
@@ -192,3 +192,37 @@ const getAccountIdsForLabels = (accountState, labels) => {
192
192
  return allAccountIdsForLabels;
193
193
  };
194
194
  exports.getAccountIdsForLabels = getAccountIdsForLabels;
195
+ /**
196
+ * Returns all accounts currently in state (single pass over accountsByKey).
197
+ * Use for dropdowns that need account names and types; map to
198
+ * { accountId, accountName, accountType } as needed.
199
+ */
200
+ const getAllAccounts = (accountState) => Object.values(accountState.accountsByKey);
201
+ exports.getAllAccounts = getAllAccounts;
202
+ /**
203
+ * Partitions accounts for transaction filter dropdowns: bank/credit_card → paymentAccountNames,
204
+ * all other account types → categoryOptions. Use for payment_account_name and category filter options.
205
+ */
206
+ const getTransactionFilterAccountOptions = (accountState) => {
207
+ const accounts = (0, exports.getAllAccounts)(accountState);
208
+ const paymentAccountNames = [];
209
+ const categoryOptions = [];
210
+ for (const account of accounts) {
211
+ if (account.accountType == null) {
212
+ continue;
213
+ }
214
+ const option = {
215
+ accountId: account.accountId,
216
+ accountName: account.accountName,
217
+ accountType: account.accountType,
218
+ };
219
+ if ((0, accountState_1.isPaymentAccountType)(account.accountType)) {
220
+ paymentAccountNames.push(option);
221
+ }
222
+ else {
223
+ categoryOptions.push(option);
224
+ }
225
+ }
226
+ return { categoryOptions, paymentAccountNames };
227
+ };
228
+ exports.getTransactionFilterAccountOptions = getTransactionFilterAccountOptions;
@@ -8,9 +8,14 @@ import { RecommendationBase } from '../../commonStateTypes/recommendationBase';
8
8
  import { Status } from '../../commonStateTypes/status';
9
9
  import { ReportIDPlusForecastID } from '../../commonStateTypes/viewAndReport/viewAndReport';
10
10
  import { ZeniUrl } from '../../zeniUrl';
11
+ export declare const ALL_ACCOUNT_TYPES: readonly ["bank", "credit_card", "expenses", "cogs", "income", "cash_in", "cash_out", "cash_position", "other_income", "other_expense", "accounts_receivable", "other_current_assets", "fixed_assets", "other_assets", "accounts_payable", "other_current_liabilities", "long_term_liabilities", "equity", "current_liabilities", "current_assets"];
11
12
  export declare const toAccountType: (v: string) => "cash_position" | "fixed_assets" | "bank" | "credit_card" | "expenses" | "cogs" | "income" | "cash_in" | "cash_out" | "other_income" | "other_expense" | "accounts_receivable" | "other_current_assets" | "other_assets" | "accounts_payable" | "other_current_liabilities" | "long_term_liabilities" | "equity" | "current_liabilities" | "current_assets";
12
13
  export type AccountType = ReturnType<typeof toAccountType>;
13
14
  export declare const toAccountTypeStrict: (v: string | null | undefined) => AccountType | undefined;
15
+ /** Account types used for payment-account filters (e.g. transaction filter "payment account name"). */
16
+ export declare const PAYMENT_ACCOUNT_TYPES: readonly ["bank", "credit_card"];
17
+ export type PaymentAccountType = (typeof PAYMENT_ACCOUNT_TYPES)[number];
18
+ export declare function isPaymentAccountType(accountType: AccountType | undefined): accountType is PaymentAccountType;
14
19
  export type UncategorizedAccountTypes = 'expense' | 'income';
15
20
  declare const toAccountLabel: (v: string) => "prepaid_expenses" | "fixed_assets" | "uncategorized_income" | "uncategorized_expense" | "bank_charges_and_fees" | "travel_transportation" | "accrued_expenses";
16
21
  export type AccountLabel = ReturnType<typeof toAccountLabel>;
@@ -1,11 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toReconciliationAccountSourceStrict = exports.toReconciliationAccountSource = exports.toAccountLabelStrict = exports.toAccountTypeStrict = exports.toAccountType = void 0;
3
+ exports.toReconciliationAccountSourceStrict = exports.toReconciliationAccountSource = exports.toAccountLabelStrict = exports.PAYMENT_ACCOUNT_TYPES = exports.toAccountTypeStrict = exports.toAccountType = exports.ALL_ACCOUNT_TYPES = void 0;
4
+ exports.isPaymentAccountType = isPaymentAccountType;
4
5
  exports.getAccountKey = getAccountKey;
5
6
  const nestedAccountID_1 = require("../../commonStateTypes/accountView/nestedAccountID");
6
7
  const nestedClassID_1 = require("../../commonStateTypes/classesView/nestedClassID");
7
8
  const stringToUnion_1 = require("../../commonStateTypes/stringToUnion");
8
- const ALL_ACCOUNT_TYPES = [
9
+ exports.ALL_ACCOUNT_TYPES = [
9
10
  'bank',
10
11
  'credit_card',
11
12
  'expenses',
@@ -27,10 +28,16 @@ const ALL_ACCOUNT_TYPES = [
27
28
  'current_liabilities',
28
29
  'current_assets',
29
30
  ];
30
- const toAccountType = (v) => (0, stringToUnion_1.stringToUnion)(v, ALL_ACCOUNT_TYPES);
31
+ const toAccountType = (v) => (0, stringToUnion_1.stringToUnion)(v, exports.ALL_ACCOUNT_TYPES);
31
32
  exports.toAccountType = toAccountType;
32
- const toAccountTypeStrict = (v) => (0, stringToUnion_1.stringToUnionStrict)(v ?? '', ALL_ACCOUNT_TYPES);
33
+ const toAccountTypeStrict = (v) => (0, stringToUnion_1.stringToUnionStrict)(v ?? '', exports.ALL_ACCOUNT_TYPES);
33
34
  exports.toAccountTypeStrict = toAccountTypeStrict;
35
+ /** Account types used for payment-account filters (e.g. transaction filter "payment account name"). */
36
+ exports.PAYMENT_ACCOUNT_TYPES = ['bank', 'credit_card'];
37
+ function isPaymentAccountType(accountType) {
38
+ return (accountType != null &&
39
+ exports.PAYMENT_ACCOUNT_TYPES.includes(accountType));
40
+ }
34
41
  const ALL_ACCOUNT_LABELS = [
35
42
  'uncategorized_income',
36
43
  'uncategorized_expense',
@@ -20,3 +20,8 @@ export declare function getClassReport(classState: ClassState, accountState: Acc
20
20
  reportId: ReportID;
21
21
  classesViewParentId?: ClassesViewParentID;
22
22
  }, filter: COABalancesFilter): ClassReport | undefined;
23
+ /**
24
+ * Returns all classes currently in state (single pass over classesByKey).
25
+ * Use for dropdowns that need class names; map to { classId, className } as needed.
26
+ */
27
+ export declare function getAllClasses(classState: ClassState): Class[];
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getClassesByIds = getClassesByIds;
7
7
  exports.getClassById = getClassById;
8
8
  exports.getClassReport = getClassReport;
9
+ exports.getAllClasses = getAllClasses;
9
10
  const get_1 = __importDefault(require("lodash/get"));
10
11
  const coaBalance_1 = require("../../commonStateTypes/coaBalance/coaBalance");
11
12
  const accountSelector_1 = require("../account/accountSelector");
@@ -58,3 +59,10 @@ function getClassReport(classState, accountState, id, filter) {
58
59
  accounts: accountReport,
59
60
  };
60
61
  }
62
+ /**
63
+ * Returns all classes currently in state (single pass over classesByKey).
64
+ * Use for dropdowns that need class names; map to { classId, className } as needed.
65
+ */
66
+ function getAllClasses(classState) {
67
+ return Object.values(classState.classesByKey);
68
+ }
@@ -1,7 +1,7 @@
1
1
  import flatMap from 'lodash/flatMap';
2
2
  import { getCOABalances } from '../../commonStateTypes/coaBalance/coaBalance';
3
3
  import { sortComparator } from '../../commonStateTypes/coaBalance/sortBalancesByFreeRangeTotal';
4
- import { getAccountKey, } from './accountState';
4
+ import { getAccountKey, isPaymentAccountType, } from './accountState';
5
5
  function getCommonAccountFields(accountState, id) {
6
6
  const key = getAccountKey(id.reportId, id.accountId, id.classesViewParentId, id.accountsViewParentId, id.projectsViewParentId);
7
7
  const account = accountState.accountsByKey[key];
@@ -179,3 +179,35 @@ export const getAccountIdsForLabels = (accountState, labels) => {
179
179
  });
180
180
  return allAccountIdsForLabels;
181
181
  };
182
+ /**
183
+ * Returns all accounts currently in state (single pass over accountsByKey).
184
+ * Use for dropdowns that need account names and types; map to
185
+ * { accountId, accountName, accountType } as needed.
186
+ */
187
+ export const getAllAccounts = (accountState) => Object.values(accountState.accountsByKey);
188
+ /**
189
+ * Partitions accounts for transaction filter dropdowns: bank/credit_card → paymentAccountNames,
190
+ * all other account types → categoryOptions. Use for payment_account_name and category filter options.
191
+ */
192
+ export const getTransactionFilterAccountOptions = (accountState) => {
193
+ const accounts = getAllAccounts(accountState);
194
+ const paymentAccountNames = [];
195
+ const categoryOptions = [];
196
+ for (const account of accounts) {
197
+ if (account.accountType == null) {
198
+ continue;
199
+ }
200
+ const option = {
201
+ accountId: account.accountId,
202
+ accountName: account.accountName,
203
+ accountType: account.accountType,
204
+ };
205
+ if (isPaymentAccountType(account.accountType)) {
206
+ paymentAccountNames.push(option);
207
+ }
208
+ else {
209
+ categoryOptions.push(option);
210
+ }
211
+ }
212
+ return { categoryOptions, paymentAccountNames };
213
+ };
@@ -1,7 +1,7 @@
1
1
  import { getNestedAccountIDStr, } from '../../commonStateTypes/accountView/nestedAccountID';
2
2
  import { getNestedClassIDStr, } from '../../commonStateTypes/classesView/nestedClassID';
3
3
  import { stringToUnion, stringToUnionStrict, } from '../../commonStateTypes/stringToUnion';
4
- const ALL_ACCOUNT_TYPES = [
4
+ export const ALL_ACCOUNT_TYPES = [
5
5
  'bank',
6
6
  'credit_card',
7
7
  'expenses',
@@ -25,6 +25,12 @@ const ALL_ACCOUNT_TYPES = [
25
25
  ];
26
26
  export const toAccountType = (v) => stringToUnion(v, ALL_ACCOUNT_TYPES);
27
27
  export const toAccountTypeStrict = (v) => stringToUnionStrict(v ?? '', ALL_ACCOUNT_TYPES);
28
+ /** Account types used for payment-account filters (e.g. transaction filter "payment account name"). */
29
+ export const PAYMENT_ACCOUNT_TYPES = ['bank', 'credit_card'];
30
+ export function isPaymentAccountType(accountType) {
31
+ return (accountType != null &&
32
+ PAYMENT_ACCOUNT_TYPES.includes(accountType));
33
+ }
28
34
  const ALL_ACCOUNT_LABELS = [
29
35
  'uncategorized_income',
30
36
  'uncategorized_expense',
@@ -50,3 +50,10 @@ export function getClassReport(classState, accountState, id, filter) {
50
50
  accounts: accountReport,
51
51
  };
52
52
  }
53
+ /**
54
+ * Returns all classes currently in state (single pass over classesByKey).
55
+ * Use for dropdowns that need class names; map to { classId, className } as needed.
56
+ */
57
+ export function getAllClasses(classState) {
58
+ return Object.values(classState.classesByKey);
59
+ }
@@ -27,6 +27,10 @@ export const initialTransactionTabViewState = {
27
27
  totalCount: {},
28
28
  limit: 10,
29
29
  },
30
+ filters: {
31
+ categoryCombinationOperator: 'AND',
32
+ categories: [],
33
+ },
30
34
  fetchState: 'Not-Started',
31
35
  error: undefined,
32
36
  hasValidState() {
@@ -236,6 +240,12 @@ const expenseAutomationTransactionsView = createSlice({
236
240
  uiState.totalCount;
237
241
  }
238
242
  },
243
+ updateTransactionFilters(draft, action) {
244
+ const { selectedTab, filters } = action.payload;
245
+ if (filters != null) {
246
+ draft.transactionCategorizationView[selectedTab].filters = filters;
247
+ }
248
+ },
239
249
  saveTransactionCategorization: {
240
250
  prepare(selectedTab, transactionIds, cotTrackingByTransactionId, isUncategorizedExpenseCategoryEnabled) {
241
251
  return {
@@ -803,5 +813,5 @@ const expenseAutomationTransactionsView = createSlice({
803
813
  },
804
814
  },
805
815
  });
806
- export const { fetchTransactionCategorization, updateTransactionCategorizationUIState, initializeTransactionCategorizationViewLocalData, saveTransactionCategorizationLocalData, fetchTransactionCategorizationFailure, saveTransactionCategorization, updateTransactionCategorization, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationSaveStatus, markTransactionAsNotMiscategorized, updateStatusForTransactionNotMiscategorizedUpdateForCategorization, updateSelectedVendorForTransaction, updateSelectedCustomerForTransaction, setAllItemsToCategoryClassInLocalDataForCategorization, updateTotalCountForTransactionCategorization, fetchTransactionCategorizationSuccess, resetOtherTabsFetchState, fetchTransactionCategorizationView, updateSelectedCheckboxTransactionIds, clearExpenseAutomationTransactionsViewPerTabView, clearExpenseAutomationTransactionsView, setEntityRecommendationForLineIdsForCategorization, updateSelectedTransactionId, syncTransactionCategorizationFromDetailSave, backgroundRefetchReviewTab, updateTransactionCategorizationUploadReceiptState, uploadTransactionCategorizationReceiptSuccess, } = expenseAutomationTransactionsView.actions;
816
+ export const { fetchTransactionCategorization, updateTransactionCategorizationUIState, updateTransactionFilters, initializeTransactionCategorizationViewLocalData, saveTransactionCategorizationLocalData, fetchTransactionCategorizationFailure, saveTransactionCategorization, updateTransactionCategorization, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationSaveStatus, markTransactionAsNotMiscategorized, updateStatusForTransactionNotMiscategorizedUpdateForCategorization, updateSelectedVendorForTransaction, updateSelectedCustomerForTransaction, setAllItemsToCategoryClassInLocalDataForCategorization, updateTotalCountForTransactionCategorization, fetchTransactionCategorizationSuccess, resetOtherTabsFetchState, fetchTransactionCategorizationView, updateSelectedCheckboxTransactionIds, clearExpenseAutomationTransactionsViewPerTabView, clearExpenseAutomationTransactionsView, setEntityRecommendationForLineIdsForCategorization, updateSelectedTransactionId, syncTransactionCategorizationFromDetailSave, backgroundRefetchReviewTab, updateTransactionCategorizationUploadReceiptState, uploadTransactionCategorizationReceiptSuccess, } = expenseAutomationTransactionsView.actions;
807
817
  export default expenseAutomationTransactionsView.reducer;
@@ -4,14 +4,33 @@ import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
4
4
  import { getCurrentTenant, getIsAccountingClassesEnabled, getIsAccountingProjectsEnabled, } from '../../../entity/tenant/tenantSelector';
5
5
  import { getTransactionWithCOT } from '../../../entity/transaction/transactionHelper';
6
6
  import { getSupportedTransactionsByIds } from '../../../entity/transaction/transactionSelector';
7
+ import { applyAdvancedFiltersOnList, } from '../../spendManagement/spendManagementFilterHelpers';
7
8
  import { getAccountList, getNestedAccountListHierarchy, getUncategorizedAccounts, } from '../../accountList/accountListSelector';
8
9
  import { getClassList, getNestedClassListHierarchy, } from '../../classList/classListSelector';
9
10
  import { getProjectList } from '../../projectList/projectListSelector';
10
11
  import { getAllSteps } from '../selectorTypes/expenseAutomationViewSelectorTypes';
12
+ export const toTransactionView = (transaction, transactionLocalData) => {
13
+ return {
14
+ id: transaction.id,
15
+ date: transaction.date,
16
+ amount: transaction.amount,
17
+ vendorName: transaction.vendorName,
18
+ customerName: transaction.customerName,
19
+ vendorId: transaction.vendorId,
20
+ customerId: transaction.customerId,
21
+ description: transaction.description,
22
+ memo: transaction.memo,
23
+ type: transaction.type,
24
+ typeName: transaction.typeName,
25
+ createTime: transaction.createTime,
26
+ transaction: transaction,
27
+ transactionLocalData,
28
+ };
29
+ };
11
30
  export function getExpenseAutomationTransactionView(state) {
12
31
  const { accountState, classState, classListState, accountListState, expenseAutomationTransactionsViewState, expenseAutomationViewState, projectState, projectListState, transactionState, monthEndCloseChecksState, monthEndCloseChecksViewState, } = state;
13
32
  const { selectedTransactionCategorizationTab } = expenseAutomationTransactionsViewState;
14
- const { uiState, refreshStatus, saveStatus, markAsNotMiscategorizedStatus, transactionIdsBySelectedPeriod, transactionReviewLocalDataById, selectedCheckBoxTransactionIds, transactionIdsWithUnsavedData, uploadReceiptStatusById, selectedTransactionId, selectedTransactionLineId, } = expenseAutomationTransactionsViewState.transactionCategorizationView[selectedTransactionCategorizationTab];
33
+ const { uiState, refreshStatus, saveStatus, markAsNotMiscategorizedStatus, transactionIdsBySelectedPeriod, transactionReviewLocalDataById, selectedCheckBoxTransactionIds, transactionIdsWithUnsavedData, uploadReceiptStatusById, selectedTransactionId, selectedTransactionLineId, filters, } = expenseAutomationTransactionsViewState.transactionCategorizationView[selectedTransactionCategorizationTab];
15
34
  const uncategorizedIncomeExpense = getUncategorizedAccounts(accountState, accountListState, 'accountList');
16
35
  const accountList = getAccountList(accountState, accountListState, 'accountList');
17
36
  const classList = getClassList(classState, classListState);
@@ -30,10 +49,42 @@ export function getExpenseAutomationTransactionView(state) {
30
49
  ? transactionIdsBySelectedPeriod[monthYearPeriodId]
31
50
  : null;
32
51
  const transactionIds = transactionIdsForSelectedPeriod ?? [];
33
- const transactionLocalData = transactionIds
34
- .map((transactionId) => {
52
+ const transactions = getSupportedTransactionsByIds(transactionState, transactionIds);
53
+ const transactionsWithCOT = transactions.map((transaction) => getTransactionWithCOT(transaction));
54
+ const transactionLocalDataMap = new Map();
55
+ transactionIds.forEach((transactionId) => {
35
56
  const transactionData = transactionReviewLocalDataById[transactionId];
36
57
  if (transactionData != null) {
58
+ transactionLocalDataMap.set(transactionId, {
59
+ transactionId,
60
+ transactionReviewLocalData: transactionData.transactionReviewLocalData,
61
+ });
62
+ }
63
+ });
64
+ // Step 2: Convert to TransactionView and apply advanced filters
65
+ const transactionViews = transactionsWithCOT.map((transaction) => {
66
+ const localData = transactionLocalDataMap.get(transaction.id);
67
+ return toTransactionView(transaction, localData);
68
+ });
69
+ // Apply advanced filters if they exist
70
+ const filteredTransactionViews = applyAdvancedFiltersOnList('transaction_categorization', transactionViews, filters);
71
+ // Convert back to TransactionWithCOT for return
72
+ const filteredTransactionsWithCOT = filteredTransactionViews.map((view) => view.transaction);
73
+ // Get transactionLocalData for filtered transactions
74
+ // Also include transactionLocalData for transactions that don't exist in transactionState
75
+ // but have local data (to match original behavior)
76
+ const filteredTransactionIds = new Set(filteredTransactionViews.map((view) => view.id));
77
+ const filteredTransactionLocalData = transactionIds
78
+ .map((transactionId) => {
79
+ const transactionData = transactionReviewLocalDataById[transactionId];
80
+ if (transactionData == null) {
81
+ return null;
82
+ }
83
+ // Include if transaction is in filtered results, or if transaction doesn't exist in state
84
+ // (to maintain backward compatibility with tests)
85
+ const transactionExists = transactions.some((t) => t.id === transactionId);
86
+ if (filteredTransactionIds.has(transactionId) ||
87
+ !transactionExists) {
37
88
  return {
38
89
  transactionId,
39
90
  transactionReviewLocalData: transactionData.transactionReviewLocalData,
@@ -41,9 +92,7 @@ export function getExpenseAutomationTransactionView(state) {
41
92
  }
42
93
  return null;
43
94
  })
44
- .filter((transaction) => transaction != null);
45
- const transactions = getSupportedTransactionsByIds(transactionState, transactionIds);
46
- const transactionsWithCOT = transactions.map((transaction) => getTransactionWithCOT(transaction));
95
+ .filter((data) => data != null);
47
96
  const monthEndFetchState = monthYearPeriodId != null
48
97
  ? (monthEndCloseChecksViewState.monthEndCloseChecksViewByTenantId[currentTenant.tenantId]?.[monthYearPeriodId] ?? { fetchState: 'Not-Started', error: undefined })
49
98
  : { fetchState: 'Not-Started', error: undefined };
@@ -90,7 +139,7 @@ export function getExpenseAutomationTransactionView(state) {
90
139
  version: 0,
91
140
  fetchState: fetchStatus.fetchState,
92
141
  error: fetchStatus.error,
93
- transactions: transactionsWithCOT,
142
+ transactions: filteredTransactionsWithCOT,
94
143
  selectedCheckBoxTransactionIds,
95
144
  transactionIdsWithUnsavedData,
96
145
  uncategorizedAccounts: uncategorizedIncomeExpense,
@@ -101,7 +150,7 @@ export function getExpenseAutomationTransactionView(state) {
101
150
  classHierarchyList,
102
151
  isAccountingProjectsEnabled,
103
152
  projectList,
104
- transactionLocalData,
153
+ transactionLocalData: filteredTransactionLocalData,
105
154
  uiState,
106
155
  refreshStatus,
107
156
  saveStatus,
@@ -7,28 +7,81 @@ import { ALL_BILL_TABS, getBillListKey, } from './billPay/billList/billListState
7
7
  import { getActualPaymentDate } from './helpers';
8
8
  import { getApproversOrRejectors as getApproversOrRejectorsForReimbursement, } from './reimbursement/remiListView/remiListSelector';
9
9
  import { ALL_REMI_TABS, getRemiListKey, } from './reimbursement/remiListView/remiListState';
10
+ export const TRANSACTION_FILTER_CATEGORIES = [
11
+ { value: 'payment_account_name', type: 'dropdown' },
12
+ { value: 'payment_account_type', type: 'dropdown' },
13
+ { value: 'payee', type: 'searchAutocomplete' },
14
+ { value: 'category', type: 'dropdown' },
15
+ { value: 'class', type: 'dropdown' },
16
+ { value: 'amount', type: 'numberRange' },
17
+ ];
10
18
  //filters items against filter values representing a range. ex: date (from-to), amount(1k<-->5k)
19
+ // For amount: supports new transaction_categorization operators (inBetween, lessThan, greaterThan) and
20
+ // backward compatibility for old format values: ["1000<->5000"] with matchingOperator "equal".
21
+ // Other entities (bill_pay, reimbursement, etc.) use only equal/not-equal and are unchanged.
11
22
  const filterItemOnRangeCategoryType = (valueForItem, currentFilter) => {
12
23
  let doesItemFallInValueRange = false;
13
24
  const filterField = currentFilter.field;
14
25
  if (filterField === 'amount') {
15
- for (const filterValue of currentFilter.values) {
16
- const currentValue = parseInt(valueForItem.toString());
17
- const lowerValue = parseInt(filterValue?.toString()?.split('<->')[0] ?? '');
18
- const higherValue = parseInt(filterValue?.toString()?.split('<->')[1] ?? '');
19
- if (currentValue >= lowerValue && currentValue <= higherValue) {
20
- doesItemFallInValueRange = true;
26
+ const currentValue = Number(valueForItem);
27
+ const op = currentFilter.matchingOperator;
28
+ const values = currentFilter.values;
29
+ if (op === 'inBetween') {
30
+ // New format: values = [min, max] (expense automation transaction_categorization only)
31
+ if (values.length >= 2) {
32
+ const min = Number(values[0]);
33
+ const max = Number(values[1]);
34
+ doesItemFallInValueRange = currentValue >= min && currentValue <= max;
21
35
  }
22
36
  }
23
- }
24
- else {
25
- const currentValue = valueForItem;
26
- const lowerValue = currentFilter.values[0];
27
- const higherValue = currentFilter.values[1];
28
- if (currentValue.isSameOrAfter(lowerValue) &&
29
- currentValue.isSameOrBefore(higherValue)) {
30
- doesItemFallInValueRange = true;
37
+ else if (op === 'lessThan') {
38
+ if (values.length >= 1) {
39
+ const threshold = Number(values[0]);
40
+ doesItemFallInValueRange = currentValue < threshold;
41
+ }
42
+ }
43
+ else if (op === 'greaterThan') {
44
+ if (values.length >= 1) {
45
+ const threshold = Number(values[0]);
46
+ doesItemFallInValueRange = currentValue > threshold;
47
+ }
48
+ }
49
+ else if (op === 'equal' &&
50
+ values.length === 1 &&
51
+ String(values[0]).includes('<->')) {
52
+ // Backward compatibility: old format ["1000<->5000"] with matchingOperator "equal"
53
+ const parts = String(values[0]).split('<->');
54
+ const lowerValue = Number(parts[0] ?? '');
55
+ const higherValue = Number(parts[1] ?? '');
56
+ doesItemFallInValueRange =
57
+ currentValue >= lowerValue && currentValue <= higherValue;
58
+ }
59
+ else if (op === 'equal' && values.length === 1) {
60
+ // New format: exact match on single value (expense automation)
61
+ const val = Number(values[0]);
62
+ doesItemFallInValueRange = currentValue === val;
63
+ }
64
+ else {
65
+ // Existing logic for equal/not-equal (bill_pay, reimbursement, etc. or multiple range values)
66
+ for (const filterValue of values) {
67
+ const lowerValue = Number.parseInt(filterValue?.toString()?.split('<->')[0] ?? '', 10);
68
+ const higherValue = Number.parseInt(filterValue?.toString()?.split('<->')[1] ?? '', 10);
69
+ if (currentValue >= lowerValue && currentValue <= higherValue) {
70
+ doesItemFallInValueRange = true;
71
+ }
72
+ }
31
73
  }
74
+ return op === 'not-equal'
75
+ ? !doesItemFallInValueRange
76
+ : doesItemFallInValueRange;
77
+ }
78
+ // Date range and other range fields (unchanged for other entities)
79
+ const currentValue = valueForItem;
80
+ const lowerValue = currentFilter.values[0];
81
+ const higherValue = currentFilter.values[1];
82
+ if (currentValue.isSameOrAfter(lowerValue) &&
83
+ currentValue.isSameOrBefore(higherValue)) {
84
+ doesItemFallInValueRange = true;
32
85
  }
33
86
  return currentFilter.matchingOperator === 'equal'
34
87
  ? doesItemFallInValueRange
@@ -113,7 +166,11 @@ export const applyAdvancedFiltersOnList = (entity, allItems, filters, filteredIt
113
166
  ? getCategoryValueForReimbursement(key, item)
114
167
  : entity === 'task_management'
115
168
  ? getCategoryValueForTaskList(key, item)
116
- : getCategoryValueForPaymentHistory(key, item);
169
+ : entity === 'transaction_categorization'
170
+ ? getCategoryValueForTransaction(key, item)
171
+ : entity === 'charge_card_payment_history'
172
+ ? getCategoryValueForPaymentHistory(key, item)
173
+ : undefined;
117
174
  if (categoryValue == null) {
118
175
  if (currentFilter.matchingOperator === 'not-equal') {
119
176
  return true;
@@ -234,6 +291,119 @@ const getCategoryValueForReimbursement = (key, reimbursement) => {
234
291
  return undefined;
235
292
  }
236
293
  };
294
+ const getCategoryValueForTransaction = (key, transaction) => {
295
+ switch (key) {
296
+ case 'amount': {
297
+ // Get from transactionLocalData line items (preferred) - sum all line item amounts
298
+ const localDataForAmount = transaction.transactionLocalData?.transactionReviewLocalData;
299
+ if (localDataForAmount?.lineItemById) {
300
+ const lineItems = Object.values(localDataForAmount.lineItemById);
301
+ if (lineItems.length > 0) {
302
+ // Sum all line item amounts
303
+ const totalAmount = lineItems.reduce((sum, lineItem) => sum + (lineItem.amount?.amount ?? 0), 0);
304
+ return totalAmount;
305
+ }
306
+ }
307
+ // Fallback to transaction-level amount or sum from transaction lines
308
+ if (transaction.transaction.lines &&
309
+ transaction.transaction.lines.length > 0) {
310
+ const totalAmount = transaction.transaction.lines.reduce((sum, line) => sum + (line.amount?.amount ?? 0), 0);
311
+ return totalAmount;
312
+ }
313
+ // Final fallback to transaction amount
314
+ return transaction.amount.amount;
315
+ }
316
+ case 'payee': {
317
+ // Priority: vendorName > customerName > from transactionLocalData
318
+ if (transaction.vendorName) {
319
+ return transaction.vendorName;
320
+ }
321
+ if (transaction.customerName) {
322
+ return transaction.customerName;
323
+ }
324
+ // Check transactionLocalData for vendor/customer
325
+ const localData = transaction.transactionLocalData?.transactionReviewLocalData;
326
+ if (localData?.vendor?.name) {
327
+ return localData.vendor.name;
328
+ }
329
+ if (localData?.customer?.name) {
330
+ return localData.customer.name;
331
+ }
332
+ return undefined;
333
+ }
334
+ case 'payment_account_name': {
335
+ // Match the value the row renders: TransactionCategorizationListRow reads
336
+ // `currentTransaction?.account?.accountName` for the Payment Account Name
337
+ // column. Returning the same field here keeps filter-match parity with the
338
+ // visible cell, so any row a user can see is also a row the filter can match.
339
+ return transaction.transaction.account?.accountName;
340
+ }
341
+ case 'payment_account_type': {
342
+ // Compare against the raw `paymentType` enum (e.g. "credit_card", "check",
343
+ // "cash"). The same enum is what TransactionCategorizationListRow uses for
344
+ // its icon switch on line ~1675, and it's what the dropdown options emit
345
+ // as `value`. Display labels come from each account's `paymentTypeName`
346
+ // field (populated by mapAccountBasePayloadToAccountBase from the backend
347
+ // payload) — no client-side label map required.
348
+ return transaction.transaction.paymentType;
349
+ }
350
+ case 'category': {
351
+ // Get from transactionLocalData (preferred) or from transaction lines
352
+ const localDataForCategory = transaction.transactionLocalData?.transactionReviewLocalData;
353
+ if (localDataForCategory) {
354
+ // Get category from first line item or most common
355
+ const firstLineItem = Object.values(localDataForCategory.lineItemById)[0];
356
+ if (firstLineItem?.account?.accountId) {
357
+ return firstLineItem.account.accountId;
358
+ }
359
+ if (firstLineItem?.account?.qboId) {
360
+ return firstLineItem.account.qboId;
361
+ }
362
+ if (firstLineItem?.account?.accountName) {
363
+ return firstLineItem.account.accountName;
364
+ }
365
+ }
366
+ // Fallback to transaction lines
367
+ const firstLineForCategory = transaction.transaction.lines?.[0];
368
+ if (firstLineForCategory?.type ===
369
+ 'transaction_with_account_and_class_line' ||
370
+ firstLineForCategory?.type ===
371
+ 'transaction_with_product_or_service_line' ||
372
+ firstLineForCategory?.type === 'journal_entry_transaction_line') {
373
+ const account = firstLineForCategory.account;
374
+ return account?.accountId ?? account?.qboId ?? account?.accountName;
375
+ }
376
+ return undefined;
377
+ }
378
+ case 'class': {
379
+ // Similar to category but for class
380
+ const localDataForClass = transaction.transactionLocalData?.transactionReviewLocalData;
381
+ if (localDataForClass) {
382
+ const firstLineItemForClass = Object.values(localDataForClass.lineItemById)[0];
383
+ if (firstLineItemForClass?.class?.classId) {
384
+ return firstLineItemForClass.class.classId;
385
+ }
386
+ if (firstLineItemForClass?.class?.qboId) {
387
+ return firstLineItemForClass.class.qboId;
388
+ }
389
+ if (firstLineItemForClass?.class?.className) {
390
+ return firstLineItemForClass.class.className;
391
+ }
392
+ }
393
+ const firstLineForClass = transaction.transaction.lines?.[0];
394
+ if (firstLineForClass?.type === 'transaction_with_account_and_class_line' ||
395
+ firstLineForClass?.type ===
396
+ 'transaction_with_product_or_service_line' ||
397
+ firstLineForClass?.type === 'journal_entry_transaction_line') {
398
+ const classData = firstLineForClass.class;
399
+ return classData?.classId ?? classData?.qboId ?? classData?.className;
400
+ }
401
+ return undefined;
402
+ }
403
+ default:
404
+ return undefined;
405
+ }
406
+ };
237
407
  const getCategoryValueForPaymentHistory = (key, repayment) => {
238
408
  switch (key) {
239
409
  case 'repaymentDate':
@@ -30,7 +30,7 @@ export const getDepositAccountDetail = (state, depositAccountId) => {
30
30
  const zeniAccountDetail = zeniAccountDetailState.depositAccountDetailById[depositAccountId];
31
31
  const depositAccount = getDepositAccountByDepositAccountId(depositAccountState, depositAccountId);
32
32
  const zeniAccountsConfig = getZeniAccountsConfigDetail(state);
33
- const transactionListView = getDepositAccountTransactionList(state, depositAccountId, 5);
33
+ const transactionListView = getDepositAccountTransactionList(state, depositAccountId);
34
34
  let accountUpdateStatus = {
35
35
  editName: {
36
36
  fetchState: 'Not-Started',
@@ -16,7 +16,7 @@ export const fetchDepositAccountDetailEpic = (actions$, state$) => actions$.pipe
16
16
  zeniAccountActions.push(fetchPaymentAccountList());
17
17
  zeniAccountActions.push(fetchZeniAccountsConfig());
18
18
  zeniAccountActions.push(fetchDepositAccount(depositAccountId));
19
- zeniAccountActions.push(fetchDepositAccountTransactionList(depositAccountId, true, 5));
19
+ zeniAccountActions.push(fetchDepositAccountTransactionList(depositAccountId, true));
20
20
  zeniAccountActions.push(fetchAccountHistory(depositAccountId));
21
21
  }
22
22
  else {
@@ -37,7 +37,12 @@ export const fetchDepositAccountDetailEpic = (actions$, state$) => actions$.pipe
37
37
  targetZeniAccountAccount.fetchState !== 'In-Progress')) {
38
38
  zeniAccountActions.push(fetchDepositAccount(depositAccountId));
39
39
  }
40
- zeniAccountActions.push(fetchDepositAccountTransactionList(depositAccountId, false, 5));
40
+ const transactionList = state.depositAccountTransactionListState.byDepositId[depositAccountId];
41
+ if (transactionList == null ||
42
+ (transactionList.fetchState !== 'Completed' &&
43
+ transactionList.fetchState !== 'In-Progress')) {
44
+ zeniAccountActions.push(fetchDepositAccountTransactionList(depositAccountId, false));
45
+ }
41
46
  const zeniAccountsConfig = state.zeniAccountsConfigState;
42
47
  if (zeniAccountsConfig.hasValidState() === false &&
43
48
  zeniAccountsConfig.fetchState !== 'In-Progress') {