@zeniai/client-epic-state 5.2.40 → 5.2.41-beta2MM
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.
- package/lib/entity/actualMatching/actualMatchingPayload.d.ts +31 -0
- package/lib/entity/actualMatching/actualMatchingPayload.js +52 -0
- package/lib/entity/actualMatching/actualMatchingReducer.d.ts +11 -0
- package/lib/entity/actualMatching/actualMatchingReducer.js +42 -0
- package/lib/entity/actualMatching/actualMatchingSelector.d.ts +3 -0
- package/lib/entity/actualMatching/actualMatchingSelector.js +6 -0
- package/lib/entity/actualMatching/actualMatchingState.d.ts +23 -0
- package/lib/entity/actualMatching/actualMatchingState.js +2 -0
- package/lib/entity/actualMatching/actualMatchingTypes.d.ts +1 -0
- package/lib/entity/actualMatching/actualMatchingTypes.js +2 -0
- package/lib/entity/file/fileReducer.d.ts +8 -0
- package/lib/entity/file/fileReducer.js +10 -2
- package/lib/entity/invoicing/invoicingCommonPayload.d.ts +14 -1
- package/lib/entity/jeSchedules/jeSchedulesPayload.d.ts +31 -0
- package/lib/entity/jeSchedules/jeSchedulesPayload.js +53 -0
- package/lib/entity/jeSchedules/jeSchedulesState.d.ts +33 -0
- package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
- package/lib/entity/snackbar/snackbarTypes.js +3 -0
- package/lib/epic.d.ts +9 -1
- package/lib/epic.js +16 -0
- package/lib/esm/entity/actualMatching/actualMatchingPayload.js +48 -0
- package/lib/esm/entity/actualMatching/actualMatchingReducer.js +38 -0
- package/lib/esm/entity/actualMatching/actualMatchingSelector.js +3 -0
- package/lib/esm/entity/actualMatching/actualMatchingState.js +1 -0
- package/lib/esm/entity/actualMatching/actualMatchingTypes.js +1 -0
- package/lib/esm/entity/file/fileReducer.js +10 -2
- package/lib/esm/entity/jeSchedules/jeSchedulesPayload.js +53 -0
- package/lib/esm/entity/snackbar/snackbarTypes.js +3 -0
- package/lib/esm/epic.js +16 -0
- package/lib/esm/index.js +7 -3
- package/lib/esm/reducer.js +6 -0
- package/lib/esm/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +25 -12
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingEpic.js +46 -0
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingMatchEpic.js +36 -0
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingPageEpic.js +7 -0
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/ignoreActualMatchingSuggestionEpic.js +49 -0
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/matchAndReverseAccrualsEpic.js +108 -0
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/refreshActualMatchingList.js +16 -0
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/searchMatchAccrualsEpic.js +60 -0
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/searchMatchTransactionsEpic.js +74 -0
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/undoReversalAccrualsEpic.js +78 -0
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.js +13 -3
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/prefetchOtherJeSchedulesTabEpic.js +40 -15
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +19 -8
- package/lib/esm/view/expenseAutomationView/expenseAutomationViewReducer.js +2 -1
- package/lib/esm/view/expenseAutomationView/expenseAutomationViewSelector.js +24 -6
- package/lib/esm/view/expenseAutomationView/expenseAutomationViewState.js +4 -2
- package/lib/esm/view/expenseAutomationView/payload/actualMatchingPayload.js +68 -0
- package/lib/esm/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +497 -0
- package/lib/esm/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +2 -1
- package/lib/esm/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.js +1 -0
- package/lib/esm/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +432 -0
- package/lib/esm/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +4 -0
- package/lib/esm/view/expenseAutomationView/types/actualMatchingViewState.js +47 -0
- package/lib/esm/view/expenseAutomationView/types/jeSchedulesViewState.js +8 -1
- package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.js +1 -1
- package/lib/esm/view/invoicing/settingsView/epics/fetchInvoicingSettingsEpic.js +6 -0
- package/lib/esm/view/invoicing/settingsView/epics/saveInvoicingSettingsEpic.js +7 -0
- package/lib/esm/view/invoicing/settingsView/invoicingBrandingFormConfig.js +1 -1
- package/lib/esm/view/invoicing/settingsView/settingsViewSelector.js +5 -0
- package/lib/esm/view/invoicing/settingsView/submitInvoicingBrandingFormEpic.js +9 -0
- package/lib/index.d.ts +14 -5
- package/lib/index.js +87 -48
- package/lib/reducer.d.ts +2 -0
- package/lib/reducer.js +6 -0
- package/lib/rootStateTypes.d.ts +4 -0
- package/lib/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +25 -12
- package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingEpic.d.ts +9 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingEpic.js +50 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingMatchEpic.d.ts +12 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingMatchEpic.js +40 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingPageEpic.d.ts +5 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingPageEpic.js +11 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/ignoreActualMatchingSuggestionEpic.d.ts +10 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/ignoreActualMatchingSuggestionEpic.js +53 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/matchAndReverseAccrualsEpic.d.ts +9 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/matchAndReverseAccrualsEpic.js +112 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/refreshActualMatchingList.d.ts +3 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/refreshActualMatchingList.js +19 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/searchMatchAccrualsEpic.d.ts +9 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/searchMatchAccrualsEpic.js +64 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/searchMatchTransactionsEpic.d.ts +8 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/searchMatchTransactionsEpic.js +78 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/undoReversalAccrualsEpic.d.ts +9 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/undoReversalAccrualsEpic.js +82 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.d.ts +2 -1
- package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.js +13 -3
- package/lib/view/expenseAutomationView/epics/jeSchedule/prefetchOtherJeSchedulesTabEpic.d.ts +5 -1
- package/lib/view/expenseAutomationView/epics/jeSchedule/prefetchOtherJeSchedulesTabEpic.js +39 -14
- package/lib/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +18 -7
- package/lib/view/expenseAutomationView/expenseAutomationViewReducer.d.ts +2 -1
- package/lib/view/expenseAutomationView/expenseAutomationViewReducer.js +2 -1
- package/lib/view/expenseAutomationView/expenseAutomationViewSelector.js +24 -6
- package/lib/view/expenseAutomationView/expenseAutomationViewState.d.ts +2 -1
- package/lib/view/expenseAutomationView/expenseAutomationViewState.js +6 -3
- package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
- package/lib/view/expenseAutomationView/payload/actualMatchingPayload.d.ts +91 -0
- package/lib/view/expenseAutomationView/payload/actualMatchingPayload.js +74 -0
- package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.d.ts +88 -0
- package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +501 -0
- package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.d.ts +6 -5
- package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +2 -1
- package/lib/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.d.ts +88 -0
- package/lib/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.js +2 -0
- package/lib/view/expenseAutomationView/selectorTypes/expenseAutomationViewSelectorTypes.d.ts +11 -0
- package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.d.ts +40 -0
- package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +452 -0
- package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +4 -0
- package/lib/view/expenseAutomationView/types/actualMatchingViewState.d.ts +116 -0
- package/lib/view/expenseAutomationView/types/actualMatchingViewState.js +56 -0
- package/lib/view/expenseAutomationView/types/jeSchedulesViewState.d.ts +1 -1
- package/lib/view/expenseAutomationView/types/jeSchedulesViewState.js +8 -1
- package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.js +1 -1
- package/lib/view/invoicing/settingsView/epics/fetchInvoicingSettingsEpic.js +6 -0
- package/lib/view/invoicing/settingsView/epics/saveInvoicingSettingsEpic.js +7 -0
- package/lib/view/invoicing/settingsView/epics/settingsViewActionType.d.ts +2 -1
- package/lib/view/invoicing/settingsView/invoicingBrandingFormConfig.js +1 -1
- package/lib/view/invoicing/settingsView/settingsViewSelector.d.ts +8 -0
- package/lib/view/invoicing/settingsView/settingsViewSelector.js +5 -0
- package/lib/view/invoicing/settingsView/submitInvoicingBrandingFormEpic.d.ts +2 -1
- package/lib/view/invoicing/settingsView/submitInvoicingBrandingFormEpic.js +9 -0
- package/package.json +1 -1
package/lib/esm/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js
CHANGED
|
@@ -10,7 +10,7 @@ import { fetchReconciliation } from '../../reducers/reconciliationViewReducer';
|
|
|
10
10
|
import { fetchTransactionCategorizationView } from '../../reducers/transactionsViewReducer';
|
|
11
11
|
import { getJESchedulePeriodTabKey } from '../../types/jeSchedulesViewState';
|
|
12
12
|
export const fetchAllExpenseAutomationTabsEpic = (actions$, state$) => actions$.pipe(filter(fetchAllExpenseAutomationTabs.match), switchMap((action) => {
|
|
13
|
-
const { cacheOverride, refreshViewInBackground } = action.payload;
|
|
13
|
+
const { cacheOverride, refreshViewInBackground, isAccrualEnabled } = action.payload;
|
|
14
14
|
const state = state$.value;
|
|
15
15
|
const { monthEndCloseChecksState } = state;
|
|
16
16
|
const { monthEndCloseChecksByTenantId } = monthEndCloseChecksState;
|
|
@@ -35,7 +35,7 @@ export const fetchAllExpenseAutomationTabsEpic = (actions$, state$) => actions$.
|
|
|
35
35
|
amount: 75.0,
|
|
36
36
|
currencyCode: 'USD',
|
|
37
37
|
currencySymbol: '$',
|
|
38
|
-
}, cacheOverride, refreshViewInBackground), fetchFluxAnalysisView(selectedPeriod, cacheOverride, refreshViewInBackground, null), fetchJeSchedulesPage({ start: timePeriodStart, end: timePeriodEnd }, cacheOverride));
|
|
38
|
+
}, cacheOverride, refreshViewInBackground), fetchFluxAnalysisView(selectedPeriod, cacheOverride, refreshViewInBackground, null), fetchJeSchedulesPage({ start: timePeriodStart, end: timePeriodEnd }, cacheOverride, isAccrualEnabled));
|
|
39
39
|
if (reconciliationEnabled) {
|
|
40
40
|
actions.push(fetchReconciliation(selectedPeriod, undefined, cacheOverride, refreshViewInBackground));
|
|
41
41
|
}
|
|
@@ -61,16 +61,29 @@ export const fetchAllExpenseAutomationTabsEpic = (actions$, state$) => actions$.
|
|
|
61
61
|
fetchActions.push(fetchFluxAnalysisView(selectedPeriod, cacheOverride, refreshViewInBackground, null));
|
|
62
62
|
}
|
|
63
63
|
// The two JE tabs cache separately, so only the tab actually being shown decides whether a
|
|
64
|
-
// fetch is still needed.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
// fetch is still needed. Actual Matching is a third tab on the same page with its own
|
|
65
|
+
// endpoint; fetchJeSchedulesPage carries isAccrualEnabled so the prefetch epic can warm AM.
|
|
66
|
+
const selectedPageTab = state.expenseAutomationJESchedulesViewState.uiState.selectedPageTab;
|
|
67
|
+
if (selectedPageTab === 'actual_matching') {
|
|
68
|
+
const actualMatchingPeriodKeys = state.expenseAutomationActualMatchingViewState.matchIdsByPeriod[monthYearPeriodId];
|
|
69
|
+
// `fetchState` is global across months. Skipping on In-Progress would
|
|
70
|
+
// drop October while September is still loading; September's success
|
|
71
|
+
// then writes the old month and October stays Not-Started with no request.
|
|
72
|
+
if (actualMatchingPeriodKeys == null) {
|
|
73
|
+
fetchActions.push(fetchJeSchedulesPage({ start: timePeriodStart, end: timePeriodEnd }, cacheOverride, isAccrualEnabled));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const jeSchedulesPeriodTabKey = getJESchedulePeriodTabKey(selectedPageTab, monthYearPeriodId);
|
|
78
|
+
if (state.expenseAutomationJESchedulesViewState.fetchState !==
|
|
79
|
+
'In-Progress' &&
|
|
80
|
+
(!state.expenseAutomationJESchedulesViewState.hasValidState() ||
|
|
81
|
+
state.expenseAutomationJESchedulesViewState
|
|
82
|
+
.jeScheduleTransactionKeysByPeriodAndTab[jeSchedulesPeriodTabKey] == null ||
|
|
83
|
+
state.expenseAutomationJESchedulesViewState
|
|
84
|
+
.failedJeScheduleTransactionKeysByPeriodAndTab[jeSchedulesPeriodTabKey] == null)) {
|
|
85
|
+
fetchActions.push(fetchJeSchedulesPage({ start: timePeriodStart, end: timePeriodEnd }, cacheOverride, isAccrualEnabled));
|
|
86
|
+
}
|
|
74
87
|
}
|
|
75
88
|
if (state.expenseAutomationReconciliationViewState.fetchState !==
|
|
76
89
|
'In-Progress' &&
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { from, of } from 'rxjs';
|
|
2
|
+
import { catchError, filter, mergeMap, switchMap, takeUntil, } from 'rxjs/operators';
|
|
3
|
+
import { updateActualMatchingMatches } from '../../../../../entity/actualMatching/actualMatchingReducer';
|
|
4
|
+
import { createZeniAPIStatus, isSuccessResponse, } from '../../../../../responsePayload';
|
|
5
|
+
import { clearAll } from '../../../../../rootActions';
|
|
6
|
+
import { toServiceMonthFromPeriod, } from '../../../payload/actualMatchingPayload';
|
|
7
|
+
import { fetchActualMatching, fetchActualMatchingFailure, fetchActualMatchingSuccess, } from '../../../reducers/actualMatchingViewReducer';
|
|
8
|
+
import { getTenantDisplayCurrency } from '../../../selectors/actualMatchingViewSelector';
|
|
9
|
+
export const fetchActualMatchingEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchActualMatching.match), switchMap((action) => {
|
|
10
|
+
const state = state$.value;
|
|
11
|
+
const { period, refreshViewInBackground } = action.payload;
|
|
12
|
+
// Key the store write off the requested month. Using the selected period
|
|
13
|
+
// would park a prefetch for October under September's slot.
|
|
14
|
+
const requestPeriod = {
|
|
15
|
+
month: period.start.month,
|
|
16
|
+
year: period.start.year,
|
|
17
|
+
};
|
|
18
|
+
const serviceMonth = toServiceMonthFromPeriod(period);
|
|
19
|
+
return zeniAPI
|
|
20
|
+
.getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/accruals/actual-matching?service_month=${encodeURIComponent(serviceMonth)}`)
|
|
21
|
+
.pipe(mergeMap((response) => {
|
|
22
|
+
if (isSuccessResponse(response) && response.data != null) {
|
|
23
|
+
const matches = response.data.matches;
|
|
24
|
+
const updateActions = [];
|
|
25
|
+
if (matches.length > 0) {
|
|
26
|
+
updateActions.push(updateActualMatchingMatches(matches, getTenantDisplayCurrency(state)));
|
|
27
|
+
}
|
|
28
|
+
updateActions.push(fetchActualMatchingSuccess({
|
|
29
|
+
matchIds: matches.map((match) => match.match_id),
|
|
30
|
+
selectedPeriod: requestPeriod,
|
|
31
|
+
refreshViewInBackground,
|
|
32
|
+
}));
|
|
33
|
+
return from(updateActions);
|
|
34
|
+
}
|
|
35
|
+
return of(fetchActualMatchingFailure({
|
|
36
|
+
status: response.status,
|
|
37
|
+
selectedPeriod: requestPeriod,
|
|
38
|
+
refreshViewInBackground,
|
|
39
|
+
}));
|
|
40
|
+
}), catchError((error) => of(fetchActualMatchingFailure({
|
|
41
|
+
status: createZeniAPIStatus('Unexpected Error', 'fetch actual matching REST API call errored out' +
|
|
42
|
+
JSON.stringify(error)),
|
|
43
|
+
selectedPeriod: requestPeriod,
|
|
44
|
+
refreshViewInBackground,
|
|
45
|
+
}))), takeUntil(actions$.pipe(filter(clearAll.match))));
|
|
46
|
+
}));
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { from } from 'rxjs';
|
|
2
|
+
import { catchError, filter, mergeMap, takeUntil } from 'rxjs/operators';
|
|
3
|
+
import { updateActualMatchingMatches } from '../../../../../entity/actualMatching/actualMatchingReducer';
|
|
4
|
+
import { createZeniAPIStatus, isSuccessResponse, } from '../../../../../responsePayload';
|
|
5
|
+
import { clearAll } from '../../../../../rootActions';
|
|
6
|
+
import { fetchActualMatchingMatch, fetchActualMatchingMatchFailure, fetchActualMatchingMatchSuccess, } from '../../../reducers/actualMatchingViewReducer';
|
|
7
|
+
import { getTenantDisplayCurrency } from '../../../selectors/actualMatchingViewSelector';
|
|
8
|
+
export const fetchActualMatchingMatchEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchActualMatchingMatch.match), mergeMap((action) => {
|
|
9
|
+
const { matchId, refreshViewInBackground } = action.payload;
|
|
10
|
+
return zeniAPI
|
|
11
|
+
.getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/accruals/actual-matching/${encodeURIComponent(matchId)}`)
|
|
12
|
+
.pipe(mergeMap((response) => {
|
|
13
|
+
if (isSuccessResponse(response) && response.data != null) {
|
|
14
|
+
return from([
|
|
15
|
+
updateActualMatchingMatches([response.data], getTenantDisplayCurrency(state$.value)),
|
|
16
|
+
fetchActualMatchingMatchSuccess({
|
|
17
|
+
matchId,
|
|
18
|
+
refreshViewInBackground,
|
|
19
|
+
}),
|
|
20
|
+
]);
|
|
21
|
+
}
|
|
22
|
+
return from([
|
|
23
|
+
fetchActualMatchingMatchFailure({
|
|
24
|
+
matchId,
|
|
25
|
+
refreshViewInBackground,
|
|
26
|
+
status: response.status,
|
|
27
|
+
}),
|
|
28
|
+
]);
|
|
29
|
+
}), catchError((error) => from([
|
|
30
|
+
fetchActualMatchingMatchFailure({
|
|
31
|
+
matchId,
|
|
32
|
+
refreshViewInBackground,
|
|
33
|
+
status: createZeniAPIStatus('Unexpected Error', `Fetch actual matching match REST API call errored out ${JSON.stringify(error)}`),
|
|
34
|
+
}),
|
|
35
|
+
])), takeUntil(actions$.pipe(filter(clearAll.match))));
|
|
36
|
+
}));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { of } from 'rxjs';
|
|
2
|
+
import { filter, mergeMap } from 'rxjs/operators';
|
|
3
|
+
import { fetchActualMatching, fetchActualMatchingPage, } from '../../../reducers/actualMatchingViewReducer';
|
|
4
|
+
export const fetchActualMatchingPageEpic = (actions$) => actions$.pipe(filter(fetchActualMatchingPage.match), mergeMap((action) => {
|
|
5
|
+
const { period, refreshViewInBackground } = action.payload;
|
|
6
|
+
return of(fetchActualMatching(period, refreshViewInBackground));
|
|
7
|
+
}));
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { from, of } from 'rxjs';
|
|
2
|
+
import { catchError, exhaustMap, filter, groupBy, mergeMap, takeUntil, } from 'rxjs/operators';
|
|
3
|
+
import { removeActualMatchingMatch } from '../../../../../entity/actualMatching/actualMatchingReducer';
|
|
4
|
+
import { getActualMatchingMatchById } from '../../../../../entity/actualMatching/actualMatchingSelector';
|
|
5
|
+
import { openSnackbar } from '../../../../../entity/snackbar/snackbarReducer';
|
|
6
|
+
import { createZeniAPIStatus, isSuccessResponse } from '../../../../../responsePayload';
|
|
7
|
+
import { clearAll } from '../../../../../rootActions';
|
|
8
|
+
import { ignoreActualMatchingSuggestion, ignoreActualMatchingSuggestionFailure, ignoreActualMatchingSuggestionSuccess, } from '../../../reducers/actualMatchingViewReducer';
|
|
9
|
+
import { refreshActualMatchingList } from './refreshActualMatchingList';
|
|
10
|
+
function matchVendorName(state, matchId) {
|
|
11
|
+
return (getActualMatchingMatchById(state.actualMatchingState, matchId)?.vendorName ??
|
|
12
|
+
'');
|
|
13
|
+
}
|
|
14
|
+
function ignoreSuggestionSnackbar(vendorName, succeeded) {
|
|
15
|
+
return openSnackbar({
|
|
16
|
+
messageSection: 'actual_matching_ignore_suggestion',
|
|
17
|
+
messageText: succeeded ? 'success' : 'failed',
|
|
18
|
+
type: succeeded ? 'success' : 'error',
|
|
19
|
+
variables: [{ variableName: '_vendor_name_', variableValue: vendorName }],
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export const ignoreActualMatchingSuggestionEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(ignoreActualMatchingSuggestion.match), groupBy((action) => action.payload.matchId), mergeMap((perMatch$) => perMatch$.pipe(exhaustMap((action) => {
|
|
23
|
+
const { matchId, reason, note } = action.payload;
|
|
24
|
+
const body = {
|
|
25
|
+
reason,
|
|
26
|
+
...(note != null && note !== '' ? { note } : {}),
|
|
27
|
+
};
|
|
28
|
+
const vendorName = matchVendorName(state$.value, matchId);
|
|
29
|
+
return zeniAPI
|
|
30
|
+
.putAndGetJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/accruals/actual-matching/${encodeURIComponent(matchId)}/ignore`, body)
|
|
31
|
+
.pipe(mergeMap((response) => {
|
|
32
|
+
if (isSuccessResponse(response) && response.data != null) {
|
|
33
|
+
return from([
|
|
34
|
+
removeActualMatchingMatch(matchId),
|
|
35
|
+
ignoreActualMatchingSuggestionSuccess({ matchId }),
|
|
36
|
+
...refreshActualMatchingList(state$.value),
|
|
37
|
+
ignoreSuggestionSnackbar(vendorName, true),
|
|
38
|
+
]);
|
|
39
|
+
}
|
|
40
|
+
return of(ignoreActualMatchingSuggestionFailure({
|
|
41
|
+
matchId,
|
|
42
|
+
status: response.status,
|
|
43
|
+
}), ignoreSuggestionSnackbar(vendorName, false));
|
|
44
|
+
}), catchError((error) => of(ignoreActualMatchingSuggestionFailure({
|
|
45
|
+
matchId,
|
|
46
|
+
status: createZeniAPIStatus('Unexpected Error', 'Ignore actual matching suggestion REST API call errored out' +
|
|
47
|
+
JSON.stringify(error)),
|
|
48
|
+
}), ignoreSuggestionSnackbar(vendorName, false))), takeUntil(actions$.pipe(filter(clearAll.match))));
|
|
49
|
+
}))));
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { from, of } from 'rxjs';
|
|
2
|
+
import { catchError, exhaustMap, filter, mergeMap, takeUntil } from 'rxjs/operators';
|
|
3
|
+
import { getActualMatchingMatchById } from '../../../../../entity/actualMatching/actualMatchingSelector';
|
|
4
|
+
import { openSnackbar } from '../../../../../entity/snackbar/snackbarReducer';
|
|
5
|
+
import { createZeniAPIStatus, isSuccessResponse } from '../../../../../responsePayload';
|
|
6
|
+
import { clearAll } from '../../../../../rootActions';
|
|
7
|
+
import { toMatchAndReverseResult, } from '../../../payload/actualMatchingPayload';
|
|
8
|
+
import { clearMatchNewModal, fetchActualMatchingMatch, matchAndReverseAccruals, matchAndReverseAccrualsFailure, matchAndReverseAccrualsSuccess, } from '../../../reducers/actualMatchingViewReducer';
|
|
9
|
+
import { isMatchAndReverseCompleteStatus, } from '../../../types/actualMatchingViewState';
|
|
10
|
+
import { refreshActualMatchingList } from './refreshActualMatchingList';
|
|
11
|
+
function isMatchFailedStatus(status) {
|
|
12
|
+
return (status === 'conflict' || status === 'claim_lost' || status === 'error');
|
|
13
|
+
}
|
|
14
|
+
function matchAndReverseSnackbar(results, vendorName) {
|
|
15
|
+
const reversedCount = results.filter((result) => result.status === 'reversed').length;
|
|
16
|
+
const failedCount = results.filter((result) => isMatchFailedStatus(result.status)).length;
|
|
17
|
+
const variables = [
|
|
18
|
+
{ variableName: '_count_', variableValue: String(reversedCount) },
|
|
19
|
+
{ variableName: '_vendor_name_', variableValue: vendorName },
|
|
20
|
+
];
|
|
21
|
+
if (results.length === 0 || (reversedCount === 0 && failedCount > 0)) {
|
|
22
|
+
return openSnackbar({
|
|
23
|
+
messageSection: 'actual_matching_match_and_reverse',
|
|
24
|
+
messageText: 'failed',
|
|
25
|
+
type: 'error',
|
|
26
|
+
variables,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (reversedCount > 0 && failedCount === 0) {
|
|
30
|
+
return openSnackbar({
|
|
31
|
+
messageSection: 'actual_matching_match_and_reverse',
|
|
32
|
+
messageText: 'success',
|
|
33
|
+
type: 'success',
|
|
34
|
+
variables,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (reversedCount > 0 && failedCount > 0) {
|
|
38
|
+
return openSnackbar({
|
|
39
|
+
messageSection: 'actual_matching_match_and_reverse',
|
|
40
|
+
messageText: 'notification',
|
|
41
|
+
type: 'warning',
|
|
42
|
+
variables,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
function matchAndReverseHttpFailSnackbar(vendorName) {
|
|
48
|
+
return openSnackbar({
|
|
49
|
+
messageSection: 'actual_matching_match_and_reverse',
|
|
50
|
+
messageText: 'failed',
|
|
51
|
+
type: 'error',
|
|
52
|
+
variables: [
|
|
53
|
+
{ variableName: '_count_', variableValue: '0' },
|
|
54
|
+
{ variableName: '_vendor_name_', variableValue: vendorName },
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function matchVendorName(state, matchId) {
|
|
59
|
+
return getActualMatchingMatchById(state.actualMatchingState, matchId)?.vendorName ?? '';
|
|
60
|
+
}
|
|
61
|
+
export const matchAndReverseAccrualsEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(matchAndReverseAccruals.match),
|
|
62
|
+
// Posts reversing JEs; drop duplicate clicks while a request is in flight.
|
|
63
|
+
exhaustMap((action) => {
|
|
64
|
+
const { transactionId, transactionIntegrationId, transactionType, lineIds, accrualIds, transactionSyncToken, } = action.payload;
|
|
65
|
+
const body = {
|
|
66
|
+
transaction_id: transactionId,
|
|
67
|
+
transaction_integration_id: transactionIntegrationId,
|
|
68
|
+
transaction_type: transactionType,
|
|
69
|
+
line_ids: lineIds,
|
|
70
|
+
transaction_sync_token: transactionSyncToken,
|
|
71
|
+
accrual_ids: accrualIds,
|
|
72
|
+
};
|
|
73
|
+
// Key leftover results and snackbar vendor to the confirmed bill, not the
|
|
74
|
+
// row Match New was opened from (user can pick a different txn).
|
|
75
|
+
const matchId = `${transactionType.toLowerCase()}:${transactionIntegrationId}`;
|
|
76
|
+
const vendorName = matchVendorName(state$.value, matchId);
|
|
77
|
+
return zeniAPI
|
|
78
|
+
.postAndGetJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/accruals/match-and-reverse`, body)
|
|
79
|
+
.pipe(mergeMap((response) => {
|
|
80
|
+
if (isSuccessResponse(response) && response.data != null) {
|
|
81
|
+
const results = response.data.results.map(toMatchAndReverseResult);
|
|
82
|
+
const successActions = [
|
|
83
|
+
matchAndReverseAccrualsSuccess({ matchId, results }),
|
|
84
|
+
];
|
|
85
|
+
const occupancyChanged = results.some((result) => isMatchAndReverseCompleteStatus(result.status));
|
|
86
|
+
const allComplete = results.length > 0 &&
|
|
87
|
+
results.every((result) => isMatchAndReverseCompleteStatus(result.status));
|
|
88
|
+
if (occupancyChanged) {
|
|
89
|
+
// List + detail: list can omit leftovers; detail GET always
|
|
90
|
+
// writes intended occupancy so Continue match prefills the
|
|
91
|
+
// remaining leftover set instead of a stale cached row.
|
|
92
|
+
successActions.push(...refreshActualMatchingList(state$.value), fetchActualMatchingMatch(matchId, true));
|
|
93
|
+
}
|
|
94
|
+
const snackbar = matchAndReverseSnackbar(results, vendorName);
|
|
95
|
+
if (snackbar != null) {
|
|
96
|
+
successActions.push(snackbar);
|
|
97
|
+
}
|
|
98
|
+
if (allComplete) {
|
|
99
|
+
successActions.push(clearMatchNewModal());
|
|
100
|
+
}
|
|
101
|
+
return from(successActions);
|
|
102
|
+
}
|
|
103
|
+
return of(matchAndReverseAccrualsFailure({ status: response.status }), matchAndReverseHttpFailSnackbar(vendorName));
|
|
104
|
+
}), catchError((error) => of(matchAndReverseAccrualsFailure({
|
|
105
|
+
status: createZeniAPIStatus('Unexpected Error', 'Match and reverse accruals REST API call errored out' +
|
|
106
|
+
JSON.stringify(error)),
|
|
107
|
+
}), matchAndReverseHttpFailSnackbar(vendorName))), takeUntil(actions$.pipe(filter(clearAll.match))));
|
|
108
|
+
}));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { convertToPeriod, toAbsoluteDay, } from '../../../../../commonStateTypes/timePeriod';
|
|
2
|
+
import { fetchActualMatching } from '../../../reducers/actualMatchingViewReducer';
|
|
3
|
+
import { getSelectedMonthYearForCurrentTenant } from '../../../selectors/actualMatchingViewSelector';
|
|
4
|
+
export function refreshActualMatchingList(state) {
|
|
5
|
+
const selectedMonthYear = getSelectedMonthYearForCurrentTenant(state);
|
|
6
|
+
if (selectedMonthYear == null) {
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
const period = convertToPeriod(selectedMonthYear);
|
|
10
|
+
const start = toAbsoluteDay(period.start);
|
|
11
|
+
const end = toAbsoluteDay(period.end);
|
|
12
|
+
if (start == null || end == null) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
return [fetchActualMatching({ start, end }, true)];
|
|
16
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { from, merge, of } from 'rxjs';
|
|
2
|
+
import { catchError, debounceTime, filter, mergeMap, switchMap, takeUntil, } from 'rxjs/operators';
|
|
3
|
+
import { withInheritedAccrualCurrency } from '../../../../../entity/actualMatching/actualMatchingPayload';
|
|
4
|
+
import { generateJEOneTimeAccrualKey } from '../../../../../entity/jeSchedules/jeScheduleHelper';
|
|
5
|
+
import { updateJEOneTimeAccruals } from '../../../../../entity/jeSchedules/jeSchedulesReducer';
|
|
6
|
+
import { createZeniAPIStatus, isSuccessResponse, } from '../../../../../responsePayload';
|
|
7
|
+
import { clearAll } from '../../../../../rootActions';
|
|
8
|
+
import { searchMatchAccruals, searchMatchAccrualsFailure, searchMatchAccrualsSuccess, } from '../../../reducers/actualMatchingViewReducer';
|
|
9
|
+
import { getTenantDisplayCurrency } from '../../../selectors/actualMatchingViewSelector';
|
|
10
|
+
import { MATCH_ACCRUAL_SEARCH_LIMIT, MATCH_SEARCH_DEBOUNCE_MS, } from '../../../types/actualMatchingViewState';
|
|
11
|
+
export const searchMatchAccrualsEpic = (actions$, state$, zeniAPI) => {
|
|
12
|
+
const searchActions$ = actions$.pipe(filter(searchMatchAccruals.match));
|
|
13
|
+
const pagination$ = searchActions$.pipe(filter((action) => action.payload.pageToken != null));
|
|
14
|
+
const newSearchDebounced$ = searchActions$.pipe(filter((action) => action.payload.pageToken == null), debounceTime(MATCH_SEARCH_DEBOUNCE_MS));
|
|
15
|
+
return merge(pagination$, newSearchDebounced$).pipe(switchMap((action) => {
|
|
16
|
+
const { query, pageToken } = action.payload;
|
|
17
|
+
const offset = pageToken != null && pageToken.length > 0 ? Number(pageToken) : 0;
|
|
18
|
+
const searchText = encodeURIComponent(query);
|
|
19
|
+
const limit = MATCH_ACCRUAL_SEARCH_LIMIT;
|
|
20
|
+
return zeniAPI
|
|
21
|
+
.getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/accruals?status=posted&reversal_policy=on_bill_match&search_text=${searchText}&limit=${limit}&offset=${offset}`)
|
|
22
|
+
.pipe(mergeMap((response) => {
|
|
23
|
+
if (isSuccessResponse(response) && response.data != null) {
|
|
24
|
+
const accruals = response.data.accruals;
|
|
25
|
+
const append = pageToken != null;
|
|
26
|
+
const nextOffset = offset + accruals.length;
|
|
27
|
+
const total = response.data.total ?? nextOffset;
|
|
28
|
+
const nextPageToken = nextOffset < total ? String(nextOffset) : null;
|
|
29
|
+
// toJEOneTimeAccrual still defaults a missing code to USD. Stamp
|
|
30
|
+
// tenant currency first, same as list members.
|
|
31
|
+
const fallbackCurrencyCode = getTenantDisplayCurrency(state$.value)?.currencyCode ?? '';
|
|
32
|
+
const stampedAccruals = accruals.map((accrual) => withInheritedAccrualCurrency(accrual, fallbackCurrencyCode));
|
|
33
|
+
const actionsOut = [];
|
|
34
|
+
// Picker payloads are thin. Do not replace a Review/Existing
|
|
35
|
+
// accrual that already has accounts, memo, cleared, matchSuggestion.
|
|
36
|
+
const existingByKey = state$.value.jeSchedulesState.oneTimeAccrualsByKey;
|
|
37
|
+
const accrualsToInsert = stampedAccruals.filter((accrual) => existingByKey[generateJEOneTimeAccrualKey(accrual._id)] ==
|
|
38
|
+
null);
|
|
39
|
+
if (accrualsToInsert.length > 0) {
|
|
40
|
+
actionsOut.push(updateJEOneTimeAccruals(accrualsToInsert));
|
|
41
|
+
}
|
|
42
|
+
actionsOut.push(searchMatchAccrualsSuccess({
|
|
43
|
+
append,
|
|
44
|
+
nextPageToken,
|
|
45
|
+
results: stampedAccruals,
|
|
46
|
+
}));
|
|
47
|
+
return from(actionsOut);
|
|
48
|
+
}
|
|
49
|
+
return of(searchMatchAccrualsFailure({
|
|
50
|
+
error: response.status,
|
|
51
|
+
}));
|
|
52
|
+
}), catchError((error) => of(searchMatchAccrualsFailure({
|
|
53
|
+
error: createZeniAPIStatus('Unexpected Error', 'Search match accruals REST API call errored out' +
|
|
54
|
+
JSON.stringify(error)),
|
|
55
|
+
}))),
|
|
56
|
+
// On the HTTP stream only. Wrapping the outer merge would complete
|
|
57
|
+
// the epic on the first `clearAll` and drop later searches.
|
|
58
|
+
takeUntil(actions$.pipe(filter(clearAll.match))));
|
|
59
|
+
}));
|
|
60
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { from, merge, of } from 'rxjs';
|
|
2
|
+
import { catchError, debounceTime, filter, mergeMap, switchMap, takeUntil, } from 'rxjs/operators';
|
|
3
|
+
import { getActualMatchingMatchById } from '../../../../../entity/actualMatching/actualMatchingSelector';
|
|
4
|
+
import { createZeniAPIStatus, isSuccessResponse, } from '../../../../../responsePayload';
|
|
5
|
+
import { clearAll } from '../../../../../rootActions';
|
|
6
|
+
import { toActualMatchingTransactionSearchResult, } from '../../../payload/actualMatchingPayload';
|
|
7
|
+
import { openMatchNewModal, searchMatchAccruals, searchMatchTransactions, searchMatchTransactionsFailure, searchMatchTransactionsSuccess, } from '../../../reducers/actualMatchingViewReducer';
|
|
8
|
+
import { getTenantDisplayCurrency, parseActualMatchingMatchId, } from '../../../selectors/actualMatchingViewSelector';
|
|
9
|
+
import { MATCH_SEARCH_DEBOUNCE_MS, MATCH_TRANSACTION_SEARCH_PAGE_SIZE, } from '../../../types/actualMatchingViewState';
|
|
10
|
+
export const searchMatchTransactionsEpic = (actions$, state$, zeniAPI) => {
|
|
11
|
+
const openModal$ = actions$.pipe(filter(openMatchNewModal.match), mergeMap((action) => {
|
|
12
|
+
const matchId = action.payload.matchId;
|
|
13
|
+
if (matchId == null || matchId === '') {
|
|
14
|
+
return from([
|
|
15
|
+
searchMatchTransactions(''),
|
|
16
|
+
searchMatchAccruals(''),
|
|
17
|
+
]);
|
|
18
|
+
}
|
|
19
|
+
const match = getActualMatchingMatchById(state$.value.actualMatchingState, matchId);
|
|
20
|
+
const parsed = parseActualMatchingMatchId(matchId);
|
|
21
|
+
const transactionType = match?.transactionType != null && match.transactionType !== ''
|
|
22
|
+
? match.transactionType
|
|
23
|
+
: parsed?.transactionType;
|
|
24
|
+
const transactionIntegrationId = match?.transactionIntegrationId != null &&
|
|
25
|
+
match.transactionIntegrationId !== ''
|
|
26
|
+
? match.transactionIntegrationId
|
|
27
|
+
: parsed?.transactionIntegrationId;
|
|
28
|
+
return from([
|
|
29
|
+
searchMatchTransactions('', undefined, transactionType, transactionIntegrationId),
|
|
30
|
+
]);
|
|
31
|
+
}));
|
|
32
|
+
const searchActions$ = actions$.pipe(filter(searchMatchTransactions.match));
|
|
33
|
+
const pagination$ = searchActions$.pipe(filter((action) => action.payload.pageToken != null));
|
|
34
|
+
const newSearchDebounced$ = searchActions$.pipe(filter((action) => action.payload.pageToken == null), debounceTime(MATCH_SEARCH_DEBOUNCE_MS));
|
|
35
|
+
const search$ = merge(pagination$, newSearchDebounced$).pipe(switchMap((action) => {
|
|
36
|
+
const { query, pageToken, transactionType, transactionIntegrationId } = action.payload;
|
|
37
|
+
const queryParam = {
|
|
38
|
+
search_text: query,
|
|
39
|
+
page_size: MATCH_TRANSACTION_SEARCH_PAGE_SIZE,
|
|
40
|
+
...(pageToken != null ? { page_token: pageToken } : {}),
|
|
41
|
+
...(transactionType != null && transactionType !== ''
|
|
42
|
+
? { transaction_type: transactionType }
|
|
43
|
+
: {}),
|
|
44
|
+
...(transactionIntegrationId != null && transactionIntegrationId !== ''
|
|
45
|
+
? { transaction_integration_id: transactionIntegrationId }
|
|
46
|
+
: {}),
|
|
47
|
+
};
|
|
48
|
+
return zeniAPI
|
|
49
|
+
.getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/accruals/match-transaction-search?query=${encodeURIComponent(JSON.stringify(queryParam))}`)
|
|
50
|
+
.pipe(mergeMap((response) => {
|
|
51
|
+
if (isSuccessResponse(response) && response.data != null) {
|
|
52
|
+
const { transactions, next_page_token } = response.data;
|
|
53
|
+
const append = pageToken != null;
|
|
54
|
+
const fallbackCurrency = getTenantDisplayCurrency(state$.value);
|
|
55
|
+
const results = transactions.map((transaction) => toActualMatchingTransactionSearchResult(transaction, fallbackCurrency));
|
|
56
|
+
return of(searchMatchTransactionsSuccess({
|
|
57
|
+
append,
|
|
58
|
+
nextPageToken: next_page_token ?? null,
|
|
59
|
+
results,
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
return of(searchMatchTransactionsFailure({
|
|
63
|
+
error: response.status,
|
|
64
|
+
}));
|
|
65
|
+
}), catchError((error) => of(searchMatchTransactionsFailure({
|
|
66
|
+
error: createZeniAPIStatus('Unexpected Error', 'Search match transactions REST API call errored out' +
|
|
67
|
+
JSON.stringify(error)),
|
|
68
|
+
}))),
|
|
69
|
+
// On the HTTP stream only. Wrapping `merge(openModal$, search$)`
|
|
70
|
+
// would complete the epic on the first `clearAll` and drop later searches.
|
|
71
|
+
takeUntil(actions$.pipe(filter(clearAll.match))));
|
|
72
|
+
}));
|
|
73
|
+
return merge(openModal$, search$);
|
|
74
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { from, of } from 'rxjs';
|
|
2
|
+
import { catchError, exhaustMap, filter, groupBy, mergeMap, takeUntil, } from 'rxjs/operators';
|
|
3
|
+
import { getActualMatchingMatchById } from '../../../../../entity/actualMatching/actualMatchingSelector';
|
|
4
|
+
import { openSnackbar } from '../../../../../entity/snackbar/snackbarReducer';
|
|
5
|
+
import { createZeniAPIStatus, isSuccessResponse, } from '../../../../../responsePayload';
|
|
6
|
+
import { clearAll } from '../../../../../rootActions';
|
|
7
|
+
import { toUndoReversalResult, } from '../../../payload/actualMatchingPayload';
|
|
8
|
+
import { fetchActualMatchingMatch, undoReversalAccruals, undoReversalAccrualsFailure, undoReversalAccrualsSuccess, } from '../../../reducers/actualMatchingViewReducer';
|
|
9
|
+
import { refreshActualMatchingList } from './refreshActualMatchingList';
|
|
10
|
+
function undoReversalSnackbar(results, vendorName) {
|
|
11
|
+
const undoneCount = results.filter((result) => result.status === 'undone').length;
|
|
12
|
+
const failedCount = results.length - undoneCount;
|
|
13
|
+
const variables = [
|
|
14
|
+
{ variableName: '_count_', variableValue: String(undoneCount) },
|
|
15
|
+
{ variableName: '_vendor_name_', variableValue: vendorName },
|
|
16
|
+
];
|
|
17
|
+
if (results.length > 0 && failedCount === 0) {
|
|
18
|
+
return openSnackbar({
|
|
19
|
+
messageSection: 'actual_matching_undo_reversal',
|
|
20
|
+
messageText: 'success',
|
|
21
|
+
type: 'success',
|
|
22
|
+
variables,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (undoneCount > 0 && failedCount > 0) {
|
|
26
|
+
return openSnackbar({
|
|
27
|
+
messageSection: 'actual_matching_undo_reversal',
|
|
28
|
+
messageText: 'notification',
|
|
29
|
+
type: 'warning',
|
|
30
|
+
variables,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return openSnackbar({
|
|
34
|
+
messageSection: 'actual_matching_undo_reversal',
|
|
35
|
+
messageText: 'failed',
|
|
36
|
+
type: 'error',
|
|
37
|
+
variables,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function matchVendorName(state, matchId) {
|
|
41
|
+
return (getActualMatchingMatchById(state.actualMatchingState, matchId)?.vendorName ??
|
|
42
|
+
'');
|
|
43
|
+
}
|
|
44
|
+
export const undoReversalAccrualsEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(undoReversalAccruals.match),
|
|
45
|
+
// Per-match exhaust: a second click on the same match drops while HTTP is in
|
|
46
|
+
// flight. A different match must still run — a global exhaustMap left the
|
|
47
|
+
// other match's reducer In-Progress with no request.
|
|
48
|
+
groupBy((action) => action.payload.matchId), mergeMap((perMatch$) => perMatch$.pipe(exhaustMap((action) => {
|
|
49
|
+
const { accrualIds, matchId } = action.payload;
|
|
50
|
+
const body = {
|
|
51
|
+
accrual_ids: accrualIds,
|
|
52
|
+
};
|
|
53
|
+
const vendorName = matchVendorName(state$.value, matchId);
|
|
54
|
+
return zeniAPI
|
|
55
|
+
.postAndGetJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/accruals/undo-reversal`, body)
|
|
56
|
+
.pipe(mergeMap((response) => {
|
|
57
|
+
if (isSuccessResponse(response) && response.data != null) {
|
|
58
|
+
const results = response.data.results.map(toUndoReversalResult);
|
|
59
|
+
const successActions = [
|
|
60
|
+
undoReversalAccrualsSuccess(matchId, results),
|
|
61
|
+
];
|
|
62
|
+
const anyUndone = results.some((result) => result.status === 'undone');
|
|
63
|
+
if (anyUndone) {
|
|
64
|
+
successActions.push(...refreshActualMatchingList(state$.value), fetchActualMatchingMatch(matchId, true));
|
|
65
|
+
}
|
|
66
|
+
successActions.push(undoReversalSnackbar(results, vendorName));
|
|
67
|
+
return from(successActions);
|
|
68
|
+
}
|
|
69
|
+
return of(undoReversalAccrualsFailure({
|
|
70
|
+
matchId,
|
|
71
|
+
status: response.status,
|
|
72
|
+
}), undoReversalSnackbar([], vendorName));
|
|
73
|
+
}), catchError((error) => of(undoReversalAccrualsFailure({
|
|
74
|
+
matchId,
|
|
75
|
+
status: createZeniAPIStatus('Unexpected Error', 'Undo reversal accruals REST API call errored out' +
|
|
76
|
+
JSON.stringify(error)),
|
|
77
|
+
}), undoReversalSnackbar([], vendorName))), takeUntil(actions$.pipe(filter(clearAll.match))));
|
|
78
|
+
}))));
|
|
@@ -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
|
|
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
|
-
|
|
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
|
|
32
|
+
if (!state.expenseAutomationJESchedulesViewState.accountSettings
|
|
23
33
|
.hasLoadedAccountSettings) {
|
|
24
34
|
jeSchedulePageActions.push(initializeAccountSettingsView());
|
|
25
35
|
}
|