@zeniai/client-epic-state 5.0.53-betaML3 → 5.0.53

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.
@@ -8,7 +8,6 @@ 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");
12
11
  const timePeriod_1 = require("../../../commonStateTypes/timePeriod");
13
12
  const vendorTransaction_1 = require("../../../entity/transaction/stateTypes/vendorTransaction");
14
13
  const transactionCategorizationLocalDataHelper_1 = require("../helpers/transactionCategorizationLocalDataHelper");
@@ -404,25 +403,9 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
404
403
  const transactionLocalData = (0, get_1.default)(draft.transactionCategorizationView[selectedTab]
405
404
  .transactionReviewLocalDataById, transactionId.id, undefined);
406
405
  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
- });
423
406
  draft.transactionCategorizationView[selectedTab].transactionReviewLocalDataById[transaction.id] = {
424
407
  transactionId: transaction.id,
425
- transactionReviewLocalData: newLocalData,
408
+ transactionReviewLocalData: (0, transactionCategorizationLocalDataHelper_1.setEntityRecommendationForLineIdInLocalData)(draft.transactionCategorizationView[selectedTab], transaction, transactionLocalData.transactionReviewLocalData, entity, lineIds, uncategorizedAccounts, action.payload.recommendationWithCOTByLineId, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled),
426
409
  };
427
410
  // Mark transaction as dirty (autofill happened)
428
411
  if (!draft.transactionCategorizationView[selectedTab].transactionIdsWithUnsavedData.includes(transaction.id)) {
@@ -477,13 +460,7 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
477
460
  action.payload.status;
478
461
  },
479
462
  updateSelectedCustomerForTransaction(draft, action) {
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;
463
+ const { transactionId, lineId, updatedCustomer, selectedTransaction, selectedTab, uncategorizedAccounts, isAccountingClassesEnabled, isUncategorizedExpenseCategoryEnabled, } = action.payload;
487
464
  const transactionLocalData = (0, get_1.default)(draft.transactionCategorizationView[selectedTab]
488
465
  .transactionReviewLocalDataById, transactionId, undefined);
489
466
  if (transactionLocalData != null) {
@@ -532,13 +509,18 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
532
509
  transactionReviewLocalData: newTransactionLocalData,
533
510
  transactionId,
534
511
  };
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".
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
+ }
542
524
  // Mark transaction as dirty (customer updated)
543
525
  if (!draft.transactionCategorizationView[selectedTab].transactionIdsWithUnsavedData.includes(transactionId)) {
544
526
  draft.transactionCategorizationView[selectedTab].transactionIdsWithUnsavedData = [
@@ -550,11 +532,7 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
550
532
  }
551
533
  },
552
534
  updateSelectedVendorForTransaction(draft, action) {
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;
535
+ const { transactionId, lineId, updatedVendor, selectedTransaction, selectedTab, uncategorizedAccounts, isAccountingClassesEnabled, isUncategorizedExpenseCategoryEnabled, } = action.payload;
558
536
  const transactionLocalData = (0, get_1.default)(draft.transactionCategorizationView[selectedTab]
559
537
  .transactionReviewLocalDataById, transactionId, undefined);
560
538
  if (transactionLocalData != null) {
@@ -607,8 +585,18 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
607
585
  },
608
586
  transactionId,
609
587
  };
610
- // See updateSelectedCustomerForTransaction above for why auto-select
611
- // is deferred to setEntityRecommendationForLineIdsForCategorization.
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
+ }
612
600
  // Mark transaction as dirty (vendor updated)
613
601
  if (!draft.transactionCategorizationView[selectedTab].transactionIdsWithUnsavedData.includes(transactionId)) {
614
602
  draft.transactionCategorizationView[selectedTab].transactionIdsWithUnsavedData = [
@@ -821,54 +809,6 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
821
809
  },
822
810
  },
823
811
  },
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
- },
872
812
  });
873
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;
874
814
  exports.default = expenseAutomationTransactionsView.reducer;
