@zeniai/client-epic-state 5.2.0-beta0AK → 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 (74) 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/invoicing/invoicingCustomer/invoicingCustomerPayload.d.ts +0 -16
  5. package/lib/entity/invoicing/invoicingCustomer/invoicingCustomerPayload.js +0 -20
  6. package/lib/entity/invoicing/invoicingCustomer/invoicingCustomerSelector.d.ts +1 -8
  7. package/lib/entity/invoicing/invoicingCustomer/invoicingCustomerSelector.js +0 -48
  8. package/lib/entity/invoicing/invoicingCustomer/invoicingCustomerState.d.ts +0 -24
  9. package/lib/entity/invoicing/invoicingSubscription/invoicingSubscriptionPayload.d.ts +0 -1
  10. package/lib/entity/invoicing/invoicingSubscription/invoicingSubscriptionPayload.js +0 -1
  11. package/lib/entity/invoicing/invoicingSubscription/invoicingSubscriptionState.d.ts +0 -1
  12. package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
  13. package/lib/entity/snackbar/snackbarTypes.js +1 -0
  14. package/lib/epic.d.ts +4 -1
  15. package/lib/epic.js +4 -1
  16. package/lib/esm/entity/accountRecon/accountReconReducer.js +4 -0
  17. package/lib/esm/entity/accountRecon/accountReconState.js +1 -0
  18. package/lib/esm/entity/invoicing/invoicingCustomer/invoicingCustomerPayload.js +0 -20
  19. package/lib/esm/entity/invoicing/invoicingCustomer/invoicingCustomerSelector.js +0 -47
  20. package/lib/esm/entity/invoicing/invoicingSubscription/invoicingSubscriptionPayload.js +0 -1
  21. package/lib/esm/entity/snackbar/snackbarTypes.js +1 -0
  22. package/lib/esm/epic.js +4 -1
  23. package/lib/esm/index.js +6 -6
  24. package/lib/esm/view/expenseAutomationView/epics/accountRecon/confirmStatementMappingEpic.js +43 -0
  25. package/lib/esm/view/expenseAutomationView/epics/accountRecon/deleteAccountStatementEpic.js +1 -2
  26. package/lib/esm/view/expenseAutomationView/epics/accountRecon/parseCombinedStatementEpic.js +31 -0
  27. package/lib/esm/view/expenseAutomationView/epics/accountRecon/parseStatementEpic.js +15 -18
  28. package/lib/esm/view/expenseAutomationView/epics/accountRecon/updateCombinedStatementInfoEpic.js +68 -0
  29. package/lib/esm/view/expenseAutomationView/epics/accountRecon/updateStatementInfoEpic.js +10 -10
  30. package/lib/esm/view/expenseAutomationView/epics/accountRecon/uploadAccountStatementDocumentAIHelper.js +12 -4
  31. package/lib/esm/view/expenseAutomationView/epics/accountRecon/uploadAccountStatementEpic.js +8 -2
  32. package/lib/esm/view/expenseAutomationView/helpers/mapStatementUpdateLocalDataToRequestBody.js +20 -5
  33. package/lib/esm/view/expenseAutomationView/payload/reconciliationPayload.js +135 -0
  34. package/lib/esm/view/expenseAutomationView/reducers/reconciliationViewReducer.js +120 -16
  35. package/lib/esm/view/expenseAutomationView/selectors/reconciliationViewSelector.js +38 -7
  36. package/lib/esm/view/expenseAutomationView/selectors/statementParseViewSelector.js +43 -10
  37. package/lib/esm/view/expenseAutomationView/types/reconciliationViewState.js +15 -1
  38. package/lib/esm/view/invoicing/createInvoice/createInvoiceSelector.js +0 -1
  39. package/lib/esm/view/invoicing/invoicingCustomerDetailView/invoicingCustomerDetailViewSelector.js +2 -4
  40. package/lib/index.d.ts +9 -9
  41. package/lib/index.js +51 -46
  42. package/lib/view/expenseAutomationView/epics/accountRecon/confirmStatementMappingEpic.d.ts +34 -0
  43. package/lib/view/expenseAutomationView/epics/accountRecon/confirmStatementMappingEpic.js +47 -0
  44. package/lib/view/expenseAutomationView/epics/accountRecon/deleteAccountStatementEpic.d.ts +2 -2
  45. package/lib/view/expenseAutomationView/epics/accountRecon/deleteAccountStatementEpic.js +0 -1
  46. package/lib/view/expenseAutomationView/epics/accountRecon/parseCombinedStatementEpic.d.ts +26 -0
  47. package/lib/view/expenseAutomationView/epics/accountRecon/parseCombinedStatementEpic.js +35 -0
  48. package/lib/view/expenseAutomationView/epics/accountRecon/parseStatementEpic.d.ts +21 -1
  49. package/lib/view/expenseAutomationView/epics/accountRecon/parseStatementEpic.js +15 -18
  50. package/lib/view/expenseAutomationView/epics/accountRecon/updateCombinedStatementInfoEpic.d.ts +27 -0
  51. package/lib/view/expenseAutomationView/epics/accountRecon/updateCombinedStatementInfoEpic.js +72 -0
  52. package/lib/view/expenseAutomationView/epics/accountRecon/updateStatementInfoEpic.js +10 -10
  53. package/lib/view/expenseAutomationView/epics/accountRecon/uploadAccountStatementDocumentAIHelper.d.ts +1 -1
  54. package/lib/view/expenseAutomationView/epics/accountRecon/uploadAccountStatementDocumentAIHelper.js +12 -4
  55. package/lib/view/expenseAutomationView/epics/accountRecon/uploadAccountStatementEpic.js +7 -1
  56. package/lib/view/expenseAutomationView/helpers/mapStatementUpdateLocalDataToRequestBody.d.ts +1 -0
  57. package/lib/view/expenseAutomationView/helpers/mapStatementUpdateLocalDataToRequestBody.js +20 -4
  58. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
  59. package/lib/view/expenseAutomationView/payload/reconciliationPayload.d.ts +109 -4
  60. package/lib/view/expenseAutomationView/payload/reconciliationPayload.js +143 -0
  61. package/lib/view/expenseAutomationView/reducers/reconciliationViewReducer.d.ts +32 -4
  62. package/lib/view/expenseAutomationView/reducers/reconciliationViewReducer.js +122 -18
  63. package/lib/view/expenseAutomationView/selectorTypes/reconciliationViewSelectorTypes.d.ts +5 -1
  64. package/lib/view/expenseAutomationView/selectors/reconciliationViewSelector.d.ts +4 -1
  65. package/lib/view/expenseAutomationView/selectors/reconciliationViewSelector.js +40 -7
  66. package/lib/view/expenseAutomationView/selectors/statementParseViewSelector.d.ts +3 -2
  67. package/lib/view/expenseAutomationView/selectors/statementParseViewSelector.js +44 -10
  68. package/lib/view/expenseAutomationView/types/reconciliationViewState.d.ts +74 -1
  69. package/lib/view/expenseAutomationView/types/reconciliationViewState.js +19 -1
  70. package/lib/view/invoicing/createInvoice/createInvoiceSelector.d.ts +1 -4
  71. package/lib/view/invoicing/createInvoice/createInvoiceSelector.js +0 -1
  72. package/lib/view/invoicing/invoicingCustomerDetailView/invoicingCustomerDetailViewSelector.d.ts +1 -2
  73. package/lib/view/invoicing/invoicingCustomerDetailView/invoicingCustomerDetailViewSelector.js +1 -3
  74. package/package.json +1 -1
