@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.
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 +152 -93
  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 +24 -2
  44. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.d.ts +16 -2
  45. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +155 -93
  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,83 +78,60 @@ 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}`;
81
+ function collectGroupedMembers(members) {
82
+ if (members.length <= 1) {
83
+ return undefined;
50
84
  }
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}`;
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 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
- }
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
- return grouped;
72
- }
73
- function toActualMatchingRow(accrual, kind, groupedAccrualIds) {
98
+ const first = toMatchModalAccrual(firstMember);
74
99
  return {
75
- ...accrual,
76
- kind,
77
- groupedAccrualIds,
78
- 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,
79
114
  };
80
115
  }
81
- function collapseActualMatchingRows(accruals, kind) {
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
- return (accrual.vendorName.toLowerCase().includes(trimmed) ||
108
- 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));
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 (row.cleared?.matchedTransactionType ??
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 { accrualKeysByPeriod, fetchState, error, refreshStatus, uiState, matchModal, } = expenseAutomationActualMatchingViewState;
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 accrualKeys = monthYearPeriodId != null
151
- ? accrualKeysByPeriod[monthYearPeriodId]
193
+ const matchIds = monthYearPeriodId != null
194
+ ? matchIdsByPeriod[monthYearPeriodId]
152
195
  : undefined;
153
- const accruals = (accrualKeys ?? [])
154
- .map((key) => getJEOneTimeAccrualByKey(key, state.jeSchedulesState))
155
- .filter((accrual) => accrual != null);
156
- const filtered = accruals.filter((accrual) => matchesSearch(accrual, uiState.searchString));
157
- const autoReversed = sortActualMatchingRows(collapseActualMatchingRows(filtered.filter(isAutoReversedAccrual), 'auto_reversed'), uiState);
158
- const pendingReview = sortActualMatchingRows(collapseActualMatchingRows(filtered.filter(isPendingReviewAccrual), 'pending_review'), uiState);
159
- const manuallyReversed = sortActualMatchingRows(collapseActualMatchingRows(filtered.filter(isManuallyReversedAccrual), 'manually_reversed'), uiState);
160
- 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];
161
206
  const kpiSummary = listedRows.length === 0
162
207
  ? emptyKpiSummary()
163
208
  : {
164
209
  autoReversed: {
165
- count: autoReversed.length,
166
- amount: sumCollapsedActualMatchingAmounts(autoReversed),
210
+ count: autoReversedForKpi.length,
211
+ amount: sumRowAmounts(autoReversedForKpi),
167
212
  },
168
213
  pendingReview: {
169
214
  count: pendingReview.length,
170
- amount: sumCollapsedActualMatchingAmounts(pendingReview),
215
+ amount: sumRowAmounts(pendingReview),
171
216
  },
172
217
  totalThisClose: {
173
218
  count: listedRows.length,
174
- amount: sumCollapsedActualMatchingAmounts(listedRows),
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 accrualSearchResults = matchModal.accrualSearch.results
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
- autoReversed,
247
+ reversed,
190
248
  pendingReview,
191
- manuallyReversed,
192
249
  kpiSummary,
193
250
  uiState,
194
251
  refreshStatus,
195
- hasLoadedForSelectedPeriod: accrualKeys != null,
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: accrualSearchResults,
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';