@zeniai/client-epic-state 5.1.34-betaRD1 → 5.1.35

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 (45) hide show
  1. package/lib/commonStateTypes/viewAndReport/agingReportStateTypes.d.ts +1 -1
  2. package/lib/entity/accountRecon/accountReconPayload.d.ts +22 -0
  3. package/lib/entity/accountRecon/accountReconPayload.js +26 -1
  4. package/lib/entity/accountRecon/accountReconReducer.d.ts +5 -2
  5. package/lib/entity/accountRecon/accountReconReducer.js +36 -2
  6. package/lib/entity/accountRecon/accountReconSelector.d.ts +6 -1
  7. package/lib/entity/accountRecon/accountReconSelector.js +5 -0
  8. package/lib/entity/accountRecon/accountReconState.d.ts +22 -0
  9. package/lib/entity/monthEndCloseChecks/monthEndCloseChecksPayload.d.ts +8 -0
  10. package/lib/entity/monthEndCloseChecks/monthEndCloseChecksPayload.js +10 -0
  11. package/lib/entity/monthEndCloseChecks/monthEndCloseChecksState.d.ts +8 -0
  12. package/lib/epic.d.ts +4 -1
  13. package/lib/epic.js +4 -1
  14. package/lib/esm/entity/accountRecon/accountReconPayload.js +26 -1
  15. package/lib/esm/entity/accountRecon/accountReconReducer.js +35 -1
  16. package/lib/esm/entity/accountRecon/accountReconSelector.js +5 -0
  17. package/lib/esm/entity/monthEndCloseChecks/monthEndCloseChecksPayload.js +10 -0
  18. package/lib/esm/epic.js +4 -1
  19. package/lib/esm/index.js +3 -3
  20. package/lib/esm/view/expenseAutomationView/epics/accountRecon/parseStatementEpic.js +34 -0
  21. package/lib/esm/view/expenseAutomationView/epics/accountRecon/reparseStatementEpic.js +31 -0
  22. package/lib/esm/view/expenseAutomationView/epics/accountRecon/updateStatementInfoEpic.js +57 -0
  23. package/lib/esm/view/expenseAutomationView/payload/reconciliationPayload.js +153 -0
  24. package/lib/esm/view/expenseAutomationView/reducers/reconciliationViewReducer.js +188 -9
  25. package/lib/esm/view/expenseAutomationView/selectors/reconciliationViewSelector.js +27 -0
  26. package/lib/esm/view/expenseAutomationView/types/reconciliationViewState.js +29 -0
  27. package/lib/index.d.ts +6 -6
  28. package/lib/index.js +51 -37
  29. package/lib/view/expenseAutomationView/epics/accountRecon/parseStatementEpic.d.ts +6 -0
  30. package/lib/view/expenseAutomationView/epics/accountRecon/parseStatementEpic.js +38 -0
  31. package/lib/view/expenseAutomationView/epics/accountRecon/reparseStatementEpic.d.ts +26 -0
  32. package/lib/view/expenseAutomationView/epics/accountRecon/reparseStatementEpic.js +35 -0
  33. package/lib/view/expenseAutomationView/epics/accountRecon/updateStatementInfoEpic.d.ts +27 -0
  34. package/lib/view/expenseAutomationView/epics/accountRecon/updateStatementInfoEpic.js +61 -0
  35. package/lib/view/expenseAutomationView/payload/reconciliationPayload.d.ts +176 -10
  36. package/lib/view/expenseAutomationView/payload/reconciliationPayload.js +159 -0
  37. package/lib/view/expenseAutomationView/reducers/reconciliationViewReducer.d.ts +53 -3
  38. package/lib/view/expenseAutomationView/reducers/reconciliationViewReducer.js +190 -10
  39. package/lib/view/expenseAutomationView/selectorTypes/reconciliationViewSelectorTypes.d.ts +7 -1
  40. package/lib/view/expenseAutomationView/selectors/reconciliationViewSelector.d.ts +2 -0
  41. package/lib/view/expenseAutomationView/selectors/reconciliationViewSelector.js +29 -1
  42. package/lib/view/expenseAutomationView/types/reconciliationViewState.d.ts +175 -0
  43. package/lib/view/expenseAutomationView/types/reconciliationViewState.js +30 -0
  44. package/lib/view/vendorFiling1099/vendorFiling1099List/vendorFiling1099ListState.d.ts +1 -1
  45. package/package.json +1 -1
