@zeniai/client-epic-state 5.2.0 → 5.2.1

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 (57) hide show
  1. package/lib/entity/accountRecon/accountReconReducer.js +4 -0
  2. package/lib/entity/accountRecon/accountReconState.d.ts +4 -1
  3. package/lib/entity/accountRecon/accountReconState.js +1 -0
  4. package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
  5. package/lib/entity/snackbar/snackbarTypes.js +1 -0
  6. package/lib/epic.d.ts +4 -1
  7. package/lib/epic.js +4 -1
  8. package/lib/esm/entity/accountRecon/accountReconReducer.js +4 -0
  9. package/lib/esm/entity/accountRecon/accountReconState.js +1 -0
  10. package/lib/esm/entity/snackbar/snackbarTypes.js +1 -0
  11. package/lib/esm/epic.js +4 -1
  12. package/lib/esm/index.js +3 -3
  13. package/lib/esm/view/expenseAutomationView/epics/accountRecon/confirmStatementMappingEpic.js +43 -0
  14. package/lib/esm/view/expenseAutomationView/epics/accountRecon/deleteAccountStatementEpic.js +1 -2
  15. package/lib/esm/view/expenseAutomationView/epics/accountRecon/parseCombinedStatementEpic.js +31 -0
  16. package/lib/esm/view/expenseAutomationView/epics/accountRecon/parseStatementEpic.js +15 -18
  17. package/lib/esm/view/expenseAutomationView/epics/accountRecon/updateCombinedStatementInfoEpic.js +68 -0
  18. package/lib/esm/view/expenseAutomationView/epics/accountRecon/updateStatementInfoEpic.js +10 -10
  19. package/lib/esm/view/expenseAutomationView/epics/accountRecon/uploadAccountStatementDocumentAIHelper.js +12 -4
  20. package/lib/esm/view/expenseAutomationView/epics/accountRecon/uploadAccountStatementEpic.js +8 -2
  21. package/lib/esm/view/expenseAutomationView/helpers/mapStatementUpdateLocalDataToRequestBody.js +20 -5
  22. package/lib/esm/view/expenseAutomationView/payload/reconciliationPayload.js +135 -0
  23. package/lib/esm/view/expenseAutomationView/reducers/reconciliationViewReducer.js +120 -16
  24. package/lib/esm/view/expenseAutomationView/selectors/reconciliationViewSelector.js +38 -7
  25. package/lib/esm/view/expenseAutomationView/selectors/statementParseViewSelector.js +43 -10
  26. package/lib/esm/view/expenseAutomationView/types/reconciliationViewState.js +15 -1
  27. package/lib/index.d.ts +4 -4
  28. package/lib/index.js +51 -46
  29. package/lib/view/expenseAutomationView/epics/accountRecon/confirmStatementMappingEpic.d.ts +34 -0
  30. package/lib/view/expenseAutomationView/epics/accountRecon/confirmStatementMappingEpic.js +47 -0
  31. package/lib/view/expenseAutomationView/epics/accountRecon/deleteAccountStatementEpic.d.ts +2 -2
  32. package/lib/view/expenseAutomationView/epics/accountRecon/deleteAccountStatementEpic.js +0 -1
  33. package/lib/view/expenseAutomationView/epics/accountRecon/parseCombinedStatementEpic.d.ts +26 -0
  34. package/lib/view/expenseAutomationView/epics/accountRecon/parseCombinedStatementEpic.js +35 -0
  35. package/lib/view/expenseAutomationView/epics/accountRecon/parseStatementEpic.d.ts +21 -1
  36. package/lib/view/expenseAutomationView/epics/accountRecon/parseStatementEpic.js +15 -18
  37. package/lib/view/expenseAutomationView/epics/accountRecon/updateCombinedStatementInfoEpic.d.ts +27 -0
  38. package/lib/view/expenseAutomationView/epics/accountRecon/updateCombinedStatementInfoEpic.js +72 -0
  39. package/lib/view/expenseAutomationView/epics/accountRecon/updateStatementInfoEpic.js +10 -10
  40. package/lib/view/expenseAutomationView/epics/accountRecon/uploadAccountStatementDocumentAIHelper.d.ts +1 -1
  41. package/lib/view/expenseAutomationView/epics/accountRecon/uploadAccountStatementDocumentAIHelper.js +12 -4
  42. package/lib/view/expenseAutomationView/epics/accountRecon/uploadAccountStatementEpic.js +7 -1
  43. package/lib/view/expenseAutomationView/helpers/mapStatementUpdateLocalDataToRequestBody.d.ts +1 -0
  44. package/lib/view/expenseAutomationView/helpers/mapStatementUpdateLocalDataToRequestBody.js +20 -4
  45. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
  46. package/lib/view/expenseAutomationView/payload/reconciliationPayload.d.ts +109 -4
  47. package/lib/view/expenseAutomationView/payload/reconciliationPayload.js +143 -0
  48. package/lib/view/expenseAutomationView/reducers/reconciliationViewReducer.d.ts +32 -4
  49. package/lib/view/expenseAutomationView/reducers/reconciliationViewReducer.js +122 -18
  50. package/lib/view/expenseAutomationView/selectorTypes/reconciliationViewSelectorTypes.d.ts +5 -1
  51. package/lib/view/expenseAutomationView/selectors/reconciliationViewSelector.d.ts +4 -1
  52. package/lib/view/expenseAutomationView/selectors/reconciliationViewSelector.js +40 -7
  53. package/lib/view/expenseAutomationView/selectors/statementParseViewSelector.d.ts +3 -2
  54. package/lib/view/expenseAutomationView/selectors/statementParseViewSelector.js +44 -10
  55. package/lib/view/expenseAutomationView/types/reconciliationViewState.d.ts +74 -1
  56. package/lib/view/expenseAutomationView/types/reconciliationViewState.js +19 -1
  57. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
