@zeniai/client-epic-state 5.0.64 → 5.0.65-betaML1

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 (53) hide show
  1. package/lib/epic.d.ts +2 -1
  2. package/lib/epic.js +2 -1
  3. package/lib/esm/epic.js +2 -1
  4. package/lib/esm/index.js +9 -8
  5. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic.js +2 -1
  6. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +6 -0
  7. package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +11 -1
  8. package/lib/esm/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.js +21 -1
  9. package/lib/esm/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +2 -1
  10. package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +243 -14
  11. package/lib/esm/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +48 -1
  12. package/lib/esm/view/expenseAutomationView/types/completedSubTab.js +17 -0
  13. package/lib/esm/view/expenseAutomationView/types/missingReceiptsViewState.js +0 -2
  14. package/lib/esm/view/recommendation/fetchEntityRecommendationsForLineUpdateEpic.js +78 -0
  15. package/lib/esm/view/recommendation/recommendationHelper.js +29 -0
  16. package/lib/esm/view/recommendation/recommendationReducer.js +36 -1
  17. package/lib/esm/view/transactionDetail/transactionDetailReducer.js +94 -1
  18. package/lib/esm/view/transactionDetail/transactionDetailSelector.js +22 -0
  19. package/lib/index.d.ts +10 -8
  20. package/lib/index.js +42 -36
  21. package/lib/tsconfig.typecheck.tsbuildinfo +1 -1
  22. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic.js +2 -1
  23. package/lib/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +6 -0
  24. package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.d.ts +2 -2
  25. package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +10 -0
  26. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -0
  27. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.js +23 -2
  28. package/lib/view/expenseAutomationView/payload/transactionCategorizationPayload.d.ts +21 -0
  29. package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.d.ts +2 -1
  30. package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +2 -1
  31. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.d.ts +22 -3
  32. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +243 -14
  33. package/lib/view/expenseAutomationView/selectorTypes/missingReceiptsSelectorTypes.d.ts +2 -1
  34. package/lib/view/expenseAutomationView/selectorTypes/transactionsViewSelectorTypes.d.ts +16 -0
  35. package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.d.ts +3 -0
  36. package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +49 -1
  37. package/lib/view/expenseAutomationView/types/completedSubTab.d.ts +10 -0
  38. package/lib/view/expenseAutomationView/types/completedSubTab.js +21 -0
  39. package/lib/view/expenseAutomationView/types/missingReceiptsViewState.d.ts +1 -2
  40. package/lib/view/expenseAutomationView/types/missingReceiptsViewState.js +1 -4
  41. package/lib/view/expenseAutomationView/types/transactionsViewState.d.ts +41 -1
  42. package/lib/view/recommendation/fetchEntityRecommendationsForLineUpdateEpic.d.ts +9 -0
  43. package/lib/view/recommendation/fetchEntityRecommendationsForLineUpdateEpic.js +82 -0
  44. package/lib/view/recommendation/recommendationHelper.d.ts +9 -0
  45. package/lib/view/recommendation/recommendationHelper.js +31 -1
  46. package/lib/view/recommendation/recommendationReducer.d.ts +18 -1
  47. package/lib/view/recommendation/recommendationReducer.js +37 -2
  48. package/lib/view/transactionDetail/transactionDetailReducer.d.ts +11 -1
  49. package/lib/view/transactionDetail/transactionDetailReducer.js +95 -2
  50. package/lib/view/transactionDetail/transactionDetailSelector.d.ts +2 -0
  51. package/lib/view/transactionDetail/transactionDetailSelector.js +24 -1
  52. package/lib/view/transactionDetail/transactionDetailTypes.d.ts +1 -0
  53. package/package.json +1 -1
@@ -8,6 +8,7 @@ const tenantSelector_1 = require("../../../../entity/tenant/tenantSelector");
8
8
  const transactionReducer_1 = require("../../../../entity/transaction/transactionReducer");
9
9
  const responsePayload_1 = require("../../../../responsePayload");
10
10
  const missingReceiptsViewReducer_1 = require("../../reducers/missingReceiptsViewReducer");
