@zeniai/client-epic-state 5.2.22-betaAM2 → 5.2.22-betaRD0

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 (78) hide show
  1. package/lib/entity/jeSchedules/jeSchedulesPayload.d.ts +0 -24
  2. package/lib/entity/jeSchedules/jeSchedulesPayload.js +0 -40
  3. package/lib/entity/jeSchedules/jeSchedulesState.d.ts +0 -26
  4. package/lib/epic.d.ts +2 -6
  5. package/lib/epic.js +2 -10
  6. package/lib/esm/entity/jeSchedules/jeSchedulesPayload.js +0 -40
  7. package/lib/esm/epic.js +2 -10
  8. package/lib/esm/index.js +4 -8
  9. package/lib/esm/reducer.js +0 -3
  10. package/lib/esm/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +11 -28
  11. package/lib/esm/view/expenseAutomationView/epics/common/refreshExpenseAutomationCurrentTabEpic.js +1 -9
  12. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.js +45 -7
  13. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/oneTimeAccrualActionsEpic.js +13 -6
  14. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/prefetchOtherJeSchedulesTabEpic.js +35 -0
  15. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +0 -5
  16. package/lib/esm/view/expenseAutomationView/expenseAutomationViewSelector.js +7 -22
  17. package/lib/esm/view/expenseAutomationView/payload/jeSchedulesPayload.js +1 -1
  18. package/lib/esm/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +31 -7
  19. package/lib/esm/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +1 -3
  20. package/lib/esm/view/expenseAutomationView/types/jeSchedulesViewState.js +1 -5
  21. package/lib/index.d.ts +6 -11
  22. package/lib/index.js +49 -73
  23. package/lib/reducer.d.ts +0 -1
  24. package/lib/reducer.js +0 -3
  25. package/lib/rootStateTypes.d.ts +0 -2
  26. package/lib/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.d.ts +1 -2
  27. package/lib/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +11 -28
  28. package/lib/view/expenseAutomationView/epics/common/refreshExpenseAutomationCurrentTabEpic.d.ts +1 -2
  29. package/lib/view/expenseAutomationView/epics/common/refreshExpenseAutomationCurrentTabEpic.js +1 -9
  30. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.d.ts +1 -1
  31. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.js +45 -7
  32. package/lib/view/expenseAutomationView/epics/jeSchedule/oneTimeAccrualActionsEpic.d.ts +2 -2
  33. package/lib/view/expenseAutomationView/epics/jeSchedule/oneTimeAccrualActionsEpic.js +12 -5
  34. package/lib/view/expenseAutomationView/epics/jeSchedule/prefetchOtherJeSchedulesTabEpic.d.ts +12 -0
  35. package/lib/view/expenseAutomationView/epics/jeSchedule/prefetchOtherJeSchedulesTabEpic.js +39 -0
  36. package/lib/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.d.ts +1 -2
  37. package/lib/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +0 -5
  38. package/lib/view/expenseAutomationView/expenseAutomationViewSelector.js +7 -22
  39. package/lib/view/expenseAutomationView/payload/jeSchedulesPayload.d.ts +54 -1
  40. package/lib/view/expenseAutomationView/payload/jeSchedulesPayload.js +3 -3
  41. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.d.ts +13 -8
  42. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +33 -9
  43. package/lib/view/expenseAutomationView/selectorTypes/expenseAutomationViewSelectorTypes.d.ts +0 -2
  44. package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.d.ts +2 -0
  45. package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +1 -3
  46. package/lib/view/expenseAutomationView/types/jeSchedulesViewState.d.ts +3 -1
  47. package/lib/view/expenseAutomationView/types/jeSchedulesViewState.js +1 -5
  48. package/package.json +1 -1
  49. package/lib/esm/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingEpic.js +0 -46
  50. package/lib/esm/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingPageEpic.js +0 -7
  51. package/lib/esm/view/expenseAutomationView/epics/actualMatching/matchAndReverseAccrualsEpic.js +0 -50
  52. package/lib/esm/view/expenseAutomationView/epics/actualMatching/searchMatchAccrualsEpic.js +0 -44
  53. package/lib/esm/view/expenseAutomationView/epics/actualMatching/searchMatchTransactionsEpic.js +0 -44
  54. package/lib/esm/view/expenseAutomationView/payload/actualMatchingPayload.js +0 -60
  55. package/lib/esm/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +0 -260
  56. package/lib/esm/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.js +0 -1
  57. package/lib/esm/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +0 -196
  58. package/lib/esm/view/expenseAutomationView/types/actualMatchingViewState.js +0 -22
  59. package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingEpic.d.ts +0 -9
  60. package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingEpic.js +0 -50
  61. package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingPageEpic.d.ts +0 -5
  62. package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingPageEpic.js +0 -11
  63. package/lib/view/expenseAutomationView/epics/actualMatching/matchAndReverseAccrualsEpic.d.ts +0 -8
  64. package/lib/view/expenseAutomationView/epics/actualMatching/matchAndReverseAccrualsEpic.js +0 -54
  65. package/lib/view/expenseAutomationView/epics/actualMatching/searchMatchAccrualsEpic.d.ts +0 -8
  66. package/lib/view/expenseAutomationView/epics/actualMatching/searchMatchAccrualsEpic.js +0 -48
  67. package/lib/view/expenseAutomationView/epics/actualMatching/searchMatchTransactionsEpic.d.ts +0 -8
  68. package/lib/view/expenseAutomationView/epics/actualMatching/searchMatchTransactionsEpic.js +0 -48
  69. package/lib/view/expenseAutomationView/payload/actualMatchingPayload.d.ts +0 -72
  70. package/lib/view/expenseAutomationView/payload/actualMatchingPayload.js +0 -65
  71. package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.d.ts +0 -53
  72. package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +0 -264
  73. package/lib/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.d.ts +0 -59
  74. package/lib/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.js +0 -2
  75. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.d.ts +0 -11
  76. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +0 -208
  77. package/lib/view/expenseAutomationView/types/actualMatchingViewState.d.ts +0 -81
  78. package/lib/view/expenseAutomationView/types/actualMatchingViewState.js +0 -27
