@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,364 @@
1
+ import { createSlice } from '@reduxjs/toolkit';
2
+ import { toMonthYearPeriodId, } from '../../../commonStateTypes/timePeriod';
3
+ import { generateJEOneTimeAccrualKey } from '../../../entity/jeSchedules/jeScheduleHelper';
4
+ import { clearAll } from '../../../rootActions';
5
+ export const initialTransactionSearchState = {
6
+ query: '',
7
+ results: [],
8
+ fetchState: 'Not-Started',
9
+ error: undefined,
10
+ nextPageToken: null,
11
+ isLoadingMore: false,
12
+ };
13
+ export const initialAccrualSearchState = {
14
+ query: '',
15
+ results: [],
16
+ fetchState: 'Not-Started',
17
+ error: undefined,
18
+ nextPageToken: null,
19
+ isLoadingMore: false,
20
+ };
21
+ export const initialMatchModalState = {
22
+ isOpen: false,
23
+ openedFromMatchId: undefined,
24
+ pinnedTransactionResults: [],
25
+ transactionSearch: initialTransactionSearchState,
26
+ accrualSearch: initialAccrualSearchState,
27
+ confirmStatus: { fetchState: 'Not-Started', error: undefined },
28
+ };
29
+ export const initialState = {
30
+ matchIdsByPeriod: {},
31
+ fetchState: 'Not-Started',
32
+ error: undefined,
33
+ refreshStatus: {
34
+ fetchState: 'Not-Started',
35
+ error: undefined,
36
+ },
37
+ detailFetchStateByMatchId: {},
38
+ detailRefreshStatusByMatchId: {},
39
+ uiState: {
40
+ sortKey: 'date',
41
+ sortOrder: 'descending',
42
+ searchString: '',
43
+ scrollPosition: { scrollTop: 0, scrollLeft: undefined },
44
+ },
45
+ matchModal: initialMatchModalState,
46
+ undoReversalStatus: { fetchState: 'Not-Started', error: undefined },
47
+ hasValidState() {
48
+ return this.fetchState == 'Completed';
49
+ },
50
+ };
51
+ function resetMatchModalSearches(draft) {
52
+ draft.matchModal.transactionSearch = initialTransactionSearchState;
53
+ draft.matchModal.accrualSearch = initialAccrualSearchState;
54
+ draft.matchModal.confirmStatus = {
55
+ fetchState: 'Not-Started',
56
+ error: undefined,
57
+ };
58
+ draft.matchModal.lastBatchResults = undefined;
59
+ draft.matchModal.openedFromMatchId = undefined;
60
+ draft.matchModal.pinnedTransactionResults = [];
61
+ }
62
+ const expenseAutomationActualMatchingView = createSlice({
63
+ name: 'expenseAutomationActualMatchingView',
64
+ initialState,
65
+ reducers: {
66
+ fetchActualMatchingPage: {
67
+ prepare(period, refreshViewInBackground = false) {
68
+ return {
69
+ payload: {
70
+ period,
71
+ refreshViewInBackground,
72
+ },
73
+ };
74
+ },
75
+ reducer() {
76
+ // Epic-only fan-out.
77
+ },
78
+ },
79
+ fetchActualMatching: {
80
+ reducer(draft, action) {
81
+ const { refreshViewInBackground } = action.payload;
82
+ if (refreshViewInBackground) {
83
+ draft.refreshStatus = { fetchState: 'In-Progress', error: undefined };
84
+ }
85
+ else {
86
+ draft.fetchState = 'In-Progress';
87
+ draft.error = undefined;
88
+ }
89
+ },
90
+ prepare(period, refreshViewInBackground = false) {
91
+ return {
92
+ payload: {
93
+ period,
94
+ refreshViewInBackground,
95
+ },
96
+ };
97
+ },
98
+ },
99
+ fetchActualMatchingSuccess(draft, action) {
100
+ const { matchIds, selectedPeriod, refreshViewInBackground } = action.payload;
101
+ const periodId = toMonthYearPeriodId(selectedPeriod);
102
+ draft.matchIdsByPeriod[periodId] = matchIds;
103
+ if (refreshViewInBackground) {
104
+ draft.refreshStatus = { fetchState: 'Completed', error: undefined };
105
+ }
106
+ else {
107
+ draft.fetchState = 'Completed';
108
+ draft.error = undefined;
109
+ }
110
+ },
111
+ fetchActualMatchingFailure(draft, action) {
112
+ const { status, refreshViewInBackground } = action.payload;
113
+ if (refreshViewInBackground) {
114
+ draft.refreshStatus = { fetchState: 'Error', error: status };
115
+ }
116
+ else {
117
+ draft.fetchState = 'Error';
118
+ draft.error = status;
119
+ }
120
+ },
121
+ fetchActualMatchingMatch: {
122
+ prepare(matchId, refreshViewInBackground = false) {
123
+ return { payload: { matchId, refreshViewInBackground } };
124
+ },
125
+ reducer(draft, action) {
126
+ const { matchId, refreshViewInBackground } = action.payload;
127
+ if (refreshViewInBackground) {
128
+ draft.detailRefreshStatusByMatchId[matchId] = {
129
+ fetchState: 'In-Progress',
130
+ error: undefined,
131
+ };
132
+ }
133
+ else {
134
+ draft.detailFetchStateByMatchId[matchId] = {
135
+ fetchState: 'In-Progress',
136
+ error: undefined,
137
+ };
138
+ }
139
+ },
140
+ },
141
+ fetchActualMatchingMatchSuccess(draft, action) {
142
+ const { matchId, refreshViewInBackground } = action.payload;
143
+ if (refreshViewInBackground) {
144
+ draft.detailRefreshStatusByMatchId[matchId] = {
145
+ fetchState: 'Completed',
146
+ error: undefined,
147
+ };
148
+ }
149
+ else {
150
+ draft.detailFetchStateByMatchId[matchId] = {
151
+ fetchState: 'Completed',
152
+ error: undefined,
153
+ };
154
+ }
155
+ },
156
+ fetchActualMatchingMatchFailure(draft, action) {
157
+ const { matchId, refreshViewInBackground, status } = action.payload;
158
+ if (refreshViewInBackground) {
159
+ draft.detailRefreshStatusByMatchId[matchId] = {
160
+ fetchState: 'Error',
161
+ error: status,
162
+ };
163
+ }
164
+ else {
165
+ draft.detailFetchStateByMatchId[matchId] = {
166
+ fetchState: 'Error',
167
+ error: status,
168
+ };
169
+ }
170
+ },
171
+ updateActualMatchingUIState(draft, action) {
172
+ if (action.payload.sortKey != null) {
173
+ draft.uiState.sortKey = action.payload.sortKey;
174
+ }
175
+ if (action.payload.sortOrder != null) {
176
+ draft.uiState.sortOrder = action.payload.sortOrder;
177
+ }
178
+ if (action.payload.searchString != null) {
179
+ draft.uiState.searchString = action.payload.searchString;
180
+ }
181
+ if (action.payload.scrollPosition != null) {
182
+ draft.uiState.scrollPosition = action.payload.scrollPosition;
183
+ }
184
+ },
185
+ openMatchNewModal: {
186
+ prepare(matchId) {
187
+ return { payload: { matchId } };
188
+ },
189
+ reducer(draft, action) {
190
+ resetMatchModalSearches(draft);
191
+ draft.matchModal.isOpen = true;
192
+ const matchId = action.payload.matchId;
193
+ draft.matchModal.openedFromMatchId =
194
+ matchId != null && matchId !== '' ? matchId : undefined;
195
+ if (draft.matchModal.openedFromMatchId != null) {
196
+ draft.matchModal.accrualSearch.fetchState = 'Completed';
197
+ }
198
+ },
199
+ },
200
+ closeMatchNewModal(draft) {
201
+ draft.matchModal.isOpen = false;
202
+ resetMatchModalSearches(draft);
203
+ },
204
+ clearMatchNewModal(draft) {
205
+ draft.matchModal.isOpen = false;
206
+ resetMatchModalSearches(draft);
207
+ },
208
+ searchMatchTransactions: {
209
+ reducer(draft, action) {
210
+ const { query, pageToken } = action.payload;
211
+ const isLoadMore = pageToken != null;
212
+ draft.matchModal.transactionSearch.query = query;
213
+ if (isLoadMore) {
214
+ draft.matchModal.transactionSearch.isLoadingMore = true;
215
+ }
216
+ else {
217
+ draft.matchModal.transactionSearch.isLoadingMore = false;
218
+ draft.matchModal.transactionSearch.results = [];
219
+ draft.matchModal.transactionSearch.nextPageToken = null;
220
+ draft.matchModal.transactionSearch.fetchState = 'In-Progress';
221
+ draft.matchModal.transactionSearch.error = undefined;
222
+ }
223
+ },
224
+ prepare({ query, pageToken, transactionType, transactionIntegrationId, }) {
225
+ return {
226
+ payload: {
227
+ query,
228
+ pageToken: pageToken ?? undefined,
229
+ transactionType,
230
+ transactionIntegrationId,
231
+ },
232
+ };
233
+ },
234
+ },
235
+ searchMatchTransactionsSuccess(draft, action) {
236
+ const { append, nextPageToken, results } = action.payload;
237
+ if (append) {
238
+ const merged = [
239
+ ...draft.matchModal.transactionSearch.results,
240
+ ...results,
241
+ ];
242
+ const byKey = new Map(merged.map((result) => [
243
+ `${result.transactionId}:${result.lineId}`,
244
+ result,
245
+ ]));
246
+ draft.matchModal.transactionSearch.results = Array.from(byKey.values());
247
+ }
248
+ else {
249
+ draft.matchModal.transactionSearch.results = results;
250
+ if (draft.matchModal.openedFromMatchId != null &&
251
+ draft.matchModal.pinnedTransactionResults.length === 0) {
252
+ draft.matchModal.pinnedTransactionResults = results;
253
+ }
254
+ }
255
+ draft.matchModal.transactionSearch.nextPageToken = nextPageToken;
256
+ draft.matchModal.transactionSearch.isLoadingMore = false;
257
+ draft.matchModal.transactionSearch.fetchState = 'Completed';
258
+ draft.matchModal.transactionSearch.error = undefined;
259
+ },
260
+ searchMatchTransactionsFailure(draft, action) {
261
+ draft.matchModal.transactionSearch.isLoadingMore = false;
262
+ draft.matchModal.transactionSearch.fetchState = 'Error';
263
+ draft.matchModal.transactionSearch.error = action.payload.error;
264
+ },
265
+ searchMatchAccruals: {
266
+ reducer(draft, action) {
267
+ const { query, pageToken } = action.payload;
268
+ const isLoadMore = pageToken != null;
269
+ draft.matchModal.accrualSearch.query = query;
270
+ if (isLoadMore) {
271
+ draft.matchModal.accrualSearch.isLoadingMore = true;
272
+ }
273
+ else {
274
+ draft.matchModal.accrualSearch.isLoadingMore = false;
275
+ draft.matchModal.accrualSearch.results = [];
276
+ draft.matchModal.accrualSearch.nextPageToken = null;
277
+ draft.matchModal.accrualSearch.fetchState = 'In-Progress';
278
+ draft.matchModal.accrualSearch.error = undefined;
279
+ }
280
+ },
281
+ prepare({ query, pageToken }) {
282
+ return { payload: { query, pageToken: pageToken ?? undefined } };
283
+ },
284
+ },
285
+ searchMatchAccrualsSuccess(draft, action) {
286
+ const { append, nextPageToken, results } = action.payload;
287
+ const keys = results.map((accrual) => generateJEOneTimeAccrualKey(accrual._id));
288
+ if (append) {
289
+ const merged = [...draft.matchModal.accrualSearch.results, ...keys];
290
+ draft.matchModal.accrualSearch.results = Array.from(new Set(merged));
291
+ }
292
+ else {
293
+ draft.matchModal.accrualSearch.results = keys;
294
+ }
295
+ draft.matchModal.accrualSearch.nextPageToken = nextPageToken;
296
+ draft.matchModal.accrualSearch.isLoadingMore = false;
297
+ draft.matchModal.accrualSearch.fetchState = 'Completed';
298
+ draft.matchModal.accrualSearch.error = undefined;
299
+ },
300
+ searchMatchAccrualsFailure(draft, action) {
301
+ draft.matchModal.accrualSearch.isLoadingMore = false;
302
+ draft.matchModal.accrualSearch.fetchState = 'Error';
303
+ draft.matchModal.accrualSearch.error = action.payload.error;
304
+ },
305
+ matchAndReverseAccruals: {
306
+ reducer(draft) {
307
+ draft.matchModal.confirmStatus = {
308
+ fetchState: 'In-Progress',
309
+ error: undefined,
310
+ };
311
+ },
312
+ prepare(params) {
313
+ return { payload: params };
314
+ },
315
+ },
316
+ matchAndReverseAccrualsSuccess(draft, action) {
317
+ draft.matchModal.confirmStatus = {
318
+ fetchState: 'Completed',
319
+ error: undefined,
320
+ };
321
+ draft.matchModal.lastBatchResults = action.payload.results;
322
+ },
323
+ matchAndReverseAccrualsFailure(draft, action) {
324
+ draft.matchModal.confirmStatus = {
325
+ fetchState: 'Error',
326
+ error: action.payload.status,
327
+ };
328
+ },
329
+ undoReversalAccruals: {
330
+ reducer(draft) {
331
+ draft.undoReversalStatus = {
332
+ fetchState: 'In-Progress',
333
+ error: undefined,
334
+ };
335
+ },
336
+ prepare(params) {
337
+ return { payload: params };
338
+ },
339
+ },
340
+ undoReversalAccrualsSuccess(draft, action) {
341
+ draft.undoReversalStatus = {
342
+ fetchState: 'Completed',
343
+ error: undefined,
344
+ };
345
+ void action;
346
+ },
347
+ undoReversalAccrualsFailure(draft, action) {
348
+ draft.undoReversalStatus = {
349
+ fetchState: 'Error',
350
+ error: action.payload.status,
351
+ };
352
+ },
353
+ clearActualMatchingView(draft) {
354
+ Object.assign(draft, initialState);
355
+ },
356
+ },
357
+ extraReducers: (builder) => {
358
+ builder.addCase(clearAll, (draft) => {
359
+ Object.assign(draft, initialState);
360
+ });
361
+ },
362
+ });
363
+ export const { clearActualMatchingView, clearMatchNewModal, closeMatchNewModal, fetchActualMatching, fetchActualMatchingFailure, fetchActualMatchingMatch, fetchActualMatchingMatchFailure, fetchActualMatchingMatchSuccess, fetchActualMatchingPage, fetchActualMatchingSuccess, matchAndReverseAccruals, matchAndReverseAccrualsFailure, matchAndReverseAccrualsSuccess, openMatchNewModal, searchMatchAccruals, searchMatchAccrualsFailure, searchMatchAccrualsSuccess, searchMatchTransactions, searchMatchTransactionsFailure, searchMatchTransactionsSuccess, undoReversalAccruals, undoReversalAccrualsFailure, undoReversalAccrualsSuccess, updateActualMatchingUIState, } = expenseAutomationActualMatchingView.actions;
364
+ export default expenseAutomationActualMatchingView.reducer;
@@ -0,0 +1,281 @@
1
+ import orderBy from 'lodash/orderBy';
2
+ import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
3
+ import { getActualMatchingMatchById } from '../../../entity/actualMatching/actualMatchingSelector';
4
+ import { generateJEOneTimeAccrualKey } from '../../../entity/jeSchedules/jeScheduleHelper';
5
+ import { getJEOneTimeAccrualByKey, } from '../../../entity/jeSchedules/jeSchedulesSelector';
6
+ import { getCurrentTenant } from '../../../entity/tenant/tenantSelector';
7
+ function toMatchModalAccrual(member) {
8
+ if ('jeOneTimeAccrualKey' in member && member.jeOneTimeAccrualKey != null) {
9
+ return member;
10
+ }
11
+ return {
12
+ ...member,
13
+ jeOneTimeAccrualKey: generateJEOneTimeAccrualKey(member.accrualId),
14
+ };
15
+ }
16
+ function uniqueMatchModalAccruals(pinned, searched) {
17
+ const byId = new Map();
18
+ pinned.forEach((row) => byId.set(row.accrualId, row));
19
+ searched.forEach((row) => {
20
+ if (!byId.has(row.accrualId)) {
21
+ byId.set(row.accrualId, row);
22
+ }
23
+ });
24
+ return Array.from(byId.values());
25
+ }
26
+ function uniqueTransactionSearchResults(pinned, searched) {
27
+ const byKey = new Map();
28
+ pinned.forEach((row) => byKey.set(`${row.transactionId}:${row.lineId}`, row));
29
+ searched.forEach((row) => {
30
+ const key = `${row.transactionId}:${row.lineId}`;
31
+ if (!byKey.has(key)) {
32
+ byKey.set(key, row);
33
+ }
34
+ });
35
+ return Array.from(byKey.values());
36
+ }
37
+ export function parseActualMatchingMatchId(matchId) {
38
+ const separator = matchId.indexOf(':');
39
+ if (separator <= 0 || separator === matchId.length - 1) {
40
+ return null;
41
+ }
42
+ return {
43
+ transactionType: matchId.slice(0, separator).toLowerCase(),
44
+ transactionIntegrationId: matchId.slice(separator + 1),
45
+ };
46
+ }
47
+ const NOT_STARTED = {
48
+ fetchState: 'Not-Started',
49
+ error: undefined,
50
+ };
51
+ export function isAutoReversedAccrual(accrual) {
52
+ return (accrual.accrualStatus === 'reversed' &&
53
+ accrual.reversalTrigger === 'bill_match');
54
+ }
55
+ export function isManuallyReversedAccrual(accrual) {
56
+ return (accrual.accrualStatus === 'reversed' &&
57
+ accrual.reversalTrigger === 'manual_reverse');
58
+ }
59
+ export function isPendingReviewAccrual(accrual) {
60
+ if (accrual.accrualStatus !== 'posted' || accrual.matchSuggestion == null) {
61
+ return false;
62
+ }
63
+ const resolution = accrual.matchSuggestion.resolution;
64
+ return resolution == null || resolution.length === 0;
65
+ }
66
+ export function isReversedMatchKind(kind) {
67
+ return kind === 'auto_reversed' || kind === 'manually_reversed';
68
+ }
69
+ export function getClearedMatchedAmount(accrual) {
70
+ return accrual.cleared?.matchedAmount?.amount;
71
+ }
72
+ export function getActualMatchingAmount(row) {
73
+ return row.actualAmount?.amount ?? row.estimatedAmount.amount;
74
+ }
75
+ export function getActualMatchingReversalDate(accrual) {
76
+ if (isPendingReviewAccrual(accrual)) {
77
+ return accrual.matchSuggestion.matchedTransactionDate;
78
+ }
79
+ return accrual.cleared?.matchedTransactionDate;
80
+ }
81
+ function collectGroupedMembers(members) {
82
+ if (members.length <= 1) {
83
+ return undefined;
84
+ }
85
+ return members.map((member) => ({
86
+ accrualId: member.accrualId,
87
+ estimatedAmount: member.estimatedAmount,
88
+ jeDebitAccountName: member.jeDebitAccountName,
89
+ memo: member.memo,
90
+ serviceMonth: member.serviceMonth,
91
+ }));
92
+ }
93
+ function toActualMatchingRow(match) {
94
+ const firstMember = match.members[0];
95
+ if (firstMember == null) {
96
+ throw new Error('Actual matching row requires at least one member');
97
+ }
98
+ const first = toMatchModalAccrual(firstMember);
99
+ return {
100
+ ...first,
101
+ matchId: match.matchId,
102
+ kind: match.kind,
103
+ accruedAmount: match.accruedAmount,
104
+ actualAmount: match.actualAmount,
105
+ matchTier: match.matchTier,
106
+ matchShape: match.matchShape,
107
+ transactionType: match.transactionType,
108
+ transactionDate: match.transactionDate,
109
+ groupedAccrualIds: match.members.map((member) => member.accrualId),
110
+ groupedMembers: collectGroupedMembers(match.members),
111
+ reversalDate: getActualMatchingReversalDate(first),
112
+ vendorName: match.vendorName !== '' ? match.vendorName : first.vendorName,
113
+ estimatedAmount: match.accruedAmount,
114
+ };
115
+ }
116
+ function matchesSearch(row, searchString) {
117
+ const trimmed = searchString.trim().toLowerCase();
118
+ if (trimmed.length === 0) {
119
+ return true;
120
+ }
121
+ if (row.vendorName.toLowerCase().includes(trimmed)) {
122
+ return true;
123
+ }
124
+ if (row.memo.toLowerCase().includes(trimmed)) {
125
+ return true;
126
+ }
127
+ return (row.groupedMembers ?? []).some((member) => (member.memo ?? '').toLowerCase().includes(trimmed));
128
+ }
129
+ function getActualMatchingSortValue(row, sortKey) {
130
+ switch (sortKey) {
131
+ case 'vendor':
132
+ return row.vendorName.toLowerCase();
133
+ case 'date':
134
+ return row.reversalDate?.valueOf() ?? row.transactionDate;
135
+ case 'amount':
136
+ return getActualMatchingAmount(row);
137
+ case 'category':
138
+ return row.jeDebitAccountName?.toLowerCase();
139
+ case 'class':
140
+ return row.jeDebitAccountingClassName?.toLowerCase();
141
+ case 'type':
142
+ return row.transactionType?.toLowerCase();
143
+ case 'lifecycle':
144
+ return row.kind;
145
+ case 'service':
146
+ return row.serviceMonth;
147
+ case 'accrued':
148
+ return row.accruedAmount?.amount ?? row.estimatedAmount.amount;
149
+ case 'actual':
150
+ return row.actualAmount?.amount;
151
+ case 'variance': {
152
+ const accrued = row.accruedAmount?.amount ?? row.estimatedAmount.amount;
153
+ const actual = row.actualAmount?.amount;
154
+ if (actual == null || accrued === 0) {
155
+ return undefined;
156
+ }
157
+ return ((actual - accrued) / Math.abs(accrued)) * 100;
158
+ }
159
+ default:
160
+ return undefined;
161
+ }
162
+ }
163
+ function sortActualMatchingRows(rows, uiState) {
164
+ const lodashOrder = uiState.sortOrder === 'ascending' ? 'asc' : 'desc';
165
+ return orderBy(rows, [(row) => getActualMatchingSortValue(row, uiState.sortKey)], [lodashOrder]);
166
+ }
167
+ function emptyKpiSummary() {
168
+ return {
169
+ autoReversed: { count: 0, amount: 0 },
170
+ pendingReview: { count: 0, amount: 0 },
171
+ totalThisClose: { count: 0, amount: 0 },
172
+ ai: { count: 0 },
173
+ manual: { count: 0 },
174
+ };
175
+ }
176
+ function sumRowAmounts(rows) {
177
+ return rows.reduce((sum, row) => sum + getActualMatchingAmount(row), 0);
178
+ }
179
+ export function getExpenseAutomationActualMatchingMatchView(state, matchId) {
180
+ const view = state.expenseAutomationActualMatchingViewState;
181
+ return {
182
+ match: getActualMatchingMatchById(state.actualMatchingState, matchId),
183
+ detailFetchState: view.detailFetchStateByMatchId[matchId] ?? NOT_STARTED,
184
+ refreshStatus: view.detailRefreshStatusByMatchId[matchId] ?? NOT_STARTED,
185
+ undoReversalStatus: view.undoReversalStatus ?? NOT_STARTED,
186
+ };
187
+ }
188
+ export function getExpenseAutomationActualMatchingView(state) {
189
+ const { expenseAutomationActualMatchingViewState, expenseAutomationViewState } = state;
190
+ const { matchIdsByPeriod, fetchState, error, refreshStatus, uiState, matchModal, undoReversalStatus, } = expenseAutomationActualMatchingViewState;
191
+ const currentTenant = getCurrentTenant(state);
192
+ const selectedPeriod = expenseAutomationViewState.selectedPeriodByTenantId[currentTenant?.tenantId ?? ''];
193
+ const monthYearPeriodId = selectedPeriod != null ? toMonthYearPeriodId(selectedPeriod) : undefined;
194
+ const matchIds = monthYearPeriodId != null ? matchIdsByPeriod[monthYearPeriodId] : undefined;
195
+ const rows = (matchIds ?? [])
196
+ .map((matchId) => getActualMatchingMatchById(state.actualMatchingState, matchId))
197
+ .filter((match) => match != null)
198
+ .filter((match) => match.members.length > 0)
199
+ .map(toActualMatchingRow)
200
+ .filter((row) => matchesSearch(row, uiState.searchString));
201
+ const reversed = sortActualMatchingRows(rows.filter((row) => isReversedMatchKind(row.kind)), uiState);
202
+ const pendingReview = sortActualMatchingRows(rows.filter((row) => row.kind === 'pending_review'), uiState);
203
+ const autoReversedForKpi = reversed.filter((row) => row.kind === 'auto_reversed');
204
+ const listedRows = [...reversed, ...pendingReview];
205
+ const kpiSummary = listedRows.length === 0
206
+ ? emptyKpiSummary()
207
+ : {
208
+ autoReversed: {
209
+ count: autoReversedForKpi.length,
210
+ amount: sumRowAmounts(autoReversedForKpi),
211
+ },
212
+ pendingReview: {
213
+ count: pendingReview.length,
214
+ amount: sumRowAmounts(pendingReview),
215
+ },
216
+ totalThisClose: {
217
+ count: listedRows.length,
218
+ amount: sumRowAmounts(listedRows),
219
+ },
220
+ ai: {
221
+ count: listedRows.filter((row) => row.origin === 'agent').length,
222
+ },
223
+ manual: {
224
+ count: listedRows.filter((row) => row.origin === 'human').length,
225
+ },
226
+ };
227
+ const searchedAccruals = matchModal.accrualSearch.results
228
+ .map((key) => getJEOneTimeAccrualByKey(key, state.jeSchedulesState))
229
+ .filter((accrual) => accrual != null);
230
+ const openedMatch = matchModal.openedFromMatchId != null
231
+ ? getActualMatchingMatchById(state.actualMatchingState, matchModal.openedFromMatchId)
232
+ : undefined;
233
+ const pinnedAccruals = (openedMatch?.members ?? []).map(toMatchModalAccrual);
234
+ const firstSuggestion = openedMatch?.members[0]?.matchSuggestion;
235
+ const prefill = openedMatch == null
236
+ ? undefined
237
+ : {
238
+ selectedAccrualIds: openedMatch.members.map((member) => member.accrualId),
239
+ actualAmount: openedMatch.actualAmount.amount,
240
+ matchedLineId: firstSuggestion?.matchedLineId,
241
+ matchedTransactionId: firstSuggestion?.matchedTransactionId,
242
+ };
243
+ return {
244
+ fetchState,
245
+ error,
246
+ reversed,
247
+ pendingReview,
248
+ kpiSummary,
249
+ uiState,
250
+ refreshStatus,
251
+ hasLoadedForSelectedPeriod: matchIds != null,
252
+ undoReversalStatus,
253
+ matchModal: {
254
+ isOpen: matchModal.isOpen,
255
+ openedFromMatchId: matchModal.openedFromMatchId,
256
+ prefill,
257
+ confirmStatus: matchModal.confirmStatus,
258
+ lastBatchResults: matchModal.lastBatchResults,
259
+ transactionSearch: {
260
+ query: matchModal.transactionSearch.query,
261
+ results: uniqueTransactionSearchResults(matchModal.pinnedTransactionResults, matchModal.transactionSearch.results),
262
+ fetchState: {
263
+ fetchState: matchModal.transactionSearch.fetchState,
264
+ error: matchModal.transactionSearch.error,
265
+ },
266
+ nextPageToken: matchModal.transactionSearch.nextPageToken,
267
+ isLoadingMore: matchModal.transactionSearch.isLoadingMore,
268
+ },
269
+ accrualSearch: {
270
+ query: matchModal.accrualSearch.query,
271
+ results: uniqueMatchModalAccruals(pinnedAccruals, searchedAccruals),
272
+ fetchState: {
273
+ fetchState: matchModal.accrualSearch.fetchState,
274
+ error: matchModal.accrualSearch.error,
275
+ },
276
+ nextPageToken: matchModal.accrualSearch.nextPageToken,
277
+ isLoadingMore: matchModal.accrualSearch.isLoadingMore,
278
+ },
279
+ },
280
+ };
281
+ }
@@ -366,6 +366,9 @@ export function getExpenseAutomationJESchedulesView(state) {
366
366
  // reports undefined rather than 0, so the UI can hide the badge instead of asserting "none".
367
367
  const scheduleCountsByTab = {};
368
368
  ALL_JE_PAGE_TABS.forEach((pageTab) => {
369
+ if (pageTab === 'actual_matching') {
370
+ return;
371
+ }
369
372
  const periodTabKey = monthYearPeriodId != null
370
373
  ? getJESchedulePeriodTabKey(pageTab, monthYearPeriodId)
371
374
  : null;
@@ -0,0 +1,32 @@
1
+ import { stringToUnion } from '../../../commonStateTypes/stringToUnion';
2
+ const ACTUAL_MATCHING_SORT_KEYS = [
3
+ 'vendor',
4
+ 'date',
5
+ 'amount',
6
+ 'category',
7
+ 'class',
8
+ 'type',
9
+ 'lifecycle',
10
+ 'service',
11
+ 'accrued',
12
+ 'actual',
13
+ 'variance',
14
+ ];
15
+ export const toActualMatchingSortKey = (v) => stringToUnion(v, ACTUAL_MATCHING_SORT_KEYS);
16
+ export const MATCH_TRANSACTION_SEARCH_PAGE_SIZE = 50;
17
+ export const MATCH_ACCRUAL_SEARCH_LIMIT = 50;
18
+ export const MATCH_SEARCH_DEBOUNCE_MS = 300;
19
+ const MATCH_AND_REVERSE_RESULT_STATUSES = [
20
+ 'reversed',
21
+ 'noop',
22
+ 'conflict',
23
+ 'claim_lost',
24
+ 'error',
25
+ ];
26
+ export const toMatchAndReverseResultStatus = (v) => stringToUnion(v, MATCH_AND_REVERSE_RESULT_STATUSES);
27
+ const UNDO_REVERSAL_RESULT_STATUSES = [
28
+ 'undone',
29
+ 'claim_lost',
30
+ 'error',
31
+ ];
32
+ export const toUndoReversalResultStatus = (v) => stringToUnion(v, UNDO_REVERSAL_RESULT_STATUSES);