11
+ const completedSubTab_1 = require("../../types/completedSubTab");
11
12
  const fetchCompletedTransactionsEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.fetchCompletedTransactions.match), (0, operators_1.switchMap)((action) => {
12
13
  const state = state$.value;
13
14
  const { expenseAutomationViewState: { selectedPeriodByTenantId }, expenseAutomationMissingReceiptsViewState: { bulkUpload }, } = state;
@@ -42,7 +43,7 @@ const fetchCompletedTransactionsEpic = (actions$, state$, zeniAPI) => actions$.p
42
43
  if (sortOrder != null) {
43
44
  queryPayload.sort_order = sortOrder === 'ascending' ? 'asc' : 'desc';
44
45
  }
45
- if (completedSubTab !== 'all') {
46
+ if (completedSubTab !== completedSubTab_1.DEFAULT_COMPLETED_SUB_TAB) {
46
47
  queryPayload.match_type = completedSubTab;
47
48
  }
48
49
  const encodedQuery = encodeURIComponent(JSON.stringify(queryPayload));
@@ -7,6 +7,7 @@ const transactionReducer_1 = require("../../../../entity/transaction/transaction
7
7
  const responsePayload_1 = require("../../../../responsePayload");
8
8
  const missingReceiptsPayload_1 = require("../../payload/missingReceiptsPayload");
9
9
  const missingReceiptsViewReducer_1 = require("../../reducers/missingReceiptsViewReducer");
10
+ const completedSubTab_1 = require("../../types/completedSubTab");
10
11
  const transactionsViewState_1 = require("../../types/transactionsViewState");
11
12
  const rollingCalendarDateRangeInclusive_1 = require("../../helpers/rollingCalendarDateRangeInclusive");
12
13
  /**
@@ -49,6 +50,11 @@ const searchTransactionsForManualMatchEpic = (actions$, state$, zeniAPI) => {
49
50
  page_token: pageToken ?? null,
50
51
  page_size: manualSearch.pageSize,
51
52
  search_text: query,
53
+ // Manual search ignores the Completed sub-tab axis — always span the
54
+ // default sentinel so the receipt-matching UX returns results
55
+ // regardless of where the transaction would otherwise be bucketed on
56
+ // the Completed tab.
57
+ sub_tab: completedSubTab_1.DEFAULT_COMPLETED_SUB_TAB,
52
58
  };
53
59
  return zeniAPI
54
60
  .getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/expense-automation/transactions?query=${encodeURIComponent(JSON.stringify(queryParam))}`)
@@ -3,6 +3,6 @@ import { Observable } from 'rxjs';
3
3
  import { updateTransactions } from '../../../../entity/transaction/transactionReducer';
4
4
  import { RootState } from '../../../../reducer';
5
5
  import { ZeniAPI } from '../../../../zeniAPI';
6
- import { fetchTransactionCategorization, fetchTransactionCategorizationFailure, fetchTransactionCategorizationSuccess, initializeTransactionCategorizationViewLocalData, resetOtherTabsFetchState, updateTotalCountForTransactionCategorization, updateTransactionCategorizationUIState } from '../../reducers/transactionsViewReducer';
7
- export type ActionType = ReturnType<typeof fetchTransactionCategorization> | ReturnType<typeof updateTransactions> | ReturnType<typeof initializeTransactionCategorizationViewLocalData> | ReturnType<typeof fetchTransactionCategorizationFailure> | ReturnType<typeof updateTransactionCategorizationUIState> | ReturnType<typeof updateTotalCountForTransactionCategorization> | ReturnType<typeof fetchTransactionCategorizationSuccess> | ReturnType<typeof resetOtherTabsFetchState>;
6
+ import { fetchTransactionCategorization, fetchTransactionCategorizationFailure, fetchTransactionCategorizationSuccess, initializeTransactionCategorizationViewLocalData, resetOtherTabsFetchState, updateParentTotalCountForTab, updateTotalCountForTransactionCategorization, updateTransactionCategorizationUIState } from '../../reducers/transactionsViewReducer';
7
+ export type ActionType = ReturnType<typeof fetchTransactionCategorization> | ReturnType<typeof updateTransactions> | ReturnType<typeof initializeTransactionCategorizationViewLocalData> | ReturnType<typeof fetchTransactionCategorizationFailure> | ReturnType<typeof updateTransactionCategorizationUIState> | ReturnType<typeof updateTotalCountForTransactionCategorization> | ReturnType<typeof updateParentTotalCountForTab> | ReturnType<typeof fetchTransactionCategorizationSuccess> | ReturnType<typeof resetOtherTabsFetchState>;
8
8
  export declare const fetchTransactionCategorizationEpic: (actions$: ActionsObservable<ActionType>, state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<ActionType>;
@@ -9,6 +9,7 @@ const transactionReducer_1 = require("../../../../entity/transaction/transaction
9
9
  const responsePayload_1 = require("../../../../responsePayload");
10
10
  const transactionCategorizationLocalDataHelper_1 = require("../../helpers/transactionCategorizationLocalDataHelper");
11
11
  const transactionsViewReducer_1 = require("../../reducers/transactionsViewReducer");
12
+ const completedSubTab_1 = require("../../types/completedSubTab");
12
13
  const transactionsViewState_1 = require("../../types/transactionsViewState");
13
14
  const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(transactionsViewReducer_1.fetchTransactionCategorization.match), (0, operators_1.switchMap)((action) => {
14
15
  const { expenseAutomationViewState: { selectedPeriodByTenantId }, expenseAutomationTransactionsViewState, } = state$.value;
@@ -26,6 +27,9 @@ const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) => action
26
27
  page_token: pageToken,
27
28
  page_size: 25,
28
29
  search_text: uiState.searchString,
30
+ sub_tab: selectedTab === 'autoCategorized'
31
+ ? expenseAutomationTransactionsViewState.selectedTransactionCategorizationCompletedSubTab
32
+ : completedSubTab_1.DEFAULT_COMPLETED_SUB_TAB,
29
33
  };
30
34
  return zeniAPI
31
35
  .getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/expense-automation/transactions?query=${encodeURIComponent(JSON.stringify(queryParam))}`)
@@ -48,6 +52,12 @@ const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) => action
48
52
  selectedTab,
49
53
  totalCount: response.data.total_count,
50
54
  }));
55
+ updateActions.push((0, transactionsViewReducer_1.updateParentTotalCountForTab)({
56
+ selectedPeriod,
57
+ selectedTab,
58
+ parentTotalCount: response.data.parent_tab_total_count ??
59
+ response.data.total_count,
60
+ }));
51
61
  updateActions.push((0, transactionsViewReducer_1.resetOtherTabsFetchState)({
52
62
  refreshViewInBackground,
53
63
  tabs: transactionsViewState_1.TRANSACTIONS_TABS.filter((tab) => selectedTab !== tab),
@@ -27,6 +27,7 @@ export declare const removeTransactionFromTabView: (tabView: TransactionsTabView
27
27
  */
28
28
  export declare const filterAutoTabLineItems: (tabView: TransactionsTabViewState, transactionId: ID, updatedTransaction: SupportedTransactionWithCOT) => void;
29
29
  export declare const MAX_SELECTION_LIMIT = 5;
30
+ export declare const shouldAutoSelectAndAdd: (transaction: SupportedTransactionWithCOT, localData: TransactionReviewLocalData, selectedCheckBoxTransactionIds: ID[], uncategorizedAccounts: UncategorizedAccounts, isUncategorizedExpenseCategoryEnabled?: boolean, isAccountingClassesEnabled?: boolean) => boolean;
30
31
  export declare const toTransactionDetailLocalData: (transaction: SupportedTransactionWithCOT, selectedTab: TransactionsTab, lineItemsByTransactionIds: string[], uncategorizedIncomeExpense: UncategorizedAccounts, isUncategorizedExpenseCategoryEnabled?: boolean) => TransactionReviewLocalData;
31
32
  export declare const toSetAllLineItemsToCategoryClass: (draft: TransactionsTabViewState, transaction: SupportedTransactionWithCOT, transactionId: ID, transactionLineId: ID, transactionDetailLocalData: TransactionReviewLocalData, updatedItem: "category" | "class" | "project", uncategorizedAccounts: UncategorizedAccounts, selectedTab: TransactionsTab, vendorId?: ID, customerId?: ID, selectedAccount?: AccountBase, selectedClassBase?: ClassBase, selectedProjectBase?: ProjectBase, isUncategorizedExpenseCategoryEnabled?: boolean, isAccountingClassesEnabled?: boolean) => {
32
33
  localData: TransactionReviewLocalData;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.hasTransactionLocalDataChanges = exports.isAnyItemWithUncategorizedExpenseAccount = exports.getSnackbarMessageForTransactionReview = exports.mergeTabSpecificLineItems = exports.getLineItemsByTransactionIdsFromLocalData = exports.getLineItemsByTransactionsIdsFromResponse = exports.checkIfAllLineItemsAreCategoryClassFilled = exports.setEntityRecommendationForLineIdInLocalData = exports.toSetAllLineItemsToCategoryClass = exports.toTransactionDetailLocalData = exports.MAX_SELECTION_LIMIT = exports.filterAutoTabLineItems = exports.removeTransactionFromTabView = exports.getPendingReviewLineIdsFromTransaction = void 0;
3
+ exports.hasTransactionLocalDataChanges = exports.isAnyItemWithUncategorizedExpenseAccount = exports.getSnackbarMessageForTransactionReview = exports.mergeTabSpecificLineItems = exports.getLineItemsByTransactionIdsFromLocalData = exports.getLineItemsByTransactionsIdsFromResponse = exports.checkIfAllLineItemsAreCategoryClassFilled = exports.setEntityRecommendationForLineIdInLocalData = exports.toSetAllLineItemsToCategoryClass = exports.toTransactionDetailLocalData = exports.shouldAutoSelectAndAdd = exports.MAX_SELECTION_LIMIT = exports.filterAutoTabLineItems = exports.removeTransactionFromTabView = exports.getPendingReviewLineIdsFromTransaction = void 0;
4
4
  const vendorTransaction_1 = require("../../../entity/transaction/stateTypes/vendorTransaction");
5
5
  const transactionDetailLocalDataHelper_1 = require("../../transactionDetail/transactionDetailLocalDataHelper");
6
6
  const transactionDetailLocalDataHelper_2 = require("../../transactionDetail/transactionDetailLocalDataHelper");
@@ -65,6 +65,23 @@ const filterAutoTabLineItems = (tabView, transactionId, updatedTransaction) => {
65
65
  };
66
66
  exports.filterAutoTabLineItems = filterAutoTabLineItems;
67
67
  exports.MAX_SELECTION_LIMIT = 5;
68
+ // Returns true when the transaction should be auto-added to the
69
+ // selectedCheckBoxTransactionIds list after a categorization-related
70
+ // state mutation: all line items are filled, the selection limit has
71
+ // not been hit, and the id isn't already in the list. Consolidates the
72
+ // triplet that was inlined at four sites (updateSelectedCustomer/Vendor,
73
+ // setAllItemsToCategoryClassInLocalDataForCategorization, and
74
+ // setEntityRecommendationForLineIdInLocalData below).
75
+ const shouldAutoSelectAndAdd = (transaction, localData, selectedCheckBoxTransactionIds, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled) => {
76
+ if (selectedCheckBoxTransactionIds.includes(transaction.id)) {
77
+ return false;
78
+ }
79
+ if (selectedCheckBoxTransactionIds.length >= exports.MAX_SELECTION_LIMIT) {
80
+ return false;
81
+ }
82
+ return (0, exports.checkIfAllLineItemsAreCategoryClassFilled)(transaction, localData, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled);
83
+ };
84
+ exports.shouldAutoSelectAndAdd = shouldAutoSelectAndAdd;
68
85
  const toTransactionDetailLocalData = (transaction, selectedTab, lineItemsByTransactionIds, uncategorizedIncomeExpense, isUncategorizedExpenseCategoryEnabled) => {
69
86
  const transactionLocal = {
70
87
  ...transactionsViewState_1.initialSupportedTransactionCategorization.transactionReviewLocalData,
@@ -552,10 +569,14 @@ const setEntityRecommendationForLineIdInLocalData = (draft, transaction, transac
552
569
  selectedEntity: transactionDetailLocalData.selectedEntity,
553
570
  tabSpecificLineItems: transactionDetailLocalData.tabSpecificLineItems,
554
571
  };
572
+ // Auto-uncheck if not all line items are filled (the row's
573
+ // category/class became stale after the entity update). Otherwise
574
+ // route through shouldAutoSelectAndAdd so the MAX_SELECTION_LIMIT
575
+ // gate is consistently applied across all auto-add sites.
555
576
  if (!(0, exports.checkIfAllLineItemsAreCategoryClassFilled)(transaction, newLocalData, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled)) {
556
577
  selectedCheckBoxTransactionIds = selectedCheckBoxTransactionIds.filter((id) => id !== transaction.id);
557
578
  }
558
- else if (selectedCheckBoxTransactionIds.includes(transaction.id) === false) {
579
+ else if ((0, exports.shouldAutoSelectAndAdd)(transaction, newLocalData, selectedCheckBoxTransactionIds, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled)) {
559
580
  selectedCheckBoxTransactionIds.push(transaction.id);
560
581
  }
561
582
  draft.selectedCheckBoxTransactionIds = selectedCheckBoxTransactionIds;
@@ -2,6 +2,7 @@ import { EntityRecommendationPayload } from '../../../commonPayloadTypes/recomme
2
2
  import { TransactionPayload } from '../../../entity/transaction/payloadTypes/transactionPayload';
3
3
  import { SupportedTransactionPayload } from '../../../entity/transaction/transactionState';
4
4
  import { ZeniAPIResponse } from '../../../responsePayload';
5
+ import { CompletedSubTab } from '../types/completedSubTab';
5
6
  export interface TransactionCategorizationQueryPayload {
6
7
  auto_categorized: boolean;
7
8
  end_date: string | null;
@@ -11,12 +12,32 @@ export interface TransactionCategorizationQueryPayload {
11
12
  sort_by: string;
12
13
  sort_order: string;
13
14
  start_date: string | null;
15
+ /**
16
+ * Required. For the Completed (`auto_categorized=true`) listing, scopes the
17
+ * returned page + `total_count` to one of the Completed sub-tabs. For the
18
+ * Review listing it is forced to `'all'` to preserve parent-tab semantics
19
+ * on the backend.
20
+ */
21
+ sub_tab: CompletedSubTab;
14
22
  }
15
23
  export interface TransactionCategorizationPayload {
16
24
  next_page_token: string | null;
17
25
  query: TransactionCategorizationQueryPayload;
18
26
  total_count: number;
19
27
  transactions: SupportedTransactionPayload[];
28
+ /**
29
+ * Count of transactions for the entire parent tab (Review or Completed),
30
+ * regardless of which sub-tab the listing was scoped to. Used solely to
31
+ * drive the parent tab badge counts on the UI; pagination, list-level
32
+ * counts, and sub-tab chips continue to consume {@link total_count}.
33
+ *
34
+ * Optional at the wire boundary so that any version-skew scenario
35
+ * (delayed BE rollout, cached SW response, hotfix without the BE change)
36
+ * deserializes cleanly. Consumers are expected to fall back to
37
+ * {@link total_count} when this is absent — see
38
+ * `fetchTransactionCategorizationEpic` for the canonical fallback.
39
+ */
40
+ parent_tab_total_count?: number;
20
41
  }
21
42
  export type TransactionCategorizationResponse = ZeniAPIResponse<TransactionCategorizationPayload>;
22
43
  export interface EntityRelatedUpdatesPayload {
@@ -6,7 +6,8 @@ import type { MonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
6
6
  import { TransactionPayload } from '../../../entity/transaction/payloadTypes/transactionPayload';
7
7
  import { SupportedTransactionPayload } from '../../../entity/transaction/transactionState';
8
8
  import { ZeniAPIStatus } from '../../../responsePayload';
9
- import { type BatchDetails, type BatchListItem, type BatchStatus, type BulkUploadResultsTab, type BulkUploadSortKey, type BulkUploadState, type CompletedSubTab, type ManualSearchResult, type MissingReceiptsViewState, type MissingReceiptsViewUIState } from '../types/missingReceiptsViewState';
9
+ import { type CompletedSubTab } from '../types/completedSubTab';
10
+ import { type BatchDetails, type BatchListItem, type BatchStatus, type BulkUploadResultsTab, type BulkUploadSortKey, type BulkUploadState, type ManualSearchResult, type MissingReceiptsViewState, type MissingReceiptsViewUIState } from '../types/missingReceiptsViewState';
10
11
  export declare const getCompletedTransactionsCacheKey: (periodId: MonthYearPeriodId, sortKey: BulkUploadSortKey | undefined, sortOrder: SortOrder | undefined, subTab: CompletedSubTab) => string;
11
12
  export declare const initialBulkUploadState: BulkUploadState;
12
13
  export declare const initialState: MissingReceiptsViewState;
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.fetchCompletedTransactionsFailure = exports.fetchCompletedTransactionsSuccess = exports.fetchCompletedTransactions = exports.acknowledgeBulkUploadConfirmMatchComplete = exports.clearManualSearchResults = exports.searchTransactionsForManualMatchFailure = exports.searchTransactionsForManualMatchSuccess = exports.searchTransactionsForManualMatch = exports.clearBulkUpload = exports.setBulkUploadSortConfig = exports.setBulkUploadCompletedSubTab = exports.setBulkUploadResultsTab = exports.confirmBulkUploadMatchFailure = exports.confirmBulkUploadMatchSuccess = exports.confirmBulkUploadMatch = exports.fetchBulkUploadBatchesFailure = exports.fetchBulkUploadBatchesSuccess = exports.fetchBulkUploadBatches = exports.fetchMoreBatchDetailsFailure = exports.fetchMoreBatchDetailsComplete = exports.fetchMoreBatchDetails = exports.setInitialBatchDetailsLoading = exports.batchDetailFetchFailed = exports.storeBatchDetails = exports.fetchBulkUploadBatchDetailsFailure = exports.fetchBulkUploadBatchDetailsSuccess = exports.clearMissingReceiptsTabNavigation = exports.requestMissingReceiptsTabNavigation = exports.pusherBatchStatusUpdate = exports.bulkUploadAutomatchingTimedOut = exports.restoreBulkUploadMatchingState = exports.bulkUploadReceiptsFailure = exports.bulkUploadReceiptsSuccess = exports.setBulkUploadUploadedFileCount = exports.updateBulkUploadProgress = exports.bulkUploadReceipts = exports.clearExpenseAutomationMissingReceiptsView = exports.markMissingReceiptAsDone = exports.updateMissingReceiptsUIState = exports.updateMissingReceiptUploadState = exports.uploadMissingReceiptSuccess = exports.fetchMissingReceiptsFailure = exports.fetchMissingReceiptsSuccess = exports.fetchMissingReceipts = exports.restoreBulkUploadAutomatchingOnMount = exports.fetchBulkUploadBatchDetails = exports.initialState = exports.initialBulkUploadState = exports.getCompletedTransactionsCacheKey = void 0;
5
5
  const toolkit_1 = require("@reduxjs/toolkit");
6
6
  const timePeriod_1 = require("../../../commonStateTypes/timePeriod");
7
+ const completedSubTab_1 = require("../types/completedSubTab");
7
8
  const missingReceiptsViewState_1 = require("../types/missingReceiptsViewState");
8
9
  const getCompletedTransactionsCacheKey = (periodId, sortKey, sortOrder, subTab) => {
9
10
  const sortKeyPart = sortKey ?? 'none';
@@ -17,7 +18,7 @@ exports.initialBulkUploadState = {
17
18
  batchListByPeriod: {},
18
19
  batchListFetchState: { fetchState: 'Not-Started', error: undefined },
19
20
  batchStatusById: {},
20
- completedSubTab: 'all',
21
+ completedSubTab: completedSubTab_1.DEFAULT_COMPLETED_SUB_TAB,
21
22
  completedTransactionsByPeriod: {},
22
23
  confirmMatchStatus: { fetchState: 'Not-Started', error: undefined },
23
24
  currentBatchId: undefined,
@@ -65,7 +65,9 @@ export declare const fetchTransactionCategorization: import("@reduxjs/toolkit").
65
65
  isUncategorizedExpenseCategoryEnabled: boolean | undefined;
66
66
  }, "expenseAutomationTransactionsView/updateTransactionCategorization", never, never>, updateCurrentSelectedTransactionCategorizationTab: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[newSelectedTab: "review" | "autoCategorized"], {
67
67
  newSelectedTab: "review" | "autoCategorized";
68
- }, "expenseAutomationTransactionsView/updateCurrentSelectedTransactionCategorizationTab", never, never>, updateTransactionCategorizationSaveStatus: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[selectedTab: "review" | "autoCategorized", fetchState: FetchState, error?: ZeniAPIStatus<Record<string, unknown>> | undefined], {
68
+ }, "expenseAutomationTransactionsView/updateCurrentSelectedTransactionCategorizationTab", never, never>, updateTransactionCategorizationCompletedSubTab: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[newSubTab: "all" | "manual" | "ai_accountant"], {
69
+ newSubTab: "all" | "manual" | "ai_accountant";
70
+ }, "expenseAutomationTransactionsView/updateTransactionCategorizationCompletedSubTab", never, never>, updateTransactionCategorizationSaveStatus: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[selectedTab: "review" | "autoCategorized", fetchState: FetchState, error?: ZeniAPIStatus<Record<string, unknown>> | undefined], {
69
71
  selectedTab: "review" | "autoCategorized";
70
72
  fetchState: FetchState;
71
73
  error: ZeniAPIStatus<Record<string, unknown>> | undefined;
@@ -120,7 +122,11 @@ export declare const fetchTransactionCategorization: import("@reduxjs/toolkit").
120
122
  selectedPeriod: MonthYearPeriod;
121
123
  selectedTab: TransactionsTab;
122
124
  totalCount: number;
123
- }, "expenseAutomationTransactionsView/updateTotalCountForTransactionCategorization">, fetchTransactionCategorizationSuccess: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
125
+ }, "expenseAutomationTransactionsView/updateTotalCountForTransactionCategorization">, updateParentTotalCountForTab: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
126
+ parentTotalCount: number;
127
+ selectedPeriod: MonthYearPeriod;
128
+ selectedTab: TransactionsTab;
129
+ }, "expenseAutomationTransactionsView/updateParentTotalCountForTab">, fetchTransactionCategorizationSuccess: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
124
130
  selectedTab: TransactionsTab;
125
131
  status: ZeniAPIStatus;
126
132
  refreshViewInBackground?: boolean;
@@ -151,7 +157,20 @@ export declare const fetchTransactionCategorization: import("@reduxjs/toolkit").
151
157
  uncategorizedAccounts: UncategorizedAccounts;
152
158
  isAccountingClassesEnabled: boolean | undefined;
153
159
  isUncategorizedExpenseCategoryEnabled: boolean | undefined;
154
- }, "expenseAutomationTransactionsView/setEntityRecommendationForLineIdsForCategorization", never, never>, updateSelectedTransactionId: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
160
+ }, "expenseAutomationTransactionsView/setEntityRecommendationForLineIdsForCategorization", never, never>, markCategoryClassRecommendationsInProgressForCategorization: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[selectedTab: "review" | "autoCategorized", transactionId: TransactionID, lineIds: string[]], {
161
+ selectedTab: "review" | "autoCategorized";
162
+ transactionId: TransactionID;
163
+ lineIds: string[];
164
+ }, "expenseAutomationTransactionsView/markCategoryClassRecommendationsInProgressForCategorization", never, never>, markCategoryClassRecommendationsCompletedForCategorization: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[selectedTab: "review" | "autoCategorized", transactionId: TransactionID, lineIds: string[]], {
165
+ selectedTab: "review" | "autoCategorized";
166
+ transactionId: TransactionID;
167
+ lineIds: string[];
168
+ }, "expenseAutomationTransactionsView/markCategoryClassRecommendationsCompletedForCategorization", never, never>, markCategoryClassRecommendationsFailureForCategorization: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[selectedTab: "review" | "autoCategorized", transactionId: TransactionID, lineIds: string[], error: ZeniAPIStatus<Record<string, unknown>>], {
169
+ selectedTab: "review" | "autoCategorized";
170
+ transactionId: TransactionID;
171
+ lineIds: string[];
172
+ error: ZeniAPIStatus<Record<string, unknown>>;
173
+ }, "expenseAutomationTransactionsView/markCategoryClassRecommendationsFailureForCategorization", never, never>, updateSelectedTransactionId: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
155
174
  lineId: ID;
156
175
  selectedTab: TransactionsTab;
157
176
  transactionId: ID;
@@ -4,13 +4,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  var _a;
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.uploadTransactionCategorizationReceiptSuccess = exports.updateTransactionCategorizationUploadReceiptState = exports.backgroundRefetchReviewTab = exports.syncTransactionCategorizationFromDetailSave = exports.updateSelectedTransactionId = exports.setEntityRecommendationForLineIdsForCategorization = exports.clearExpenseAutomationTransactionsView = exports.clearExpenseAutomationTransactionsViewPerTabView = exports.updateSelectedCheckboxTransactionIds = exports.fetchTransactionCategorizationView = exports.resetOtherTabsFetchState = exports.fetchTransactionCategorizationSuccess = exports.updateTotalCountForTransactionCategorization = exports.setAllItemsToCategoryClassInLocalDataForCategorization = exports.updateSelectedCustomerForTransaction = exports.updateSelectedVendorForTransaction = exports.updateStatusForTransactionNotMiscategorizedUpdateForCategorization = exports.markTransactionAsNotMiscategorized = exports.updateTransactionCategorizationSaveStatus = exports.updateCurrentSelectedTransactionCategorizationTab = exports.updateTransactionCategorization = exports.saveTransactionCategorization = exports.fetchTransactionCategorizationFailure = exports.saveTransactionCategorizationLocalData = exports.initializeTransactionCategorizationViewLocalData = exports.updateTransactionCategorizationUIState = exports.fetchTransactionCategorization = exports.initialState = exports.initialTransactionTabViewState = void 0;
7
+ exports.uploadTransactionCategorizationReceiptSuccess = exports.updateTransactionCategorizationUploadReceiptState = exports.backgroundRefetchReviewTab = exports.syncTransactionCategorizationFromDetailSave = exports.updateSelectedTransactionId = exports.markCategoryClassRecommendationsFailureForCategorization = exports.markCategoryClassRecommendationsCompletedForCategorization = exports.markCategoryClassRecommendationsInProgressForCategorization = exports.setEntityRecommendationForLineIdsForCategorization = exports.clearExpenseAutomationTransactionsView = exports.clearExpenseAutomationTransactionsViewPerTabView = exports.updateSelectedCheckboxTransactionIds = exports.fetchTransactionCategorizationView = exports.resetOtherTabsFetchState = exports.fetchTransactionCategorizationSuccess = exports.updateParentTotalCountForTab = exports.updateTotalCountForTransactionCategorization = exports.setAllItemsToCategoryClassInLocalDataForCategorization = exports.updateSelectedCustomerForTransaction = exports.updateSelectedVendorForTransaction = exports.updateStatusForTransactionNotMiscategorizedUpdateForCategorization = exports.markTransactionAsNotMiscategorized = exports.updateTransactionCategorizationSaveStatus = exports.updateTransactionCategorizationCompletedSubTab = exports.updateCurrentSelectedTransactionCategorizationTab = exports.updateTransactionCategorization = exports.saveTransactionCategorization = exports.fetchTransactionCategorizationFailure = exports.saveTransactionCategorizationLocalData = exports.initializeTransactionCategorizationViewLocalData = exports.updateTransactionCategorizationUIState = exports.fetchTransactionCategorization = exports.initialState = exports.initialTransactionTabViewState = void 0;
8
8
  const toolkit_1 = require("@reduxjs/toolkit");
9
9
  const get_1 = __importDefault(require("lodash/get"));
10
10
  const uniq_1 = __importDefault(require("lodash/uniq"));
11
11
  const timePeriod_1 = require("../../../commonStateTypes/timePeriod");
12
12
  const vendorTransaction_1 = require("../../../entity/transaction/stateTypes/vendorTransaction");
13
13
  const transactionCategorizationLocalDataHelper_1 = require("../helpers/transactionCategorizationLocalDataHelper");
14
+ const completedSubTab_1 = require("../types/completedSubTab");
14
15
  exports.initialTransactionTabViewState = {
15
16
  saveStatus: {
16
17
  fetchState: 'Not-Started',
@@ -48,12 +49,74 @@ exports.initialTransactionTabViewState = {
48
49
  selectedTransactionLineId: undefined,
49
50
  };
50
51
  exports.initialState = {
52
+ autoCategorizedSubTabCache: {},
53
+ parentTotalCountByTab: {
54
+ review: {},
55
+ autoCategorized: {},
56
+ },
51
57
  selectedTransactionCategorizationTab: 'review',
58
+ selectedTransactionCategorizationCompletedSubTab: completedSubTab_1.DEFAULT_COMPLETED_SUB_TAB,
52
59
  transactionCategorizationView: {
53
60
  review: { ...exports.initialTransactionTabViewState },
54
61
  autoCategorized: { ...exports.initialTransactionTabViewState },
55
62
  },
56
63
  };
64
+ /**
65
+ * Capture the autoCategorized tab's current paginated dataset for a sub-tab.
66
+ * The mid-flight `'In-Progress'` state is normalised to `'Completed'` because
67
+ * the in-flight request is cancelled by `switchMap` in the fetch epic when the
68
+ * sub-tab changes — restoring `'In-Progress'` later would leave the UI stuck
69
+ * showing a phantom loading state with no fetch actually running. Length-zero
70
+ * datasets degrade to `'Not-Started'` so the next visit triggers a fresh
71
+ * fetch from page 1 instead of short-circuiting on stale empty caches.
72
+ *
73
+ * Snapshot scope (deliberately narrow):
74
+ * - Captured: `pageToken`, `totalCount`, `transactionIdsBySelectedPeriod`,
75
+ * `fetchState`, `error`, `scrollPosition`. The first five are the keys the
76
+ * fetch epic's cache check reads to decide whether to short-circuit a
77
+ * sub-tab revisit; `scrollPosition` is captured per sub-tab so revisiting
78
+ * restores the user's last offset and switching to a never-visited sub-tab
79
+ * starts at the top instead of inheriting the outgoing sub-tab's offset
80
+ * (which would land the user at a meaningless row index against a
81
+ * completely different list).
82
+ * - Intentionally NOT captured (left in place on `autoCat` and inherited by
83
+ * the incoming sub-tab):
84
+ * - `uiState.searchString`, `uiState.sortKey`, `uiState.sortOrder`,
85
+ * `uiState.nodeCollapseState`, `uiState.limit` — search and sort are
86
+ * user-applied preferences that persist across sub-tabs by design (a
87
+ * search query stays active when the user toggles between Manual / AI
88
+ * Accountant).
89
+ * - `selectedCheckBoxTransactionIds` — selection state is intentionally
90
+ * cleared at the action source (the fetch epic / save flow), not
91
+ * by the snapshot path. Carrying selection across sub-tabs is safe
92
+ * because every sub-tab change goes through a fetch round-trip that
93
+ * reconciles selection against the new row IDs.
94
+ * - `transactionReviewLocalDataById`, `transactionIdsWithUnsavedData`,
95
+ * `uploadReceiptStatusById`, `saveStatus`, `refreshStatus`,
96
+ * `markAsNotMiscategorizedStatus` — these are per-row / per-flight
97
+ * caches that are write-through global state, not sub-tab-scoped.
98
+ *
99
+ * If the contract changes (e.g. selection should reset on every sub-tab
100
+ * switch), update both this comment and the snapshot return shape together.
101
+ */
102
+ function snapshotAutoCategorizedTab(autoCat) {
103
+ const transactionIdsBySelectedPeriod = {
104
+ ...autoCat.transactionIdsBySelectedPeriod,
105
+ };
106
+ const hasAnyLoadedRows = Object.values(transactionIdsBySelectedPeriod).some((ids) => ids.length > 0);
107
+ let normalisedFetchState = autoCat.fetchState;
108
+ if (normalisedFetchState === 'In-Progress') {
109
+ normalisedFetchState = hasAnyLoadedRows ? 'Completed' : 'Not-Started';
110
+ }
111
+ return {
112
+ error: autoCat.error,
113
+ fetchState: normalisedFetchState,
114
+ pageToken: autoCat.uiState.pageToken,
115
+ scrollPosition: autoCat.uiState.scrollPosition,
116
+ totalCount: { ...autoCat.uiState.totalCount },
117
+ transactionIdsBySelectedPeriod,
118
+ };
119
+ }
57
120
  const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
58
121
  name: 'expenseAutomationTransactionsView',
59
122
  initialState: exports.initialState,
@@ -267,6 +330,55 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
267
330
  return { payload: { newSelectedTab } };
268
331
  },
269
332
  },
333
+ updateTransactionCategorizationCompletedSubTab: {
334
+ reducer(draft, action) {
335
+ const { newSubTab } = action.payload;
336
+ const previousSubTab = draft.selectedTransactionCategorizationCompletedSubTab;
337
+ if (previousSubTab === newSubTab) {
338
+ return;
339
+ }
340
+ const autoCat = draft.transactionCategorizationView.autoCategorized;
341
+ // Snapshot the outgoing sub-tab so a future revisit can short-circuit
342
+ // the fetch epic (mirrors the parent-tab "don't refetch what's
343
+ // already cached" behaviour). Period-scoped data is preserved as-is
344
+ // because the snapshot itself is keyed only by sub-tab; the active
345
+ // tab's per-period maps remain the source of truth while live.
346
+ draft.autoCategorizedSubTabCache[previousSubTab] =
347
+ snapshotAutoCategorizedTab(autoCat);
348
+ const cached = draft.autoCategorizedSubTabCache[newSubTab];
349
+ if (cached != null) {
350
+ autoCat.uiState.pageToken = cached.pageToken;
351
+ autoCat.uiState.totalCount = { ...cached.totalCount };
352
+ autoCat.uiState.scrollPosition = cached.scrollPosition;
353
+ autoCat.transactionIdsBySelectedPeriod = {
354
+ ...cached.transactionIdsBySelectedPeriod,
355
+ };
356
+ autoCat.fetchState = cached.fetchState;
357
+ autoCat.error = cached.error;
358
+ }
359
+ else {
360
+ // Sub-tab has never been visited — clear the active slot so the
361
+ // view-epic's `fetchState === 'Not-Started' || transactionIds.length
362
+ // === 0` cache check falls through and triggers a fresh page-1
363
+ // fetch for the new dataset. `scrollPosition` is reset to the top
364
+ // so the incoming list isn't anchored to the outgoing sub-tab's
365
+ // offset (different rows → previous offset is meaningless).
366
+ autoCat.uiState.pageToken = null;
367
+ autoCat.uiState.totalCount = {};
368
+ autoCat.uiState.scrollPosition = {
369
+ scrollTop: 0,
370
+ scrollLeft: undefined,
371
+ };
372
+ autoCat.transactionIdsBySelectedPeriod = {};
373
+ autoCat.fetchState = 'Not-Started';
374
+ autoCat.error = undefined;
375
+ }
376
+ draft.selectedTransactionCategorizationCompletedSubTab = newSubTab;
377
+ },
378
+ prepare(newSubTab) {
379
+ return { payload: { newSubTab } };
380
+ },
381
+ },
270
382
  updateTransactionCategorization: {
271
383
  reducer(draft, action) {
272
384
  const selectedTab = action.payload.selectedTab;
@@ -315,12 +427,9 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
315
427
  const selectedCheckBoxTransactionIds = draft.transactionCategorizationView[selectedTab]
316
428
  .selectedCheckBoxTransactionIds;
317
429
  if (selectedTransaction != null &&
318
- (0, transactionCategorizationLocalDataHelper_1.checkIfAllLineItemsAreCategoryClassFilled)(selectedTransaction, newTransactionLocalData, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled) === true && // check if all line items are filled
319
- selectedCheckBoxTransactionIds.length < transactionCategorizationLocalDataHelper_1.MAX_SELECTION_LIMIT &&
320
- selectedCheckBoxTransactionIds.includes(transactionId) === false) {
430
+ (0, transactionCategorizationLocalDataHelper_1.shouldAutoSelectAndAdd)(selectedTransaction, newTransactionLocalData, selectedCheckBoxTransactionIds, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled)) {
321
431
  draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds = [
322
- ...draft.transactionCategorizationView[selectedTab]
323
- .selectedCheckBoxTransactionIds,
432
+ ...selectedCheckBoxTransactionIds,
324
433
  transactionId,
325
434
  ];
326
435
  }
@@ -403,9 +512,26 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
403
512
  const transactionLocalData = (0, get_1.default)(draft.transactionCategorizationView[selectedTab]
404
513
  .transactionReviewLocalDataById, transactionId.id, undefined);
405
514
  if (transactionLocalData != null) {
515
+ const newLocalData = (0, transactionCategorizationLocalDataHelper_1.setEntityRecommendationForLineIdInLocalData)(draft.transactionCategorizationView[selectedTab], transaction, transactionLocalData.transactionReviewLocalData, entity, lineIds, uncategorizedAccounts, action.payload.recommendationWithCOTByLineId, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled);
516
+ // Release the per-line fetch flag for every line this fetch
517
+ // covered. Done in-place on the freshly returned local data
518
+ // (the helper produces a new mutable object), before we write
519
+ // it back to the draft.
520
+ lineIds.forEach((lineId) => {
521
+ const lineItem = newLocalData.lineItemById[lineId];
522
+ if (lineItem != null) {
523
+ newLocalData.lineItemById[lineId] = {
524
+ ...lineItem,
525
+ categoryClassRecommendationsFetchState: {
526
+ fetchState: 'Completed',
527
+ error: undefined,
528
+ },
529
+ };
530
+ }
531
+ });
406
532
  draft.transactionCategorizationView[selectedTab].transactionReviewLocalDataById[transaction.id] = {
407
533
  transactionId: transaction.id,
408
- transactionReviewLocalData: (0, transactionCategorizationLocalDataHelper_1.setEntityRecommendationForLineIdInLocalData)(draft.transactionCategorizationView[selectedTab], transaction, transactionLocalData.transactionReviewLocalData, entity, lineIds, uncategorizedAccounts, action.payload.recommendationWithCOTByLineId, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled),
534
+ transactionReviewLocalData: newLocalData,
409
535
  };
410
536
  // Mark transaction as dirty (autofill happened)
411
537
  if (!draft.transactionCategorizationView[selectedTab].transactionIdsWithUnsavedData.includes(transaction.id)) {
@@ -418,6 +544,99 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
418
544
  }
419
545
  },
420
546
  },
547
+ // Start arm of the line-update recommendations fetch. Flips covered
548
+ // lines to In-Progress AND removes the transaction from
549
+ // selectedCheckBoxTransactionIds — the row is unchecked + disabled
550
+ // until either the success arm (recheck if limit allows) or the
551
+ // failure arm releases the per-line flag.
552
+ markCategoryClassRecommendationsInProgressForCategorization: {
553
+ reducer(draft, action) {
554
+ const { selectedTab, transactionId, lineIds } = action.payload;
555
+ const tab = draft.transactionCategorizationView[selectedTab];
556
+ const transactionLocalData = (0, get_1.default)(tab.transactionReviewLocalDataById, transactionId.id, undefined);
557
+ if (transactionLocalData != null) {
558
+ lineIds.forEach((lineId) => {
559
+ const lineItem = transactionLocalData.transactionReviewLocalData.lineItemById[lineId];
560
+ if (lineItem != null) {
561
+ transactionLocalData.transactionReviewLocalData.lineItemById[lineId] = {
562
+ ...lineItem,
563
+ categoryClassRecommendationsFetchState: {
564
+ fetchState: 'In-Progress',
565
+ error: undefined,
566
+ },
567
+ };
568
+ }
569
+ });
570
+ }
571
+ // Uncheck on fetch start: the row's existing category/class are
572
+ // stale after a payee change, so we drop the auto-selection until
573
+ // fresh recommendations land.
574
+ if (tab.selectedCheckBoxTransactionIds.includes(transactionId.id)) {
575
+ tab.selectedCheckBoxTransactionIds =
576
+ tab.selectedCheckBoxTransactionIds.filter((id) => id !== transactionId.id);
577
+ }
578
+ },
579
+ prepare(selectedTab, transactionId, lineIds) {
580
+ return { payload: { selectedTab, transactionId, lineIds } };
581
+ },
582
+ },
583
+ // Completed arm. Used by the epic when the HTTP call succeeds but the
584
+ // server returned no per-line recommendations — we still need to
585
+ // release the In-Progress flag so the skeleton resolves. Successful
586
+ // responses with recommendations go through
587
+ // setEntityRecommendationForLineIdsForCategorization instead (which
588
+ // also flips the lines to Completed).
589
+ markCategoryClassRecommendationsCompletedForCategorization: {
590
+ reducer(draft, action) {
591
+ const { selectedTab, transactionId, lineIds } = action.payload;
592
+ const transactionLocalData = (0, get_1.default)(draft.transactionCategorizationView[selectedTab]
593
+ .transactionReviewLocalDataById, transactionId.id, undefined);
594
+ if (transactionLocalData != null) {
595
+ lineIds.forEach((lineId) => {
596
+ const lineItem = transactionLocalData.transactionReviewLocalData.lineItemById[lineId];
597
+ if (lineItem != null) {
598
+ transactionLocalData.transactionReviewLocalData.lineItemById[lineId] = {
599
+ ...lineItem,
600
+ categoryClassRecommendationsFetchState: {
601
+ fetchState: 'Completed',
602
+ error: undefined,
603
+ },
604
+ };
605
+ }
606
+ });
607
+ }
608
+ },
609
+ prepare(selectedTab, transactionId, lineIds) {
610
+ return { payload: { selectedTab, transactionId, lineIds } };
611
+ },
612
+ },
613
+ // Failure arm. Flips covered lines to Error so the skeleton resolves
614
+ // and downstream UI can react to the failure. The transaction stays
615
+ // unchecked from the start-arm action.
616
+ markCategoryClassRecommendationsFailureForCategorization: {
617
+ reducer(draft, action) {
618
+ const { selectedTab, transactionId, lineIds, error } = action.payload;
619
+ const transactionLocalData = (0, get_1.default)(draft.transactionCategorizationView[selectedTab]
620
+ .transactionReviewLocalDataById, transactionId.id, undefined);
621
+ if (transactionLocalData != null) {
622
+ lineIds.forEach((lineId) => {
623
+ const lineItem = transactionLocalData.transactionReviewLocalData.lineItemById[lineId];
624
+ if (lineItem != null) {
625
+ transactionLocalData.transactionReviewLocalData.lineItemById[lineId] = {
626
+ ...lineItem,
627
+ categoryClassRecommendationsFetchState: {
628
+ fetchState: 'Error',
629
+ error,
630
+ },
631
+ };
632
+ }
633
+ });
634
+ }
635
+ },
636
+ prepare(selectedTab, transactionId, lineIds, error) {
637
+ return { payload: { selectedTab, transactionId, lineIds, error } };
638
+ },
639
+ },
421
640
  fetchTransactionCategorizationSuccess(draft, action) {
422
641
  const { selectedTab, refreshViewInBackground } = action.payload;
423
642
  if (draft.transactionCategorizationView[selectedTab].fetchState ===
@@ -513,9 +732,7 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
513
732
  .selectedCheckBoxTransactionIds;
514
733
  if (newTransactionLocalData.customer != null &&
515
734
  newTransactionLocalData.customer.id != null &&
516
- (0, transactionCategorizationLocalDataHelper_1.checkIfAllLineItemsAreCategoryClassFilled)(selectedTransaction, newTransactionLocalData, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled) === true && // check if all line items are filled
517
- selectedCheckBoxTransactionIds.length < transactionCategorizationLocalDataHelper_1.MAX_SELECTION_LIMIT &&
518
- selectedCheckBoxTransactionIds.includes(transactionId) === false) {
735
+ (0, transactionCategorizationLocalDataHelper_1.shouldAutoSelectAndAdd)(selectedTransaction, newTransactionLocalData, selectedCheckBoxTransactionIds, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled)) {
519
736
  draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds = [
520
737
  ...selectedCheckBoxTransactionIds,
521
738
  transactionId,
@@ -589,9 +806,7 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
589
806
  .selectedCheckBoxTransactionIds;
590
807
  if (newTransactionLocalData.vendor != null &&
591
808
  newTransactionLocalData.vendor.id != null &&
592
- (0, transactionCategorizationLocalDataHelper_1.checkIfAllLineItemsAreCategoryClassFilled)(selectedTransaction, newTransactionLocalData, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled) === true && // check if all line items are filled
593
- selectedCheckBoxTransactionIds.length < transactionCategorizationLocalDataHelper_1.MAX_SELECTION_LIMIT &&
594
- selectedCheckBoxTransactionIds.includes(transactionId) === false) {
809
+ (0, transactionCategorizationLocalDataHelper_1.shouldAutoSelectAndAdd)(selectedTransaction, newTransactionLocalData, selectedCheckBoxTransactionIds, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled)) {
595
810
  draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds = [
596
811
  ...selectedCheckBoxTransactionIds,
597
812
  transactionId,
@@ -704,6 +919,10 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
704
919
  const { selectedTab, selectedPeriod, totalCount } = action.payload;
705
920
  draft.transactionCategorizationView[selectedTab].uiState.totalCount[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)] = totalCount;
706
921
  },
922
+ updateParentTotalCountForTab(draft, action) {
923
+ const { selectedTab, selectedPeriod, parentTotalCount } = action.payload;
924
+ draft.parentTotalCountByTab[selectedTab][(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)] = parentTotalCount;
925
+ },
707
926
  updateSelectedCheckboxTransactionIds(draft, action) {
708
927
  const selectedTab = action.payload.selectedTab;
709
928
  const transactionIds = action.payload.transactionIds;
@@ -714,6 +933,16 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
714
933
  draft.transactionCategorizationView[selectedTab] = {
715
934
  ...exports.initialTransactionTabViewState,
716
935
  };
936
+ draft.parentTotalCountByTab[selectedTab] = {};
937
+ // The per-sub-tab snapshot cache lives at the slice root and is
938
+ // exclusively populated by the autoCategorized tab's sub-tab switching
939
+ // (see updateTransactionCategorizationCompletedSubTab). When that tab
940
+ // is reset, leaving stale snapshots behind would short-circuit the
941
+ // next sub-tab visit with phantom row IDs / page tokens that no longer
942
+ // correspond to anything in state — drop them in lockstep.
943
+ if (selectedTab === 'autoCategorized') {
944
+ draft.autoCategorizedSubTabCache = {};
945
+ }
717
946
  },
718
947
  clearExpenseAutomationTransactionsView(draft) {
719
948
  Object.assign(draft, exports.initialState);
@@ -810,5 +1039,5 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
810
1039
  },
811
1040
  },
812
1041
  });
813
- _a = expenseAutomationTransactionsView.actions, exports.fetchTransactionCategorization = _a.fetchTransactionCategorization, exports.updateTransactionCategorizationUIState = _a.updateTransactionCategorizationUIState, exports.initializeTransactionCategorizationViewLocalData = _a.initializeTransactionCategorizationViewLocalData, exports.saveTransactionCategorizationLocalData = _a.saveTransactionCategorizationLocalData, exports.fetchTransactionCategorizationFailure = _a.fetchTransactionCategorizationFailure, exports.saveTransactionCategorization = _a.saveTransactionCategorization, exports.updateTransactionCategorization = _a.updateTransactionCategorization, exports.updateCurrentSelectedTransactionCategorizationTab = _a.updateCurrentSelectedTransactionCategorizationTab, exports.updateTransactionCategorizationSaveStatus = _a.updateTransactionCategorizationSaveStatus, exports.markTransactionAsNotMiscategorized = _a.markTransactionAsNotMiscategorized, exports.updateStatusForTransactionNotMiscategorizedUpdateForCategorization = _a.updateStatusForTransactionNotMiscategorizedUpdateForCategorization, exports.updateSelectedVendorForTransaction = _a.updateSelectedVendorForTransaction, exports.updateSelectedCustomerForTransaction = _a.updateSelectedCustomerForTransaction, exports.setAllItemsToCategoryClassInLocalDataForCategorization = _a.setAllItemsToCategoryClassInLocalDataForCategorization, exports.updateTotalCountForTransactionCategorization = _a.updateTotalCountForTransactionCategorization, exports.fetchTransactionCategorizationSuccess = _a.fetchTransactionCategorizationSuccess, exports.resetOtherTabsFetchState = _a.resetOtherTabsFetchState, exports.fetchTransactionCategorizationView = _a.fetchTransactionCategorizationView, exports.updateSelectedCheckboxTransactionIds = _a.updateSelectedCheckboxTransactionIds, exports.clearExpenseAutomationTransactionsViewPerTabView = _a.clearExpenseAutomationTransactionsViewPerTabView, exports.clearExpenseAutomationTransactionsView = _a.clearExpenseAutomationTransactionsView, exports.setEntityRecommendationForLineIdsForCategorization = _a.setEntityRecommendationForLineIdsForCategorization, exports.updateSelectedTransactionId = _a.updateSelectedTransactionId, exports.syncTransactionCategorizationFromDetailSave = _a.syncTransactionCategorizationFromDetailSave, exports.backgroundRefetchReviewTab = _a.backgroundRefetchReviewTab, exports.updateTransactionCategorizationUploadReceiptState = _a.updateTransactionCategorizationUploadReceiptState, exports.uploadTransactionCategorizationReceiptSuccess = _a.uploadTransactionCategorizationReceiptSuccess;
1042
+ _a = expenseAutomationTransactionsView.actions, exports.fetchTransactionCategorization = _a.fetchTransactionCategorization, exports.updateTransactionCategorizationUIState = _a.updateTransactionCategorizationUIState, exports.initializeTransactionCategorizationViewLocalData = _a.initializeTransactionCategorizationViewLocalData, exports.saveTransactionCategorizationLocalData = _a.saveTransactionCategorizationLocalData, exports.fetchTransactionCategorizationFailure = _a.fetchTransactionCategorizationFailure, exports.saveTransactionCategorization = _a.saveTransactionCategorization, exports.updateTransactionCategorization = _a.updateTransactionCategorization, exports.updateCurrentSelectedTransactionCategorizationTab = _a.updateCurrentSelectedTransactionCategorizationTab, exports.updateTransactionCategorizationCompletedSubTab = _a.updateTransactionCategorizationCompletedSubTab, exports.updateTransactionCategorizationSaveStatus = _a.updateTransactionCategorizationSaveStatus, exports.markTransactionAsNotMiscategorized = _a.markTransactionAsNotMiscategorized, exports.updateStatusForTransactionNotMiscategorizedUpdateForCategorization = _a.updateStatusForTransactionNotMiscategorizedUpdateForCategorization, exports.updateSelectedVendorForTransaction = _a.updateSelectedVendorForTransaction, exports.updateSelectedCustomerForTransaction = _a.updateSelectedCustomerForTransaction, exports.setAllItemsToCategoryClassInLocalDataForCategorization = _a.setAllItemsToCategoryClassInLocalDataForCategorization, exports.updateTotalCountForTransactionCategorization = _a.updateTotalCountForTransactionCategorization, exports.updateParentTotalCountForTab = _a.updateParentTotalCountForTab, exports.fetchTransactionCategorizationSuccess = _a.fetchTransactionCategorizationSuccess, exports.resetOtherTabsFetchState = _a.resetOtherTabsFetchState, exports.fetchTransactionCategorizationView = _a.fetchTransactionCategorizationView, exports.updateSelectedCheckboxTransactionIds = _a.updateSelectedCheckboxTransactionIds, exports.clearExpenseAutomationTransactionsViewPerTabView = _a.clearExpenseAutomationTransactionsViewPerTabView, exports.clearExpenseAutomationTransactionsView = _a.clearExpenseAutomationTransactionsView, exports.setEntityRecommendationForLineIdsForCategorization = _a.setEntityRecommendationForLineIdsForCategorization, exports.markCategoryClassRecommendationsInProgressForCategorization = _a.markCategoryClassRecommendationsInProgressForCategorization, exports.markCategoryClassRecommendationsCompletedForCategorization = _a.markCategoryClassRecommendationsCompletedForCategorization, exports.markCategoryClassRecommendationsFailureForCategorization = _a.markCategoryClassRecommendationsFailureForCategorization, exports.updateSelectedTransactionId = _a.updateSelectedTransactionId, exports.syncTransactionCategorizationFromDetailSave = _a.syncTransactionCategorizationFromDetailSave, exports.backgroundRefetchReviewTab = _a.backgroundRefetchReviewTab, exports.updateTransactionCategorizationUploadReceiptState = _a.updateTransactionCategorizationUploadReceiptState, exports.uploadTransactionCategorizationReceiptSuccess = _a.uploadTransactionCategorizationReceiptSuccess;
814
1043
  exports.default = expenseAutomationTransactionsView.reducer;