@zeniai/client-epic-state 4.19.37-betaVR2 → 4.19.37-betaVR21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/lib/epic.d.ts +2 -1
  2. package/lib/epic.js +2 -1
  3. package/lib/esm/epic.js +2 -1
  4. package/lib/esm/index.js +2 -2
  5. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/bulkUploadReceiptsEpic.js +4 -8
  6. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchDetailsEpic.js +2 -2
  7. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic.js +6 -1
  8. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic.js +7 -3
  9. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic.js +36 -0
  10. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js +24 -15
  11. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +65 -24
  12. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js +32 -32
  13. package/lib/esm/view/expenseAutomationView/payload/missingReceiptsPayload.js +25 -18
  14. package/lib/esm/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +108 -22
  15. package/lib/esm/view/expenseAutomationView/selectors/missingReceiptsSelector.js +110 -13
  16. package/lib/index.d.ts +2 -2
  17. package/lib/index.js +36 -32
  18. package/lib/view/expenseAutomationView/epics/missingReceipts/bulkUploadReceiptsEpic.d.ts +2 -5
  19. package/lib/view/expenseAutomationView/epics/missingReceipts/bulkUploadReceiptsEpic.js +3 -7
  20. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchDetailsEpic.js +2 -2
  21. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic.js +6 -1
  22. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic.js +6 -2
  23. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic.d.ts +8 -0
  24. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic.js +43 -0
  25. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.d.ts +3 -2
  26. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js +25 -12
  27. package/lib/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.d.ts +11 -3
  28. package/lib/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +63 -22
  29. package/lib/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.d.ts +5 -7
  30. package/lib/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js +28 -28
  31. package/lib/view/expenseAutomationView/payload/missingReceiptsPayload.d.ts +17 -35
  32. package/lib/view/expenseAutomationView/payload/missingReceiptsPayload.js +27 -20
  33. package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.d.ts +20 -5
  34. package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +110 -23
  35. package/lib/view/expenseAutomationView/selectorTypes/missingReceiptsSelectorTypes.d.ts +7 -1
  36. package/lib/view/expenseAutomationView/selectors/missingReceiptsSelector.js +110 -13
  37. package/lib/view/expenseAutomationView/types/missingReceiptsViewState.d.ts +17 -2
  38. package/package.json +2 -2
  39. package/lib/tsconfig.typecheck.tsbuildinfo +0 -1
@@ -6,6 +6,8 @@ import { TransactionPayload } from '../../../entity/transaction/payloadTypes/tra
6
6
  import { SupportedTransactionPayload } from '../../../entity/transaction/transactionState';
7
7
  import { ZeniAPIStatus } from '../../../responsePayload';
8
8
  import type { BatchDetails, BatchListItem, BatchStatus, BulkUploadResultsTab, BulkUploadSortKey, BulkUploadState, CompletedSubTab, ManualSearchResult, MissingReceiptsViewState, MissingReceiptsViewUIState } from '../types/missingReceiptsViewState';
9
+ import type { MonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
10
+ export declare const getCompletedTransactionsCacheKey: (periodId: MonthYearPeriodId, sortKey: BulkUploadSortKey, sortOrder: SortOrder, subTab: CompletedSubTab) => string;
9
11
  export declare const initialBulkUploadState: BulkUploadState;
10
12
  export declare const initialState: MissingReceiptsViewState;
11
13
  export declare const fetchMissingReceipts: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[period: TimePeriod, minimumTransactionAmount: Amount, cacheOverride?: any, refreshViewInBackground?: any], {
@@ -34,9 +36,15 @@ export declare const fetchMissingReceipts: import("@reduxjs/toolkit").ActionCrea
34
36
  batchId: ID;
35
37
  }, "expenseAutomationMissingReceiptsView/bulkUploadReceiptsSuccess">, bulkUploadReceiptsFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
36
38
  error: ZeniAPIStatus;
37
- }, "expenseAutomationMissingReceiptsView/bulkUploadReceiptsFailure">, updateBulkUploadBatchStatus: import("@reduxjs/toolkit").ActionCreatorWithPayload<BatchStatus, "expenseAutomationMissingReceiptsView/updateBulkUploadBatchStatus">, pusherBatchStatusUpdate: import("@reduxjs/toolkit").ActionCreatorWithPayload<BatchStatus, "expenseAutomationMissingReceiptsView/pusherBatchStatusUpdate">, fetchBulkUploadBatchDetails: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"expenseAutomationMissingReceiptsView/fetchBulkUploadBatchDetails">, fetchBulkUploadBatchDetailsSuccess: import("@reduxjs/toolkit").ActionCreatorWithPayload<BatchDetails, "expenseAutomationMissingReceiptsView/fetchBulkUploadBatchDetailsSuccess">, fetchBulkUploadBatchDetailsFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
39
+ }, "expenseAutomationMissingReceiptsView/bulkUploadReceiptsFailure">, pusherBatchStatusUpdate: import("@reduxjs/toolkit").ActionCreatorWithPayload<BatchStatus, "expenseAutomationMissingReceiptsView/pusherBatchStatusUpdate">, requestMissingReceiptsTabNavigation: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
40
+ tab: BulkUploadResultsTab;
41
+ }, "expenseAutomationMissingReceiptsView/requestMissingReceiptsTabNavigation">, clearMissingReceiptsTabNavigation: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"expenseAutomationMissingReceiptsView/clearMissingReceiptsTabNavigation">, fetchBulkUploadBatchDetails: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"expenseAutomationMissingReceiptsView/fetchBulkUploadBatchDetails">, fetchBulkUploadBatchDetailsSuccess: import("@reduxjs/toolkit").ActionCreatorWithPayload<BatchDetails, "expenseAutomationMissingReceiptsView/fetchBulkUploadBatchDetailsSuccess">, fetchBulkUploadBatchDetailsFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
38
42
  error: ZeniAPIStatus;
