@zeniai/client-epic-state 5.2.22-betaAM4 → 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.
Files changed (48) hide show
  1. package/lib/entity/actualMatching/actualMatchingPayload.d.ts +25 -0
  2. package/lib/entity/actualMatching/actualMatchingPayload.js +42 -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/epic.d.ts +2 -1
  12. package/lib/epic.js +2 -0
  13. package/lib/esm/entity/actualMatching/actualMatchingPayload.js +39 -0
  14. package/lib/esm/entity/actualMatching/actualMatchingReducer.js +30 -0
  15. package/lib/esm/entity/actualMatching/actualMatchingSelector.js +3 -0
  16. package/lib/esm/entity/actualMatching/actualMatchingState.js +1 -0
  17. package/lib/esm/entity/actualMatching/actualMatchingTypes.js +1 -0
  18. package/lib/esm/epic.js +2 -0
  19. package/lib/esm/index.js +3 -3
  20. package/lib/esm/reducer.js +3 -0
  21. package/lib/esm/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingEpic.js +6 -6
  22. package/lib/esm/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingMatchEpic.js +35 -0
  23. package/lib/esm/view/expenseAutomationView/epics/actualMatching/searchMatchTransactionsEpic.js +35 -6
  24. package/lib/esm/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +1 -1
  25. package/lib/esm/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +87 -8
  26. package/lib/esm/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +143 -98
  27. package/lib/esm/view/expenseAutomationView/types/actualMatchingViewState.js +4 -0
  28. package/lib/index.d.ts +6 -3
  29. package/lib/index.js +21 -18
  30. package/lib/reducer.d.ts +1 -0
  31. package/lib/reducer.js +3 -0
  32. package/lib/rootStateTypes.d.ts +2 -0
  33. package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingEpic.d.ts +2 -2
  34. package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingEpic.js +6 -6
  35. package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingMatchEpic.d.ts +12 -0
  36. package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingMatchEpic.js +39 -0
  37. package/lib/view/expenseAutomationView/epics/actualMatching/searchMatchTransactionsEpic.d.ts +1 -1
  38. package/lib/view/expenseAutomationView/epics/actualMatching/searchMatchTransactionsEpic.js +35 -6
  39. package/lib/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +1 -1
  40. package/lib/view/expenseAutomationView/payload/actualMatchingPayload.d.ts +3 -6
  41. package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.d.ts +20 -3
  42. package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +88 -9
  43. package/lib/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.d.ts +15 -2
  44. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.d.ts +16 -2
  45. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +146 -98
  46. package/lib/view/expenseAutomationView/types/actualMatchingViewState.d.ts +7 -2
  47. package/lib/view/expenseAutomationView/types/actualMatchingViewState.js +4 -0
  48. 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
- accrualKeysByPeriod: {},
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 { accruals, selectedPeriod, refreshViewInBackground } = action.payload;
99
+ const { matchIds, selectedPeriod, refreshViewInBackground } = action.payload;
94
100
  const periodId = toMonthYearPeriodId(selectedPeriod);
