@zeniai/client-epic-state 5.2.34-beta0ND → 5.2.35-beta0MM

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 (114) hide show
  1. package/lib/entity/actualMatching/actualMatchingPayload.d.ts +25 -0
  2. package/lib/entity/actualMatching/actualMatchingPayload.js +43 -0
  3. package/lib/entity/actualMatching/actualMatchingReducer.d.ts +8 -0
  4. package/lib/entity/actualMatching/actualMatchingReducer.js +33 -0
  5. package/lib/entity/actualMatching/actualMatchingSelector.d.ts +3 -0
  6. package/lib/entity/actualMatching/actualMatchingSelector.js +6 -0
  7. package/lib/entity/actualMatching/actualMatchingState.d.ts +22 -0
  8. package/lib/entity/actualMatching/actualMatchingState.js +2 -0
  9. package/lib/entity/actualMatching/actualMatchingTypes.d.ts +1 -0
  10. package/lib/entity/actualMatching/actualMatchingTypes.js +2 -0
  11. package/lib/entity/jeSchedules/jeSchedulesPayload.d.ts +29 -0
  12. package/lib/entity/jeSchedules/jeSchedulesPayload.js +49 -0
  13. package/lib/entity/jeSchedules/jeSchedulesState.d.ts +31 -0
  14. package/lib/entity/notificationRegistry/notificationRegistryReducer.d.ts +1 -2
  15. package/lib/entity/notificationRegistry/notificationRegistryReducer.js +9 -20
  16. package/lib/entity/notificationRegistry/notificationRegistrySelector.d.ts +1 -2
  17. package/lib/entity/notificationRegistry/notificationRegistrySelector.js +1 -7
  18. package/lib/entity/notificationRegistry/notificationRegistryState.d.ts +0 -5
  19. package/lib/entity/notificationRegistry/notificationRegistryState.js +1 -7
  20. package/lib/entity/sectionProjectView/sectionProjectViewReducer.js +1 -3
  21. package/lib/epic.d.ts +8 -1
  22. package/lib/epic.js +18 -4
  23. package/lib/esm/entity/actualMatching/actualMatchingPayload.js +40 -0
  24. package/lib/esm/entity/actualMatching/actualMatchingReducer.js +30 -0
  25. package/lib/esm/entity/actualMatching/actualMatchingSelector.js +3 -0
  26. package/lib/esm/entity/actualMatching/actualMatchingState.js +1 -0
  27. package/lib/esm/entity/actualMatching/actualMatchingTypes.js +1 -0
  28. package/lib/esm/entity/jeSchedules/jeSchedulesPayload.js +49 -0
  29. package/lib/esm/entity/notificationRegistry/notificationRegistryReducer.js +9 -20
  30. package/lib/esm/entity/notificationRegistry/notificationRegistrySelector.js +0 -5
  31. package/lib/esm/entity/notificationRegistry/notificationRegistryState.js +0 -5
  32. package/lib/esm/entity/sectionProjectView/sectionProjectViewReducer.js +1 -3
  33. package/lib/esm/epic.js +18 -4
  34. package/lib/esm/index.js +10 -6
  35. package/lib/esm/reducer.js +6 -0
  36. package/lib/esm/view/aiCfoView/aiCfoViewReducer.js +2 -4
  37. package/lib/esm/view/aiCfoView/epics/fetchRoutineRunsEpic.js +1 -3
  38. package/lib/esm/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingEpic.js +46 -0
  39. package/lib/esm/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingMatchEpic.js +35 -0
  40. package/lib/esm/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingPageEpic.js +7 -0
  41. package/lib/esm/view/expenseAutomationView/epics/actualMatching/matchAndReverseAccrualsEpic.js +52 -0
  42. package/lib/esm/view/expenseAutomationView/epics/actualMatching/searchMatchAccrualsEpic.js +44 -0
  43. package/lib/esm/view/expenseAutomationView/epics/actualMatching/searchMatchTransactionsEpic.js +73 -0
  44. package/lib/esm/view/expenseAutomationView/epics/actualMatching/undoReversalAccrualsEpic.js +52 -0
  45. package/lib/esm/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +28 -11
  46. package/lib/esm/view/expenseAutomationView/epics/common/refreshExpenseAutomationCurrentTabEpic.js +9 -1
  47. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/prefetchOtherJeSchedulesTabEpic.js +3 -1
  48. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +5 -0
  49. package/lib/esm/view/expenseAutomationView/expenseAutomationViewSelector.js +22 -7
  50. package/lib/esm/view/expenseAutomationView/payload/actualMatchingPayload.js +68 -0
  51. package/lib/esm/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +364 -0
  52. package/lib/esm/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.js +1 -0
  53. package/lib/esm/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +281 -0
  54. package/lib/esm/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +3 -0
  55. package/lib/esm/view/expenseAutomationView/types/actualMatchingViewState.js +32 -0
  56. package/lib/esm/view/expenseAutomationView/types/jeSchedulesViewState.js +5 -1
  57. package/lib/esm/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +7 -20
  58. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewReducer.js +17 -43
  59. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewSelector.js +4 -24
  60. package/lib/esm/view/settingsView/epic/fetchNotificationSettingsEpic.js +2 -12
  61. package/lib/index.d.ts +16 -8
  62. package/lib/index.js +78 -55
  63. package/lib/reducer.d.ts +2 -0
  64. package/lib/reducer.js +6 -0
  65. package/lib/rootStateTypes.d.ts +4 -0
  66. package/lib/view/aiCfoView/aiCfoViewReducer.js +2 -4
  67. package/lib/view/aiCfoView/aiCfoViewSelector.d.ts +1 -1
  68. package/lib/view/aiCfoView/epics/fetchRoutineRunsEpic.js +1 -3
  69. package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingEpic.d.ts +9 -0
  70. package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingEpic.js +50 -0
  71. package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingMatchEpic.d.ts +12 -0
  72. package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingMatchEpic.js +39 -0
  73. package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingPageEpic.d.ts +5 -0
  74. package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingPageEpic.js +11 -0
  75. package/lib/view/expenseAutomationView/epics/actualMatching/matchAndReverseAccrualsEpic.d.ts +8 -0
  76. package/lib/view/expenseAutomationView/epics/actualMatching/matchAndReverseAccrualsEpic.js +56 -0
  77. package/lib/view/expenseAutomationView/epics/actualMatching/searchMatchAccrualsEpic.d.ts +8 -0
  78. package/lib/view/expenseAutomationView/epics/actualMatching/searchMatchAccrualsEpic.js +48 -0
  79. package/lib/view/expenseAutomationView/epics/actualMatching/searchMatchTransactionsEpic.d.ts +8 -0
  80. package/lib/view/expenseAutomationView/epics/actualMatching/searchMatchTransactionsEpic.js +77 -0
  81. package/lib/view/expenseAutomationView/epics/actualMatching/undoReversalAccrualsEpic.d.ts +8 -0
  82. package/lib/view/expenseAutomationView/epics/actualMatching/undoReversalAccrualsEpic.js +56 -0
  83. package/lib/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.d.ts +2 -1
  84. package/lib/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +28 -11
  85. package/lib/view/expenseAutomationView/epics/common/refreshExpenseAutomationCurrentTabEpic.d.ts +2 -1
  86. package/lib/view/expenseAutomationView/epics/common/refreshExpenseAutomationCurrentTabEpic.js +9 -1
  87. package/lib/view/expenseAutomationView/epics/jeSchedule/prefetchOtherJeSchedulesTabEpic.js +3 -1
  88. package/lib/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.d.ts +2 -1
  89. package/lib/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +5 -0
  90. package/lib/view/expenseAutomationView/expenseAutomationViewSelector.js +22 -7
  91. package/lib/view/expenseAutomationView/payload/actualMatchingPayload.d.ts +85 -0
  92. package/lib/view/expenseAutomationView/payload/actualMatchingPayload.js +74 -0
  93. package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.d.ts +74 -0
  94. package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +368 -0
  95. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.d.ts +4 -4
  96. package/lib/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.d.ts +83 -0
  97. package/lib/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.js +2 -0
  98. package/lib/view/expenseAutomationView/selectorTypes/expenseAutomationViewSelectorTypes.d.ts +2 -0
  99. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.d.ts +29 -0
  100. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +296 -0
  101. package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +3 -0
  102. package/lib/view/expenseAutomationView/types/actualMatchingViewState.d.ts +99 -0
  103. package/lib/view/expenseAutomationView/types/actualMatchingViewState.js +38 -0
  104. package/lib/view/expenseAutomationView/types/jeSchedulesViewState.d.ts +1 -1
  105. package/lib/view/expenseAutomationView/types/jeSchedulesViewState.js +5 -1
  106. package/lib/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +6 -19
  107. package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.d.ts +5 -8
  108. package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.js +17 -43
  109. package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.d.ts +0 -4
  110. package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.js +5 -30
  111. package/lib/view/notificationPreferencesView/notificationPreferencesViewState.d.ts +3 -4
  112. package/lib/view/settingsView/epic/fetchNotificationSettingsEpic.js +2 -12
  113. package/lib/view/settingsView/settingsViewPayload.d.ts +0 -2
  114. package/package.json +1 -1
