@zeniai/client-epic-state 5.1.93 → 5.1.94
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/entity/aiCfo/aiCfoPayload.d.ts +7 -0
- package/lib/entity/aiCfo/aiCfoReducer.d.ts +3 -2
- package/lib/entity/aiCfo/aiCfoReducer.js +25 -2
- package/lib/entity/aiCfo/aiCfoState.d.ts +7 -0
- package/lib/entity/company/companyPayload.d.ts +4 -0
- package/lib/entity/company/companyPayload.js +18 -8
- package/lib/entity/company/companyStateTypes.d.ts +4 -0
- package/lib/entity/customerSatisfaction/customerSatisfactionReducer.d.ts +1 -1
- package/lib/entity/customerSatisfaction/customerSatisfactionReducer.js +6 -2
- package/lib/entity/snackbar/snackbarApiError.d.ts +8 -0
- package/lib/entity/snackbar/snackbarApiError.js +15 -0
- package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
- package/lib/entity/snackbar/snackbarTypes.js +31 -0
- package/lib/entity/tenant/tenantReducer.d.ts +5 -1
- package/lib/entity/tenant/tenantReducer.js +21 -2
- package/lib/epic.d.ts +3 -1
- package/lib/epic.js +5 -1
- package/lib/esm/entity/aiCfo/aiCfoReducer.js +23 -1
- package/lib/esm/entity/company/companyPayload.js +18 -8
- package/lib/esm/entity/customerSatisfaction/customerSatisfactionReducer.js +4 -1
- package/lib/esm/entity/snackbar/snackbarApiError.js +11 -0
- package/lib/esm/entity/snackbar/snackbarTypes.js +31 -0
- package/lib/esm/entity/tenant/tenantReducer.js +20 -1
- package/lib/esm/epic.js +5 -1
- package/lib/esm/index.js +9 -8
- package/lib/esm/view/companyHealthMetricView/companyHealthMetricConfigReducer.js +4 -1
- package/lib/esm/view/companyView/companyViewReducer.js +58 -1
- package/lib/esm/view/companyView/epic/updateCompanyProductServicesEpic.js +91 -0
- package/lib/esm/view/companyView/epic/updateCompanyQboRealmIdEpic.js +58 -0
- package/lib/esm/view/companyView/selector/companyManagementViewSelector.js +52 -0
- package/lib/esm/view/companyView/types/cockpitTypes.js +13 -1
- package/lib/esm/view/companyView/types/companyManagementViewState.js +2 -0
- package/lib/esm/view/invoicing/createCreditNote/createCreditNoteEpic.js +26 -4
- package/lib/esm/view/invoicing/createInvoice/createInvoiceEpic.js +26 -4
- package/lib/esm/view/invoicing/createInvoice/createInvoiceSelector.js +37 -10
- package/lib/esm/view/invoicing/discountAction/discountActionEpic.js +37 -10
- package/lib/esm/view/invoicing/dunningAction/dunningActionEpic.js +39 -10
- package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/catalogDetailEditPayload.js +5 -1
- package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.js +5 -4
- package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +35 -5
- package/lib/esm/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.js +25 -3
- package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.js +26 -4
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +26 -4
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/syncInvoicingSubscriptionCouponsEpic.js +20 -3
- package/lib/esm/view/invoicing/invoiceAction/invoiceActionEpic.js +65 -18
- package/lib/esm/view/invoicing/invoicingConfigView/invoicingConfigViewReducer.js +30 -0
- package/lib/esm/view/invoicing/invoicingConfigView/invoicingConfigViewSelector.js +4 -29
- package/lib/esm/view/invoicing/invoicingDunningCaseDetailView/fetchInvoicingDunningEmailPreviewEpic.js +26 -0
- package/lib/esm/view/invoicing/invoicingDunningCaseDetailView/fetchInvoicingDunningEmailPreviewPageEpic.js +16 -0
- package/lib/esm/view/invoicing/invoicingDunningCaseDetailView/invoicingDunningCaseDetailViewPayload.js +9 -1
- package/lib/esm/view/invoicing/invoicingDunningCaseDetailView/invoicingDunningCaseDetailViewReducer.js +50 -1
- package/lib/esm/view/invoicing/invoicingDunningCaseDetailView/invoicingDunningCaseDetailViewSelector.js +14 -1
- package/lib/esm/view/invoicing/invoicingQboView/epics/retryInvoicingQboSyncTaskEpic.js +31 -10
- package/lib/esm/view/invoicing/invoicingQboView/epics/startInvoicingQboBackfillEpic.js +30 -5
- package/lib/esm/view/invoicing/issueCreditNote/issueCreditNoteEpic.js +26 -4
- package/lib/esm/view/invoicing/issueCreditNote/issueCreditNoteSelector.js +22 -14
- package/lib/esm/view/invoicing/recordPayment/recordPaymentEpic.js +49 -7
- package/lib/esm/view/invoicing/settingsView/settingsViewEpics.js +30 -9
- package/lib/esm/view/invoicing/settingsView/settingsViewReducer.js +1 -1
- package/lib/esm/view/invoicing/settingsView/settingsViewSelector.js +56 -1
- package/lib/esm/view/invoicing/subscriptionAction/subscriptionActionEpic.js +45 -10
- package/lib/esm/view/vendorFiling1099/vendorFiling1099UploadDetails/vendorFiling1099UploadDetailsReducer.js +4 -1
- package/lib/esm/view/vendorTabView/vendorTabViewReducer.js +4 -1
- package/lib/index.d.ts +17 -13
- package/lib/index.js +62 -50
- package/lib/view/companyHealthMetricView/companyHealthMetricConfigReducer.d.ts +1 -1
- package/lib/view/companyHealthMetricView/companyHealthMetricConfigReducer.js +5 -2
- package/lib/view/companyView/companyViewReducer.d.ts +22 -1
- package/lib/view/companyView/companyViewReducer.js +60 -3
- package/lib/view/companyView/epic/updateCompanyProductServicesEpic.d.ts +10 -0
- package/lib/view/companyView/epic/updateCompanyProductServicesEpic.js +95 -0
- package/lib/view/companyView/epic/updateCompanyQboRealmIdEpic.d.ts +8 -0
- package/lib/view/companyView/epic/updateCompanyQboRealmIdEpic.js +62 -0
- package/lib/view/companyView/selector/companyManagementViewSelector.d.ts +3 -0
- package/lib/view/companyView/selector/companyManagementViewSelector.js +55 -0
- package/lib/view/companyView/types/cockpitTypes.d.ts +7 -3
- package/lib/view/companyView/types/cockpitTypes.js +15 -1
- package/lib/view/companyView/types/companyManagementViewState.d.ts +2 -0
- package/lib/view/companyView/types/companyManagementViewState.js +2 -0
- package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
- package/lib/view/invoicing/createCreditNote/createCreditNoteEpic.d.ts +2 -1
- package/lib/view/invoicing/createCreditNote/createCreditNoteEpic.js +25 -3
- package/lib/view/invoicing/createInvoice/createInvoiceEpic.d.ts +2 -1
- package/lib/view/invoicing/createInvoice/createInvoiceEpic.js +25 -3
- package/lib/view/invoicing/createInvoice/createInvoiceSelector.d.ts +22 -8
- package/lib/view/invoicing/createInvoice/createInvoiceSelector.js +37 -10
- package/lib/view/invoicing/discountAction/discountActionEpic.d.ts +2 -1
- package/lib/view/invoicing/discountAction/discountActionEpic.js +35 -8
- package/lib/view/invoicing/dunningAction/dunningActionEpic.d.ts +2 -1
- package/lib/view/invoicing/dunningAction/dunningActionEpic.js +37 -8
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/catalogDetailEditPayload.d.ts +3 -1
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/catalogDetailEditPayload.js +5 -1
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.js +5 -4
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.d.ts +2 -1
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +34 -4
- package/lib/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.d.ts +2 -1
- package/lib/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.js +24 -2
- package/lib/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.d.ts +2 -1
- package/lib/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.js +25 -3
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.d.ts +2 -1
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +25 -3
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/syncInvoicingSubscriptionCouponsEpic.d.ts +2 -1
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/syncInvoicingSubscriptionCouponsEpic.js +19 -2
- package/lib/view/invoicing/invoiceAction/invoiceActionEpic.d.ts +2 -1
- package/lib/view/invoicing/invoiceAction/invoiceActionEpic.js +63 -16
- package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewPayload.d.ts +13 -0
- package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewReducer.d.ts +1 -1
- package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewReducer.js +30 -0
- package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewSelector.d.ts +11 -31
- package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewSelector.js +4 -29
- package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewState.d.ts +15 -0
- package/lib/view/invoicing/invoicingDunningCaseDetailView/fetchInvoicingDunningEmailPreviewEpic.d.ts +7 -0
- package/lib/view/invoicing/invoicingDunningCaseDetailView/fetchInvoicingDunningEmailPreviewEpic.js +30 -0
- package/lib/view/invoicing/invoicingDunningCaseDetailView/fetchInvoicingDunningEmailPreviewPageEpic.d.ts +6 -0
- package/lib/view/invoicing/invoicingDunningCaseDetailView/fetchInvoicingDunningEmailPreviewPageEpic.js +20 -0
- package/lib/view/invoicing/invoicingDunningCaseDetailView/invoicingDunningCaseDetailViewEpics.d.ts +2 -2
- package/lib/view/invoicing/invoicingDunningCaseDetailView/invoicingDunningCaseDetailViewPayload.d.ts +12 -0
- package/lib/view/invoicing/invoicingDunningCaseDetailView/invoicingDunningCaseDetailViewPayload.js +11 -0
- package/lib/view/invoicing/invoicingDunningCaseDetailView/invoicingDunningCaseDetailViewReducer.d.ts +21 -4
- package/lib/view/invoicing/invoicingDunningCaseDetailView/invoicingDunningCaseDetailViewReducer.js +51 -2
- package/lib/view/invoicing/invoicingDunningCaseDetailView/invoicingDunningCaseDetailViewSelector.d.ts +3 -0
- package/lib/view/invoicing/invoicingDunningCaseDetailView/invoicingDunningCaseDetailViewSelector.js +16 -2
- package/lib/view/invoicing/invoicingDunningCaseDetailView/invoicingDunningCaseDetailViewState.d.ts +13 -0
- package/lib/view/invoicing/invoicingQboView/epics/invoicingQboViewActionType.d.ts +2 -1
- package/lib/view/invoicing/invoicingQboView/epics/retryInvoicingQboSyncTaskEpic.js +30 -9
- package/lib/view/invoicing/invoicingQboView/epics/startInvoicingQboBackfillEpic.js +29 -4
- package/lib/view/invoicing/issueCreditNote/issueCreditNoteEpic.d.ts +2 -1
- package/lib/view/invoicing/issueCreditNote/issueCreditNoteEpic.js +25 -3
- package/lib/view/invoicing/issueCreditNote/issueCreditNoteSelector.d.ts +24 -7
- package/lib/view/invoicing/issueCreditNote/issueCreditNoteSelector.js +23 -16
- package/lib/view/invoicing/recordPayment/recordPaymentEpic.d.ts +2 -1
- package/lib/view/invoicing/recordPayment/recordPaymentEpic.js +48 -6
- package/lib/view/invoicing/settingsView/settingsViewEpics.d.ts +2 -1
- package/lib/view/invoicing/settingsView/settingsViewEpics.js +28 -7
- package/lib/view/invoicing/settingsView/settingsViewReducer.d.ts +10 -1
- package/lib/view/invoicing/settingsView/settingsViewReducer.js +1 -1
- package/lib/view/invoicing/settingsView/settingsViewSelector.d.ts +44 -2
- package/lib/view/invoicing/settingsView/settingsViewSelector.js +59 -2
- package/lib/view/invoicing/subscriptionAction/subscriptionActionEpic.d.ts +2 -1
- package/lib/view/invoicing/subscriptionAction/subscriptionActionEpic.js +43 -8
- package/lib/view/vendorFiling1099/vendorFiling1099UploadDetails/vendorFiling1099UploadDetailsReducer.d.ts +1 -1
- package/lib/view/vendorFiling1099/vendorFiling1099UploadDetails/vendorFiling1099UploadDetailsReducer.js +5 -2
- package/lib/view/vendorTabView/vendorTabViewReducer.d.ts +1 -1
- package/lib/view/vendorTabView/vendorTabViewReducer.js +5 -2
- package/package.json +2 -2
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { of } from 'rxjs';
|
|
2
|
+
import { catchError, filter, map, switchMap } from 'rxjs/operators';
|
|
3
|
+
import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
|
|
4
|
+
import { toInvoicingDunningEmailPreview, } from './invoicingDunningCaseDetailViewPayload';
|
|
5
|
+
import { fetchInvoicingDunningEmailPreview, updateInvoicingDunningEmailPreview, updateInvoicingDunningEmailPreviewFailure, } from './invoicingDunningCaseDetailViewReducer';
|
|
6
|
+
export const fetchInvoicingDunningEmailPreviewEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchInvoicingDunningEmailPreview.match), switchMap((action) => {
|
|
7
|
+
const { id } = action.payload;
|
|
8
|
+
const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/dunning/${encodeURIComponent(id)}/email-preview`;
|
|
9
|
+
return zeniAPI
|
|
10
|
+
.postAndGetJSON(apiUrl, {})
|
|
11
|
+
.pipe(map((response) => {
|
|
12
|
+
if (isSuccessResponse(response) && response.data != null) {
|
|
13
|
+
return updateInvoicingDunningEmailPreview({
|
|
14
|
+
id,
|
|
15
|
+
preview: toInvoicingDunningEmailPreview(response.data),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return updateInvoicingDunningEmailPreviewFailure({
|
|
19
|
+
id,
|
|
20
|
+
status: response.status,
|
|
21
|
+
});
|
|
22
|
+
}), catchError((error) => of(updateInvoicingDunningEmailPreviewFailure({
|
|
23
|
+
id,
|
|
24
|
+
status: createZeniAPIStatus('Unexpected Error', `Dunning email preview REST API call errored out: ${error instanceof Error ? error.message : String(error)}`),
|
|
25
|
+
}))));
|
|
26
|
+
}));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { EMPTY, of } from 'rxjs';
|
|
2
|
+
import { filter, mergeMap } from 'rxjs/operators';
|
|
3
|
+
import { fetchInvoicingDunningEmailPreview, fetchInvoicingDunningEmailPreviewPage, } from './invoicingDunningCaseDetailViewReducer';
|
|
4
|
+
const shouldFetch = (fetchStateAndError, cacheOverride) => cacheOverride ||
|
|
5
|
+
(fetchStateAndError.fetchState !== 'Completed' &&
|
|
6
|
+
fetchStateAndError.fetchState !== 'In-Progress');
|
|
7
|
+
export const fetchInvoicingDunningEmailPreviewPageEpic = (actions$, state$) => actions$.pipe(filter(fetchInvoicingDunningEmailPreviewPage.match), mergeMap((action) => {
|
|
8
|
+
const { cacheOverride, id } = action.payload;
|
|
9
|
+
const detailState = state$.value.invoicingDunningCaseDetailViewState;
|
|
10
|
+
if (detailState.id !== id) {
|
|
11
|
+
return EMPTY;
|
|
12
|
+
}
|
|
13
|
+
return shouldFetch(detailState.emailPreview, cacheOverride)
|
|
14
|
+
? of(fetchInvoicingDunningEmailPreview({ id }))
|
|
15
|
+
: EMPTY;
|
|
16
|
+
}));
|
|
@@ -1 +1,9 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export const toInvoicingDunningEmailPreview = (payload) => ({
|
|
2
|
+
body: payload.body,
|
|
3
|
+
bodyToken: payload.body_token,
|
|
4
|
+
cc: payload.cc,
|
|
5
|
+
renderedHtmlTemplate: payload.rendered_html_template,
|
|
6
|
+
subject: payload.subject,
|
|
7
|
+
subjectToken: payload.subject_token,
|
|
8
|
+
to: payload.to,
|
|
9
|
+
});
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { createSlice } from '@reduxjs/toolkit';
|
|
2
2
|
import { clearAll } from '../../../rootActions';
|
|
3
|
+
export const initialEmailPreviewState = {
|
|
4
|
+
fetchState: 'Not-Started',
|
|
5
|
+
error: undefined,
|
|
6
|
+
preview: undefined,
|
|
7
|
+
};
|
|
3
8
|
export const initialState = {
|
|
4
9
|
fetchState: 'Not-Started',
|
|
5
10
|
error: undefined,
|
|
11
|
+
emailPreview: initialEmailPreviewState,
|
|
6
12
|
id: undefined,
|
|
7
13
|
};
|
|
8
14
|
const invoicingDunningCaseDetailView = createSlice({
|
|
@@ -10,10 +16,38 @@ const invoicingDunningCaseDetailView = createSlice({
|
|
|
10
16
|
initialState,
|
|
11
17
|
reducers: {
|
|
12
18
|
fetchInvoicingDunningCaseDetail(draft, action) {
|
|
19
|
+
if (draft.id !== action.payload.id) {
|
|
20
|
+
draft.emailPreview = { ...initialEmailPreviewState };
|
|
21
|
+
}
|
|
13
22
|
draft.id = action.payload.id;
|
|
14
23
|
draft.fetchState = 'In-Progress';
|
|
15
24
|
draft.error = undefined;
|
|
16
25
|
},
|
|
26
|
+
fetchInvoicingDunningEmailPreview(draft, action) {
|
|
27
|
+
if (draft.id !== action.payload.id) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
draft.emailPreview.fetchState = 'In-Progress';
|
|
31
|
+
draft.emailPreview.error = undefined;
|
|
32
|
+
},
|
|
33
|
+
fetchInvoicingDunningEmailPreviewPage: {
|
|
34
|
+
reducer() {
|
|
35
|
+
// Cache-aware orchestrator trigger only.
|
|
36
|
+
},
|
|
37
|
+
prepare(payload) {
|
|
38
|
+
return {
|
|
39
|
+
payload: {
|
|
40
|
+
cacheOverride: payload.cacheOverride ?? false,
|
|
41
|
+
id: payload.id,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
invalidateInvoicingDunningEmailPreview(draft, action) {
|
|
47
|
+
if (draft.id === action.payload.id) {
|
|
48
|
+
draft.emailPreview = { ...initialEmailPreviewState };
|
|
49
|
+
}
|
|
50
|
+
},
|
|
17
51
|
updateInvoicingDunningCaseDetail(draft, action) {
|
|
18
52
|
draft.id = action.payload.id;
|
|
19
53
|
draft.fetchState = 'Completed';
|
|
@@ -23,6 +57,21 @@ const invoicingDunningCaseDetailView = createSlice({
|
|
|
23
57
|
draft.fetchState = 'Error';
|
|
24
58
|
draft.error = action.payload;
|
|
25
59
|
},
|
|
60
|
+
updateInvoicingDunningEmailPreview(draft, action) {
|
|
61
|
+
if (draft.id === action.payload.id) {
|
|
62
|
+
draft.emailPreview = {
|
|
63
|
+
error: undefined,
|
|
64
|
+
fetchState: 'Completed',
|
|
65
|
+
preview: action.payload.preview,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
updateInvoicingDunningEmailPreviewFailure(draft, action) {
|
|
70
|
+
if (draft.id === action.payload.id) {
|
|
71
|
+
draft.emailPreview.fetchState = 'Error';
|
|
72
|
+
draft.emailPreview.error = action.payload.status;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
26
75
|
clearInvoicingDunningCaseDetailView(draft) {
|
|
27
76
|
Object.assign(draft, initialState);
|
|
28
77
|
},
|
|
@@ -33,5 +82,5 @@ const invoicingDunningCaseDetailView = createSlice({
|
|
|
33
82
|
});
|
|
34
83
|
},
|
|
35
84
|
});
|
|
36
|
-
export const { fetchInvoicingDunningCaseDetail, updateInvoicingDunningCaseDetail, updateInvoicingDunningCaseDetailFailure,
|
|
85
|
+
export const { clearInvoicingDunningCaseDetailView, fetchInvoicingDunningCaseDetail, fetchInvoicingDunningEmailPreview, fetchInvoicingDunningEmailPreviewPage, invalidateInvoicingDunningEmailPreview, updateInvoicingDunningCaseDetail, updateInvoicingDunningCaseDetailFailure, updateInvoicingDunningEmailPreview, updateInvoicingDunningEmailPreviewFailure, } = invoicingDunningCaseDetailView.actions;
|
|
37
86
|
export default invoicingDunningCaseDetailView.reducer;
|
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
import { getInvoicingDunningCaseById as getInvoicingDunningCaseByIdFromState } from '../../../entity/invoicing/dunningCase/dunningCaseSelector';
|
|
2
2
|
import { getInvoicingDunningActionState } from '../dunningAction/dunningActionSelector';
|
|
3
|
+
const EMAIL_PREVIEW_NOT_STARTED = {
|
|
4
|
+
error: undefined,
|
|
5
|
+
fetchState: 'Not-Started',
|
|
6
|
+
preview: undefined,
|
|
7
|
+
};
|
|
3
8
|
export const getInvoicingDunningCaseById = (state, id) => getInvoicingDunningCaseByIdFromState(state.invoicingDunningCaseState, id);
|
|
4
9
|
export const getInvoicingDunningCaseDetail = (state, invoicingDunningCaseID) => {
|
|
5
|
-
const { error, fetchState } = state.invoicingDunningCaseDetailViewState;
|
|
10
|
+
const { emailPreview, error, fetchState, id } = state.invoicingDunningCaseDetailViewState;
|
|
6
11
|
return {
|
|
7
12
|
actionState: getInvoicingDunningActionState(invoicingDunningCaseID)(state),
|
|
8
13
|
dunningCase: getInvoicingDunningCaseById(state, invoicingDunningCaseID),
|
|
14
|
+
emailPreview: id === invoicingDunningCaseID ? emailPreview : EMAIL_PREVIEW_NOT_STARTED,
|
|
9
15
|
fetchState,
|
|
10
16
|
error,
|
|
11
17
|
version: 1.0,
|
|
12
18
|
};
|
|
13
19
|
};
|
|
20
|
+
export const getInvoicingDunningEmailPreview = (state, invoicingDunningCaseID) => {
|
|
21
|
+
const detailState = state.invoicingDunningCaseDetailViewState;
|
|
22
|
+
if (detailState.id !== invoicingDunningCaseID) {
|
|
23
|
+
return EMAIL_PREVIEW_NOT_STARTED;
|
|
24
|
+
}
|
|
25
|
+
return detailState.emailPreview;
|
|
26
|
+
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { of } from 'rxjs';
|
|
1
|
+
import { from, of } from 'rxjs';
|
|
2
2
|
import { catchError, filter, mergeMap } from 'rxjs/operators';
|
|
3
|
+
import { apiErrorSnackbarVariables } from '../../../../entity/snackbar/snackbarApiError';
|
|
4
|
+
import { openSnackbar } from '../../../../entity/snackbar/snackbarReducer';
|
|
3
5
|
import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
|
|
4
6
|
import { fetchInvoicingQboSyncHealth, retryInvoicingQboSyncTask, retryInvoicingQboSyncTaskFailure, retryInvoicingQboSyncTaskSuccess, } from '../invoicingQboViewReducer';
|
|
5
7
|
export const retryInvoicingQboSyncTaskEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(retryInvoicingQboSyncTask.match), mergeMap((action) => {
|
|
@@ -12,15 +14,34 @@ export const retryInvoicingQboSyncTaskEpic = (actions$, _state$, zeniAPI) => act
|
|
|
12
14
|
return of(retryInvoicingQboSyncTaskSuccess({
|
|
13
15
|
status: response.data?.status ?? '',
|
|
14
16
|
taskId: response.data?.task_id ?? taskId,
|
|
15
|
-
}), fetchInvoicingQboSyncHealth()
|
|
17
|
+
}), fetchInvoicingQboSyncHealth(), openSnackbar({
|
|
18
|
+
messageSection: 'invoicing_qbo_sync_health_retry',
|
|
19
|
+
messageText: 'success',
|
|
20
|
+
type: 'success',
|
|
21
|
+
}));
|
|
16
22
|
}
|
|
17
|
-
return
|
|
18
|
-
|
|
23
|
+
return from([
|
|
24
|
+
retryInvoicingQboSyncTaskFailure({
|
|
25
|
+
status: response.status,
|
|
26
|
+
taskId,
|
|
27
|
+
}),
|
|
28
|
+
openSnackbar({
|
|
29
|
+
messageSection: 'invoicing_qbo_sync_health_retry',
|
|
30
|
+
messageText: 'failed',
|
|
31
|
+
type: 'error',
|
|
32
|
+
variables: apiErrorSnackbarVariables(response.status),
|
|
33
|
+
}),
|
|
34
|
+
]);
|
|
35
|
+
}), catchError((error) => from([
|
|
36
|
+
retryInvoicingQboSyncTaskFailure({
|
|
37
|
+
status: createZeniAPIStatus('Unexpected Error', 'Retry QBO sync task REST API call errored out ' +
|
|
38
|
+
JSON.stringify(error)),
|
|
19
39
|
taskId,
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
40
|
+
}),
|
|
41
|
+
openSnackbar({
|
|
42
|
+
messageSection: 'invoicing_qbo_sync_health_retry',
|
|
43
|
+
messageText: 'failed',
|
|
44
|
+
type: 'error',
|
|
45
|
+
}),
|
|
46
|
+
])));
|
|
26
47
|
}));
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { from } from 'rxjs';
|
|
2
2
|
import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
|
|
3
|
+
import { apiErrorSnackbarVariables } from '../../../../entity/snackbar/snackbarApiError';
|
|
4
|
+
import { openSnackbar } from '../../../../entity/snackbar/snackbarReducer';
|
|
3
5
|
import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
|
|
4
6
|
import { fetchInvoicingQboSyncHealth, startInvoicingQboBackfill, startInvoicingQboBackfillFailure, startInvoicingQboBackfillSuccess, } from '../invoicingQboViewReducer';
|
|
5
7
|
export const startInvoicingQboBackfillEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(startInvoicingQboBackfill.match), switchMap((action) => {
|
|
@@ -10,9 +12,32 @@ export const startInvoicingQboBackfillEpic = (actions$, _state$, zeniAPI) => act
|
|
|
10
12
|
})
|
|
11
13
|
.pipe(mergeMap((response) => {
|
|
12
14
|
if (isSuccessResponse(response)) {
|
|
13
|
-
return
|
|
15
|
+
return from([
|
|
16
|
+
startInvoicingQboBackfillSuccess(),
|
|
17
|
+
fetchInvoicingQboSyncHealth(),
|
|
18
|
+
openSnackbar({
|
|
19
|
+
messageSection: 'invoicing_qbo_backfill',
|
|
20
|
+
messageText: 'success',
|
|
21
|
+
type: 'success',
|
|
22
|
+
}),
|
|
23
|
+
]);
|
|
14
24
|
}
|
|
15
|
-
return
|
|
16
|
-
|
|
17
|
-
|
|
25
|
+
return from([
|
|
26
|
+
startInvoicingQboBackfillFailure(response.status),
|
|
27
|
+
openSnackbar({
|
|
28
|
+
messageSection: 'invoicing_qbo_backfill',
|
|
29
|
+
messageText: 'failed',
|
|
30
|
+
type: 'error',
|
|
31
|
+
variables: apiErrorSnackbarVariables(response.status),
|
|
32
|
+
}),
|
|
33
|
+
]);
|
|
34
|
+
}), catchError((error) => from([
|
|
35
|
+
startInvoicingQboBackfillFailure(createZeniAPIStatus('Unexpected Error', 'Start QBO backfill REST API call errored out ' +
|
|
36
|
+
JSON.stringify(error))),
|
|
37
|
+
openSnackbar({
|
|
38
|
+
messageSection: 'invoicing_qbo_backfill',
|
|
39
|
+
messageText: 'failed',
|
|
40
|
+
type: 'error',
|
|
41
|
+
}),
|
|
42
|
+
])));
|
|
18
43
|
}));
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { from } from 'rxjs';
|
|
2
2
|
import { catchError, filter, mergeMap } from 'rxjs/operators';
|
|
3
3
|
import { updateInvoicingCreditNotes } from '../../../entity/invoicing/creditNote/creditNoteReducer';
|
|
4
|
+
import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiError';
|
|
5
|
+
import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
|
|
4
6
|
import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
|
|
5
7
|
import { fetchInvoiceDetail } from '../invoiceDetail/invoiceDetailReducer';
|
|
6
8
|
import { localDataToIssueCreditNotePayload, } from './issueCreditNotePayload';
|
|
@@ -18,9 +20,29 @@ export const issueCreditNoteEpic = (actions$, state$, zeniAPI) => actions$.pipe(
|
|
|
18
20
|
updateInvoicingCreditNotes([response.data], 'merge'),
|
|
19
21
|
fetchInvoiceDetail({ invoiceId }),
|
|
20
22
|
issueCreditNoteSuccess({ creditNoteId: response.data.id }),
|
|
23
|
+
openSnackbar({
|
|
24
|
+
messageSection: 'invoicing_credit_note_saved',
|
|
25
|
+
messageText: 'success',
|
|
26
|
+
type: 'success',
|
|
27
|
+
}),
|
|
21
28
|
];
|
|
22
29
|
}
|
|
23
|
-
return
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
return from([
|
|
31
|
+
issueCreditNoteFailure(response.status),
|
|
32
|
+
openSnackbar({
|
|
33
|
+
messageSection: 'invoicing_credit_note_saved',
|
|
34
|
+
messageText: 'failed',
|
|
35
|
+
type: 'error',
|
|
36
|
+
variables: apiErrorSnackbarVariables(response.status),
|
|
37
|
+
}),
|
|
38
|
+
]);
|
|
39
|
+
}), catchError((error) => from([
|
|
40
|
+
issueCreditNoteFailure(createZeniAPIStatus('Unexpected Error', 'Issue credit note REST API call errored out ' +
|
|
41
|
+
JSON.stringify(error))),
|
|
42
|
+
openSnackbar({
|
|
43
|
+
messageSection: 'invoicing_credit_note_saved',
|
|
44
|
+
messageText: 'failed',
|
|
45
|
+
type: 'error',
|
|
46
|
+
}),
|
|
47
|
+
])));
|
|
26
48
|
}));
|
|
@@ -1,20 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { getInvoiceDetail } from '../invoiceDetail/invoiceDetailSelector';
|
|
2
|
+
import { INVOICING_CREDIT_NOTE_REASON_CODES, initialIssueCreditNoteFormLocalData, } from './issueCreditNoteFormConfig';
|
|
3
|
+
/**
|
|
4
|
+
* The draft is a single global slice, so a draft seeded for a previously opened
|
|
5
|
+
* invoice can still be in state when another invoice's screen mounts. Anything
|
|
6
|
+
* that would render that stale draft next to this invoice is dropped until the
|
|
7
|
+
* screen re-seeds it for `invoiceId`.
|
|
8
|
+
*/
|
|
9
|
+
export const getIssueCreditNoteView = (state, invoiceId) => {
|
|
10
|
+
const invoiceView = getInvoiceDetail(state, invoiceId);
|
|
11
|
+
const { draft, error, fetchState, issuedCreditNoteId } = state.invoicingIssueCreditNoteState;
|
|
12
|
+
const isDraftForInvoice = draft.invoiceId === invoiceId;
|
|
4
13
|
return {
|
|
5
|
-
|
|
6
|
-
fetchState
|
|
14
|
+
invoice: invoiceView.invoice,
|
|
15
|
+
isPageReady: invoiceView.fetchState === 'Completed' &&
|
|
16
|
+
isDraftForInvoice &&
|
|
17
|
+
draft.lineItems.length > 0,
|
|
7
18
|
issuedCreditNoteId,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export const getIssueCreditNoteFormView = (state) => {
|
|
12
|
-
const { draft, error, fetchState } = state.invoicingIssueCreditNoteState;
|
|
13
|
-
return {
|
|
14
|
-
localData: draft,
|
|
19
|
+
localData: isDraftForInvoice
|
|
20
|
+
? draft
|
|
21
|
+
: initialIssueCreditNoteFormLocalData(invoiceId),
|
|
15
22
|
reasonCodeValues: [...INVOICING_CREDIT_NOTE_REASON_CODES],
|
|
16
|
-
error,
|
|
17
|
-
|
|
23
|
+
submitState: { error, fetchState },
|
|
24
|
+
error: invoiceView.error,
|
|
25
|
+
fetchState: invoiceView.fetchState,
|
|
18
26
|
version: 1.0,
|
|
19
27
|
};
|
|
20
28
|
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { from } from 'rxjs';
|
|
2
2
|
import { catchError, filter, mergeMap } from 'rxjs/operators';
|
|
3
3
|
import { updateInvoices } from '../../../entity/invoicing/invoice/invoiceReducer';
|
|
4
4
|
import { updateInvoicingTransactions } from '../../../entity/invoicing/invoicingTransaction/invoicingTransactionReducer';
|
|
5
|
+
import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiError';
|
|
6
|
+
import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
|
|
5
7
|
import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
|
|
6
8
|
import { fetchInvoiceDetail } from '../invoiceDetail/invoiceDetailReducer';
|
|
7
9
|
import { addInvoicingTransactionToList } from '../invoicingTransactionListView/invoicingTransactionListViewReducer';
|
|
@@ -26,11 +28,31 @@ export const recordPaymentEpic = (actions$, state$, zeniAPI) => actions$.pipe(fi
|
|
|
26
28
|
updateInvoices([response.data], 'merge'),
|
|
27
29
|
fetchInvoiceDetail({ invoiceId }),
|
|
28
30
|
recordPaymentSuccess({ invoiceId }),
|
|
31
|
+
openSnackbar({
|
|
32
|
+
messageSection: 'invoicing_payment_recorded',
|
|
33
|
+
messageText: 'success',
|
|
34
|
+
type: 'success',
|
|
35
|
+
}),
|
|
29
36
|
];
|
|
30
37
|
}
|
|
31
|
-
return
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
return from([
|
|
39
|
+
recordPaymentFailure(response.status),
|
|
40
|
+
openSnackbar({
|
|
41
|
+
messageSection: 'invoicing_payment_recorded',
|
|
42
|
+
messageText: 'failed',
|
|
43
|
+
type: 'error',
|
|
44
|
+
variables: apiErrorSnackbarVariables(response.status),
|
|
45
|
+
}),
|
|
46
|
+
]);
|
|
47
|
+
}), catchError((error) => from([
|
|
48
|
+
recordPaymentFailure(createZeniAPIStatus('Unexpected Error', 'Record payment REST API call errored out ' +
|
|
49
|
+
JSON.stringify(error))),
|
|
50
|
+
openSnackbar({
|
|
51
|
+
messageSection: 'invoicing_payment_recorded',
|
|
52
|
+
messageText: 'failed',
|
|
53
|
+
type: 'error',
|
|
54
|
+
}),
|
|
55
|
+
])));
|
|
34
56
|
}
|
|
35
57
|
const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/payments`;
|
|
36
58
|
const data = localDataToRecordPaymentPayload(draft);
|
|
@@ -49,9 +71,29 @@ export const recordPaymentEpic = (actions$, state$, zeniAPI) => actions$.pipe(fi
|
|
|
49
71
|
if (linkedInvoiceId != null) {
|
|
50
72
|
successActions.push(fetchInvoiceDetail({ invoiceId: linkedInvoiceId }));
|
|
51
73
|
}
|
|
74
|
+
successActions.push(openSnackbar({
|
|
75
|
+
messageSection: 'invoicing_payment_recorded',
|
|
76
|
+
messageText: 'success',
|
|
77
|
+
type: 'success',
|
|
78
|
+
}));
|
|
52
79
|
return successActions;
|
|
53
80
|
}
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
|
|
81
|
+
return from([
|
|
82
|
+
recordPaymentFailure(response.status),
|
|
83
|
+
openSnackbar({
|
|
84
|
+
messageSection: 'invoicing_payment_recorded',
|
|
85
|
+
messageText: 'failed',
|
|
86
|
+
type: 'error',
|
|
87
|
+
variables: apiErrorSnackbarVariables(response.status),
|
|
88
|
+
}),
|
|
89
|
+
]);
|
|
90
|
+
}), catchError((error) => from([
|
|
91
|
+
recordPaymentFailure(createZeniAPIStatus('Unexpected Error', 'Record payment REST API call errored out ' +
|
|
92
|
+
JSON.stringify(error))),
|
|
93
|
+
openSnackbar({
|
|
94
|
+
messageSection: 'invoicing_payment_recorded',
|
|
95
|
+
messageText: 'failed',
|
|
96
|
+
type: 'error',
|
|
97
|
+
}),
|
|
98
|
+
])));
|
|
57
99
|
}));
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { of } from 'rxjs';
|
|
1
|
+
import { from, of } from 'rxjs';
|
|
2
2
|
import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
|
|
3
3
|
import { updateAddresses } from '../../../entity/address/addressReducer';
|
|
4
|
-
import {
|
|
4
|
+
import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiError';
|
|
5
|
+
import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
|
|
6
|
+
import { createZeniAPIStatus, isSuccessResponse, } from '../../../responsePayload';
|
|
5
7
|
import { connectInvoicingStripe, disconnectInvoicingStripe, fetchInvoicingSettings, invoicingStripeConnectionFailure, saveInvoicingSettings, saveInvoicingSettingsFailure, saveInvoicingSettingsSuccess, updateInvoicingSettings, updateInvoicingSettingsFailure, updateInvoicingStripeConnection, } from './settingsViewReducer';
|
|
6
8
|
export const fetchInvoicingSettingsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchInvoicingSettings.match), switchMap(() => {
|
|
7
9
|
const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/settings`;
|
|
@@ -57,15 +59,34 @@ export const disconnectInvoicingStripeEpic = (actions$, _state$, zeniAPI) => act
|
|
|
57
59
|
}));
|
|
58
60
|
export const saveInvoicingSettingsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(saveInvoicingSettings.match), mergeMap((action) => {
|
|
59
61
|
const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/settings`;
|
|
62
|
+
const { settings, snackbarSection } = action.payload;
|
|
63
|
+
/** `status` surfaces the platform's error text; omit it to keep the generic copy. */
|
|
64
|
+
const snackbar = (messageText, status) => snackbarSection == null
|
|
65
|
+
? []
|
|
66
|
+
: [
|
|
67
|
+
openSnackbar({
|
|
68
|
+
messageSection: snackbarSection,
|
|
69
|
+
messageText,
|
|
70
|
+
type: messageText === 'success' ? 'success' : 'error',
|
|
71
|
+
variables: apiErrorSnackbarVariables(status),
|
|
72
|
+
}),
|
|
73
|
+
];
|
|
60
74
|
return zeniAPI
|
|
61
|
-
.putAndGetJSON(apiUrl, {
|
|
62
|
-
...action.payload,
|
|
63
|
-
})
|
|
75
|
+
.putAndGetJSON(apiUrl, { ...settings })
|
|
64
76
|
.pipe(switchMap((response) => {
|
|
65
77
|
if (isSuccessResponse(response) && response.data != null) {
|
|
66
|
-
return
|
|
78
|
+
return from([
|
|
79
|
+
saveInvoicingSettingsSuccess(response.data),
|
|
80
|
+
...snackbar('success'),
|
|
81
|
+
]);
|
|
67
82
|
}
|
|
68
|
-
return
|
|
69
|
-
|
|
70
|
-
|
|
83
|
+
return from([
|
|
84
|
+
saveInvoicingSettingsFailure(response.status),
|
|
85
|
+
...snackbar('failed', response.status),
|
|
86
|
+
]);
|
|
87
|
+
}), catchError((error) => from([
|
|
88
|
+
saveInvoicingSettingsFailure(createZeniAPIStatus('Unexpected Error', 'Save settings REST API call errored out ' +
|
|
89
|
+
JSON.stringify(error))),
|
|
90
|
+
...snackbar('failed'),
|
|
91
|
+
])));
|
|
71
92
|
}));
|
|
@@ -30,7 +30,7 @@ const settingsView = createSlice({
|
|
|
30
30
|
saveInvoicingSettings(draft, action) {
|
|
31
31
|
draft.saveFetchState = { fetchState: 'In-Progress', error: undefined };
|
|
32
32
|
if (draft.settings != null) {
|
|
33
|
-
draft.settings = { ...draft.settings, ...action.payload };
|
|
33
|
+
draft.settings = { ...draft.settings, ...action.payload.settings };
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
saveInvoicingSettingsSuccess(draft, action) {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { getInvoicingQboAccountMappingId, } from '../../../entity/invoicing/invoicingCommonPayload';
|
|
1
2
|
import { getFormattedAddress } from '../../addressView/addressViewSelector';
|
|
3
|
+
import { getInvoicingConfigView } from '../invoicingConfigView/invoicingConfigViewSelector';
|
|
2
4
|
import { INVOICING_BUSINESS_ADDRESS_TYPE, entityToInvoicingBrandingFormLocalData, } from './invoicingBrandingFormConfig';
|
|
3
5
|
/**
|
|
4
6
|
* Shared primitive: the raw invoicing settings. Consumed internally by other
|
|
@@ -29,11 +31,63 @@ export const getInvoicingSettingsView = (state) => {
|
|
|
29
31
|
stripeConnectState: stripeFetchState,
|
|
30
32
|
};
|
|
31
33
|
};
|
|
34
|
+
/**
|
|
35
|
+
* Gates the Enable Invoicing action: payments must be able to land (Stripe
|
|
36
|
+
* connected *and* accepting charges) and the QBO accounting step must be
|
|
37
|
+
* finished, so an enabled tenant can always book what it invoices.
|
|
38
|
+
*
|
|
39
|
+
* Deliberately not part of `deriveInvoicingOnboardingStatus`, which routes the
|
|
40
|
+
* module off the config flags alone. Folding these in there would (a) demote a
|
|
41
|
+
* live tenant to the settings tree the moment an integration broke, stranding
|
|
42
|
+
* its existing invoices, and (b) deadlock the route gate, which fetches only the
|
|
43
|
+
* config — never the settings or QBO connection these read.
|
|
44
|
+
*/
|
|
45
|
+
export function deriveInvoicingEnablePrerequisites(settings, isQboConnected, qboAccountMappingKindOptions) {
|
|
46
|
+
const isStripeConnected = settings?.stripe_connected === true &&
|
|
47
|
+
settings?.stripe_account?.charges_enabled === true;
|
|
48
|
+
const accountMappings = settings?.qbo?.account_mappings ?? undefined;
|
|
49
|
+
// An empty option list means the config has not landed yet — treating that as
|
|
50
|
+
// configured would let `every` pass vacuously and unlock the button early.
|
|
51
|
+
const isQboConfigured = isQboConnected &&
|
|
52
|
+
qboAccountMappingKindOptions.length > 0 &&
|
|
53
|
+
qboAccountMappingKindOptions.every((option) => getInvoicingQboAccountMappingId(accountMappings, option.code) != null);
|
|
54
|
+
return {
|
|
55
|
+
canEnable: isStripeConnected && isQboConfigured,
|
|
56
|
+
isQboConfigured,
|
|
57
|
+
isStripeConnected,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The settings hub's composite of everything it derives from the store: whether
|
|
62
|
+
* to offer Enable Invoicing at all, the in-flight flag for that action, and the
|
|
63
|
+
* two integration prerequisites kept separate so the hub can name the step still
|
|
64
|
+
* outstanding instead of showing a dead button.
|
|
65
|
+
*
|
|
66
|
+
* `canEnableInvoicing` folds in the onboarding step, so the screen does not need
|
|
67
|
+
* to re-check it: the footer is only offered mid-onboarding and only once both
|
|
68
|
+
* integrations are ready.
|
|
69
|
+
*
|
|
70
|
+
* Deliberately not folded into `getInvoicingConfigView`: that selector gates
|
|
71
|
+
* every invoicing route and stays a pure read of the config slice. The settings
|
|
72
|
+
* and QBO slices read here are only fetched once a settings screen is mounted.
|
|
73
|
+
*/
|
|
74
|
+
export const getInvoicingSettingsHubView = (state) => {
|
|
75
|
+
const { enableState, qboAccountMappingKindOptions, status } = getInvoicingConfigView(state);
|
|
76
|
+
const { canEnable, isQboConfigured, isStripeConnected } = deriveInvoicingEnablePrerequisites(state.invoicingSettingsViewState.settings, state.invoicingQboViewState.connection?.connected === true, qboAccountMappingKindOptions);
|
|
77
|
+
return {
|
|
78
|
+
canEnableInvoicing: status === 'settings' && canEnable,
|
|
79
|
+
isEnablingInvoicing: enableState.fetchState === 'In-Progress',
|
|
80
|
+
isQboConfigured,
|
|
81
|
+
isStripeConnected,
|
|
82
|
+
status,
|
|
83
|
+
};
|
|
84
|
+
};
|
|
32
85
|
/**
|
|
33
86
|
* Bundled view for the branding settings form: the draft `localData` (seeded
|
|
34
87
|
* from the existing settings until the user edits), the business-address label
|
|
35
88
|
* (from the shared Address screen, seeded on load from `address_id`), the raw
|
|
36
|
-
* settings (for the logo/preview)
|
|
89
|
+
* settings (for the logo/preview), the backend date-format choices and the
|
|
90
|
+
* save fetch state.
|
|
37
91
|
*/
|
|
38
92
|
export const getInvoicingBrandingFormView = (state) => {
|
|
39
93
|
const { brandingDraft, saveFetchState, settings } = state.invoicingSettingsViewState;
|
|
@@ -43,6 +97,7 @@ export const getInvoicingBrandingFormView = (state) => {
|
|
|
43
97
|
: undefined;
|
|
44
98
|
return {
|
|
45
99
|
addressLabel,
|
|
100
|
+
dateFormatOptions: getInvoicingConfigView(state).dateFormatOptions,
|
|
46
101
|
localData: brandingDraft ?? entityToInvoicingBrandingFormLocalData(settings),
|
|
47
102
|
saveState: saveFetchState,
|
|
48
103
|
settings,
|
|
@@ -1,13 +1,41 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { from } from 'rxjs';
|
|
2
2
|
import { catchError, filter, mergeMap } from 'rxjs/operators';
|
|
3
3
|
import { updateAddresses } from '../../../entity/address/addressReducer';
|
|
4
4
|
import { extractInvoicingSubscriptionAddresses } from '../../../entity/invoicing/invoicingSubscription/invoicingSubscriptionPayload';
|
|
5
5
|
import { updateInvoicingSubscriptions } from '../../../entity/invoicing/invoicingSubscription/invoicingSubscriptionReducer';
|
|
6
|
-
import {
|
|
6
|
+
import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiError';
|
|
7
|
+
import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
|
|
8
|
+
import { createZeniAPIStatus, isSuccessResponse, } from '../../../responsePayload';
|
|
7
9
|
import { runInvoicingSubscriptionAction, runInvoicingSubscriptionActionFailure, runInvoicingSubscriptionActionSuccess, } from './subscriptionActionReducer';
|
|
10
|
+
/**
|
|
11
|
+
* Each lifecycle action reports with its own copy. Keyed loosely because the
|
|
12
|
+
* action payload carries a plain `string`; an unmapped kind stays silent.
|
|
13
|
+
*/
|
|
14
|
+
const SNACKBAR_SECTION_BY_ACTION = {
|
|
15
|
+
activate: 'invoicing_subscription_activated',
|
|
16
|
+
cancel: 'invoicing_subscription_cancelled',
|
|
17
|
+
change_term_end: 'invoicing_subscription_term_end_changed',
|
|
18
|
+
pause: 'invoicing_subscription_paused',
|
|
19
|
+
reactivate: 'invoicing_subscription_reactivated',
|
|
20
|
+
remove_scheduled_cancellation: 'invoicing_subscription_cancellation_removed',
|
|
21
|
+
resume: 'invoicing_subscription_resumed',
|
|
22
|
+
set_non_renewing: 'invoicing_subscription_non_renewing',
|
|
23
|
+
};
|
|
8
24
|
export const runInvoicingSubscriptionActionEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(runInvoicingSubscriptionAction.match), mergeMap((action) => {
|
|
9
25
|
const { action: kind, body, id } = action.payload;
|
|
10
26
|
const requestBody = { action: kind, ...(body ?? {}) };
|
|
27
|
+
const snackbarSection = SNACKBAR_SECTION_BY_ACTION[kind];
|
|
28
|
+
/** `status` surfaces the platform's error text; omit it to keep the generic copy. */
|
|
29
|
+
const snackbar = (messageText, status) => snackbarSection == null
|
|
30
|
+
? []
|
|
31
|
+
: [
|
|
32
|
+
openSnackbar({
|
|
33
|
+
messageSection: snackbarSection,
|
|
34
|
+
messageText,
|
|
35
|
+
type: messageText === 'success' ? 'success' : 'error',
|
|
36
|
+
variables: apiErrorSnackbarVariables(status),
|
|
37
|
+
}),
|
|
38
|
+
];
|
|
11
39
|
const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/subscriptions/${encodeURIComponent(id)}/actions`;
|
|
12
40
|
return zeniAPI
|
|
13
41
|
.postAndGetJSON(apiUrl, requestBody)
|
|
@@ -17,15 +45,22 @@ export const runInvoicingSubscriptionActionEpic = (actions$, _state$, zeniAPI) =
|
|
|
17
45
|
updateInvoicingSubscriptions([response.data], 'merge'),
|
|
18
46
|
updateAddresses(extractInvoicingSubscriptionAddresses([response.data])),
|
|
19
47
|
runInvoicingSubscriptionActionSuccess({ id }),
|
|
48
|
+
...snackbar('success'),
|
|
20
49
|
];
|
|
21
50
|
}
|
|
22
|
-
return
|
|
23
|
-
|
|
51
|
+
return from([
|
|
52
|
+
runInvoicingSubscriptionActionFailure({
|
|
53
|
+
error: response.status,
|
|
54
|
+
id,
|
|
55
|
+
}),
|
|
56
|
+
...snackbar('failed', response.status),
|
|
57
|
+
]);
|
|
58
|
+
}), catchError((error) => from([
|
|
59
|
+
runInvoicingSubscriptionActionFailure({
|
|
60
|
+
error: createZeniAPIStatus('Unexpected Error', 'Subscription action REST API call errored out ' +
|
|
61
|
+
JSON.stringify(error)),
|
|
24
62
|
id,
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
JSON.stringify(error)),
|
|
29
|
-
id,
|
|
30
|
-
}))));
|
|
63
|
+
}),
|
|
64
|
+
...snackbar('failed'),
|
|
65
|
+
])));
|
|
31
66
|
}));
|