@zeniai/client-epic-state 4.19.94 → 4.19.95

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 (122) hide show
  1. package/lib/entity/account/accountPayload.d.ts +2 -0
  2. package/lib/entity/account/accountPayload.js +2 -0
  3. package/lib/entity/account/accountState.d.ts +2 -0
  4. package/lib/entity/depositAccount/depositAccount.d.ts +1 -1
  5. package/lib/entity/depositAccount/depositAccount.js +8 -1
  6. package/lib/entity/reimbursement/reimbursementPayload.d.ts +2 -0
  7. package/lib/entity/reimbursement/reimbursementPayload.js +7 -0
  8. package/lib/entity/reimbursement/reimbursementState.d.ts +2 -0
  9. package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
  10. package/lib/entity/snackbar/snackbarTypes.js +1 -0
  11. package/lib/entity/task/taskPayload.d.ts +1 -0
  12. package/lib/entity/task/taskPayload.js +1 -0
  13. package/lib/entity/task/taskState.d.ts +1 -0
  14. package/lib/entity/userGroups/userGroupsPayload.d.ts +7 -0
  15. package/lib/entity/userGroups/userGroupsPayload.js +10 -0
  16. package/lib/entity/userGroups/userGroupsReducer.d.ts +8 -0
  17. package/lib/entity/userGroups/userGroupsReducer.js +29 -0
  18. package/lib/entity/userGroups/userGroupsSelector.d.ts +4 -0
  19. package/lib/entity/userGroups/userGroupsSelector.js +12 -0
  20. package/lib/entity/userGroups/userGroupsState.d.ts +9 -0
  21. package/lib/entity/userGroups/userGroupsState.js +2 -0
  22. package/lib/epic.d.ts +12 -1
  23. package/lib/epic.js +12 -1
  24. package/lib/esm/entity/account/accountPayload.js +2 -0
  25. package/lib/esm/entity/depositAccount/depositAccount.js +8 -1
  26. package/lib/esm/entity/reimbursement/reimbursementPayload.js +7 -0
  27. package/lib/esm/entity/snackbar/snackbarTypes.js +1 -0
  28. package/lib/esm/entity/task/taskPayload.js +1 -0
  29. package/lib/esm/entity/userGroups/userGroupsPayload.js +7 -0
  30. package/lib/esm/entity/userGroups/userGroupsReducer.js +25 -0
  31. package/lib/esm/entity/userGroups/userGroupsSelector.js +8 -0
  32. package/lib/esm/entity/userGroups/userGroupsState.js +1 -0
  33. package/lib/esm/epic.js +12 -1
  34. package/lib/esm/index.js +9 -3
  35. package/lib/esm/reducer.js +6 -0
  36. package/lib/esm/view/expenseAutomationView/bulkUploadTiming.js +10 -0
  37. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/bulkUploadReceiptsEpic.js +29 -0
  38. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/confirmBulkUploadMatchEpic.js +49 -0
  39. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchDetailsEpic.js +40 -0
  40. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic.js +47 -0
  41. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic.js +61 -0
  42. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic.js +33 -0
  43. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js +56 -0
  44. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +77 -0
  45. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/uploadMissingReceiptSuccessEpic.js +7 -5
  46. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js +79 -0
  47. package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/uploadTransactionReceiptSuccessEpic.js +24 -0
  48. package/lib/esm/view/expenseAutomationView/payload/missingReceiptsPayload.js +189 -1
  49. package/lib/esm/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +433 -2
  50. package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +23 -1
  51. package/lib/esm/view/expenseAutomationView/selectors/missingReceiptsSelector.js +244 -1
  52. package/lib/esm/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +2 -1
  53. package/lib/esm/view/expenseAutomationView/types/missingReceiptsViewState.js +44 -0
  54. package/lib/esm/view/expenseAutomationView/types/transactionsViewState.js +2 -1
  55. package/lib/esm/view/taskManager/taskDetailView/taskDetailSelector.js +1 -1
  56. package/lib/esm/view/taskManager/taskListView/epics/bulkUpdateTaskListEpic.js +1 -0
  57. package/lib/esm/view/taskManager/taskListView/taskListSelector.js +3 -1
  58. package/lib/esm/view/userGroupListView/userGroupListViewReducer.js +65 -0
  59. package/lib/esm/view/userGroupListView/userGroupListViewSelector.js +16 -0
  60. package/lib/esm/view/userGroupListView/userGroupListViewState.js +1 -0
  61. package/lib/esm/view/userListView/fetchUserListByTypeEpic.js +11 -0
  62. package/lib/esm/zeniAPI.js +0 -2
  63. package/lib/index.d.ts +8 -4
  64. package/lib/index.js +82 -32
  65. package/lib/reducer.d.ts +6 -0
  66. package/lib/reducer.js +6 -0
  67. package/lib/tsconfig.typecheck.tsbuildinfo +1 -0
  68. package/lib/view/expenseAutomationView/bulkUploadTiming.d.ts +10 -0
  69. package/lib/view/expenseAutomationView/bulkUploadTiming.js +13 -0
  70. package/lib/view/expenseAutomationView/epics/missingReceipts/bulkUploadReceiptsEpic.d.ts +18 -0
  71. package/lib/view/expenseAutomationView/epics/missingReceipts/bulkUploadReceiptsEpic.js +33 -0
  72. package/lib/view/expenseAutomationView/epics/missingReceipts/confirmBulkUploadMatchEpic.d.ts +7 -0
  73. package/lib/view/expenseAutomationView/epics/missingReceipts/confirmBulkUploadMatchEpic.js +53 -0
  74. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchDetailsEpic.d.ts +8 -0
  75. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchDetailsEpic.js +44 -0
  76. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic.d.ts +7 -0
  77. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic.js +51 -0
  78. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic.d.ts +8 -0
  79. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic.js +65 -0
  80. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic.d.ts +8 -0
  81. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic.js +37 -0
  82. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.d.ts +9 -0
  83. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js +61 -0
  84. package/lib/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.d.ts +14 -0
  85. package/lib/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +81 -0
  86. package/lib/view/expenseAutomationView/epics/missingReceipts/uploadMissingReceiptSuccessEpic.js +7 -5
  87. package/lib/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.d.ts +25 -0
  88. package/lib/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js +85 -0
  89. package/lib/view/expenseAutomationView/epics/transactionCategorization/uploadTransactionReceiptSuccessEpic.d.ts +7 -0
  90. package/lib/view/expenseAutomationView/epics/transactionCategorization/uploadTransactionReceiptSuccessEpic.js +28 -0
  91. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
  92. package/lib/view/expenseAutomationView/payload/missingReceiptsPayload.d.ts +118 -0
  93. package/lib/view/expenseAutomationView/payload/missingReceiptsPayload.js +201 -0
  94. package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.d.ts +76 -3
  95. package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +434 -2
  96. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.d.ts +11 -3
  97. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +24 -2
  98. package/lib/view/expenseAutomationView/selectorTypes/missingReceiptsSelectorTypes.d.ts +69 -1
  99. package/lib/view/expenseAutomationView/selectorTypes/transactionsViewSelectorTypes.d.ts +1 -0
  100. package/lib/view/expenseAutomationView/selectors/missingReceiptsSelector.d.ts +1 -1
  101. package/lib/view/expenseAutomationView/selectors/missingReceiptsSelector.js +243 -0
  102. package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +2 -1
  103. package/lib/view/expenseAutomationView/types/missingReceiptsViewState.d.ts +142 -0
  104. package/lib/view/expenseAutomationView/types/missingReceiptsViewState.js +54 -1
  105. package/lib/view/expenseAutomationView/types/transactionsViewState.d.ts +2 -1
  106. package/lib/view/expenseAutomationView/types/transactionsViewState.js +2 -1
  107. package/lib/view/taskManager/taskDetailView/taskDetailSelector.js +1 -1
  108. package/lib/view/taskManager/taskListView/epics/bulkUpdateTaskListEpic.d.ts +1 -0
  109. package/lib/view/taskManager/taskListView/epics/bulkUpdateTaskListEpic.js +1 -0
  110. package/lib/view/taskManager/taskListView/taskListSelector.d.ts +2 -0
  111. package/lib/view/taskManager/taskListView/taskListSelector.js +3 -1
  112. package/lib/view/userGroupListView/userGroupListViewReducer.d.ts +23 -0
  113. package/lib/view/userGroupListView/userGroupListViewReducer.js +69 -0
  114. package/lib/view/userGroupListView/userGroupListViewSelector.d.ts +7 -0
  115. package/lib/view/userGroupListView/userGroupListViewSelector.js +20 -0
  116. package/lib/view/userGroupListView/userGroupListViewState.d.ts +8 -0
  117. package/lib/view/userGroupListView/userGroupListViewState.js +2 -0
  118. package/lib/view/userListView/fetchUserListByTypeEpic.d.ts +3 -1
  119. package/lib/view/userListView/fetchUserListByTypeEpic.js +11 -0
  120. package/lib/view/userListView/userListViewPayload.d.ts +2 -0
  121. package/lib/zeniAPI.js +0 -2
  122. package/package.json +2 -2