@@ -0,0 +1,43 @@
1
+ import { of } from 'rxjs';
2
+ import { catchError, exhaustMap, filter, mergeMap } from 'rxjs/operators';
3
+ import { openSnackbar } from '../../../../entity/snackbar/snackbarReducer';
4
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
5
+ import { isConfirmStatementMappingConflictResponse, transformConfirmStatementMappingConflictToState, transformConfirmStatementMappingPayloadToState, } from '../../payload/reconciliationPayload';
6
+ import { confirmStatementMapping, confirmStatementMappingFailure, confirmStatementMappingSuccess, } from '../../reducers/reconciliationViewReducer';
7
+ const confirmStatementMappingErrorSnackbar = openSnackbar({
8
+ messageSection: 'confirm_statement_mapping',
9
+ messageText: 'failed',
10
+ type: 'error',
11
+ });
12
+ export const confirmStatementMappingEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(confirmStatementMapping.match), exhaustMap((action) => {
13
+ const { accountId, selectedPeriod, statementUploadId, accountMappings } = action.payload;
14
+ const confirmMappingApi$ = zeniAPI.postAndGetJSON(`${zeniAPI.apiEndPoints.reconciliationMicroServiceBaseUrl}/1.0/reconciliation/statements/${statementUploadId}/confirm-mapping`, {
15
+ account_mappings: accountMappings.map((mapping) => ({
16
+ ledger_identifier: mapping.ledgerIdentifier,
17
+ account_id: mapping.accountId,
18
+ })),
19
+ });
20
+ return confirmMappingApi$.pipe(mergeMap((response) => {
21
+ if (isSuccessResponse(response) === true && response.data != null) {
22
+ return of(confirmStatementMappingSuccess({
23
+ accountId,
24
+ selectedPeriod,
25
+ statementUploads: transformConfirmStatementMappingPayloadToState(response.data),
26
+ }));
27
+ }
28
+ const mappingConflict = isConfirmStatementMappingConflictResponse(response)
29
+ ? transformConfirmStatementMappingConflictToState(response)
30
+ : null;
31
+ return of(confirmStatementMappingFailure({
32
+ accountId,
33
+ selectedPeriod,
34
+ error: response.status,
35
+ mappingConflict,
36
+ }), confirmStatementMappingErrorSnackbar);
37
+ }), catchError((error) => of(confirmStatementMappingFailure({
38
+ accountId,
39
+ selectedPeriod,
40
+ error: createZeniAPIStatus('Unexpected Error', 'Confirm Statement Mapping REST API call errored out ' +
41
+ (error instanceof Error ? error.message : String(error))),
42
+ }), confirmStatementMappingErrorSnackbar)));
43
+ }));
@@ -4,7 +4,7 @@ import { updateAccountReconDeleteStatement } from '../../../../entity/accountRec
4
4
  import { toAccountReconKey } from '../../../../entity/accountRecon/accountReconState';