@@ -0,0 +1,57 @@
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 { isStatementDateConflictResponse, toStatementUpdateRequestBody, transformStatementDateConflictToState, transformUpdateStatementInfoPayloadToState, } from '../../payload/reconciliationPayload';
6
+ import { submitStatementUpdate, submitStatementUpdateFailure, submitStatementUpdateSuccess, updateParsedStatementData, } from '../../reducers/reconciliationViewReducer';
7
+ export const updateStatementInfoEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(submitStatementUpdate.match), switchMap((action) => {
8
+ const { accountId, selectedPeriod, statementUploadId } = action.payload;
9
+ const state = state$.value;
10
+ const reconciliationView = state.expenseAutomationReconciliationViewState;
11
+ const accountRecon = reconciliationView.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
12
+ const localData = accountRecon?.localData?.statementUpdateLocalData;
13
+ if (!localData) {
14
+ return of(submitStatementUpdateFailure({
15
+ accountId,
16
+ selectedPeriod,
17
+ error: createZeniAPIStatus('Missing Data', 'No statement update data found'),
18
+ }));
19
+ }
20
+ const hasInvalidUpdates = localData.statementTransactions.updated.some((txn) => txn.statementTransactionId == null);
21
+ if (hasInvalidUpdates) {
22
+ return of(submitStatementUpdateFailure({
23
+ accountId,
24
+ selectedPeriod,
25
+ error: createZeniAPIStatus('Invalid Data', 'One or more edited transactions are missing statement transaction ids'),
26
+ }));
27
+ }
28
+ const body = toStatementUpdateRequestBody(localData);
29
+ const updateStatementApi$ = zeniAPI.putAndGetJSON(`${zeniAPI.apiEndPoints.reconciliationMicroServiceBaseUrl}/2.0/statement-uploads/${statementUploadId}`, body);
30
+ return updateStatementApi$.pipe(mergeMap((response) => {
31
+ if (isSuccessResponse(response) === true && response.data != null) {
32
+ const parsedStatementData = transformUpdateStatementInfoPayloadToState(response.data);
33
+ return of(updateParsedStatementData({
34
+ accountId,
35
+ selectedPeriod,
36
+ parsedStatementData,
37
+ }), submitStatementUpdateSuccess({
38
+ accountId,
39
+ selectedPeriod,
40
+ }));
41
+ }
42
+ const dateConflict = isStatementDateConflictResponse(response)
43
+ ? transformStatementDateConflictToState(response)
44
+ : null;
45
+ return of(submitStatementUpdateFailure({
46
+ accountId,
47
+ selectedPeriod,
48
+ error: response.status,
49
+ dateConflict,
50
+ }));
51
+ }), catchError((error) => of(submitStatementUpdateFailure({
52
+ accountId,
53
+ selectedPeriod,
54
+ error: createZeniAPIStatus('Unexpected Error', 'Update Statement REST API call errored out ' +
55
+ (error instanceof Error ? error.message : String(error))),
56
+ }))));
57
+ }));
@@ -67,3 +67,156 @@ function createReviewTransactionPayload(accountReconciliationEntity) {
67
67
  });
68
68
  return reviewTransactionsPayload;
69
69
  }
