@zeniai/client-epic-state 5.2.41-beta5MM → 5.2.41-beta7MM
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/billPay/billTransaction/billTransactionReducer.d.ts +5 -1
- package/lib/entity/billPay/billTransaction/billTransactionReducer.js +17 -2
- package/lib/entity/invoicing/coupon/couponPayload.d.ts +8 -0
- package/lib/entity/invoicing/coupon/couponPayload.js +8 -0
- package/lib/entity/invoicing/coupon/couponState.d.ts +6 -0
- package/lib/entity/invoicing/dunningCase/dunningCaseTypes.d.ts +1 -1
- package/lib/entity/invoicing/plan/planSelector.d.ts +20 -0
- package/lib/entity/invoicing/plan/planSelector.js +42 -0
- package/lib/entity/invoicing/product/productPayload.d.ts +8 -1
- package/lib/entity/invoicing/product/productPayload.js +4 -1
- package/lib/entity/invoicing/product/productState.d.ts +16 -0
- package/lib/entity/jeSchedules/jeSchedulesReducer.d.ts +4 -1
- package/lib/entity/jeSchedules/jeSchedulesReducer.js +79 -5
- package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
- package/lib/entity/snackbar/snackbarTypes.js +1 -0
- package/lib/entity/task/taskState.d.ts +1 -1
- package/lib/epic.d.ts +4 -1
- package/lib/epic.js +10 -4
- package/lib/esm/entity/billPay/billTransaction/billTransactionReducer.js +16 -1
- package/lib/esm/entity/invoicing/coupon/couponPayload.js +8 -0
- package/lib/esm/entity/invoicing/plan/planSelector.js +39 -0
- package/lib/esm/entity/invoicing/product/productPayload.js +4 -1
- package/lib/esm/entity/jeSchedules/jeSchedulesReducer.js +78 -4
- package/lib/esm/entity/snackbar/snackbarTypes.js +1 -0
- package/lib/esm/epic.js +10 -4
- package/lib/esm/index.js +8 -7
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/ignoreActualMatchingSuggestionEpic.js +3 -2
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/fetchFailedJePostingsEpic.js +39 -0
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.js +14 -1
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.js +10 -13
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/initializeJeScheduleLocalDataEpic.js +38 -17
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/retryJeSchedulesEpic.js +17 -1
- package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +15 -4
- package/lib/esm/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +6 -0
- package/lib/esm/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +74 -1
- package/lib/esm/view/expenseAutomationView/reducers/reconciliationViewReducer.js +80 -53
- package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +10 -0
- package/lib/esm/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +11 -2
- package/lib/esm/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +49 -14
- package/lib/esm/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +28 -11
- package/lib/esm/view/invoicing/createInvoice/createInvoiceSelector.js +28 -1
- package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.js +67 -35
- package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewReducer.js +22 -11
- package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +23 -4
- package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/invoicingCatalogItemFormHelper.js +1 -7
- package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +19 -5
- package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/seedInvoicingCatalogItemFormDraftEpic.js +30 -7
- package/lib/esm/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewPayload.js +16 -1
- package/lib/esm/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewReducer.js +22 -11
- package/lib/esm/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewSelector.js +69 -6
- package/lib/esm/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewState.js +22 -0
- package/lib/esm/view/invoicing/editInvoicingCouponDetailView/fetchInvoicingCouponDetailPageEpic.js +22 -4
- package/lib/esm/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.js +18 -4
- package/lib/esm/view/invoicing/editInvoicingCouponDetailView/seedInvoicingCouponFormDraftEpic.js +29 -6
- package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.js +28 -12
- package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.js +37 -2
- package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/initializeInvoicingCustomerAddressEpic.js +43 -17
- package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.js +1 -1
- package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/seedInvoicingCustomerFormDraftEpic.js +29 -6
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.js +30 -14
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +143 -8
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionFormPageEpic.js +4 -1
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/initializeInvoicingSubscriptionAddressEpic.js +44 -18
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +1 -1
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/seedInvoicingSubscriptionFormDraftEpic.js +29 -8
- package/lib/esm/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewSelector.js +12 -0
- package/lib/esm/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewState.js +6 -6
- package/lib/esm/view/invoicing/invoicingFormDraft.js +78 -0
- package/lib/esm/view/invoicing/invoicingQboView/invoicingQboViewSelector.js +57 -9
- package/lib/esm/view/recommendation/recommendationPayload.js +3 -1
- package/lib/esm/view/recommendation/recommendationReducer.js +27 -7
- package/lib/esm/view/recommendation/recommendationSelector.js +1 -0
- package/lib/esm/view/recommendation/submitRecommendationFeedbackEpic.js +20 -8
- package/lib/esm/view/spendManagement/billPay/billDetailView/billDetailViewReducer.js +87 -1
- package/lib/esm/view/spendManagement/billPay/billDetailView/billDetailViewSelector.js +1 -0
- package/lib/esm/view/spendManagement/billPay/billDetailView/billDetailViewState.js +4 -0
- package/lib/esm/view/spendManagement/billPay/billDetailView/epics/fetchBillRecommendationsEpic.js +97 -0
- package/lib/esm/view/spendManagement/billPay/billDetailView/epics/fetchRecommendationsForBill.js +85 -0
- package/lib/esm/view/spendManagement/billPay/editBillView/editBillViewReducer.js +18 -1
- package/lib/esm/view/spendManagement/billPay/editBillView/editBillViewSelector.js +3 -0
- package/lib/esm/view/spendManagement/billPay/editBillView/epics/applyBillRecommendationsToLocalStoreEpic.js +95 -0
- package/lib/index.d.ts +9 -8
- package/lib/index.js +54 -48
- package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/ignoreActualMatchingSuggestionEpic.js +3 -2
- package/lib/view/expenseAutomationView/epics/jeSchedule/fetchFailedJePostingsEpic.d.ts +20 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/fetchFailedJePostingsEpic.js +43 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.d.ts +2 -2
- package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.js +13 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.js +8 -11
- package/lib/view/expenseAutomationView/epics/jeSchedule/initializeJeScheduleLocalDataEpic.js +38 -17
- package/lib/view/expenseAutomationView/epics/jeSchedule/retryJeSchedulesEpic.d.ts +2 -2
- package/lib/view/expenseAutomationView/epics/jeSchedule/retryJeSchedulesEpic.js +17 -1
- package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +15 -4
- package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
- package/lib/view/expenseAutomationView/payload/jeSchedulesPayload.d.ts +18 -0
- package/lib/view/expenseAutomationView/payload/transactionCategorizationPayload.d.ts +18 -7
- package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +6 -0
- package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.d.ts +10 -3
- package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +76 -3
- package/lib/view/expenseAutomationView/reducers/reconciliationViewReducer.js +80 -53
- package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +10 -0
- package/lib/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.d.ts +3 -0
- package/lib/view/expenseAutomationView/selectorTypes/jeSchedulesSelectorTypes.d.ts +3 -0
- package/lib/view/expenseAutomationView/selectorTypes/transactionsViewSelectorTypes.d.ts +8 -4
- package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.d.ts +1 -0
- package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +12 -2
- package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +49 -14
- package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +27 -10
- package/lib/view/expenseAutomationView/types/jeSchedulesViewState.d.ts +16 -0
- package/lib/view/expenseAutomationView/types/transactionsViewState.d.ts +8 -5
- package/lib/view/invoicing/createInvoice/createInvoiceSelector.d.ts +7 -2
- package/lib/view/invoicing/createInvoice/createInvoiceSelector.js +28 -1
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.d.ts +46 -5
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.js +69 -36
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewReducer.d.ts +3 -1
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewReducer.js +22 -11
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.d.ts +17 -0
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +22 -3
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewState.d.ts +2 -1
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/invoicingCatalogItemFormHelper.d.ts +2 -2
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/invoicingCatalogItemFormHelper.js +1 -7
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +17 -3
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/seedInvoicingCatalogItemFormDraftEpic.d.ts +10 -6
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/seedInvoicingCatalogItemFormDraftEpic.js +29 -6
- package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewPayload.d.ts +5 -1
- package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewPayload.js +16 -1
- package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewReducer.d.ts +3 -1
- package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewReducer.js +22 -11
- package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewSelector.d.ts +46 -0
- package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewSelector.js +68 -5
- package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewState.d.ts +21 -1
- package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewState.js +24 -1
- package/lib/view/invoicing/editInvoicingCouponDetailView/fetchInvoicingCouponDetailPageEpic.d.ts +9 -5
- package/lib/view/invoicing/editInvoicingCouponDetailView/fetchInvoicingCouponDetailPageEpic.js +22 -4
- package/lib/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.js +18 -4
- package/lib/view/invoicing/editInvoicingCouponDetailView/seedInvoicingCouponFormDraftEpic.d.ts +10 -6
- package/lib/view/invoicing/editInvoicingCouponDetailView/seedInvoicingCouponFormDraftEpic.js +28 -5
- package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.d.ts +5 -1
- package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.js +29 -13
- package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.d.ts +16 -7
- package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.js +37 -2
- package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewState.d.ts +8 -1
- package/lib/view/invoicing/editInvoicingCustomerDetailView/initializeInvoicingCustomerAddressEpic.d.ts +15 -10
- package/lib/view/invoicing/editInvoicingCustomerDetailView/initializeInvoicingCustomerAddressEpic.js +42 -16
- package/lib/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.js +1 -1
- package/lib/view/invoicing/editInvoicingCustomerDetailView/seedInvoicingCustomerFormDraftEpic.d.ts +10 -6
- package/lib/view/invoicing/editInvoicingCustomerDetailView/seedInvoicingCustomerFormDraftEpic.js +28 -5
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.d.ts +5 -1
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.js +31 -15
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.d.ts +79 -12
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +145 -9
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewState.d.ts +8 -1
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionFormPageEpic.js +4 -1
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/initializeInvoicingSubscriptionAddressEpic.d.ts +15 -11
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/initializeInvoicingSubscriptionAddressEpic.js +42 -16
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +1 -1
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/seedInvoicingSubscriptionFormDraftEpic.d.ts +10 -6
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/seedInvoicingSubscriptionFormDraftEpic.js +28 -7
- package/lib/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewSelector.d.ts +10 -0
- package/lib/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewSelector.js +12 -0
- package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewReducer.d.ts +1 -1
- package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewState.d.ts +2 -2
- package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewState.js +6 -6
- package/lib/view/invoicing/invoicingFormDraft.d.ts +63 -0
- package/lib/view/invoicing/invoicingFormDraft.js +86 -0
- package/lib/view/invoicing/invoicingQboView/invoicingQboViewSelector.d.ts +49 -0
- package/lib/view/invoicing/invoicingQboView/invoicingQboViewSelector.js +59 -10
- package/lib/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewReducer.d.ts +1 -1
- package/lib/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewState.d.ts +1 -1
- package/lib/view/recommendation/recommendationPayload.d.ts +1 -0
- package/lib/view/recommendation/recommendationPayload.js +5 -0
- package/lib/view/recommendation/recommendationReducer.d.ts +7 -1
- package/lib/view/recommendation/recommendationReducer.js +28 -8
- package/lib/view/recommendation/recommendationSelector.d.ts +2 -0
- package/lib/view/recommendation/recommendationSelector.js +3 -1
- package/lib/view/recommendation/recommendationState.d.ts +2 -1
- package/lib/view/recommendation/submitRecommendationFeedbackEpic.d.ts +2 -2
- package/lib/view/recommendation/submitRecommendationFeedbackEpic.js +18 -6
- package/lib/view/spendManagement/billPay/billDetailView/billDetailViewPayload.d.ts +19 -0
- package/lib/view/spendManagement/billPay/billDetailView/billDetailViewReducer.d.ts +10 -1
- package/lib/view/spendManagement/billPay/billDetailView/billDetailViewReducer.js +88 -2
- package/lib/view/spendManagement/billPay/billDetailView/billDetailViewSelector.d.ts +1 -0
- package/lib/view/spendManagement/billPay/billDetailView/billDetailViewSelector.js +1 -0
- package/lib/view/spendManagement/billPay/billDetailView/billDetailViewState.d.ts +7 -0
- package/lib/view/spendManagement/billPay/billDetailView/billDetailViewState.js +4 -0
- package/lib/view/spendManagement/billPay/billDetailView/epics/fetchBillRecommendationsEpic.d.ts +37 -0
- package/lib/view/spendManagement/billPay/billDetailView/epics/fetchBillRecommendationsEpic.js +104 -0
- package/lib/view/spendManagement/billPay/billDetailView/epics/fetchRecommendationsForBill.d.ts +24 -0
- package/lib/view/spendManagement/billPay/billDetailView/epics/fetchRecommendationsForBill.js +92 -0
- package/lib/view/spendManagement/billPay/editBillView/editBillViewReducer.d.ts +4 -1
- package/lib/view/spendManagement/billPay/editBillView/editBillViewReducer.js +19 -2
- package/lib/view/spendManagement/billPay/editBillView/editBillViewSelector.d.ts +1 -0
- package/lib/view/spendManagement/billPay/editBillView/editBillViewSelector.js +3 -0
- package/lib/view/spendManagement/billPay/editBillView/epics/applyBillRecommendationsToLocalStoreEpic.d.ts +18 -0
- package/lib/view/spendManagement/billPay/editBillView/epics/applyBillRecommendationsToLocalStoreEpic.js +102 -0
- package/package.json +2 -2
|
@@ -3,7 +3,6 @@ import { toClassBasePayload } from '../../../entity/class/classPayload';
|
|
|
3
3
|
import { blankInvoicingCatalogSubFamily, isTieredCatalogPricingModel, } from './invoicingCatalogItemFormHelper';
|
|
4
4
|
const CURRENCY_CODE = 'USD';
|
|
5
5
|
const trimOrUndefined = (value) => value.trim() !== '' ? value.trim() : undefined;
|
|
6
|
-
const trimOrNull = (value) => value.trim() !== '' ? value.trim() : null;
|
|
7
6
|
const toProductCode = (value) => value
|
|
8
7
|
.trim()
|
|
9
8
|
.toLowerCase()
|
|
@@ -91,13 +90,24 @@ const buildSubFamilyPayload = (localData, subFamily, clearEmptyValues, lookup) =
|
|
|
91
90
|
/** `unit` is the item's unit of measure, not a currency; `CURRENCY_CODE` above prices Price points. */
|
|
92
91
|
const DEFAULT_CATALOG_ITEM_UNIT = 'USD';
|
|
93
92
|
/**
|
|
94
|
-
* Build the parent catalog-item request body from the form draft.
|
|
95
|
-
*
|
|
93
|
+
* Build the parent catalog-item request body from the form draft. Every field
|
|
94
|
+
* the form holds is sent, so a value cleared on the form is cleared on the
|
|
95
|
+
* item: the service reads an absent key as "leave it alone", which is why
|
|
96
|
+
* `description` travels as an empty string and `applicable_items` as an empty
|
|
97
|
+
* list rather than being dropped. Fields the form does not collect
|
|
98
|
+
* (`external_name`, `item_family_id`, `metadata`, …) stay off the body so an
|
|
99
|
+
* edit cannot overwrite them with a value nobody chose.
|
|
96
100
|
*/
|
|
97
101
|
export const localDataToSaveInvoicingCatalogItemPayload = (localData, isCreate, lookup) => {
|
|
98
|
-
const description = trimOrUndefined(localData.description);
|
|
99
102
|
const productCode = trimOrUndefined(localData.productCode) ?? toProductCode(localData.name);
|
|
100
103
|
const itemPayload = {
|
|
104
|
+
// `applicable_items` only means anything while the item is restricted;
|
|
105
|
+
// sending the empty list otherwise is what clears a restriction that was
|
|
106
|
+
// just lifted.
|
|
107
|
+
applicable_items: localData.itemApplicability === 'restricted'
|
|
108
|
+
? localData.applicableItems
|
|
109
|
+
: [],
|
|
110
|
+
description: trimOrUndefined(localData.description) ?? '',
|
|
101
111
|
enabled_for_checkout: true,
|
|
102
112
|
enabled_in_portal: true,
|
|
103
113
|
is_giftable: false,
|
|
@@ -107,41 +117,63 @@ export const localDataToSaveInvoicingCatalogItemPayload = (localData, isCreate,
|
|
|
107
117
|
name: localData.name.trim(),
|
|
108
118
|
product_code: productCode,
|
|
109
119
|
type: localData.catalogType,
|
|
120
|
+
// The form no longer collects a unit; an item that already has one keeps it.
|
|
121
|
+
unit: trimOrUndefined(localData.unit) ?? DEFAULT_CATALOG_ITEM_UNIT,
|
|
110
122
|
};
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (localData.itemApplicability === 'restricted' &&
|
|
118
|
-
localData.applicableItems.length > 0) {
|
|
119
|
-
itemPayload.applicable_items = localData.applicableItems;
|
|
123
|
+
// The external id is assigned by the billing service and shown read-only, so
|
|
124
|
+
// it is echoed back only when the item already has one: sending the blank a
|
|
125
|
+
// create starts with would clear an id assigned in the meantime.
|
|
126
|
+
const externalId = trimOrUndefined(localData.externalId);
|
|
127
|
+
if (externalId != null) {
|
|
128
|
+
itemPayload.external_id = externalId;
|
|
120
129
|
}
|
|
121
130
|
const subFamilies = localData.subFamilies.length > 0
|
|
122
131
|
? localData.subFamilies
|
|
123
132
|
: [blankInvoicingCatalogSubFamily()];
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
133
|
+
if (isCreate) {
|
|
134
|
+
itemPayload.sub_families = subFamilies.map((subFamily) => buildSubFamilyPayload(localData, subFamily, false, lookup));
|
|
135
|
+
return itemPayload;
|
|
136
|
+
}
|
|
137
|
+
// Only price points the service has already stored travel in an update: it
|
|
138
|
+
// addresses them by id, and a blank one has nothing to address. A price point
|
|
139
|
+
// added on the form is created by its own request once the item is saved,
|
|
140
|
+
// rather than riding along as a row the update cannot place.
|
|
141
|
+
itemPayload.sub_families = subFamilies
|
|
142
|
+
.filter((subFamily) => subFamily.id !== '')
|
|
143
|
+
.map((subFamily) => {
|
|
144
|
+
// Edits send the same body a create does. The narrower hand-built one
|
|
145
|
+
// this replaces had to be kept in step by hand and was not: it dropped
|
|
146
|
+
// `accounting_detail` once, and `billing_cycles`, `price`,
|
|
147
|
+
// `pricing_model`, `period`, the trial fields, `proration_type`,
|
|
148
|
+
// `free_quantity`, `is_taxable` and `tiers` were never added at all, so
|
|
149
|
+
// editing any of them looked saved and changed nothing.
|
|
150
|
+
const detail = buildSubFamilyPayload(localData, subFamily, true, lookup);
|
|
151
|
+
// `null` rather than omitted when empty: an absent key on an update
|
|
152
|
+
// reads as "leave it alone", which would make a cleared mapping
|
|
153
|
+
// impossible to save — the same reason `description` and
|
|
154
|
+
// `invoice_notes` are cleared rather than dropped above.
|
|
155
|
+
detail.accounting_detail = detail.accounting_detail ?? null;
|
|
156
|
+
// Never blank: the filter above leaves only price points the service has
|
|
157
|
+
// already stored, which is the only kind an update can address.
|
|
158
|
+
return { ...detail, id: subFamily.id };
|
|
159
|
+
});
|
|
146
160
|
return itemPayload;
|
|
147
161
|
};
|
|
162
|
+
/**
|
|
163
|
+
* The bodies that create the price points added on an edit form, one per
|
|
164
|
+
* request to `/catalog/products`.
|
|
165
|
+
*
|
|
166
|
+
* An update cannot carry them: it addresses price points by id, and these have
|
|
167
|
+
* none until the service assigns one. They are bound to their item by
|
|
168
|
+
* `item_id`, so this needs the saved item's id and runs after the item itself
|
|
169
|
+
* is saved.
|
|
170
|
+
*
|
|
171
|
+
* Empty values are left off rather than sent as `null`: there is nothing on a
|
|
172
|
+
* price point that does not exist yet to clear.
|
|
173
|
+
*/
|
|
174
|
+
export const localDataToCreateInvoicingCatalogProductPayloads = (localData, itemId, lookup) => localData.subFamilies
|
|
175
|
+
.filter((subFamily) => subFamily.id === '')
|
|
176
|
+
.map((subFamily) => ({
|
|
177
|
+
...buildSubFamilyPayload(localData, subFamily, false, lookup),
|
|
178
|
+
item_id: itemId,
|
|
179
|
+
}));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createSlice } from '@reduxjs/toolkit';
|
|
2
2
|
import { clearAll } from '../../../rootActions';
|
|
3
|
+
import { applyInvoicingFormDraftEdit, applyInvoicingFormDraftFailure, applyInvoicingFormDraftSaved, applyInvoicingFormDraftSeed, } from '../invoicingFormDraft';
|
|
3
4
|
import { initialInvoicingCatalogItemFormLocalData } from './invoicingCatalogItemFormHelper';
|
|
4
5
|
export const initialState = {
|
|
5
6
|
fetchState: 'Not-Started',
|
|
@@ -21,7 +22,7 @@ const editInvoicingCatalogItemDetailView = createSlice({
|
|
|
21
22
|
updateInvoicingCatalogItemFormDraft(draft, action) {
|
|
22
23
|
const { id, localData } = action.payload;
|
|
23
24
|
if (id != null) {
|
|
24
|
-
draft.editDraftById
|
|
25
|
+
applyInvoicingFormDraftEdit(draft.editDraftById, id, localData);
|
|
25
26
|
}
|
|
26
27
|
else {
|
|
27
28
|
draft.newItemDraft = localData;
|
|
@@ -42,6 +43,9 @@ const editInvoicingCatalogItemDetailView = createSlice({
|
|
|
42
43
|
draft.error = undefined;
|
|
43
44
|
draft.savedItemId = action.payload.itemId;
|
|
44
45
|
draft.savedKind = action.payload.kind;
|
|
46
|
+
// An edit is refetched on success; keep the saved values on screen until
|
|
47
|
+
// that lands, and let it replace them.
|
|
48
|
+
applyInvoicingFormDraftSaved(draft.editDraftById, action.payload.itemId);
|
|
45
49
|
},
|
|
46
50
|
submitInvoicingCatalogItemFormFailure(draft, action) {
|
|
47
51
|
draft.fetchState = 'Error';
|
|
@@ -64,6 +68,7 @@ const editInvoicingCatalogItemDetailView = createSlice({
|
|
|
64
68
|
fetchState: 'Error',
|
|
65
69
|
error: action.payload.error,
|
|
66
70
|
};
|
|
71
|
+
applyInvoicingFormDraftFailure(draft.editDraftById, action.payload.id, initialInvoicingCatalogItemFormLocalData);
|
|
67
72
|
},
|
|
68
73
|
fetchInvoicingCatalogItemDetailPage: {
|
|
69
74
|
reducer() {
|
|
@@ -79,14 +84,10 @@ const editInvoicingCatalogItemDetailView = createSlice({
|
|
|
79
84
|
};
|
|
80
85
|
},
|
|
81
86
|
},
|
|
82
|
-
/**
|
|
83
|
-
* Seed the draft from the loaded record, once. Skipped when a draft already
|
|
84
|
-
* exists so a late-arriving refetch cannot overwrite in-progress edits.
|
|
85
|
-
*/
|
|
87
|
+
/** Fill the draft from the loaded record — see `applyInvoicingFormDraftSeed`. */
|
|
86
88
|
seedInvoicingCatalogItemFormDraft(draft, action) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
89
|
+
const { id, localData } = action.payload;
|
|
90
|
+
applyInvoicingFormDraftSeed(draft.editDraftById, id, localData);
|
|
90
91
|
},
|
|
91
92
|
runInvoicingCatalogPlanAction(draft, action) {
|
|
92
93
|
draft.actionStateById[action.payload.id] = {
|
|
@@ -125,13 +126,23 @@ const editInvoicingCatalogItemDetailView = createSlice({
|
|
|
125
126
|
error: action.payload.error,
|
|
126
127
|
};
|
|
127
128
|
},
|
|
128
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Discard a form's draft — the catalog item's own when `id` is given,
|
|
131
|
+
* the create draft otherwise. Scoped to the one form on purpose: another
|
|
132
|
+
* catalog item's half-finished edit is not this form's to throw away.
|
|
133
|
+
* Clearing every draft at once is what `clearAll` is for.
|
|
134
|
+
*/
|
|
135
|
+
resetEditInvoicingCatalogItemDetailView(draft, action) {
|
|
129
136
|
draft.fetchState = 'Not-Started';
|
|
130
137
|
draft.error = undefined;
|
|
131
138
|
draft.savedItemId = undefined;
|
|
132
139
|
draft.savedKind = undefined;
|
|
133
|
-
|
|
134
|
-
|
|
140
|
+
const id = action.payload?.id;
|
|
141
|
+
if (id == null) {
|
|
142
|
+
draft.newItemDraft = { ...initialInvoicingCatalogItemFormLocalData };
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
delete draft.editDraftById[id];
|
|
135
146
|
},
|
|
136
147
|
},
|
|
137
148
|
extraReducers: (builder) => {
|
|
@@ -2,7 +2,8 @@ import { createSelector } from '@reduxjs/toolkit';
|
|
|
2
2
|
import { reduceAllFetchState } from '../../../commonStateTypes/reduceFetchState';
|
|
3
3
|
import { getInvoicingPlanById, getInvoicingPlansByIds, } from '../../../entity/invoicing/plan/planSelector';
|
|
4
4
|
import { getInvoicingConfigView, invoicingFieldHelpForGroup, } from '../invoicingConfigView/invoicingConfigViewSelector';
|
|
5
|
-
import {
|
|
5
|
+
import { getInvoicingFormDraftLocalData, isInvoicingFormDraftPending, } from '../invoicingFormDraft';
|
|
6
|
+
import { INVOICING_DEFAULT_INCOME_ACCOUNT_TYPES, INVOICING_INCOME_ACCOUNT_MAPPING_SLOT, NO_INVOICING_ACCOUNT_HIERARCHY, NO_INVOICING_ACCOUNT_IDS, NO_INVOICING_CLASS_HIERARCHY, getInvoicingQboAccountOptions, getInvoicingQboView, invoicingQboAccountIdsForMappingSlot, isInvoicingAccountingPickerDataLoading, isInvoicingQboConnected, } from '../invoicingQboView/invoicingQboViewSelector';
|
|
6
7
|
import { INVOICING_CATALOG_APPLICABILITY_VALUES, INVOICING_CATALOG_PRICING_MODEL_VALUES, INVOICING_CATALOG_PRORATION_VALUES, INVOICING_CATALOG_TIER_TYPE_VALUES, initialInvoicingCatalogItemFormLocalData, } from './invoicingCatalogItemFormHelper';
|
|
7
8
|
export const getEditInvoicingCatalogItemDetailViewState = (state) => ({
|
|
8
9
|
fetchState: state.editInvoicingCatalogItemDetailViewState.fetchState,
|
|
@@ -43,6 +44,21 @@ const NOT_STARTED = {
|
|
|
43
44
|
error: undefined,
|
|
44
45
|
fetchState: 'Not-Started',
|
|
45
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* The QuickBooks ids a price point's income account may be picked from.
|
|
49
|
+
*
|
|
50
|
+
* Memoized, and collapsing to one shared empty array while QBO is disconnected:
|
|
51
|
+
* the pages turn this into their picker options and re-seed their form defaults
|
|
52
|
+
* when those change, so a fresh array on every store change would reset the form
|
|
53
|
+
* under the user — including mid-save.
|
|
54
|
+
*/
|
|
55
|
+
const getInvoicingCatalogIncomeAccountIds = createSelector([
|
|
56
|
+
getInvoicingQboAccountOptions,
|
|
57
|
+
(state) => getInvoicingConfigView(state).qboAccountMappingKindOptions,
|
|
58
|
+
isInvoicingQboConnected,
|
|
59
|
+
], (accounts, qboAccountMappingKindOptions, isQboConnected) => isQboConnected
|
|
60
|
+
? invoicingQboAccountIdsForMappingSlot(accounts, qboAccountMappingKindOptions, INVOICING_INCOME_ACCOUNT_MAPPING_SLOT, INVOICING_DEFAULT_INCOME_ACCOUNT_TYPES)
|
|
61
|
+
: NO_INVOICING_ACCOUNT_IDS);
|
|
46
62
|
export const getInvoicingCatalogItemFormView = (state, id) => {
|
|
47
63
|
const { editDraftById, error, fetchState, newItemDraft, savedItemId, savedKind, } = state.editInvoicingCatalogItemDetailViewState;
|
|
48
64
|
const plan = id != null ? getInvoicingPlanById(state.invoicingPlanState, id) : undefined;
|
|
@@ -52,7 +68,7 @@ export const getInvoicingCatalogItemFormView = (state, id) => {
|
|
|
52
68
|
// lands. Until then the page is showing its skeleton, so the blank shape here
|
|
53
69
|
// is never rendered.
|
|
54
70
|
const localData = id != null
|
|
55
|
-
? (editDraftById
|
|
71
|
+
? getInvoicingFormDraftLocalData(editDraftById, id, initialInvoicingCatalogItemFormLocalData)
|
|
56
72
|
: newItemDraft;
|
|
57
73
|
const allApplicableItemOptions = getInvoicingApplicableCatalogItemOptions(state);
|
|
58
74
|
const applicableItemOptions = id != null
|
|
@@ -90,9 +106,12 @@ export const getInvoicingCatalogItemFormView = (state, id) => {
|
|
|
90
106
|
catalogProductLifecycleActionOptions,
|
|
91
107
|
recordState,
|
|
92
108
|
fieldHelp: invoicingFieldHelpForGroup(state, 'catalog'),
|
|
109
|
+
isFormLoading: isInvoicingAccountingPickerDataLoading(state) ||
|
|
110
|
+
isInvoicingFormDraftPending(editDraftById, id),
|
|
93
111
|
isQboPickerDataLoading: isInvoicingAccountingPickerDataLoading(state),
|
|
94
|
-
qboAccountHierarchy: qboView?.nestedAccountHierarchy ??
|
|
95
|
-
qboClassHierarchy: qboView?.nestedClassHierarchy ??
|
|
112
|
+
qboAccountHierarchy: qboView?.nestedAccountHierarchy ?? NO_INVOICING_ACCOUNT_HIERARCHY,
|
|
113
|
+
qboClassHierarchy: qboView?.nestedClassHierarchy ?? NO_INVOICING_CLASS_HIERARCHY,
|
|
114
|
+
qboIncomeAccountIds: getInvoicingCatalogIncomeAccountIds(state),
|
|
96
115
|
applicabilityValues: INVOICING_CATALOG_APPLICABILITY_VALUES,
|
|
97
116
|
applicableItemOptions,
|
|
98
117
|
localData,
|
package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/invoicingCatalogItemFormHelper.js
CHANGED
|
@@ -3,13 +3,7 @@
|
|
|
3
3
|
* for pricing model, tier type, proration or applicability yet, so these stay
|
|
4
4
|
* here until it does. Everything else on this form comes from `/1.0/config`.
|
|
5
5
|
*/
|
|
6
|
-
export const INVOICING_CATALOG_PRICING_MODEL_VALUES = [
|
|
7
|
-
'flat_fee',
|
|
8
|
-
'per_unit',
|
|
9
|
-
'tiered',
|
|
10
|
-
'volume',
|
|
11
|
-
'stairstep',
|
|
12
|
-
];
|
|
6
|
+
export const INVOICING_CATALOG_PRICING_MODEL_VALUES = ['flat_fee', 'per_unit', 'tiered', 'volume', 'stairstep'];
|
|
13
7
|
export const INVOICING_CATALOG_TIER_TYPE_VALUES = [
|
|
14
8
|
'per_unit',
|
|
15
9
|
'flat_fee',
|
package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { from } from 'rxjs';
|
|
1
|
+
import { forkJoin, from } from 'rxjs';
|
|
2
2
|
import { catchError, filter, mergeMap } from 'rxjs/operators';
|
|
3
3
|
import { mergeInvoicingPlanSubFamily, updateInvoicingPlans, } from '../../../entity/invoicing/plan/planReducer';
|
|
4
4
|
import { updateInvoicingProducts } from '../../../entity/invoicing/product/productReducer';
|
|
@@ -7,7 +7,7 @@ import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
|
|
|
7
7
|
import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
|
|
8
8
|
import { addInvoicingCatalogItem } from '../invoicingCatalogListView/invoicingCatalogListViewReducer';
|
|
9
9
|
import { getInvoicingCatalogAccountingLookup } from '../invoicingQboView/invoicingQboViewSelector';
|
|
10
|
-
import { localDataToSaveInvoicingCatalogItemPayload, } from './editInvoicingCatalogItemDetailViewPayload';
|
|
10
|
+
import { localDataToCreateInvoicingCatalogProductPayloads, localDataToSaveInvoicingCatalogItemPayload, } from './editInvoicingCatalogItemDetailViewPayload';
|
|
11
11
|
import { submitInvoicingCatalogItemForm, submitInvoicingCatalogItemFormFailure, submitInvoicingCatalogItemFormSuccess, } from './editInvoicingCatalogItemDetailViewReducer';
|
|
12
12
|
export const saveInvoicingCatalogItemEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter((action) => submitInvoicingCatalogItemForm.match(action)), mergeMap((action) => {
|
|
13
13
|
const { id } = action.payload;
|
|
@@ -17,8 +17,9 @@ export const saveInvoicingCatalogItemEpic = (actions$, state$, zeniAPI) => actio
|
|
|
17
17
|
// class become the records the service stores. Price points ride along
|
|
18
18
|
// nested, so the detail page and the create/edit form share one request.
|
|
19
19
|
const viewState = state.editInvoicingCatalogItemDetailViewState;
|
|
20
|
-
const localData = (id != null
|
|
21
|
-
viewState.
|
|
20
|
+
const localData = (id != null
|
|
21
|
+
? viewState.editDraftById[id]?.localData
|
|
22
|
+
: viewState.newItemDraft) ?? viewState.newItemDraft;
|
|
22
23
|
const data = localDataToSaveInvoicingCatalogItemPayload(localData, id == null, accountingLookup);
|
|
23
24
|
const baseUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0`;
|
|
24
25
|
const path = id != null
|
|
@@ -80,7 +81,20 @@ export const saveInvoicingCatalogItemEpic = (actions$, state$, zeniAPI) => actio
|
|
|
80
81
|
}));
|
|
81
82
|
return resultActions;
|
|
82
83
|
};
|
|
83
|
-
|
|
84
|
+
// Price points added on an edit form cannot ride in the update:
|
|
85
|
+
// it addresses them by id and they have none yet. They are created
|
|
86
|
+
// against the item that was just saved, and their responses join
|
|
87
|
+
// the same success — one of them failing is the whole save
|
|
88
|
+
// failing, since the item on screen would otherwise claim price
|
|
89
|
+
// points the service never stored. A create needs none of this:
|
|
90
|
+
// its price points are nested in the item body.
|
|
91
|
+
const newProductPayloads = id != null
|
|
92
|
+
? localDataToCreateInvoicingCatalogProductPayloads(localData, savedItem.id, accountingLookup)
|
|
93
|
+
: [];
|
|
94
|
+
if (newProductPayloads.length === 0) {
|
|
95
|
+
return completeSave([]);
|
|
96
|
+
}
|
|
97
|
+
return forkJoin(newProductPayloads.map((productPayload) => zeniAPI.postAndGetJSON(`${baseUrl}/catalog/products`, productPayload))).pipe(mergeMap((productResponses) => completeSave(productResponses)));
|
|
84
98
|
}
|
|
85
99
|
return from([
|
|
86
100
|
submitInvoicingCatalogItemFormFailure(response.status),
|
|
@@ -1,16 +1,39 @@
|
|
|
1
1
|
import { EMPTY, of } from 'rxjs';
|
|
2
2
|
import { filter, mergeMap, withLatestFrom } from 'rxjs/operators';
|
|
3
3
|
import { getInvoicingPlanById } from '../../../entity/invoicing/plan/planSelector';
|
|
4
|
-
import { seedInvoicingCatalogItemFormDraft, updateInvoicingCatalogItemDetail, } from './editInvoicingCatalogItemDetailViewReducer';
|
|
4
|
+
import { fetchInvoicingCatalogItemDetailPage, seedInvoicingCatalogItemFormDraft, updateInvoicingCatalogItemDetail, } from './editInvoicingCatalogItemDetailViewReducer';
|
|
5
5
|
import { entityToInvoicingCatalogItemFormLocalData } from './invoicingCatalogItemFormHelper';
|
|
6
6
|
/**
|
|
7
|
-
* Seeds the edit draft from the record the
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* Seeds the edit draft from the record, so the form has its values before it
|
|
8
|
+
* mounts. The reducer ignores a seed once the draft holds the record, which is
|
|
9
|
+
* what stops a background refetch from overwriting edits in progress.
|
|
10
|
+
*
|
|
11
|
+
* Opening the page counts as a trigger, not just the detail response: the page
|
|
12
|
+
* fetch is cache-guarded, so re-opening a form for a record already loaded this
|
|
13
|
+
* session issues no request at all — and a draft cleared in between (by a save
|
|
14
|
+
* or a cancel) would have nothing left to seed it.
|
|
11
15
|
*/
|
|
12
|
-
export const seedInvoicingCatalogItemFormDraftEpic = (actions$, state$) => actions$.pipe(filter(updateInvoicingCatalogItemDetail.match
|
|
13
|
-
|
|
16
|
+
export const seedInvoicingCatalogItemFormDraftEpic = (actions$, state$) => actions$.pipe(filter((action) => updateInvoicingCatalogItemDetail.match(action) ||
|
|
17
|
+
fetchInvoicingCatalogItemDetailPage.match(action)), withLatestFrom(state$), mergeMap(([action, state]) => {
|
|
18
|
+
const id = action.payload.id;
|
|
19
|
+
if (id == null) {
|
|
20
|
+
return EMPTY;
|
|
21
|
+
}
|
|
22
|
+
// The entity alone does not say the whole record is loaded — the list
|
|
23
|
+
// endpoints merge their own list-shaped rows into the same store. Only a
|
|
24
|
+
// completed detail load does, which is also exactly when the page's
|
|
25
|
+
// cache-guarded fetch issues no request and this is the sole seed.
|
|
26
|
+
// A forced refetch is the caller asking for fresh data: the response will
|
|
27
|
+
// seed, and seeding first from the entity as it stands would mark the
|
|
28
|
+
// draft loaded and lock that response out.
|
|
29
|
+
if (fetchInvoicingCatalogItemDetailPage.match(action) &&
|
|
30
|
+
(action.payload.cacheOverride === true ||
|
|
31
|
+
state.editInvoicingCatalogItemDetailViewState.recordStateById[id]
|
|
32
|
+
?.fetchState !== 'Completed')) {
|
|
33
|
+
return EMPTY;
|
|
34
|
+
}
|
|
35
|
+
// Nothing to seed from on a cold open; the detail response triggers this
|
|
36
|
+
// again once it lands.
|
|
14
37
|
const plan = getInvoicingPlanById(state.invoicingPlanState, id);
|
|
15
38
|
if (plan == null) {
|
|
16
39
|
return EMPTY;
|
package/lib/esm/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewPayload.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import { toAccountPayload } from '../../../entity/account/accountPayload';
|
|
1
2
|
/**
|
|
2
3
|
* Build the create/update discount request body from the form draft (snake_case
|
|
3
4
|
* matching the `/discounts` schema). On create the coupon id is part of the
|
|
4
5
|
* body; on edit it is the URL param, so it is omitted here.
|
|
6
|
+
*
|
|
7
|
+
* `accountByQboId` resolves the discounts-account picker back to the account
|
|
8
|
+
* record the body carries, the same way the catalog and QBO settings bodies do.
|
|
5
9
|
*/
|
|
6
|
-
export const localDataToSaveInvoicingCouponPayload = (localData, isCreate) => {
|
|
10
|
+
export const localDataToSaveInvoicingCouponPayload = (localData, isCreate, accountByQboId) => {
|
|
7
11
|
const data = {
|
|
8
12
|
apply_on: localData.applyOn,
|
|
9
13
|
discount_type: localData.discountType,
|
|
@@ -54,5 +58,16 @@ export const localDataToSaveInvoicingCouponPayload = (localData, isCreate) => {
|
|
|
54
58
|
if (invoiceNotes !== '') {
|
|
55
59
|
data.invoice_notes = invoiceNotes;
|
|
56
60
|
}
|
|
61
|
+
// Omitted rather than sent as `null` when there is none: the service reads a
|
|
62
|
+
// blank discounts account as "not given" and leaves what it has, which is
|
|
63
|
+
// what keeps a form that never showed the picker — QBO disconnected — from
|
|
64
|
+
// clearing an account set elsewhere.
|
|
65
|
+
const discountsAccountId = localData.discountsAccountId.trim();
|
|
66
|
+
const discountsAccount = discountsAccountId !== ''
|
|
67
|
+
? accountByQboId?.(discountsAccountId)
|
|
68
|
+
: undefined;
|
|
69
|
+
if (discountsAccount != null) {
|
|
70
|
+
data.discounts_account = toAccountPayload(discountsAccount);
|
|
71
|
+
}
|
|
57
72
|
return data;
|
|
58
73
|
};
|
package/lib/esm/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewReducer.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createSlice } from '@reduxjs/toolkit';
|
|
2
2
|
import { clearAll } from '../../../rootActions';
|
|
3
|
+
import { applyInvoicingFormDraftEdit, applyInvoicingFormDraftFailure, applyInvoicingFormDraftSaved, applyInvoicingFormDraftSeed, } from '../invoicingFormDraft';
|
|
3
4
|
import { initialInvoicingCouponFormLocalData, } from './editInvoicingCouponDetailViewState';
|
|
4
5
|
export const initialState = {
|
|
5
6
|
fetchState: 'Not-Started',
|
|
@@ -20,7 +21,7 @@ const editInvoicingCouponDetailView = createSlice({
|
|
|
20
21
|
updateInvoicingCouponFormDraft(draft, action) {
|
|
21
22
|
const { id, localData } = action.payload;
|
|
22
23
|
if (id != null) {
|
|
23
|
-
draft.editDraftById
|
|
24
|
+
applyInvoicingFormDraftEdit(draft.editDraftById, id, localData);
|
|
24
25
|
}
|
|
25
26
|
else {
|
|
26
27
|
draft.newCouponDraft = localData;
|
|
@@ -50,6 +51,9 @@ const editInvoicingCouponDetailView = createSlice({
|
|
|
50
51
|
draft.fetchState = 'Completed';
|
|
51
52
|
draft.error = undefined;
|
|
52
53
|
draft.savedCouponId = action.payload.couponId;
|
|
54
|
+
// An edit is refetched on success; keep the saved values on screen until
|
|
55
|
+
// that lands, and let it replace them.
|
|
56
|
+
applyInvoicingFormDraftSaved(draft.editDraftById, action.payload.couponId);
|
|
53
57
|
},
|
|
54
58
|
submitInvoicingCouponFormFailure(draft, action) {
|
|
55
59
|
draft.fetchState = 'Error';
|
|
@@ -85,15 +89,12 @@ const editInvoicingCouponDetailView = createSlice({
|
|
|
85
89
|
fetchState: 'Error',
|
|
86
90
|
error: action.payload.status,
|
|
87
91
|
};
|
|
92
|
+
applyInvoicingFormDraftFailure(draft.editDraftById, action.payload.id, initialInvoicingCouponFormLocalData);
|
|
88
93
|
},
|
|
89
|
-
/**
|
|
90
|
-
* Seed the draft from the loaded record, once. Skipped when a draft already
|
|
91
|
-
* exists so a late refetch cannot overwrite in-progress edits.
|
|
92
|
-
*/
|
|
94
|
+
/** Fill the draft from the loaded record — see `applyInvoicingFormDraftSeed`. */
|
|
93
95
|
seedInvoicingCouponFormDraft(draft, action) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
96
|
+
const { id, localData } = action.payload;
|
|
97
|
+
applyInvoicingFormDraftSeed(draft.editDraftById, id, localData);
|
|
97
98
|
},
|
|
98
99
|
runInvoicingDiscountAction(draft, action) {
|
|
99
100
|
draft.actionStateById[action.payload.id] = {
|
|
@@ -113,12 +114,22 @@ const editInvoicingCouponDetailView = createSlice({
|
|
|
113
114
|
error: action.payload.error,
|
|
114
115
|
};
|
|
115
116
|
},
|
|
116
|
-
|
|
117
|
+
/**
|
|
118
|
+
* Discard a form's draft — the coupon's own when `id` is given,
|
|
119
|
+
* the create draft otherwise. Scoped to the one form on purpose: another
|
|
120
|
+
* coupon's half-finished edit is not this form's to throw away.
|
|
121
|
+
* Clearing every draft at once is what `clearAll` is for.
|
|
122
|
+
*/
|
|
123
|
+
resetEditInvoicingCouponDetailView(draft, action) {
|
|
117
124
|
draft.fetchState = 'Not-Started';
|
|
118
125
|
draft.error = undefined;
|
|
119
126
|
draft.savedCouponId = undefined;
|
|
120
|
-
|
|
121
|
-
|
|
127
|
+
const id = action.payload?.id;
|
|
128
|
+
if (id == null) {
|
|
129
|
+
draft.newCouponDraft = { ...initialInvoicingCouponFormLocalData };
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
delete draft.editDraftById[id];
|
|
122
133
|
},
|
|
123
134
|
},
|
|
124
135
|
extraReducers: (builder) => {
|
|
@@ -1,13 +1,32 @@
|
|
|
1
|
+
import { createSelector } from '@reduxjs/toolkit';
|
|
1
2
|
import { reduceAllFetchState } from '../../../commonStateTypes/reduceFetchState';
|
|
2
3
|
import { getInvoicingCouponById } from '../../../entity/invoicing/coupon/couponSelector';
|
|
3
4
|
import { toInvoicingFormOption, } from '../../../entity/invoicing/invoicingCommonPayload';
|
|
4
5
|
import { getInvoicingConfigView, invoicingFieldHelpForGroup, } from '../invoicingConfigView/invoicingConfigViewSelector';
|
|
5
|
-
import {
|
|
6
|
+
import { getInvoicingFormDraftLocalData, isInvoicingFormDraftPending, } from '../invoicingFormDraft';
|
|
7
|
+
import { INVOICING_DEFAULT_INCOME_ACCOUNT_TYPES, INVOICING_DISCOUNTS_ACCOUNT_MAPPING_SLOT, NO_INVOICING_ACCOUNT_HIERARCHY, NO_INVOICING_ACCOUNT_IDS, getInvoicingQboAccountOptions, getInvoicingQboView, invoicingQboAccountByQboId, invoicingQboAccountIdsForMappingSlot, isInvoicingAccountingPickerDataLoading, isInvoicingQboConnected, } from '../invoicingQboView/invoicingQboViewSelector';
|
|
8
|
+
import { getInvoicingSettings, getInvoicingSettingsFetchState, } from '../settingsView/settingsViewSelector';
|
|
9
|
+
import { initialInvoicingCouponFormLocalData, invoicingCouponDiscountsAccountIdForCreate, } from './editInvoicingCouponDetailViewState';
|
|
6
10
|
/**
|
|
7
11
|
* Bundled view for the discount create/edit form: the save fetch state/error,
|
|
8
12
|
* the id of the coupon last saved, and — when a `couponId` is passed (edit
|
|
9
13
|
* mode) — the existing coupon entity to prefill the form.
|
|
10
14
|
*/
|
|
15
|
+
/**
|
|
16
|
+
* The QuickBooks ids the discounts account may be picked from.
|
|
17
|
+
*
|
|
18
|
+
* Memoized, and collapsing to one shared empty array while QBO is disconnected:
|
|
19
|
+
* the form turns this into its picker options and re-seeds its defaults when
|
|
20
|
+
* those change, so a fresh array on every store change would reset the form
|
|
21
|
+
* under the user until their first edit.
|
|
22
|
+
*/
|
|
23
|
+
const getInvoicingCouponDiscountsAccountIds = createSelector([
|
|
24
|
+
getInvoicingQboAccountOptions,
|
|
25
|
+
(state) => getInvoicingConfigView(state).qboAccountMappingKindOptions,
|
|
26
|
+
isInvoicingQboConnected,
|
|
27
|
+
], (accounts, qboAccountMappingKindOptions, isQboConnected) => isQboConnected
|
|
28
|
+
? invoicingQboAccountIdsForMappingSlot(accounts, qboAccountMappingKindOptions, INVOICING_DISCOUNTS_ACCOUNT_MAPPING_SLOT, INVOICING_DEFAULT_INCOME_ACCOUNT_TYPES)
|
|
29
|
+
: NO_INVOICING_ACCOUNT_IDS);
|
|
11
30
|
const NOT_STARTED = {
|
|
12
31
|
error: undefined,
|
|
13
32
|
fetchState: 'Not-Started',
|
|
@@ -23,6 +42,9 @@ export const getEditInvoicingCouponDetailViewState = (state, couponId) => {
|
|
|
23
42
|
fetchState: 'Not-Started',
|
|
24
43
|
})
|
|
25
44
|
: { error: undefined, fetchState: 'Completed' };
|
|
45
|
+
const coupon = couponId != null
|
|
46
|
+
? getInvoicingCouponById(state.invoicingCouponState, couponId)
|
|
47
|
+
: undefined;
|
|
26
48
|
return {
|
|
27
49
|
actionState: couponId != null
|
|
28
50
|
? (state.editInvoicingCouponDetailViewState.actionStateById[couponId] ??
|
|
@@ -36,9 +58,9 @@ export const getEditInvoicingCouponDetailViewState = (state, couponId) => {
|
|
|
36
58
|
{ error: undefined, fetchState: configFetchState },
|
|
37
59
|
]),
|
|
38
60
|
recordState,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
61
|
+
isFormLoading: isInvoicingFormDraftPending(state.editInvoicingCouponDetailViewState.editDraftById, couponId),
|
|
62
|
+
coupon,
|
|
63
|
+
discountsAccountName: invoicingQboAccountByQboId(state, coupon?.discountsAccountId)?.accountName,
|
|
42
64
|
savedCouponId,
|
|
43
65
|
fetchState,
|
|
44
66
|
error,
|
|
@@ -58,16 +80,57 @@ export const getInvoicingCouponFormView = (state, couponId) => {
|
|
|
58
80
|
// `seedInvoicingCouponFormDraftEpic` when the detail load lands. Until then
|
|
59
81
|
// the page is showing its skeleton.
|
|
60
82
|
const localData = couponId != null
|
|
61
|
-
? (editDraftById
|
|
83
|
+
? getInvoicingFormDraftLocalData(editDraftById, couponId, initialInvoicingCouponFormLocalData)
|
|
62
84
|
: newCouponDraft;
|
|
63
85
|
const { billingPeriodUnitOptions, discountApplyOnOptions, discountDurationTypeOptions, discountTypeOptions, } = getInvoicingConfigView(state);
|
|
86
|
+
const isQboConnected = isInvoicingQboConnected(state);
|
|
87
|
+
const qboView = isQboConnected ? getInvoicingQboView(state) : undefined;
|
|
88
|
+
const isQboPickerDataLoading = isInvoicingAccountingPickerDataLoading(state);
|
|
89
|
+
const settingsFetchState = getInvoicingSettingsFetchState(state).fetchState;
|
|
90
|
+
// Only a create reads the settings, and only until they arrive: an `Error`
|
|
91
|
+
// is a create that starts on no account, not a page that never renders.
|
|
92
|
+
const isCreateSeedPending = couponId == null &&
|
|
93
|
+
(settingsFetchState === 'Not-Started' ||
|
|
94
|
+
settingsFetchState === 'In-Progress');
|
|
64
95
|
return {
|
|
65
96
|
...base,
|
|
66
97
|
applyOnOptions: discountApplyOnOptions.map(toInvoicingFormOption),
|
|
67
98
|
discountTypeOptions: discountTypeOptions.map(toInvoicingFormOption),
|
|
68
99
|
durationTypeOptions: discountDurationTypeOptions.map(toInvoicingFormOption),
|
|
69
|
-
|
|
100
|
+
isQboConnected,
|
|
101
|
+
isQboPickerDataLoading,
|
|
102
|
+
// Widened past the record and config the detail page waits on: the form
|
|
103
|
+
// also renders from the account list, and seeds a create from the settings
|
|
104
|
+
// discounts account. A form that mounted before those landed would offer an
|
|
105
|
+
// empty picker and save a coupon with no account of its own — not wrong,
|
|
106
|
+
// since blank means "use the settings account", but not what the user was
|
|
107
|
+
// shown either. Widened here rather than on the base view, so the detail
|
|
108
|
+
// page does not start waiting on lists it never renders.
|
|
109
|
+
//
|
|
110
|
+
// Only while they are still coming, though, and the settings only on a
|
|
111
|
+
// create: a coupon saves fine without a discounts account, so neither is
|
|
112
|
+
// allowed to hold the form behind a skeleton once it has failed.
|
|
113
|
+
pageFetchState: reduceAllFetchState([
|
|
114
|
+
base.pageFetchState,
|
|
115
|
+
{
|
|
116
|
+
error: undefined,
|
|
117
|
+
fetchState: isQboPickerDataLoading || isCreateSeedPending
|
|
118
|
+
? 'In-Progress'
|
|
119
|
+
: 'Completed',
|
|
120
|
+
},
|
|
121
|
+
]),
|
|
122
|
+
localData: couponId != null
|
|
123
|
+
? localData
|
|
124
|
+
: {
|
|
125
|
+
...localData,
|
|
126
|
+
// Seeded from settings only while the draft has none of its own, so
|
|
127
|
+
// the picker shows the account a create would post against before
|
|
128
|
+
// the user touches it, and stops seeding the moment they pick.
|
|
129
|
+
discountsAccountId: invoicingCouponDiscountsAccountIdForCreate(localData.discountsAccountId, getInvoicingSettings(state)?.qbo?.account_mappings),
|
|
130
|
+
},
|
|
70
131
|
mode: couponId != null ? 'edit' : 'create',
|
|
71
132
|
periodUnitOptions: billingPeriodUnitOptions.map(toInvoicingFormOption),
|
|
133
|
+
qboAccountHierarchy: qboView?.nestedAccountHierarchy ?? NO_INVOICING_ACCOUNT_HIERARCHY,
|
|
134
|
+
qboIncomeAccountIds: getInvoicingCouponDiscountsAccountIds(state),
|
|
72
135
|
};
|
|
73
136
|
};
|