@zeniai/client-epic-state 5.2.41 → 5.2.42

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 (100) hide show
  1. package/lib/entity/actualMatching/actualMatchingPayload.d.ts +31 -0
  2. package/lib/entity/actualMatching/actualMatchingPayload.js +52 -0
  3. package/lib/entity/actualMatching/actualMatchingReducer.d.ts +9 -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 +23 -0
  8. package/lib/entity/actualMatching/actualMatchingState.js +2 -0
  9. package/lib/entity/actualMatching/actualMatchingTypes.d.ts +1 -0
  10. package/lib/entity/actualMatching/actualMatchingTypes.js +2 -0
  11. package/lib/entity/jeSchedules/jeSchedulesPayload.d.ts +31 -0
  12. package/lib/entity/jeSchedules/jeSchedulesPayload.js +53 -0
  13. package/lib/entity/jeSchedules/jeSchedulesState.d.ts +33 -0
  14. package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
  15. package/lib/entity/snackbar/snackbarTypes.js +2 -0
  16. package/lib/epic.d.ts +8 -1
  17. package/lib/epic.js +14 -0
  18. package/lib/esm/entity/actualMatching/actualMatchingPayload.js +48 -0
  19. package/lib/esm/entity/actualMatching/actualMatchingReducer.js +30 -0
  20. package/lib/esm/entity/actualMatching/actualMatchingSelector.js +3 -0
  21. package/lib/esm/entity/actualMatching/actualMatchingState.js +1 -0
  22. package/lib/esm/entity/actualMatching/actualMatchingTypes.js +1 -0
  23. package/lib/esm/entity/jeSchedules/jeSchedulesPayload.js +53 -0
  24. package/lib/esm/entity/snackbar/snackbarTypes.js +2 -0
  25. package/lib/esm/epic.js +14 -0
  26. package/lib/esm/index.js +7 -3
  27. package/lib/esm/reducer.js +6 -0
  28. package/lib/esm/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +25 -12
  29. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingEpic.js +46 -0
  30. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingMatchEpic.js +36 -0
  31. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingPageEpic.js +7 -0
  32. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/matchAndReverseAccrualsEpic.js +108 -0
  33. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/refreshActualMatchingList.js +16 -0
  34. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/searchMatchAccrualsEpic.js +60 -0
  35. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/searchMatchTransactionsEpic.js +74 -0
  36. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/undoReversalAccrualsEpic.js +78 -0
  37. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.js +13 -3
  38. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/prefetchOtherJeSchedulesTabEpic.js +40 -15
  39. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +19 -8
  40. package/lib/esm/view/expenseAutomationView/expenseAutomationViewReducer.js +2 -1
  41. package/lib/esm/view/expenseAutomationView/expenseAutomationViewSelector.js +24 -6
  42. package/lib/esm/view/expenseAutomationView/expenseAutomationViewState.js +4 -2
  43. package/lib/esm/view/expenseAutomationView/payload/actualMatchingPayload.js +68 -0
  44. package/lib/esm/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +462 -0
  45. package/lib/esm/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +2 -1
  46. package/lib/esm/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.js +1 -0
  47. package/lib/esm/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +426 -0
  48. package/lib/esm/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +4 -0
  49. package/lib/esm/view/expenseAutomationView/types/actualMatchingViewState.js +38 -0
  50. package/lib/esm/view/expenseAutomationView/types/jeSchedulesViewState.js +8 -1
  51. package/lib/index.d.ts +14 -5
  52. package/lib/index.js +82 -48
  53. package/lib/reducer.d.ts +2 -0
  54. package/lib/reducer.js +6 -0
  55. package/lib/rootStateTypes.d.ts +4 -0
  56. package/lib/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +25 -12
  57. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingEpic.d.ts +9 -0
  58. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingEpic.js +50 -0
  59. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingMatchEpic.d.ts +12 -0
  60. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingMatchEpic.js +40 -0
  61. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingPageEpic.d.ts +5 -0
  62. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingPageEpic.js +11 -0
  63. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/matchAndReverseAccrualsEpic.d.ts +9 -0
  64. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/matchAndReverseAccrualsEpic.js +112 -0
  65. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/refreshActualMatchingList.d.ts +3 -0
  66. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/refreshActualMatchingList.js +19 -0
  67. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/searchMatchAccrualsEpic.d.ts +9 -0
  68. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/searchMatchAccrualsEpic.js +64 -0
  69. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/searchMatchTransactionsEpic.d.ts +8 -0
  70. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/searchMatchTransactionsEpic.js +78 -0
  71. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/undoReversalAccrualsEpic.d.ts +9 -0
  72. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/undoReversalAccrualsEpic.js +82 -0
  73. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.d.ts +2 -1
  74. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.js +13 -3
  75. package/lib/view/expenseAutomationView/epics/jeSchedule/prefetchOtherJeSchedulesTabEpic.d.ts +5 -1
  76. package/lib/view/expenseAutomationView/epics/jeSchedule/prefetchOtherJeSchedulesTabEpic.js +39 -14
  77. package/lib/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +18 -7
  78. package/lib/view/expenseAutomationView/expenseAutomationViewReducer.d.ts +2 -1
  79. package/lib/view/expenseAutomationView/expenseAutomationViewReducer.js +2 -1
  80. package/lib/view/expenseAutomationView/expenseAutomationViewSelector.js +24 -6
  81. package/lib/view/expenseAutomationView/expenseAutomationViewState.d.ts +2 -1
  82. package/lib/view/expenseAutomationView/expenseAutomationViewState.js +6 -3
  83. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
  84. package/lib/view/expenseAutomationView/payload/actualMatchingPayload.d.ts +85 -0
  85. package/lib/view/expenseAutomationView/payload/actualMatchingPayload.js +74 -0
  86. package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.d.ts +79 -0
  87. package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +466 -0
  88. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.d.ts +6 -5
  89. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +2 -1
  90. package/lib/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.d.ts +87 -0
  91. package/lib/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.js +2 -0
  92. package/lib/view/expenseAutomationView/selectorTypes/expenseAutomationViewSelectorTypes.d.ts +11 -0
  93. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.d.ts +38 -0
  94. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +445 -0
  95. package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +4 -0
  96. package/lib/view/expenseAutomationView/types/actualMatchingViewState.d.ts +106 -0
  97. package/lib/view/expenseAutomationView/types/actualMatchingViewState.js +46 -0
  98. package/lib/view/expenseAutomationView/types/jeSchedulesViewState.d.ts +1 -1
  99. package/lib/view/expenseAutomationView/types/jeSchedulesViewState.js +8 -1
  100. package/package.json +1 -1
