@zeniai/client-epic-state 5.2.5 → 5.2.7-beta0ND
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/lib/epic.d.ts +2 -1
- package/lib/epic.js +4 -4
- package/lib/esm/epic.js +4 -4
- package/lib/esm/index.js +4 -3
- package/lib/esm/view/accountList/accountListReducer.js +1 -0
- package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +8 -0
- package/lib/esm/view/invoicing/invoicingQboView/epics/fetchInvoicingAccountingPickersEpic.js +29 -0
- package/lib/esm/view/invoicing/invoicingQboView/invoicingQboViewPayload.js +0 -23
- package/lib/esm/view/invoicing/invoicingQboView/invoicingQboViewReducer.js +3 -40
- package/lib/esm/view/invoicing/invoicingQboView/invoicingQboViewSelector.js +124 -6
- package/lib/esm/view/recommendation/recommendationReducer.js +16 -1
- package/lib/esm/view/recommendation/submitRecommendationFeedbackEpic.js +52 -0
- package/lib/index.d.ts +7 -4
- package/lib/index.js +29 -26
- package/lib/view/accountList/accountListReducer.js +1 -0
- package/lib/view/accountList/accountListState.d.ts +1 -1
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.d.ts +9 -0
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +8 -0
- package/lib/view/invoicing/invoicingQboView/epics/fetchInvoicingAccountingPickersEpic.d.ts +5 -0
- package/lib/view/invoicing/invoicingQboView/epics/fetchInvoicingAccountingPickersEpic.js +33 -0
- package/lib/view/invoicing/invoicingQboView/epics/invoicingQboViewActionType.d.ts +4 -2
- package/lib/view/invoicing/invoicingQboView/invoicingQboViewPayload.d.ts +0 -26
- package/lib/view/invoicing/invoicingQboView/invoicingQboViewPayload.js +0 -24
- package/lib/view/invoicing/invoicingQboView/invoicingQboViewReducer.d.ts +2 -10
- package/lib/view/invoicing/invoicingQboView/invoicingQboViewReducer.js +4 -41
- package/lib/view/invoicing/invoicingQboView/invoicingQboViewSelector.d.ts +43 -6
- package/lib/view/invoicing/invoicingQboView/invoicingQboViewSelector.js +128 -7
- package/lib/view/invoicing/invoicingQboView/invoicingQboViewState.d.ts +1 -5
- package/lib/view/recommendation/recommendationPayload.d.ts +34 -0
- package/lib/view/recommendation/recommendationReducer.d.ts +2 -1
- package/lib/view/recommendation/recommendationReducer.js +17 -2
- package/lib/view/recommendation/submitRecommendationFeedbackEpic.d.ts +7 -0
- package/lib/view/recommendation/submitRecommendationFeedbackEpic.js +56 -0
- package/package.json +1 -1
- package/lib/esm/view/invoicing/invoicingQboView/epics/fetchInvoicingQboAccountsEpic.js +0 -25
- package/lib/esm/view/invoicing/invoicingQboView/epics/fetchInvoicingQboClassesEpic.js +0 -15
- package/lib/view/invoicing/invoicingQboView/epics/fetchInvoicingQboAccountsEpic.d.ts +0 -6
- package/lib/view/invoicing/invoicingQboView/epics/fetchInvoicingQboAccountsEpic.js +0 -29
- package/lib/view/invoicing/invoicingQboView/epics/fetchInvoicingQboClassesEpic.d.ts +0 -6
- package/lib/view/invoicing/invoicingQboView/epics/fetchInvoicingQboClassesEpic.js +0 -19
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var _a;
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.startInvoicingQboBackfillFailure = exports.startInvoicingQboBackfillSuccess = exports.startInvoicingQboBackfill = exports.retryInvoicingQboSyncTaskFailure = exports.retryInvoicingQboSyncTaskSuccess = exports.retryInvoicingQboSyncTask = exports.updateInvoicingQboSyncHealthFailure = exports.updateInvoicingQboSyncHealth = exports.fetchInvoicingQboSyncHealth = exports.
|
|
4
|
+
exports.startInvoicingQboBackfillFailure = exports.startInvoicingQboBackfillSuccess = exports.startInvoicingQboBackfill = exports.retryInvoicingQboSyncTaskFailure = exports.retryInvoicingQboSyncTaskSuccess = exports.retryInvoicingQboSyncTask = exports.updateInvoicingQboSyncHealthFailure = exports.updateInvoicingQboSyncHealth = exports.fetchInvoicingQboSyncHealth = exports.fetchInvoicingAccountingPickers = exports.updateInvoicingQboConnectionFailure = exports.updateInvoicingQboConnection = exports.fetchInvoicingQboConnection = exports.initialState = void 0;
|
|
5
5
|
const toolkit_1 = require("@reduxjs/toolkit");
|
|
6
6
|
const rootActions_1 = require("../../../rootActions");
|
|
7
7
|
exports.initialState = {
|
|
8
|
-
accountsByAccountTypes: {},
|
|
9
|
-
accountsFetchStateByAccountTypes: {},
|
|
10
8
|
backfillFetchState: { fetchState: 'Not-Started', error: undefined },
|
|
11
|
-
classes: [],
|
|
12
|
-
classesFetchState: { fetchState: 'Not-Started', error: undefined },
|
|
13
9
|
connection: undefined,
|
|
14
10
|
connectionFetchState: { fetchState: 'Not-Started', error: undefined },
|
|
15
11
|
retryFetchStateByTaskId: {},
|
|
@@ -33,41 +29,8 @@ const invoicingQboView = (0, toolkit_1.createSlice)({
|
|
|
33
29
|
updateInvoicingQboConnectionFailure(draft, action) {
|
|
34
30
|
draft.connectionFetchState = { fetchState: 'Error', error: action.payload };
|
|
35
31
|
},
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
draft.accountsFetchStateByAccountTypes[action.payload.accountTypes] = {
|
|
39
|
-
fetchState: 'In-Progress',
|
|
40
|
-
error: undefined,
|
|
41
|
-
};
|
|
42
|
-
},
|
|
43
|
-
prepare(accountTypes) {
|
|
44
|
-
return { payload: { accountTypes } };
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
updateInvoicingQboAccounts(draft, action) {
|
|
48
|
-
draft.accountsByAccountTypes[action.payload.accountTypes] =
|
|
49
|
-
action.payload.accounts;
|
|
50
|
-
draft.accountsFetchStateByAccountTypes[action.payload.accountTypes] = {
|
|
51
|
-
fetchState: 'Completed',
|
|
52
|
-
error: undefined,
|
|
53
|
-
};
|
|
54
|
-
},
|
|
55
|
-
updateInvoicingQboAccountsFailure(draft, action) {
|
|
56
|
-
draft.accountsFetchStateByAccountTypes[action.payload.accountTypes] = {
|
|
57
|
-
fetchState: 'Error',
|
|
58
|
-
error: action.payload.status,
|
|
59
|
-
};
|
|
60
|
-
},
|
|
61
|
-
fetchInvoicingQboClasses(draft) {
|
|
62
|
-
draft.classesFetchState = { fetchState: 'In-Progress', error: undefined };
|
|
63
|
-
},
|
|
64
|
-
updateInvoicingQboClasses(draft, action) {
|
|
65
|
-
draft.classes = action.payload;
|
|
66
|
-
draft.classesFetchState = { fetchState: 'Completed', error: undefined };
|
|
67
|
-
},
|
|
68
|
-
updateInvoicingQboClassesFailure(draft, action) {
|
|
69
|
-
draft.classesFetchState = { fetchState: 'Error', error: action.payload };
|
|
70
|
-
},
|
|
32
|
+
// No state of its own: the epic decides which shared lists to load.
|
|
33
|
+
fetchInvoicingAccountingPickers() { },
|
|
71
34
|
fetchInvoicingQboSyncHealth(draft) {
|
|
72
35
|
draft.syncHealthFetchState = {
|
|
73
36
|
fetchState: 'In-Progress',
|
|
@@ -131,5 +94,5 @@ const invoicingQboView = (0, toolkit_1.createSlice)({
|
|
|
131
94
|
});
|
|
132
95
|
},
|
|
133
96
|
});
|
|
134
|
-
_a = invoicingQboView.actions, exports.fetchInvoicingQboConnection = _a.fetchInvoicingQboConnection, exports.updateInvoicingQboConnection = _a.updateInvoicingQboConnection, exports.updateInvoicingQboConnectionFailure = _a.updateInvoicingQboConnectionFailure, exports.
|
|
97
|
+
_a = invoicingQboView.actions, exports.fetchInvoicingQboConnection = _a.fetchInvoicingQboConnection, exports.updateInvoicingQboConnection = _a.updateInvoicingQboConnection, exports.updateInvoicingQboConnectionFailure = _a.updateInvoicingQboConnectionFailure, exports.fetchInvoicingAccountingPickers = _a.fetchInvoicingAccountingPickers, exports.fetchInvoicingQboSyncHealth = _a.fetchInvoicingQboSyncHealth, exports.updateInvoicingQboSyncHealth = _a.updateInvoicingQboSyncHealth, exports.updateInvoicingQboSyncHealthFailure = _a.updateInvoicingQboSyncHealthFailure, exports.retryInvoicingQboSyncTask = _a.retryInvoicingQboSyncTask, exports.retryInvoicingQboSyncTaskSuccess = _a.retryInvoicingQboSyncTaskSuccess, exports.retryInvoicingQboSyncTaskFailure = _a.retryInvoicingQboSyncTaskFailure, exports.startInvoicingQboBackfill = _a.startInvoicingQboBackfill, exports.startInvoicingQboBackfillSuccess = _a.startInvoicingQboBackfillSuccess, exports.startInvoicingQboBackfillFailure = _a.startInvoicingQboBackfillFailure;
|
|
135
98
|
exports.default = invoicingQboView.reducer;
|
|
@@ -1,21 +1,58 @@
|
|
|
1
|
+
import { NestedAccountHierarchyForReport } from '../../../commonStateTypes/accountView/nestedAccountID';
|
|
2
|
+
import { NestedClassHierarchyForReport } from '../../../commonStateTypes/classesView/nestedClassID';
|
|
1
3
|
import { FetchStateAndError } from '../../../commonStateTypes/common';
|
|
4
|
+
import { AccountType } from '../../../entity/account/accountState';
|
|
2
5
|
import { RootState } from '../../../rootStateTypes';
|
|
3
|
-
import {
|
|
6
|
+
import { InvoicingQboConnection, InvoicingQboSyncHealth } from './invoicingQboViewPayload';
|
|
7
|
+
export declare const INVOICING_ACCOUNT_LIST_KEY: "invoicingAccountList";
|
|
8
|
+
/** Whether QBO is connected, without building the option lists to find out. */
|
|
9
|
+
export declare const isInvoicingQboConnected: (state: RootState) => boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Account types that can back invoicing revenue. Named here so screens ask for
|
|
12
|
+
* "income accounts" rather than repeating the accounting service's vocabulary.
|
|
13
|
+
*/
|
|
14
|
+
export declare const INVOICING_INCOME_ACCOUNT_TYPES: readonly AccountType[];
|
|
15
|
+
/** An account or class as the invoicing pickers render and submit it. */
|
|
16
|
+
export interface InvoicingAccountingOption {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
accountType?: string;
|
|
20
|
+
}
|
|
4
21
|
export interface InvoicingQboView {
|
|
5
|
-
|
|
6
|
-
|
|
22
|
+
accounts: InvoicingAccountingOption[];
|
|
23
|
+
accountsFetchState: FetchStateAndError;
|
|
7
24
|
backfillState: FetchStateAndError;
|
|
8
|
-
classes:
|
|
25
|
+
classes: InvoicingAccountingOption[];
|
|
9
26
|
classesFetchState: FetchStateAndError;
|
|
10
27
|
connectionFetchState: FetchStateAndError;
|
|
28
|
+
/**
|
|
29
|
+
* The account and class trees the grouped pickers render. The flat option
|
|
30
|
+
* lists above stay for callers that only need id and name.
|
|
31
|
+
*/
|
|
32
|
+
nestedAccountHierarchy: NestedAccountHierarchyForReport[];
|
|
33
|
+
nestedClassHierarchy: NestedClassHierarchyForReport[];
|
|
11
34
|
retryFetchStateByTaskId: Record<string, FetchStateAndError>;
|
|
12
35
|
syncHealthFetchState: FetchStateAndError;
|
|
13
36
|
connection?: InvoicingQboConnection;
|
|
14
37
|
syncHealth?: InvoicingQboSyncHealth;
|
|
15
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* The accounts valid for one QBO mapping kind, whose `accountTypes` is a
|
|
41
|
+
* comma-separated list of QuickBooks type names. An unrecognised list yields no
|
|
42
|
+
* accounts rather than every account: offering a bank account where income is
|
|
43
|
+
* required would post a mapping QuickBooks rejects.
|
|
44
|
+
*/
|
|
45
|
+
export declare const invoicingAccountsForQboAccountTypes: (accounts: InvoicingAccountingOption[], accountTypes: string) => InvoicingAccountingOption[];
|
|
46
|
+
/**
|
|
47
|
+
* The account tree narrowed to the given account types. The tree itself carries
|
|
48
|
+
* no account type, so the flat account list supplies that and the matching
|
|
49
|
+
* `qboId`s are kept — which is why callers that only need "income accounts, as a
|
|
50
|
+
* tree" do not have to hold a second flat list to filter with.
|
|
51
|
+
*/
|
|
52
|
+
export declare const invoicingAccountHierarchyForTypes: (state: RootState, accountTypes: readonly AccountType[]) => NestedAccountHierarchyForReport[];
|
|
16
53
|
/**
|
|
17
54
|
* Single bundled view for the QBO integration settings screen — the
|
|
18
|
-
* connection,
|
|
19
|
-
*
|
|
55
|
+
* connection, the account and class options, sync health, and the fetch/action
|
|
56
|
+
* states for each of them.
|
|
20
57
|
*/
|
|
21
58
|
export declare const getInvoicingQboView: (state: RootState) => InvoicingQboView;
|
|
@@ -1,19 +1,140 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getInvoicingQboView = void 0;
|
|
3
|
+
exports.getInvoicingQboView = exports.invoicingAccountHierarchyForTypes = exports.invoicingAccountsForQboAccountTypes = exports.INVOICING_INCOME_ACCOUNT_TYPES = exports.isInvoicingQboConnected = exports.INVOICING_ACCOUNT_LIST_KEY = void 0;
|
|
4
|
+
const accountListSelector_1 = require("../../accountList/accountListSelector");
|
|
5
|
+
const classListSelector_1 = require("../../classList/classListSelector");
|
|
6
|
+
// Its own bucket: invoicing needs the AP/AR exclusions that the vendor and
|
|
7
|
+
// transaction screens sharing `accountList` do not pass.
|
|
8
|
+
exports.INVOICING_ACCOUNT_LIST_KEY = 'invoicingAccountList';
|
|
9
|
+
/** Whether QBO is connected, without building the option lists to find out. */
|
|
10
|
+
const isInvoicingQboConnected = (state) => state.invoicingQboViewState.connection?.connected === true;
|
|
11
|
+
exports.isInvoicingQboConnected = isInvoicingQboConnected;
|
|
12
|
+
/**
|
|
13
|
+
* Account types that can back invoicing revenue. Named here so screens ask for
|
|
14
|
+
* "income accounts" rather than repeating the accounting service's vocabulary.
|
|
15
|
+
*/
|
|
16
|
+
exports.INVOICING_INCOME_ACCOUNT_TYPES = [
|
|
17
|
+
'income',
|
|
18
|
+
'other_income',
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Accounts and classes as the invoicing pickers need them. Both come from the
|
|
22
|
+
* shared accounting-service lists, so whatever another screen already loaded
|
|
23
|
+
* is reused. `qboId` is the submitted value because that is what the invoicing
|
|
24
|
+
* API stores; an account without one cannot be posted, so it is dropped rather
|
|
25
|
+
* than offered.
|
|
26
|
+
*/
|
|
27
|
+
const invoicingAccountingOptions = (state, accountTypes) => {
|
|
28
|
+
const { accounts } = (0, accountListSelector_1.getAccountList)(state.accountState, state.accountListState, exports.INVOICING_ACCOUNT_LIST_KEY);
|
|
29
|
+
return accounts.flatMap((account) => {
|
|
30
|
+
if (account.qboId == null || account.isDeleted === true) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
if (accountTypes != null &&
|
|
34
|
+
(account.accountType == null ||
|
|
35
|
+
!accountTypes.includes(account.accountType))) {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
return [
|
|
39
|
+
{
|
|
40
|
+
id: String(account.qboId),
|
|
41
|
+
name: account.accountName,
|
|
42
|
+
accountType: account.accountType,
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Normalises an account-type name for comparison. The config's mapping kinds
|
|
49
|
+
* name QuickBooks types the way QuickBooks displays them ("Other Current
|
|
50
|
+
* Asset", "Expense"), while the accounting service uses snake_case and
|
|
51
|
+
* sometimes pluralises ("other_current_assets", "expenses"). Comparing the
|
|
52
|
+
* normalised, de-pluralised forms keeps a picker from silently emptying because
|
|
53
|
+
* of a trailing "s".
|
|
54
|
+
*/
|
|
55
|
+
const normalizeAccountTypeName = (value) => {
|
|
56
|
+
const normalized = value
|
|
57
|
+
.trim()
|
|
58
|
+
.toLowerCase()
|
|
59
|
+
.replace(/[^a-z0-9]+/g, '_');
|
|
60
|
+
// Singularise so "Other Current Liability" meets `other_current_liabilities`
|
|
61
|
+
// and "Expense" meets `expenses`. `-ies` needs its own case: stripping the
|
|
62
|
+
// trailing "s" alone would leave "liabilitie", matching nothing.
|
|
63
|
+
return normalized.replace(/ies$/, 'y').replace(/([^s])s$/, '$1');
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* The accounts valid for one QBO mapping kind, whose `accountTypes` is a
|
|
67
|
+
* comma-separated list of QuickBooks type names. An unrecognised list yields no
|
|
68
|
+
* accounts rather than every account: offering a bank account where income is
|
|
69
|
+
* required would post a mapping QuickBooks rejects.
|
|
70
|
+
*/
|
|
71
|
+
const invoicingAccountsForQboAccountTypes = (accounts, accountTypes) => {
|
|
72
|
+
const wanted = new Set(accountTypes
|
|
73
|
+
.split(',')
|
|
74
|
+
.map(normalizeAccountTypeName)
|
|
75
|
+
.filter((accountType) => accountType !== ''));
|
|
76
|
+
if (wanted.size === 0) {
|
|
77
|
+
return accounts;
|
|
78
|
+
}
|
|
79
|
+
return accounts.filter((account) => account.accountType != null &&
|
|
80
|
+
wanted.has(normalizeAccountTypeName(account.accountType)));
|
|
81
|
+
};
|
|
82
|
+
exports.invoicingAccountsForQboAccountTypes = invoicingAccountsForQboAccountTypes;
|
|
83
|
+
/** Classes as the invoicing pickers need them, from the shared class list. */
|
|
84
|
+
const invoicingAccountingClassOptions = (state) => {
|
|
85
|
+
const { classes } = (0, classListSelector_1.getClassList)(state.classState, state.classListState);
|
|
86
|
+
return classes.flatMap((accountingClass) => accountingClass.qboId == null || accountingClass.isDeleted === true
|
|
87
|
+
? []
|
|
88
|
+
: [
|
|
89
|
+
{
|
|
90
|
+
id: String(accountingClass.qboId),
|
|
91
|
+
name: accountingClass.className,
|
|
92
|
+
},
|
|
93
|
+
]);
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* The account tree narrowed to the given account types. The tree itself carries
|
|
97
|
+
* no account type, so the flat account list supplies that and the matching
|
|
98
|
+
* `qboId`s are kept — which is why callers that only need "income accounts, as a
|
|
99
|
+
* tree" do not have to hold a second flat list to filter with.
|
|
100
|
+
*/
|
|
101
|
+
const invoicingAccountHierarchyForTypes = (state, accountTypes) => {
|
|
102
|
+
const allowedQboIds = new Set(invoicingAccountingOptions(state, accountTypes).map((account) => account.id));
|
|
103
|
+
const keepMatching = (nodes) => nodes.flatMap((node) => {
|
|
104
|
+
const children = keepMatching(node.children);
|
|
105
|
+
const isMatch = node.qboId != null && allowedQboIds.has(String(node.qboId));
|
|
106
|
+
// A parent that is not itself a match is still kept when it holds one, so
|
|
107
|
+
// the tree's shape survives the filter.
|
|
108
|
+
if (!isMatch && children.length === 0) {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
return [{ ...node, children }];
|
|
112
|
+
});
|
|
113
|
+
return keepMatching((0, accountListSelector_1.getNestedAccountListHierarchy)(state.accountListState, exports.INVOICING_ACCOUNT_LIST_KEY));
|
|
114
|
+
};
|
|
115
|
+
exports.invoicingAccountHierarchyForTypes = invoicingAccountHierarchyForTypes;
|
|
4
116
|
/**
|
|
5
117
|
* Single bundled view for the QBO integration settings screen — the
|
|
6
|
-
* connection,
|
|
7
|
-
*
|
|
118
|
+
* connection, the account and class options, sync health, and the fetch/action
|
|
119
|
+
* states for each of them.
|
|
8
120
|
*/
|
|
9
121
|
const getInvoicingQboView = (state) => {
|
|
10
122
|
const qboState = state.invoicingQboViewState;
|
|
123
|
+
const accountList = state.accountListState.byReportId[exports.INVOICING_ACCOUNT_LIST_KEY];
|
|
11
124
|
return {
|
|
12
|
-
|
|
13
|
-
|
|
125
|
+
accounts: invoicingAccountingOptions(state),
|
|
126
|
+
accountsFetchState: {
|
|
127
|
+
fetchState: accountList.fetchState,
|
|
128
|
+
error: accountList.error,
|
|
129
|
+
},
|
|
130
|
+
nestedAccountHierarchy: (0, accountListSelector_1.getNestedAccountListHierarchy)(state.accountListState, exports.INVOICING_ACCOUNT_LIST_KEY),
|
|
131
|
+
nestedClassHierarchy: (0, classListSelector_1.getNestedClassListHierarchy)(state.classListState),
|
|
14
132
|
backfillState: qboState.backfillFetchState,
|
|
15
|
-
classes:
|
|
16
|
-
classesFetchState:
|
|
133
|
+
classes: invoicingAccountingClassOptions(state),
|
|
134
|
+
classesFetchState: {
|
|
135
|
+
fetchState: state.classListState.fetchState,
|
|
136
|
+
error: state.classListState.error,
|
|
137
|
+
},
|
|
17
138
|
connection: qboState.connection,
|
|
18
139
|
connectionFetchState: qboState.connectionFetchState,
|
|
19
140
|
retryFetchStateByTaskId: qboState.retryFetchStateByTaskId,
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { FetchStateAndError } from '../../../commonStateTypes/common';
|
|
2
|
-
import {
|
|
2
|
+
import { InvoicingQboConnection, InvoicingQboSyncHealth } from './invoicingQboViewPayload';
|
|
3
3
|
export interface InvoicingQboViewState {
|
|
4
|
-
accountsByAccountTypes: Record<string, InvoicingQboAccountOption[]>;
|
|
5
|
-
accountsFetchStateByAccountTypes: Record<string, FetchStateAndError>;
|
|
6
4
|
backfillFetchState: FetchStateAndError;
|
|
7
|
-
classes: InvoicingQboAccountOption[];
|
|
8
|
-
classesFetchState: FetchStateAndError;
|
|
9
5
|
connectionFetchState: FetchStateAndError;
|
|
10
6
|
retryFetchStateByTaskId: Record<string, FetchStateAndError>;
|
|
11
7
|
syncHealthFetchState: FetchStateAndError;
|
|
@@ -12,4 +12,38 @@ interface FetchRecommendationPayloadWithCOT {
|
|
|
12
12
|
recommendations: RecommendationPayloadWithCOT[];
|
|
13
13
|
}
|
|
14
14
|
export type FetchRecommendationResponseWithCOT = ZeniAPIResponse<FetchRecommendationPayloadWithCOT>;
|
|
15
|
+
export type RecommendationFeedbackDimension = 'vendor' | 'category' | 'class';
|
|
16
|
+
export type RecommendationFeedbackSource = 'primary' | 'alternate' | 'shadow' | 'qbo';
|
|
17
|
+
export type RecommendationFeedbackOutcome = 'accepted' | 'override_ranked' | 'override_outside' | 'retracted';
|
|
18
|
+
export interface RecommendationFeedbackValue {
|
|
19
|
+
name: string;
|
|
20
|
+
id?: string | null;
|
|
21
|
+
integrationId?: string | null;
|
|
22
|
+
}
|
|
23
|
+
export interface ShownRecommendationForFeedback {
|
|
24
|
+
rank: number;
|
|
25
|
+
source: RecommendationFeedbackSource;
|
|
26
|
+
value: RecommendationFeedbackValue;
|
|
27
|
+
confidence?: number | null;
|
|
28
|
+
cot?: string[];
|
|
29
|
+
reasoning?: string | null;
|
|
30
|
+
}
|
|
31
|
+
export interface SubmitRecommendationFeedbackPayload {
|
|
32
|
+
dimension: RecommendationFeedbackDimension;
|
|
33
|
+
outcome: RecommendationFeedbackOutcome;
|
|
34
|
+
selected: RecommendationFeedbackValue & {
|
|
35
|
+
isNewEntity?: boolean;
|
|
36
|
+
};
|
|
37
|
+
transactionId: string;
|
|
38
|
+
features?: Record<string, unknown> | null;
|
|
39
|
+
lineId?: string | null;
|
|
40
|
+
matchedRank?: number | null;
|
|
41
|
+
matchedSource?: RecommendationFeedbackSource | null;
|
|
42
|
+
reasonText?: string | null;
|
|
43
|
+
recommendationSetId?: string | null;
|
|
44
|
+
retractsFeedbackId?: string | null;
|
|
45
|
+
shownRecommendations?: ShownRecommendationForFeedback[];
|
|
46
|
+
skipped?: boolean;
|
|
47
|
+
topPickSource?: RecommendationFeedbackSource | null;
|
|
48
|
+
}
|
|
15
49
|
export {};
|
|
@@ -5,6 +5,7 @@ import { TransactionID } from '../../entity/transaction/stateTypes/transactionID
|
|
|
5
5
|
import { SupportedTransaction } from '../../entity/transaction/transactionState';
|
|
6
6
|
import { ZeniAPIStatus } from '../../responsePayload';
|
|
7
7
|
import { TransactionsTab } from '../expenseAutomationView/types/transactionsViewState';
|
|
8
|
+
import { SubmitRecommendationFeedbackPayload } from './recommendationPayload';
|
|
8
9
|
import { EntityRecommendationState } from './recommendationState';
|
|
9
10
|
export type RecommendationLineUpdateFlowType = 'categorization' | 'detail';
|
|
10
11
|
type FetchEntityRecommendationsForLineUpdateCommonPayload = {
|
|
@@ -57,6 +58,6 @@ export declare const fetchEntityRecommendationsByTransactionId: import("@reduxjs
|
|
|
57
58
|
}, "recommendation/updateRecommendationForEntityFailure">, indicateReadyToInitializeRecommendation: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[entityIdOrName: string, entityType: EntityType], {
|
|
58
59
|
entityIdOrName: string;
|
|
59
60
|
entityType: EntityType;
|
|
60
|
-
}, "recommendation/indicateReadyToInitializeRecommendation", never, never>;
|
|
61
|
+
}, "recommendation/indicateReadyToInitializeRecommendation", never, never>, clearRecommendation: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"recommendation/clearRecommendation">, submitRecommendationFeedback: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[args: SubmitRecommendationFeedbackPayload], SubmitRecommendationFeedbackPayload, "recommendation/submitRecommendationFeedback", never, never>;
|
|
61
62
|
declare const _default: import("redux").Reducer<EntityRecommendationState>;
|
|
62
63
|
export default _default;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var _a;
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.indicateReadyToInitializeRecommendation = exports.updateRecommendationForEntityFailure = exports.updateRecommendationForEntity = exports.fetchRecommendationByEntityId = exports.fetchRecommendationByEntityName = exports.fetchEntityRecommendationsForLineUpdate = exports.fetchEntityRecommendationsByTransactionId = exports.initialState = void 0;
|
|
4
|
+
exports.submitRecommendationFeedback = exports.clearRecommendation = exports.indicateReadyToInitializeRecommendation = exports.updateRecommendationForEntityFailure = exports.updateRecommendationForEntity = exports.fetchRecommendationByEntityId = exports.fetchRecommendationByEntityName = exports.fetchEntityRecommendationsForLineUpdate = exports.fetchEntityRecommendationsByTransactionId = exports.initialState = void 0;
|
|
5
5
|
const toolkit_1 = require("@reduxjs/toolkit");
|
|
6
6
|
const rootActions_1 = require("../../rootActions");
|
|
7
7
|
const recommendationState_1 = require("./recommendationState");
|
|
@@ -135,6 +135,21 @@ const recommendation = (0, toolkit_1.createSlice)({
|
|
|
135
135
|
};
|
|
136
136
|
},
|
|
137
137
|
},
|
|
138
|
+
clearRecommendation(draft) {
|
|
139
|
+
Object.assign(draft, exports.initialState);
|
|
140
|
+
},
|
|
141
|
+
// Fire-and-forget human feedback on a recommendation (accept, ranked
|
|
142
|
+
// override or outside override). Handled by submitRecommendationFeedbackEpic
|
|
143
|
+
// which POSTs to the accounting recommendation-feedback endpoint. No state
|
|
144
|
+
// mutation: the categorization UX must not depend on the feedback round-trip.
|
|
145
|
+
submitRecommendationFeedback: {
|
|
146
|
+
reducer() {
|
|
147
|
+
// Fire-and-forget: no state mutation; the epic performs the POST.
|
|
148
|
+
},
|
|
149
|
+
prepare(args) {
|
|
150
|
+
return { payload: args };
|
|
151
|
+
},
|
|
152
|
+
},
|
|
138
153
|
},
|
|
139
154
|
extraReducers: (builder) => {
|
|
140
155
|
builder.addCase(rootActions_1.clearAll, (draft) => {
|
|
@@ -142,7 +157,7 @@ const recommendation = (0, toolkit_1.createSlice)({
|
|
|
142
157
|
});
|
|
143
158
|
},
|
|
144
159
|
});
|
|
145
|
-
_a = recommendation.actions, exports.fetchEntityRecommendationsByTransactionId = _a.fetchEntityRecommendationsByTransactionId, exports.fetchEntityRecommendationsForLineUpdate = _a.fetchEntityRecommendationsForLineUpdate, exports.fetchRecommendationByEntityName = _a.fetchRecommendationByEntityName, exports.fetchRecommendationByEntityId = _a.fetchRecommendationByEntityId, exports.updateRecommendationForEntity = _a.updateRecommendationForEntity, exports.updateRecommendationForEntityFailure = _a.updateRecommendationForEntityFailure, exports.indicateReadyToInitializeRecommendation = _a.indicateReadyToInitializeRecommendation;
|
|
160
|
+
_a = recommendation.actions, exports.fetchEntityRecommendationsByTransactionId = _a.fetchEntityRecommendationsByTransactionId, exports.fetchEntityRecommendationsForLineUpdate = _a.fetchEntityRecommendationsForLineUpdate, exports.fetchRecommendationByEntityName = _a.fetchRecommendationByEntityName, exports.fetchRecommendationByEntityId = _a.fetchRecommendationByEntityId, exports.updateRecommendationForEntity = _a.updateRecommendationForEntity, exports.updateRecommendationForEntityFailure = _a.updateRecommendationForEntityFailure, exports.indicateReadyToInitializeRecommendation = _a.indicateReadyToInitializeRecommendation, exports.clearRecommendation = _a.clearRecommendation, exports.submitRecommendationFeedback = _a.submitRecommendationFeedback;
|
|
146
161
|
exports.default = recommendation.reducer;
|
|
147
162
|
const doUpdateRecommendationForEntity = (draft, entityId, entityType, payload) => {
|
|
148
163
|
const key = (0, recommendationState_1.getEntityRecommendationKey)(entityType, entityId);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ActionsObservable, StateObservable } from 'redux-observable';
|
|
2
|
+
import { Observable } from 'rxjs';
|
|
3
|
+
import { RootState } from '../../reducer';
|
|
4
|
+
import { ZeniAPI } from '../../zeniAPI';
|
|
5
|
+
import { submitRecommendationFeedback } from './recommendationReducer';
|
|
6
|
+
export type ActionType = ReturnType<typeof submitRecommendationFeedback>;
|
|
7
|
+
export declare const submitRecommendationFeedbackEpic: (actions$: ActionsObservable<ActionType>, _state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<ActionType>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.submitRecommendationFeedbackEpic = void 0;
|
|
4
|
+
const rxjs_1 = require("rxjs");
|
|
5
|
+
const operators_1 = require("rxjs/operators");
|
|
6
|
+
const responsePayload_1 = require("../../responsePayload");
|
|
7
|
+
const recommendationReducer_1 = require("./recommendationReducer");
|
|
8
|
+
const toRequestBody = (payload) => ({
|
|
9
|
+
line_id: payload.lineId ?? null,
|
|
10
|
+
dimension: payload.dimension,
|
|
11
|
+
recommendation_set_id: payload.recommendationSetId ?? null,
|
|
12
|
+
top_pick_source: payload.topPickSource ?? null,
|
|
13
|
+
shown_recommendations: (payload.shownRecommendations ?? []).map((recommendation) => ({
|
|
14
|
+
source: recommendation.source,
|
|
15
|
+
rank: recommendation.rank,
|
|
16
|
+
value: {
|
|
17
|
+
name: recommendation.value.name,
|
|
18
|
+
id: recommendation.value.id ?? null,
|
|
19
|
+
integration_id: recommendation.value.integrationId ?? null,
|
|
20
|
+
},
|
|
21
|
+
confidence: recommendation.confidence ?? null,
|
|
22
|
+
reasoning: recommendation.reasoning ?? null,
|
|
23
|
+
cot: recommendation.cot ?? [],
|
|
24
|
+
})),
|
|
25
|
+
selected: {
|
|
26
|
+
name: payload.selected.name,
|
|
27
|
+
id: payload.selected.id ?? null,
|
|
28
|
+
integration_id: payload.selected.integrationId ?? null,
|
|
29
|
+
is_new_entity: payload.selected.isNewEntity ?? false,
|
|
30
|
+
},
|
|
31
|
+
matched_source: payload.matchedSource ?? null,
|
|
32
|
+
matched_rank: payload.matchedRank ?? null,
|
|
33
|
+
outcome: payload.outcome,
|
|
34
|
+
reason_text: payload.reasonText ?? null,
|
|
35
|
+
skipped: payload.skipped ?? false,
|
|
36
|
+
retracts_feedback_id: payload.retractsFeedbackId ?? null,
|
|
37
|
+
features: payload.features ?? null,
|
|
38
|
+
});
|
|
39
|
+
// Fire-and-forget: the epic never re-emits an action. Feedback delivery is a
|
|
40
|
+
// side-effect that must not disrupt the categorization UX, so both the
|
|
41
|
+
// success and error paths complete silently (errors are logged for debugging).
|
|
42
|
+
const submitRecommendationFeedbackEpic = (actions$, _state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(recommendationReducer_1.submitRecommendationFeedback.match), (0, operators_1.mergeMap)((action) => {
|
|
43
|
+
const { transactionId } = action.payload;
|
|
44
|
+
return zeniAPI
|
|
45
|
+
.postAndGetJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/transactions/${transactionId}/recommendation-feedback`, toRequestBody(action.payload))
|
|
46
|
+
.pipe((0, operators_1.mergeMap)((response) => {
|
|
47
|
+
if (!(0, responsePayload_1.isSuccessStatus)(response)) {
|
|
48
|
+
console.error('submitRecommendationFeedbackEpic: recommendation feedback was not recorded', response.status);
|
|
49
|
+
}
|
|
50
|
+
return rxjs_1.EMPTY;
|
|
51
|
+
}), (0, operators_1.catchError)((error) => {
|
|
52
|
+
console.error('submitRecommendationFeedbackEpic: failed to submit recommendation feedback', error);
|
|
53
|
+
return rxjs_1.EMPTY;
|
|
54
|
+
}));
|
|
55
|
+
}));
|
|
56
|
+
exports.submitRecommendationFeedbackEpic = submitRecommendationFeedbackEpic;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeniai/client-epic-state",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.7-beta0ND",
|
|
4
4
|
"description": "Shared module between Web & Mobile containing required abstractions for state management, async network communication. ",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "lib/esm/index.js",
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { of } from 'rxjs';
|
|
2
|
-
import { catchError, filter, map, mergeMap } from 'rxjs/operators';
|
|
3
|
-
import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
|
|
4
|
-
import { mapInvoicingQboAccountOptions, } from '../invoicingQboViewPayload';
|
|
5
|
-
import { fetchInvoicingQboAccounts, updateInvoicingQboAccounts, updateInvoicingQboAccountsFailure, } from '../invoicingQboViewReducer';
|
|
6
|
-
export const fetchInvoicingQboAccountsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchInvoicingQboAccounts.match), mergeMap((action) => {
|
|
7
|
-
const { accountTypes } = action.payload;
|
|
8
|
-
const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/qbo/accounts?account_types=${encodeURIComponent(accountTypes)}`;
|
|
9
|
-
return zeniAPI.getJSON(apiUrl).pipe(map((response) => {
|
|
10
|
-
if (isSuccessResponse(response)) {
|
|
11
|
-
return updateInvoicingQboAccounts({
|
|
12
|
-
accountTypes,
|
|
13
|
-
accounts: mapInvoicingQboAccountOptions(response.data?.accounts ?? []),
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
return updateInvoicingQboAccountsFailure({
|
|
17
|
-
accountTypes,
|
|
18
|
-
status: response.status,
|
|
19
|
-
});
|
|
20
|
-
}), catchError((error) => of(updateInvoicingQboAccountsFailure({
|
|
21
|
-
accountTypes,
|
|
22
|
-
status: createZeniAPIStatus('Unexpected Error', 'Fetch QBO accounts REST API call errored out ' +
|
|
23
|
-
JSON.stringify(error)),
|
|
24
|
-
}))));
|
|
25
|
-
}));
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { of } from 'rxjs';
|
|
2
|
-
import { catchError, filter, map, switchMap } from 'rxjs/operators';
|
|
3
|
-
import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
|
|
4
|
-
import { mapInvoicingQboAccountOptions, } from '../invoicingQboViewPayload';
|
|
5
|
-
import { fetchInvoicingQboClasses, updateInvoicingQboClasses, updateInvoicingQboClassesFailure, } from '../invoicingQboViewReducer';
|
|
6
|
-
export const fetchInvoicingQboClassesEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchInvoicingQboClasses.match), switchMap(() => {
|
|
7
|
-
const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/qbo/classes`;
|
|
8
|
-
return zeniAPI.getJSON(apiUrl).pipe(map((response) => {
|
|
9
|
-
if (isSuccessResponse(response)) {
|
|
10
|
-
return updateInvoicingQboClasses(mapInvoicingQboAccountOptions(response.data?.accounts ?? []));
|
|
11
|
-
}
|
|
12
|
-
return updateInvoicingQboClassesFailure(response.status);
|
|
13
|
-
}), catchError((error) => of(updateInvoicingQboClassesFailure(createZeniAPIStatus('Unexpected Error', 'Fetch QBO classes REST API call errored out ' +
|
|
14
|
-
JSON.stringify(error))))));
|
|
15
|
-
}));
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { ActionsObservable, StateObservable } from 'redux-observable';
|
|
2
|
-
import { Observable } from 'rxjs';
|
|
3
|
-
import { RootState } from '../../../../rootStateTypes';
|
|
4
|
-
import { ZeniAPI } from '../../../../zeniAPI';
|
|
5
|
-
import { InvoicingQboViewActionType } from './invoicingQboViewActionType';
|
|
6
|
-
export declare const fetchInvoicingQboAccountsEpic: (actions$: ActionsObservable<InvoicingQboViewActionType>, _state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<InvoicingQboViewActionType>;
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.fetchInvoicingQboAccountsEpic = void 0;
|
|
4
|
-
const rxjs_1 = require("rxjs");
|
|
5
|
-
const operators_1 = require("rxjs/operators");
|
|
6
|
-
const responsePayload_1 = require("../../../../responsePayload");
|
|
7
|
-
const invoicingQboViewPayload_1 = require("../invoicingQboViewPayload");
|
|
8
|
-
const invoicingQboViewReducer_1 = require("../invoicingQboViewReducer");
|
|
9
|
-
const fetchInvoicingQboAccountsEpic = (actions$, _state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(invoicingQboViewReducer_1.fetchInvoicingQboAccounts.match), (0, operators_1.mergeMap)((action) => {
|
|
10
|
-
const { accountTypes } = action.payload;
|
|
11
|
-
const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/qbo/accounts?account_types=${encodeURIComponent(accountTypes)}`;
|
|
12
|
-
return zeniAPI.getJSON(apiUrl).pipe((0, operators_1.map)((response) => {
|
|
13
|
-
if ((0, responsePayload_1.isSuccessResponse)(response)) {
|
|
14
|
-
return (0, invoicingQboViewReducer_1.updateInvoicingQboAccounts)({
|
|
15
|
-
accountTypes,
|
|
16
|
-
accounts: (0, invoicingQboViewPayload_1.mapInvoicingQboAccountOptions)(response.data?.accounts ?? []),
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
return (0, invoicingQboViewReducer_1.updateInvoicingQboAccountsFailure)({
|
|
20
|
-
accountTypes,
|
|
21
|
-
status: response.status,
|
|
22
|
-
});
|
|
23
|
-
}), (0, operators_1.catchError)((error) => (0, rxjs_1.of)((0, invoicingQboViewReducer_1.updateInvoicingQboAccountsFailure)({
|
|
24
|
-
accountTypes,
|
|
25
|
-
status: (0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', 'Fetch QBO accounts REST API call errored out ' +
|
|
26
|
-
JSON.stringify(error)),
|
|
27
|
-
}))));
|
|
28
|
-
}));
|
|
29
|
-
exports.fetchInvoicingQboAccountsEpic = fetchInvoicingQboAccountsEpic;
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { ActionsObservable, StateObservable } from 'redux-observable';
|
|
2
|
-
import { Observable } from 'rxjs';
|
|
3
|
-
import { RootState } from '../../../../rootStateTypes';
|
|
4
|
-
import { ZeniAPI } from '../../../../zeniAPI';
|
|
5
|
-
import { InvoicingQboViewActionType } from './invoicingQboViewActionType';
|
|
6
|
-
export declare const fetchInvoicingQboClassesEpic: (actions$: ActionsObservable<InvoicingQboViewActionType>, _state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<InvoicingQboViewActionType>;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.fetchInvoicingQboClassesEpic = void 0;
|
|
4
|
-
const rxjs_1 = require("rxjs");
|
|
5
|
-
const operators_1 = require("rxjs/operators");
|
|
6
|
-
const responsePayload_1 = require("../../../../responsePayload");
|
|
7
|
-
const invoicingQboViewPayload_1 = require("../invoicingQboViewPayload");
|
|
8
|
-
const invoicingQboViewReducer_1 = require("../invoicingQboViewReducer");
|
|
9
|
-
const fetchInvoicingQboClassesEpic = (actions$, _state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(invoicingQboViewReducer_1.fetchInvoicingQboClasses.match), (0, operators_1.switchMap)(() => {
|
|
10
|
-
const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/qbo/classes`;
|
|
11
|
-
return zeniAPI.getJSON(apiUrl).pipe((0, operators_1.map)((response) => {
|
|
12
|
-
if ((0, responsePayload_1.isSuccessResponse)(response)) {
|
|
13
|
-
return (0, invoicingQboViewReducer_1.updateInvoicingQboClasses)((0, invoicingQboViewPayload_1.mapInvoicingQboAccountOptions)(response.data?.accounts ?? []));
|
|
14
|
-
}
|
|
15
|
-
return (0, invoicingQboViewReducer_1.updateInvoicingQboClassesFailure)(response.status);
|
|
16
|
-
}), (0, operators_1.catchError)((error) => (0, rxjs_1.of)((0, invoicingQboViewReducer_1.updateInvoicingQboClassesFailure)((0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', 'Fetch QBO classes REST API call errored out ' +
|
|
17
|
-
JSON.stringify(error))))));
|
|
18
|
-
}));
|
|
19
|
-
exports.fetchInvoicingQboClassesEpic = fetchInvoicingQboClassesEpic;
|