@zeniai/client-epic-state 5.2.35-beta0MM → 5.2.35-beta2MM
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/entity/actualMatching/actualMatchingPayload.js +6 -4
- package/lib/esm/entity/actualMatching/actualMatchingPayload.js +6 -4
- package/lib/esm/index.js +2 -2
- package/lib/esm/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingEpic.js +2 -4
- package/lib/esm/view/expenseAutomationView/epics/actualMatching/matchAndReverseAccrualsEpic.js +2 -3
- package/lib/esm/view/expenseAutomationView/epics/actualMatching/searchMatchAccrualsEpic.js +6 -2
- package/lib/esm/view/expenseAutomationView/epics/actualMatching/searchMatchTransactionsEpic.js +6 -2
- package/lib/esm/view/expenseAutomationView/epics/actualMatching/undoReversalAccrualsEpic.js +2 -3
- package/lib/esm/view/expenseAutomationView/expenseAutomationViewSelector.js +1 -0
- package/lib/esm/view/expenseAutomationView/expenseAutomationViewState.js +4 -2
- package/lib/esm/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +13 -6
- package/lib/esm/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +83 -36
- package/lib/esm/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +1 -0
- package/lib/esm/view/expenseAutomationView/types/jeSchedulesViewState.js +3 -0
- package/lib/index.d.ts +2 -2
- package/lib/index.js +49 -48
- package/lib/view/expenseAutomationView/epics/actualMatching/fetchActualMatchingEpic.js +2 -4
- package/lib/view/expenseAutomationView/epics/actualMatching/matchAndReverseAccrualsEpic.js +2 -3
- package/lib/view/expenseAutomationView/epics/actualMatching/searchMatchAccrualsEpic.d.ts +2 -1
- package/lib/view/expenseAutomationView/epics/actualMatching/searchMatchAccrualsEpic.js +5 -1
- package/lib/view/expenseAutomationView/epics/actualMatching/searchMatchTransactionsEpic.js +5 -1
- package/lib/view/expenseAutomationView/epics/actualMatching/undoReversalAccrualsEpic.js +2 -3
- package/lib/view/expenseAutomationView/expenseAutomationViewSelector.js +1 -0
- package/lib/view/expenseAutomationView/expenseAutomationViewState.d.ts +2 -1
- package/lib/view/expenseAutomationView/expenseAutomationViewState.js +6 -3
- package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.d.ts +4 -2
- package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +13 -6
- package/lib/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.d.ts +10 -8
- package/lib/view/expenseAutomationView/selectorTypes/expenseAutomationViewSelectorTypes.d.ts +9 -0
- package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.d.ts +4 -1
- package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +84 -35
- package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +1 -0
- package/lib/view/expenseAutomationView/types/jeSchedulesViewState.js +3 -0
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ const rxjs_1 = require("rxjs");
|
|
|
5
5
|
const operators_1 = require("rxjs/operators");
|
|
6
6
|
const actualMatchingSelector_1 = require("../../../../entity/actualMatching/actualMatchingSelector");
|
|
7
7
|
const responsePayload_1 = require("../../../../responsePayload");
|
|
8
|
+
const rootActions_1 = require("../../../../rootActions");
|
|
8
9
|
const actualMatchingPayload_1 = require("../../payload/actualMatchingPayload");
|
|
9
10
|
const actualMatchingViewReducer_1 = require("../../reducers/actualMatchingViewReducer");
|
|
10
11
|
const actualMatchingViewSelector_1 = require("../../selectors/actualMatchingViewSelector");
|
|
@@ -70,7 +71,10 @@ const searchMatchTransactionsEpic = (actions$, state$, zeniAPI) => {
|
|
|
70
71
|
}), (0, operators_1.catchError)((error) => (0, rxjs_1.of)((0, actualMatchingViewReducer_1.searchMatchTransactionsFailure)({
|
|
71
72
|
error: (0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', 'Search match transactions REST API call errored out' +
|
|
72
73
|
JSON.stringify(error)),
|
|
73
|
-
})))
|
|
74
|
+
}))),
|
|
75
|
+
// On the HTTP stream only. Wrapping `merge(openModal$, search$)`
|
|
76
|
+
// would complete the epic on the first `clearAll` and drop later searches.
|
|
77
|
+
(0, operators_1.takeUntil)(actions$.pipe((0, operators_1.filter)(rootActions_1.clearAll.match))));
|
|
74
78
|
}));
|
|
75
79
|
return (0, rxjs_1.merge)(openModal$, search$);
|
|
76
80
|
};
|
|
@@ -4,14 +4,13 @@ exports.undoReversalAccrualsEpic = void 0;
|
|
|
4
4
|
const rxjs_1 = require("rxjs");
|
|
5
5
|
const operators_1 = require("rxjs/operators");
|
|
6
6
|
const timePeriod_1 = require("../../../../commonStateTypes/timePeriod");
|
|
7
|
-
const tenantSelector_1 = require("../../../../entity/tenant/tenantSelector");
|
|
8
7
|
const responsePayload_1 = require("../../../../responsePayload");
|
|
9
8
|
const rootActions_1 = require("../../../../rootActions");
|
|
10
9
|
const actualMatchingPayload_1 = require("../../payload/actualMatchingPayload");
|
|
11
10
|
const actualMatchingViewReducer_1 = require("../../reducers/actualMatchingViewReducer");
|
|
11
|
+
const actualMatchingViewSelector_1 = require("../../selectors/actualMatchingViewSelector");
|
|
12
12
|
function refreshActualMatchingList(state) {
|
|
13
|
-
const
|
|
14
|
-
const selectedMonthYear = state.expenseAutomationViewState.selectedPeriodByTenantId[currentTenant?.tenantId ?? ''];
|
|
13
|
+
const selectedMonthYear = (0, actualMatchingViewSelector_1.getSelectedMonthYearForCurrentTenant)(state);
|
|
15
14
|
if (selectedMonthYear == null) {
|
|
16
15
|
return [];
|
|
17
16
|
}
|
|
@@ -46,6 +46,7 @@ function getExpenseAutomationView(state) {
|
|
|
46
46
|
const reconciliation = (0, reconciliationViewSelector_1.getExpenseAutomationReconciliationView)(state);
|
|
47
47
|
const expenseAutomationTransactionsView = (0, transactionCategorizationSelector_1.getExpenseAutomationTransactionView)(state);
|
|
48
48
|
const jeSchedules = (0, jeSchedulesViewSelector_1.getExpenseAutomationJESchedulesView)(state);
|
|
49
|
+
// Top-level sibling of `jeSchedules`: see ExpenseAutomationViewSelector.
|
|
49
50
|
const actualMatching = (0, actualMatchingViewSelector_1.getExpenseAutomationActualMatchingView)(state);
|
|
50
51
|
switch (currentSelectedView) {
|
|
51
52
|
case 'reconciliation':
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { FetchStateAndError, FetchedState, ID } from '../../commonStateTypes/common';
|
|
2
2
|
import { MonthYearPeriod } from '../../commonStateTypes/timePeriod';
|
|
3
|
-
export declare const EXPENSE_AUTOMATION_VIEW_TYPES: readonly ["transaction_categorization", "je_schedules", "reconciliation", "missing_receipts", "flux_analysis", "month_end_insights"];
|
|
4
3
|
export declare const toExpenseAutomationViewType: (v: string) => "transaction_categorization" | "je_schedules" | "reconciliation" | "missing_receipts" | "flux_analysis" | "month_end_insights";
|
|
5
4
|
export type ExpenseAutomationViewType = ReturnType<typeof toExpenseAutomationViewType>;
|
|
5
|
+
/** Unlike `toExpenseAutomationViewType`, unknown strings return `undefined`. */
|
|
6
|
+
export declare const toExpenseAutomationViewTypeStrict: (v: string) => ExpenseAutomationViewType | undefined;
|
|
6
7
|
export interface ExpenseAutomationViewState extends FetchedState {
|
|
7
8
|
currentSelectedView: ExpenseAutomationViewType;
|
|
8
9
|
saveStatus: FetchStateAndError;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.toExpenseAutomationViewTypeStrict = exports.toExpenseAutomationViewType = void 0;
|
|
4
4
|
const stringToUnion_1 = require("../../commonStateTypes/stringToUnion");
|
|
5
|
-
|
|
5
|
+
const EXPENSE_AUTOMATION_VIEW_TYPES = [
|
|
6
6
|
'transaction_categorization',
|
|
7
7
|
'je_schedules',
|
|
8
8
|
'reconciliation',
|
|
@@ -10,5 +10,8 @@ exports.EXPENSE_AUTOMATION_VIEW_TYPES = [
|
|
|
10
10
|
'flux_analysis',
|
|
11
11
|
'month_end_insights',
|
|
12
12
|
];
|
|
13
|
-
const toExpenseAutomationViewType = (v) => (0, stringToUnion_1.stringToUnion)(v,
|
|
13
|
+
const toExpenseAutomationViewType = (v) => (0, stringToUnion_1.stringToUnion)(v, EXPENSE_AUTOMATION_VIEW_TYPES);
|
|
14
14
|
exports.toExpenseAutomationViewType = toExpenseAutomationViewType;
|
|
15
|
+
/** Unlike `toExpenseAutomationViewType`, unknown strings return `undefined`. */
|
|
16
|
+
const toExpenseAutomationViewTypeStrict = (v) => (0, stringToUnion_1.stringToUnionStrict)(v, EXPENSE_AUTOMATION_VIEW_TYPES);
|
|
17
|
+
exports.toExpenseAutomationViewTypeStrict = toExpenseAutomationViewTypeStrict;
|
|
@@ -67,8 +67,10 @@ export declare const clearActualMatchingView: import("@reduxjs/toolkit").ActionC
|
|
|
67
67
|
results: ActualMatchingTransactionSearchResult[];
|
|
68
68
|
}, "expenseAutomationActualMatchingView/searchMatchTransactionsSuccess">, undoReversalAccruals: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[params: UndoReversalAccrualsParams], UndoReversalAccrualsParams, "expenseAutomationActualMatchingView/undoReversalAccruals", never, never>, undoReversalAccrualsFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
69
69
|
status: ZeniAPIStatus;
|
|
70
|
-
}, "expenseAutomationActualMatchingView/undoReversalAccrualsFailure">, undoReversalAccrualsSuccess: import("@reduxjs/toolkit").
|
|
70
|
+
}, "expenseAutomationActualMatchingView/undoReversalAccrualsFailure">, undoReversalAccrualsSuccess: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[payload: {
|
|
71
71
|
results: UndoReversalResult[];
|
|
72
|
-
},
|
|
72
|
+
}], {
|
|
73
|
+
results: UndoReversalResult[];
|
|
74
|
+
}, "expenseAutomationActualMatchingView/undoReversalAccrualsSuccess", never, never>, updateActualMatchingUIState: import("@reduxjs/toolkit").ActionCreatorWithPayload<Partial<ActualMatchingViewUIState>, "expenseAutomationActualMatchingView/updateActualMatchingUIState">;
|
|
73
75
|
declare const _default: import("redux").Reducer<ActualMatchingViewState>;
|
|
74
76
|
export default _default;
|
|
@@ -201,10 +201,13 @@ const expenseAutomationActualMatchingView = (0, toolkit_1.createSlice)({
|
|
|
201
201
|
}
|
|
202
202
|
},
|
|
203
203
|
},
|
|
204
|
+
// Dismiss (Cancel / overlay). Store reset matches `clearMatchNewModal`;
|
|
205
|
+
// the actions stay distinct so confirm-success vs user-dismiss stay observable.
|
|
204
206
|
closeMatchNewModal(draft) {
|
|
205
207
|
draft.matchModal.isOpen = false;
|
|
206
208
|
resetMatchModalSearches(draft);
|
|
207
209
|
},
|
|
210
|
+
// Confirm-success / host reset. Same slice write as close; different action type.
|
|
208
211
|
clearMatchNewModal(draft) {
|
|
209
212
|
draft.matchModal.isOpen = false;
|
|
210
213
|
resetMatchModalSearches(draft);
|
|
@@ -341,12 +344,16 @@ const expenseAutomationActualMatchingView = (0, toolkit_1.createSlice)({
|
|
|
341
344
|
return { payload: params };
|
|
342
345
|
},
|
|
343
346
|
},
|
|
344
|
-
undoReversalAccrualsSuccess
|
|
345
|
-
draft
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
347
|
+
undoReversalAccrualsSuccess: {
|
|
348
|
+
reducer(draft) {
|
|
349
|
+
draft.undoReversalStatus = {
|
|
350
|
+
fetchState: 'Completed',
|
|
351
|
+
error: undefined,
|
|
352
|
+
};
|
|
353
|
+
},
|
|
354
|
+
prepare(payload) {
|
|
355
|
+
return { payload };
|
|
356
|
+
},
|
|
350
357
|
},
|
|
351
358
|
undoReversalAccrualsFailure(draft, action) {
|
|
352
359
|
draft.undoReversalStatus = {
|
|
@@ -26,23 +26,25 @@ export interface ActualMatchingRow extends JEOneTimeAccrual {
|
|
|
26
26
|
transactionType?: string;
|
|
27
27
|
}
|
|
28
28
|
export interface ActualMatchingKpiSummary {
|
|
29
|
-
|
|
29
|
+
actualsReceived: {
|
|
30
30
|
count: number;
|
|
31
|
+
expectedCount: number;
|
|
32
|
+
vendorsPending: number;
|
|
31
33
|
};
|
|
32
34
|
autoReversed: {
|
|
33
|
-
|
|
34
|
-
count: number;
|
|
35
|
-
};
|
|
36
|
-
manual: {
|
|
35
|
+
clearedAmount: number;
|
|
37
36
|
count: number;
|
|
37
|
+
percent: number;
|
|
38
38
|
};
|
|
39
39
|
pendingReview: {
|
|
40
40
|
amount: number;
|
|
41
41
|
count: number;
|
|
42
|
+
estimatedMinutesToClear: number;
|
|
42
43
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
variance: {
|
|
45
|
+
averagePercentPerMatch: number;
|
|
46
|
+
netAmount: number;
|
|
47
|
+
percent: number;
|
|
46
48
|
};
|
|
47
49
|
}
|
|
48
50
|
export interface ActualMatchingMatchModalView {
|
package/lib/view/expenseAutomationView/selectorTypes/expenseAutomationViewSelectorTypes.d.ts
CHANGED
|
@@ -22,6 +22,15 @@ export interface ExpenseAutomationStepDetails {
|
|
|
22
22
|
title: string;
|
|
23
23
|
}
|
|
24
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
|
+
*/
|
|
25
34
|
actualMatching: ExpenseAutomationActualMatchingViewSelector;
|
|
26
35
|
allSteps: ExpenseAutomationStepDetails[];
|
|
27
36
|
completionStatus: CompletionStatusType;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { FetchStateAndError } from '../../../commonStateTypes/common';
|
|
2
|
+
import { MonthYearPeriod } from '../../../commonStateTypes/timePeriod';
|
|
2
3
|
import { ActualMatchingMatch } from '../../../entity/actualMatching/actualMatchingState';
|
|
3
4
|
import { ActualMatchingMatchId } from '../../../entity/actualMatching/actualMatchingTypes';
|
|
4
5
|
import { JEOneTimeAccrual } from '../../../entity/jeSchedules/jeSchedulesSelector';
|
|
5
6
|
import { JEOneTimeAccrualMatchSuggestion } from '../../../entity/jeSchedules/jeSchedulesState';
|
|
6
7
|
import { RootState } from '../../../rootStateTypes';
|
|
7
8
|
import { ZeniDate } from '../../../zeniDayJS';
|
|
8
|
-
import { ActualMatchingRow, ActualMatchingRowKind, ExpenseAutomationActualMatchingViewSelector } from '../selectorTypes/actualMatchingViewSelectorTypes';
|
|
9
|
+
import { ActualMatchingKpiSummary, ActualMatchingRow, ActualMatchingRowKind, ExpenseAutomationActualMatchingViewSelector } from '../selectorTypes/actualMatchingViewSelectorTypes';
|
|
10
|
+
export declare function getSelectedMonthYearForCurrentTenant(state: RootState): MonthYearPeriod | undefined;
|
|
9
11
|
export declare function parseActualMatchingMatchId(matchId: string): {
|
|
10
12
|
transactionIntegrationId: string;
|
|
11
13
|
transactionType: string;
|
|
@@ -19,6 +21,7 @@ export declare function isReversedMatchKind(kind: ActualMatchingRowKind): boolea
|
|
|
19
21
|
export declare function getClearedMatchedAmount(accrual: JEOneTimeAccrual): number | undefined;
|
|
20
22
|
export declare function getActualMatchingAmount(row: ActualMatchingRow): number;
|
|
21
23
|
export declare function getActualMatchingReversalDate(accrual: JEOneTimeAccrual): ZeniDate | undefined;
|
|
24
|
+
export declare function buildActualMatchingKpiSummary(rows: ActualMatchingRow[]): ActualMatchingKpiSummary;
|
|
22
25
|
export interface ExpenseAutomationActualMatchingMatchView {
|
|
23
26
|
detailFetchState: FetchStateAndError;
|
|
24
27
|
refreshStatus: FetchStateAndError;
|
|
@@ -3,6 +3,7 @@ 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;
|
|
6
7
|
exports.parseActualMatchingMatchId = parseActualMatchingMatchId;
|
|
7
8
|
exports.isAutoReversedAccrual = isAutoReversedAccrual;
|
|
8
9
|
exports.isManuallyReversedAccrual = isManuallyReversedAccrual;
|
|
@@ -11,6 +12,7 @@ exports.isReversedMatchKind = isReversedMatchKind;
|
|
|
11
12
|
exports.getClearedMatchedAmount = getClearedMatchedAmount;
|
|
12
13
|
exports.getActualMatchingAmount = getActualMatchingAmount;
|
|
13
14
|
exports.getActualMatchingReversalDate = getActualMatchingReversalDate;
|
|
15
|
+
exports.buildActualMatchingKpiSummary = buildActualMatchingKpiSummary;
|
|
14
16
|
exports.getExpenseAutomationActualMatchingMatchView = getExpenseAutomationActualMatchingMatchView;
|
|
15
17
|
exports.getExpenseAutomationActualMatchingView = getExpenseAutomationActualMatchingView;
|
|
16
18
|
const orderBy_1 = __importDefault(require("lodash/orderBy"));
|
|
@@ -19,6 +21,13 @@ const actualMatchingSelector_1 = require("../../../entity/actualMatching/actualM
|
|
|
19
21
|
const jeScheduleHelper_1 = require("../../../entity/jeSchedules/jeScheduleHelper");
|
|
20
22
|
const jeSchedulesSelector_1 = require("../../../entity/jeSchedules/jeSchedulesSelector");
|
|
21
23
|
const tenantSelector_1 = require("../../../entity/tenant/tenantSelector");
|
|
24
|
+
function getSelectedMonthYearForCurrentTenant(state) {
|
|
25
|
+
const tenantId = (0, tenantSelector_1.getCurrentTenant)(state)?.tenantId;
|
|
26
|
+
if (tenantId == null) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
return state.expenseAutomationViewState.selectedPeriodByTenantId[tenantId];
|
|
30
|
+
}
|
|
22
31
|
function toMatchModalAccrual(member) {
|
|
23
32
|
if ('jeOneTimeAccrualKey' in member && member.jeOneTimeAccrualKey != null) {
|
|
24
33
|
return member;
|
|
@@ -179,18 +188,82 @@ function sortActualMatchingRows(rows, uiState) {
|
|
|
179
188
|
const lodashOrder = uiState.sortOrder === 'ascending' ? 'asc' : 'desc';
|
|
180
189
|
return (0, orderBy_1.default)(rows, [(row) => getActualMatchingSortValue(row, uiState.sortKey)], [lodashOrder]);
|
|
181
190
|
}
|
|
191
|
+
const ESTIMATED_MINUTES_PER_PENDING_MATCH = 4;
|
|
182
192
|
function emptyKpiSummary() {
|
|
183
193
|
return {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
manual: { count: 0 },
|
|
194
|
+
actualsReceived: { count: 0, expectedCount: 0, vendorsPending: 0 },
|
|
195
|
+
autoReversed: { count: 0, percent: 0, clearedAmount: 0 },
|
|
196
|
+
pendingReview: { count: 0, amount: 0, estimatedMinutesToClear: 0 },
|
|
197
|
+
variance: { percent: 0, averagePercentPerMatch: 0, netAmount: 0 },
|
|
189
198
|
};
|
|
190
199
|
}
|
|
191
200
|
function sumRowAmounts(rows) {
|
|
192
201
|
return rows.reduce((sum, row) => sum + getActualMatchingAmount(row), 0);
|
|
193
202
|
}
|
|
203
|
+
function uniqueVendorCount(rows) {
|
|
204
|
+
const ids = new Set();
|
|
205
|
+
rows.forEach((row) => {
|
|
206
|
+
if (row.vendorId != null && row.vendorId !== '') {
|
|
207
|
+
ids.add(row.vendorId);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (row.vendorName !== '') {
|
|
211
|
+
ids.add(row.vendorName);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
return ids.size;
|
|
215
|
+
}
|
|
216
|
+
function rowVariancePercent(row) {
|
|
217
|
+
const accrued = row.accruedAmount?.amount ?? row.estimatedAmount.amount;
|
|
218
|
+
const actual = row.actualAmount?.amount;
|
|
219
|
+
if (actual == null || accrued === 0) {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
return ((actual - accrued) / accrued) * 100;
|
|
223
|
+
}
|
|
224
|
+
function buildActualMatchingKpiSummary(rows) {
|
|
225
|
+
if (rows.length === 0) {
|
|
226
|
+
return emptyKpiSummary();
|
|
227
|
+
}
|
|
228
|
+
const autoReversed = rows.filter((row) => row.kind === 'auto_reversed');
|
|
229
|
+
const pendingReview = rows.filter((row) => row.kind === 'pending_review');
|
|
230
|
+
const receivedCount = rows.length;
|
|
231
|
+
const rowsWithActual = rows.filter((row) => row.actualAmount?.amount != null);
|
|
232
|
+
const accruedTotal = rowsWithActual.reduce((sum, row) => sum + (row.accruedAmount?.amount ?? row.estimatedAmount.amount), 0);
|
|
233
|
+
const actualTotal = rowsWithActual.reduce((sum, row) => sum + (row.actualAmount?.amount ?? 0), 0);
|
|
234
|
+
const variancePercents = rows
|
|
235
|
+
.map(rowVariancePercent)
|
|
236
|
+
.filter((percent) => percent != null);
|
|
237
|
+
return {
|
|
238
|
+
actualsReceived: {
|
|
239
|
+
count: receivedCount,
|
|
240
|
+
// List API does not return still-expected accruals; hide "of N expected"
|
|
241
|
+
// in the strip until expectedCount is greater than received.
|
|
242
|
+
expectedCount: receivedCount,
|
|
243
|
+
vendorsPending: uniqueVendorCount(pendingReview),
|
|
244
|
+
},
|
|
245
|
+
autoReversed: {
|
|
246
|
+
count: autoReversed.length,
|
|
247
|
+
percent: Math.round((autoReversed.length / receivedCount) * 100),
|
|
248
|
+
clearedAmount: sumRowAmounts(autoReversed),
|
|
249
|
+
},
|
|
250
|
+
pendingReview: {
|
|
251
|
+
count: pendingReview.length,
|
|
252
|
+
amount: sumRowAmounts(pendingReview),
|
|
253
|
+
estimatedMinutesToClear: pendingReview.length * ESTIMATED_MINUTES_PER_PENDING_MATCH,
|
|
254
|
+
},
|
|
255
|
+
variance: {
|
|
256
|
+
percent: accruedTotal === 0
|
|
257
|
+
? 0
|
|
258
|
+
: ((actualTotal - accruedTotal) / accruedTotal) * 100,
|
|
259
|
+
averagePercentPerMatch: variancePercents.length === 0
|
|
260
|
+
? 0
|
|
261
|
+
: variancePercents.reduce((sum, percent) => sum + percent, 0) /
|
|
262
|
+
variancePercents.length,
|
|
263
|
+
netAmount: actualTotal - accruedTotal,
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
194
267
|
function getExpenseAutomationActualMatchingMatchView(state, matchId) {
|
|
195
268
|
const view = state.expenseAutomationActualMatchingViewState;
|
|
196
269
|
return {
|
|
@@ -201,44 +274,20 @@ function getExpenseAutomationActualMatchingMatchView(state, matchId) {
|
|
|
201
274
|
};
|
|
202
275
|
}
|
|
203
276
|
function getExpenseAutomationActualMatchingView(state) {
|
|
204
|
-
const { expenseAutomationActualMatchingViewState
|
|
277
|
+
const { expenseAutomationActualMatchingViewState } = state;
|
|
205
278
|
const { matchIdsByPeriod, fetchState, error, refreshStatus, uiState, matchModal, undoReversalStatus, } = expenseAutomationActualMatchingViewState;
|
|
206
|
-
const
|
|
207
|
-
const selectedPeriod = expenseAutomationViewState.selectedPeriodByTenantId[currentTenant?.tenantId ?? ''];
|
|
279
|
+
const selectedPeriod = getSelectedMonthYearForCurrentTenant(state);
|
|
208
280
|
const monthYearPeriodId = selectedPeriod != null ? (0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod) : undefined;
|
|
209
281
|
const matchIds = monthYearPeriodId != null ? matchIdsByPeriod[monthYearPeriodId] : undefined;
|
|
210
|
-
const
|
|
282
|
+
const allRows = (matchIds ?? [])
|
|
211
283
|
.map((matchId) => (0, actualMatchingSelector_1.getActualMatchingMatchById)(state.actualMatchingState, matchId))
|
|
212
284
|
.filter((match) => match != null)
|
|
213
285
|
.filter((match) => match.members.length > 0)
|
|
214
|
-
.map(toActualMatchingRow)
|
|
215
|
-
|
|
286
|
+
.map(toActualMatchingRow);
|
|
287
|
+
const rows = allRows.filter((row) => matchesSearch(row, uiState.searchString));
|
|
216
288
|
const reversed = sortActualMatchingRows(rows.filter((row) => isReversedMatchKind(row.kind)), uiState);
|
|
217
289
|
const pendingReview = sortActualMatchingRows(rows.filter((row) => row.kind === 'pending_review'), uiState);
|
|
218
|
-
const
|
|
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
|
-
};
|
|
290
|
+
const kpiSummary = buildActualMatchingKpiSummary(allRows);
|
|
242
291
|
const searchedAccruals = matchModal.accrualSearch.results
|
|
243
292
|
.map((key) => (0, jeSchedulesSelector_1.getJEOneTimeAccrualByKey)(key, state.jeSchedulesState))
|
|
244
293
|
.filter((accrual) => accrual != null);
|
|
@@ -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
|
}
|
|
@@ -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',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeniai/client-epic-state",
|
|
3
|
-
"version": "5.2.35-
|
|
3
|
+
"version": "5.2.35-beta2MM",
|
|
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",
|