@zeniai/client-epic-state 5.0.71-betaVR3 → 5.0.72

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 (138) hide show
  1. package/lib/coreEpics.js +1 -1
  2. package/lib/entity/account/accountSelector.d.ts +1 -1
  3. package/lib/entity/account/accountState.d.ts +1 -1
  4. package/lib/entity/account/subAccountSelector.d.ts +1 -1
  5. package/lib/entity/aiAccountantCustomer/aiAccountantCustomerPayload.js +3 -1
  6. package/lib/entity/aiAccountantCustomer/aiAccountantCustomerReducer.js +2 -1
  7. package/lib/entity/aiAccountantCustomer/aiAccountantCustomerSelector.d.ts +1 -1
  8. package/lib/entity/chargeCard/chargeCardReducer.d.ts +2 -19
  9. package/lib/entity/chargeCard/chargeCardReducer.js +2 -35
  10. package/lib/entity/chargeCardTransaction/chargeCardTransactionReducer.d.ts +1 -14
  11. package/lib/entity/chargeCardTransaction/chargeCardTransactionReducer.js +2 -36
  12. package/lib/entity/sectionProjectView/sectionProjectViewReducer.d.ts +1 -1
  13. package/lib/entity/sectionProjectView/sectionProjectViewSelector.js +1 -1
  14. package/lib/entity/tenant/SessionManager.js +3 -6
  15. package/lib/entity/tenant/clearAllEpic.d.ts +2 -2
  16. package/lib/entity/tenant/clearAllEpic.js +2 -2
  17. package/lib/entity/tenant/epic/saveAPIKeyConnectionEpic.js +2 -6
  18. package/lib/entity/tenant/epic/saveOAuthConnectionEpic.js +2 -6
  19. package/lib/entity/tenant/tenantReducer.js +1 -6
  20. package/lib/entity/transaction/payloadTypes/transactionLinePayload.js +1 -3
  21. package/lib/epic.d.ts +10 -12
  22. package/lib/epic.js +11 -13
  23. package/lib/esm/coreEpics.js +1 -1
  24. package/lib/esm/entity/aiAccountantCustomer/aiAccountantCustomerPayload.js +3 -1
  25. package/lib/esm/entity/aiAccountantCustomer/aiAccountantCustomerReducer.js +2 -1
  26. package/lib/esm/entity/chargeCard/chargeCardReducer.js +1 -34
  27. package/lib/esm/entity/chargeCardTransaction/chargeCardTransactionReducer.js +1 -35
  28. package/lib/esm/entity/sectionProjectView/sectionProjectViewSelector.js +1 -1
  29. package/lib/esm/entity/tenant/SessionManager.js +1 -4
  30. package/lib/esm/entity/tenant/clearAllEpic.js +2 -2
  31. package/lib/esm/entity/tenant/epic/saveAPIKeyConnectionEpic.js +2 -6
  32. package/lib/esm/entity/tenant/epic/saveOAuthConnectionEpic.js +2 -6
  33. package/lib/esm/entity/tenant/tenantReducer.js +1 -6
  34. package/lib/esm/entity/transaction/payloadTypes/transactionLinePayload.js +1 -3
  35. package/lib/esm/epic.js +11 -13
  36. package/lib/esm/index.js +15 -19
  37. package/lib/esm/reducer.js +2 -2
  38. package/lib/esm/view/aiAccountantView/epics/fetchAiAccountantCustomersEpic.js +5 -2
  39. package/lib/esm/view/aiAccountantView/epics/fetchAiAccountantJobsEpic.js +5 -2
  40. package/lib/esm/view/aiAccountantView/epics/triggerAiAccountantJobEpic.js +1 -4
  41. package/lib/esm/view/companyTaskManagerView/companyTaskManagerViewSelector.js +1 -1
  42. package/lib/esm/view/companyTaskManagerView/epics/fetchTaskManagerMetricsEpic.js +1 -1
  43. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic.js +2 -2
  44. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic.js +1 -2
  45. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js +5 -21
  46. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/restoreBulkUploadAutomatchingOnMountEpic.js +6 -8
  47. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +1 -7
  48. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js +9 -13
  49. package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +1 -11
  50. package/lib/esm/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +2 -81
  51. package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +1 -127
  52. package/lib/esm/view/expenseAutomationView/selectors/missingReceiptsSelector.js +0 -2
  53. package/lib/esm/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +1 -20
  54. package/lib/esm/view/expenseAutomationView/types/missingReceiptsViewState.js +2 -0
  55. package/lib/esm/view/financeStatement/financeStatementSelector.js +1 -1
  56. package/lib/esm/view/spendManagement/chargeCards/cashbackDetail/cashbackDetailReducer.js +2 -3
  57. package/lib/esm/view/spendManagement/chargeCards/chargeCardConfig/chargeCardConfigReducer.js +2 -3
  58. package/lib/esm/view/spendManagement/chargeCards/chargeCardDetail/chargeCardDetailReducer.js +18 -55
  59. package/lib/esm/view/spendManagement/chargeCards/chargeCardDetail/fetchChargeCardDetailEpic.js +2 -16
  60. package/lib/esm/view/spendManagement/chargeCards/chargeCardDetail/fetchChargeCardDetailPageEpic.js +4 -5
  61. package/lib/esm/view/spendManagement/chargeCards/chargeCardList/chargeCardListReducer.js +9 -26
  62. package/lib/esm/view/spendManagement/chargeCards/chargeCardList/fetchChargeCardListPageEpic.js +6 -9
  63. package/lib/esm/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryReducer.js +2 -3
  64. package/lib/esm/view/spendManagement/chargeCards/chargeCardPaymentHistory/fetchChargeCardPaymentPageEpic.js +1 -1
  65. package/lib/esm/view/spendManagement/chargeCards/chargeCardRepaymentDetail/chargeCardRepaymentDetailReducer.js +3 -7
  66. package/lib/esm/view/taskManager/cannedResponsesView/epics/fetchCannedResponsesEpic.js +1 -1
  67. package/lib/esm/view/taskManager/cannedResponsesView/epics/saveCannedResponseEpic.js +1 -1
  68. package/lib/esm/view/taskManager/taskDetailView/epics/unsnoozeTaskEpic.js +1 -1
  69. package/lib/esm/view/transactionDetail/transactionDetailSelector.js +1 -7
  70. package/lib/index.d.ts +16 -20
  71. package/lib/index.js +58 -75
  72. package/lib/reducer.d.ts +2 -2
  73. package/lib/reducer.js +2 -2
  74. package/lib/tsconfig.typecheck.tsbuildinfo +1 -0
  75. package/lib/view/aiAccountantView/aiAccountantViewPayload.d.ts +2 -2
  76. package/lib/view/aiAccountantView/aiAccountantViewReducer.d.ts +1 -1
  77. package/lib/view/aiAccountantView/epics/fetchAiAccountantCustomersEpic.js +5 -2
  78. package/lib/view/aiAccountantView/epics/fetchAiAccountantJobsEpic.js +5 -2
  79. package/lib/view/aiAccountantView/epics/triggerAiAccountantJobEpic.js +1 -4
  80. package/lib/view/companyTaskManagerView/companyTaskManagerViewSelector.d.ts +1 -1
  81. package/lib/view/companyTaskManagerView/companyTaskManagerViewSelector.js +1 -1
  82. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic.js +2 -2
  83. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic.js +1 -2
  84. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js +5 -21
  85. package/lib/view/expenseAutomationView/epics/missingReceipts/restoreBulkUploadAutomatchingOnMountEpic.js +6 -8
  86. package/lib/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +1 -7
  87. package/lib/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.d.ts +5 -11
  88. package/lib/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js +8 -12
  89. package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.d.ts +2 -2
  90. package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +0 -10
  91. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
  92. package/lib/view/expenseAutomationView/payload/transactionCategorizationPayload.d.ts +0 -21
  93. package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.d.ts +2 -15
  94. package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +3 -83
  95. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.d.ts +3 -9
  96. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +2 -128
  97. package/lib/view/expenseAutomationView/selectorTypes/missingReceiptsSelectorTypes.d.ts +1 -14
  98. package/lib/view/expenseAutomationView/selectorTypes/transactionsViewSelectorTypes.d.ts +0 -14
  99. package/lib/view/expenseAutomationView/selectors/missingReceiptsSelector.js +0 -2
  100. package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +1 -20
  101. package/lib/view/expenseAutomationView/types/missingReceiptsViewState.d.ts +2 -20
  102. package/lib/view/expenseAutomationView/types/missingReceiptsViewState.js +4 -1
  103. package/lib/view/expenseAutomationView/types/transactionsViewState.d.ts +2 -41
  104. package/lib/view/financeStatement/financeStatementSelector.js +1 -1
  105. package/lib/view/profitAndLossProjectView/profitAndLossProjectViewSelectorTypes.d.ts +1 -1
  106. package/lib/view/spendManagement/chargeCards/cashbackDetail/cashbackDetailReducer.d.ts +1 -3
  107. package/lib/view/spendManagement/chargeCards/cashbackDetail/cashbackDetailReducer.js +2 -3
  108. package/lib/view/spendManagement/chargeCards/chargeCardConfig/chargeCardConfigReducer.d.ts +1 -3
  109. package/lib/view/spendManagement/chargeCards/chargeCardConfig/chargeCardConfigReducer.js +2 -3
  110. package/lib/view/spendManagement/chargeCards/chargeCardDetail/chargeCardDetailReducer.js +18 -55
  111. package/lib/view/spendManagement/chargeCards/chargeCardDetail/fetchChargeCardDetailEpic.js +2 -16
  112. package/lib/view/spendManagement/chargeCards/chargeCardDetail/fetchChargeCardDetailPageEpic.js +4 -5
  113. package/lib/view/spendManagement/chargeCards/chargeCardList/chargeCardListReducer.d.ts +3 -18
  114. package/lib/view/spendManagement/chargeCards/chargeCardList/chargeCardListReducer.js +10 -27
  115. package/lib/view/spendManagement/chargeCards/chargeCardList/fetchChargeCardListPageEpic.js +6 -9
  116. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryReducer.d.ts +1 -3
  117. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryReducer.js +2 -3
  118. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/fetchChargeCardPaymentPageEpic.js +1 -1
  119. package/lib/view/spendManagement/chargeCards/chargeCardRepaymentDetail/chargeCardRepaymentDetailReducer.d.ts +1 -3
  120. package/lib/view/spendManagement/chargeCards/chargeCardRepaymentDetail/chargeCardRepaymentDetailReducer.js +3 -7
  121. package/lib/view/transactionDetail/transactionDetailSelector.js +1 -7
  122. package/lib/view/zeniOAuthView/zeniOAuthSelector.d.ts +1 -1
  123. package/package.json +4 -9
  124. package/lib/commonStateTypes/cacheOverrideFetchReducer.d.ts +0 -17
  125. package/lib/commonStateTypes/cacheOverrideFetchReducer.js +0 -20
  126. package/lib/commonStateTypes/pusherActions.d.ts +0 -11
  127. package/lib/commonStateTypes/pusherActions.js +0 -6
  128. package/lib/esm/commonStateTypes/cacheOverrideFetchReducer.js +0 -16
  129. package/lib/esm/commonStateTypes/pusherActions.js +0 -3
  130. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/refreshBatchDetailsForBatchIdEpic.js +0 -52
  131. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/syncTabsAfterAutomatchEpic.js +0 -53
  132. package/lib/esm/view/expenseAutomationView/types/completedSubTab.js +0 -17
  133. package/lib/view/expenseAutomationView/epics/missingReceipts/refreshBatchDetailsForBatchIdEpic.d.ts +0 -18
  134. package/lib/view/expenseAutomationView/epics/missingReceipts/refreshBatchDetailsForBatchIdEpic.js +0 -56
  135. package/lib/view/expenseAutomationView/epics/missingReceipts/syncTabsAfterAutomatchEpic.d.ts +0 -28
  136. package/lib/view/expenseAutomationView/epics/missingReceipts/syncTabsAfterAutomatchEpic.js +0 -57
  137. package/lib/view/expenseAutomationView/types/completedSubTab.d.ts +0 -10
  138. package/lib/view/expenseAutomationView/types/completedSubTab.js +0 -21
