@zeniai/client-epic-state 5.2.83-beta1UT → 5.2.84
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/esm/view/invoicing/createInvoice/epics/fetchCreateInvoiceFormPageEpic.js +19 -5
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewPayload.js +7 -4
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +3 -1
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionDetailPageEpic.js +7 -4
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionFormPageEpic.js +1 -0
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/invoicingSubscriptionFormConfig.js +2 -0
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +6 -2
- package/lib/esm/view/invoicing/invoicingCatalogListView/epics/fetchInvoicingCatalogPlanListEpic.js +14 -3
- package/lib/esm/view/invoicing/invoicingCatalogListView/epics/fetchInvoicingCatalogProductListEpic.js +14 -3
- package/lib/esm/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewReducer.js +2 -0
- package/lib/esm/view/invoicing/invoicingListTabCache.js +8 -0
- package/lib/esm/view/settingsView/epic/saveNotificationSettingsEpic.js +0 -7
- package/lib/esm/view/settingsView/settingsViewHelpers.js +0 -1
- package/lib/esm/view/settingsView/settingsViewReducer.js +1 -16
- package/lib/esm/view/settingsView/settingsViewState.js +0 -1
- package/lib/view/invoicing/createInvoice/epics/fetchCreateInvoiceFormPageEpic.js +18 -4
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewPayload.d.ts +1 -1
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewPayload.js +7 -4
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.d.ts +2 -0
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +2 -0
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionDetailPageEpic.js +6 -3
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionFormPageEpic.js +1 -0
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/invoicingSubscriptionFormConfig.d.ts +2 -0
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/invoicingSubscriptionFormConfig.js +4 -1
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +5 -1
- package/lib/view/invoicing/invoicingCatalogListView/epics/fetchInvoicingCatalogPlanListEpic.js +14 -3
- package/lib/view/invoicing/invoicingCatalogListView/epics/fetchInvoicingCatalogProductListEpic.js +14 -3
- package/lib/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewReducer.d.ts +6 -0
- package/lib/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewReducer.js +2 -0
- package/lib/view/invoicing/invoicingListTabCache.d.ts +11 -1
- package/lib/view/invoicing/invoicingListTabCache.js +10 -1
- package/lib/view/settingsView/epic/saveNotificationSettingsEpic.js +0 -7
- package/lib/view/settingsView/settingsViewHelpers.js +0 -1
- package/lib/view/settingsView/settingsViewPayload.d.ts +0 -2
- package/lib/view/settingsView/settingsViewReducer.js +1 -16
- package/lib/view/settingsView/settingsViewState.d.ts +2 -2
- package/lib/view/settingsView/settingsViewState.js +0 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@ import { from } from 'rxjs';
|
|
|
2
2
|
import { filter, mergeMap } from 'rxjs/operators';
|
|
3
3
|
import { fetchInvoicingCatalogPlanList, fetchInvoicingCatalogProductList, } from '../../invoicingCatalogListView/invoicingCatalogListViewReducer';
|
|
4
4
|
import { fetchInvoicingCustomerList } from '../../invoicingCustomerListView/invoicingCustomerListViewReducer';
|
|
5
|
-
import { INVOICING_LIST_ALL_TAB, getInvoicingListTab, } from '../../invoicingListTabCache';
|
|
5
|
+
import { INVOICING_LIST_ALL_TAB, getInvoicingListTab, shouldFetchFullInvoicingList, } from '../../invoicingListTabCache';
|
|
6
6
|
import { fetchInvoicingSettings } from '../../settingsView/settingsViewReducer';
|
|
7
7
|
import { fetchCreateInvoiceFormPage } from '../createInvoiceReducer';
|
|
8
8
|
/**
|
|
@@ -13,6 +13,8 @@ import { fetchCreateInvoiceFormPage } from '../createInvoiceReducer';
|
|
|
13
13
|
const shouldFetch = (fetchStateAndError, cacheOverride) => cacheOverride ||
|
|
14
14
|
(fetchStateAndError.fetchState !== 'Completed' &&
|
|
15
15
|
fetchStateAndError.fetchState !== 'In-Progress');
|
|
16
|
+
const PICKER_PLAN_FILTERS = { type: 'plan' };
|
|
17
|
+
const PICKER_PRODUCT_FILTERS = { type: 'addon|charge' };
|
|
16
18
|
/**
|
|
17
19
|
* Orchestrates the create-invoice form's option sources: on
|
|
18
20
|
* `fetchCreateInvoiceFormPage`, fans out to the customer, catalog plan, catalog
|
|
@@ -34,15 +36,27 @@ export const fetchCreateInvoiceFormPageEpic = (actions$, state$) => actions$.pip
|
|
|
34
36
|
tabId: INVOICING_LIST_ALL_TAB,
|
|
35
37
|
}));
|
|
36
38
|
}
|
|
37
|
-
if (
|
|
39
|
+
if (shouldFetchFullInvoicingList({
|
|
40
|
+
fetchState: catalogState.fetchState,
|
|
41
|
+
filters: catalogState.planFilters,
|
|
42
|
+
nextCursor: catalogState.planNextCursor,
|
|
43
|
+
sort: catalogState.planSort,
|
|
44
|
+
}, PICKER_PLAN_FILTERS, cacheOverride)) {
|
|
38
45
|
pageActions.push(fetchInvoicingCatalogPlanList({
|
|
39
|
-
|
|
46
|
+
fetchAll: true,
|
|
47
|
+
filters: PICKER_PLAN_FILTERS,
|
|
40
48
|
keepExistingListItems: false,
|
|
41
49
|
}));
|
|
42
50
|
}
|
|
43
|
-
if (
|
|
51
|
+
if (shouldFetchFullInvoicingList({
|
|
52
|
+
fetchState: catalogState.productFetchState.fetchState,
|
|
53
|
+
filters: catalogState.productFilters,
|
|
54
|
+
nextCursor: catalogState.productNextCursor,
|
|
55
|
+
sort: catalogState.productSort,
|
|
56
|
+
}, PICKER_PRODUCT_FILTERS, cacheOverride)) {
|
|
44
57
|
pageActions.push(fetchInvoicingCatalogProductList({
|
|
45
|
-
|
|
58
|
+
fetchAll: true,
|
|
59
|
+
filters: PICKER_PRODUCT_FILTERS,
|
|
46
60
|
keepExistingListItems: false,
|
|
47
61
|
}));
|
|
48
62
|
}
|
|
@@ -17,22 +17,25 @@ import { invoicingSubscriptionTodayIso, toInvoicingSubscriptionBillingCycles, to
|
|
|
17
17
|
* Sending them made every price point whose cadence the form could not express
|
|
18
18
|
* — a daily plan, for instance — fail with a 422.
|
|
19
19
|
*/
|
|
20
|
-
export const localDataToSaveInvoicingSubscriptionPayload = (localData, shippingAddress) => {
|
|
20
|
+
export const localDataToSaveInvoicingSubscriptionPayload = (localData, shippingAddress, flatFeePriceCodes = new Set()) => {
|
|
21
|
+
const itemQuantity = (priceCode, quantity) => flatFeePriceCodes.has(priceCode)
|
|
22
|
+
? 1
|
|
23
|
+
: toInvoicingSubscriptionQuantity(quantity);
|
|
21
24
|
const items = [
|
|
22
25
|
{
|
|
23
26
|
item_price_code: localData.planPriceCode,
|
|
24
27
|
item_type: 'plan',
|
|
25
|
-
quantity:
|
|
28
|
+
quantity: itemQuantity(localData.planPriceCode, localData.planQuantity),
|
|
26
29
|
},
|
|
27
30
|
...localData.addonPriceCodes.map((priceCode) => ({
|
|
28
31
|
item_price_code: priceCode,
|
|
29
32
|
item_type: 'addon',
|
|
30
|
-
quantity:
|
|
33
|
+
quantity: itemQuantity(priceCode, localData.addonQuantities[priceCode]),
|
|
31
34
|
})),
|
|
32
35
|
...localData.chargePriceCodes.map((priceCode) => ({
|
|
33
36
|
item_price_code: priceCode,
|
|
34
37
|
item_type: 'charge',
|
|
35
|
-
quantity:
|
|
38
|
+
quantity: itemQuantity(priceCode, localData.chargeQuantities[priceCode]),
|
|
36
39
|
})),
|
|
37
40
|
];
|
|
38
41
|
const setupFee = localData.setupFee?.amount;
|
|
@@ -7,7 +7,7 @@ import { getInvoicingCatalogListView, getInvoicingCatalogPrices, } from '../invo
|
|
|
7
7
|
import { getInvoicingConfigView, invoicingFieldHelpForGroup, } from '../invoicingConfigView/invoicingConfigViewSelector';
|
|
8
8
|
import { getInvoicingAllCustomers } from '../invoicingCustomerListView/invoicingCustomerListViewSelector';
|
|
9
9
|
import { getInvoicingFormDraftLocalData, isInvoicingFormDraftPending, } from '../invoicingFormDraft';
|
|
10
|
-
import { initialInvoicingSubscriptionFormLocalData } from './invoicingSubscriptionFormConfig';
|
|
10
|
+
import { initialInvoicingSubscriptionFormLocalData, invoicingPriceHasQuantity, } from './invoicingSubscriptionFormConfig';
|
|
11
11
|
import { INVOICING_SUBSCRIPTION_SHIPPING_ADDRESS_TYPE } from './invoicingSubscriptionFormConfig';
|
|
12
12
|
const notStartedEstimateView = {
|
|
13
13
|
creditsMayChange: false,
|
|
@@ -175,6 +175,7 @@ const toInvoicingPlanOption = (plan) => ({
|
|
|
175
175
|
// price point and rejects a subscription that claims a different one.
|
|
176
176
|
billingPeriod: plan.period,
|
|
177
177
|
billingPeriodUnit: plan.periodUnit,
|
|
178
|
+
hasQuantity: invoicingPriceHasQuantity(plan.pricingModel),
|
|
178
179
|
label: plan.name ?? plan.id,
|
|
179
180
|
price: invoicingUnitPrice(plan),
|
|
180
181
|
productCode: plan.productCode,
|
|
@@ -186,6 +187,7 @@ const toInvoicingCatalogItemOption = (product) => ({
|
|
|
186
187
|
// A one-time charge has no cadence, so its price is quoted flat rather than
|
|
187
188
|
// per period.
|
|
188
189
|
billingPeriodUnit: product.itemType === 'charge' ? undefined : product.periodUnit,
|
|
190
|
+
hasQuantity: invoicingPriceHasQuantity(product.pricingModel),
|
|
189
191
|
label: product.name ?? product.id,
|
|
190
192
|
price: invoicingUnitPrice(product),
|
|
191
193
|
productCode: product.productCode,
|
|
@@ -4,7 +4,7 @@ import { fetchInvoicingCouponList } from '../couponView/couponViewReducer';
|
|
|
4
4
|
import { getInvoicingCouponListFetchState } from '../couponView/couponViewSelector';
|
|
5
5
|
import { fetchInvoicingCatalogPlanList } from '../invoicingCatalogListView/invoicingCatalogListViewReducer';
|
|
6
6
|
import { fetchInvoicingConfig } from '../invoicingConfigView/invoicingConfigViewReducer';
|
|
7
|
-
import { INVOICING_LIST_ALL_TAB } from '../invoicingListTabCache';
|
|
7
|
+
import { INVOICING_LIST_ALL_TAB, shouldFetchFullInvoicingList, } from '../invoicingListTabCache';
|
|
8
8
|
import { fetchInvoicingSubscriptionDetail, fetchInvoicingSubscriptionDetailPage, } from './editInvoicingSubscriptionDetailViewReducer';
|
|
9
9
|
/**
|
|
10
10
|
* Fetch unless the endpoint is already loaded or in flight — or always when the
|
|
@@ -40,11 +40,14 @@ export const fetchInvoicingSubscriptionDetailPageEpic = (actions$, state$) => ac
|
|
|
40
40
|
// Plan and coupon lists back the names the page renders for its line
|
|
41
41
|
// items; both list slices cache, so this only refetches when asked to.
|
|
42
42
|
const catalogListState = state.invoicingCatalogListViewState;
|
|
43
|
-
if (
|
|
44
|
-
error: catalogListState.error,
|
|
43
|
+
if (shouldFetchFullInvoicingList({
|
|
45
44
|
fetchState: catalogListState.fetchState,
|
|
46
|
-
|
|
45
|
+
filters: catalogListState.planFilters,
|
|
46
|
+
nextCursor: catalogListState.planNextCursor,
|
|
47
|
+
sort: catalogListState.planSort,
|
|
48
|
+
}, { type: 'plan' }, cacheOverride)) {
|
|
47
49
|
pageActions.push(fetchInvoicingCatalogPlanList({
|
|
50
|
+
fetchAll: true,
|
|
48
51
|
filters: { type: 'plan' },
|
|
49
52
|
keepExistingListItems: false,
|
|
50
53
|
}));
|
|
@@ -20,6 +20,7 @@ export const fetchInvoicingSubscriptionFormPageEpic = (actions$) => actions$.pip
|
|
|
20
20
|
// unfiltered list would replace the store's plans with every catalog
|
|
21
21
|
// item.
|
|
22
22
|
fetchInvoicingCatalogPlanList({
|
|
23
|
+
fetchAll: true,
|
|
23
24
|
filters: { type: 'plan' },
|
|
24
25
|
keepExistingListItems: false,
|
|
25
26
|
}),
|
|
@@ -22,6 +22,8 @@ const subscriptionItemQuantities = (subscription, itemType) => {
|
|
|
22
22
|
* their day gets their own calendar date rather than yesterday's.
|
|
23
23
|
*/
|
|
24
24
|
export const invoicingSubscriptionTodayIso = () => dateNow().format('YYYY-MM-DD');
|
|
25
|
+
/** A flat fee bills its price once, so its quantity is always 1. */
|
|
26
|
+
export const invoicingPriceHasQuantity = (pricingModel) => pricingModel !== 'flat_fee';
|
|
25
27
|
/**
|
|
26
28
|
* Clamp a subscription-item quantity to the minimum the API accepts. Shared by
|
|
27
29
|
* the plan, add-on and charge items so none of them can bill a 0 or a fraction.
|
package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js
CHANGED
|
@@ -9,10 +9,11 @@ import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload
|
|
|
9
9
|
import { resetNewAddressDataInLocalStore } from '../../addressView/addressViewReducer';
|
|
10
10
|
import { invalidateInvoicingCustomerDetail } from '../editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer';
|
|
11
11
|
import { invalidateInvoiceListForCustomer } from '../invoiceList/invoiceListReducer';
|
|
12
|
+
import { getInvoicingCatalogPrices } from '../invoicingCatalogListView/invoicingCatalogListViewSelector';
|
|
12
13
|
import { addInvoicingSubscriptionToList } from '../invoicingSubscriptionListView/invoicingSubscriptionListViewReducer';
|
|
13
14
|
import { localDataToSaveInvoicingSubscriptionPayload, } from './editInvoicingSubscriptionDetailViewPayload';
|
|
14
15
|
import { submitInvoicingSubscriptionForm, submitInvoicingSubscriptionFormFailure, submitInvoicingSubscriptionFormSuccess, syncInvoicingSubscriptionCoupons, } from './editInvoicingSubscriptionDetailViewReducer';
|
|
15
|
-
import { INVOICING_SUBSCRIPTION_SHIPPING_ADDRESS_TYPE } from './invoicingSubscriptionFormConfig';
|
|
16
|
+
import { INVOICING_SUBSCRIPTION_SHIPPING_ADDRESS_TYPE, invoicingPriceHasQuantity, } from './invoicingSubscriptionFormConfig';
|
|
16
17
|
const normalizeCouponCode = (value) => (value ?? '').trim().toUpperCase();
|
|
17
18
|
export const isInvoicingSubscriptionCouponChanged = (previousCouponCode, nextCouponCode) => normalizeCouponCode(previousCouponCode) !==
|
|
18
19
|
normalizeCouponCode(nextCouponCode);
|
|
@@ -25,7 +26,10 @@ export const invoicingSubscriptionSaveRequest = (rootState, id) => {
|
|
|
25
26
|
// Shipping address captured via the shared Address screen is sent as an
|
|
26
27
|
// object in the payload (same pattern as the customer save flow).
|
|
27
28
|
const shippingAddress = rootState.addressViewState.newAddressState?.[INVOICING_SUBSCRIPTION_SHIPPING_ADDRESS_TYPE]?.addressToCreate;
|
|
28
|
-
const
|
|
29
|
+
const flatFeePriceCodes = new Set(getInvoicingCatalogPrices(Object.values(rootState.invoicingCatalogState.itemByID))
|
|
30
|
+
.filter((price) => !invoicingPriceHasQuantity(price.pricingModel))
|
|
31
|
+
.flatMap((price) => price.priceCode ?? []));
|
|
32
|
+
const data = localDataToSaveInvoicingSubscriptionPayload(localData, shippingAddress, flatFeePriceCodes);
|
|
29
33
|
if (id != null) {
|
|
30
34
|
delete data.coupon_ids;
|
|
31
35
|
}
|
package/lib/esm/view/invoicing/invoicingCatalogListView/epics/fetchInvoicingCatalogPlanListEpic.js
CHANGED
|
@@ -23,19 +23,30 @@ const buildPlanQuery = (filters, cursor, sort) => {
|
|
|
23
23
|
return params;
|
|
24
24
|
};
|
|
25
25
|
export const fetchInvoicingCatalogPlanListEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchInvoicingCatalogPlanList.match), switchMap((action) => {
|
|
26
|
-
const { filters, cursor, keepExistingListItems, sort } = action.payload;
|
|
26
|
+
const { filters, cursor, fetchAll, keepExistingListItems, sort } = action.payload;
|
|
27
27
|
const apiUrl = withQuery(`${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/catalog/plans`, buildPlanQuery(filters, cursor, sort));
|
|
28
28
|
return zeniAPI.getJSON(apiUrl).pipe(switchMap((response) => {
|
|
29
29
|
if (isSuccessResponse(response)) {
|
|
30
30
|
const items = response.data?.items ?? [];
|
|
31
|
-
|
|
31
|
+
const nextCursor = response.data?.next_cursor ?? null;
|
|
32
|
+
const pageActions = [
|
|
32
33
|
updateInvoicingCatalogItems(items, 'merge'),
|
|
33
34
|
updateInvoicingCatalogPlanList({
|
|
34
35
|
ids: items.map((item) => item.id),
|
|
35
|
-
nextCursor
|
|
36
|
+
nextCursor,
|
|
36
37
|
keepExistingListItems,
|
|
37
38
|
}),
|
|
38
39
|
];
|
|
40
|
+
if (fetchAll && nextCursor != null && nextCursor !== cursor) {
|
|
41
|
+
pageActions.push(fetchInvoicingCatalogPlanList({
|
|
42
|
+
cursor: nextCursor,
|
|
43
|
+
fetchAll,
|
|
44
|
+
filters,
|
|
45
|
+
keepExistingListItems: true,
|
|
46
|
+
sort,
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
return pageActions;
|
|
39
50
|
}
|
|
40
51
|
return of(updateInvoicingCatalogPlanListFailure(response.status));
|
|
41
52
|
}), catchError((error) => of(updateInvoicingCatalogPlanListFailure(createZeniAPIStatus('Unexpected Error', 'Fetch catalog plan list REST API call errored out ' +
|
|
@@ -23,19 +23,30 @@ const buildProductQuery = (filters, cursor, sort) => {
|
|
|
23
23
|
return params;
|
|
24
24
|
};
|
|
25
25
|
export const fetchInvoicingCatalogProductListEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchInvoicingCatalogProductList.match), switchMap((action) => {
|
|
26
|
-
const { filters, cursor, keepExistingListItems, sort } = action.payload;
|
|
26
|
+
const { filters, cursor, fetchAll, keepExistingListItems, sort } = action.payload;
|
|
27
27
|
const apiUrl = withQuery(`${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/catalog/plans`, buildProductQuery(filters, cursor, sort));
|
|
28
28
|
return zeniAPI.getJSON(apiUrl).pipe(switchMap((response) => {
|
|
29
29
|
if (isSuccessResponse(response)) {
|
|
30
30
|
const items = response.data?.items ?? [];
|
|
31
|
-
|
|
31
|
+
const nextCursor = response.data?.next_cursor ?? null;
|
|
32
|
+
const pageActions = [
|
|
32
33
|
updateInvoicingCatalogItems(items, 'merge'),
|
|
33
34
|
updateInvoicingCatalogProductList({
|
|
34
35
|
ids: items.map((item) => item.id),
|
|
35
|
-
nextCursor
|
|
36
|
+
nextCursor,
|
|
36
37
|
keepExistingListItems,
|
|
37
38
|
}),
|
|
38
39
|
];
|
|
40
|
+
if (fetchAll && nextCursor != null && nextCursor !== cursor) {
|
|
41
|
+
pageActions.push(fetchInvoicingCatalogProductList({
|
|
42
|
+
cursor: nextCursor,
|
|
43
|
+
fetchAll,
|
|
44
|
+
filters,
|
|
45
|
+
keepExistingListItems: true,
|
|
46
|
+
sort,
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
return pageActions;
|
|
39
50
|
}
|
|
40
51
|
return of(updateInvoicingCatalogProductListFailure(response.status));
|
|
41
52
|
}), catchError((error) => of(updateInvoicingCatalogProductListFailure(createZeniAPIStatus('Unexpected Error', 'Fetch catalog product list REST API call errored out ' +
|
|
@@ -36,6 +36,7 @@ const invoicingCatalogListView = createSlice({
|
|
|
36
36
|
payload: {
|
|
37
37
|
filters: payload.filters ?? {},
|
|
38
38
|
cursor: payload.cursor,
|
|
39
|
+
fetchAll: payload.fetchAll ?? false,
|
|
39
40
|
keepExistingListItems: payload.keepExistingListItems ?? false,
|
|
40
41
|
sort: payload.sort,
|
|
41
42
|
},
|
|
@@ -70,6 +71,7 @@ const invoicingCatalogListView = createSlice({
|
|
|
70
71
|
payload: {
|
|
71
72
|
filters: payload.filters ?? {},
|
|
72
73
|
cursor: payload.cursor,
|
|
74
|
+
fetchAll: payload.fetchAll ?? false,
|
|
73
75
|
keepExistingListItems: payload.keepExistingListItems ?? false,
|
|
74
76
|
sort: payload.sort,
|
|
75
77
|
},
|
|
@@ -33,6 +33,14 @@ export const isSameInvoicingListQuery = (a, b) => {
|
|
|
33
33
|
a.sort?.field === b.sort?.field &&
|
|
34
34
|
a.sort?.dir === b.sort?.dir);
|
|
35
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* For a picker that searches client-side: reuse only a complete list fetched
|
|
38
|
+
* with the picker's own query, else load every page of it.
|
|
39
|
+
*/
|
|
40
|
+
export const shouldFetchFullInvoicingList = (list, pickerFilters, cacheOverride) => cacheOverride ||
|
|
41
|
+
list.fetchState !== 'Completed' ||
|
|
42
|
+
list.nextCursor != null ||
|
|
43
|
+
!isSameInvoicingListQuery({ filters: list.filters, sort: list.sort }, { filters: pickerFilters });
|
|
36
44
|
/**
|
|
37
45
|
* Fetch a tab that is empty or failed; top up one that holds less than a page
|
|
38
46
|
* while the server says there is more; leave a full or complete one cached.
|
|
@@ -50,8 +50,6 @@ function toSaveNotificationSettingsPayload(localData, notificationSettingsData)
|
|
|
50
50
|
pending_bill_approval_notification: null,
|
|
51
51
|
pending_reimbursement_approval_notification: null,
|
|
52
52
|
is_pending_reimbursement_approval_notification_active: null,
|
|
53
|
-
is_pending_task_notification_active: null,
|
|
54
|
-
pending_task_notification: null,
|
|
55
53
|
is_month_end_insights_active: monthEndInsightsNotificationSettings.enabled != null
|
|
56
54
|
? monthEndInsights.notify
|
|
57
55
|
: null,
|
|
@@ -79,10 +77,5 @@ function toSaveNotificationSettingsPayload(localData, notificationSettingsData)
|
|
|
79
77
|
0;
|
|
80
78
|
payload.pending_reimbursement_approval_notification = toRemindersPayload(reminders.pendingReimbursementApprovalNotification);
|
|
81
79
|
}
|
|
82
|
-
if (reminderNotificationSettings.pendingTaskNotification != null) {
|
|
83
|
-
payload.is_pending_task_notification_active =
|
|
84
|
-
reminders.pendingTaskNotification.preferredMode.length > 0;
|
|
85
|
-
payload.pending_task_notification = toRemindersPayload(reminders.pendingTaskNotification);
|
|
86
|
-
}
|
|
87
80
|
return payload;
|
|
88
81
|
}
|
|
@@ -140,7 +140,6 @@ export const isRemindersSectionVisibleOnSettingsPage = (reminderNotificationSett
|
|
|
140
140
|
reminderNotificationSettings.pendingBillApprovalNotification != null ||
|
|
141
141
|
reminderNotificationSettings.pendingReimbursementApprovalNotification !=
|
|
142
142
|
null ||
|
|
143
|
-
reminderNotificationSettings.pendingTaskNotification != null ||
|
|
144
143
|
reminderNotificationSettings.uncategorizedTransactionNotification != null);
|
|
145
144
|
};
|
|
146
145
|
export const isMonthEndNotificationsSectionVisibleOnSettingsPage = (monthEndInsightsNotificationSettings, signedInUseEmail) => {
|
|
@@ -4,7 +4,6 @@ import { toNotificationMode, toRemindersFrequencyType, } from './settingsViewSta
|
|
|
4
4
|
const initialNotificationSettingsData = {
|
|
5
5
|
reminderNotificationSettings: {
|
|
6
6
|
missingReceiptNotification: null,
|
|
7
|
-
pendingTaskNotification: null,
|
|
8
7
|
uncategorizedTransactionNotification: null,
|
|
9
8
|
pendingBillApprovalNotification: null,
|
|
10
9
|
pendingReimbursementApprovalNotification: null,
|
|
@@ -45,7 +44,6 @@ const initialNotificationSettingsLocalData = {
|
|
|
45
44
|
missingReceiptNotification: initialRemindersLocalData,
|
|
46
45
|
pendingBillApprovalNotification: initialRemindersLocalData,
|
|
47
46
|
pendingReimbursementApprovalNotification: initialRemindersLocalData,
|
|
48
|
-
pendingTaskNotification: initialRemindersLocalData,
|
|
49
47
|
uncategorizedTransactionNotification: initialRemindersLocalData,
|
|
50
48
|
},
|
|
51
49
|
};
|
|
@@ -375,7 +373,7 @@ function toRemindersLocalData(reminderNotificationSettingDetails) {
|
|
|
375
373
|
}
|
|
376
374
|
const toNotificationSettingsLocalData = (data, loggedInUserEmail) => {
|
|
377
375
|
const { monthEndInsightsNotificationSettings, reminderNotificationSettings } = data;
|
|
378
|
-
const { missingReceiptNotification, pendingBillApprovalNotification, pendingReimbursementApprovalNotification,
|
|
376
|
+
const { missingReceiptNotification, pendingBillApprovalNotification, pendingReimbursementApprovalNotification, uncategorizedTransactionNotification, } = reminderNotificationSettings;
|
|
379
377
|
return {
|
|
380
378
|
monthEndInsights: {
|
|
381
379
|
notify: monthEndInsightsNotificationSettings.enabled ??
|
|
@@ -386,22 +384,10 @@ const toNotificationSettingsLocalData = (data, loggedInUserEmail) => {
|
|
|
386
384
|
missingReceiptNotification: toRemindersLocalData(missingReceiptNotification),
|
|
387
385
|
pendingBillApprovalNotification: toRemindersLocalData(pendingBillApprovalNotification),
|
|
388
386
|
pendingReimbursementApprovalNotification: toRemindersLocalData(pendingReimbursementApprovalNotification),
|
|
389
|
-
pendingTaskNotification: toRemindersLocalData(pendingTaskNotification),
|
|
390
387
|
uncategorizedTransactionNotification: toRemindersLocalData(uncategorizedTransactionNotification),
|
|
391
388
|
},
|
|
392
389
|
};
|
|
393
390
|
};
|
|
394
|
-
function toPendingTaskNotificationActive(data) {
|
|
395
|
-
if (data.is_pending_task_notification_active != null) {
|
|
396
|
-
return data.is_pending_task_notification_active;
|
|
397
|
-
}
|
|
398
|
-
// Rows saved before this flag existed store null. It shares the
|
|
399
|
-
// transactions-reminder scope with missing receipts and uncategorized
|
|
400
|
-
// transactions, so a set sibling means the user has access.
|
|
401
|
-
const hasTransactionReminderAccess = data.is_missing_receipt_notification_active != null ||
|
|
402
|
-
data.is_uncategorized_transaction_notification_active != null;
|
|
403
|
-
return hasTransactionReminderAccess ? false : null;
|
|
404
|
-
}
|
|
405
391
|
function toReminderNotificationSettingsDetails(isNotificationActive, remindersPayload) {
|
|
406
392
|
if (isNotificationActive != null) {
|
|
407
393
|
return {
|
|
@@ -422,7 +408,6 @@ const toNotificationSettings = (data) => {
|
|
|
422
408
|
missingReceiptNotification: toReminderNotificationSettingsDetails(data.is_missing_receipt_notification_active, data.missing_receipt_notification),
|
|
423
409
|
pendingBillApprovalNotification: toReminderNotificationSettingsDetails(data.is_pending_bill_approval_notification_active, data.pending_bill_approval_notification),
|
|
424
410
|
pendingReimbursementApprovalNotification: toReminderNotificationSettingsDetails(data.is_pending_reimbursement_approval_notification_active, data.pending_reimbursement_approval_notification),
|
|
425
|
-
pendingTaskNotification: toReminderNotificationSettingsDetails(toPendingTaskNotificationActive(data), data.pending_task_notification),
|
|
426
411
|
uncategorizedTransactionNotification: toReminderNotificationSettingsDetails(data.is_uncategorized_transaction_notification_active, data.uncategorized_transaction_notification),
|
|
427
412
|
},
|
|
428
413
|
monthEndInsightsNotificationSettings: {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { stringToUnion } from '../../commonStateTypes/stringToUnion';
|
|
2
2
|
export const REMINDER_NOTIFICATION_TYPES = [
|
|
3
3
|
'missingReceiptNotification',
|
|
4
|
-
'pendingTaskNotification',
|
|
5
4
|
'uncategorizedTransactionNotification',
|
|
6
5
|
'pendingBillApprovalNotification',
|
|
7
6
|
'pendingReimbursementApprovalNotification',
|
|
@@ -16,6 +16,8 @@ const createInvoiceReducer_1 = require("../createInvoiceReducer");
|
|
|
16
16
|
const shouldFetch = (fetchStateAndError, cacheOverride) => cacheOverride ||
|
|
17
17
|
(fetchStateAndError.fetchState !== 'Completed' &&
|
|
18
18
|
fetchStateAndError.fetchState !== 'In-Progress');
|
|
19
|
+
const PICKER_PLAN_FILTERS = { type: 'plan' };
|
|
20
|
+
const PICKER_PRODUCT_FILTERS = { type: 'addon|charge' };
|
|
19
21
|
/**
|
|
20
22
|
* Orchestrates the create-invoice form's option sources: on
|
|
21
23
|
* `fetchCreateInvoiceFormPage`, fans out to the customer, catalog plan, catalog
|
|
@@ -37,15 +39,27 @@ const fetchCreateInvoiceFormPageEpic = (actions$, state$) => actions$.pipe((0, o
|
|
|
37
39
|
tabId: invoicingListTabCache_1.INVOICING_LIST_ALL_TAB,
|
|
38
40
|
}));
|
|
39
41
|
}
|
|
40
|
-
if (
|
|
42
|
+
if ((0, invoicingListTabCache_1.shouldFetchFullInvoicingList)({
|
|
43
|
+
fetchState: catalogState.fetchState,
|
|
44
|
+
filters: catalogState.planFilters,
|
|
45
|
+
nextCursor: catalogState.planNextCursor,
|
|
46
|
+
sort: catalogState.planSort,
|
|
47
|
+
}, PICKER_PLAN_FILTERS, cacheOverride)) {
|
|
41
48
|
pageActions.push((0, invoicingCatalogListViewReducer_1.fetchInvoicingCatalogPlanList)({
|
|
42
|
-
|
|
49
|
+
fetchAll: true,
|
|
50
|
+
filters: PICKER_PLAN_FILTERS,
|
|
43
51
|
keepExistingListItems: false,
|
|
44
52
|
}));
|
|
45
53
|
}
|
|
46
|
-
if (
|
|
54
|
+
if ((0, invoicingListTabCache_1.shouldFetchFullInvoicingList)({
|
|
55
|
+
fetchState: catalogState.productFetchState.fetchState,
|
|
56
|
+
filters: catalogState.productFilters,
|
|
57
|
+
nextCursor: catalogState.productNextCursor,
|
|
58
|
+
sort: catalogState.productSort,
|
|
59
|
+
}, PICKER_PRODUCT_FILTERS, cacheOverride)) {
|
|
47
60
|
pageActions.push((0, invoicingCatalogListViewReducer_1.fetchInvoicingCatalogProductList)({
|
|
48
|
-
|
|
61
|
+
fetchAll: true,
|
|
62
|
+
filters: PICKER_PRODUCT_FILTERS,
|
|
49
63
|
keepExistingListItems: false,
|
|
50
64
|
}));
|
|
51
65
|
}
|
|
@@ -18,7 +18,7 @@ export type SaveInvoicingSubscriptionResponse = ZeniAPIResponse<InvoicingSubscri
|
|
|
18
18
|
* Sending them made every price point whose cadence the form could not express
|
|
19
19
|
* — a daily plan, for instance — fail with a 422.
|
|
20
20
|
*/
|
|
21
|
-
export declare const localDataToSaveInvoicingSubscriptionPayload: (localData: InvoicingSubscriptionFormLocalData, shippingAddress?: AddressUpdatableInfo) => Record<string, unknown>;
|
|
21
|
+
export declare const localDataToSaveInvoicingSubscriptionPayload: (localData: InvoicingSubscriptionFormLocalData, shippingAddress?: AddressUpdatableInfo, flatFeePriceCodes?: ReadonlySet<string>) => Record<string, unknown>;
|
|
22
22
|
export type RunInvoicingSubscriptionActionResponse = ZeniAPIResponse<InvoicingSubscriptionPayload>;
|
|
23
23
|
/** Snake_case request bodies for subscription action endpoints. */
|
|
24
24
|
export type ApplyCouponBody = {
|
|
@@ -20,22 +20,25 @@ const invoicingSubscriptionFormConfig_1 = require("./invoicingSubscriptionFormCo
|
|
|
20
20
|
* Sending them made every price point whose cadence the form could not express
|
|
21
21
|
* — a daily plan, for instance — fail with a 422.
|
|
22
22
|
*/
|
|
23
|
-
const localDataToSaveInvoicingSubscriptionPayload = (localData, shippingAddress) => {
|
|
23
|
+
const localDataToSaveInvoicingSubscriptionPayload = (localData, shippingAddress, flatFeePriceCodes = new Set()) => {
|
|
24
|
+
const itemQuantity = (priceCode, quantity) => flatFeePriceCodes.has(priceCode)
|
|
25
|
+
? 1
|
|
26
|
+
: (0, invoicingSubscriptionFormConfig_1.toInvoicingSubscriptionQuantity)(quantity);
|
|
24
27
|
const items = [
|
|
25
28
|
{
|
|
26
29
|
item_price_code: localData.planPriceCode,
|
|
27
30
|
item_type: 'plan',
|
|
28
|
-
quantity: (
|
|
31
|
+
quantity: itemQuantity(localData.planPriceCode, localData.planQuantity),
|
|
29
32
|
},
|
|
30
33
|
...localData.addonPriceCodes.map((priceCode) => ({
|
|
31
34
|
item_price_code: priceCode,
|
|
32
35
|
item_type: 'addon',
|
|
33
|
-
quantity: (
|
|
36
|
+
quantity: itemQuantity(priceCode, localData.addonQuantities[priceCode]),
|
|
34
37
|
})),
|
|
35
38
|
...localData.chargePriceCodes.map((priceCode) => ({
|
|
36
39
|
item_price_code: priceCode,
|
|
37
40
|
item_type: 'charge',
|
|
38
|
-
quantity: (
|
|
41
|
+
quantity: itemQuantity(priceCode, localData.chargeQuantities[priceCode]),
|
|
39
42
|
})),
|
|
40
43
|
];
|
|
41
44
|
const setupFee = localData.setupFee?.amount;
|
|
@@ -147,6 +147,8 @@ export interface InvoicingSubscriptionOption {
|
|
|
147
147
|
billingCycles?: number;
|
|
148
148
|
billingPeriod?: number;
|
|
149
149
|
billingPeriodUnit?: string;
|
|
150
|
+
/** Catalog options only: false for a flat fee, which takes no quantity. */
|
|
151
|
+
hasQuantity?: boolean;
|
|
150
152
|
price?: Amount;
|
|
151
153
|
productCode?: string;
|
|
152
154
|
subtitle?: string;
|
|
@@ -182,6 +182,7 @@ const toInvoicingPlanOption = (plan) => ({
|
|
|
182
182
|
// price point and rejects a subscription that claims a different one.
|
|
183
183
|
billingPeriod: plan.period,
|
|
184
184
|
billingPeriodUnit: plan.periodUnit,
|
|
185
|
+
hasQuantity: (0, invoicingSubscriptionFormConfig_1.invoicingPriceHasQuantity)(plan.pricingModel),
|
|
185
186
|
label: plan.name ?? plan.id,
|
|
186
187
|
price: invoicingUnitPrice(plan),
|
|
187
188
|
productCode: plan.productCode,
|
|
@@ -193,6 +194,7 @@ const toInvoicingCatalogItemOption = (product) => ({
|
|
|
193
194
|
// A one-time charge has no cadence, so its price is quoted flat rather than
|
|
194
195
|
// per period.
|
|
195
196
|
billingPeriodUnit: product.itemType === 'charge' ? undefined : product.periodUnit,
|
|
197
|
+
hasQuantity: (0, invoicingSubscriptionFormConfig_1.invoicingPriceHasQuantity)(product.pricingModel),
|
|
196
198
|
label: product.name ?? product.id,
|
|
197
199
|
price: invoicingUnitPrice(product),
|
|
198
200
|
productCode: product.productCode,
|
|
@@ -43,11 +43,14 @@ const fetchInvoicingSubscriptionDetailPageEpic = (actions$, state$) => actions$.
|
|
|
43
43
|
// Plan and coupon lists back the names the page renders for its line
|
|
44
44
|
// items; both list slices cache, so this only refetches when asked to.
|
|
45
45
|
const catalogListState = state.invoicingCatalogListViewState;
|
|
46
|
-
if (
|
|
47
|
-
error: catalogListState.error,
|
|
46
|
+
if ((0, invoicingListTabCache_1.shouldFetchFullInvoicingList)({
|
|
48
47
|
fetchState: catalogListState.fetchState,
|
|
49
|
-
|
|
48
|
+
filters: catalogListState.planFilters,
|
|
49
|
+
nextCursor: catalogListState.planNextCursor,
|
|
50
|
+
sort: catalogListState.planSort,
|
|
51
|
+
}, { type: 'plan' }, cacheOverride)) {
|
|
50
52
|
pageActions.push((0, invoicingCatalogListViewReducer_1.fetchInvoicingCatalogPlanList)({
|
|
53
|
+
fetchAll: true,
|
|
51
54
|
filters: { type: 'plan' },
|
|
52
55
|
keepExistingListItems: false,
|
|
53
56
|
}));
|
|
@@ -23,6 +23,7 @@ const fetchInvoicingSubscriptionFormPageEpic = (actions$) => actions$.pipe((0, o
|
|
|
23
23
|
// unfiltered list would replace the store's plans with every catalog
|
|
24
24
|
// item.
|
|
25
25
|
(0, invoicingCatalogListViewReducer_1.fetchInvoicingCatalogPlanList)({
|
|
26
|
+
fetchAll: true,
|
|
26
27
|
filters: { type: 'plan' },
|
|
27
28
|
keepExistingListItems: false,
|
|
28
29
|
}),
|
package/lib/view/invoicing/editInvoicingSubscriptionDetailView/invoicingSubscriptionFormConfig.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export declare const INVOICING_SUBSCRIPTION_SHIPPING_ADDRESS_TYPE: "invoicing_su
|
|
|
8
8
|
* their day gets their own calendar date rather than yesterday's.
|
|
9
9
|
*/
|
|
10
10
|
export declare const invoicingSubscriptionTodayIso: () => string;
|
|
11
|
+
/** A flat fee bills its price once, so its quantity is always 1. */
|
|
12
|
+
export declare const invoicingPriceHasQuantity: (pricingModel?: string) => boolean;
|
|
11
13
|
/**
|
|
12
14
|
* Clamp a subscription-item quantity to the minimum the API accepts. Shared by
|
|
13
15
|
* the plan, add-on and charge items so none of them can bill a 0 or a fraction.
|
package/lib/view/invoicing/editInvoicingSubscriptionDetailView/invoicingSubscriptionFormConfig.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.entityToInvoicingSubscriptionFormLocalData = exports.initialInvoicingSubscriptionFormLocalData = exports.toInvoicingSubscriptionBillingCycles = exports.toInvoicingSubscriptionQuantity = exports.invoicingSubscriptionTodayIso = exports.INVOICING_SUBSCRIPTION_SHIPPING_ADDRESS_TYPE = void 0;
|
|
3
|
+
exports.entityToInvoicingSubscriptionFormLocalData = exports.initialInvoicingSubscriptionFormLocalData = exports.toInvoicingSubscriptionBillingCycles = exports.toInvoicingSubscriptionQuantity = exports.invoicingPriceHasQuantity = exports.invoicingSubscriptionTodayIso = exports.INVOICING_SUBSCRIPTION_SHIPPING_ADDRESS_TYPE = void 0;
|
|
4
4
|
const zeniDayJS_1 = require("../../../zeniDayJS");
|
|
5
5
|
/** `AddressType` key used when the subscription form opens the shared Address screen. */
|
|
6
6
|
exports.INVOICING_SUBSCRIPTION_SHIPPING_ADDRESS_TYPE = 'invoicing_subscription_shipping_address';
|
|
@@ -26,6 +26,9 @@ const subscriptionItemQuantities = (subscription, itemType) => {
|
|
|
26
26
|
*/
|
|
27
27
|
const invoicingSubscriptionTodayIso = () => (0, zeniDayJS_1.dateNow)().format('YYYY-MM-DD');
|
|
28
28
|
exports.invoicingSubscriptionTodayIso = invoicingSubscriptionTodayIso;
|
|
29
|
+
/** A flat fee bills its price once, so its quantity is always 1. */
|
|
30
|
+
const invoicingPriceHasQuantity = (pricingModel) => pricingModel !== 'flat_fee';
|
|
31
|
+
exports.invoicingPriceHasQuantity = invoicingPriceHasQuantity;
|
|
29
32
|
/**
|
|
30
33
|
* Clamp a subscription-item quantity to the minimum the API accepts. Shared by
|
|
31
34
|
* the plan, add-on and charge items so none of them can bill a 0 or a fraction.
|
package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js
CHANGED
|
@@ -12,6 +12,7 @@ const responsePayload_1 = require("../../../responsePayload");
|
|
|
12
12
|
const addressViewReducer_1 = require("../../addressView/addressViewReducer");
|
|
13
13
|
const editInvoicingCustomerDetailViewReducer_1 = require("../editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer");
|
|
14
14
|
const invoiceListReducer_1 = require("../invoiceList/invoiceListReducer");
|
|
15
|
+
const invoicingCatalogListViewSelector_1 = require("../invoicingCatalogListView/invoicingCatalogListViewSelector");
|
|
15
16
|
const invoicingSubscriptionListViewReducer_1 = require("../invoicingSubscriptionListView/invoicingSubscriptionListViewReducer");
|
|
16
17
|
const editInvoicingSubscriptionDetailViewPayload_1 = require("./editInvoicingSubscriptionDetailViewPayload");
|
|
17
18
|
const editInvoicingSubscriptionDetailViewReducer_1 = require("./editInvoicingSubscriptionDetailViewReducer");
|
|
@@ -29,7 +30,10 @@ const invoicingSubscriptionSaveRequest = (rootState, id) => {
|
|
|
29
30
|
// Shipping address captured via the shared Address screen is sent as an
|
|
30
31
|
// object in the payload (same pattern as the customer save flow).
|
|
31
32
|
const shippingAddress = rootState.addressViewState.newAddressState?.[invoicingSubscriptionFormConfig_1.INVOICING_SUBSCRIPTION_SHIPPING_ADDRESS_TYPE]?.addressToCreate;
|
|
32
|
-
const
|
|
33
|
+
const flatFeePriceCodes = new Set((0, invoicingCatalogListViewSelector_1.getInvoicingCatalogPrices)(Object.values(rootState.invoicingCatalogState.itemByID))
|
|
34
|
+
.filter((price) => !(0, invoicingSubscriptionFormConfig_1.invoicingPriceHasQuantity)(price.pricingModel))
|
|
35
|
+
.flatMap((price) => price.priceCode ?? []));
|
|
36
|
+
const data = (0, editInvoicingSubscriptionDetailViewPayload_1.localDataToSaveInvoicingSubscriptionPayload)(localData, shippingAddress, flatFeePriceCodes);
|
|
33
37
|
if (id != null) {
|
|
34
38
|
delete data.coupon_ids;
|
|
35
39
|
}
|
package/lib/view/invoicing/invoicingCatalogListView/epics/fetchInvoicingCatalogPlanListEpic.js
CHANGED
|
@@ -26,19 +26,30 @@ const buildPlanQuery = (filters, cursor, sort) => {
|
|
|
26
26
|
return params;
|
|
27
27
|
};
|
|
28
28
|
const fetchInvoicingCatalogPlanListEpic = (actions$, _state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(invoicingCatalogListViewReducer_1.fetchInvoicingCatalogPlanList.match), (0, operators_1.switchMap)((action) => {
|
|
29
|
-
const { filters, cursor, keepExistingListItems, sort } = action.payload;
|
|
29
|
+
const { filters, cursor, fetchAll, keepExistingListItems, sort } = action.payload;
|
|
30
30
|
const apiUrl = (0, invoicingApiHelper_1.withQuery)(`${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/catalog/plans`, buildPlanQuery(filters, cursor, sort));
|
|
31
31
|
return zeniAPI.getJSON(apiUrl).pipe((0, operators_1.switchMap)((response) => {
|
|
32
32
|
if ((0, responsePayload_1.isSuccessResponse)(response)) {
|
|
33
33
|
const items = response.data?.items ?? [];
|
|
34
|
-
|
|
34
|
+
const nextCursor = response.data?.next_cursor ?? null;
|
|
35
|
+
const pageActions = [
|
|
35
36
|
(0, invoicingCatalogReducer_1.updateInvoicingCatalogItems)(items, 'merge'),
|
|
36
37
|
(0, invoicingCatalogListViewReducer_1.updateInvoicingCatalogPlanList)({
|
|
37
38
|
ids: items.map((item) => item.id),
|
|
38
|
-
nextCursor
|
|
39
|
+
nextCursor,
|
|
39
40
|
keepExistingListItems,
|
|
40
41
|
}),
|
|
41
42
|
];
|
|
43
|
+
if (fetchAll && nextCursor != null && nextCursor !== cursor) {
|
|
44
|
+
pageActions.push((0, invoicingCatalogListViewReducer_1.fetchInvoicingCatalogPlanList)({
|
|
45
|
+
cursor: nextCursor,
|
|
46
|
+
fetchAll,
|
|
47
|
+
filters,
|
|
48
|
+
keepExistingListItems: true,
|
|
49
|
+
sort,
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
return pageActions;
|
|
42
53
|
}
|
|
43
54
|
return (0, rxjs_1.of)((0, invoicingCatalogListViewReducer_1.updateInvoicingCatalogPlanListFailure)(response.status));
|
|
44
55
|
}), (0, operators_1.catchError)((error) => (0, rxjs_1.of)((0, invoicingCatalogListViewReducer_1.updateInvoicingCatalogPlanListFailure)((0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', 'Fetch catalog plan list REST API call errored out ' +
|
package/lib/view/invoicing/invoicingCatalogListView/epics/fetchInvoicingCatalogProductListEpic.js
CHANGED
|
@@ -26,19 +26,30 @@ const buildProductQuery = (filters, cursor, sort) => {
|
|
|
26
26
|
return params;
|
|
27
27
|
};
|
|
28
28
|
const fetchInvoicingCatalogProductListEpic = (actions$, _state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(invoicingCatalogListViewReducer_1.fetchInvoicingCatalogProductList.match), (0, operators_1.switchMap)((action) => {
|
|
29
|
-
const { filters, cursor, keepExistingListItems, sort } = action.payload;
|
|
29
|
+
const { filters, cursor, fetchAll, keepExistingListItems, sort } = action.payload;
|
|
30
30
|
const apiUrl = (0, invoicingApiHelper_1.withQuery)(`${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/catalog/plans`, buildProductQuery(filters, cursor, sort));
|
|
31
31
|
return zeniAPI.getJSON(apiUrl).pipe((0, operators_1.switchMap)((response) => {
|
|
32
32
|
if ((0, responsePayload_1.isSuccessResponse)(response)) {
|
|
33
33
|
const items = response.data?.items ?? [];
|
|
34
|
-
|
|
34
|
+
const nextCursor = response.data?.next_cursor ?? null;
|
|
35
|
+
const pageActions = [
|
|
35
36
|
(0, invoicingCatalogReducer_1.updateInvoicingCatalogItems)(items, 'merge'),
|
|
36
37
|
(0, invoicingCatalogListViewReducer_1.updateInvoicingCatalogProductList)({
|
|
37
38
|
ids: items.map((item) => item.id),
|
|
38
|
-
nextCursor
|
|
39
|
+
nextCursor,
|
|
39
40
|
keepExistingListItems,
|
|
40
41
|
}),
|
|
41
42
|
];
|
|
43
|
+
if (fetchAll && nextCursor != null && nextCursor !== cursor) {
|
|
44
|
+
pageActions.push((0, invoicingCatalogListViewReducer_1.fetchInvoicingCatalogProductList)({
|
|
45
|
+
cursor: nextCursor,
|
|
46
|
+
fetchAll,
|
|
47
|
+
filters,
|
|
48
|
+
keepExistingListItems: true,
|
|
49
|
+
sort,
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
return pageActions;
|
|
42
53
|
}
|
|
43
54
|
return (0, rxjs_1.of)((0, invoicingCatalogListViewReducer_1.updateInvoicingCatalogProductListFailure)(response.status));
|
|
44
55
|
}), (0, operators_1.catchError)((error) => (0, rxjs_1.of)((0, invoicingCatalogListViewReducer_1.updateInvoicingCatalogProductListFailure)((0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', 'Fetch catalog product list REST API call errored out ' +
|
|
@@ -6,12 +6,15 @@ import { InvoicingCatalogListViewState, InvoicingCatalogPlanFilters, InvoicingCa
|
|
|
6
6
|
export declare const initialState: InvoicingCatalogListViewState;
|
|
7
7
|
export declare const fetchInvoicingCatalogPlanList: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[payload: {
|
|
8
8
|
cursor?: string;
|
|
9
|
+
/** Keep requesting the next page until the list is complete. */
|
|
10
|
+
fetchAll?: boolean;
|
|
9
11
|
filters?: InvoicingCatalogPlanFilters;
|
|
10
12
|
keepExistingListItems?: boolean;
|
|
11
13
|
sort?: InvoicingListSort;
|
|
12
14
|
}], {
|
|
13
15
|
filters: InvoicingCatalogPlanFilters;
|
|
14
16
|
cursor: string | undefined;
|
|
17
|
+
fetchAll: boolean;
|
|
15
18
|
keepExistingListItems: boolean;
|
|
16
19
|
sort: InvoicingListSort | undefined;
|
|
17
20
|
}, "invoicingCatalogListView/fetchInvoicingCatalogPlanList", never, never>, updateInvoicingCatalogPlanList: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
@@ -20,12 +23,15 @@ export declare const fetchInvoicingCatalogPlanList: import("@reduxjs/toolkit").A
|
|
|
20
23
|
nextCursor: string | null;
|
|
21
24
|
}, "invoicingCatalogListView/updateInvoicingCatalogPlanList">, updateInvoicingCatalogPlanListFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<ZeniAPIStatus<Record<string, unknown>>, "invoicingCatalogListView/updateInvoicingCatalogPlanListFailure">, fetchInvoicingCatalogProductList: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[payload: {
|
|
22
25
|
cursor?: string;
|
|
26
|
+
/** Keep requesting the next page until the list is complete. */
|
|
27
|
+
fetchAll?: boolean;
|
|
23
28
|
filters?: InvoicingCatalogProductFilters;
|
|
24
29
|
keepExistingListItems?: boolean;
|
|
25
30
|
sort?: InvoicingListSort;
|
|
26
31
|
}], {
|
|
27
32
|
filters: InvoicingCatalogProductFilters;
|
|
28
33
|
cursor: string | undefined;
|
|
34
|
+
fetchAll: boolean;
|
|
29
35
|
keepExistingListItems: boolean;
|
|
30
36
|
sort: InvoicingListSort | undefined;
|
|
31
37
|
}, "invoicingCatalogListView/fetchInvoicingCatalogProductList", never, never>, updateInvoicingCatalogProductList: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
@@ -40,6 +40,7 @@ const invoicingCatalogListView = (0, toolkit_1.createSlice)({
|
|
|
40
40
|
payload: {
|
|
41
41
|
filters: payload.filters ?? {},
|
|
42
42
|
cursor: payload.cursor,
|
|
43
|
+
fetchAll: payload.fetchAll ?? false,
|
|
43
44
|
keepExistingListItems: payload.keepExistingListItems ?? false,
|
|
44
45
|
sort: payload.sort,
|
|
45
46
|
},
|
|
@@ -74,6 +75,7 @@ const invoicingCatalogListView = (0, toolkit_1.createSlice)({
|
|
|
74
75
|
payload: {
|
|
75
76
|
filters: payload.filters ?? {},
|
|
76
77
|
cursor: payload.cursor,
|
|
78
|
+
fetchAll: payload.fetchAll ?? false,
|
|
77
79
|
keepExistingListItems: payload.keepExistingListItems ?? false,
|
|
78
80
|
sort: payload.sort,
|
|
79
81
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FetchStateAndError, ID } from '../../commonStateTypes/common';
|
|
1
|
+
import { FetchState, FetchStateAndError, ID } from '../../commonStateTypes/common';
|
|
2
2
|
import { ZeniAPIStatus } from '../../responsePayload';
|
|
3
3
|
import { InvoicingListSort } from './invoicingApiHelper';
|
|
4
4
|
/**
|
|
@@ -42,6 +42,16 @@ export declare const isSameInvoicingListQuery: (a: {
|
|
|
42
42
|
filters: object;
|
|
43
43
|
sort?: InvoicingListSort;
|
|
44
44
|
}) => boolean;
|
|
45
|
+
/**
|
|
46
|
+
* For a picker that searches client-side: reuse only a complete list fetched
|
|
47
|
+
* with the picker's own query, else load every page of it.
|
|
48
|
+
*/
|
|
49
|
+
export declare const shouldFetchFullInvoicingList: (list: {
|
|
50
|
+
fetchState: FetchState;
|
|
51
|
+
filters: object;
|
|
52
|
+
nextCursor: string | null;
|
|
53
|
+
sort?: InvoicingListSort;
|
|
54
|
+
}, pickerFilters: object, cacheOverride: boolean) => boolean;
|
|
45
55
|
/** What opening a tab should fetch, if anything. */
|
|
46
56
|
export type InvoicingListTabFetch = {
|
|
47
57
|
kind: 'none';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isSameInvoicingListQuery = exports.invoicingListStatusParam = exports.invoicingListFetchTab = exports.invoicingListSourceTab = exports.getInvoicingListTab = exports.INVOICING_LIST_ALL_TAB = void 0;
|
|
3
|
+
exports.shouldFetchFullInvoicingList = exports.isSameInvoicingListQuery = exports.invoicingListStatusParam = exports.invoicingListFetchTab = exports.invoicingListSourceTab = exports.getInvoicingListTab = exports.INVOICING_LIST_ALL_TAB = void 0;
|
|
4
4
|
exports.planInvoicingListTabFetch = planInvoicingListTabFetch;
|
|
5
5
|
exports.startInvoicingListTabFetch = startInvoicingListTabFetch;
|
|
6
6
|
exports.completeInvoicingListTabFetch = completeInvoicingListTabFetch;
|
|
@@ -46,6 +46,15 @@ const isSameInvoicingListQuery = (a, b) => {
|
|
|
46
46
|
a.sort?.dir === b.sort?.dir);
|
|
47
47
|
};
|
|
48
48
|
exports.isSameInvoicingListQuery = isSameInvoicingListQuery;
|
|
49
|
+
/**
|
|
50
|
+
* For a picker that searches client-side: reuse only a complete list fetched
|
|
51
|
+
* with the picker's own query, else load every page of it.
|
|
52
|
+
*/
|
|
53
|
+
const shouldFetchFullInvoicingList = (list, pickerFilters, cacheOverride) => cacheOverride ||
|
|
54
|
+
list.fetchState !== 'Completed' ||
|
|
55
|
+
list.nextCursor != null ||
|
|
56
|
+
!(0, exports.isSameInvoicingListQuery)({ filters: list.filters, sort: list.sort }, { filters: pickerFilters });
|
|
57
|
+
exports.shouldFetchFullInvoicingList = shouldFetchFullInvoicingList;
|
|
49
58
|
/**
|
|
50
59
|
* Fetch a tab that is empty or failed; top up one that holds less than a page
|
|
51
60
|
* while the server says there is more; leave a full or complete one cached.
|
|
@@ -54,8 +54,6 @@ function toSaveNotificationSettingsPayload(localData, notificationSettingsData)
|
|
|
54
54
|
pending_bill_approval_notification: null,
|
|
55
55
|
pending_reimbursement_approval_notification: null,
|
|
56
56
|
is_pending_reimbursement_approval_notification_active: null,
|
|
57
|
-
is_pending_task_notification_active: null,
|
|
58
|
-
pending_task_notification: null,
|
|
59
57
|
is_month_end_insights_active: monthEndInsightsNotificationSettings.enabled != null
|
|
60
58
|
? monthEndInsights.notify
|
|
61
59
|
: null,
|
|
@@ -83,10 +81,5 @@ function toSaveNotificationSettingsPayload(localData, notificationSettingsData)
|
|
|
83
81
|
0;
|
|
84
82
|
payload.pending_reimbursement_approval_notification = toRemindersPayload(reminders.pendingReimbursementApprovalNotification);
|
|
85
83
|
}
|
|
86
|
-
if (reminderNotificationSettings.pendingTaskNotification != null) {
|
|
87
|
-
payload.is_pending_task_notification_active =
|
|
88
|
-
reminders.pendingTaskNotification.preferredMode.length > 0;
|
|
89
|
-
payload.pending_task_notification = toRemindersPayload(reminders.pendingTaskNotification);
|
|
90
|
-
}
|
|
91
84
|
return payload;
|
|
92
85
|
}
|
|
@@ -153,7 +153,6 @@ const isRemindersSectionVisibleOnSettingsPage = (reminderNotificationSettings) =
|
|
|
153
153
|
reminderNotificationSettings.pendingBillApprovalNotification != null ||
|
|
154
154
|
reminderNotificationSettings.pendingReimbursementApprovalNotification !=
|
|
155
155
|
null ||
|
|
156
|
-
reminderNotificationSettings.pendingTaskNotification != null ||
|
|
157
156
|
reminderNotificationSettings.uncategorizedTransactionNotification != null);
|
|
158
157
|
};
|
|
159
158
|
exports.isRemindersSectionVisibleOnSettingsPage = isRemindersSectionVisibleOnSettingsPage;
|
|
@@ -32,12 +32,10 @@ export interface NotificationSettingsPayload {
|
|
|
32
32
|
is_month_end_insights_active: boolean | null;
|
|
33
33
|
is_pending_bill_approval_notification_active: boolean | null;
|
|
34
34
|
is_pending_reimbursement_approval_notification_active: boolean | null;
|
|
35
|
-
is_pending_task_notification_active: boolean | null;
|
|
36
35
|
is_uncategorized_transaction_notification_active: boolean | null;
|
|
37
36
|
missing_receipt_notification: RemindersPayload | null;
|
|
38
37
|
pending_bill_approval_notification: RemindersPayload | null;
|
|
39
38
|
pending_reimbursement_approval_notification: RemindersPayload | null;
|
|
40
|
-
pending_task_notification: RemindersPayload | null;
|
|
41
39
|
uncategorized_transaction_notification: RemindersPayload | null;
|
|
42
40
|
month_end_email_recipient_email_ids?: string[];
|
|
43
41
|
task_checklist_preferences?: NotificationPreferencesPayload;
|
|
@@ -8,7 +8,6 @@ const settingsViewState_1 = require("./settingsViewState");
|
|
|
8
8
|
const initialNotificationSettingsData = {
|
|
9
9
|
reminderNotificationSettings: {
|
|
10
10
|
missingReceiptNotification: null,
|
|
11
|
-
pendingTaskNotification: null,
|
|
12
11
|
uncategorizedTransactionNotification: null,
|
|
13
12
|
pendingBillApprovalNotification: null,
|
|
14
13
|
pendingReimbursementApprovalNotification: null,
|
|
@@ -49,7 +48,6 @@ const initialNotificationSettingsLocalData = {
|
|
|
49
48
|
missingReceiptNotification: initialRemindersLocalData,
|
|
50
49
|
pendingBillApprovalNotification: initialRemindersLocalData,
|
|
51
50
|
pendingReimbursementApprovalNotification: initialRemindersLocalData,
|
|
52
|
-
pendingTaskNotification: initialRemindersLocalData,
|
|
53
51
|
uncategorizedTransactionNotification: initialRemindersLocalData,
|
|
54
52
|
},
|
|
55
53
|
};
|
|
@@ -379,7 +377,7 @@ function toRemindersLocalData(reminderNotificationSettingDetails) {
|
|
|
379
377
|
}
|
|
380
378
|
const toNotificationSettingsLocalData = (data, loggedInUserEmail) => {
|
|
381
379
|
const { monthEndInsightsNotificationSettings, reminderNotificationSettings } = data;
|
|
382
|
-
const { missingReceiptNotification, pendingBillApprovalNotification, pendingReimbursementApprovalNotification,
|
|
380
|
+
const { missingReceiptNotification, pendingBillApprovalNotification, pendingReimbursementApprovalNotification, uncategorizedTransactionNotification, } = reminderNotificationSettings;
|
|
383
381
|
return {
|
|
384
382
|
monthEndInsights: {
|
|
385
383
|
notify: monthEndInsightsNotificationSettings.enabled ??
|
|
@@ -390,22 +388,10 @@ const toNotificationSettingsLocalData = (data, loggedInUserEmail) => {
|
|
|
390
388
|
missingReceiptNotification: toRemindersLocalData(missingReceiptNotification),
|
|
391
389
|
pendingBillApprovalNotification: toRemindersLocalData(pendingBillApprovalNotification),
|
|
392
390
|
pendingReimbursementApprovalNotification: toRemindersLocalData(pendingReimbursementApprovalNotification),
|
|
393
|
-
pendingTaskNotification: toRemindersLocalData(pendingTaskNotification),
|
|
394
391
|
uncategorizedTransactionNotification: toRemindersLocalData(uncategorizedTransactionNotification),
|
|
395
392
|
},
|
|
396
393
|
};
|
|
397
394
|
};
|
|
398
|
-
function toPendingTaskNotificationActive(data) {
|
|
399
|
-
if (data.is_pending_task_notification_active != null) {
|
|
400
|
-
return data.is_pending_task_notification_active;
|
|
401
|
-
}
|
|
402
|
-
// Rows saved before this flag existed store null. It shares the
|
|
403
|
-
// transactions-reminder scope with missing receipts and uncategorized
|
|
404
|
-
// transactions, so a set sibling means the user has access.
|
|
405
|
-
const hasTransactionReminderAccess = data.is_missing_receipt_notification_active != null ||
|
|
406
|
-
data.is_uncategorized_transaction_notification_active != null;
|
|
407
|
-
return hasTransactionReminderAccess ? false : null;
|
|
408
|
-
}
|
|
409
395
|
function toReminderNotificationSettingsDetails(isNotificationActive, remindersPayload) {
|
|
410
396
|
if (isNotificationActive != null) {
|
|
411
397
|
return {
|
|
@@ -426,7 +412,6 @@ const toNotificationSettings = (data) => {
|
|
|
426
412
|
missingReceiptNotification: toReminderNotificationSettingsDetails(data.is_missing_receipt_notification_active, data.missing_receipt_notification),
|
|
427
413
|
pendingBillApprovalNotification: toReminderNotificationSettingsDetails(data.is_pending_bill_approval_notification_active, data.pending_bill_approval_notification),
|
|
428
414
|
pendingReimbursementApprovalNotification: toReminderNotificationSettingsDetails(data.is_pending_reimbursement_approval_notification_active, data.pending_reimbursement_approval_notification),
|
|
429
|
-
pendingTaskNotification: toReminderNotificationSettingsDetails(toPendingTaskNotificationActive(data), data.pending_task_notification),
|
|
430
415
|
uncategorizedTransactionNotification: toReminderNotificationSettingsDetails(data.is_uncategorized_transaction_notification_active, data.uncategorized_transaction_notification),
|
|
431
416
|
},
|
|
432
417
|
monthEndInsightsNotificationSettings: {
|
|
@@ -9,8 +9,8 @@ interface BillingAddress extends FetchStateAndError {
|
|
|
9
9
|
export interface IntegrationsSettings {
|
|
10
10
|
refreshQBO: FetchStateAndError;
|
|
11
11
|
}
|
|
12
|
-
export declare const REMINDER_NOTIFICATION_TYPES: readonly ["missingReceiptNotification", "
|
|
13
|
-
declare const toReminderTypeKey: (v: string) => "missingReceiptNotification" | "
|
|
12
|
+
export declare const REMINDER_NOTIFICATION_TYPES: readonly ["missingReceiptNotification", "uncategorizedTransactionNotification", "pendingBillApprovalNotification", "pendingReimbursementApprovalNotification"];
|
|
13
|
+
declare const toReminderTypeKey: (v: string) => "missingReceiptNotification" | "uncategorizedTransactionNotification" | "pendingBillApprovalNotification" | "pendingReimbursementApprovalNotification";
|
|
14
14
|
export type ReminderTypeKey = ReturnType<typeof toReminderTypeKey>;
|
|
15
15
|
export declare const toRemindersFrequencyType: (v: string) => "weekly" | "monthly" | "daily" | "bi-weekly";
|
|
16
16
|
export type FrequencyTypeKey = ReturnType<typeof toRemindersFrequencyType>;
|
|
@@ -4,7 +4,6 @@ exports.toNotificationMode = exports.NOTIFICATION_MODES = exports.toRemindersFre
|
|
|
4
4
|
const stringToUnion_1 = require("../../commonStateTypes/stringToUnion");
|
|
5
5
|
exports.REMINDER_NOTIFICATION_TYPES = [
|
|
6
6
|
'missingReceiptNotification',
|
|
7
|
-
'pendingTaskNotification',
|
|
8
7
|
'uncategorizedTransactionNotification',
|
|
9
8
|
'pendingBillApprovalNotification',
|
|
10
9
|
'pendingReimbursementApprovalNotification',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeniai/client-epic-state",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.84",
|
|
4
4
|
"description": "Shared module between Web & Mobile containing required abstractions for state management, async network communication. ",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "lib/esm/index.js",
|