95
- draft.accrualKeysByPeriod[periodId] = accruals.map((accrual) => generateJEOneTimeAccrualKey(accrual._id));
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(draft) {
129
- draft.matchModal.isOpen = true;
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 { payload: { query, pageToken: pageToken ?? undefined } };
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(accrual) {
24
- if (isPendingReviewAccrual(accrual)) {
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,40 +78,6 @@ export function getActualMatchingReversalDate(accrual) {
36
78
  }
37
79
  return accrual.cleared?.matchedTransactionDate;
38
80
  }
39
- function getActualMatchingBillCollapseKey(accrual) {
40
- const stamp = isPendingReviewAccrual(accrual)
41
- ? accrual.matchSuggestion
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}`;
50
- }
51
- const ids = accrual.cleared?.accrualIds;
52
- if (ids != null && ids.length > 1) {
53
- return `cleared:${[...ids].sort().join(',')}`;
54
- }
55
- return `accrual:${accrual.accrualId}`;
56
- }
57
- function collectGroupedAccrualIds(members) {
58
- const stampIds = members[0]?.cleared?.accrualIds;
59
- const memberIds = members.map((member) => member.accrualId);
60
- if (stampIds != null && stampIds.length >= memberIds.length) {
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
- }
70
- }
71
- return grouped;
72
- }
73
81
  function collectGroupedMembers(members) {
74
82
  if (members.length <= 1) {
75
83
  return undefined;
@@ -82,51 +90,48 @@ function collectGroupedMembers(members) {
82
90
  serviceMonth: member.serviceMonth,
83
91
  }));
84
92
  }
85
- function toActualMatchingRow(members, kind) {
86
- const accrual = members[0];
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);
87
99
  return {
88
- ...accrual,
89
- kind,
90
- groupedAccrualIds: collectGroupedAccrualIds(members),
91
- groupedMembers: collectGroupedMembers(members),
92
- reversalDate: getActualMatchingReversalDate(accrual),
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,
93
114
  };
94
115
  }
95
- function collapseActualMatchingRows(accruals, kind) {
96
- const groups = new Map();
97
- for (const accrual of accruals) {
98
- const key = getActualMatchingBillCollapseKey(accrual);
99
- const members = groups.get(key);
100
- if (members != null) {
101
- members.push(accrual);
102
- }
103
- else {
104
- groups.set(key, [accrual]);
105
- }
106
- }
107
- const rows = [];
108
- for (const members of groups.values()) {
109
- rows.push(toActualMatchingRow(members, kind));
110
- }
111
- return rows;
112
- }
113
- function sumCollapsedActualMatchingAmounts(rows) {
114
- return rows.reduce((sum, row) => sum + getActualMatchingAmount(row), 0);
115
- }
116
- function matchesSearch(accrual, searchString) {
116
+ function matchesSearch(row, searchString) {
117
117
  const trimmed = searchString.trim().toLowerCase();
118
118
  if (trimmed.length === 0) {
119
119
  return true;
120
120
  }
121
- return (accrual.vendorName.toLowerCase().includes(trimmed) ||
122
- accrual.memo.toLowerCase().includes(trimmed));
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));
123
128
  }
124
129
  function getActualMatchingSortValue(row, sortKey) {
125
130
  switch (sortKey) {
126
131
  case 'vendor':
127
132
  return row.vendorName.toLowerCase();
128
133
  case 'date':
129
- return row.reversalDate?.valueOf();
134
+ return row.reversalDate?.valueOf() ?? row.transactionDate;
130
135
  case 'amount':
131
136
  return getActualMatchingAmount(row);
132
137
  case 'category':
@@ -134,10 +139,23 @@ function getActualMatchingSortValue(row, sortKey) {
134
139
  case 'class':
135
140
  return row.jeDebitAccountingClassName?.toLowerCase();
136
141
  case 'type':
137
- return (row.cleared?.matchedTransactionType ??
138
- row.matchSuggestion?.matchedTransactionType)?.toLowerCase();
142
+ return row.transactionType?.toLowerCase();
139
143
  case 'lifecycle':
140
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
+ }
141
159
  default:
142
160
  return undefined;
143
161
  }
@@ -155,37 +173,50 @@ function emptyKpiSummary() {
155
173
  manual: { count: 0 },
156
174
  };
157
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
+ }
158
187
  export function getExpenseAutomationActualMatchingView(state) {
159
188
  const { expenseAutomationActualMatchingViewState, expenseAutomationViewState } = state;
160
- const { accrualKeysByPeriod, fetchState, error, refreshStatus, uiState, matchModal, } = expenseAutomationActualMatchingViewState;
189
+ const { matchIdsByPeriod, fetchState, error, refreshStatus, uiState, matchModal, } = expenseAutomationActualMatchingViewState;
161
190
  const currentTenant = getCurrentTenant(state);
162
191
  const selectedPeriod = expenseAutomationViewState.selectedPeriodByTenantId[currentTenant.tenantId];
163
192
  const monthYearPeriodId = selectedPeriod != null ? toMonthYearPeriodId(selectedPeriod) : undefined;
164
- const accrualKeys = monthYearPeriodId != null
165
- ? accrualKeysByPeriod[monthYearPeriodId]
193
+ const matchIds = monthYearPeriodId != null
194
+ ? matchIdsByPeriod[monthYearPeriodId]
166
195
  : undefined;
167
- const accruals = (accrualKeys ?? [])
168
- .map((key) => getJEOneTimeAccrualByKey(key, state.jeSchedulesState))
169
- .filter((accrual) => accrual != null);
170
- const filtered = accruals.filter((accrual) => matchesSearch(accrual, uiState.searchString));
171
- const autoReversed = sortActualMatchingRows(collapseActualMatchingRows(filtered.filter(isAutoReversedAccrual), 'auto_reversed'), uiState);
172
- const pendingReview = sortActualMatchingRows(collapseActualMatchingRows(filtered.filter(isPendingReviewAccrual), 'pending_review'), uiState);
173
- const manuallyReversed = sortActualMatchingRows(collapseActualMatchingRows(filtered.filter(isManuallyReversedAccrual), 'manually_reversed'), uiState);
174
- const listedRows = [...autoReversed, ...pendingReview, ...manuallyReversed];
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];
175
206
  const kpiSummary = listedRows.length === 0
176
207
  ? emptyKpiSummary()
177
208
  : {
178
209
  autoReversed: {
179
- count: autoReversed.length,
180
- amount: sumCollapsedActualMatchingAmounts(autoReversed),
210
+ count: autoReversedForKpi.length,
211
+ amount: sumRowAmounts(autoReversedForKpi),
181
212
  },
182
213
  pendingReview: {
183
214
  count: pendingReview.length,
184
- amount: sumCollapsedActualMatchingAmounts(pendingReview),
215
+ amount: sumRowAmounts(pendingReview),
185
216
  },
186
217
  totalThisClose: {
187
218
  count: listedRows.length,
188
- amount: sumCollapsedActualMatchingAmounts(listedRows),
219
+ amount: sumRowAmounts(listedRows),
189
220
  },
190
221
  ai: {
191
222
  count: listedRows.filter((row) => row.origin === 'agent').length,
@@ -194,26 +225,40 @@ export function getExpenseAutomationActualMatchingView(state) {
194
225
  count: listedRows.filter((row) => row.origin === 'human').length,
195
226
  },
196
227
  };
197
- const accrualSearchResults = matchModal.accrualSearch.results
228
+ const searchedAccruals = matchModal.accrualSearch.results
198
229
  .map((key) => getJEOneTimeAccrualByKey(key, state.jeSchedulesState))
199
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
+ };
200
244
  return {
201
245
  fetchState,
202
246
  error,
203
- autoReversed,
247
+ reversed,
204
248
  pendingReview,
205
- manuallyReversed,
206
249
  kpiSummary,
207
250
  uiState,
208
251
  refreshStatus,
209
- hasLoadedForSelectedPeriod: accrualKeys != null,
252
+ hasLoadedForSelectedPeriod: matchIds != null,
210
253
  matchModal: {
211
254
  isOpen: matchModal.isOpen,
255
+ openedFromMatchId: matchModal.openedFromMatchId,
256
+ prefill,
212
257
  confirmStatus: matchModal.confirmStatus,
213
258
  lastBatchResults: matchModal.lastBatchResults,
214
259
  transactionSearch: {
215
260
  query: matchModal.transactionSearch.query,
216
- results: matchModal.transactionSearch.results,
261
+ results: uniqueTransactionSearchResults(matchModal.pinnedTransactionResults, matchModal.transactionSearch.results),
217
262
  fetchState: {
218
263
  fetchState: matchModal.transactionSearch.fetchState,
219
264
  error: matchModal.transactionSearch.error,
@@ -223,7 +268,7 @@ export function getExpenseAutomationActualMatchingView(state) {
223
268
  },
224
269
  accrualSearch: {
225
270
  query: matchModal.accrualSearch.query,
226
- results: accrualSearchResults,
271
+ results: uniqueMatchModalAccruals(pinnedAccruals, searchedAccruals),
227
272
  fetchState: {
228
273
  fetchState: matchModal.accrualSearch.fetchState,
229
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';