@zeniai/client-epic-state 4.19.37-betaAS2 → 4.19.37-betaVR10

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 (70) hide show
  1. package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
  2. package/lib/entity/snackbar/snackbarTypes.js +1 -0
  3. package/lib/epic.d.ts +10 -1
  4. package/lib/epic.js +10 -1
  5. package/lib/esm/entity/snackbar/snackbarTypes.js +1 -0
  6. package/lib/esm/epic.js +10 -1
  7. package/lib/esm/index.js +8 -6
  8. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/bulkUploadReceiptsEpic.js +24 -0
  9. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/confirmBulkUploadMatchEpic.js +49 -0
  10. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchDetailsEpic.js +31 -0
  11. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic.js +41 -0
  12. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic.js +57 -0
  13. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic.js +31 -0
  14. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js +39 -0
  15. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +27 -0
  16. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js +44 -0
  17. package/lib/esm/view/expenseAutomationView/payload/missingReceiptsPayload.js +96 -1
  18. package/lib/esm/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +280 -1
  19. package/lib/esm/view/expenseAutomationView/selectors/missingReceiptsSelector.js +134 -1
  20. package/lib/esm/view/spendManagement/billPay/billPaySetupApproverView/billPaySetupApproverViewReducer.js +1 -10
  21. package/lib/esm/view/spendManagement/billPay/billPaySetupApproverView/billPaySetupApproverViewSelector.js +2 -4
  22. package/lib/esm/view/spendManagement/billPay/billPaySetupApproverView/types/commonPayload.js +2 -5
  23. package/lib/esm/view/spendManagement/reimbursement/remiSetupApproverView/remiSetupApproverViewReducer.js +1 -10
  24. package/lib/esm/view/spendManagement/reimbursement/remiSetupApproverView/remiSetupApproverViewSelector.js +2 -4
  25. package/lib/esm/zeniAPI.js +41 -3
  26. package/lib/index.d.ts +11 -9
  27. package/lib/index.js +60 -32
  28. package/lib/view/expenseAutomationView/epics/missingReceipts/bulkUploadReceiptsEpic.d.ts +15 -0
  29. package/lib/view/expenseAutomationView/epics/missingReceipts/bulkUploadReceiptsEpic.js +28 -0
  30. package/lib/view/expenseAutomationView/epics/missingReceipts/confirmBulkUploadMatchEpic.d.ts +7 -0
  31. package/lib/view/expenseAutomationView/epics/missingReceipts/confirmBulkUploadMatchEpic.js +53 -0
  32. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchDetailsEpic.d.ts +8 -0
  33. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchDetailsEpic.js +35 -0
  34. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic.d.ts +7 -0
  35. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic.js +45 -0
  36. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic.d.ts +8 -0
  37. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic.js +61 -0
  38. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic.d.ts +8 -0
  39. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic.js +35 -0
  40. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.d.ts +9 -0
  41. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js +44 -0
  42. package/lib/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.d.ts +6 -0
  43. package/lib/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +31 -0
  44. package/lib/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.d.ts +8 -0
  45. package/lib/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js +49 -0
  46. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
  47. package/lib/view/expenseAutomationView/payload/missingReceiptsPayload.d.ts +98 -0
  48. package/lib/view/expenseAutomationView/payload/missingReceiptsPayload.js +102 -0
  49. package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.d.ts +53 -3
  50. package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +281 -2
  51. package/lib/view/expenseAutomationView/selectorTypes/missingReceiptsSelectorTypes.d.ts +64 -1
  52. package/lib/view/expenseAutomationView/selectors/missingReceiptsSelector.d.ts +1 -1
  53. package/lib/view/expenseAutomationView/selectors/missingReceiptsSelector.js +133 -0
  54. package/lib/view/expenseAutomationView/types/missingReceiptsViewState.d.ts +106 -0
  55. package/lib/view/spendManagement/billPay/billPaySetupApproverView/billPaySetupApproverViewReducer.d.ts +1 -1
  56. package/lib/view/spendManagement/billPay/billPaySetupApproverView/billPaySetupApproverViewReducer.js +2 -11
  57. package/lib/view/spendManagement/billPay/billPaySetupApproverView/billPaySetupApproverViewSelector.js +2 -4
  58. package/lib/view/spendManagement/billPay/billPaySetupApproverView/billPaySetupApproverViewSelectorTypes.d.ts +0 -2
  59. package/lib/view/spendManagement/billPay/billPaySetupApproverView/billPaySetupApproverViewState.d.ts +0 -1
  60. package/lib/view/spendManagement/billPay/billPaySetupApproverView/types/commonPayload.d.ts +0 -1
  61. package/lib/view/spendManagement/billPay/billPaySetupApproverView/types/commonPayload.js +2 -5
  62. package/lib/view/spendManagement/billPay/billPaySetupApproverView/types/commonState.d.ts +0 -2
  63. package/lib/view/spendManagement/reimbursement/remiSetupApproverView/remiSetupApproverViewReducer.d.ts +1 -1
  64. package/lib/view/spendManagement/reimbursement/remiSetupApproverView/remiSetupApproverViewReducer.js +2 -11
  65. package/lib/view/spendManagement/reimbursement/remiSetupApproverView/remiSetupApproverViewSelector.js +2 -4
  66. package/lib/view/spendManagement/reimbursement/remiSetupApproverView/remiSetupApproverViewSelectorTypes.d.ts +0 -2
  67. package/lib/view/spendManagement/reimbursement/remiSetupApproverView/remiSetupApproverViewState.d.ts +0 -1
  68. package/lib/zeniAPI.d.ts +9 -0
  69. package/lib/zeniAPI.js +40 -2
  70. package/package.json +2 -2