@@ -4,7 +4,7 @@ import { getTagsByIds } from '../../entity/tag/tagSelector';
4
4
  import { getTaskById } from '../../entity/task/taskSelector';
5
5
  import { getUserByUserId, getUsersByUserIds, } from '../../entity/user/userSelector';
6
6
  export const getCompanyTaskManagerView = createSelector((state) => state.companyTaskManagerViewState, (state) => state.companyState, (state) => state.userState, (state) => state.taskState, (state) => state.tagState, (companyTaskManagerViewState, companyState, userState, taskState, tagState) => {
7
- const { taskIds, uiState, fetchState, error, totalTaskCount, metrics, metricsFetchState, } = companyTaskManagerViewState;
7
+ const { taskIds, uiState, fetchState, error, totalTaskCount, metrics, metricsFetchState } = companyTaskManagerViewState;
8
8
  const tasksList = [];
9
9
  taskIds.forEach((taskId) => {
10
10
  const task = getTaskById(taskState, taskId);
@@ -1,6 +1,6 @@
1
1
  import { of } from 'rxjs';
2
2
  import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
3
- import { isSuccessResponse } from '../../../responsePayload';
3
+ import { isSuccessResponse, } from '../../../responsePayload';
4
4
  import { fetchTaskManagerMetrics, updateTaskManagerMetrics, updateTaskManagerMetricsOnFailure, } from '../companyTaskManagerViewReducer';
5
5
  export const fetchTaskManagerMetricsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchTaskManagerMetrics.match), switchMap(() => {
6
6
  const query = JSON.stringify({ is_filter_by_logged_in_user: true });
@@ -3,10 +3,10 @@ import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
3
3
  import { toMonthYearPeriodId } from '../../../../commonStateTypes/timePeriod';
4
4
  import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
5
5
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
6
- import { BULK_UPLOAD_BATCH_LIST_DATE_RANGE_DAYS } from '../../helpers/bulkUploadTiming';
7
- import { rollingCalendarDateRangeInclusive } from '../../helpers/rollingCalendarDateRangeInclusive';
8
6
  import { toBatchListItem, } from '../../payload/missingReceiptsPayload';
9
7
  import { fetchBulkUploadBatches, fetchBulkUploadBatchesFailure, fetchBulkUploadBatchesSuccess, } from '../../reducers/missingReceiptsViewReducer';
8
+ import { BULK_UPLOAD_BATCH_LIST_DATE_RANGE_DAYS } from '../../helpers/bulkUploadTiming';
9
+ import { rollingCalendarDateRangeInclusive } from '../../helpers/rollingCalendarDateRangeInclusive';
10
10
  export const fetchBulkUploadBatchesEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchBulkUploadBatches.match), switchMap((action) => {
11
11
  const { cacheOverride } = action.payload;
12
12
  const state = state$.value;
@@ -5,7 +5,6 @@ import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
5
5
  import { updateTransactions } from '../../../../entity/transaction/transactionReducer';
6
6
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
7
7
  import { fetchCompletedTransactions, fetchCompletedTransactionsFailure, fetchCompletedTransactionsSuccess, getCompletedTransactionsCacheKey, } from '../../reducers/missingReceiptsViewReducer';
8
- import { DEFAULT_COMPLETED_SUB_TAB } from '../../types/completedSubTab';
9
8
  export const fetchCompletedTransactionsEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchCompletedTransactions.match), switchMap((action) => {
10
9
  const state = state$.value;
11
10
  const { expenseAutomationViewState: { selectedPeriodByTenantId }, expenseAutomationMissingReceiptsViewState: { bulkUpload }, } = state;
@@ -40,7 +39,7 @@ export const fetchCompletedTransactionsEpic = (actions$, state$, zeniAPI) => act
40
39
  if (sortOrder != null) {
41
40
  queryPayload.sort_order = sortOrder === 'ascending' ? 'asc' : 'desc';
42
41
  }
43
- if (completedSubTab !== DEFAULT_COMPLETED_SUB_TAB) {
42
+ if (completedSubTab !== 'all') {
44
43
  queryPayload.match_type = completedSubTab;
45
44
  }
46
45
  const encodedQuery = encodeURIComponent(JSON.stringify(queryPayload));
@@ -31,19 +31,10 @@ export function fetchBatchDetailsByIds(batchIds, zeniAPI) {
31
31
  }
32
32
  export const fetchMultipleBatchDetailsEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchBulkUploadBatchesSuccess.match), mergeMap((action) => {
33
33
  const batchList = action.payload.batchList;
34
- const bulkUpload = state$.value.expenseAutomationMissingReceiptsViewState.bulkUpload;
35
- const { batchDetailsById, batchDetailsRevalidating } = bulkUpload;
34
+ const { batchDetailsById } = state$.value.expenseAutomationMissingReceiptsViewState.bulkUpload;
36
35
  /** Only completed list rows may trigger batch-details API; preserve platform order. */
37
- const completedRows = filterBatchListItemsForBatchDetailsFetch(batchList);
38
- /**
39
- * Stale-while-revalidate: when a manual refresh has set `batchDetailsRevalidating`, refresh
40
- * every completed list row (replacing entries via `storeBatchDetails` as they arrive). On a
41
- * normal first fetch we still only request rows whose details are missing.
42
- */
43
- const candidateRows = batchDetailsRevalidating === true
44
- ? completedRows
45
- : completedRows.filter((batch) => batchDetailsById[batch.batchId] == null);
46
- const completedBatchIds = candidateRows.map((batch) => batch.batchId);
36
+ const completedBatchesMissingDetails = filterBatchListItemsForBatchDetailsFetch(batchList).filter((batch) => batchDetailsById[batch.batchId] == null);
37
+ const completedBatchIds = completedBatchesMissingDetails.map((batch) => batch.batchId);
47
38
  const batchIdsToFetch = completedBatchIds.slice(0, INITIAL_BATCH_PAGE_SIZE);
48
39
  if (batchIdsToFetch.length === 0) {
49
40
  /**
@@ -51,6 +42,7 @@ export const fetchMultipleBatchDetailsEpic = (actions$, state$, zeniAPI) => acti
51
42
  * `currentBatchId` may not appear in the filtered list but still needs `GET /batches/:id`.
52
43
  * Fall back to the single-batch-details epic so `phase` and details can resolve.
53
44
  */
45
+ const bulkUpload = state$.value.expenseAutomationMissingReceiptsViewState.bulkUpload;
54
46
  const currentId = bulkUpload.currentBatchId;
55
47
  if (currentId != null &&
56
48
  bulkUpload.phase === 'matching' &&
@@ -58,15 +50,7 @@ export const fetchMultipleBatchDetailsEpic = (actions$, state$, zeniAPI) => acti
58
50
  shouldFetchBatchDetailsForBatchId(bulkUpload.batchStatusById, bulkUpload.batchListByPeriod, currentId)) {
59
51
  return of(fetchBulkUploadBatchDetails());
60
52
  }
61
- /**
62
- * Close the pagination loop so the Unmatched-tab loading heuristic can settle.
63
- * `fetchMoreBatchDetailsComplete` also clears `batchDetailsRevalidating` and flips
64
- * `initialBatchDetailsLoaded` to true. Without this, `batchDetailsPaginationState` stays
65
- * at `Not-Started` forever after a list refresh that finds zero completed rows (or all
66
- * already cached) — the direct-open-Unmatched-without-upload path used to render an
67
- * infinite skeleton.
68
- */
69
- return of(fetchMoreBatchDetailsComplete());
53
+ return EMPTY;
70
54
  }
71
55
  return concat(of(setInitialBatchDetailsLoading()), fetchBatchDetailsByIds(batchIdsToFetch, zeniAPI), of(fetchMoreBatchDetailsComplete()));
72
56
  }));
@@ -1,12 +1,12 @@
1
1
  import { EMPTY, concat, from, of } from 'rxjs';
2
2
  import { catchError, concatMap, filter, mergeMap, take } from 'rxjs/operators';
3
- import { toMonthYearPeriodId } from '../../../../commonStateTypes/timePeriod';
4
- import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
5
3
  import { isSuccessResponse } from '../../../../responsePayload';
6
- import { BULK_UPLOAD_BATCH_LIST_DATE_RANGE_DAYS } from '../../helpers/bulkUploadTiming';
7
- import { rollingCalendarDateRangeInclusive } from '../../helpers/rollingCalendarDateRangeInclusive';
8
4
  import { isBatchDetailsStatusPendingOrProcessing, toBatchListItem, } from '../../payload/missingReceiptsPayload';
9
5
  import { fetchBulkUploadBatchesSuccess, restoreBulkUploadAutomatchingOnMount, restoreBulkUploadMatchingState, } from '../../reducers/missingReceiptsViewReducer';
6
+ import { BULK_UPLOAD_BATCH_LIST_DATE_RANGE_DAYS } from '../../helpers/bulkUploadTiming';
7
+ import { rollingCalendarDateRangeInclusive } from '../../helpers/rollingCalendarDateRangeInclusive';
8
+ import { toMonthYearPeriodId } from '../../../../commonStateTypes/timePeriod';
9
+ import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
10
10
  /**
11
11
  * On missing-receipts entry / period switch, hydrate the automatching banner for the logged-in
12
12
  * user without relying on UI-driven list fetches (the Missing tab does not fetch the batch list
@@ -49,12 +49,10 @@ export const restoreBulkUploadAutomatchingOnMountEpic = (actions$, state$, zeniA
49
49
  bulkUpload.batchListFetchState.fetchState === 'Completed';
50
50
  const listSource = alreadyCached
51
51
  ? of(null)
52
- : zeniAPI
53
- .getJSON(listUrl)
54
- .pipe(catchError(() => of(null)));
52
+ : zeniAPI.getJSON(listUrl).pipe(catchError(() => of(null)));
55
53
  return listSource.pipe(mergeMap((listResponse) => {
56
54
  const batchList = alreadyCached
57
- ? (bulkUpload.batchListByPeriod[periodId] ?? [])
55
+ ? bulkUpload.batchListByPeriod[periodId] ?? []
58
56
  : listResponse != null &&
59
57
  isSuccessResponse(listResponse) &&
60
58
  listResponse.data != null
@@ -2,11 +2,10 @@ import { EMPTY, from, merge, of } from 'rxjs';
2
2
  import { catchError, debounceTime, filter, mergeMap, switchMap, } from 'rxjs/operators';
3
3
  import { updateTransactions } from '../../../../entity/transaction/transactionReducer';
4
4
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
5
- import { rollingCalendarDateRangeInclusive } from '../../helpers/rollingCalendarDateRangeInclusive';
6
5
  import { supportedTransactionPayloadToManualSearchResult } from '../../payload/missingReceiptsPayload';
7
6
  import { searchTransactionsForManualMatch, searchTransactionsForManualMatchFailure, searchTransactionsForManualMatchSuccess, } from '../../reducers/missingReceiptsViewReducer';
8
- import { DEFAULT_COMPLETED_SUB_TAB } from '../../types/completedSubTab';
9
7
  import { toTransactionsSortKey } from '../../types/transactionsViewState';
8
+ import { rollingCalendarDateRangeInclusive } from '../../helpers/rollingCalendarDateRangeInclusive';
10
9
  /**
11
10
  * `auto_categorized` for manual transaction search (Unmatched).
12
11
  * Backend contract TBD — align with `fetchTransactionCategorizationEpic` (`selectedTab === 'autoCategorized'`)
@@ -47,11 +46,6 @@ export const searchTransactionsForManualMatchEpic = (actions$, state$, zeniAPI)
47
46
  page_token: pageToken ?? null,
48
47
  page_size: manualSearch.pageSize,
49
48
  search_text: query,
50
- // Manual search ignores the Completed sub-tab axis — always span the
51
- // default sentinel so the receipt-matching UX returns results
52
- // regardless of where the transaction would otherwise be bucketed on
53
- // the Completed tab.
54
- sub_tab: DEFAULT_COMPLETED_SUB_TAB,
55
49
  };
56
50
  return zeniAPI
57
51
  .getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/expense-automation/transactions?query=${encodeURIComponent(JSON.stringify(queryParam))}`)
@@ -5,27 +5,23 @@ import { updateTransactions } from '../../../../entity/transaction/transactionRe
5
5
  import { isSuccessResponse } from '../../../../responsePayload';
6
6
  import { BULK_UPLOAD_AUTOMATCHING_TIMEOUT_MS } from '../../helpers/bulkUploadTiming';
7
7
  import { extractTransactionPayloadsFromBatchFiles, isBatchDetailsApiStatusCompleted, toBatchDetails, } from '../../payload/missingReceiptsPayload';
8
- import { bulkUploadAutomatchingTimedOut, bulkUploadReceiptsSuccess, clearBulkUpload, fetchBulkUploadBatchDetails, fetchBulkUploadBatchDetailsSuccess, fetchBulkUploadBatches, pusherBatchStatusUpdate, refreshBatchDetailsForBatchId, restoreBulkUploadMatchingState, } from '../../reducers/missingReceiptsViewReducer';
8
+ import { bulkUploadAutomatchingTimedOut, bulkUploadReceiptsSuccess, clearBulkUpload, fetchBulkUploadBatchDetails, fetchBulkUploadBatchDetailsSuccess, fetchBulkUploadBatches, pusherBatchStatusUpdate, restoreBulkUploadMatchingState, } from '../../reducers/missingReceiptsViewReducer';
9
9
  /** First poll after upload; then interval while batch not resolved via Pusher or API. */
10
10
  const FALLBACK_FIRST_DELAY_MS = 30000;
11
11
  /** Shorter first poll for restored sessions — batch may already be near completion. */
12
12
  const RESTORE_FIRST_DELAY_MS = 5000;
13
13
  const FALLBACK_POLL_INTERVAL_MS = 10000;
14
14
  /**
15
- * On Pusher batch completion: refresh the batch list (so summary counts update) and dispatch a
16
- * targeted single-batch details refresh for the batch that just completed. Stale-while-revalidate:
17
- * we no longer pass `invalidateBatchDetailsCache: true` existing `batchDetailsById` entries stay
18
- * on screen and the just-completed row replaces in place via `storeBatchDetails`. The post-match
19
- * tab decision (Unmatched vs Completed) is made client-side in `ExpenseAutomationPage` once fresh
15
+ * On Pusher batch completion: refresh batch list (then fetchMultipleBatchDetailsEpic runs).
16
+ * Debounced to reduce duplicate refreshes. The post-match tab decision
17
+ * (Unmatched vs Completed) is made client-side in `ExpenseAutomationPage` once fresh
20
18
  * batch details land -- do not dispatch `requestMissingReceiptsTabNavigation` here.
21
- *
22
- * Pusher resend storms (same batch completing twice within the dedupe window) are throttled in
23
- * `refreshBatchDetailsForBatchIdEpic` via `lastBatchDetailRefreshAtById`, so this epic stays
24
- * stateless and just forwards the event.
25
19
  */
26
- export const pusherBatchStatusCompletionEpic = (actions$) => actions$.pipe(filter(pusherBatchStatusUpdate.match), filter((action) => action.payload.status === 'completed'), debounceTime(300), mergeMap((action) => from([
27
- fetchBulkUploadBatches({ cacheOverride: true }),
28
- refreshBatchDetailsForBatchId({ batchId: action.payload.batchId }),
20
+ export const pusherBatchStatusCompletionEpic = (actions$) => actions$.pipe(filter(pusherBatchStatusUpdate.match), filter((action) => action.payload.status === 'completed'), debounceTime(300), mergeMap(() => from([
21
+ fetchBulkUploadBatches({
22
+ cacheOverride: true,
23
+ invalidateBatchDetailsCache: true,
24
+ }),
29
25
  ])));
30
26
  /**
31
27
  * If Pusher and batch-details are delayed beyond {@link BULK_UPLOAD_AUTOMATCHING_TIMEOUT_MS},
@@ -5,8 +5,7 @@ import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
5
5
  import { updateTransactions } from '../../../../entity/transaction/transactionReducer';
6
6
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
7
7
  import { getLineItemsByTransactionsIdsFromResponse } from '../../helpers/transactionCategorizationLocalDataHelper';
8
- import { fetchTransactionCategorization, fetchTransactionCategorizationFailure, fetchTransactionCategorizationSuccess, initializeTransactionCategorizationViewLocalData, resetOtherTabsFetchState, updateParentTotalCountForTab, updateTotalCountForTransactionCategorization, updateTransactionCategorizationUIState, } from '../../reducers/transactionsViewReducer';
9
- import { DEFAULT_COMPLETED_SUB_TAB } from '../../types/completedSubTab';
8
+ import { fetchTransactionCategorization, fetchTransactionCategorizationFailure, fetchTransactionCategorizationSuccess, initializeTransactionCategorizationViewLocalData, resetOtherTabsFetchState, updateTotalCountForTransactionCategorization, updateTransactionCategorizationUIState, } from '../../reducers/transactionsViewReducer';
10
9
  import { TRANSACTIONS_TABS, toTransactionsSortKey, } from '../../types/transactionsViewState';
11
10
  export const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchTransactionCategorization.match), switchMap((action) => {
12
11
  const { expenseAutomationViewState: { selectedPeriodByTenantId }, expenseAutomationTransactionsViewState, } = state$.value;
@@ -24,9 +23,6 @@ export const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) =>
24
23
  page_token: pageToken,
25
24
  page_size: 25,
26
25
  search_text: uiState.searchString,
27
- sub_tab: selectedTab === 'autoCategorized'
28
- ? expenseAutomationTransactionsViewState.selectedTransactionCategorizationCompletedSubTab
29
- : DEFAULT_COMPLETED_SUB_TAB,
30
26
  };
31
27
  return zeniAPI
32
28
  .getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/expense-automation/transactions?query=${encodeURIComponent(JSON.stringify(queryParam))}`)
@@ -49,12 +45,6 @@ export const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) =>
49
45
  selectedTab,
50
46
  totalCount: response.data.total_count,
51
47
  }));
52
- updateActions.push(updateParentTotalCountForTab({
53
- selectedPeriod,
54
- selectedTab,
55
- parentTotalCount: response.data.parent_tab_total_count ??
56
- response.data.total_count,
57
- }));
58
48
  updateActions.push(resetOtherTabsFetchState({
59
49
  refreshViewInBackground,
60
50
  tabs: TRANSACTIONS_TABS.filter((tab) => selectedTab !== tab),
@@ -1,6 +1,5 @@
1
1
  import { createAction, createSlice } from '@reduxjs/toolkit';
2
2
  import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
3
- import { DEFAULT_COMPLETED_SUB_TAB, } from '../types/completedSubTab';
4
3
  import { MIN_MANUAL_TRANSACTION_SEARCH_LENGTH, } from '../types/missingReceiptsViewState';
5
4
  export const getCompletedTransactionsCacheKey = (periodId, sortKey, sortOrder, subTab) => {
6
5
  const sortKeyPart = sortKey ?? 'none';
@@ -10,18 +9,15 @@ export const getCompletedTransactionsCacheKey = (periodId, sortKey, sortOrder, s
10
9
  export const initialBulkUploadState = {
11
10
  batchDetailsById: {},
12
11
  batchDetailsPaginationState: { fetchState: 'Not-Started', error: undefined },
13
- batchDetailsRevalidating: false,
14
12
  batchListByPeriod: {},
15
13
  batchListFetchState: { fetchState: 'Not-Started', error: undefined },
16
14
  batchStatusById: {},
17
- completedSubTab: DEFAULT_COMPLETED_SUB_TAB,
15
+ completedSubTab: 'all',
18
16
  completedTransactionsByPeriod: {},
19
17
  confirmMatchStatus: { fetchState: 'Not-Started', error: undefined },
20
18
  currentBatchId: undefined,
21
19
  currentResultsTab: 'unmatched',
22
20
  failedBatchDetailIds: [],
23
- initialBatchDetailsLoaded: false,
24
- lastBatchDetailRefreshAtById: {},
25
21
  manualSearchUiResetKey: 0,
26
22
  manualSearch: {
27
23
  fetchState: { fetchState: 'Not-Started', error: undefined },
@@ -68,13 +64,6 @@ export const fetchBulkUploadBatchDetails = createAction('expenseAutomationMissin
68
64
  * after a hard refresh. The epic owns the list + details API calls end-to-end.
69
65
  */
70
66
  export const restoreBulkUploadAutomatchingOnMount = createAction('expenseAutomationMissingReceiptsView/restoreBulkUploadAutomatchingOnMount');
71
- /**
72
- * Epic trigger only. Targeted stale-while-revalidate refresh for a single batch's details — used
73
- * by `pusherBatchStatusCompletionEpic` so a Pusher 'completed' event re-fetches only that one
74
- * batch instead of wiping the entire `batchDetailsById` cache. The epic dedupes via
75
- * `lastBatchDetailRefreshAtById` so resend storms don't trigger waves of API calls.
76
- */
77
- export const refreshBatchDetailsForBatchId = createAction('expenseAutomationMissingReceiptsView/refreshBatchDetailsForBatchId');
78
67
  const expenseAutomationMissingReceiptsView = createSlice({
79
68
  name: 'expenseAutomationMissingReceiptsView',
80
69
  initialState,
@@ -261,16 +250,6 @@ const expenseAutomationMissingReceiptsView = createSlice({
261
250
  const details = action.payload;
262
251
  draft.bulkUpload.batchDetailsById[details.batchId] = details;
263
252
  draft.bulkUpload.phase = 'completed';
264
- draft.bulkUpload.initialBatchDetailsLoaded = true;
265
- /**
266
- * A successful detail load supersedes any previous failure for this batch — without this,
267
- * `getPrimaryBatchIdFromBatchListOrder` (and pagination) would keep skipping the batch
268
- * across the recovery, hiding now-valid Unmatched-tab work.
269
- */
270
- const failedIndex = draft.bulkUpload.failedBatchDetailIds.indexOf(details.batchId);
271
- if (failedIndex >= 0) {
272
- draft.bulkUpload.failedBatchDetailIds.splice(failedIndex, 1);
273
- }
274
253
  },
275
254
  fetchBulkUploadBatchDetailsFailure: {
276
255
  reducer(draft) {
@@ -292,19 +271,6 @@ const expenseAutomationMissingReceiptsView = createSlice({
292
271
  draft.bulkUpload.currentBatchId === details.batchId) {
293
272
  draft.bulkUpload.phase = 'completed';
294
273
  }
295
- /** Stale-while-revalidate: any successful detail load means we have rendered data. */
296
- draft.bulkUpload.initialBatchDetailsLoaded = true;
297
- /**
298
- * Stale-while-revalidate recovery: if this batch had previously failed, a successful
299
- * targeted refresh (Pusher `completed` → `refreshBatchDetailsForBatchIdEpic`) or
300
- * multi-batch revalidation (manual refresh) supersedes the failure. Selectors that gate
301
- * on `failedBatchDetailIds` (primary-batch resolution, `hasMoreBatchDetails`) must see the
302
- * batch as recovered so it can show fresh Unmatched work.
303
- */
304
- const failedIndex = draft.bulkUpload.failedBatchDetailIds.indexOf(details.batchId);
305
- if (failedIndex >= 0) {
306
- draft.bulkUpload.failedBatchDetailIds.splice(failedIndex, 1);
307
- }
308
274
  },
309
275
  batchDetailFetchFailed(draft, action) {
310
276
  const { batchId } = action.payload;
@@ -333,14 +299,6 @@ const expenseAutomationMissingReceiptsView = createSlice({
333
299
  fetchState: 'Completed',
334
300
  error: undefined,
335
301
  };
336
- /**
337
- * Stale-while-revalidate: a completed pagination round means the initial page (or a
338
- * subsequent revalidation) has fully resolved. Once `initialBatchDetailsLoaded` flips to
339
- * `true` it stays `true` for the session — only `clearBulkUpload` /
340
- * `clearBulkUploadBatchDetailsForScopeChange` can reset it.
341
- */
342
- draft.bulkUpload.initialBatchDetailsLoaded = true;
343
- draft.bulkUpload.batchDetailsRevalidating = false;
344
302
  const currentId = draft.bulkUpload.currentBatchId;
345
303
  const currentDetails = currentId != null
346
304
  ? draft.bulkUpload.batchDetailsById[currentId]
@@ -356,24 +314,12 @@ const expenseAutomationMissingReceiptsView = createSlice({
356
314
  fetchState: 'Error',
357
315
  error: action.payload.error,
358
316
  };
359
- draft.bulkUpload.batchDetailsRevalidating = false;
360
317
  },
361
318
  fetchBulkUploadBatches: {
362
319
  reducer(draft, action) {
363
- /**
364
- * Manual refresh button (and any other caller passing `invalidateBatchDetailsCache: true`):
365
- * wipe the details cache, failed-id list, and pagination state so the Unmatched tab drops
366
- * its existing rows and re-renders the initial-load skeleton while the next list +
367
- * details fetches resolve. The post-success epic (`fetchMultipleBatchDetailsEpic`) then
368
- * re-runs the details chain from a clean slate.
369
- */
370
320
  if (action.payload.invalidateBatchDetailsCache === true) {
371
321
  draft.bulkUpload.batchDetailsById = {};
372
322
  draft.bulkUpload.failedBatchDetailIds = [];
373
- draft.bulkUpload.batchDetailsPaginationState = {
374
- fetchState: 'Not-Started',
375
- error: undefined,
376
- };
377
323
  }
378
324
  draft.bulkUpload.batchListFetchState = {
379
325
  fetchState: 'In-Progress',
@@ -390,31 +336,6 @@ const expenseAutomationMissingReceiptsView = createSlice({
390
336
  };
391
337
  },
392
338
  },
393
- /**
394
- * Tenant/period scope change — the new scope's data is genuinely unknown so the existing
395
- * details cache and `initialBatchDetailsLoaded` flag must be reset (the Unmatched skeleton
396
- * is acceptable on scope change). Distinct from a stale-while-revalidate refresh.
397
- */
398
- clearBulkUploadBatchDetailsForScopeChange(draft) {
399
- draft.bulkUpload.batchDetailsById = {};
400
- draft.bulkUpload.failedBatchDetailIds = [];
401
- draft.bulkUpload.batchDetailsPaginationState = {
402
- fetchState: 'Not-Started',
403
- error: undefined,
404
- };
405
- draft.bulkUpload.batchDetailsRevalidating = false;
406
- draft.bulkUpload.initialBatchDetailsLoaded = false;
407
- draft.bulkUpload.lastBatchDetailRefreshAtById = {};
408
- },
409
- /**
410
- * Records the last time we issued a targeted refresh for `batchId`. Set by
411
- * `refreshBatchDetailsForBatchIdEpic` before each API call so subsequent Pusher resends in the
412
- * dedupe window are skipped.
413
- */
414
- markBatchDetailRefreshAttempted(draft, action) {
415
- draft.bulkUpload.lastBatchDetailRefreshAtById[action.payload.batchId] =
416
- action.payload.timestamp;
417
- },
418
339
  fetchBulkUploadBatchesSuccess(draft, action) {
419
340
  const periodId = toMonthYearPeriodId(action.payload.selectedPeriod);
420
341
  draft.bulkUpload.batchListByPeriod[periodId] = action.payload.batchList;
@@ -657,7 +578,7 @@ const expenseAutomationMissingReceiptsView = createSlice({
657
578
  });
658
579
  export const { fetchMissingReceipts, fetchMissingReceiptsSuccess, fetchMissingReceiptsFailure, uploadMissingReceiptSuccess, updateMissingReceiptUploadState, updateMissingReceiptsUIState, markMissingReceiptAsDone, clearExpenseAutomationMissingReceiptsView,
659
580
  // Bulk Upload
660
- bulkUploadReceipts, updateBulkUploadProgress, setBulkUploadUploadedFileCount, bulkUploadReceiptsSuccess, bulkUploadReceiptsFailure, restoreBulkUploadMatchingState, bulkUploadAutomatchingTimedOut, pusherBatchStatusUpdate, requestMissingReceiptsTabNavigation, clearMissingReceiptsTabNavigation, fetchBulkUploadBatchDetailsSuccess, fetchBulkUploadBatchDetailsFailure, storeBatchDetails, batchDetailFetchFailed, setInitialBatchDetailsLoading, fetchMoreBatchDetails, fetchMoreBatchDetailsComplete, fetchMoreBatchDetailsFailure, fetchBulkUploadBatches, fetchBulkUploadBatchesSuccess, fetchBulkUploadBatchesFailure, clearBulkUploadBatchDetailsForScopeChange, markBatchDetailRefreshAttempted, confirmBulkUploadMatch, confirmBulkUploadMatchSuccess, confirmBulkUploadMatchFailure, setBulkUploadResultsTab, setBulkUploadCompletedSubTab, setBulkUploadSortConfig, clearBulkUpload, searchTransactionsForManualMatch, searchTransactionsForManualMatchSuccess, searchTransactionsForManualMatchFailure, clearManualSearchResults, acknowledgeBulkUploadConfirmMatchComplete,
581
+ bulkUploadReceipts, updateBulkUploadProgress, setBulkUploadUploadedFileCount, bulkUploadReceiptsSuccess, bulkUploadReceiptsFailure, restoreBulkUploadMatchingState, bulkUploadAutomatchingTimedOut, pusherBatchStatusUpdate, requestMissingReceiptsTabNavigation, clearMissingReceiptsTabNavigation, fetchBulkUploadBatchDetailsSuccess, fetchBulkUploadBatchDetailsFailure, storeBatchDetails, batchDetailFetchFailed, setInitialBatchDetailsLoading, fetchMoreBatchDetails, fetchMoreBatchDetailsComplete, fetchMoreBatchDetailsFailure, fetchBulkUploadBatches, fetchBulkUploadBatchesSuccess, fetchBulkUploadBatchesFailure, confirmBulkUploadMatch, confirmBulkUploadMatchSuccess, confirmBulkUploadMatchFailure, setBulkUploadResultsTab, setBulkUploadCompletedSubTab, setBulkUploadSortConfig, clearBulkUpload, searchTransactionsForManualMatch, searchTransactionsForManualMatchSuccess, searchTransactionsForManualMatchFailure, clearManualSearchResults, acknowledgeBulkUploadConfirmMatchComplete,
661
582
  // Completed Transactions
662
583
  fetchCompletedTransactions, fetchCompletedTransactionsSuccess, fetchCompletedTransactionsFailure, } = expenseAutomationMissingReceiptsView.actions;
663
584
  export default expenseAutomationMissingReceiptsView.reducer;
@@ -4,7 +4,6 @@ import uniq from 'lodash/uniq';
4
4
  import { toMonthYearPeriodId, } from '../../../commonStateTypes/timePeriod';
5
5
  import { isVendorTransaction } from '../../../entity/transaction/stateTypes/vendorTransaction';
6
6
  import { MAX_SELECTION_LIMIT, checkIfAllLineItemsAreCategoryClassFilled, filterAutoTabLineItems, mergeTabSpecificLineItems, removeTransactionFromTabView, setEntityRecommendationForLineIdInLocalData, toSetAllLineItemsToCategoryClass, toTransactionDetailLocalData, } from '../helpers/transactionCategorizationLocalDataHelper';
7
- import { DEFAULT_COMPLETED_SUB_TAB, } from '../types/completedSubTab';
8
7
  export const initialTransactionTabViewState = {
9
8
  saveStatus: {
10
9
  fetchState: 'Not-Started',
@@ -42,74 +41,12 @@ export const initialTransactionTabViewState = {
42
41
  selectedTransactionLineId: undefined,
43
42
  };
44
43
  export const initialState = {
45
- autoCategorizedSubTabCache: {},
46
- parentTotalCountByTab: {
47
- review: {},
48
- autoCategorized: {},
49
- },
50
44
  selectedTransactionCategorizationTab: 'review',
51
- selectedTransactionCategorizationCompletedSubTab: DEFAULT_COMPLETED_SUB_TAB,
52
45
  transactionCategorizationView: {
53
46
  review: { ...initialTransactionTabViewState },
54
47
  autoCategorized: { ...initialTransactionTabViewState },
55
48
  },
56
49
  };
57
- /**
58
- * Capture the autoCategorized tab's current paginated dataset for a sub-tab.
59
- * The mid-flight `'In-Progress'` state is normalised to `'Completed'` because
60
- * the in-flight request is cancelled by `switchMap` in the fetch epic when the
61
- * sub-tab changes — restoring `'In-Progress'` later would leave the UI stuck
62
- * showing a phantom loading state with no fetch actually running. Length-zero
63
- * datasets degrade to `'Not-Started'` so the next visit triggers a fresh
64
- * fetch from page 1 instead of short-circuiting on stale empty caches.
65
- *
66
- * Snapshot scope (deliberately narrow):
67
- * - Captured: `pageToken`, `totalCount`, `transactionIdsBySelectedPeriod`,
68
- * `fetchState`, `error`, `scrollPosition`. The first five are the keys the
69
- * fetch epic's cache check reads to decide whether to short-circuit a
70
- * sub-tab revisit; `scrollPosition` is captured per sub-tab so revisiting
71
- * restores the user's last offset and switching to a never-visited sub-tab
72
- * starts at the top instead of inheriting the outgoing sub-tab's offset
73
- * (which would land the user at a meaningless row index against a
74
- * completely different list).
75
- * - Intentionally NOT captured (left in place on `autoCat` and inherited by
76
- * the incoming sub-tab):
77
- * - `uiState.searchString`, `uiState.sortKey`, `uiState.sortOrder`,
78
- * `uiState.nodeCollapseState`, `uiState.limit` — search and sort are
79
- * user-applied preferences that persist across sub-tabs by design (a
80
- * search query stays active when the user toggles between Manual / AI
81
- * Accountant).
82
- * - `selectedCheckBoxTransactionIds` — selection state is intentionally
83
- * cleared at the action source (the fetch epic / save flow), not
84
- * by the snapshot path. Carrying selection across sub-tabs is safe
85
- * because every sub-tab change goes through a fetch round-trip that
86
- * reconciles selection against the new row IDs.
87
- * - `transactionReviewLocalDataById`, `transactionIdsWithUnsavedData`,
88
- * `uploadReceiptStatusById`, `saveStatus`, `refreshStatus`,
89
- * `markAsNotMiscategorizedStatus` — these are per-row / per-flight
90
- * caches that are write-through global state, not sub-tab-scoped.
91
- *
92
- * If the contract changes (e.g. selection should reset on every sub-tab
93
- * switch), update both this comment and the snapshot return shape together.
94
- */
95
- function snapshotAutoCategorizedTab(autoCat) {
96
- const transactionIdsBySelectedPeriod = {
97
- ...autoCat.transactionIdsBySelectedPeriod,
98
- };
99
- const hasAnyLoadedRows = Object.values(transactionIdsBySelectedPeriod).some((ids) => ids.length > 0);
100
- let normalisedFetchState = autoCat.fetchState;
101
- if (normalisedFetchState === 'In-Progress') {
102
- normalisedFetchState = hasAnyLoadedRows ? 'Completed' : 'Not-Started';
103
- }
104
- return {
105
- error: autoCat.error,
106
- fetchState: normalisedFetchState,
107
- pageToken: autoCat.uiState.pageToken,
108
- scrollPosition: autoCat.uiState.scrollPosition,
109
- totalCount: { ...autoCat.uiState.totalCount },
110
- transactionIdsBySelectedPeriod,
111
- };
112
- }
113
50
  const expenseAutomationTransactionsView = createSlice({
114
51
  name: 'expenseAutomationTransactionsView',
115
52
  initialState,
@@ -323,55 +260,6 @@ const expenseAutomationTransactionsView = createSlice({
323
260
  return { payload: { newSelectedTab } };
324
261
  },
325
262
  },
326
- updateTransactionCategorizationCompletedSubTab: {
327
- reducer(draft, action) {
328
- const { newSubTab } = action.payload;
329
- const previousSubTab = draft.selectedTransactionCategorizationCompletedSubTab;
330
- if (previousSubTab === newSubTab) {
331
- return;
332
- }
333
- const autoCat = draft.transactionCategorizationView.autoCategorized;
334
- // Snapshot the outgoing sub-tab so a future revisit can short-circuit
335
- // the fetch epic (mirrors the parent-tab "don't refetch what's
336
- // already cached" behaviour). Period-scoped data is preserved as-is
337
- // because the snapshot itself is keyed only by sub-tab; the active
338
- // tab's per-period maps remain the source of truth while live.
339
- draft.autoCategorizedSubTabCache[previousSubTab] =
340
- snapshotAutoCategorizedTab(autoCat);
341
- const cached = draft.autoCategorizedSubTabCache[newSubTab];
342
- if (cached != null) {
343
- autoCat.uiState.pageToken = cached.pageToken;
344
- autoCat.uiState.totalCount = { ...cached.totalCount };
345
- autoCat.uiState.scrollPosition = cached.scrollPosition;
346
- autoCat.transactionIdsBySelectedPeriod = {
347
- ...cached.transactionIdsBySelectedPeriod,
348
- };
349
- autoCat.fetchState = cached.fetchState;
350
- autoCat.error = cached.error;
351
- }
352
- else {
353
- // Sub-tab has never been visited — clear the active slot so the
354
- // view-epic's `fetchState === 'Not-Started' || transactionIds.length
355
- // === 0` cache check falls through and triggers a fresh page-1
356
- // fetch for the new dataset. `scrollPosition` is reset to the top
357
- // so the incoming list isn't anchored to the outgoing sub-tab's
358
- // offset (different rows → previous offset is meaningless).
359
- autoCat.uiState.pageToken = null;
360
- autoCat.uiState.totalCount = {};
361
- autoCat.uiState.scrollPosition = {
362
- scrollTop: 0,
363
- scrollLeft: undefined,
364
- };
365
- autoCat.transactionIdsBySelectedPeriod = {};
366
- autoCat.fetchState = 'Not-Started';
367
- autoCat.error = undefined;
368
- }
369
- draft.selectedTransactionCategorizationCompletedSubTab = newSubTab;
370
- },
371
- prepare(newSubTab) {
372
- return { payload: { newSubTab } };
373
- },
374
- },
375
263
  updateTransactionCategorization: {
376
264
  reducer(draft, action) {
377
265
  const selectedTab = action.payload.selectedTab;
@@ -809,10 +697,6 @@ const expenseAutomationTransactionsView = createSlice({
809
697
  const { selectedTab, selectedPeriod, totalCount } = action.payload;
810
698
  draft.transactionCategorizationView[selectedTab].uiState.totalCount[toMonthYearPeriodId(selectedPeriod)] = totalCount;
811
699
  },
812
- updateParentTotalCountForTab(draft, action) {
813
- const { selectedTab, selectedPeriod, parentTotalCount } = action.payload;
814
- draft.parentTotalCountByTab[selectedTab][toMonthYearPeriodId(selectedPeriod)] = parentTotalCount;
815
- },
816
700
  updateSelectedCheckboxTransactionIds(draft, action) {
817
701
  const selectedTab = action.payload.selectedTab;
818
702
  const transactionIds = action.payload.transactionIds;
@@ -823,16 +707,6 @@ const expenseAutomationTransactionsView = createSlice({
823
707
  draft.transactionCategorizationView[selectedTab] = {
824
708
  ...initialTransactionTabViewState,
825
709
  };
826
- draft.parentTotalCountByTab[selectedTab] = {};
827
- // The per-sub-tab snapshot cache lives at the slice root and is
828
- // exclusively populated by the autoCategorized tab's sub-tab switching
829
- // (see updateTransactionCategorizationCompletedSubTab). When that tab
830
- // is reset, leaving stale snapshots behind would short-circuit the
831
- // next sub-tab visit with phantom row IDs / page tokens that no longer
832
- // correspond to anything in state — drop them in lockstep.
833
- if (selectedTab === 'autoCategorized') {
834
- draft.autoCategorizedSubTabCache = {};
835
- }
836
710
  },
837
711
  clearExpenseAutomationTransactionsView(draft) {
838
712
  Object.assign(draft, initialState);
@@ -929,5 +803,5 @@ const expenseAutomationTransactionsView = createSlice({
929
803
  },
930
804
  },
931
805
  });
932
- export const { fetchTransactionCategorization, updateTransactionCategorizationUIState, initializeTransactionCategorizationViewLocalData, saveTransactionCategorizationLocalData, fetchTransactionCategorizationFailure, saveTransactionCategorization, updateTransactionCategorization, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationCompletedSubTab, updateTransactionCategorizationSaveStatus, markTransactionAsNotMiscategorized, updateStatusForTransactionNotMiscategorizedUpdateForCategorization, updateSelectedVendorForTransaction, updateSelectedCustomerForTransaction, setAllItemsToCategoryClassInLocalDataForCategorization, updateTotalCountForTransactionCategorization, updateParentTotalCountForTab, fetchTransactionCategorizationSuccess, resetOtherTabsFetchState, fetchTransactionCategorizationView, updateSelectedCheckboxTransactionIds, clearExpenseAutomationTransactionsViewPerTabView, clearExpenseAutomationTransactionsView, setEntityRecommendationForLineIdsForCategorization, 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;
933
807
  export default expenseAutomationTransactionsView.reducer;
@@ -224,8 +224,6 @@ export function getExpenseAutomationMissingReceiptsView(state) {
224
224
  manualSearchUiResetKey: bulkUpload.manualSearchUiResetKey,
225
225
  hasMoreBatchDetails,
226
226
  batchDetailsPaginationState: bulkUpload.batchDetailsPaginationState,
227
- batchDetailsRevalidating: bulkUpload.batchDetailsRevalidating,
228
- initialBatchDetailsLoaded: bulkUpload.initialBatchDetailsLoaded,
229
227
  manualSearch: {
230
228
  ...bulkUpload.manualSearch,
231
229
  results: manualSearchResults,
@@ -10,7 +10,7 @@ import { getProjectList } from '../../projectList/projectListSelector';
10
10
  import { getAllSteps } from '../selectorTypes/expenseAutomationViewSelectorTypes';
11
11
  export function getExpenseAutomationTransactionView(state) {
12
12
  const { accountState, classState, classListState, accountListState, expenseAutomationTransactionsViewState, expenseAutomationViewState, projectState, projectListState, transactionState, monthEndCloseChecksState, monthEndCloseChecksViewState, } = state;
13
- const { selectedTransactionCategorizationCompletedSubTab, selectedTransactionCategorizationTab, } = expenseAutomationTransactionsViewState;
13
+ const { selectedTransactionCategorizationTab } = expenseAutomationTransactionsViewState;
14
14
  const { uiState, refreshStatus, saveStatus, markAsNotMiscategorizedStatus, transactionIdsBySelectedPeriod, transactionReviewLocalDataById, selectedCheckBoxTransactionIds, transactionIdsWithUnsavedData, uploadReceiptStatusById, selectedTransactionId, selectedTransactionLineId, } = expenseAutomationTransactionsViewState.transactionCategorizationView[selectedTransactionCategorizationTab];
15
15
  const uncategorizedIncomeExpense = getUncategorizedAccounts(accountState, accountListState, 'accountList');
16
16
  const accountList = getAccountList(accountState, accountListState, 'accountList');
@@ -63,27 +63,10 @@ export function getExpenseAutomationTransactionView(state) {
63
63
  .uiState.totalCount;
64
64
  const totalCountByAutoCat = expenseAutomationTransactionsViewState.transactionCategorizationView
65
65
  .autoCategorized.uiState.totalCount;
66
- // Per-(sub-)tab counts powering pagination, in-list "showing X of Y", and
67
- // any save/refresh round-trips. Sourced from `uiState.totalCount` which
68
- // mirrors the response's `total_count` for whatever tab/sub-tab the user is
69
- // viewing (e.g. only the Manual sub-tab rows for Completed → Manual).
70
66
  const totalCountByTab = {
71
67
  review: monthYearPeriodId != null ? totalCountByReview[monthYearPeriodId] : 0,
72
68
  autoCategorized: monthYearPeriodId != null ? totalCountByAutoCat[monthYearPeriodId] : 0,
73
69
  };
74
- // Parent-tab badge counts. Sourced from `parent_tab_total_count` in the
75
- // listing response and used exclusively by the navbar / tab strip badges.
76
- // Independent of which Completed sub-tab is active so the badge stays
77
- // stable across sub-tab switches.
78
- const parentTotalCountByTab = expenseAutomationTransactionsViewState.parentTotalCountByTab;
79
- const parentTabTotalCountByTab = {
80
- review: monthYearPeriodId != null
81
- ? (parentTotalCountByTab.review[monthYearPeriodId] ?? 0)
82
- : 0,
83
- autoCategorized: monthYearPeriodId != null
84
- ? (parentTotalCountByTab.autoCategorized[monthYearPeriodId] ?? 0)
85
- : 0,
86
- };
87
70
  const fetchStateByTab = {
88
71
  review: {
89
72
  fetchState: expenseAutomationTransactionsViewState.transactionCategorizationView
@@ -125,11 +108,9 @@ export function getExpenseAutomationTransactionView(state) {
125
108
  markAsNotMiscategorizedStatus,
126
109
  completionStatus,
127
110
  totalCountByTab,
128
- parentTabTotalCountByTab,
129
111
  fetchStateByTransactionTabs: fetchStateByTab,
130
112
  uploadReceiptStatusById,
131
113
  selectedTransactionId,
132
114
  selectedTransactionLineId,
133
- selectedTransactionCategorizationCompletedSubTab,
134
115
  };
135
116
  }