@zeniai/client-epic-state 5.0.71-betaVR3 → 5.0.71
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/epic.d.ts +1 -3
- package/lib/epic.js +1 -3
- package/lib/esm/epic.js +1 -3
- package/lib/esm/index.js +2 -2
- package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js +5 -21
- package/lib/esm/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js +9 -13
- package/lib/esm/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +1 -79
- package/lib/esm/view/expenseAutomationView/selectors/missingReceiptsSelector.js +0 -2
- package/lib/index.d.ts +2 -2
- package/lib/index.js +34 -37
- package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js +5 -21
- package/lib/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.d.ts +5 -11
- package/lib/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js +8 -12
- package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.d.ts +1 -13
- package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +2 -81
- package/lib/view/expenseAutomationView/selectorTypes/missingReceiptsSelectorTypes.d.ts +0 -12
- package/lib/view/expenseAutomationView/selectors/missingReceiptsSelector.js +0 -2
- package/lib/view/expenseAutomationView/types/missingReceiptsViewState.d.ts +0 -19
- package/package.json +2 -2
- package/lib/esm/view/expenseAutomationView/epics/missingReceipts/refreshBatchDetailsForBatchIdEpic.js +0 -52
- package/lib/esm/view/expenseAutomationView/epics/missingReceipts/syncTabsAfterAutomatchEpic.js +0 -53
- package/lib/view/expenseAutomationView/epics/missingReceipts/refreshBatchDetailsForBatchIdEpic.d.ts +0 -18
- package/lib/view/expenseAutomationView/epics/missingReceipts/refreshBatchDetailsForBatchIdEpic.js +0 -56
- package/lib/view/expenseAutomationView/epics/missingReceipts/syncTabsAfterAutomatchEpic.d.ts +0 -28
- package/lib/view/expenseAutomationView/epics/missingReceipts/syncTabsAfterAutomatchEpic.js +0 -57
package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js
CHANGED
|
@@ -35,19 +35,10 @@ function fetchBatchDetailsByIds(batchIds, zeniAPI) {
|
|
|
35
35
|
}
|
|
36
36
|
const fetchMultipleBatchDetailsEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.fetchBulkUploadBatchesSuccess.match), (0, operators_1.mergeMap)((action) => {
|
|
37
37
|
const batchList = action.payload.batchList;
|
|
38
|
-
const
|
|
39
|
-
const { batchDetailsById, batchDetailsRevalidating } = bulkUpload;
|
|
38
|
+
const { batchDetailsById } = state$.value.expenseAutomationMissingReceiptsViewState.bulkUpload;
|
|
40
39
|
/** Only completed list rows may trigger batch-details API; preserve platform order. */
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
* Stale-while-revalidate: when a manual refresh has set `batchDetailsRevalidating`, refresh
|
|
44
|
-
* every completed list row (replacing entries via `storeBatchDetails` as they arrive). On a
|
|
45
|
-
* normal first fetch we still only request rows whose details are missing.
|
|
46
|
-
*/
|
|
47
|
-
const candidateRows = batchDetailsRevalidating === true
|
|
48
|
-
? completedRows
|
|
49
|
-
: completedRows.filter((batch) => batchDetailsById[batch.batchId] == null);
|
|
50
|
-
const completedBatchIds = candidateRows.map((batch) => batch.batchId);
|
|
40
|
+
const completedBatchesMissingDetails = (0, missingReceiptsPayload_1.filterBatchListItemsForBatchDetailsFetch)(batchList).filter((batch) => batchDetailsById[batch.batchId] == null);
|
|
41
|
+
const completedBatchIds = completedBatchesMissingDetails.map((batch) => batch.batchId);
|
|
51
42
|
const batchIdsToFetch = completedBatchIds.slice(0, INITIAL_BATCH_PAGE_SIZE);
|
|
52
43
|
if (batchIdsToFetch.length === 0) {
|
|
53
44
|
/**
|
|
@@ -55,6 +46,7 @@ const fetchMultipleBatchDetailsEpic = (actions$, state$, zeniAPI) => actions$.pi
|
|
|
55
46
|
* `currentBatchId` may not appear in the filtered list but still needs `GET /batches/:id`.
|
|
56
47
|
* Fall back to the single-batch-details epic so `phase` and details can resolve.
|
|
57
48
|
*/
|
|
49
|
+
const bulkUpload = state$.value.expenseAutomationMissingReceiptsViewState.bulkUpload;
|
|
58
50
|
const currentId = bulkUpload.currentBatchId;
|
|
59
51
|
if (currentId != null &&
|
|
60
52
|
bulkUpload.phase === 'matching' &&
|
|
@@ -62,15 +54,7 @@ const fetchMultipleBatchDetailsEpic = (actions$, state$, zeniAPI) => actions$.pi
|
|
|
62
54
|
(0, missingReceiptsPayload_1.shouldFetchBatchDetailsForBatchId)(bulkUpload.batchStatusById, bulkUpload.batchListByPeriod, currentId)) {
|
|
63
55
|
return (0, rxjs_1.of)((0, missingReceiptsViewReducer_1.fetchBulkUploadBatchDetails)());
|
|
64
56
|
}
|
|
65
|
-
|
|
66
|
-
* Close the pagination loop so the Unmatched-tab loading heuristic can settle.
|
|
67
|
-
* `fetchMoreBatchDetailsComplete` also clears `batchDetailsRevalidating` and flips
|
|
68
|
-
* `initialBatchDetailsLoaded` to true. Without this, `batchDetailsPaginationState` stays
|
|
69
|
-
* at `Not-Started` forever after a list refresh that finds zero completed rows (or all
|
|
70
|
-
* already cached) — the direct-open-Unmatched-without-upload path used to render an
|
|
71
|
-
* infinite skeleton.
|
|
72
|
-
*/
|
|
73
|
-
return (0, rxjs_1.of)((0, missingReceiptsViewReducer_1.fetchMoreBatchDetailsComplete)());
|
|
57
|
+
return rxjs_1.EMPTY;
|
|
74
58
|
}
|
|
75
59
|
return (0, rxjs_1.concat)((0, rxjs_1.of)((0, missingReceiptsViewReducer_1.setInitialBatchDetailsLoading)()), fetchBatchDetailsByIds(batchIdsToFetch, zeniAPI), (0, rxjs_1.of)((0, missingReceiptsViewReducer_1.fetchMoreBatchDetailsComplete)()));
|
|
76
60
|
}));
|
package/lib/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.d.ts
CHANGED
|
@@ -4,19 +4,13 @@ import { openSnackbar } from '../../../../entity/snackbar/snackbarReducer';
|
|
|
4
4
|
import { updateTransactions } from '../../../../entity/transaction/transactionReducer';
|
|
5
5
|
import { RootState } from '../../../../reducer';
|
|
6
6
|
import { ZeniAPI } from '../../../../zeniAPI';
|
|
7
|
-
import { bulkUploadAutomatchingTimedOut, bulkUploadReceiptsSuccess, clearBulkUpload, fetchBulkUploadBatchDetails, fetchBulkUploadBatchDetailsSuccess, fetchBulkUploadBatches, pusherBatchStatusUpdate,
|
|
8
|
-
export type ActionType = ReturnType<typeof bulkUploadReceiptsSuccess> | ReturnType<typeof restoreBulkUploadMatchingState> | ReturnType<typeof pusherBatchStatusUpdate> | ReturnType<typeof fetchBulkUploadBatchDetails> | ReturnType<typeof fetchBulkUploadBatchDetailsSuccess> | ReturnType<typeof fetchBulkUploadBatches> | ReturnType<typeof
|
|
7
|
+
import { bulkUploadAutomatchingTimedOut, bulkUploadReceiptsSuccess, clearBulkUpload, fetchBulkUploadBatchDetails, fetchBulkUploadBatchDetailsSuccess, fetchBulkUploadBatches, pusherBatchStatusUpdate, restoreBulkUploadMatchingState } from '../../reducers/missingReceiptsViewReducer';
|
|
8
|
+
export type ActionType = ReturnType<typeof bulkUploadReceiptsSuccess> | ReturnType<typeof restoreBulkUploadMatchingState> | ReturnType<typeof pusherBatchStatusUpdate> | ReturnType<typeof fetchBulkUploadBatchDetails> | ReturnType<typeof fetchBulkUploadBatchDetailsSuccess> | ReturnType<typeof fetchBulkUploadBatches> | ReturnType<typeof updateTransactions> | ReturnType<typeof clearBulkUpload> | ReturnType<typeof openSnackbar> | ReturnType<typeof bulkUploadAutomatchingTimedOut>;
|
|
9
9
|
/**
|
|
10
|
-
* On Pusher batch completion: refresh
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* on screen and the just-completed row replaces in place via `storeBatchDetails`. The post-match
|
|
14
|
-
* tab decision (Unmatched vs Completed) is made client-side in `ExpenseAutomationPage` once fresh
|
|
10
|
+
* On Pusher batch completion: refresh batch list (then fetchMultipleBatchDetailsEpic runs).
|
|
11
|
+
* Debounced to reduce duplicate refreshes. The post-match tab decision
|
|
12
|
+
* (Unmatched vs Completed) is made client-side in `ExpenseAutomationPage` once fresh
|
|
15
13
|
* batch details land -- do not dispatch `requestMissingReceiptsTabNavigation` here.
|
|
16
|
-
*
|
|
17
|
-
* Pusher resend storms (same batch completing twice within the dedupe window) are throttled in
|
|
18
|
-
* `refreshBatchDetailsForBatchIdEpic` via `lastBatchDetailRefreshAtById`, so this epic stays
|
|
19
|
-
* stateless and just forwards the event.
|
|
20
14
|
*/
|
|
21
15
|
export declare const pusherBatchStatusCompletionEpic: (actions$: ActionsObservable<ActionType>) => Observable<ActionType>;
|
|
22
16
|
/**
|
package/lib/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js
CHANGED
|
@@ -15,20 +15,16 @@ const FALLBACK_FIRST_DELAY_MS = 30000;
|
|
|
15
15
|
const RESTORE_FIRST_DELAY_MS = 5000;
|
|
16
16
|
const FALLBACK_POLL_INTERVAL_MS = 10000;
|
|
17
17
|
/**
|
|
18
|
-
* On Pusher batch completion: refresh
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* on screen and the just-completed row replaces in place via `storeBatchDetails`. The post-match
|
|
22
|
-
* tab decision (Unmatched vs Completed) is made client-side in `ExpenseAutomationPage` once fresh
|
|
18
|
+
* On Pusher batch completion: refresh batch list (then fetchMultipleBatchDetailsEpic runs).
|
|
19
|
+
* Debounced to reduce duplicate refreshes. The post-match tab decision
|
|
20
|
+
* (Unmatched vs Completed) is made client-side in `ExpenseAutomationPage` once fresh
|
|
23
21
|
* batch details land -- do not dispatch `requestMissingReceiptsTabNavigation` here.
|
|
24
|
-
*
|
|
25
|
-
* Pusher resend storms (same batch completing twice within the dedupe window) are throttled in
|
|
26
|
-
* `refreshBatchDetailsForBatchIdEpic` via `lastBatchDetailRefreshAtById`, so this epic stays
|
|
27
|
-
* stateless and just forwards the event.
|
|
28
22
|
*/
|
|
29
|
-
const pusherBatchStatusCompletionEpic = (actions$) => actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.pusherBatchStatusUpdate.match), (0, operators_1.filter)((action) => action.payload.status === 'completed'), (0, operators_1.debounceTime)(300), (0, operators_1.mergeMap)((
|
|
30
|
-
(0, missingReceiptsViewReducer_1.fetchBulkUploadBatches)({
|
|
31
|
-
|
|
23
|
+
const pusherBatchStatusCompletionEpic = (actions$) => actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.pusherBatchStatusUpdate.match), (0, operators_1.filter)((action) => action.payload.status === 'completed'), (0, operators_1.debounceTime)(300), (0, operators_1.mergeMap)(() => (0, rxjs_1.from)([
|
|
24
|
+
(0, missingReceiptsViewReducer_1.fetchBulkUploadBatches)({
|
|
25
|
+
cacheOverride: true,
|
|
26
|
+
invalidateBatchDetailsCache: true,
|
|
27
|
+
}),
|
|
32
28
|
])));
|
|
33
29
|
exports.pusherBatchStatusCompletionEpic = pusherBatchStatusCompletionEpic;
|
|
34
30
|
/**
|
|
@@ -19,15 +19,6 @@ export declare const fetchBulkUploadBatchDetails: import("@reduxjs/toolkit").Act
|
|
|
19
19
|
* after a hard refresh. The epic owns the list + details API calls end-to-end.
|
|
20
20
|
*/
|
|
21
21
|
export declare const restoreBulkUploadAutomatchingOnMount: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"expenseAutomationMissingReceiptsView/restoreBulkUploadAutomatchingOnMount">;
|
|
22
|
-
/**
|
|
23
|
-
* Epic trigger only. Targeted stale-while-revalidate refresh for a single batch's details — used
|
|
24
|
-
* by `pusherBatchStatusCompletionEpic` so a Pusher 'completed' event re-fetches only that one
|
|
25
|
-
* batch instead of wiping the entire `batchDetailsById` cache. The epic dedupes via
|
|
26
|
-
* `lastBatchDetailRefreshAtById` so resend storms don't trigger waves of API calls.
|
|
27
|
-
*/
|
|
28
|
-
export declare const refreshBatchDetailsForBatchId: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
29
|
-
batchId: ID;
|
|
30
|
-
}, string>;
|
|
31
22
|
export declare const fetchMissingReceipts: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[period: TimePeriod, minimumTransactionAmount: Amount, cacheOverride?: any, refreshViewInBackground?: any], {
|
|
32
23
|
period: TimePeriod;
|
|
33
24
|
minimumTransactionAmount: Amount;
|
|
@@ -82,10 +73,7 @@ export declare const fetchMissingReceipts: import("@reduxjs/toolkit").ActionCrea
|
|
|
82
73
|
}, "expenseAutomationMissingReceiptsView/fetchBulkUploadBatchesSuccess">, fetchBulkUploadBatchesFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
83
74
|
error: ZeniAPIStatus;
|
|
84
75
|
selectedPeriod: MonthYearPeriod;
|
|
85
|
-
}, "expenseAutomationMissingReceiptsView/fetchBulkUploadBatchesFailure">,
|
|
86
|
-
batchId: ID;
|
|
87
|
-
timestamp: number;
|
|
88
|
-
}, "expenseAutomationMissingReceiptsView/markBatchDetailRefreshAttempted">, confirmBulkUploadMatch: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[batchId: string, attachmentId: string, transactionId: string, transactionType: string, fileName: string], {
|
|
76
|
+
}, "expenseAutomationMissingReceiptsView/fetchBulkUploadBatchesFailure">, confirmBulkUploadMatch: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[batchId: string, attachmentId: string, transactionId: string, transactionType: string, fileName: string], {
|
|
89
77
|
batchId: string;
|
|
90
78
|
attachmentId: string;
|
|
91
79
|
transactionId: string;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var _a;
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.fetchCompletedTransactions = exports.acknowledgeBulkUploadConfirmMatchComplete = exports.clearManualSearchResults = exports.searchTransactionsForManualMatchFailure = exports.searchTransactionsForManualMatchSuccess = exports.searchTransactionsForManualMatch = exports.clearBulkUpload = exports.setBulkUploadSortConfig = exports.setBulkUploadCompletedSubTab = exports.setBulkUploadResultsTab = exports.confirmBulkUploadMatchFailure = exports.confirmBulkUploadMatchSuccess = exports.confirmBulkUploadMatch = exports.
|
|
5
|
-
exports.fetchCompletedTransactionsFailure = exports.fetchCompletedTransactionsSuccess = void 0;
|
|
4
|
+
exports.fetchCompletedTransactionsFailure = exports.fetchCompletedTransactionsSuccess = exports.fetchCompletedTransactions = exports.acknowledgeBulkUploadConfirmMatchComplete = exports.clearManualSearchResults = exports.searchTransactionsForManualMatchFailure = exports.searchTransactionsForManualMatchSuccess = exports.searchTransactionsForManualMatch = exports.clearBulkUpload = exports.setBulkUploadSortConfig = exports.setBulkUploadCompletedSubTab = exports.setBulkUploadResultsTab = exports.confirmBulkUploadMatchFailure = exports.confirmBulkUploadMatchSuccess = exports.confirmBulkUploadMatch = exports.fetchBulkUploadBatchesFailure = exports.fetchBulkUploadBatchesSuccess = exports.fetchBulkUploadBatches = exports.fetchMoreBatchDetailsFailure = exports.fetchMoreBatchDetailsComplete = exports.fetchMoreBatchDetails = exports.setInitialBatchDetailsLoading = exports.batchDetailFetchFailed = exports.storeBatchDetails = exports.fetchBulkUploadBatchDetailsFailure = exports.fetchBulkUploadBatchDetailsSuccess = exports.clearMissingReceiptsTabNavigation = exports.requestMissingReceiptsTabNavigation = exports.pusherBatchStatusUpdate = exports.bulkUploadAutomatchingTimedOut = exports.restoreBulkUploadMatchingState = exports.bulkUploadReceiptsFailure = exports.bulkUploadReceiptsSuccess = exports.setBulkUploadUploadedFileCount = exports.updateBulkUploadProgress = exports.bulkUploadReceipts = exports.clearExpenseAutomationMissingReceiptsView = exports.markMissingReceiptAsDone = exports.updateMissingReceiptsUIState = exports.updateMissingReceiptUploadState = exports.uploadMissingReceiptSuccess = exports.fetchMissingReceiptsFailure = exports.fetchMissingReceiptsSuccess = exports.fetchMissingReceipts = exports.restoreBulkUploadAutomatchingOnMount = exports.fetchBulkUploadBatchDetails = exports.initialState = exports.initialBulkUploadState = exports.getCompletedTransactionsCacheKey = void 0;
|
|
6
5
|
const toolkit_1 = require("@reduxjs/toolkit");
|
|
7
6
|
const timePeriod_1 = require("../../../commonStateTypes/timePeriod");
|
|
8
7
|
const completedSubTab_1 = require("../types/completedSubTab");
|
|
@@ -16,7 +15,6 @@ exports.getCompletedTransactionsCacheKey = getCompletedTransactionsCacheKey;
|
|
|
16
15
|
exports.initialBulkUploadState = {
|
|
17
16
|
batchDetailsById: {},
|
|
18
17
|
batchDetailsPaginationState: { fetchState: 'Not-Started', error: undefined },
|
|
19
|
-
batchDetailsRevalidating: false,
|
|
20
18
|
batchListByPeriod: {},
|
|
21
19
|
batchListFetchState: { fetchState: 'Not-Started', error: undefined },
|
|
22
20
|
batchStatusById: {},
|
|
@@ -26,8 +24,6 @@ exports.initialBulkUploadState = {
|
|
|
26
24
|
currentBatchId: undefined,
|
|
27
25
|
currentResultsTab: 'unmatched',
|
|
28
26
|
failedBatchDetailIds: [],
|
|
29
|
-
initialBatchDetailsLoaded: false,
|
|
30
|
-
lastBatchDetailRefreshAtById: {},
|
|
31
27
|
manualSearchUiResetKey: 0,
|
|
32
28
|
manualSearch: {
|
|
33
29
|
fetchState: { fetchState: 'Not-Started', error: undefined },
|
|
@@ -74,13 +70,6 @@ exports.fetchBulkUploadBatchDetails = (0, toolkit_1.createAction)('expenseAutoma
|
|
|
74
70
|
* after a hard refresh. The epic owns the list + details API calls end-to-end.
|
|
75
71
|
*/
|
|
76
72
|
exports.restoreBulkUploadAutomatchingOnMount = (0, toolkit_1.createAction)('expenseAutomationMissingReceiptsView/restoreBulkUploadAutomatchingOnMount');
|
|
77
|
-
/**
|
|
78
|
-
* Epic trigger only. Targeted stale-while-revalidate refresh for a single batch's details — used
|
|
79
|
-
* by `pusherBatchStatusCompletionEpic` so a Pusher 'completed' event re-fetches only that one
|
|
80
|
-
* batch instead of wiping the entire `batchDetailsById` cache. The epic dedupes via
|
|
81
|
-
* `lastBatchDetailRefreshAtById` so resend storms don't trigger waves of API calls.
|
|
82
|
-
*/
|
|
83
|
-
exports.refreshBatchDetailsForBatchId = (0, toolkit_1.createAction)('expenseAutomationMissingReceiptsView/refreshBatchDetailsForBatchId');
|
|
84
73
|
const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
|
|
85
74
|
name: 'expenseAutomationMissingReceiptsView',
|
|
86
75
|
initialState: exports.initialState,
|
|
@@ -267,16 +256,6 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
|
|
|
267
256
|
const details = action.payload;
|
|
268
257
|
draft.bulkUpload.batchDetailsById[details.batchId] = details;
|
|
269
258
|
draft.bulkUpload.phase = 'completed';
|
|
270
|
-
draft.bulkUpload.initialBatchDetailsLoaded = true;
|
|
271
|
-
/**
|
|
272
|
-
* A successful detail load supersedes any previous failure for this batch — without this,
|
|
273
|
-
* `getPrimaryBatchIdFromBatchListOrder` (and pagination) would keep skipping the batch
|
|
274
|
-
* across the recovery, hiding now-valid Unmatched-tab work.
|
|
275
|
-
*/
|
|
276
|
-
const failedIndex = draft.bulkUpload.failedBatchDetailIds.indexOf(details.batchId);
|
|
277
|
-
if (failedIndex >= 0) {
|
|
278
|
-
draft.bulkUpload.failedBatchDetailIds.splice(failedIndex, 1);
|
|
279
|
-
}
|
|
280
259
|
},
|
|
281
260
|
fetchBulkUploadBatchDetailsFailure: {
|
|
282
261
|
reducer(draft) {
|
|
@@ -298,19 +277,6 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
|
|
|
298
277
|
draft.bulkUpload.currentBatchId === details.batchId) {
|
|
299
278
|
draft.bulkUpload.phase = 'completed';
|
|
300
279
|
}
|
|
301
|
-
/** Stale-while-revalidate: any successful detail load means we have rendered data. */
|
|
302
|
-
draft.bulkUpload.initialBatchDetailsLoaded = true;
|
|
303
|
-
/**
|
|
304
|
-
* Stale-while-revalidate recovery: if this batch had previously failed, a successful
|
|
305
|
-
* targeted refresh (Pusher `completed` → `refreshBatchDetailsForBatchIdEpic`) or
|
|
306
|
-
* multi-batch revalidation (manual refresh) supersedes the failure. Selectors that gate
|
|
307
|
-
* on `failedBatchDetailIds` (primary-batch resolution, `hasMoreBatchDetails`) must see the
|
|
308
|
-
* batch as recovered so it can show fresh Unmatched work.
|
|
309
|
-
*/
|
|
310
|
-
const failedIndex = draft.bulkUpload.failedBatchDetailIds.indexOf(details.batchId);
|
|
311
|
-
if (failedIndex >= 0) {
|
|
312
|
-
draft.bulkUpload.failedBatchDetailIds.splice(failedIndex, 1);
|
|
313
|
-
}
|
|
314
280
|
},
|
|
315
281
|
batchDetailFetchFailed(draft, action) {
|
|
316
282
|
const { batchId } = action.payload;
|
|
@@ -339,14 +305,6 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
|
|
|
339
305
|
fetchState: 'Completed',
|
|
340
306
|
error: undefined,
|
|
341
307
|
};
|
|
342
|
-
/**
|
|
343
|
-
* Stale-while-revalidate: a completed pagination round means the initial page (or a
|
|
344
|
-
* subsequent revalidation) has fully resolved. Once `initialBatchDetailsLoaded` flips to
|
|
345
|
-
* `true` it stays `true` for the session — only `clearBulkUpload` /
|
|
346
|
-
* `clearBulkUploadBatchDetailsForScopeChange` can reset it.
|
|
347
|
-
*/
|
|
348
|
-
draft.bulkUpload.initialBatchDetailsLoaded = true;
|
|
349
|
-
draft.bulkUpload.batchDetailsRevalidating = false;
|
|
350
308
|
const currentId = draft.bulkUpload.currentBatchId;
|
|
351
309
|
const currentDetails = currentId != null
|
|
352
310
|
? draft.bulkUpload.batchDetailsById[currentId]
|
|
@@ -362,24 +320,12 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
|
|
|
362
320
|
fetchState: 'Error',
|
|
363
321
|
error: action.payload.error,
|
|
364
322
|
};
|
|
365
|
-
draft.bulkUpload.batchDetailsRevalidating = false;
|
|
366
323
|
},
|
|
367
324
|
fetchBulkUploadBatches: {
|
|
368
325
|
reducer(draft, action) {
|
|
369
|
-
/**
|
|
370
|
-
* Manual refresh button (and any other caller passing `invalidateBatchDetailsCache: true`):
|
|
371
|
-
* wipe the details cache, failed-id list, and pagination state so the Unmatched tab drops
|
|
372
|
-
* its existing rows and re-renders the initial-load skeleton while the next list +
|
|
373
|
-
* details fetches resolve. The post-success epic (`fetchMultipleBatchDetailsEpic`) then
|
|
374
|
-
* re-runs the details chain from a clean slate.
|
|
375
|
-
*/
|
|
376
326
|
if (action.payload.invalidateBatchDetailsCache === true) {
|
|
377
327
|
draft.bulkUpload.batchDetailsById = {};
|
|
378
328
|
draft.bulkUpload.failedBatchDetailIds = [];
|
|
379
|
-
draft.bulkUpload.batchDetailsPaginationState = {
|
|
380
|
-
fetchState: 'Not-Started',
|
|
381
|
-
error: undefined,
|
|
382
|
-
};
|
|
383
329
|
}
|
|
384
330
|
draft.bulkUpload.batchListFetchState = {
|
|
385
331
|
fetchState: 'In-Progress',
|
|
@@ -396,31 +342,6 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
|
|
|
396
342
|
};
|
|
397
343
|
},
|
|
398
344
|
},
|
|
399
|
-
/**
|
|
400
|
-
* Tenant/period scope change — the new scope's data is genuinely unknown so the existing
|
|
401
|
-
* details cache and `initialBatchDetailsLoaded` flag must be reset (the Unmatched skeleton
|
|
402
|
-
* is acceptable on scope change). Distinct from a stale-while-revalidate refresh.
|
|
403
|
-
*/
|
|
404
|
-
clearBulkUploadBatchDetailsForScopeChange(draft) {
|
|
405
|
-
draft.bulkUpload.batchDetailsById = {};
|
|
406
|
-
draft.bulkUpload.failedBatchDetailIds = [];
|
|
407
|
-
draft.bulkUpload.batchDetailsPaginationState = {
|
|
408
|
-
fetchState: 'Not-Started',
|
|
409
|
-
error: undefined,
|
|
410
|
-
};
|
|
411
|
-
draft.bulkUpload.batchDetailsRevalidating = false;
|
|
412
|
-
draft.bulkUpload.initialBatchDetailsLoaded = false;
|
|
413
|
-
draft.bulkUpload.lastBatchDetailRefreshAtById = {};
|
|
414
|
-
},
|
|
415
|
-
/**
|
|
416
|
-
* Records the last time we issued a targeted refresh for `batchId`. Set by
|
|
417
|
-
* `refreshBatchDetailsForBatchIdEpic` before each API call so subsequent Pusher resends in the
|
|
418
|
-
* dedupe window are skipped.
|
|
419
|
-
*/
|
|
420
|
-
markBatchDetailRefreshAttempted(draft, action) {
|
|
421
|
-
draft.bulkUpload.lastBatchDetailRefreshAtById[action.payload.batchId] =
|
|
422
|
-
action.payload.timestamp;
|
|
423
|
-
},
|
|
424
345
|
fetchBulkUploadBatchesSuccess(draft, action) {
|
|
425
346
|
const periodId = (0, timePeriod_1.toMonthYearPeriodId)(action.payload.selectedPeriod);
|
|
426
347
|
draft.bulkUpload.batchListByPeriod[periodId] = action.payload.batchList;
|
|
@@ -663,7 +584,7 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
|
|
|
663
584
|
});
|
|
664
585
|
_a = expenseAutomationMissingReceiptsView.actions, exports.fetchMissingReceipts = _a.fetchMissingReceipts, exports.fetchMissingReceiptsSuccess = _a.fetchMissingReceiptsSuccess, exports.fetchMissingReceiptsFailure = _a.fetchMissingReceiptsFailure, exports.uploadMissingReceiptSuccess = _a.uploadMissingReceiptSuccess, exports.updateMissingReceiptUploadState = _a.updateMissingReceiptUploadState, exports.updateMissingReceiptsUIState = _a.updateMissingReceiptsUIState, exports.markMissingReceiptAsDone = _a.markMissingReceiptAsDone, exports.clearExpenseAutomationMissingReceiptsView = _a.clearExpenseAutomationMissingReceiptsView,
|
|
665
586
|
// Bulk Upload
|
|
666
|
-
exports.bulkUploadReceipts = _a.bulkUploadReceipts, exports.updateBulkUploadProgress = _a.updateBulkUploadProgress, exports.setBulkUploadUploadedFileCount = _a.setBulkUploadUploadedFileCount, exports.bulkUploadReceiptsSuccess = _a.bulkUploadReceiptsSuccess, exports.bulkUploadReceiptsFailure = _a.bulkUploadReceiptsFailure, exports.restoreBulkUploadMatchingState = _a.restoreBulkUploadMatchingState, exports.bulkUploadAutomatchingTimedOut = _a.bulkUploadAutomatchingTimedOut, exports.pusherBatchStatusUpdate = _a.pusherBatchStatusUpdate, exports.requestMissingReceiptsTabNavigation = _a.requestMissingReceiptsTabNavigation, exports.clearMissingReceiptsTabNavigation = _a.clearMissingReceiptsTabNavigation, exports.fetchBulkUploadBatchDetailsSuccess = _a.fetchBulkUploadBatchDetailsSuccess, exports.fetchBulkUploadBatchDetailsFailure = _a.fetchBulkUploadBatchDetailsFailure, exports.storeBatchDetails = _a.storeBatchDetails, exports.batchDetailFetchFailed = _a.batchDetailFetchFailed, exports.setInitialBatchDetailsLoading = _a.setInitialBatchDetailsLoading, exports.fetchMoreBatchDetails = _a.fetchMoreBatchDetails, exports.fetchMoreBatchDetailsComplete = _a.fetchMoreBatchDetailsComplete, exports.fetchMoreBatchDetailsFailure = _a.fetchMoreBatchDetailsFailure, exports.fetchBulkUploadBatches = _a.fetchBulkUploadBatches, exports.fetchBulkUploadBatchesSuccess = _a.fetchBulkUploadBatchesSuccess, exports.fetchBulkUploadBatchesFailure = _a.fetchBulkUploadBatchesFailure, exports.
|
|
587
|
+
exports.bulkUploadReceipts = _a.bulkUploadReceipts, exports.updateBulkUploadProgress = _a.updateBulkUploadProgress, exports.setBulkUploadUploadedFileCount = _a.setBulkUploadUploadedFileCount, exports.bulkUploadReceiptsSuccess = _a.bulkUploadReceiptsSuccess, exports.bulkUploadReceiptsFailure = _a.bulkUploadReceiptsFailure, exports.restoreBulkUploadMatchingState = _a.restoreBulkUploadMatchingState, exports.bulkUploadAutomatchingTimedOut = _a.bulkUploadAutomatchingTimedOut, exports.pusherBatchStatusUpdate = _a.pusherBatchStatusUpdate, exports.requestMissingReceiptsTabNavigation = _a.requestMissingReceiptsTabNavigation, exports.clearMissingReceiptsTabNavigation = _a.clearMissingReceiptsTabNavigation, exports.fetchBulkUploadBatchDetailsSuccess = _a.fetchBulkUploadBatchDetailsSuccess, exports.fetchBulkUploadBatchDetailsFailure = _a.fetchBulkUploadBatchDetailsFailure, exports.storeBatchDetails = _a.storeBatchDetails, exports.batchDetailFetchFailed = _a.batchDetailFetchFailed, exports.setInitialBatchDetailsLoading = _a.setInitialBatchDetailsLoading, exports.fetchMoreBatchDetails = _a.fetchMoreBatchDetails, exports.fetchMoreBatchDetailsComplete = _a.fetchMoreBatchDetailsComplete, exports.fetchMoreBatchDetailsFailure = _a.fetchMoreBatchDetailsFailure, exports.fetchBulkUploadBatches = _a.fetchBulkUploadBatches, exports.fetchBulkUploadBatchesSuccess = _a.fetchBulkUploadBatchesSuccess, exports.fetchBulkUploadBatchesFailure = _a.fetchBulkUploadBatchesFailure, exports.confirmBulkUploadMatch = _a.confirmBulkUploadMatch, exports.confirmBulkUploadMatchSuccess = _a.confirmBulkUploadMatchSuccess, exports.confirmBulkUploadMatchFailure = _a.confirmBulkUploadMatchFailure, exports.setBulkUploadResultsTab = _a.setBulkUploadResultsTab, exports.setBulkUploadCompletedSubTab = _a.setBulkUploadCompletedSubTab, exports.setBulkUploadSortConfig = _a.setBulkUploadSortConfig, exports.clearBulkUpload = _a.clearBulkUpload, exports.searchTransactionsForManualMatch = _a.searchTransactionsForManualMatch, exports.searchTransactionsForManualMatchSuccess = _a.searchTransactionsForManualMatchSuccess, exports.searchTransactionsForManualMatchFailure = _a.searchTransactionsForManualMatchFailure, exports.clearManualSearchResults = _a.clearManualSearchResults, exports.acknowledgeBulkUploadConfirmMatchComplete = _a.acknowledgeBulkUploadConfirmMatchComplete,
|
|
667
588
|
// Completed Transactions
|
|
668
589
|
exports.fetchCompletedTransactions = _a.fetchCompletedTransactions, exports.fetchCompletedTransactionsSuccess = _a.fetchCompletedTransactionsSuccess, exports.fetchCompletedTransactionsFailure = _a.fetchCompletedTransactionsFailure;
|
|
669
590
|
exports.default = expenseAutomationMissingReceiptsView.reducer;
|
|
@@ -29,12 +29,6 @@ export interface ResolvedBatchFile {
|
|
|
29
29
|
export interface BulkUploadSelectorData {
|
|
30
30
|
batchDetails: ResolvedBatchDetails | undefined;
|
|
31
31
|
batchDetailsPaginationState: FetchStateAndError;
|
|
32
|
-
/**
|
|
33
|
-
* True while a stale-while-revalidate refresh of `batchDetailsById` is in flight (e.g. manual
|
|
34
|
-
* refresh button). UI should keep rendering existing data and show only a lightweight refresh
|
|
35
|
-
* indicator — never the full skeleton.
|
|
36
|
-
*/
|
|
37
|
-
batchDetailsRevalidating: boolean;
|
|
38
32
|
batchList: BatchListItem[];
|
|
39
33
|
batchListFetchState: FetchStateAndError;
|
|
40
34
|
batchStatus: BatchStatus | undefined;
|
|
@@ -43,12 +37,6 @@ export interface BulkUploadSelectorData {
|
|
|
43
37
|
confirmMatchStatus: FetchStateAndError;
|
|
44
38
|
currentResultsTab: BulkUploadResultsTab;
|
|
45
39
|
hasMoreBatchDetails: boolean;
|
|
46
|
-
/**
|
|
47
|
-
* `true` once the first batch-details page has loaded for the current scope (period/tenant).
|
|
48
|
-
* Stays `true` across refreshes — flips back to `false` only on `clearBulkUpload` or on an
|
|
49
|
-
* explicit scope change. Used by the Unmatched tab to gate the initial-load skeleton.
|
|
50
|
-
*/
|
|
51
|
-
initialBatchDetailsLoaded: boolean;
|
|
52
40
|
manualSearch: ManualSearchState;
|
|
53
41
|
/** Bumps when manual search state is cleared — remount manual search inputs. */
|
|
54
42
|
manualSearchUiResetKey: number;
|
|
@@ -230,8 +230,6 @@ function getExpenseAutomationMissingReceiptsView(state) {
|
|
|
230
230
|
manualSearchUiResetKey: bulkUpload.manualSearchUiResetKey,
|
|
231
231
|
hasMoreBatchDetails,
|
|
232
232
|
batchDetailsPaginationState: bulkUpload.batchDetailsPaginationState,
|
|
233
|
-
batchDetailsRevalidating: bulkUpload.batchDetailsRevalidating,
|
|
234
|
-
initialBatchDetailsLoaded: bulkUpload.initialBatchDetailsLoaded,
|
|
235
233
|
manualSearch: {
|
|
236
234
|
...bulkUpload.manualSearch,
|
|
237
235
|
results: manualSearchResults,
|
|
@@ -129,13 +129,6 @@ export interface CompletedTransactionsState {
|
|
|
129
129
|
export interface BulkUploadState {
|
|
130
130
|
batchDetailsById: Record<ID, BatchDetails>;
|
|
131
131
|
batchDetailsPaginationState: FetchStateAndError;
|
|
132
|
-
/**
|
|
133
|
-
* True while a stale-while-revalidate refresh of `batchDetailsById` is in flight (manual refresh
|
|
134
|
-
* or scope-change replacement). Existing entries are kept on screen during this window; the UI
|
|
135
|
-
* can surface a lightweight refresh indicator. Cleared on `fetchMoreBatchDetailsComplete` /
|
|
136
|
-
* `fetchMoreBatchDetailsFailure`.
|
|
137
|
-
*/
|
|
138
|
-
batchDetailsRevalidating: boolean;
|
|
139
132
|
batchListByPeriod: Record<MonthYearPeriodId, BatchListItem[]>;
|
|
140
133
|
batchListFetchState: FetchStateAndError;
|
|
141
134
|
batchStatusById: Record<ID, BatchStatus>;
|
|
@@ -144,18 +137,6 @@ export interface BulkUploadState {
|
|
|
144
137
|
confirmMatchStatus: FetchStateAndError;
|
|
145
138
|
currentResultsTab: BulkUploadResultsTab;
|
|
146
139
|
failedBatchDetailIds: ID[];
|
|
147
|
-
/**
|
|
148
|
-
* Set to `true` once the first batch-details page has resolved (success or empty completion).
|
|
149
|
-
* Drives stale-while-revalidate: the Unmatched skeleton is shown only while this is `false`.
|
|
150
|
-
* Reset only by `clearBulkUpload` or by an explicit scope-change (period/tenant) clear.
|
|
151
|
-
*/
|
|
152
|
-
initialBatchDetailsLoaded: boolean;
|
|
153
|
-
/**
|
|
154
|
-
* Last time `GET /1.0/batches/:id` was issued for a given batch via the targeted refresh epic.
|
|
155
|
-
* Used to throttle Pusher resend storms — repeat completion events for the same batchId within
|
|
156
|
-
* the dedupe window are dropped.
|
|
157
|
-
*/
|
|
158
|
-
lastBatchDetailRefreshAtById: Record<ID, number>;
|
|
159
140
|
/**
|
|
160
141
|
* Incremented when manual search Redux state is cleared so Unmatched `ManualSearchInput`
|
|
161
142
|
* instances remount and drop local query text.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeniai/client-epic-state",
|
|
3
|
-
"version": "5.0.71
|
|
3
|
+
"version": "5.0.71",
|
|
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",
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
"find-dead-code": "ts-prune | grep -v '(used in module)'",
|
|
117
117
|
"find-unused-exports": "ts-unused-exports ./tsconfig.json",
|
|
118
118
|
"circular-dependency": "npx madge --circular --extensions ts ./src",
|
|
119
|
-
"build": "concurrently --kill-others-on-fail --handle-input \"echo 'TypeScript build in progress...' && time tsc && echo 'ESM build...' && tsc -p tsconfig.esm.json\"",
|
|
119
|
+
"build": "concurrently --kill-others-on-fail --handle-input \"echo 'TypeScript build in progress...' && time tsc && echo 'ESM build...' && tsc -p tsconfig.esm.json\" \"echo 'Running tests and typecheck (incl. lint-modified-files)...' && pnpm test\" && rimraf \"lib/**/__mocks__\" \"lib/**/__testHelpers__\"",
|
|
120
120
|
"only-build": "eslint . --ext .js,.jsx,.ts,.tsx && time tsc && tsc -p tsconfig.esm.json && rimraf \"lib/**/__mocks__\" \"lib/**/__testHelpers__\"",
|
|
121
121
|
"only-build-dev": "eslint . --ext .js,.jsx,.ts,.tsx && time tsc -p tsconfig.dev.json && tsc -p tsconfig.esm.dev.json && rimraf \"lib/**/__mocks__\" \"lib/**/__testHelpers__\"",
|
|
122
122
|
"format": "prettier --write --ignore-unknown \"src/**/*\" && pnpm lint",
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { EMPTY, concat, from, of } from 'rxjs';
|
|
2
|
-
import { catchError, filter, mergeMap } from 'rxjs/operators';
|
|
3
|
-
import { updateTransactions } from '../../../../entity/transaction/transactionReducer';
|
|
4
|
-
import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
|
|
5
|
-
import { extractTransactionPayloadsFromBatchFiles, isBatchDetailsApiStatusCompleted, toBatchDetails, } from '../../payload/missingReceiptsPayload';
|
|
6
|
-
import { batchDetailFetchFailed, markBatchDetailRefreshAttempted, refreshBatchDetailsForBatchId, storeBatchDetails, } from '../../reducers/missingReceiptsViewReducer';
|
|
7
|
-
/**
|
|
8
|
-
* Pusher resends the same `completed` event in some cases (reconnect, server retry). Skip a
|
|
9
|
-
* targeted refresh if we issued one for the same batch within this window — the cached details
|
|
10
|
-
* are already fresh, so another `GET /1.0/batches/:id` is wasteful and visually flickers nothing.
|
|
11
|
-
*/
|
|
12
|
-
const TARGETED_REFRESH_DEDUPE_MS = 5000;
|
|
13
|
-
/**
|
|
14
|
-
* Single-batch stale-while-revalidate refresh. Triggered by `pusherBatchStatusCompletionEpic`
|
|
15
|
-
* (and any other path that wants to update one batch without wiping the cache). The response is
|
|
16
|
-
* stored via `storeBatchDetails`, which replaces the entry by `batchId` so the row updates in
|
|
17
|
-
* place — no skeleton, no list reflow.
|
|
18
|
-
*
|
|
19
|
-
* Throttle: `lastBatchDetailRefreshAtById[batchId]` is checked against `TARGETED_REFRESH_DEDUPE_MS`
|
|
20
|
-
* before issuing the call. The marker is emitted first so concurrent dispatches (e.g. Pusher
|
|
21
|
-
* resend during an in-flight request) see the dedupe timestamp and collapse to a single call.
|
|
22
|
-
*/
|
|
23
|
-
export const refreshBatchDetailsForBatchIdEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(refreshBatchDetailsForBatchId.match), mergeMap((action) => {
|
|
24
|
-
const { batchId } = action.payload;
|
|
25
|
-
const now = Date.now();
|
|
26
|
-
const lastAttempt = state$.value.expenseAutomationMissingReceiptsViewState.bulkUpload
|
|
27
|
-
.lastBatchDetailRefreshAtById[batchId];
|
|
28
|
-
if (lastAttempt != null &&
|
|
29
|
-
now - lastAttempt < TARGETED_REFRESH_DEDUPE_MS) {
|
|
30
|
-
return EMPTY;
|
|
31
|
-
}
|
|
32
|
-
const apiCall = zeniAPI
|
|
33
|
-
.getJSON(`${zeniAPI.apiEndPoints.communicationAgentMicroServiceBaseUrl}/1.0/batches/${batchId}`)
|
|
34
|
-
.pipe(mergeMap((response) => {
|
|
35
|
-
if (!isSuccessResponse(response) ||
|
|
36
|
-
response.data == null ||
|
|
37
|
-
!isBatchDetailsApiStatusCompleted(response.data.status)) {
|
|
38
|
-
return EMPTY;
|
|
39
|
-
}
|
|
40
|
-
const actions = [];
|
|
41
|
-
const transactionPayloads = extractTransactionPayloadsFromBatchFiles(response.data.files);
|
|
42
|
-
if (transactionPayloads.length > 0) {
|
|
43
|
-
actions.push(updateTransactions(transactionPayloads, (value) => value.transaction_id));
|
|
44
|
-
}
|
|
45
|
-
actions.push(storeBatchDetails(toBatchDetails(response.data, zeniAPI.apiEndPoints.fileMicroServiceBaseUrl)));
|
|
46
|
-
return from(actions);
|
|
47
|
-
}), catchError((error) => {
|
|
48
|
-
console.error(`Failed to refresh batch details for ${batchId}:`, createZeniAPIStatus('Unexpected Error', 'Refresh batch details errored out: ' + JSON.stringify(error)));
|
|
49
|
-
return of(batchDetailFetchFailed({ batchId }));
|
|
50
|
-
}));
|
|
51
|
-
return concat(of(markBatchDetailRefreshAttempted({ batchId, timestamp: now })), apiCall);
|
|
52
|
-
}));
|
package/lib/esm/view/expenseAutomationView/epics/missingReceipts/syncTabsAfterAutomatchEpic.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { EMPTY, from, merge } from 'rxjs';
|
|
2
|
-
import { filter, switchMap, take, takeUntil } from 'rxjs/operators';
|
|
3
|
-
import { convertToPeriod, toAbsoluteDay, } from '../../../../commonStateTypes/timePeriod';
|
|
4
|
-
import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
|
|
5
|
-
import { bulkUploadReceiptsSuccess, clearBulkUpload, fetchBulkUploadBatchDetailsSuccess, fetchCompletedTransactions, fetchMissingReceipts, restoreBulkUploadMatchingState, storeBatchDetails, } from '../../reducers/missingReceiptsViewReducer';
|
|
6
|
-
/**
|
|
7
|
-
* After automatch completes for the active bulk-upload batch, refresh the two
|
|
8
|
-
* caches that are not invalidated by the existing batch-details refresh path:
|
|
9
|
-
* - Missing Receipts list (`transactionIdsBySelectedPeriod`)
|
|
10
|
-
* - Completed transactions cache (`bulkUpload.completedTransactionsByPeriod`)
|
|
11
|
-
*
|
|
12
|
-
* The Unmatched tab is already kept fresh via `pusherBatchStatusCompletionEpic`
|
|
13
|
-
* dispatching `fetchBulkUploadBatches({invalidateBatchDetailsCache: true})`,
|
|
14
|
-
* which feeds `fetchMultipleBatchDetailsEpic` and re-stores batch details.
|
|
15
|
-
*
|
|
16
|
-
* Lifecycle mirrors `bulkUploadMatchResultToastEpic`: trigger on
|
|
17
|
-
* `bulkUploadReceiptsSuccess` (fresh upload) or `restoreBulkUploadMatchingState`
|
|
18
|
-
* (restored session), then wait for the first batch-details action for that
|
|
19
|
-
* batchId — landing as either `fetchBulkUploadBatchDetailsSuccess` or
|
|
20
|
-
* `storeBatchDetails`. `clearBulkUpload` cancels the in-flight wait.
|
|
21
|
-
*
|
|
22
|
-
* Refetches use `cacheOverride: true` to bypass per-tab cache guards, and
|
|
23
|
-
* `fetchMissingReceipts` uses `refreshViewInBackground: true` to keep the
|
|
24
|
-
* existing rows visible while fresh data is in flight (the authoritative
|
|
25
|
-
* server filter `is_attachment_missing_only=true` removes matched rows once
|
|
26
|
-
* the response lands).
|
|
27
|
-
*/
|
|
28
|
-
export const syncTabsAfterAutomatchEpic = (actions$, state$) => actions$.pipe(filter((action) => bulkUploadReceiptsSuccess.match(action) ||
|
|
29
|
-
restoreBulkUploadMatchingState.match(action)), switchMap((uploadAction) => {
|
|
30
|
-
const { batchId } = uploadAction.payload;
|
|
31
|
-
return merge(actions$.pipe(filter(fetchBulkUploadBatchDetailsSuccess.match)), actions$.pipe(filter(storeBatchDetails.match))).pipe(filter((a) => a.payload.batchId === batchId), take(1), switchMap((action) => {
|
|
32
|
-
const { summary } = action.payload;
|
|
33
|
-
if (summary.matched <= 0) {
|
|
34
|
-
return EMPTY;
|
|
35
|
-
}
|
|
36
|
-
const state = state$.value;
|
|
37
|
-
const currentTenant = getCurrentTenant(state);
|
|
38
|
-
const selectedPeriod = state.expenseAutomationViewState.selectedPeriodByTenantId[currentTenant.tenantId];
|
|
39
|
-
if (selectedPeriod == null) {
|
|
40
|
-
return from([fetchCompletedTransactions(undefined, true)]);
|
|
41
|
-
}
|
|
42
|
-
const period = convertToPeriod(selectedPeriod);
|
|
43
|
-
const timePeriodStart = toAbsoluteDay(period.start);
|
|
44
|
-
const timePeriodEnd = toAbsoluteDay(period.end);
|
|
45
|
-
if (timePeriodStart == null || timePeriodEnd == null) {
|
|
46
|
-
return from([fetchCompletedTransactions(undefined, true)]);
|
|
47
|
-
}
|
|
48
|
-
return from([
|
|
49
|
-
fetchMissingReceipts({ start: timePeriodStart, end: timePeriodEnd }, { amount: 75.0, currencyCode: 'USD', currencySymbol: '$' }, true, true),
|
|
50
|
-
fetchCompletedTransactions(undefined, true),
|
|
51
|
-
]);
|
|
52
|
-
}), takeUntil(actions$.pipe(filter(clearBulkUpload.match))));
|
|
53
|
-
}));
|
package/lib/view/expenseAutomationView/epics/missingReceipts/refreshBatchDetailsForBatchIdEpic.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { ActionsObservable, StateObservable } from 'redux-observable';
|
|
2
|
-
import { Observable } from 'rxjs';
|
|
3
|
-
import { updateTransactions } from '../../../../entity/transaction/transactionReducer';
|
|
4
|
-
import { RootState } from '../../../../reducer';
|
|
5
|
-
import { ZeniAPI } from '../../../../zeniAPI';
|
|
6
|
-
import { batchDetailFetchFailed, markBatchDetailRefreshAttempted, refreshBatchDetailsForBatchId, storeBatchDetails } from '../../reducers/missingReceiptsViewReducer';
|
|
7
|
-
export type ActionType = ReturnType<typeof refreshBatchDetailsForBatchId> | ReturnType<typeof markBatchDetailRefreshAttempted> | ReturnType<typeof updateTransactions> | ReturnType<typeof storeBatchDetails> | ReturnType<typeof batchDetailFetchFailed>;
|
|
8
|
-
/**
|
|
9
|
-
* Single-batch stale-while-revalidate refresh. Triggered by `pusherBatchStatusCompletionEpic`
|
|
10
|
-
* (and any other path that wants to update one batch without wiping the cache). The response is
|
|
11
|
-
* stored via `storeBatchDetails`, which replaces the entry by `batchId` so the row updates in
|
|
12
|
-
* place — no skeleton, no list reflow.
|
|
13
|
-
*
|
|
14
|
-
* Throttle: `lastBatchDetailRefreshAtById[batchId]` is checked against `TARGETED_REFRESH_DEDUPE_MS`
|
|
15
|
-
* before issuing the call. The marker is emitted first so concurrent dispatches (e.g. Pusher
|
|
16
|
-
* resend during an in-flight request) see the dedupe timestamp and collapse to a single call.
|
|
17
|
-
*/
|
|
18
|
-
export declare const refreshBatchDetailsForBatchIdEpic: (actions$: ActionsObservable<ActionType>, state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<ActionType>;
|
package/lib/view/expenseAutomationView/epics/missingReceipts/refreshBatchDetailsForBatchIdEpic.js
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.refreshBatchDetailsForBatchIdEpic = void 0;
|
|
4
|
-
const rxjs_1 = require("rxjs");
|
|
5
|
-
const operators_1 = require("rxjs/operators");
|
|
6
|
-
const transactionReducer_1 = require("../../../../entity/transaction/transactionReducer");
|
|
7
|
-
const responsePayload_1 = require("../../../../responsePayload");
|
|
8
|
-
const missingReceiptsPayload_1 = require("../../payload/missingReceiptsPayload");
|
|
9
|
-
const missingReceiptsViewReducer_1 = require("../../reducers/missingReceiptsViewReducer");
|
|
10
|
-
/**
|
|
11
|
-
* Pusher resends the same `completed` event in some cases (reconnect, server retry). Skip a
|
|
12
|
-
* targeted refresh if we issued one for the same batch within this window — the cached details
|
|
13
|
-
* are already fresh, so another `GET /1.0/batches/:id` is wasteful and visually flickers nothing.
|
|
14
|
-
*/
|
|
15
|
-
const TARGETED_REFRESH_DEDUPE_MS = 5000;
|
|
16
|
-
/**
|
|
17
|
-
* Single-batch stale-while-revalidate refresh. Triggered by `pusherBatchStatusCompletionEpic`
|
|
18
|
-
* (and any other path that wants to update one batch without wiping the cache). The response is
|
|
19
|
-
* stored via `storeBatchDetails`, which replaces the entry by `batchId` so the row updates in
|
|
20
|
-
* place — no skeleton, no list reflow.
|
|
21
|
-
*
|
|
22
|
-
* Throttle: `lastBatchDetailRefreshAtById[batchId]` is checked against `TARGETED_REFRESH_DEDUPE_MS`
|
|
23
|
-
* before issuing the call. The marker is emitted first so concurrent dispatches (e.g. Pusher
|
|
24
|
-
* resend during an in-flight request) see the dedupe timestamp and collapse to a single call.
|
|
25
|
-
*/
|
|
26
|
-
const refreshBatchDetailsForBatchIdEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.refreshBatchDetailsForBatchId.match), (0, operators_1.mergeMap)((action) => {
|
|
27
|
-
const { batchId } = action.payload;
|
|
28
|
-
const now = Date.now();
|
|
29
|
-
const lastAttempt = state$.value.expenseAutomationMissingReceiptsViewState.bulkUpload
|
|
30
|
-
.lastBatchDetailRefreshAtById[batchId];
|
|
31
|
-
if (lastAttempt != null &&
|
|
32
|
-
now - lastAttempt < TARGETED_REFRESH_DEDUPE_MS) {
|
|
33
|
-
return rxjs_1.EMPTY;
|
|
34
|
-
}
|
|
35
|
-
const apiCall = zeniAPI
|
|
36
|
-
.getJSON(`${zeniAPI.apiEndPoints.communicationAgentMicroServiceBaseUrl}/1.0/batches/${batchId}`)
|
|
37
|
-
.pipe((0, operators_1.mergeMap)((response) => {
|
|
38
|
-
if (!(0, responsePayload_1.isSuccessResponse)(response) ||
|
|
39
|
-
response.data == null ||
|
|
40
|
-
!(0, missingReceiptsPayload_1.isBatchDetailsApiStatusCompleted)(response.data.status)) {
|
|
41
|
-
return rxjs_1.EMPTY;
|
|
42
|
-
}
|
|
43
|
-
const actions = [];
|
|
44
|
-
const transactionPayloads = (0, missingReceiptsPayload_1.extractTransactionPayloadsFromBatchFiles)(response.data.files);
|
|
45
|
-
if (transactionPayloads.length > 0) {
|
|
46
|
-
actions.push((0, transactionReducer_1.updateTransactions)(transactionPayloads, (value) => value.transaction_id));
|
|
47
|
-
}
|
|
48
|
-
actions.push((0, missingReceiptsViewReducer_1.storeBatchDetails)((0, missingReceiptsPayload_1.toBatchDetails)(response.data, zeniAPI.apiEndPoints.fileMicroServiceBaseUrl)));
|
|
49
|
-
return (0, rxjs_1.from)(actions);
|
|
50
|
-
}), (0, operators_1.catchError)((error) => {
|
|
51
|
-
console.error(`Failed to refresh batch details for ${batchId}:`, (0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', 'Refresh batch details errored out: ' + JSON.stringify(error)));
|
|
52
|
-
return (0, rxjs_1.of)((0, missingReceiptsViewReducer_1.batchDetailFetchFailed)({ batchId }));
|
|
53
|
-
}));
|
|
54
|
-
return (0, rxjs_1.concat)((0, rxjs_1.of)((0, missingReceiptsViewReducer_1.markBatchDetailRefreshAttempted)({ batchId, timestamp: now })), apiCall);
|
|
55
|
-
}));
|
|
56
|
-
exports.refreshBatchDetailsForBatchIdEpic = refreshBatchDetailsForBatchIdEpic;
|
package/lib/view/expenseAutomationView/epics/missingReceipts/syncTabsAfterAutomatchEpic.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { ActionsObservable, StateObservable } from 'redux-observable';
|
|
2
|
-
import { Observable } from 'rxjs';
|
|
3
|
-
import { RootState } from '../../../../reducer';
|
|
4
|
-
import { bulkUploadReceiptsSuccess, clearBulkUpload, fetchBulkUploadBatchDetailsSuccess, fetchCompletedTransactions, fetchMissingReceipts, restoreBulkUploadMatchingState, storeBatchDetails } from '../../reducers/missingReceiptsViewReducer';
|
|
5
|
-
export type ActionType = ReturnType<typeof bulkUploadReceiptsSuccess> | ReturnType<typeof restoreBulkUploadMatchingState> | ReturnType<typeof fetchBulkUploadBatchDetailsSuccess> | ReturnType<typeof storeBatchDetails> | ReturnType<typeof clearBulkUpload> | ReturnType<typeof fetchMissingReceipts> | ReturnType<typeof fetchCompletedTransactions>;
|
|
6
|
-
/**
|
|
7
|
-
* After automatch completes for the active bulk-upload batch, refresh the two
|
|
8
|
-
* caches that are not invalidated by the existing batch-details refresh path:
|
|
9
|
-
* - Missing Receipts list (`transactionIdsBySelectedPeriod`)
|
|
10
|
-
* - Completed transactions cache (`bulkUpload.completedTransactionsByPeriod`)
|
|
11
|
-
*
|
|
12
|
-
* The Unmatched tab is already kept fresh via `pusherBatchStatusCompletionEpic`
|
|
13
|
-
* dispatching `fetchBulkUploadBatches({invalidateBatchDetailsCache: true})`,
|
|
14
|
-
* which feeds `fetchMultipleBatchDetailsEpic` and re-stores batch details.
|
|
15
|
-
*
|
|
16
|
-
* Lifecycle mirrors `bulkUploadMatchResultToastEpic`: trigger on
|
|
17
|
-
* `bulkUploadReceiptsSuccess` (fresh upload) or `restoreBulkUploadMatchingState`
|
|
18
|
-
* (restored session), then wait for the first batch-details action for that
|
|
19
|
-
* batchId — landing as either `fetchBulkUploadBatchDetailsSuccess` or
|
|
20
|
-
* `storeBatchDetails`. `clearBulkUpload` cancels the in-flight wait.
|
|
21
|
-
*
|
|
22
|
-
* Refetches use `cacheOverride: true` to bypass per-tab cache guards, and
|
|
23
|
-
* `fetchMissingReceipts` uses `refreshViewInBackground: true` to keep the
|
|
24
|
-
* existing rows visible while fresh data is in flight (the authoritative
|
|
25
|
-
* server filter `is_attachment_missing_only=true` removes matched rows once
|
|
26
|
-
* the response lands).
|
|
27
|
-
*/
|
|
28
|
-
export declare const syncTabsAfterAutomatchEpic: (actions$: ActionsObservable<ActionType>, state$: StateObservable<RootState>) => Observable<ActionType>;
|