@zeniai/client-epic-state 5.0.78 → 5.0.79

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 (190) hide show
  1. package/lib/commonStateTypes/cacheOverrideFetchReducer.d.ts +17 -0
  2. package/lib/commonStateTypes/cacheOverrideFetchReducer.js +20 -0
  3. package/lib/commonStateTypes/filterPrimitives.d.ts +23 -0
  4. package/lib/commonStateTypes/filterPrimitives.js +21 -0
  5. package/lib/commonStateTypes/pusherActions.d.ts +11 -0
  6. package/lib/commonStateTypes/pusherActions.js +6 -0
  7. package/lib/coreEpics.js +1 -1
  8. package/lib/entity/account/accountSelector.d.ts +27 -1
  9. package/lib/entity/account/accountSelector.js +47 -1
  10. package/lib/entity/account/accountState.d.ts +6 -1
  11. package/lib/entity/account/accountState.js +11 -4
  12. package/lib/entity/account/subAccountSelector.d.ts +1 -1
  13. package/lib/entity/aiAccountantCustomer/aiAccountantCustomerPayload.js +1 -3
  14. package/lib/entity/aiAccountantCustomer/aiAccountantCustomerReducer.js +1 -2
  15. package/lib/entity/aiAccountantCustomer/aiAccountantCustomerSelector.d.ts +1 -1
  16. package/lib/entity/chargeCard/chargeCardReducer.d.ts +19 -2
  17. package/lib/entity/chargeCard/chargeCardReducer.js +35 -2
  18. package/lib/entity/chargeCardTransaction/chargeCardTransactionReducer.d.ts +14 -1
  19. package/lib/entity/chargeCardTransaction/chargeCardTransactionReducer.js +36 -2
  20. package/lib/entity/class/classSelector.d.ts +35 -0
  21. package/lib/entity/class/classSelector.js +50 -0
  22. package/lib/entity/sectionProjectView/sectionProjectViewReducer.d.ts +1 -1
  23. package/lib/entity/sectionProjectView/sectionProjectViewSelector.js +1 -1
  24. package/lib/entity/tenant/SessionManager.js +6 -3
  25. package/lib/entity/tenant/clearAllEpic.d.ts +2 -2
  26. package/lib/entity/tenant/clearAllEpic.js +2 -2
  27. package/lib/entity/tenant/epic/saveAPIKeyConnectionEpic.js +6 -2
  28. package/lib/entity/tenant/epic/saveOAuthConnectionEpic.js +6 -2
  29. package/lib/entity/tenant/tenantReducer.js +6 -1
  30. package/lib/entity/transaction/payloadTypes/transactionLinePayload.js +3 -1
  31. package/lib/epic.d.ts +11 -11
  32. package/lib/epic.js +12 -12
  33. package/lib/esm/commonStateTypes/cacheOverrideFetchReducer.js +16 -0
  34. package/lib/esm/commonStateTypes/filterPrimitives.js +20 -0
  35. package/lib/esm/commonStateTypes/pusherActions.js +3 -0
  36. package/lib/esm/coreEpics.js +1 -1
  37. package/lib/esm/entity/account/accountSelector.js +46 -1
  38. package/lib/esm/entity/account/accountState.js +7 -1
  39. package/lib/esm/entity/aiAccountantCustomer/aiAccountantCustomerPayload.js +1 -3
  40. package/lib/esm/entity/aiAccountantCustomer/aiAccountantCustomerReducer.js +1 -2
  41. package/lib/esm/entity/chargeCard/chargeCardReducer.js +34 -1
  42. package/lib/esm/entity/chargeCardTransaction/chargeCardTransactionReducer.js +35 -1
  43. package/lib/esm/entity/class/classSelector.js +48 -0
  44. package/lib/esm/entity/sectionProjectView/sectionProjectViewSelector.js +1 -1
  45. package/lib/esm/entity/tenant/SessionManager.js +4 -1
  46. package/lib/esm/entity/tenant/clearAllEpic.js +2 -2
  47. package/lib/esm/entity/tenant/epic/saveAPIKeyConnectionEpic.js +6 -2
  48. package/lib/esm/entity/tenant/epic/saveOAuthConnectionEpic.js +6 -2
  49. package/lib/esm/entity/tenant/tenantReducer.js +6 -1
  50. package/lib/esm/entity/transaction/payloadTypes/transactionLinePayload.js +3 -1
  51. package/lib/esm/epic.js +12 -12
  52. package/lib/esm/index.js +31 -21
  53. package/lib/esm/reducer.js +2 -2
  54. package/lib/esm/view/aiAccountantView/epics/fetchAiAccountantCustomersEpic.js +2 -5
  55. package/lib/esm/view/aiAccountantView/epics/fetchAiAccountantJobsEpic.js +2 -5
  56. package/lib/esm/view/aiAccountantView/epics/triggerAiAccountantJobEpic.js +4 -1
  57. package/lib/esm/view/companyHealthMetricView/companyHealthMetricViewSelector.js +1 -1
  58. package/lib/esm/view/companyTaskManagerView/companyTaskManagerViewSelector.js +1 -1
  59. package/lib/esm/view/companyTaskManagerView/epics/fetchTaskManagerMetricsEpic.js +1 -1
  60. package/lib/esm/view/companyView/helpers/cockpitHelpers.js +1 -1
  61. package/lib/esm/view/companyView/selector/companyManagementViewSelector.js +1 -1
  62. package/lib/esm/view/companyView/selector/companyPortfolioViewSelector.js +1 -1
  63. package/lib/esm/view/creditAgentView/creditAgentViewReducer.js +1 -16
  64. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic.js +2 -2
  65. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic.js +2 -1
  66. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/restoreBulkUploadAutomatchingOnMountEpic.js +8 -6
  67. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +7 -1
  68. package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +11 -1
  69. package/lib/esm/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.js +21 -1
  70. package/lib/esm/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +2 -1
  71. package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +253 -14
  72. package/lib/esm/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +147 -13
  73. package/lib/esm/view/expenseAutomationView/transactionFilterHelpers.js +258 -0
  74. package/lib/esm/view/expenseAutomationView/types/completedSubTab.js +17 -0
  75. package/lib/esm/view/expenseAutomationView/types/missingReceiptsViewState.js +0 -2
  76. package/lib/esm/view/financeStatement/financeStatementSelector.js +1 -1
  77. package/lib/esm/view/recommendation/fetchEntityRecommendationsForLineUpdateEpic.js +87 -0
  78. package/lib/esm/view/recommendation/recommendationHelper.js +29 -0
  79. package/lib/esm/view/recommendation/recommendationReducer.js +26 -1
  80. package/lib/esm/view/spendManagement/chargeCards/cashbackDetail/cashbackDetailReducer.js +3 -2
  81. package/lib/esm/view/spendManagement/chargeCards/chargeCardConfig/chargeCardConfigReducer.js +3 -2
  82. package/lib/esm/view/spendManagement/chargeCards/chargeCardDetail/chargeCardDetailReducer.js +55 -18
  83. package/lib/esm/view/spendManagement/chargeCards/chargeCardDetail/fetchChargeCardDetailEpic.js +16 -2
  84. package/lib/esm/view/spendManagement/chargeCards/chargeCardDetail/fetchChargeCardDetailPageEpic.js +5 -4
  85. package/lib/esm/view/spendManagement/chargeCards/chargeCardList/chargeCardListReducer.js +26 -9
  86. package/lib/esm/view/spendManagement/chargeCards/chargeCardList/fetchChargeCardListPageEpic.js +9 -6
  87. package/lib/esm/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryReducer.js +3 -2
  88. package/lib/esm/view/spendManagement/chargeCards/chargeCardPaymentHistory/fetchChargeCardPaymentPageEpic.js +1 -1
  89. package/lib/esm/view/spendManagement/chargeCards/chargeCardRepaymentDetail/chargeCardRepaymentDetailReducer.js +7 -3
  90. package/lib/esm/view/spendManagement/commonHistoryView/commonHistoryPayload.js +8 -3
  91. package/lib/esm/view/spendManagement/commonHistoryView/commonHistoryReducer.js +3 -2
  92. package/lib/esm/view/spendManagement/commonHistoryView/fetchEntityHistoryEpic.js +2 -0
  93. package/lib/esm/view/spendManagement/spendManagementFilterHelpers.js +4 -2
  94. package/lib/esm/view/subscriptionView/subscriptionViewSelector.js +1 -1
  95. package/lib/esm/view/taskManager/cannedResponsesView/epics/fetchCannedResponsesEpic.js +1 -1
  96. package/lib/esm/view/taskManager/cannedResponsesView/epics/saveCannedResponseEpic.js +1 -1
  97. package/lib/esm/view/taskManager/taskDetailView/epics/unsnoozeTaskEpic.js +1 -1
  98. package/lib/esm/view/taskManager/taskListView/taskListReducer.js +1 -1
  99. package/lib/esm/view/transactionDetail/transactionDetailReducer.js +121 -6
  100. package/lib/esm/view/transactionDetail/transactionDetailSelector.js +29 -1
  101. package/lib/index.d.ts +31 -24
  102. package/lib/index.js +84 -60
  103. package/lib/reducer.d.ts +2 -2
  104. package/lib/reducer.js +2 -2
  105. package/lib/view/aiAccountantView/aiAccountantViewPayload.d.ts +2 -2
  106. package/lib/view/aiAccountantView/aiAccountantViewReducer.d.ts +1 -1
  107. package/lib/view/aiAccountantView/epics/fetchAiAccountantCustomersEpic.js +2 -5
  108. package/lib/view/aiAccountantView/epics/fetchAiAccountantJobsEpic.js +2 -5
  109. package/lib/view/aiAccountantView/epics/triggerAiAccountantJobEpic.js +4 -1
  110. package/lib/view/companyHealthMetricView/companyHealthMetricViewSelector.js +1 -1
  111. package/lib/view/companyTaskManagerView/companyTaskManagerViewSelector.d.ts +1 -1
  112. package/lib/view/companyTaskManagerView/companyTaskManagerViewSelector.js +1 -1
  113. package/lib/view/companyView/helpers/cockpitHelpers.js +1 -1
  114. package/lib/view/companyView/selector/companyManagementViewSelector.js +1 -1
  115. package/lib/view/companyView/selector/companyPortfolioViewSelector.js +1 -1
  116. package/lib/view/companyView/types/cockpitTypes.d.ts +1 -1
  117. package/lib/view/creditAgentView/creditAgentViewPayload.d.ts +0 -3
  118. package/lib/view/creditAgentView/creditAgentViewReducer.d.ts +1 -1
  119. package/lib/view/creditAgentView/creditAgentViewReducer.js +2 -17
  120. package/lib/view/creditAgentView/creditAgentViewState.d.ts +0 -5
  121. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic.js +2 -2
  122. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic.js +2 -1
  123. package/lib/view/expenseAutomationView/epics/missingReceipts/restoreBulkUploadAutomatchingOnMountEpic.js +8 -6
  124. package/lib/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +7 -1
  125. package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.d.ts +2 -2
  126. package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +10 -0
  127. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +2 -1
  128. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.js +23 -2
  129. package/lib/view/expenseAutomationView/payload/transactionCategorizationPayload.d.ts +21 -0
  130. package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.d.ts +2 -1
  131. package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +2 -1
  132. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.d.ts +28 -5
  133. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +253 -14
  134. package/lib/view/expenseAutomationView/selectorTypes/missingReceiptsSelectorTypes.d.ts +2 -1
  135. package/lib/view/expenseAutomationView/selectorTypes/transactionsViewSelectorTypes.d.ts +16 -0
  136. package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.d.ts +7 -1
  137. package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +150 -13
  138. package/lib/view/expenseAutomationView/transactionFilterHelpers.d.ts +66 -0
  139. package/lib/view/expenseAutomationView/transactionFilterHelpers.js +262 -0
  140. package/lib/view/expenseAutomationView/types/completedSubTab.d.ts +10 -0
  141. package/lib/view/expenseAutomationView/types/completedSubTab.js +21 -0
  142. package/lib/view/expenseAutomationView/types/missingReceiptsViewState.d.ts +1 -2
  143. package/lib/view/expenseAutomationView/types/missingReceiptsViewState.js +1 -4
  144. package/lib/view/expenseAutomationView/types/transactionsViewState.d.ts +44 -2
  145. package/lib/view/financeStatement/financeStatementSelector.js +1 -1
  146. package/lib/view/profitAndLossProjectView/profitAndLossProjectViewSelectorTypes.d.ts +1 -1
  147. package/lib/view/recommendation/fetchEntityRecommendationsForLineUpdateEpic.d.ts +9 -0
  148. package/lib/view/recommendation/fetchEntityRecommendationsForLineUpdateEpic.js +91 -0
  149. package/lib/view/recommendation/recommendationHelper.d.ts +9 -0
  150. package/lib/view/recommendation/recommendationHelper.js +31 -1
  151. package/lib/view/recommendation/recommendationReducer.d.ts +20 -1
  152. package/lib/view/recommendation/recommendationReducer.js +27 -2
  153. package/lib/view/spendManagement/billPay/billList/billListState.d.ts +1 -1
  154. package/lib/view/spendManagement/chargeCards/cashbackDetail/cashbackDetailReducer.d.ts +3 -1
  155. package/lib/view/spendManagement/chargeCards/cashbackDetail/cashbackDetailReducer.js +3 -2
  156. package/lib/view/spendManagement/chargeCards/chargeCardConfig/chargeCardConfigReducer.d.ts +3 -1
  157. package/lib/view/spendManagement/chargeCards/chargeCardConfig/chargeCardConfigReducer.js +3 -2
  158. package/lib/view/spendManagement/chargeCards/chargeCardDetail/chargeCardDetailReducer.js +55 -18
  159. package/lib/view/spendManagement/chargeCards/chargeCardDetail/fetchChargeCardDetailEpic.js +16 -2
  160. package/lib/view/spendManagement/chargeCards/chargeCardDetail/fetchChargeCardDetailPageEpic.js +5 -4
  161. package/lib/view/spendManagement/chargeCards/chargeCardList/chargeCardListReducer.d.ts +18 -3
  162. package/lib/view/spendManagement/chargeCards/chargeCardList/chargeCardListReducer.js +27 -10
  163. package/lib/view/spendManagement/chargeCards/chargeCardList/fetchChargeCardListPageEpic.js +9 -6
  164. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistory.d.ts +1 -1
  165. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryReducer.d.ts +3 -1
  166. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryReducer.js +3 -2
  167. package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/fetchChargeCardPaymentPageEpic.js +1 -1
  168. package/lib/view/spendManagement/chargeCards/chargeCardRepaymentDetail/chargeCardRepaymentDetailReducer.d.ts +3 -1
  169. package/lib/view/spendManagement/chargeCards/chargeCardRepaymentDetail/chargeCardRepaymentDetailReducer.js +7 -3
  170. package/lib/view/spendManagement/commonHistoryView/commonHistoryPayload.d.ts +1 -1
  171. package/lib/view/spendManagement/commonHistoryView/commonHistoryPayload.js +7 -2
  172. package/lib/view/spendManagement/commonHistoryView/commonHistoryReducer.d.ts +1 -0
  173. package/lib/view/spendManagement/commonHistoryView/commonHistoryReducer.js +3 -2
  174. package/lib/view/spendManagement/commonHistoryView/fetchEntityHistoryEpic.js +2 -0
  175. package/lib/view/spendManagement/reimbursement/remiListView/remiListState.d.ts +1 -1
  176. package/lib/view/spendManagement/spendManagementFilterHelpers.d.ts +12 -3
  177. package/lib/view/spendManagement/spendManagementFilterHelpers.js +4 -2
  178. package/lib/view/subscriptionView/subscriptionViewSelector.js +1 -1
  179. package/lib/view/taskManager/taskListView/taskList.d.ts +1 -1
  180. package/lib/view/taskManager/taskListView/taskListReducer.js +1 -1
  181. package/lib/view/transactionDetail/transactionDetailReducer.d.ts +11 -1
  182. package/lib/view/transactionDetail/transactionDetailReducer.js +122 -7
  183. package/lib/view/transactionDetail/transactionDetailSelector.d.ts +2 -0
  184. package/lib/view/transactionDetail/transactionDetailSelector.js +31 -2
  185. package/lib/view/transactionDetail/transactionDetailTypes.d.ts +1 -0
  186. package/lib/view/zeniOAuthView/zeniOAuthSelector.d.ts +1 -1
  187. package/package.json +9 -4
  188. package/lib/esm/view/creditAgentView/epics/fetchCreditAgentAccessEpic.js +0 -13
  189. package/lib/view/creditAgentView/epics/fetchCreditAgentAccessEpic.d.ts +0 -11
  190. package/lib/view/creditAgentView/epics/fetchCreditAgentAccessEpic.js +0 -17
