@zeniai/client-epic-state 4.19.51 → 4.19.53-betaAR1

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 (74) hide show
  1. package/lib/entity/account/accountSelector.d.ts +18 -0
  2. package/lib/entity/account/accountSelector.js +31 -1
  3. package/lib/entity/account/accountState.d.ts +5 -0
  4. package/lib/entity/account/accountState.js +11 -4
  5. package/lib/entity/chargeCardRepayment/chargeCardRepayment.d.ts +6 -1
  6. package/lib/entity/chargeCardRepayment/chargeCardRepayment.js +4 -1
  7. package/lib/entity/chargeCardRepayment/chargeCardRepaymentPayload.d.ts +8 -3
  8. package/lib/entity/chargeCardRepayment/chargeCardRepaymentPayload.js +5 -2
  9. package/lib/entity/class/classSelector.d.ts +5 -0
  10. package/lib/entity/class/classSelector.js +8 -0
  11. package/lib/epic.d.ts +3 -1
  12. package/lib/epic.js +3 -1
  13. package/lib/esm/entity/account/accountSelector.js +29 -1
  14. package/lib/esm/entity/account/accountState.js +7 -1
  15. package/lib/esm/entity/chargeCardRepayment/chargeCardRepayment.js +2 -0
  16. package/lib/esm/entity/chargeCardRepayment/chargeCardRepaymentPayload.js +6 -3
  17. package/lib/esm/entity/class/classSelector.js +7 -0
  18. package/lib/esm/epic.js +3 -1
  19. package/lib/esm/index.js +13 -7
  20. package/lib/esm/reducer.js +3 -0
  21. package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +11 -1
  22. package/lib/esm/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +57 -8
  23. package/lib/esm/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistory.js +32 -0
  24. package/lib/esm/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryDownloadSelector.js +22 -0
  25. package/lib/esm/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryHelpers.js +79 -0
  26. package/lib/esm/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryPayload.js +1 -0
  27. package/lib/esm/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryReducer.js +83 -0
  28. package/lib/esm/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistorySelector.js +104 -0
  29. package/lib/esm/view/spendManagement/chargeCards/chargeCardPaymentHistory/fetchChargeCardPaymentHistoryEpic.js +31 -0
  30. package/lib/esm/view/spendManagement/chargeCards/chargeCardPaymentHistory/fetchChargeCardPaymentPageEpic.js +31 -0
  31. package/lib/esm/view/spendManagement/chargeCards/chargeCardRepaymentDetail/chargeCardRepaymentDetailSelector.js +12 -6
  32. package/lib/esm/view/spendManagement/chargeCards/chargeCardRepaymentDetail/fetchChargeCardRepaymentDetailEpic.js +2 -2
  33. package/lib/esm/view/spendManagement/chargeCards/chargeCardRepaymentDetail/initiateChargeCardRepaymentEpic.js +5 -0
  34. package/lib/esm/view/spendManagement/chargeCards/chargeCardSetUp/epic/fetchChargeCardSetupViewEpic.js +1 -1
  35. package/lib/esm/view/spendManagement/commonSetup/setupViewSelector.js +15 -0
  36. package/lib/esm/view/spendManagement/spendManagementFilterHelpers.js +226 -16
  37. package/lib/esm/view/spendManagement/zeniAccounts/depositAccountList/fetchDepositAccountListEpic.js +5 -3
  38. package/lib/index.d.ts +16 -10
  39. package/lib/index.js +53 -29
  40. package/lib/reducer.d.ts +3 -0
  41. package/lib/reducer.js +3 -0
  42. package/lib/tsconfig.typecheck.tsbuildinfo +1 -1
  43. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.d.ts +5 -1
  44. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +12 -2
  45. package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.d.ts +4 -1
  46. package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +59 -8
  47. package/lib/view/expenseAutomationView/types/transactionsViewState.d.ts +2 -0
  48. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistory.d.ts +50 -0
  49. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistory.js +35 -0
  50. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryDownloadSelector.d.ts +8 -0
  51. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryDownloadSelector.js +26 -0
  52. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryHelpers.d.ts +7 -0
  53. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryHelpers.js +87 -0
  54. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryPayload.d.ts +13 -0
  55. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryPayload.js +2 -0
  56. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryReducer.d.ts +19 -0
  57. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryReducer.js +87 -0
  58. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistorySelector.d.ts +24 -0
  59. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistorySelector.js +108 -0
  60. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/fetchChargeCardPaymentHistoryEpic.d.ts +9 -0
  61. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/fetchChargeCardPaymentHistoryEpic.js +35 -0
  62. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/fetchChargeCardPaymentPageEpic.d.ts +7 -0
  63. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/fetchChargeCardPaymentPageEpic.js +35 -0
  64. package/lib/view/spendManagement/chargeCards/chargeCardRepaymentDetail/chargeCardRepaymentDetailSelector.js +10 -4
  65. package/lib/view/spendManagement/chargeCards/chargeCardRepaymentDetail/fetchChargeCardRepaymentDetailEpic.js +2 -2
  66. package/lib/view/spendManagement/chargeCards/chargeCardRepaymentDetail/initiateChargeCardRepaymentEpic.d.ts +2 -1
  67. package/lib/view/spendManagement/chargeCards/chargeCardRepaymentDetail/initiateChargeCardRepaymentEpic.js +5 -0
  68. package/lib/view/spendManagement/chargeCards/chargeCardSetUp/epic/fetchChargeCardSetupViewEpic.js +1 -1
  69. package/lib/view/spendManagement/commonSetup/setupViewSelector.d.ts +2 -1
  70. package/lib/view/spendManagement/commonSetup/setupViewSelector.js +17 -1
  71. package/lib/view/spendManagement/spendManagementFilterHelpers.d.ts +47 -4
  72. package/lib/view/spendManagement/spendManagementFilterHelpers.js +227 -17
  73. package/lib/view/spendManagement/zeniAccounts/depositAccountList/fetchDepositAccountListEpic.js +5 -3
  74. package/package.json +1 -1
