@zeniai/client-epic-state 5.2.35-beta0MM → 5.2.35-beta10MM

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 (89) hide show
  1. package/lib/entity/actualMatching/actualMatchingPayload.d.ts +7 -1
  2. package/lib/entity/actualMatching/actualMatchingPayload.js +19 -12
  3. package/lib/entity/actualMatching/actualMatchingReducer.d.ts +3 -2
  4. package/lib/entity/actualMatching/actualMatchingReducer.js +3 -3
  5. package/lib/entity/actualMatching/actualMatchingState.d.ts +3 -2
  6. package/lib/entity/aiCfo/aiCfoReducer.js +78 -4
  7. package/lib/entity/aiCfo/aiCfoState.d.ts +16 -0
  8. package/lib/entity/jeSchedules/jeSchedulesPayload.d.ts +2 -0
  9. package/lib/entity/jeSchedules/jeSchedulesPayload.js +4 -0
  10. package/lib/entity/jeSchedules/jeSchedulesState.d.ts +2 -0
  11. package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
  12. package/lib/entity/snackbar/snackbarTypes.js +2 -0
  13. package/lib/esm/entity/actualMatching/actualMatchingPayload.js +18 -12
  14. package/lib/esm/entity/actualMatching/actualMatchingReducer.js +3 -3
  15. package/lib/esm/entity/aiCfo/aiCfoReducer.js +79 -5
  16. package/lib/esm/entity/jeSchedules/jeSchedulesPayload.js +4 -0
  17. package/lib/esm/entity/snackbar/snackbarTypes.js +2 -0
  18. package/lib/esm/index.js +6 -6
  19. package/lib/esm/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingEpic.js +3 -5
  20. package/lib/esm/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingMatchEpic.js +3 -2
  21. package/lib/esm/view/expenseAutomationView/epics/actualMatching/matchAndReverseAccrualsEpic.js +80 -25
  22. package/lib/esm/view/expenseAutomationView/epics/actualMatching/refreshActualMatchingList.js +16 -0
  23. package/lib/esm/view/expenseAutomationView/epics/actualMatching/searchMatchAccrualsEpic.js +16 -6
  24. package/lib/esm/view/expenseAutomationView/epics/actualMatching/searchMatchTransactionsEpic.js +12 -11
  25. package/lib/esm/view/expenseAutomationView/epics/actualMatching/undoReversalAccrualsEpic.js +50 -26
  26. package/lib/esm/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +5 -9
  27. package/lib/esm/view/expenseAutomationView/epics/common/refreshExpenseAutomationCurrentTabEpic.js +1 -9
  28. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.js +13 -3
  29. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/prefetchOtherJeSchedulesTabEpic.js +34 -16
  30. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +8 -8
  31. package/lib/esm/view/expenseAutomationView/expenseAutomationViewReducer.js +2 -1
  32. package/lib/esm/view/expenseAutomationView/expenseAutomationViewSelector.js +5 -2
  33. package/lib/esm/view/expenseAutomationView/expenseAutomationViewState.js +4 -2
  34. package/lib/esm/view/expenseAutomationView/payload/actualMatchingPayload.js +10 -10
  35. package/lib/esm/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +90 -20
  36. package/lib/esm/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +2 -1
  37. package/lib/esm/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +206 -61
  38. package/lib/esm/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +1 -0
  39. package/lib/esm/view/expenseAutomationView/types/actualMatchingViewState.js +3 -0
  40. package/lib/esm/view/expenseAutomationView/types/jeSchedulesViewState.js +3 -0
  41. package/lib/esm/view/financeStatement/financeStatementReducer.js +17 -27
  42. package/lib/esm/view/spendManagement/billPay/billList/billListSelector.js +3 -3
  43. package/lib/index.d.ts +7 -6
  44. package/lib/index.js +52 -48
  45. package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingEpic.js +3 -5
  46. package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingMatchEpic.d.ts +1 -1
  47. package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingMatchEpic.js +3 -2
  48. package/lib/view/expenseAutomationView/epics/actualMatching/matchAndReverseAccrualsEpic.d.ts +2 -1
  49. package/lib/view/expenseAutomationView/epics/actualMatching/matchAndReverseAccrualsEpic.js +77 -22
  50. package/lib/view/expenseAutomationView/epics/actualMatching/refreshActualMatchingList.d.ts +3 -0
  51. package/lib/view/expenseAutomationView/epics/actualMatching/refreshActualMatchingList.js +19 -0
  52. package/lib/view/expenseAutomationView/epics/actualMatching/searchMatchAccrualsEpic.d.ts +3 -2
  53. package/lib/view/expenseAutomationView/epics/actualMatching/searchMatchAccrualsEpic.js +15 -5
  54. package/lib/view/expenseAutomationView/epics/actualMatching/searchMatchTransactionsEpic.js +10 -9
  55. package/lib/view/expenseAutomationView/epics/actualMatching/undoReversalAccrualsEpic.d.ts +2 -1
  56. package/lib/view/expenseAutomationView/epics/actualMatching/undoReversalAccrualsEpic.js +48 -24
  57. package/lib/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.d.ts +1 -2
  58. package/lib/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +5 -9
  59. package/lib/view/expenseAutomationView/epics/common/refreshExpenseAutomationCurrentTabEpic.d.ts +1 -2
  60. package/lib/view/expenseAutomationView/epics/common/refreshExpenseAutomationCurrentTabEpic.js +1 -9
  61. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.d.ts +2 -1
  62. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.js +13 -3
  63. package/lib/view/expenseAutomationView/epics/jeSchedule/prefetchOtherJeSchedulesTabEpic.d.ts +5 -1
  64. package/lib/view/expenseAutomationView/epics/jeSchedule/prefetchOtherJeSchedulesTabEpic.js +33 -15
  65. package/lib/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.d.ts +1 -2
  66. package/lib/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +8 -8
  67. package/lib/view/expenseAutomationView/expenseAutomationViewReducer.d.ts +2 -1
  68. package/lib/view/expenseAutomationView/expenseAutomationViewReducer.js +2 -1
  69. package/lib/view/expenseAutomationView/expenseAutomationViewSelector.js +5 -2
  70. package/lib/view/expenseAutomationView/expenseAutomationViewState.d.ts +2 -1
  71. package/lib/view/expenseAutomationView/expenseAutomationViewState.js +6 -3
  72. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
  73. package/lib/view/expenseAutomationView/payload/actualMatchingPayload.d.ts +3 -3
  74. package/lib/view/expenseAutomationView/payload/actualMatchingPayload.js +10 -10
  75. package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.d.ts +21 -16
  76. package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +90 -20
  77. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.d.ts +2 -1
  78. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +2 -1
  79. package/lib/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.d.ts +13 -9
  80. package/lib/view/expenseAutomationView/selectorTypes/expenseAutomationViewSelectorTypes.d.ts +9 -0
  81. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.d.ts +11 -2
  82. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +209 -60
  83. package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +1 -0
  84. package/lib/view/expenseAutomationView/types/actualMatchingViewState.d.ts +8 -2
  85. package/lib/view/expenseAutomationView/types/actualMatchingViewState.js +4 -0
  86. package/lib/view/expenseAutomationView/types/jeSchedulesViewState.js +3 -0
  87. package/lib/view/financeStatement/financeStatementReducer.js +17 -27
  88. package/lib/view/spendManagement/billPay/billList/billListSelector.js +2 -2
  89. package/package.json +1 -1