@@ -13,13 +13,24 @@ export const fetchChargeCardDetailEpic = (actions$, state$, zeniAPI) => actions$
13
13
  const chargeCardAction = [];
14
14
  const state = state$.value;
15
15
  const chargeCardDetail = recordGet(state.chargeCardDetailState.detailById, chargeCardId, undefined);
16
+ // Skip if a user-triggered fetch is already in-flight (not applicable
17
+ // to cacheOverride/Pusher refreshes, which always proceed).
16
18
  if (cacheOverride === true ||
17
19
  chargeCardDetail == null ||
18
20
  chargeCardDetail.fetchState !== 'In-Progress') {
21
+ // Silent refresh: preserve fetchState to avoid skeleton flash.
22
+ // Discriminate via fetchState (not null-check) because the reducer
23
+ // always creates an entry before the epic runs.
24
+ const isSilentRefresh = cacheOverride === true &&
25
+ chargeCardDetail != null &&
26
+ chargeCardDetail.fetchState !== 'Not-Started';
19
27
  chargeCardAction.push(updateChargeCardDetailFetchState({
20
28
  chargeCardId,
21
- isRefreshingViewInBackground: cacheOverride,
22
- fetchState: 'In-Progress',
29
+ isRefreshingViewInBackground: isSilentRefresh,
30
+ fetchState: isSilentRefresh
31
+ ? chargeCardDetail.fetchState
32
+ : 'In-Progress',
33
+ error: isSilentRefresh ? chargeCardDetail.error : undefined,
23
34
  }));
24
35
  const query = {
25
36
  is_include_card_activation_events: true,
@@ -37,6 +48,8 @@ export const fetchChargeCardDetailEpic = (actions$, state$, zeniAPI) => actions$
37
48
  }));
