@zeniai/client-epic-state 5.1.18-betaDI11 → 5.1.18-betaDI12

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 (127) hide show
  1. package/README.md +3 -1
  2. package/lib/commonStateTypes/viewAndReport/agingReportStateTypes.d.ts +1 -1
  3. package/lib/entity/accountRecon/accountReconPayload.d.ts +22 -0
  4. package/lib/entity/accountRecon/accountReconPayload.js +26 -1
  5. package/lib/entity/accountRecon/accountReconReducer.d.ts +5 -2
  6. package/lib/entity/accountRecon/accountReconReducer.js +36 -2
  7. package/lib/entity/accountRecon/accountReconSelector.d.ts +6 -1
  8. package/lib/entity/accountRecon/accountReconSelector.js +5 -0
  9. package/lib/entity/accountRecon/accountReconState.d.ts +22 -0
  10. package/lib/entity/monthEndCloseChecks/monthEndCloseChecksPayload.d.ts +8 -0
  11. package/lib/entity/monthEndCloseChecks/monthEndCloseChecksPayload.js +10 -0
  12. package/lib/entity/monthEndCloseChecks/monthEndCloseChecksState.d.ts +8 -0
  13. package/lib/entity/task/taskReducer.js +3 -0
  14. package/lib/entity/task/taskState.d.ts +1 -1
  15. package/lib/entity/transaction/payloadTypes/transactionPayload.d.ts +1 -0
  16. package/lib/entity/transaction/payloadTypes/transactionPayload.js +5 -0
  17. package/lib/entity/transaction/stateTypes/transaction.d.ts +1 -0
  18. package/lib/epic.d.ts +7 -2
  19. package/lib/epic.js +7 -2
  20. package/lib/esm/entity/accountRecon/accountReconPayload.js +26 -1
  21. package/lib/esm/entity/accountRecon/accountReconReducer.js +35 -1
  22. package/lib/esm/entity/accountRecon/accountReconSelector.js +5 -0
  23. package/lib/esm/entity/monthEndCloseChecks/monthEndCloseChecksPayload.js +10 -0
  24. package/lib/esm/entity/task/taskReducer.js +3 -0
  25. package/lib/esm/entity/transaction/payloadTypes/transactionPayload.js +5 -0
  26. package/lib/esm/epic.js +7 -2
  27. package/lib/esm/index.js +9 -9
  28. package/lib/esm/view/aiCfoView/aiCfoViewReducer.js +29 -3
  29. package/lib/esm/view/aiCfoView/aiCfoViewSelector.js +11 -0
  30. package/lib/esm/view/aiCfoView/epics/createSessionAndSubmitEpic.js +2 -2
  31. package/lib/esm/view/aiCfoView/epics/fetchSkillsEpic.js +34 -0
  32. package/lib/esm/view/aiCfoView/epics/submitQuestionEpic.js +7 -1
  33. package/lib/esm/view/expenseAutomationView/epics/accountRecon/parseStatementEpic.js +34 -0
  34. package/lib/esm/view/expenseAutomationView/epics/accountRecon/reparseStatementEpic.js +31 -0
  35. package/lib/esm/view/expenseAutomationView/epics/accountRecon/updateStatementInfoEpic.js +57 -0
  36. package/lib/esm/view/expenseAutomationView/epics/fluxAnalysis/fetchFluxAnalysisViewEpic.js +1 -0
  37. package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +49 -11
  38. package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationViewEpic.js +30 -3
  39. package/lib/esm/view/expenseAutomationView/payload/reconciliationPayload.js +153 -0
  40. package/lib/esm/view/expenseAutomationView/reducers/fluxAnalysisViewReducer.js +6 -1
  41. package/lib/esm/view/expenseAutomationView/reducers/reconciliationViewReducer.js +188 -9
  42. package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +37 -17
  43. package/lib/esm/view/expenseAutomationView/selectors/fluxAnalysisViewSelector.js +2 -1
  44. package/lib/esm/view/expenseAutomationView/selectors/reconciliationViewSelector.js +27 -0
  45. package/lib/esm/view/expenseAutomationView/types/reconciliationViewState.js +29 -0
  46. package/lib/esm/view/onboardingView/cockpitView/epic/initializeOnboardingCustomerViewUpdateDataEpic.js +4 -16
  47. package/lib/esm/view/onboardingView/cockpitView/onboardingCockpitViewPayload.js +2 -2
  48. package/lib/esm/view/opExByVendor/opExByVendorReducer.js +28 -5
  49. package/lib/esm/view/opExByVendor/opExByVendorSelector.js +1 -0
  50. package/lib/esm/view/opExByVendor/refreshOpExByVendorReportEpic.js +21 -0
  51. package/lib/esm/view/referralView/epics/sendReferralInviteEpic.js +9 -1
  52. package/lib/esm/view/referralView/referralReducer.js +3 -2
  53. package/lib/esm/view/spendManagement/commonSetup/setupViewReducer.js +5 -1
  54. package/lib/esm/view/taskManager/taskDetailView/epics/saveTaskDetailEpic.js +2 -2
  55. package/lib/esm/view/taskManager/taskDetailView/epics/sendCommentMentionNotificationEpic.js +2 -2
  56. package/lib/esm/view/taskManager/taskDetailView/taskDetailSelector.js +1 -1
  57. package/lib/esm/view/taskManager/taskListView/taskListReducer.js +3 -1
  58. package/lib/esm/view/transactionDetail/epics/saveTransactionDetailEpic.js +3 -1
  59. package/lib/esm/view/transactionDetail/epics/saveTransactionHelper.js +11 -2
  60. package/lib/esm/view/transactionDetail/epics/updateTransactionDetailEpic.js +34 -5
  61. package/lib/esm/view/transactionDetail/transactionDetailLocalDataHelper.js +17 -0
  62. package/lib/index.d.ts +14 -14
  63. package/lib/index.js +57 -37
  64. package/lib/view/aiCfoView/aiCfoViewPayload.d.ts +14 -0
  65. package/lib/view/aiCfoView/aiCfoViewReducer.d.ts +8 -4
  66. package/lib/view/aiCfoView/aiCfoViewReducer.js +30 -4
  67. package/lib/view/aiCfoView/aiCfoViewSelector.d.ts +2 -1
  68. package/lib/view/aiCfoView/aiCfoViewSelector.js +12 -0
  69. package/lib/view/aiCfoView/aiCfoViewState.d.ts +15 -0
  70. package/lib/view/aiCfoView/epics/createSessionAndSubmitEpic.js +2 -2
  71. package/lib/view/aiCfoView/epics/fetchSkillsEpic.d.ts +8 -0
  72. package/lib/view/aiCfoView/epics/fetchSkillsEpic.js +38 -0
  73. package/lib/view/aiCfoView/epics/submitQuestionEpic.js +7 -1
  74. package/lib/view/expenseAutomationView/epics/accountRecon/parseStatementEpic.d.ts +6 -0
  75. package/lib/view/expenseAutomationView/epics/accountRecon/parseStatementEpic.js +38 -0
  76. package/lib/view/expenseAutomationView/epics/accountRecon/reparseStatementEpic.d.ts +26 -0
  77. package/lib/view/expenseAutomationView/epics/accountRecon/reparseStatementEpic.js +35 -0
  78. package/lib/view/expenseAutomationView/epics/accountRecon/updateStatementInfoEpic.d.ts +27 -0
  79. package/lib/view/expenseAutomationView/epics/accountRecon/updateStatementInfoEpic.js +61 -0
  80. package/lib/view/expenseAutomationView/epics/fluxAnalysis/fetchFluxAnalysisViewEpic.js +1 -0
  81. package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +47 -9
  82. package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationViewEpic.js +30 -3
  83. package/lib/view/expenseAutomationView/payload/fluxAnalysisPayload.d.ts +8 -0
  84. package/lib/view/expenseAutomationView/payload/reconciliationPayload.d.ts +176 -10
  85. package/lib/view/expenseAutomationView/payload/reconciliationPayload.js +159 -0
  86. package/lib/view/expenseAutomationView/payload/transactionCategorizationPayload.d.ts +7 -0
  87. package/lib/view/expenseAutomationView/reducers/fluxAnalysisViewReducer.d.ts +2 -1
  88. package/lib/view/expenseAutomationView/reducers/fluxAnalysisViewReducer.js +6 -1
  89. package/lib/view/expenseAutomationView/reducers/reconciliationViewReducer.d.ts +53 -3
  90. package/lib/view/expenseAutomationView/reducers/reconciliationViewReducer.js +190 -10
  91. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +37 -17
  92. package/lib/view/expenseAutomationView/selectorTypes/reconciliationViewSelectorTypes.d.ts +7 -1
  93. package/lib/view/expenseAutomationView/selectors/fluxAnalysisViewSelector.js +2 -1
  94. package/lib/view/expenseAutomationView/selectors/reconciliationViewSelector.d.ts +2 -0
  95. package/lib/view/expenseAutomationView/selectors/reconciliationViewSelector.js +29 -1
  96. package/lib/view/expenseAutomationView/types/fluxAnalysisViewState.d.ts +2 -0
  97. package/lib/view/expenseAutomationView/types/reconciliationViewState.d.ts +175 -0
  98. package/lib/view/expenseAutomationView/types/reconciliationViewState.js +30 -0
  99. package/lib/view/onboardingView/cockpitView/epic/initializeOnboardingCustomerViewUpdateDataEpic.js +3 -15
  100. package/lib/view/onboardingView/cockpitView/onboardingCockpitViewPayload.js +2 -2
  101. package/lib/view/opExByVendor/opExByVendorReducer.d.ts +6 -1
  102. package/lib/view/opExByVendor/opExByVendorReducer.js +29 -6
  103. package/lib/view/opExByVendor/opExByVendorSelector.d.ts +1 -0
  104. package/lib/view/opExByVendor/opExByVendorSelector.js +1 -0
  105. package/lib/view/opExByVendor/opExByVendorState.d.ts +1 -0
  106. package/lib/view/opExByVendor/refreshOpExByVendorReportEpic.d.ts +17 -0
  107. package/lib/view/opExByVendor/refreshOpExByVendorReportEpic.js +25 -0
  108. package/lib/view/referralView/epics/sendReferralInviteEpic.js +9 -1
  109. package/lib/view/referralView/referralReducer.d.ts +1 -0
  110. package/lib/view/referralView/referralReducer.js +4 -3
  111. package/lib/view/spendManagement/commonSetup/setupViewReducer.js +5 -1
  112. package/lib/view/taskManager/taskDetailView/epics/saveTaskDetailEpic.js +2 -2
  113. package/lib/view/taskManager/taskDetailView/epics/sendCommentMentionNotificationEpic.d.ts +1 -1
  114. package/lib/view/taskManager/taskDetailView/epics/sendCommentMentionNotificationEpic.js +1 -1
  115. package/lib/view/taskManager/taskDetailView/taskDetail.d.ts +1 -1
  116. package/lib/view/taskManager/taskDetailView/taskDetailSelector.js +1 -1
  117. package/lib/view/taskManager/taskListView/taskListReducer.js +3 -1
  118. package/lib/view/transactionDetail/epics/saveTransactionDetailEpic.js +3 -1
  119. package/lib/view/transactionDetail/epics/saveTransactionHelper.d.ts +1 -1
  120. package/lib/view/transactionDetail/epics/saveTransactionHelper.js +11 -2
  121. package/lib/view/transactionDetail/epics/updateTransactionDetailEpic.d.ts +8 -0
  122. package/lib/view/transactionDetail/epics/updateTransactionDetailEpic.js +36 -6
  123. package/lib/view/transactionDetail/transactionDetailLocalDataHelper.js +17 -0
  124. package/lib/view/transactionDetail/transactionDetailState.d.ts +1 -0
  125. package/lib/view/transactionDetail/transactionDetailTypes.d.ts +1 -0
  126. package/lib/view/vendorFiling1099/vendorFiling1099List/vendorFiling1099ListState.d.ts +1 -1
  127. package/package.json +5 -2
