@zeniai/client-epic-state 5.2.35-beta0MM → 5.2.35-beta11MM

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 (89) hide show
  1. package/lib/entity/actualMatching/actualMatchingPayload.d.ts +7 -1
  2. package/lib/entity/actualMatching/actualMatchingPayload.js +19 -12
  3. package/lib/entity/actualMatching/actualMatchingReducer.d.ts +3 -2
  4. package/lib/entity/actualMatching/actualMatchingReducer.js +3 -3
  5. package/lib/entity/actualMatching/actualMatchingState.d.ts +3 -2
  6. package/lib/entity/aiCfo/aiCfoReducer.js +78 -4
  7. package/lib/entity/aiCfo/aiCfoState.d.ts +16 -0
  8. package/lib/entity/jeSchedules/jeSchedulesPayload.d.ts +2 -0
  9. package/lib/entity/jeSchedules/jeSchedulesPayload.js +4 -0
  10. package/lib/entity/jeSchedules/jeSchedulesState.d.ts +2 -0
  11. package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
  12. package/lib/entity/snackbar/snackbarTypes.js +2 -0
  13. package/lib/esm/entity/actualMatching/actualMatchingPayload.js +18 -12
  14. package/lib/esm/entity/actualMatching/actualMatchingReducer.js +3 -3
  15. package/lib/esm/entity/aiCfo/aiCfoReducer.js +79 -5
  16. package/lib/esm/entity/jeSchedules/jeSchedulesPayload.js +4 -0
  17. package/lib/esm/entity/snackbar/snackbarTypes.js +2 -0
  18. package/lib/esm/index.js +6 -6
  19. package/lib/esm/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingEpic.js +3 -5
  20. package/lib/esm/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingMatchEpic.js +3 -2
  21. package/lib/esm/view/expenseAutomationView/epics/actualMatching/matchAndReverseAccrualsEpic.js +80 -25
  22. package/lib/esm/view/expenseAutomationView/epics/actualMatching/refreshActualMatchingList.js +16 -0
  23. package/lib/esm/view/expenseAutomationView/epics/actualMatching/searchMatchAccrualsEpic.js +16 -6
  24. package/lib/esm/view/expenseAutomationView/epics/actualMatching/searchMatchTransactionsEpic.js +12 -11
  25. package/lib/esm/view/expenseAutomationView/epics/actualMatching/undoReversalAccrualsEpic.js +50 -26
  26. package/lib/esm/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +5 -9
  27. package/lib/esm/view/expenseAutomationView/epics/common/refreshExpenseAutomationCurrentTabEpic.js +1 -9
  28. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.js +13 -3
  29. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/prefetchOtherJeSchedulesTabEpic.js +34 -16
  30. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +8 -8
  31. package/lib/esm/view/expenseAutomationView/expenseAutomationViewReducer.js +2 -1
  32. package/lib/esm/view/expenseAutomationView/expenseAutomationViewSelector.js +5 -2
  33. package/lib/esm/view/expenseAutomationView/expenseAutomationViewState.js +4 -2
  34. package/lib/esm/view/expenseAutomationView/payload/actualMatchingPayload.js +10 -10
  35. package/lib/esm/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +107 -20
  36. package/lib/esm/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +2 -1
  37. package/lib/esm/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +206 -61
  38. package/lib/esm/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +1 -0
  39. package/lib/esm/view/expenseAutomationView/types/actualMatchingViewState.js +3 -0
  40. package/lib/esm/view/expenseAutomationView/types/jeSchedulesViewState.js +3 -0
  41. package/lib/esm/view/financeStatement/financeStatementReducer.js +17 -27
  42. package/lib/esm/view/spendManagement/billPay/billList/billListSelector.js +3 -3
  43. package/lib/index.d.ts +7 -6
  44. package/lib/index.js +52 -48
  45. package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingEpic.js +3 -5
  46. package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingMatchEpic.d.ts +1 -1
  47. package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingMatchEpic.js +3 -2
  48. package/lib/view/expenseAutomationView/epics/actualMatching/matchAndReverseAccrualsEpic.d.ts +2 -1
  49. package/lib/view/expenseAutomationView/epics/actualMatching/matchAndReverseAccrualsEpic.js +77 -22
  50. package/lib/view/expenseAutomationView/epics/actualMatching/refreshActualMatchingList.d.ts +3 -0
  51. package/lib/view/expenseAutomationView/epics/actualMatching/refreshActualMatchingList.js +19 -0
  52. package/lib/view/expenseAutomationView/epics/actualMatching/searchMatchAccrualsEpic.d.ts +3 -2
  53. package/lib/view/expenseAutomationView/epics/actualMatching/searchMatchAccrualsEpic.js +15 -5
  54. package/lib/view/expenseAutomationView/epics/actualMatching/searchMatchTransactionsEpic.js +10 -9
  55. package/lib/view/expenseAutomationView/epics/actualMatching/undoReversalAccrualsEpic.d.ts +2 -1
  56. package/lib/view/expenseAutomationView/epics/actualMatching/undoReversalAccrualsEpic.js +48 -24
  57. package/lib/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.d.ts +1 -2
  58. package/lib/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +5 -9
  59. package/lib/view/expenseAutomationView/epics/common/refreshExpenseAutomationCurrentTabEpic.d.ts +1 -2
  60. package/lib/view/expenseAutomationView/epics/common/refreshExpenseAutomationCurrentTabEpic.js +1 -9
  61. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.d.ts +2 -1
  62. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.js +13 -3
  63. package/lib/view/expenseAutomationView/epics/jeSchedule/prefetchOtherJeSchedulesTabEpic.d.ts +5 -1
  64. package/lib/view/expenseAutomationView/epics/jeSchedule/prefetchOtherJeSchedulesTabEpic.js +33 -15
  65. package/lib/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.d.ts +1 -2
  66. package/lib/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +8 -8
  67. package/lib/view/expenseAutomationView/expenseAutomationViewReducer.d.ts +2 -1
  68. package/lib/view/expenseAutomationView/expenseAutomationViewReducer.js +2 -1
  69. package/lib/view/expenseAutomationView/expenseAutomationViewSelector.js +5 -2
  70. package/lib/view/expenseAutomationView/expenseAutomationViewState.d.ts +2 -1
  71. package/lib/view/expenseAutomationView/expenseAutomationViewState.js +6 -3
  72. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
  73. package/lib/view/expenseAutomationView/payload/actualMatchingPayload.d.ts +3 -3
  74. package/lib/view/expenseAutomationView/payload/actualMatchingPayload.js +10 -10
  75. package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.d.ts +21 -16
  76. package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +107 -20
  77. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.d.ts +2 -1
  78. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +2 -1
  79. package/lib/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.d.ts +13 -9
  80. package/lib/view/expenseAutomationView/selectorTypes/expenseAutomationViewSelectorTypes.d.ts +9 -0
  81. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.d.ts +11 -2
  82. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +209 -60
  83. package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +1 -0
  84. package/lib/view/expenseAutomationView/types/actualMatchingViewState.d.ts +8 -2
  85. package/lib/view/expenseAutomationView/types/actualMatchingViewState.js +4 -0
  86. package/lib/view/expenseAutomationView/types/jeSchedulesViewState.js +3 -0
  87. package/lib/view/financeStatement/financeStatementReducer.js +17 -27
  88. package/lib/view/spendManagement/billPay/billList/billListSelector.js +2 -2
  89. package/package.json +1 -1