70
+ export function isStatementDateConflictResponse(response) {
71
+ return response.status.code === 409 && response.error_code != null;
72
+ }
73
+ function transformStatementUploadPayloadToState(statementUploadPayload) {
74
+ return {
75
+ account: {
76
+ accountId: statementUploadPayload.account.account_id,
77
+ accountName: statementUploadPayload.account.account_name,
78
+ accountType: statementUploadPayload.account.account_type,
79
+ currencyCode: statementUploadPayload.account.currency_code,
80
+ last4Digits: statementUploadPayload.account.last_4_digits,
81
+ },
82
+ file: statementUploadPayload.file != null
83
+ ? {
84
+ fileId: statementUploadPayload.file.file_id,
85
+ fileName: statementUploadPayload.file.file_name,
86
+ signedUrl: statementUploadPayload.file.signed_url,
87
+ }
88
+ : null,
89
+ statementMeta: {
90
+ closingBalance: statementUploadPayload.statement_meta.closing_balance,
91
+ openingBalance: statementUploadPayload.statement_meta.opening_balance,
92
+ statementDataStatus: statementUploadPayload.statement_meta.statement_data_status,
93
+ statementEndDate: statementUploadPayload.statement_meta.statement_end_date,
94
+ statementStartDate: statementUploadPayload.statement_meta.statement_start_date,
95
+ statementStatus: statementUploadPayload.statement_meta.statement_status,
96
+ statementUploadId: statementUploadPayload.statement_meta.statement_upload_id,
97
+ totalDeposits: statementUploadPayload.statement_meta.total_deposits,
98
+ totalPayments: statementUploadPayload.statement_meta.total_payments,
99
+ },
100
+ statementTransactions: (statementUploadPayload.statement_transactions ?? []).map((txn) => ({
101
+ amount: txn.amount,
102
+ citations: (txn.citation ?? []).map((c) => ({
103
+ page: c.page,
104
+ pageHeight: c.page_height,
105
+ pageWidth: c.page_width,
106
+ polygon: (c.polygon ?? []).map((point) => ({
107
+ x: point.x,
108
+ y: point.y,
109
+ })),
110
+ referenceText: c.reference_text,
111
+ })),
112
+ isUserAdded: txn.is_user_added,
113
+ isUserEdited: txn.is_user_edited,
114
+ statementTransactionId: txn.statement_transaction_id,
115
+ transactionDate: txn.transaction_date,
116
+ transactionDirection: txn.transaction_direction,
117
+ transactionMemo: txn.transaction_memo,
118
+ })),
119
+ aiSummary: transformAiSummaryPayloadToState(statementUploadPayload.ai_summary),
120
+ };
121
+ }
122
+ function transformPreviousReconciliationInfoToState(payload) {
123
+ if (payload == null) {
124
+ return null;
125
+ }
126
+ return {
127
+ endDate: payload.end_date,
128
+ reconciledAt: payload.reconciled_at,
129
+ reconciledByUserId: payload.reconciled_by_user_id,
130
+ reconciliationId: payload.reconciliation_id,
131
+ startDate: payload.start_date,
132
+ };
133
+ }
134
+ function transformAiSummaryPayloadToState(aiSummaryPayload) {
135
+ if (aiSummaryPayload == null) {
136
+ return null;
137
+ }
138
+ const { chain_status: chainStatus } = aiSummaryPayload;
139
+ return {
140
+ accountIdentified: aiSummaryPayload.account_identified,
141
+ transactionsExtracted: aiSummaryPayload.transactions_extracted,
142
+ exceptionsDetected: aiSummaryPayload.exceptions_detected,
143
+ fieldsCaptured: (aiSummaryPayload.fields_captured ?? []).map((field) => ({
144
+ code: field.code,
145
+ label: field.label,
146
+ })),
147
+ chainStatus: {
148
+ code: chainStatus?.code ?? '',
149
+ label: chainStatus?.label ?? '',
150
+ expectedStartDate: chainStatus?.expected_start_date ?? null,
151
+ previousReconciliationInfo: transformPreviousReconciliationInfoToState(chainStatus?.previous_reconciliation_info),
152
+ },
153
+ };
154
+ }
155
+ /**
156
+ * Builds date-conflict state from a 409 Confirm & Save response.
157
+ * Returns null for non-409 responses or 409s without a recognized error code.
158
+ */
159
+ export function transformStatementDateConflictToState(response) {
160
+ if (isStatementDateConflictResponse(response) === false) {
161
+ return null;
162
+ }
163
+ const previousReconciliationInfo = transformPreviousReconciliationInfoToState(response.previous_reconciliation_info);
164
+ if (response.error_code === 'STATEMENT_RANGE_ALREADY_RECONCILED' &&
165
+ previousReconciliationInfo == null) {
166
+ return null;
167
+ }
168
+ if (response.error_code === 'STATEMENT_RANGE_HAS_GAP' &&
169
+ response.expected_start_date == null &&
170
+ previousReconciliationInfo == null) {
171
+ return null;
172
+ }
173
+ return {
174
+ errorCode: response.error_code,
175
+ expectedStartDate: response.expected_start_date ?? null,
176
+ ...(previousReconciliationInfo != null ? { previousReconciliationInfo } : {}),
177
+ };
178
+ }
179
+ export function transformParseStatementPayloadToState(payload) {
180
+ return {
181
+ statementUpload: transformStatementUploadPayloadToState(payload.statement_upload),
182
+ };
183
+ }
184
+ export function transformUpdateStatementInfoPayloadToState(payload) {
185
+ return {
186
+ statementUpload: transformStatementUploadPayloadToState(payload.statement_upload),
187
+ };
188
+ }
189
+ export function transformStatementUpdateStateToPayload(localData) {
190
+ const { statementMeta, statementTransactions } = localData;
191
+ return {
192
+ statement_meta: {
193
+ opening_balance: statementMeta.openingBalance,
194
+ statement_end_date: statementMeta.statementEndDate,
195
+ statement_start_date: statementMeta.statementStartDate,
196
+ total_deposits: statementMeta.totalDeposits,
197
+ total_payments: statementMeta.totalPayments,
198
+ },
199
+ statement_transactions: {
200
+ added: statementTransactions.added.map((txn) => ({
201
+ amount: txn.amount,
202
+ transaction_date: txn.transactionDate,
203
+ transaction_direction: txn.transactionDirection,
204
+ transaction_memo: txn.transactionMemo,
205
+ })),
206
+ deleted_ids: statementTransactions.deletedIds,
207
+ updated: statementTransactions.updated
208
+ .filter((txn) => txn.statementTransactionId != null)
209
+ .map((txn) => ({
210
+ amount: txn.amount,
211
+ statement_transaction_id: txn.statementTransactionId,
212
+ transaction_date: txn.transactionDate,
213
+ transaction_direction: txn.transactionDirection,
214
+ transaction_memo: txn.transactionMemo,
215
+ })),
216
+ },
217
+ };
218
+ }
219
+ /** API request body for PUT statement-uploads; cast lives here for putAndGetJSON. */
220
+ export function toStatementUpdateRequestBody(localData) {
221
+ return transformStatementUpdateStateToPayload(localData);
222
+ }
@@ -2,6 +2,7 @@ import { createSlice } from '@reduxjs/toolkit';
2
2
  import { toMonthYearPeriodId, } from '../../../commonStateTypes/timePeriod';