@@ -0,0 +1,83 @@
1
+ import { createSlice } from '@reduxjs/toolkit';
2
+ import { initialChargeCardPaymentHistoryState, } from './chargeCardPaymentHistory';
3
+ export const initialState = initialChargeCardPaymentHistoryState;
4
+ const chargeCardPaymentHistory = createSlice({
5
+ name: 'chargeCardPaymentHistory',
6
+ initialState,
7
+ reducers: {
8
+ fetchChargeCardPaymentPage: {
9
+ reducer() {
10
+ // do nothing
11
+ },
12
+ prepare(cacheOverride = false) {
13
+ return {
14
+ payload: {
15
+ cacheOverride,
16
+ },
17
+ };
18
+ },
19
+ },
20
+ fetchChargeCardPaymentHistory(draft) {
21
+ draft.fetchState = 'In-Progress';
22
+ draft.error = undefined;
23
+ },
24
+ updateChargeCardPaymentHistory(draft, action) {
25
+ const payload = action.payload;
26
+ draft.creditAccountId = payload.credit_account_id;
27
+ draft.currencyCode = payload.currency_code;
28
+ draft.currencySymbol = payload.currency_symbol;
29
+ draft.isAutoPayEnabled = payload.is_auto_pay_enabled;
30
+ draft.repaymentHistoryIds = payload.repayment_history.map((repayment) => repayment.card_repayment_id);
31
+ draft.totalRepayments = payload.total_repayments;
32
+ draft.fetchState = 'Completed';
33
+ draft.error = undefined;
34
+ },
35
+ updateChargeCardPaymentHistoryFailure(draft, action) {
36
+ draft.fetchState = 'Error';
37
+ draft.error = action.payload;
38
+ },
39
+ addRepaymentToChargeCardPaymentHistory(draft, action) {
40
+ const { cardRepaymentId, zeniCreditAccountId } = action.payload;
41
+ if (draft.creditAccountId !== zeniCreditAccountId) {
42
+ return;
43
+ }
44
+ if (draft.repaymentHistoryIds.includes(cardRepaymentId)) {
45
+ return;
46
+ }
47
+ draft.repaymentHistoryIds.unshift(cardRepaymentId);
48
+ draft.totalRepayments += 1;
49
+ },
50
+ clearChargeCardPaymentHistory(draft) {
51
+ Object.assign(draft, initialState);
52
+ },
53
+ updatePaymentHistoryFilters(draft, action) {
54
+ if (action.payload.filters != null) {
55
+ draft.filters = action.payload.filters;
56
+ }
57
+ },
58
+ updatePaymentHistorySearchText(draft, action) {
59
+ if (action.payload != null) {
60
+ draft.searchText = action.payload;
61
+ }
62
+ },
63
+ updatePaymentHistoryUIState(draft, action) {
64
+ if (action.payload.uiState.sortKey != null) {
65
+ draft.uiState.sortKey = action.payload.uiState.sortKey;
66
+ }
67
+ if (action.payload.uiState.sortOrder != null) {
68
+ draft.uiState.sortOrder = action.payload.uiState.sortOrder;
69
+ }
70
+ if (action.payload.uiState.scrollYOffset != null) {
71
+ draft.uiState.scrollYOffset = action.payload.uiState.scrollYOffset;
72
+ }
73
+ },
74
+ updatePaymentHistoryDownloadUIState(draft, action) {
75
+ if (action.payload.uiState.downloadState != null) {
76
+ draft.downloadUIState.downloadState =
77
+ action.payload.uiState.downloadState;
78
+ }
79
+ },
80
+ },
81
+ });
82
+ export const { addRepaymentToChargeCardPaymentHistory, fetchChargeCardPaymentPage, fetchChargeCardPaymentHistory, updateChargeCardPaymentHistory, updateChargeCardPaymentHistoryFailure, clearChargeCardPaymentHistory, updatePaymentHistoryFilters, updatePaymentHistorySearchText, updatePaymentHistoryDownloadUIState, updatePaymentHistoryUIState, } = chargeCardPaymentHistory.actions;
83
+ export default chargeCardPaymentHistory.reducer;
@@ -0,0 +1,104 @@
1
+ import { reduceAllFetchState } from '../../../../commonStateTypes/reduceFetchState';
2
+ import { getChargeCardRepaymentByIds } from '../../../../entity/chargeCardRepayment/chargeCardRepaymentSelector';
3
+ import { getUsersByUserIds } from '../../../../entity/user/userSelector';
4
+ import { getAllFundingAccounts, mapDepositAccToFundingAccount, mapPaymentAccToFundingAccount, } from '../../commonSetup/setupViewSelector';
5
+ import { getDepositAccountByDepositAccountId } from '../../../../entity/depositAccount/depositAccountSelector';
6
+ import { getPaymentAccountByPaymentAccountId } from '../../../../entity/paymentAccount/paymentAccountSelector';
7
+ import { getAllDepositAccounts } from '../../zeniAccounts/depositAccountList/depositAccountListSelector';
8
+ import { getAllPaymentAccounts } from '../../zeniAccounts/paymentAccountList/paymentAccountListSelector';
9
+ import { applyPaymentHistoryFiltersSearchAndSort } from './chargeCardPaymentHistoryHelpers';
10
+ export const getChargeCardPaymentHistoryView = (state) => {
11
+ const { chargeCardPaymentHistoryState, chargeCardRepaymentState, userState, depositAccountState, paymentAccountState, depositAccountListState, paymentAccountListState, } = state;
12
+ const { creditAccountId, currencyCode, currencySymbol, filters, isAutoPayEnabled, repaymentHistoryIds, searchText, totalRepayments, uiState, fetchState, error, } = chargeCardPaymentHistoryState;
13
+ // Get the full repayment details from the entity state
14
+ const repaymentHistory = getChargeCardRepaymentByIds(chargeCardRepaymentState, repaymentHistoryIds);
15
+ const repaymentHistoryUserIds = Array.from(new Set(repaymentHistory.map((r) => r.paidById)));
16
+ // Get users first to enrich repayments
17
+ const repaymentHistoryUsers = getUsersByUserIds(userState, repaymentHistoryUserIds);
18
+ // Create a map of userId -> User for quick lookup
19
+ const userMap = new Map();
20
+ repaymentHistoryUsers.forEach((user) => {
21
+ userMap.set(user.userId, user);
22
+ });
23
+ // Enrich repayments with paidByUser details
24
+ let repaymentHistoryWithUsers = repaymentHistory.map((repayment) => ({
25
+ ...repayment,
26
+ paidByUser: userMap.get(repayment.paidById),
27
+ }));
28
+ // Get deposit and payment accounts views (used for funding accounts and fetch state)
29
+ const depositAccountsView = getAllDepositAccounts(depositAccountState, depositAccountListState);
30
+ const paymentAccountsView = getAllPaymentAccounts(paymentAccountState, paymentAccountListState);
31
+ // Extract unique funding account IDs from unfiltered repayment history (for accountInfoMap used in filter/sort)
32
+ const repaymentHistoryFundingAccountIdsForMap = Array.from(new Set(repaymentHistory
33
+ .map((repayment) => repayment.paymentAccountId)
34
+ .filter((id) => id !== undefined && id !== null)));
35
+ let fundingAccountsForMap = [];
36
+ if (repaymentHistoryFundingAccountIdsForMap.length > 0) {
37
+ const hasDepositData = depositAccountsView.fetchState === 'Completed';
38
+ const hasPaymentData = paymentAccountsView.fetchState === 'Completed';
39
+ if (hasDepositData || hasPaymentData) {
40
+ const matchingDepositAccountsForMap = hasDepositData
41
+ ? depositAccountsView.depositAccounts.filter((depositAccount) => repaymentHistoryFundingAccountIdsForMap.includes(depositAccount.id))
42
+ : [];
43
+ const matchingPaymentAccountsForMap = hasPaymentData
44
+ ? paymentAccountsView.paymentAccounts.filter((paymentAccount) => repaymentHistoryFundingAccountIdsForMap.includes(paymentAccount.paymentAccountId))
45
+ : [];
46
+ fundingAccountsForMap = getAllFundingAccounts(matchingDepositAccountsForMap, matchingPaymentAccountsForMap, paymentAccountListState.paymentAccountBalanceFetchStatus, false);
47
+ // Fallback: look up repayment-referenced accounts directly from entity stores
48
+ // in case they're not in the list state (e.g. race condition, stale list)
49
+ const existingIds = new Set(fundingAccountsForMap.map((fa) => fa.id).filter(Boolean));
50
+ for (const accountId of repaymentHistoryFundingAccountIdsForMap) {
51
+ if (existingIds.has(accountId)) {
52
+ continue;
53
+ }
54
+ const depositAccount = getDepositAccountByDepositAccountId(depositAccountState, accountId);
55
+ if (depositAccount != null) {
56
+ fundingAccountsForMap.push(mapDepositAccToFundingAccount(depositAccount));
57
+ existingIds.add(accountId);
58
+ continue;
59
+ }
60
+ const paymentAccount = getPaymentAccountByPaymentAccountId(paymentAccountState, accountId);
61
+ if (paymentAccount != null) {
62
+ fundingAccountsForMap.push(mapPaymentAccToFundingAccount(paymentAccount, paymentAccountListState.paymentAccountBalanceFetchStatus));
63
+ existingIds.add(accountId);
64
+ }
65
+ }
66
+ }
67
+ }
68
+ const accountInfoByAccountId = {};
69
+ fundingAccountsForMap.forEach((fa) => {
70
+ if (fa.id != null) {
71
+ accountInfoByAccountId[fa.id] = {
72
+ accountId: fa.id,
73
+ accountLogo: fa.logo,
74
+ accountType: fa.accType === 'depositAccount' ? 'depositAccount' : 'paymentAccount',
75
+ depositAccountLast4: fa.accountLast4,
76
+ depositAccountName: fa.label,
77
+ };
78
+ }
79
+ });
80
+ repaymentHistoryWithUsers = applyPaymentHistoryFiltersSearchAndSort(repaymentHistoryWithUsers, filters, searchText, uiState, accountInfoByAccountId);
81
+ const reducedFetchState = reduceAllFetchState([
82
+ { fetchState, error },
83
+ depositAccountsView,
84
+ paymentAccountsView,
85
+ ...Object.values(paymentAccountListState.paymentAccountBalanceFetchStatus),
86
+ ]);
87
+ return {
88
+ accountInfoByAccountId,
89
+ creditAccountId,
90
+ currencyCode,
91
+ currencySymbol,
92
+ filters,
93
+ isAutoPayEnabled,
94
+ repaymentHistory: repaymentHistoryWithUsers,
95
+ repaymentHistoryIds,
96
+ repaymentHistoryUsers,
97
+ searchText,
98
+ totalRepayments,
99
+ uiState,
100
+ fetchState: reducedFetchState.fetchState,
101
+ error: reducedFetchState.error,
102
+ version: 0,
103
+ };
104
+ };
@@ -0,0 +1,31 @@
1
+ import { of } from 'rxjs';
2
+ import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
3
+ import { updateChargeCardRepayments } from '../../../../entity/chargeCardRepayment/chargeCardRepaymentReducer';
4
+ import { updateAllUsers } from '../../../../entity/user/userReducer';
5
+ import { createZeniAPIStatus, isSuccessStatus, } from '../../../../responsePayload';
6
+ import { fetchChargeCardPaymentHistory, updateChargeCardPaymentHistory, updateChargeCardPaymentHistoryFailure, } from './chargeCardPaymentHistoryReducer';
7
+ export const fetchChargeCardPaymentHistoryEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchChargeCardPaymentHistory.match), switchMap(() => {
8
+ const queryParams = { include_history: true };
9
+ const queryString = encodeURIComponent(JSON.stringify(queryParams));
10
+ return zeniAPI
11
+ .getJSON(`${zeniAPI.apiEndPoints.cardMicroServiceBaseUrl}/1.0/cards/repayments?query=${queryString}`)
12
+ .pipe(mergeMap((response) => {
13
+ if (isSuccessStatus(response) && response.data != null) {
14
+ return of(
15
+ // Store repayments FIRST before marking as completed
16
+ updateChargeCardRepayments(response.data.repayment_history), updateAllUsers({
17
+ users: response.data.repayment_history
18
+ .map((repayment) => repayment.paid_by_user)
19
+ .filter((user) => user != null),
20
+ }),
21
+ // Mark as completed LAST, after all data is stored
22
+ updateChargeCardPaymentHistory(response.data));
23
+ }
24
+ else {
25
+ return of(updateChargeCardPaymentHistoryFailure(response.status));
26
+ }
27
+ }), catchError((error) => {
28
+ return of(updateChargeCardPaymentHistoryFailure(createZeniAPIStatus('Unexpected Error', 'Fetch charge card payment history REST API call errored out' +
29
+ JSON.stringify(error))));
30
+ }));
31
+ }));
@@ -0,0 +1,31 @@
1
+ import { concat, from } from 'rxjs';
2
+ import { filter, mergeMap } from 'rxjs/operators';
3
+ import { fetchChargeCardRepaymentDetail } from '../chargeCardRepaymentDetail/chargeCardRepaymentDetailReducer';
4
+ import { fetchChargeCardPaymentHistory, fetchChargeCardPaymentPage, } from './chargeCardPaymentHistoryReducer';
5
+ export const fetchChargeCardPaymentPageEpic = (actions$, state$) => actions$.pipe(filter(fetchChargeCardPaymentPage.match), mergeMap((action) => {
6
+ const { cacheOverride } = action.payload;
7
+ const chargeCardPaymentPageActions = [];
8
+ const state = state$.value;
9
+ const chargeCardPaymentHistoryState = state.chargeCardPaymentHistoryState;
10
+ if (cacheOverride === true) {
11
+ chargeCardPaymentPageActions.push(fetchChargeCardPaymentHistory());
12
+ }
13
+ else {
14
+ if (chargeCardPaymentHistoryState.fetchState !== 'Completed' &&
15
+ chargeCardPaymentHistoryState.fetchState !== 'In-Progress') {
16
+ chargeCardPaymentPageActions.push(fetchChargeCardPaymentHistory());
17
+ }
18
+ }
19
+ // Fetch repayment detail if deposit or payment accounts are not fetched
20
+ //These are needed to get the transfer from accounts for the repayment history
21
+ const depositAccountListState = state.depositAccountListState;
22
+ const paymentAccountListState = state.paymentAccountListState;
23
+ const needsRepaymentDetailFetch = (depositAccountListState.fetchState !== 'Completed' &&
24
+ depositAccountListState.fetchState !== 'In-Progress') ||
25
+ (paymentAccountListState.fetchState !== 'Completed' &&
26
+ paymentAccountListState.fetchState !== 'In-Progress');
27
+ if (needsRepaymentDetailFetch) {
28
+ chargeCardPaymentPageActions.push(fetchChargeCardRepaymentDetail());
29
+ }
30
+ return concat(from(chargeCardPaymentPageActions));
31
+ }));
@@ -1,5 +1,5 @@
1
- import { reduceAllFetchState } from '../../../../commonStateTypes/reduceFetchState';
2
- import { getAllFundingAccounts, } from '../../commonSetup/setupViewSelector';
1
+ import { reduceAnyCompletedFetchState } from '../../../../commonStateTypes/reduceFetchState';
2
+ import { getAllFundingAccounts, mapDepositAccountWithLimitToFundingAccount, } from '../../commonSetup/setupViewSelector';
3
3
  import { getAllDepositAccounts } from '../../zeniAccounts/depositAccountList/depositAccountListSelector';