39
- }, "expenseAutomationMissingReceiptsView/fetchBulkUploadBatchDetailsFailure">, storeBatchDetails: import("@reduxjs/toolkit").ActionCreatorWithPayload<BatchDetails, "expenseAutomationMissingReceiptsView/storeBatchDetails">, fetchBulkUploadBatches: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[cacheOverride?: any], {
43
+ }, "expenseAutomationMissingReceiptsView/fetchBulkUploadBatchDetailsFailure">, storeBatchDetails: import("@reduxjs/toolkit").ActionCreatorWithPayload<BatchDetails, "expenseAutomationMissingReceiptsView/storeBatchDetails">, batchDetailFetchFailed: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
44
+ batchId: ID;
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
+ error: ZeniAPIStatus;
47
+ }, "expenseAutomationMissingReceiptsView/fetchMoreBatchDetailsFailure">, fetchBulkUploadBatches: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[cacheOverride?: any], {
40
48
  cacheOverride: any;
41
49
  }, "expenseAutomationMissingReceiptsView/fetchBulkUploadBatches", never, never>, fetchBulkUploadBatchesSuccess: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
42
50
  batchList: BatchListItem[];
@@ -59,12 +67,19 @@ export declare const fetchMissingReceipts: import("@reduxjs/toolkit").ActionCrea
59
67
  }, "expenseAutomationMissingReceiptsView/confirmBulkUploadMatchFailure">, setBulkUploadResultsTab: import("@reduxjs/toolkit").ActionCreatorWithPayload<BulkUploadResultsTab, "expenseAutomationMissingReceiptsView/setBulkUploadResultsTab">, setBulkUploadCompletedSubTab: import("@reduxjs/toolkit").ActionCreatorWithPayload<CompletedSubTab, "expenseAutomationMissingReceiptsView/setBulkUploadCompletedSubTab">, setBulkUploadSortConfig: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
60
68
  sortKey: BulkUploadSortKey;
61
69
  sortOrder: SortOrder;
62
- }, "expenseAutomationMissingReceiptsView/setBulkUploadSortConfig">, clearBulkUpload: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"expenseAutomationMissingReceiptsView/clearBulkUpload">, searchTransactionsForManualMatch: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[query: string], {
70
+ }, "expenseAutomationMissingReceiptsView/setBulkUploadSortConfig">, clearBulkUpload: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"expenseAutomationMissingReceiptsView/clearBulkUpload">, searchTransactionsForManualMatch: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[query: string, pageToken?: string | null | undefined], {
63
71
  query: string;
64
- }, "expenseAutomationMissingReceiptsView/searchTransactionsForManualMatch", never, never>, searchTransactionsForManualMatchSuccess: import("@reduxjs/toolkit").ActionCreatorWithPayload<ManualSearchResult[], "expenseAutomationMissingReceiptsView/searchTransactionsForManualMatchSuccess">, searchTransactionsForManualMatchFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
72
+ pageToken: string | undefined;
73
+ }, "expenseAutomationMissingReceiptsView/searchTransactionsForManualMatch", never, never>, searchTransactionsForManualMatchSuccess: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
74
+ append: boolean;
75
+ nextPageToken: string | null;
76
+ results: ManualSearchResult[];
77
+ totalCount: number;
78
+ }, "expenseAutomationMissingReceiptsView/searchTransactionsForManualMatchSuccess">, searchTransactionsForManualMatchFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
65
79
  error: ZeniAPIStatus;
66
- }, "expenseAutomationMissingReceiptsView/searchTransactionsForManualMatchFailure">, clearManualSearchResults: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"expenseAutomationMissingReceiptsView/clearManualSearchResults">, fetchCompletedTransactions: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[pageToken?: string | undefined], {
80
+ }, "expenseAutomationMissingReceiptsView/searchTransactionsForManualMatchFailure">, clearManualSearchResults: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"expenseAutomationMissingReceiptsView/clearManualSearchResults">, fetchCompletedTransactions: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[pageToken?: string | undefined, cacheOverride?: boolean | undefined], {
67
81
  pageToken: string | undefined;
82
+ cacheOverride: boolean | undefined;
68
83
  }, "expenseAutomationMissingReceiptsView/fetchCompletedTransactions", never, never>, fetchCompletedTransactionsSuccess: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
69
84
  isInitialLoad: boolean;
70
85
  nextPageToken: string | null;
@@ -1,11 +1,14 @@
1
1
  "use strict";
