@zeniai/client-epic-state 5.1.51-betaRD11 → 5.1.52-beta0RD

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.
@@ -67,18 +67,10 @@ export const toTransactionUpdatesFromTransactionCategorizationLocalData = (trans
67
67
  hasClassChanged =
68
68
  (lineItem.class?.qboId != null || accountLine.class?.qboId != null) &&
69
69
  lineItem.class?.qboId !== accountLine.class?.qboId;
70
- // JE/deposit lines carry vendor/customer per-line rather than at the
71
- // transaction level, so a lone entity pick with no category/class edit
72
- // must still be diffed here or it never reaches entityUpdates below.
73
- const originalLineEntityId = accountLine.vendor?.id ?? accountLine.customer?.id;
74
- const hasEntityChanged = currentSelectedEntityLine?.id !== originalLineEntityId &&
75
- Boolean(currentSelectedEntityLine?.id);
76
70
  const recommendedAccountBase = toRecommendedAccountBase(lineItem.amount.amount, lineItem.account);
77
71
  const recommendedClassBase = toRecommendedClassBase(lineItem.amount.amount, lineItem.class);
78
72
  if (entityDetails != null &&
79
- (hasCategoryChanged === true ||
80
- hasClassChanged === true ||
81
- hasEntityChanged === true)) {
73
+ (hasCategoryChanged === true || hasClassChanged === true)) {
82
74
  lastUpdatedCategory =
83
75
  hasCategoryChanged === true ? recommendedAccountBase : undefined;
84
76
  lastUpdatedClass =
@@ -89,9 +81,7 @@ export const toTransactionUpdatesFromTransactionCategorizationLocalData = (trans
89
81
  account: lastUpdatedCategory,
90
82
  class: lastUpdatedClass,
91
83
  };
92
- if (recommended.class != null ||
93
- recommended.account != null ||
94
- hasEntityChanged === true) {
84
+ if (recommended.class != null || recommended.account != null) {
95
85
  const entityId = entityDetails.entityId;
96
86
  doUpdateEntityUpdates(entityUpdates, entityId, entityDetails.entityType, recommended, false, undefined, entityDetails.entityName);
97
87
  }
@@ -5,7 +5,7 @@ import { getTransactionWithCOT, getTransactionWithCOTFromRecommendations, } from
5
5
  import { createZeniAPIStatus, isSuccessResponse } from '../../responsePayload';
6
6
  import { getUncategorizedAccounts } from '../accountList/accountListSelector';
7
7
  import { markCategoryClassRecommendationsCompletedForCategorization, markCategoryClassRecommendationsFailureForCategorization, markCategoryClassRecommendationsInProgressForCategorization, setEntityRecommendationForLineIdsForCategorization, } from '../expenseAutomationView/reducers/transactionsViewReducer';
8
- import { markCategoryClassRecommendationsCompletedForTransactionDetail, markCategoryClassRecommendationsFailureForTransactionDetail, markCategoryClassRecommendationsInProgressForTransactionDetail, setEntityRecommendationForLineIdsForTransactionDetail, } from '../transactionDetail/transactionDetailReducer';
8
+ import { markCategoryClassRecommendationsCompletedForTransactionDetail, markCategoryClassRecommendationsFailureForTransactionDetail, markCategoryClassRecommendationsInProgressForTransactionDetail, setEntityRecommendationForLineIdsForTransactionDetail, updateVendorBulkRecommendationsFetchStatus, } from '../transactionDetail/transactionDetailReducer';
9
9
  import { buildRecommendationUrl, getRecommendationByLineIdWithCOTFromRecommendationPayloadWithCOT, } from './recommendationHelper';
10
10
  import { fetchEntityRecommendationsForLineUpdate } from './recommendationReducer';
11
11
  // Close the per-target groupBy bucket after this much idle time so inactive
@@ -38,6 +38,16 @@ export const fetchEntityRecommendationsForLineUpdateEpic = (actions$, state$, ze
38
38
  const payload = action.payload;
39
39
  const { transactionId, transaction, lineIds } = payload.transactionDetails;
40
40
  const { entity, amount, isFetchCOT, isUncategorizedExpenseCategoryEnabled, } = payload;
41
+ // The vendor "Update all lines" popup on the detail page watches
42
+ // its own transaction-level fetch status, separate from the
43
+ // per-line statuses below — surface completion/failure there too
44
+ // when this fetch was triggered by that bulk action, or the popup
45
+ // hangs until its own 60s timeout regardless of how this request
46
+ // resolves.
47
+ const isBulkDetailFetch = payload.flowType === 'detail' && payload.isBulkFetch === true;
48
+ const emitWithBulkStatus = (primaryAction, bulkAction) => isBulkDetailFetch && bulkAction != null
49
+ ? of(primaryAction, bulkAction)
50
+ : of(primaryAction);
41
51
  // Synchronously emit the start arm so the per-line skeleton +
42
52
  // row uncheck happen before the HTTP request begins. The
43
53
  // discriminated-union payload narrows `payload.selectedTab` to
@@ -51,9 +61,9 @@ export const fetchEntityRecommendationsForLineUpdateEpic = (actions$, state$, ze
51
61
  .getJSON(url)
52
62
  .pipe(mergeMap((response) => {
53
63
  if (!isSuccessResponse(response) || response.data == null) {
54
- return of(payload.flowType === 'categorization'
64
+ return emitWithBulkStatus(payload.flowType === 'categorization'
55
65
  ? markCategoryClassRecommendationsFailureForCategorization(payload.selectedTab, transactionId, lineIds, response.status)
56
- : markCategoryClassRecommendationsFailureForTransactionDetail(transactionId, lineIds, response.status));
66
+ : markCategoryClassRecommendationsFailureForTransactionDetail(transactionId, lineIds, response.status), updateVendorBulkRecommendationsFetchStatus(transactionId, 'Error', response.status));
57
67
  }
58
68
  const recommendationWithCOTByLineId = getRecommendationByLineIdWithCOTFromRecommendationPayloadWithCOT(response.data.recommendations, lineIds);
59
69
  // HTTP 200 with no per-line recommendations: NOT an error.
@@ -61,9 +71,9 @@ export const fetchEntityRecommendationsForLineUpdateEpic = (actions$, state$, ze
61
71
  // downstream UI can treat this as "we tried, nothing to
62
72
  // apply".
63
73
  if (recommendationWithCOTByLineId == null) {
64
- return of(payload.flowType === 'categorization'
74
+ return emitWithBulkStatus(payload.flowType === 'categorization'
65
75
  ? markCategoryClassRecommendationsCompletedForCategorization(payload.selectedTab, transactionId, lineIds)
66
- : markCategoryClassRecommendationsCompletedForTransactionDetail(transactionId, lineIds));
76
+ : markCategoryClassRecommendationsCompletedForTransactionDetail(transactionId, lineIds), updateVendorBulkRecommendationsFetchStatus(transactionId, 'Completed'));
67
77
  }
68
78
  const transactionWithCOT = isFetchCOT === true
69
79
  ? getTransactionWithCOTFromRecommendations(transaction, recommendationWithCOTByLineId)
@@ -72,16 +82,16 @@ export const fetchEntityRecommendationsForLineUpdateEpic = (actions$, state$, ze
72
82
  if (payload.flowType === 'categorization') {
73
83
  return of(setEntityRecommendationForLineIdsForCategorization(payload.selectedTab, transactionId, transactionWithCOT, lineIds, entity, amount, recommendationWithCOTByLineId, uncategorizedIncomeExpense, isUncategorizedExpenseCategoryEnabled, getIsAccountingClassesEnabled(state$.value)));
74
84
  }
75
- return of(setEntityRecommendationForLineIdsForTransactionDetail(transactionId, transactionWithCOT, lineIds, entity, amount, recommendationWithCOTByLineId, uncategorizedIncomeExpense));
85
+ return emitWithBulkStatus(setEntityRecommendationForLineIdsForTransactionDetail(transactionId, transactionWithCOT, lineIds, entity, amount, recommendationWithCOTByLineId, uncategorizedIncomeExpense), updateVendorBulkRecommendationsFetchStatus(transactionId, 'Completed'));
76
86
  }), catchError((error) => {
77
87
  const errorMessage = 'fetchEntityRecommendationsForLineUpdate failed: ' +
78
88
  (error instanceof Error
79
89
  ? error.message
80
90
  : JSON.stringify(error));
81
91
  const status = createZeniAPIStatus('Unexpected Error', errorMessage);
82
- return of(payload.flowType === 'categorization'
92
+ return emitWithBulkStatus(payload.flowType === 'categorization'
83
93
  ? markCategoryClassRecommendationsFailureForCategorization(payload.selectedTab, transactionId, lineIds, status)
84
- : markCategoryClassRecommendationsFailureForTransactionDetail(transactionId, lineIds, status));
94
+ : markCategoryClassRecommendationsFailureForTransactionDetail(transactionId, lineIds, status), updateVendorBulkRecommendationsFetchStatus(transactionId, 'Error', status));
85
95
  }));
86
96
  return concat(of(startAction), httpStream$);
87
97
  }))));
@@ -108,12 +108,6 @@ export const toTransactionUpdatesFromTransactionDetailLocalData = (transactionDe
108
108
  hasClassChanged =
109
109
  (lineItem.class?.qboId != null || accountLine.class?.qboId != null) &&
110
110
  lineItem.class?.qboId !== accountLine.class?.qboId;
111
- // JE/deposit lines carry vendor/customer per-line rather than at the
112
- // transaction level, so a lone entity pick with no category/class edit
113
- // must still be diffed here or it never reaches entityUpdates below.
114
- const originalLineEntityId = accountLine.vendor?.id ?? accountLine.customer?.id;
115
- const hasEntityChanged = lineItem.lineEntity?.id !== originalLineEntityId &&
116
- Boolean(lineItem.lineEntity?.id);
117
111
  const recommendedAccountBase = toRecommendedAccountBase(lineItem.amount.amount, lineItem.account);
118
112
  const recommendedClassBase = toRecommendedClassBase(lineItem.amount.amount, lineItem.class);
119
113
  const latestRecommendedCategory = lineItem.recommendedAccount;
@@ -156,8 +150,7 @@ export const toTransactionUpdatesFromTransactionDetailLocalData = (transactionDe
156
150
  };
157
151
  if (recommended.class != null ||
158
152
  recommended.account != null ||
159
- updatedEntityId != null ||
160
- hasEntityChanged === true) {
153
+ updatedEntityId != null) {
161
154
  const vendorIdToUpdate = transactionDetailLocalData.vendorUpdates?.qboId;
162
155
  const customerIdToUpdate = transactionDetailLocalData.customerUpdates?.qboId;
163
156
  let entityId = entityDetails.entityId;
@@ -70,18 +70,10 @@ const toTransactionUpdatesFromTransactionCategorizationLocalData = (transactionC
70
70
  hasClassChanged =
71
71
  (lineItem.class?.qboId != null || accountLine.class?.qboId != null) &&
72
72
  lineItem.class?.qboId !== accountLine.class?.qboId;
73
- // JE/deposit lines carry vendor/customer per-line rather than at the
74
- // transaction level, so a lone entity pick with no category/class edit
75
- // must still be diffed here or it never reaches entityUpdates below.
76
- const originalLineEntityId = accountLine.vendor?.id ?? accountLine.customer?.id;
77
- const hasEntityChanged = currentSelectedEntityLine?.id !== originalLineEntityId &&
78
- Boolean(currentSelectedEntityLine?.id);
79
73
  const recommendedAccountBase = (0, saveTransactionHelper_1.toRecommendedAccountBase)(lineItem.amount.amount, lineItem.account);
80
74
  const recommendedClassBase = (0, saveTransactionHelper_1.toRecommendedClassBase)(lineItem.amount.amount, lineItem.class);
81
75
  if (entityDetails != null &&
82
- (hasCategoryChanged === true ||
83
- hasClassChanged === true ||
84
- hasEntityChanged === true)) {
76
+ (hasCategoryChanged === true || hasClassChanged === true)) {
85
77
  lastUpdatedCategory =
86
78
  hasCategoryChanged === true ? recommendedAccountBase : undefined;
87
79
  lastUpdatedClass =
@@ -92,9 +84,7 @@ const toTransactionUpdatesFromTransactionCategorizationLocalData = (transactionC
92
84
  account: lastUpdatedCategory,
93
85
  class: lastUpdatedClass,
94
86
  };
95
- if (recommended.class != null ||
96
- recommended.account != null ||
97
- hasEntityChanged === true) {
87
+ if (recommended.class != null || recommended.account != null) {
98
88
  const entityId = entityDetails.entityId;
99
89
  doUpdateEntityUpdates(entityUpdates, entityId, entityDetails.entityType, recommended, false, undefined, entityDetails.entityName);
100
90
  }
@@ -3,7 +3,7 @@ import { Observable } from 'rxjs';
3
3
  import { RootState } from '../../reducer';
4
4
  import { ZeniAPI } from '../../zeniAPI';
5
5
  import { markCategoryClassRecommendationsCompletedForCategorization, markCategoryClassRecommendationsFailureForCategorization, markCategoryClassRecommendationsInProgressForCategorization, setEntityRecommendationForLineIdsForCategorization } from '../expenseAutomationView/reducers/transactionsViewReducer';
6
- import { markCategoryClassRecommendationsCompletedForTransactionDetail, markCategoryClassRecommendationsFailureForTransactionDetail, markCategoryClassRecommendationsInProgressForTransactionDetail, setEntityRecommendationForLineIdsForTransactionDetail } from '../transactionDetail/transactionDetailReducer';
6
+ import { markCategoryClassRecommendationsCompletedForTransactionDetail, markCategoryClassRecommendationsFailureForTransactionDetail, markCategoryClassRecommendationsInProgressForTransactionDetail, setEntityRecommendationForLineIdsForTransactionDetail, updateVendorBulkRecommendationsFetchStatus } from '../transactionDetail/transactionDetailReducer';
7
7
  import { fetchEntityRecommendationsForLineUpdate } from './recommendationReducer';
8
- export type ActionType = ReturnType<typeof fetchEntityRecommendationsForLineUpdate> | ReturnType<typeof markCategoryClassRecommendationsInProgressForCategorization> | ReturnType<typeof markCategoryClassRecommendationsCompletedForCategorization> | ReturnType<typeof markCategoryClassRecommendationsFailureForCategorization> | ReturnType<typeof markCategoryClassRecommendationsInProgressForTransactionDetail> | ReturnType<typeof markCategoryClassRecommendationsCompletedForTransactionDetail> | ReturnType<typeof markCategoryClassRecommendationsFailureForTransactionDetail> | ReturnType<typeof setEntityRecommendationForLineIdsForCategorization> | ReturnType<typeof setEntityRecommendationForLineIdsForTransactionDetail>;
8
+ export type ActionType = ReturnType<typeof fetchEntityRecommendationsForLineUpdate> | ReturnType<typeof markCategoryClassRecommendationsInProgressForCategorization> | ReturnType<typeof markCategoryClassRecommendationsCompletedForCategorization> | ReturnType<typeof markCategoryClassRecommendationsFailureForCategorization> | ReturnType<typeof markCategoryClassRecommendationsInProgressForTransactionDetail> | ReturnType<typeof markCategoryClassRecommendationsCompletedForTransactionDetail> | ReturnType<typeof markCategoryClassRecommendationsFailureForTransactionDetail> | ReturnType<typeof setEntityRecommendationForLineIdsForCategorization> | ReturnType<typeof setEntityRecommendationForLineIdsForTransactionDetail> | ReturnType<typeof updateVendorBulkRecommendationsFetchStatus>;
9
9
  export declare const fetchEntityRecommendationsForLineUpdateEpic: (actions$: ActionsObservable<ActionType>, state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<ActionType>;
@@ -41,6 +41,16 @@ const fetchEntityRecommendationsForLineUpdateEpic = (actions$, state$, zeniAPI)
41
41
  const payload = action.payload;
42
42
  const { transactionId, transaction, lineIds } = payload.transactionDetails;
43
43
  const { entity, amount, isFetchCOT, isUncategorizedExpenseCategoryEnabled, } = payload;
44
+ // The vendor "Update all lines" popup on the detail page watches
45
+ // its own transaction-level fetch status, separate from the
46
+ // per-line statuses below — surface completion/failure there too
47
+ // when this fetch was triggered by that bulk action, or the popup
48
+ // hangs until its own 60s timeout regardless of how this request
49
+ // resolves.
50
+ const isBulkDetailFetch = payload.flowType === 'detail' && payload.isBulkFetch === true;
51
+ const emitWithBulkStatus = (primaryAction, bulkAction) => isBulkDetailFetch && bulkAction != null
52
+ ? (0, rxjs_1.of)(primaryAction, bulkAction)
53
+ : (0, rxjs_1.of)(primaryAction);
44
54
  // Synchronously emit the start arm so the per-line skeleton +
45
55
  // row uncheck happen before the HTTP request begins. The
46
56
  // discriminated-union payload narrows `payload.selectedTab` to
@@ -54,9 +64,9 @@ const fetchEntityRecommendationsForLineUpdateEpic = (actions$, state$, zeniAPI)
54
64
  .getJSON(url)
55
65
  .pipe((0, operators_1.mergeMap)((response) => {
56
66
  if (!(0, responsePayload_1.isSuccessResponse)(response) || response.data == null) {
57
- return (0, rxjs_1.of)(payload.flowType === 'categorization'
67
+ return emitWithBulkStatus(payload.flowType === 'categorization'
58
68
  ? (0, transactionsViewReducer_1.markCategoryClassRecommendationsFailureForCategorization)(payload.selectedTab, transactionId, lineIds, response.status)
59
- : (0, transactionDetailReducer_1.markCategoryClassRecommendationsFailureForTransactionDetail)(transactionId, lineIds, response.status));
69
+ : (0, transactionDetailReducer_1.markCategoryClassRecommendationsFailureForTransactionDetail)(transactionId, lineIds, response.status), (0, transactionDetailReducer_1.updateVendorBulkRecommendationsFetchStatus)(transactionId, 'Error', response.status));
60
70
  }
61
71
  const recommendationWithCOTByLineId = (0, recommendationHelper_1.getRecommendationByLineIdWithCOTFromRecommendationPayloadWithCOT)(response.data.recommendations, lineIds);
62
72
  // HTTP 200 with no per-line recommendations: NOT an error.
@@ -64,9 +74,9 @@ const fetchEntityRecommendationsForLineUpdateEpic = (actions$, state$, zeniAPI)
64
74
  // downstream UI can treat this as "we tried, nothing to
65
75
  // apply".
66
76
  if (recommendationWithCOTByLineId == null) {
67
- return (0, rxjs_1.of)(payload.flowType === 'categorization'
77
+ return emitWithBulkStatus(payload.flowType === 'categorization'
68
78
  ? (0, transactionsViewReducer_1.markCategoryClassRecommendationsCompletedForCategorization)(payload.selectedTab, transactionId, lineIds)
69
- : (0, transactionDetailReducer_1.markCategoryClassRecommendationsCompletedForTransactionDetail)(transactionId, lineIds));
79
+ : (0, transactionDetailReducer_1.markCategoryClassRecommendationsCompletedForTransactionDetail)(transactionId, lineIds), (0, transactionDetailReducer_1.updateVendorBulkRecommendationsFetchStatus)(transactionId, 'Completed'));
70
80
  }
71
81
  const transactionWithCOT = isFetchCOT === true
72
82
  ? (0, transactionHelper_1.getTransactionWithCOTFromRecommendations)(transaction, recommendationWithCOTByLineId)
@@ -75,16 +85,16 @@ const fetchEntityRecommendationsForLineUpdateEpic = (actions$, state$, zeniAPI)
75
85
  if (payload.flowType === 'categorization') {
76
86
  return (0, rxjs_1.of)((0, transactionsViewReducer_1.setEntityRecommendationForLineIdsForCategorization)(payload.selectedTab, transactionId, transactionWithCOT, lineIds, entity, amount, recommendationWithCOTByLineId, uncategorizedIncomeExpense, isUncategorizedExpenseCategoryEnabled, (0, tenantSelector_1.getIsAccountingClassesEnabled)(state$.value)));
77
87
  }
78
- return (0, rxjs_1.of)((0, transactionDetailReducer_1.setEntityRecommendationForLineIdsForTransactionDetail)(transactionId, transactionWithCOT, lineIds, entity, amount, recommendationWithCOTByLineId, uncategorizedIncomeExpense));
88
+ return emitWithBulkStatus((0, transactionDetailReducer_1.setEntityRecommendationForLineIdsForTransactionDetail)(transactionId, transactionWithCOT, lineIds, entity, amount, recommendationWithCOTByLineId, uncategorizedIncomeExpense), (0, transactionDetailReducer_1.updateVendorBulkRecommendationsFetchStatus)(transactionId, 'Completed'));
79
89
  }), (0, operators_1.catchError)((error) => {
80
90
  const errorMessage = 'fetchEntityRecommendationsForLineUpdate failed: ' +
81
91
  (error instanceof Error
82
92
  ? error.message
83
93
  : JSON.stringify(error));
84
94
  const status = (0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', errorMessage);
85
- return (0, rxjs_1.of)(payload.flowType === 'categorization'
95
+ return emitWithBulkStatus(payload.flowType === 'categorization'
86
96
  ? (0, transactionsViewReducer_1.markCategoryClassRecommendationsFailureForCategorization)(payload.selectedTab, transactionId, lineIds, status)
87
- : (0, transactionDetailReducer_1.markCategoryClassRecommendationsFailureForTransactionDetail)(transactionId, lineIds, status));
97
+ : (0, transactionDetailReducer_1.markCategoryClassRecommendationsFailureForTransactionDetail)(transactionId, lineIds, status), (0, transactionDetailReducer_1.updateVendorBulkRecommendationsFetchStatus)(transactionId, 'Error', status));
88
98
  }));
89
99
  return (0, rxjs_1.concat)((0, rxjs_1.of)(startAction), httpStream$);
90
100
  }))));
@@ -15,6 +15,7 @@ type FetchEntityRecommendationsForLineUpdateCommonPayload = {
15
15
  transaction: SupportedTransaction;
16
16
  transactionId: TransactionID;
17
17
  };
18
+ isBulkFetch?: boolean;
18
19
  isFetchCOT?: boolean;
19
20
  isUncategorizedExpenseCategoryEnabled?: boolean;
20
21
  };
@@ -111,12 +111,6 @@ const toTransactionUpdatesFromTransactionDetailLocalData = (transactionDetailLoc
111
111
  hasClassChanged =
112
112
  (lineItem.class?.qboId != null || accountLine.class?.qboId != null) &&
113
113
  lineItem.class?.qboId !== accountLine.class?.qboId;
114
- // JE/deposit lines carry vendor/customer per-line rather than at the
115
- // transaction level, so a lone entity pick with no category/class edit
116
- // must still be diffed here or it never reaches entityUpdates below.
117
- const originalLineEntityId = accountLine.vendor?.id ?? accountLine.customer?.id;
118
- const hasEntityChanged = lineItem.lineEntity?.id !== originalLineEntityId &&
119
- Boolean(lineItem.lineEntity?.id);
120
114
  const recommendedAccountBase = (0, exports.toRecommendedAccountBase)(lineItem.amount.amount, lineItem.account);
121
115
  const recommendedClassBase = (0, exports.toRecommendedClassBase)(lineItem.amount.amount, lineItem.class);
122
116
  const latestRecommendedCategory = lineItem.recommendedAccount;
@@ -159,8 +153,7 @@ const toTransactionUpdatesFromTransactionDetailLocalData = (transactionDetailLoc
159
153
  };
160
154
  if (recommended.class != null ||
161
155
  recommended.account != null ||
162
- updatedEntityId != null ||
163
- hasEntityChanged === true) {
156
+ updatedEntityId != null) {
164
157
  const vendorIdToUpdate = transactionDetailLocalData.vendorUpdates?.qboId;
165
158
  const customerIdToUpdate = transactionDetailLocalData.customerUpdates?.qboId;
166
159
  let entityId = entityDetails.entityId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "5.1.51-betaRD11",
3
+ "version": "5.1.52-beta0RD",
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",