@@ -19,7 +19,6 @@ export interface ExpenseAutomationTransactionViewSelector extends SelectorView {
19
19
  classList: ClassBase[];
20
20
  completionStatus: CompletionStatusType;
21
21
  fetchStateByTransactionTabs: Record<TransactionsTab, FetchStateAndError>;
22
- hasInFlightCategoryClassRecommendations: boolean;
23
22
  isAccountingProjectsEnabled: boolean;
24
23
  markAsNotMiscategorizedStatus: FetchStateAndError;
25
24
  projectList: ProjectListSelectorView;
@@ -48,11 +48,6 @@ 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'));
56
51
  const transactions = (0, transactionSelector_1.getSupportedTransactionsByIds)(transactionState, transactionIds);
57
52
  const transactionsWithCOT = transactions.map((transaction) => (0, transactionHelper_1.getTransactionWithCOT)(transaction));
58
53
  const monthEndFetchState = monthYearPeriodId != null
@@ -123,6 +118,5 @@ function getExpenseAutomationTransactionView(state) {
123
118
  uploadReceiptStatusById,
124
119
  selectedTransactionId,
125
120
  selectedTransactionLineId,
126
- hasInFlightCategoryClassRecommendations,
127
121
  };
128
122
  }
@@ -46,7 +46,6 @@ export interface TransactionCategorizationLineItemData extends TransactionLineBa
46
46
  transactionId: ID;
47
47
  account?: AccountBase;
48
48
  categorizationStatus?: CategorizationStatusType;
49
- categoryClassRecommendationsFetchState?: FetchStateAndError;
50
49
  class?: ClassBase;
51
50
  customer?: CustomerBase;
52
51
  item?: ProductOrService;
@@ -22,27 +22,7 @@ export declare const fetchEntityRecommendationsByTransactionId: import("@reduxjs
22
22
  isTransactionCategorizationFlow: any;
23
23
  isFetchCOT: any;
24
24
  isUncategorizedExpenseCategoryEnabled: boolean | undefined;
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], {
25
+ }, "recommendation/fetchEntityRecommendationsByTransactionId", never, never>, fetchRecommendationByEntityName: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[entityName: string, entityType: EntityType], {
46
26
  entityName: string;
47
27
  entityType: EntityType;
48
28
  }, "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.setCategoryClassRecommendationsForCategorizationFailure = exports.fetchEntityRecommendationsForCategorization = exports.fetchEntityRecommendationsByTransactionId = exports.initialState = void 0;
4
+ exports.clearRecommendation = exports.indicateReadyToInitializeRecommendation = exports.updateRecommendationForEntityFailure = exports.updateRecommendationForEntity = exports.fetchRecommendationByEntityId = exports.fetchRecommendationByEntityName = 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,63 +110,12 @@ 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
- },
164
113
  clearRecommendation(draft) {
165
114
  Object.assign(draft, exports.initialState);
166
115
  },
167
116
  },
168
117
  });
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;
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;
170
119
  exports.default = recommendation.reducer;