4
4
  import { getAllPaymentAccounts } from '../../zeniAccounts/paymentAccountList/paymentAccountListSelector';
5
5
  export const getChargeCardRepaymentDetail = (state) => {
@@ -8,11 +8,17 @@ export const getChargeCardRepaymentDetail = (state) => {
8
8
  const depositAccountsView = getAllDepositAccounts(depositAccountState, depositAccountListState);
9
9
  const paymentAccountsView = getAllPaymentAccounts(paymentAccountState, paymentAccountListState);
10
10
  let transferFromAccounts = [];
11
- if (depositAccountsView.fetchState === 'Completed' &&
12
- paymentAccountsView.fetchState === 'Completed') {
13
- transferFromAccounts = getAllFundingAccounts(depositAccountsView.depositAccounts, paymentAccountsView.paymentAccounts, paymentAccountListState.paymentAccountBalanceFetchStatus);
11
+ if (paymentAccountsView.fetchState === 'Completed') {
12
+ if (depositAccountsView.fetchState === 'Completed') {
13
+ transferFromAccounts = getAllFundingAccounts(depositAccountsView.depositAccounts, paymentAccountsView.paymentAccounts, paymentAccountListState.paymentAccountBalanceFetchStatus);
14
+ }
15
+ else {
16
+ const depositFundingFromLimit = Object.values(depositAccountState.depositAccountLimitForChargeCardByID ?? {}).map(mapDepositAccountWithLimitToFundingAccount);
17
+ const paymentFundingAccounts = getAllFundingAccounts([], paymentAccountsView.paymentAccounts, paymentAccountListState.paymentAccountBalanceFetchStatus);
18
+ transferFromAccounts = [...depositFundingFromLimit, ...paymentFundingAccounts];
19
+ }
14
20
  }
15
- const reducedFetchState = reduceAllFetchState([
21
+ const reducedFetchState = reduceAnyCompletedFetchState([
16
22
  depositAccountsView,
17
23
  paymentAccountsView,
18
24
  ...Object.values(paymentAccountListState.paymentAccountBalanceFetchStatus),
@@ -7,8 +7,8 @@ export const fetchChargeCardRepaymentDetailEpic = (actions$, state$) => actions$
7
7
  const fetchChargeCardRepaymentDetailActions = [];
8
8
  const state = state$.value;
9
9
  const depositAccounts = state.depositAccountListState;
10
- if (depositAccounts.fetchState !== 'In-Progress' &&
11
- depositAccounts.fetchState !== 'Completed') {
10
+ const willFetchDeposit = depositAccounts.fetchState !== 'In-Progress' && depositAccounts.fetchState !== 'Completed';
11
+ if (willFetchDeposit) {
12
12
  fetchChargeCardRepaymentDetailActions.push(fetchDepositAccountList());
13
13
  }
14
14
  const paymentAccounts = state.paymentAccountListState;
@@ -9,6 +9,7 @@ import { getAllFundingAccounts } from '../../commonSetup/setupViewSelector';
9
9
  import { accountTypeSubConfigCodeToAccType } from '../../helpers';
10
10
  import { getAllDepositAccounts } from '../../zeniAccounts/depositAccountList/depositAccountListSelector';
11
11
  import { getAllPaymentAccounts } from '../../zeniAccounts/paymentAccountList/paymentAccountListSelector';
12
+ import { addRepaymentToChargeCardPaymentHistory } from '../chargeCardPaymentHistory/chargeCardPaymentHistoryReducer';
12
13
  import { updateCreditAccountRepaymentAmountAndDate } from '../chargeCardList/chargeCardListReducer';
13
14
  import { initiateChargeCardRepayment, updateChargeCardRepaymentStatus, } from './chargeCardRepaymentDetailReducer';
14
15
  export const initiateChargeCardRepaymentEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(initiateChargeCardRepayment.match), switchMap((action) => {
@@ -36,6 +37,10 @@ export const initiateChargeCardRepaymentEpic = (actions$, state$, zeniAPI) => ac
36
37
  if (isSuccessResponse(response) && response.data != null) {
37
38
  const actions = [
38
39
  updateChargeCardRepayment(response.data.card_repayment),
40
+ addRepaymentToChargeCardPaymentHistory({
41
+ cardRepaymentId: response.data.card_repayment.card_repayment_id,
42
+ zeniCreditAccountId: response.data.card_repayment.zeni_credit_account_id,
43
+ }),
39
44
  updateCreditAccountRepaymentAmountAndDate({
40
45
  amount: toAmount(response.data.card_repayment.amount, response.data.card_repayment.currency_code, response.data.card_repayment.currency_symbol),
41
46
  date: date(response.data.card_repayment.repayment_date),
@@ -48,7 +48,7 @@ export const fetchChargeCardSetupViewEpic = (actions$, state$, zeniAPI) => actio
48
48
  updateDepositAccounts(response.data.deposit_accounts ?? []),
49
49
  updateDepositAccountList({
50
50
  data: response.data?.deposit_accounts ?? [],
51
- updateType: 'replace',
51
+ updateType: 'merge',
52
52
  }),
53
53
  updateSetupViewLocalStoreData({
54
54
  companyId: response.data.companies[0].company_id,
@@ -219,6 +219,21 @@ export const mapDepositAccToFundingAccount = (depositAcc) => ({
219
219
  createTime: depositAcc.createTime,
220
220
  accountLast4: depositAcc.accountLast4,
221
221
  });
222
+ export const mapDepositAccountWithLimitToFundingAccount = (depositAcc) => ({
223
+ id: depositAcc.id,
224
+ accType: 'depositAccount',
225
+ label: depositAcc.accountName,
226
+ maskedAccountNumber: depositAcc.maskedAccountNumber,
227
+ accountBalance: depositAcc.accountBalance,
228
+ logo: undefined,
229
+ zeniAccountId: depositAcc.zeniAccountId,
230
+ balanceFetchStatus: {
231
+ fetchState: 'Completed',
232
+ error: undefined,
233
+ },
234
+ createTime: depositAcc.createTime,
235
+ accountLast4: depositAcc.maskedAccountNumber.match(/(\d{4})$/)?.[1] ?? undefined,
236
+ });
222
237
  export const mapPaymentAccToFundingAccount = (paymentAcc, paymentAccountBalanceFetchStatus) => ({
223
238
  id: paymentAcc.paymentAccountId,
224
239
  accType: 'paymentAccount',
@@ -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;
31
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
+ }
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
@@ -66,10 +119,17 @@ const filterItemOnCategoriesWithMultipleValues = (categoryValue, currentFilter)
66
119
  return true;
67
120
  }
68
121
  };
122
+ const getItemId = (item) => {
123
+ if ('cardRepaymentId' in item) {
124
+ return item.cardRepaymentId;
125
+ }
126
+ return item
127
+ .id;
128
+ };
69
129
  const removeDuplicatesFromList = (list) => {
70
130
  const foundIds = [];
71
131
  return list.filter((item) => {
72
- const itemId = item.id;
132
+ const itemId = getItemId(item);
73
133
  if (foundIds.indexOf(itemId) === -1) {
74
134
  foundIds.push(itemId);
75
135
  return true;
@@ -104,7 +164,13 @@ export const applyAdvancedFiltersOnList = (entity, allItems, filters, filteredIt
104
164
  ? getCategoryValueForBill(key, item)
105
165
  : entity === 'reimbursement'
106
166
  ? getCategoryValueForReimbursement(key, item)
107
- : getCategoryValueForTaskList(key, item);
167
+ : entity === 'task_management'
168
+ ? getCategoryValueForTaskList(key, item)
169
+ : entity === 'transaction_categorization'
170
+ ? getCategoryValueForTransaction(key, item)
171
+ : entity === 'charge_card_payment_history'
172
+ ? getCategoryValueForPaymentHistory(key, item)
173
+ : undefined;
108
174
  if (categoryValue == null) {
109
175
  if (currentFilter.matchingOperator === 'not-equal') {
110
176
  return true;
@@ -124,6 +190,7 @@ export const applyAdvancedFiltersOnList = (entity, allItems, filters, filteredIt
124
190
  'dueDate',
125
191
  'creationDate',
126
192
  'receiptDate',
193
+ 'repaymentDate',
127
194
  ]);
128
195
  }
129
196
  });
@@ -224,6 +291,149 @@ const getCategoryValueForReimbursement = (key, reimbursement) => {
224
291
  return undefined;
225
292
  }
226
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
+ // Get from transaction.account or from lines
336
+ if (transaction.transaction.account?.accountName) {
337
+ return transaction.transaction.account.accountName;
338
+ }
339
+ // Get from first line's account
340
+ const firstLine = transaction.transaction.lines?.[0];
341
+ if (firstLine?.type === 'transaction_with_account_and_class_line' ||
342
+ firstLine?.type === 'transaction_with_product_or_service_line' ||
343
+ firstLine?.type === 'journal_entry_transaction_line') {
344
+ const accountLine = firstLine;
345
+ return accountLine.account?.accountName;
346
+ }
347
+ return undefined;
348
+ }
349
+ case 'payment_account_type': {
350
+ // Similar to payment_account_name but for type
351
+ if (transaction.transaction.account?.accountType) {
352
+ return transaction.transaction.account.accountType;
353
+ }
354
+ const firstLineForType = transaction.transaction.lines?.[0];
355
+ if (firstLineForType?.type === 'transaction_with_account_and_class_line' ||
356
+ firstLineForType?.type === 'transaction_with_product_or_service_line' ||
357
+ firstLineForType?.type === 'journal_entry_transaction_line') {
358
+ const accountLineForType = firstLineForType;
359
+ return accountLineForType.account?.accountType;
360
+ }
361
+ return undefined;
362
+ }
363
+ case 'category': {
364
+ // Get from transactionLocalData (preferred) or from transaction lines
365
+ const localDataForCategory = transaction.transactionLocalData?.transactionReviewLocalData;
366
+ if (localDataForCategory) {
367
+ // Get category from first line item or most common
368
+ const firstLineItem = Object.values(localDataForCategory.lineItemById)[0];
369
+ if (firstLineItem?.account?.accountId) {
370
+ return firstLineItem.account.accountId;
371
+ }
372
+ if (firstLineItem?.account?.qboId) {
373
+ return firstLineItem.account.qboId;
374
+ }
375
+ if (firstLineItem?.account?.accountName) {
376
+ return firstLineItem.account.accountName;
377
+ }
378
+ }
379
+ // Fallback to transaction lines
380
+ const firstLineForCategory = transaction.transaction.lines?.[0];
381
+ if (firstLineForCategory?.type ===
382
+ 'transaction_with_account_and_class_line' ||
383
+ firstLineForCategory?.type ===
384
+ 'transaction_with_product_or_service_line' ||
385
+ firstLineForCategory?.type === 'journal_entry_transaction_line') {
386
+ const account = firstLineForCategory.account;
387
+ return account?.accountId ?? account?.qboId ?? account?.accountName;
388
+ }
389
+ return undefined;
390
+ }
391
+ case 'class': {
392
+ // Similar to category but for class
393
+ const localDataForClass = transaction.transactionLocalData?.transactionReviewLocalData;
394
+ if (localDataForClass) {
395
+ const firstLineItemForClass = Object.values(localDataForClass.lineItemById)[0];
396
+ if (firstLineItemForClass?.class?.classId) {
397
+ return firstLineItemForClass.class.classId;
398
+ }
399
+ if (firstLineItemForClass?.class?.qboId) {
400
+ return firstLineItemForClass.class.qboId;
401
+ }
402
+ if (firstLineItemForClass?.class?.className) {
403
+ return firstLineItemForClass.class.className;
404
+ }
405
+ }
406
+ const firstLineForClass = transaction.transaction.lines?.[0];
407
+ if (firstLineForClass?.type === 'transaction_with_account_and_class_line' ||
408
+ firstLineForClass?.type ===
409
+ 'transaction_with_product_or_service_line' ||
410
+ firstLineForClass?.type === 'journal_entry_transaction_line') {
411
+ const classData = firstLineForClass.class;
412
+ return classData?.classId ?? classData?.qboId ?? classData?.className;
413
+ }
414
+ return undefined;
415
+ }
416
+ default:
417
+ return undefined;
418
+ }
419
+ };
420
+ const getCategoryValueForPaymentHistory = (key, repayment) => {
421
+ switch (key) {
422
+ case 'repaymentDate':
423
+ return repayment.repaymentDate;
424
+ case 'sourceAccount': {
425
+ return repayment.paymentAccountId;
426
+ }
427
+ case 'status':
428
+ return repayment.status;
429
+ case 'initiatedBy':
430
+ return repayment.repaymentType === 'scheduled'
431
+ ? 'autopay'
432
+ : repayment.paidById;
433
+ default:
434
+ return undefined;
435
+ }
436
+ };
227
437
  export const isBillListDownloadable = (state, currentTimePeriod) => {
228
438
  const { billListState, billTransactionState, entityApprovalStatusState, userState, } = state;
229
439
  const keys = ALL_BILL_TABS.filter((tab) => tab !== 'draft').map((tab) => getBillListKey(currentTimePeriod, tab));
@@ -8,11 +8,13 @@ export const fetchDepositAccountListEpic = (actions$, _state$, zeniAPI) => actio
8
8
  return zeniAPI
9
9
  .getJSON(`${zeniAPI.apiEndPoints.bankingMicroServiceBaseUrl}/1.0/deposit-accounts?query=${encodeURIComponent(`{"is_include_deposit_accounts_summary":true}`)}`)
10
10
  .pipe(mergeMap((response) => {
11
- if (isSuccessResponse(response)) {
12
- return of(updateDepositAccounts(response.data?.deposit_accounts ?? []), updateZeniAccountsSummary({
11
+ const isSuccess = isSuccessResponse(response);
12
+ if (isSuccess) {
13
+ const depositAccounts = response.data?.deposit_accounts ?? [];
14
+ return of(updateDepositAccounts(depositAccounts), updateZeniAccountsSummary({
13
15
  summaryPayload: response.data?.all_deposit_accounts_summary,
14
16
  }), updateDepositAccountList({
15
- data: response.data?.deposit_accounts ?? [],
17
+ data: depositAccounts,
16
18
  updateType: 'replace',
17
19
  }));
18
20
  }