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

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 +107 -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 +107 -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,48 @@ 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
+ }
74
+ function pruneLastUndoResultsForMatched(draft, matchId, results) {
75
+ const matchedIds = new Set(results.map((result) => result.accrualId));
76
+ if (matchedIds.size === 0) {
77
+ return;
78
+ }
79
+ const existing = draft.lastUndoResultsByMatchId[matchId];
80
+ if (existing == null) {
81
+ return;
82
+ }
83
+ const kept = existing.filter((result) => !matchedIds.has(result.accrualId));
84
+ if (kept.length === 0) {
85
+ delete draft.lastUndoResultsByMatchId[matchId];
86
+ return;
87
+ }
88
+ draft.lastUndoResultsByMatchId[matchId] = kept;
89
+ }
51
90
  function resetMatchModalSearches(draft) {
52
91
  draft.matchModal.transactionSearch = initialTransactionSearchState;
53
92
  draft.matchModal.accrualSearch = initialAccrualSearchState;
@@ -57,6 +96,7 @@ function resetMatchModalSearches(draft) {
57
96
  };
58
97
  draft.matchModal.lastBatchResults = undefined;
59
98
  draft.matchModal.openedFromMatchId = undefined;
99
+ draft.matchModal.didPinPrefillTransactions = false;
60
100
  draft.matchModal.pinnedTransactionResults = [];
61
101
  }