@@ -0,0 +1,87 @@
1
+ import { Amount } from '../../../commonStateTypes/amount';
2
+ import { FetchStateAndError, ID } from '../../../commonStateTypes/common';
3
+ import { SelectorView } from '../../../commonStateTypes/viewAndReport/viewAndReport';
4
+ import { JEOneTimeAccrual } from '../../../entity/jeSchedules/jeSchedulesSelector';
5
+ import { ZeniDate } from '../../../zeniDayJS';
6
+ import { ActualMatchingTransactionSearchResult, ActualMatchingViewUIState, MatchAndReverseResult } from '../types/actualMatchingViewState';
7
+ export type ActualMatchingRowKind = 'auto_reversed' | 'pending_review' | 'manually_reversed';
8
+ export type ActualMatchingGroupedAccrual = {
9
+ accrualId: ID;
10
+ estimatedAmount?: Amount;
11
+ jeDebitAccountName?: string;
12
+ memo?: string;
13
+ serviceMonth?: string;
14
+ };
15
+ export interface ActualMatchingRow extends JEOneTimeAccrual {
16
+ accruedAmount: Amount;
17
+ actualAmount: Amount;
18
+ kind: ActualMatchingRowKind;
19
+ matchId: string;
20
+ groupedAccrualIds?: ID[];
21
+ groupedMembers?: ActualMatchingGroupedAccrual[];
22
+ intendedAccrualIds?: ID[];
23
+ leftovers?: ActualMatchingGroupedAccrual[];
24
+ matchShape?: string;
25
+ matchTier?: string;
26
+ reversalDate?: ZeniDate;
27
+ transactionDate?: string;
28
+ transactionType?: string;
29
+ }
30
+ export interface ActualMatchingKpiSummary {
31
+ actualsReceived: {
32
+ count: number;
33
+ expectedCount: number;
34
+ vendorsPending: number;
35
+ };
36
+ autoReversed: {
37
+ clearedAmount: number;
38
+ count: number;
39
+ percent: number;
40
+ };
41
+ pendingReview: {
42
+ amount: number;
43
+ count: number;
44
+ estimatedMinutesToClear: number;
45
+ };
46
+ variance: {
47
+ averagePercentPerMatch: number;
48
+ netAmount: number;
49
+ percent: number;
50
+ };
51
+ }
52
+ export interface ActualMatchingMatchModalView {
53
+ accrualSearch: {
54
+ fetchState: FetchStateAndError;
55
+ isLoadingMore: boolean;
56
+ nextPageToken: string | null;
57
+ query: string;
58
+ results: JEOneTimeAccrual[];
59
+ };
60
+ confirmStatus: FetchStateAndError;
61
+ isOpen: boolean;
62
+ transactionSearch: {
63
+ fetchState: FetchStateAndError;
64
+ isLoadingMore: boolean;
65
+ nextPageToken: string | null;
66
+ query: string;
67
+ results: ActualMatchingTransactionSearchResult[];
68
+ };
69
+ lastBatchResults?: MatchAndReverseResult[];
70
+ openedFromMatchId?: string;
71
+ prefill?: {
72
+ selectedAccrualIds: ID[];
73
+ actualAmount?: number;
74
+ consumedLineIds?: ID[];
75
+ matchedLineId?: string;
76
+ matchedTransactionId?: string;
77
+ };
78
+ }
79
+ export interface ExpenseAutomationActualMatchingViewSelector extends SelectorView {
80
+ hasLoadedForSelectedPeriod: boolean;
81
+ kpiSummary: ActualMatchingKpiSummary;
82
+ matchModal: ActualMatchingMatchModalView;
83
+ pendingReview: ActualMatchingRow[];
84
+ refreshStatus: FetchStateAndError;
85
+ reversed: ActualMatchingRow[];
86
+ uiState: ActualMatchingViewUIState;
87
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -6,6 +6,7 @@ import { MonthEndCloseChecksView } from '../../../view/monthEndCloseChecksView/m
6
6
  import { CompanyMonthEndReportSelectorView } from '../../companyMonthEndReportView/companyMonthEndReportViewSelector';
7
7
  import { ExpenseAutomationViewType } from '../expenseAutomationViewState';
8
8
  import { TransactionsTab } from '../types/transactionsViewState';
9
+ import { ExpenseAutomationActualMatchingViewSelector } from './actualMatchingViewSelectorTypes';
9
10
  import { ExpenseAutomationFluxAnalysisViewSelector } from './fluxAnalysisViewSelectorTypes';
10
11
  import { ExpenseAutomationJESchedulesViewSelector } from './jeSchedulesSelectorTypes';
11
12
  import { ExpenseAutomationMissingReceiptsViewSelector } from './missingReceiptsSelectorTypes';
@@ -21,6 +22,16 @@ export interface ExpenseAutomationStepDetails {
21
22
  title: string;
22
23
  }
23
24
  export interface ExpenseAutomationViewSelector extends SelectorView {
25
+ /**
26
+ * Sibling of `jeSchedules`, not nested under it. The Journal Entries navbar
27
+ * hosts Actual Matching (`selectedPageTab === 'actual_matching'`), but AM is
28
+ * a different GET, entity map (`byMatchId`), and period cache
29
+ * (`matchIdsByPeriod`) than Review / Existing. Nesting it on `jeSchedules`
30
+ * would hide a second store inside the schedules selector. Chrome coupling
31
+ * stays `jeSchedules.uiState.selectedPageTab`; AM rows, Match New, and undo
32
+ * live here.
33
+ */
34
+ actualMatching: ExpenseAutomationActualMatchingViewSelector;
24
35
  allSteps: ExpenseAutomationStepDetails[];
25
36
  completionStatus: CompletionStatusType;
26
37
  currentSelectedView: ExpenseAutomationViewType;
@@ -0,0 +1,38 @@
1
+ import { FetchStateAndError } from '../../../commonStateTypes/common';
2
+ import { MonthYearPeriod } from '../../../commonStateTypes/timePeriod';
3
+ import { DisplayCurrencyFallback } from '../../../entity/actualMatching/actualMatchingPayload';
4
+ import { ActualMatchingMatch } from '../../../entity/actualMatching/actualMatchingState';
5
+ import { ActualMatchingMatchId } from '../../../entity/actualMatching/actualMatchingTypes';
6
+ import { JEOneTimeAccrual } from '../../../entity/jeSchedules/jeSchedulesSelector';
7
+ import { JEOneTimeAccrualMatchSuggestion } from '../../../entity/jeSchedules/jeSchedulesState';
8
+ import { RootState } from '../../../rootStateTypes';
9
+ import { ZeniDate } from '../../../zeniDayJS';
10
+ import { ActualMatchingKpiSummary, ActualMatchingRow, ActualMatchingRowKind, ExpenseAutomationActualMatchingViewSelector } from '../selectorTypes/actualMatchingViewSelectorTypes';
11
+ import { MatchAndReverseResult, UndoReversalResult } from '../types/actualMatchingViewState';
12
+ export declare function getSelectedMonthYearForCurrentTenant(state: RootState): MonthYearPeriod | undefined;
13
+ export declare function getTenantDisplayCurrency(state: RootState): DisplayCurrencyFallback | undefined;
14
+ export declare function parseActualMatchingMatchId(matchId: string): {
15
+ transactionIntegrationId: string;
16
+ transactionType: string;
17
+ } | null;
18
+ export declare function isAutoReversedAccrual(accrual: JEOneTimeAccrual): boolean;
19
+ export declare function isManuallyReversedAccrual(accrual: JEOneTimeAccrual): boolean;
20
+ export declare function isPendingReviewAccrual(accrual: JEOneTimeAccrual): accrual is JEOneTimeAccrual & {
21
+ matchSuggestion: JEOneTimeAccrualMatchSuggestion;
22
+ };
23
+ export declare function isReversedMatchKind(kind: ActualMatchingRowKind): boolean;
24
+ export declare function getClearedMatchedAmount(accrual: JEOneTimeAccrual): number | undefined;
25
+ export declare function getActualMatchingAmount(row: ActualMatchingRow): number;
26
+ export declare function getActualMatchingReversalDate(accrual: JEOneTimeAccrual, matchTransactionDate?: string): ZeniDate | undefined;
27
+ export declare function buildActualMatchingKpiSummary(rows: ActualMatchingRow[], displayCurrencyCode?: string): ActualMatchingKpiSummary;
28
+ export interface ExpenseAutomationActualMatchingMatchView {
29
+ detailFetchState: FetchStateAndError;
30
+ refreshStatus: FetchStateAndError;
31
+ undoReversalStatus: FetchStateAndError;
32
+ lastMatchResults?: MatchAndReverseResult[];
33
+ lastUndoResults?: UndoReversalResult[];
34
+ match?: ActualMatchingMatch;
35
+ }
36
+ export declare function getExpenseAutomationActualMatchingMatchView(state: RootState, matchId: ActualMatchingMatchId): ExpenseAutomationActualMatchingMatchView;
37
+ export declare function getCloseMatchAfterUndo(state: RootState): boolean;
38
+ export declare function getExpenseAutomationActualMatchingView(state: RootState): ExpenseAutomationActualMatchingViewSelector;
@@ -0,0 +1,445 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getSelectedMonthYearForCurrentTenant = getSelectedMonthYearForCurrentTenant;
7
+ exports.getTenantDisplayCurrency = getTenantDisplayCurrency;
8
+ exports.parseActualMatchingMatchId = parseActualMatchingMatchId;
9
+ exports.isAutoReversedAccrual = isAutoReversedAccrual;
10
+ exports.isManuallyReversedAccrual = isManuallyReversedAccrual;
11
+ exports.isPendingReviewAccrual = isPendingReviewAccrual;
12
+ exports.isReversedMatchKind = isReversedMatchKind;
13
+ exports.getClearedMatchedAmount = getClearedMatchedAmount;
14
+ exports.getActualMatchingAmount = getActualMatchingAmount;
15
+ exports.getActualMatchingReversalDate = getActualMatchingReversalDate;
16
+ exports.buildActualMatchingKpiSummary = buildActualMatchingKpiSummary;
17
+ exports.getExpenseAutomationActualMatchingMatchView = getExpenseAutomationActualMatchingMatchView;
18
+ exports.getCloseMatchAfterUndo = getCloseMatchAfterUndo;
19
+ exports.getExpenseAutomationActualMatchingView = getExpenseAutomationActualMatchingView;
20
+ const orderBy_1 = __importDefault(require("lodash/orderBy"));
21
+ const timePeriod_1 = require("../../../commonStateTypes/timePeriod");
22
+ const actualMatchingSelector_1 = require("../../../entity/actualMatching/actualMatchingSelector");
23
+ const jeScheduleHelper_1 = require("../../../entity/jeSchedules/jeScheduleHelper");
24
+ const jeSchedulesSelector_1 = require("../../../entity/jeSchedules/jeSchedulesSelector");
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
+ }
47
+ function toMatchModalAccrual(member) {
48
+ if ('jeOneTimeAccrualKey' in member && member.jeOneTimeAccrualKey != null) {
49
+ return member;
50
+ }
51
+ return {
52
+ ...member,
53
+ jeOneTimeAccrualKey: (0, jeScheduleHelper_1.generateJEOneTimeAccrualKey)(member.accrualId),
54
+ };
55
+ }
56
+ function uniqueMatchModalAccruals(pinned, searched) {
57
+ const byId = new Map();
58
+ pinned.forEach((row) => byId.set(row.accrualId, row));
59
+ searched.forEach((row) => {
60
+ if (!byId.has(row.accrualId)) {
61
+ byId.set(row.accrualId, row);
62
+ }
63
+ });
64
+ return Array.from(byId.values());
65
+ }
66
+ function uniqueTransactionSearchResults(pinned, searched) {
67
+ const byKey = new Map();
68
+ pinned.forEach((row) => byKey.set((0, actualMatchingViewState_1.actualMatchingTransactionLineKey)(row.transactionType, row.transactionIntegrationId, row.lineId), row));
69
+ searched.forEach((row) => {
70
+ const key = (0, actualMatchingViewState_1.actualMatchingTransactionLineKey)(row.transactionType, row.transactionIntegrationId, row.lineId);
71
+ if (!byKey.has(key)) {
72
+ byKey.set(key, row);
73
+ }
74
+ });
75
+ return Array.from(byKey.values());
76
+ }
77
+ function parseActualMatchingMatchId(matchId) {
78
+ const separator = matchId.indexOf(':');
79
+ if (separator <= 0 || separator === matchId.length - 1) {
80
+ return null;
81
+ }
82
+ return {
83
+ transactionType: matchId.slice(0, separator).toLowerCase(),
84
+ transactionIntegrationId: matchId.slice(separator + 1),
85
+ };
86
+ }
87
+ const NOT_STARTED = {
88
+ fetchState: 'Not-Started',
89
+ error: undefined,
90
+ };
91
+ function isAutoReversedAccrual(accrual) {
92
+ return (accrual.accrualStatus === 'reversed' &&
93
+ accrual.reversalTrigger === 'bill_match');
94
+ }
95
+ function isManuallyReversedAccrual(accrual) {
96
+ return (accrual.accrualStatus === 'reversed' &&
97
+ accrual.reversalTrigger === 'manual_reverse');
98
+ }
99
+ function isPendingReviewAccrual(accrual) {
100
+ if (accrual.accrualStatus !== 'posted' || accrual.matchSuggestion == null) {
101
+ return false;
102
+ }
103
+ const resolution = accrual.matchSuggestion.resolution;
104
+ return resolution == null || resolution.length === 0;
105
+ }
106
+ function isReversedMatchKind(kind) {
107
+ return kind === 'auto_reversed' || kind === 'manually_reversed';
108
+ }
109
+ function getClearedMatchedAmount(accrual) {
110
+ return accrual.cleared?.matchedAmount?.amount;
111
+ }
112
+ function getActualMatchingAmount(row) {
113
+ return row.actualAmount?.amount ?? row.estimatedAmount.amount;
114
+ }
115
+ function parseMatchTransactionDate(transactionDate) {
116
+ if (transactionDate == null || transactionDate === '') {
117
+ return undefined;
118
+ }
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);
127
+ }
128
+ function collectGroupedMembers(members) {
129
+ if (members.length <= 1) {
130
+ return undefined;
131
+ }
132
+ return members.map((member) => ({
133
+ accrualId: member.accrualId,
134
+ estimatedAmount: member.estimatedAmount,
135
+ jeDebitAccountName: member.jeDebitAccountName,
136
+ memo: member.memo,
137
+ serviceMonth: member.serviceMonth,
138
+ }));
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
+ }
152
+ function toActualMatchingRow(match) {
153
+ const firstMember = match.members[0];
154
+ if (firstMember == null) {
155
+ throw new Error('Actual matching row requires at least one member');
156
+ }
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
+ ];
166
+ return {
167
+ ...first,
168
+ matchId: match.matchId,
169
+ kind: match.kind,
170
+ accruedAmount: match.accruedAmount,
171
+ actualAmount: match.actualAmount,
172
+ matchTier: match.matchTier,
173
+ matchShape: match.matchShape,
174
+ transactionType: match.transactionType,
175
+ transactionDate: match.transactionDate,
176
+ groupedAccrualIds: match.members.map((member) => member.accrualId),
177
+ groupedMembers: collectGroupedMembers(match.members),
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,
184
+ estimatedAmount: match.accruedAmount,
185
+ };
186
+ }
187
+ function matchesSearch(row, searchString) {
188
+ const trimmed = searchString.trim().toLowerCase();
189
+ if (trimmed.length === 0) {
190
+ return true;
191
+ }
192
+ if ((row.vendorName ?? '').toLowerCase().includes(trimmed)) {
193
+ return true;
194
+ }
195
+ if ((row.memo ?? '').toLowerCase().includes(trimmed)) {
196
+ return true;
197
+ }
198
+ return (row.groupedMembers ?? []).some((member) => (member.memo ?? '').toLowerCase().includes(trimmed));
199
+ }
200
+ function getActualMatchingSortValue(row, sortKey) {
201
+ switch (sortKey) {
202
+ case 'vendor':
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
+ }
211
+ case 'amount':
212
+ return getActualMatchingAmount(row);
213
+ case 'category':
214
+ return row.jeDebitAccountName?.toLowerCase();
215
+ case 'class':
216
+ return row.jeDebitAccountingClassName?.toLowerCase();
217
+ case 'type':
218
+ return row.transactionType?.toLowerCase();
219
+ case 'lifecycle':
220
+ return row.kind;
221
+ case 'service':
222
+ return row.serviceMonth;
223
+ case 'accrued':
224
+ return row.accruedAmount?.amount ?? row.estimatedAmount.amount;
225
+ case 'actual':
226
+ return row.actualAmount?.amount;
227
+ case 'variance': {
228
+ const accrued = row.accruedAmount?.amount ?? row.estimatedAmount.amount;
229
+ const actual = row.actualAmount?.amount;
230
+ if (actual == null || accrued === 0) {
231
+ return undefined;
232
+ }
233
+ return ((actual - accrued) / Math.abs(accrued)) * 100;
234
+ }
235
+ default:
236
+ return undefined;
237
+ }
238
+ }
239
+ function sortActualMatchingRows(rows, uiState) {
240
+ const lodashOrder = uiState.sortOrder === 'ascending' ? 'asc' : 'desc';
241
+ return (0, orderBy_1.default)(rows, [(row) => getActualMatchingSortValue(row, uiState.sortKey)], [lodashOrder]);
242
+ }
243
+ const ESTIMATED_MINUTES_PER_PENDING_MATCH = 4;
244
+ function emptyKpiSummary() {
245
+ return {
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 },
250
+ };
251
+ }
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 isKnownKpiKind(kind) {
262
+ return (kind === 'auto_reversed' ||
263
+ kind === 'pending_review' ||
264
+ kind === 'manually_reversed');
265
+ }
266
+ function rowMatchesDisplayCurrency(row, displayCurrencyCode) {
267
+ if (displayCurrencyCode == null) {
268
+ return true;
269
+ }
270
+ const rowCurrency = row.accruedAmount?.currencyCode ?? row.estimatedAmount.currencyCode;
271
+ return rowCurrency === displayCurrencyCode;
272
+ }
273
+ function uniqueVendorCount(rows) {
274
+ const ids = new Set();
275
+ rows.forEach((row) => {
276
+ if (row.vendorId != null && row.vendorId !== '') {
277
+ ids.add(row.vendorId);
278
+ return;
279
+ }
280
+ if (row.vendorName != null && row.vendorName !== '') {
281
+ ids.add(row.vendorName);
282
+ }
283
+ });
284
+ return ids.size;
285
+ }
286
+ function rowVariancePercent(row) {
287
+ const accrued = row.accruedAmount?.amount ?? row.estimatedAmount.amount;
288
+ const actual = row.actualAmount?.amount;
289
+ if (actual == null || accrued === 0) {
290
+ return undefined;
291
+ }
292
+ return ((actual - accrued) / Math.abs(accrued)) * 100;
293
+ }
294
+ function buildActualMatchingKpiSummary(rows, displayCurrencyCode) {
295
+ // Strip counts and amounts share this set: known kinds only, and (when a
296
+ // display currency is set) that currency only. Lists still render every row.
297
+ const kpiRows = rows.filter((row) => isKnownKpiKind(row.kind) &&
298
+ rowMatchesDisplayCurrency(row, displayCurrencyCode));
299
+ if (kpiRows.length === 0) {
300
+ return emptyKpiSummary();
301
+ }
302
+ const autoReversed = kpiRows.filter((row) => row.kind === 'auto_reversed');
303
+ const pendingReview = kpiRows.filter((row) => row.kind === 'pending_review');
304
+ const receivedCount = kpiRows.length;
305
+ const rowsWithActual = kpiRows.filter((row) => row.actualAmount?.amount != null);
306
+ const accruedTotal = rowsWithActual.reduce((sum, row) => sum + (row.accruedAmount?.amount ?? row.estimatedAmount.amount), 0);
307
+ const actualTotal = rowsWithActual.reduce((sum, row) => sum + (row.actualAmount?.amount ?? 0), 0);
308
+ const variancePercents = kpiRows
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
+ };
340
+ }
341
+ function getExpenseAutomationActualMatchingMatchView(state, matchId) {
342
+ const view = state.expenseAutomationActualMatchingViewState;
343
+ return {
344
+ match: (0, actualMatchingSelector_1.getActualMatchingMatchById)(state.actualMatchingState, matchId),
345
+ detailFetchState: view.detailFetchStateByMatchId[matchId] ?? NOT_STARTED,
346
+ refreshStatus: view.detailRefreshStatusByMatchId[matchId] ?? NOT_STARTED,
347
+ undoReversalStatus: view.undoReversalStatusByMatchId[matchId] ?? NOT_STARTED,
348
+ lastMatchResults: view.lastMatchResultsByMatchId?.[matchId],
349
+ lastUndoResults: view.lastUndoResultsByMatchId?.[matchId],
350
+ };
351
+ }
352
+ function getCloseMatchAfterUndo(state) {
353
+ return state.expenseAutomationActualMatchingViewState.closeMatchAfterUndo;
354
+ }
355
+ function getExpenseAutomationActualMatchingView(state) {
356
+ const { expenseAutomationActualMatchingViewState } = state;
357
+ const { matchIdsByPeriod, fetchState, error, refreshStatus, uiState, matchModal, } = expenseAutomationActualMatchingViewState;
358
+ const selectedPeriod = getSelectedMonthYearForCurrentTenant(state);
359
+ const monthYearPeriodId = selectedPeriod != null ? (0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod) : undefined;
360
+ const matchIds = monthYearPeriodId != null ? matchIdsByPeriod[monthYearPeriodId] : undefined;
361
+ const allRows = (matchIds ?? [])
362
+ .map((matchId) => (0, actualMatchingSelector_1.getActualMatchingMatchById)(state.actualMatchingState, matchId))
363
+ .filter((match) => match != null)
364
+ .filter((match) => match.members.length > 0)
365
+ .map(toActualMatchingRow);
366
+ const rows = allRows.filter((row) => matchesSearch(row, uiState.searchString));
367
+ const reversed = sortActualMatchingRows(rows.filter((row) => isReversedMatchKind(row.kind)), uiState);
368
+ const pendingReview = sortActualMatchingRows(rows.filter((row) => row.kind === 'pending_review'), uiState);
369
+ const kpiSummary = buildActualMatchingKpiSummary(allRows, getTenantDisplayCurrency(state)?.currencyCode);
370
+ const searchedAccruals = matchModal.accrualSearch.results
371
+ .map((key) => (0, jeSchedulesSelector_1.getJEOneTimeAccrualByKey)(key, state.jeSchedulesState))
372
+ .filter((accrual) => accrual != null);
373
+ const openedMatch = matchModal.openedFromMatchId != null
374
+ ? (0, actualMatchingSelector_1.getActualMatchingMatchById)(state.actualMatchingState, matchModal.openedFromMatchId)
375
+ : undefined;
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;
385
+ const prefill = openedMatch == null
386
+ ? undefined
387
+ : {
388
+ selectedAccrualIds: [
389
+ ...openedMatch.members.map((member) => member.accrualId),
390
+ ...(openedMatch.leftovers ?? []).map((leftover) => leftover.accrualId),
391
+ ],
392
+ actualAmount: openedMatch.actualAmount.amount,
393
+ matchedTransactionId: stamp?.matchedTransactionId,
394
+ matchedLineId: cleared == null ? suggestion?.matchedLineId : undefined,
395
+ ...(consumedLineIds != null && consumedLineIds.length > 0
396
+ ? { consumedLineIds }
397
+ : {}),
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 };
408
+ return {
409
+ fetchState: fetchStatus.fetchState,
410
+ error: fetchStatus.error,
411
+ reversed,
412
+ pendingReview,
413
+ kpiSummary,
414
+ uiState,
415
+ refreshStatus,
416
+ hasLoadedForSelectedPeriod: matchIds != null,
417
+ matchModal: {
418
+ isOpen: matchModal.isOpen,
419
+ openedFromMatchId: matchModal.openedFromMatchId,
420
+ prefill,
421
+ confirmStatus: matchModal.confirmStatus,
422
+ lastBatchResults: matchModal.lastBatchResults,
423
+ transactionSearch: {
424
+ query: matchModal.transactionSearch.query,
425
+ results: uniqueTransactionSearchResults(matchModal.pinnedTransactionResults, matchModal.transactionSearch.results),
426
+ fetchState: {
427
+ fetchState: matchModal.transactionSearch.fetchState,
428
+ error: matchModal.transactionSearch.error,
429
+ },
430
+ nextPageToken: matchModal.transactionSearch.nextPageToken,
431
+ isLoadingMore: matchModal.transactionSearch.isLoadingMore,
432
+ },
433
+ accrualSearch: {
434
+ query: matchModal.accrualSearch.query,
435
+ results: uniqueMatchModalAccruals(pinnedAccruals, searchedAccruals),
436
+ fetchState: {
437
+ fetchState: matchModal.accrualSearch.fetchState,
438
+ error: matchModal.accrualSearch.error,
439
+ },
440
+ nextPageToken: matchModal.accrualSearch.nextPageToken,
441
+ isLoadingMore: matchModal.accrualSearch.isLoadingMore,
442
+ },
443
+ },
444
+ };
445
+ }
@@ -381,6 +381,10 @@ 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.
385
+ if (pageTab === 'actual_matching') {
386
+ return;
387
+ }
384
388
  const periodTabKey = monthYearPeriodId != null
385
389
  ? (0, jeSchedulesViewState_1.getJESchedulePeriodTabKey)(pageTab, monthYearPeriodId)
386
390
  : null;