@@ -1,6 +1,38 @@
1
- import { createSlice } from '@reduxjs/toolkit';
1
+ import { createAction, createSlice } from '@reduxjs/toolkit';
2
2
  import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
3
+ import { MIN_MANUAL_TRANSACTION_SEARCH_LENGTH, } from '../types/missingReceiptsViewState';
4
+ export const getCompletedTransactionsCacheKey = (periodId, sortKey, sortOrder, subTab) => `completed-${subTab}-${sortKey}-${sortOrder === 'ascending' ? 'asc' : 'desc'}-${periodId}`;
5
+ export const initialBulkUploadState = {
6
+ batchDetailsById: {},
7
+ batchDetailsPaginationState: { fetchState: 'Not-Started', error: undefined },
8
+ batchListByPeriod: {},
9
+ batchListFetchState: { fetchState: 'Not-Started', error: undefined },
10
+ batchStatusById: {},
11
+ completedSubTab: 'all',
12
+ completedTransactionsByPeriod: {},
13
+ confirmMatchStatus: { fetchState: 'Not-Started', error: undefined },
14
+ currentBatchId: undefined,
15
+ currentResultsTab: 'unmatched',
16
+ failedBatchDetailIds: [],
17
+ manualSearchUiResetKey: 0,
18
+ manualSearch: {
19
+ fetchState: { fetchState: 'Not-Started', error: undefined },
20
+ isLoadingMore: false,
21
+ nextPageToken: null,
22
+ pageSize: 25,
23
+ results: [],
24
+ searchQuery: '',
25
+ totalCount: 0,
26
+ },
27
+ phase: 'idle',
28
+ sortKey: 'date',
29
+ sortOrder: 'descending',
30
+ uploadProgress: 0,
31
+ uploadStatus: { fetchState: 'Not-Started', error: undefined },
32
+ };
3
33
  export const initialState = {
34
+ bulkUpload: initialBulkUploadState,
35
+ pendingMissingReceiptsTabNavigation: null,
4
36
  uploadReceiptStatusById: {},
5
37
  refreshStatus: {
6
38
  fetchState: 'Not-Started',
@@ -19,6 +51,8 @@ export const initialState = {
19
51
  return this.fetchState == 'Completed';
20
52
  },
21
53
  };
54
+ /** Epic trigger only; batch-details fetch is handled in epics. */
55
+ export const fetchBulkUploadBatchDetails = createAction('expenseAutomationMissingReceiptsView/fetchBulkUploadBatchDetails');
22
56
  const expenseAutomationMissingReceiptsView = createSlice({
23
57
  name: 'expenseAutomationMissingReceiptsView',
24
58
  initialState,
@@ -120,7 +154,404 @@ const expenseAutomationMissingReceiptsView = createSlice({
120
154
  clearExpenseAutomationMissingReceiptsView(draft) {
121
155
  Object.assign(draft, initialState);
122
156
  },
157
+ // -- Bulk Upload Actions --
158
+ bulkUploadReceipts: {
159
+ reducer(draft) {
160
+ draft.bulkUpload.phase = 'uploading';
161
+ draft.bulkUpload.uploadProgress = 0;
162
+ draft.bulkUpload.uploadStatus = {
163
+ fetchState: 'In-Progress',
164
+ error: undefined,
165
+ };
166
+ },
167
+ prepare(files) {
168
+ return { payload: { files } };
169
+ },
170
+ },
171
+ updateBulkUploadProgress(draft, action) {
172
+ draft.bulkUpload.uploadProgress = action.payload;
173
+ },
174
+ bulkUploadReceiptsSuccess(draft, action) {
175
+ draft.bulkUpload.currentBatchId = action.payload.batchId;
176
+ draft.bulkUpload.uploadProgress = 100;
177
+ draft.bulkUpload.uploadStatus = {
178
+ fetchState: 'Completed',
179
+ error: undefined,
180
+ };
181
+ draft.bulkUpload.phase = 'matching';
182
+ },
183
+ bulkUploadReceiptsFailure(draft, action) {
184
+ draft.bulkUpload.uploadStatus = {
185
+ fetchState: 'Error',
186
+ error: action.payload.error,
187
+ };
188
+ draft.bulkUpload.phase = 'idle';
189
+ },
190
+ bulkUploadAutomatchingTimedOut: {
191
+ reducer(draft, action) {
192
+ if (draft.bulkUpload.phase !== 'matching' ||
193
+ draft.bulkUpload.currentBatchId !== action.payload.batchId) {
194
+ return;
195
+ }
196
+ draft.bulkUpload.phase = 'completed';
197
+ },
198
+ prepare(batchId) {
199
+ return { payload: { batchId } };
200
+ },
201
+ },
202
+ pusherBatchStatusUpdate(draft, action) {
203
+ const batchStatus = action.payload;
204
+ draft.bulkUpload.batchStatusById[batchStatus.batchId] = batchStatus;
205
+ /**
206
+ * Auto-matching banner (`phase === 'matching'`) must clear when the server reports this
207
+ * upload batch is done — otherwise the Missing tab keeps MatchingProgressBanner until
208
+ * batch-details APIs run; EmailUploadBanner should show again for the next upload.
209
+ */
210
+ if (batchStatus.status === 'completed' &&
211
+ draft.bulkUpload.phase === 'matching' &&
212
+ draft.bulkUpload.currentBatchId === batchStatus.batchId) {
213
+ draft.bulkUpload.phase = 'completed';
214
+ }
215
+ },
216
+ requestMissingReceiptsTabNavigation(draft, action) {
217
+ draft.pendingMissingReceiptsTabNavigation = action.payload.tab;
218
+ },
219
+ clearMissingReceiptsTabNavigation(draft) {
220
+ draft.pendingMissingReceiptsTabNavigation = null;
221
+ },
222
+ fetchBulkUploadBatchDetailsSuccess(draft, action) {
223
+ const details = action.payload;
224
+ draft.bulkUpload.batchDetailsById[details.batchId] = details;
225
+ draft.bulkUpload.phase = 'completed';
226
+ },
227
+ fetchBulkUploadBatchDetailsFailure: {
228
+ reducer(draft) {
229
+ draft.bulkUpload.phase = 'completed';
230
+ },
231
+ prepare(payload) {
232
+ return { payload };
233
+ },
234
+ },
235
+ storeBatchDetails(draft, action) {
236
+ const details = action.payload;
237
+ draft.bulkUpload.batchDetailsById[details.batchId] = details;
238
+ /**
239
+ * Multi-batch fetch paths use `storeBatchDetails` (not `fetchBulkUploadBatchDetailsSuccess`).
240
+ * Without this, `phase` stays `matching` after upload forever and the Unmatched tab keeps
241
+ * showing the skeleton (`bulkUploadPhase === 'matching'` in web-components).
242
+ */
243
+ if (draft.bulkUpload.phase === 'matching' &&
244
+ draft.bulkUpload.currentBatchId === details.batchId) {
245
+ draft.bulkUpload.phase = 'completed';
246
+ }
247
+ },
248
+ batchDetailFetchFailed(draft, action) {
249
+ const { batchId } = action.payload;
250
+ if (!draft.bulkUpload.failedBatchDetailIds.includes(batchId)) {
251
+ draft.bulkUpload.failedBatchDetailIds.push(batchId);
252
+ }
253
+ if (draft.bulkUpload.phase === 'matching' &&
254
+ draft.bulkUpload.currentBatchId === batchId) {
255
+ draft.bulkUpload.phase = 'completed';
256
+ }
257
+ },
258
+ setInitialBatchDetailsLoading(draft) {
259
+ draft.bulkUpload.batchDetailsPaginationState = {
260
+ fetchState: 'In-Progress',
261
+ error: undefined,
262
+ };
263
+ },
264
+ fetchMoreBatchDetails(draft) {
265
+ draft.bulkUpload.batchDetailsPaginationState = {
266
+ fetchState: 'In-Progress',
267
+ error: undefined,
268
+ };
269
+ },
270
+ fetchMoreBatchDetailsComplete(draft) {
271
+ draft.bulkUpload.batchDetailsPaginationState = {
272
+ fetchState: 'Completed',
273
+ error: undefined,
274
+ };
275
+ const currentId = draft.bulkUpload.currentBatchId;
276
+ const currentDetails = currentId != null
277
+ ? draft.bulkUpload.batchDetailsById[currentId]
278
+ : undefined;
279
+ if (draft.bulkUpload.phase === 'matching' &&
280
+ currentId != null &&
281
+ currentDetails?.status === 'completed') {
282
+ draft.bulkUpload.phase = 'completed';
283
+ }
284
+ },
285
+ fetchMoreBatchDetailsFailure(draft, action) {
286
+ draft.bulkUpload.batchDetailsPaginationState = {
287
+ fetchState: 'Error',
288
+ error: action.payload.error,
289
+ };
290
+ },
291
+ fetchBulkUploadBatches: {
292
+ reducer(draft, action) {
293
+ if (action.payload.invalidateBatchDetailsCache === true) {
294
+ draft.bulkUpload.batchDetailsById = {};
295
+ draft.bulkUpload.failedBatchDetailIds = [];
296
+ }
297
+ draft.bulkUpload.batchListFetchState = {
298
+ fetchState: 'In-Progress',
299
+ error: undefined,
300
+ };
301
+ },
302
+ prepare(input) {
303
+ const fetchOptions = input ?? {};
304
+ return {
305
+ payload: {
306
+ cacheOverride: fetchOptions.cacheOverride === true,
307
+ invalidateBatchDetailsCache: fetchOptions.invalidateBatchDetailsCache === true,
308
+ },
309
+ };
310
+ },
311
+ },
312
+ fetchBulkUploadBatchesSuccess(draft, action) {
313
+ const periodId = toMonthYearPeriodId(action.payload.selectedPeriod);
314
+ draft.bulkUpload.batchListByPeriod[periodId] = action.payload.batchList;
315
+ draft.bulkUpload.batchListFetchState = {
316
+ fetchState: 'Completed',
317
+ error: undefined,
318
+ };
319
+ /**
320
+ * When the multi-batch-details epic has nothing to fetch (e.g. list rows not `completed`
321
+ * yet, or details already present), `phase` can stay `matching` even though we already
322
+ * have completed details for `currentBatchId` (from polling / Pusher). Sync so the UI can
323
+ * dismiss the Unmatched skeleton.
324
+ */
325
+ const currentId = draft.bulkUpload.currentBatchId;
326
+ if (draft.bulkUpload.phase === 'matching' && currentId != null) {
327
+ const existing = draft.bulkUpload.batchDetailsById[currentId];
328
+ if (existing?.status === 'completed') {
329
+ draft.bulkUpload.phase = 'completed';
330
+ }
331
+ }
332
+ },
333
+ fetchBulkUploadBatchesFailure(draft, action) {
334
+ draft.bulkUpload.batchListFetchState = {
335
+ fetchState: 'Error',
336
+ error: action.payload.error,
337
+ };
338
+ },
339
+ confirmBulkUploadMatch: {
340
+ reducer(draft) {
341
+ draft.bulkUpload.confirmMatchStatus = {
342
+ fetchState: 'In-Progress',
343
+ error: undefined,
344
+ };
345
+ },
346
+ prepare(batchId, attachmentId, transactionId, transactionType, fileName) {
347
+ return {
348
+ payload: {
349
+ batchId,
350
+ attachmentId,
351
+ transactionId,
352
+ transactionType,
353
+ fileName,
354
+ },
355
+ };
356
+ },
357
+ },
358
+ confirmBulkUploadMatchSuccess(draft, action) {
359
+ const { batchId, attachmentId, transactionId } = action.payload;
360
+ draft.bulkUpload.confirmMatchStatus = {
361
+ fetchState: 'Completed',
362
+ error: undefined,
363
+ };
364
+ const details = draft.bulkUpload.batchDetailsById[batchId];
365
+ if (details != null) {
366
+ const file = details.files.find((batchFile) => batchFile.attachmentId === attachmentId);
367
+ if (file != null) {
368
+ const originalStatus = file.status;
369
+ file.status = 'exact_match';
370
+ file.matchedTransactionId = transactionId;
371
+ file.candidates = undefined;
372
+ details.summary.matched += 1;
373
+ if (originalStatus === 'no_match') {
374
+ details.summary.noMatch = Math.max(0, details.summary.noMatch - 1);
375
+ }
376
+ else if (originalStatus === 'possible_matches') {
377
+ details.summary.possibleMatches = Math.max(0, details.summary.possibleMatches - 1);
378
+ }
379
+ }
380
+ }
381
+ draft.bulkUpload.manualSearch = initialBulkUploadState.manualSearch;
382
+ draft.bulkUpload.manualSearchUiResetKey += 1;
383
+ },
384
+ confirmBulkUploadMatchFailure(draft, action) {
385
+ draft.bulkUpload.confirmMatchStatus = {
386
+ fetchState: 'Error',
387
+ error: action.payload.error,
388
+ };
389
+ },
390
+ setBulkUploadResultsTab(draft, action) {
391
+ draft.bulkUpload.currentResultsTab = action.payload;
392
+ },
393
+ setBulkUploadCompletedSubTab(draft, action) {
394
+ draft.bulkUpload.completedSubTab = action.payload;
395
+ },
396
+ setBulkUploadSortConfig(draft, action) {
397
+ draft.bulkUpload.sortKey = action.payload.sortKey;
398
+ draft.bulkUpload.sortOrder = action.payload.sortOrder;
399
+ },
400
+ clearBulkUpload(draft) {
401
+ Object.assign(draft.bulkUpload, initialBulkUploadState);
402
+ draft.pendingMissingReceiptsTabNavigation = null;
403
+ },
404
+ searchTransactionsForManualMatch: {
405
+ reducer(draft, action) {
406
+ const { query, pageToken } = action.payload;
407
+ const isLoadMore = pageToken != null;
408
+ const trimmed = query.trim();
409
+ /**
410
+ * Cleared or too-short query: no API (see epic). Reset fully (no In-Progress).
411
+ * Only bump `manualSearchUiResetKey` when clearing a real search — not when the UI
412
+ * dispatches `""` on every keystroke while length is below MIN (web-components always sends
413
+ * `onSearch("")` in that case), or ReceiptCard remounts and the input loses focus.
414
+ */
415
+ if (!isLoadMore &&
416
+ trimmed.length < MIN_MANUAL_TRANSACTION_SEARCH_LENGTH) {
417
+ const hadActiveSearch = draft.bulkUpload.manualSearch.searchQuery.trim().length >=
418
+ MIN_MANUAL_TRANSACTION_SEARCH_LENGTH ||
419
+ draft.bulkUpload.manualSearch.results.length > 0;
420
+ draft.bulkUpload.manualSearch = initialBulkUploadState.manualSearch;
421
+ if (hadActiveSearch) {
422
+ draft.bulkUpload.manualSearchUiResetKey += 1;
423
+ }
424
+ return;
425
+ }
426
+ draft.bulkUpload.manualSearch.searchQuery = query;
427
+ if (isLoadMore) {
428
+ draft.bulkUpload.manualSearch.isLoadingMore = true;
429
+ }
430
+ else {
431
+ draft.bulkUpload.manualSearch.isLoadingMore = false;
432
+ draft.bulkUpload.manualSearch.results = [];
433
+ draft.bulkUpload.manualSearch.nextPageToken = null;
434
+ draft.bulkUpload.manualSearch.totalCount = 0;
435
+ draft.bulkUpload.manualSearch.fetchState = {
436
+ fetchState: 'In-Progress',
437
+ error: undefined,
438
+ };
439
+ }
440
+ },
441
+ prepare(query, pageToken) {
442
+ return { payload: { query, pageToken: pageToken ?? undefined } };
443
+ },
444
+ },
445
+ searchTransactionsForManualMatchSuccess(draft, action) {
446
+ const { append, nextPageToken, results, totalCount } = action.payload;
447
+ if (append) {
448
+ const merged = [...draft.bulkUpload.manualSearch.results, ...results];
449
+ const byId = new Map(merged.map((result) => [result.transactionId, result]));
450
+ draft.bulkUpload.manualSearch.results = Array.from(byId.values());
451
+ }
452
+ else {
453
+ draft.bulkUpload.manualSearch.results = results;
454
+ }
455
+ draft.bulkUpload.manualSearch.nextPageToken = nextPageToken;
456
+ draft.bulkUpload.manualSearch.totalCount = totalCount;
457
+ draft.bulkUpload.manualSearch.isLoadingMore = false;
458
+ draft.bulkUpload.manualSearch.fetchState = {
459
+ fetchState: 'Completed',
460
+ error: undefined,
461
+ };
462
+ },
463
+ searchTransactionsForManualMatchFailure(draft, action) {
464
+ draft.bulkUpload.manualSearch.isLoadingMore = false;
465
+ draft.bulkUpload.manualSearch.fetchState = {
466
+ fetchState: 'Error',
467
+ error: action.payload.error,
468
+ };
469
+ },
470
+ clearManualSearchResults(draft) {
471
+ draft.bulkUpload.manualSearch = initialBulkUploadState.manualSearch;
472
+ draft.bulkUpload.manualSearchUiResetKey += 1;
473
+ },
474
+ acknowledgeBulkUploadConfirmMatchComplete(draft) {
475
+ draft.bulkUpload.confirmMatchStatus = {
476
+ fetchState: 'Not-Started',
477
+ error: undefined,
478
+ };
479
+ },
480
+ // -- Completed Transactions Actions --
481
+ fetchCompletedTransactions: {
482
+ reducer(draft, action) {
483
+ const { pageToken } = action.payload;
484
+ if (pageToken != null) {
485
+ const periods = draft.bulkUpload.completedTransactionsByPeriod;
486
+ for (const entry of Object.values(periods)) {
487
+ if (entry.nextPageToken === pageToken) {
488
+ entry.fetchState = {
489
+ fetchState: 'In-Progress',
490
+ error: undefined,
491
+ };
492
+ break;
493
+ }
494
+ }
495
+ }
496
+ },
497
+ prepare(pageToken, cacheOverride) {
498
+ return { payload: { pageToken, cacheOverride } };
499
+ },
500
+ },
501
+ fetchCompletedTransactionsSuccess(draft, action) {
502
+ const { transactionIds, nextPageToken, totalCount, selectedPeriod, isInitialLoad, } = action.payload;
503
+ const periodId = toMonthYearPeriodId(selectedPeriod);
504
+ const cacheKey = getCompletedTransactionsCacheKey(periodId, draft.bulkUpload.sortKey, draft.bulkUpload.sortOrder, draft.bulkUpload.completedSubTab);
505
+ if (isInitialLoad) {
506
+ draft.bulkUpload.completedTransactionsByPeriod[cacheKey] = {
507
+ transactionIds,
508
+ nextPageToken,
509
+ totalCount,
510
+ fetchState: { fetchState: 'Completed', error: undefined },
511
+ };
512
+ }
513
+ else {
514
+ const existing = draft.bulkUpload.completedTransactionsByPeriod[cacheKey];
515
+ if (existing != null) {
516
+ existing.transactionIds.push(...transactionIds);
517
+ existing.nextPageToken = nextPageToken;
518
+ existing.totalCount = totalCount;
519
+ existing.fetchState = { fetchState: 'Completed', error: undefined };
520
+ }
521
+ else {
522
+ draft.bulkUpload.completedTransactionsByPeriod[cacheKey] = {
523
+ transactionIds,
524
+ nextPageToken,
525
+ totalCount,
526
+ fetchState: { fetchState: 'Completed', error: undefined },
527
+ };
528
+ }
529
+ }
530
+ },
531
+ fetchCompletedTransactionsFailure(draft, action) {
532
+ const periodId = toMonthYearPeriodId(action.payload.selectedPeriod);
533
+ const cacheKey = getCompletedTransactionsCacheKey(periodId, draft.bulkUpload.sortKey, draft.bulkUpload.sortOrder, draft.bulkUpload.completedSubTab);
534
+ const existing = draft.bulkUpload.completedTransactionsByPeriod[cacheKey];
535
+ if (existing != null) {
536
+ existing.fetchState = {
537
+ fetchState: 'Error',
538
+ error: action.payload.error,
539
+ };
540
+ }
541
+ else {
542
+ draft.bulkUpload.completedTransactionsByPeriod[cacheKey] = {
543
+ fetchState: { fetchState: 'Error', error: action.payload.error },
544
+ nextPageToken: null,
545
+ totalCount: 0,
546
+ transactionIds: [],
547
+ };
548
+ }
549
+ },
123
550
  },
124
551
  });
125
- export const { fetchMissingReceipts, fetchMissingReceiptsSuccess, fetchMissingReceiptsFailure, uploadMissingReceiptSuccess, updateMissingReceiptUploadState, updateMissingReceiptsUIState, markMissingReceiptAsDone, clearExpenseAutomationMissingReceiptsView, } = expenseAutomationMissingReceiptsView.actions;
552
+ export const { fetchMissingReceipts, fetchMissingReceiptsSuccess, fetchMissingReceiptsFailure, uploadMissingReceiptSuccess, updateMissingReceiptUploadState, updateMissingReceiptsUIState, markMissingReceiptAsDone, clearExpenseAutomationMissingReceiptsView,
553
+ // Bulk Upload
554
+ bulkUploadReceipts, updateBulkUploadProgress, bulkUploadReceiptsSuccess, bulkUploadReceiptsFailure, bulkUploadAutomatchingTimedOut, pusherBatchStatusUpdate, requestMissingReceiptsTabNavigation, clearMissingReceiptsTabNavigation, fetchBulkUploadBatchDetailsSuccess, fetchBulkUploadBatchDetailsFailure, storeBatchDetails, batchDetailFetchFailed, setInitialBatchDetailsLoading, fetchMoreBatchDetails, fetchMoreBatchDetailsComplete, fetchMoreBatchDetailsFailure, fetchBulkUploadBatches, fetchBulkUploadBatchesSuccess, fetchBulkUploadBatchesFailure, confirmBulkUploadMatch, confirmBulkUploadMatchSuccess, confirmBulkUploadMatchFailure, setBulkUploadResultsTab, setBulkUploadCompletedSubTab, setBulkUploadSortConfig, clearBulkUpload, searchTransactionsForManualMatch, searchTransactionsForManualMatchSuccess, searchTransactionsForManualMatchFailure, clearManualSearchResults, acknowledgeBulkUploadConfirmMatchComplete,
555
+ // Completed Transactions
556
+ fetchCompletedTransactions, fetchCompletedTransactionsSuccess, fetchCompletedTransactionsFailure, } = expenseAutomationMissingReceiptsView.actions;
126
557
  export default expenseAutomationMissingReceiptsView.reducer;
@@ -36,6 +36,7 @@ export const initialTransactionTabViewState = {
36
36
  transactionIdsBySelectedPeriod: {},
37
37
  selectedCheckBoxTransactionIds: [],
38
38
  transactionIdsWithUnsavedData: [],
39
+ uploadReceiptStatusById: {},
39
40
  selectedTransactionId: undefined,
40
41
  selectedTransactionLineId: undefined,
41
42
  };
@@ -699,6 +700,27 @@ const expenseAutomationTransactionsView = createSlice({
699
700
  clearExpenseAutomationTransactionsView(draft) {
700
701
  Object.assign(draft, initialState);
701
702
  },
703
+ updateTransactionCategorizationUploadReceiptState: {
704
+ reducer(draft, action) {
705
+ const { transactionId, fetchStatus, selectedTab } = action.payload;
706
+ draft.transactionCategorizationView[selectedTab].uploadReceiptStatusById[transactionId] = fetchStatus;
707
+ },
708
+ prepare(selectedTab, transactionId, fetchStatus) {
709
+ return { payload: { selectedTab, transactionId, fetchStatus } };
710
+ },
711
+ },
712
+ uploadTransactionCategorizationReceiptSuccess: {
713
+ reducer(draft, action) {
714
+ const { transactionPayload, selectedTab } = action.payload;
715
+ draft.transactionCategorizationView[selectedTab].uploadReceiptStatusById[transactionPayload.transaction_id] = {
716
+ fetchState: 'Completed',
717
+ error: undefined,
718
+ };
719
+ },
720
+ prepare(selectedTab, transactionPayload, updateType = 'merge') {
721
+ return { payload: { selectedTab, transactionPayload, updateType } };
722
+ },
723
+ },
702
724
  syncTransactionCategorizationFromDetailSave: {
703
725
  prepare(transactionId, updatedTransaction, pendingReviewLineIds, uncategorizedIncomeExpense, isUncategorizedExpenseCategoryEnabled) {
704
726
  return {
@@ -770,5 +792,5 @@ const expenseAutomationTransactionsView = createSlice({
770
792
  },
771
793
  },
772
794
  });
773
- export const { fetchTransactionCategorization, updateTransactionCategorizationUIState, initializeTransactionCategorizationViewLocalData, saveTransactionCategorizationLocalData, fetchTransactionCategorizationFailure, saveTransactionCategorization, updateTransactionCategorization, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationSaveStatus, markTransactionAsNotMiscategorized, updateStatusForTransactionNotMiscategorizedUpdateForCategorization, updateSelectedVendorForTransaction, updateSelectedCustomerForTransaction, setAllItemsToCategoryClassInLocalDataForCategorization, updateTotalCountForTransactionCategorization, fetchTransactionCategorizationSuccess, resetOtherTabsFetchState, fetchTransactionCategorizationView, updateSelectedCheckboxTransactionIds, clearExpenseAutomationTransactionsViewPerTabView, clearExpenseAutomationTransactionsView, setEntityRecommendationForLineIdsForCategorization, updateSelectedTransactionId, syncTransactionCategorizationFromDetailSave, backgroundRefetchReviewTab, } = expenseAutomationTransactionsView.actions;
795
+ export const { fetchTransactionCategorization, updateTransactionCategorizationUIState, initializeTransactionCategorizationViewLocalData, saveTransactionCategorizationLocalData, fetchTransactionCategorizationFailure, saveTransactionCategorization, updateTransactionCategorization, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationSaveStatus, markTransactionAsNotMiscategorized, updateStatusForTransactionNotMiscategorizedUpdateForCategorization, updateSelectedVendorForTransaction, updateSelectedCustomerForTransaction, setAllItemsToCategoryClassInLocalDataForCategorization, updateTotalCountForTransactionCategorization, fetchTransactionCategorizationSuccess, resetOtherTabsFetchState, fetchTransactionCategorizationView, updateSelectedCheckboxTransactionIds, clearExpenseAutomationTransactionsViewPerTabView, clearExpenseAutomationTransactionsView, setEntityRecommendationForLineIdsForCategorization, updateSelectedTransactionId, syncTransactionCategorizationFromDetailSave, backgroundRefetchReviewTab, updateTransactionCategorizationUploadReceiptState, uploadTransactionCategorizationReceiptSuccess, } = expenseAutomationTransactionsView.actions;
774
796
  export default expenseAutomationTransactionsView.reducer;