@@ -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 { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
5
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
6
+ import { clearAll } from '../../../../rootActions';
7
+ import { toServiceMonthFromPeriod, } from '../../payload/actualMatchingPayload';
8
+ import { fetchActualMatching, fetchActualMatchingFailure, fetchActualMatchingSuccess, } from '../../reducers/actualMatchingViewReducer';
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
+ const currentTenant = getCurrentTenant(state);
13
+ const selectedPeriod = state.expenseAutomationViewState
14
+ .selectedPeriodByTenantId[currentTenant?.tenantId ?? ''] ?? {
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));
27
+ }
28
+ updateActions.push(fetchActualMatchingSuccess({
29
+ matchIds: matches.map((match) => match.match_id),
30
+ selectedPeriod,
31
+ refreshViewInBackground,
32
+ }));
33
+ return from(updateActions);
34
+ }
35
+ return of(fetchActualMatchingFailure({
36
+ status: response.status,
37
+ selectedPeriod,
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,
44
+ refreshViewInBackground,
45
+ }))), takeUntil(actions$.pipe(filter(clearAll.match))));
46
+ }));
@@ -0,0 +1,35 @@
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
+ export const fetchActualMatchingMatchEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchActualMatchingMatch.match), mergeMap((action) => {
8
+ const { matchId, refreshViewInBackground } = action.payload;
9
+ return zeniAPI
10
+ .getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/accruals/actual-matching/${encodeURIComponent(matchId)}`)
11
+ .pipe(mergeMap((response) => {
12
+ if (isSuccessResponse(response) && response.data != null) {
13
+ return from([
14
+ updateActualMatchingMatches([response.data]),
15
+ fetchActualMatchingMatchSuccess({
16
+ matchId,
17
+ refreshViewInBackground,
18
+ }),
19
+ ]);
20
+ }
21
+ return from([
22
+ fetchActualMatchingMatchFailure({
23
+ matchId,
24
+ refreshViewInBackground,
25
+ status: response.status,
26
+ }),
27
+ ]);
28
+ }), catchError((error) => from([
29
+ fetchActualMatchingMatchFailure({
30
+ matchId,
31
+ refreshViewInBackground,
32
+ status: createZeniAPIStatus('Unexpected Error', `Fetch actual matching match REST API call errored out ${JSON.stringify(error)}`),
33
+ }),
34
+ ])), takeUntil(actions$.pipe(filter(clearAll.match))));
35
+ }));
@@ -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,52 @@
1
+ import { from, of } from 'rxjs';
2
+ import { catchError, filter, mergeMap, takeUntil } from 'rxjs/operators';
3
+ import { convertToPeriod, toAbsoluteDay, } from '../../../../commonStateTypes/timePeriod';
4
+ import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
5
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
6
+ import { clearAll } from '../../../../rootActions';
7
+ import { toMatchAndReverseResult, } from '../../payload/actualMatchingPayload';
8
+ import { clearMatchNewModal, fetchActualMatching, matchAndReverseAccruals, matchAndReverseAccrualsFailure, matchAndReverseAccrualsSuccess, } from '../../reducers/actualMatchingViewReducer';
9
+ function refreshActualMatchingList(state) {
10
+ const currentTenant = getCurrentTenant(state);
11
+ const selectedMonthYear = state.expenseAutomationViewState.selectedPeriodByTenantId[currentTenant?.tenantId ?? ''];
12
+ if (selectedMonthYear == null) {
13
+ return [];
14
+ }
15
+ const period = convertToPeriod(selectedMonthYear);
16
+ const start = toAbsoluteDay(period.start);
17
+ const end = toAbsoluteDay(period.end);
18
+ if (start == null || end == null) {
19
+ return [];
20
+ }
21
+ return [fetchActualMatching({ start, end }, true)];
22
+ }
23
+ export const matchAndReverseAccrualsEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(matchAndReverseAccruals.match), mergeMap((action) => {
24
+ const { transactionId, transactionIntegrationId, transactionType, lineId, accrualIds, transactionSyncToken, } = action.payload;
25
+ const body = {
26
+ transaction_id: transactionId,
27
+ transaction_integration_id: transactionIntegrationId,
28
+ transaction_type: transactionType,
29
+ line_id: lineId,
30
+ transaction_sync_token: transactionSyncToken,
31
+ accrual_ids: accrualIds,
32
+ };
33
+ return zeniAPI
34
+ .postAndGetJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/accruals/match-and-reverse`, body)
35
+ .pipe(mergeMap((response) => {
36
+ if (isSuccessResponse(response) && response.data != null) {
37
+ const results = response.data.results.map(toMatchAndReverseResult);
38
+ const successActions = [
39
+ matchAndReverseAccrualsSuccess({ results }),
40
+ ];
41
+ const anyReversed = results.some((result) => result.status === 'reversed');
42
+ if (anyReversed) {
43
+ successActions.push(...refreshActualMatchingList(state$.value), clearMatchNewModal());
44
+ }
45
+ return from(successActions);
46
+ }
47
+ return of(matchAndReverseAccrualsFailure({ status: response.status }));
48
+ }), catchError((error) => of(matchAndReverseAccrualsFailure({
49
+ status: createZeniAPIStatus('Unexpected Error', 'Match and reverse accruals REST API call errored out' +
50
+ JSON.stringify(error)),
51
+ }))), takeUntil(actions$.pipe(filter(clearAll.match))));
52
+ }));
@@ -0,0 +1,44 @@
1
+ import { from, merge, of } from 'rxjs';
2
+ import { catchError, debounceTime, filter, mergeMap, switchMap, } from 'rxjs/operators';
3
+ import { updateJEOneTimeAccruals } from '../../../../entity/jeSchedules/jeSchedulesReducer';
4
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
5
+ import { searchMatchAccruals, searchMatchAccrualsFailure, searchMatchAccrualsSuccess, } from '../../reducers/actualMatchingViewReducer';
6
+ import { MATCH_ACCRUAL_SEARCH_LIMIT, MATCH_SEARCH_DEBOUNCE_MS, } from '../../types/actualMatchingViewState';
7
+ export const searchMatchAccrualsEpic = (actions$, _state$, zeniAPI) => {
8
+ const searchActions$ = actions$.pipe(filter(searchMatchAccruals.match));
9
+ const pagination$ = searchActions$.pipe(filter((action) => action.payload.pageToken != null));
10
+ const newSearchDebounced$ = searchActions$.pipe(filter((action) => action.payload.pageToken == null), debounceTime(MATCH_SEARCH_DEBOUNCE_MS));
11
+ return merge(pagination$, newSearchDebounced$).pipe(switchMap((action) => {
12
+ const { query, pageToken } = action.payload;
13
+ const offset = pageToken != null && pageToken.length > 0 ? Number(pageToken) : 0;
14
+ const searchText = encodeURIComponent(query);
15
+ const limit = MATCH_ACCRUAL_SEARCH_LIMIT;
16
+ return zeniAPI
17
+ .getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/accruals?status=posted&reversal_policy=on_bill_match&search_text=${searchText}&limit=${limit}&offset=${offset}`)
18
+ .pipe(mergeMap((response) => {
19
+ if (isSuccessResponse(response) && response.data != null) {
20
+ const accruals = response.data.accruals;
21
+ const append = pageToken != null;
22
+ const nextOffset = offset + accruals.length;
23
+ const total = response.data.total ?? nextOffset;
24
+ const nextPageToken = nextOffset < total ? String(nextOffset) : null;
25
+ const actionsOut = [];
26
+ if (accruals.length > 0) {
27
+ actionsOut.push(updateJEOneTimeAccruals(accruals));
28
+ }
29
+ actionsOut.push(searchMatchAccrualsSuccess({
30
+ append,
31
+ nextPageToken,
32
+ results: accruals,
33
+ }));
34
+ return from(actionsOut);
35
+ }
36
+ return of(searchMatchAccrualsFailure({
37
+ error: response.status,
38
+ }));
39
+ }), catchError((error) => of(searchMatchAccrualsFailure({
40
+ error: createZeniAPIStatus('Unexpected Error', 'Search match accruals REST API call errored out' +
41
+ JSON.stringify(error)),
42
+ }))));
43
+ }));
44
+ };
@@ -0,0 +1,73 @@
1
+ import { from, merge, of } from 'rxjs';
2
+ import { catchError, debounceTime, filter, mergeMap, switchMap, } from 'rxjs/operators';
3
+ import { getActualMatchingMatchById } from '../../../../entity/actualMatching/actualMatchingSelector';
4
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
5
+ import { toActualMatchingTransactionSearchResult, } from '../../payload/actualMatchingPayload';
6
+ import { openMatchNewModal, searchMatchAccruals, searchMatchTransactions, searchMatchTransactionsFailure, searchMatchTransactionsSuccess, } from '../../reducers/actualMatchingViewReducer';
7
+ import { parseActualMatchingMatchId } from '../../selectors/actualMatchingViewSelector';
8
+ import { MATCH_SEARCH_DEBOUNCE_MS, MATCH_TRANSACTION_SEARCH_PAGE_SIZE, } from '../../types/actualMatchingViewState';
9
+ export const searchMatchTransactionsEpic = (actions$, state$, zeniAPI) => {
10
+ const openModal$ = actions$.pipe(filter(openMatchNewModal.match), mergeMap((action) => {
11
+ const matchId = action.payload.matchId;
12
+ if (matchId == null || matchId === '') {
13
+ return from([
14
+ searchMatchTransactions({ query: '' }),
15
+ searchMatchAccruals({ query: '' }),
16
+ ]);
17
+ }
18
+ const match = getActualMatchingMatchById(state$.value.actualMatchingState, matchId);
19
+ const parsed = parseActualMatchingMatchId(matchId);
20
+ const transactionType = match?.transactionType != null && match.transactionType !== ''
21
+ ? match.transactionType
22
+ : parsed?.transactionType;
23
+ const transactionIntegrationId = match?.transactionIntegrationId != null &&
24
+ match.transactionIntegrationId !== ''
25
+ ? match.transactionIntegrationId
26
+ : parsed?.transactionIntegrationId;
27
+ return from([
28
+ searchMatchTransactions({
29
+ query: '',
30
+ transactionType,
31
+ transactionIntegrationId,
32
+ }),
33
+ ]);
34
+ }));
35
+ const searchActions$ = actions$.pipe(filter(searchMatchTransactions.match));
36
+ const pagination$ = searchActions$.pipe(filter((action) => action.payload.pageToken != null));
37
+ const newSearchDebounced$ = searchActions$.pipe(filter((action) => action.payload.pageToken == null), debounceTime(MATCH_SEARCH_DEBOUNCE_MS));
38
+ const search$ = merge(pagination$, newSearchDebounced$).pipe(switchMap((action) => {
39
+ const { query, pageToken, transactionType, transactionIntegrationId } = action.payload;
40
+ const queryParam = {
41
+ search_text: query,
42
+ page_size: MATCH_TRANSACTION_SEARCH_PAGE_SIZE,
43
+ ...(pageToken != null ? { page_token: pageToken } : {}),
44
+ ...(transactionType != null && transactionType !== ''
45
+ ? { transaction_type: transactionType }
46
+ : {}),
47
+ ...(transactionIntegrationId != null && transactionIntegrationId !== ''
48
+ ? { transaction_integration_id: transactionIntegrationId }
49
+ : {}),
50
+ };
51
+ return zeniAPI
52
+ .getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/accruals/match-transaction-search?query=${encodeURIComponent(JSON.stringify(queryParam))}`)
53
+ .pipe(mergeMap((response) => {
54
+ if (isSuccessResponse(response) && response.data != null) {
55
+ const { transactions, next_page_token } = response.data;
56
+ const append = pageToken != null;
57
+ const results = transactions.map(toActualMatchingTransactionSearchResult);
58
+ return of(searchMatchTransactionsSuccess({
59
+ append,
60
+ nextPageToken: next_page_token ?? null,
61
+ results,
62
+ }));
63
+ }
64
+ return of(searchMatchTransactionsFailure({
65
+ error: response.status,
66
+ }));
67
+ }), catchError((error) => of(searchMatchTransactionsFailure({
68
+ error: createZeniAPIStatus('Unexpected Error', 'Search match transactions REST API call errored out' +
69
+ JSON.stringify(error)),
70
+ }))));
71
+ }));
72
+ return merge(openModal$, search$);
73
+ };
@@ -0,0 +1,52 @@
1
+ import { from, of } from 'rxjs';
2
+ import { catchError, filter, mergeMap, takeUntil } from 'rxjs/operators';
3
+ import { convertToPeriod, toAbsoluteDay, } from '../../../../commonStateTypes/timePeriod';
4
+ import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
5
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
6
+ import { clearAll } from '../../../../rootActions';
7
+ import { toUndoReversalResult, } from '../../payload/actualMatchingPayload';
8
+ import { fetchActualMatching, fetchActualMatchingMatch, undoReversalAccruals, undoReversalAccrualsFailure, undoReversalAccrualsSuccess, } from '../../reducers/actualMatchingViewReducer';
9
+ function refreshActualMatchingList(state) {
10
+ const currentTenant = getCurrentTenant(state);
11
+ const selectedMonthYear = state.expenseAutomationViewState.selectedPeriodByTenantId[currentTenant?.tenantId ?? ''];
12
+ if (selectedMonthYear == null) {
13
+ return [];
14
+ }
15
+ const period = convertToPeriod(selectedMonthYear);
16
+ const start = toAbsoluteDay(period.start);
17
+ const end = toAbsoluteDay(period.end);
18
+ if (start == null || end == null) {
19
+ return [];
20
+ }
21
+ return [fetchActualMatching({ start, end }, true)];
22
+ }
23
+ function refreshOpenMatchDetails(state) {
24
+ const detailState = state.expenseAutomationActualMatchingViewState.detailFetchStateByMatchId;
25
+ return Object.keys(detailState)
26
+ .filter((matchId) => detailState[matchId]?.fetchState !== 'Not-Started')
27
+ .map((matchId) => fetchActualMatchingMatch(matchId, true));
28
+ }
29
+ export const undoReversalAccrualsEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(undoReversalAccruals.match), mergeMap((action) => {
30
+ const body = {
31
+ accrual_ids: action.payload.accrualIds,
32
+ };
33
+ return zeniAPI
34
+ .postAndGetJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/accruals/undo-reversal`, body)
35
+ .pipe(mergeMap((response) => {
36
+ if (isSuccessResponse(response) && response.data != null) {
37
+ const results = response.data.results.map(toUndoReversalResult);
38
+ const successActions = [
39
+ undoReversalAccrualsSuccess({ results }),
40
+ ];
41
+ const anyUndone = results.some((result) => result.status === 'undone');
42
+ if (anyUndone) {
43
+ successActions.push(...refreshActualMatchingList(state$.value), ...refreshOpenMatchDetails(state$.value));
44
+ }
45
+ return from(successActions);
46
+ }
47
+ return of(undoReversalAccrualsFailure({ status: response.status }));
48
+ }), catchError((error) => of(undoReversalAccrualsFailure({
49
+ status: createZeniAPIStatus('Unexpected Error', 'Undo reversal accruals REST API call errored out' +
50
+ JSON.stringify(error)),
51
+ }))), takeUntil(actions$.pipe(filter(clearAll.match))));
52
+ }));
@@ -3,6 +3,7 @@ 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';
6
7
  import { fetchFluxAnalysisView } from '../../reducers/fluxAnalysisViewReducer';
7
8
  import { fetchJeSchedulesPage } from '../../reducers/jeSchedulesViewReducer';
8
9
  import { fetchMissingReceipts } from '../../reducers/missingReceiptsViewReducer';
@@ -31,11 +32,14 @@ export const fetchAllExpenseAutomationTabsEpic = (actions$, state$) => actions$.
31
32
  }
32
33
  if (cacheOverride === true) {
33
34
  const actions = [];
35
+ const selectedPageTab = state.expenseAutomationJESchedulesViewState.uiState.selectedPageTab;
34
36
  actions.push(fetchTransactionCategorizationView(selectedTransactionCategorizationTab, { start: timePeriodStart, end: timePeriodEnd }, cacheOverride, false, undefined, undefined, refreshViewInBackground, false), fetchMissingReceipts({ start: timePeriodStart, end: timePeriodEnd }, {
35
37
  amount: 75.0,
36
38
  currencyCode: 'USD',
37
39
  currencySymbol: '$',
38
- }, cacheOverride, refreshViewInBackground), fetchFluxAnalysisView(selectedPeriod, cacheOverride, refreshViewInBackground, null), fetchJeSchedulesPage({ start: timePeriodStart, end: timePeriodEnd }, cacheOverride));
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));
39
43
  if (reconciliationEnabled) {
40
44
  actions.push(fetchReconciliation(selectedPeriod, undefined, cacheOverride, refreshViewInBackground));
41
45
  }
@@ -61,16 +65,29 @@ export const fetchAllExpenseAutomationTabsEpic = (actions$, state$) => actions$.
61
65
  fetchActions.push(fetchFluxAnalysisView(selectedPeriod, cacheOverride, refreshViewInBackground, null));
62
66
  }
63
67
  // The two JE tabs cache separately, so only the tab actually being shown decides whether a
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));
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.matchIdsByPeriod[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
+ }
74
91
  }
75
92
  if (state.expenseAutomationReconciliationViewState.fetchState !==
76
93
  'In-Progress' &&
@@ -4,6 +4,7 @@ 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';
7
8
  import { fetchFluxAnalysisView } from '../../reducers/fluxAnalysisViewReducer';
8
9
  import { fetchJeSchedulesPage } from '../../reducers/jeSchedulesViewReducer';
9
10
  import { fetchMissingReceipts } from '../../reducers/missingReceiptsViewReducer';
@@ -38,10 +39,17 @@ export const refreshExpenseAutomationCurrentTabEpic = (actions$, state$) => acti
38
39
  return from([
39
40
  fetchFluxAnalysisView(selectedPeriod, true, true, null),
40
41
  ]);
41
- case 'je_schedules':
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
+ }
42
49
  return from([
43
50
  fetchJeSchedulesPage({ start: timePeriodStart, end: timePeriodEnd }, true),
44
51
  ]);
52
+ }
45
53
  case 'month_end_insights':
46
54
  return from([
47
55
  fetchCompanyMonthEndReportView(true, companyId, currentTenant.tenantId, period.end, true),
@@ -14,7 +14,9 @@ export const prefetchOtherJeSchedulesTabEpic = (actions$, state$) => actions$.pi
14
14
  const state = state$.value;
15
15
  const { period } = action.payload;
16
16
  const { selectedPageTab } = state.expenseAutomationJESchedulesViewState.uiState;
17
- const otherPageTab = ALL_JE_PAGE_TABS.find((pageTab) => pageTab !== selectedPageTab);
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.
19
+ const otherPageTab = ALL_JE_PAGE_TABS.find((pageTab) => pageTab !== selectedPageTab && pageTab !== 'actual_matching');
18
20
  if (otherPageTab == null) {
19
21
  return EMPTY;
20
22
  }
@@ -2,6 +2,7 @@ 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';
5
6
  import { fetchJeSchedulesPage, updateJESchedulesUIState, } from '../../reducers/jeSchedulesViewReducer';
6
7
  export const switchJESchedulesTabEpic = (actions$, state$) => actions$.pipe(filter(updateJESchedulesUIState.match), filter((action) => action.payload.selectedPageTab != null), mergeMap(() => {
7
8
  const state = state$.value;
@@ -24,5 +25,9 @@ export const switchJESchedulesTabEpic = (actions$, state$) => actions$.pipe(filt
24
25
  if (timePeriodStart == null || timePeriodEnd == null) {
25
26
  return EMPTY;
26
27
  }
28
+ const { selectedPageTab } = state.expenseAutomationJESchedulesViewState.uiState;
29
+ if (selectedPageTab === 'actual_matching') {
30
+ return of(fetchActualMatchingPage({ start: timePeriodStart, end: timePeriodEnd }, true));
31
+ }
27
32
  return of(fetchJeSchedulesPage({ start: timePeriodStart, end: timePeriodEnd }, true));
28
33
  }));
@@ -4,6 +4,7 @@ import { getMonthEndCloseChecksForATenantForSelectedPeriod } from '../../entity/
4
4
  import { getCurrentTenant, } from '../../entity/tenant/tenantSelector';
5
5
  import { getCompanyMonthEndReportSelectorView } from '../companyMonthEndReportView/companyMonthEndReportViewSelector';
6
6
  import { getMonthEndCloseChecksViewByTenantId, isMonthEndInsightDisabled, } from '../monthEndCloseChecksView/monthEndCloseChecksViewSelector';
7
+ import { getExpenseAutomationActualMatchingView } from './selectors/actualMatchingViewSelector';
7
8
  import { getExpenseAutomationFluxAnalysisView } from './selectors/fluxAnalysisViewSelector';
8
9
  import { getAllSteps } from './selectors/getAllStepsHelper';
9
10
  import { getExpenseAutomationJESchedulesView } from './selectors/jeSchedulesViewSelector';
@@ -21,7 +22,7 @@ const isPreviousStepDisabled = (selectedTransactionCategorizationTab, allSteps,
21
22
  return allSteps[stepIndex - 1].isDisabled;
22
23
  };
23
24
  export function getExpenseAutomationView(state) {
24
- const { expenseAutomationViewState, tenantState, expenseAutomationMissingReceiptsViewState, expenseAutomationJESchedulesViewState, expenseAutomationTransactionsViewState, expenseAutomationReconciliationViewState, monthEndCloseChecksState, companyState, } = state;
25
+ const { expenseAutomationViewState, tenantState, expenseAutomationMissingReceiptsViewState, expenseAutomationJESchedulesViewState, expenseAutomationActualMatchingViewState, expenseAutomationTransactionsViewState, expenseAutomationReconciliationViewState, monthEndCloseChecksState, companyState, } = state;
25
26
  const { loggedInUser, currentTenantId } = tenantState;
26
27
  const currentTenant = getCurrentTenant(state);
27
28
  const { currentSelectedView, selectedPeriodByTenantId } = expenseAutomationViewState;
@@ -42,6 +43,7 @@ export function getExpenseAutomationView(state) {
42
43
  const reconciliation = getExpenseAutomationReconciliationView(state);
43
44
  const expenseAutomationTransactionsView = getExpenseAutomationTransactionView(state);
44
45
  const jeSchedules = getExpenseAutomationJESchedulesView(state);
46
+ const actualMatching = getExpenseAutomationActualMatchingView(state);
45
47
  switch (currentSelectedView) {
46
48
  case 'reconciliation':
47
49
  currentSelectedViewFetchState = reconciliation.fetchStatus;
@@ -54,12 +56,23 @@ export function getExpenseAutomationView(state) {
54
56
  fluxAnalysis.refreshStatus;
55
57
  break;
56
58
  case 'je_schedules':
57
- currentSelectedViewFetchState = {
58
- fetchState: jeSchedules.fetchState,
59
- error: jeSchedules.error,
60
- };
61
- currentSelectedViewBackgroundRefreshFetchState =
62
- jeSchedules.refreshStatus;
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
+ }
63
76
  break;
64
77
  case 'missing_receipts':
65
78
  currentSelectedViewFetchState = {
@@ -91,6 +104,7 @@ export function getExpenseAutomationView(state) {
91
104
  const fetchStateVariables = [
92
105
  expenseAutomationMissingReceiptsViewState,
93
106
  expenseAutomationJESchedulesViewState,
107
+ expenseAutomationActualMatchingViewState,
94
108
  expenseAutomationTransactionsView,
95
109
  expenseAutomationReconciliationViewState,
96
110
  ];
@@ -176,6 +190,7 @@ export function getExpenseAutomationView(state) {
176
190
  ]);
177
191
  return {
178
192
  jeSchedules,
193
+ actualMatching,
179
194
  transactionView: expenseAutomationTransactionsView,
180
195
  missingReceipts,
181
196
  reconciliation,
@@ -0,0 +1,68 @@
1
+ import { toString } from '../../../commonStateTypes/timePeriod';
2
+ import { toMatchAndReverseResultStatus, toUndoReversalResultStatus, } from '../types/actualMatchingViewState';
3
+ export function toServiceMonthFromPeriod(period) {
4
+ return toString(period.start).slice(0, 7);
5
+ }
6
+ const DEFAULT_CURRENCY_CODE = 'USD';
7
+ const DEFAULT_CURRENCY_SYMBOL = '$';
8
+ function resolveMatchSearchAmount(payload) {
9
+ const raw = payload.total_amount ?? payload.amount;
10
+ if (typeof raw === 'number' && Number.isFinite(raw)) {
11
+ return raw;
12
+ }
13
+ if (typeof raw === 'string' && raw !== '') {
14
+ const parsed = Number(raw);
15
+ if (Number.isFinite(parsed)) {
16
+ return parsed;
17
+ }
18
+ }
19
+ return 0;
20
+ }
21
+ function resolveMatchSearchCurrencySymbol(payload, currencyCode) {
22
+ if (payload.currency_symbol != null && payload.currency_symbol !== '') {
23
+ return payload.currency_symbol;
24
+ }
25
+ return currencyCode === DEFAULT_CURRENCY_CODE
26
+ ? DEFAULT_CURRENCY_SYMBOL
27
+ : currencyCode;
28
+ }
29
+ export function toActualMatchingTransactionSearchResult(payload) {
30
+ const currencyCode = payload.currency_code != null && payload.currency_code !== ''
31
+ ? payload.currency_code
32
+ : DEFAULT_CURRENCY_CODE;
33
+ return {
34
+ transactionId: payload.transaction_id,
35
+ transactionIntegrationId: payload.transaction_integration_id,
36
+ transactionType: payload.transaction_type,
37
+ lineId: payload.line_id,
38
+ vendorId: payload.vendor_id ?? undefined,
39
+ vendorName: payload.vendor_name ?? '',
40
+ amount: resolveMatchSearchAmount(payload),
41
+ transactionDate: payload.transaction_date,
42
+ accountId: payload.account_id ?? undefined,
43
+ accountName: payload.account_name ?? undefined,
44
+ accountDescription: payload.account_description ?? undefined,
45
+ accountingClassId: payload.accounting_class_id ?? undefined,
46
+ accountingClassName: payload.accounting_class_name ?? undefined,
47
+ currencyCode,
48
+ currencySymbol: resolveMatchSearchCurrencySymbol(payload, currencyCode),
49
+ transactionMemo: payload.transaction_memo ?? undefined,
50
+ transactionDescription: payload.transaction_description ?? undefined,
51
+ transactionTypeName: payload.transaction_type_name ?? undefined,
52
+ syncToken: payload.sync_token == null ? '' : String(payload.sync_token),
53
+ };
54
+ }
55
+ export function toMatchAndReverseResult(payload) {
56
+ return {
57
+ accrualId: payload.accrual_id,
58
+ status: toMatchAndReverseResultStatus(payload.status),
59
+ reason: payload.reason ?? undefined,
60
+ };
61
+ }
62
+ export function toUndoReversalResult(payload) {
63
+ return {
64
+ accrualId: payload.accrual_id,
65
+ status: toUndoReversalResultStatus(payload.status),
66
+ reason: payload.reason ?? undefined,
67
+ };
68
+ }