@zeniai/client-epic-state 4.19.37-betaVR24 → 4.19.37-betaVR26

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.
@@ -6,7 +6,7 @@ import { createZeniAPIStatus } from '../../../../responsePayload';
6
6
  import { filterBatchListItemsForBatchDetailsFetch } from '../../payload/missingReceiptsPayload';
7
7
  import { fetchMoreBatchDetails, fetchMoreBatchDetailsComplete, fetchMoreBatchDetailsFailure, } from '../../reducers/missingReceiptsViewReducer';
8
8
  import { fetchBatchDetailsByIds } from './fetchMultipleBatchDetailsEpic';
9
- const MORE_BATCH_PAGE_SIZE = 4;
9
+ const MORE_BATCH_PAGE_SIZE = 1;
10
10
  export const fetchMoreBatchDetailsEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchMoreBatchDetails.match),
11
11
  /** One page at a time — avoids duplicate loads if IO + scroll fallback both fire. */
12
12
  exhaustMap(() => {
@@ -2,8 +2,8 @@ import { EMPTY, concat, from, of } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { updateTransactions } from '../../../../entity/transaction/transactionReducer';
4
4
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
5
- import { extractTransactionPayloadsFromBatchFiles, filterBatchListItemsForBatchDetailsFetch, isBatchDetailsApiStatusCompleted, toBatchDetails, } from '../../payload/missingReceiptsPayload';
6
- import { batchDetailFetchFailed, fetchBulkUploadBatchesSuccess, fetchMoreBatchDetailsComplete, setInitialBatchDetailsLoading, storeBatchDetails, } from '../../reducers/missingReceiptsViewReducer';
5
+ import { extractTransactionPayloadsFromBatchFiles, filterBatchListItemsForBatchDetailsFetch, isBatchDetailsApiStatusCompleted, shouldFetchBatchDetailsForBatchId, toBatchDetails, } from '../../payload/missingReceiptsPayload';
6
+ import { batchDetailFetchFailed, fetchBulkUploadBatchDetails, fetchBulkUploadBatchesSuccess, fetchMoreBatchDetailsComplete, setInitialBatchDetailsLoading, storeBatchDetails, } from '../../reducers/missingReceiptsViewReducer';
7
7
  const INITIAL_BATCH_PAGE_SIZE = 1;
8
8
  export function fetchBatchDetailsByIds(batchIds, zeniAPI) {
9
9
  if (batchIds.length === 0) {
@@ -38,6 +38,19 @@ export const fetchMultipleBatchDetailsEpic = (actions$, state$, zeniAPI) => acti
38
38
  const completedBatchIds = completedBatchesMissingDetails.map((batch) => batch.batchId);
39
39
  const batchIdsToFetch = completedBatchIds.slice(0, INITIAL_BATCH_PAGE_SIZE);
40
40
  if (batchIdsToFetch.length === 0) {
41
+ /**
42
+ * List refresh can complete while no row is `completed` yet (multi-fetch skips), or
43
+ * `currentBatchId` may not appear in the filtered list but still needs `GET /batches/:id`.
44
+ * Fall back to the single-batch-details epic so `phase` and details can resolve.
45
+ */
46
+ const bulkUpload = state$.value.expenseAutomationMissingReceiptsViewState.bulkUpload;
47
+ const currentId = bulkUpload.currentBatchId;
48
+ if (currentId != null &&
49
+ bulkUpload.phase === 'matching' &&
50
+ bulkUpload.batchDetailsById[currentId] == null &&
51
+ shouldFetchBatchDetailsForBatchId(bulkUpload.batchStatusById, bulkUpload.batchListByPeriod, currentId)) {
52
+ return of(fetchBulkUploadBatchDetails());
53
+ }
41
54
  return EMPTY;
42
55
  }
43
56
  return concat(of(setInitialBatchDetailsLoading()), fetchBatchDetailsByIds(batchIdsToFetch, zeniAPI), of(fetchMoreBatchDetailsComplete()));
@@ -12,7 +12,10 @@ const FALLBACK_POLL_INTERVAL_MS = 10000;
12
12
  * and request switching to the Unmatched tab. Debounced to reduce duplicate refreshes.
13
13
  */
14
14
  export const pusherBatchStatusCompletionEpic = (actions$) => actions$.pipe(filter(pusherBatchStatusUpdate.match), filter((action) => action.payload.status === 'completed'), debounceTime(300), mergeMap(() => from([
15
- fetchBulkUploadBatches(true),
15
+ fetchBulkUploadBatches({
16
+ cacheOverride: true,
17
+ invalidateBatchDetailsCache: true,
18
+ }),
16
19
  requestMissingReceiptsTabNavigation({ tab: 'unmatched' }),
17
20
  ])));
18
21
  export const pollBulkUploadBatchStatusEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(bulkUploadReceiptsSuccess.match), mergeMap((action) => {
@@ -186,6 +186,16 @@ const expenseAutomationMissingReceiptsView = createSlice({
186
186
  pusherBatchStatusUpdate(draft, action) {
187
187
  const batchStatus = action.payload;
188
188
  draft.bulkUpload.batchStatusById[batchStatus.batchId] = batchStatus;
189
+ /**
190
+ * Auto-matching banner (`phase === 'matching'`) must clear when the server reports this
191
+ * upload batch is done — otherwise the Missing tab keeps MatchingProgressBanner until
192
+ * batch-details APIs run; EmailUploadBanner should show again for the next upload.
193
+ */
194
+ if (batchStatus.status === 'completed' &&
195
+ draft.bulkUpload.phase === 'matching' &&
196
+ draft.bulkUpload.currentBatchId === batchStatus.batchId) {
197
+ draft.bulkUpload.phase = 'completed';
198
+ }
189
199
  },
190
200
  requestMissingReceiptsTabNavigation(draft, action) {
191
201
  draft.pendingMissingReceiptsTabNavigation = action.payload.tab;
@@ -263,15 +273,32 @@ const expenseAutomationMissingReceiptsView = createSlice({
263
273
  };
264
274
  },
265
275
  fetchBulkUploadBatches: {
266
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
267
- reducer(draft, _action) {
276
+ reducer(draft, action) {
277
+ if (action.payload.invalidateBatchDetailsCache === true) {
278
+ draft.bulkUpload.batchDetailsById = {};
279
+ draft.bulkUpload.failedBatchDetailIds = [];
280
+ }
268
281
  draft.bulkUpload.batchListFetchState = {
269
282
  fetchState: 'In-Progress',
270
283
  error: undefined,
271
284
  };
272
285
  },
273
- prepare(cacheOverride = false) {
274
- return { payload: { cacheOverride } };
286
+ prepare(input) {
287
+ if (input === true || input === false) {
288
+ return {
289
+ payload: {
290
+ cacheOverride: input === true,
291
+ invalidateBatchDetailsCache: false,
292
+ },
293
+ };
294
+ }
295
+ const o = input ?? {};
296
+ return {
297
+ payload: {
298
+ cacheOverride: o.cacheOverride === true,
299
+ invalidateBatchDetailsCache: o.invalidateBatchDetailsCache === true,
300
+ },
301
+ };
275
302
  },
276
303
  },
277
304
  fetchBulkUploadBatchesSuccess(draft, action) {
@@ -281,6 +308,20 @@ const expenseAutomationMissingReceiptsView = createSlice({
281
308
  fetchState: 'Completed',
282
309
  error: undefined,
283
310
  };
311
+ /**
312
+ * When the multi-batch-details epic has nothing to fetch (e.g. list rows not `completed`
313
+ * yet, or details already present), `phase` can stay `matching` even though we already
314
+ * have completed details for `currentBatchId` (from polling / Pusher). Sync so the UI can
315
+ * dismiss the Unmatched skeleton.
316
+ */
317
+ const currentId = draft.bulkUpload.currentBatchId;
318
+ if (draft.bulkUpload.phase === 'matching' &&
319
+ currentId != null) {
320
+ const existing = draft.bulkUpload.batchDetailsById[currentId];
321
+ if (existing?.status === 'completed') {
322
+ draft.bulkUpload.phase = 'completed';
323
+ }
324
+ }
284
325
  },
285
326
  fetchBulkUploadBatchesFailure(draft, action) {
286
327
  draft.bulkUpload.batchListFetchState = {
@@ -3,6 +3,6 @@ import { Observable } from 'rxjs';
3
3
  import { updateTransactions } from '../../../../entity/transaction/transactionReducer';
4
4
  import { RootState } from '../../../../reducer';
5
5
  import { ZeniAPI } from '../../../../zeniAPI';
6
- import { batchDetailFetchFailed, fetchBulkUploadBatchesSuccess, fetchMoreBatchDetails, fetchMoreBatchDetailsComplete, fetchMoreBatchDetailsFailure, setInitialBatchDetailsLoading, storeBatchDetails } from '../../reducers/missingReceiptsViewReducer';
7
- export type ActionType = ReturnType<typeof fetchBulkUploadBatchesSuccess> | ReturnType<typeof fetchMoreBatchDetails> | ReturnType<typeof fetchMoreBatchDetailsComplete> | ReturnType<typeof fetchMoreBatchDetailsFailure> | ReturnType<typeof batchDetailFetchFailed> | ReturnType<typeof setInitialBatchDetailsLoading> | ReturnType<typeof storeBatchDetails> | ReturnType<typeof updateTransactions>;
6
+ import { batchDetailFetchFailed, fetchBulkUploadBatchDetails, fetchBulkUploadBatchesSuccess, fetchMoreBatchDetails, fetchMoreBatchDetailsComplete, fetchMoreBatchDetailsFailure, setInitialBatchDetailsLoading, storeBatchDetails } from '../../reducers/missingReceiptsViewReducer';
7
+ export type ActionType = ReturnType<typeof fetchBulkUploadBatchDetails> | ReturnType<typeof fetchBulkUploadBatchesSuccess> | ReturnType<typeof fetchMoreBatchDetails> | ReturnType<typeof fetchMoreBatchDetailsComplete> | ReturnType<typeof fetchMoreBatchDetailsFailure> | ReturnType<typeof batchDetailFetchFailed> | ReturnType<typeof setInitialBatchDetailsLoading> | ReturnType<typeof storeBatchDetails> | ReturnType<typeof updateTransactions>;
8
8
  export declare const fetchMoreBatchDetailsEpic: (actions$: ActionsObservable<ActionType>, state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<ActionType>;
@@ -9,7 +9,7 @@ const responsePayload_1 = require("../../../../responsePayload");
9
9
  const missingReceiptsPayload_1 = require("../../payload/missingReceiptsPayload");
10
10
  const missingReceiptsViewReducer_1 = require("../../reducers/missingReceiptsViewReducer");
11
11
  const fetchMultipleBatchDetailsEpic_1 = require("./fetchMultipleBatchDetailsEpic");
12
- const MORE_BATCH_PAGE_SIZE = 4;
12
+ const MORE_BATCH_PAGE_SIZE = 1;
13
13
  const fetchMoreBatchDetailsEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.fetchMoreBatchDetails.match),
14
14
  /** One page at a time — avoids duplicate loads if IO + scroll fallback both fire. */
15
15
  (0, operators_1.exhaustMap)(() => {
@@ -3,7 +3,7 @@ import { Observable } from 'rxjs';
3
3
  import { updateTransactions } from '../../../../entity/transaction/transactionReducer';
4
4
  import { RootState } from '../../../../reducer';
5
5
  import { ZeniAPI } from '../../../../zeniAPI';
6
- import { batchDetailFetchFailed, fetchBulkUploadBatchesSuccess, fetchMoreBatchDetailsComplete, setInitialBatchDetailsLoading, storeBatchDetails } from '../../reducers/missingReceiptsViewReducer';
7
- export type ActionType = ReturnType<typeof fetchBulkUploadBatchesSuccess> | ReturnType<typeof updateTransactions> | ReturnType<typeof storeBatchDetails> | ReturnType<typeof batchDetailFetchFailed> | ReturnType<typeof setInitialBatchDetailsLoading> | ReturnType<typeof fetchMoreBatchDetailsComplete>;
6
+ import { batchDetailFetchFailed, fetchBulkUploadBatchDetails, fetchBulkUploadBatchesSuccess, fetchMoreBatchDetailsComplete, setInitialBatchDetailsLoading, storeBatchDetails } from '../../reducers/missingReceiptsViewReducer';
7
+ export type ActionType = ReturnType<typeof fetchBulkUploadBatchDetails> | ReturnType<typeof fetchBulkUploadBatchesSuccess> | ReturnType<typeof updateTransactions> | ReturnType<typeof storeBatchDetails> | ReturnType<typeof batchDetailFetchFailed> | ReturnType<typeof setInitialBatchDetailsLoading> | ReturnType<typeof fetchMoreBatchDetailsComplete>;
8
8
  export declare function fetchBatchDetailsByIds(batchIds: string[], zeniAPI: ZeniAPI): Observable<ActionType>;
9
9
  export declare const fetchMultipleBatchDetailsEpic: (actions$: ActionsObservable<ActionType>, state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<ActionType>;
@@ -42,6 +42,19 @@ const fetchMultipleBatchDetailsEpic = (actions$, state$, zeniAPI) => actions$.pi
42
42
  const completedBatchIds = completedBatchesMissingDetails.map((batch) => batch.batchId);
43
43
  const batchIdsToFetch = completedBatchIds.slice(0, INITIAL_BATCH_PAGE_SIZE);
44
44
  if (batchIdsToFetch.length === 0) {
45
+ /**
46
+ * List refresh can complete while no row is `completed` yet (multi-fetch skips), or
47
+ * `currentBatchId` may not appear in the filtered list but still needs `GET /batches/:id`.
48
+ * Fall back to the single-batch-details epic so `phase` and details can resolve.
49
+ */
50
+ const bulkUpload = state$.value.expenseAutomationMissingReceiptsViewState.bulkUpload;
51
+ const currentId = bulkUpload.currentBatchId;
52
+ if (currentId != null &&
53
+ bulkUpload.phase === 'matching' &&
54
+ bulkUpload.batchDetailsById[currentId] == null &&
55
+ (0, missingReceiptsPayload_1.shouldFetchBatchDetailsForBatchId)(bulkUpload.batchStatusById, bulkUpload.batchListByPeriod, currentId)) {
56
+ return (0, rxjs_1.of)((0, missingReceiptsViewReducer_1.fetchBulkUploadBatchDetails)());
57
+ }
45
58
  return rxjs_1.EMPTY;
46
59
  }
47
60
  return (0, rxjs_1.concat)((0, rxjs_1.of)((0, missingReceiptsViewReducer_1.setInitialBatchDetailsLoading)()), fetchBatchDetailsByIds(batchIdsToFetch, zeniAPI), (0, rxjs_1.of)((0, missingReceiptsViewReducer_1.fetchMoreBatchDetailsComplete)()));
@@ -15,7 +15,10 @@ const FALLBACK_POLL_INTERVAL_MS = 10000;
15
15
  * and request switching to the Unmatched tab. Debounced to reduce duplicate refreshes.
16
16
  */
17
17
  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)([
18
- (0, missingReceiptsViewReducer_1.fetchBulkUploadBatches)(true),
18
+ (0, missingReceiptsViewReducer_1.fetchBulkUploadBatches)({
19
+ cacheOverride: true,
20
+ invalidateBatchDetailsCache: true,
21
+ }),
19
22
  (0, missingReceiptsViewReducer_1.requestMissingReceiptsTabNavigation)({ tab: 'unmatched' }),
20
23
  ])));
21
24
  exports.pusherBatchStatusCompletionEpic = pusherBatchStatusCompletionEpic;
@@ -44,8 +44,12 @@ export declare const fetchMissingReceipts: import("@reduxjs/toolkit").ActionCrea
44
44
  batchId: ID;
45
45
  }, "expenseAutomationMissingReceiptsView/batchDetailFetchFailed">, setInitialBatchDetailsLoading: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"expenseAutomationMissingReceiptsView/setInitialBatchDetailsLoading">, fetchMoreBatchDetails: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"expenseAutomationMissingReceiptsView/fetchMoreBatchDetails">, fetchMoreBatchDetailsComplete: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"expenseAutomationMissingReceiptsView/fetchMoreBatchDetailsComplete">, fetchMoreBatchDetailsFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
46
46
  error: ZeniAPIStatus;
47
- }, "expenseAutomationMissingReceiptsView/fetchMoreBatchDetailsFailure">, fetchBulkUploadBatches: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[cacheOverride?: any], {
48
- cacheOverride: any;
47
+ }, "expenseAutomationMissingReceiptsView/fetchMoreBatchDetailsFailure">, fetchBulkUploadBatches: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[input?: boolean | {
48
+ cacheOverride?: boolean;
49
+ invalidateBatchDetailsCache?: boolean;
50
+ } | undefined], {
51
+ cacheOverride: boolean;
52
+ invalidateBatchDetailsCache: boolean;
49
53
  }, "expenseAutomationMissingReceiptsView/fetchBulkUploadBatches", never, never>, fetchBulkUploadBatchesSuccess: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
50
54
  batchList: BatchListItem[];
51
55
  selectedPeriod: MonthYearPeriod;
@@ -191,6 +191,16 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
191
191
  pusherBatchStatusUpdate(draft, action) {
192
192
  const batchStatus = action.payload;
193
193
  draft.bulkUpload.batchStatusById[batchStatus.batchId] = batchStatus;
194
+ /**
195
+ * Auto-matching banner (`phase === 'matching'`) must clear when the server reports this
196
+ * upload batch is done — otherwise the Missing tab keeps MatchingProgressBanner until
197
+ * batch-details APIs run; EmailUploadBanner should show again for the next upload.
198
+ */
199
+ if (batchStatus.status === 'completed' &&
200
+ draft.bulkUpload.phase === 'matching' &&
201
+ draft.bulkUpload.currentBatchId === batchStatus.batchId) {
202
+ draft.bulkUpload.phase = 'completed';
203
+ }
194
204
  },
195
205
  requestMissingReceiptsTabNavigation(draft, action) {
196
206
  draft.pendingMissingReceiptsTabNavigation = action.payload.tab;
@@ -268,15 +278,32 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
268
278
  };
269
279
  },
270
280
  fetchBulkUploadBatches: {
271
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
272
- reducer(draft, _action) {
281
+ reducer(draft, action) {
282
+ if (action.payload.invalidateBatchDetailsCache === true) {
283
+ draft.bulkUpload.batchDetailsById = {};
284
+ draft.bulkUpload.failedBatchDetailIds = [];
285
+ }
273
286
  draft.bulkUpload.batchListFetchState = {
274
287
  fetchState: 'In-Progress',
275
288
  error: undefined,
276
289
  };
277
290
  },
278
- prepare(cacheOverride = false) {
279
- return { payload: { cacheOverride } };
291
+ prepare(input) {
292
+ if (input === true || input === false) {
293
+ return {
294
+ payload: {
295
+ cacheOverride: input === true,
296
+ invalidateBatchDetailsCache: false,
297
+ },
298
+ };
299
+ }
300
+ const o = input ?? {};
301
+ return {
302
+ payload: {
303
+ cacheOverride: o.cacheOverride === true,
304
+ invalidateBatchDetailsCache: o.invalidateBatchDetailsCache === true,
305
+ },
306
+ };
280
307
  },
281
308
  },
282
309
  fetchBulkUploadBatchesSuccess(draft, action) {
@@ -286,6 +313,20 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
286
313
  fetchState: 'Completed',
287
314
  error: undefined,
288
315
  };
316
+ /**
317
+ * When the multi-batch-details epic has nothing to fetch (e.g. list rows not `completed`
318
+ * yet, or details already present), `phase` can stay `matching` even though we already
319
+ * have completed details for `currentBatchId` (from polling / Pusher). Sync so the UI can
320
+ * dismiss the Unmatched skeleton.
321
+ */
322
+ const currentId = draft.bulkUpload.currentBatchId;
323
+ if (draft.bulkUpload.phase === 'matching' &&
324
+ currentId != null) {
325
+ const existing = draft.bulkUpload.batchDetailsById[currentId];
326
+ if (existing?.status === 'completed') {
327
+ draft.bulkUpload.phase = 'completed';
328
+ }
329
+ }
289
330
  },
290
331
  fetchBulkUploadBatchesFailure(draft, action) {
291
332
  draft.bulkUpload.batchListFetchState = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "4.19.37-betaVR24",
3
+ "version": "4.19.37-betaVR26",
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",