@zeniai/client-epic-state 5.0.50-betaAS3 → 5.0.50-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 (23) hide show
  1. package/lib/epic.js +2 -1
  2. package/lib/esm/epic.js +2 -1
  3. package/lib/esm/index.js +2 -2
  4. package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +87 -27
  5. package/lib/esm/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +6 -0
  6. package/lib/esm/view/recommendation/fetchEntityRecommendationsForCategorizationEpic.js +83 -0
  7. package/lib/esm/view/recommendation/recommendationReducer.js +52 -1
  8. package/lib/esm/view/spendManagement/zeniAccounts/depositAccountDetail/depositAccountDetailSelector.js +1 -1
  9. package/lib/esm/view/spendManagement/zeniAccounts/depositAccountDetail/fetchDepositAccountDetailEpic.js +2 -2
  10. package/lib/index.d.ts +2 -2
  11. package/lib/index.js +15 -13
  12. package/lib/tsconfig.typecheck.tsbuildinfo +1 -0
  13. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +87 -27
  14. package/lib/view/expenseAutomationView/selectorTypes/transactionsViewSelectorTypes.d.ts +1 -0
  15. package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +6 -0
  16. package/lib/view/expenseAutomationView/types/transactionsViewState.d.ts +1 -0
  17. package/lib/view/recommendation/fetchEntityRecommendationsForCategorizationEpic.d.ts +8 -0
  18. package/lib/view/recommendation/fetchEntityRecommendationsForCategorizationEpic.js +87 -0
  19. package/lib/view/recommendation/recommendationReducer.d.ts +21 -1
  20. package/lib/view/recommendation/recommendationReducer.js +53 -2
  21. package/lib/view/spendManagement/zeniAccounts/depositAccountDetail/depositAccountDetailSelector.js +1 -1
  22. package/lib/view/spendManagement/zeniAccounts/depositAccountDetail/fetchDepositAccountDetailEpic.js +2 -2
  23. package/package.json +1 -1
@@ -8,6 +8,7 @@ exports.uploadTransactionCategorizationReceiptSuccess = exports.updateTransactio
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
+ const recommendationReducer_1 = require("../../recommendation/recommendationReducer");
11
12
  const timePeriod_1 = require("../../../commonStateTypes/timePeriod");
12
13
  const vendorTransaction_1 = require("../../../entity/transaction/stateTypes/vendorTransaction");
13
14
  const transactionCategorizationLocalDataHelper_1 = require("../helpers/transactionCategorizationLocalDataHelper");
@@ -403,9 +404,25 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
403
404
  const transactionLocalData = (0, get_1.default)(draft.transactionCategorizationView[selectedTab]
404
405
  .transactionReviewLocalDataById, transactionId.id, undefined);
