@zeniai/client-epic-state 5.2.40 → 5.2.41-beta1MM

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 (119) hide show
  1. package/lib/entity/actualMatching/actualMatchingPayload.d.ts +31 -0
  2. package/lib/entity/actualMatching/actualMatchingPayload.js +52 -0
  3. package/lib/entity/actualMatching/actualMatchingReducer.d.ts +9 -0
  4. package/lib/entity/actualMatching/actualMatchingReducer.js +33 -0
  5. package/lib/entity/actualMatching/actualMatchingSelector.d.ts +3 -0
  6. package/lib/entity/actualMatching/actualMatchingSelector.js +6 -0
  7. package/lib/entity/actualMatching/actualMatchingState.d.ts +23 -0
  8. package/lib/entity/actualMatching/actualMatchingState.js +2 -0
  9. package/lib/entity/actualMatching/actualMatchingTypes.d.ts +1 -0
  10. package/lib/entity/actualMatching/actualMatchingTypes.js +2 -0
  11. package/lib/entity/file/fileReducer.d.ts +8 -0
  12. package/lib/entity/file/fileReducer.js +10 -2
  13. package/lib/entity/invoicing/invoicingCommonPayload.d.ts +14 -1
  14. package/lib/entity/jeSchedules/jeSchedulesPayload.d.ts +31 -0
  15. package/lib/entity/jeSchedules/jeSchedulesPayload.js +53 -0
  16. package/lib/entity/jeSchedules/jeSchedulesState.d.ts +33 -0
  17. package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
  18. package/lib/entity/snackbar/snackbarTypes.js +2 -0
  19. package/lib/epic.d.ts +8 -1
  20. package/lib/epic.js +14 -0
  21. package/lib/esm/entity/actualMatching/actualMatchingPayload.js +48 -0
  22. package/lib/esm/entity/actualMatching/actualMatchingReducer.js +30 -0
  23. package/lib/esm/entity/actualMatching/actualMatchingSelector.js +3 -0
  24. package/lib/esm/entity/actualMatching/actualMatchingState.js +1 -0
  25. package/lib/esm/entity/actualMatching/actualMatchingTypes.js +1 -0
  26. package/lib/esm/entity/file/fileReducer.js +10 -2
  27. package/lib/esm/entity/jeSchedules/jeSchedulesPayload.js +53 -0
  28. package/lib/esm/entity/snackbar/snackbarTypes.js +2 -0
  29. package/lib/esm/epic.js +14 -0
  30. package/lib/esm/index.js +7 -3
  31. package/lib/esm/reducer.js +6 -0
  32. package/lib/esm/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +25 -12
  33. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingEpic.js +46 -0
  34. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingMatchEpic.js +36 -0
  35. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingPageEpic.js +7 -0
  36. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/matchAndReverseAccrualsEpic.js +108 -0
  37. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/refreshActualMatchingList.js +16 -0
  38. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/searchMatchAccrualsEpic.js +60 -0
  39. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/searchMatchTransactionsEpic.js +74 -0
  40. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/undoReversalAccrualsEpic.js +78 -0
  41. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.js +13 -3
  42. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/prefetchOtherJeSchedulesTabEpic.js +40 -15
  43. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +19 -8
  44. package/lib/esm/view/expenseAutomationView/expenseAutomationViewReducer.js +2 -1
  45. package/lib/esm/view/expenseAutomationView/expenseAutomationViewSelector.js +24 -6
  46. package/lib/esm/view/expenseAutomationView/expenseAutomationViewState.js +4 -2
  47. package/lib/esm/view/expenseAutomationView/payload/actualMatchingPayload.js +68 -0
  48. package/lib/esm/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +462 -0
  49. package/lib/esm/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +2 -1
  50. package/lib/esm/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.js +1 -0
  51. package/lib/esm/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +426 -0
  52. package/lib/esm/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +4 -0
  53. package/lib/esm/view/expenseAutomationView/types/actualMatchingViewState.js +38 -0
  54. package/lib/esm/view/expenseAutomationView/types/jeSchedulesViewState.js +8 -1
  55. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.js +1 -1
  56. package/lib/esm/view/invoicing/settingsView/epics/fetchInvoicingSettingsEpic.js +6 -0
  57. package/lib/esm/view/invoicing/settingsView/epics/saveInvoicingSettingsEpic.js +7 -0
  58. package/lib/esm/view/invoicing/settingsView/invoicingBrandingFormConfig.js +1 -1
  59. package/lib/esm/view/invoicing/settingsView/settingsViewSelector.js +5 -0
  60. package/lib/esm/view/invoicing/settingsView/submitInvoicingBrandingFormEpic.js +9 -0
  61. package/lib/index.d.ts +14 -5
  62. package/lib/index.js +82 -48
  63. package/lib/reducer.d.ts +2 -0
  64. package/lib/reducer.js +6 -0
  65. package/lib/rootStateTypes.d.ts +4 -0
  66. package/lib/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +25 -12
  67. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingEpic.d.ts +9 -0
  68. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingEpic.js +50 -0
  69. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingMatchEpic.d.ts +12 -0
  70. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingMatchEpic.js +40 -0
  71. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingPageEpic.d.ts +5 -0
  72. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingPageEpic.js +11 -0
  73. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/matchAndReverseAccrualsEpic.d.ts +9 -0
  74. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/matchAndReverseAccrualsEpic.js +112 -0
  75. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/refreshActualMatchingList.d.ts +3 -0
  76. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/refreshActualMatchingList.js +19 -0
  77. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/searchMatchAccrualsEpic.d.ts +9 -0
  78. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/searchMatchAccrualsEpic.js +64 -0
  79. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/searchMatchTransactionsEpic.d.ts +8 -0
  80. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/searchMatchTransactionsEpic.js +78 -0
  81. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/undoReversalAccrualsEpic.d.ts +9 -0
  82. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/undoReversalAccrualsEpic.js +82 -0
  83. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.d.ts +2 -1
  84. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.js +13 -3
  85. package/lib/view/expenseAutomationView/epics/jeSchedule/prefetchOtherJeSchedulesTabEpic.d.ts +5 -1
  86. package/lib/view/expenseAutomationView/epics/jeSchedule/prefetchOtherJeSchedulesTabEpic.js +39 -14
  87. package/lib/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +18 -7
  88. package/lib/view/expenseAutomationView/expenseAutomationViewReducer.d.ts +2 -1
  89. package/lib/view/expenseAutomationView/expenseAutomationViewReducer.js +2 -1
  90. package/lib/view/expenseAutomationView/expenseAutomationViewSelector.js +24 -6
  91. package/lib/view/expenseAutomationView/expenseAutomationViewState.d.ts +2 -1
  92. package/lib/view/expenseAutomationView/expenseAutomationViewState.js +6 -3
  93. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
  94. package/lib/view/expenseAutomationView/payload/actualMatchingPayload.d.ts +85 -0
  95. package/lib/view/expenseAutomationView/payload/actualMatchingPayload.js +74 -0
  96. package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.d.ts +79 -0
  97. package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +466 -0
  98. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.d.ts +6 -5
  99. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +2 -1
  100. package/lib/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.d.ts +87 -0
  101. package/lib/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.js +2 -0
  102. package/lib/view/expenseAutomationView/selectorTypes/expenseAutomationViewSelectorTypes.d.ts +11 -0
  103. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.d.ts +38 -0
  104. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +445 -0
  105. package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +4 -0
  106. package/lib/view/expenseAutomationView/types/actualMatchingViewState.d.ts +106 -0
  107. package/lib/view/expenseAutomationView/types/actualMatchingViewState.js +46 -0
  108. package/lib/view/expenseAutomationView/types/jeSchedulesViewState.d.ts +1 -1
  109. package/lib/view/expenseAutomationView/types/jeSchedulesViewState.js +8 -1
  110. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.js +1 -1
  111. package/lib/view/invoicing/settingsView/epics/fetchInvoicingSettingsEpic.js +6 -0
  112. package/lib/view/invoicing/settingsView/epics/saveInvoicingSettingsEpic.js +7 -0
  113. package/lib/view/invoicing/settingsView/epics/settingsViewActionType.d.ts +2 -1
  114. package/lib/view/invoicing/settingsView/invoicingBrandingFormConfig.js +1 -1
  115. package/lib/view/invoicing/settingsView/settingsViewSelector.d.ts +8 -0
  116. package/lib/view/invoicing/settingsView/settingsViewSelector.js +5 -0
  117. package/lib/view/invoicing/settingsView/submitInvoicingBrandingFormEpic.d.ts +2 -1
  118. package/lib/view/invoicing/settingsView/submitInvoicingBrandingFormEpic.js +9 -0
  119. package/package.json +1 -1