2
2
  var _a;
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.fetchCompletedTransactionsFailure = exports.fetchCompletedTransactionsSuccess = exports.fetchCompletedTransactions = 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.storeBatchDetails = exports.fetchBulkUploadBatchDetailsFailure = exports.fetchBulkUploadBatchDetailsSuccess = exports.fetchBulkUploadBatchDetails = exports.pusherBatchStatusUpdate = exports.updateBulkUploadBatchStatus = exports.bulkUploadReceiptsFailure = exports.bulkUploadReceiptsSuccess = exports.updateBulkUploadProgress = exports.bulkUploadReceipts = exports.clearExpenseAutomationMissingReceiptsView = exports.markMissingReceiptAsDone = exports.updateMissingReceiptsUIState = exports.updateMissingReceiptUploadState = exports.uploadMissingReceiptSuccess = exports.fetchMissingReceiptsFailure = exports.fetchMissingReceiptsSuccess = exports.fetchMissingReceipts = exports.initialState = exports.initialBulkUploadState = void 0;
4
+ exports.fetchCompletedTransactionsFailure = exports.fetchCompletedTransactionsSuccess = exports.fetchCompletedTransactions = 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.fetchBulkUploadBatchDetails = exports.clearMissingReceiptsTabNavigation = exports.requestMissingReceiptsTabNavigation = exports.pusherBatchStatusUpdate = exports.bulkUploadReceiptsFailure = exports.bulkUploadReceiptsSuccess = exports.updateBulkUploadProgress = exports.bulkUploadReceipts = exports.clearExpenseAutomationMissingReceiptsView = exports.markMissingReceiptAsDone = exports.updateMissingReceiptsUIState = exports.updateMissingReceiptUploadState = exports.uploadMissingReceiptSuccess = exports.fetchMissingReceiptsFailure = exports.fetchMissingReceiptsSuccess = exports.fetchMissingReceipts = exports.initialState = exports.initialBulkUploadState = exports.getCompletedTransactionsCacheKey = void 0;
5
5
  const toolkit_1 = require("@reduxjs/toolkit");
6
6
  const timePeriod_1 = require("../../../commonStateTypes/timePeriod");