405
406
  if (transactionLocalData != null) {
407
+ const newLocalData = (0, transactionCategorizationLocalDataHelper_1.setEntityRecommendationForLineIdInLocalData)(draft.transactionCategorizationView[selectedTab], transaction, transactionLocalData.transactionReviewLocalData, entity, lineIds, uncategorizedAccounts, action.payload.recommendationWithCOTByLineId, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled);
408
+ // Flip the per-line "fetching" flag to Completed for the lines this
409
+ // fetch covered. Reads as the success arm of the
410
+ // fetchEntityRecommendationsForCategorization → epic → reducer cycle.
411
+ lineIds.forEach((lineId) => {
412
+ const lineItem = newLocalData.lineItemById[lineId];
413
+ if (lineItem != null) {
414
+ newLocalData.lineItemById[lineId] = {
415
+ ...lineItem,
416
+ categoryClassRecommendationsFetchState: {
417
+ fetchState: 'Completed',
418
+ error: undefined,
419
+ },
420
+ };
421
+ }
422
+ });
406
423
  draft.transactionCategorizationView[selectedTab].transactionReviewLocalDataById[transaction.id] = {
407
424
  transactionId: transaction.id,
408
- transactionReviewLocalData: (0, transactionCategorizationLocalDataHelper_1.setEntityRecommendationForLineIdInLocalData)(draft.transactionCategorizationView[selectedTab], transaction, transactionLocalData.transactionReviewLocalData, entity, lineIds, uncategorizedAccounts, action.payload.recommendationWithCOTByLineId, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled),
425
+ transactionReviewLocalData: newLocalData,
409
426
  };
410
427
  // Mark transaction as dirty (autofill happened)
411
428
  if (!draft.transactionCategorizationView[selectedTab].transactionIdsWithUnsavedData.includes(transaction.id)) {
@@ -460,7 +477,13 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
460
477
  action.payload.status;
461
478
  },
462
479
  updateSelectedCustomerForTransaction(draft, action) {
463
- const { transactionId, lineId, updatedCustomer, selectedTransaction, selectedTab, uncategorizedAccounts, isAccountingClassesEnabled, isUncategorizedExpenseCategoryEnabled, } = action.payload;
480
+ const { transactionId, lineId, updatedCustomer, selectedTransaction, selectedTab,
481
+ // uncategorizedAccounts, isAccountingClassesEnabled, and
482
+ // isUncategorizedExpenseCategoryEnabled remain in the payload type for
483
+ // backward compatibility but are no longer read here — the post-fetch
484
+ // success reducer (setEntityRecommendationForLineIdsForCategorization)
485
+ // owns the all-fields-filled check that drives auto-selection.
486
+ } = action.payload;
464
487
  const transactionLocalData = (0, get_1.default)(draft.transactionCategorizationView[selectedTab]
465
488
  .transactionReviewLocalDataById, transactionId, undefined);
466
489
  if (transactionLocalData != null) {
@@ -509,18 +532,13 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
509
532
  transactionReviewLocalData: newTransactionLocalData,
510
533
  transactionId,
511
534
  };
512
- const selectedCheckBoxTransactionIds = draft.transactionCategorizationView[selectedTab]
513
- .selectedCheckBoxTransactionIds;
514
- if (newTransactionLocalData.customer != null &&
515
- 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) {
519
- draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds = [
520
- ...selectedCheckBoxTransactionIds,
521
- transactionId,
522
- ];
523
- }
535
+ // Auto-selection of the row checkbox is intentionally deferred to
536
+ // setEntityRecommendationForLineIdsForCategorization (success path of
537
+ // the recommendations fetch). On a payee change the existing
538
+ // category/class are stale, so auto-selecting against them would
539
+ // either trigger save flows on the wrong data or rely on a later
540
+ // recommendations write to fix it. Letting the success reducer do the
541
+ // auto-add keeps "checkbox flips on" aligned with "fresh recs are in".
524
542
  // Mark transaction as dirty (customer updated)
525
543
  if (!draft.transactionCategorizationView[selectedTab].transactionIdsWithUnsavedData.includes(transactionId)) {
526
544
  draft.transactionCategorizationView[selectedTab].transactionIdsWithUnsavedData = [
@@ -532,7 +550,11 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
532
550
  }
533
551
  },
534
552
  updateSelectedVendorForTransaction(draft, action) {
535
- const { transactionId, lineId, updatedVendor, selectedTransaction, selectedTab, uncategorizedAccounts, isAccountingClassesEnabled, isUncategorizedExpenseCategoryEnabled, } = action.payload;
553
+ const { transactionId, lineId, updatedVendor, selectedTransaction, selectedTab,
554
+ // See updateSelectedCustomerForTransaction for why
555
+ // uncategorizedAccounts, isAccountingClassesEnabled, and
556
+ // isUncategorizedExpenseCategoryEnabled are no longer destructured.
557
+ } = action.payload;
536
558
  const transactionLocalData = (0, get_1.default)(draft.transactionCategorizationView[selectedTab]
537
559
  .transactionReviewLocalDataById, transactionId, undefined);
538
560
  if (transactionLocalData != null) {
@@ -585,18 +607,8 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
585
607
  },
586
608
  transactionId,
587
609
  };
588
- const selectedCheckBoxTransactionIds = draft.transactionCategorizationView[selectedTab]
589
- .selectedCheckBoxTransactionIds;
590
- if (newTransactionLocalData.vendor != null &&
591
- 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) {
595
- draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds = [
596
- ...selectedCheckBoxTransactionIds,
597
- transactionId,
598
- ];
599
- }
610
+ // See updateSelectedCustomerForTransaction above for why auto-select
611
+ // is deferred to setEntityRecommendationForLineIdsForCategorization.
600
612
  // Mark transaction as dirty (vendor updated)
601
613
  if (!draft.transactionCategorizationView[selectedTab].transactionIdsWithUnsavedData.includes(transactionId)) {
602
614
  draft.transactionCategorizationView[selectedTab].transactionIdsWithUnsavedData = [
@@ -809,6 +821,54 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
809
821
  },
810
822
  },
811
823
  },
824
+ extraReducers: (builder) => {
825
+ // Start arm of the categorization-only recommendations fetch. Marks every
826
+ // affected line's per-line flag as In-Progress. The epic uses
827
+ // groupBy(transactionId+lineIds) + switchMap, so concurrent fetches for
828
+ // distinct (transaction, lineIds-set) targets do not cancel each other —
829
+ // there's no need for a global cleanup sweep here.
830
+ builder.addCase(recommendationReducer_1.fetchEntityRecommendationsForCategorization, (draft, action) => {
831
+ const { selectedTab, transactionDetails } = action.payload;
832
+ const { transactionId, lineIds } = transactionDetails;
833
+ const transactionLocalData = (0, get_1.default)(draft.transactionCategorizationView[selectedTab]
834
+ .transactionReviewLocalDataById, transactionId.id, undefined);
835
+ if (transactionLocalData != null) {
836
+ lineIds.forEach((lineId) => {
837
+ const lineItem = transactionLocalData.transactionReviewLocalData.lineItemById[lineId];
838
+ if (lineItem != null) {
839
+ transactionLocalData.transactionReviewLocalData.lineItemById[lineId] = {
840
+ ...lineItem,
841
+ categoryClassRecommendationsFetchState: {
842
+ fetchState: 'In-Progress',
843
+ error: undefined,
844
+ },
845
+ };
846
+ }
847
+ });
848
+ }
849
+ });
850
+ // Failure arm. Flips the affected lines' per-line flag to Error so the
851
+ // skeleton resolves and the queued checkbox re-evaluation drops the entry.
852
+ builder.addCase(recommendationReducer_1.setCategoryClassRecommendationsForCategorizationFailure, (draft, action) => {
853
+ const { selectedTab, transactionId, lineIds, error } = action.payload;
854
+ const transactionLocalData = (0, get_1.default)(draft.transactionCategorizationView[selectedTab]
855
+ .transactionReviewLocalDataById, transactionId.id, undefined);
856
+ if (transactionLocalData != null) {
857
+ lineIds.forEach((lineId) => {
858
+ const lineItem = transactionLocalData.transactionReviewLocalData.lineItemById[lineId];
859
+ if (lineItem != null) {
860
+ transactionLocalData.transactionReviewLocalData.lineItemById[lineId] = {
861
+ ...lineItem,
862
+ categoryClassRecommendationsFetchState: {
863
+ fetchState: 'Error',
864
+ error,
865
+ },
866
+ };
867
+ }
868
+ });
869
+ }
870
+ });
871
+ },
812
872
  });