@@ -3,6 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getSelectedMonthYearForCurrentTenant = getSelectedMonthYearForCurrentTenant;
7
+ exports.getTenantDisplayCurrency = getTenantDisplayCurrency;
6
8
  exports.parseActualMatchingMatchId = parseActualMatchingMatchId;
7
9
  exports.isAutoReversedAccrual = isAutoReversedAccrual;
8
10
  exports.isManuallyReversedAccrual = isManuallyReversedAccrual;
@@ -11,7 +13,9 @@ exports.isReversedMatchKind = isReversedMatchKind;
11
13
  exports.getClearedMatchedAmount = getClearedMatchedAmount;
12
14
  exports.getActualMatchingAmount = getActualMatchingAmount;
13
15
  exports.getActualMatchingReversalDate = getActualMatchingReversalDate;
16
+ exports.buildActualMatchingKpiSummary = buildActualMatchingKpiSummary;
14
17
  exports.getExpenseAutomationActualMatchingMatchView = getExpenseAutomationActualMatchingMatchView;
18
+ exports.getCloseMatchAfterUndo = getCloseMatchAfterUndo;
15
19
  exports.getExpenseAutomationActualMatchingView = getExpenseAutomationActualMatchingView;
16
20
  const orderBy_1 = __importDefault(require("lodash/orderBy"));
17
21
  const timePeriod_1 = require("../../../commonStateTypes/timePeriod");