3
3
  import { toReconciliationAccountSource } from '../../../entity/account/accountState';
4
4
  import { toBankStatusCodeType } from '../../../entity/accountRecon/accountReconState';
5
+ import { toReconciliationViewSummary, } from '../types/reconciliationViewState';
5
6
  // Initial state
6
7
  export const initialReconciliationTabsState = {
7
8
  balances: {
@@ -67,6 +68,7 @@ export const initialState = {
67
68
  refreshStatus: { fetchState: 'Not-Started', error: undefined },
68
69
  actionFetchState: initialActionFetchState,
69
70
  selectedAccountId: undefined,
71
+ statementProcessingFailed: false,
70
72
  statementUploadChosen: false,
71
73
  reconListUIState: {
72
74
  nodeCollapseState: {},
@@ -75,6 +77,7 @@ export const initialState = {
75
77
  },
76
78
  },
77
79
  excludedAccountIDs: [],
80
+ summary: undefined,
78
81
  };
79
82
  // Create slice with reducers
80
83
  const expenseAutomationReconciliationView = createSlice({
@@ -143,7 +146,7 @@ const expenseAutomationReconciliationView = createSlice({
143
146
  fetchReconciliationSuccess: {
144
147
  reducer(draft, action) {
145
148
  if (action.payload.accountId == null) {
146
- updateAllReconciliation(draft, action.payload.reconciliation.accounts, action.payload.selectedPeriod, action.payload.reconciliation.reconciliation, action.payload.refreshViewInBackground, action.payload.reconciliation.excluded_accounts ?? []);
149
+ updateAllReconciliation(draft, action.payload.reconciliation.accounts, action.payload.selectedPeriod, action.payload.reconciliation.reconciliation, action.payload.refreshViewInBackground, action.payload.reconciliation.excluded_accounts ?? [], action.payload.reconciliation.summary);
147
150
  }
148
151
  else if (action.payload.reconciliation.reconciliation.length > 0 &&
149
152
  action.payload.reconciliation.accounts.length > 0) {
@@ -287,6 +290,10 @@ const expenseAutomationReconciliationView = createSlice({
287
290
  updateSelectedDrawerAccountId: (draft, action) => {
288
291
  draft.selectedDrawerAccountId = action.payload.selectedDrawerAccountId;
289
292
  },
293
+ updateStatementProcessingFailed: (draft, action) => {
294
+ draft.statementProcessingFailed =
295
+ action.payload.statementProcessingFailed;
296
+ },
290
297
  initialiseLocalDataForSelectedAccountId: () => { },
291
298
  updateSelectedTab: (draft, action) => {
292
299
  const { selectedTab } = action.payload;
@@ -378,10 +385,22 @@ const expenseAutomationReconciliationView = createSlice({
378
385
  },
379
386
  uploadAccountStatement: (draft, action) => {
380
387
  const { accountId, selectedPeriod } = action.payload;
381
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].statementUploadStatus = {
382
- fetchState: 'In-Progress',
383
- error: undefined,
384
- };
388
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
389
+ if (accountRecon != null) {
390
+ accountRecon.statementUploadStatus = {
391
+ fetchState: 'In-Progress',
392
+ error: undefined,
393
+ };
394
+ accountRecon.parsedStatementData = undefined;
395
+ accountRecon.statementParseStatus = {
396
+ fetchState: 'Not-Started',
397
+ error: undefined,
398
+ };
399
+ accountRecon.reparseStatementStatus = {
400
+ fetchState: 'Not-Started',
401
+ error: undefined,
402
+ };
403
+ }
385
404
  },
386
405
  uploadAccountStatementSuccess: (draft, action) => {
387
406
  const { accountId, selectedPeriod } = action.payload;
@@ -419,6 +438,143 @@ const expenseAutomationReconciliationView = createSlice({
419
438
  error: action.payload.error,
420
439
  };
421
440
  },
441
+ parseStatement: (draft, action) => {
442
+ const { accountId, selectedPeriod } = action.payload;
443
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
444
+ if (accountRecon != null) {
445
+ accountRecon.parsedStatementData = undefined;
446
+ accountRecon.statementParseStatus = {
447
+ fetchState: 'In-Progress',
448
+ error: undefined,
449
+ };
450
+ }
451
+ },
452
+ parseStatementSuccess: (draft, action) => {
453
+ const { accountId, selectedPeriod } = action.payload;
454
+ draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].statementParseStatus = {
455
+ fetchState: 'Completed',
456
+ error: undefined,
457
+ };
458
+ draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].statementParseInProgress = false;
459
+ draft.statementProcessingFailed = false;
460
+ },
461
+ parseStatementFailure: (draft, action) => {
462
+ const { accountId, selectedPeriod } = action.payload;
463
+ draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].statementParseStatus = {
464
+ fetchState: 'Error',
465
+ error: action.payload.error,
466
+ };
467
+ draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].statementParseInProgress = false;
468
+ draft.statementProcessingFailed = true;
469
+ },
470
+ reparseStatement: (draft, action) => {
471
+ const { accountId, selectedPeriod } = action.payload;
472
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
473
+ if (accountRecon != null) {
474
+ accountRecon.parsedStatementData = undefined;
475
+ accountRecon.reparseStatementStatus = {
476
+ fetchState: 'In-Progress',
477
+ error: undefined,
478
+ };
479
+ accountRecon.statementParseStatus = {
480
+ fetchState: 'Not-Started',
481
+ error: undefined,
482
+ };
483
+ accountRecon.statementParseInProgress = false;
484
+ }
485
+ },
486
+ reparseStatementSuccess: (draft, action) => {
487
+ const { accountId, selectedPeriod } = action.payload;
488
+ draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].reparseStatementStatus = {
489
+ fetchState: 'Completed',
490
+ error: undefined,
491
+ };
492
+ draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].statementParseStatus = {
493
+ fetchState: 'Not-Started',
494
+ error: undefined,
495
+ };
496
+ draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].statementParseInProgress = false;
497
+ draft.statementProcessingFailed = false;
498
+ },
499
+ reparseStatementFailure: (draft, action) => {
500
+ const { accountId, selectedPeriod } = action.payload;
501
+ draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].reparseStatementStatus = {
502
+ fetchState: 'Error',
503
+ error: action.payload.error,
504
+ };
505
+ draft.statementProcessingFailed = true;
506
+ },
507
+ resetReparseStatementStatus: (draft, action) => {
508
+ const { accountId, selectedPeriod } = action.payload;
509
+ draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].reparseStatementStatus = {
510
+ fetchState: 'Not-Started',
511
+ error: undefined,
512
+ };
513
+ },
514
+ updateParsedStatementData: (draft, action) => {
515
+ const { accountId, selectedPeriod, parsedStatementData } = action.payload;
516
+ draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].parsedStatementData =
517
+ parsedStatementData;
518
+ },
519
+ updateStatementUpdateLocalData: (draft, action) => {
520
+ const { accountId, selectedPeriod, statementUpdateLocalData } = action.payload;
521
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
522
+ if (accountRecon != null) {
523
+ if (accountRecon.localData == null) {
524
+ accountRecon.localData = {
525
+ statementUpdateLocalData,
526
+ };
527
+ }
528
+ else {
529
+ accountRecon.localData.statementUpdateLocalData =
530
+ statementUpdateLocalData;
531
+ }
532
+ }
533
+ },
534
+ submitStatementUpdate: (draft, action) => {
535
+ const { accountId, selectedPeriod } = action.payload;
536
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
537
+ if (accountRecon != null) {
538
+ accountRecon.statementUpdateStatus = {
539
+ fetchState: 'In-Progress',
540
+ error: undefined,
541
+ };
542
+ // Clear any stale date conflict from a previous attempt.
543
+ accountRecon.statementDateConflict = null;
544
+ }
545
+ },
546
+ submitStatementUpdateSuccess: (draft, action) => {
547
+ const { accountId, selectedPeriod } = action.payload;
548
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
549
+ if (accountRecon != null) {
550
+ accountRecon.statementUpdateStatus = {
551
+ fetchState: 'Completed',
552
+ error: undefined,
553
+ };
554
+ accountRecon.statementDateConflict = null;
555
+ if (accountRecon.localData != null) {
556
+ accountRecon.localData.statementUpdateLocalData = undefined;
557
+ }
558
+ }
559
+ },
560
+ submitStatementUpdateFailure: (draft, action) => {
561
+ const { accountId, selectedPeriod, dateConflict } = action.payload;
562
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
563
+ if (accountRecon != null) {
564
+ accountRecon.statementUpdateStatus = {
565
+ fetchState: 'Error',
566
+ error: action.payload.error,
567
+ };
568
+ accountRecon.statementDateConflict = dateConflict ?? null;
569
+ }
570
+ },
571
+ clearStatementDateConflict: (draft, action) => {
572
+ const { accountId, selectedPeriod } = action.payload;
573
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
574
+ if (accountRecon != null) {
575
+ accountRecon.statementDateConflict = null;
576
+ }
577
+ },
422
578
  updateAccountReconciliationLocalData: (draft, action) => {
423
579
  const { accountId, selectedPeriod, localData } = action.payload;
424
580
  const oldLocalData = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].localData;
@@ -524,10 +680,10 @@ const expenseAutomationReconciliationView = createSlice({
524
680
  },
525
681
  });
526
682
  // Export actions
527
- export const { fetchReconciliation, fetchReconciliationFailure, fetchReconciliationSuccess, saveReconciliationDetail, saveReconciliationDetailFailure, updateSelectedAccountId, setConnectionInProgressForAccount, setStatementParseInProgress, updateReconcileTabLocalData, updateReconcileTabListSortState, updateReconcileTabListScrollState, saveReconciliationDetailSuccess, updateSelectedTab, updateBalancesLocalData, initialiseLocalDataForSelectedAccountId, clearExpenseAutomationReconciliationView, updateSelectedDrawerAccountId, updateReviewTabSortState, initializeReconciliationReviewTabLocalData, updateReviewTabLocalData, saveReconciliationReview, updateSaveReconciliationReviewFetchStatus, updateReconListScrollPosition, updateAccountReconciliationLocalData, deleteAccountStatement, deleteAccountStatementSuccess, deleteAccountStatementFailure, excludeAccountFromReconciliation, excludeAccountFromReconciliationSuccess, excludeAccountFromReconciliationFailure, includeAccountInReconciliation, includeAccountInReconciliationSuccess, includeAccountInReconciliationFailure, uploadAccountStatement, uploadAccountStatementSuccess, uploadAccountStatementFailure, updateStatementUploadChosen, updateNodeCollapseState, } = expenseAutomationReconciliationView.actions;
683
+ export const { fetchReconciliation, fetchReconciliationFailure, fetchReconciliationSuccess, saveReconciliationDetail, saveReconciliationDetailFailure, updateSelectedAccountId, setConnectionInProgressForAccount, setStatementParseInProgress, 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;
528
684
  // Export reducer
529
685
  export default expenseAutomationReconciliationView.reducer;
530
- function updateAllReconciliation(draft, accounts, selectedPeriod, reconciliationData, refreshViewInBackground, excludedAccounts) {
686
+ function updateAllReconciliation(draft, accounts, selectedPeriod, reconciliationData, refreshViewInBackground, excludedAccounts, summary) {
531
687
  draft.excludedAccountIDs = excludedAccounts.map((ea) => ea.account_id);
532
688
  draft.excludedAccountExclusionInfo = {};
533
689
  excludedAccounts.forEach((ea) => {
@@ -556,6 +712,7 @@ function updateAllReconciliation(draft, accounts, selectedPeriod, reconciliation
556
712
  : undefined,
557
713
  };
558
714
  });
715
+ const oldReconciliationByAccountID = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID ?? {};
559
716
  draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)] = {
560
717
  accountIDs: [],
561
718
  reconciliationByAccountID: {},
@@ -579,6 +736,7 @@ function updateAllReconciliation(draft, accounts, selectedPeriod, reconciliation
579
736
  paymentAccountId: account?.payment_account_id ?? undefined,
580
737
  };
581
738
  }
739
+ const oldRecord = oldReconciliationByAccountID[reconciliation.account_id];
582
740
  accountReconciliationRecords.reconciliationByAccountID[reconciliation.account_id] = {
583
741
  accountId: reconciliation.account_id,
584
742
  reconciliationId: reconciliation.reconciliation_id ?? undefined,
@@ -586,16 +744,30 @@ function updateAllReconciliation(draft, accounts, selectedPeriod, reconciliation
586
744
  refreshStatus: { fetchState: 'Not-Started', error: undefined },
587
745
  statementDeleteStatus: { fetchState: 'Not-Started', error: undefined },
588
746
  statementUploadStatus: { fetchState: 'Not-Started', error: undefined },
589
- statementUpdateStatus: { fetchState: 'Not-Started', error: undefined },
747
+ statementUpdateStatus: oldRecord?.statementUpdateStatus ?? {
748
+ fetchState: 'Not-Started',
749
+ error: undefined,
750
+ },
590
751
  statementParseInProgress: reconciliation.bank_status.code === 'parsing',
752
+ statementParseStatus: oldRecord?.statementParseStatus ?? {
753
+ fetchState: 'Not-Started',
754
+ error: undefined,
755
+ },
756
+ reparseStatementStatus: oldRecord?.reparseStatementStatus ?? {
757
+ fetchState: 'Not-Started',
758
+ error: undefined,
759
+ },
760
+ statementDateConflict: oldRecord?.statementDateConflict ?? null,
591
761
  localData: {
762
+ ...oldRecord?.localData,
592
763
  accountSource: account?.reconciliation_source != null
593
764
  ? toReconciliationAccountSource(account.reconciliation_source)
594
- : undefined,
765
+ : oldRecord?.localData?.accountSource,
595
766
  },
596
767
  accountDetectionReason: account?.detection_info != null && account.detection_reason != null
597
768
  ? account.detection_reason
598
769
  : undefined,
770
+ parsedStatementData: oldRecord?.parsedStatementData,
599
771
  };
600
772
  accountReconciliationRecords.accountIDs.push(reconciliation.account_id);
601
773
  });
@@ -609,6 +781,10 @@ function updateAllReconciliation(draft, accounts, selectedPeriod, reconciliation
609
781
  draft.fetchState = 'Completed';
610
782
  draft.error = undefined;
611
783
  }
784
+ if (summary !== undefined) {
785
+ draft.summary =
786
+ summary != null ? toReconciliationViewSummary(summary) : undefined;
787
+ }
612
788
  }
613
789
  function updateReconciliationByAccountID(draft, accounts, selectedPeriod, accountId, reconciliationData, refreshViewInBackground) {
614
790
  const oldRecord = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId];
@@ -676,6 +852,9 @@ const updateReconciliationByAccountIDOnIncludeAccount = (draft, accountId, recon
676
852
  statementUploadStatus: { fetchState: 'Not-Started', error: undefined },
677
853
  statementUpdateStatus: { fetchState: 'Not-Started', error: undefined },
678
854
  statementParseInProgress: reconciliation.bank_status.code === 'parsing',
855
+ statementParseStatus: { fetchState: 'Not-Started', error: undefined },
856
+ reparseStatementStatus: { fetchState: 'Not-Started', error: undefined },
857
+ statementDateConflict: null,
679
858
  localData: {},
680
859
  accountDetectionReason: undefined,
681
860
  };
@@ -43,6 +43,17 @@ export const getExpenseAutomationReconciliationView = createSelector((state) =>
43
43
  error: undefined,
44
44
  })