813
873
  _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;
814
874
  exports.default = expenseAutomationTransactionsView.reducer;
@@ -19,6 +19,7 @@ export interface ExpenseAutomationTransactionViewSelector extends SelectorView {
19
19
  classList: ClassBase[];
20
20
  completionStatus: CompletionStatusType;
21
21
  fetchStateByTransactionTabs: Record<TransactionsTab, FetchStateAndError>;
22
+ hasInFlightCategoryClassRecommendations: boolean;
22
23
  isAccountingProjectsEnabled: boolean;
23
24
  markAsNotMiscategorizedStatus: FetchStateAndError;
24
25
  projectList: ProjectListSelectorView;
@@ -48,6 +48,11 @@ function getExpenseAutomationTransactionView(state) {
48
48
  return null;
49
49
  })
50
50
  .filter((transaction) => transaction != null);
51
+ // True if any visible line is currently fetching category/class
52
+ // recommendations because the user changed the payee. Used to disable Save
53
+ // and to defer manual checkbox selection.
54
+ const hasInFlightCategoryClassRecommendations = transactionLocalData.some((transaction) => Object.values(transaction.transactionReviewLocalData.lineItemById).some((lineItem) => lineItem.categoryClassRecommendationsFetchState?.fetchState ===
55
+ 'In-Progress'));
51
56
  const transactions = (0, transactionSelector_1.getSupportedTransactionsByIds)(transactionState, transactionIds);
