@zeniai/client-epic-state 4.19.37-betaVR17 → 4.19.37-betaVR19
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/esm/view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic.js +8 -6
- package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js +5 -5
- package/lib/esm/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +22 -0
- package/lib/esm/view/expenseAutomationView/selectors/missingReceiptsSelector.js +27 -9
- package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic.js +10 -5
- package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js +8 -5
- package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +22 -0
- package/lib/view/expenseAutomationView/selectors/missingReceiptsSelector.js +27 -9
- package/package.json +1 -1
package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import orderBy from 'lodash/orderBy';
|
|
1
2
|
import { concat, of } from 'rxjs';
|
|
2
|
-
import { catchError,
|
|
3
|
+
import { catchError, exhaustMap, filter } from 'rxjs/operators';
|
|
3
4
|
import { toMonthYearPeriodId } from '../../../../commonStateTypes/timePeriod';
|
|
4
5
|
import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
|
|
5
6
|
import { createZeniAPIStatus } from '../../../../responsePayload';
|
|
@@ -7,7 +8,9 @@ import { isBatchListStatusCompleted } from '../../payload/missingReceiptsPayload
|
|
|
7
8
|
import { fetchMoreBatchDetails, fetchMoreBatchDetailsComplete, fetchMoreBatchDetailsFailure, } from '../../reducers/missingReceiptsViewReducer';
|
|
8
9
|
import { fetchBatchDetailsByIds } from './fetchMultipleBatchDetailsEpic';
|
|
9
10
|
const MORE_BATCH_PAGE_SIZE = 4;
|
|
10
|
-
export const fetchMoreBatchDetailsEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchMoreBatchDetails.match),
|
|
11
|
+
export const fetchMoreBatchDetailsEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchMoreBatchDetails.match),
|
|
12
|
+
/** One page at a time — avoids duplicate loads if IO + scroll fallback both fire. */
|
|
13
|
+
exhaustMap(() => {
|
|
11
14
|
const state = state$.value;
|
|
12
15
|
const { expenseAutomationViewState: { selectedPeriodByTenantId }, expenseAutomationMissingReceiptsViewState: { bulkUpload }, } = state;
|
|
13
16
|
const currentTenant = getCurrentTenant(state);
|
|
@@ -18,11 +21,10 @@ export const fetchMoreBatchDetailsEpic = (actions$, state$, zeniAPI) => actions$
|
|
|
18
21
|
const periodId = toMonthYearPeriodId(selectedPeriod);
|
|
19
22
|
const batchList = bulkUpload.batchListByPeriod[periodId] ?? [];
|
|
20
23
|
const failedIds = new Set(bulkUpload.failedBatchDetailIds);
|
|
21
|
-
const
|
|
22
|
-
.filter((batch) => isBatchListStatusCompleted(batch.status) &&
|
|
24
|
+
const unfetchedBatches = batchList.filter((batch) => isBatchListStatusCompleted(batch.status) &&
|
|
23
25
|
bulkUpload.batchDetailsById[batch.batchId] == null &&
|
|
24
|
-
!failedIds.has(batch.batchId))
|
|
25
|
-
|
|
26
|
+
!failedIds.has(batch.batchId));
|
|
27
|
+
const unfetchedBatchIds = orderBy(unfetchedBatches, (b) => new Date(b.createdAt).valueOf(), 'asc').map((batch) => batch.batchId);
|
|
26
28
|
if (unfetchedBatchIds.length === 0) {
|
|
27
29
|
return of(fetchMoreBatchDetailsComplete());
|
|
28
30
|
}
|
package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import orderBy from 'lodash/orderBy';
|
|
1
2
|
import { EMPTY, concat, from, of } from 'rxjs';
|
|
2
3
|
import { catchError, filter, mergeMap } from 'rxjs/operators';
|
|
3
4
|
import { updateTransactions } from '../../../../entity/transaction/transactionReducer';
|
|
@@ -9,7 +10,6 @@ export function fetchBatchDetailsByIds(batchIds, zeniAPI) {
|
|
|
9
10
|
if (batchIds.length === 0) {
|
|
10
11
|
return EMPTY;
|
|
11
12
|
}
|
|
12
|
-
console.log('fetchBatchDetailsByIds', batchIds);
|
|
13
13
|
return from(batchIds).pipe(mergeMap((batchId) => zeniAPI
|
|
14
14
|
.getJSON(`${zeniAPI.apiEndPoints.communicationAgentMicroServiceBaseUrl}/1.0/batches/${batchId}`)
|
|
15
15
|
.pipe(mergeMap((response) => {
|
|
@@ -32,10 +32,10 @@ export function fetchBatchDetailsByIds(batchIds, zeniAPI) {
|
|
|
32
32
|
export const fetchMultipleBatchDetailsEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchBulkUploadBatchesSuccess.match), mergeMap((action) => {
|
|
33
33
|
const batchList = action.payload.batchList;
|
|
34
34
|
const { batchDetailsById } = state$.value.expenseAutomationMissingReceiptsViewState.bulkUpload;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
batchDetailsById[batch.batchId] == null)
|
|
38
|
-
|
|
35
|
+
/** Oldest first — matches primary-batch selection in missingReceiptsSelector (chronological). */
|
|
36
|
+
const completedBatchesMissingDetails = batchList.filter((batch) => isBatchListStatusCompleted(batch.status) &&
|
|
37
|
+
batchDetailsById[batch.batchId] == null);
|
|
38
|
+
const completedBatchIds = orderBy(completedBatchesMissingDetails, (b) => new Date(b.createdAt).valueOf(), 'asc').map((batch) => batch.batchId);
|
|
39
39
|
const batchIdsToFetch = completedBatchIds.slice(0, INITIAL_BATCH_PAGE_SIZE);
|
|
40
40
|
if (batchIdsToFetch.length === 0) {
|
|
41
41
|
return EMPTY;
|
|
@@ -205,12 +205,25 @@ const expenseAutomationMissingReceiptsView = createSlice({
|
|
|
205
205
|
storeBatchDetails(draft, action) {
|
|
206
206
|
const details = action.payload;
|
|
207
207
|
draft.bulkUpload.batchDetailsById[details.batchId] = details;
|
|
208
|
+
/**
|
|
209
|
+
* Multi-batch fetch paths use `storeBatchDetails` (not `fetchBulkUploadBatchDetailsSuccess`).
|
|
210
|
+
* Without this, `phase` stays `matching` after upload forever and the Unmatched tab keeps
|
|
211
|
+
* showing the skeleton (`bulkUploadPhase === 'matching'` in web-components).
|
|
212
|
+
*/
|
|
213
|
+
if (draft.bulkUpload.phase === 'matching' &&
|
|
214
|
+
draft.bulkUpload.currentBatchId === details.batchId) {
|
|
215
|
+
draft.bulkUpload.phase = 'completed';
|
|
216
|
+
}
|
|
208
217
|
},
|
|
209
218
|
batchDetailFetchFailed(draft, action) {
|
|
210
219
|
const { batchId } = action.payload;
|
|
211
220
|
if (!draft.bulkUpload.failedBatchDetailIds.includes(batchId)) {
|
|
212
221
|
draft.bulkUpload.failedBatchDetailIds.push(batchId);
|
|
213
222
|
}
|
|
223
|
+
if (draft.bulkUpload.phase === 'matching' &&
|
|
224
|
+
draft.bulkUpload.currentBatchId === batchId) {
|
|
225
|
+
draft.bulkUpload.phase = 'completed';
|
|
226
|
+
}
|
|
214
227
|
},
|
|
215
228
|
setInitialBatchDetailsLoading(draft) {
|
|
216
229
|
draft.bulkUpload.batchDetailsPaginationState = {
|
|
@@ -229,6 +242,15 @@ const expenseAutomationMissingReceiptsView = createSlice({
|
|
|
229
242
|
fetchState: 'Completed',
|
|
230
243
|
error: undefined,
|
|
231
244
|
};
|
|
245
|
+
const currentId = draft.bulkUpload.currentBatchId;
|
|
246
|
+
const currentDetails = currentId != null
|
|
247
|
+
? draft.bulkUpload.batchDetailsById[currentId]
|
|
248
|
+
: undefined;
|
|
249
|
+
if (draft.bulkUpload.phase === 'matching' &&
|
|
250
|
+
currentId != null &&
|
|
251
|
+
currentDetails?.status === 'completed') {
|
|
252
|
+
draft.bulkUpload.phase = 'completed';
|
|
253
|
+
}
|
|
232
254
|
},
|
|
233
255
|
fetchMoreBatchDetailsFailure(draft, action) {
|
|
234
256
|
draft.bulkUpload.batchDetailsPaginationState = {
|
|
@@ -60,9 +60,9 @@ export function getExpenseAutomationMissingReceiptsView(state) {
|
|
|
60
60
|
const batchListChronological = orderBy(batchList, (b) => new Date(b.createdAt).valueOf(), 'asc');
|
|
61
61
|
/**
|
|
62
62
|
* Primary "Unmatched" batch = first completed batch in chronological order, preferring the
|
|
63
|
-
* first batch that still has un_matched/no_match work when any completed batch has such work
|
|
64
|
-
*
|
|
65
|
-
*
|
|
63
|
+
* first batch that still has un_matched/no_match work when any completed batch has such work.
|
|
64
|
+
* Batches without details yet: wait if the list row still reports unmatched work; otherwise skip
|
|
65
|
+
* so we do not block the whole tab while older rows are not fetched yet.
|
|
66
66
|
*/
|
|
67
67
|
const primaryBatchIdFromDetailsOrder = getPrimaryBatchIdFromBatchListOrder(batchListChronological, bulkUpload.batchDetailsById, bulkUpload.failedBatchDetailIds);
|
|
68
68
|
/** During upload/matching, the in-flight batch may not appear in batchList yet — prefer currentBatchId. */
|
|
@@ -115,9 +115,13 @@ export function getExpenseAutomationMissingReceiptsView(state) {
|
|
|
115
115
|
}
|
|
116
116
|
: { total: 0, processed: 0, percentage: 0 };
|
|
117
117
|
const batchListFetchState = bulkUpload.batchListFetchState;
|
|
118
|
+
/**
|
|
119
|
+
* When primary batch is not resolved yet, still aggregate unmatched files from every batch
|
|
120
|
+
* that has details loaded (otherwise past is empty and receipts vanish from the UI).
|
|
121
|
+
*/
|
|
118
122
|
const pastBatches = unmatchedSectionBatchId != null
|
|
119
123
|
? batchList.filter((b) => b.batchId !== unmatchedSectionBatchId)
|
|
120
|
-
:
|
|
124
|
+
: batchList;
|
|
121
125
|
const pastUnmatchedFiles = pastBatches.flatMap((b) => {
|
|
122
126
|
const details = bulkUpload.batchDetailsById[b.batchId];
|
|
123
127
|
if (details == null) {
|
|
@@ -189,10 +193,18 @@ export function getExpenseAutomationMissingReceiptsView(state) {
|
|
|
189
193
|
function batchDetailsHasUnmatchedWork(details) {
|
|
190
194
|
return details.files.some((f) => f.status === 'un_matched' || f.status === 'no_match');
|
|
191
195
|
}
|
|
196
|
+
/** List row summary before batch details load — aligns with unmatched / possible-match work. */
|
|
197
|
+
function batchListItemHasUnmatchedWork(batch) {
|
|
198
|
+
return batch.noMatchCount > 0 || batch.possibleMatchesCount > 0;
|
|
199
|
+
}
|
|
192
200
|
/**
|
|
193
201
|
* `batchList` must be in chronological order (oldest first). Picks the primary batch for the
|
|
194
|
-
* top "Unmatched" section. Skips failed detail fetches.
|
|
195
|
-
*
|
|
202
|
+
* top "Unmatched" section. Skips failed detail fetches.
|
|
203
|
+
*
|
|
204
|
+
* If details are not loaded for an older batch but the list row reports no unmatched work, we
|
|
205
|
+
* skip that batch so newer batches with loaded details can be primary (avoids an empty tab when
|
|
206
|
+
* the first fetch page only loaded newer batches). If the list reports unmatched work but details
|
|
207
|
+
* are still loading, we wait (undefined) so primary order stays correct once details arrive.
|
|
196
208
|
*/
|
|
197
209
|
function getPrimaryBatchIdFromBatchListOrder(batchListChronological, batchDetailsById, failedBatchDetailIds) {
|
|
198
210
|
const failedIds = new Set(failedBatchDetailIds);
|
|
@@ -201,9 +213,12 @@ function getPrimaryBatchIdFromBatchListOrder(batchListChronological, batchDetail
|
|
|
201
213
|
return false;
|
|
202
214
|
}
|
|
203
215
|
const d = batchDetailsById[b.batchId];
|
|
204
|
-
|
|
216
|
+
if (d != null &&
|
|
205
217
|
d.status === 'completed' &&
|
|
206
|
-
batchDetailsHasUnmatchedWork(d))
|
|
218
|
+
batchDetailsHasUnmatchedWork(d)) {
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
return d == null && batchListItemHasUnmatchedWork(b);
|
|
207
222
|
});
|
|
208
223
|
for (const batch of batchListChronological) {
|
|
209
224
|
if (failedIds.has(batch.batchId)) {
|
|
@@ -211,7 +226,10 @@ function getPrimaryBatchIdFromBatchListOrder(batchListChronological, batchDetail
|
|
|
211
226
|
}
|
|
212
227
|
const details = batchDetailsById[batch.batchId];
|
|
213
228
|
if (details == null) {
|
|
214
|
-
|
|
229
|
+
if (batchListItemHasUnmatchedWork(batch)) {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
continue;
|
|
215
233
|
}
|
|
216
234
|
if (details.status !== 'completed') {
|
|
217
235
|
continue;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.fetchMoreBatchDetailsEpic = void 0;
|
|
7
|
+
const orderBy_1 = __importDefault(require("lodash/orderBy"));
|
|
4
8
|
const rxjs_1 = require("rxjs");
|
|
5
9
|
const operators_1 = require("rxjs/operators");
|
|
6
10
|
const timePeriod_1 = require("../../../../commonStateTypes/timePeriod");
|
|
@@ -10,7 +14,9 @@ const missingReceiptsPayload_1 = require("../../payload/missingReceiptsPayload")
|
|
|
10
14
|
const missingReceiptsViewReducer_1 = require("../../reducers/missingReceiptsViewReducer");
|
|
11
15
|
const fetchMultipleBatchDetailsEpic_1 = require("./fetchMultipleBatchDetailsEpic");
|
|
12
16
|
const MORE_BATCH_PAGE_SIZE = 4;
|
|
13
|
-
const fetchMoreBatchDetailsEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.fetchMoreBatchDetails.match),
|
|
17
|
+
const fetchMoreBatchDetailsEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.fetchMoreBatchDetails.match),
|
|
18
|
+
/** One page at a time — avoids duplicate loads if IO + scroll fallback both fire. */
|
|
19
|
+
(0, operators_1.exhaustMap)(() => {
|
|
14
20
|
const state = state$.value;
|
|
15
21
|
const { expenseAutomationViewState: { selectedPeriodByTenantId }, expenseAutomationMissingReceiptsViewState: { bulkUpload }, } = state;
|
|
16
22
|
const currentTenant = (0, tenantSelector_1.getCurrentTenant)(state);
|
|
@@ -21,11 +27,10 @@ const fetchMoreBatchDetailsEpic = (actions$, state$, zeniAPI) => actions$.pipe((
|
|
|
21
27
|
const periodId = (0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod);
|
|
22
28
|
const batchList = bulkUpload.batchListByPeriod[periodId] ?? [];
|
|
23
29
|
const failedIds = new Set(bulkUpload.failedBatchDetailIds);
|
|
24
|
-
const
|
|
25
|
-
.filter((batch) => (0, missingReceiptsPayload_1.isBatchListStatusCompleted)(batch.status) &&
|
|
30
|
+
const unfetchedBatches = batchList.filter((batch) => (0, missingReceiptsPayload_1.isBatchListStatusCompleted)(batch.status) &&
|
|
26
31
|
bulkUpload.batchDetailsById[batch.batchId] == null &&
|
|
27
|
-
!failedIds.has(batch.batchId))
|
|
28
|
-
|
|
32
|
+
!failedIds.has(batch.batchId));
|
|
33
|
+
const unfetchedBatchIds = (0, orderBy_1.default)(unfetchedBatches, (b) => new Date(b.createdAt).valueOf(), 'asc').map((batch) => batch.batchId);
|
|
29
34
|
if (unfetchedBatchIds.length === 0) {
|
|
30
35
|
return (0, rxjs_1.of)((0, missingReceiptsViewReducer_1.fetchMoreBatchDetailsComplete)());
|
|
31
36
|
}
|
package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.fetchMultipleBatchDetailsEpic = void 0;
|
|
4
7
|
exports.fetchBatchDetailsByIds = fetchBatchDetailsByIds;
|
|
8
|
+
const orderBy_1 = __importDefault(require("lodash/orderBy"));
|
|
5
9
|
const rxjs_1 = require("rxjs");
|
|
6
10
|
const operators_1 = require("rxjs/operators");
|
|
7
11
|
const transactionReducer_1 = require("../../../../entity/transaction/transactionReducer");
|
|
@@ -13,7 +17,6 @@ function fetchBatchDetailsByIds(batchIds, zeniAPI) {
|
|
|
13
17
|
if (batchIds.length === 0) {
|
|
14
18
|
return rxjs_1.EMPTY;
|
|
15
19
|
}
|
|
16
|
-
console.log('fetchBatchDetailsByIds', batchIds);
|
|
17
20
|
return (0, rxjs_1.from)(batchIds).pipe((0, operators_1.mergeMap)((batchId) => zeniAPI
|
|
18
21
|
.getJSON(`${zeniAPI.apiEndPoints.communicationAgentMicroServiceBaseUrl}/1.0/batches/${batchId}`)
|
|
19
22
|
.pipe((0, operators_1.mergeMap)((response) => {
|
|
@@ -36,10 +39,10 @@ function fetchBatchDetailsByIds(batchIds, zeniAPI) {
|
|
|
36
39
|
const fetchMultipleBatchDetailsEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.fetchBulkUploadBatchesSuccess.match), (0, operators_1.mergeMap)((action) => {
|
|
37
40
|
const batchList = action.payload.batchList;
|
|
38
41
|
const { batchDetailsById } = state$.value.expenseAutomationMissingReceiptsViewState.bulkUpload;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
batchDetailsById[batch.batchId] == null)
|
|
42
|
-
|
|
42
|
+
/** Oldest first — matches primary-batch selection in missingReceiptsSelector (chronological). */
|
|
43
|
+
const completedBatchesMissingDetails = batchList.filter((batch) => (0, missingReceiptsPayload_1.isBatchListStatusCompleted)(batch.status) &&
|
|
44
|
+
batchDetailsById[batch.batchId] == null);
|
|
45
|
+
const completedBatchIds = (0, orderBy_1.default)(completedBatchesMissingDetails, (b) => new Date(b.createdAt).valueOf(), 'asc').map((batch) => batch.batchId);
|
|
43
46
|
const batchIdsToFetch = completedBatchIds.slice(0, INITIAL_BATCH_PAGE_SIZE);
|
|
44
47
|
if (batchIdsToFetch.length === 0) {
|
|
45
48
|
return rxjs_1.EMPTY;
|
|
@@ -210,12 +210,25 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
|
|
|
210
210
|
storeBatchDetails(draft, action) {
|
|
211
211
|
const details = action.payload;
|
|
212
212
|
draft.bulkUpload.batchDetailsById[details.batchId] = details;
|
|
213
|
+
/**
|
|
214
|
+
* Multi-batch fetch paths use `storeBatchDetails` (not `fetchBulkUploadBatchDetailsSuccess`).
|
|
215
|
+
* Without this, `phase` stays `matching` after upload forever and the Unmatched tab keeps
|
|
216
|
+
* showing the skeleton (`bulkUploadPhase === 'matching'` in web-components).
|
|
217
|
+
*/
|
|
218
|
+
if (draft.bulkUpload.phase === 'matching' &&
|
|
219
|
+
draft.bulkUpload.currentBatchId === details.batchId) {
|
|
220
|
+
draft.bulkUpload.phase = 'completed';
|
|
221
|
+
}
|
|
213
222
|
},
|
|
214
223
|
batchDetailFetchFailed(draft, action) {
|
|
215
224
|
const { batchId } = action.payload;
|
|
216
225
|
if (!draft.bulkUpload.failedBatchDetailIds.includes(batchId)) {
|
|
217
226
|
draft.bulkUpload.failedBatchDetailIds.push(batchId);
|
|
218
227
|
}
|
|
228
|
+
if (draft.bulkUpload.phase === 'matching' &&
|
|
229
|
+
draft.bulkUpload.currentBatchId === batchId) {
|
|
230
|
+
draft.bulkUpload.phase = 'completed';
|
|
231
|
+
}
|
|
219
232
|
},
|
|
220
233
|
setInitialBatchDetailsLoading(draft) {
|
|
221
234
|
draft.bulkUpload.batchDetailsPaginationState = {
|
|
@@ -234,6 +247,15 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
|
|
|
234
247
|
fetchState: 'Completed',
|
|
235
248
|
error: undefined,
|
|
236
249
|
};
|
|
250
|
+
const currentId = draft.bulkUpload.currentBatchId;
|
|
251
|
+
const currentDetails = currentId != null
|
|
252
|
+
? draft.bulkUpload.batchDetailsById[currentId]
|
|
253
|
+
: undefined;
|
|
254
|
+
if (draft.bulkUpload.phase === 'matching' &&
|
|
255
|
+
currentId != null &&
|
|
256
|
+
currentDetails?.status === 'completed') {
|
|
257
|
+
draft.bulkUpload.phase = 'completed';
|
|
258
|
+
}
|
|
237
259
|
},
|
|
238
260
|
fetchMoreBatchDetailsFailure(draft, action) {
|
|
239
261
|
draft.bulkUpload.batchDetailsPaginationState = {
|
|
@@ -66,9 +66,9 @@ function getExpenseAutomationMissingReceiptsView(state) {
|
|
|
66
66
|
const batchListChronological = (0, orderBy_1.default)(batchList, (b) => new Date(b.createdAt).valueOf(), 'asc');
|
|
67
67
|
/**
|
|
68
68
|
* Primary "Unmatched" batch = first completed batch in chronological order, preferring the
|
|
69
|
-
* first batch that still has un_matched/no_match work when any completed batch has such work
|
|
70
|
-
*
|
|
71
|
-
*
|
|
69
|
+
* first batch that still has un_matched/no_match work when any completed batch has such work.
|
|
70
|
+
* Batches without details yet: wait if the list row still reports unmatched work; otherwise skip
|
|
71
|
+
* so we do not block the whole tab while older rows are not fetched yet.
|
|
72
72
|
*/
|
|
73
73
|
const primaryBatchIdFromDetailsOrder = getPrimaryBatchIdFromBatchListOrder(batchListChronological, bulkUpload.batchDetailsById, bulkUpload.failedBatchDetailIds);
|
|
74
74
|
/** During upload/matching, the in-flight batch may not appear in batchList yet — prefer currentBatchId. */
|
|
@@ -121,9 +121,13 @@ function getExpenseAutomationMissingReceiptsView(state) {
|
|
|
121
121
|
}
|
|
122
122
|
: { total: 0, processed: 0, percentage: 0 };
|
|
123
123
|
const batchListFetchState = bulkUpload.batchListFetchState;
|
|
124
|
+
/**
|
|
125
|
+
* When primary batch is not resolved yet, still aggregate unmatched files from every batch
|
|
126
|
+
* that has details loaded (otherwise past is empty and receipts vanish from the UI).
|
|
127
|
+
*/
|
|
124
128
|
const pastBatches = unmatchedSectionBatchId != null
|
|
125
129
|
? batchList.filter((b) => b.batchId !== unmatchedSectionBatchId)
|
|
126
|
-
:
|
|
130
|
+
: batchList;
|
|
127
131
|
const pastUnmatchedFiles = pastBatches.flatMap((b) => {
|
|
128
132
|
const details = bulkUpload.batchDetailsById[b.batchId];
|
|
129
133
|
if (details == null) {
|
|
@@ -195,10 +199,18 @@ function getExpenseAutomationMissingReceiptsView(state) {
|
|
|
195
199
|
function batchDetailsHasUnmatchedWork(details) {
|
|
196
200
|
return details.files.some((f) => f.status === 'un_matched' || f.status === 'no_match');
|
|
197
201
|
}
|
|
202
|
+
/** List row summary before batch details load — aligns with unmatched / possible-match work. */
|
|
203
|
+
function batchListItemHasUnmatchedWork(batch) {
|
|
204
|
+
return batch.noMatchCount > 0 || batch.possibleMatchesCount > 0;
|
|
205
|
+
}
|
|
198
206
|
/**
|
|
199
207
|
* `batchList` must be in chronological order (oldest first). Picks the primary batch for the
|
|
200
|
-
* top "Unmatched" section. Skips failed detail fetches.
|
|
201
|
-
*
|
|
208
|
+
* top "Unmatched" section. Skips failed detail fetches.
|
|
209
|
+
*
|
|
210
|
+
* If details are not loaded for an older batch but the list row reports no unmatched work, we
|
|
211
|
+
* skip that batch so newer batches with loaded details can be primary (avoids an empty tab when
|
|
212
|
+
* the first fetch page only loaded newer batches). If the list reports unmatched work but details
|
|
213
|
+
* are still loading, we wait (undefined) so primary order stays correct once details arrive.
|
|
202
214
|
*/
|
|
203
215
|
function getPrimaryBatchIdFromBatchListOrder(batchListChronological, batchDetailsById, failedBatchDetailIds) {
|
|
204
216
|
const failedIds = new Set(failedBatchDetailIds);
|
|
@@ -207,9 +219,12 @@ function getPrimaryBatchIdFromBatchListOrder(batchListChronological, batchDetail
|
|
|
207
219
|
return false;
|
|
208
220
|
}
|
|
209
221
|
const d = batchDetailsById[b.batchId];
|
|
210
|
-
|
|
222
|
+
if (d != null &&
|
|
211
223
|
d.status === 'completed' &&
|
|
212
|
-
batchDetailsHasUnmatchedWork(d))
|
|
224
|
+
batchDetailsHasUnmatchedWork(d)) {
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
return d == null && batchListItemHasUnmatchedWork(b);
|
|
213
228
|
});
|
|
214
229
|
for (const batch of batchListChronological) {
|
|
215
230
|
if (failedIds.has(batch.batchId)) {
|
|
@@ -217,7 +232,10 @@ function getPrimaryBatchIdFromBatchListOrder(batchListChronological, batchDetail
|
|
|
217
232
|
}
|
|
218
233
|
const details = batchDetailsById[batch.batchId];
|
|
219
234
|
if (details == null) {
|
|
220
|
-
|
|
235
|
+
if (batchListItemHasUnmatchedWork(batch)) {
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
continue;
|
|
221
239
|
}
|
|
222
240
|
if (details.status !== 'completed') {
|
|
223
241
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeniai/client-epic-state",
|
|
3
|
-
"version": "4.19.37-
|
|
3
|
+
"version": "4.19.37-betaVR19",
|
|
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",
|