@zeniai/client-epic-state 5.2.22-betaAM3 → 5.2.22-betaAM5
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 +25 -0
- package/lib/entity/actualMatching/actualMatchingPayload.js +42 -0
- package/lib/entity/actualMatching/actualMatchingReducer.d.ts +8 -0
- package/lib/entity/actualMatching/actualMatchingReducer.js +33 -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 +22 -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/epic.d.ts +2 -1
- package/lib/epic.js +2 -0
- package/lib/esm/entity/actualMatching/actualMatchingPayload.js +39 -0
- package/lib/esm/entity/actualMatching/actualMatchingReducer.js +30 -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/epic.js +2 -0
- package/lib/esm/index.js +3 -3
- package/lib/esm/reducer.js +3 -0
- package/lib/esm/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingEpic.js +6 -6
- package/lib/esm/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingMatchEpic.js +35 -0
- package/lib/esm/view/expenseAutomationView/epics/actualMatching/searchMatchTransactionsEpic.js +35 -6
- package/lib/esm/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +1 -1
- package/lib/esm/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +87 -8
- package/lib/esm/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +152 -93
- package/lib/esm/view/expenseAutomationView/types/actualMatchingViewState.js +4 -0
- package/lib/index.d.ts +6 -3
- package/lib/index.js +21 -18
- package/lib/reducer.d.ts +1 -0
- package/lib/reducer.js +3 -0
- package/lib/rootStateTypes.d.ts +2 -0
- package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingEpic.d.ts +2 -2
- package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingEpic.js +6 -6
- package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingMatchEpic.d.ts +12 -0
- package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingMatchEpic.js +39 -0
- package/lib/view/expenseAutomationView/epics/actualMatching/searchMatchTransactionsEpic.d.ts +1 -1
- package/lib/view/expenseAutomationView/epics/actualMatching/searchMatchTransactionsEpic.js +35 -6
- package/lib/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +1 -1
- package/lib/view/expenseAutomationView/payload/actualMatchingPayload.d.ts +3 -6
- package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.d.ts +20 -3
- package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +88 -9
- package/lib/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.d.ts +24 -2
- package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.d.ts +16 -2
- package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +155 -93
- package/lib/view/expenseAutomationView/types/actualMatchingViewState.d.ts +7 -2
- package/lib/view/expenseAutomationView/types/actualMatchingViewState.js +4 -0
- package/package.json +1 -1
|
@@ -20,18 +20,22 @@ export const initialAccrualSearchState = {
|
|
|
20
20
|
};
|
|
21
21
|
export const initialMatchModalState = {
|
|
22
22
|
isOpen: false,
|
|
23
|
+
openedFromMatchId: undefined,
|
|
24
|
+
pinnedTransactionResults: [],
|
|
23
25
|
transactionSearch: initialTransactionSearchState,
|
|
24
26
|
accrualSearch: initialAccrualSearchState,
|
|
25
27
|
confirmStatus: { fetchState: 'Not-Started', error: undefined },
|
|
26
28
|
};
|
|
27
29
|
export const initialState = {
|
|
28
|
-
|
|
30
|
+
matchIdsByPeriod: {},
|
|
29
31
|
fetchState: 'Not-Started',
|
|
30
32
|
error: undefined,
|
|
31
33
|
refreshStatus: {
|
|
32
34
|
fetchState: 'Not-Started',
|
|
33
35
|
error: undefined,
|
|
34
36
|
},
|
|
37
|
+
detailFetchStateByMatchId: {},
|
|
38
|
+
detailRefreshStatusByMatchId: {},
|
|
35
39
|
uiState: {
|
|
36
40
|
sortKey: 'date',
|
|
37
41
|
sortOrder: 'descending',
|
|
@@ -51,6 +55,8 @@ function resetMatchModalSearches(draft) {
|
|
|
51
55
|
error: undefined,
|
|
52
56
|
};
|
|
53
57
|
draft.matchModal.lastBatchResults = undefined;
|
|
58
|
+
draft.matchModal.openedFromMatchId = undefined;
|
|
59
|
+
draft.matchModal.pinnedTransactionResults = [];
|
|
54
60
|
}
|
|
55
61
|
const expenseAutomationActualMatchingView = createSlice({
|
|
56
62
|
name: 'expenseAutomationActualMatchingView',
|
|
@@ -90,9 +96,9 @@ const expenseAutomationActualMatchingView = createSlice({
|
|
|
90
96
|
},
|
|
91
97
|
},
|
|
92
98
|
fetchActualMatchingSuccess(draft, action) {
|
|
93
|
-
const {
|
|
99
|
+
const { matchIds, selectedPeriod, refreshViewInBackground } = action.payload;
|
|
94
100
|
const periodId = toMonthYearPeriodId(selectedPeriod);
|
|
95
|
-
draft.
|
|
101
|
+
draft.matchIdsByPeriod[periodId] = matchIds;
|
|
96
102
|
if (refreshViewInBackground) {
|
|
97
103
|
draft.refreshStatus = { fetchState: 'Completed', error: undefined };
|
|
98
104
|
}
|
|
@@ -111,6 +117,56 @@ const expenseAutomationActualMatchingView = createSlice({
|
|
|
111
117
|
draft.error = status;
|
|
112
118
|
}
|
|
113
119
|
},
|
|
120
|
+
fetchActualMatchingMatch: {
|
|
121
|
+
prepare(matchId, refreshViewInBackground = false) {
|
|
122
|
+
return { payload: { matchId, refreshViewInBackground } };
|
|
123
|
+
},
|
|
124
|
+
reducer(draft, action) {
|
|
125
|
+
const { matchId, refreshViewInBackground } = action.payload;
|
|
126
|
+
if (refreshViewInBackground) {
|
|
127
|
+
draft.detailRefreshStatusByMatchId[matchId] = {
|
|
128
|
+
fetchState: 'In-Progress',
|
|
129
|
+
error: undefined,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
draft.detailFetchStateByMatchId[matchId] = {
|
|
134
|
+
fetchState: 'In-Progress',
|
|
135
|
+
error: undefined,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
fetchActualMatchingMatchSuccess(draft, action) {
|
|
141
|
+
const { matchId, refreshViewInBackground } = action.payload;
|
|
142
|
+
if (refreshViewInBackground) {
|
|
143
|
+
draft.detailRefreshStatusByMatchId[matchId] = {
|
|
144
|
+
fetchState: 'Completed',
|
|
145
|
+
error: undefined,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
draft.detailFetchStateByMatchId[matchId] = {
|
|
150
|
+
fetchState: 'Completed',
|
|
151
|
+
error: undefined,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
fetchActualMatchingMatchFailure(draft, action) {
|
|
156
|
+
const { matchId, refreshViewInBackground, status } = action.payload;
|
|
157
|
+
if (refreshViewInBackground) {
|
|
158
|
+
draft.detailRefreshStatusByMatchId[matchId] = {
|
|
159
|
+
fetchState: 'Error',
|
|
160
|
+
error: status,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
draft.detailFetchStateByMatchId[matchId] = {
|
|
165
|
+
fetchState: 'Error',
|
|
166
|
+
error: status,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
},
|
|
114
170
|
updateActualMatchingUIState(draft, action) {
|
|
115
171
|
if (action.payload.sortKey != null) {
|
|
116
172
|
draft.uiState.sortKey = action.payload.sortKey;
|
|
@@ -125,8 +181,20 @@ const expenseAutomationActualMatchingView = createSlice({
|
|
|
125
181
|
draft.uiState.scrollPosition = action.payload.scrollPosition;
|
|
126
182
|
}
|
|
127
183
|
},
|
|
128
|
-
openMatchNewModal
|
|
129
|
-
|
|
184
|
+
openMatchNewModal: {
|
|
185
|
+
prepare(matchId) {
|
|
186
|
+
return { payload: { matchId } };
|
|
187
|
+
},
|
|
188
|
+
reducer(draft, action) {
|
|
189
|
+
resetMatchModalSearches(draft);
|
|
190
|
+
draft.matchModal.isOpen = true;
|
|
191
|
+
const matchId = action.payload.matchId;
|
|
192
|
+
draft.matchModal.openedFromMatchId =
|
|
193
|
+
matchId != null && matchId !== '' ? matchId : undefined;
|
|
194
|
+
if (draft.matchModal.openedFromMatchId != null) {
|
|
195
|
+
draft.matchModal.accrualSearch.fetchState = 'Completed';
|
|
196
|
+
}
|
|
197
|
+
},
|
|
130
198
|
},
|
|
131
199
|
closeMatchNewModal(draft) {
|
|
132
200
|
draft.matchModal.isOpen = false;
|
|
@@ -152,8 +220,15 @@ const expenseAutomationActualMatchingView = createSlice({
|
|
|
152
220
|
draft.matchModal.transactionSearch.error = undefined;
|
|
153
221
|
}
|
|
154
222
|
},
|
|
155
|
-
prepare({ query, pageToken }) {
|
|
156
|
-
return {
|
|
223
|
+
prepare({ query, pageToken, transactionType, transactionIntegrationId, }) {
|
|
224
|
+
return {
|
|
225
|
+
payload: {
|
|
226
|
+
query,
|
|
227
|
+
pageToken: pageToken ?? undefined,
|
|
228
|
+
transactionType,
|
|
229
|
+
transactionIntegrationId,
|
|
230
|
+
},
|
|
231
|
+
};
|
|
157
232
|
},
|
|
158
233
|
},
|
|
159
234
|
searchMatchTransactionsSuccess(draft, action) {
|
|
@@ -171,6 +246,10 @@ const expenseAutomationActualMatchingView = createSlice({
|
|
|
171
246
|
}
|
|
172
247
|
else {
|
|
173
248
|
draft.matchModal.transactionSearch.results = results;
|
|
249
|
+
if (draft.matchModal.openedFromMatchId != null &&
|
|
250
|
+
draft.matchModal.pinnedTransactionResults.length === 0) {
|
|
251
|
+
draft.matchModal.pinnedTransactionResults = results;
|
|
252
|
+
}
|
|
174
253
|
}
|
|
175
254
|
draft.matchModal.transactionSearch.nextPageToken = nextPageToken;
|
|
176
255
|
draft.matchModal.transactionSearch.isLoadingMore = false;
|
|
@@ -256,5 +335,5 @@ const expenseAutomationActualMatchingView = createSlice({
|
|
|
256
335
|
});
|
|
257
336
|
},
|
|
258
337
|
});
|
|
259
|
-
export const { clearActualMatchingView, clearMatchNewModal, closeMatchNewModal, fetchActualMatching, fetchActualMatchingFailure, fetchActualMatchingPage, fetchActualMatchingSuccess, matchAndReverseAccruals, matchAndReverseAccrualsFailure, matchAndReverseAccrualsSuccess, openMatchNewModal, searchMatchAccruals, searchMatchAccrualsFailure, searchMatchAccrualsSuccess, searchMatchTransactions, searchMatchTransactionsFailure, searchMatchTransactionsSuccess, updateActualMatchingUIState, } = expenseAutomationActualMatchingView.actions;
|
|
338
|
+
export const { clearActualMatchingView, clearMatchNewModal, closeMatchNewModal, fetchActualMatching, fetchActualMatchingFailure, fetchActualMatchingMatch, fetchActualMatchingMatchFailure, fetchActualMatchingMatchSuccess, fetchActualMatchingPage, fetchActualMatchingSuccess, matchAndReverseAccruals, matchAndReverseAccrualsFailure, matchAndReverseAccrualsSuccess, openMatchNewModal, searchMatchAccruals, searchMatchAccrualsFailure, searchMatchAccrualsSuccess, searchMatchTransactions, searchMatchTransactionsFailure, searchMatchTransactionsSuccess, updateActualMatchingUIState, } = expenseAutomationActualMatchingView.actions;
|
|
260
339
|
export default expenseAutomationActualMatchingView.reducer;
|
|
@@ -1,7 +1,53 @@
|
|
|
1
1
|
import orderBy from 'lodash/orderBy';
|
|
2
2
|
import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
|
|
3
|
+
import { getActualMatchingMatchById } from '../../../entity/actualMatching/actualMatchingSelector';
|
|
4
|
+
import { generateJEOneTimeAccrualKey } from '../../../entity/jeSchedules/jeScheduleHelper';
|
|
3
5
|
import { getJEOneTimeAccrualByKey, } from '../../../entity/jeSchedules/jeSchedulesSelector';
|
|
4
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
|
+
};
|
|
5
51
|
export function isAutoReversedAccrual(accrual) {
|
|
6
52
|
return (accrual.accrualStatus === 'reversed' &&
|
|
7
53
|
accrual.reversalTrigger === 'bill_match');
|
|
@@ -17,18 +63,14 @@ export function isPendingReviewAccrual(accrual) {
|
|
|
17
63
|
const resolution = accrual.matchSuggestion.resolution;
|
|
18
64
|
return resolution == null || resolution.length === 0;
|
|
19
65
|
}
|
|
66
|
+
export function isReversedMatchKind(kind) {
|
|
67
|
+
return kind === 'auto_reversed' || kind === 'manually_reversed';
|
|
68
|
+
}
|
|
20
69
|
export function getClearedMatchedAmount(accrual) {
|
|
21
70
|
return accrual.cleared?.matchedAmount?.amount;
|
|
22
71
|
}
|
|
23
|
-
export function getActualMatchingAmount(
|
|
24
|
-
|
|
25
|
-
return (accrual.matchSuggestion?.matchedAmount?.amount ??
|
|
26
|
-
accrual.estimatedAmount.amount);
|
|
27
|
-
}
|
|
28
|
-
if (isAutoReversedAccrual(accrual) || isManuallyReversedAccrual(accrual)) {
|
|
29
|
-
return getClearedMatchedAmount(accrual) ?? accrual.estimatedAmount.amount;
|
|
30
|
-
}
|
|
31
|
-
return accrual.estimatedAmount.amount;
|
|
72
|
+
export function getActualMatchingAmount(row) {
|
|
73
|
+
return row.actualAmount?.amount ?? row.estimatedAmount.amount;
|
|
32
74
|
}
|
|
33
75
|
export function getActualMatchingReversalDate(accrual) {
|
|
34
76
|
if (isPendingReviewAccrual(accrual)) {
|
|
@@ -36,83 +78,60 @@ export function getActualMatchingReversalDate(accrual) {
|
|
|
36
78
|
}
|
|
37
79
|
return accrual.cleared?.matchedTransactionDate;
|
|
38
80
|
}
|
|
39
|
-
function
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
: accrual.cleared;
|
|
43
|
-
const txnId = stamp?.matchedTransactionId;
|
|
44
|
-
if (txnId != null && txnId !== '') {
|
|
45
|
-
return `txn:${txnId}`;
|
|
46
|
-
}
|
|
47
|
-
const integrationId = stamp?.matchedTransactionIntegrationId;
|
|
48
|
-
if (integrationId != null && integrationId !== '') {
|
|
49
|
-
return `int:${integrationId}`;
|
|
81
|
+
function collectGroupedMembers(members) {
|
|
82
|
+
if (members.length <= 1) {
|
|
83
|
+
return undefined;
|
|
50
84
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
+
}));
|
|
56
92
|
}
|
|
57
|
-
function
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return stampIds;
|
|
62
|
-
}
|
|
63
|
-
const seen = new Set();
|
|
64
|
-
const grouped = [];
|
|
65
|
-
for (const id of [...(stampIds ?? []), ...memberIds]) {
|
|
66
|
-
if (!seen.has(id)) {
|
|
67
|
-
seen.add(id);
|
|
68
|
-
grouped.push(id);
|
|
69
|
-
}
|
|
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');
|
|
70
97
|
}
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
function toActualMatchingRow(accrual, kind, groupedAccrualIds) {
|
|
98
|
+
const first = toMatchModalAccrual(firstMember);
|
|
74
99
|
return {
|
|
75
|
-
...
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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,
|
|
79
114
|
};
|
|
80
115
|
}
|
|
81
|
-
function
|
|
82
|
-
const groups = new Map();
|
|
83
|
-
for (const accrual of accruals) {
|
|
84
|
-
const key = getActualMatchingBillCollapseKey(accrual);
|
|
85
|
-
const members = groups.get(key);
|
|
86
|
-
if (members != null) {
|
|
87
|
-
members.push(accrual);
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
groups.set(key, [accrual]);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
const rows = [];
|
|
94
|
-
for (const members of groups.values()) {
|
|
95
|
-
rows.push(toActualMatchingRow(members[0], kind, collectGroupedAccrualIds(members)));
|
|
96
|
-
}
|
|
97
|
-
return rows;
|
|
98
|
-
}
|
|
99
|
-
function sumCollapsedActualMatchingAmounts(rows) {
|
|
100
|
-
return rows.reduce((sum, row) => sum + getActualMatchingAmount(row), 0);
|
|
101
|
-
}
|
|
102
|
-
function matchesSearch(accrual, searchString) {
|
|
116
|
+
function matchesSearch(row, searchString) {
|
|
103
117
|
const trimmed = searchString.trim().toLowerCase();
|
|
104
118
|
if (trimmed.length === 0) {
|
|
105
119
|
return true;
|
|
106
120
|
}
|
|
107
|
-
|
|
108
|
-
|
|
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));
|
|
109
128
|
}
|
|
110
129
|
function getActualMatchingSortValue(row, sortKey) {
|
|
111
130
|
switch (sortKey) {
|
|
112
131
|
case 'vendor':
|
|
113
132
|
return row.vendorName.toLowerCase();
|
|
114
133
|
case 'date':
|
|
115
|
-
return row.reversalDate?.valueOf();
|
|
134
|
+
return row.reversalDate?.valueOf() ?? row.transactionDate;
|
|
116
135
|
case 'amount':
|
|
117
136
|
return getActualMatchingAmount(row);
|
|
118
137
|
case 'category':
|
|
@@ -120,10 +139,23 @@ function getActualMatchingSortValue(row, sortKey) {
|
|
|
120
139
|
case 'class':
|
|
121
140
|
return row.jeDebitAccountingClassName?.toLowerCase();
|
|
122
141
|
case 'type':
|
|
123
|
-
return
|
|
124
|
-
row.matchSuggestion?.matchedTransactionType)?.toLowerCase();
|
|
142
|
+
return row.transactionType?.toLowerCase();
|
|
125
143
|
case 'lifecycle':
|
|
126
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
|
+
}
|
|
127
159
|
default:
|
|
128
160
|
return undefined;
|
|
129
161
|
}
|
|
@@ -141,37 +173,50 @@ function emptyKpiSummary() {
|
|
|
141
173
|
manual: { count: 0 },
|
|
142
174
|
};
|
|
143
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(matchId, state.actualMatchingState),
|
|
183
|
+
detailFetchState: view.detailFetchStateByMatchId[matchId] ?? NOT_STARTED,
|
|
184
|
+
refreshStatus: view.detailRefreshStatusByMatchId[matchId] ?? NOT_STARTED,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
144
187
|
export function getExpenseAutomationActualMatchingView(state) {
|
|
145
188
|
const { expenseAutomationActualMatchingViewState, expenseAutomationViewState } = state;
|
|
146
|
-
const {
|
|
189
|
+
const { matchIdsByPeriod, fetchState, error, refreshStatus, uiState, matchModal, } = expenseAutomationActualMatchingViewState;
|
|
147
190
|
const currentTenant = getCurrentTenant(state);
|
|
148
191
|
const selectedPeriod = expenseAutomationViewState.selectedPeriodByTenantId[currentTenant.tenantId];
|
|
149
192
|
const monthYearPeriodId = selectedPeriod != null ? toMonthYearPeriodId(selectedPeriod) : undefined;
|
|
150
|
-
const
|
|
151
|
-
?
|
|
193
|
+
const matchIds = monthYearPeriodId != null
|
|
194
|
+
? matchIdsByPeriod[monthYearPeriodId]
|
|
152
195
|
: undefined;
|
|
153
|
-
const
|
|
154
|
-
.map((
|
|
155
|
-
.filter((
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
const
|
|
196
|
+
const rows = (matchIds ?? [])
|
|
197
|
+
.map((matchId) => getActualMatchingMatchById(matchId, state.actualMatchingState))
|
|
198
|
+
.filter((match) => match != null)
|
|
199
|
+
.filter((match) => match.members.length > 0)
|
|
200
|
+
.map(toActualMatchingRow)
|
|
201
|
+
.filter((row) => matchesSearch(row, uiState.searchString));
|
|
202
|
+
const reversed = sortActualMatchingRows(rows.filter((row) => isReversedMatchKind(row.kind)), uiState);
|
|
203
|
+
const pendingReview = sortActualMatchingRows(rows.filter((row) => row.kind === 'pending_review'), uiState);
|
|
204
|
+
const autoReversedForKpi = reversed.filter((row) => row.kind === 'auto_reversed');
|
|
205
|
+
const listedRows = [...reversed, ...pendingReview];
|
|
161
206
|
const kpiSummary = listedRows.length === 0
|
|
162
207
|
? emptyKpiSummary()
|
|
163
208
|
: {
|
|
164
209
|
autoReversed: {
|
|
165
|
-
count:
|
|
166
|
-
amount:
|
|
210
|
+
count: autoReversedForKpi.length,
|
|
211
|
+
amount: sumRowAmounts(autoReversedForKpi),
|
|
167
212
|
},
|
|
168
213
|
pendingReview: {
|
|
169
214
|
count: pendingReview.length,
|
|
170
|
-
amount:
|
|
215
|
+
amount: sumRowAmounts(pendingReview),
|
|
171
216
|
},
|
|
172
217
|
totalThisClose: {
|
|
173
218
|
count: listedRows.length,
|
|
174
|
-
amount:
|
|
219
|
+
amount: sumRowAmounts(listedRows),
|
|
175
220
|
},
|
|
176
221
|
ai: {
|
|
177
222
|
count: listedRows.filter((row) => row.origin === 'agent').length,
|
|
@@ -180,26 +225,40 @@ export function getExpenseAutomationActualMatchingView(state) {
|
|
|
180
225
|
count: listedRows.filter((row) => row.origin === 'human').length,
|
|
181
226
|
},
|
|
182
227
|
};
|
|
183
|
-
const
|
|
228
|
+
const searchedAccruals = matchModal.accrualSearch.results
|
|
184
229
|
.map((key) => getJEOneTimeAccrualByKey(key, state.jeSchedulesState))
|
|
185
230
|
.filter((accrual) => accrual != null);
|
|
231
|
+
const openedMatch = matchModal.openedFromMatchId != null
|
|
232
|
+
? getActualMatchingMatchById(matchModal.openedFromMatchId, state.actualMatchingState)
|
|
233
|
+
: undefined;
|
|
234
|
+
const pinnedAccruals = (openedMatch?.members ?? []).map(toMatchModalAccrual);
|
|
235
|
+
const firstSuggestion = openedMatch?.members[0]?.matchSuggestion;
|
|
236
|
+
const prefill = openedMatch == null
|
|
237
|
+
? undefined
|
|
238
|
+
: {
|
|
239
|
+
selectedAccrualIds: openedMatch.members.map((member) => member.accrualId),
|
|
240
|
+
actualAmount: openedMatch.actualAmount.amount,
|
|
241
|
+
matchedLineId: firstSuggestion?.matchedLineId,
|
|
242
|
+
matchedTransactionId: firstSuggestion?.matchedTransactionId,
|
|
243
|
+
};
|
|
186
244
|
return {
|
|
187
245
|
fetchState,
|
|
188
246
|
error,
|
|
189
|
-
|
|
247
|
+
reversed,
|
|
190
248
|
pendingReview,
|
|
191
|
-
manuallyReversed,
|
|
192
249
|
kpiSummary,
|
|
193
250
|
uiState,
|
|
194
251
|
refreshStatus,
|
|
195
|
-
hasLoadedForSelectedPeriod:
|
|
252
|
+
hasLoadedForSelectedPeriod: matchIds != null,
|
|
196
253
|
matchModal: {
|
|
197
254
|
isOpen: matchModal.isOpen,
|
|
255
|
+
openedFromMatchId: matchModal.openedFromMatchId,
|
|
256
|
+
prefill,
|
|
198
257
|
confirmStatus: matchModal.confirmStatus,
|
|
199
258
|
lastBatchResults: matchModal.lastBatchResults,
|
|
200
259
|
transactionSearch: {
|
|
201
260
|
query: matchModal.transactionSearch.query,
|
|
202
|
-
results: matchModal.transactionSearch.results,
|
|
261
|
+
results: uniqueTransactionSearchResults(matchModal.pinnedTransactionResults, matchModal.transactionSearch.results),
|
|
203
262
|
fetchState: {
|
|
204
263
|
fetchState: matchModal.transactionSearch.fetchState,
|
|
205
264
|
error: matchModal.transactionSearch.error,
|
|
@@ -209,7 +268,7 @@ export function getExpenseAutomationActualMatchingView(state) {
|
|
|
209
268
|
},
|
|
210
269
|
accrualSearch: {
|
|
211
270
|
query: matchModal.accrualSearch.query,
|
|
212
|
-
results:
|
|
271
|
+
results: uniqueMatchModalAccruals(pinnedAccruals, searchedAccruals),
|
|
213
272
|
fetchState: {
|
|
214
273
|
fetchState: matchModal.accrualSearch.fetchState,
|
|
215
274
|
error: matchModal.accrualSearch.error,
|
|
@@ -7,6 +7,10 @@ const ACTUAL_MATCHING_SORT_KEYS = [
|
|
|
7
7
|
'class',
|
|
8
8
|
'type',
|
|
9
9
|
'lifecycle',
|
|
10
|
+
'service',
|
|
11
|
+
'accrued',
|
|
12
|
+
'actual',
|
|
13
|
+
'variance',
|
|
10
14
|
];
|
|
11
15
|
export const toActualMatchingSortKey = (v) => stringToUnion(v, ACTUAL_MATCHING_SORT_KEYS);
|
|
12
16
|
export const MATCH_TRANSACTION_SEARCH_PAGE_SIZE = 50;
|
package/lib/index.d.ts
CHANGED
|
@@ -302,7 +302,7 @@ import { computeNewScheduleJeDetails } from './view/expenseAutomationView/helper
|
|
|
302
302
|
import { isReviewTransactionBankTransferType, isReviewTransactionBillPaymentType, isReviewTransactionCreditCardCreditType, isReviewTransactionCreditCardPaymentType, isReviewTransactionDepositType, isReviewTransactionExpenseType } from './view/expenseAutomationView/helpers/reconciliationHelpers';
|
|
303
303
|
import { MAX_SELECTION_LIMIT, checkIfAllLineItemsAreCategoryClassFilled, getLineItemsByTransactionIdsFromLocalData, isAnyItemWithUncategorizedExpenseAccount } from './view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper';
|
|
304
304
|
import { UploadStatementDocumentAIPayload, UploadStatementDocumentAIResponse } from './view/expenseAutomationView/payload/reconciliationPayload';
|
|
305
|
-
import { clearActualMatchingView as clearExpenseAutomationActualMatchingView, clearMatchNewModal, closeMatchNewModal, fetchActualMatching as fetchExpenseAutomationActualMatching, fetchActualMatchingPage as fetchExpenseAutomationActualMatchingPage, matchAndReverseAccruals, openMatchNewModal, searchMatchAccruals, searchMatchTransactions, updateActualMatchingUIState as updateExpenseAutomationActualMatchingUIState } from './view/expenseAutomationView/reducers/actualMatchingViewReducer';
|
|
305
|
+
import { clearActualMatchingView as clearExpenseAutomationActualMatchingView, clearMatchNewModal, closeMatchNewModal, fetchActualMatching as fetchExpenseAutomationActualMatching, fetchActualMatchingMatch, fetchActualMatchingPage as fetchExpenseAutomationActualMatchingPage, matchAndReverseAccruals, openMatchNewModal, searchMatchAccruals, searchMatchTransactions, updateActualMatchingUIState as updateExpenseAutomationActualMatchingUIState } from './view/expenseAutomationView/reducers/actualMatchingViewReducer';
|
|
306
306
|
import { clearExpenseAutomationFluxAnalysisView, fetchFluxAnalysisView, reviewFluxAnalysisView, updateFluxAnalysisViewPageMetaData, updateFluxAnalysisViewUIState, updateOperatingExpensesIdsForReview, updateSelectedSectionIdsForReview } from './view/expenseAutomationView/reducers/fluxAnalysisViewReducer';
|
|
307
307
|
import { clearJeScheduleLocalData as clearExpenseAutomationJEScheduleLocalData, clearExpenseAutomationJESchedulesView, clearSearchTransactionsForNewSchedule as clearJeSearchTransactionsForNewSchedule, createOneTimeAccrual, createOneTimeAccrualFailure, createOneTimeAccrualSuccess, fetchJeSchedulesPage as fetchExpenseAutomationJESchedulesPage, fetchOneTimeAccrual, ignoreRecommendedJeSchedule as ignoreExpenseAutomationJESchedule, ignoreOneTimeAccrual, initializeAccountSettingsView as initializeJeAccountSettingsView, initializeNewSchedule as initializeJeNewSchedule, initializeJeScheduleLocalData, promoteOneTimeAccrual, removeJeScheduleTransactionKey, resetNewScheduleLocalData as resetJeNewScheduleLocalData, retryJeSchedule as retryExpenseAutomationJESchedule, revertJeSchedule, revertOneTimeAccrual, saveAccountSettings as saveJeAccountSettings, saveAccountSettingsLocalData as saveJeAccountSettingsLocalData, saveNewSchedule as saveJeNewSchedule, saveNewScheduleFailure as saveJeNewScheduleFailure, saveNewScheduleSuccess as saveJeNewScheduleSuccess, searchTransactionsForNewSchedule as searchJeTransactionsForNewSchedule, searchTransactionsForNewScheduleFailure as searchJeTransactionsForNewScheduleFailure, searchTransactionsForNewScheduleSuccess as searchJeTransactionsForNewScheduleSuccess, toggleJECreatedGroupCollapsed, toggleJEPendingGroupCollapsed, updateJESchedulesUIState as updateExpenseAutomationJESchedulesUIState, updateJEScheduleTypeFilter, updateNewScheduleLocalData as updateJeNewScheduleLocalData, updateJeScheduleLocalDataById, updateJeScheduleTransactionKeys } from './view/expenseAutomationView/reducers/jeSchedulesViewReducer';
|
|
308
308
|
import { acknowledgeBulkUploadConfirmMatchComplete, bulkUploadAutomatchingTimedOut, bulkUploadReceipts, bulkUploadReceiptsFailure, bulkUploadReceiptsSuccess, clearBulkUpload, clearBulkUploadBatchDetailsForScopeChange, clearManualSearchResults, clearMissingReceiptsTabNavigation, confirmBulkUploadMatch, confirmBulkUploadMatchFailure, confirmBulkUploadMatchSuccess, fetchBulkUploadBatchDetails, fetchBulkUploadBatchDetailsFailure, fetchBulkUploadBatchDetailsSuccess, fetchBulkUploadBatches, fetchBulkUploadBatchesFailure, fetchBulkUploadBatchesSuccess, fetchCompletedTransactions, fetchCompletedTransactionsFailure, fetchCompletedTransactionsSuccess, fetchMissingReceipts as fetchExpenseAutomationMissingReceipts, fetchMoreBatchDetails, fetchMoreBatchDetailsComplete, fetchMoreBatchDetailsFailure, markBatchDetailRefreshAttempted, markMissingReceiptAsDone as markExpenseAutomationMissingReceiptAsDone, pusherBatchStatusUpdate, refreshBatchDetailsForBatchId, requestMissingReceiptsTabNavigation, restoreBulkUploadAutomatchingOnMount, restoreBulkUploadMatchingState, searchTransactionsForManualMatch, searchTransactionsForManualMatchFailure, searchTransactionsForManualMatchSuccess, setBulkUploadCompletedSubTab, setBulkUploadResultsTab, setBulkUploadSortConfig, setBulkUploadUploadedFileCount, storeBatchDetails, updateBulkUploadProgress, updateMissingReceiptUploadState as updateExpenseAutomationMissingReceiptUploadState, updateMissingReceiptsUIState as updateExpenseAutomationMissingReceiptsUIState, uploadMissingReceiptSuccess as uploadExpenseAutomationMissingReceiptSuccess } from './view/expenseAutomationView/reducers/missingReceiptsViewReducer';
|
|
@@ -943,7 +943,7 @@ export { fetchApAging, getApAgingReport, updateApAgingUIState, AgingReportId, Ap
|
|
|
943
943
|
export { fetchApAgingDetail, AgingReportInvoice, ApAgingDetail, getApAgingDetailForVendor, updateApAgingDetailUIState, };
|
|
944
944
|
export { LinkBillExpenseKey };
|
|
945
945
|
export { clearExpenseAutomationJEScheduleLocalData, clearExpenseAutomationJESchedulesView, clearJeSearchTransactionsForNewSchedule, fetchExpenseAutomationJESchedulesPage, ignoreExpenseAutomationJESchedule, initializeJeAccountSettingsView, initializeJeNewSchedule, initializeJeScheduleLocalData, removeJeScheduleTransactionKey, resetJeNewScheduleLocalData, retryExpenseAutomationJESchedule, saveJeNewSchedule, saveJeNewScheduleFailure, saveJeNewScheduleSuccess, searchJeTransactionsForNewSchedule, searchJeTransactionsForNewScheduleFailure, searchJeTransactionsForNewScheduleSuccess, updateExpenseAutomationJESchedulesUIState, updateJeNewScheduleLocalData, updateJeScheduleLocalDataById, updateJeScheduleTransactionKeys, };
|
|
946
|
-
export { clearExpenseAutomationActualMatchingView, closeMatchNewModal, clearMatchNewModal, fetchExpenseAutomationActualMatching, fetchExpenseAutomationActualMatchingPage, matchAndReverseAccruals, openMatchNewModal, searchMatchAccruals, searchMatchTransactions, updateExpenseAutomationActualMatchingUIState, };
|
|
946
|
+
export { clearExpenseAutomationActualMatchingView, closeMatchNewModal, clearMatchNewModal, fetchExpenseAutomationActualMatching, fetchActualMatchingMatch, fetchExpenseAutomationActualMatchingPage, matchAndReverseAccruals, openMatchNewModal, searchMatchAccruals, searchMatchTransactions, updateExpenseAutomationActualMatchingUIState, };
|
|
947
947
|
export { createNewSchedulesAccrued, deleteScheduleAccruedDetail, cancelScheduleAccruedJournalEntry, fetchScheduleAccruedDetails, fetchScheduleAccruedDetailsPage, resetJEAccruedLinkInLocalData, saveScheduleAccruedDetails, updateAmountsInScheduleAccruedDetail, updatedJEAccruedLinkWithRecommendedLocalData, updatedJELinkInLocalDataAccruedExpenses, fetchRecommendedTransactionRowIndex, clearSelectedJELinkRowIndex, updateLinkBillExpenseLocalData, updateScheduleAccruedDetailsLocalData, updateSelectedJEAccruedScheduleKey, resetSelectedJEAccruedScheduleKey, resetAccruedDetailNewScheduleState, };
|
|
948
948
|
export { JEScheduleKey, JEScheduleTransactionKey, ScheduleTransactionID, getJEScheduleTransactionKey, ALL_JE_SCHEDULE_IGNORE_REASONS, ALL_JE_SCHEDULE_REVERT_REASONS, ALL_ONE_TIME_ACCRUAL_IGNORE_REASONS, ALL_ONE_TIME_ACCRUAL_REVERT_REASONS, ALL_SCHEDULES_TYPES, JEScheduledTransaction, JEAccruedSchedule, JEOneTimeAccrual, OneTimeAccrualEdits, JEOneTimeAccrualAiSuggestion, JEOneTimeAccrualBillingPattern, JEOneTimeAccrualCleared, JEOneTimeAccrualMatchSuggestion, JEOneTimeAccrualMonthlyTotal, JEOneTimeAccrualProfileSnapshot, JEOneTimeAccrualKey, JEScheduleAISummaries, JEScheduleFieldRecommendation, JEScheduleSummary, ScheduleTypes, ScheduleJournalEntryStatusCodeType, ScheduleListReport, ScheduleAccruedListReport, getScheduleListReport, getAccruedScheduleListReport, ScheduleSubTabType, ScheduleListSortKey, fetchScheduleList, fetchAccruedScheduleList, fetchDownloadSchedules, fetchSchedulesAccount, updateScheduleListLocalData, getFetchStateForScheduleAccountList, toScheduleTypesType, toScheduleTypesTypeStrict, toScheduleListTabsFileTypeStrict, ScheduleListLocalData, ScheduleDetailsLocalDataFixedAssets, ScheduleDetailsLocalDataAccruedExpenses, ScheduleDetailsView, ScheduleAccruedDetailsView, LinkBillExpenseView, LinkBillExpenseLocalData, JEScheduleDetailsLocalData, ScheduleDetailsLocalData, toScheduleSubTabType, ScheduleStatusCodeType, JEScheduleIgnoreReasonType, JEScheduleRevertReasonType, JournalEntryErrorCodeType, OneTimeAccrualIgnoreReasonType, OneTimeAccrualRevertReasonType, ScheduleTransaction, updateScheduleListSubTab, updateScheduleListSearchText, updateScheduleListScrollState, updateScheduleListSortState, ScheduleDetailSortKey, ScheduledJournalEntry, updateSelectedJEScheduleKey, fetchScheduleDetails, getScheduleDetailsView, getAccruedScheduleDetailsView, fetchScheduleDetailsPage, saveScheduleDetails, deleteScheduleDetail, createNewSchedules, updateScheduleDetailsLocalData, JETransactionLink, updateScheduleListDownloadState, updateAccruedJEScheduleAccruedByListKey, DownloadJEScheduleTabOptions, updatedSelectedJELinkRowIndex, ScheduleListTabsFileType, getQBOUrlForLink, getThirdPartyIDFromQBOURL, updatedJELinkInLocalData, updateAmountsInScheduleDetail, ScheduleDetailUIState, resetJELinkInLocalData, JEScheduledTransactionWithBalance, JEScheduleWithBalance, JELinkType, updatedJELinkWithRecommendedLocalData, getFetchStateForScheduleListByType, getDefaultSelectedTimeframeForScheduleType, markAsCompleteScheduleDetail, resetMarkAsCompleteStatus, fetchVendorTabView, updateVendorTabViewTab, VendorTabViewTabType, getVendorTabView, VendorTabViewSelectorView, };
|
|
949
949
|
export { GlobalMerchant, GlobalMerchantBase, TenantMerchant };
|
|
@@ -1269,4 +1269,7 @@ export type { InvoicingOnboardingGateView } from './view/invoicing/invoicingConf
|
|
|
1269
1269
|
export type { InvoicingConfigView } from './view/invoicing/invoicingConfigView/invoicingConfigViewSelector';
|
|
1270
1270
|
export type { InvoicingOnboardingStatus } from './view/invoicing/invoicingConfigView/invoicingConfigViewPayload';
|
|
1271
1271
|
export { getExpenseAutomationJEOneTimeAccrualDetailView, getJEScheduleRowType, isAccruedScheduleRow, isAiAccountantSchedule, isExistingTabRow, isOneTimeAccrualRow, isRenderableOneTimeAccrual, isPendingReviewRow, } from './view/expenseAutomationView/selectors/jeSchedulesViewSelector';
|
|
1272
|
-
export { getActualMatchingAmount, getActualMatchingReversalDate, getExpenseAutomationActualMatchingView, isAutoReversedAccrual, isManuallyReversedAccrual, isPendingReviewAccrual, } from './view/expenseAutomationView/selectors/actualMatchingViewSelector';
|
|
1272
|
+
export { getActualMatchingAmount, getActualMatchingReversalDate, parseActualMatchingMatchId, getExpenseAutomationActualMatchingMatchView, getExpenseAutomationActualMatchingView, isAutoReversedAccrual, isManuallyReversedAccrual, isPendingReviewAccrual, } from './view/expenseAutomationView/selectors/actualMatchingViewSelector';
|
|
1273
|
+
export type { ExpenseAutomationActualMatchingMatchView } from './view/expenseAutomationView/selectors/actualMatchingViewSelector';
|
|
1274
|
+
export type { ActualMatchingMatch, ActualMatchingMatchKind, } from './entity/actualMatching/actualMatchingState';
|
|
1275
|
+
export type { ActualMatchingMatchId } from './entity/actualMatching/actualMatchingTypes';
|