@@ -3,7 +3,6 @@ import { filter, switchMap } from 'rxjs/operators';
3
3
  import { convertToPeriod, toAbsoluteDay, toMonthYearPeriodId, } from '../../../../commonStateTypes/timePeriod';
4
4
  import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
5
5
  import { fetchAllExpenseAutomationTabs } from '../../expenseAutomationViewReducer';
6
- import { fetchActualMatchingPage } from '../../reducers/actualMatchingViewReducer';
7
6
  import { fetchFluxAnalysisView } from '../../reducers/fluxAnalysisViewReducer';
8
7
  import { fetchJeSchedulesPage } from '../../reducers/jeSchedulesViewReducer';
9
8
  import { fetchMissingReceipts } from '../../reducers/missingReceiptsViewReducer';
@@ -32,14 +31,11 @@ export const fetchAllExpenseAutomationTabsEpic = (actions$, state$) => actions$.
32
31
  }
33
32
  if (cacheOverride === true) {
34
33
  const actions = [];
35
- const selectedPageTab = state.expenseAutomationJESchedulesViewState.uiState.selectedPageTab;
36
34
  actions.push(fetchTransactionCategorizationView(selectedTransactionCategorizationTab, { start: timePeriodStart, end: timePeriodEnd }, cacheOverride, false, undefined, undefined, refreshViewInBackground, false), fetchMissingReceipts({ start: timePeriodStart, end: timePeriodEnd }, {
37
35
  amount: 75.0,
38
36
  currencyCode: 'USD',
39
37
  currencySymbol: '$',
40
- }, cacheOverride, refreshViewInBackground), fetchFluxAnalysisView(selectedPeriod, cacheOverride, refreshViewInBackground, null), selectedPageTab === 'actual_matching'
41
- ? fetchActualMatchingPage({ start: timePeriodStart, end: timePeriodEnd }, cacheOverride)
42
- : fetchJeSchedulesPage({ start: timePeriodStart, end: timePeriodEnd }, cacheOverride));
38
+ }, cacheOverride, refreshViewInBackground), fetchFluxAnalysisView(selectedPeriod, cacheOverride, refreshViewInBackground, null), fetchJeSchedulesPage({ start: timePeriodStart, end: timePeriodEnd }, cacheOverride));
43
39
  if (reconciliationEnabled) {
44
40
  actions.push(fetchReconciliation(selectedPeriod, undefined, cacheOverride, refreshViewInBackground));
45
41
  }
@@ -65,29 +61,16 @@ export const fetchAllExpenseAutomationTabsEpic = (actions$, state$) => actions$.
65
61
  fetchActions.push(fetchFluxAnalysisView(selectedPeriod, cacheOverride, refreshViewInBackground, null));
66
62
  }
67
63
  // The two JE tabs cache separately, so only the tab actually being shown decides whether a
