@zeniai/client-epic-state 5.0.75 → 5.0.76

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 (174) hide show
  1. package/lib/coreEpics.js +1 -1
  2. package/lib/entity/account/accountSelector.d.ts +1 -27
  3. package/lib/entity/account/accountSelector.js +1 -47
  4. package/lib/entity/account/accountState.d.ts +1 -6
  5. package/lib/entity/account/accountState.js +4 -11
  6. package/lib/entity/account/subAccountSelector.d.ts +1 -1
  7. package/lib/entity/aiAccountantCustomer/aiAccountantCustomerPayload.js +3 -1
  8. package/lib/entity/aiAccountantCustomer/aiAccountantCustomerReducer.js +2 -1
  9. package/lib/entity/aiAccountantCustomer/aiAccountantCustomerSelector.d.ts +1 -1
  10. package/lib/entity/chargeCard/chargeCardReducer.d.ts +2 -19
  11. package/lib/entity/chargeCard/chargeCardReducer.js +2 -35
  12. package/lib/entity/chargeCardTransaction/chargeCardTransactionReducer.d.ts +1 -14
  13. package/lib/entity/chargeCardTransaction/chargeCardTransactionReducer.js +2 -36
  14. package/lib/entity/class/classSelector.d.ts +0 -35
  15. package/lib/entity/class/classSelector.js +0 -50
  16. package/lib/entity/sectionProjectView/sectionProjectViewReducer.d.ts +1 -1
  17. package/lib/entity/sectionProjectView/sectionProjectViewSelector.js +1 -1
  18. package/lib/entity/tenant/SessionManager.js +3 -6
  19. package/lib/entity/tenant/clearAllEpic.d.ts +2 -2
  20. package/lib/entity/tenant/clearAllEpic.js +2 -2
  21. package/lib/entity/tenant/epic/saveAPIKeyConnectionEpic.js +2 -6
  22. package/lib/entity/tenant/epic/saveOAuthConnectionEpic.js +2 -6
  23. package/lib/entity/tenant/tenantReducer.js +1 -6
  24. package/lib/entity/transaction/payloadTypes/transactionLinePayload.js +1 -3
  25. package/lib/epic.d.ts +10 -11
  26. package/lib/epic.js +11 -12
  27. package/lib/esm/coreEpics.js +1 -1
  28. package/lib/esm/entity/account/accountSelector.js +1 -46
  29. package/lib/esm/entity/account/accountState.js +1 -7
  30. package/lib/esm/entity/aiAccountantCustomer/aiAccountantCustomerPayload.js +3 -1
  31. package/lib/esm/entity/aiAccountantCustomer/aiAccountantCustomerReducer.js +2 -1
  32. package/lib/esm/entity/chargeCard/chargeCardReducer.js +1 -34
  33. package/lib/esm/entity/chargeCardTransaction/chargeCardTransactionReducer.js +1 -35
  34. package/lib/esm/entity/class/classSelector.js +0 -48
  35. package/lib/esm/entity/sectionProjectView/sectionProjectViewSelector.js +1 -1
  36. package/lib/esm/entity/tenant/SessionManager.js +1 -4
  37. package/lib/esm/entity/tenant/clearAllEpic.js +2 -2
  38. package/lib/esm/entity/tenant/epic/saveAPIKeyConnectionEpic.js +2 -6
  39. package/lib/esm/entity/tenant/epic/saveOAuthConnectionEpic.js +2 -6
  40. package/lib/esm/entity/tenant/tenantReducer.js +1 -6
  41. package/lib/esm/entity/transaction/payloadTypes/transactionLinePayload.js +1 -3
  42. package/lib/esm/epic.js +11 -12
  43. package/lib/esm/index.js +20 -30
  44. package/lib/esm/reducer.js +2 -2
  45. package/lib/esm/view/aiAccountantView/epics/fetchAiAccountantCustomersEpic.js +5 -2
  46. package/lib/esm/view/aiAccountantView/epics/fetchAiAccountantJobsEpic.js +5 -2
  47. package/lib/esm/view/aiAccountantView/epics/triggerAiAccountantJobEpic.js +1 -4
  48. package/lib/esm/view/companyHealthMetricView/companyHealthMetricViewSelector.js +1 -1
  49. package/lib/esm/view/companyTaskManagerView/companyTaskManagerViewSelector.js +1 -1
  50. package/lib/esm/view/companyTaskManagerView/epics/fetchTaskManagerMetricsEpic.js +1 -1
  51. package/lib/esm/view/companyView/helpers/cockpitHelpers.js +1 -1
  52. package/lib/esm/view/companyView/selector/companyManagementViewSelector.js +1 -1
  53. package/lib/esm/view/companyView/selector/companyPortfolioViewSelector.js +1 -1
  54. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic.js +2 -2
  55. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic.js +1 -2
  56. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/restoreBulkUploadAutomatchingOnMountEpic.js +6 -8
  57. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +1 -7
  58. package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +1 -11
  59. package/lib/esm/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.js +1 -21
  60. package/lib/esm/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +1 -2
  61. package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +14 -253
  62. package/lib/esm/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +13 -147
  63. package/lib/esm/view/expenseAutomationView/types/missingReceiptsViewState.js +2 -0
  64. package/lib/esm/view/financeStatement/financeStatementSelector.js +1 -1
  65. package/lib/esm/view/recommendation/recommendationHelper.js +0 -29
  66. package/lib/esm/view/recommendation/recommendationReducer.js +1 -26
  67. package/lib/esm/view/spendManagement/chargeCards/cashbackDetail/cashbackDetailReducer.js +2 -3
  68. package/lib/esm/view/spendManagement/chargeCards/chargeCardConfig/chargeCardConfigReducer.js +2 -3
  69. package/lib/esm/view/spendManagement/chargeCards/chargeCardDetail/chargeCardDetailReducer.js +18 -55
  70. package/lib/esm/view/spendManagement/chargeCards/chargeCardDetail/fetchChargeCardDetailEpic.js +2 -16
  71. package/lib/esm/view/spendManagement/chargeCards/chargeCardDetail/fetchChargeCardDetailPageEpic.js +4 -5
  72. package/lib/esm/view/spendManagement/chargeCards/chargeCardList/chargeCardListReducer.js +9 -26
  73. package/lib/esm/view/spendManagement/chargeCards/chargeCardList/fetchChargeCardListPageEpic.js +6 -9
  74. package/lib/esm/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryReducer.js +2 -3
  75. package/lib/esm/view/spendManagement/chargeCards/chargeCardPaymentHistory/fetchChargeCardPaymentPageEpic.js +1 -1
  76. package/lib/esm/view/spendManagement/chargeCards/chargeCardRepaymentDetail/chargeCardRepaymentDetailReducer.js +3 -7
  77. package/lib/esm/view/spendManagement/spendManagementFilterHelpers.js +2 -4
  78. package/lib/esm/view/subscriptionView/subscriptionViewSelector.js +1 -1
  79. package/lib/esm/view/taskManager/cannedResponsesView/epics/fetchCannedResponsesEpic.js +1 -1
  80. package/lib/esm/view/taskManager/cannedResponsesView/epics/saveCannedResponseEpic.js +1 -1
  81. package/lib/esm/view/taskManager/taskDetailView/epics/unsnoozeTaskEpic.js +1 -1
  82. package/lib/esm/view/taskManager/taskListView/taskListReducer.js +1 -1
  83. package/lib/esm/view/transactionDetail/transactionDetailReducer.js +6 -121
  84. package/lib/esm/view/transactionDetail/transactionDetailSelector.js +1 -29
  85. package/lib/index.d.ts +23 -30
  86. package/lib/index.js +58 -84
  87. package/lib/reducer.d.ts +2 -2
  88. package/lib/reducer.js +2 -2
  89. package/lib/view/aiAccountantView/aiAccountantViewPayload.d.ts +2 -2
  90. package/lib/view/aiAccountantView/aiAccountantViewReducer.d.ts +1 -1
  91. package/lib/view/aiAccountantView/epics/fetchAiAccountantCustomersEpic.js +5 -2
  92. package/lib/view/aiAccountantView/epics/fetchAiAccountantJobsEpic.js +5 -2
  93. package/lib/view/aiAccountantView/epics/triggerAiAccountantJobEpic.js +1 -4
  94. package/lib/view/companyHealthMetricView/companyHealthMetricViewSelector.js +1 -1
  95. package/lib/view/companyTaskManagerView/companyTaskManagerViewSelector.d.ts +1 -1
  96. package/lib/view/companyTaskManagerView/companyTaskManagerViewSelector.js +1 -1
  97. package/lib/view/companyView/helpers/cockpitHelpers.js +1 -1
  98. package/lib/view/companyView/selector/companyManagementViewSelector.js +1 -1
  99. package/lib/view/companyView/selector/companyPortfolioViewSelector.js +1 -1
  100. package/lib/view/companyView/types/cockpitTypes.d.ts +1 -1
  101. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic.js +2 -2
  102. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic.js +1 -2
  103. package/lib/view/expenseAutomationView/epics/missingReceipts/restoreBulkUploadAutomatchingOnMountEpic.js +6 -8
  104. package/lib/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +1 -7
  105. package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.d.ts +2 -2
  106. package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +0 -10
  107. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -2
  108. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.js +2 -23
  109. package/lib/view/expenseAutomationView/payload/transactionCategorizationPayload.d.ts +0 -21
  110. package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.d.ts +1 -2
  111. package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +1 -2
  112. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.d.ts +5 -28
  113. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +14 -253
  114. package/lib/view/expenseAutomationView/selectorTypes/missingReceiptsSelectorTypes.d.ts +1 -2
  115. package/lib/view/expenseAutomationView/selectorTypes/transactionsViewSelectorTypes.d.ts +0 -16
  116. package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.d.ts +1 -7
  117. package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +13 -150
  118. package/lib/view/expenseAutomationView/types/missingReceiptsViewState.d.ts +2 -1
  119. package/lib/view/expenseAutomationView/types/missingReceiptsViewState.js +4 -1
  120. package/lib/view/expenseAutomationView/types/transactionsViewState.d.ts +2 -44
  121. package/lib/view/financeStatement/financeStatementSelector.js +1 -1
  122. package/lib/view/profitAndLossProjectView/profitAndLossProjectViewSelectorTypes.d.ts +1 -1
  123. package/lib/view/recommendation/recommendationHelper.d.ts +0 -9
  124. package/lib/view/recommendation/recommendationHelper.js +1 -31
  125. package/lib/view/recommendation/recommendationReducer.d.ts +1 -20
  126. package/lib/view/recommendation/recommendationReducer.js +2 -27
  127. package/lib/view/spendManagement/billPay/billList/billListState.d.ts +1 -1
  128. package/lib/view/spendManagement/chargeCards/cashbackDetail/cashbackDetailReducer.d.ts +1 -3
  129. package/lib/view/spendManagement/chargeCards/cashbackDetail/cashbackDetailReducer.js +2 -3
  130. package/lib/view/spendManagement/chargeCards/chargeCardConfig/chargeCardConfigReducer.d.ts +1 -3
  131. package/lib/view/spendManagement/chargeCards/chargeCardConfig/chargeCardConfigReducer.js +2 -3
  132. package/lib/view/spendManagement/chargeCards/chargeCardDetail/chargeCardDetailReducer.js +18 -55
  133. package/lib/view/spendManagement/chargeCards/chargeCardDetail/fetchChargeCardDetailEpic.js +2 -16
  134. package/lib/view/spendManagement/chargeCards/chargeCardDetail/fetchChargeCardDetailPageEpic.js +4 -5
  135. package/lib/view/spendManagement/chargeCards/chargeCardList/chargeCardListReducer.d.ts +3 -18
  136. package/lib/view/spendManagement/chargeCards/chargeCardList/chargeCardListReducer.js +10 -27
  137. package/lib/view/spendManagement/chargeCards/chargeCardList/fetchChargeCardListPageEpic.js +6 -9
  138. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistory.d.ts +1 -1
  139. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryReducer.d.ts +1 -3
  140. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryReducer.js +2 -3
  141. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/fetchChargeCardPaymentPageEpic.js +1 -1
  142. package/lib/view/spendManagement/chargeCards/chargeCardRepaymentDetail/chargeCardRepaymentDetailReducer.d.ts +1 -3
  143. package/lib/view/spendManagement/chargeCards/chargeCardRepaymentDetail/chargeCardRepaymentDetailReducer.js +3 -7
  144. package/lib/view/spendManagement/reimbursement/remiListView/remiListState.d.ts +1 -1
  145. package/lib/view/spendManagement/spendManagementFilterHelpers.d.ts +3 -12
  146. package/lib/view/spendManagement/spendManagementFilterHelpers.js +2 -4
  147. package/lib/view/subscriptionView/subscriptionViewSelector.js +1 -1
  148. package/lib/view/taskManager/taskListView/taskList.d.ts +1 -1
  149. package/lib/view/taskManager/taskListView/taskListReducer.js +1 -1
  150. package/lib/view/transactionDetail/transactionDetailReducer.d.ts +1 -11
  151. package/lib/view/transactionDetail/transactionDetailReducer.js +7 -122
  152. package/lib/view/transactionDetail/transactionDetailSelector.d.ts +0 -2
  153. package/lib/view/transactionDetail/transactionDetailSelector.js +2 -31
  154. package/lib/view/transactionDetail/transactionDetailTypes.d.ts +0 -1
  155. package/lib/view/zeniOAuthView/zeniOAuthSelector.d.ts +1 -1
  156. package/package.json +4 -9
  157. package/lib/commonStateTypes/cacheOverrideFetchReducer.d.ts +0 -17
  158. package/lib/commonStateTypes/cacheOverrideFetchReducer.js +0 -20
  159. package/lib/commonStateTypes/filterPrimitives.d.ts +0 -23
  160. package/lib/commonStateTypes/filterPrimitives.js +0 -21
  161. package/lib/commonStateTypes/pusherActions.d.ts +0 -11
  162. package/lib/commonStateTypes/pusherActions.js +0 -6
  163. package/lib/esm/commonStateTypes/cacheOverrideFetchReducer.js +0 -16
  164. package/lib/esm/commonStateTypes/filterPrimitives.js +0 -20
  165. package/lib/esm/commonStateTypes/pusherActions.js +0 -3
  166. package/lib/esm/view/expenseAutomationView/transactionFilterHelpers.js +0 -258
  167. package/lib/esm/view/expenseAutomationView/types/completedSubTab.js +0 -17
  168. package/lib/esm/view/recommendation/fetchEntityRecommendationsForLineUpdateEpic.js +0 -87
  169. package/lib/view/expenseAutomationView/transactionFilterHelpers.d.ts +0 -66
  170. package/lib/view/expenseAutomationView/transactionFilterHelpers.js +0 -262
  171. package/lib/view/expenseAutomationView/types/completedSubTab.d.ts +0 -10
  172. package/lib/view/expenseAutomationView/types/completedSubTab.js +0 -21
  173. package/lib/view/recommendation/fetchEntityRecommendationsForLineUpdateEpic.d.ts +0 -9
  174. package/lib/view/recommendation/fetchEntityRecommendationsForLineUpdateEpic.js +0 -91