@@ -19,6 +23,27 @@ const actualMatchingSelector_1 = require("../../../entity/actualMatching/actualM
19
23
  const jeScheduleHelper_1 = require("../../../entity/jeSchedules/jeScheduleHelper");
20
24
  const jeSchedulesSelector_1 = require("../../../entity/jeSchedules/jeSchedulesSelector");
21
25
  const tenantSelector_1 = require("../../../entity/tenant/tenantSelector");
26
+ const zeniDayJS_1 = require("../../../zeniDayJS");
27
+ const actualMatchingViewState_1 = require("../types/actualMatchingViewState");
28
+ function getSelectedMonthYearForCurrentTenant(state) {
29
+ const tenantId = (0, tenantSelector_1.getCurrentTenant)(state)?.tenantId;
30
+ if (tenantId == null) {
31
+ return undefined;
32
+ }
33
+ return state.expenseAutomationViewState.selectedPeriodByTenantId[tenantId];
34
+ }
35
+ function getTenantDisplayCurrency(state) {
36
+ const locale = (0, tenantSelector_1.getCurrentTenant)(state)?.company?.companyLocaleInfo;
37
+ if (locale?.currencyCode == null || locale.currencyCode === '') {
38
+ return undefined;
39
+ }
40
+ return {
41
+ currencyCode: locale.currencyCode,
42
+ currencySymbol: locale.currencySymbol != null && locale.currencySymbol !== ''
43
+ ? locale.currencySymbol
44
+ : locale.currencyCode,
45
+ };
46
+ }
22
47
  function toMatchModalAccrual(member) {
23
48
  if ('jeOneTimeAccrualKey' in member && member.jeOneTimeAccrualKey != null) {
24
49
  return member;
@@ -40,9 +65,9 @@ function uniqueMatchModalAccruals(pinned, searched) {
40
65
  }
41
66
  function uniqueTransactionSearchResults(pinned, searched) {
42
67
  const byKey = new Map();
43
- pinned.forEach((row) => byKey.set(`${row.transactionId}:${row.lineId}`, row));
68
+ pinned.forEach((row) => byKey.set((0, actualMatchingViewState_1.actualMatchingTransactionLineKey)(row.transactionType, row.transactionIntegrationId, row.lineId), row));
44
69
  searched.forEach((row) => {
45
- const key = `${row.transactionId}:${row.lineId}`;
70
+ const key = (0, actualMatchingViewState_1.actualMatchingTransactionLineKey)(row.transactionType, row.transactionIntegrationId, row.lineId);
46
71
  if (!byKey.has(key)) {
47
72
  byKey.set(key, row);
48
73
  }
@@ -87,11 +112,18 @@ function getClearedMatchedAmount(accrual) {
87
112
  function getActualMatchingAmount(row) {
88
113
  return row.actualAmount?.amount ?? row.estimatedAmount.amount;
89
114
  }
90
- function getActualMatchingReversalDate(accrual) {
91
- if (isPendingReviewAccrual(accrual)) {
92
- return accrual.matchSuggestion.matchedTransactionDate;
115
+ function parseMatchTransactionDate(transactionDate) {
116
+ if (transactionDate == null || transactionDate === '') {
117
+ return undefined;
93
118
  }
94
- return accrual.cleared?.matchedTransactionDate;
119
+ const parsed = (0, zeniDayJS_1.date)(transactionDate);
120
+ return Number.isFinite(parsed.valueOf()) ? parsed : undefined;
121
+ }
122
+ function getActualMatchingReversalDate(accrual, matchTransactionDate) {
123
+ const matchedDate = isPendingReviewAccrual(accrual)
124
+ ? accrual.matchSuggestion.matchedTransactionDate
125
+ : accrual.cleared?.matchedTransactionDate;
126
+ return matchedDate ?? parseMatchTransactionDate(matchTransactionDate);
95
127
  }
96
128
  function collectGroupedMembers(members) {
97
129
  if (members.length <= 1) {
@@ -105,12 +137,32 @@ function collectGroupedMembers(members) {
105
137
  serviceMonth: member.serviceMonth,
106
138
  }));
107
139
  }
140
+ function collectLeftoverMembers(leftovers) {
141
+ if (leftovers == null || leftovers.length === 0) {
142
+ return undefined;
143
+ }
144
+ return leftovers.map((member) => ({
145
+ accrualId: member.accrualId,
146
+ estimatedAmount: member.estimatedAmount,
147
+ jeDebitAccountName: member.jeDebitAccountName,
148
+ memo: member.memo,
149
+ serviceMonth: member.serviceMonth,
150
+ }));
151
+ }
108
152
  function toActualMatchingRow(match) {
109
153
  const firstMember = match.members[0];
110
154
  if (firstMember == null) {
111
155
  throw new Error('Actual matching row requires at least one member');
112
156
  }
113
157
  const first = toMatchModalAccrual(firstMember);
158
+ const leftoverMembers = match.leftovers ?? [];
159
+ const fromCleared = firstMember.cleared?.intendedAccrualIds;
160
+ const intendedAccrualIds = fromCleared != null && fromCleared.length > 0
161
+ ? fromCleared
162
+ : [
163
+ ...match.members.map((member) => member.accrualId),
164
+ ...leftoverMembers.map((leftover) => leftover.accrualId),
165
+ ];
114
166
  return {
115
167
  ...first,
116
168
  matchId: match.matchId,
@@ -123,8 +175,12 @@ function toActualMatchingRow(match) {
123
175
  transactionDate: match.transactionDate,
124
176
  groupedAccrualIds: match.members.map((member) => member.accrualId),
125
177
  groupedMembers: collectGroupedMembers(match.members),
126
- reversalDate: getActualMatchingReversalDate(first),
127
- vendorName: match.vendorName !== '' ? match.vendorName : first.vendorName,
178
+ intendedAccrualIds,
179
+ leftovers: collectLeftoverMembers(match.leftovers),
180
+ reversalDate: getActualMatchingReversalDate(first, match.transactionDate),
181
+ vendorName: match.vendorName != null && match.vendorName !== ''
182
+ ? match.vendorName
183
+ : first.vendorName,
128
184
  estimatedAmount: match.accruedAmount,
129
185
  };
130
186
  }
@@ -133,10 +189,10 @@ function matchesSearch(row, searchString) {
133
189
  if (trimmed.length === 0) {
134
190
  return true;
135
191
  }
136
- if (row.vendorName.toLowerCase().includes(trimmed)) {
192
+ if ((row.vendorName ?? '').toLowerCase().includes(trimmed)) {
137
193
  return true;
138
194
  }
139
- if (row.memo.toLowerCase().includes(trimmed)) {
195
+ if ((row.memo ?? '').toLowerCase().includes(trimmed)) {
140
196
  return true;
141
197
  }
142
198
  return (row.groupedMembers ?? []).some((member) => (member.memo ?? '').toLowerCase().includes(trimmed));
@@ -144,9 +200,14 @@ function matchesSearch(row, searchString) {
144
200
  function getActualMatchingSortValue(row, sortKey) {
145
201
  switch (sortKey) {
146
202
  case 'vendor':
147
- return row.vendorName.toLowerCase();
148
- case 'date':
149
- return row.reversalDate?.valueOf() ?? row.transactionDate;
203
+ return (row.vendorName ?? '').toLowerCase();
204
+ case 'date': {
205
+ const millis = row.reversalDate?.valueOf() ??
206
+ (row.transactionDate != null && row.transactionDate !== ''
207
+ ? (0, zeniDayJS_1.date)(row.transactionDate).valueOf()
208
+ : 0);
209
+ return Number.isFinite(millis) ? millis : 0;
210
+ }
150
211
  case 'amount':
151
212
  return getActualMatchingAmount(row);
152
213
  case 'category':
@@ -179,17 +240,103 @@ function sortActualMatchingRows(rows, uiState) {
179
240
  const lodashOrder = uiState.sortOrder === 'ascending' ? 'asc' : 'desc';
180
241
  return (0, orderBy_1.default)(rows, [(row) => getActualMatchingSortValue(row, uiState.sortKey)], [lodashOrder]);
181
242
  }
243
+ const ESTIMATED_MINUTES_PER_PENDING_MATCH = 4;
182
244
  function emptyKpiSummary() {
183
245
  return {
184
- autoReversed: { count: 0, amount: 0 },
185
- pendingReview: { count: 0, amount: 0 },
186
- totalThisClose: { count: 0, amount: 0 },
187
- ai: { count: 0 },
188
- manual: { count: 0 },
246
+ actualsReceived: { count: 0, expectedCount: 0, vendorsPending: 0 },
247
+ autoReversed: { count: 0, percent: 0, clearedAmount: 0 },
248
+ pendingReview: { count: 0, amount: 0, estimatedMinutesToClear: 0 },
249
+ variance: { percent: 0, averagePercentPerMatch: 0, netAmount: 0 },
189
250
  };
190
251
  }
191
- function sumRowAmounts(rows) {
192
- return rows.reduce((sum, row) => sum + getActualMatchingAmount(row), 0);
252
+ function sumRowAmounts(rows, displayCurrencyCode) {
253
+ return rows.reduce((sum, row) => {
254
+ if (displayCurrencyCode != null &&
255
+ row.accruedAmount.currencyCode !== displayCurrencyCode) {
256
+ return sum;
257
+ }
258
+ return sum + getActualMatchingAmount(row);
259
+ }, 0);
260
+ }
261
+ function uniqueVendorCount(rows) {
262
+ const ids = new Set();
263
+ rows.forEach((row) => {
264
+ if (row.vendorId != null && row.vendorId !== '') {
265
+ ids.add(row.vendorId);
266
+ return;
267
+ }
268
+ if (row.vendorName != null && row.vendorName !== '') {
269
+ ids.add(row.vendorName);
270
+ }
271
+ });
272
+ return ids.size;
273
+ }
274
+ function rowVariancePercent(row) {
275
+ const accrued = row.accruedAmount?.amount ?? row.estimatedAmount.amount;
276
+ const actual = row.actualAmount?.amount;
277
+ if (actual == null || accrued === 0) {
278
+ return undefined;
279
+ }
280
+ return ((actual - accrued) / Math.abs(accrued)) * 100;
281
+ }
282
+ function buildActualMatchingKpiSummary(rows, displayCurrencyCode) {
283
+ if (rows.length === 0) {
284
+ return emptyKpiSummary();
285
+ }
286
+ const autoReversed = rows.filter((row) => row.kind === 'auto_reversed');
287
+ const pendingReview = rows.filter((row) => row.kind === 'pending_review');
288
+ const receivedCount = rows.length;
289
+ const rowsWithActual = rows.filter((row) => {
290
+ if (row.actualAmount?.amount == null) {
291
+ return false;
292
+ }
293
+ if (displayCurrencyCode == null) {
294
+ return true;
295
+ }
296
+ const rowCurrency = row.accruedAmount?.currencyCode ?? row.estimatedAmount.currencyCode;
297
+ return rowCurrency === displayCurrencyCode;
298
+ });
299
+ const accruedTotal = rowsWithActual.reduce((sum, row) => sum + (row.accruedAmount?.amount ?? row.estimatedAmount.amount), 0);
300
+ const actualTotal = rowsWithActual.reduce((sum, row) => sum + (row.actualAmount?.amount ?? 0), 0);
301
+ const variancePercents = rows
302
+ .filter((row) => {
303
+ if (displayCurrencyCode == null) {
304
+ return true;
305
+ }
306
+ const rowCurrency = row.accruedAmount?.currencyCode ?? row.estimatedAmount.currencyCode;
307
+ return rowCurrency === displayCurrencyCode;
308
+ })
309
+ .map(rowVariancePercent)
310
+ .filter((percent) => percent != null);
311
+ return {
312
+ actualsReceived: {
313
+ count: receivedCount,
314
+ // List API does not return still-expected accruals; hide "of N expected"
315
+ // in the strip until expectedCount is greater than received.
316
+ expectedCount: receivedCount,
317
+ vendorsPending: uniqueVendorCount(pendingReview),
318
+ },
319
+ autoReversed: {
320
+ count: autoReversed.length,
321
+ percent: Math.round((autoReversed.length / receivedCount) * 100),
322
+ clearedAmount: sumRowAmounts(autoReversed, displayCurrencyCode),
323
+ },
324
+ pendingReview: {
325
+ count: pendingReview.length,
326
+ amount: sumRowAmounts(pendingReview, displayCurrencyCode),
327
+ estimatedMinutesToClear: pendingReview.length * ESTIMATED_MINUTES_PER_PENDING_MATCH,
328
+ },
329
+ variance: {
330
+ percent: accruedTotal === 0
331
+ ? 0
332
+ : ((actualTotal - accruedTotal) / Math.abs(accruedTotal)) * 100,
333
+ averagePercentPerMatch: variancePercents.length === 0
334
+ ? 0
335
+ : variancePercents.reduce((sum, percent) => sum + percent, 0) /
336
+ variancePercents.length,
337
+ netAmount: actualTotal - accruedTotal,
338
+ },
339
+ };
193
340
  }
194
341
  function getExpenseAutomationActualMatchingMatchView(state, matchId) {
195
342
  const view = state.expenseAutomationActualMatchingViewState;
@@ -197,74 +344,76 @@ function getExpenseAutomationActualMatchingMatchView(state, matchId) {
197
344
  match: (0, actualMatchingSelector_1.getActualMatchingMatchById)(state.actualMatchingState, matchId),
198
345
  detailFetchState: view.detailFetchStateByMatchId[matchId] ?? NOT_STARTED,
199
346
  refreshStatus: view.detailRefreshStatusByMatchId[matchId] ?? NOT_STARTED,
200
- undoReversalStatus: view.undoReversalStatus ?? NOT_STARTED,
347
+ undoReversalStatus: view.undoReversalStatusByMatchId[matchId] ?? NOT_STARTED,
348
+ lastMatchResults: view.lastMatchResultsByMatchId?.[matchId],
349
+ lastUndoResults: view.lastUndoResultsByMatchId?.[matchId],
201
350
  };
202
351
  }
352
+ function getCloseMatchAfterUndo(state) {
353
+ return state.expenseAutomationActualMatchingViewState.closeMatchAfterUndo;
354
+ }
203
355
  function getExpenseAutomationActualMatchingView(state) {
204
- const { expenseAutomationActualMatchingViewState, expenseAutomationViewState } = state;
205
- const { matchIdsByPeriod, fetchState, error, refreshStatus, uiState, matchModal, undoReversalStatus, } = expenseAutomationActualMatchingViewState;
206
- const currentTenant = (0, tenantSelector_1.getCurrentTenant)(state);
207
- const selectedPeriod = expenseAutomationViewState.selectedPeriodByTenantId[currentTenant?.tenantId ?? ''];
356
+ const { expenseAutomationActualMatchingViewState } = state;
357
+ const { matchIdsByPeriod, fetchState, error, refreshStatus, uiState, matchModal, } = expenseAutomationActualMatchingViewState;
358
+ const selectedPeriod = getSelectedMonthYearForCurrentTenant(state);
208
359
  const monthYearPeriodId = selectedPeriod != null ? (0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod) : undefined;
209
360
  const matchIds = monthYearPeriodId != null ? matchIdsByPeriod[monthYearPeriodId] : undefined;
210
- const rows = (matchIds ?? [])
361
+ const allRows = (matchIds ?? [])
211
362
  .map((matchId) => (0, actualMatchingSelector_1.getActualMatchingMatchById)(state.actualMatchingState, matchId))
212
363
  .filter((match) => match != null)
213
364
  .filter((match) => match.members.length > 0)
214
- .map(toActualMatchingRow)
215
- .filter((row) => matchesSearch(row, uiState.searchString));
365
+ .map(toActualMatchingRow);
366
+ const rows = allRows.filter((row) => matchesSearch(row, uiState.searchString));
216
367
  const reversed = sortActualMatchingRows(rows.filter((row) => isReversedMatchKind(row.kind)), uiState);
217
368
  const pendingReview = sortActualMatchingRows(rows.filter((row) => row.kind === 'pending_review'), uiState);
218
- const autoReversedForKpi = reversed.filter((row) => row.kind === 'auto_reversed');
219
- const listedRows = [...reversed, ...pendingReview];
220
- const kpiSummary = listedRows.length === 0
221
- ? emptyKpiSummary()
222
- : {
223
- autoReversed: {
224
- count: autoReversedForKpi.length,
225
- amount: sumRowAmounts(autoReversedForKpi),
226
- },
227
- pendingReview: {
228
- count: pendingReview.length,
229
- amount: sumRowAmounts(pendingReview),
230
- },
231
- totalThisClose: {
232
- count: listedRows.length,
233
- amount: sumRowAmounts(listedRows),
234
- },
235
- ai: {
236
- count: listedRows.filter((row) => row.origin === 'agent').length,
237
- },
238
- manual: {
239
- count: listedRows.filter((row) => row.origin === 'human').length,
240
- },
241
- };
369
+ const kpiSummary = buildActualMatchingKpiSummary(allRows, getTenantDisplayCurrency(state)?.currencyCode);
242
370
  const searchedAccruals = matchModal.accrualSearch.results
243
371
  .map((key) => (0, jeSchedulesSelector_1.getJEOneTimeAccrualByKey)(key, state.jeSchedulesState))
244
372
  .filter((accrual) => accrual != null);
245
373
  const openedMatch = matchModal.openedFromMatchId != null
246
374
  ? (0, actualMatchingSelector_1.getActualMatchingMatchById)(state.actualMatchingState, matchModal.openedFromMatchId)
247
375
  : undefined;
248
- const pinnedAccruals = (openedMatch?.members ?? []).map(toMatchModalAccrual);
249
- const firstSuggestion = openedMatch?.members[0]?.matchSuggestion;
376
+ const pinnedAccruals = [
377
+ ...(openedMatch?.members ?? []),
378
+ ...(openedMatch?.leftovers ?? []),
379
+ ].map(toMatchModalAccrual);
380
+ const firstMember = openedMatch?.members[0];
381
+ const cleared = firstMember?.cleared;
382
+ const suggestion = firstMember?.matchSuggestion;
383
+ const stamp = cleared ?? suggestion;
384
+ const consumedLineIds = cleared?.consumedLineIds;
250
385
  const prefill = openedMatch == null
251
386
  ? undefined
252
387
  : {
253
- selectedAccrualIds: openedMatch.members.map((member) => member.accrualId),
388
+ selectedAccrualIds: [
389
+ ...openedMatch.members.map((member) => member.accrualId),
390
+ ...(openedMatch.leftovers ?? []).map((leftover) => leftover.accrualId),
391
+ ],
254
392
  actualAmount: openedMatch.actualAmount.amount,
255
- matchedLineId: firstSuggestion?.matchedLineId,
256
- matchedTransactionId: firstSuggestion?.matchedTransactionId,
393
+ matchedTransactionId: stamp?.matchedTransactionId,
394
+ matchedLineId: cleared == null ? suggestion?.matchedLineId : undefined,
395
+ ...(consumedLineIds != null && consumedLineIds.length > 0
396
+ ? { consumedLineIds }
397
+ : {}),
257
398
  };
399
+ // `fetchState` is a flat slice field that survives a period switch. Until this
400
+ // month's ids land, leftover Completed/Error is the previous month. Report
401
+ // Not-Started so chrome matches Review's "cache slot missing" skeleton, not
402
+ // empty-complete or a stale error. In-Progress is this month's fetch.
403
+ const fetchStatus = matchIds == null &&
404
+ fetchState !== 'In-Progress' &&
405
+ fetchState !== 'Not-Started'
406
+ ? { fetchState: 'Not-Started', error: undefined }
407
+ : { fetchState, error };
258
408
  return {
259
- fetchState,
260
- error,
409
+ fetchState: fetchStatus.fetchState,
410
+ error: fetchStatus.error,
261
411
  reversed,
262
412
  pendingReview,
263
413
  kpiSummary,
264
414
  uiState,
265
415
  refreshStatus,
266
416
  hasLoadedForSelectedPeriod: matchIds != null,
267
- undoReversalStatus,
268
417
  matchModal: {
269
418
  isOpen: matchModal.isOpen,
270
419
  openedFromMatchId: matchModal.openedFromMatchId,
@@ -381,6 +381,7 @@ function getExpenseAutomationJESchedulesView(state) {
381
381
  // reports undefined rather than 0, so the UI can hide the badge instead of asserting "none".
382
382
  const scheduleCountsByTab = {};
383
383
  jeSchedulesViewState_1.ALL_JE_PAGE_TABS.forEach((pageTab) => {
384
+ // AM's badge is `actualMatching.kpiSummary`, not schedule row keys.
384
385
  if (pageTab === 'actual_matching') {
385
386
  return;
386
387
  }
@@ -51,7 +51,7 @@ export interface MatchAndReverseResult {
51
51
  }
52
52
  export interface MatchAndReverseAccrualsParams {
53
53
  accrualIds: ID[];
54
- lineId: string;
54
+ lineIds: ID[];
55
55
  transactionId: ID;
56
56
  transactionIntegrationId: string;
57
57
  transactionSyncToken: string;
@@ -66,6 +66,7 @@ export interface UndoReversalResult {
66
66
  }
67
67
  export interface UndoReversalAccrualsParams {
68
68
  accrualIds: ID[];
69
+ matchId: ActualMatchingMatchId;
69
70
  }
70
71
  export interface ActualMatchingTransactionSearchState extends FetchStateAndError {
71
72
  isLoadingMore: boolean;
@@ -82,18 +83,23 @@ export interface ActualMatchingAccrualSearchState extends FetchStateAndError {
82
83
  export interface ActualMatchingMatchModalState {
83
84
  accrualSearch: ActualMatchingAccrualSearchState;
84
85
  confirmStatus: FetchStateAndError;
86
+ didPinPrefillTransactions: boolean;
85
87
  isOpen: boolean;
86
88
  pinnedTransactionResults: ActualMatchingTransactionSearchResult[];
87
89
  transactionSearch: ActualMatchingTransactionSearchState;
88
90
  lastBatchResults?: MatchAndReverseResult[];
89
91
  openedFromMatchId?: ActualMatchingMatchId;
90
92
  }
93
+ export declare function actualMatchingTransactionLineKey(transactionType: string, transactionIntegrationId: string, lineId: string): string;
91
94
  export interface ActualMatchingViewState extends FetchedState {
95
+ closeMatchAfterUndo: boolean;
92
96
  detailFetchStateByMatchId: Record<ActualMatchingMatchId, FetchStateAndError>;
93
97
  detailRefreshStatusByMatchId: Record<ActualMatchingMatchId, FetchStateAndError>;
98
+ lastMatchResultsByMatchId: Record<ActualMatchingMatchId, MatchAndReverseResult[]>;
99
+ lastUndoResultsByMatchId: Record<ActualMatchingMatchId, UndoReversalResult[]>;
94
100
  matchIdsByPeriod: Record<MonthYearPeriodId, ActualMatchingMatchId[]>;
95
101
  matchModal: ActualMatchingMatchModalState;
96
102
  refreshStatus: FetchStateAndError;
97
103
  uiState: ActualMatchingViewUIState;
98
- undoReversalStatus: FetchStateAndError;
104
+ undoReversalStatusByMatchId: Record<ActualMatchingMatchId, FetchStateAndError>;
99
105
  }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.toUndoReversalResultStatus = exports.toMatchAndReverseResultStatus = exports.MATCH_SEARCH_DEBOUNCE_MS = exports.MATCH_ACCRUAL_SEARCH_LIMIT = exports.MATCH_TRANSACTION_SEARCH_PAGE_SIZE = exports.toActualMatchingSortKey = void 0;
4
+ exports.actualMatchingTransactionLineKey = actualMatchingTransactionLineKey;
4
5
  const stringToUnion_1 = require("../../../commonStateTypes/stringToUnion");
5
6
  const ACTUAL_MATCHING_SORT_KEYS = [
6
7
  'vendor',
@@ -36,3 +37,6 @@ const UNDO_REVERSAL_RESULT_STATUSES = [
36
37
  ];
37
38
  const toUndoReversalResultStatus = (v) => (0, stringToUnion_1.stringToUnion)(v, UNDO_REVERSAL_RESULT_STATUSES);
38
39
  exports.toUndoReversalResultStatus = toUndoReversalResultStatus;
40
+ function actualMatchingTransactionLineKey(transactionType, transactionIntegrationId, lineId) {
41
+ return `${transactionType}:${transactionIntegrationId}:${lineId}`;
42
+ }
@@ -20,6 +20,9 @@ const JE_SCHEDULE_SORT_KEYS = [
20
20
  ];
21
21
  const toJEScheduleSortKey = (v) => (0, stringToUnion_1.stringToUnion)(v, JE_SCHEDULE_SORT_KEYS);
22
22
  exports.toJEScheduleSortKey = toJEScheduleSortKey;
23
+ // `actual_matching` is chrome only: the JE tab bar includes it, but AM data is
24
+ // `expenseAutomationActualMatchingViewState`, not the schedules period-tab
25
+ // cache below. Walks of ALL_JE_PAGE_TABS that read schedule keys must skip it.
23
26
  const JE_PAGE_TABS = [
24
27
  'schedules',
25
28
  'actual_matching',
@@ -37,9 +37,18 @@ const financeStatement = (0, toolkit_1.createSlice)({
37
37
  },
38
38
  },
39
39
  updateFinanceStatementTimeframe(draft, action) {
40
+ // Unrelated UI re-dispatches the *current* timeframe as a side effect (the
41
+ // reports page hangs its table scroll reset off that callback); dropping the
42
+ // anchor there would silently reset the user's period to the latest one.
43
+ if (draft.timeframe === action.payload) {
44
+ return;
45
+ }
40
46
  draft.timeframe = action.payload;
47
+ // A month anchor is meaningless once the timeframe becomes quarter/year, so
48
+ // a real change drops it and returns the width to the default — 12 months
49
+ // must not become 12 quarters. Order is the user's, so it survives.
41
50
  draft.selectedCOABalancesRange = {
42
- numberOfPeriods: draft.selectedCOABalancesRange.numberOfPeriods,
51
+ numberOfPeriods: exports.initialFinanceStatementState.selectedCOABalancesRange.numberOfPeriods,
43
52
  orderBy: draft.selectedCOABalancesRange.orderBy,
44
53
  };
45
54
  },
@@ -81,8 +90,7 @@ const financeStatement = (0, toolkit_1.createSlice)({
81
90
  draft.selectedReportId = action.payload;
82
91
  },
83
92
  updateFinanceStatementAdditionalBalancesSelection(draft, action) {
84
- const { firstMonthOfFY, additionalBalances: additionalBalancesPayload, coaBalances, maxNumOfPeriodsToHighlight, } = action.payload;
85
- const safeCoaBalances = coaBalances != null ? coaBalances : [];
93
+ const { additionalBalances: additionalBalancesPayload, maxNumOfPeriodsToHighlight, } = action.payload;
86
94
  const additionalBalances = additionalBalancesPayload ?? [];
87
95
  let tempAdditionalBalances = [...additionalBalances];
88
96
  if (additionalBalances.includes('this_period_vs_last_period') ||
@@ -98,30 +106,12 @@ const financeStatement = (0, toolkit_1.createSlice)({
98
106
  draft.maxNumOfPeriodsToHighlight = maxNumOfPeriodsToHighlight;
99
107
  draft.isAdditionalBalancesShown =
100
108
  additionalBalances.length != 0 ? true : false;
101
- const { timeframe, selectedCOABalancesRange } = draft;
102
- if (safeCoaBalances.length > 0) {
103
- const thisPeriod = (0, thisPeriodHelpers_1.extractThisPeriod)(action.payload.firstMonthOfFY, timeframe, selectedCOABalancesRange, safeCoaBalances);
104
- if (thisPeriod != null) {
105
- const selectedCoaBalancesRangeWithThisPeriod = {
106
- ...selectedCOABalancesRange,
107
- thisPeriod,
108
- };
109
- const selectionRanges = (0, getSelectedAndHighlightedRanges_1.getSelectedAndHighlightedRangesForThisPeriod)({
110
- firstMonthOfFY,
111
- thisPeriod: thisPeriod,
112
- coaBalances: safeCoaBalances,
113
- timeframe,
114
- maxNumOfPeriodsToHighlight: maxNumOfPeriodsToHighlight,
115
- currentSelection: {
116
- selectedCOABalancesRange: selectedCoaBalancesRangeWithThisPeriod,
117
- },
118
- orderBy: 'ascending_date',
119
- maxNumOfPeriodsToSelect: maxNumOfPeriodsToHighlight,
120
- });
121
- draft.selectedCOABalancesRange =
122
- selectionRanges.selectedCOABalancesRange;
123
- }
124
- }
109
+ // Column metadata only — must not touch selectedCOABalancesRange. The
110
+ // reports page fires this on load, on resize and on every fetch-state
111
+ // transition (so also on report switch and on remount after a drill-down)
112
+ // with a viewport-derived period count that knows nothing about the range
113
+ // the user picked. The width default comes from initial state, and from
114
+ // updateFinanceStatementTimeframe on a real timeframe change.
125
115
  },
126
116
  updateDownloadState(draft, action) {
127
117
  draft.downloadState = action.payload;
@@ -440,7 +440,7 @@ const getBillListOnSort = (sortKey, sortOrder, currentTab, transactions) => {
440
440
  }
441
441
  }
442
442
  else {
443
- if (Boolean(transaction.billPayInfo.paymentDate) === true) {
443
+ if (Boolean((0, billListFilterHelpers_1.getActualPaymentDate)(transaction.billPayInfo)) === true) {
444
444
  transactionsWithSortKeyValue.push(transaction);
445
445
  }
446
446
  else {
@@ -525,7 +525,7 @@ const getBillListOnSort = (sortKey, sortOrder, currentTab, transactions) => {
525
525
  case 'dueDate':
526
526
  return currentTab === 'draft'
527
527
  ? transaction.dueDate
528
- : transaction.billPayInfo.paymentDate;
528
+ : (0, billListFilterHelpers_1.getActualPaymentDate)(transaction.billPayInfo);
529
529
  case 'status':
530
530
  return getStatusPerTab(transaction);
531
531
  case 'amount':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "5.2.35-beta0MM",
3
+ "version": "5.2.35-beta11MM",
4
4
  "description": "Shared module between Web & Mobile containing required abstractions for state management, async network communication. ",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib/esm/index.js",