@@ -1,23 +1,34 @@
1
1
  import { EMPTY, of } from 'rxjs';
2
2
  import { filter, mergeMap } from 'rxjs/operators';
3
- import { convertToPeriod, toAbsoluteDay, } from '../../../../commonStateTypes/timePeriod';
3
+ import { convertToPeriod, toAbsoluteDay, toMonthYearPeriodId, } from '../../../../commonStateTypes/timePeriod';
4
4
  import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
5
5
  import { fetchJeSchedulesPage, updateJESchedulesUIState, } from '../../reducers/jeSchedulesViewReducer';
6
- export const switchJESchedulesTabEpic = (actions$, state$) => actions$.pipe(filter(updateJESchedulesUIState.match), filter((action) => action.payload.selectedPageTab != null), mergeMap(() => {
6
+ import { getJESchedulePeriodTabKey } from '../../types/jeSchedulesViewState';
7
+ export const switchJESchedulesTabEpic = (actions$, state$) => actions$.pipe(filter(updateJESchedulesUIState.match), filter((action) => action.payload.selectedPageTab != null), mergeMap((action) => {
7
8
  const state = state$.value;
8
- const { fetchState, refreshStatus } = state.expenseAutomationJESchedulesViewState;
9
- if (fetchState === 'In-Progress' ||
10
- refreshStatus.fetchState === 'In-Progress') {
11
- return EMPTY;
12
- }
9
+ const nextTab = action.payload.selectedPageTab;
13
10
  const currentTenant = getCurrentTenant(state);
14
11
  const selectedPeriod = state.expenseAutomationViewState.selectedPeriodByTenantId[currentTenant.tenantId];
15
12
  // convertToPeriod destructures, so an unset period throws and takes this epic's subscription
16
13
  // down for the rest of the session -- every later tab switch would then stop refreshing. A
17
14
  // skipped refresh is the cheaper failure, as in refreshCurrentPage.
18
- if (selectedPeriod == null) {
15
+ if (selectedPeriod == null || nextTab == null) {
19
16
  return EMPTY;
20
17
  }
18
+ const { fetchState, refreshStatus, jeScheduleTransactionKeysByPeriodAndTab } = state.expenseAutomationJESchedulesViewState;
19
+ // Review/Existing share `/1.0/schedules` but not a cache slot. A Review
20
+ // prefetch must not skip Existing when Existing has no keys yet — AM→Existing
21
+ // would sit on a skeleton with no request. Skip only when this tab is warm.
22
+ // AM is a different endpoint — never block it on JE In-Progress.
23
+ if (nextTab !== 'actual_matching') {
24
+ const slotKey = getJESchedulePeriodTabKey(nextTab, toMonthYearPeriodId(selectedPeriod));
25
+ const slotLoaded = jeScheduleTransactionKeysByPeriodAndTab[slotKey] != null;
26
+ if (slotLoaded &&
27
+ (fetchState === 'In-Progress' ||
28
+ refreshStatus.fetchState === 'In-Progress')) {
29
+ return EMPTY;
30
+ }
31
+ }
21
32
  const period = convertToPeriod(selectedPeriod);
22
33
  const timePeriodStart = toAbsoluteDay(period.start);
23
34
  const timePeriodEnd = toAbsoluteDay(period.end);
@@ -19,11 +19,12 @@ const expenseAutomationView = createSlice({
19
19
  reducer() {
20
20
  //do nothing
21
21
  },
22
- prepare(cacheOverride = false, refreshViewInBackground = false) {
22
+ prepare(cacheOverride = false, refreshViewInBackground = false, isAccrualEnabled = false) {
23
23
  return {
24
24
  payload: {
25
25
  cacheOverride,
26
26
  refreshViewInBackground,
27
+ isAccrualEnabled,
27
28
  },
28
29
  };
29
30
  },
@@ -4,6 +4,7 @@ import { getMonthEndCloseChecksForATenantForSelectedPeriod } from '../../entity/
4
4
  import { getCurrentTenant, } from '../../entity/tenant/tenantSelector';
5
5
  import { getCompanyMonthEndReportSelectorView } from '../companyMonthEndReportView/companyMonthEndReportViewSelector';
6
6
  import { getMonthEndCloseChecksViewByTenantId, isMonthEndInsightDisabled, } from '../monthEndCloseChecksView/monthEndCloseChecksViewSelector';
7
+ import { getExpenseAutomationActualMatchingView } from './selectors/actualMatchingViewSelector';
7
8
  import { getExpenseAutomationFluxAnalysisView } from './selectors/fluxAnalysisViewSelector';
8
9
  import { getAllSteps } from './selectors/getAllStepsHelper';
9
10
  import { getExpenseAutomationJESchedulesView } from './selectors/jeSchedulesViewSelector';
@@ -42,6 +43,8 @@ export function getExpenseAutomationView(state) {
42
43
  const reconciliation = getExpenseAutomationReconciliationView(state);
43
44
  const expenseAutomationTransactionsView = getExpenseAutomationTransactionView(state);
44
45
  const jeSchedules = getExpenseAutomationJESchedulesView(state);
46
+ // Top-level sibling of `jeSchedules`: see ExpenseAutomationViewSelector.
47
+ const actualMatching = getExpenseAutomationActualMatchingView(state);
45
48
  switch (currentSelectedView) {
46
49
  case 'reconciliation':
47
50
  currentSelectedViewFetchState = reconciliation.fetchStatus;
@@ -54,12 +57,23 @@ export function getExpenseAutomationView(state) {
54
57
  fluxAnalysis.refreshStatus;
55
58
  break;
56
59
  case 'je_schedules':
57
- currentSelectedViewFetchState = {
58
- fetchState: jeSchedules.fetchState,
59
- error: jeSchedules.error,
60
- };
61
- currentSelectedViewBackgroundRefreshFetchState =
62
- jeSchedules.refreshStatus;
60
+ if (expenseAutomationJESchedulesViewState.uiState.selectedPageTab ===
61
+ 'actual_matching') {
62
+ currentSelectedViewFetchState = {
63
+ fetchState: actualMatching.fetchState,
64
+ error: actualMatching.error,
65
+ };
66
+ currentSelectedViewBackgroundRefreshFetchState =
67
+ actualMatching.refreshStatus;
68
+ }
69
+ else {
70
+ currentSelectedViewFetchState = {
71
+ fetchState: jeSchedules.fetchState,
72
+ error: jeSchedules.error,
73
+ };
74
+ currentSelectedViewBackgroundRefreshFetchState =
75
+ jeSchedules.refreshStatus;
76
+ }
63
77
  break;
64
78
  case 'missing_receipts':
65
79
  currentSelectedViewFetchState = {
@@ -88,6 +102,9 @@ export function getExpenseAutomationView(state) {
88
102
  };
89
103
  break;
90
104
  }
105
+ // AM is optional (flag-off / background prefetch) and must not keep the
106
+ // page-level AND-complete fetchState stuck on Not-Started. Tab chrome uses
107
+ // currentSelectedViewFetchState, which already switches to AM when selected.
91
108
  const fetchStateVariables = [
92
109
  expenseAutomationMissingReceiptsViewState,
93
110
  expenseAutomationJESchedulesViewState,
@@ -176,6 +193,7 @@ export function getExpenseAutomationView(state) {
176
193
  ]);
177
194
  return {
178
195
  jeSchedules,
196
+ actualMatching,
179
197
  transactionView: expenseAutomationTransactionsView,
180
198
  missingReceipts,
181
199
  reconciliation,
@@ -1,5 +1,5 @@
1
- import { stringToUnion } from '../../commonStateTypes/stringToUnion';
2
- export const EXPENSE_AUTOMATION_VIEW_TYPES = [
1
+ import { stringToUnion, stringToUnionStrict, } from '../../commonStateTypes/stringToUnion';
2
+ const EXPENSE_AUTOMATION_VIEW_TYPES = [
3
3
  'transaction_categorization',
4
4
  'je_schedules',
5
5
  'reconciliation',
@@ -8,3 +8,5 @@ export const EXPENSE_AUTOMATION_VIEW_TYPES = [
8
8
  'month_end_insights',
9
9
  ];
10
10
  export const toExpenseAutomationViewType = (v) => stringToUnion(v, EXPENSE_AUTOMATION_VIEW_TYPES);
11
+ /** Unlike `toExpenseAutomationViewType`, unknown strings return `undefined`. */
12
+ export const toExpenseAutomationViewTypeStrict = (v) => stringToUnionStrict(v, EXPENSE_AUTOMATION_VIEW_TYPES);
@@ -0,0 +1,68 @@
1
+ import { toString } from '../../../commonStateTypes/timePeriod';
2
+ import { toMatchAndReverseResultStatus, toUndoReversalResultStatus, } from '../types/actualMatchingViewState';
3
+ export function toServiceMonthFromPeriod(period) {
4
+ return toString(period.start).slice(0, 7);
5
+ }
6
+ function resolveMatchSearchAmount(payload) {
7
+ const raw = payload.total_amount ?? payload.amount;
8
+ if (typeof raw === 'number' && Number.isFinite(raw)) {
9
+ return raw;
10
+ }
11
+ if (typeof raw === 'string' && raw !== '') {
12
+ const parsed = Number(raw);
13
+ if (Number.isFinite(parsed)) {
14
+ return parsed;
15
+ }
16
+ }
17
+ return 0;
18
+ }
19
+ function resolveMatchSearchCurrencySymbol(payload, currencyCode, fallbackCurrency) {
20
+ if (payload.currency_symbol != null && payload.currency_symbol !== '') {
21
+ return payload.currency_symbol;
22
+ }
23
+ if (fallbackCurrency != null &&
24
+ currencyCode === fallbackCurrency.currencyCode) {
25
+ return fallbackCurrency.currencySymbol;
26
+ }
27
+ return currencyCode;
28
+ }
29
+ export function toActualMatchingTransactionSearchResult(payload, fallbackCurrency) {
30
+ const currencyCode = payload.currency_code != null && payload.currency_code !== ''
31
+ ? payload.currency_code
32
+ : fallbackCurrency?.currencyCode ?? '';
33
+ return {
34
+ transactionId: payload.transaction_id,
35
+ transactionIntegrationId: payload.transaction_integration_id,
36
+ transactionType: (payload.transaction_type || '').toLowerCase(),
37
+ lineId: payload.line_id,
38
+ vendorId: payload.vendor_id ?? undefined,
39
+ vendorName: payload.vendor_name ?? '',
40
+ amount: resolveMatchSearchAmount(payload),
41
+ transactionDate: payload.transaction_date,
42
+ accountId: payload.account_id ?? undefined,
43
+ accountName: payload.account_name ?? undefined,
44
+ accountDescription: payload.account_description ?? undefined,
45
+ accountingClassId: payload.accounting_class_id ?? undefined,
46
+ accountingClassName: payload.accounting_class_name ?? undefined,
47
+ currencyCode,
48
+ currencySymbol: resolveMatchSearchCurrencySymbol(payload, currencyCode, fallbackCurrency),
49
+ transactionMemo: payload.transaction_memo ?? undefined,
50
+ transactionDescription: payload.transaction_description ?? undefined,
51
+ transactionTypeName: payload.transaction_type_name ?? undefined,
52
+ syncToken: payload.sync_token == null ? '' : String(payload.sync_token),
53
+ };
54
+ }
55
+ export function toMatchAndReverseResult(payload) {
56
+ return {
57
+ accrualId: payload.accrual_id,
58
+ status: toMatchAndReverseResultStatus(payload.status),
59
+ reason: payload.reason ?? undefined,
60
+ };
61
+ }
62
+ export function toUndoReversalResult(payload) {
63
+ return {
64
+ accrualId: payload.accrual_id,
65
+ status: toUndoReversalResultStatus(payload.status),
66
+ reason: payload.reason ?? undefined,
67
+ };
68
+ }
@@ -0,0 +1,462 @@
1
+ import { createSlice } from '@reduxjs/toolkit';
2
+ import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
3
+ import { generateJEOneTimeAccrualKey } from '../../../entity/jeSchedules/jeScheduleHelper';
4
+ import { createZeniAPIStatus } from '../../../responsePayload';
5
+ import { clearAll } from '../../../rootActions';
6
+ import { actualMatchingTransactionLineKey, isMatchAndReverseCompleteStatus } from '../types/actualMatchingViewState';
7
+ export const initialTransactionSearchState = {
8
+ query: '',
9
+ results: [],
10
+ fetchState: 'Not-Started',
11
+ error: undefined,
12
+ nextPageToken: null,
13
+ isLoadingMore: false,
14
+ };
15
+ export const initialAccrualSearchState = {
16
+ query: '',
17
+ results: [],
18
+ fetchState: 'Not-Started',
19
+ error: undefined,
20
+ nextPageToken: null,
21
+ isLoadingMore: false,
22
+ };
23
+ export const initialMatchModalState = {
24
+ isOpen: false,
25
+ openedFromMatchId: undefined,
26
+ didPinPrefillTransactions: false,
27
+ pinnedTransactionResults: [],
28
+ transactionSearch: initialTransactionSearchState,
29
+ accrualSearch: initialAccrualSearchState,
30
+ confirmStatus: { fetchState: 'Not-Started', error: undefined },
31
+ };
32
+ export const initialState = {
33
+ matchIdsByPeriod: {},
34
+ fetchState: 'Not-Started',
35
+ error: undefined,
36
+ refreshStatus: {
37
+ fetchState: 'Not-Started',
38
+ error: undefined,
39
+ },
40
+ detailFetchStateByMatchId: {},
41
+ detailRefreshStatusByMatchId: {},
42
+ uiState: {
43
+ sortKey: 'date',
44
+ sortOrder: 'descending',
45
+ searchString: '',
46
+ scrollPosition: { scrollTop: 0, scrollLeft: undefined },
47
+ },
48
+ matchModal: initialMatchModalState,
49
+ undoReversalStatusByMatchId: {},
50
+ closeMatchAfterUndo: false,
51
+ lastMatchResultsByMatchId: {},
52
+ lastUndoResultsByMatchId: {},
53
+ hasValidState() {
54
+ return this.fetchState == 'Completed';
55
+ },
56
+ };
57
+ function pruneLastMatchResultsForUndone(draft, matchId, results) {
58
+ const undoneIds = new Set(results
59
+ .filter((result) => result.status === 'undone')
60
+ .map((result) => result.accrualId));
61
+ if (undoneIds.size === 0) {
62
+ return;
63
+ }
64
+ const existing = draft.lastMatchResultsByMatchId[matchId];
65
+ if (existing == null) {
66
+ return;
67
+ }
68
+ const kept = existing.filter((result) => !undoneIds.has(result.accrualId));
69
+ if (kept.length === 0) {
70
+ delete draft.lastMatchResultsByMatchId[matchId];
71
+ return;
72
+ }
73
+ draft.lastMatchResultsByMatchId[matchId] = kept;
74
+ }
75
+ function pruneLastUndoResultsForMatched(draft, matchId, results) {
76
+ const matchedIds = new Set(results.map((result) => result.accrualId));
77
+ if (matchedIds.size === 0) {
78
+ return;
79
+ }
80
+ const existing = draft.lastUndoResultsByMatchId[matchId];
81
+ if (existing == null) {
82
+ return;
83
+ }
84
+ const kept = existing.filter((result) => !matchedIds.has(result.accrualId));
85
+ if (kept.length === 0) {
86
+ delete draft.lastUndoResultsByMatchId[matchId];
87
+ return;
88
+ }
89
+ draft.lastUndoResultsByMatchId[matchId] = kept;
90
+ }
91
+ function resetMatchModalSearches(draft) {
92
+ draft.matchModal.transactionSearch = initialTransactionSearchState;
93
+ draft.matchModal.accrualSearch = initialAccrualSearchState;
94
+ draft.matchModal.confirmStatus = {
95
+ fetchState: 'Not-Started',
96
+ error: undefined,
97
+ };
98
+ draft.matchModal.lastBatchResults = undefined;
99
+ draft.matchModal.openedFromMatchId = undefined;
100
+ draft.matchModal.didPinPrefillTransactions = false;
101
+ draft.matchModal.pinnedTransactionResults = [];
102
+ }
103
+ const expenseAutomationActualMatchingView = createSlice({
104
+ name: 'expenseAutomationActualMatchingView',
105
+ initialState,
106
+ reducers: {
107
+ fetchActualMatchingPage: {
108
+ prepare(period, refreshViewInBackground = false) {
109
+ return {
110
+ payload: {
111
+ period,
112
+ refreshViewInBackground,
113
+ },
114
+ };
115
+ },
116
+ reducer() {
117
+ // Epic-only fan-out.
118
+ },
119
+ },
120
+ fetchActualMatching: {
121
+ reducer(draft, action) {
122
+ const { refreshViewInBackground } = action.payload;
123
+ if (refreshViewInBackground) {
124
+ draft.refreshStatus = { fetchState: 'In-Progress', error: undefined };
125
+ if (draft.fetchState === 'Not-Started') {
126
+ draft.fetchState = 'In-Progress';
127
+ }
128
+ }
129
+ else {
130
+ draft.fetchState = 'In-Progress';
131
+ draft.error = undefined;
132
+ }
133
+ },
134
+ prepare(period, refreshViewInBackground = false) {
135
+ return {
136
+ payload: {
137
+ period,
138
+ refreshViewInBackground,
139
+ },
140
+ };
141
+ },
142
+ },
143
+ fetchActualMatchingSuccess(draft, action) {
144
+ const { matchIds, selectedPeriod, refreshViewInBackground } = action.payload;
145
+ const periodId = toMonthYearPeriodId(selectedPeriod);
146
+ draft.matchIdsByPeriod[periodId] = matchIds;
147
+ if (refreshViewInBackground) {
148
+ draft.refreshStatus = { fetchState: 'Completed', error: undefined };
149
+ if (draft.fetchState !== 'Completed') {
150
+ draft.fetchState = 'Completed';
151
+ draft.error = undefined;
152
+ }
153
+ }
154
+ else {
155
+ draft.fetchState = 'Completed';
156
+ draft.error = undefined;
157
+ }
158
+ },
159
+ fetchActualMatchingFailure(draft, action) {
160
+ const { status, refreshViewInBackground, selectedPeriod } = action.payload;
161
+ const periodId = toMonthYearPeriodId(selectedPeriod);
162
+ // Stamp an empty slot so the selector does not remap this month's Error
163
+ // to leftover Not-Started chrome. Do not clobber ids from a prior success.
164
+ if (draft.matchIdsByPeriod[periodId] == null) {
165
+ draft.matchIdsByPeriod[periodId] = [];
166
+ }
167
+ if (refreshViewInBackground) {
168
+ draft.refreshStatus = { fetchState: 'Error', error: status };
169
+ if (draft.fetchState !== 'Completed') {
170
+ draft.fetchState = 'Error';
171
+ draft.error = status;
172
+ }
173
+ }
174
+ else {
175
+ draft.fetchState = 'Error';
176
+ draft.error = status;
177
+ }
178
+ },
179
+ fetchActualMatchingMatch: {
180
+ prepare(matchId, refreshViewInBackground = false) {
181
+ return { payload: { matchId, refreshViewInBackground } };
182
+ },
183
+ reducer(draft, action) {
184
+ const { matchId, refreshViewInBackground } = action.payload;
185
+ if (refreshViewInBackground) {
186
+ draft.detailRefreshStatusByMatchId[matchId] = {
187
+ fetchState: 'In-Progress',
188
+ error: undefined,
189
+ };
190
+ }
191
+ else {
192
+ draft.detailFetchStateByMatchId[matchId] = {
193
+ fetchState: 'In-Progress',
194
+ error: undefined,
195
+ };
196
+ }
197
+ },
198
+ },
199
+ fetchActualMatchingMatchSuccess(draft, action) {
200
+ const { matchId, refreshViewInBackground } = action.payload;
201
+ if (refreshViewInBackground) {
202
+ draft.detailRefreshStatusByMatchId[matchId] = {
203
+ fetchState: 'Completed',
204
+ error: undefined,
205
+ };
206
+ }
207
+ else {
208
+ draft.detailFetchStateByMatchId[matchId] = {
209
+ fetchState: 'Completed',
210
+ error: undefined,
211
+ };
212
+ }
213
+ },
214
+ fetchActualMatchingMatchFailure(draft, action) {
215
+ const { matchId, refreshViewInBackground, status } = action.payload;
216
+ if (refreshViewInBackground) {
217
+ draft.detailRefreshStatusByMatchId[matchId] = {
218
+ fetchState: 'Error',
219
+ error: status,
220
+ };
221
+ }
222
+ else {
223
+ draft.detailFetchStateByMatchId[matchId] = {
224
+ fetchState: 'Error',
225
+ error: status,
226
+ };
227
+ }
228
+ },
229
+ updateActualMatchingUIState(draft, action) {
230
+ if (action.payload.sortKey != null) {
231
+ draft.uiState.sortKey = action.payload.sortKey;
232
+ }
233
+ if (action.payload.sortOrder != null) {
234
+ draft.uiState.sortOrder = action.payload.sortOrder;
235
+ }
236
+ if (action.payload.searchString != null) {
237
+ draft.uiState.searchString = action.payload.searchString;
238
+ }
239
+ if (action.payload.scrollPosition != null) {
240
+ draft.uiState.scrollPosition = action.payload.scrollPosition;
241
+ }
242
+ },
243
+ openMatchNewModal: {
244
+ prepare(matchId) {
245
+ return { payload: { matchId } };
246
+ },
247
+ reducer(draft, action) {
248
+ resetMatchModalSearches(draft);
249
+ draft.matchModal.isOpen = true;
250
+ const matchId = action.payload.matchId;
251
+ draft.matchModal.openedFromMatchId =
252
+ matchId != null && matchId !== '' ? matchId : undefined;
253
+ if (draft.matchModal.openedFromMatchId != null) {
254
+ draft.matchModal.accrualSearch.fetchState = 'Completed';
255
+ }
256
+ },
257
+ },
258
+ // Dismiss (Cancel / overlay). Store reset matches `clearMatchNewModal`;
259
+ // the actions stay distinct so confirm-success vs user-dismiss stay observable.
260
+ closeMatchNewModal(draft) {
261
+ draft.matchModal.isOpen = false;
262
+ resetMatchModalSearches(draft);
263
+ },
264
+ // Confirm-success / host reset. Same slice write as close; different action type.
265
+ clearMatchNewModal(draft) {
266
+ draft.matchModal.isOpen = false;
267
+ resetMatchModalSearches(draft);
268
+ },
269
+ searchMatchTransactions: {
270
+ reducer(draft, action) {
271
+ const { query, pageToken } = action.payload;
272
+ const isLoadMore = pageToken != null;
273
+ draft.matchModal.transactionSearch.query = query;
274
+ if (isLoadMore) {
275
+ draft.matchModal.transactionSearch.isLoadingMore = true;
276
+ }
277
+ else {
278
+ draft.matchModal.transactionSearch.isLoadingMore = false;
279
+ draft.matchModal.transactionSearch.results = [];
280
+ draft.matchModal.transactionSearch.nextPageToken = null;
281
+ draft.matchModal.transactionSearch.fetchState = 'In-Progress';
282
+ draft.matchModal.transactionSearch.error = undefined;
283
+ }
284
+ },
285
+ prepare(query, pageToken, transactionType, transactionIntegrationId) {
286
+ return {
287
+ payload: {
288
+ query,
289
+ pageToken: pageToken ?? undefined,
290
+ transactionType,
291
+ transactionIntegrationId,
292
+ },
293
+ };
294
+ },
295
+ },
296
+ searchMatchTransactionsSuccess(draft, action) {
297
+ const { append, nextPageToken, results } = action.payload;
298
+ if (append) {
299
+ const merged = [
300
+ ...draft.matchModal.transactionSearch.results,
301
+ ...results,
302
+ ];
303
+ const byKey = new Map(merged.map((result) => [
304
+ actualMatchingTransactionLineKey(result.transactionType, result.transactionIntegrationId, result.lineId),
305
+ result,
306
+ ]));
307
+ draft.matchModal.transactionSearch.results = Array.from(byKey.values());
308
+ }
309
+ else {
310
+ draft.matchModal.transactionSearch.results = results;
311
+ if (draft.matchModal.openedFromMatchId != null &&
312
+ !draft.matchModal.didPinPrefillTransactions) {
313
+ draft.matchModal.didPinPrefillTransactions = true;
314
+ draft.matchModal.pinnedTransactionResults = results;
315
+ }
316
+ }
317
+ draft.matchModal.transactionSearch.nextPageToken = nextPageToken;
318
+ draft.matchModal.transactionSearch.isLoadingMore = false;
319
+ draft.matchModal.transactionSearch.fetchState = 'Completed';
320
+ draft.matchModal.transactionSearch.error = undefined;
321
+ },
322
+ searchMatchTransactionsFailure(draft, action) {
323
+ draft.matchModal.transactionSearch.isLoadingMore = false;
324
+ draft.matchModal.transactionSearch.fetchState = 'Error';
325
+ draft.matchModal.transactionSearch.error = action.payload.error;
326
+ },
327
+ searchMatchAccruals: {
328
+ reducer(draft, action) {
329
+ const { query, pageToken } = action.payload;
330
+ const isLoadMore = pageToken != null;
331
+ draft.matchModal.accrualSearch.query = query;
332
+ if (isLoadMore) {
333
+ draft.matchModal.accrualSearch.isLoadingMore = true;
334
+ }
335
+ else {
336
+ draft.matchModal.accrualSearch.isLoadingMore = false;
337
+ draft.matchModal.accrualSearch.results = [];
338
+ draft.matchModal.accrualSearch.nextPageToken = null;
339
+ draft.matchModal.accrualSearch.fetchState = 'In-Progress';
340
+ draft.matchModal.accrualSearch.error = undefined;
341
+ }
342
+ },
343
+ prepare(query, pageToken) {
344
+ return { payload: { query, pageToken: pageToken ?? undefined } };
345
+ },
346
+ },
347
+ searchMatchAccrualsSuccess(draft, action) {
348
+ const { append, nextPageToken, results } = action.payload;
349
+ const keys = results.map((accrual) => generateJEOneTimeAccrualKey(accrual._id));
350
+ if (append) {
351
+ const merged = [...draft.matchModal.accrualSearch.results, ...keys];
352
+ draft.matchModal.accrualSearch.results = Array.from(new Set(merged));
353
+ }
354
+ else {
355
+ draft.matchModal.accrualSearch.results = keys;
356
+ }
357
+ draft.matchModal.accrualSearch.nextPageToken = nextPageToken;
358
+ draft.matchModal.accrualSearch.isLoadingMore = false;
359
+ draft.matchModal.accrualSearch.fetchState = 'Completed';
360
+ draft.matchModal.accrualSearch.error = undefined;
361
+ },
362
+ searchMatchAccrualsFailure(draft, action) {
363
+ draft.matchModal.accrualSearch.isLoadingMore = false;
364
+ draft.matchModal.accrualSearch.fetchState = 'Error';
365
+ draft.matchModal.accrualSearch.error = action.payload.error;
366
+ },
367
+ matchAndReverseAccruals: {
368
+ reducer(draft) {
369
+ draft.matchModal.confirmStatus = {
370
+ fetchState: 'In-Progress',
371
+ error: undefined,
372
+ };
373
+ },
374
+ prepare(transactionId, transactionIntegrationId, transactionType, lineIds, transactionSyncToken, accrualIds) {
375
+ return {
376
+ payload: {
377
+ transactionId,
378
+ transactionIntegrationId,
379
+ transactionType,
380
+ lineIds,
381
+ transactionSyncToken,
382
+ accrualIds,
383
+ },
384
+ };
385
+ },
386
+ },
387
+ matchAndReverseAccrualsSuccess(draft, action) {
388
+ const { results } = action.payload;
389
+ const allComplete = results.length > 0 &&
390
+ results.every((result) => isMatchAndReverseCompleteStatus(result.status));
391
+ draft.matchModal.confirmStatus = allComplete
392
+ ? { fetchState: 'Completed', error: undefined }
393
+ : {
394
+ fetchState: 'Error',
395
+ error: createZeniAPIStatus('Partial match-and-reverse', 'Some accruals were not reversed'),
396
+ };
397
+ draft.matchModal.lastBatchResults = results;
398
+ if (action.payload.matchId != null) {
399
+ draft.lastMatchResultsByMatchId[action.payload.matchId] = results;
400
+ pruneLastUndoResultsForMatched(draft, action.payload.matchId, action.payload.results);
401
+ }
402
+ },
403
+ matchAndReverseAccrualsFailure(draft, action) {
404
+ draft.matchModal.confirmStatus = {
405
+ fetchState: 'Error',
406
+ error: action.payload.status,
407
+ };
408
+ },
409
+ undoReversalAccruals: {
410
+ reducer(draft, action) {
411
+ draft.undoReversalStatusByMatchId[action.payload.matchId] = {
412
+ fetchState: 'In-Progress',
413
+ error: undefined,
414
+ };
415
+ },
416
+ prepare(matchId, accrualIds) {
417
+ return { payload: { matchId, accrualIds } };
418
+ },
419
+ },
420
+ undoReversalAccrualsSuccess: {
421
+ reducer(draft, action) {
422
+ const { matchId, results } = action.payload;
423
+ draft.undoReversalStatusByMatchId[matchId] = {
424
+ fetchState: 'Completed',
425
+ error: undefined,
426
+ };
427
+ const allUndone = results.length > 0 &&
428
+ results.every((result) => result.status === 'undone');
429
+ draft.closeMatchAfterUndo = allUndone;
430
+ pruneLastMatchResultsForUndone(draft, matchId, results);
431
+ if (allUndone) {
432
+ delete draft.lastUndoResultsByMatchId[matchId];
433
+ }
434
+ else {
435
+ draft.lastUndoResultsByMatchId[matchId] = results;
436
+ }
437
+ },
438
+ prepare(matchId, results) {
439
+ return { payload: { matchId, results } };
440
+ },
441
+ },
442
+ ackCloseMatchAfterUndo(draft) {
443
+ draft.closeMatchAfterUndo = false;
444
+ },
445
+ undoReversalAccrualsFailure(draft, action) {
446
+ draft.undoReversalStatusByMatchId[action.payload.matchId] = {
447
+ fetchState: 'Error',
448
+ error: action.payload.status,
449
+ };
450
+ },
451
+ clearActualMatchingView(draft) {
452
+ Object.assign(draft, initialState);
453
+ },
454
+ },
455
+ extraReducers: (builder) => {
456
+ builder.addCase(clearAll, (draft) => {
457
+ Object.assign(draft, initialState);
458
+ });
459
+ },
460
+ });
461
+ export const { ackCloseMatchAfterUndo, clearActualMatchingView, clearMatchNewModal, closeMatchNewModal, fetchActualMatching, fetchActualMatchingFailure, fetchActualMatchingMatch, fetchActualMatchingMatchFailure, fetchActualMatchingMatchSuccess, fetchActualMatchingPage, fetchActualMatchingSuccess, matchAndReverseAccruals, matchAndReverseAccrualsFailure, matchAndReverseAccrualsSuccess, openMatchNewModal, searchMatchAccruals, searchMatchAccrualsFailure, searchMatchAccrualsSuccess, searchMatchTransactions, searchMatchTransactionsFailure, searchMatchTransactionsSuccess, undoReversalAccruals, undoReversalAccrualsFailure, undoReversalAccrualsSuccess, updateActualMatchingUIState, } = expenseAutomationActualMatchingView.actions;
462
+ export default expenseAutomationActualMatchingView.reducer;
@@ -105,11 +105,12 @@ const expenseAutomationJESchedulesView = createSlice({
105
105
  initialState,
106
106
  reducers: {
107
107
  fetchJeSchedulesPage: {
108
- prepare(period, refreshViewInBackground = false) {
108
+ prepare(period, refreshViewInBackground = false, isAccrualEnabled = false) {
109
109
  return {
110
110
  payload: {
111
111
  period,
112
112
  refreshViewInBackground,
113
+ isAccrualEnabled,
113
114
  },
114
115
  };
115
116
  },