62
102
  const expenseAutomationActualMatchingView = createSlice({
@@ -81,6 +121,9 @@ const expenseAutomationActualMatchingView = createSlice({
81
121
  const { refreshViewInBackground } = action.payload;
82
122
  if (refreshViewInBackground) {
83
123
  draft.refreshStatus = { fetchState: 'In-Progress', error: undefined };
124
+ if (draft.fetchState === 'Not-Started') {
125
+ draft.fetchState = 'In-Progress';
126
+ }
84
127
  }
85
128
  else {
86
129
  draft.fetchState = 'In-Progress';
@@ -102,6 +145,10 @@ const expenseAutomationActualMatchingView = createSlice({
102
145
  draft.matchIdsByPeriod[periodId] = matchIds;
103
146
  if (refreshViewInBackground) {
104
147
  draft.refreshStatus = { fetchState: 'Completed', error: undefined };
148
+ if (draft.fetchState !== 'Completed') {
149
+ draft.fetchState = 'Completed';
150
+ draft.error = undefined;
151
+ }
105
152
  }
106
153
  else {
107
154
  draft.fetchState = 'Completed';
@@ -112,6 +159,10 @@ const expenseAutomationActualMatchingView = createSlice({
112
159
  const { status, refreshViewInBackground } = action.payload;
113
160
  if (refreshViewInBackground) {
114
161
  draft.refreshStatus = { fetchState: 'Error', error: status };
162
+ if (draft.fetchState !== 'Completed') {
163
+ draft.fetchState = 'Error';
164
+ draft.error = status;
165
+ }
115
166
  }
116
167
  else {
117
168
  draft.fetchState = 'Error';
@@ -197,10 +248,13 @@ const expenseAutomationActualMatchingView = createSlice({
197
248
  }
198
249
  },
199
250
  },
251
+ // Dismiss (Cancel / overlay). Store reset matches `clearMatchNewModal`;
252
+ // the actions stay distinct so confirm-success vs user-dismiss stay observable.
200
253
  closeMatchNewModal(draft) {
201
254
  draft.matchModal.isOpen = false;
202
255
  resetMatchModalSearches(draft);
203
256
  },
257
+ // Confirm-success / host reset. Same slice write as close; different action type.
204
258
  clearMatchNewModal(draft) {
205
259
  draft.matchModal.isOpen = false;
206
260
  resetMatchModalSearches(draft);
@@ -221,7 +275,7 @@ const expenseAutomationActualMatchingView = createSlice({
221
275
  draft.matchModal.transactionSearch.error = undefined;
222
276
  }
223
277
  },
224
- prepare({ query, pageToken, transactionType, transactionIntegrationId, }) {
278
+ prepare(query, pageToken, transactionType, transactionIntegrationId) {
225
279
  return {
226
280
  payload: {
227
281
  query,
@@ -240,7 +294,7 @@ const expenseAutomationActualMatchingView = createSlice({
240
294
  ...results,
241
295
  ];
242
296
  const byKey = new Map(merged.map((result) => [
243
- `${result.transactionId}:${result.lineId}`,
297
+ actualMatchingTransactionLineKey(result.transactionType, result.transactionIntegrationId, result.lineId),
244
298
  result,
245
299
  ]));
246
300
  draft.matchModal.transactionSearch.results = Array.from(byKey.values());
@@ -248,7 +302,8 @@ const expenseAutomationActualMatchingView = createSlice({
248
302
  else {
249
303
  draft.matchModal.transactionSearch.results = results;
250
304
  if (draft.matchModal.openedFromMatchId != null &&
251
- draft.matchModal.pinnedTransactionResults.length === 0) {
305
+ !draft.matchModal.didPinPrefillTransactions) {
306
+ draft.matchModal.didPinPrefillTransactions = true;
252
307
  draft.matchModal.pinnedTransactionResults = results;
253
308
  }
254
309
  }
@@ -278,7 +333,7 @@ const expenseAutomationActualMatchingView = createSlice({
278
333
  draft.matchModal.accrualSearch.error = undefined;
279
334
  }
280
335
  },
281
- prepare({ query, pageToken }) {
336
+ prepare(query, pageToken) {
282
337
  return { payload: { query, pageToken: pageToken ?? undefined } };
283
338
  },
284
339
  },
@@ -309,8 +364,17 @@ const expenseAutomationActualMatchingView = createSlice({
309
364
  error: undefined,
310
365
  };
311
366
  },
312
- prepare(params) {
313
- return { payload: params };
367
+ prepare(transactionId, transactionIntegrationId, transactionType, lineIds, transactionSyncToken, accrualIds) {
368
+ return {
369
+ payload: {
370
+ transactionId,
371
+ transactionIntegrationId,
372
+ transactionType,
373
+ lineIds,
374
+ transactionSyncToken,
375
+ accrualIds,
376
+ },
377
+ };
314
378
  },
315
379
  },
316
380
  matchAndReverseAccrualsSuccess(draft, action) {
@@ -319,6 +383,11 @@ const expenseAutomationActualMatchingView = createSlice({
319
383
  error: undefined,
320
384
  };
321
385
  draft.matchModal.lastBatchResults = action.payload.results;
386
+ if (action.payload.matchId != null) {
387
+ draft.lastMatchResultsByMatchId[action.payload.matchId] =
388
+ action.payload.results;
389
+ pruneLastUndoResultsForMatched(draft, action.payload.matchId, action.payload.results);
390
+ }
322
391
  },
323
392
  matchAndReverseAccrualsFailure(draft, action) {
324
393
  draft.matchModal.confirmStatus = {
@@ -327,25 +396,43 @@ const expenseAutomationActualMatchingView = createSlice({
327
396
  };
328
397
  },
329
398
  undoReversalAccruals: {
330
- reducer(draft) {
331
- draft.undoReversalStatus = {
399
+ reducer(draft, action) {
400
+ draft.undoReversalStatusByMatchId[action.payload.matchId] = {
332
401
  fetchState: 'In-Progress',
333
402
  error: undefined,
334
403
  };
335
404
  },
336
- prepare(params) {
337
- return { payload: params };
405
+ prepare(matchId, accrualIds) {
406
+ return { payload: { matchId, accrualIds } };
338
407
  },
339
408
  },
340
- undoReversalAccrualsSuccess(draft, action) {
341
- draft.undoReversalStatus = {
342
- fetchState: 'Completed',
343
- error: undefined,
344
- };
345
- void action;
409
+ undoReversalAccrualsSuccess: {
410
+ reducer(draft, action) {
411
+ const { matchId, results } = action.payload;
412
+ draft.undoReversalStatusByMatchId[matchId] = {
413
+ fetchState: 'Completed',
414
+ error: undefined,
415
+ };
416
+ const allUndone = results.length > 0 &&
417
+ results.every((result) => result.status === 'undone');
418
+ draft.closeMatchAfterUndo = allUndone;
419
+ pruneLastMatchResultsForUndone(draft, matchId, results);
420
+ if (allUndone) {
421
+ delete draft.lastUndoResultsByMatchId[matchId];
422
+ }
423
+ else {
424
+ draft.lastUndoResultsByMatchId[matchId] = results;
425
+ }
426
+ },
427
+ prepare(matchId, results) {
428
+ return { payload: { matchId, results } };
429
+ },
430
+ },
431
+ ackCloseMatchAfterUndo(draft) {
432
+ draft.closeMatchAfterUndo = false;
346
433
  },
347
434
  undoReversalAccrualsFailure(draft, action) {
348
- draft.undoReversalStatus = {
435
+ draft.undoReversalStatusByMatchId[action.payload.matchId] = {
349
436
  fetchState: 'Error',
350
437
  error: action.payload.status,
351
438
  };
@@ -360,5 +447,5 @@ const expenseAutomationActualMatchingView = createSlice({
360
447
  });
361
448
  },
362
449
  });
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;
450
+ 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
451
  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
  },