38
49
  }
39
50
  else {
51
+ // Surface errors even on silent refresh so the user
52
+ // knows data is stale (follow-up: consider toast instead).
40
53
  return of(updateChargeCardDetailFetchState({
41
54
  chargeCardId,
42
55
  fetchState: 'Error',
@@ -45,6 +58,7 @@ export const fetchChargeCardDetailEpic = (actions$, state$, zeniAPI) => actions$
45
58
  }));
46
59
  }
47
60
  }), catchError((error) => {
61
+ // Surface network failures even on silent refresh.
48
62
  return of(updateChargeCardDetailFetchState({
49
63
  chargeCardId,
50
64
  fetchState: 'Error',
@@ -15,15 +15,16 @@ export const fetchChargeCardDetailPageEpic = (actions$, state$) => actions$.pipe
15
15
  ? exsitingChargeCardDetail.transactionList.uiState.pageTokenType
16
16
  : null;
17
17
  if (cacheOverride === true) {
18
+ // Forward cacheOverride to all sub-fetches to prevent skeleton flash.
18
19
  chargeCardTransactionListAction.push(fetchChargeCardDetail(chargeCardId, cacheOverride));
19
20
  //latest transactions need to be fetched hence removing pageToken and pageTokenType
20
- chargeCardTransactionListAction.push(fetchChargeCardTransactionList(chargeCardId, null, null));
21
- chargeCardTransactionListAction.push(fetchChargeCardConfig());
21
+ chargeCardTransactionListAction.push(fetchChargeCardTransactionList(chargeCardId, null, null, true));
22
+ chargeCardTransactionListAction.push(fetchChargeCardConfig(true));
22
23
  if (doFetchCreditAccounts && isCreditCardEnabled) {
23
- chargeCardTransactionListAction.push(fetchCreditAccount());
24
+ chargeCardTransactionListAction.push(fetchCreditAccount(true));
24
25
  }
25
26
  if (doFetchCreditAccounts && isCreditCardEnabled) {
26
- chargeCardTransactionListAction.push(fetchCreditAccountRepayment());
27
+ chargeCardTransactionListAction.push(fetchCreditAccountRepayment(true));
27
28
  }
28
29
  }
29
30
  else {
@@ -1,5 +1,6 @@
1
1
  import { createSlice } from '@reduxjs/toolkit';
2
2
  import { toAmount } from '../../../../commonStateTypes/amount';
3
+ import { createCacheOverrideFetchReducer } from '../../../../commonStateTypes/cacheOverrideFetchReducer';
3
4
  import { date } from '../../../../zeniDayJS';
4
5
  import { initialCreditAcc, initialCreditAccountRepayment, initialDebitCardSummaries, initialResendRevokeCardInvite, toCreditAccountStatusType, } from './chargeCardList';
5
6
  export const initialState = {
@@ -43,6 +44,10 @@ export const initialState = {
43
44
  revokeCardInvite: initialResendRevokeCardInvite,
44
45
  creditAccountRepayment: initialCreditAccountRepayment,
45
46
  };
47
+ // Silent-Pusher-refresh contract: when cacheOverride=true, fetch reducers
48
+ // leave fetchState and error unchanged so the list page's reduceFetchState
49
+ // selector stays 'Completed' and doesn't flash ChargeCardListLoading.
50
+ // See createCacheOverrideFetchReducer for details.
46
51
  const chargeCardList = createSlice({
47
52
  name: 'chargeCardList',
48
53
  initialState,
@@ -62,10 +67,10 @@ const chargeCardList = createSlice({
62
67
  };
63
68
  },
64
69
  },
65
- fetchCreditAccountRepayment(draft) {
70
+ fetchCreditAccountRepayment: createCacheOverrideFetchReducer((draft) => {
66
71
  draft.creditAccountRepayment.fetchState = 'In-Progress';
67
72
  draft.creditAccountRepayment.error = undefined;
68
- },
73
+ }),
69
74
  updateCreditAccountRepaymentAmountAndDate(draft, action) {
70
75
  draft.creditAccountRepayment.initiatedRepayments = action.payload.amount;
71
76
  draft.creditAccountRepayment.initiatedRepaymentDate = action.payload.date;
@@ -83,10 +88,10 @@ const chargeCardList = createSlice({
83
88
  updateRowActionCardId(draft, action) {
84
89
  draft.rowActionCardId = action.payload;
85
90
  },
86
- fetchCreditAccount(draft) {
91
+ fetchCreditAccount: createCacheOverrideFetchReducer((draft) => {
87
92
  draft.creditAccount.fetchState = 'In-Progress';
88
93
  draft.creditAccount.error = undefined;
89
- },
94
+ }),
90
95
  updateCreditAccount(draft, action) {
91
96
  if (action.payload.length > 0) {
92
97
  draft.creditAccount = toCreditAccount(action.payload[0]);
@@ -99,10 +104,10 @@ const chargeCardList = createSlice({
99
104
  error: action.payload,
100
105
  };
101
106
  },
102
- fetchDebitCardSummary(draft) {
107
+ fetchDebitCardSummary: createCacheOverrideFetchReducer((draft) => {
103
108
  draft.debitCardSummaries.fetchState = 'In-Progress';
104
109
  draft.debitCardSummaries.error = undefined;
105
- },
110
+ }),
106
111
  updateDebitCardSummary(draft, action) {
107
112
  draft.debitCardSummaries = toDebitCardSummaries(action.payload);
108
113
  },
@@ -113,10 +118,10 @@ const chargeCardList = createSlice({
113
118
  error: action.payload,
114
119
  };
115
120
  },
116
- fetchChargeCardList(draft) {
121
+ fetchChargeCardList: createCacheOverrideFetchReducer((draft) => {
117
122
  draft.fetchState = 'In-Progress';
118
123
  draft.error = undefined;
119
- },
124
+ }),
120
125
  updateChargeCardList(draft, action) {
121
126
  const { cards, unassigned_credit } = action.payload.chargeCardListPayload;
122
127
  const newCard = cards.map((card) => card.card_id);
@@ -152,6 +157,18 @@ const chargeCardList = createSlice({
152
157
  draft.uiState.scrollYOffset = action.payload.uiState.scrollYOffset;
153
158
  }
154
159
  },
160
+ updateCreditAccountBalanceFromPusher(draft, action) {
161
+ const { currencyCode, currencySymbol } = action.payload;
162
+ const amt = (v) => v !== undefined ? toAmount(v, currencyCode, currencySymbol) : undefined;
163
+ draft.creditAccount.available =
164
+ amt(action.payload.available) ?? draft.creditAccount.available;
165
+ draft.creditAccount.balance =
166
+ amt(action.payload.balance) ?? draft.creditAccount.balance;
167
+ draft.creditAccount.hold =
168
+ amt(action.payload.hold) ?? draft.creditAccount.hold;
169
+ draft.creditAccount.cashback =
170
+ amt(action.payload.cashback) ?? draft.creditAccount.cashback;
171
+ },
155
172
  clearChargeCardList(draft) {
156
173
  Object.assign(draft, initialState);
157
174
  },
@@ -307,7 +324,7 @@ const chargeCardList = createSlice({
307
324
  },
308
325
  },
309
326
  });
310
- export const { fetchChargeCardListPage, fetchCreditAccountRepayment, updateCreditAccountRepayment, updateCreditAccountRepaymentFailure, updateCreditAccountRepaymentAmountAndDate, updateRowActionCardId, fetchCreditAccount, updateCreditAccount, updateCreditAccountFailure, fetchDebitCardSummary, updateDebitCardSummary, updateDebitCardSummaryFailure, fetchChargeCardList, updateChargeCardList, updateChargeCardListFailure, updateSearchText, updateBulkActionCardIds, updateChargeCardListUIState, clearChargeCardList, resendCardInvite, resendCardInviteSuccess, resendCardInviteFailure, revokeCardInvite, revokeCardInviteSuccess, revokeCardInviteFailure, updateChargeCardsSpendLimit, updateChargeCardsSpendLimitSuccessOrFailure, lockChargeCards, lockChargeCardsSuccessOrFailure, unlockChargeCards, unlockChargeCardsSuccessOrFailure, closeChargeCards, closeChargeCardsSuccessOrFailure, revokeChargeCardsInvite, revokeChargeCardsInviteSuccessOrFailure, } = chargeCardList.actions;
327
+ export const { fetchChargeCardListPage, fetchCreditAccountRepayment, updateCreditAccountRepayment, updateCreditAccountRepaymentFailure, updateCreditAccountRepaymentAmountAndDate, updateRowActionCardId, fetchCreditAccount, updateCreditAccount, updateCreditAccountFailure, fetchDebitCardSummary, updateDebitCardSummary, updateDebitCardSummaryFailure, fetchChargeCardList, updateChargeCardList, updateChargeCardListFailure, updateSearchText, updateBulkActionCardIds, updateChargeCardListUIState, updateCreditAccountBalanceFromPusher, clearChargeCardList, resendCardInvite, resendCardInviteSuccess, resendCardInviteFailure, revokeCardInvite, revokeCardInviteSuccess, revokeCardInviteFailure, updateChargeCardsSpendLimit, updateChargeCardsSpendLimitSuccessOrFailure, lockChargeCards, lockChargeCardsSuccessOrFailure, unlockChargeCards, unlockChargeCardsSuccessOrFailure, closeChargeCards, closeChargeCardsSuccessOrFailure, revokeChargeCardsInvite, revokeChargeCardsInviteSuccessOrFailure, } = chargeCardList.actions;
311
328
  export default chargeCardList.reducer;
312
329
  // helper
313
330
  const toCreditAccount = (payload) => ({
@@ -14,18 +14,21 @@ export const fetchChargeCardListPageEpic = (actions$, state$) => actions$.pipe(f
14
14
  const state = state$.value;
15
15
  const cashbackDetailState = state.cashbackDetailState;
16
16
  if (cacheOverride === true) {
17
+ // Forward cacheOverride to all sub-fetches to prevent skeleton flash.
17
18
  if (doFetchCreditAccountsDebitSummaryAndCashback === true) {
18
19
  if (isCreditCardEnabled) {
19
- chargeCardListPageAction.push(fetchCreditAccount());
20
- chargeCardListPageAction.push(fetchCreditAccountRepayment());
20
+ chargeCardListPageAction.push(fetchCreditAccount(true));
21
+ chargeCardListPageAction.push(fetchCreditAccountRepayment(true));
21
22
  }
22
23
  if (isDebitCardEnabled) {
23
- chargeCardListPageAction.push(fetchDebitCardSummary());
24
+ chargeCardListPageAction.push(fetchDebitCardSummary(true));
24
25
  }
25
- chargeCardListPageAction.push(fetchCashbackDetail());
26
+ chargeCardListPageAction.push(fetchCashbackDetail(true));
26
27
  }
27
- chargeCardListPageAction.push(fetchChargeCardList());
28
- chargeCardListPageAction.push(fetchChargeCardConfig());
28
+ chargeCardListPageAction.push(fetchChargeCardList(true));
29
+ // Config participates in the list page's reduceFetchState — needs cacheOverride.
30
+ chargeCardListPageAction.push(fetchChargeCardConfig(true));
31
+ // These don't participate in the list page's reduceFetchState.
29
32
  chargeCardListPageAction.push(fetchUserListByType('cardHolderCandidate', true));
30
33
  chargeCardListPageAction.push(fetchUserListByType('cardAdmin', true));
31
34
  chargeCardListPageAction.push(fetchClassList());
@@ -1,4 +1,5 @@
1
1
  import { createSlice } from '@reduxjs/toolkit';
2
+ import { createCacheOverrideFetchReducer } from '../../../../commonStateTypes/cacheOverrideFetchReducer';
2
3
  import { initialChargeCardPaymentHistoryState, } from './chargeCardPaymentHistory';
3
4
  export const initialState = initialChargeCardPaymentHistoryState;
4
5
  const chargeCardPaymentHistory = createSlice({
@@ -17,10 +18,10 @@ const chargeCardPaymentHistory = createSlice({
17
18
  };
18
19
  },
19
20
  },
20
- fetchChargeCardPaymentHistory(draft) {
21
+ fetchChargeCardPaymentHistory: createCacheOverrideFetchReducer((draft) => {
21
22
  draft.fetchState = 'In-Progress';
22
23
  draft.error = undefined;
23
- },
24
+ }),
24
25
  updateChargeCardPaymentHistory(draft, action) {
25
26
  const payload = action.payload;
26
27
  draft.creditAccountId = payload.credit_account_id;
@@ -8,7 +8,7 @@ export const fetchChargeCardPaymentPageEpic = (actions$, state$) => actions$.pip
8
8
  const state = state$.value;
9
9
  const chargeCardPaymentHistoryState = state.chargeCardPaymentHistoryState;
10
10
  if (cacheOverride === true) {
11
- chargeCardPaymentPageActions.push(fetchChargeCardPaymentHistory());
11
+ chargeCardPaymentPageActions.push(fetchChargeCardPaymentHistory(true));
12
12
  }
13
13
  else {
14
14
  if (chargeCardPaymentHistoryState.fetchState !== 'Completed' &&
@@ -1,4 +1,5 @@
1
1
  import { createSlice } from '@reduxjs/toolkit';
2
+ import { createCacheOverrideFetchReducer } from '../../../../commonStateTypes/cacheOverrideFetchReducer';
2
3
  export const initialState = {
3
4
  localData: {
4
5
  amount: {
@@ -17,9 +18,12 @@ const chargeCardRepaymentDetail = createSlice({
17
18
  name: 'chargeCardRepaymentDetail',
18
19
  initialState,
19
20
  reducers: {
20
- fetchChargeCardRepaymentDetail() {
21
- //do nothing
22
- },
21
+ // No-op reducer — fetchState is managed by the Pay flow reducers.
22
+ // Uses createCacheOverrideFetchReducer for API consistency with the
23
+ // silent-refresh family (cacheOverride flag at dispatch site).
24
+ fetchChargeCardRepaymentDetail: createCacheOverrideFetchReducer(() => {
25
+ // do nothing
26
+ }),
23
27
  updateChargeCardRepaymentLocalStore(draft, action) {
24
28
  draft.localData = action.payload;
25
29
  },
@@ -1,9 +1,14 @@
1
- import { dateLocal } from '../../../zeniDayJS';
1
+ import { dateInUTC, dateLocal } from '../../../zeniDayJS';
2
2
  import { toHistoricEventUpdateDisplayType, toHistoricEventUpdateType, } from './commonHistory';
3
- export const toHistoricEvent = (payload) => ({
3
+ export const toHistoricEvent = (payload, keepTimeInUTC = false) => ({
4
4
  eventText: payload.event_text,
5
5
  eventSubText: payload.event_sub_text ?? [],
6
- timestamp: payload.timestamp != null ? dateLocal(payload.timestamp) : undefined,
6
+ // Bills: parse as UTC (dateInUTC). Other entities: convert to local (dateLocal).
7
+ timestamp: payload.timestamp != null
8
+ ? keepTimeInUTC
9
+ ? dateInUTC(payload.timestamp)
10
+ : dateLocal(payload.timestamp)
11
+ : undefined,
7
12
  updates: payload.updates.map((eventUpdatePayload) => toHistoricEventUpdate(eventUpdatePayload)),
8
13
  paymentTraceId: payload.metadata?.payment_trace_id ?? null,
9
14
  });
@@ -29,9 +29,10 @@ const commonHistory = createSlice({
29
29
  },
30
30
  },
31
31
  updateCommonHistory(draft, action) {
32
- const { entityId, entityType, historicEventsPayload } = action.payload;
32
+ const { entityId, entityType, historicEventsPayload, keepTimeInUTC } = action.payload;
33
33
  const key = getEntityHistoryDetailKey(entityId, entityType);
34
- const historicEvents = historicEventsPayload.map((historicEventPayload) => toHistoricEvent(historicEventPayload));
34
+ // Bills: keepTimeInUTC true → dateInUTC. Other entities: undefined → dateLocal.
35
+ const historicEvents = historicEventsPayload.map((historicEventPayload) => toHistoricEvent(historicEventPayload, keepTimeInUTC));
35
36
  draft.historyById[key] = {
36
37
  ...initialCommonHistory,
37
38
  fetchState: 'Completed',
@@ -27,6 +27,8 @@ export const fetchEntityHistoryEpic = (actions$, state$, zeniAPI) => actions$.pi
27
27
  entityId,
28
28
  entityType,
29
29
  historicEventsPayload: response.data.activities,
30
+ // Bills: parse timestamps as UTC. Other entities: convert to local.
31
+ keepTimeInUTC: entityType === 'bill_pay',
30
32
  }));
31
33
  }
32
34
  else {
@@ -8,6 +8,8 @@ import { getActualPaymentDate } from './helpers';
8
8
  import { getApproversOrRejectors as getApproversOrRejectorsForReimbursement, } from './reimbursement/remiListView/remiListSelector';
9
9
  import { ALL_REMI_TABS, getRemiListKey, } from './reimbursement/remiListView/remiListState';
10
10
  //filters items against filter values representing a range. ex: date (from-to), amount(1k<-->5k)
11
+ // Spend Management uses only equal/not_equal — TC amount operators
12
+ // (inBetween, lessThan, greaterThan) live in the TC filter pipeline.
11
13
  const filterItemOnRangeCategoryType = (valueForItem, currentFilter) => {
12
14
  let doesItemFallInValueRange = false;
13
15
  const filterField = currentFilter.field;
@@ -41,7 +43,7 @@ const filterItemOnCategoriesWithSingleValue = (categoryValue, currentFilter, ran
41
43
  return filterItemOnRangeCategoryType(categoryValue, currentFilter);
42
44
  }
43
45
  // if filter category is not rangeType
44
- if (currentFilter.matchingOperator === 'not-equal') {
46
+ if (currentFilter.matchingOperator === 'not_equal') {
45
47
  return currentFilter.values.indexOf(categoryValue.toString()) === -1;
46
48
  }
47
49
  return currentFilter.values.indexOf(categoryValue.toString()) > -1;
@@ -115,7 +117,7 @@ export const applyAdvancedFiltersOnList = (entity, allItems, filters, filteredIt
115
117
  ? getCategoryValueForTaskList(key, item)
116
118
  : getCategoryValueForPaymentHistory(key, item);
117
119
  if (categoryValue == null) {
118
- if (currentFilter.matchingOperator === 'not-equal') {
120
+ if (currentFilter.matchingOperator === 'not_equal') {
119
121
  return true;
120
122
  }
121
123
  return false;
@@ -208,7 +208,7 @@ const applyAdvancedFiltersOnCompaniesList = (allCompanies, filters, filteredComp
208
208
  const key = currentFilter.field;
209
209
  const categoryValue = getCategoryValueForCompany(key, company);
210
210
  if (categoryValue == null) {
211
- if (currentFilter.matchingOperator === 'not-equal') {
211
+ if (currentFilter.matchingOperator === 'not_equal') {
212
212
  return true;
213
213
  }
214
214
  return false;
@@ -1,5 +1,5 @@
1
1
  import { of } from 'rxjs';
2
- import { catchError, filter, switchMap, mergeMap } from 'rxjs/operators';
2
+ import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
3
3
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
4
4
  import { mapCannedResponsePayloadToCannedResponse, } from '../cannedResponsesPayload';
5
5
  import { fetchCannedResponses, updateCannedResponses, updateCannedResponsesFetchStatus, } from '../cannedResponsesReducer';
@@ -1,5 +1,5 @@
1
1
  import { of } from 'rxjs';
2
- import { catchError, filter, switchMap, mergeMap } from 'rxjs/operators';
2
+ import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
3
3
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
4
4
  import { fetchCannedResponses, saveCannedResponse, saveCannedResponseFailure, saveCannedResponseSuccess, } from '../cannedResponsesReducer';
5
5
  export const saveCannedResponseEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(saveCannedResponse.match), switchMap((action) => {
@@ -4,7 +4,7 @@ import { openSnackbar } from '../../../../entity/snackbar/snackbarReducer';
4
4
  import { updateTasks } from '../../../../entity/task/taskReducer';
5
5
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
6
6
  import { fetchTaskList, removeTaskFromList, } from '../../taskListView/taskListReducer';
7
- import { snoozeTaskSuccessOrFailure, unsnoozeTask, } from '../taskDetailReducer';
7
+ import { snoozeTaskSuccessOrFailure, unsnoozeTask } from '../taskDetailReducer';
8
8
  export const unsnoozeTaskEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(unsnoozeTask.match), mergeMap((action) => handleUnsnoozeTask(action.payload.taskId, zeniAPI)));
9
9
  const handleUnsnoozeTask = (taskId, zeniAPI) => {
10
10
  const unsnoozeTaskApi$ = zeniAPI.deleteAndGetJSON(`${zeniAPI.apiEndPoints.taskMicroServiceBaseUrl}/1.0/task-manager/tasks/${taskId}/snooze`);
@@ -66,7 +66,7 @@ export const initialState = {
66
66
  categories: [
67
67
  {
68
68
  field: 'status',
69
- matchingOperator: 'not-equal',
69
+ matchingOperator: 'not_equal',
70
70
  values: ['resolved'],
71
71
  valuesCombinationOperator: 'ANY',
72
72
  },
@@ -238,12 +238,34 @@ const transactionDetail = createSlice({
238
238
  saveTransactionDetailLocalData(draft, action) {
239
239
  const key = getTransactionDetailKey(action.payload.transactionId);
240
240
  const detail = recordGet(draft.transactionDetailById, key, undefined);
241
- if (detail != null) {
242
- draft.transactionDetailById[key] = {
243
- ...detail,
244
- transactionDetailLocalData: action.payload.transactionLocalData,
245
- };
241
+ if (detail == null) {
242
+ return;
246
243
  }
244
+ const incoming = action.payload.transactionLocalData;
245
+ const prevById = detail.transactionDetailLocalData.lineItemById;
246
+ // Per-line `categoryClassRecommendationsFetchState` is owned by the
247
+ // recommendation epic/reducers (markInProgress / markCompleted /
248
+ // markFailure / setEntityRecommendationForLineIdsForTransactionDetail).
249
+ // Callers that rebuild `transactionLocalData` from a UI snapshot will
250
+ // typically have an out-of-date copy of this field (e.g. payee-change
251
+ // captures the snapshot before the epic's start arm runs and would
252
+ // otherwise clobber In-Progress back to undefined). Treat the field as
253
+ // sticky: only let the caller override it when they explicitly set it.
254
+ const mergedLineItemById = {};
255
+ Object.entries(incoming.lineItemById ?? {}).forEach(([lineId, line]) => {
256
+ mergedLineItemById[lineId] = {
257
+ ...line,
258
+ categoryClassRecommendationsFetchState: line.categoryClassRecommendationsFetchState ??
259
+ prevById?.[lineId]?.categoryClassRecommendationsFetchState,
260
+ };
261
+ });
262
+ draft.transactionDetailById[key] = {
263
+ ...detail,
264
+ transactionDetailLocalData: {
265
+ ...incoming,
266
+ lineItemById: mergedLineItemById,
267
+ },
268
+ };
247
269
  },
248
270
  saveTransactionDetail(draft, action) {
249
271
  const key = getTransactionDetailKey(action.payload.transactionId);
@@ -365,6 +387,21 @@ const transactionDetail = createSlice({
365
387
  const detail = recordGet(draft.transactionDetailById, key, undefined);
366
388
  if (detail != null) {
367
389
  const newLocalData = setEntityRecommendationForLineIdInLocalData(transaction, detail.transactionDetailLocalData, action.payload.entity, lineIds, uncategorizedIncomeExpense, recommendationWithCOTByLineId);
390
+ // Release the per-line fetch flag for every line this fetch
391
+ // covered so the inline skeleton resolves and the page-level
392
+ // Save button can re-enable.
393
+ lineIds.forEach((lineId) => {
394
+ const lineItem = newLocalData.lineItemById[lineId];
395
+ if (lineItem != null) {
396
+ newLocalData.lineItemById[lineId] = {
397
+ ...lineItem,
398
+ categoryClassRecommendationsFetchState: {
399
+ fetchState: 'Completed',
400
+ error: undefined,
401
+ },
402
+ };
403
+ }
404
+ });
368
405
  draft.transactionDetailById[key] = {
369
406
  ...detail,
370
407
  transactionDetailLocalData: newLocalData,
@@ -385,6 +422,84 @@ const transactionDetail = createSlice({
385
422
  };
386
423
  },
387
424
  },
425
+ // Start arm of the line-update recommendations fetch for the detail
426
+ // page. Flips covered lines to In-Progress; detail page has no
427
+ // checkbox, so no auto-uncheck side effect.
428
+ markCategoryClassRecommendationsInProgressForTransactionDetail: {
429
+ reducer(draft, action) {
430
+ const { transactionId, lineIds } = action.payload;
431
+ const key = getTransactionDetailKey(transactionId);
432
+ const detail = recordGet(draft.transactionDetailById, key, undefined);
433
+ if (detail != null) {
434
+ lineIds.forEach((lineId) => {
435
+ const lineItem = detail.transactionDetailLocalData.lineItemById[lineId];
436
+ if (lineItem != null) {
437
+ detail.transactionDetailLocalData.lineItemById[lineId] = {
438
+ ...lineItem,
439
+ categoryClassRecommendationsFetchState: {
440
+ fetchState: 'In-Progress',
441
+ error: undefined,
442
+ },
443
+ };
444
+ }
445
+ });
446
+ }
447
+ },
448
+ prepare(transactionId, lineIds) {
449
+ return { payload: { transactionId, lineIds } };
450
+ },
451
+ },
452
+ // Completed arm. Used when the HTTP call succeeded but the server
453
+ // returned no per-line recommendations — still need to release the
454
+ // In-Progress flag.
455
+ markCategoryClassRecommendationsCompletedForTransactionDetail: {
456
+ reducer(draft, action) {
457
+ const { transactionId, lineIds } = action.payload;
458
+ const key = getTransactionDetailKey(transactionId);
459
+ const detail = recordGet(draft.transactionDetailById, key, undefined);
460
+ if (detail != null) {
461
+ lineIds.forEach((lineId) => {
462
+ const lineItem = detail.transactionDetailLocalData.lineItemById[lineId];
463
+ if (lineItem != null) {
464
+ detail.transactionDetailLocalData.lineItemById[lineId] = {
465
+ ...lineItem,
466
+ categoryClassRecommendationsFetchState: {
467
+ fetchState: 'Completed',
468
+ error: undefined,
469
+ },
470
+ };
471
+ }
472
+ });
473
+ }
474
+ },
475
+ prepare(transactionId, lineIds) {
476
+ return { payload: { transactionId, lineIds } };
477
+ },
478
+ },
479
+ markCategoryClassRecommendationsFailureForTransactionDetail: {
480
+ reducer(draft, action) {
481
+ const { transactionId, lineIds, error } = action.payload;
482
+ const key = getTransactionDetailKey(transactionId);
483
+ const detail = recordGet(draft.transactionDetailById, key, undefined);
484
+ if (detail != null) {
485
+ lineIds.forEach((lineId) => {
486
+ const lineItem = detail.transactionDetailLocalData.lineItemById[lineId];
487
+ if (lineItem != null) {
488
+ detail.transactionDetailLocalData.lineItemById[lineId] = {
489
+ ...lineItem,
490
+ categoryClassRecommendationsFetchState: {
491
+ fetchState: 'Error',
492
+ error,
493
+ },
494
+ };
495
+ }
496
+ });
497
+ }
498
+ },
499
+ prepare(transactionId, lineIds, error) {
500
+ return { payload: { transactionId, lineIds, error } };
501
+ },
502
+ },
388
503
  removeEntityRecommendationForLineIdForTransactionDetail: {
389
504
  reducer(draft, action) {
390
505
  const { transaction, lineId } = action.payload;
@@ -656,7 +771,7 @@ const transactionDetail = createSlice({
656
771
  },
657
772
  },
658
773
  });
659
- export const { downloadAccountingProviderAttachment, downloadAccountingProviderAttachmentSuccess, downloadAccountingProviderAttachmentFailure, fetchTransactionDetail, updateTransactionDetailFetchState, removeTransactionDetail, clearTransactionDetail, uploadMissingAttachmentSuccess, updateTransactionDetail, updateTransactionDetailSaveStatus, clearTransactionDetailSaveStatus, markTransactionAsNotMiscategorized, deleteTransactionAttachment, deleteTransactionAttachmentFailure, deleteTransactionAttachmentSuccess, updateStatusForTransactionNotMiscategorizedUpdate, updateStatusConsolidatedSaveTransactionDetail, updateTransactionStateIfUpdateActionNotAllowed, saveTransactionDetail, initializeTransactionDetailLocalData, saveTransactionDetailLocalData, clearTransactionDetailLocalData, resetLineItemsCategoryClassInLocalData, setAllLineItemsToCategoryClassInLocalData, setAllLineItemsLocalDataIsUpdateAllChecked, setEntityRecommendationForLineIdsForTransactionDetail, removeEntityRecommendationForLineIdForTransactionDetail, updateSelectedVendor, updateSelectedCustomer, resetVendor, resetSelectedCustomer, updateLocalDataWithTransactionsUpdateWithVendorCheckbox, updateVendorToAllLineItems, resetAllLineItemsToVendor, updateLatestSelectedEntity, updateVendorBulkRecommendationsFetchStatus, setIsVendorUpdateAllChecked, } = transactionDetail.actions;
774
+ export const { downloadAccountingProviderAttachment, downloadAccountingProviderAttachmentSuccess, downloadAccountingProviderAttachmentFailure, fetchTransactionDetail, updateTransactionDetailFetchState, removeTransactionDetail, clearTransactionDetail, uploadMissingAttachmentSuccess, updateTransactionDetail, updateTransactionDetailSaveStatus, clearTransactionDetailSaveStatus, markTransactionAsNotMiscategorized, deleteTransactionAttachment, deleteTransactionAttachmentFailure, deleteTransactionAttachmentSuccess, updateStatusForTransactionNotMiscategorizedUpdate, updateStatusConsolidatedSaveTransactionDetail, updateTransactionStateIfUpdateActionNotAllowed, saveTransactionDetail, initializeTransactionDetailLocalData, saveTransactionDetailLocalData, clearTransactionDetailLocalData, resetLineItemsCategoryClassInLocalData, setAllLineItemsToCategoryClassInLocalData, setAllLineItemsLocalDataIsUpdateAllChecked, setEntityRecommendationForLineIdsForTransactionDetail, markCategoryClassRecommendationsInProgressForTransactionDetail, markCategoryClassRecommendationsCompletedForTransactionDetail, markCategoryClassRecommendationsFailureForTransactionDetail, removeEntityRecommendationForLineIdForTransactionDetail, updateSelectedVendor, updateSelectedCustomer, resetVendor, resetSelectedCustomer, updateLocalDataWithTransactionsUpdateWithVendorCheckbox, updateVendorToAllLineItems, resetAllLineItemsToVendor, updateLatestSelectedEntity, updateVendorBulkRecommendationsFetchStatus, setIsVendorUpdateAllChecked, } = transactionDetail.actions;
660
775
  export default transactionDetail.reducer;
661
776
  function getUpdatedEntityLocalData(detail, updates, entity, resetRecommendations, type) {
662
777
  const key = type === 'customer' ? 'customerUpdates' : 'vendorUpdates';
@@ -10,6 +10,26 @@ import { getAccountList, getNestedAccountListHierarchy, } from '../accountList/a
10
10
  import { getClassList, getNestedClassListHierarchy, } from '../classList/classListSelector';
11
11
  import { getProjectList, } from '../projectList/projectListSelector';
12
12
  import { getAllLinkedTransactions, getTransactionDetailKey, initialSupportedTransactionDetail, } from './transactionDetailState';
13
+ // "Any line on this transaction is fetching category/class recommendations?"
14
+ // Inlined as a plain function because the upstream selector
15
+ // `getTransactionDetail` is also un-memoized and the iteration is O(lines)
16
+ // — typically <10. Exported standalone so consumers that only need the
17
+ // flag (e.g. footer save-disable) can avoid materializing the whole
18
+ // detail report.
19
+ export const getDetailInFlightRecommendations = (state, transactionId) => {
20
+ const detailKey = getTransactionDetailKey(transactionId);
21
+ const detail = recordGet(state.transactionDetailState.transactionDetailById, detailKey, undefined);
22
+ if (detail == null) {
23
+ return false;
24
+ }
25
+ for (const lineItem of Object.values(detail.transactionDetailLocalData.lineItemById)) {
26
+ if (lineItem.categoryClassRecommendationsFetchState?.fetchState ===
27
+ 'In-Progress') {
28
+ return true;
29
+ }
30
+ }
31
+ return false;
32
+ };
13
33
  export const getTransactionDetail = (state, transactionId, fetchLinkedTransactions = true) => {
14
34
  const { transactionDetailState, transactionState, vendorState, vendorListState, accountState, classState, classListState, accountListState, projectState, projectListState, } = state;
15
35
  const detailKey = getTransactionDetailKey(transactionId);
@@ -56,7 +76,13 @@ export const getTransactionDetail = (state, transactionId, fetchLinkedTransactio
56
76
  const isAccountingClassesEnabled = getIsAccountingClassesEnabled(state);
57
77
  const classList = isAccountingClassesEnabled
58
78
  ? getClassList(classState, classListState)
59
- : { classes: [], nestedClassHierarchy: [], fetchState: 'Completed', error: undefined, version: 0 };
79
+ : {
80
+ classes: [],
81
+ nestedClassHierarchy: [],
82
+ fetchState: 'Completed',
83
+ error: undefined,
84
+ version: 0,
85
+ };
60
86
  const isAccountingProjectsEnabled = getIsAccountingProjectsEnabled(state);
61
87
  const projectList = isAccountingProjectsEnabled
62
88
  ? getProjectList(projectState, projectListState)
@@ -72,6 +98,7 @@ export const getTransactionDetail = (state, transactionId, fetchLinkedTransactio
72
98
  : [];
73
99
  const transactionDetailAnyUpdateStatus = reduceUpdateFetchStates(transactionDetail);
74
100
  const vendors = getVendorsByVendorIds(vendorState, vendorListState.vendorIds);
101
+ const hasInFlightCategoryClassRecommendations = getDetailInFlightRecommendations(state, transactionId);
75
102
  return {
76
103
  reportId: 'transaction_detail',
77
104
  reportTitle: 'Transaction Detail',
@@ -87,6 +114,7 @@ export const getTransactionDetail = (state, transactionId, fetchLinkedTransactio
87
114
  classHierarchyList,
88
115
  accountsHierarchyList,
89
116
  classList: classList,
117
+ hasInFlightCategoryClassRecommendations,
90
118
  isAccountingClassesEnabled,
91
119
  isAccountingProjectsEnabled,
92
120
  projectList,