1
  import { convertToPeriod, } from '../../../commonStateTypes/timePeriod';
2
2
  import { toTransactionsToReviewPayload, } from '../../../entity/accountRecon/accountReconPayload';
3
3
  import { toMatchedTransactionPayload, } from '../../../entity/transaction/payloadTypes/reconciliationTransactionPayloads';
4
+ import { toStatementBalanceSourceStrict, } from '../types/reconciliationViewState';
4
5
  export function createSaveReconciliationPayloadBasedOnActionAndMethod(state, selectedPeriod, localData, accountReconciliationEntity, action, method) {
5
6
  const { balance, initialClearedBalance } = accountReconciliationEntity;
6
7
  const { expenseAutomationReconciliationViewState: { selectedAccountId, reconTabsState: { reconcile: { localData: { selectedTransactionIds }, }, }, }, } = state;
@@ -67,10 +68,60 @@ function createReviewTransactionPayload(accountReconciliationEntity) {
67
68
  });
68
69
  return reviewTransactionsPayload;
69
70
  }
71
+ export function isParseStatementCombinedResponsePayload(payload) {
72
+ return ('statement_uploads' in payload &&
73
+ Array.isArray(payload.statement_uploads) &&
74
+ payload.statement_uploads.length > 0 &&
75
+ // Batch group GET nests each upload under statement_upload — exclude that shape.
76
+ 'statement_upload' in (payload.statement_uploads[0] ?? {}) === false);
77
+ }
78
+ export function isUpdateStatementInfoCombinedResponsePayload(payload) {
79
+ return ('statement_uploads' in payload &&
80
+ Array.isArray(payload.statement_uploads) &&
81
+ payload.statement_uploads.length > 0);
82
+ }
70
83
  export function isStatementDateConflictResponse(response) {
71
84
  return response.status.code === 409 && response.error_code != null;
72
85
  }