5
5
  import { removeFiles } from '../../../../entity/file/fileReducer';
6
6
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
7
- import { deleteAccountStatement, deleteAccountStatementFailure, deleteAccountStatementSuccess, fetchReconciliation, } from '../../reducers/reconciliationViewReducer';
7
+ import { deleteAccountStatement, deleteAccountStatementFailure, deleteAccountStatementSuccess, } from '../../reducers/reconciliationViewReducer';
8
8
  export const deleteAccountStatementEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(deleteAccountStatement.match), switchMap((action) => {
9
9
  const { accountId, selectedPeriod, statementUploadId, fileId } = action.payload;
10
10
  const request = zeniAPI.deleteAndGetJSON(`${zeniAPI.apiEndPoints.reconciliationMicroServiceBaseUrl}/1.0/reconciliation/statements/${statementUploadId}`);
@@ -17,7 +17,6 @@ export const deleteAccountStatementEpic = (actions$, _state$, zeniAPI) => action
17
17
  actions.push(updateAccountReconDeleteStatement(accountReconKey, response.data));
18
18
  }
19
19
  actions.push(deleteAccountStatementSuccess({ accountId, selectedPeriod }));
20
- actions.push(fetchReconciliation(selectedPeriod, undefined, true, true));
21
20
  return of(...actions);
22
21
  }
