@zeniai/client-epic-state 5.1.21 → 5.1.23-betaNB1
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.
- package/README.md +2 -1
- package/lib/commonStateTypes/viewAndReport/agingReportStateTypes.d.ts +1 -1
- package/lib/entity/accountRecon/accountReconPayload.d.ts +23 -1
- package/lib/entity/accountRecon/accountReconPayload.js +26 -1
- package/lib/entity/accountRecon/accountReconReducer.d.ts +5 -2
- package/lib/entity/accountRecon/accountReconReducer.js +33 -2
- package/lib/entity/accountRecon/accountReconSelector.d.ts +6 -1
- package/lib/entity/accountRecon/accountReconSelector.js +5 -0
- package/lib/entity/accountRecon/accountReconState.d.ts +22 -0
- package/lib/entity/monthEndCloseChecks/monthEndCloseChecksPayload.d.ts +8 -0
- package/lib/entity/monthEndCloseChecks/monthEndCloseChecksPayload.js +10 -0
- package/lib/entity/monthEndCloseChecks/monthEndCloseChecksState.d.ts +8 -0
- package/lib/epic.d.ts +6 -1
- package/lib/epic.js +6 -1
- package/lib/esm/entity/accountRecon/accountReconPayload.js +26 -1
- package/lib/esm/entity/accountRecon/accountReconReducer.js +32 -1
- package/lib/esm/entity/accountRecon/accountReconSelector.js +5 -0
- package/lib/esm/entity/monthEndCloseChecks/monthEndCloseChecksPayload.js +10 -0
- package/lib/esm/epic.js +6 -1
- package/lib/esm/index.js +7 -7
- package/lib/esm/view/aiCfoView/aiCfoViewReducer.js +29 -3
- package/lib/esm/view/aiCfoView/aiCfoViewSelector.js +11 -0
- package/lib/esm/view/aiCfoView/epics/createSessionAndSubmitEpic.js +2 -2
- package/lib/esm/view/aiCfoView/epics/fetchSkillsEpic.js +34 -0
- package/lib/esm/view/aiCfoView/epics/submitQuestionEpic.js +7 -1
- package/lib/esm/view/expenseAutomationView/epics/accountRecon/parseStatementEpic.js +34 -0
- package/lib/esm/view/expenseAutomationView/epics/accountRecon/reparseStatementEpic.js +31 -0
- package/lib/esm/view/expenseAutomationView/epics/accountRecon/updateStatementInfoEpic.js +57 -0
- package/lib/esm/view/expenseAutomationView/epics/fluxAnalysis/fetchFluxAnalysisViewEpic.js +1 -0
- package/lib/esm/view/expenseAutomationView/payload/reconciliationPayload.js +142 -0
- package/lib/esm/view/expenseAutomationView/reducers/fluxAnalysisViewReducer.js +6 -1
- package/lib/esm/view/expenseAutomationView/reducers/reconciliationViewReducer.js +178 -8
- package/lib/esm/view/expenseAutomationView/selectors/fluxAnalysisViewSelector.js +2 -1
- package/lib/esm/view/expenseAutomationView/selectors/reconciliationViewSelector.js +27 -0
- package/lib/esm/view/expenseAutomationView/types/reconciliationViewState.js +29 -0
- package/lib/esm/view/opExByVendor/opExByVendorReducer.js +28 -5
- package/lib/esm/view/opExByVendor/opExByVendorSelector.js +1 -0
- package/lib/esm/view/opExByVendor/refreshOpExByVendorReportEpic.js +21 -0
- package/lib/index.d.ts +12 -12
- package/lib/index.js +56 -37
- package/lib/view/aiCfoView/aiCfoViewPayload.d.ts +14 -0
- package/lib/view/aiCfoView/aiCfoViewReducer.d.ts +8 -4
- package/lib/view/aiCfoView/aiCfoViewReducer.js +30 -4
- package/lib/view/aiCfoView/aiCfoViewSelector.d.ts +2 -1
- package/lib/view/aiCfoView/aiCfoViewSelector.js +12 -0
- package/lib/view/aiCfoView/aiCfoViewState.d.ts +15 -0
- package/lib/view/aiCfoView/epics/createSessionAndSubmitEpic.js +2 -2
- package/lib/view/aiCfoView/epics/fetchSkillsEpic.d.ts +8 -0
- package/lib/view/aiCfoView/epics/fetchSkillsEpic.js +38 -0
- package/lib/view/aiCfoView/epics/submitQuestionEpic.js +7 -1
- package/lib/view/expenseAutomationView/epics/accountRecon/parseStatementEpic.d.ts +6 -0
- package/lib/view/expenseAutomationView/epics/accountRecon/parseStatementEpic.js +38 -0
- package/lib/view/expenseAutomationView/epics/accountRecon/reparseStatementEpic.d.ts +26 -0
- package/lib/view/expenseAutomationView/epics/accountRecon/reparseStatementEpic.js +35 -0
- package/lib/view/expenseAutomationView/epics/accountRecon/updateStatementInfoEpic.d.ts +27 -0
- package/lib/view/expenseAutomationView/epics/accountRecon/updateStatementInfoEpic.js +61 -0
- package/lib/view/expenseAutomationView/epics/fluxAnalysis/fetchFluxAnalysisViewEpic.js +1 -0
- package/lib/view/expenseAutomationView/payload/fluxAnalysisPayload.d.ts +8 -0
- package/lib/view/expenseAutomationView/payload/reconciliationPayload.d.ts +163 -9
- package/lib/view/expenseAutomationView/payload/reconciliationPayload.js +146 -0
- package/lib/view/expenseAutomationView/reducers/fluxAnalysisViewReducer.d.ts +2 -1
- package/lib/view/expenseAutomationView/reducers/fluxAnalysisViewReducer.js +6 -1
- package/lib/view/expenseAutomationView/reducers/reconciliationViewReducer.d.ts +53 -3
- package/lib/view/expenseAutomationView/reducers/reconciliationViewReducer.js +180 -9
- package/lib/view/expenseAutomationView/selectorTypes/reconciliationViewSelectorTypes.d.ts +7 -1
- package/lib/view/expenseAutomationView/selectors/fluxAnalysisViewSelector.js +2 -1
- package/lib/view/expenseAutomationView/selectors/reconciliationViewSelector.d.ts +2 -0
- package/lib/view/expenseAutomationView/selectors/reconciliationViewSelector.js +29 -1
- package/lib/view/expenseAutomationView/types/fluxAnalysisViewState.d.ts +2 -0
- package/lib/view/expenseAutomationView/types/reconciliationViewState.d.ts +172 -0
- package/lib/view/expenseAutomationView/types/reconciliationViewState.js +30 -0
- package/lib/view/opExByVendor/opExByVendorReducer.d.ts +6 -1
- package/lib/view/opExByVendor/opExByVendorReducer.js +29 -6
- package/lib/view/opExByVendor/opExByVendorSelector.d.ts +1 -0
- package/lib/view/opExByVendor/opExByVendorSelector.js +1 -0
- package/lib/view/opExByVendor/opExByVendorState.d.ts +1 -0
- package/lib/view/opExByVendor/refreshOpExByVendorReportEpic.d.ts +17 -0
- package/lib/view/opExByVendor/refreshOpExByVendorReportEpic.js +25 -0
- package/lib/view/vendorFiling1099/vendorFiling1099List/vendorFiling1099ListState.d.ts +1 -1
- package/package.json +4 -2
|
@@ -47,6 +47,7 @@ export const initialAiCfoViewState = {
|
|
|
47
47
|
responseStateByChatSessionId: {},
|
|
48
48
|
stopSubmitStateByChatSessionId: {},
|
|
49
49
|
suggestedQuestionsByPageContext: {},
|
|
50
|
+
skills: { fetchState: 'Not-Started', error: undefined, skills: [] },
|
|
50
51
|
};
|
|
51
52
|
const aiCfoView = createSlice({
|
|
52
53
|
name: 'aiCfoView',
|
|
@@ -62,7 +63,7 @@ const aiCfoView = createSlice({
|
|
|
62
63
|
answer: { fetchState: 'In-Progress', error: undefined },
|
|
63
64
|
};
|
|
64
65
|
},
|
|
65
|
-
prepare(sessionId, questionAnswerId, userId, questionId, createdAt, question, files) {
|
|
66
|
+
prepare(sessionId, questionAnswerId, userId, questionId, createdAt, question, files, macroId) {
|
|
66
67
|
return {
|
|
67
68
|
payload: {
|
|
68
69
|
sessionId,
|
|
@@ -72,6 +73,7 @@ const aiCfoView = createSlice({
|
|
|
72
73
|
createdAt,
|
|
73
74
|
question,
|
|
74
75
|
files,
|
|
76
|
+
macroId,
|
|
75
77
|
},
|
|
76
78
|
};
|
|
77
79
|
},
|
|
@@ -222,7 +224,7 @@ const aiCfoView = createSlice({
|
|
|
222
224
|
draft.createSessionAndSubmitState.fetchState = 'In-Progress';
|
|
223
225
|
draft.createSessionAndSubmitState.error = undefined;
|
|
224
226
|
},
|
|
225
|
-
prepare(agentId, userId, questionAnswerId, questionId, createdAt, question, files, pageContext) {
|
|
227
|
+
prepare(agentId, userId, questionAnswerId, questionId, createdAt, question, files, pageContext, macroId) {
|
|
226
228
|
return {
|
|
227
229
|
payload: {
|
|
228
230
|
agentId,
|
|
@@ -233,6 +235,7 @@ const aiCfoView = createSlice({
|
|
|
233
235
|
question,
|
|
234
236
|
files,
|
|
235
237
|
pageContext,
|
|
238
|
+
macroId,
|
|
236
239
|
},
|
|
237
240
|
};
|
|
238
241
|
},
|
|
@@ -445,6 +448,29 @@ const aiCfoView = createSlice({
|
|
|
445
448
|
error,
|
|
446
449
|
};
|
|
447
450
|
},
|
|
451
|
+
fetchSkills(draft) {
|
|
452
|
+
// Optional-chain: persisted/rehydrated state from before this slice existed
|
|
453
|
+
// may have no `skills`, so don't assume draft.skills is set.
|
|
454
|
+
draft.skills = {
|
|
455
|
+
fetchState: 'In-Progress',
|
|
456
|
+
error: undefined,
|
|
457
|
+
skills: draft.skills?.skills ?? [],
|
|
458
|
+
};
|
|
459
|
+
},
|
|
460
|
+
fetchSkillsSuccess(draft, action) {
|
|
461
|
+
draft.skills = {
|
|
462
|
+
fetchState: 'Completed',
|
|
463
|
+
error: undefined,
|
|
464
|
+
skills: action.payload.skills,
|
|
465
|
+
};
|
|
466
|
+
},
|
|
467
|
+
fetchSkillsFailure(draft, action) {
|
|
468
|
+
draft.skills = {
|
|
469
|
+
fetchState: 'Error',
|
|
470
|
+
error: action.payload,
|
|
471
|
+
skills: draft.skills?.skills ?? [],
|
|
472
|
+
};
|
|
473
|
+
},
|
|
448
474
|
clearAiCfoView(draft) {
|
|
449
475
|
Object.assign(draft, initialAiCfoViewState);
|
|
450
476
|
},
|
|
@@ -455,5 +481,5 @@ const aiCfoView = createSlice({
|
|
|
455
481
|
},
|
|
456
482
|
},
|
|
457
483
|
});
|
|
458
|
-
export const { submitQuestion, submitQuestionSuccess, submitQuestionFailure, updateResponseState, updateAiCfoViewScrollPosition, updateCurrentInput, clearInput, setSession, createSession, createSessionSuccess, createSessionFailure, createSessionAndSubmit, initializeNewSessionState, createSessionAndSubmitSuccess, createSessionAndSubmitFailure, fetchChatSessionsForUser, fetchChatSessionsForUserSuccess, fetchChatSessionsForUserFailure, fetchChatHistory, fetchChatHistorySuccess, fetchChatHistoryFailure, clearCurrentSessionId, clearLastContextMessage, stopSubmit, stopSubmitSuccess, stopSubmitFailure, stopSubmitQuestion, stopSubmitQuestionSuccess, stopSubmitQuestionFailure, acceptMasterTOS, acceptMasterTOSSuccess, acceptMasterTOSFailure, deleteChatSession, deleteChatSessionSuccess, deleteChatSessionFailure, clearDeleteChatSessionStatus, clearAiCfoView, updateCotCollapsedState, fetchSuggestedQuestions, fetchSuggestedQuestionsSuccess, fetchSuggestedQuestionsFailure, applyAiCfoSidePanelHostPageTransition, clearAiCfoSidePanelHostPageContext, } = aiCfoView.actions;
|
|
484
|
+
export const { submitQuestion, submitQuestionSuccess, submitQuestionFailure, updateResponseState, updateAiCfoViewScrollPosition, updateCurrentInput, clearInput, setSession, createSession, createSessionSuccess, createSessionFailure, createSessionAndSubmit, initializeNewSessionState, createSessionAndSubmitSuccess, createSessionAndSubmitFailure, fetchChatSessionsForUser, fetchChatSessionsForUserSuccess, fetchChatSessionsForUserFailure, fetchChatHistory, fetchChatHistorySuccess, fetchChatHistoryFailure, clearCurrentSessionId, clearLastContextMessage, stopSubmit, stopSubmitSuccess, stopSubmitFailure, stopSubmitQuestion, stopSubmitQuestionSuccess, stopSubmitQuestionFailure, acceptMasterTOS, acceptMasterTOSSuccess, acceptMasterTOSFailure, deleteChatSession, deleteChatSessionSuccess, deleteChatSessionFailure, clearDeleteChatSessionStatus, clearAiCfoView, updateCotCollapsedState, fetchSuggestedQuestions, fetchSuggestedQuestionsSuccess, fetchSuggestedQuestionsFailure, fetchSkills, fetchSkillsSuccess, fetchSkillsFailure, applyAiCfoSidePanelHostPageTransition, clearAiCfoSidePanelHostPageContext, } = aiCfoView.actions;
|
|
459
485
|
export default aiCfoView.reducer;
|
|
@@ -22,3 +22,14 @@ export const getAiCfoView = (state) => {
|
|
|
22
22
|
export function getSuggestedQuestionsForPageContext(state, pageContext) {
|
|
23
23
|
return state.aiCfoViewState?.suggestedQuestionsByPageContext?.[pageContext];
|
|
24
24
|
}
|
|
25
|
+
// Stable fallback so getSkills returns a referentially-stable object when skills
|
|
26
|
+
// state is absent — a fresh literal each call would defeat useSelector identity
|
|
27
|
+
// checks and cause needless re-renders.
|
|
28
|
+
const EMPTY_SKILLS_STATE = {
|
|
29
|
+
fetchState: 'Not-Started',
|
|
30
|
+
error: undefined,
|
|
31
|
+
skills: [],
|
|
32
|
+
};
|
|
33
|
+
export function getSkills(state) {
|
|
34
|
+
return state.aiCfoViewState?.skills ?? EMPTY_SKILLS_STATE;
|
|
35
|
+
}
|
|
@@ -66,7 +66,7 @@ function waitForChannelSubscription(pusherInstance, channelName, timeoutMs = 500
|
|
|
66
66
|
}));
|
|
67
67
|
}
|
|
68
68
|
export const createSessionAndSubmitEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(createSessionAndSubmit.match), switchMap((action) => {
|
|
69
|
-
const { agentId, userId, questionAnswerId, questionId, createdAt, question, files, pageContext, } = action.payload;
|
|
69
|
+
const { agentId, userId, questionAnswerId, questionId, createdAt, question, files, pageContext, macroId, } = action.payload;
|
|
70
70
|
const payload = {
|
|
71
71
|
agent_id: agentId,
|
|
72
72
|
...(pageContext != null && { page_context: pageContext }),
|
|
@@ -113,7 +113,7 @@ export const createSessionAndSubmitEpic = (actions$, _state$, zeniAPI) => action
|
|
|
113
113
|
// This allows fetchChatHistory to run after the question is submitted
|
|
114
114
|
of(createSessionAndSubmitSuccess(),
|
|
115
115
|
// Submit the question after handlers are bound
|
|
116
|
-
submitQuestion(sessionId, questionAnswerId, userId, questionId, createdAt, question, files),
|
|
116
|
+
submitQuestion(sessionId, questionAnswerId, userId, questionId, createdAt, question, files, macroId),
|
|
117
117
|
// Add question payload optimistically after submitQuestion (matches original order)
|
|
118
118
|
// This is idempotent - won't create duplicates if called multiple times
|
|
119
119
|
// submitQuestionEpic will find the question already exists and only update the response
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { of } from 'rxjs';
|
|
2
|
+
import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
|
|
3
|
+
import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
|
|
4
|
+
import { fetchSkills, fetchSkillsFailure, fetchSkillsSuccess, } from '../aiCfoViewReducer';
|
|
5
|
+
const toSkill = (s) => ({
|
|
6
|
+
macroId: s.macro_id,
|
|
7
|
+
name: s.name,
|
|
8
|
+
displayName: s.display_name,
|
|
9
|
+
description: s.description,
|
|
10
|
+
semver: s.semver,
|
|
11
|
+
parameterHints: s.parameter_hints,
|
|
12
|
+
hasScripts: s.has_scripts,
|
|
13
|
+
});
|
|
14
|
+
/** GET {aiCfoMicroServiceBaseUrl}/1.0/skills — chat-visible skills for the `/` menu. */
|
|
15
|
+
export const fetchSkillsEpic = (actions$,
|
|
16
|
+
// state$ intentionally unused: the `/` menu dispatches this once per slash-entry
|
|
17
|
+
// (the composer dedupes), so we always re-fetch the (small, singleton) list.
|
|
18
|
+
_state$, zeniAPI) => actions$.pipe(filter(fetchSkills.match),
|
|
19
|
+
// switchMap: a singleton list endpoint with no key — cancel any in-flight
|
|
20
|
+
// fetch so the latest dispatch wins (no racing writes to `skills`).
|
|
21
|
+
switchMap(() => {
|
|
22
|
+
// aiCfoMicroServiceBaseUrl already targets the chat service (it serves the
|
|
23
|
+
// /1.0/agents/{id} submit route too); /1.0/skills lives on the same service.
|
|
24
|
+
const url = `${zeniAPI.apiEndPoints.aiCfoMicroServiceBaseUrl}/1.0/skills`;
|
|
25
|
+
return zeniAPI.getJSON(url).pipe(mergeMap((response) => {
|
|
26
|
+
if (isSuccessResponse(response) && response.data != null) {
|
|
27
|
+
// Guard the array: a malformed 2xx body without `skills` must not crash .map.
|
|
28
|
+
return of(fetchSkillsSuccess({
|
|
29
|
+
skills: (response.data.skills ?? []).map(toSkill),
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
return of(fetchSkillsFailure(response.status));
|
|
33
|
+
}), catchError((error) => of(fetchSkillsFailure(createZeniAPIStatus('Unexpected Error', 'Fetch skills REST API call failed', error instanceof Error ? { message: error.message } : undefined)))));
|
|
34
|
+
}));
|
|
@@ -4,7 +4,7 @@ import { upsertOrAddQuestionAnswerPayload } from '../../../entity/aiCfo/aiCfoRed
|
|
|
4
4
|
import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
|
|
5
5
|
import { submitQuestion, submitQuestionFailure, submitQuestionSuccess, } from '../aiCfoViewReducer';
|
|
6
6
|
export const submitQuestionEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(submitQuestion.match), mergeMap((action) => {
|
|
7
|
-
const { questionAnswerId, userId, sessionId, questionId, createdAt, question, files, } = action.payload;
|
|
7
|
+
const { questionAnswerId, userId, sessionId, questionId, createdAt, question, files, macroId, } = action.payload;
|
|
8
8
|
const { aiCfoViewState } = state$.value;
|
|
9
9
|
const { uiState } = aiCfoViewState;
|
|
10
10
|
const agentId = uiState.agentId;
|
|
@@ -17,15 +17,21 @@ export const submitQuestionEpic = (actions$, state$, zeniAPI) => actions$.pipe(f
|
|
|
17
17
|
const formData = new FormData();
|
|
18
18
|
formData.append('chat_session_id', sessionId);
|
|
19
19
|
formData.append('message', question);
|
|
20
|
+
if (macroId != null) {
|
|
21
|
+
formData.append('macro_id', macroId);
|
|
22
|
+
}
|
|
20
23
|
for (const file of files) {
|
|
21
24
|
formData.append('files', file, file.name);
|
|
22
25
|
}
|
|
23
26
|
apiCall = zeniAPI.postFormData(endpoint, formData);
|
|
24
27
|
}
|
|
25
28
|
else {
|
|
29
|
+
// `message` is the display token (e.g. "/flux"); `macro_id` tells the chat
|
|
30
|
+
// service to expand the skill's instructions as the actual agent prompt.
|
|
26
31
|
apiCall = zeniAPI.postAndGetJSON(endpoint, {
|
|
27
32
|
chat_session_id: sessionId,
|
|
28
33
|
message: question,
|
|
34
|
+
...(macroId != null && { macro_id: macroId }),
|
|
29
35
|
});
|
|
30
36
|
}
|
|
31
37
|
return apiCall.pipe(mergeMap((response) => {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { from, of } from 'rxjs';
|
|
2
|
+
import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
|
|
3
|
+
import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
|
|
4
|
+
import { transformParseStatementPayloadToState, } from '../../payload/reconciliationPayload';
|
|
5
|
+
import { parseStatement, parseStatementFailure, parseStatementSuccess, updateParsedStatementData, } from '../../reducers/reconciliationViewReducer';
|
|
6
|
+
export const parseStatementEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(parseStatement.match), switchMap((action) => {
|
|
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) => {
|
|
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
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
}), catchError((error) => of(parseStatementFailure({
|
|
29
|
+
accountId,
|
|
30
|
+
selectedPeriod,
|
|
31
|
+
error: createZeniAPIStatus('Unexpected Error', 'Parse Statement REST API call errored out ' +
|
|
32
|
+
(error instanceof Error ? error.message : String(error))),
|
|
33
|
+
}))));
|
|
34
|
+
}));
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { from, of } from 'rxjs';
|
|
2
|
+
import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
|
|
3
|
+
import { updateAccountReconReparseStatement } from '../../../../entity/accountRecon/accountReconReducer';
|
|
4
|
+
import { toAccountReconKey } from '../../../../entity/accountRecon/accountReconState';
|
|
5
|
+
import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
|
|
6
|
+
import { reparseStatement, reparseStatementFailure, reparseStatementSuccess, } from '../../reducers/reconciliationViewReducer';
|
|
7
|
+
export const reparseStatementEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(reparseStatement.match), switchMap((action) => {
|
|
8
|
+
const { accountId, selectedPeriod, statementUploadId } = action.payload;
|
|
9
|
+
const reparseStatementApi$ = zeniAPI.postAndGetJSON(`${zeniAPI.apiEndPoints.reconciliationMicroServiceBaseUrl}/1.0/reconciliation/statements/${statementUploadId}/reparse`);
|
|
10
|
+
return reparseStatementApi$.pipe(mergeMap((response) => {
|
|
11
|
+
if (isSuccessResponse(response) === true && response.data != null) {
|
|
12
|
+
const accountReconKey = toAccountReconKey(accountId, selectedPeriod);
|
|
13
|
+
return from([
|
|
14
|
+
updateAccountReconReparseStatement(accountReconKey, response.data),
|
|
15
|
+
reparseStatementSuccess({ accountId, selectedPeriod }),
|
|
16
|
+
]);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
return of(reparseStatementFailure({
|
|
20
|
+
accountId,
|
|
21
|
+
selectedPeriod,
|
|
22
|
+
error: response.status,
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
}), catchError((error) => of(reparseStatementFailure({
|
|
26
|
+
accountId,
|
|
27
|
+
selectedPeriod,
|
|
28
|
+
error: createZeniAPIStatus('Unexpected Error', 'Reparse Statement REST API call errored out ' +
|
|
29
|
+
(error instanceof Error ? error.message : String(error))),
|
|
30
|
+
}))));
|
|
31
|
+
}));
|
|
@@ -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 { transformStatementDateConflictToState, transformStatementUpdateStateToPayload, 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 payload = transformStatementUpdateStateToPayload(localData);
|
|
29
|
+
const updateStatementApi$ = zeniAPI.putAndGetJSON(`${zeniAPI.apiEndPoints.reconciliationMicroServiceBaseUrl}/2.0/statement-uploads/${statementUploadId}`, payload);
|
|
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
|
+
// A 409 chain-validation conflict carries the blocking reconciled
|
|
43
|
+
// period at the top level so the UI can prompt a re-upload.
|
|
44
|
+
const dateConflict = transformStatementDateConflictToState(response);
|
|
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,145 @@ function createReviewTransactionPayload(accountReconciliationEntity) {
|
|
|
67
67
|
});
|
|
68
68
|
return reviewTransactionsPayload;
|
|
69
69
|
}
|
|
70
|
+
function transformStatementUploadPayloadToState(statementUploadPayload) {
|
|
71
|
+
return {
|
|
72
|
+
account: {
|
|
73
|
+
accountId: statementUploadPayload.account.account_id,
|
|
74
|
+
accountName: statementUploadPayload.account.account_name,
|
|
75
|
+
accountType: statementUploadPayload.account.account_type,
|
|
76
|
+
currencyCode: statementUploadPayload.account.currency_code,
|
|
77
|
+
last4Digits: statementUploadPayload.account.last_4_digits,
|
|
78
|
+
},
|
|
79
|
+
file: statementUploadPayload.file != null
|
|
80
|
+
? {
|
|
81
|
+
fileId: statementUploadPayload.file.file_id,
|
|
82
|
+
fileName: statementUploadPayload.file.file_name,
|
|
83
|
+
signedUrl: statementUploadPayload.file.signed_url,
|
|
84
|
+
}
|
|
85
|
+
: null,
|
|
86
|
+
statementMeta: {
|
|
87
|
+
closingBalance: statementUploadPayload.statement_meta.closing_balance,
|
|
88
|
+
openingBalance: statementUploadPayload.statement_meta.opening_balance,
|
|
89
|
+
statementDataStatus: statementUploadPayload.statement_meta.statement_data_status,
|
|
90
|
+
statementEndDate: statementUploadPayload.statement_meta.statement_end_date,
|
|
91
|
+
statementStartDate: statementUploadPayload.statement_meta.statement_start_date,
|
|
92
|
+
statementStatus: statementUploadPayload.statement_meta.statement_status,
|
|
93
|
+
statementUploadId: statementUploadPayload.statement_meta.statement_upload_id,
|
|
94
|
+
totalDeposits: statementUploadPayload.statement_meta.total_deposits,
|
|
95
|
+
totalPayments: statementUploadPayload.statement_meta.total_payments,
|
|
96
|
+
},
|
|
97
|
+
statementTransactions: (statementUploadPayload.statement_transactions ?? []).map((txn) => ({
|
|
98
|
+
amount: txn.amount,
|
|
99
|
+
citations: (txn.citation ?? []).map((c) => ({
|
|
100
|
+
page: c.page,
|
|
101
|
+
pageHeight: c.page_height,
|
|
102
|
+
pageWidth: c.page_width,
|
|
103
|
+
polygon: (c.polygon ?? []).map((point) => ({
|
|
104
|
+
x: point.x,
|
|
105
|
+
y: point.y,
|
|
106
|
+
})),
|
|
107
|
+
referenceText: c.reference_text,
|
|
108
|
+
})),
|
|
109
|
+
isUserAdded: txn.is_user_added,
|
|
110
|
+
isUserEdited: txn.is_user_edited,
|
|
111
|
+
statementTransactionId: txn.statement_transaction_id,
|
|
112
|
+
transactionDate: txn.transaction_date,
|
|
113
|
+
transactionDirection: txn.transaction_direction,
|
|
114
|
+
transactionMemo: txn.transaction_memo,
|
|
115
|
+
})),
|
|
116
|
+
aiSummary: transformAiSummaryPayloadToState(statementUploadPayload.ai_summary),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function transformPreviousReconciliationInfoToState(payload) {
|
|
120
|
+
if (payload == null) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
endDate: payload.end_date,
|
|
125
|
+
reconciledAt: payload.reconciled_at,
|
|
126
|
+
reconciledByUserId: payload.reconciled_by_user_id,
|
|
127
|
+
reconciliationId: payload.reconciliation_id,
|
|
128
|
+
startDate: payload.start_date,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function transformAiSummaryPayloadToState(aiSummaryPayload) {
|
|
132
|
+
if (aiSummaryPayload == null) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const { chain_status: chainStatus } = aiSummaryPayload;
|
|
136
|
+
return {
|
|
137
|
+
accountIdentified: aiSummaryPayload.account_identified,
|
|
138
|
+
transactionsExtracted: aiSummaryPayload.transactions_extracted,
|
|
139
|
+
exceptionsDetected: aiSummaryPayload.exceptions_detected,
|
|
140
|
+
fieldsCaptured: (aiSummaryPayload.fields_captured ?? []).map((field) => ({
|
|
141
|
+
code: field.code,
|
|
142
|
+
label: field.label,
|
|
143
|
+
})),
|
|
144
|
+
chainStatus: {
|
|
145
|
+
code: chainStatus?.code ?? '',
|
|
146
|
+
label: chainStatus?.label ?? '',
|
|
147
|
+
expectedStartDate: chainStatus?.expected_start_date ?? null,
|
|
148
|
+
previousReconciliationInfo: transformPreviousReconciliationInfoToState(chainStatus?.previous_reconciliation_info),
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Builds the date-conflict state from a 409 Confirm & Save response. Returns
|
|
154
|
+
* null unless the response is a 409 chain-validation conflict that carries an
|
|
155
|
+
* error code and a previous reconciliation period to render. Any other error
|
|
156
|
+
* status (4xx/5xx) is treated as a generic failure, not a date conflict.
|
|
157
|
+
*/
|
|
158
|
+
export function transformStatementDateConflictToState(response) {
|
|
159
|
+
const isConflictStatus = String(response.status?.code) === '409';
|
|
160
|
+
const previousReconciliationInfo = transformPreviousReconciliationInfoToState(response.previous_reconciliation_info);
|
|
161
|
+
if (isConflictStatus === false ||
|
|
162
|
+
response.error_code == null ||
|
|
163
|
+
previousReconciliationInfo == null) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
errorCode: response.error_code,
|
|
168
|
+
expectedStartDate: response.expected_start_date ?? null,
|
|
169
|
+
previousReconciliationInfo,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
export function transformParseStatementPayloadToState(payload) {
|
|
173
|
+
return {
|
|
174
|
+
statementUpload: transformStatementUploadPayloadToState(payload.statement_upload),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
export function transformUpdateStatementInfoPayloadToState(payload) {
|
|
178
|
+
return {
|
|
179
|
+
statementUpload: transformStatementUploadPayloadToState(payload.statement_upload),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
export function transformStatementUpdateStateToPayload(localData) {
|
|
183
|
+
const { statementMeta, statementTransactions } = localData;
|
|
184
|
+
return {
|
|
185
|
+
statement_meta: {
|
|
186
|
+
opening_balance: statementMeta.openingBalance,
|
|
187
|
+
statement_end_date: statementMeta.statementEndDate,
|
|
188
|
+
statement_start_date: statementMeta.statementStartDate,
|
|
189
|
+
total_deposits: statementMeta.totalDeposits,
|
|
190
|
+
total_payments: statementMeta.totalPayments,
|
|
191
|
+
},
|
|
192
|
+
statement_transactions: {
|
|
193
|
+
added: statementTransactions.added.map((txn) => ({
|
|
194
|
+
amount: txn.amount,
|
|
195
|
+
transaction_date: txn.transactionDate,
|
|
196
|
+
transaction_direction: txn.transactionDirection,
|
|
197
|
+
transaction_memo: txn.transactionMemo,
|
|
198
|
+
})),
|
|
199
|
+
deleted_ids: statementTransactions.deletedIds,
|
|
200
|
+
updated: statementTransactions.updated
|
|
201
|
+
.filter((txn) => txn.statementTransactionId != null)
|
|
202
|
+
.map((txn) => ({
|
|
203
|
+
amount: txn.amount,
|
|
204
|
+
statement_transaction_id: txn.statementTransactionId,
|
|
205
|
+
transaction_date: txn.transactionDate,
|
|
206
|
+
transaction_direction: txn.transactionDirection,
|
|
207
|
+
transaction_memo: txn.transactionMemo,
|
|
208
|
+
})),
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
|
@@ -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
|