52
57
  const transactionsWithCOT = transactions.map((transaction) => (0, transactionHelper_1.getTransactionWithCOT)(transaction));
53
58
  const monthEndFetchState = monthYearPeriodId != null
@@ -118,5 +123,6 @@ function getExpenseAutomationTransactionView(state) {
118
123
  uploadReceiptStatusById,
119
124
  selectedTransactionId,
120
125
  selectedTransactionLineId,
126
+ hasInFlightCategoryClassRecommendations,
121
127
  };
122
128
  }
@@ -46,6 +46,7 @@ export interface TransactionCategorizationLineItemData extends TransactionLineBa
46
46
  transactionId: ID;
47
47
  account?: AccountBase;
48
48
  categorizationStatus?: CategorizationStatusType;
49
+ categoryClassRecommendationsFetchState?: FetchStateAndError;
49
50
  class?: ClassBase;
50
51
  customer?: CustomerBase;
51
52
  item?: ProductOrService;
@@ -0,0 +1,8 @@
1
+ import { ActionsObservable, StateObservable } from 'redux-observable';
2
+ import { Observable } from 'rxjs';
3
+ import { RootState } from '../../reducer';
4
+ import { setEntityRecommendationForLineIdsForCategorization } from '../../view/expenseAutomationView/reducers/transactionsViewReducer';
5
+ import { ZeniAPI } from '../../zeniAPI';
6
+ import { fetchEntityRecommendationsForCategorization, setCategoryClassRecommendationsForCategorizationFailure, updateRecommendationForEntity } from './recommendationReducer';
7
+ export type ActionType = ReturnType<typeof updateRecommendationForEntity> | ReturnType<typeof fetchEntityRecommendationsForCategorization> | ReturnType<typeof setCategoryClassRecommendationsForCategorizationFailure> | ReturnType<typeof setEntityRecommendationForLineIdsForCategorization>;
8
+ export declare const fetchEntityRecommendationsForCategorizationEpic: (actions$: ActionsObservable<ActionType>, state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<ActionType>;
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchEntityRecommendationsForCategorizationEpic = void 0;
4
+ const rxjs_1 = require("rxjs");
5
+ const operators_1 = require("rxjs/operators");
6
+ const tenantSelector_1 = require("../../entity/tenant/tenantSelector");
7
+ const transactionHelper_1 = require("../../entity/transaction/transactionHelper");
8
+ const responsePayload_1 = require("../../responsePayload");
9
+ const transactionsViewReducer_1 = require("../../view/expenseAutomationView/reducers/transactionsViewReducer");
10
+ const accountListSelector_1 = require("../accountList/accountListSelector");
11
+ const transactionDetailState_1 = require("../transactionDetail/transactionDetailState");
12
+ const recommendationHelper_1 = require("./recommendationHelper");
13
+ const recommendationReducer_1 = require("./recommendationReducer");
14
+ // Categorization-list-only recommendations fetch.
15
+ //
16
+ // Concurrency model: groupBy on (transactionId.id, sorted lineIds) → switchMap
17
+ // inside each group → mergeMap to flatten groups. This means:
18
+ // • Same target (same transaction + same lineIds-set) — newer fetch cancels
19
+ // the prior in-flight fetch. Avoids the same-line stale-response race.
20
+ // • Different targets — run fully in parallel. Avoids the silent
21
+ // "stuck spinner" cancellation that the global-switchMap epic suffers
22
+ // from when the user moves between rows or JE lines mid-flight.
23
+ //
24
+ // The transactionsViewReducer's per-line categoryClassRecommendationsFetchState
25
+ // flag is set to In-Progress when the action enters this epic (start arm in
26
+ // extraReducers), Completed via setEntityRecommendationForLineIdsForCategorization,
27
+ // or Error via setCategoryClassRecommendationsForCategorizationFailure.
28
+ const fetchEntityRecommendationsForCategorizationEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(recommendationReducer_1.fetchEntityRecommendationsForCategorization.match), (0, operators_1.groupBy)((action) => {
29
+ const { transactionId, lineIds } = action.payload.transactionDetails;
30
+ return `${transactionId.id}|${[...lineIds].sort().join('|')}`;
31
+ }), (0, operators_1.mergeMap)((group$) => group$.pipe((0, operators_1.switchMap)((action) => {
32
+ const { entity, amount, transactionDetails, isFetchCOT, selectedTab, isUncategorizedExpenseCategoryEnabled, } = action.payload;
33
+ const query = {
34
+ amount,
35
+ transaction_id: (0, transactionDetailState_1.getActualTransactionID)(transactionDetails.transactionId.id),
36
+ transaction_type: transactionDetails.transaction.type,
37
+ line_ids: transactionDetails.lineIds,
38
+ };
39
+ if (isFetchCOT === true) {
40
+ query.include_cot = true;
41
+ }
42
+ const entityQuery = {
43
+ entity_name: entity.name,
44
+ entity_type: entity.type,
45
+ };
46
+ const recommendationUrl = entity.qboId === entity.name // entity id null case
47
+ ? `${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/recommendations?query=${encodeURIComponent(JSON.stringify(entityQuery))}`
48
+ : `${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/${toEntityTypeURL(entity.type)}/${entity.qboId}/recommendations?query=${encodeURIComponent(JSON.stringify(query))}`;
49
+ return zeniAPI
50
+ .getJSON(recommendationUrl)
51
+ .pipe((0, operators_1.mergeMap)((response) => {
52
+ if ((0, responsePayload_1.isSuccessResponse)(response) && response.data != null) {
53
+ const actions = [];
54
+ const { transactionId, transaction, lineIds } = transactionDetails;
55
+ const recommendationWithCOTByLineId = (0, recommendationHelper_1.getRecommendationByLineIdWithCOTFromRecommendationPayloadWithCOT)(response.data.recommendations, lineIds);
56
+ const recommendations = (0, recommendationHelper_1.getRecommendationFromRecommendationPayload)(response.data.recommendations);
57
+ const transactionWithCOT = isFetchCOT === true &&
58
+ recommendationWithCOTByLineId != null
59
+ ? (0, transactionHelper_1.getTransactionWithCOTFromRecommendations)(transaction, recommendationWithCOTByLineId)
60
+ : (0, transactionHelper_1.getTransactionWithCOT)(transaction);
61
+ actions.push((0, recommendationReducer_1.updateRecommendationForEntity)({
62
+ entityId: entity.id,
63
+ entityType: entity.type,
64
+ recommendations,
65
+ }));
66
+ if (recommendationWithCOTByLineId != null) {
67
+ const uncategorizedIncomeExpense = (0, accountListSelector_1.getUncategorizedAccounts)(state$.value.accountState, state$.value.accountListState, 'accountList');
68
+ actions.push((0, transactionsViewReducer_1.setEntityRecommendationForLineIdsForCategorization)(selectedTab, transactionId, transactionWithCOT, lineIds, entity, amount, recommendationWithCOTByLineId, uncategorizedIncomeExpense, isUncategorizedExpenseCategoryEnabled, (0, tenantSelector_1.getIsAccountingClassesEnabled)(state$.value)));
69
+ }
70
+ else {
71
+ // Successful HTTP but no per-line recommendations — still
72
+ // need to clear the In-Progress flag, otherwise the
73
+ // skeleton sticks. Treat as a "no recommendations" success
74
+ // by firing the failure action with a benign error so the
75
+ // line flag flips out of In-Progress.
76
+ actions.push((0, recommendationReducer_1.setCategoryClassRecommendationsForCategorizationFailure)(selectedTab, transactionId, lineIds, (0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', 'Recommendations response missing per-line data')));
77
+ }
78
+ return (0, rxjs_1.from)(actions);
79
+ }
80
+ else {
81
+ return (0, rxjs_1.of)((0, recommendationReducer_1.setCategoryClassRecommendationsForCategorizationFailure)(selectedTab, transactionDetails.transactionId, transactionDetails.lineIds, response.status));
82
+ }
83
+ }), (0, operators_1.catchError)((error) => (0, rxjs_1.of)((0, recommendationReducer_1.setCategoryClassRecommendationsForCategorizationFailure)(selectedTab, transactionDetails.transactionId, transactionDetails.lineIds, (0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', 'Fetch recommendations for categorization REST API call errored out' +
84
+ JSON.stringify(error))))));
85
+ }))));
86
+ exports.fetchEntityRecommendationsForCategorizationEpic = fetchEntityRecommendationsForCategorizationEpic;
87
+ const toEntityTypeURL = (entityType) => `${entityType}s`;
@@ -22,7 +22,27 @@ export declare const fetchEntityRecommendationsByTransactionId: import("@reduxjs
22
22
  isTransactionCategorizationFlow: any;
23
23
  isFetchCOT: any;
24
24
  isUncategorizedExpenseCategoryEnabled: boolean | undefined;
25
- }, "recommendation/fetchEntityRecommendationsByTransactionId", never, never>, fetchRecommendationByEntityName: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[entityName: string, entityType: EntityType], {
25
+ }, "recommendation/fetchEntityRecommendationsByTransactionId", never, never>, fetchEntityRecommendationsForCategorization: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[entity: Entity, amount: number, transactionDetails: {
26
+ lineIds: ID[];
27
+ transaction: SupportedTransaction;
28
+ transactionId: TransactionID;
29
+ }, selectedTab: "review" | "autoCategorized", isFetchCOT?: any, isUncategorizedExpenseCategoryEnabled?: boolean | undefined], {
30
+ entity: Entity;
31
+ amount: number;
32
+ transactionDetails: {
33
+ lineIds: ID[];
34
+ transaction: SupportedTransaction;
35
+ transactionId: TransactionID;
36
+ };
37
+ selectedTab: "review" | "autoCategorized";
38
+ isFetchCOT: any;
39
+ isUncategorizedExpenseCategoryEnabled: boolean | undefined;
40
+ }, "recommendation/fetchEntityRecommendationsForCategorization", never, never>, setCategoryClassRecommendationsForCategorizationFailure: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[selectedTab: "review" | "autoCategorized", transactionId: TransactionID, lineIds: string[], error: ZeniAPIStatus<Record<string, unknown>>], {
41
+ selectedTab: "review" | "autoCategorized";
42
+ transactionId: TransactionID;
43
+ lineIds: string[];
44
+ error: ZeniAPIStatus<Record<string, unknown>>;
45
+ }, "recommendation/setCategoryClassRecommendationsForCategorizationFailure", never, never>, fetchRecommendationByEntityName: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[entityName: string, entityType: EntityType], {
26
46
  entityName: string;
27
47
  entityType: EntityType;
28
48
  }, "recommendation/fetchRecommendationByEntityName", never, never>, fetchRecommendationByEntityId: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[entityId: string, entityType: EntityType], {
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  var _a;
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.clearRecommendation = exports.indicateReadyToInitializeRecommendation = exports.updateRecommendationForEntityFailure = exports.updateRecommendationForEntity = exports.fetchRecommendationByEntityId = exports.fetchRecommendationByEntityName = exports.fetchEntityRecommendationsByTransactionId = exports.initialState = void 0;
4
+ exports.clearRecommendation = exports.indicateReadyToInitializeRecommendation = exports.updateRecommendationForEntityFailure = exports.updateRecommendationForEntity = exports.fetchRecommendationByEntityId = exports.fetchRecommendationByEntityName = exports.setCategoryClassRecommendationsForCategorizationFailure = exports.fetchEntityRecommendationsForCategorization = exports.fetchEntityRecommendationsByTransactionId = exports.initialState = void 0;
5
5
  const toolkit_1 = require("@reduxjs/toolkit");
6
6
  const recommendationState_1 = require("./recommendationState");
7
7
  exports.initialState = {
@@ -110,12 +110,63 @@ const recommendation = (0, toolkit_1.createSlice)({
110
110
  };
111
111
  },
112
112
  },
113
+ // Categorization-list-only fetch action. Mirrors the
114
+ // fetchEntityRecommendationsByTransactionId payload but is observed by a
115
+ // separate epic (with groupBy + switchMap, keyed on
116
+ // transactionId+lineIds-set) so concurrent fetches for distinct targets do
117
+ // not cancel each other. Reducer hooks in transactionsViewReducer use this
118
+ // to drive the per-line categoryClassRecommendationsFetchState flag.
119
+ fetchEntityRecommendationsForCategorization: {
120
+ reducer(draft, action) {
121
+ const { entity } = action.payload;
122
+ const key = (0, recommendationState_1.getEntityRecommendationKey)(entity.type, entity.id);
123
+ const existingRecommendation = draft.recommendationByEntity[key];
124
+ if (existingRecommendation == null) {
125
+ draft.recommendationByEntity[key] = {
126
+ ...recommendationState_1.initialEntityRecommendationState,
127
+ fetchState: 'In-Progress',
128
+ };
129
+ }
130
+ },
131
+ prepare(entity, amount, transactionDetails, selectedTab, isFetchCOT = false, isUncategorizedExpenseCategoryEnabled) {
132
+ return {
133
+ payload: {
134
+ entity,
135
+ amount,
136
+ transactionDetails,
137
+ selectedTab,
138
+ isFetchCOT,
139
+ isUncategorizedExpenseCategoryEnabled,
140
+ },
141
+ };
142
+ },
143
+ },
144
+ setCategoryClassRecommendationsForCategorizationFailure: {
145
+ reducer(
146
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
147
+ _draft,
148
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
149
+ _action) {
150
+ // The transactionsViewReducer handles the per-line flag flip; this
151
+ // reducer slice has no recommendation cache to update for the failure.
152
+ },
153
+ prepare(selectedTab, transactionId, lineIds, error) {
154
+ return {
155
+ payload: {
156
+ selectedTab,
157
+ transactionId,
158
+ lineIds,
159
+ error,
160
+ },
161
+ };
162
+ },
163
+ },
113
164
  clearRecommendation(draft) {
114
165
  Object.assign(draft, exports.initialState);
115
166
  },
116
167
  },
117
168
  });
118
- _a = recommendation.actions, exports.fetchEntityRecommendationsByTransactionId = _a.fetchEntityRecommendationsByTransactionId, exports.fetchRecommendationByEntityName = _a.fetchRecommendationByEntityName, exports.fetchRecommendationByEntityId = _a.fetchRecommendationByEntityId, exports.updateRecommendationForEntity = _a.updateRecommendationForEntity, exports.updateRecommendationForEntityFailure = _a.updateRecommendationForEntityFailure, exports.indicateReadyToInitializeRecommendation = _a.indicateReadyToInitializeRecommendation, exports.clearRecommendation = _a.clearRecommendation;
169
+ _a = recommendation.actions, exports.fetchEntityRecommendationsByTransactionId = _a.fetchEntityRecommendationsByTransactionId, exports.fetchEntityRecommendationsForCategorization = _a.fetchEntityRecommendationsForCategorization, exports.setCategoryClassRecommendationsForCategorizationFailure = _a.setCategoryClassRecommendationsForCategorizationFailure, exports.fetchRecommendationByEntityName = _a.fetchRecommendationByEntityName, exports.fetchRecommendationByEntityId = _a.fetchRecommendationByEntityId, exports.updateRecommendationForEntity = _a.updateRecommendationForEntity, exports.updateRecommendationForEntityFailure = _a.updateRecommendationForEntityFailure, exports.indicateReadyToInitializeRecommendation = _a.indicateReadyToInitializeRecommendation, exports.clearRecommendation = _a.clearRecommendation;
119
170
  exports.default = recommendation.reducer;
120
171
  const doUpdateRecommendationForEntity = (draft, entityId, entityType, payload) => {
121
172
  const key = (0, recommendationState_1.getEntityRecommendationKey)(entityType, entityId);
@@ -34,7 +34,7 @@ const getDepositAccountDetail = (state, depositAccountId) => {
34
34
  const zeniAccountDetail = zeniAccountDetailState.depositAccountDetailById[depositAccountId];
35
35
  const depositAccount = (0, depositAccountSelector_1.getDepositAccountByDepositAccountId)(depositAccountState, depositAccountId);
36
36
  const zeniAccountsConfig = (0, zeniAccountsConfigSelector_1.getZeniAccountsConfigDetail)(state);
37
- const transactionListView = (0, depositAccountTransactionListSelector_1.getDepositAccountTransactionList)(state, depositAccountId);
37
+ const transactionListView = (0, depositAccountTransactionListSelector_1.getDepositAccountTransactionList)(state, depositAccountId, 5);
38
38
  let accountUpdateStatus = {
39
39
  editName: {
40
40
  fetchState: 'Not-Started',
@@ -19,7 +19,7 @@ const fetchDepositAccountDetailEpic = (actions$, state$) => actions$.pipe((0, op
19
19
  zeniAccountActions.push((0, paymentAccountListReducer_1.fetchPaymentAccountList)());
20
20
  zeniAccountActions.push((0, zeniAccountsConfigReducer_1.fetchZeniAccountsConfig)());
21
21
  zeniAccountActions.push((0, depositAccountDetailReducer_1.fetchDepositAccount)(depositAccountId));
22
- zeniAccountActions.push((0, depositAccountTransactionListReducer_1.fetchDepositAccountTransactionList)(depositAccountId, true));
22
+ zeniAccountActions.push((0, depositAccountTransactionListReducer_1.fetchDepositAccountTransactionList)(depositAccountId, true, 5));
23
23
  zeniAccountActions.push((0, depositAccountDetailReducer_1.fetchAccountHistory)(depositAccountId));
24
24
  }
25
25
  else {
@@ -40,7 +40,7 @@ const fetchDepositAccountDetailEpic = (actions$, state$) => actions$.pipe((0, op
40
40
  targetZeniAccountAccount.fetchState !== 'In-Progress')) {
41
41
  zeniAccountActions.push((0, depositAccountDetailReducer_1.fetchDepositAccount)(depositAccountId));
42
42
  }
43
- zeniAccountActions.push((0, depositAccountTransactionListReducer_1.fetchDepositAccountTransactionList)(depositAccountId, false));
43
+ zeniAccountActions.push((0, depositAccountTransactionListReducer_1.fetchDepositAccountTransactionList)(depositAccountId, false, 5));
44
44
  const zeniAccountsConfig = state.zeniAccountsConfigState;
45
45
  if (zeniAccountsConfig.hasValidState() === false &&
46
46
  zeniAccountsConfig.fetchState !== 'In-Progress') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "5.0.50-betaAS3",
3
+ "version": "5.0.50-betaML1",
4
4
  "description": "Shared module between Web & Mobile containing required abstractions for state management, async network communication. ",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib/esm/index.js",