@@ -1,6 +1,29 @@
1
1
  import { createSlice } from '@reduxjs/toolkit';
2
2
  import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
3
+ export const initialBulkUploadState = {
4
+ batchDetailsById: {},
5
+ batchDetailsPaginationState: { fetchState: 'Not-Started', error: undefined },
6
+ batchListByPeriod: {},
7
+ batchListFetchState: { fetchState: 'Not-Started', error: undefined },
8
+ batchStatusById: {},
9
+ completedSubTab: 'all',
10
+ completedTransactionsByPeriod: {},
11
+ confirmMatchStatus: { fetchState: 'Not-Started', error: undefined },
12
+ currentBatchId: undefined,
13
+ currentResultsTab: 'unmatched',
14
+ manualSearch: {
15
+ fetchState: { fetchState: 'Not-Started', error: undefined },
16
+ results: [],
17
+ searchQuery: '',
18
+ },
19
+ phase: 'idle',
20
+ sortKey: 'date',
21
+ sortOrder: 'descending',
22
+ uploadProgress: 0,
23
+ uploadStatus: { fetchState: 'Not-Started', error: undefined },
24
+ };
3
25
  export const initialState = {
26
+ bulkUpload: initialBulkUploadState,
4
27
  uploadReceiptStatusById: {},
5
28
  refreshStatus: {
6
29
  fetchState: 'Not-Started',
@@ -120,7 +143,263 @@ const expenseAutomationMissingReceiptsView = createSlice({
120
143
  clearExpenseAutomationMissingReceiptsView(draft) {
121
144
  Object.assign(draft, initialState);
122
145
  },
146
+ // -- Bulk Upload Actions --
147
+ bulkUploadReceipts: {
148
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
149
+ reducer(draft, _action) {
150
+ draft.bulkUpload.phase = 'uploading';
151
+ draft.bulkUpload.uploadProgress = 0;
152
+ draft.bulkUpload.uploadStatus = {
153
+ fetchState: 'In-Progress',
154
+ error: undefined,
155
+ };
156
+ },
157
+ prepare(files) {
158
+ return { payload: { files } };
159
+ },
160
+ },
161
+ updateBulkUploadProgress(draft, action) {
162
+ draft.bulkUpload.uploadProgress = action.payload;
163
+ },
164
+ bulkUploadReceiptsSuccess(draft, action) {
165
+ draft.bulkUpload.currentBatchId = action.payload.batchId;
166
+ draft.bulkUpload.uploadStatus = {
167
+ fetchState: 'Completed',
168
+ error: undefined,
169
+ };
170
+ draft.bulkUpload.phase = 'matching';
171
+ },
172
+ bulkUploadReceiptsFailure(draft, action) {
173
+ draft.bulkUpload.uploadStatus = {
174
+ fetchState: 'Error',
175
+ error: action.payload.error,
176
+ };
177
+ draft.bulkUpload.phase = 'idle';
178
+ },
179
+ pusherBatchStatusUpdate(draft, action) {
180
+ const batchStatus = action.payload;
181
+ draft.bulkUpload.batchStatusById[batchStatus.batchId] = batchStatus;
182
+ },
183
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
184
+ fetchBulkUploadBatchDetails(_draft) {
185
+ // trigger epic, no state change needed
186
+ },
187
+ fetchBulkUploadBatchDetailsSuccess(draft, action) {
188
+ const details = action.payload;
189
+ draft.bulkUpload.batchDetailsById[details.batchId] = details;
190
+ draft.bulkUpload.phase = 'completed';
191
+ },
192
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
193
+ fetchBulkUploadBatchDetailsFailure(draft, _action) {
194
+ draft.bulkUpload.phase = 'completed';
195
+ },
196
+ storeBatchDetails(draft, action) {
197
+ const details = action.payload;
198
+ draft.bulkUpload.batchDetailsById[details.batchId] = details;
199
+ },
200
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
201
+ fetchMoreBatchDetails(_draft) {
202
+ _draft.bulkUpload.batchDetailsPaginationState = {
203
+ fetchState: 'In-Progress',
204
+ error: undefined,
205
+ };
206
+ },
207
+ fetchMoreBatchDetailsComplete(draft) {
208
+ draft.bulkUpload.batchDetailsPaginationState = {
209
+ fetchState: 'Completed',
210
+ error: undefined,
211
+ };
212
+ },
213
+ fetchMoreBatchDetailsFailure(draft, action) {
214
+ draft.bulkUpload.batchDetailsPaginationState = {
215
+ fetchState: 'Error',
216
+ error: action.payload.error,
217
+ };
218
+ },
219
+ fetchBulkUploadBatches: {
220
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
221
+ reducer(draft, _action) {
222
+ draft.bulkUpload.batchListFetchState = {
223
+ fetchState: 'In-Progress',
224
+ error: undefined,
225
+ };
226
+ },
227
+ prepare(cacheOverride = false) {
228
+ return { payload: { cacheOverride } };
229
+ },
230
+ },
231
+ fetchBulkUploadBatchesSuccess(draft, action) {
232
+ const periodId = toMonthYearPeriodId(action.payload.selectedPeriod);
233
+ draft.bulkUpload.batchListByPeriod[periodId] = action.payload.batchList;
234
+ draft.bulkUpload.batchListFetchState = {
235
+ fetchState: 'Completed',
236
+ error: undefined,
237
+ };
238
+ },
239
+ fetchBulkUploadBatchesFailure(draft, action) {
240
+ draft.bulkUpload.batchListFetchState = {
241
+ fetchState: 'Error',
242
+ error: action.payload.error,
243
+ };
244
+ },
245
+ confirmBulkUploadMatch: {
246
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
247
+ reducer(draft, _action) {
248
+ draft.bulkUpload.confirmMatchStatus = {
249
+ fetchState: 'In-Progress',
250
+ error: undefined,
251
+ };
252
+ },
253
+ prepare(batchId, attachmentId, transactionId, transactionType, fileName) {
254
+ return { payload: { batchId, attachmentId, transactionId, transactionType, fileName } };
255
+ },
256
+ },
257
+ confirmBulkUploadMatchSuccess(draft, action) {
258
+ const { batchId, attachmentId, transactionId } = action.payload;
259
+ draft.bulkUpload.confirmMatchStatus = {
260
+ fetchState: 'Completed',
261
+ error: undefined,
262
+ };
263
+ const details = draft.bulkUpload.batchDetailsById[batchId];
264
+ if (details != null) {
265
+ const file = details.files.find((f) => f.attachmentId === attachmentId);
266
+ if (file != null) {
267
+ const originalStatus = file.status;
268
+ file.status = 'matched';
269
+ file.matchedTransactionId = transactionId;
270
+ file.candidates = undefined;
271
+ details.summary.matched += 1;
272
+ if (originalStatus === 'no_match') {
273
+ details.summary.noMatch = Math.max(0, details.summary.noMatch - 1);
274
+ }
275
+ else {
276
+ details.summary.possibleMatches = Math.max(0, details.summary.possibleMatches - 1);
277
+ }
278
+ }
279
+ }
280
+ },
281
+ confirmBulkUploadMatchFailure(draft, action) {
282
+ draft.bulkUpload.confirmMatchStatus = {
283
+ fetchState: 'Error',
284
+ error: action.payload.error,
285
+ };
286
+ },
287
+ setBulkUploadResultsTab(draft, action) {
288
+ draft.bulkUpload.currentResultsTab = action.payload;
289
+ },
290
+ setBulkUploadCompletedSubTab(draft, action) {
291
+ draft.bulkUpload.completedSubTab = action.payload;
292
+ draft.bulkUpload.completedTransactionsByPeriod = {};
293
+ },
294
+ setBulkUploadSortConfig(draft, action) {
295
+ draft.bulkUpload.sortKey = action.payload.sortKey;
296
+ draft.bulkUpload.sortOrder = action.payload.sortOrder;
297
+ },
298
+ clearBulkUpload(draft) {
299
+ Object.assign(draft.bulkUpload, initialBulkUploadState);
300
+ },
301
+ searchTransactionsForManualMatch: {
302
+ reducer(draft, action) {
303
+ draft.bulkUpload.manualSearch.searchQuery = action.payload.query;
304
+ draft.bulkUpload.manualSearch.fetchState = {
305
+ fetchState: 'In-Progress',
306
+ error: undefined,
307
+ };
308
+ draft.bulkUpload.manualSearch.results = [];
309
+ },
310
+ prepare(query) {
311
+ return { payload: { query } };
312
+ },
313
+ },
314
+ searchTransactionsForManualMatchSuccess(draft, action) {
315
+ draft.bulkUpload.manualSearch.results = action.payload;
316
+ draft.bulkUpload.manualSearch.fetchState = {
317
+ fetchState: 'Completed',
318
+ error: undefined,
319
+ };
320
+ },
321
+ searchTransactionsForManualMatchFailure(draft, action) {
322
+ draft.bulkUpload.manualSearch.fetchState = {
323
+ fetchState: 'Error',
324
+ error: action.payload.error,
325
+ };
326
+ },
327
+ clearManualSearchResults(draft) {
328
+ draft.bulkUpload.manualSearch = initialBulkUploadState.manualSearch;
329
+ },
330
+ // -- Completed Transactions Actions --
331
+ fetchCompletedTransactions: {
332
+ reducer(draft, action) {
333
+ const { pageToken } = action.payload;
334
+ if (pageToken != null) {
335
+ const periods = draft.bulkUpload.completedTransactionsByPeriod;
336
+ for (const entry of Object.values(periods)) {
337
+ if (entry.nextPageToken === pageToken) {
338
+ entry.fetchState = {
339
+ fetchState: 'In-Progress',
340
+ error: undefined,
341
+ };
342
+ break;
343
+ }
344
+ }
345
+ }
346
+ },
347
+ prepare(pageToken) {
348
+ return { payload: { pageToken } };
349
+ },
350
+ },
351
+ fetchCompletedTransactionsSuccess(draft, action) {
352
+ const { transactionIds, nextPageToken, totalCount, selectedPeriod, isInitialLoad } = action.payload;
353
+ const periodId = toMonthYearPeriodId(selectedPeriod);
354
+ if (isInitialLoad) {
355
+ draft.bulkUpload.completedTransactionsByPeriod[periodId] = {
356
+ transactionIds,
357
+ nextPageToken,
358
+ totalCount,
359
+ fetchState: { fetchState: 'Completed', error: undefined },
360
+ };
361
+ }
362
+ else {
363
+ const existing = draft.bulkUpload.completedTransactionsByPeriod[periodId];
364
+ if (existing != null) {
365
+ existing.transactionIds.push(...transactionIds);
366
+ existing.nextPageToken = nextPageToken;
367
+ existing.totalCount = totalCount;
368
+ existing.fetchState = { fetchState: 'Completed', error: undefined };
369
+ }
370
+ else {
371
+ draft.bulkUpload.completedTransactionsByPeriod[periodId] = {
372
+ transactionIds,
373
+ nextPageToken,
374
+ totalCount,
375
+ fetchState: { fetchState: 'Completed', error: undefined },
376
+ };
377
+ }
378
+ }
379
+ },
380
+ fetchCompletedTransactionsFailure(draft, action) {
381
+ const periodId = toMonthYearPeriodId(action.payload.selectedPeriod);
382
+ const existing = draft.bulkUpload.completedTransactionsByPeriod[periodId];
383
+ if (existing != null) {
384
+ existing.fetchState = {
385
+ fetchState: 'Error',
386
+ error: action.payload.error,
387
+ };
388
+ }
389
+ else {
390
+ draft.bulkUpload.completedTransactionsByPeriod[periodId] = {
391
+ fetchState: { fetchState: 'Error', error: action.payload.error },
392
+ nextPageToken: null,
393
+ totalCount: 0,
394
+ transactionIds: [],
395
+ };
396
+ }
397
+ },
123
398
  },
124
399
  });
125
- export const { fetchMissingReceipts, fetchMissingReceiptsSuccess, fetchMissingReceiptsFailure, uploadMissingReceiptSuccess, updateMissingReceiptUploadState, updateMissingReceiptsUIState, markMissingReceiptAsDone, clearExpenseAutomationMissingReceiptsView, } = expenseAutomationMissingReceiptsView.actions;
400
+ export const { fetchMissingReceipts, fetchMissingReceiptsSuccess, fetchMissingReceiptsFailure, uploadMissingReceiptSuccess, updateMissingReceiptUploadState, updateMissingReceiptsUIState, markMissingReceiptAsDone, clearExpenseAutomationMissingReceiptsView,
401
+ // Bulk Upload
402
+ bulkUploadReceipts, updateBulkUploadProgress, bulkUploadReceiptsSuccess, bulkUploadReceiptsFailure, pusherBatchStatusUpdate, fetchBulkUploadBatchDetails, fetchBulkUploadBatchDetailsSuccess, fetchBulkUploadBatchDetailsFailure, storeBatchDetails, fetchMoreBatchDetails, fetchMoreBatchDetailsComplete, fetchMoreBatchDetailsFailure, fetchBulkUploadBatches, fetchBulkUploadBatchesSuccess, fetchBulkUploadBatchesFailure, confirmBulkUploadMatch, confirmBulkUploadMatchSuccess, confirmBulkUploadMatchFailure, setBulkUploadResultsTab, setBulkUploadCompletedSubTab, setBulkUploadSortConfig, clearBulkUpload, searchTransactionsForManualMatch, searchTransactionsForManualMatchSuccess, searchTransactionsForManualMatchFailure, clearManualSearchResults,
403
+ // Completed Transactions
404
+ fetchCompletedTransactions, fetchCompletedTransactionsSuccess, fetchCompletedTransactionsFailure, } = expenseAutomationMissingReceiptsView.actions;
126
405
  export default expenseAutomationMissingReceiptsView.reducer;
@@ -1,7 +1,7 @@
1
1
  import orderBy from 'lodash/orderBy';
2
2
  import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
3
3
  import { getCurrentTenant } from '../../../entity/tenant/tenantSelector';
4
- import { getSupportedTransactionsByIds } from '../../../entity/transaction/transactionSelector';
4
+ import { getSupportedTransactionById, getSupportedTransactionsByIds, } from '../../../entity/transaction/transactionSelector';
5
5
  import { getAllSteps } from '../selectorTypes/expenseAutomationViewSelectorTypes';
6
6
  export function getExpenseAutomationMissingReceiptsView(state) {
7
7
  const { expenseAutomationMissingReceiptsViewState, expenseAutomationViewState, transactionState, monthEndCloseChecksState, } = state;
@@ -47,6 +47,82 @@ export function getExpenseAutomationMissingReceiptsView(state) {
47
47
  const completionStatus = missingReceiptsCompletionStatus != null
48
48
  ? missingReceiptsCompletionStatus
49
49
  : 'incomplete';
50
+ const { bulkUpload } = expenseAutomationMissingReceiptsViewState;
51
+ const currentBatchStatus = bulkUpload.currentBatchId != null
52
+ ? bulkUpload.batchStatusById[bulkUpload.currentBatchId]
53
+ : undefined;
54
+ const batchList = monthYearPeriodId != null
55
+ ? bulkUpload.batchListByPeriod[monthYearPeriodId] ?? []
56
+ : [];
57
+ const completedBatchesSorted = orderBy(batchList.filter((b) => b.status === 'completed'), (b) => b.createdAt, 'desc');
58
+ const effectiveCurrentBatchId = bulkUpload.currentBatchId ?? completedBatchesSorted[0]?.batchId;
59
+ const currentBatchDetails = effectiveCurrentBatchId != null
60
+ ? bulkUpload.batchDetailsById[effectiveCurrentBatchId]
61
+ : undefined;
62
+ const resolvedFiles = currentBatchDetails?.files.map((file) => resolveBatchFile(file, transactionState));
63
+ const hasMoreBatchDetails = completedBatchesSorted.some((b) => bulkUpload.batchDetailsById[b.batchId] == null);
64
+ const getSortValue = (file) => {
65
+ const t = file.matchedTransaction;
66
+ if (t == null) {
67
+ return null;
68
+ }
69
+ switch (bulkUpload.sortKey) {
70
+ case 'vendor':
71
+ return (t.vendorName ?? t.description)?.toLowerCase() ?? null;
72
+ case 'category': {
73
+ const line = t.lines?.[0];
74
+ return line?.account?.accountName?.toLowerCase() ?? null;
75
+ }
76
+ case 'class': {
77
+ const line = t.lines?.[0];
78
+ return line?.class?.className?.toLowerCase() ?? null;
79
+ }
80
+ case 'date':
81
+ return t.date.valueOf();
82
+ case 'amount':
83
+ return t.amount.amount;
84
+ default:
85
+ return null;
86
+ }
87
+ };
88
+ const bulkUploadSortDirection = bulkUpload.sortOrder === 'ascending' ? 'asc' : 'desc';
89
+ const matchedFiles = orderBy(resolvedFiles?.filter((f) => f.status === 'matched') ?? [], getSortValue, bulkUploadSortDirection);
90
+ const unmatchedFiles = orderBy(resolvedFiles?.filter((f) => f.status === 'un_matched' || f.status === 'no_match') ?? [], getSortValue, bulkUploadSortDirection);
91
+ const progress = currentBatchStatus != null
92
+ ? {
93
+ ...currentBatchStatus.progress,
94
+ percentage: currentBatchStatus.progress.total > 0
95
+ ? Math.round((currentBatchStatus.progress.processed /
96
+ currentBatchStatus.progress.total) *
97
+ 100)
98
+ : 0,
99
+ }
100
+ : { total: 0, processed: 0, percentage: 0 };
101
+ const batchListFetchState = bulkUpload.batchListFetchState;
102
+ const pastBatches = batchList.filter((b) => b.batchId !== effectiveCurrentBatchId);
103
+ const pastUnmatchedFiles = pastBatches.flatMap((b) => {
104
+ const details = bulkUpload.batchDetailsById[b.batchId];
105
+ if (details == null) {
106
+ return [];
107
+ }
108
+ return details.files
109
+ .filter((f) => f.status === 'un_matched' || f.status === 'no_match')
110
+ .map((f) => resolveBatchFile(f, transactionState));
111
+ });
112
+ const completedTransactionsForPeriod = monthYearPeriodId != null
113
+ ? bulkUpload.completedTransactionsByPeriod[monthYearPeriodId]
114
+ : undefined;
115
+ const completedTransactionIds = completedTransactionsForPeriod?.transactionIds ?? [];
116
+ const resolvedCompletedTransactions = getSupportedTransactionsByIds(transactionState, completedTransactionIds);
117
+ const resolvedBatchDetails = currentBatchDetails != null && resolvedFiles != null
118
+ ? {
119
+ batchId: currentBatchDetails.batchId,
120
+ createdAt: currentBatchDetails.createdAt,
121
+ files: resolvedFiles,
122
+ status: currentBatchDetails.status,
123
+ summary: currentBatchDetails.summary,
124
+ }
125
+ : undefined;
50
126
  return {
51
127
  fetchState,
52
128
  error,
@@ -55,5 +131,62 @@ export function getExpenseAutomationMissingReceiptsView(state) {
55
131
  refreshStatus,
56
132
  transactions: sortedTransactions,
57
133
  completionStatus,
134
+ bulkUpload: {
135
+ phase: bulkUpload.phase,
136
+ uploadStatus: bulkUpload.uploadStatus,
137
+ batchStatus: currentBatchStatus,
138
+ batchDetails: resolvedBatchDetails,
139
+ completedSubTab: bulkUpload.completedSubTab,
140
+ completedTransactions: {
141
+ fetchState: completedTransactionsForPeriod?.fetchState ?? {
142
+ fetchState: 'Not-Started',
143
+ error: undefined,
144
+ },
145
+ nextPageToken: completedTransactionsForPeriod?.nextPageToken ?? null,
146
+ totalCount: completedTransactionsForPeriod?.totalCount ?? 0,
147
+ transactions: resolvedCompletedTransactions,
148
+ },
149
+ matchedFiles,
150
+ unmatchedFiles,
151
+ progress,
152
+ currentResultsTab: bulkUpload.currentResultsTab,
153
+ sortKey: bulkUpload.sortKey,
154
+ sortOrder: bulkUpload.sortOrder,
155
+ batchList,
156
+ batchListFetchState,
157
+ pastBatches,
158
+ pastUnmatchedFiles,
159
+ confirmMatchStatus: bulkUpload.confirmMatchStatus,
160
+ hasMoreBatchDetails,
161
+ batchDetailsPaginationState: bulkUpload.batchDetailsPaginationState,
162
+ manualSearch: bulkUpload.manualSearch,
163
+ uploadProgress: bulkUpload.uploadProgress,
164
+ },
165
+ };
166
+ }
167
+ function resolveBatchFile(file, transactionState) {
168
+ const matchedTransaction = file.matchedTransactionId != null
169
+ ? getSupportedTransactionById(transactionState, file.matchedTransactionId)
170
+ : undefined;
171
+ const candidates = file.candidates
172
+ ?.map((ref) => {
173
+ const transaction = getSupportedTransactionById(transactionState, ref.transactionId);
174
+ return transaction != null
175
+ ? { transaction, matchScore: ref.matchScore }
176
+ : undefined;
177
+ })
178
+ .filter((c) => c != null);
179
+ return {
180
+ attachmentId: file.attachmentId,
181
+ fileDownloadUrl: file.fileDownloadUrl,
182
+ fileId: file.fileId,
183
+ filename: file.filename,
184
+ filePreviewUrl: file.filePreviewUrl,
185
+ status: file.status,
186
+ matchedTransaction,
187
+ candidates: candidates != null && candidates.length > 0
188
+ ? candidates
189
+ : undefined,
190
+ matchSource: file.matchSource,
58
191
  };
59
192
  }
@@ -2,7 +2,6 @@ import { createSlice } from '@reduxjs/toolkit';
2
2
  export const initialState = {
3
3
  fetchState: 'Not-Started',
4
4
  error: undefined,
5
- listeningToPusherEvent: false,
6
5
  approverListState: {
7
6
  approverIds: [],
8
7
  attributes: [],
@@ -155,11 +154,6 @@ const billPaySetupApproverView = createSlice({
155
154
  reducer(draft, action) {
156
155
  const { approvalRuleIds } = action.payload;
157
156
  if (draft.billPaySetupApproverViewUpdateData != null) {
158
- if (draft.billPaySetupApproverViewUpdateData.type === 'approval_update' &&
159
- draft.billPaySetupApproverViewUpdateData.data
160
- ?.isApplicableOnPendingApprovalEntity === true) {
161
- draft.listeningToPusherEvent = true;
162
- }
163
157
  draft.billPaySetupApproverViewUpdateData.updateStatus = {
164
158
  fetchState: 'Completed',
165
159
  error: undefined,
@@ -220,9 +214,6 @@ const billPaySetupApproverView = createSlice({
220
214
  return { payload: { billPaySetupApproverViewUpdateData } };
221
215
  },
222
216
  },
223
- setListeningToPusherEvent(draft, action) {
224
- draft.listeningToPusherEvent = action.payload;
225
- },
226
217
  clearBillPaySetupApproverViewUpdateData(draft) {
227
218
  if (draft.billPaySetupApproverViewUpdateData != null) {
228
219
  draft.billPaySetupApproverViewUpdateData.data = undefined;
@@ -233,5 +224,5 @@ const billPaySetupApproverView = createSlice({
233
224
  },
234
225
  },
235
226
  });
236
- export const { fetchBillPaySetupApproverView, fetchBillPaySetupApproverViewSuccess, fetchBillPaySetupApproverViewFailure, fetchBillPayApproversDetails, fetchBillPayApproversList, fetchBillPayApproversListSuccess, fetchBillPayApproversListFailure, deleteBillPayApprovalRule, deleteBillPayApprovalRuleSuccess, deleteBillPayApprovalRuleFailure, saveBillPaySetupApproverViewUpdates, saveBillPaySetupApproverViewUpdatesSuccess, saveBillPaySetupApproverViewUpdatesFailure, initializeBillPaySetupApproverViewUpdateData, saveBillPaySetupApproverViewUpdateData, clearBillPaySetupApproverViewUpdateData, setListeningToPusherEvent, clearBillPaySetupApproverView, } = billPaySetupApproverView.actions;
227
+ export const { fetchBillPaySetupApproverView, fetchBillPaySetupApproverViewSuccess, fetchBillPaySetupApproverViewFailure, fetchBillPayApproversDetails, fetchBillPayApproversList, fetchBillPayApproversListSuccess, fetchBillPayApproversListFailure, deleteBillPayApprovalRule, deleteBillPayApprovalRuleSuccess, deleteBillPayApprovalRuleFailure, saveBillPaySetupApproverViewUpdates, saveBillPaySetupApproverViewUpdatesSuccess, saveBillPaySetupApproverViewUpdatesFailure, initializeBillPaySetupApproverViewUpdateData, saveBillPaySetupApproverViewUpdateData, clearBillPaySetupApproverViewUpdateData, clearBillPaySetupApproverView, } = billPaySetupApproverView.actions;
237
228
  export default billPaySetupApproverView.reducer;
@@ -6,7 +6,7 @@ import { getClassesByIds } from '../../../../entity/class/classSelector';
6
6
  import { getUserByUserId } from '../../../../entity/user/userSelector';
7
7
  import { getUserRoleByUserId } from '../../../../entity/userRole/userRoleSelector';
8
8
  export const getBillPaySetupApproverView = createSelector((state) => state.billPaySetupApproverViewState, (state) => state.approvalRuleState, (state) => state.userState, (billPaySetupApproverViewState, approvalRuleState, userState) => {
9
- const { approvalRulesDeleteStatusById, approvalRuleIds, fetchState, error, listeningToPusherEvent, } = billPaySetupApproverViewState;
9
+ const { approvalRulesDeleteStatusById, approvalRuleIds, fetchState, error } = billPaySetupApproverViewState;
10
10
  const deleteStateList = Object.values(approvalRulesDeleteStatusById);
11
11
  const approvalRules = getApprovalRulesByIds(approvalRuleState, approvalRuleIds);
12
12
  const allApprovalRules = approvalRules.map((approvalRule) => {
@@ -40,11 +40,10 @@ export const getBillPaySetupApproverView = createSelector((state) => state.billP
40
40
  approvalRulesStatus,
41
41
  fetchState,
42
42
  error,
43
- listeningToPusherEvent,
44
43
  };
45
44
  });
46
45
  export const getBillPaySetupApproverUpdateDataView = createSelector((state) => state.billPaySetupApproverViewState, (state) => state.approvalRuleState, (state) => state.userState, (state) => state.classState, (state) => state.classListState, (state) => state.userRoleState, (billPaySetupApproverViewState, approvalRuleState, userState, classState, classListState, userRoleState) => {
47
- const { approvalRuleIds, approverListState, billPaySetupApproverViewUpdateData, fetchState, error, listeningToPusherEvent, } = billPaySetupApproverViewState;
46
+ const { approvalRuleIds, approverListState, billPaySetupApproverViewUpdateData, fetchState, error, } = billPaySetupApproverViewState;
48
47
  const { approverIds, attributes } = approverListState;
49
48
  const allApprovalRules = getApprovalRulesByIds(approvalRuleState, approvalRuleIds);
50
49
  const allApprovers = approverIds
@@ -97,6 +96,5 @@ export const getBillPaySetupApproverUpdateDataView = createSelector((state) => s
97
96
  fetchState,
98
97
  allAccountingClasses,
99
98
  error,
100
- listeningToPusherEvent,
101
99
  };
102
100
  });
@@ -33,15 +33,12 @@ export const toApprovalChangableInfoPayload = (approverViewUpdateData) => {
33
33
  }),
34
34
  };
35
35
  });
36
- const isUpdate = approverViewUpdateData.approvalRuleId != null;
37
36
  const payload = {
38
37
  criteria: criteriaPayload,
39
38
  steps,
40
39
  };
41
- if (isUpdate) {
42
- payload.approval_rule_id = approverViewUpdateData.approvalRuleId;
43
- payload.is_applicable_on_pending_approval_entity =
44
- approverViewUpdateData.isApplicableOnPendingApprovalEntity ?? false;
40
+ if (approverViewUpdateData.approvalRuleId != null) {
41
+ payload['approval_rule_id'] = approverViewUpdateData.approvalRuleId;
45
42
  }
46
43
  return payload;
47
44
  };
@@ -2,7 +2,6 @@ import { createSlice } from '@reduxjs/toolkit';
2
2
  export const initialState = {
3
3
  fetchState: 'Not-Started',
4
4
  error: undefined,
5
- listeningToPusherEvent: false,
6
5
  approverListState: {
7
6
  approverIds: [],
8
7
  attributes: [],
@@ -177,11 +176,6 @@ const remiSetupApproverView = createSlice({
177
176
  reducer(draft, action) {
178
177
  const { approvalRuleIds } = action.payload;
179
178
  if (draft.remiSetupApproverViewUpdateData != null) {
180
- if (draft.remiSetupApproverViewUpdateData.type === 'approval_update' &&
181
- draft.remiSetupApproverViewUpdateData.data
182
- ?.isApplicableOnPendingApprovalEntity === true) {
183
- draft.listeningToPusherEvent = true;
184
- }
185
179
  draft.remiSetupApproverViewUpdateData.updateStatus = {
186
180
  fetchState: 'Completed',
187
181
  error: undefined,
@@ -242,9 +236,6 @@ const remiSetupApproverView = createSlice({
242
236
  return { payload: { remiSetupApproverViewUpdateData } };
243
237
  },
244
238
  },
245
- setListeningToPusherEvent(draft, action) {
246
- draft.listeningToPusherEvent = action.payload;
247
- },
248
239
  clearRemiSetupApproverViewUpdateData(draft) {
249
240
  if (draft.remiSetupApproverViewUpdateData != null) {
250
241
  draft.remiSetupApproverViewUpdateData.data = undefined;
@@ -255,5 +246,5 @@ const remiSetupApproverView = createSlice({
255
246
  },
256
247
  },
257
248
  });
258
- export const { fetchRemiSetupApproverView, fetchRemiSetupApproverViewSuccess, fetchRemiSetupApproverViewFailure, fetchRemiApproversDetails, fetchRemiApproversList, fetchRemiApproversListSuccess, fetchRemiApproversListFailure, deleteRemiApprovalRule, deleteRemiApprovalRuleSuccess, deleteRemiApprovalRuleFailure, saveRemiSetupApproverViewUpdates, saveRemiSetupApproverViewUpdatesSuccess, saveRemiSetupApproverViewUpdatesFailure, initializeRemiSetupApproverViewUpdateData, saveRemiSetupApproverViewUpdateData, setListeningToPusherEvent, clearRemiSetupApproverViewUpdateData, clearRemiSetupApproverView, } = remiSetupApproverView.actions;
249
+ export const { fetchRemiSetupApproverView, fetchRemiSetupApproverViewSuccess, fetchRemiSetupApproverViewFailure, fetchRemiApproversDetails, fetchRemiApproversList, fetchRemiApproversListSuccess, fetchRemiApproversListFailure, deleteRemiApprovalRule, deleteRemiApprovalRuleSuccess, deleteRemiApprovalRuleFailure, saveRemiSetupApproverViewUpdates, saveRemiSetupApproverViewUpdatesSuccess, saveRemiSetupApproverViewUpdatesFailure, initializeRemiSetupApproverViewUpdateData, saveRemiSetupApproverViewUpdateData, clearRemiSetupApproverViewUpdateData, clearRemiSetupApproverView, } = remiSetupApproverView.actions;
259
250
  export default remiSetupApproverView.reducer;
@@ -7,7 +7,7 @@ import { getUserByUserId } from '../../../../entity/user/userSelector';
7
7
  import { getUserRoleByUserId } from '../../../../entity/userRole/userRoleSelector';
8
8
  export const getRemiSetupApproverView = (state, filterAdmins = true) => {
9
9
  const { remiSetupApproverViewState, approvalRuleState, userState } = state;
10
- const { approvalRulesDeleteStatusById, approvalRuleIds, fetchState, error, listeningToPusherEvent, } = remiSetupApproverViewState;
10
+ const { approvalRulesDeleteStatusById, approvalRuleIds, fetchState, error } = remiSetupApproverViewState;
11
11
  const deleteStateList = Object.values(approvalRulesDeleteStatusById);
12
12
  const approvalRules = getApprovalRulesByIds(approvalRuleState, approvalRuleIds);
13
13
  const allApprovalRules = approvalRules.map((approvalRule) => {
@@ -42,11 +42,10 @@ export const getRemiSetupApproverView = (state, filterAdmins = true) => {
42
42
  approvalRulesStatus,
43
43
  fetchState,
44
44
  error,
45
- listeningToPusherEvent,
46
45
  };
47
46
  };
48
47
  export const getRemiSetupApproverUpdateDataView = createSelector((state) => state.remiSetupApproverViewState, (state) => state.approvalRuleState, (state) => state.classState, (state) => state.classListState, (state) => state.userState, (state) => state.userRoleState, (remiSetupApproverViewState, approvalRuleState, classState, classListState, userState, userRoleState) => {
49
- const { approvalRuleIds, approverListState, remiSetupApproverViewUpdateData, fetchState, error, listeningToPusherEvent, } = remiSetupApproverViewState;
48
+ const { approvalRuleIds, approverListState, remiSetupApproverViewUpdateData, fetchState, error, } = remiSetupApproverViewState;
50
49
  const { approverIds, attributes, fetchStatus } = approverListState;
51
50
  const { fetchState: classListFetchState, error: classListFetchStateError, classIds, } = classListState;
52
51
  const allApprovalRules = getApprovalRulesByIds(approvalRuleState, approvalRuleIds).map((approvalRule) => {
@@ -114,6 +113,5 @@ export const getRemiSetupApproverUpdateDataView = createSelector((state) => stat
114
113
  ]),
115
114
  fetchState,
116
115
  error,
117
- listeningToPusherEvent,
118
116
  };
119
117
  });
@@ -1,5 +1,5 @@
1
1
  import { fetch } from 'cross-fetch';
2
- import { from } from 'rxjs';
2
+ import { Observable, from } from 'rxjs';
3
3
  import { isInvalidSessionError } from './responsePayload';
4
4
  export class ZeniAPIClient {
5
5
  constructor(apiEndPoints, userId, sessionId, tenantId, onSessionInvalidated) {
@@ -79,8 +79,6 @@ export class ZeniAPIClient {
79
79
  return from(promise);
80
80
  }
81
81
  postFormData(restEndPoint, formData, signal) {
82
- // Use apiHeaders for auth headers, then remove Content-Type
83
- // so the browser sets multipart/form-data with the correct boundary
84
82
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
85
83
  const { 'Content-Type': _contentType, ...headers } = this.apiHeaders();
86
84
  const promise = fetch(restEndPoint, {
@@ -91,6 +89,46 @@ export class ZeniAPIClient {
91
89
  }).then((response) => this.processJsonResponse(response));
92
90
  return from(promise);
93
91
  }
92
+ postFormDataWithProgress(restEndPoint, formData, signal) {
93
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
94
+ const { 'Content-Type': _ct, ...headers } = this.apiHeaders();
95
+ return new Observable((subscriber) => {
96
+ const xhr = new XMLHttpRequest();
97
+ xhr.upload.onprogress = (event) => {
98
+ if (event.lengthComputable) {
99
+ const percentage = Math.round((event.loaded / event.total) * 100);
100
+ subscriber.next({ type: 'progress', percentage });
101
+ }
102
+ };
103
+ xhr.onload = () => {
104
+ try {
105
+ const json = JSON.parse(xhr.responseText);
106
+ this.checkSessionValidity(json);
107
+ subscriber.next({ type: 'response', data: json });
108
+ subscriber.complete();
109
+ }
110
+ catch (e) {
111
+ subscriber.error(e);
112
+ }
113
+ };
114
+ xhr.onerror = () => {
115
+ subscriber.error(new Error('Upload failed'));
116
+ };
117
+ xhr.open('POST', restEndPoint);
118
+ Object.entries(headers).forEach(([key, value]) => {
119
+ xhr.setRequestHeader(key, String(value));
120
+ });
121
+ xhr.send(formData);
122
+ if (signal != null) {
123
+ signal.addEventListener('abort', () => xhr.abort());
124
+ }
125
+ return () => {
126
+ if (xhr.readyState !== XMLHttpRequest.DONE) {
127
+ xhr.abort();
128
+ }
129
+ };
130
+ });
131
+ }
94
132
  deleteAndGetJSON(restEndPoint, body, headers, signal) {
95
133
  let apiHeaders = this.apiHeaders(headers);
96
134
  if (body != null) {