23
22
  else {
@@ -0,0 +1,31 @@
1
+ import { from, of } from 'rxjs';
2
+ import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
3
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
4
+ import { transformCombinedStatementGroupPayloadToState, } from '../../payload/reconciliationPayload';
5
+ import { parseCombinedStatement, parseStatementFailure, parseStatementSuccess, updateParsedStatementData, } from '../../reducers/reconciliationViewReducer';
6
+ export const parseCombinedStatementEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(parseCombinedStatement.match), switchMap((action) => {
7
+ const { accountId, selectedPeriod, combinedStatementGroupId } = action.payload;
8
+ const parseUrl = `${zeniAPI.apiEndPoints.reconciliationMicroServiceBaseUrl}/2.0/statement-uploads/combined-statement-groups/${combinedStatementGroupId}`;
9
+ return zeniAPI.getJSON(parseUrl).pipe(mergeMap((response) => {
10
+ if (isSuccessResponse(response) === true && response.data != null) {
11
+ return from([
12
+ updateParsedStatementData({
13
+ accountId,
14
+ selectedPeriod,
15
+ parsedStatementData: transformCombinedStatementGroupPayloadToState(response.data),
16
+ }),
17
+ parseStatementSuccess({ accountId, selectedPeriod }),
18
+ ]);
19
+ }
20
+ return of(parseStatementFailure({
21
+ accountId,
22
+ selectedPeriod,
23
+ error: response.status,
24
+ }));
25
+ }), catchError((error) => of(parseStatementFailure({
26
+ accountId,
27
+ selectedPeriod,
28
+ error: createZeniAPIStatus('Unexpected Error', 'Parse Combined Statement REST API call errored out ' +
29
+ (error instanceof Error ? error.message : String(error))),
30
+ }))));
31
+ }));
@@ -5,26 +5,23 @@ import { transformParseStatementPayloadToState, } from '../../payload/reconcilia
5
5
  import { parseStatement, parseStatementFailure, parseStatementSuccess, updateParsedStatementData, } from '../../reducers/reconciliationViewReducer';