171
120
  const doUpdateRecommendationForEntity = (draft, entityId, entityType, payload) => {
172
121
  const key = (0, recommendationState_1.getEntityRecommendationKey)(entityType, entityId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "5.0.53-betaML3",
3
+ "version": "5.0.53",
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",
@@ -1,83 +0,0 @@
1
- import { from, of } from 'rxjs';
2
- import { catchError, filter, groupBy, mergeMap, switchMap } from 'rxjs/operators';
3
- import { getIsAccountingClassesEnabled } from '../../entity/tenant/tenantSelector';
4
- import { getTransactionWithCOT, getTransactionWithCOTFromRecommendations, } from '../../entity/transaction/transactionHelper';
5
- import { createZeniAPIStatus, isSuccessResponse } from '../../responsePayload';
6
- import { setEntityRecommendationForLineIdsForCategorization } from '../../view/expenseAutomationView/reducers/transactionsViewReducer';
7
- import { getUncategorizedAccounts } from '../accountList/accountListSelector';
8
- import { getActualTransactionID } from '../transactionDetail/transactionDetailState';
9
- import { getRecommendationByLineIdWithCOTFromRecommendationPayloadWithCOT, getRecommendationFromRecommendationPayload, } from './recommendationHelper';
10
- import { fetchEntityRecommendationsForCategorization, setCategoryClassRecommendationsForCategorizationFailure, updateRecommendationForEntity, } from './recommendationReducer';
11
- // Categorization-list-only recommendations fetch.
12
- //
13
- // Concurrency model: groupBy on (transactionId.id, sorted lineIds) → switchMap
14
- // inside each group → mergeMap to flatten groups. This means:
15
- // • Same target (same transaction + same lineIds-set) — newer fetch cancels
16
- // the prior in-flight fetch. Avoids the same-line stale-response race.
17
- // • Different targets — run fully in parallel. Avoids the silent
18
- // "stuck spinner" cancellation that the global-switchMap epic suffers
19
- // from when the user moves between rows or JE lines mid-flight.
20
- //
21
- // The transactionsViewReducer's per-line categoryClassRecommendationsFetchState
22
- // flag is set to In-Progress when the action enters this epic (start arm in
23
- // extraReducers), Completed via setEntityRecommendationForLineIdsForCategorization,
24
- // or Error via setCategoryClassRecommendationsForCategorizationFailure.
25
- export const fetchEntityRecommendationsForCategorizationEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchEntityRecommendationsForCategorization.match), groupBy((action) => {
26
- const { transactionId, lineIds } = action.payload.transactionDetails;
27
- return `${transactionId.id}|${[...lineIds].sort().join('|')}`;
28
- }), mergeMap((group$) => group$.pipe(switchMap((action) => {
29
- const { entity, amount, transactionDetails, isFetchCOT, selectedTab, isUncategorizedExpenseCategoryEnabled, } = action.payload;
30
- const query = {
31
- amount,
32
- transaction_id: getActualTransactionID(transactionDetails.transactionId.id),
33
- transaction_type: transactionDetails.transaction.type,
34
- line_ids: transactionDetails.lineIds,
35
- };
36
- if (isFetchCOT === true) {
37
- query.include_cot = true;
38
- }
39
- const entityQuery = {
40
- entity_name: entity.name,
41
- entity_type: entity.type,
42
- };
43
- const recommendationUrl = entity.qboId === entity.name // entity id null case
44
- ? `${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/recommendations?query=${encodeURIComponent(JSON.stringify(entityQuery))}`
45
- : `${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/${toEntityTypeURL(entity.type)}/${entity.qboId}/recommendations?query=${encodeURIComponent(JSON.stringify(query))}`;
46
- return zeniAPI
47
- .getJSON(recommendationUrl)
48
- .pipe(mergeMap((response) => {
49
- if (isSuccessResponse(response) && response.data != null) {
50
- const actions = [];
51
- const { transactionId, transaction, lineIds } = transactionDetails;
52
- const recommendationWithCOTByLineId = getRecommendationByLineIdWithCOTFromRecommendationPayloadWithCOT(response.data.recommendations, lineIds);
53
- const recommendations = getRecommendationFromRecommendationPayload(response.data.recommendations);
54
- const transactionWithCOT = isFetchCOT === true &&
55
- recommendationWithCOTByLineId != null
56
- ? getTransactionWithCOTFromRecommendations(transaction, recommendationWithCOTByLineId)
57
- : getTransactionWithCOT(transaction);
58
- actions.push(updateRecommendationForEntity({
59
- entityId: entity.id,
60
- entityType: entity.type,
61
- recommendations,
62
- }));
63
- if (recommendationWithCOTByLineId != null) {
64
- const uncategorizedIncomeExpense = getUncategorizedAccounts(state$.value.accountState, state$.value.accountListState, 'accountList');
65
- actions.push(setEntityRecommendationForLineIdsForCategorization(selectedTab, transactionId, transactionWithCOT, lineIds, entity, amount, recommendationWithCOTByLineId, uncategorizedIncomeExpense, isUncategorizedExpenseCategoryEnabled, getIsAccountingClassesEnabled(state$.value)));
66
- }
67
- else {
68
- // Successful HTTP but no per-line recommendations — still
69
- // need to clear the In-Progress flag, otherwise the
70
- // skeleton sticks. Treat as a "no recommendations" success
71
- // by firing the failure action with a benign error so the
72
- // line flag flips out of In-Progress.
73
- actions.push(setCategoryClassRecommendationsForCategorizationFailure(selectedTab, transactionId, lineIds, createZeniAPIStatus('Unexpected Error', 'Recommendations response missing per-line data')));
74
- }
75
- return from(actions);
76
- }
77
- else {
78
- return of(setCategoryClassRecommendationsForCategorizationFailure(selectedTab, transactionDetails.transactionId, transactionDetails.lineIds, response.status));
79
- }
80
- }), catchError((error) => of(setCategoryClassRecommendationsForCategorizationFailure(selectedTab, transactionDetails.transactionId, transactionDetails.lineIds, createZeniAPIStatus('Unexpected Error', 'Fetch recommendations for categorization REST API call errored out' +
81
- JSON.stringify(error))))));
82
- }))));
83
- const toEntityTypeURL = (entityType) => `${entityType}s`;
@@ -1,8 +0,0 @@
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>;
@@ -1,87 +0,0 @@
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`;