@@ -2,24 +2,34 @@ import { concat, from } from 'rxjs';
2
2
  import { filter, mergeMap } from 'rxjs/operators';
3
3
  import { fetchAccountList } from '../../../accountList/accountListReducer';
4
4
  import { getAccountsTypesForScheduleEpics } from '../../../scheduleView/scheduleListView/scheduleListHelper';
5
+ import { fetchActualMatchingPage } from '../../reducers/actualMatchingViewReducer';
5
6
  import { fetchJeSchedules, fetchJeSchedulesPage, initializeAccountSettingsView, } from '../../reducers/jeSchedulesViewReducer';
6
7
  export const fetchJeSchedulesPageEpic = (actions$, state$) => actions$.pipe(filter(fetchJeSchedulesPage.match), mergeMap((action) => {
7
8
  const jeSchedulePageActions = [];
8
9
  const { period, refreshViewInBackground } = action.payload;
10
+ const state = state$.value;
11
+ const { selectedPageTab } = state.expenseAutomationJESchedulesViewState.uiState;
9
12
  const accountType = getAccountsTypesForScheduleEpics();
10
- const accountListState = state$.value.accountListState;
13
+ const accountListState = state.accountListState;
11
14
  if (accountListState.byReportId[accountType.accountListKey].fetchState ===
12
15
  'Not-Started') {
13
16
  // call this only if the account list for schedules is not fetched already
14
17
  jeSchedulePageActions.push(fetchAccountList(accountType.accountListKey, true, false, false, false, accountType.allowedAccountList));
15
18
  }
16
- jeSchedulePageActions.push(fetchJeSchedules(period, refreshViewInBackground));
19
+ // AM is chrome on this page but a different endpoint. fetchJeSchedules with
20
+ // selectedPageTab === 'actual_matching' would hit /1.0/schedules and write a bogus cache slot.
21
+ if (selectedPageTab === 'actual_matching') {
22
+ jeSchedulePageActions.push(fetchActualMatchingPage(period, refreshViewInBackground));
23
+ }
24
+ else {
25
+ jeSchedulePageActions.push(fetchJeSchedules(period, refreshViewInBackground));
26
+ }
17
27
  // Only on the first page load. Account settings cannot change from a tab switch or a
18
28
  // refresh, and re-initializing puts accountSettingsView back to 'In-Progress' -- which the
19
29
  // page reads as "still loading" and answers with a full skeleton over rows it already has.
20
30
  // The action also resets localData, so re-dispatching it blindly would empty the settings
21
31
  // form until the refetch lands. Saving account settings still re-initializes explicitly.
22
- if (!state$.value.expenseAutomationJESchedulesViewState.accountSettings
32
+ if (!state.expenseAutomationJESchedulesViewState.accountSettings
23
33
  .hasLoadedAccountSettings) {
24
34
  jeSchedulePageActions.push(initializeAccountSettingsView());
25
35
  }
@@ -1,7 +1,8 @@
1
- import { EMPTY, of } from 'rxjs';
1
+ import { EMPTY, from } from 'rxjs';
2
2
  import { filter, mergeMap } from 'rxjs/operators';
3
3
  import { toMonthYearPeriodId } from '../../../../commonStateTypes/timePeriod';
4
4
  import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
5
+ import { fetchActualMatchingPage } from '../../reducers/actualMatchingViewReducer';
5
6
  import { fetchJeSchedules, fetchJeSchedulesPage, } from '../../reducers/jeSchedulesViewReducer';
6
7
  import { ALL_JE_PAGE_TABS, getJESchedulePeriodTabKey, } from '../../types/jeSchedulesViewState';
7
8
  /**
@@ -9,29 +10,46 @@ import { ALL_JE_PAGE_TABS, getJESchedulePeriodTabKey, } from '../../types/jeSche
9
10
  *
10
11
  * Listens for `fetchJeSchedulesPage` rather than `fetchJeSchedules` on purpose: the prefetch emits
11
12
  * `fetchJeSchedules`, so listening for that would make this epic feed itself.
13
+ *
14
+ * Review <-> Existing share `/1.0/schedules`. Actual Matching is a different endpoint; it is
15
+ * warmed with `fetchActualMatchingPage` when the page action carries `isAccrualEnabled`.
12
16
  */
13
17
  export const prefetchOtherJeSchedulesTabEpic = (actions$, state$) => actions$.pipe(filter(fetchJeSchedulesPage.match), mergeMap((action) => {
14
18
  const state = state$.value;
15
- const { period } = action.payload;
19
+ const { period, isAccrualEnabled } = action.payload;
16
20
  const { selectedPageTab } = state.expenseAutomationJESchedulesViewState.uiState;
17
- // Actual Matching has its own CES slice and API; warming it with fetchJeSchedules
18
- // would prefetch the wrong endpoint. Only Review <-> Existing share that fetch.
21
+ const actions = [];
22
+ // AM is chrome-only on ALL_JE_PAGE_TABS; warming it with fetchJeSchedules
23
+ // would hit the wrong endpoint.
19
24
  const otherPageTab = ALL_JE_PAGE_TABS.find((pageTab) => pageTab !== selectedPageTab && pageTab !== 'actual_matching');
20
- if (otherPageTab == null) {
21
- return EMPTY;
22
- }
23
25
  const currentTenant = getCurrentTenant(state);
24
26
  const selectedPeriod = state.expenseAutomationViewState.selectedPeriodByTenantId[currentTenant.tenantId];
25
- if (selectedPeriod == null) {
26
- return EMPTY;
27
+ if (otherPageTab != null && selectedPeriod != null) {
28
+ const otherPeriodTabKey = getJESchedulePeriodTabKey(otherPageTab, toMonthYearPeriodId(selectedPeriod));
29
+ const isOtherTabAlreadyLoaded = state.expenseAutomationJESchedulesViewState
30
+ .jeScheduleTransactionKeysByPeriodAndTab[otherPeriodTabKey] != null;
31
+ if (!isOtherTabAlreadyLoaded) {
32
+ actions.push(fetchJeSchedules(period, true, otherPageTab));
33
+ }
34
+ }
35
+ // GET /actual-matching is not Statsig-gated; CES has no Statsig. The screen
36
+ // passes is_accrual_enabled so disabled tenants do not pay for a list they
37
+ // cannot open. Visible AM is fetchJeSchedulesPageEpic's job.
38
+ if (isAccrualEnabled === true &&
39
+ selectedPageTab !== 'actual_matching') {
40
+ const periodId = toMonthYearPeriodId({
41
+ month: period.start.month,
42
+ year: period.start.year,
43
+ });
44
+ // `fetchState` is global across months. Skipping on In-Progress would drop
45
+ // October prefetch while September is still loading; September's success
46
+ // then marks the slice Completed and October looks loaded with no ids.
47
+ if (state.expenseAutomationActualMatchingViewState.matchIdsByPeriod[periodId] == null) {
48
+ actions.push(fetchActualMatchingPage(period, true));
49
+ }
27
50
  }
28
- // Already warm for this period: without this check every tab switch would refetch the tab
29
- // just left, since a switch also fires fetchJeSchedulesPage.
30
- const otherPeriodTabKey = getJESchedulePeriodTabKey(otherPageTab, toMonthYearPeriodId(selectedPeriod));
31
- const isOtherTabAlreadyLoaded = state.expenseAutomationJESchedulesViewState
32
- .jeScheduleTransactionKeysByPeriodAndTab[otherPeriodTabKey] != null;
33
- if (isOtherTabAlreadyLoaded) {
51
+ if (actions.length === 0) {
34
52
  return EMPTY;
35
53
  }
36
- return of(fetchJeSchedules(period, true, otherPageTab));
54
+ return from(actions);
37
55
  }));
@@ -2,13 +2,17 @@ import { EMPTY, of } from 'rxjs';
2
2
  import { filter, mergeMap } from 'rxjs/operators';
3
3
  import { convertToPeriod, toAbsoluteDay, } from '../../../../commonStateTypes/timePeriod';
4
4
  import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
5
- import { fetchActualMatchingPage } from '../../reducers/actualMatchingViewReducer';
6
5
  import { fetchJeSchedulesPage, updateJESchedulesUIState, } from '../../reducers/jeSchedulesViewReducer';
7
- export const switchJESchedulesTabEpic = (actions$, state$) => actions$.pipe(filter(updateJESchedulesUIState.match), filter((action) => action.payload.selectedPageTab != null), mergeMap(() => {
6
+ export const switchJESchedulesTabEpic = (actions$, state$) => actions$.pipe(filter(updateJESchedulesUIState.match), filter((action) => action.payload.selectedPageTab != null), mergeMap((action) => {
8
7
  const state = state$.value;
8
+ const nextTab = action.payload.selectedPageTab;
9
9
  const { fetchState, refreshStatus } = state.expenseAutomationJESchedulesViewState;
10
- if (fetchState === 'In-Progress' ||
11
- refreshStatus.fetchState === 'In-Progress') {
10
+ // Review/Existing share `/1.0/schedules`; skip a second JE fetch while one
11
+ // is in flight. AM is a different endpoint — do not block it on JE
12
+ // In-Progress or the tab stays on a skeleton until the user leaves.
13
+ if (nextTab !== 'actual_matching' &&
14
+ (fetchState === 'In-Progress' ||
15
+ refreshStatus.fetchState === 'In-Progress')) {
12
16
  return EMPTY;
13
17
  }
14
18
  const currentTenant = getCurrentTenant(state);
@@ -25,9 +29,5 @@ export const switchJESchedulesTabEpic = (actions$, state$) => actions$.pipe(filt
25
29
  if (timePeriodStart == null || timePeriodEnd == null) {
26
30
  return EMPTY;
27
31
  }
28
- const { selectedPageTab } = state.expenseAutomationJESchedulesViewState.uiState;
29
- if (selectedPageTab === 'actual_matching') {
30
- return of(fetchActualMatchingPage({ start: timePeriodStart, end: timePeriodEnd }, true));
31
- }
32
32
  return of(fetchJeSchedulesPage({ start: timePeriodStart, end: timePeriodEnd }, true));
33
33
  }));
@@ -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
  },
@@ -22,7 +22,7 @@ const isPreviousStepDisabled = (selectedTransactionCategorizationTab, allSteps,
22
22
  return allSteps[stepIndex - 1].isDisabled;
23
23
  };
24
24
  export function getExpenseAutomationView(state) {
25
- const { expenseAutomationViewState, tenantState, expenseAutomationMissingReceiptsViewState, expenseAutomationJESchedulesViewState, expenseAutomationActualMatchingViewState, expenseAutomationTransactionsViewState, expenseAutomationReconciliationViewState, monthEndCloseChecksState, companyState, } = state;
25
+ const { expenseAutomationViewState, tenantState, expenseAutomationMissingReceiptsViewState, expenseAutomationJESchedulesViewState, expenseAutomationTransactionsViewState, expenseAutomationReconciliationViewState, monthEndCloseChecksState, companyState, } = state;
26
26
  const { loggedInUser, currentTenantId } = tenantState;
27
27
  const currentTenant = getCurrentTenant(state);
28
28
  const { currentSelectedView, selectedPeriodByTenantId } = expenseAutomationViewState;
@@ -43,6 +43,7 @@ export function getExpenseAutomationView(state) {
43
43
  const reconciliation = getExpenseAutomationReconciliationView(state);
44
44
  const expenseAutomationTransactionsView = getExpenseAutomationTransactionView(state);
45
45
  const jeSchedules = getExpenseAutomationJESchedulesView(state);
46
+ // Top-level sibling of `jeSchedules`: see ExpenseAutomationViewSelector.
46
47
  const actualMatching = getExpenseAutomationActualMatchingView(state);
47
48
  switch (currentSelectedView) {
48
49
  case 'reconciliation':
@@ -101,10 +102,12 @@ export function getExpenseAutomationView(state) {
101
102
  };
102
103
  break;
103
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.
104
108
  const fetchStateVariables = [
105
109
  expenseAutomationMissingReceiptsViewState,
106
110
  expenseAutomationJESchedulesViewState,
107
- expenseAutomationActualMatchingViewState,
108
111
  expenseAutomationTransactionsView,
109
112
  expenseAutomationReconciliationViewState,
110
113
  ];
@@ -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);
@@ -3,8 +3,6 @@ import { toMatchAndReverseResultStatus, toUndoReversalResultStatus, } from '../t
3
3
  export function toServiceMonthFromPeriod(period) {
4
4
  return toString(period.start).slice(0, 7);
5
5
  }
6
- const DEFAULT_CURRENCY_CODE = 'USD';
7
- const DEFAULT_CURRENCY_SYMBOL = '$';
8
6
  function resolveMatchSearchAmount(payload) {
9
7
  const raw = payload.total_amount ?? payload.amount;
10
8
  if (typeof raw === 'number' && Number.isFinite(raw)) {
@@ -18,22 +16,24 @@ function resolveMatchSearchAmount(payload) {
18
16
  }
19
17
  return 0;
20
18
  }
21
- function resolveMatchSearchCurrencySymbol(payload, currencyCode) {
19
+ function resolveMatchSearchCurrencySymbol(payload, currencyCode, fallbackCurrency) {
22
20
  if (payload.currency_symbol != null && payload.currency_symbol !== '') {
23
21
  return payload.currency_symbol;
24
22
  }
25
- return currencyCode === DEFAULT_CURRENCY_CODE
26
- ? DEFAULT_CURRENCY_SYMBOL
27
- : currencyCode;
23
+ if (fallbackCurrency != null &&
24
+ currencyCode === fallbackCurrency.currencyCode) {
25
+ return fallbackCurrency.currencySymbol;
26
+ }
27
+ return currencyCode;
28
28
  }
29
- export function toActualMatchingTransactionSearchResult(payload) {
29
+ export function toActualMatchingTransactionSearchResult(payload, fallbackCurrency) {
30
30
  const currencyCode = payload.currency_code != null && payload.currency_code !== ''
31
31
  ? payload.currency_code
32
- : DEFAULT_CURRENCY_CODE;
32
+ : fallbackCurrency?.currencyCode ?? '';
33
33
  return {
34
34
  transactionId: payload.transaction_id,
35
35
  transactionIntegrationId: payload.transaction_integration_id,
36
- transactionType: payload.transaction_type,
36
+ transactionType: (payload.transaction_type || '').toLowerCase(),
37
37
  lineId: payload.line_id,
38
38
  vendorId: payload.vendor_id ?? undefined,
39
39
  vendorName: payload.vendor_name ?? '',
@@ -45,7 +45,7 @@ export function toActualMatchingTransactionSearchResult(payload) {
45
45
  accountingClassId: payload.accounting_class_id ?? undefined,
46
46
  accountingClassName: payload.accounting_class_name ?? undefined,
47
47
  currencyCode,
48
- currencySymbol: resolveMatchSearchCurrencySymbol(payload, currencyCode),
48
+ currencySymbol: resolveMatchSearchCurrencySymbol(payload, currencyCode, fallbackCurrency),
49
49
  transactionMemo: payload.transaction_memo ?? undefined,
50
50
  transactionDescription: payload.transaction_description ?? undefined,
51
51
  transactionTypeName: payload.transaction_type_name ?? undefined,
@@ -1,7 +1,8 @@
1
1
  import { createSlice } from '@reduxjs/toolkit';
2
- import { toMonthYearPeriodId, } from '../../../commonStateTypes/timePeriod';
2
+ import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
3
3
  import { generateJEOneTimeAccrualKey } from '../../../entity/jeSchedules/jeScheduleHelper';
4
4
  import { clearAll } from '../../../rootActions';
5
+ import { actualMatchingTransactionLineKey } from '../types/actualMatchingViewState';
5
6
  export const initialTransactionSearchState = {
6
7
  query: '',
7
8
  results: [],
@@ -21,6 +22,7 @@ export const initialAccrualSearchState = {
21
22
  export const initialMatchModalState = {
22
23
  isOpen: false,
23
24
  openedFromMatchId: undefined,
25
+ didPinPrefillTransactions: false,
24
26
  pinnedTransactionResults: [],
25
27
  transactionSearch: initialTransactionSearchState,
26
28
  accrualSearch: initialAccrualSearchState,
@@ -43,11 +45,32 @@ export const initialState = {
43
45
  scrollPosition: { scrollTop: 0, scrollLeft: undefined },
44
46
  },
45
47
  matchModal: initialMatchModalState,
46
- undoReversalStatus: { fetchState: 'Not-Started', error: undefined },
48
+ undoReversalStatusByMatchId: {},
49
+ closeMatchAfterUndo: false,
50
+ lastMatchResultsByMatchId: {},
51
+ lastUndoResultsByMatchId: {},
47
52
  hasValidState() {
48
53
  return this.fetchState == 'Completed';
49
54
  },
50
55
  };
56
+ function pruneLastMatchResultsForUndone(draft, matchId, results) {
57
+ const undoneIds = new Set(results
58
+ .filter((result) => result.status === 'undone')
59
+ .map((result) => result.accrualId));
60
+ if (undoneIds.size === 0) {
61
+ return;
62
+ }
63
+ const existing = draft.lastMatchResultsByMatchId[matchId];
64
+ if (existing == null) {
65
+ return;
66
+ }
67
+ const kept = existing.filter((result) => !undoneIds.has(result.accrualId));
68
+ if (kept.length === 0) {
69
+ delete draft.lastMatchResultsByMatchId[matchId];
70
+ return;
71
+ }
72
+ draft.lastMatchResultsByMatchId[matchId] = kept;
73
+ }
51
74
  function resetMatchModalSearches(draft) {
52
75
  draft.matchModal.transactionSearch = initialTransactionSearchState;
53
76
  draft.matchModal.accrualSearch = initialAccrualSearchState;
@@ -57,6 +80,7 @@ function resetMatchModalSearches(draft) {
57
80
  };
58
81
  draft.matchModal.lastBatchResults = undefined;
59
82
  draft.matchModal.openedFromMatchId = undefined;
83
+ draft.matchModal.didPinPrefillTransactions = false;
60
84
  draft.matchModal.pinnedTransactionResults = [];
61
85
  }
62
86
  const expenseAutomationActualMatchingView = createSlice({
@@ -81,6 +105,9 @@ const expenseAutomationActualMatchingView = createSlice({
81
105
  const { refreshViewInBackground } = action.payload;
82
106
  if (refreshViewInBackground) {
83
107
  draft.refreshStatus = { fetchState: 'In-Progress', error: undefined };
108
+ if (draft.fetchState === 'Not-Started') {
109
+ draft.fetchState = 'In-Progress';
110
+ }
84
111
  }
85
112
  else {
86
113
  draft.fetchState = 'In-Progress';
@@ -102,6 +129,10 @@ const expenseAutomationActualMatchingView = createSlice({
102
129
  draft.matchIdsByPeriod[periodId] = matchIds;
103
130
  if (refreshViewInBackground) {
104
131
  draft.refreshStatus = { fetchState: 'Completed', error: undefined };
132
+ if (draft.fetchState !== 'Completed') {
133
+ draft.fetchState = 'Completed';
134
+ draft.error = undefined;
135
+ }
105
136
  }
106
137
  else {
107
138
  draft.fetchState = 'Completed';
@@ -112,6 +143,10 @@ const expenseAutomationActualMatchingView = createSlice({
112
143
  const { status, refreshViewInBackground } = action.payload;
113
144
  if (refreshViewInBackground) {
114
145
  draft.refreshStatus = { fetchState: 'Error', error: status };
146
+ if (draft.fetchState !== 'Completed') {
147
+ draft.fetchState = 'Error';
148
+ draft.error = status;
149
+ }
115
150
  }
116
151
  else {
117
152
  draft.fetchState = 'Error';
@@ -197,10 +232,13 @@ const expenseAutomationActualMatchingView = createSlice({
197
232
  }
198
233
  },
199
234
  },
235
+ // Dismiss (Cancel / overlay). Store reset matches `clearMatchNewModal`;
236
+ // the actions stay distinct so confirm-success vs user-dismiss stay observable.
200
237
  closeMatchNewModal(draft) {
201
238
  draft.matchModal.isOpen = false;
202
239
  resetMatchModalSearches(draft);
203
240
  },
241
+ // Confirm-success / host reset. Same slice write as close; different action type.
204
242
  clearMatchNewModal(draft) {
205
243
  draft.matchModal.isOpen = false;
206
244
  resetMatchModalSearches(draft);
@@ -221,7 +259,7 @@ const expenseAutomationActualMatchingView = createSlice({
221
259
  draft.matchModal.transactionSearch.error = undefined;
222
260
  }
223
261
  },
224
- prepare({ query, pageToken, transactionType, transactionIntegrationId, }) {
262
+ prepare(query, pageToken, transactionType, transactionIntegrationId) {
225
263
  return {
226
264
  payload: {
227
265
  query,
@@ -240,7 +278,7 @@ const expenseAutomationActualMatchingView = createSlice({
240
278
  ...results,
241
279
  ];
242
280
  const byKey = new Map(merged.map((result) => [
243
- `${result.transactionId}:${result.lineId}`,
281
+ actualMatchingTransactionLineKey(result.transactionType, result.transactionIntegrationId, result.lineId),
244
282
  result,
245
283
  ]));
246
284
  draft.matchModal.transactionSearch.results = Array.from(byKey.values());
@@ -248,7 +286,8 @@ const expenseAutomationActualMatchingView = createSlice({
248
286
  else {
249
287
  draft.matchModal.transactionSearch.results = results;
250
288
  if (draft.matchModal.openedFromMatchId != null &&
251
- draft.matchModal.pinnedTransactionResults.length === 0) {
289
+ !draft.matchModal.didPinPrefillTransactions) {
290
+ draft.matchModal.didPinPrefillTransactions = true;
252
291
  draft.matchModal.pinnedTransactionResults = results;
253
292
  }
254
293
  }
@@ -278,7 +317,7 @@ const expenseAutomationActualMatchingView = createSlice({
278
317
  draft.matchModal.accrualSearch.error = undefined;
279
318
  }
280
319
  },
281
- prepare({ query, pageToken }) {
320
+ prepare(query, pageToken) {
282
321
  return { payload: { query, pageToken: pageToken ?? undefined } };
283
322
  },
284
323
  },
@@ -309,8 +348,17 @@ const expenseAutomationActualMatchingView = createSlice({
309
348
  error: undefined,
310
349
  };
311
350
  },
312
- prepare(params) {
313
- return { payload: params };
351
+ prepare(transactionId, transactionIntegrationId, transactionType, lineIds, transactionSyncToken, accrualIds) {
352
+ return {
353
+ payload: {
354
+ transactionId,
355
+ transactionIntegrationId,
356
+ transactionType,
357
+ lineIds,
358
+ transactionSyncToken,
359
+ accrualIds,
360
+ },
361
+ };
314
362
  },
315
363
  },
316
364
  matchAndReverseAccrualsSuccess(draft, action) {
@@ -319,6 +367,10 @@ const expenseAutomationActualMatchingView = createSlice({
319
367
  error: undefined,
320
368
  };
321
369
  draft.matchModal.lastBatchResults = action.payload.results;
370
+ if (action.payload.matchId != null) {
371
+ draft.lastMatchResultsByMatchId[action.payload.matchId] =
372
+ action.payload.results;
373
+ }
322
374
  },
323
375
  matchAndReverseAccrualsFailure(draft, action) {
324
376
  draft.matchModal.confirmStatus = {
@@ -327,25 +379,43 @@ const expenseAutomationActualMatchingView = createSlice({
327
379
  };
328
380
  },
329
381
  undoReversalAccruals: {
330
- reducer(draft) {
331
- draft.undoReversalStatus = {
382
+ reducer(draft, action) {
383
+ draft.undoReversalStatusByMatchId[action.payload.matchId] = {
332
384
  fetchState: 'In-Progress',
333
385
  error: undefined,
334
386
  };
335
387
  },
336
- prepare(params) {
337
- return { payload: params };
388
+ prepare(matchId, accrualIds) {
389
+ return { payload: { matchId, accrualIds } };
338
390
  },
339
391
  },
340
- undoReversalAccrualsSuccess(draft, action) {
341
- draft.undoReversalStatus = {
342
- fetchState: 'Completed',
343
- error: undefined,
344
- };
345
- void action;
392
+ undoReversalAccrualsSuccess: {
393
+ reducer(draft, action) {
394
+ const { matchId, results } = action.payload;
395
+ draft.undoReversalStatusByMatchId[matchId] = {
396
+ fetchState: 'Completed',
397
+ error: undefined,
398
+ };
399
+ const allUndone = results.length > 0 &&
400
+ results.every((result) => result.status === 'undone');
401
+ draft.closeMatchAfterUndo = allUndone;
402
+ pruneLastMatchResultsForUndone(draft, matchId, results);
403
+ if (allUndone) {
404
+ delete draft.lastUndoResultsByMatchId[matchId];
405
+ }
406
+ else {
407
+ draft.lastUndoResultsByMatchId[matchId] = results;
408
+ }
409
+ },
410
+ prepare(matchId, results) {
411
+ return { payload: { matchId, results } };
412
+ },
413
+ },
414
+ ackCloseMatchAfterUndo(draft) {
415
+ draft.closeMatchAfterUndo = false;
346
416
  },
347
417
  undoReversalAccrualsFailure(draft, action) {
348
- draft.undoReversalStatus = {
418
+ draft.undoReversalStatusByMatchId[action.payload.matchId] = {
349
419
  fetchState: 'Error',
350
420
  error: action.payload.status,
351
421
  };
@@ -360,5 +430,5 @@ const expenseAutomationActualMatchingView = createSlice({
360
430
  });
361
431
  },
362
432
  });
363
- export const { 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;
433
+ 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;
364
434
  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
  },