6
6
  export const parseStatementEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(parseStatement.match), switchMap((action) => {
7
7
  const { accountId, selectedPeriod, statementUploadId } = action.payload;
8
- const parseStatementApi$ = zeniAPI.getJSON(`${zeniAPI.apiEndPoints.reconciliationMicroServiceBaseUrl}/2.0/statement-uploads/${statementUploadId}`);
9
- return parseStatementApi$.pipe(mergeMap((response) => {
8
+ const parseUrl = `${zeniAPI.apiEndPoints.reconciliationMicroServiceBaseUrl}/2.0/statement-uploads/${statementUploadId}`;
9
+ return zeniAPI.getJSON(parseUrl).pipe(mergeMap((response) => {
10
10
  if (isSuccessResponse(response) === true && response.data != null) {
11
- const actions = [];
12
- const transformedData = transformParseStatementPayloadToState(response.data);
13
- actions.push(updateParsedStatementData({
14
- accountId,
15
- selectedPeriod,
16
- parsedStatementData: transformedData,
17
- }));
18
- actions.push(parseStatementSuccess({ accountId, selectedPeriod }));
19
- return from(actions);
20
- }
21
- else {
22
- return of(parseStatementFailure({
23
- accountId,
24
- selectedPeriod,
25
- error: response.status,
26
- }));
11
+ return from([
12
+ updateParsedStatementData({
13
+ accountId,
14
+ selectedPeriod,
15
+ parsedStatementData: transformParseStatementPayloadToState(response.data),
16
+ }),
17
+ parseStatementSuccess({ accountId, selectedPeriod }),
18
+ ]);
27
19
  }
20
+ return of(parseStatementFailure({
21
+ accountId,
22
+ selectedPeriod,
23
+ error: response.status,
24
+ }));
28
25
  }), catchError((error) => of(parseStatementFailure({
29
26
  accountId,
30
27
  selectedPeriod,
@@ -0,0 +1,68 @@
1
+ import { of } from 'rxjs';
2
+ import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
3
+ import { toMonthYearPeriodId } from '../../../../commonStateTypes/timePeriod';
4
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
5
+ import { mapCombinedStatementUpdateLocalDataToRequestBody } from '../../helpers/mapStatementUpdateLocalDataToRequestBody';
6
+ import { getStatementUploadsFromParsedData } from '../../selectors/statementParseViewSelector';
7
+ import { isStatementDateConflictResponse, transformStatementDateConflictToState, transformUpdateStatementInfoPayloadToState, } from '../../payload/reconciliationPayload';
8
+ import { submitCombinedStatementUpdate, submitStatementUpdateFailure, submitStatementUpdateSuccess, updateParsedStatementData, } from '../../reducers/reconciliationViewReducer';
9
+ export const updateCombinedStatementInfoEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(submitCombinedStatementUpdate.match), switchMap((action) => {
10
+ const { accountId, selectedPeriod } = action.payload;
11
+ const accountRecon = state$.value.expenseAutomationReconciliationViewState
12
+ .accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
13
+ const parsedStatementData = accountRecon?.parsedStatementData;
14
+ const localDataByUploadId = accountRecon?.localData?.statementUpdateLocalDataByUploadId;
15
+ const statementUploads = getStatementUploadsFromParsedData(parsedStatementData);
16
+ if (parsedStatementData == null ||
17
+ localDataByUploadId == null ||
18
+ statementUploads.length === 0 ||
19
+ statementUploads.some((statementUpload) => localDataByUploadId[statementUpload.statementMeta.statementUploadId] == null)) {
20
+ return of(submitStatementUpdateFailure({
21
+ accountId,
22
+ selectedPeriod,
23
+ error: createZeniAPIStatus('Missing Data', 'No statement update data found for combined accounts'),
24
+ }));
25
+ }
26
+ const combinedStatementGroupId = accountRecon?.combinedStatementGroupId ??
27
+ parsedStatementData.statementUpload.statementMeta
28
+ .combinedStatementGroupId ??
29
+ parsedStatementData.statementUpload.combinedStatementDetails
30
+ ?.combinedStatementGroupId;
31
+ if (combinedStatementGroupId == null || combinedStatementGroupId === '') {
32
+ return of(submitStatementUpdateFailure({
33
+ accountId,
34
+ selectedPeriod,
35
+ error: createZeniAPIStatus('Missing Data', 'No combined statement group id found for bulk update'),
36
+ }));
37
+ }
38
+ const body = mapCombinedStatementUpdateLocalDataToRequestBody(localDataByUploadId, parsedStatementData);
39
+ const updateUrl = `${zeniAPI.apiEndPoints.reconciliationMicroServiceBaseUrl}/2.0/statement-uploads/combined-statement-groups/${combinedStatementGroupId}`;
40
+ return zeniAPI
41
+ .putAndGetJSON(updateUrl, body)
42
+ .pipe(mergeMap((response) => {
43
+ if (isSuccessResponse(response) === true && response.data != null) {
44
+ return of(updateParsedStatementData({
45
+ accountId,
46
+ selectedPeriod,
47
+ parsedStatementData: transformUpdateStatementInfoPayloadToState(response.data),
48
+ }), submitStatementUpdateSuccess({
49
+ accountId,
50
+ selectedPeriod,
51
+ }));
52
+ }
53
+ const dateConflict = isStatementDateConflictResponse(response)
54
+ ? transformStatementDateConflictToState(response)
55
+ : null;
56
+ return of(submitStatementUpdateFailure({
57
+ accountId,
58
+ selectedPeriod,
59
+ error: response.status,
60
+ dateConflict,
61
+ }));
62
+ }), catchError((error) => of(submitStatementUpdateFailure({
63
+ accountId,
64
+ selectedPeriod,
65
+ error: createZeniAPIStatus('Unexpected Error', 'Update Statement REST API call errored out ' +
66
+ (error instanceof Error ? error.message : String(error))),
67
+ }))));
68
+ }));
@@ -7,12 +7,11 @@ import { isStatementDateConflictResponse, transformStatementDateConflictToState,
7
7
  import { submitStatementUpdate, submitStatementUpdateFailure, submitStatementUpdateSuccess, updateParsedStatementData, } from '../../reducers/reconciliationViewReducer';
8
8
  export const updateStatementInfoEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(submitStatementUpdate.match), switchMap((action) => {
9
9
  const { accountId, selectedPeriod, statementUploadId } = action.payload;
10
- const state = state$.value;
11
- const reconciliationView = state.expenseAutomationReconciliationViewState;
12
- const accountRecon = reconciliationView.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
13
- const localData = accountRecon?.localData?.statementUpdateLocalData;
10
+ const accountRecon = state$.value.expenseAutomationReconciliationViewState
11
+ .accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
14
12
  const parsedStatementData = accountRecon?.parsedStatementData;
15
- if (localData == null || parsedStatementData == null) {
13
+ const localData = accountRecon?.localData?.statementUpdateLocalDataByUploadId?.[statementUploadId];
14
+ if (parsedStatementData == null || localData == null) {
16
15
  return of(submitStatementUpdateFailure({
17
16
  accountId,
18
17
  selectedPeriod,
@@ -20,7 +19,7 @@ export const updateStatementInfoEpic = (actions$, state$, zeniAPI) => actions$.p
20
19
  }));
21
20
  }
22
21
  const hasInvalidUpdates = localData.statementTransactions.updated.some((txn) => txn.statementTransactionId == null);
23
- if (hasInvalidUpdates) {
22
+ if (hasInvalidUpdates === true) {
24
23
  return of(submitStatementUpdateFailure({
25
24
  accountId,
26
25
  selectedPeriod,
@@ -28,14 +27,15 @@ export const updateStatementInfoEpic = (actions$, state$, zeniAPI) => actions$.p
28
27
  }));
29
28
  }
30
29
  const body = mapStatementUpdateLocalDataToRequestBody(localData, parsedStatementData);
31
- const updateStatementApi$ = zeniAPI.putAndGetJSON(`${zeniAPI.apiEndPoints.reconciliationMicroServiceBaseUrl}/2.0/statement-uploads/${statementUploadId}`, body);
32
- return updateStatementApi$.pipe(mergeMap((response) => {
30
+ const updateUrl = `${zeniAPI.apiEndPoints.reconciliationMicroServiceBaseUrl}/2.0/statement-uploads/${statementUploadId}`;
31
+ return zeniAPI
32
+ .putAndGetJSON(updateUrl, body)
33
+ .pipe(mergeMap((response) => {
33
34
  if (isSuccessResponse(response) === true && response.data != null) {
34
- const parsedStatementData = transformUpdateStatementInfoPayloadToState(response.data);
35
35
  return of(updateParsedStatementData({
36
36
  accountId,
37
37
  selectedPeriod,
38
- parsedStatementData,
38
+ parsedStatementData: transformUpdateStatementInfoPayloadToState(response.data),
39
39
  }), submitStatementUpdateSuccess({
40
40
  accountId,
41
41
  selectedPeriod,
@@ -1,14 +1,22 @@
1
1
  import fetch from 'cross-fetch';
2
2
  import { zeniAPI } from '../../../../init';
3
- export default async function uploadAccountStatementIntoDocumentAI(accountType, fileBlob, authParams) {
3
+ function toStatementDocumentType(accountType, isCombinedStatement) {
4
+ if (isCombinedStatement === true) {
5
+ return accountType === 'credit_card'
6
+ ? 'reconciliation_combined_card_statement_upload'
7
+ : 'reconciliation_combined_bank_statement_upload';
8
+ }
9
+ return accountType === 'credit_card'
10
+ ? 'reconciliation_card_statement_upload'
11
+ : 'reconciliation_bank_statement_upload';
12
+ }
13
+ export default async function uploadAccountStatementIntoDocumentAI(accountType, fileBlob, authParams, isCombinedStatement = false) {
4
14
  let statementUpload;
5
15
  const urlEndPoint = `${zeniAPI.apiEndPoints.documentAIMicroServiceBaseUrl}/1.0/processors/statement`;
6
16
  const data = new FormData();
7
17
  data.append('attachment_file_0', fileBlob);
8
18
  const attachment_json = JSON.stringify({
9
- document_type: accountType === 'credit_card'
10
- ? 'reconciliation_card_statement_upload'
11
- : 'reconciliation_bank_statement_upload',
19
+ document_type: toStatementDocumentType(accountType, isCombinedStatement),
12
20
  purpose_id: 'reconciliation',
13
21
  });
14
22
  data.append('attachment_json_0', attachment_json);
@@ -1,6 +1,6 @@
1
1
  import { from, of } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
- import { convertToPeriod } from '../../../../commonStateTypes/timePeriod';
3
+ import { convertToPeriod, toMonthYearPeriodId, } from '../../../../commonStateTypes/timePeriod';
4
4
  import { getAccountBase } from '../../../../entity/account/accountSelector';
5
5
  import { updateAccountReconUploadStatement } from '../../../../entity/accountRecon/accountReconReducer';
6
6
  import { toAccountReconKey } from '../../../../entity/accountRecon/accountReconState';
@@ -8,7 +8,7 @@ import { updateFiles } from '../../../../entity/file/fileReducer';
8
8
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
9
9
  import { uploadAccountStatement, uploadAccountStatementFailure, uploadAccountStatementSuccess, } from '../../reducers/reconciliationViewReducer';
10
10
  export const uploadAccountStatementEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(uploadAccountStatement.match), mergeMap((action) => {
11
- const { accountId, selectedPeriod, payload } = action.payload;
11
+ const { accountId, selectedPeriod, payload, statementAccountScope } = action.payload;
12
12
  const { start, end } = convertToPeriod(selectedPeriod, true);
13
13
  const accountBase = getAccountBase(state$.value.accountState, accountId, 'account_reconciliation');
14
14
  if (accountBase == null) {
@@ -19,6 +19,11 @@ export const uploadAccountStatementEpic = (actions$, state$, zeniAPI) => actions
19
19
  }));
20
20
  }
21
21
  const statementType = accountBase.accountType == 'credit_card' ? 'card' : 'bank';
22
+ const localStatementAccountScope = state$.value.expenseAutomationReconciliationViewState
23
+ .accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId]?.localData
24
+ ?.statementAccountScope;
25
+ const resolvedStatementAccountScope = statementAccountScope ?? localStatementAccountScope ?? 'single_account';
26
+ const isCombinedStatement = resolvedStatementAccountScope === 'multi_account';
22
27
  const requestPayload = {
23
28
  account_id: accountId,
24
29
  statement_start_date: start.toString(),
@@ -26,6 +31,7 @@ export const uploadAccountStatementEpic = (actions$, state$, zeniAPI) => actions
26
31
  processor_run_id: payload.processor_run_id,
27
32
  file_id: payload.files[0].file_id,
28
33
  statement_type: statementType,
34
+ is_combined_statement: isCombinedStatement,
29
35
  };
30
36
  const uploadAccountStatementApi$ = zeniAPI.postAndGetJSON(`${zeniAPI.apiEndPoints.reconciliationMicroServiceBaseUrl}/1.0/reconciliation/statements`, requestPayload);
31
37
  return uploadAccountStatementApi$.pipe(mergeMap((response) => {
@@ -1,7 +1,7 @@
1
- import { toStatementUpdateRequestBody } from '../payload/reconciliationPayload';
2
- import { getEffectiveStatementPeriod, mergeStatementParseDeletedTransactionIds, } from '../selectors/statementParseViewSelector';
3
- const prepareStatementUpdateLocalData = (localData, parsedStatementData) => {
4
- const { statementMeta, statementTransactions } = parsedStatementData.statementUpload;
1
+ import { toBulkStatementUpdateRequestBody, toStatementUpdateRequestBody, } from '../payload/reconciliationPayload';
2
+ import { getEffectiveStatementPeriod, getStatementUploadsFromParsedData, mergeStatementParseDeletedTransactionIds, } from '../selectors/statementParseViewSelector';
3
+ const prepareStatementUpdateLocalData = (localData, statementUpload) => {
4
+ const { statementMeta, statementTransactions } = statementUpload;
5
5
  const statementPeriod = getEffectiveStatementPeriod(statementMeta, localData.statementMeta);
6
6
  const localDeletedIds = localData.statementTransactions?.deletedIds ?? [];
7
7
  const currentTransactionIds = new Set(statementTransactions
@@ -27,4 +27,19 @@ const prepareStatementUpdateLocalData = (localData, parsedStatementData) => {
27
27
  },
28
28
  };
29
29
  };
30
- export const mapStatementUpdateLocalDataToRequestBody = (localData, parsedStatementData) => toStatementUpdateRequestBody(prepareStatementUpdateLocalData(localData, parsedStatementData));
30
+ export const mapStatementUpdateLocalDataToRequestBody = (localData, parsedStatementData) => toStatementUpdateRequestBody(prepareStatementUpdateLocalData(localData, parsedStatementData.statementUpload));
31
+ export const mapCombinedStatementUpdateLocalDataToRequestBody = (localDataByUploadId, parsedStatementData) => {
32
+ const statementUploads = getStatementUploadsFromParsedData(parsedStatementData);
33
+ const updates = statementUploads.map((statementUpload) => {
34
+ const statementUploadId = statementUpload.statementMeta.statementUploadId;
35
+ const localData = localDataByUploadId[statementUploadId];
36
+ if (localData == null) {
37
+ throw new Error(`Missing statement update local data for upload ${statementUploadId}`);
38
+ }
39
+ return {
40
+ statementUploadId,
41
+ localData: prepareStatementUpdateLocalData(localData, statementUpload),
42
+ };
43
+ });
44
+ return toBulkStatementUpdateRequestBody(updates);
45
+ };
@@ -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
+ }