7
+ const getCompletedTransactionsCacheKey = (periodId, sortKey, sortOrder, subTab) => `completed-${subTab}-${sortKey}-${sortOrder === 'ascending' ? 'asc' : 'desc'}-${periodId}`;
8
+ exports.getCompletedTransactionsCacheKey = getCompletedTransactionsCacheKey;
7
9
  exports.initialBulkUploadState = {
8
10
  batchDetailsById: {},
11
+ batchDetailsPaginationState: { fetchState: 'Not-Started', error: undefined },
9
12
  batchListByPeriod: {},
10
13
  batchListFetchState: { fetchState: 'Not-Started', error: undefined },
11
14
  batchStatusById: {},
@@ -14,10 +17,15 @@ exports.initialBulkUploadState = {
14
17
  confirmMatchStatus: { fetchState: 'Not-Started', error: undefined },
15
18
  currentBatchId: undefined,
16
19
  currentResultsTab: 'unmatched',
20
+ failedBatchDetailIds: [],
17
21
  manualSearch: {
18
22
  fetchState: { fetchState: 'Not-Started', error: undefined },
23
+ isLoadingMore: false,
24
+ nextPageToken: null,
25
+ pageSize: 25,
19
26
  results: [],
20
27
  searchQuery: '',
28
+ totalCount: 0,
21
29
  },
22
30
  phase: 'idle',
23
31
  sortKey: 'date',
@@ -27,6 +35,7 @@ exports.initialBulkUploadState = {
27
35
  };
28
36
  exports.initialState = {
29
37
  bulkUpload: exports.initialBulkUploadState,
38
+ pendingMissingReceiptsTabNavigation: null,
30
39
  uploadReceiptStatusById: {},
31
40
  refreshStatus: {
32
41
  fetchState: 'Not-Started',
@@ -179,14 +188,16 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
179
188
  };
180
189
  draft.bulkUpload.phase = 'idle';
181
190
  },
182
- updateBulkUploadBatchStatus(draft, action) {
183
- const batchStatus = action.payload;
184
- draft.bulkUpload.batchStatusById[batchStatus.batchId] = batchStatus;
185
- },
186
191
  pusherBatchStatusUpdate(draft, action) {
187
192
  const batchStatus = action.payload;
188
193
  draft.bulkUpload.batchStatusById[batchStatus.batchId] = batchStatus;
189
194
  },
195
+ requestMissingReceiptsTabNavigation(draft, action) {
196
+ draft.pendingMissingReceiptsTabNavigation = action.payload.tab;
197
+ },
198
+ clearMissingReceiptsTabNavigation(draft) {
199
+ draft.pendingMissingReceiptsTabNavigation = null;
200
+ },
190
201
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
191
202
  fetchBulkUploadBatchDetails(_draft) {
192
203
  // trigger epic, no state change needed
@@ -203,6 +214,58 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
203
214
  storeBatchDetails(draft, action) {
204
215
  const details = action.payload;
205
216
  draft.bulkUpload.batchDetailsById[details.batchId] = details;
217
+ /**
218
+ * Multi-batch fetch paths use `storeBatchDetails` (not `fetchBulkUploadBatchDetailsSuccess`).
219
+ * Without this, `phase` stays `matching` after upload forever and the Unmatched tab keeps
220
+ * showing the skeleton (`bulkUploadPhase === 'matching'` in web-components).
221
+ */
222
+ if (draft.bulkUpload.phase === 'matching' &&
223
+ draft.bulkUpload.currentBatchId === details.batchId) {
224
+ draft.bulkUpload.phase = 'completed';
225
+ }
226
+ },
227
+ batchDetailFetchFailed(draft, action) {
228
+ const { batchId } = action.payload;
229
+ if (!draft.bulkUpload.failedBatchDetailIds.includes(batchId)) {
230
+ draft.bulkUpload.failedBatchDetailIds.push(batchId);
231
+ }
232
+ if (draft.bulkUpload.phase === 'matching' &&
233
+ draft.bulkUpload.currentBatchId === batchId) {
234
+ draft.bulkUpload.phase = 'completed';
235
+ }
236
+ },
237
+ setInitialBatchDetailsLoading(draft) {
238
+ draft.bulkUpload.batchDetailsPaginationState = {
239
+ fetchState: 'In-Progress',
240
+ error: undefined,
241
+ };
242
+ },
243
+ fetchMoreBatchDetails(_draft) {
244
+ _draft.bulkUpload.batchDetailsPaginationState = {
245
+ fetchState: 'In-Progress',
246
+ error: undefined,
247
+ };
248
+ },
249
+ fetchMoreBatchDetailsComplete(draft) {
250
+ draft.bulkUpload.batchDetailsPaginationState = {
251
+ fetchState: 'Completed',
252
+ error: undefined,
253
+ };
254
+ const currentId = draft.bulkUpload.currentBatchId;
255
+ const currentDetails = currentId != null
256
+ ? draft.bulkUpload.batchDetailsById[currentId]
257
+ : undefined;
258
+ if (draft.bulkUpload.phase === 'matching' &&
259
+ currentId != null &&
260
+ currentDetails?.status === 'completed') {
261
+ draft.bulkUpload.phase = 'completed';
262
+ }
263
+ },
264
+ fetchMoreBatchDetailsFailure(draft, action) {
265
+ draft.bulkUpload.batchDetailsPaginationState = {
266
+ fetchState: 'Error',
267
+ error: action.payload.error,
268
+ };
206
269
  },
207
270
  fetchBulkUploadBatches: {
208
271
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -277,7 +340,6 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
277
340
  },
278
341
  setBulkUploadCompletedSubTab(draft, action) {
279
342
  draft.bulkUpload.completedSubTab = action.payload;
280
- draft.bulkUpload.completedTransactionsByPeriod = {};
281
343
  },
282
344
  setBulkUploadSortConfig(draft, action) {
283
345
  draft.bulkUpload.sortKey = action.payload.sortKey;
@@ -285,28 +347,51 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
285
347
  },
286
348
  clearBulkUpload(draft) {
287
349
  Object.assign(draft.bulkUpload, exports.initialBulkUploadState);
350
+ draft.pendingMissingReceiptsTabNavigation = null;
288
351
  },
289
352
  searchTransactionsForManualMatch: {
290
353
  reducer(draft, action) {
291
- draft.bulkUpload.manualSearch.searchQuery = action.payload.query;
292
- draft.bulkUpload.manualSearch.fetchState = {
293
- fetchState: 'In-Progress',
294
- error: undefined,
295
- };
296
- draft.bulkUpload.manualSearch.results = [];
354
+ const { query, pageToken } = action.payload;
355
+ const isLoadMore = pageToken != null;
356
+ draft.bulkUpload.manualSearch.searchQuery = query;
357
+ if (isLoadMore) {
358
+ draft.bulkUpload.manualSearch.isLoadingMore = true;
359
+ }
360
+ else {
361
+ draft.bulkUpload.manualSearch.isLoadingMore = false;
362
+ draft.bulkUpload.manualSearch.results = [];
363
+ draft.bulkUpload.manualSearch.nextPageToken = null;
364
+ draft.bulkUpload.manualSearch.totalCount = 0;
365
+ draft.bulkUpload.manualSearch.fetchState = {
366
+ fetchState: 'In-Progress',
367
+ error: undefined,
368
+ };
369
+ }
297
370
  },
298
- prepare(query) {
299
- return { payload: { query } };
371
+ prepare(query, pageToken) {
372
+ return { payload: { query, pageToken: pageToken ?? undefined } };
300
373
  },
301
374
  },
302
375
  searchTransactionsForManualMatchSuccess(draft, action) {
303
- draft.bulkUpload.manualSearch.results = action.payload;
376
+ const { append, nextPageToken, results, totalCount } = action.payload;
377
+ if (append) {
378
+ const merged = [...draft.bulkUpload.manualSearch.results, ...results];
379
+ const byId = new Map(merged.map((r) => [r.transactionId, r]));
380
+ draft.bulkUpload.manualSearch.results = Array.from(byId.values());
381
+ }
382
+ else {
383
+ draft.bulkUpload.manualSearch.results = results;
384
+ }
385
+ draft.bulkUpload.manualSearch.nextPageToken = nextPageToken;
386
+ draft.bulkUpload.manualSearch.totalCount = totalCount;
387
+ draft.bulkUpload.manualSearch.isLoadingMore = false;
304
388
  draft.bulkUpload.manualSearch.fetchState = {
305
389
  fetchState: 'Completed',
306
390
  error: undefined,
307
391
  };
308
392
  },
309
393
  searchTransactionsForManualMatchFailure(draft, action) {
394
+ draft.bulkUpload.manualSearch.isLoadingMore = false;
310
395
  draft.bulkUpload.manualSearch.fetchState = {
311
396
  fetchState: 'Error',
312
397
  error: action.payload.error,
@@ -332,15 +417,16 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
332
417
  }
333
418
  }
334
419
  },
335
- prepare(pageToken) {
336
- return { payload: { pageToken } };
420
+ prepare(pageToken, cacheOverride) {
421
+ return { payload: { pageToken, cacheOverride } };
337
422
  },
338
423
  },
339
424
  fetchCompletedTransactionsSuccess(draft, action) {
340
425
  const { transactionIds, nextPageToken, totalCount, selectedPeriod, isInitialLoad } = action.payload;
341
426
  const periodId = (0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod);
427
+ const cacheKey = (0, exports.getCompletedTransactionsCacheKey)(periodId, draft.bulkUpload.sortKey, draft.bulkUpload.sortOrder, draft.bulkUpload.completedSubTab);
342
428
  if (isInitialLoad) {
343
- draft.bulkUpload.completedTransactionsByPeriod[periodId] = {
429
+ draft.bulkUpload.completedTransactionsByPeriod[cacheKey] = {
344
430
  transactionIds,
345
431
  nextPageToken,
346
432
  totalCount,
@@ -348,7 +434,7 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
348
434
  };
349
435
  }
350
436
  else {
351
- const existing = draft.bulkUpload.completedTransactionsByPeriod[periodId];
437
+ const existing = draft.bulkUpload.completedTransactionsByPeriod[cacheKey];
352
438
  if (existing != null) {
353
439
  existing.transactionIds.push(...transactionIds);
354
440
  existing.nextPageToken = nextPageToken;
@@ -356,7 +442,7 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
356
442
  existing.fetchState = { fetchState: 'Completed', error: undefined };
357
443
  }
358
444
  else {
359
- draft.bulkUpload.completedTransactionsByPeriod[periodId] = {
445
+ draft.bulkUpload.completedTransactionsByPeriod[cacheKey] = {
360
446
  transactionIds,
361
447
  nextPageToken,
362
448
  totalCount,
@@ -367,7 +453,8 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
367
453
  },
368
454
  fetchCompletedTransactionsFailure(draft, action) {
369
455
  const periodId = (0, timePeriod_1.toMonthYearPeriodId)(action.payload.selectedPeriod);
370
- const existing = draft.bulkUpload.completedTransactionsByPeriod[periodId];
456
+ const cacheKey = (0, exports.getCompletedTransactionsCacheKey)(periodId, draft.bulkUpload.sortKey, draft.bulkUpload.sortOrder, draft.bulkUpload.completedSubTab);
457
+ const existing = draft.bulkUpload.completedTransactionsByPeriod[cacheKey];
371
458
  if (existing != null) {
372
459
  existing.fetchState = {
373
460
  fetchState: 'Error',
@@ -375,7 +462,7 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
375
462
  };
376
463
  }
377
464
  else {
378
- draft.bulkUpload.completedTransactionsByPeriod[periodId] = {
465
+ draft.bulkUpload.completedTransactionsByPeriod[cacheKey] = {
379
466
  fetchState: { fetchState: 'Error', error: action.payload.error },
380
467
  nextPageToken: null,
381
468
  totalCount: 0,
@@ -387,7 +474,7 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
387
474
  });
388
475
  _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,
389
476
  // Bulk Upload
390
- exports.bulkUploadReceipts = _a.bulkUploadReceipts, exports.updateBulkUploadProgress = _a.updateBulkUploadProgress, exports.bulkUploadReceiptsSuccess = _a.bulkUploadReceiptsSuccess, exports.bulkUploadReceiptsFailure = _a.bulkUploadReceiptsFailure, exports.updateBulkUploadBatchStatus = _a.updateBulkUploadBatchStatus, exports.pusherBatchStatusUpdate = _a.pusherBatchStatusUpdate, exports.fetchBulkUploadBatchDetails = _a.fetchBulkUploadBatchDetails, exports.fetchBulkUploadBatchDetailsSuccess = _a.fetchBulkUploadBatchDetailsSuccess, exports.fetchBulkUploadBatchDetailsFailure = _a.fetchBulkUploadBatchDetailsFailure, exports.storeBatchDetails = _a.storeBatchDetails, 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,
477
+ exports.bulkUploadReceipts = _a.bulkUploadReceipts, exports.updateBulkUploadProgress = _a.updateBulkUploadProgress, exports.bulkUploadReceiptsSuccess = _a.bulkUploadReceiptsSuccess, exports.bulkUploadReceiptsFailure = _a.bulkUploadReceiptsFailure, exports.pusherBatchStatusUpdate = _a.pusherBatchStatusUpdate, exports.requestMissingReceiptsTabNavigation = _a.requestMissingReceiptsTabNavigation, exports.clearMissingReceiptsTabNavigation = _a.clearMissingReceiptsTabNavigation, exports.fetchBulkUploadBatchDetails = _a.fetchBulkUploadBatchDetails, 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,
391
478
  // Completed Transactions
392
479
  exports.fetchCompletedTransactions = _a.fetchCompletedTransactions, exports.fetchCompletedTransactionsSuccess = _a.fetchCompletedTransactionsSuccess, exports.fetchCompletedTransactionsFailure = _a.fetchCompletedTransactionsFailure;
393
480
  exports.default = expenseAutomationMissingReceiptsView.reducer;
@@ -15,9 +15,11 @@ export interface ResolvedCandidate {
15
15
  }
16
16
  export interface ResolvedBatchFile {
17
17
  attachmentId: ID;
18
+ batchId: ID;
19
+ fileDownloadUrl: string | null;
18
20
  fileId: ID;
19
21
  filename: string;
20
- filePreviewUrl: string;
22
+ filePreviewUrl: string | null;
21
23
  status: BatchFileStatus;
22
24
  candidates?: ResolvedCandidate[];
23
25
  matchedTransaction?: SupportedTransaction;
@@ -25,6 +27,7 @@ export interface ResolvedBatchFile {
25
27
  }
26
28
  export interface BulkUploadSelectorData {
27
29
  batchDetails: ResolvedBatchDetails | undefined;
30
+ batchDetailsPaginationState: FetchStateAndError;
28
31
  batchList: BatchListItem[];
29
32
  batchListFetchState: FetchStateAndError;
30
33
  batchStatus: BatchStatus | undefined;
@@ -32,6 +35,7 @@ export interface BulkUploadSelectorData {
32
35
  completedTransactions: CompletedTransactionsSelectorData;
33
36
  confirmMatchStatus: FetchStateAndError;
34
37
  currentResultsTab: BulkUploadResultsTab;
38
+ hasMoreBatchDetails: boolean;
35
39
  manualSearch: ManualSearchState;
36
40
  matchedFiles: ResolvedBatchFile[];
37
41
  pastBatches: BatchListItem[];
@@ -64,6 +68,8 @@ export interface ResolvedBatchDetails {
64
68
  export interface ExpenseAutomationMissingReceiptsViewSelector extends SelectorView {
65
69
  bulkUpload: BulkUploadSelectorData;
66
70
  completionStatus: CompletionStatusType;
71
+ /** When set, UI should switch bulk-upload sub-tab once (e.g. Unmatched on batch completion). */
72
+ pendingMissingReceiptsTabNavigation: BulkUploadResultsTab | null;
67
73
  refreshStatus: FetchStateAndError;
68
74
  transactions: SupportedTransaction[];
69
75
  uiState: MissingReceiptsViewUIState;
@@ -9,6 +9,8 @@ const timePeriod_1 = require("../../../commonStateTypes/timePeriod");
9
9
  const tenantSelector_1 = require("../../../entity/tenant/tenantSelector");
10
10
  const transactionSelector_1 = require("../../../entity/transaction/transactionSelector");
11
11
  const expenseAutomationViewSelectorTypes_1 = require("../selectorTypes/expenseAutomationViewSelectorTypes");
12
+ const missingReceiptsPayload_1 = require("../payload/missingReceiptsPayload");
13
+ const missingReceiptsViewReducer_1 = require("../reducers/missingReceiptsViewReducer");
12
14
  function getExpenseAutomationMissingReceiptsView(state) {
13
15
  const { expenseAutomationMissingReceiptsViewState, expenseAutomationViewState, transactionState, monthEndCloseChecksState, } = state;
14
16
  const currentTenant = (0, tenantSelector_1.getCurrentTenant)(state);
@@ -53,14 +55,34 @@ function getExpenseAutomationMissingReceiptsView(state) {
53
55
  const completionStatus = missingReceiptsCompletionStatus != null
54
56
  ? missingReceiptsCompletionStatus
55
57
  : 'incomplete';
56
- const { bulkUpload } = expenseAutomationMissingReceiptsViewState;
58
+ const { bulkUpload, pendingMissingReceiptsTabNavigation, } = expenseAutomationMissingReceiptsViewState;
57
59
  const currentBatchStatus = bulkUpload.currentBatchId != null
58
60
  ? bulkUpload.batchStatusById[bulkUpload.currentBatchId]
59
61
  : undefined;
60
- const currentBatchDetails = bulkUpload.currentBatchId != null
61
- ? bulkUpload.batchDetailsById[bulkUpload.currentBatchId]
62
+ const batchList = monthYearPeriodId != null
63
+ ? bulkUpload.batchListByPeriod[monthYearPeriodId] ?? []
64
+ : [];
65
+ /** API returns newest-first (`sort_order: desc`); primary batch uses oldest-first order. */
66
+ const batchListChronological = (0, orderBy_1.default)(batchList, (b) => new Date(b.createdAt).valueOf(), 'asc');
67
+ /**
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
+ * 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
+ */
73
+ const primaryBatchIdFromDetailsOrder = getPrimaryBatchIdFromBatchListOrder(batchListChronological, bulkUpload.batchDetailsById, bulkUpload.failedBatchDetailIds);
74
+ /** During upload/matching, the in-flight batch may not appear in batchList yet — prefer currentBatchId. */
75
+ const unmatchedSectionBatchId = bulkUpload.phase === 'matching' || bulkUpload.phase === 'uploading'
76
+ ? bulkUpload.currentBatchId ?? primaryBatchIdFromDetailsOrder
77
+ : primaryBatchIdFromDetailsOrder;
78
+ const completedBatchesInApiOrder = batchList.filter((b) => (0, missingReceiptsPayload_1.isBatchListStatusCompleted)(b.status));
79
+ const currentBatchDetails = unmatchedSectionBatchId != null
80
+ ? bulkUpload.batchDetailsById[unmatchedSectionBatchId]
62
81
  : undefined;
63
- const resolvedFiles = currentBatchDetails?.files.map((file) => resolveBatchFile(file, transactionState));
82
+ const resolvedFiles = currentBatchDetails?.files.map((file) => resolveBatchFile(file, currentBatchDetails.batchId, transactionState));
83
+ const failedIds = new Set(bulkUpload.failedBatchDetailIds);
84
+ const hasMoreBatchDetails = completedBatchesInApiOrder.some((b) => bulkUpload.batchDetailsById[b.batchId] == null &&
85
+ !failedIds.has(b.batchId));
64
86
  const getSortValue = (file) => {
65
87
  const t = file.matchedTransaction;
66
88
  if (t == null) {
@@ -98,11 +120,14 @@ function getExpenseAutomationMissingReceiptsView(state) {
98
120
  : 0,
99
121
  }
100
122
  : { total: 0, processed: 0, percentage: 0 };
101
- const batchList = monthYearPeriodId != null
102
- ? bulkUpload.batchListByPeriod[monthYearPeriodId] ?? []
103
- : [];
104
123
  const batchListFetchState = bulkUpload.batchListFetchState;
105
- const pastBatches = batchList.filter((b) => b.batchId !== bulkUpload.currentBatchId);
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
+ */
128
+ const pastBatches = unmatchedSectionBatchId != null
129
+ ? batchList.filter((b) => b.batchId !== unmatchedSectionBatchId)
130
+ : batchList;
106
131
  const pastUnmatchedFiles = pastBatches.flatMap((b) => {
107
132
  const details = bulkUpload.batchDetailsById[b.batchId];
108
133
  if (details == null) {
@@ -110,10 +135,13 @@ function getExpenseAutomationMissingReceiptsView(state) {
110
135
  }
111
136
  return details.files
112
137
  .filter((f) => f.status === 'un_matched' || f.status === 'no_match')
113
- .map((f) => resolveBatchFile(f, transactionState));
138
+ .map((f) => resolveBatchFile(f, b.batchId, transactionState));
114
139
  });
115
- const completedTransactionsForPeriod = monthYearPeriodId != null
116
- ? bulkUpload.completedTransactionsByPeriod[monthYearPeriodId]
140
+ const completedTransactionsCacheKey = monthYearPeriodId != null
141
+ ? (0, missingReceiptsViewReducer_1.getCompletedTransactionsCacheKey)(monthYearPeriodId, bulkUpload.sortKey, bulkUpload.sortOrder, bulkUpload.completedSubTab)
142
+ : undefined;
143
+ const completedTransactionsForPeriod = completedTransactionsCacheKey != null
144
+ ? bulkUpload.completedTransactionsByPeriod[completedTransactionsCacheKey]
117
145
  : undefined;
118
146
  const completedTransactionIds = completedTransactionsForPeriod?.transactionIds ?? [];
119
147
  const resolvedCompletedTransactions = (0, transactionSelector_1.getSupportedTransactionsByIds)(transactionState, completedTransactionIds);
@@ -126,6 +154,13 @@ function getExpenseAutomationMissingReceiptsView(state) {
126
154
  summary: currentBatchDetails.summary,
127
155
  }
128
156
  : undefined;
157
+ const manualSearchResults = bulkUpload.manualSearch.results.map((r) => {
158
+ const t = (0, transactionSelector_1.getSupportedTransactionById)(transactionState, r.transactionId);
159
+ return {
160
+ ...r,
161
+ vendorLogo: r.vendorLogo ?? t?.logo?.href,
162
+ };
163
+ });
129
164
  return {
130
165
  fetchState,
131
166
  error,
@@ -134,6 +169,7 @@ function getExpenseAutomationMissingReceiptsView(state) {
134
169
  refreshStatus,
135
170
  transactions: sortedTransactions,
136
171
  completionStatus,
172
+ pendingMissingReceiptsTabNavigation: pendingMissingReceiptsTabNavigation ?? null,
137
173
  bulkUpload: {
138
174
  phase: bulkUpload.phase,
139
175
  uploadStatus: bulkUpload.uploadStatus,
@@ -160,12 +196,71 @@ function getExpenseAutomationMissingReceiptsView(state) {
160
196
  pastBatches,
161
197
  pastUnmatchedFiles,
162
198
  confirmMatchStatus: bulkUpload.confirmMatchStatus,
163
- manualSearch: bulkUpload.manualSearch,
199
+ hasMoreBatchDetails,
200
+ batchDetailsPaginationState: bulkUpload.batchDetailsPaginationState,
201
+ manualSearch: {
202
+ ...bulkUpload.manualSearch,
203
+ results: manualSearchResults,
204
+ },
164
205
  uploadProgress: bulkUpload.uploadProgress,
165
206
  },
166
207
  };
167
208
  }
168
- function resolveBatchFile(file, transactionState) {
209
+ function batchDetailsHasUnmatchedWork(details) {
210
+ return details.files.some((f) => f.status === 'un_matched' || f.status === 'no_match');
211
+ }
212
+ /** List row summary before batch details load — aligns with unmatched / possible-match work. */
213
+ function batchListItemHasUnmatchedWork(batch) {
214
+ return batch.noMatchCount > 0 || batch.possibleMatchesCount > 0;
215
+ }
216
+ /**
217
+ * `batchList` must be in chronological order (oldest first). Picks the primary batch for the
218
+ * top "Unmatched" section. Skips failed detail fetches.
219
+ *
220
+ * If details are not loaded for an older batch but the list row reports no unmatched work, we
221
+ * skip that batch so newer batches with loaded details can be primary (avoids an empty tab when
222
+ * the first fetch page only loaded newer batches). If the list reports unmatched work but details
223
+ * are still loading, we wait (undefined) so primary order stays correct once details arrive.
224
+ */
225
+ function getPrimaryBatchIdFromBatchListOrder(batchListChronological, batchDetailsById, failedBatchDetailIds) {
226
+ const failedIds = new Set(failedBatchDetailIds);
227
+ const anyCompletedBatchHasUnmatchedWork = batchListChronological.some((b) => {
228
+ if (failedIds.has(b.batchId)) {
229
+ return false;
230
+ }
231
+ const d = batchDetailsById[b.batchId];
232
+ if (d != null &&
233
+ d.status === 'completed' &&
234
+ batchDetailsHasUnmatchedWork(d)) {
235
+ return true;
236
+ }
237
+ return d == null && batchListItemHasUnmatchedWork(b);
238
+ });
239
+ for (const batch of batchListChronological) {
240
+ if (failedIds.has(batch.batchId)) {
241
+ continue;
242
+ }
243
+ const details = batchDetailsById[batch.batchId];
244
+ if (details == null) {
245
+ if (batchListItemHasUnmatchedWork(batch)) {
246
+ return undefined;
247
+ }
248
+ continue;
249
+ }
250
+ if (details.status !== 'completed') {
251
+ continue;
252
+ }
253
+ if (anyCompletedBatchHasUnmatchedWork) {
254
+ if (batchDetailsHasUnmatchedWork(details)) {
255
+ return batch.batchId;
256
+ }
257
+ continue;
258
+ }
259
+ return batch.batchId;
260
+ }
261
+ return undefined;
262
+ }
263
+ function resolveBatchFile(file, batchId, transactionState) {
169
264
  const matchedTransaction = file.matchedTransactionId != null
170
265
  ? (0, transactionSelector_1.getSupportedTransactionById)(transactionState, file.matchedTransactionId)
171
266
  : undefined;
@@ -179,6 +274,8 @@ function resolveBatchFile(file, transactionState) {
179
274
  .filter((c) => c != null);
180
275
  return {
181
276
  attachmentId: file.attachmentId,
277
+ batchId,
278
+ fileDownloadUrl: file.fileDownloadUrl,
182
279
  fileId: file.fileId,
183
280
  filename: file.filename,
184
281
  filePreviewUrl: file.filePreviewUrl,
@@ -36,9 +36,10 @@ export interface MatchCandidate {
36
36
  }
37
37
  export interface BatchFile {
38
38
  attachmentId: ID;
39
+ fileDownloadUrl: string | null;
39
40
  fileId: ID;
40
41
  filename: string;
41
- filePreviewUrl: string;
42
+ filePreviewUrl: string | null;
42
43
  status: BatchFileStatus;
43
44
  candidates?: CandidateRef[];
44
45
  matchedTransactionId?: ID;
@@ -85,11 +86,18 @@ export interface ManualSearchResult {
85
86
  transactionId: ID;
86
87
  transactionType: string;
87
88
  vendorName: string;
89
+ /** Present when API/merged transaction state supplies a logo URL. */
90
+ vendorLogo?: string;
88
91
  }
89
92
  export interface ManualSearchState {
90
93
  fetchState: FetchStateAndError;
94
+ /** True while appending the next page (initial search uses fetchState only). */
95
+ isLoadingMore: boolean;
96
+ nextPageToken: string | null;
97
+ pageSize: number;
91
98
  results: ManualSearchResult[];
92
99
  searchQuery: string;
100
+ totalCount: number;
93
101
  }
94
102
  export type CompletedTransactionsMatchType = 'all' | 'ai_accountant' | 'manual';
95
103
  export interface CompletedTransactionsState {
@@ -100,13 +108,15 @@ export interface CompletedTransactionsState {
100
108
  }
101
109
  export interface BulkUploadState {
102
110
  batchDetailsById: Record<ID, BatchDetails>;
111
+ batchDetailsPaginationState: FetchStateAndError;
103
112
  batchListByPeriod: Record<MonthYearPeriodId, BatchListItem[]>;
104
113
  batchListFetchState: FetchStateAndError;
105
114
  batchStatusById: Record<ID, BatchStatus>;
106
115
  completedSubTab: CompletedSubTab;
107
- completedTransactionsByPeriod: Record<MonthYearPeriodId, CompletedTransactionsState>;
116
+ completedTransactionsByPeriod: Record<string, CompletedTransactionsState>;
108
117
  confirmMatchStatus: FetchStateAndError;
109
118
  currentResultsTab: BulkUploadResultsTab;
119
+ failedBatchDetailIds: ID[];
110
120
  manualSearch: ManualSearchState;
111
121
  phase: BulkUploadPhase;
112
122
  sortKey: BulkUploadSortKey;
@@ -117,6 +127,11 @@ export interface BulkUploadState {
117
127
  }
118
128
  export interface MissingReceiptsViewState extends FetchedState {
119
129
  bulkUpload: BulkUploadState;
130
+ /**
131
+ * When set, the app shell should switch the Missing Receipts sub-tab (e.g. to Unmatched)
132
+ * once, then clear via `clearMissingReceiptsTabNavigation`.
133
+ */
134
+ pendingMissingReceiptsTabNavigation: BulkUploadResultsTab | null;
120
135
  refreshStatus: FetchStateAndError;
121
136
  transactionIdsBySelectedPeriod: Record<MonthYearPeriodId, ID[]>;
122
137
  uiState: MissingReceiptsViewUIState;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "4.19.37-betaVR2",
3
+ "version": "4.19.37-betaVR21",
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",
@@ -111,7 +111,7 @@
111
111
  "find-dead-code": "ts-prune | grep -v '(used in module)'",
112
112
  "find-unused-exports": "ts-unused-exports ./tsconfig.json",
113
113
  "circular-dependency": "npx madge --circular --extensions ts ./src",
114
- "build": "concurrently --kill-others-on-fail --handle-input \"echo 'Running ESLint...' && eslint . --ext .js,.jsx,.ts,.tsx\" \"echo 'TypeScript build in progress...' && time tsc && echo 'ESM build...' && tsc -p tsconfig.esm.json\" \"echo 'Running tests and typecheck...' && pnpm test\" && rimraf \"lib/**/__mocks__\" \"lib/**/__testHelpers__\"",
114
+ "build": "concurrently --kill-others-on-fail --handle-input \"echo 'Running ESLint...' && eslint . --ext .js,.jsx,.ts,.tsx\" \"echo 'TypeScript build in progress...' && time tsc && echo 'ESM build...' && tsc -p tsconfig.esm.json\"",
115
115
  "only-build": "eslint . --ext .js,.jsx,.ts,.tsx && time tsc && tsc -p tsconfig.esm.json && rimraf \"lib/**/__mocks__\" \"lib/**/__testHelpers__\"",
116
116
  "format": "prettier --write --ignore-unknown \"src/**/*\" && pnpm lint",
117
117
  "format-watch": "(git diff --name-only --diff-filter=ACM && git ls-files --others --exclude-standard) | grep -E '\\.(ts|tsx)$' | xargs prettier --write",