@@ -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
+ }
@@ -1,6 +1,7 @@
1
1
  import { createSlice } from '@reduxjs/toolkit';
2
2
  import { toMonthYearPeriodId, } from '../../../commonStateTypes/timePeriod';
3
3
  import { defaultVendorCurrency } from '../../../entity/vendor/vendorState';
4
+ import { date } from '../../../zeniDayJS';
4
5
  import { toBalancesByMonthFromPayload, toVendorFluxAnalysisStatusFromPayload, } from '../helpers/helpers';
5
6
  export const initialState = {
6
7
  fluxAnalysisEntities: { operatingExpenses: [] },
@@ -31,6 +32,7 @@ export const initialState = {
31
32
  fetchState: 'Not-Started',
32
33
  error: undefined,
33
34
  currency: defaultVendorCurrency,
35
+ lastRefreshSuccessTime: undefined,
34
36
  totalBalancesAndVariance: undefined,
35
37
  hasValidState() {
36
38
  return this.fetchState == 'Completed';
@@ -99,7 +101,7 @@ const expenseAutomationFluxAnalysisView = createSlice({
99
101
  },
100
102
  },
101
103
  fetchFluxAnalysisViewSuccess(draft, action) {
102
- const { expensesPayload, selectedPeriod, keepExistingListItems, fetchReimbursement, currency, totalsPayload, } = action.payload;
104
+ const { expensesPayload, selectedPeriod, keepExistingListItems, fetchReimbursement, currency, status, totalsPayload, } = action.payload;
103
105
  const periodId = toMonthYearPeriodId(selectedPeriod);
104
106
  const group = draft.groupsBySelectedPeriod[periodId];
105
107
  const groupToBeFetched = fetchReimbursement
@@ -109,6 +111,9 @@ const expenseAutomationFluxAnalysisView = createSlice({
109
111
  // Handle fetch completion
110
112
  handleFetchCompletion(draft, selectedPeriod, fetchReimbursement, groupToBeFetched);
111
113
  draft.fetchState = 'Completed';
114
+ if (status?.last_refresh_success_time != null) {
115
+ draft.lastRefreshSuccessTime = date(status.last_refresh_success_time);
116
+ }
112
117
  // Update flux analysis entities
113
118
  const allFluxAnalysisEntities = updateFluxAnalysisEntities(draft.fluxAnalysisEntities, expensesPayload, currency);
114
119
  // Update operating expense ids based on the "keepExistingListItems" flag
@@ -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
  };
@@ -153,27 +153,39 @@ const expenseAutomationTransactionsView = createSlice({
153
153
  draft.transactionCategorizationView[selectedTab].fetchState =
154
154
  'In-Progress';
155
155
  draft.transactionCategorizationView[selectedTab].error = undefined;
156
+ // A foreground fetch supersedes any in-flight background preload.
157
+ // Reset refreshStatus so a cancelled preload doesn't leave the
158
+ // spinner stuck at In-Progress.
159
+ draft.transactionCategorizationView[selectedTab].refreshStatus = {
160
+ fetchState: 'Not-Started',
161
+ error: undefined,
162
+ };
156
163
  }
157
164
  draft.transactionCategorizationView[selectedTab].uiState.pageToken =
158
165
  pageToken ?? null;
159
- draft.transactionCategorizationView[selectedTab].uiState.searchString =
160
- searchString ?? '';
166
+ // Only overwrite when explicitly set; undefined means a non-search fetch
167
+ // (tab switch, period change) should preserve the existing search string.
168
+ if (searchString !== undefined) {
169
+ draft.transactionCategorizationView[selectedTab].uiState.searchString =
170
+ searchString;
171
+ }
161
172
  draft.transactionCategorizationView[selectedTab].error = undefined;
173
+ const monthYearPeriod = {
174
+ month: period.start.month,
175
+ year: period.start.year,
176
+ };
177
+ const periodId = toMonthYearPeriodId(monthYearPeriod);
178
+ const transactionIdsBySelectedPeriod = draft.transactionCategorizationView[selectedTab]
179
+ .transactionIdsBySelectedPeriod[periodId] ?? [];
162
180
  // Reset list when we change the sort order and fetch list again
163
181
  if (resetListItems === true) {
164
- const monthYearPeriod = {
165
- month: period.start.month,
166
- year: period.start.year,
167
- };
168
- const transactionIdsBySelectedPeriod = draft.transactionCategorizationView[selectedTab]
169
- .transactionIdsBySelectedPeriod[toMonthYearPeriodId(monthYearPeriod)];
170
182
  transactionIdsBySelectedPeriod.forEach((transactionId) => {
171
183
  delete draft.transactionCategorizationView[selectedTab]
172
184
  .transactionReviewLocalDataById[transactionId];
173
185
  });
174
186
  draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds =
175
187
  draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds.filter((selectedCheckBoxTransactionId) => transactionIdsBySelectedPeriod.includes(selectedCheckBoxTransactionId));
176
- draft.transactionCategorizationView[selectedTab].transactionIdsBySelectedPeriod[toMonthYearPeriodId(monthYearPeriod)] = [];
188
+ draft.transactionCategorizationView[selectedTab].transactionIdsBySelectedPeriod[periodId] = [];
177
189
  draft.transactionCategorizationView[selectedTab].refreshStatus = {
178
190
  fetchState: 'Not-Started',
179
191
  error: undefined,
@@ -305,10 +317,12 @@ const expenseAutomationTransactionsView = createSlice({
305
317
  }
306
318
  },
307
319
  updateTransactionFilters(draft, action) {
308
- const { selectedTab, filters } = action.payload;
309
- // `filters` is non-nullable per the payload type, so no runtime
310
- // null-check is needed dropping it removes a dead defensive branch.
311
- draft.transactionCategorizationView[selectedTab].filters = filters;
320
+ const { filters } = action.payload;
321
+ // Filters are tab-agnostic (payee/category/class/amount), so apply to
322
+ // all tabs so switching tabs after filtering shows consistent results.
323
+ TRANSACTIONS_TABS.forEach((tab) => {
324
+ draft.transactionCategorizationView[tab].filters = filters;
325
+ });
312
326
  },
313
327
  saveTransactionCategorization: {
314
328
  prepare(selectedTab, transactionIds, cotTrackingByTransactionId, isUncategorizedExpenseCategoryEnabled) {
@@ -660,18 +674,24 @@ const expenseAutomationTransactionsView = createSlice({
660
674
  resetOtherTabsFetchState(draft, action) {
661
675
  const { tabs, refreshViewInBackground } = action.payload;
662
676
  tabs.forEach((tab) => {
663
- if (draft.transactionCategorizationView[tab].fetchState ===
664
- 'In-Progress' ||
665
- refreshViewInBackground !== true) {
677
+ const tabFetchState = draft.transactionCategorizationView[tab].fetchState;
678
+ if (refreshViewInBackground !== true) {
679
+ // Foreground fetch completed: mark other tabs as Completed.
666
680
  draft.transactionCategorizationView[tab].fetchState = 'Completed';
667
681
  draft.transactionCategorizationView[tab].error = undefined;
668
682
  }
669
- else {
683
+ else if (tabFetchState !== 'In-Progress') {
684
+ // Background fetch completed: reset the other tab's refreshStatus,
685
+ // but only when it is not mid-load. During a dual-search the active
686
+ // tab has its own foreground fetch in flight — clobbering its
687
+ // fetchState here would drop the skeleton and show an empty list.
670
688
  draft.transactionCategorizationView[tab].refreshStatus = {
671
689
  fetchState: 'Not-Started',
672
690
  error: undefined,
673
691
  };
674
692
  }
693
+ // If tabFetchState === 'In-Progress' and refreshViewInBackground === true,
694
+ // leave the other tab's state untouched — it has its own active fetch.
675
695
  });
676
696
  },
677
697
  fetchTransactionCategorizationFailure(draft, action) {
@@ -8,7 +8,7 @@ import { getVendorByVendorId } from '../../../entity/vendor/vendorSelector';
8
8
  import { getAllSteps } from '../selectorTypes/expenseAutomationViewSelectorTypes';
9
9
  export function getExpenseAutomationFluxAnalysisView(state) {
10
10
  const { expenseAutomationFluxAnalysisViewState, monthEndCloseChecksState, monthEndCloseChecksViewState, } = state;
11
- const { fetchState, error, refreshStatus, uiState, reviewOperatingExpenseIdsByPeriod, selectedSectionIdsByPeriod, groupsBySelectedPeriod, fluxAnalysisEntities, bulkReviewStatus, currency, totalBalancesAndVariance, } = expenseAutomationFluxAnalysisViewState;
11
+ const { fetchState, error, lastRefreshSuccessTime, refreshStatus, uiState, reviewOperatingExpenseIdsByPeriod, selectedSectionIdsByPeriod, groupsBySelectedPeriod, fluxAnalysisEntities, bulkReviewStatus, currency, totalBalancesAndVariance, } = expenseAutomationFluxAnalysisViewState;
12
12
  const fetchStateAndError = {
13
13
  fetchState,
14
14
  error,
@@ -110,6 +110,7 @@ export function getExpenseAutomationFluxAnalysisView(state) {
110
110
  refreshStatus,
111
111
  currency,
112
112
  completionStatus,
113
+ lastRefreshSuccessTime,
113
114
  };
114
115
  }
115
116
  // Helper function to transform operating expense to vendor view