68
- // fetch is still needed. Actual Matching is a third tab on the same page with its own
69
- // endpoint and cache slot.
70
- const selectedPageTab = state.expenseAutomationJESchedulesViewState.uiState.selectedPageTab;
71
- if (selectedPageTab === 'actual_matching') {
72
- const actualMatchingPeriodKeys = state.expenseAutomationActualMatchingViewState.accrualKeysByPeriod[monthYearPeriodId];
73
- if (state.expenseAutomationActualMatchingViewState.fetchState !==
74
- 'In-Progress' &&
75
- (!state.expenseAutomationActualMatchingViewState.hasValidState() ||
76
- actualMatchingPeriodKeys == null)) {
77
- fetchActions.push(fetchActualMatchingPage({ start: timePeriodStart, end: timePeriodEnd }, cacheOverride));
78
- }
79
- }
80
- else {
81
- const jeSchedulesPeriodTabKey = getJESchedulePeriodTabKey(selectedPageTab, monthYearPeriodId);
82
- if (state.expenseAutomationJESchedulesViewState.fetchState !==
83
- 'In-Progress' &&
84
- (!state.expenseAutomationJESchedulesViewState.hasValidState() ||
85
- state.expenseAutomationJESchedulesViewState
86
- .jeScheduleTransactionKeysByPeriodAndTab[jeSchedulesPeriodTabKey] == null ||
87
- state.expenseAutomationJESchedulesViewState
88
- .failedJeScheduleTransactionKeysByPeriodAndTab[jeSchedulesPeriodTabKey] == null)) {
89
- fetchActions.push(fetchJeSchedulesPage({ start: timePeriodStart, end: timePeriodEnd }, cacheOverride));
90
- }
64
+ // fetch is still needed.
65
+ const jeSchedulesPeriodTabKey = getJESchedulePeriodTabKey(state.expenseAutomationJESchedulesViewState.uiState.selectedPageTab, monthYearPeriodId);
66
+ if (state.expenseAutomationJESchedulesViewState.fetchState !==
67
+ 'In-Progress' &&
68
+ (!state.expenseAutomationJESchedulesViewState.hasValidState() ||
69
+ state.expenseAutomationJESchedulesViewState
70
+ .jeScheduleTransactionKeysByPeriodAndTab[jeSchedulesPeriodTabKey] == null ||
71
+ state.expenseAutomationJESchedulesViewState
72
+ .failedJeScheduleTransactionKeysByPeriodAndTab[jeSchedulesPeriodTabKey] == null)) {
73
+ fetchActions.push(fetchJeSchedulesPage({ start: timePeriodStart, end: timePeriodEnd }, cacheOverride));
91
74
  }
92
75
  if (state.expenseAutomationReconciliationViewState.fetchState !==
93
76
  'In-Progress' &&
@@ -4,7 +4,6 @@ import { convertToPeriod, toAbsoluteDay, } from '../../../../commonStateTypes/ti
4
4
  import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
5
5
  import { fetchCompanyMonthEndReportTemplates, fetchCompanyMonthEndReportView, } from '../../../companyMonthEndReportView/companyMonthEndReportViewReducer';
6
6
  import { refreshExpenseAutomationCurrentTab } from '../../expenseAutomationViewReducer';
7
- import { fetchActualMatchingPage } from '../../reducers/actualMatchingViewReducer';
8
7
  import { fetchFluxAnalysisView } from '../../reducers/fluxAnalysisViewReducer';
9
8
  import { fetchJeSchedulesPage } from '../../reducers/jeSchedulesViewReducer';
10
9
  import { fetchMissingReceipts } from '../../reducers/missingReceiptsViewReducer';
@@ -39,17 +38,10 @@ export const refreshExpenseAutomationCurrentTabEpic = (actions$, state$) => acti
39
38
  return from([
40
39
  fetchFluxAnalysisView(selectedPeriod, true, true, null),
41
40
  ]);
42
- case 'je_schedules': {
43
- const selectedPageTab = state.expenseAutomationJESchedulesViewState.uiState.selectedPageTab;
44
- if (selectedPageTab === 'actual_matching') {
45
- return from([
46
- fetchActualMatchingPage({ start: timePeriodStart, end: timePeriodEnd }, true),
47
- ]);
48
- }
41
+ case 'je_schedules':
49
42
  return from([
50
43
  fetchJeSchedulesPage({ start: timePeriodStart, end: timePeriodEnd }, true),
51
44
  ]);
52
- }
53
45
  case 'month_end_insights':
54
46
  return from([
55
47
  fetchCompanyMonthEndReportView(true, companyId, currentTenant.tenantId, period.end, true),
@@ -7,6 +7,7 @@ import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
7
7
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
8
8
  import { clearAll } from '../../../../rootActions';
9
9
  import { date } from '../../../../zeniDayJS';
10
+ import { SERVICE_MONTH_FORMAT, } from '../../payload/jeSchedulesPayload';
10
11
  import { fetchJeSchedules, fetchJeSchedulesFailure, fetchJeSchedulesSuccess, initializeJeScheduleLocalData, } from '../../reducers/jeSchedulesViewReducer';
11
12
  export const fetchJeSchedulesEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchJeSchedules.match), mergeMap((action) => {
12
13
  const state = state$.value;
@@ -14,21 +15,58 @@ export const fetchJeSchedulesEpic = (actions$, state$, zeniAPI) => actions$.pipe
14
15
  const { period, refreshViewInBackground } = action.payload;
15
16
  const currentTenant = getCurrentTenant(state);
16
17
  const selectedPeriod = selectedPeriodByTenantId[currentTenant.tenantId];
17
- const { selectedPageTab } = state.expenseAutomationJESchedulesViewState.uiState;
18
+ // The prefetch names the tab it is warming; everything else means the visible one. Reading
19
+ // this from state unconditionally would make the prefetch fetch the visible tab's window and
20
+ // then overwrite its cache with the result.
21
+ const selectedPageTab = action.payload.selectedPageTab ??
22
+ state.expenseAutomationJESchedulesViewState.uiState.selectedPageTab;
18
23
  const isClosePeriodTab = selectedPageTab === 'schedules';
19
24
  const existingEndDate = toAbsoluteDay(date(toString(period.start))
20
25
  .subtract(1, 'month')
21
26
  .endOf('month')
22
27
  .format(DEFAULT_DATE_FORMAT));
28
+ // Existing Schedules looks at months already closed, so it stops at the previous month end;
29
+ // Review Schedules is closing the selected month and runs to its end.
30
+ const endDate = isClosePeriodTab || existingEndDate == null
31
+ ? toString(period.end)
32
+ : toString(existingEndDate);
33
+ // Accrued rows are windowed on the schedule's own start_date rather than a transaction date,
34
+ // so they get their own bounds. Review wants the month being closed; Existing wants the year
35
+ // up to endDate, snapped to the 1st — the same window the server used to compute for itself.
36
+ const accruedStartDate = isClosePeriodTab
37
+ ? toString(period.start)
38
+ : date(endDate)
39
+ .subtract(1, 'year')
40
+ .startOf('month')
41
+ .format(DEFAULT_DATE_FORMAT);
23
42
  const queryParam = {
24
- start_date: toString(period.start),
25
- end_date: isClosePeriodTab || existingEndDate == null
26
- ? toString(period.end)
27
- : toString(existingEndDate),
43
+ base_transaction_end_date: endDate,
44
+ end_date: endDate,
45
+ accrued_start_date: accruedStartDate,
46
+ accrued_end_date: endDate,
28
47
  include_accrued: true,
29
48
  ...(isClosePeriodTab
30
- ? { close_period_start_date: toString(period.start) }
31
- : {}),
49
+ ? {
50
+ // Both types share the close month, so one bound covers them. Existing sends none,
51
+ // which is what keeps the differing server defaults — a year back for prepaid, five
52
+ // for fixed assets.
53
+ base_transaction_start_date: toString(period.start),
54
+ close_period_start_date: toString(period.start),
55
+ one_time_accrual_service_month: date(toString(period.start)).format(SERVICE_MONTH_FORMAT),
56
+ // Empty means no status filter: the month being closed is reviewed whatever state
57
+ // each accrual is in.
58
+ one_time_accrual_statuses: [],
59
+ filter_failed_je_postings_by_date: true,
60
+ }
61
+ : {
62
+ one_time_accrual_service_month_start: date(accruedStartDate).format(SERVICE_MONTH_FORMAT),
63
+ one_time_accrual_service_month_end: date(endDate).format(SERVICE_MONTH_FORMAT),
64
+ // Existing lists what actually reached the books.
65
+ one_time_accrual_statuses: ['posted'],
66
+ // A failed posting from an earlier month still needs to surface here, so this list
67
+ // is deliberately not windowed.
68
+ filter_failed_je_postings_by_date: false,
69
+ }),
32
70
  };
33
71
  return zeniAPI
34
72
  .getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/expenses_automation/je_schedules?query=${encodeURIComponent(JSON.stringify(queryParam))}`)
@@ -6,9 +6,9 @@ import { updateJEOneTimeAccruals } from '../../../../entity/jeSchedules/jeSchedu
6
6
  import { getJEOneTimeAccrualByKey } from '../../../../entity/jeSchedules/jeSchedulesSelector';
7
7
  import { openSnackbar } from '../../../../entity/snackbar/snackbarReducer';
8
8
  import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
9
- import { clearAll } from '../../../../rootActions';
10
9
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
11
- import { fetchJeSchedules, ignoreOneTimeAccrual, oneTimeAccrualActionFailure, oneTimeAccrualActionSuccess, promoteOneTimeAccrual, revertOneTimeAccrual, } from '../../reducers/jeSchedulesViewReducer';
10
+ import { clearAll } from '../../../../rootActions';
11
+ import { fetchJeSchedules, ignoreOneTimeAccrual, oneTimeAccrualActionFailure, oneTimeAccrualActionSuccess, promoteOneTimeAccrual, removeJeOneTimeAccrualLocalData, revertOneTimeAccrual, } from '../../reducers/jeSchedulesViewReducer';
12
12
  /**
13
13
  * The page the accrual came from, so a promoted or ignored row lands in the right bucket. Guarded
14
14
  * because convertToPeriod destructures: an unset period would throw and take the epic subscription
@@ -50,8 +50,11 @@ messageSection, accrualId, message, status) {
50
50
  }
51
51
  export const promoteOneTimeAccrualEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(promoteOneTimeAccrual.match), mergeMap((action) => {
52
52
  const state = state$.value;
53
- const { accrualId, amount, edits } = action.payload;
53
+ const { accrualId, amount } = action.payload;
54
54
  const accrual = getJEOneTimeAccrualByKey(generateJEOneTimeAccrualKey(accrualId), state.jeSchedulesState);
55
+ // The detail form writes what the user changed here as they change it. The list's inline
56
+ // Post has no form, so it finds nothing and every value below falls back to the stored draft.
57
+ const edits = state.expenseAutomationJESchedulesViewState.oneTimeAccrualLocalDataById[accrualId];
55
58
  // An edited value wins; anything untouched falls back to the stored draft, which is what the
56
59
  // list's inline Post sends.
57
60
  const debitAccountId = edits?.jeDebitAccountId ?? accrual?.jeDebitAccountId;
@@ -65,8 +68,7 @@ export const promoteOneTimeAccrualEpic = (actions$, state$, zeniAPI) => actions$
65
68
  // that never happens.
66
69
  // Untouched falls back to the stored policy, so posting without editing cannot silently
67
70
  // flip a scheduled reversal to on-bill-match.
68
- const reverseNextMonth = edits?.reverseNextMonth ??
69
- accrual?.reversalPolicy === 'scheduled_date';
71
+ const reverseNextMonth = edits?.reverseNextMonth ?? accrual?.reversalPolicy === 'scheduled_date';
70
72
  const expectedReversalDate = edits?.expectedReversalDate ?? accrual?.expectedReversalDate;
71
73
  const debitClassId = edits?.jeDebitAccountingClassId ?? accrual?.jeDebitAccountingClassId;
72
74
  const debitClassName = edits?.jeDebitAccountingClassName ??
@@ -76,7 +78,9 @@ export const promoteOneTimeAccrualEpic = (actions$, state$, zeniAPI) => actions$
76
78
  memo: edits?.memo ?? accrual?.memo ?? '',
77
79
  je_debit_account_id: debitAccountId,
78
80
  je_credit_account_id: creditAccountId,
79
- ...(debitClassId != null ? { je_debit_accounting_class_id: debitClassId } : {}),
81
+ ...(debitClassId != null
82
+ ? { je_debit_accounting_class_id: debitClassId }
83
+ : {}),
80
84
  ...(debitClassName != null
81
85
  ? { je_debit_accounting_class_name: debitClassName }
82
86
  : {}),
@@ -96,6 +100,9 @@ export const promoteOneTimeAccrualEpic = (actions$, state$, zeniAPI) => actions$
96
100
  return from([
97
101
  updateJEOneTimeAccruals([response.data.accrual]),
98
102
  oneTimeAccrualActionSuccess({ accrualId }),
103
+ // The edits are now on the server copy; leaving them would re-apply them to a
104
+ // later promote of the same accrual.
105
+ removeJeOneTimeAccrualLocalData({ accrualId }),
99
106
  openSnackbar({
100
107
  messageSection: 'save_schedule',
101
108
  messageText: 'success',
@@ -0,0 +1,35 @@
1
+ import { EMPTY, of } from 'rxjs';
2
+ import { filter, mergeMap } from 'rxjs/operators';
3
+ import { toMonthYearPeriodId } from '../../../../commonStateTypes/timePeriod';
4
+ import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
5
+ import { fetchJeSchedules, fetchJeSchedulesPage, } from '../../reducers/jeSchedulesViewReducer';
6
+ import { ALL_JE_PAGE_TABS, getJESchedulePeriodTabKey, } from '../../types/jeSchedulesViewState';
7
+ /**
8
+ * Warms the tab the user is not looking at, so switching to it renders rows instead of a skeleton.
9
+ *
10
+ * Listens for `fetchJeSchedulesPage` rather than `fetchJeSchedules` on purpose: the prefetch emits
11
+ * `fetchJeSchedules`, so listening for that would make this epic feed itself.
12
+ */
13
+ export const prefetchOtherJeSchedulesTabEpic = (actions$, state$) => actions$.pipe(filter(fetchJeSchedulesPage.match), mergeMap((action) => {
14
+ const state = state$.value;
15
+ const { period } = action.payload;
16
+ const { selectedPageTab } = state.expenseAutomationJESchedulesViewState.uiState;
17
+ const otherPageTab = ALL_JE_PAGE_TABS.find((pageTab) => pageTab !== selectedPageTab);
18
+ if (otherPageTab == null) {
19
+ return EMPTY;
20
+ }
21
+ const currentTenant = getCurrentTenant(state);
22
+ const selectedPeriod = state.expenseAutomationViewState.selectedPeriodByTenantId[currentTenant.tenantId];
23
+ if (selectedPeriod == null) {
24
+ return EMPTY;
25
+ }
26
+ // Already warm for this period: without this check every tab switch would refetch the tab
27
+ // just left, since a switch also fires fetchJeSchedulesPage.
28
+ const otherPeriodTabKey = getJESchedulePeriodTabKey(otherPageTab, toMonthYearPeriodId(selectedPeriod));
29
+ const isOtherTabAlreadyLoaded = state.expenseAutomationJESchedulesViewState
30
+ .jeScheduleTransactionKeysByPeriodAndTab[otherPeriodTabKey] != null;
31
+ if (isOtherTabAlreadyLoaded) {
32
+ return EMPTY;
33
+ }
34
+ return of(fetchJeSchedules(period, true, otherPageTab));
35
+ }));
@@ -2,7 +2,6 @@ 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
6
  export const switchJESchedulesTabEpic = (actions$, state$) => actions$.pipe(filter(updateJESchedulesUIState.match), filter((action) => action.payload.selectedPageTab != null), mergeMap(() => {
8
7
  const state = state$.value;
@@ -25,9 +24,5 @@ export const switchJESchedulesTabEpic = (actions$, state$) => actions$.pipe(filt
25
24
  if (timePeriodStart == null || timePeriodEnd == null) {
26
25
  return EMPTY;
27
26
  }
28
- const { selectedPageTab } = state.expenseAutomationJESchedulesViewState.uiState;
29
- if (selectedPageTab === 'actual_matching') {
30
- return of(fetchActualMatchingPage({ start: timePeriodStart, end: timePeriodEnd }, true));
31
- }
32
27
  return of(fetchJeSchedulesPage({ start: timePeriodStart, end: timePeriodEnd }, true));
33
28
  }));
@@ -4,7 +4,6 @@ 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';
8
7
  import { getExpenseAutomationFluxAnalysisView } from './selectors/fluxAnalysisViewSelector';
9
8
  import { getAllSteps } from './selectors/getAllStepsHelper';
10
9
  import { getExpenseAutomationJESchedulesView } from './selectors/jeSchedulesViewSelector';
@@ -22,7 +21,7 @@ const isPreviousStepDisabled = (selectedTransactionCategorizationTab, allSteps,
22
21
  return allSteps[stepIndex - 1].isDisabled;
23
22
  };
24
23
  export function getExpenseAutomationView(state) {
25
- const { expenseAutomationViewState, tenantState, expenseAutomationMissingReceiptsViewState, expenseAutomationJESchedulesViewState, expenseAutomationActualMatchingViewState, expenseAutomationTransactionsViewState, expenseAutomationReconciliationViewState, monthEndCloseChecksState, companyState, } = state;
24
+ const { expenseAutomationViewState, tenantState, expenseAutomationMissingReceiptsViewState, expenseAutomationJESchedulesViewState, expenseAutomationTransactionsViewState, expenseAutomationReconciliationViewState, monthEndCloseChecksState, companyState, } = state;
26
25
  const { loggedInUser, currentTenantId } = tenantState;
27
26
  const currentTenant = getCurrentTenant(state);
28
27
  const { currentSelectedView, selectedPeriodByTenantId } = expenseAutomationViewState;
@@ -43,7 +42,6 @@ export function getExpenseAutomationView(state) {
43
42
  const reconciliation = getExpenseAutomationReconciliationView(state);
44
43
  const expenseAutomationTransactionsView = getExpenseAutomationTransactionView(state);
45
44
  const jeSchedules = getExpenseAutomationJESchedulesView(state);
46
- const actualMatching = getExpenseAutomationActualMatchingView(state);
47
45
  switch (currentSelectedView) {
48
46
  case 'reconciliation':
49
47
  currentSelectedViewFetchState = reconciliation.fetchStatus;
@@ -56,23 +54,12 @@ export function getExpenseAutomationView(state) {
56
54
  fluxAnalysis.refreshStatus;
57
55
  break;
58
56
  case 'je_schedules':
59
- if (expenseAutomationJESchedulesViewState.uiState.selectedPageTab ===
60
- 'actual_matching') {
61
- currentSelectedViewFetchState = {
62
- fetchState: actualMatching.fetchState,
63
- error: actualMatching.error,
64
- };
65
- currentSelectedViewBackgroundRefreshFetchState =
66
- actualMatching.refreshStatus;
67
- }
68
- else {
69
- currentSelectedViewFetchState = {
70
- fetchState: jeSchedules.fetchState,
71
- error: jeSchedules.error,
72
- };
73
- currentSelectedViewBackgroundRefreshFetchState =
74
- jeSchedules.refreshStatus;
75
- }
57
+ currentSelectedViewFetchState = {
58
+ fetchState: jeSchedules.fetchState,
59
+ error: jeSchedules.error,
60
+ };
61
+ currentSelectedViewBackgroundRefreshFetchState =
62
+ jeSchedules.refreshStatus;
76
63
  break;
77
64
  case 'missing_receipts':
78
65
  currentSelectedViewFetchState = {
@@ -104,7 +91,6 @@ export function getExpenseAutomationView(state) {
104
91
  const fetchStateVariables = [
105
92
  expenseAutomationMissingReceiptsViewState,
106
93
  expenseAutomationJESchedulesViewState,
107
- expenseAutomationActualMatchingViewState,
108
94
  expenseAutomationTransactionsView,
109
95
  expenseAutomationReconciliationViewState,
110
96
  ];
@@ -190,7 +176,6 @@ export function getExpenseAutomationView(state) {
190
176
  ]);
191
177
  return {
192
178
  jeSchedules,
193
- actualMatching,
194
179
  transactionView: expenseAutomationTransactionsView,
195
180
  missingReceipts,
196
181
  reconciliation,
@@ -2,7 +2,7 @@ import { DEFAULT_DATE_FORMAT } from '../../../commonStateTypes/timePeriod';
2
2
  import { getClassList } from '../../classList/classListSelector';
3
3
  import { mapScheduleTransactionToBaseScheduleTransactionPayload, mapVendorToVendorPayload, } from '../../scheduleView/scheduleDetailView/scheduleDetailPayload';
4
4
  const BALANCE_SHEET_CLASS_LABEL = 'balance_sheet';
5
- const SERVICE_MONTH_FORMAT = 'YYYY-MM';
5
+ export const SERVICE_MONTH_FORMAT = 'YYYY-MM';
6
6
  /**
7
7
  * The credit line is the accrued liability, which always sits on the balance sheet — the
8
8
  * recurring accrued path resolves the same class the same way (see
@@ -66,6 +66,7 @@ export const initialState = {
66
66
  ignoreStatusById: {},
67
67
  revertStatusById: {},
68
68
  jeScheduleLocalDataById: {},
69
+ oneTimeAccrualLocalDataById: {},
69
70
  oneTimeAccrualActionStatusById: {},
70
71
  oneTimeAccrualFetchStatusById: {},
71
72
  uiState: {
@@ -127,11 +128,15 @@ const expenseAutomationJESchedulesView = createSlice({
127
128
  draft.error = undefined;
128
129
  }
129
130
  },
130
- prepare(period, refreshViewInBackground = false) {
131
+ // `selectedPageTab` is for the prefetch, which fetches the tab the user is NOT on. Everyone
132
+ // else omits it and the epic uses the selected tab -- passing the visible tab explicitly
133
+ // would be the same thing.
134
+ prepare(period, refreshViewInBackground = false, selectedPageTab) {
131
135
  return {
132
136
  payload: {
133
137
  period,
134
138
  refreshViewInBackground,
139
+ selectedPageTab,
135
140
  },
136
141
  };
137
142
  },
@@ -399,6 +404,24 @@ const expenseAutomationJESchedulesView = createSlice({
399
404
  const { scheduledJournalEntryID } = action.payload;
400
405
  delete draft.jeScheduleLocalDataById[scheduledJournalEntryID];
401
406
  },
407
+ /**
408
+ * Merge, not replace: the detail form reports one field at a time, so a full overwrite would
409
+ * drop every other edit on each keystroke.
410
+ */
411
+ updateJeOneTimeAccrualLocalData(draft, action) {
412
+ const { accrualId, localData } = action.payload;
413
+ draft.oneTimeAccrualLocalDataById = {
414
+ ...draft.oneTimeAccrualLocalDataById,
415
+ [accrualId]: {
416
+ ...draft.oneTimeAccrualLocalDataById[accrualId],
417
+ ...localData,
418
+ },
419
+ };
420
+ },
421
+ /** Called when the detail page unmounts and after a successful promote. */
422
+ removeJeOneTimeAccrualLocalData(draft, action) {
423
+ delete draft.oneTimeAccrualLocalDataById[action.payload.accrualId];
424
+ },
402
425
  initializeAccountSettingsView(draft) {
403
426
  draft.accountSettings.localData = accountSettingsInitialState.localData;
404
427
  },
@@ -575,13 +598,14 @@ const expenseAutomationJESchedulesView = createSlice({
575
598
  };
576
599
  },
577
600
  /**
578
- * Promote applies the body and posts in one step, so it carries whatever the user edited on the
579
- * detail page. `edits` is absent when nothing was changed -- the epic then falls back to the
580
- * stored accrual, which is what the list's inline Post does.
601
+ * Promote applies the body and posts in one step. The edits it applies come from
602
+ * `oneTimeAccrualLocalDataById`, not from this action -- the list's inline Post has no form, so
603
+ * it writes no local data and the epic falls back to the stored accrual. `amount` stays on the
604
+ * action because both callers already have it and `OneTimeAccrualEdits` has no slot for it.
581
605
  */
582
606
  promoteOneTimeAccrual: {
583
- prepare(accrualId, amount, edits) {
584
- return { payload: { accrualId, amount, edits } };
607
+ prepare(accrualId, amount) {
608
+ return { payload: { accrualId, amount } };
585
609
  },
586
610
  reducer(draft, action) {
587
611
  draft.oneTimeAccrualActionStatusById[action.payload.accrualId] = {
@@ -685,7 +709,7 @@ const expenseAutomationJESchedulesView = createSlice({
685
709
  });
686
710
  },
687
711
  });
688
- export const { clearExpenseAutomationJESchedulesView, clearJeAccountSettingsLocalData, clearJeScheduleLocalData, clearSearchTransactionsForNewSchedule, createOneTimeAccrual, createOneTimeAccrualFailure, createOneTimeAccrualSuccess, ignoreOneTimeAccrual, oneTimeAccrualActionFailure, oneTimeAccrualActionSuccess, promoteOneTimeAccrual, revertJeSchedule, revertJeScheduleFailure, revertJeScheduleSuccess, revertOneTimeAccrual, fetchAccountSettingsListForAccountTypes, fetchJeSchedules, fetchJeSchedulesFailure, fetchJeSchedulesPage, fetchJeSchedulesSuccess, fetchOneTimeAccrual, fetchOneTimeAccrualFailure, fetchOneTimeAccrualSuccess, fetchRecommendationForAccountSettings, ignoreRecommendedJeSchedule, ignoreRecommendedJeScheduleFailure, ignoreRecommendedJeScheduleSuccess, initializeAccountSettingsView, initializeJeScheduleLocalData, initializeNewSchedule, removeFailedJeScheduleTransactionKey, removeJeScheduleLocalDataById, removeJeAccruedScheduleKey, removeJeScheduleTransactionKey, resetNewScheduleLocalData, retryJeSchedule, retryJeScheduleFailure, retryJeScheduleSuccess, saveAccountSettings, saveAccountSettingsFailure, saveAccountSettingsLocalData, saveAccountSettingsSuccess, saveNewSchedule, saveNewScheduleFailure, saveNewScheduleSuccess, searchTransactionsForNewSchedule, searchTransactionsForNewScheduleFailure, searchTransactionsForNewScheduleSuccess, toggleJECreatedGroupCollapsed, toggleJEPendingGroupCollapsed, updateAccountSettingsListForAccountTypes, updateAccountSettingsListForAccountTypesFailure, updateJESchedulesUIState, updateJEScheduleTypeFilter, updateJeScheduleLocalDataById, updateJeScheduleTransactionKeys, updateNewScheduleLocalData, updateRecommendationForAccountSettings, updateRecommendationForAccountSettingsFailure, } = expenseAutomationJESchedulesView.actions;
712
+ export const { clearExpenseAutomationJESchedulesView, clearJeAccountSettingsLocalData, clearJeScheduleLocalData, clearSearchTransactionsForNewSchedule, createOneTimeAccrual, createOneTimeAccrualFailure, createOneTimeAccrualSuccess, ignoreOneTimeAccrual, oneTimeAccrualActionFailure, oneTimeAccrualActionSuccess, promoteOneTimeAccrual, revertJeSchedule, revertJeScheduleFailure, revertJeScheduleSuccess, revertOneTimeAccrual, fetchAccountSettingsListForAccountTypes, fetchJeSchedules, fetchJeSchedulesFailure, fetchJeSchedulesPage, fetchJeSchedulesSuccess, fetchOneTimeAccrual, fetchOneTimeAccrualFailure, fetchOneTimeAccrualSuccess, fetchRecommendationForAccountSettings, ignoreRecommendedJeSchedule, ignoreRecommendedJeScheduleFailure, ignoreRecommendedJeScheduleSuccess, initializeAccountSettingsView, initializeJeScheduleLocalData, initializeNewSchedule, removeFailedJeScheduleTransactionKey, removeJeOneTimeAccrualLocalData, removeJeScheduleLocalDataById, removeJeAccruedScheduleKey, removeJeScheduleTransactionKey, resetNewScheduleLocalData, retryJeSchedule, retryJeScheduleFailure, retryJeScheduleSuccess, saveAccountSettings, saveAccountSettingsFailure, saveAccountSettingsLocalData, saveAccountSettingsSuccess, saveNewSchedule, saveNewScheduleFailure, saveNewScheduleSuccess, searchTransactionsForNewSchedule, searchTransactionsForNewScheduleFailure, searchTransactionsForNewScheduleSuccess, toggleJECreatedGroupCollapsed, toggleJEPendingGroupCollapsed, updateAccountSettingsListForAccountTypes, updateAccountSettingsListForAccountTypesFailure, updateJESchedulesUIState, updateJEScheduleTypeFilter, updateJeOneTimeAccrualLocalData, updateJeScheduleLocalDataById, updateJeScheduleTransactionKeys, updateNewScheduleLocalData, updateRecommendationForAccountSettings, updateRecommendationForAccountSettingsFailure, } = expenseAutomationJESchedulesView.actions;
689
713
  export default expenseAutomationJESchedulesView.reducer;
690
714
  const doUpdateRecommendationForAccountSettings = (draft, payload) => {
691
715
  const { prepaidExpensesRecommendations, fixedAssetsRecommendations, accruedExpensesRecommendations, } = mapAccountTypeRecommendationPayloadToAccountRecommendationByType(payload);
@@ -367,9 +367,6 @@ export function getExpenseAutomationJESchedulesView(state) {
367
367
  // reports undefined rather than 0, so the UI can hide the badge instead of asserting "none".
368
368
  const scheduleCountsByTab = {};
369
369
  ALL_JE_PAGE_TABS.forEach((pageTab) => {
370
- if (pageTab === 'actual_matching') {
371
- return;
372
- }
373
370
  const periodTabKey = monthYearPeriodId != null
374
371
  ? getJESchedulePeriodTabKey(pageTab, monthYearPeriodId)
375
372
  : null;
@@ -477,5 +474,6 @@ export function getExpenseAutomationJEOneTimeAccrualDetailView(state, accrualId)
477
474
  fetchState: 'Not-Started',
478
475
  error: undefined,
479
476
  },
477
+ localData: state.expenseAutomationJESchedulesViewState.oneTimeAccrualLocalDataById[accrualId],
480
478
  };
481
479
  }
@@ -16,11 +16,7 @@ const JE_SCHEDULE_SORT_KEYS = [
16
16
  'memo',
17
17
  ];
18
18
  export const toJEScheduleSortKey = (v) => stringToUnion(v, JE_SCHEDULE_SORT_KEYS);
19
- const JE_PAGE_TABS = [
20
- 'schedules',
21
- 'actual_matching',
22
- 'existing_schedules',
23
- ];
19
+ const JE_PAGE_TABS = ['schedules', 'existing_schedules'];
24
20
  export const toJEPageTab = (v) => stringToUnion(v, JE_PAGE_TABS);
25
21
  export const ALL_JE_PAGE_TABS = JE_PAGE_TABS;
26
22
  export const getJESchedulePeriodTabKey = (selectedPageTab, monthYearPeriodId) => `${selectedPageTab}_${monthYearPeriodId}`;