86
+ function transformConfirmedCombinedStatementUploadsToState(statementUploads) {
87
+ return (statementUploads ?? []).map((statementUpload) => ({
88
+ accountId: statementUpload.account_id,
89
+ isCombinedStatementAnchor: statementUpload.is_combined_statement_anchor,
90
+ ledgerIdentifiers: statementUpload.ledger_identifiers ?? [],
91
+ statementUploadId: statementUpload.statement_upload_id,
92
+ }));
93
+ }
94
+ function transformCombinedStatementDetailsToState(details) {
95
+ if (details == null) {
96
+ return undefined;
97
+ }
98
+ return {
99
+ combinedStatementGroupId: details.combined_statement_group_id,
100
+ isMappingConfirmed: details.is_mapping_confirmed,
101
+ accounts: (details.accounts ?? []).map((account) => ({
102
+ ledgerIdentifier: account.ledger_identifier,
103
+ accountName: account.account_name,
104
+ accountType: account.account_type,
105
+ maskedAccountNumber: account.masked_account_number,
106
+ openingBalance: account.opening_balance,
107
+ closingBalance: account.closing_balance,
108
+ transactionCount: account.transaction_count,
109
+ isUploadContext: account.is_upload_context,
110
+ existingStatementStatus: account.existing_statement_status ?? null,
111
+ suggestion: account.suggestion != null
112
+ ? {
113
+ accountId: account.suggestion.account_id,
114
+ accountName: account.suggestion.account_name,
115
+ matchedBy: account.suggestion.matched_by,
116
+ matchConfidence: account.suggestion.match_confidence,
117
+ }
118
+ : null,
119
+ })),
120
+ statementUploads: transformConfirmedCombinedStatementUploadsToState(details.statement_uploads),
121
+ };
122
+ }
73
123
  function transformStatementUploadPayloadToState(statementUploadPayload) {
124
+ const balanceSource = toStatementBalanceSourceStrict(statementUploadPayload.statement_meta.balance_source);
74
125
  return {
75
126
  account: {
76
127
  accountId: statementUploadPayload.account.account_id,
@@ -96,6 +147,13 @@ function transformStatementUploadPayloadToState(statementUploadPayload) {
96
147
  statementUploadId: statementUploadPayload.statement_meta.statement_upload_id,
97
148
  totalDeposits: statementUploadPayload.statement_meta.total_deposits,
98
149
  totalPayments: statementUploadPayload.statement_meta.total_payments,
150
+ isCombinedStatement: statementUploadPayload.statement_meta.is_combined_statement ??
151
+ undefined,
152
+ isCombinedStatementAnchor: statementUploadPayload.statement_meta.is_combined_statement_anchor ??
153
+ undefined,
154
+ combinedStatementGroupId: statementUploadPayload.statement_meta.combined_statement_group_id ??
155
+ undefined,
156
+ ...(balanceSource != null ? { balanceSource } : {}),
99
157
  },
100
158
  statementTransactions: (statementUploadPayload.statement_transactions ?? []).map((txn) => ({
101
159
  amount: txn.amount,
@@ -117,6 +175,7 @@ function transformStatementUploadPayloadToState(statementUploadPayload) {
117
175
  transactionMemo: txn.transaction_memo,
118
176
  })),
119
177
  aiSummary: transformAiSummaryPayloadToState(statementUploadPayload.ai_summary),
178
+ combinedStatementDetails: transformCombinedStatementDetailsToState(statementUploadPayload.combined_statement_details),
120
179
  };
121
180
  }
122
181
  function transformPreviousReconciliationInfoToState(payload) {
@@ -177,11 +236,71 @@ export function transformStatementDateConflictToState(response) {
177
236
  };
178
237
  }
179
238
  export function transformParseStatementPayloadToState(payload) {
239
+ if (isParseStatementCombinedResponsePayload(payload) === true) {
240
+ const statementUploads = payload.statement_uploads.map(transformStatementUploadPayloadToState);
241
+ const anchorUpload = statementUploads.find((upload) => upload.statementMeta.isCombinedStatementAnchor === true) ?? statementUploads[0];
242
+ return {
243
+ statementUpload: anchorUpload,
244
+ statementUploads,
245
+ };
246
+ }
180
247
  return {
181
248
  statementUpload: transformStatementUploadPayloadToState(payload.statement_upload),
182
249
  };
183
250
  }
251
+ export function transformCombinedStatementGroupPayloadToState(payload) {
252
+ const groupId = payload.combined_statement_group_id;
253
+ const statementUploads = (payload.statement_uploads ?? []).map((entry) => {
254
+ const upload = transformStatementUploadPayloadToState(entry.statement_upload);
255
+ if ((upload.statementMeta.combinedStatementGroupId == null ||
256
+ upload.statementMeta.combinedStatementGroupId === '') &&
257
+ groupId !== '') {
258
+ return {
259
+ ...upload,
260
+ statementMeta: {
261
+ ...upload.statementMeta,
262
+ combinedStatementGroupId: groupId,
263
+ },
264
+ };
265
+ }
266
+ return upload;
267
+ });
268
+ if (statementUploads.length === 0) {
269
+ throw new Error('Combined statement response has no statement uploads');
270
+ }
271
+ const anchorUpload = statementUploads.find((upload) => upload.statementMeta.isCombinedStatementAnchor === true) ?? statementUploads[0];
272
+ return {
273
+ statementUpload: anchorUpload,
274
+ statementUploads,
275
+ };
276
+ }
277
+ export function isConfirmStatementMappingConflictResponse(response) {
278
+ return response.status.code === 409;
279
+ }
280
+ export function transformConfirmStatementMappingPayloadToState(payload) {
281
+ return transformConfirmedCombinedStatementUploadsToState(payload.statement_uploads);
282
+ }
283
+ export function transformConfirmStatementMappingConflictToState(response) {
284
+ if (isConfirmStatementMappingConflictResponse(response) === false) {
285
+ return null;
286
+ }
287
+ return {
288
+ collidingAccountId: response.colliding_account_id != null
289
+ ? response.colliding_account_id
290
+ : undefined,
291
+ collidingLedgerIdentifiers: response.colliding_ledger_identifiers ?? [],
292
+ previousReconciliationInfo: transformPreviousReconciliationInfoToState(response.previous_reconciliation_info),
293
+ };
294
+ }
184
295
  export function transformUpdateStatementInfoPayloadToState(payload) {
296
+ if (isUpdateStatementInfoCombinedResponsePayload(payload) === true) {
297
+ const statementUploads = payload.statement_uploads.map(transformStatementUploadPayloadToState);
298
+ const anchorUpload = statementUploads.find((upload) => upload.statementMeta.isCombinedStatementAnchor === true) ?? statementUploads[0];
299
+ return {
300
+ statementUpload: anchorUpload,
301
+ statementUploads,
302
+ };
303
+ }
185
304
  return {
186
305
  statementUpload: transformStatementUploadPayloadToState(payload.statement_upload),
187
306
  };
@@ -221,3 +340,19 @@ export function transformStatementUpdateStateToPayload(localData) {
221
340
  export function toStatementUpdateRequestBody(localData) {
222
341
  return transformStatementUpdateStateToPayload(localData);
223
342
  }
343
+ export function transformBulkStatementUpdateStateToPayload(updates) {
344
+ return {
345
+ statement_uploads: updates.map(({ localData, statementUploadId }) => {
346
+ const body = transformStatementUpdateStateToPayload(localData);
347
+ return {
348
+ statement_upload_id: statementUploadId,
349
+ statement_meta: body.statement_meta,
350
+ statement_transactions: body.statement_transactions,
351
+ };
352
+ }),
353
+ };
354
+ }
355
+ /** Bulk PUT body for combined multi-account Confirm & Save. */
356
+ export function toBulkStatementUpdateRequestBody(updates) {
357
+ return transformBulkStatementUpdateStateToPayload(updates);
358
+ }
@@ -430,7 +430,7 @@ const expenseAutomationReconciliationView = createSlice({
430
430
  }
431
431
  },
432
432
  uploadAccountStatement: (draft, action) => {
433
- const { accountId, selectedPeriod } = action.payload;
433
+ const { accountId, selectedPeriod, statementAccountScope } = action.payload;
434
434
  const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
435
435
  if (accountRecon != null) {
436
436
  accountRecon.statementUploadStatus = {
@@ -455,6 +455,22 @@ const expenseAutomationReconciliationView = createSlice({
455
455
  fetchState: 'Not-Started',
456
456
  error: undefined,
457
457
  };
458
+ accountRecon.combinedStatementGroupId = undefined;
459
+ accountRecon.confirmedCombinedStatementUploads = undefined;
460
+ accountRecon.confirmStatementMappingStatus = {
461
+ fetchState: 'Not-Started',
462
+ error: undefined,
463
+ };
464
+ accountRecon.confirmStatementMappingConflict = null;
465
+ if (accountRecon.localData != null) {
466
+ accountRecon.localData.statementUpdateLocalDataByUploadId = undefined;
467
+ }
468
+ if (statementAccountScope != null) {
469
+ accountRecon.localData = {
470
+ ...accountRecon.localData,
471
+ statementAccountScope,
472
+ };
473
+ }
458
474
  }
459
475
  },
460
476
  uploadAccountStatementSuccess: (draft, action) => {
@@ -517,8 +533,16 @@ const expenseAutomationReconciliationView = createSlice({
517
533
  fetchState: 'Not-Started',
518
534
  error: undefined,
519
535
  };
536
+ accountRecon.combinedStatementGroupId = undefined;
537
+ accountRecon.confirmedCombinedStatementUploads = undefined;
538
+ accountRecon.confirmStatementMappingStatus = {
539
+ fetchState: 'Not-Started',
540
+ error: undefined,
541
+ };
542
+ accountRecon.confirmStatementMappingConflict = null;
520
543
  if (accountRecon.localData != null) {
521
- accountRecon.localData.statementUpdateLocalData = undefined;
544
+ accountRecon.localData.statementUpdateLocalDataByUploadId = undefined;
545
+ accountRecon.localData.statementAccountScope = undefined;
522
546
  }
523
547
  }
524
548
  },
@@ -544,6 +568,17 @@ const expenseAutomationReconciliationView = createSlice({
544
568
  };
545
569
  }
546
570
  },
571
+ parseCombinedStatement: (draft, action) => {
572
+ const { accountId, selectedPeriod } = action.payload;
573
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
574
+ if (accountRecon != null) {
575
+ accountRecon.parsedStatementData = undefined;
576
+ accountRecon.statementParseStatus = {
577
+ fetchState: 'In-Progress',
578
+ error: undefined,
579
+ };
580
+ }
581
+ },
547
582
  parseStatementSuccess: (draft, action) => {
548
583
  const { accountId, selectedPeriod } = action.payload;
549
584
  const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
@@ -598,7 +633,7 @@ const expenseAutomationReconciliationView = createSlice({
598
633
  accountRecon.statementProcessingFailed = false;
599
634
  accountRecon.statementParseInProgress = true;
600
635
  if (accountRecon.localData != null) {
601
- accountRecon.localData.statementUpdateLocalData = undefined;
636
+ accountRecon.localData.statementUpdateLocalDataByUploadId = undefined;
602
637
  }
603
638
  }
604
639
  },
@@ -644,19 +679,18 @@ const expenseAutomationReconciliationView = createSlice({
644
679
  parsedStatementData;
645
680
  },
646
681
  updateStatementUpdateLocalData: (draft, action) => {
647
- const { accountId, selectedPeriod, statementUpdateLocalData } = action.payload;
682
+ const { accountId, selectedPeriod, statementUpdateLocalData, statementUploadId, } = action.payload;
648
683
  const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
649
- if (accountRecon != null) {
650
- if (accountRecon.localData == null) {
651
- accountRecon.localData = {
652
- statementUpdateLocalData,
653
- };
654
- }
655
- else {
656
- accountRecon.localData.statementUpdateLocalData =
657
- statementUpdateLocalData;
658
- }
684
+ if (accountRecon == null || statementUploadId === '') {
685
+ return;
659
686
  }
687
+ if (accountRecon.localData == null) {
688
+ accountRecon.localData = {};
689
+ }
690
+ accountRecon.localData.statementUpdateLocalDataByUploadId = {
691
+ ...accountRecon.localData.statementUpdateLocalDataByUploadId,
692
+ [statementUploadId]: statementUpdateLocalData,
693
+ };
660
694
  },
661
695
  submitStatementUpdate: (draft, action) => {
662
696
  const { accountId, selectedPeriod } = action.payload;
@@ -670,6 +704,17 @@ const expenseAutomationReconciliationView = createSlice({
670
704
  accountRecon.statementDateConflict = null;
671
705
  }
672
706
  },
707
+ submitCombinedStatementUpdate: (draft, action) => {
708
+ const { accountId, selectedPeriod } = action.payload;
709
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
710
+ if (accountRecon != null) {
711
+ accountRecon.statementUpdateStatus = {
712
+ fetchState: 'In-Progress',
713
+ error: undefined,
714
+ };
715
+ accountRecon.statementDateConflict = null;
716
+ }
717
+ },
673
718
  submitStatementUpdateSuccess: (draft, action) => {
674
719
  const { accountId, selectedPeriod } = action.payload;
675
720
  const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
@@ -684,7 +729,7 @@ const expenseAutomationReconciliationView = createSlice({
684
729
  accountRecon.statementProcessingStartedAtMs = undefined;
685
730
  accountRecon.statementProcessingTotalDurationSeconds = undefined;
686
731
  if (accountRecon.localData != null) {
687
- accountRecon.localData.statementUpdateLocalData = undefined;
732
+ accountRecon.localData.statementUpdateLocalDataByUploadId = undefined;
688
733
  }
689
734
  }
690
735
  },
@@ -706,6 +751,52 @@ const expenseAutomationReconciliationView = createSlice({
706
751
  accountRecon.statementDateConflict = null;
707
752
  }
708
753
  },
754
+ confirmStatementMapping: (draft, action) => {
755
+ const { accountId, selectedPeriod } = action.payload;
756
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
757
+ if (accountRecon != null) {
758
+ accountRecon.confirmStatementMappingStatus = {
759
+ fetchState: 'In-Progress',
760
+ error: undefined,
761
+ };
762
+ accountRecon.confirmStatementMappingConflict = null;
763
+ }
764
+ },
765
+ confirmStatementMappingSuccess: (draft, action) => {
766
+ const { accountId, selectedPeriod, statementUploads } = action.payload;
767
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
768
+ if (accountRecon != null) {
769
+ accountRecon.confirmStatementMappingStatus = {
770
+ fetchState: 'Completed',
771
+ error: undefined,
772
+ };
773
+ accountRecon.confirmStatementMappingConflict = null;
774
+ accountRecon.confirmedCombinedStatementUploads = statementUploads;
775
+ const combinedStatementDetails = accountRecon.parsedStatementData?.statementUpload
776
+ .combinedStatementDetails;
777
+ if (combinedStatementDetails != null) {
778
+ combinedStatementDetails.isMappingConfirmed = true;
779
+ }
780
+ const combinedStatementGroupId = combinedStatementDetails?.combinedStatementGroupId ??
781
+ accountRecon.parsedStatementData?.statementUpload.statementMeta
782
+ .combinedStatementGroupId;
783
+ if (combinedStatementGroupId != null &&
784
+ combinedStatementGroupId !== '') {
785
+ accountRecon.combinedStatementGroupId = combinedStatementGroupId;
786
+ }
787
+ }
788
+ },
789
+ confirmStatementMappingFailure: (draft, action) => {
790
+ const { accountId, selectedPeriod, mappingConflict } = action.payload;
791
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
792
+ if (accountRecon != null) {
793
+ accountRecon.confirmStatementMappingStatus = {
794
+ fetchState: 'Error',
795
+ error: action.payload.error,
796
+ };
797
+ accountRecon.confirmStatementMappingConflict = mappingConflict ?? null;
798
+ }
799
+ },
709
800
  updateAccountReconciliationLocalData: (draft, action) => {
710
801
  const { accountId, selectedPeriod, localData } = action.payload;
711
802
  const oldLocalData = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].localData;
@@ -816,7 +907,7 @@ const expenseAutomationReconciliationView = createSlice({
816
907
  },
817
908
  });
818
909
  // Export actions
819
- export const { fetchReconciliation, fetchReconciliationFailure, fetchReconciliationSuccess, saveReconciliationDetail, saveReconciliationDetailFailure, updateSelectedAccountId, setConnectionInProgressForAccount, setStatementParseInProgress, setStatementProcessingDismissedToBackground, updateStatementProcessingBackgroundCompletedSteps, updateReconcileTabLocalData, updateReconcileTabListSortState, updateReconcileTabListScrollState, saveReconciliationDetailSuccess, updateSelectedTab, updateBalancesLocalData, initialiseLocalDataForSelectedAccountId, clearExpenseAutomationReconciliationView, updateSelectedDrawerAccountId, updateStatementProcessingFailed, updateReviewTabSortState, initializeReconciliationReviewTabLocalData, updateReviewTabLocalData, saveReconciliationReview, updateSaveReconciliationReviewFetchStatus, updateReconListScrollPosition, updateAccountReconciliationLocalData, deleteAccountStatement, deleteAccountStatementSuccess, deleteAccountStatementFailure, excludeAccountFromReconciliation, excludeAccountFromReconciliationSuccess, excludeAccountFromReconciliationFailure, includeAccountInReconciliation, includeAccountInReconciliationSuccess, includeAccountInReconciliationFailure, uploadAccountStatement, uploadAccountStatementSuccess, uploadAccountStatementFailure, parseStatement, parseStatementSuccess, parseStatementFailure, reparseStatement, reparseStatementSuccess, reparseStatementFailure, resetReparseStatementStatus, updateParsedStatementData, updateStatementUpdateLocalData, submitStatementUpdate, submitStatementUpdateSuccess, submitStatementUpdateFailure, clearStatementDateConflict, updateStatementUploadChosen, updateNodeCollapseState, } = expenseAutomationReconciliationView.actions;
910
+ export const { fetchReconciliation, fetchReconciliationFailure, fetchReconciliationSuccess, saveReconciliationDetail, saveReconciliationDetailFailure, updateSelectedAccountId, setConnectionInProgressForAccount, setStatementParseInProgress, setStatementProcessingDismissedToBackground, updateStatementProcessingBackgroundCompletedSteps, updateReconcileTabLocalData, updateReconcileTabListSortState, updateReconcileTabListScrollState, saveReconciliationDetailSuccess, updateSelectedTab, updateBalancesLocalData, initialiseLocalDataForSelectedAccountId, clearExpenseAutomationReconciliationView, updateSelectedDrawerAccountId, updateStatementProcessingFailed, updateReviewTabSortState, initializeReconciliationReviewTabLocalData, updateReviewTabLocalData, saveReconciliationReview, updateSaveReconciliationReviewFetchStatus, updateReconListScrollPosition, updateAccountReconciliationLocalData, deleteAccountStatement, deleteAccountStatementSuccess, deleteAccountStatementFailure, excludeAccountFromReconciliation, excludeAccountFromReconciliationSuccess, excludeAccountFromReconciliationFailure, includeAccountInReconciliation, includeAccountInReconciliationSuccess, includeAccountInReconciliationFailure, uploadAccountStatement, uploadAccountStatementSuccess, uploadAccountStatementFailure, parseStatement, parseCombinedStatement, parseStatementSuccess, parseStatementFailure, reparseStatement, reparseStatementSuccess, reparseStatementFailure, resetReparseStatementStatus, updateParsedStatementData, updateStatementUpdateLocalData, submitStatementUpdate, submitCombinedStatementUpdate, submitStatementUpdateSuccess, submitStatementUpdateFailure, clearStatementDateConflict, confirmStatementMapping, confirmStatementMappingSuccess, confirmStatementMappingFailure, updateStatementUploadChosen, updateNodeCollapseState, } = expenseAutomationReconciliationView.actions;
820
911
  // Export reducer
821
912
  export default expenseAutomationReconciliationView.reducer;
822
913
  function isEntityStatementParsing(reconciliation) {
@@ -830,6 +921,7 @@ function isEntityStatementParseTerminal(reconciliation) {
830
921
  statusCode === 'parsing_failed' ||
831
922
  statusCode === 'validation_failed' ||
832
923
  statusCode === 'processing_failed' ||
924
+ statusCode === 'parsed_awaiting_mapping' ||
833
925
  statusCode === 'deleting' ||
834
926
  statusCode === 'deleted');
835
927
  }
@@ -965,6 +1057,13 @@ function updateAllReconciliation(draft, accounts, selectedPeriod, reconciliation
965
1057
  error: undefined,
966
1058
  },
967
1059
  statementDateConflict: oldRecord?.statementDateConflict ?? null,
1060
+ confirmStatementMappingStatus: oldRecord?.confirmStatementMappingStatus ?? {
1061
+ fetchState: 'Not-Started',
1062
+ error: undefined,
1063
+ },
1064
+ confirmStatementMappingConflict: oldRecord?.confirmStatementMappingConflict ?? null,
1065
+ confirmedCombinedStatementUploads: oldRecord?.confirmedCombinedStatementUploads,
1066
+ combinedStatementGroupId: oldRecord?.combinedStatementGroupId,
968
1067
  localData: {
969
1068
  ...oldRecord?.localData,
970
1069
  accountSource: account?.reconciliation_source != null
@@ -1063,6 +1162,11 @@ const updateReconciliationByAccountIDOnIncludeAccount = (draft, accountId, recon
1063
1162
  statementParseStatus: { fetchState: 'Not-Started', error: undefined },
1064
1163
  reparseStatementStatus: { fetchState: 'Not-Started', error: undefined },
1065
1164
  statementDateConflict: null,
1165
+ confirmStatementMappingStatus: {
1166
+ fetchState: 'Not-Started',
1167
+ error: undefined,
1168
+ },
1169
+ confirmStatementMappingConflict: null,
1066
1170
  localData: {},
1067
1171
  accountDetectionReason: undefined,
1068
1172
  };
@@ -245,8 +245,23 @@ export const getExpenseAutomationReconciliationView = createSelector((state) =>
245
245
  }
246
246
  const accountReconState = accountReconForMonthYearPeriod.reconciliationByAccountID[accountID];
247
247
  const rawParsedStatementData = accountReconState.parsedStatementData;
248
- const statementUpdateLocalData = accountReconState.localData?.statementUpdateLocalData;
249
- const parsedStatementDataForView = getParsedStatementDataForView(rawParsedStatementData, statementUpdateLocalData);
248
+ const statementUpdateLocalDataByUploadId = accountReconState.localData?.statementUpdateLocalDataByUploadId;
249
+ const statementAccountScope = accountReconState.localData?.statementAccountScope;
250
+ const parsedStatementDataForView = getParsedStatementDataForView(rawParsedStatementData, statementUpdateLocalDataByUploadId, statementAccountScope);
251
+ const rawStatementUploads = rawParsedStatementData?.statementUploads != null &&
252
+ rawParsedStatementData.statementUploads.length > 0
253
+ ? rawParsedStatementData.statementUploads
254
+ : rawParsedStatementData?.statementUpload != null
255
+ ? [rawParsedStatementData.statementUpload]
256
+ : [];
257
+ const allCitationTransactions = rawStatementUploads.flatMap((statementUpload) => statementUpload.statementTransactions);
258
+ const statementTransactionPeriodById = rawStatementUploads.length > 0
259
+ ? Object.assign({}, ...rawStatementUploads.map((statementUpload) => {
260
+ const uploadId = statementUpload.statementMeta.statementUploadId;
261
+ const localMeta = statementUpdateLocalDataByUploadId?.[uploadId]?.statementMeta;
262
+ return getStatementTransactionPeriodById(statementUpload.statementTransactions, statementUpload.statementMeta, localMeta);
263
+ }))
264
+ : undefined;
250
265
  reconList[accountID] = {
251
266
  accountConnectionStatus: accountConnectionStatus.status,
252
267
  paymentAccountId: accountConnectionStatus.paymentAccountId,
@@ -257,6 +272,10 @@ export const getExpenseAutomationReconciliationView = createSelector((state) =>
257
272
  statementParseStatus: accountReconState.statementParseStatus,
258
273
  reparseStatementStatus: accountReconState.reparseStatementStatus,
259
274
  statementDateConflict: accountReconState.statementDateConflict,
275
+ confirmStatementMappingStatus: accountReconState.confirmStatementMappingStatus,
276
+ confirmStatementMappingConflict: accountReconState.confirmStatementMappingConflict,
277
+ confirmedCombinedStatementUploads: accountReconState.confirmedCombinedStatementUploads,
278
+ combinedStatementGroupId: accountReconState.combinedStatementGroupId,
260
279
  statementUpdateStatus: accountReconState.statementUpdateStatus,
261
280
  statementUploadStatus: accountReconState.statementUploadStatus,
262
281
  localData: accountReconState.localData,
@@ -267,11 +286,8 @@ export const getExpenseAutomationReconciliationView = createSelector((state) =>
267
286
  statementProcessingTotalDurationSeconds: accountReconState.statementProcessingTotalDurationSeconds,
268
287
  statementProcessingFailed: accountReconState.statementProcessingFailed,
269
288
  parsedStatementData: parsedStatementDataForView,
270
- allCitationTransactions: rawParsedStatementData?.statementUpload?.statementTransactions,
271
- statementTransactionPeriodById: rawParsedStatementData?.statementUpload != null
272
- ? getStatementTransactionPeriodById(rawParsedStatementData.statementUpload.statementTransactions, rawParsedStatementData.statementUpload.statementMeta, accountReconState.localData?.statementUpdateLocalData
273
- ?.statementMeta)
274
- : undefined,
289
+ allCitationTransactions,
290
+ statementTransactionPeriodById,
275
291
  transactionFetchState: reduceAnyFetchState([
276
292
  accountReconState.transactionFetchState,
277
293
  accountListState.byReportId.accountList,
@@ -341,6 +357,8 @@ export const getExpenseAutomationReconciliationView = createSelector((state) =>
341
357
  statementParseStatus: { fetchState: 'Not-Started' },
342
358
  reparseStatementStatus: { fetchState: 'Not-Started' },
343
359
  statementDateConflict: null,
360
+ confirmStatementMappingStatus: { fetchState: 'Not-Started' },
361
+ confirmStatementMappingConflict: null,
344
362
  localData: {},
345
363
  connectionInProgress: false,
346
364
  statementParseInProgress: false,
@@ -457,6 +475,19 @@ export const getExpenseAutomationReconciliationView = createSelector((state) =>
457
475
  });
458
476
  export const getReparseStatementStatusByAccountId = (state, accountId) => getExpenseAutomationReconciliationView(state)
459
477
  .accountReconciliationsByAccountID[accountId]?.reparseStatementStatus;
478
+ export function getLedgerAccountsForCombinedStatementMapping(accountReconciliationsByAccountID) {
479
+ return Object.values(accountReconciliationsByAccountID).map((accountReconciliation) => accountReconciliation.accountReconEntity.account);
480
+ }
481
+ const UNAVAILABLE_LEDGER_ACCOUNT_RECON_STATUSES = new Set(['in_progress', 'reconciled', 'reconciled_on_qbo']);
482
+ export function getUnavailableLedgerAccountIds(accountReconciliationsByAccountID) {
483
+ return Object.values(accountReconciliationsByAccountID)
484
+ .filter((accountReconciliation) => {
485
+ const statusCode = accountReconciliation.accountReconEntity.reconciliationStatus?.code;
486
+ return (statusCode != null &&
487
+ UNAVAILABLE_LEDGER_ACCOUNT_RECON_STATUSES.has(statusCode));
488
+ })
489
+ .map((accountReconciliation) => accountReconciliation.accountReconEntity.account.accountId);
490
+ }
460
491
  export const isAccountReconReport = (reportId) => {
461
492
  return reportId.endsWith('reconciliation');
462
493
  };
@@ -1,4 +1,17 @@
1
1
  import { date } from '../../../zeniDayJS';
2
+ export function getStatementUploadsFromParsedData(parsedStatementData) {
3
+ if (parsedStatementData == null) {
4
+ return [];
5
+ }
6
+ if (parsedStatementData.statementUploads != null &&
7
+ parsedStatementData.statementUploads.length > 0) {
8
+ return parsedStatementData.statementUploads;
9
+ }
10
+ if (parsedStatementData.statementUpload != null) {
11
+ return [parsedStatementData.statementUpload];
12
+ }
13
+ return [];
14
+ }
2
15
  export const getEffectiveStatementPeriod = (parsedMeta, localMeta) => ({
3
16
  statementStartDate: localMeta?.statementStartDate != null && localMeta.statementStartDate !== ''
4
17
  ? localMeta.statementStartDate
@@ -25,24 +38,44 @@ export const filterStatementTransactionsByPeriod = (transactions, statementPerio
25
38
  }
26
39
  return transactions.filter((transaction) => isStatementTransactionInPeriod(transaction.transactionDate, statementStartDate, statementEndDate));
27
40
  };
28
- export const getParsedStatementDataForView = (parsedStatementData, statementUpdateLocalData) => {
41
+ const getStatementUploadForView = (statementUpload, statementUpdateLocalData) => {
42
+ const parsedMeta = statementUpload.statementMeta;
43
+ const localMeta = statementUpdateLocalData?.statementMeta;
44
+ const effectiveMeta = localMeta ?? parsedMeta;
45
+ const statementPeriod = getEffectiveStatementPeriod(parsedMeta, localMeta);
46
+ return {
47
+ ...statementUpload,
48
+ statementMeta: effectiveMeta,
49
+ statementTransactions: filterStatementTransactionsByPeriod(statementUpload.statementTransactions, statementPeriod),
50
+ };
51
+ };
52
+ export const getParsedStatementDataForView = (parsedStatementData, statementUpdateLocalDataByUploadId, statementAccountScope) => {
29
53
  if (parsedStatementData == null) {
30
54
  return undefined;
31
55
  }
56
+ const statementUploads = getStatementUploadsFromParsedData(parsedStatementData);
57
+ if (statementUploads.length === 0) {
58
+ return undefined;
59
+ }
60
+ if (statementAccountScope === 'multi_account') {
61
+ const viewedUploads = statementUploads.map((statementUpload) => {
62
+ const uploadId = statementUpload.statementMeta.statementUploadId;
63
+ return getStatementUploadForView(statementUpload, statementUpdateLocalDataByUploadId?.[uploadId]);
64
+ });
65
+ const anchorUploadId = parsedStatementData.statementUpload.statementMeta.statementUploadId;
66
+ const viewedAnchor = viewedUploads.find((upload) => upload.statementMeta.statementUploadId === anchorUploadId) ?? viewedUploads[0];
67
+ return {
68
+ statementUpload: viewedAnchor,
69
+ statementUploads: viewedUploads,
70
+ };
71
+ }
32
72
  const statementUpload = parsedStatementData.statementUpload;
33
73
  if (statementUpload == null) {
34
74
  return undefined;
35
75
  }
36
- const parsedMeta = statementUpload.statementMeta;
37
- const localMeta = statementUpdateLocalData?.statementMeta;
38
- const effectiveMeta = localMeta ?? parsedMeta;
39
- const statementPeriod = getEffectiveStatementPeriod(parsedMeta, localMeta);
76
+ const uploadId = statementUpload.statementMeta.statementUploadId;
40
77
  return {
41
- statementUpload: {
42
- ...statementUpload,
43
- statementMeta: effectiveMeta,
44
- statementTransactions: filterStatementTransactionsByPeriod(statementUpload.statementTransactions, statementPeriod),
45
- },
78
+ statementUpload: getStatementUploadForView(statementUpload, statementUpdateLocalDataByUploadId?.[uploadId]),
46
79
  };
47
80
  };
48
81
  export const getStatementTransactionPeriodById = (allParsedTransactions, parsedMeta, localMeta) => {
@@ -1,4 +1,11 @@
1
- import { stringToUnion } from '../../../commonStateTypes/stringToUnion';
1
+ import { stringToUnion, stringToUnionStrict, } from '../../../commonStateTypes/stringToUnion';
2
+ const ALL_STATEMENT_BALANCE_SOURCES = [
3
+ 'ocr',
4
+ 'prior_recon',
5
+ 'default_zero',
6
+ ];
7
+ export const toStatementBalanceSource = (v) => stringToUnion(v, ALL_STATEMENT_BALANCE_SOURCES);
8
+ export const toStatementBalanceSourceStrict = (v) => stringToUnionStrict(v ?? '', ALL_STATEMENT_BALANCE_SOURCES);
2
9
  export function toReconciliationViewSummary(payload) {
3
10
  return {
4
11
  accounts: {
@@ -59,3 +66,10 @@ const RECON_REVIEW_SORT_KEYS = [
59
66
  ];
60
67
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
61
68
  const toReconReviewSortKey = (v) => stringToUnion(v, RECON_REVIEW_SORT_KEYS);
69
+ const ALL_STATEMENT_ACCOUNT_SCOPES = [
70
+ 'single_account',
71
+ 'multi_account',
72
+ ];
73
+ export const toStatementAccountScope = (v) => stringToUnion(v, ALL_STATEMENT_ACCOUNT_SCOPES);
74
+ export const toStatementAccountScopeStrict = (v) => stringToUnionStrict(v ?? '', ALL_STATEMENT_ACCOUNT_SCOPES);
75
+ export const STATEMENT_ACCOUNT_SCOPES = ALL_STATEMENT_ACCOUNT_SCOPES;