@@ -3,8 +3,7 @@ import recordGet from 'lodash/get';
3
3
  import uniq from 'lodash/uniq';
4
4
  import { toMonthYearPeriodId, } from '../../../commonStateTypes/timePeriod';
5
5
  import { isVendorTransaction } from '../../../entity/transaction/stateTypes/vendorTransaction';
6
- import { filterAutoTabLineItems, mergeTabSpecificLineItems, removeTransactionFromTabView, setEntityRecommendationForLineIdInLocalData, shouldAutoSelectAndAdd, toSetAllLineItemsToCategoryClass, toTransactionDetailLocalData, } from '../helpers/transactionCategorizationLocalDataHelper';
7
- import { DEFAULT_COMPLETED_SUB_TAB, } from '../types/completedSubTab';
6
+ import { MAX_SELECTION_LIMIT, checkIfAllLineItemsAreCategoryClassFilled, filterAutoTabLineItems, mergeTabSpecificLineItems, removeTransactionFromTabView, setEntityRecommendationForLineIdInLocalData, toSetAllLineItemsToCategoryClass, toTransactionDetailLocalData, } from '../helpers/transactionCategorizationLocalDataHelper';
8
7
  export const initialTransactionTabViewState = {
9
8
  saveStatus: {
10
9
  fetchState: 'Not-Started',
@@ -28,10 +27,6 @@ export const initialTransactionTabViewState = {
28
27
  totalCount: {},
29
28
  limit: 10,
30
29
  },
31
- filters: {
32
- categoryCombinationOperator: 'AND',
33
- categories: [],
34
- },
35
30
  fetchState: 'Not-Started',
36
31
  error: undefined,
37
32
  hasValidState() {
@@ -46,74 +41,12 @@ export const initialTransactionTabViewState = {
46
41
  selectedTransactionLineId: undefined,
47
42
  };
48
43
  export const initialState = {
49
- autoCategorizedSubTabCache: {},
50
- parentTotalCountByTab: {
51
- review: {},
52
- autoCategorized: {},
53
- },
54
44
  selectedTransactionCategorizationTab: 'review',
55
- selectedTransactionCategorizationCompletedSubTab: DEFAULT_COMPLETED_SUB_TAB,
56
45
  transactionCategorizationView: {
57
46
  review: { ...initialTransactionTabViewState },
58
47
  autoCategorized: { ...initialTransactionTabViewState },
59
48
  },
60
49
  };
61
- /**
62
- * Capture the autoCategorized tab's current paginated dataset for a sub-tab.
63
- * The mid-flight `'In-Progress'` state is normalised to `'Completed'` because
64
- * the in-flight request is cancelled by `switchMap` in the fetch epic when the
65
- * sub-tab changes — restoring `'In-Progress'` later would leave the UI stuck
66
- * showing a phantom loading state with no fetch actually running. Length-zero
67
- * datasets degrade to `'Not-Started'` so the next visit triggers a fresh
68
- * fetch from page 1 instead of short-circuiting on stale empty caches.
69
- *
70
- * Snapshot scope (deliberately narrow):
71
- * - Captured: `pageToken`, `totalCount`, `transactionIdsBySelectedPeriod`,
72
- * `fetchState`, `error`, `scrollPosition`. The first five are the keys the
73
- * fetch epic's cache check reads to decide whether to short-circuit a
74
- * sub-tab revisit; `scrollPosition` is captured per sub-tab so revisiting
75
- * restores the user's last offset and switching to a never-visited sub-tab
76
- * starts at the top instead of inheriting the outgoing sub-tab's offset
77
- * (which would land the user at a meaningless row index against a
78
- * completely different list).
79
- * - Intentionally NOT captured (left in place on `autoCat` and inherited by
80
- * the incoming sub-tab):
81
- * - `uiState.searchString`, `uiState.sortKey`, `uiState.sortOrder`,
82
- * `uiState.nodeCollapseState`, `uiState.limit` — search and sort are
83
- * user-applied preferences that persist across sub-tabs by design (a
84
- * search query stays active when the user toggles between Manual / AI
85
- * Accountant).
86
- * - `selectedCheckBoxTransactionIds` — selection state is intentionally
87
- * cleared at the action source (the fetch epic / save flow), not
88
- * by the snapshot path. Carrying selection across sub-tabs is safe
89
- * because every sub-tab change goes through a fetch round-trip that
90
- * reconciles selection against the new row IDs.
91
- * - `transactionReviewLocalDataById`, `transactionIdsWithUnsavedData`,
92
- * `uploadReceiptStatusById`, `saveStatus`, `refreshStatus`,
93
- * `markAsNotMiscategorizedStatus` — these are per-row / per-flight
94
- * caches that are write-through global state, not sub-tab-scoped.
95
- *
96
- * If the contract changes (e.g. selection should reset on every sub-tab
97
- * switch), update both this comment and the snapshot return shape together.
98
- */
99
- function snapshotAutoCategorizedTab(autoCat) {
100
- const transactionIdsBySelectedPeriod = {
101
- ...autoCat.transactionIdsBySelectedPeriod,
102
- };
103
- const hasAnyLoadedRows = Object.values(transactionIdsBySelectedPeriod).some((ids) => ids.length > 0);
104
- let normalisedFetchState = autoCat.fetchState;
105
- if (normalisedFetchState === 'In-Progress') {
106
- normalisedFetchState = hasAnyLoadedRows ? 'Completed' : 'Not-Started';
107
- }
108
- return {
109
- error: autoCat.error,
110
- fetchState: normalisedFetchState,
111
- pageToken: autoCat.uiState.pageToken,
112
- scrollPosition: autoCat.uiState.scrollPosition,
113
- totalCount: { ...autoCat.uiState.totalCount },
114
- transactionIdsBySelectedPeriod,
115
- };
116
- }
117
50
  const expenseAutomationTransactionsView = createSlice({
118
51
  name: 'expenseAutomationTransactionsView',
119
52
  initialState,
@@ -303,12 +236,6 @@ const expenseAutomationTransactionsView = createSlice({
303
236
  uiState.totalCount;
304
237
  }
305
238
  },
306
- updateTransactionFilters(draft, action) {
307
- const { selectedTab, filters } = action.payload;
308
- // `filters` is non-nullable per the payload type, so no runtime
309
- // null-check is needed — dropping it removes a dead defensive branch.
310
- draft.transactionCategorizationView[selectedTab].filters = filters;
311
- },
312
239
  saveTransactionCategorization: {
313
240
  prepare(selectedTab, transactionIds, cotTrackingByTransactionId, isUncategorizedExpenseCategoryEnabled) {
314
241
  return {
@@ -333,55 +260,6 @@ const expenseAutomationTransactionsView = createSlice({
333
260
  return { payload: { newSelectedTab } };
334
261
  },
335
262
  },
336
- updateTransactionCategorizationCompletedSubTab: {
337
- reducer(draft, action) {
338
- const { newSubTab } = action.payload;
339
- const previousSubTab = draft.selectedTransactionCategorizationCompletedSubTab;
340
- if (previousSubTab === newSubTab) {
341
- return;
342
- }
343
- const autoCat = draft.transactionCategorizationView.autoCategorized;
344
- // Snapshot the outgoing sub-tab so a future revisit can short-circuit
345
- // the fetch epic (mirrors the parent-tab "don't refetch what's
346
- // already cached" behaviour). Period-scoped data is preserved as-is
347
- // because the snapshot itself is keyed only by sub-tab; the active
348
- // tab's per-period maps remain the source of truth while live.
349
- draft.autoCategorizedSubTabCache[previousSubTab] =
350
- snapshotAutoCategorizedTab(autoCat);
351
- const cached = draft.autoCategorizedSubTabCache[newSubTab];
352
- if (cached != null) {
353
- autoCat.uiState.pageToken = cached.pageToken;
354
- autoCat.uiState.totalCount = { ...cached.totalCount };
355
- autoCat.uiState.scrollPosition = cached.scrollPosition;
356
- autoCat.transactionIdsBySelectedPeriod = {
357
- ...cached.transactionIdsBySelectedPeriod,
358
- };
359
- autoCat.fetchState = cached.fetchState;
360
- autoCat.error = cached.error;
361
- }
362
- else {
363
- // Sub-tab has never been visited — clear the active slot so the
364
- // view-epic's `fetchState === 'Not-Started' || transactionIds.length
365
- // === 0` cache check falls through and triggers a fresh page-1
366
- // fetch for the new dataset. `scrollPosition` is reset to the top
367
- // so the incoming list isn't anchored to the outgoing sub-tab's
368
- // offset (different rows → previous offset is meaningless).
369
- autoCat.uiState.pageToken = null;
370
- autoCat.uiState.totalCount = {};
371
- autoCat.uiState.scrollPosition = {
372
- scrollTop: 0,
373
- scrollLeft: undefined,
374
- };
375
- autoCat.transactionIdsBySelectedPeriod = {};
376
- autoCat.fetchState = 'Not-Started';
377
- autoCat.error = undefined;
378
- }
379
- draft.selectedTransactionCategorizationCompletedSubTab = newSubTab;
380
- },
381
- prepare(newSubTab) {
382
- return { payload: { newSubTab } };
383
- },
384
- },
385
263
  updateTransactionCategorization: {
386
264
  reducer(draft, action) {
387
265
  const selectedTab = action.payload.selectedTab;
@@ -430,9 +308,12 @@ const expenseAutomationTransactionsView = createSlice({
430
308
  const selectedCheckBoxTransactionIds = draft.transactionCategorizationView[selectedTab]
431
309
  .selectedCheckBoxTransactionIds;
432
310
  if (selectedTransaction != null &&
433
- shouldAutoSelectAndAdd(selectedTransaction, newTransactionLocalData, selectedCheckBoxTransactionIds, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled)) {
311
+ checkIfAllLineItemsAreCategoryClassFilled(selectedTransaction, newTransactionLocalData, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled) === true && // check if all line items are filled
312
+ selectedCheckBoxTransactionIds.length < MAX_SELECTION_LIMIT &&
313
+ selectedCheckBoxTransactionIds.includes(transactionId) === false) {
434
314
  draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds = [
435
- ...selectedCheckBoxTransactionIds,
315
+ ...draft.transactionCategorizationView[selectedTab]
316
+ .selectedCheckBoxTransactionIds,
436
317
  transactionId,
437
318
  ];
438
319
  }
@@ -515,26 +396,9 @@ const expenseAutomationTransactionsView = createSlice({
515
396
  const transactionLocalData = recordGet(draft.transactionCategorizationView[selectedTab]
516
397
  .transactionReviewLocalDataById, transactionId.id, undefined);
517
398
  if (transactionLocalData != null) {
518
- const newLocalData = setEntityRecommendationForLineIdInLocalData(draft.transactionCategorizationView[selectedTab], transaction, transactionLocalData.transactionReviewLocalData, entity, lineIds, uncategorizedAccounts, action.payload.recommendationWithCOTByLineId, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled);
519
- // Release the per-line fetch flag for every line this fetch
520
- // covered. Done in-place on the freshly returned local data
521
- // (the helper produces a new mutable object), before we write
522
- // it back to the draft.
523
- lineIds.forEach((lineId) => {
524
- const lineItem = newLocalData.lineItemById[lineId];
525
- if (lineItem != null) {
526
- newLocalData.lineItemById[lineId] = {
527
- ...lineItem,
528
- categoryClassRecommendationsFetchState: {
529
- fetchState: 'Completed',
530
- error: undefined,
531
- },
532
- };
533
- }
534
- });
535
399
  draft.transactionCategorizationView[selectedTab].transactionReviewLocalDataById[transaction.id] = {
536
400
  transactionId: transaction.id,
537
- transactionReviewLocalData: newLocalData,
401
+ transactionReviewLocalData: setEntityRecommendationForLineIdInLocalData(draft.transactionCategorizationView[selectedTab], transaction, transactionLocalData.transactionReviewLocalData, entity, lineIds, uncategorizedAccounts, action.payload.recommendationWithCOTByLineId, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled),
538
402
  };
539
403
  // Mark transaction as dirty (autofill happened)
540
404
  if (!draft.transactionCategorizationView[selectedTab].transactionIdsWithUnsavedData.includes(transaction.id)) {
@@ -547,99 +411,6 @@ const expenseAutomationTransactionsView = createSlice({
547
411
  }
548
412
  },
549
413
  },
550
- // Start arm of the line-update recommendations fetch. Flips covered
551
- // lines to In-Progress AND removes the transaction from
552
- // selectedCheckBoxTransactionIds — the row is unchecked + disabled
553
- // until either the success arm (recheck if limit allows) or the
554
- // failure arm releases the per-line flag.
555
- markCategoryClassRecommendationsInProgressForCategorization: {
556
- reducer(draft, action) {
557
- const { selectedTab, transactionId, lineIds } = action.payload;
558
- const tab = draft.transactionCategorizationView[selectedTab];
559
- const transactionLocalData = recordGet(tab.transactionReviewLocalDataById, transactionId.id, undefined);
560
- if (transactionLocalData != null) {
561
- lineIds.forEach((lineId) => {
562
- const lineItem = transactionLocalData.transactionReviewLocalData.lineItemById[lineId];
563
- if (lineItem != null) {
564
- transactionLocalData.transactionReviewLocalData.lineItemById[lineId] = {
565
- ...lineItem,
566
- categoryClassRecommendationsFetchState: {
567
- fetchState: 'In-Progress',
568
- error: undefined,
569
- },
570
- };
571
- }
572
- });
573
- }
574
- // Uncheck on fetch start: the row's existing category/class are
575
- // stale after a payee change, so we drop the auto-selection until
576
- // fresh recommendations land.
577
- if (tab.selectedCheckBoxTransactionIds.includes(transactionId.id)) {
578
- tab.selectedCheckBoxTransactionIds =
579
- tab.selectedCheckBoxTransactionIds.filter((id) => id !== transactionId.id);
580
- }
581
- },
582
- prepare(selectedTab, transactionId, lineIds) {
583
- return { payload: { selectedTab, transactionId, lineIds } };
584
- },
585
- },
586
- // Completed arm. Used by the epic when the HTTP call succeeds but the
587
- // server returned no per-line recommendations — we still need to
588
- // release the In-Progress flag so the skeleton resolves. Successful
589
- // responses with recommendations go through
590
- // setEntityRecommendationForLineIdsForCategorization instead (which
591
- // also flips the lines to Completed).
592
- markCategoryClassRecommendationsCompletedForCategorization: {
593
- reducer(draft, action) {
594
- const { selectedTab, transactionId, lineIds } = action.payload;
595
- const transactionLocalData = recordGet(draft.transactionCategorizationView[selectedTab]
596
- .transactionReviewLocalDataById, transactionId.id, undefined);
597
- if (transactionLocalData != null) {
598
- lineIds.forEach((lineId) => {
599
- const lineItem = transactionLocalData.transactionReviewLocalData.lineItemById[lineId];
600
- if (lineItem != null) {
601
- transactionLocalData.transactionReviewLocalData.lineItemById[lineId] = {
602
- ...lineItem,
603
- categoryClassRecommendationsFetchState: {
604
- fetchState: 'Completed',
605
- error: undefined,
606
- },
607
- };
608
- }
609
- });
610
- }
611
- },
612
- prepare(selectedTab, transactionId, lineIds) {
613
- return { payload: { selectedTab, transactionId, lineIds } };
614
- },
615
- },
616
- // Failure arm. Flips covered lines to Error so the skeleton resolves
617
- // and downstream UI can react to the failure. The transaction stays
618
- // unchecked from the start-arm action.
619
- markCategoryClassRecommendationsFailureForCategorization: {
620
- reducer(draft, action) {
621
- const { selectedTab, transactionId, lineIds, error } = action.payload;
622
- const transactionLocalData = recordGet(draft.transactionCategorizationView[selectedTab]
623
- .transactionReviewLocalDataById, transactionId.id, undefined);
624
- if (transactionLocalData != null) {
625
- lineIds.forEach((lineId) => {
626
- const lineItem = transactionLocalData.transactionReviewLocalData.lineItemById[lineId];
627
- if (lineItem != null) {
628
- transactionLocalData.transactionReviewLocalData.lineItemById[lineId] = {
629
- ...lineItem,
630
- categoryClassRecommendationsFetchState: {
631
- fetchState: 'Error',
632
- error,
633
- },
634
- };
635
- }
636
- });
637
- }
638
- },
639
- prepare(selectedTab, transactionId, lineIds, error) {
640
- return { payload: { selectedTab, transactionId, lineIds, error } };
641
- },
642
- },
643
414
  fetchTransactionCategorizationSuccess(draft, action) {
644
415
  const { selectedTab, refreshViewInBackground } = action.payload;
645
416
  if (draft.transactionCategorizationView[selectedTab].fetchState ===
@@ -735,7 +506,9 @@ const expenseAutomationTransactionsView = createSlice({
735
506
  .selectedCheckBoxTransactionIds;
736
507
  if (newTransactionLocalData.customer != null &&
737
508
  newTransactionLocalData.customer.id != null &&
738
- shouldAutoSelectAndAdd(selectedTransaction, newTransactionLocalData, selectedCheckBoxTransactionIds, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled)) {
509
+ checkIfAllLineItemsAreCategoryClassFilled(selectedTransaction, newTransactionLocalData, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled) === true && // check if all line items are filled
510
+ selectedCheckBoxTransactionIds.length < MAX_SELECTION_LIMIT &&
511
+ selectedCheckBoxTransactionIds.includes(transactionId) === false) {
739
512
  draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds = [
740
513
  ...selectedCheckBoxTransactionIds,
741
514
  transactionId,
@@ -809,7 +582,9 @@ const expenseAutomationTransactionsView = createSlice({
809
582
  .selectedCheckBoxTransactionIds;
810
583
  if (newTransactionLocalData.vendor != null &&
811
584
  newTransactionLocalData.vendor.id != null &&
812
- shouldAutoSelectAndAdd(selectedTransaction, newTransactionLocalData, selectedCheckBoxTransactionIds, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled)) {
585
+ checkIfAllLineItemsAreCategoryClassFilled(selectedTransaction, newTransactionLocalData, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled) === true && // check if all line items are filled
586
+ selectedCheckBoxTransactionIds.length < MAX_SELECTION_LIMIT &&
587
+ selectedCheckBoxTransactionIds.includes(transactionId) === false) {
813
588
  draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds = [
814
589
  ...selectedCheckBoxTransactionIds,
815
590
  transactionId,
@@ -922,10 +697,6 @@ const expenseAutomationTransactionsView = createSlice({
922
697
  const { selectedTab, selectedPeriod, totalCount } = action.payload;
923
698
  draft.transactionCategorizationView[selectedTab].uiState.totalCount[toMonthYearPeriodId(selectedPeriod)] = totalCount;
924
699
  },
925
- updateParentTotalCountForTab(draft, action) {
926
- const { selectedTab, selectedPeriod, parentTotalCount } = action.payload;
927
- draft.parentTotalCountByTab[selectedTab][toMonthYearPeriodId(selectedPeriod)] = parentTotalCount;
928
- },
929
700
  updateSelectedCheckboxTransactionIds(draft, action) {
930
701
  const selectedTab = action.payload.selectedTab;
931
702
  const transactionIds = action.payload.transactionIds;
@@ -936,16 +707,6 @@ const expenseAutomationTransactionsView = createSlice({
936
707
  draft.transactionCategorizationView[selectedTab] = {
937
708
  ...initialTransactionTabViewState,
938
709
  };
939
- draft.parentTotalCountByTab[selectedTab] = {};
940
- // The per-sub-tab snapshot cache lives at the slice root and is
941
- // exclusively populated by the autoCategorized tab's sub-tab switching
942
- // (see updateTransactionCategorizationCompletedSubTab). When that tab
943
- // is reset, leaving stale snapshots behind would short-circuit the
944
- // next sub-tab visit with phantom row IDs / page tokens that no longer
945
- // correspond to anything in state — drop them in lockstep.
946
- if (selectedTab === 'autoCategorized') {
947
- draft.autoCategorizedSubTabCache = {};
948
- }
949
710
  },
950
711
  clearExpenseAutomationTransactionsView(draft) {
951
712
  Object.assign(draft, initialState);
@@ -1042,5 +803,5 @@ const expenseAutomationTransactionsView = createSlice({
1042
803
  },
1043
804
  },
1044
805
  });
1045
- export const { fetchTransactionCategorization, updateTransactionCategorizationUIState, updateTransactionFilters, initializeTransactionCategorizationViewLocalData, saveTransactionCategorizationLocalData, fetchTransactionCategorizationFailure, saveTransactionCategorization, updateTransactionCategorization, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationCompletedSubTab, updateTransactionCategorizationSaveStatus, markTransactionAsNotMiscategorized, updateStatusForTransactionNotMiscategorizedUpdateForCategorization, updateSelectedVendorForTransaction, updateSelectedCustomerForTransaction, setAllItemsToCategoryClassInLocalDataForCategorization, updateTotalCountForTransactionCategorization, updateParentTotalCountForTab, fetchTransactionCategorizationSuccess, resetOtherTabsFetchState, fetchTransactionCategorizationView, updateSelectedCheckboxTransactionIds, clearExpenseAutomationTransactionsViewPerTabView, clearExpenseAutomationTransactionsView, setEntityRecommendationForLineIdsForCategorization, markCategoryClassRecommendationsInProgressForCategorization, markCategoryClassRecommendationsCompletedForCategorization, markCategoryClassRecommendationsFailureForCategorization, updateSelectedTransactionId, syncTransactionCategorizationFromDetailSave, backgroundRefetchReviewTab, updateTransactionCategorizationUploadReceiptState, uploadTransactionCategorizationReceiptSuccess, } = expenseAutomationTransactionsView.actions;
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;
1046
807
  export default expenseAutomationTransactionsView.reducer;
@@ -1,90 +1,17 @@
1
- import { createSelector } from '@reduxjs/toolkit';
2
1
  import omit from 'lodash/omit';
3
2
  import { reduceAllFetchState, reduceAnyFetchState, } from '../../../commonStateTypes/reduceFetchState';
4
3
  import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
5
4
  import { getCurrentTenant, getIsAccountingClassesEnabled, getIsAccountingProjectsEnabled, } from '../../../entity/tenant/tenantSelector';
6
5
  import { getTransactionWithCOT } from '../../../entity/transaction/transactionHelper';
7
6
  import { getSupportedTransactionsByIds } from '../../../entity/transaction/transactionSelector';
8
- import { applyTransactionFilters, } from '../transactionFilterHelpers';
9
7
  import { getAccountList, getNestedAccountListHierarchy, getUncategorizedAccounts, } from '../../accountList/accountListSelector';
10
8
  import { getClassList, getNestedClassListHierarchy, } from '../../classList/classListSelector';
11
9
  import { getProjectList } from '../../projectList/projectListSelector';
12
10
  import { getAllSteps } from '../selectorTypes/expenseAutomationViewSelectorTypes';
13
- // Walks the tab's transactionReviewLocalDataById map and returns
14
- // `{ [transactionId]: true }` for every transaction that has at least
15
- // one line whose targeted-fetch is In-Progress. Pulled out as a plain
16
- // function so each tab's createSelector can share it.
17
- const computeCategorizationInFlightRecommendations = (transactionReviewLocalDataById) => {
18
- const result = {};
19
- if (transactionReviewLocalDataById == null) {
20
- return result;
21
- }
22
- for (const [transactionId, data] of Object.entries(transactionReviewLocalDataById)) {
23
- const lineItems = Object.values(data.transactionReviewLocalData.lineItemById);
24
- if (lineItems.some((lineItem) => lineItem.categoryClassRecommendationsFetchState?.fetchState ===
25
- 'In-Progress')) {
26
- result[transactionId] = true;
27
- }
28
- }
29
- return result;
30
- };
31
- // Per-tab createSelector instances. Each one is memoized on its OWN tab's
32
- // transactionReviewLocalDataById reference. Switching tabs A->B->A no
33
- // longer busts the memo for tab A — the dispatcher below just returns
34
- // whichever per-tab selector matches the active tab, and reselect keeps
35
- // the prior result alive as long as the underlying slice ref is stable.
36
- const getReviewInFlightRecommendationsByTransactionId = createSelector((state) => state.expenseAutomationTransactionsViewState.transactionCategorizationView
37
- .review.transactionReviewLocalDataById, computeCategorizationInFlightRecommendations);
38
- const getAutoCategorizedInFlightRecommendationsByTransactionId = createSelector((state) => state.expenseAutomationTransactionsViewState.transactionCategorizationView
39
- .autoCategorized.transactionReviewLocalDataById, computeCategorizationInFlightRecommendations);
40
- // Per-transaction "any line is fetching category/class recommendations?"
41
- // map for the active categorization tab. Dispatches to the per-tab
42
- // memoized selector above so steady state (no fetches in flight)
43
- // recomputes only when the active tab's local data changes; tab switches
44
- // are O(1) cache lookups.
45
- //
46
- // `default` guards against the union "lying" at runtime: `TransactionsTab`
47
- // is produced via `stringToUnion`, which falls back to `s as StringUnion`
48
- // for unrecognized strings, so an unexpected value can land in state. The
49
- // downstream `getCategorizationHasInFlightRecommendations` calls
50
- // `Object.keys` on this result, so we must always return an object.
51
- export const getCategorizationInFlightRecommendationsByTransactionId = (state) => {
52
- const tab = state.expenseAutomationTransactionsViewState
53
- .selectedTransactionCategorizationTab;
54
- switch (tab) {
55
- case 'review':
56
- return getReviewInFlightRecommendationsByTransactionId(state);
57
- case 'autoCategorized':
58
- return getAutoCategorizedInFlightRecommendationsByTransactionId(state);
59
- default:
60
- return {};
61
- }
62
- };
63
- // Page-level "any line on the active tab is in flight?" flag. Drives the
64
- // list-page Save disable.
65
- export const getCategorizationHasInFlightRecommendations = createSelector(getCategorizationInFlightRecommendationsByTransactionId, (inFlightByTransactionId) => Object.keys(inFlightByTransactionId).length > 0);
66
- export const toTransactionView = (transaction, transactionLocalData) => {
67
- return {
68
- id: transaction.id,
69
- date: transaction.date,
70
- amount: transaction.amount,
71
- vendorName: transaction.vendorName,
72
- customerName: transaction.customerName,
73
- vendorId: transaction.vendorId,
74
- customerId: transaction.customerId,
75
- description: transaction.description,
76
- memo: transaction.memo,
77
- type: transaction.type,
78
- typeName: transaction.typeName,
79
- createTime: transaction.createTime,
80
- transaction: transaction,
81
- transactionLocalData,
82
- };
83
- };
84
11
  export function getExpenseAutomationTransactionView(state) {
85
12
  const { accountState, classState, classListState, accountListState, expenseAutomationTransactionsViewState, expenseAutomationViewState, projectState, projectListState, transactionState, monthEndCloseChecksState, monthEndCloseChecksViewState, } = state;
86
- const { selectedTransactionCategorizationCompletedSubTab, selectedTransactionCategorizationTab, } = expenseAutomationTransactionsViewState;
87
- const { uiState, refreshStatus, saveStatus, markAsNotMiscategorizedStatus, transactionIdsBySelectedPeriod, transactionReviewLocalDataById, selectedCheckBoxTransactionIds, transactionIdsWithUnsavedData, uploadReceiptStatusById, selectedTransactionId, selectedTransactionLineId, filters, } = expenseAutomationTransactionsViewState.transactionCategorizationView[selectedTransactionCategorizationTab];
13
+ const { selectedTransactionCategorizationTab } = expenseAutomationTransactionsViewState;
14
+ const { uiState, refreshStatus, saveStatus, markAsNotMiscategorizedStatus, transactionIdsBySelectedPeriod, transactionReviewLocalDataById, selectedCheckBoxTransactionIds, transactionIdsWithUnsavedData, uploadReceiptStatusById, selectedTransactionId, selectedTransactionLineId, } = expenseAutomationTransactionsViewState.transactionCategorizationView[selectedTransactionCategorizationTab];
88
15
  const uncategorizedIncomeExpense = getUncategorizedAccounts(accountState, accountListState, 'accountList');
89
16
  const accountList = getAccountList(accountState, accountListState, 'accountList');
90
17
  const classList = getClassList(classState, classListState);
@@ -103,58 +30,20 @@ export function getExpenseAutomationTransactionView(state) {
103
30
  ? transactionIdsBySelectedPeriod[monthYearPeriodId]
104
31
  : null;
105
32
  const transactionIds = transactionIdsForSelectedPeriod ?? [];
106
- const transactions = getSupportedTransactionsByIds(transactionState, transactionIds);
107
- const transactionsWithCOT = transactions.map((transaction) => getTransactionWithCOT(transaction));
108
- const transactionLocalDataMap = new Map();
109
- transactionIds.forEach((transactionId) => {
33
+ const transactionLocalData = transactionIds
34
+ .map((transactionId) => {
110
35
  const transactionData = transactionReviewLocalDataById[transactionId];
111
36
  if (transactionData != null) {
112
- transactionLocalDataMap.set(transactionId, {
37
+ return {
113
38
  transactionId,
114
39
  transactionReviewLocalData: transactionData.transactionReviewLocalData,
115
- });
40
+ };
116
41
  }
117
- });
118
- // Step 2: Convert to TransactionView and apply advanced filters
119
- const transactionViews = transactionsWithCOT.map((transaction) => {
120
- const localData = transactionLocalDataMap.get(transaction.id);
121
- return toTransactionView(transaction, localData);
122
- });
123
- // Apply TC-specific filters (lives under expenseAutomationView, independent
124
- // of the Spend Management filter pipeline).
125
- const filteredTransactionViews = applyTransactionFilters(transactionViews, filters);
126
- // Convert back to TransactionWithCOT for return
127
- const filteredTransactionsWithCOT = filteredTransactionViews.map((view) => view.transaction);
128
- // Get transactionLocalData for filtered transactions
129
- // Also include transactionLocalData for transactions that don't exist in transactionState
130
- // but have local data (to match original behavior)
131
- const filteredTransactionIds = new Set(filteredTransactionViews.map((view) => view.id));
132
- // Pre-compute the membership set once. The previous implementation called
133
- // `transactions.some(t => t.id === transactionId)` inside the per-id loop,
134
- // turning the pipeline into O(n × m) — measurable on tenants with large
135
- // transaction lists since this selector runs on every Redux dispatch.
136
- const transactionExistsById = new Set(transactions.map((t) => t.id));
137
- // `.flatMap` returns `X[]` directly when the callback returns `[]` for the
138
- // skip case, so we don't need the trailing `.filter((d): d is X => …)` type
139
- // predicate that the previous `map → filter` pipeline required.
140
- const filteredTransactionLocalData = transactionIds.flatMap((transactionId) => {
141
- const transactionData = transactionReviewLocalDataById[transactionId];
142
- if (transactionData == null) {
143
- return [];
144
- }
145
- // Include if transaction is in filtered results, or if transaction doesn't exist in state
146
- // (to maintain backward compatibility with tests)
147
- const transactionExists = transactionExistsById.has(transactionId);
148
- if (filteredTransactionIds.has(transactionId) || !transactionExists) {
149
- return [
150
- {
151
- transactionId,
152
- transactionReviewLocalData: transactionData.transactionReviewLocalData,
153
- },
154
- ];
155
- }
156
- return [];
157
- });
42
+ return null;
43
+ })
44
+ .filter((transaction) => transaction != null);
45
+ const transactions = getSupportedTransactionsByIds(transactionState, transactionIds);
46
+ const transactionsWithCOT = transactions.map((transaction) => getTransactionWithCOT(transaction));
158
47
  const monthEndFetchState = monthYearPeriodId != null
159
48
  ? (monthEndCloseChecksViewState.monthEndCloseChecksViewByTenantId[currentTenant.tenantId]?.[monthYearPeriodId] ?? { fetchState: 'Not-Started', error: undefined })
160
49
  : { fetchState: 'Not-Started', error: undefined };
@@ -174,27 +63,10 @@ export function getExpenseAutomationTransactionView(state) {
174
63
  .uiState.totalCount;
175
64
  const totalCountByAutoCat = expenseAutomationTransactionsViewState.transactionCategorizationView
176
65
  .autoCategorized.uiState.totalCount;
177
- // Per-(sub-)tab counts powering pagination, in-list "showing X of Y", and
178
- // any save/refresh round-trips. Sourced from `uiState.totalCount` which
179
- // mirrors the response's `total_count` for whatever tab/sub-tab the user is
180
- // viewing (e.g. only the Manual sub-tab rows for Completed → Manual).
181
66
  const totalCountByTab = {
182
67
  review: monthYearPeriodId != null ? totalCountByReview[monthYearPeriodId] : 0,
183
68
  autoCategorized: monthYearPeriodId != null ? totalCountByAutoCat[monthYearPeriodId] : 0,
184
69
  };
185
- // Parent-tab badge counts. Sourced from `parent_tab_total_count` in the
186
- // listing response and used exclusively by the navbar / tab strip badges.
187
- // Independent of which Completed sub-tab is active so the badge stays
188
- // stable across sub-tab switches.
189
- const parentTotalCountByTab = expenseAutomationTransactionsViewState.parentTotalCountByTab;
190
- const parentTabTotalCountByTab = {
191
- review: monthYearPeriodId != null
192
- ? (parentTotalCountByTab.review[monthYearPeriodId] ?? 0)
193
- : 0,
194
- autoCategorized: monthYearPeriodId != null
195
- ? (parentTotalCountByTab.autoCategorized[monthYearPeriodId] ?? 0)
196
- : 0,
197
- };
198
70
  const fetchStateByTab = {
199
71
  review: {
200
72
  fetchState: expenseAutomationTransactionsViewState.transactionCategorizationView
@@ -214,13 +86,11 @@ export function getExpenseAutomationTransactionView(state) {
214
86
  : [];
215
87
  const transactionCompletionStatus = allSteps.find((step) => step.step === 'transaction_categorization')?.completionStatus;
216
88
  const completionStatus = transactionCompletionStatus ?? 'incomplete';
217
- const inFlightCategoryClassRecommendationsByTransactionId = getCategorizationInFlightRecommendationsByTransactionId(state);
218
- const hasInFlightCategoryClassRecommendations = getCategorizationHasInFlightRecommendations(state);
219
89
  return {
220
90
  version: 0,
221
91
  fetchState: fetchStatus.fetchState,
222
92
  error: fetchStatus.error,
223
- transactions: filteredTransactionsWithCOT,
93
+ transactions: transactionsWithCOT,
224
94
  selectedCheckBoxTransactionIds,
225
95
  transactionIdsWithUnsavedData,
226
96
  uncategorizedAccounts: uncategorizedIncomeExpense,
@@ -229,22 +99,18 @@ export function getExpenseAutomationTransactionView(state) {
229
99
  classList: allClasses,
230
100
  accountsHierarchyList,
231
101
  classHierarchyList,
232
- hasInFlightCategoryClassRecommendations,
233
- inFlightCategoryClassRecommendationsByTransactionId,
234
102
  isAccountingProjectsEnabled,
235
103
  projectList,
236
- transactionLocalData: filteredTransactionLocalData,
104
+ transactionLocalData,
237
105
  uiState,
238
106
  refreshStatus,
239
107
  saveStatus,
240
108
  markAsNotMiscategorizedStatus,
241
109
  completionStatus,
242
110
  totalCountByTab,
243
- parentTabTotalCountByTab,
244
111
  fetchStateByTransactionTabs: fetchStateByTab,
245
112
  uploadReceiptStatusById,
246
113
  selectedTransactionId,
247
114
  selectedTransactionLineId,
248
- selectedTransactionCategorizationCompletedSubTab,
249
115
  };
250
116
  }
@@ -39,6 +39,8 @@ const BULK_UPLOAD_RESULTS_TABS = ['completed', 'unmatched'];
39
39
  export const toBulkUploadResultsTab = (v) => stringToUnion(v.trim().toLowerCase(), BULK_UPLOAD_RESULTS_TABS);
40
40
  const MISSING_RECEIPTS_TABS = ['missing', 'completed', 'unmatched'];
41
41
  export const toMissingReceiptsTab = (v) => stringToUnion(v.trim().toLowerCase(), MISSING_RECEIPTS_TABS);
42
+ const COMPLETED_SUB_TABS = ['all', 'ai_accountant', 'manual'];
43
+ export const toCompletedSubTab = (v) => stringToUnion(v.trim().toLowerCase(), COMPLETED_SUB_TABS);
42
44
  const MATCH_SOURCES = ['ai', 'manual'];
43
45
  export const toMatchSource = (v) => stringToUnion(v.trim().toLowerCase(), MATCH_SOURCES);
44
46
  const BULK_UPLOAD_SORT_KEYS = [
@@ -2,7 +2,6 @@ import { createSelector } from '@reduxjs/toolkit';
2
2
  import { stripCompareAdditionalBalanceTypes } from '../../commonStateTypes/coaBalance/coaBalanceType';
3
3
  import { newBalancesFilter, newBalancesFilterForDateRange, } from '../../commonStateTypes/coaBalance/coaBalancesFilter';
4
4
  import { newBalancesFilterClassesView } from '../../commonStateTypes/coaBalance/coaBalancesFilterClassesView';
5
- import { newBalancesFilterProjectView } from '../../commonStateTypes/coaBalance/coaBalancesFilterProjectView';
6
5
  import { getNumberOfPeriods, } from '../../commonStateTypes/dataAvailable';
7
6
  import { reduceAnyCompletedFetchState } from '../../commonStateTypes/reduceFetchState';
8
7
  import { toTimeframeTick, } from '../../commonStateTypes/timeframeTick';
@@ -10,6 +9,7 @@ import { extractThisPeriod } from '../../commonStateTypes/viewAndReport/thisPeri
10
9
  import { getBalanceSheet } from '../balanceSheet/balanceSheetSelector';
11
10
  import { getCashFlow } from '../cashFlow/cashFlowSelector';
12
11
  import { getProfitAndLossReport, } from '../profitAndLoss/profitAndLossSelector';
12
+ import { newBalancesFilterProjectView } from '../../commonStateTypes/coaBalance/coaBalancesFilterProjectView';
13
13
  import { getProfitAndLossClassesHorizontalView } from '../profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelector';
14
14
  import { getProfitAndLossClassesView } from '../profitAndLossClassesView/profitAndLossClassesViewSelector';
15
15
  import { getProfitAndLossProjectView } from '../profitAndLossProjectView/profitAndLossProjectViewSelector';