45
45
  : { fetchState: 'Not-Started', error: undefined };
46
+ const monthEndChecksForPeriod = selectedPeriod != null
47
+ ? monthEndCloseChecksState.monthEndCloseChecksByTenantId[currentTenant.tenantId]?.[toMonthYearPeriodId(selectedPeriod)]
48
+ : undefined;
49
+ const reconCheckTimeSavedHours = monthEndChecksForPeriod?.monthEndCloseCheckList?.find((check) => check.id === 'reconciliation')?.progressJson?.timeSavedHours;
50
+ const summaryWithTimeSaved = reconciliationViewState.summary != null
51
+ ? {
52
+ ...reconciliationViewState.summary,
53
+ timeSavedHours: reconCheckTimeSavedHours ??
54
+ reconciliationViewState.summary.timeSavedHours,
55
+ }
56
+ : undefined;
46
57
  if (accountReconForMonthYearPeriod == null) {
47
58
  return {
48
59
  bankAccounts: [],
@@ -66,6 +77,7 @@ export const getExpenseAutomationReconciliationView = createSelector((state) =>
66
77
  selectedTab: reconciliationViewState.selectedTab,
67
78
  monthYearPeriod: selectedPeriod,
68
79
  selectedAccountId: reconciliationViewState.selectedAccountId,
80
+ statementProcessingFailed: reconciliationViewState.statementProcessingFailed,
69
81
  statementUploadChosen: reconciliationViewState.statementUploadChosen,
70
82
  accountReconciliationsByAccountID: {},
71
83
  reconciliationTabsState: initialReconciliationTabsState,
@@ -115,6 +127,7 @@ export const getExpenseAutomationReconciliationView = createSelector((state) =>
115
127
  excludeAccountFetchState: reconciliationViewState.actionFetchState.excludeAccountFetch,
116
128
  includeAccountFetchState: reconciliationViewState.actionFetchState.includeAccountFetch,
117
129
  excludedAccountIDs: reconciliationViewState.excludedAccountIDs,
130
+ summary: summaryWithTimeSaved,
118
131
  };
119
132
  }
120
133
  const allAccountIDs = accountReconForMonthYearPeriod.accountIDs;
@@ -239,6 +252,12 @@ export const getExpenseAutomationReconciliationView = createSelector((state) =>
239
252
  .refreshStatus,
240
253
  statementDeleteStatus: accountReconForMonthYearPeriod.reconciliationByAccountID[accountID]
241
254
  .statementDeleteStatus,
255
+ statementParseStatus: accountReconForMonthYearPeriod.reconciliationByAccountID[accountID]
256
+ .statementParseStatus,
257
+ reparseStatementStatus: accountReconForMonthYearPeriod.reconciliationByAccountID[accountID]
258
+ .reparseStatementStatus,
259
+ statementDateConflict: accountReconForMonthYearPeriod.reconciliationByAccountID[accountID]
260
+ .statementDateConflict,
242
261
  statementUpdateStatus: accountReconForMonthYearPeriod.reconciliationByAccountID[accountID]
243
262
  .statementUpdateStatus,
244
263
  statementUploadStatus: accountReconForMonthYearPeriod.reconciliationByAccountID[accountID]
@@ -247,6 +266,8 @@ export const getExpenseAutomationReconciliationView = createSelector((state) =>
247
266
  .localData,
248
267
  statementParseInProgress: accountReconForMonthYearPeriod.reconciliationByAccountID[accountID]
249
268
  .statementParseInProgress,
269
+ parsedStatementData: accountReconForMonthYearPeriod.reconciliationByAccountID[accountID]
270
+ .parsedStatementData,
250
271
  transactionFetchState: reduceAnyFetchState([
251
272
  accountReconForMonthYearPeriod.reconciliationByAccountID[accountID]
252
273
  .transactionFetchState,
@@ -315,6 +336,9 @@ export const getExpenseAutomationReconciliationView = createSelector((state) =>
315
336
  statementUpdateStatus: { fetchState: 'Not-Started' },
316
337
  statementUploadStatus: { fetchState: 'Not-Started' },
317
338
  statementDeleteStatus: { fetchState: 'Not-Started' },
339
+ statementParseStatus: { fetchState: 'Not-Started' },
340
+ reparseStatementStatus: { fetchState: 'Not-Started' },
341
+ statementDateConflict: null,
318
342
  localData: {},
319
343
  connectionInProgress: false,
320
344
  statementParseInProgress: false,
@@ -403,6 +427,7 @@ export const getExpenseAutomationReconciliationView = createSelector((state) =>
403
427
  refreshStatus: reconciliationViewState.refreshStatus,
404
428
  selectedTab: reconciliationViewState.selectedTab,
405
429
  selectedDrawerAccountId: reconciliationViewState.selectedDrawerAccountId,
430
+ statementProcessingFailed: reconciliationViewState.statementProcessingFailed,
406
431
  statementUploadChosen: reconciliationViewState.statementUploadChosen,
407
432
  monthYearPeriod: selectedPeriod,
408
433
  accountReconciliationBySectionID,
@@ -423,8 +448,10 @@ export const getExpenseAutomationReconciliationView = createSelector((state) =>
423
448
  excludeAccountFetchState: reconciliationViewState.actionFetchState.excludeAccountFetch,
424
449
  includeAccountFetchState: reconciliationViewState.actionFetchState.includeAccountFetch,
425
450
  excludedAccountIDs: reconciliationViewState.excludedAccountIDs,
451
+ summary: summaryWithTimeSaved,
426
452
  };
427
453
  });
454
+ export const getReparseStatementStatusByAccountId = (state, accountId) => getExpenseAutomationReconciliationView(state).accountReconciliationsByAccountID[accountId]?.reparseStatementStatus;
428
455
  export const isAccountReconReport = (reportId) => {
429
456
  return reportId.endsWith('reconciliation');
430
457
  };
@@ -1,4 +1,33 @@
1
1
  import { stringToUnion } from '../../../commonStateTypes/stringToUnion';
2
+ export function toReconciliationViewSummary(payload) {
3
+ return {
4
+ accounts: {
5
+ done: payload.accounts.done,
6
+ failed: payload.accounts.failed,
7
+ inProgress: payload.accounts.in_progress,
8
+ total: payload.accounts.total,
9
+ },
10
+ accountsToConnectCount: payload.accounts_to_connect_count,
11
+ autoMatched: {
12
+ matchedTxns: payload.auto_matched.matched_txns,
13
+ totalTxns: payload.auto_matched.total_txns,
14
+ },
15
+ needsReview: {
16
+ accountCount: payload.needs_review.account_count,
17
+ txnCount: payload.needs_review.txn_count,
18
+ },
19
+ timeSavedHours: 0,
20
+ timeSavedPercentage: payload.time_saved_percentage,
21
+ progressBar: payload.progress_bar != null
22
+ ? {
23
+ aiPercentageShare: payload.progress_bar.ai_percentage_share,
24
+ completePercentage: payload.progress_bar.complete_percentage,
25
+ humanPercentageShare: payload.progress_bar.human_percentage_share,
26
+ remainingPercentage: payload.progress_bar.remaining_percentage,
27
+ }
28
+ : undefined,
29
+ };
30
+ }
2
31
  export const RECONCILE_ACTIONS = [
3
32
  'reconcile',
4
33
  'save_reconcile_for_later',