@zeniai/client-epic-state 5.2.73 → 5.2.74-betaAK0

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.
Files changed (131) hide show
  1. package/lib/entity/aiCfo/aiCfoState.d.ts +2 -2
  2. package/lib/entity/billPay/billTransaction/billTransactionState.d.ts +2 -2
  3. package/lib/entity/chargeCardTransaction/chargeCardTransaction.d.ts +1 -1
  4. package/lib/entity/depositAccountTransaction/depositAccountTransaction.d.ts +1 -1
  5. package/lib/entity/invoicing/coupon/couponSelector.d.ts +1 -0
  6. package/lib/entity/invoicing/coupon/couponSelector.js +5 -0
  7. package/lib/entity/invoicing/{product/productPayload.d.ts → invoicingCatalog/invoicingCatalogPayload.d.ts} +44 -6
  8. package/lib/entity/invoicing/{product/productPayload.js → invoicingCatalog/invoicingCatalogPayload.js} +50 -6
  9. package/lib/entity/invoicing/invoicingCatalog/invoicingCatalogReducer.d.ts +12 -0
  10. package/lib/entity/invoicing/{plan/planReducer.js → invoicingCatalog/invoicingCatalogReducer.js} +20 -21
  11. package/lib/entity/invoicing/{plan/planSelector.d.ts → invoicingCatalog/invoicingCatalogSelector.d.ts} +5 -6
  12. package/lib/entity/invoicing/{plan/planSelector.js → invoicingCatalog/invoicingCatalogSelector.js} +12 -12
  13. package/lib/entity/invoicing/{product/productState.d.ts → invoicingCatalog/invoicingCatalogState.d.ts} +42 -6
  14. package/lib/entity/jeSchedules/jeSchedulesTypes.d.ts +4 -4
  15. package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
  16. package/lib/entity/snackbar/snackbarTypes.js +1 -0
  17. package/lib/epic.d.ts +4 -2
  18. package/lib/epic.js +5 -1
  19. package/lib/esm/entity/invoicing/coupon/couponSelector.js +4 -0
  20. package/lib/esm/entity/invoicing/{product/productPayload.js → invoicingCatalog/invoicingCatalogPayload.js} +46 -3
  21. package/lib/esm/entity/invoicing/{plan/planReducer.js → invoicingCatalog/invoicingCatalogReducer.js} +19 -20
  22. package/lib/esm/entity/invoicing/{plan/planSelector.js → invoicingCatalog/invoicingCatalogSelector.js} +8 -8
  23. package/lib/esm/entity/snackbar/snackbarTypes.js +1 -0
  24. package/lib/esm/epic.js +5 -1
  25. package/lib/esm/index.js +7 -10
  26. package/lib/esm/reducer.js +4 -7
  27. package/lib/esm/view/invoicing/createInvoice/createInvoiceEpic.js +9 -1
  28. package/lib/esm/view/invoicing/createInvoice/createInvoiceSelector.js +1 -1
  29. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +5 -3
  30. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/fetchInvoicingCatalogItemDetailEpic.js +2 -2
  31. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/runInvoicingCatalogPlanActionEpic.js +2 -2
  32. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/runInvoicingCatalogProductActionEpic.js +4 -4
  33. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +3 -7
  34. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/seedInvoicingCatalogItemFormDraftEpic.js +2 -2
  35. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.js +5 -1
  36. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewPayload.js +55 -0
  37. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.js +35 -1
  38. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +80 -2
  39. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionEstimateEpic.js +49 -0
  40. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +23 -14
  41. package/lib/esm/view/invoicing/invoiceList/invoiceListReducer.js +8 -1
  42. package/lib/esm/view/invoicing/invoicingCatalogListView/epics/fetchInvoicingCatalogPlanListEpic.js +2 -2
  43. package/lib/esm/view/invoicing/invoicingCatalogListView/epics/fetchInvoicingCatalogProductListEpic.js +2 -2
  44. package/lib/esm/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewSelector.js +55 -22
  45. package/lib/esm/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewState.js +1 -20
  46. package/lib/esm/view/spendManagement/commonSetup/epic/setup/resetKycKybEpic.js +37 -0
  47. package/lib/esm/view/spendManagement/commonSetup/setupViewReducer.js +21 -1
  48. package/lib/esm/view/spendManagement/commonSetup/setupViewSelector.js +2 -1
  49. package/lib/index.d.ts +12 -16
  50. package/lib/index.js +41 -45
  51. package/lib/reducer.d.ts +1 -2
  52. package/lib/reducer.js +4 -7
  53. package/lib/rootStateTypes.d.ts +3 -5
  54. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
  55. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.d.ts +2 -2
  56. package/lib/view/invoicing/createInvoice/createInvoiceEpic.d.ts +3 -2
  57. package/lib/view/invoicing/createInvoice/createInvoiceEpic.js +8 -0
  58. package/lib/view/invoicing/createInvoice/createInvoiceSelector.d.ts +2 -2
  59. package/lib/view/invoicing/createInvoice/createInvoiceSelector.js +2 -2
  60. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.d.ts +8 -9
  61. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.d.ts +2 -2
  62. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +5 -3
  63. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/fetchInvoicingCatalogItemDetailEpic.d.ts +2 -2
  64. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/fetchInvoicingCatalogItemDetailEpic.js +2 -2
  65. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/invoicingCatalogItemFormHelper.d.ts +3 -4
  66. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/runInvoicingCatalogPlanActionEpic.d.ts +2 -2
  67. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/runInvoicingCatalogPlanActionEpic.js +2 -2
  68. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/runInvoicingCatalogProductActionEpic.d.ts +2 -3
  69. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/runInvoicingCatalogProductActionEpic.js +4 -4
  70. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.d.ts +2 -3
  71. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +3 -7
  72. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/seedInvoicingCatalogItemFormDraftEpic.js +2 -2
  73. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.d.ts +3 -1
  74. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.js +6 -2
  75. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewPayload.d.ts +46 -1
  76. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewPayload.js +57 -1
  77. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.d.ts +10 -2
  78. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.js +36 -2
  79. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.d.ts +23 -3
  80. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +83 -3
  81. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewState.d.ts +58 -0
  82. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionEstimateEpic.d.ts +7 -0
  83. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionEstimateEpic.js +53 -0
  84. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.d.ts +11 -1
  85. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +26 -15
  86. package/lib/view/invoicing/invoiceList/invoiceListReducer.d.ts +3 -1
  87. package/lib/view/invoicing/invoiceList/invoiceListReducer.js +9 -2
  88. package/lib/view/invoicing/invoicingCatalogListView/epics/fetchInvoicingCatalogListPageEpic.d.ts +2 -2
  89. package/lib/view/invoicing/invoicingCatalogListView/epics/fetchInvoicingCatalogPlanListEpic.d.ts +2 -2
  90. package/lib/view/invoicing/invoicingCatalogListView/epics/fetchInvoicingCatalogPlanListEpic.js +2 -2
  91. package/lib/view/invoicing/invoicingCatalogListView/epics/fetchInvoicingCatalogProductListEpic.d.ts +2 -2
  92. package/lib/view/invoicing/invoicingCatalogListView/epics/fetchInvoicingCatalogProductListEpic.js +2 -2
  93. package/lib/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewPayload.d.ts +3 -3
  94. package/lib/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewSelector.d.ts +33 -15
  95. package/lib/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewSelector.js +57 -23
  96. package/lib/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewState.d.ts +0 -15
  97. package/lib/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewState.js +0 -22
  98. package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewReducer.d.ts +1 -1
  99. package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewState.d.ts +1 -1
  100. package/lib/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewReducer.d.ts +1 -1
  101. package/lib/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewState.d.ts +1 -1
  102. package/lib/view/scheduleView/scheduleListView/scheduleListTypes.d.ts +1 -1
  103. package/lib/view/spendManagement/billPay/billList/billListReducer.d.ts +5 -5
  104. package/lib/view/spendManagement/billPay/billList/billListState.d.ts +1 -1
  105. package/lib/view/spendManagement/billPay/editBillView/editBillViewReducer.d.ts +2 -2
  106. package/lib/view/spendManagement/commonSetup/epic/setup/resetKycKybEpic.d.ts +8 -0
  107. package/lib/view/spendManagement/commonSetup/epic/setup/resetKycKybEpic.js +41 -0
  108. package/lib/view/spendManagement/commonSetup/setupViewReducer.d.ts +1 -1
  109. package/lib/view/spendManagement/commonSetup/setupViewReducer.js +22 -2
  110. package/lib/view/spendManagement/commonSetup/setupViewSelector.d.ts +1 -0
  111. package/lib/view/spendManagement/commonSetup/setupViewSelector.js +2 -1
  112. package/lib/view/spendManagement/commonSetup/setupViewState.d.ts +1 -0
  113. package/lib/view/spendManagement/reimbursement/editRemiView/editRemiViewReducer.d.ts +2 -2
  114. package/lib/view/spendManagement/reimbursement/remiListView/remiListReducer.d.ts +5 -5
  115. package/lib/view/spendManagement/reimbursement/remiListView/remiListState.d.ts +2 -2
  116. package/package.json +1 -26
  117. package/lib/entity/invoicing/plan/planPayload.d.ts +0 -40
  118. package/lib/entity/invoicing/plan/planPayload.js +0 -51
  119. package/lib/entity/invoicing/plan/planReducer.d.ts +0 -14
  120. package/lib/entity/invoicing/plan/planState.d.ts +0 -43
  121. package/lib/entity/invoicing/product/productReducer.d.ts +0 -9
  122. package/lib/entity/invoicing/product/productReducer.js +0 -50
  123. package/lib/entity/invoicing/product/productSelector.d.ts +0 -4
  124. package/lib/entity/invoicing/product/productSelector.js +0 -17
  125. package/lib/entity/invoicing/product/productState.js +0 -2
  126. package/lib/esm/entity/invoicing/plan/planPayload.js +0 -47
  127. package/lib/esm/entity/invoicing/product/productReducer.js +0 -43
  128. package/lib/esm/entity/invoicing/product/productSelector.js +0 -10
  129. package/lib/esm/entity/invoicing/product/productState.js +0 -1
  130. /package/lib/entity/invoicing/{plan/planState.js → invoicingCatalog/invoicingCatalogState.js} +0 -0
  131. /package/lib/esm/entity/invoicing/{plan/planState.js → invoicingCatalog/invoicingCatalogState.js} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { getAddressByAddressId } from '../../../entity/address/addressSelector';
2
- import { invoicingPricePointDisplayAmount } from '../../../entity/invoicing/plan/planSelector';
2
+ import { invoicingPricePointDisplayAmount } from '../../../entity/invoicing/invoicingCatalog/invoicingCatalogSelector';
3
3
  import { getInvoicingCatalogListView, getInvoicingCatalogPrices, } from '../invoicingCatalogListView/invoicingCatalogListViewSelector';
4
4
  import { invoicingFieldHelpForGroup } from '../invoicingConfigView/invoicingConfigViewSelector';
5
5
  import { getInvoicingCustomerListView } from '../invoicingCustomerListView/invoicingCustomerListViewSelector';
@@ -1,6 +1,6 @@
1
1
  import { createSelector } from '@reduxjs/toolkit';
2
2
  import { reduceAllFetchState } from '../../../commonStateTypes/reduceFetchState';
3
- import { getInvoicingPlanById, getInvoicingPlansByIds, } from '../../../entity/invoicing/plan/planSelector';
3
+ import { getInvoicingCatalogItemById, getInvoicingCatalogItemsByIds, } from '../../../entity/invoicing/invoicingCatalog/invoicingCatalogSelector';
4
4
  import { getInvoicingConfigView, invoicingFieldHelpForGroup, } from '../invoicingConfigView/invoicingConfigViewSelector';
5
5
  import { getInvoicingFormDraftLocalData, isInvoicingFormDraftPending, } from '../invoicingFormDraft';
6
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';
@@ -36,7 +36,7 @@ const buildApplicableCatalogItemOptions = (plans) => {
36
36
  * Kept referentially stable across unrelated dispatches; the form view filters
37
37
  * out the item being edited (see `getInvoicingCatalogItemFormView`).
38
38
  */
39
- export const getInvoicingApplicableCatalogItemOptions = createSelector((state) => state.invoicingPlanState, (state) => state.invoicingCatalogListViewState, (planState, listViewState) => buildApplicableCatalogItemOptions(getInvoicingPlansByIds(planState, [
39
+ export const getInvoicingApplicableCatalogItemOptions = createSelector((state) => state.invoicingCatalogState, (state) => state.invoicingCatalogListViewState, (catalogState, listViewState) => buildApplicableCatalogItemOptions(getInvoicingCatalogItemsByIds(catalogState, [
40
40
  ...listViewState.planIds,
41
41
  ...listViewState.productIds,
42
42
  ])));
@@ -61,7 +61,9 @@ const getInvoicingCatalogIncomeAccountIds = createSelector([
61
61
  : NO_INVOICING_ACCOUNT_IDS);
62
62
  export const getInvoicingCatalogItemFormView = (state, id) => {
63
63
  const { editDraftById, error, fetchState, newItemDraft, savedItemId, savedKind, } = state.editInvoicingCatalogItemDetailViewState;
64
- const plan = id != null ? getInvoicingPlanById(state.invoicingPlanState, id) : undefined;
64
+ const plan = id != null
65
+ ? getInvoicingCatalogItemById(state.invoicingCatalogState, id)
66
+ : undefined;
65
67
  const kind = plan != null ? 'plan' : undefined;
66
68
  // No entity fallback: the draft is the one source of form values, seeded from
67
69
  // the record by `seedInvoicingCatalogItemFormDraftEpic` when the detail load
@@ -1,6 +1,6 @@
1
1
  import { of } from 'rxjs';
2
2
  import { catchError, filter, switchMap } from 'rxjs/operators';
3
- import { updateInvoicingPlans } from '../../../entity/invoicing/plan/planReducer';
3
+ import { updateInvoicingCatalogItems } from '../../../entity/invoicing/invoicingCatalog/invoicingCatalogReducer';
4
4
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
5
5
  import { fetchInvoicingCatalogItemDetail, updateInvoicingCatalogItemDetail, updateInvoicingCatalogItemDetailFailure, } from './editInvoicingCatalogItemDetailViewReducer';
6
6
  export const fetchInvoicingCatalogItemDetailEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchInvoicingCatalogItemDetail.match), switchMap((action) => {
@@ -9,7 +9,7 @@ export const fetchInvoicingCatalogItemDetailEpic = (actions$, _state$, zeniAPI)
9
9
  return zeniAPI.getJSON(apiUrl).pipe(switchMap((response) => {
10
10
  if (isSuccessResponse(response) && response.data != null) {
11
11
  return [
12
- updateInvoicingPlans([response.data], 'merge'),
12
+ updateInvoicingCatalogItems([response.data], 'merge'),
13
13
  updateInvoicingCatalogItemDetail({ id: response.data.id, kind }),
14
14
  ];
15
15
  }
@@ -1,6 +1,6 @@
1
1
  import { of } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
- import { updateInvoicingPlans } from '../../../entity/invoicing/plan/planReducer';
3
+ import { updateInvoicingCatalogItems } from '../../../entity/invoicing/invoicingCatalog/invoicingCatalogReducer';
4
4
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
5
5
  import { runInvoicingCatalogPlanAction, runInvoicingCatalogPlanActionFailure, runInvoicingCatalogPlanActionSuccess, } from './editInvoicingCatalogItemDetailViewReducer';
6
6
  export const runInvoicingCatalogPlanActionEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(runInvoicingCatalogPlanAction.match), mergeMap((action) => {
@@ -12,7 +12,7 @@ export const runInvoicingCatalogPlanActionEpic = (actions$, _state$, zeniAPI) =>
12
12
  .pipe(mergeMap((response) => {
13
13
  if (isSuccessResponse(response) && response.data != null) {
14
14
  return [
15
- updateInvoicingPlans([response.data], 'merge'),
15
+ updateInvoicingCatalogItems([response.data], 'merge'),
16
16
  runInvoicingCatalogPlanActionSuccess({ id }),
17
17
  ];
18
18
  }
@@ -1,7 +1,6 @@
1
1
  import { of } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
- import { mergeInvoicingPlanSubFamily } from '../../../entity/invoicing/plan/planReducer';
4
- import { updateInvoicingProducts } from '../../../entity/invoicing/product/productReducer';
3
+ import { mergeInvoicingCatalogPricePoint } from '../../../entity/invoicing/invoicingCatalog/invoicingCatalogReducer';
5
4
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
6
5
  import { runInvoicingCatalogProductAction, runInvoicingCatalogProductActionFailure, runInvoicingCatalogProductActionSuccess, } from './editInvoicingCatalogItemDetailViewReducer';
7
6
  export const runInvoicingCatalogProductActionEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(runInvoicingCatalogProductAction.match), mergeMap((action) => {
@@ -16,9 +15,10 @@ export const runInvoicingCatalogProductActionEpic = (actions$, _state$, zeniAPI)
16
15
  .pipe(mergeMap((response) => {
17
16
  if (isSuccessResponse(response) && response.data != null) {
18
17
  return [
19
- updateInvoicingProducts([response.data], 'merge'),
20
18
  ...(parentItemId != null
21
- ? [mergeInvoicingPlanSubFamily(parentItemId, response.data)]
19
+ ? [
20
+ mergeInvoicingCatalogPricePoint(parentItemId, response.data),
21
+ ]
22
22
  : []),
23
23
  runInvoicingCatalogProductActionSuccess({ id: stateKey }),
24
24
  ];
@@ -1,7 +1,6 @@
1
1
  import { forkJoin, from } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
- import { mergeInvoicingPlanSubFamily, updateInvoicingPlans, } from '../../../entity/invoicing/plan/planReducer';
4
- import { updateInvoicingProducts } from '../../../entity/invoicing/product/productReducer';
3
+ import { mergeInvoicingCatalogPricePoint, updateInvoicingCatalogItems, } from '../../../entity/invoicing/invoicingCatalog/invoicingCatalogReducer';
5
4
  import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiError';
6
5
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
7
6
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
@@ -51,11 +50,8 @@ export const saveInvoicingCatalogItemEpic = (actions$, state$, zeniAPI) => actio
51
50
  }
52
51
  const savedProducts = productResponses.flatMap((productResponse) => productResponse.data != null ? [productResponse.data] : []);
53
52
  const resultActions = [
54
- updateInvoicingPlans([savedItem], 'merge'),
55
- ...(savedProducts.length > 0
56
- ? [updateInvoicingProducts(savedProducts, 'merge')]
57
- : []),
58
- ...savedProducts.map((product) => mergeInvoicingPlanSubFamily(savedItem.id, product)),
53
+ updateInvoicingCatalogItems([savedItem], 'merge'),
54
+ ...savedProducts.map((product) => mergeInvoicingCatalogPricePoint(savedItem.id, product)),
59
55
  submitInvoicingCatalogItemFormSuccess({
60
56
  itemId: savedItem.id,
61
57
  kind: 'plan',
@@ -1,6 +1,6 @@
1
1
  import { EMPTY, of } from 'rxjs';
2
2
  import { filter, mergeMap, withLatestFrom } from 'rxjs/operators';
3
- import { getInvoicingPlanById } from '../../../entity/invoicing/plan/planSelector';
3
+ import { getInvoicingCatalogItemById } from '../../../entity/invoicing/invoicingCatalog/invoicingCatalogSelector';
4
4
  import { fetchInvoicingCatalogItemDetailPage, seedInvoicingCatalogItemFormDraft, updateInvoicingCatalogItemDetail, } from './editInvoicingCatalogItemDetailViewReducer';
5
5
  import { entityToInvoicingCatalogItemFormLocalData } from './invoicingCatalogItemFormHelper';
6
6
  /**
@@ -34,7 +34,7 @@ export const seedInvoicingCatalogItemFormDraftEpic = (actions$, state$) => actio
34
34
  }
35
35
  // Nothing to seed from on a cold open; the detail response triggers this
36
36
  // again once it lands.
37
- const plan = getInvoicingPlanById(state.invoicingPlanState, id);
37
+ const plan = getInvoicingCatalogItemById(state.invoicingCatalogState, id);
38
38
  if (plan == null) {
39
39
  return EMPTY;
40
40
  }
@@ -95,6 +95,10 @@ const editInvoicingCustomerDetailView = createSlice({
95
95
  error: undefined,
96
96
  };
97
97
  },
98
+ // Makes the page epic refetch a record that changed server-side (e.g. a new subscription).
99
+ invalidateInvoicingCustomerDetail(draft, action) {
100
+ delete draft.recordStateById[action.payload.id];
101
+ },
98
102
  fetchInvoicingCustomerDetail(draft, action) {
99
103
  draft.recordStateById[action.payload.id] = {
100
104
  fetchState: 'In-Progress',
@@ -198,5 +202,5 @@ const editInvoicingCustomerDetailView = createSlice({
198
202
  });
199
203
  },
200
204
  });
201
- export const { fetchInvoicingPaymentLinkEmailPreview, fetchInvoicingPaymentLinkEmailPreviewPage, updateInvoicingPaymentLinkEmailPreview, updateInvoicingPaymentLinkEmailPreviewFailure, addPromotionalCreditsFailure, addPromotionalCreditsSuccess, fetchInvoicingCustomerDetail, fetchInvoicingCustomerDetailPage, seedInvoicingCustomerFormDraft, updateInvoicingCustomerDetail, resetPromotionalCreditAction, submitAddPromotionalCredits, updateInvoicingCustomerDetailFailure, updateInvoicingCustomerFormDraft, submitInvoicingCustomerForm, submitInvoicingCustomerFormSuccess, submitInvoicingCustomerFormFailure, resetEditInvoicingCustomerDetailView, initializeInvoicingCustomerAddress, claimInvoicingCustomerBillingAddress, } = editInvoicingCustomerDetailView.actions;
205
+ export const { fetchInvoicingPaymentLinkEmailPreview, fetchInvoicingPaymentLinkEmailPreviewPage, updateInvoicingPaymentLinkEmailPreview, updateInvoicingPaymentLinkEmailPreviewFailure, addPromotionalCreditsFailure, addPromotionalCreditsSuccess, fetchInvoicingCustomerDetail, fetchInvoicingCustomerDetailPage, invalidateInvoicingCustomerDetail, seedInvoicingCustomerFormDraft, updateInvoicingCustomerDetail, resetPromotionalCreditAction, submitAddPromotionalCredits, updateInvoicingCustomerDetailFailure, updateInvoicingCustomerFormDraft, submitInvoicingCustomerForm, submitInvoicingCustomerFormSuccess, submitInvoicingCustomerFormFailure, resetEditInvoicingCustomerDetailView, initializeInvoicingCustomerAddress, claimInvoicingCustomerBillingAddress, } = editInvoicingCustomerDetailView.actions;
202
206
  export default editInvoicingCustomerDetailView.reducer;
@@ -1,3 +1,5 @@
1
+ import { toAmount } from '../../../commonStateTypes/amount';
2
+ import { date } from '../../../zeniDayJS';
1
3
  import { toAddressUpdatableInfoPayload } from '../../addressView/addressViewPayload';
2
4
  import { invoicingSubscriptionTodayIso, toInvoicingSubscriptionBillingCycles, toInvoicingSubscriptionQuantity, } from './invoicingSubscriptionFormConfig';
3
5
  /**
@@ -56,3 +58,56 @@ export const localDataToSaveInvoicingSubscriptionPayload = (localData, shippingA
56
58
  : {}),
57
59
  };
58
60
  };
61
+ const toEstimateDate = (value) => value != null && value !== '' ? date(value) : undefined;
62
+ const mapInvoiceEstimate = (payload, money) => payload == null
63
+ ? undefined
64
+ : {
65
+ amountDue: money(payload.amount_due),
66
+ couponDiscountTotal: money(payload.coupon_discount_total),
67
+ creditsApplied: money(payload.credits_applied),
68
+ dueDate: toEstimateDate(payload.due_date),
69
+ grossSubTotal: money(payload.gross_sub_total),
70
+ invoiceDate: toEstimateDate(payload.invoice_date),
71
+ lineItems: payload.line_items.map((lineItem) => ({
72
+ amount: money(lineItem.amount),
73
+ dateFrom: toEstimateDate(lineItem.date_from),
74
+ dateTo: toEstimateDate(lineItem.date_to),
75
+ description: lineItem.description,
76
+ discountAmount: money(lineItem.discount_amount),
77
+ quantity: lineItem.quantity,
78
+ taxAmount: lineItem.tax_amount != null
79
+ ? money(lineItem.tax_amount)
80
+ : undefined,
81
+ unitAmount: money(lineItem.unit_amount),
82
+ })),
83
+ promotionalCreditsApplied: money(payload.promotional_credits_applied),
84
+ subTotal: money(payload.sub_total),
85
+ tax: payload.tax != null ? money(payload.tax) : undefined,
86
+ total: money(payload.total),
87
+ };
88
+ export const mapInvoicingSubscriptionEstimatePayloadToState = (payload) => {
89
+ const money = (amount) => toAmount(amount, payload.currency_code, payload.currency_symbol);
90
+ const term = payload.subscription_estimate;
91
+ const proration = payload.proration_estimate;
92
+ return {
93
+ invoice: mapInvoiceEstimate(payload.invoice_estimate, money),
94
+ nextInvoice: mapInvoiceEstimate(payload.next_invoice_estimate, money),
95
+ proration: proration != null
96
+ ? {
97
+ documentType: proration.document_type,
98
+ invoice: mapInvoiceEstimate(proration.invoice_estimate, money),
99
+ net: money(proration.net),
100
+ }
101
+ : undefined,
102
+ reason: payload.reason,
103
+ term: term != null
104
+ ? {
105
+ becomesNonRenewing: term.becomes_non_renewing_after_this_term === true,
106
+ currentTermEnd: toEstimateDate(term.current_term_end),
107
+ currentTermStart: toEstimateDate(term.current_term_start),
108
+ nextBillingAt: toEstimateDate(term.next_billing_at),
109
+ }
110
+ : undefined,
111
+ willBeInvoiced: payload.will_be_invoiced,
112
+ };
113
+ };
@@ -2,10 +2,14 @@ import { createSlice } from '@reduxjs/toolkit';
2
2
  import { clearAll } from '../../../rootActions';
3
3
  import { applyInvoicingFormDraftEdit, applyInvoicingFormDraftFailure, applyInvoicingFormDraftSaved, applyInvoicingFormDraftSeed, } from '../invoicingFormDraft';
4
4
  import { initialInvoicingSubscriptionFormLocalData } from './invoicingSubscriptionFormConfig';
5
+ const initialEstimateState = {
6
+ fetchState: { fetchState: 'Not-Started', error: undefined },
7
+ };
5
8
  export const initialState = {
6
9
  fetchState: 'Not-Started',
7
10
  error: undefined,
8
11
  editDraftById: {},
12
+ estimateState: initialEstimateState,
9
13
  actionStateById: {},
10
14
  recordStateById: {},
11
15
  shippingAddressOwnerId: undefined,
@@ -19,6 +23,36 @@ const editInvoicingSubscriptionDetailView = createSlice({
19
23
  name: 'editInvoicingSubscriptionDetailView',
20
24
  initialState,
21
25
  reducers: {
26
+ // What saving the draft would bill (no id: create); see the estimate epic.
27
+ fetchInvoicingSubscriptionEstimate(draft, action) {
28
+ draft.estimateState = {
29
+ fetchState: { fetchState: 'In-Progress', error: undefined },
30
+ subscriptionId: action.payload.id,
31
+ };
32
+ },
33
+ // Dropped when the dialog has since moved to another subscription.
34
+ updateInvoicingSubscriptionEstimate(draft, action) {
35
+ if (draft.estimateState.subscriptionId !== action.payload.id) {
36
+ return;
37
+ }
38
+ draft.estimateState.estimate = action.payload.estimate;
39
+ draft.estimateState.fetchState = {
40
+ fetchState: 'Completed',
41
+ error: undefined,
42
+ };
43
+ },
44
+ updateInvoicingSubscriptionEstimateFailure(draft, action) {
45
+ if (draft.estimateState.subscriptionId !== action.payload.id) {
46
+ return;
47
+ }
48
+ draft.estimateState.fetchState = {
49
+ fetchState: 'Error',
50
+ error: action.payload.status,
51
+ };
52
+ },
53
+ clearInvoicingSubscriptionEstimate(draft) {
54
+ draft.estimateState = initialEstimateState;
55
+ },
22
56
  runInvoicingSubscriptionAction(draft, action) {
23
57
  draft.actionStateById[action.payload.id] = {
24
58
  fetchState: 'In-Progress',
@@ -158,5 +192,5 @@ const editInvoicingSubscriptionDetailView = createSlice({
158
192
  });
159
193
  },
160
194
  });
161
- export const { runInvoicingSubscriptionAction, runInvoicingSubscriptionActionFailure, runInvoicingSubscriptionActionSuccess, fetchInvoicingSubscriptionDetail, fetchInvoicingSubscriptionDetailPage, fetchInvoicingSubscriptionFormPage, seedInvoicingSubscriptionFormDraft, updateInvoicingSubscriptionDetail, updateInvoicingSubscriptionDetailFailure, updateInvoicingSubscriptionFormDraft, submitInvoicingSubscriptionForm, submitInvoicingSubscriptionFormSuccess, submitInvoicingSubscriptionFormFailure, resetEditInvoicingSubscriptionDetailView, initializeInvoicingSubscriptionAddress, claimInvoicingSubscriptionShippingAddress, syncInvoicingSubscriptionCoupons, } = editInvoicingSubscriptionDetailView.actions;
195
+ export const { runInvoicingSubscriptionAction, runInvoicingSubscriptionActionFailure, runInvoicingSubscriptionActionSuccess, fetchInvoicingSubscriptionDetail, fetchInvoicingSubscriptionDetailPage, fetchInvoicingSubscriptionFormPage, seedInvoicingSubscriptionFormDraft, updateInvoicingSubscriptionDetail, updateInvoicingSubscriptionDetailFailure, updateInvoicingSubscriptionFormDraft, submitInvoicingSubscriptionForm, submitInvoicingSubscriptionFormSuccess, submitInvoicingSubscriptionFormFailure, resetEditInvoicingSubscriptionDetailView, initializeInvoicingSubscriptionAddress, claimInvoicingSubscriptionShippingAddress, syncInvoicingSubscriptionCoupons, fetchInvoicingSubscriptionEstimate, updateInvoicingSubscriptionEstimate, updateInvoicingSubscriptionEstimateFailure, clearInvoicingSubscriptionEstimate, } = editInvoicingSubscriptionDetailView.actions;
162
196
  export default editInvoicingSubscriptionDetailView.reducer;
@@ -9,6 +9,57 @@ import { getInvoicingCustomerListView } from '../invoicingCustomerListView/invoi
9
9
  import { getInvoicingFormDraftLocalData, isInvoicingFormDraftPending, } from '../invoicingFormDraft';
10
10
  import { initialInvoicingSubscriptionFormLocalData } from './invoicingSubscriptionFormConfig';
11
11
  import { INVOICING_SUBSCRIPTION_SHIPPING_ADDRESS_TYPE } from './invoicingSubscriptionFormConfig';
12
+ const notStartedEstimateView = {
13
+ creditsMayChange: false,
14
+ fetchState: { fetchState: 'Not-Started', error: undefined },
15
+ nonRenewing: false,
16
+ };
17
+ export const toInvoicingSubscriptionEstimateView = ({ estimate, fetchState, subscriptionId }, invoicingSubscriptionID) => {
18
+ // A slot asked for another subscription is not this form's estimate.
19
+ if (subscriptionId !== invoicingSubscriptionID) {
20
+ return notStartedEstimateView;
21
+ }
22
+ if (estimate == null) {
23
+ return { ...notStartedEstimateView, fetchState };
24
+ }
25
+ const { invoice, nextInvoice, proration, reason, term } = estimate;
26
+ const base = {
27
+ fetchState,
28
+ nonRenewing: term?.becomesNonRenewing === true,
29
+ nextBillingAt: term?.nextBillingAt,
30
+ reason: reason !== '' ? reason : undefined,
31
+ termEnd: term?.currentTermEnd,
32
+ termStart: term?.currentTermStart,
33
+ };
34
+ if (proration?.documentType === 'invoice' && proration.invoice != null) {
35
+ return {
36
+ ...base,
37
+ creditsMayChange: false,
38
+ invoice: proration.invoice,
39
+ kind: 'proration_invoice',
40
+ };
41
+ }
42
+ if (proration?.documentType === 'credit_note') {
43
+ return {
44
+ ...base,
45
+ creditsMayChange: false,
46
+ kind: 'proration_credit',
47
+ storeCredit: { ...proration.net, amount: Math.abs(proration.net.amount) },
48
+ };
49
+ }
50
+ if (estimate.willBeInvoiced && invoice != null) {
51
+ return { ...base, creditsMayChange: false, invoice, kind: 'invoice_now' };
52
+ }
53
+ if (nextInvoice != null) {
54
+ return {
55
+ ...base,
56
+ creditsMayChange: true,
57
+ invoice: nextInvoice,
58
+ kind: 'later_invoice',
59
+ };
60
+ }
61
+ return { ...base, creditsMayChange: false, kind: 'reason_only' };
62
+ };
12
63
  export const getEditInvoicingSubscriptionDetailViewState = (state, invoicingSubscriptionID) => {
13
64
  const { error, fetchState, savedSubscriptionId } = state.editInvoicingSubscriptionDetailViewState;
14
65
  const { subscriptionCommercialEditAllowedFromStatuses, subscriptionLifecycleActionOptions, subscriptionMetadataEditAllowedFromStatuses, } = getInvoicingConfigView(state);
@@ -86,6 +137,27 @@ export const getInvoicingItemsAllowedForPlan = (products, plan) => {
86
137
  }
87
138
  return products.filter((product) => product.itemID != null && allowedItemIDs.includes(product.itemID));
88
139
  };
140
+ // Years count as months so they compare; any other unit only meets itself.
141
+ const billingPeriodInCommonUnit = (pricePoint) => {
142
+ const { period, periodUnit } = pricePoint;
143
+ if (period == null || periodUnit == null || periodUnit === '') {
144
+ return undefined;
145
+ }
146
+ return periodUnit === 'year'
147
+ ? { count: period * 12, unit: 'month' }
148
+ : { count: period, unit: periodUnit };
149
+ };
150
+ // The add-on's period must divide the plan's (create 422s otherwise); fails open when they cannot be compared.
151
+ export const isInvoicingAddonPeriodCompatible = (addon, plan) => {
152
+ const addonPeriod = billingPeriodInCommonUnit(addon);
153
+ const planPeriod = plan != null ? billingPeriodInCommonUnit(plan) : undefined;
154
+ if (addonPeriod == null ||
155
+ planPeriod == null ||
156
+ addonPeriod.unit !== planPeriod.unit) {
157
+ return true;
158
+ }
159
+ return addonPeriod.count > 0 && planPeriod.count % addonPeriod.count === 0;
160
+ };
89
161
  /**
90
162
  * A price row can only be subscribed by its price code, so one without a code
91
163
  * is not an option the form can offer.
@@ -149,7 +221,7 @@ const toInvoicingCustomerOption = (customer) => ({
149
221
  value: customer.id,
150
222
  });
151
223
  export const getInvoicingSubscriptionFormView = (state, invoicingSubscriptionID) => {
152
- const { editDraftById, error, fetchState, newSubscriptionDraft, savedSubscriptionId, } = state.editInvoicingSubscriptionDetailViewState;
224
+ const { editDraftById, error, estimateState, fetchState, newSubscriptionDraft, savedSubscriptionId, } = state.editInvoicingSubscriptionDetailViewState;
153
225
  const mode = invoicingSubscriptionID != null ? 'edit' : 'create';
154
226
  const subscription = invoicingSubscriptionID != null
155
227
  ? getInvoicingSubscriptionById(state.invoicingSubscriptionState, invoicingSubscriptionID)
@@ -174,7 +246,12 @@ export const getInvoicingSubscriptionFormView = (state, invoicingSubscriptionID)
174
246
  // pickers offer only those: subscription create rejects the rest with a 422,
175
247
  // and an option that can only fail is not a choice. The applicability rides
176
248
  // on the price rows already in the store, so this is a join, not a refetch.
177
- const allowedCatalogPrices = getInvoicingItemsAllowedForPlan(getInvoicingCatalogPrices(catalogListView.products), planPrices.find((plan) => plan.priceCode === localData.planPriceCode)).filter(hasInvoicingPriceCode);
249
+ const selectedPlanPrice = planPrices.find((plan) => plan.priceCode === localData.planPriceCode);
250
+ // Add-ons also have to bill on the plan's cadence; charges are one-time.
251
+ const allowedCatalogPrices = getInvoicingItemsAllowedForPlan(getInvoicingCatalogPrices(catalogListView.products), selectedPlanPrice)
252
+ .filter(hasInvoicingPriceCode)
253
+ .filter((product) => product.itemType !== 'addon' ||
254
+ isInvoicingAddonPeriodCompatible(product, selectedPlanPrice));
178
255
  const catalogItemOptionsOfType = (itemType) => allowedCatalogPrices
179
256
  .filter((product) => product.itemType === itemType)
180
257
  .map(toInvoicingCatalogItemOption);
@@ -198,6 +275,7 @@ export const getInvoicingSubscriptionFormView = (state, invoicingSubscriptionID)
198
275
  subscriptionMetadataEditAllowedFromStatuses: configState.subscriptionMetadataEditAllowedFromStatuses,
199
276
  recordState,
200
277
  isFormLoading: isInvoicingFormDraftPending(editDraftById, invoicingSubscriptionID),
278
+ estimate: toInvoicingSubscriptionEstimateView(estimateState, invoicingSubscriptionID),
201
279
  fieldHelp: invoicingFieldHelpForGroup(state, 'subscription'),
202
280
  addons: catalogItemOptionsOfType('addon'),
203
281
  charges: catalogItemOptionsOfType('charge'),
@@ -0,0 +1,49 @@
1
+ import { of } from 'rxjs';
2
+ import { catchError, filter, map, switchMap } from 'rxjs/operators';
3
+ import { getInvoicingCouponByCode } from '../../../entity/invoicing/coupon/couponSelector';
4
+ import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
5
+ import { mapInvoicingSubscriptionEstimatePayloadToState, } from './editInvoicingSubscriptionDetailViewPayload';
6
+ import { fetchInvoicingSubscriptionEstimate, updateInvoicingSubscriptionEstimate, updateInvoicingSubscriptionEstimateFailure, } from './editInvoicingSubscriptionDetailViewReducer';
7
+ import { invoicingSubscriptionSaveRequest, isInvoicingSubscriptionCouponChanged, } from './saveInvoicingSubscriptionEpic';
8
+ // Posts Save's exact body; a changed coupon rides along as `coupons` (Save syncs it after the PUT).
9
+ export const fetchInvoicingSubscriptionEstimateEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchInvoicingSubscriptionEstimate.match), switchMap((action) => {
10
+ const { id } = action.payload;
11
+ const { data, localData, previousCouponCode } = invoicingSubscriptionSaveRequest(state$.value, id);
12
+ const couponCode = localData.couponId;
13
+ const coupon = getInvoicingCouponByCode(state$.value.invoicingCouponState, couponCode);
14
+ const body = id != null &&
15
+ isInvoicingSubscriptionCouponChanged(previousCouponCode, couponCode)
16
+ ? {
17
+ ...data,
18
+ coupons: couponCode !== ''
19
+ ? [
20
+ {
21
+ applied_count: 0,
22
+ apply_till: null,
23
+ coupon_code: couponCode,
24
+ coupon_id: coupon?.id ?? couponCode,
25
+ },
26
+ ]
27
+ : [],
28
+ }
29
+ : data;
30
+ const baseUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/subscriptions`;
31
+ const url = id != null
32
+ ? `${baseUrl}/${encodeURIComponent(id)}/estimate`
33
+ : `${baseUrl}/estimate`;
34
+ return zeniAPI
35
+ .postAndGetJSON(url, body)
36
+ .pipe(map((response) => isSuccessResponse(response) && response.data != null
37
+ ? updateInvoicingSubscriptionEstimate({
38
+ estimate: mapInvoicingSubscriptionEstimatePayloadToState(response.data),
39
+ id,
40
+ })
41
+ : updateInvoicingSubscriptionEstimateFailure({
42
+ id,
43
+ status: response.status,
44
+ })), catchError((error) => of(updateInvoicingSubscriptionEstimateFailure({
45
+ id,
46
+ status: createZeniAPIStatus('Unexpected Error', 'Subscription estimate REST API call errored out ' +
47
+ JSON.stringify(error)),
48
+ }))));
49
+ }));
@@ -7,14 +7,17 @@ import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiE
7
7
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
8
8
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
9
9
  import { resetNewAddressDataInLocalStore } from '../../addressView/addressViewReducer';
10
+ import { invalidateInvoicingCustomerDetail } from '../editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer';
11
+ import { invalidateInvoiceListForCustomer } from '../invoiceList/invoiceListReducer';
10
12
  import { addInvoicingSubscriptionToList } from '../invoicingSubscriptionListView/invoicingSubscriptionListViewReducer';
11
13
  import { localDataToSaveInvoicingSubscriptionPayload, } from './editInvoicingSubscriptionDetailViewPayload';
12
14
  import { submitInvoicingSubscriptionForm, submitInvoicingSubscriptionFormFailure, submitInvoicingSubscriptionFormSuccess, syncInvoicingSubscriptionCoupons, } from './editInvoicingSubscriptionDetailViewReducer';
13
15
  import { INVOICING_SUBSCRIPTION_SHIPPING_ADDRESS_TYPE } from './invoicingSubscriptionFormConfig';
14
16
  const normalizeCouponCode = (value) => (value ?? '').trim().toUpperCase();
15
- export const saveInvoicingSubscriptionEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(submitInvoicingSubscriptionForm.match), mergeMap((action) => {
16
- const { id } = action.payload;
17
- const rootState = state$.value;
17
+ export const isInvoicingSubscriptionCouponChanged = (previousCouponCode, nextCouponCode) => normalizeCouponCode(previousCouponCode) !==
18
+ normalizeCouponCode(nextCouponCode);
19
+ // The draft a save reads and the body it sends; the estimate previews the same body.
20
+ export const invoicingSubscriptionSaveRequest = (rootState, id) => {
18
21
  const viewState = rootState.editInvoicingSubscriptionDetailViewState;
19
22
  const localData = (id != null
20
23
  ? viewState.editDraftById[id]?.localData
@@ -23,18 +26,23 @@ export const saveInvoicingSubscriptionEpic = (actions$, state$, zeniAPI) => acti
23
26
  // object in the payload (same pattern as the customer save flow).
24
27
  const shippingAddress = rootState.addressViewState.newAddressState?.[INVOICING_SUBSCRIPTION_SHIPPING_ADDRESS_TYPE]?.addressToCreate;
25
28
  const data = localDataToSaveInvoicingSubscriptionPayload(localData, shippingAddress);
26
- // Coupons are synced via apply/remove-coupon after PUT (not via coupon_ids).
27
29
  if (id != null) {
28
30
  delete data.coupon_ids;
29
31
  }
32
+ const savedCoupon = id != null
33
+ ? rootState.invoicingSubscriptionState.invoicingSubscriptionByID[id]
34
+ ?.coupons?.[0]
35
+ : undefined;
36
+ return {
37
+ data,
38
+ localData,
39
+ previousCouponCode: savedCoupon?.couponCode ?? savedCoupon?.couponID ?? '',
40
+ };
41
+ };
42
+ export const saveInvoicingSubscriptionEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(submitInvoicingSubscriptionForm.match), mergeMap((action) => {
43
+ const { id } = action.payload;
44
+ const { data, localData, previousCouponCode } = invoicingSubscriptionSaveRequest(state$.value, id);
30
45
  const baseUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0`;
31
- const previousCouponCode = id != null
32
- ? (rootState.invoicingSubscriptionState.invoicingSubscriptionByID[id]
33
- ?.coupons?.[0]?.couponCode ??
34
- rootState.invoicingSubscriptionState.invoicingSubscriptionByID[id]
35
- ?.coupons?.[0]?.couponID ??
36
- '')
37
- : '';
38
46
  const request$ = id != null
39
47
  ? zeniAPI.putAndGetJSON(`${baseUrl}/subscriptions/${encodeURIComponent(id)}`, { ...data })
40
48
  : zeniAPI.postAndGetJSON(`${baseUrl}/subscriptions`, { ...data });
@@ -52,8 +60,7 @@ export const saveInvoicingSubscriptionEpic = (actions$, state$, zeniAPI) => acti
52
60
  }
53
61
  const saved = response.data;
54
62
  const couponChanged = id != null &&
55
- normalizeCouponCode(previousCouponCode) !==
56
- normalizeCouponCode(localData.couponId);
63
+ isInvoicingSubscriptionCouponChanged(previousCouponCode, localData.couponId);
57
64
  // Edit + coupon change: merge the saved subscription, then hand the
58
65
  // coupon attach/detach to syncInvoicingSubscriptionCouponsEpic, which
59
66
  // finishes the flow (form success on both calls ok, form failure
@@ -86,7 +93,9 @@ export const saveInvoicingSubscriptionEpic = (actions$, state$, zeniAPI) => acti
86
93
  resultActions.push(addInvoicingSubscriptionToList({
87
94
  id: saved.id,
88
95
  status: saved.status,
89
- }));
96
+ }),
97
+ // The service may bill the first invoice on create.
98
+ invalidateInvoicingCustomerDetail({ id: saved.customer_id }), invalidateInvoiceListForCustomer({ customerId: saved.customer_id }));
90
99
  }
91
100
  resultActions.push(openSnackbar({
92
101
  messageSection: 'invoicing_subscription_saved',
@@ -124,6 +124,13 @@ const invoiceList = createSlice({
124
124
  draft.kpisFetchState.fetchState = 'Error';
125
125
  draft.kpisFetchState.error = action.payload;
126
126
  },
127
+ // Counts are fetched under the same customer filter, so they go stale with the tabs.
128
+ invalidateInvoiceListForCustomer(draft, action) {
129
+ if (draft.filters.customerId === action.payload.customerId) {
130
+ draft.invoiceByTab = makeInvoiceByTab();
131
+ draft.countsFetchState = { fetchState: 'Not-Started', error: undefined };
132
+ }
133
+ },
127
134
  updateInvoiceListFilters(draft, action) {
128
135
  draft.filters = action.payload;
129
136
  },
@@ -166,5 +173,5 @@ const invoiceList = createSlice({
166
173
  });
167
174
  },
168
175
  });
169
- export const { fetchInvoiceList, fetchInvoiceListPage, updateInvoiceList, updateInvoiceListFailure, fetchInvoiceCounts, updateInvoiceCounts, updateInvoiceCountsFailure, fetchInvoiceKPIs, updateInvoiceKPIs, updateInvoiceKPIsFailure, updateInvoiceListFilters, setInvoiceActiveTab, addInvoiceToList, } = invoiceList.actions;
176
+ export const { fetchInvoiceList, fetchInvoiceListPage, updateInvoiceList, updateInvoiceListFailure, fetchInvoiceCounts, updateInvoiceCounts, updateInvoiceCountsFailure, fetchInvoiceKPIs, updateInvoiceKPIs, updateInvoiceKPIsFailure, updateInvoiceListFilters, setInvoiceActiveTab, addInvoiceToList, invalidateInvoiceListForCustomer, } = invoiceList.actions;
170
177
  export default invoiceList.reducer;
@@ -1,6 +1,6 @@
1
1
  import { of } from 'rxjs';
2
2
  import { catchError, filter, switchMap } from 'rxjs/operators';
3
- import { updateInvoicingPlans } from '../../../../entity/invoicing/plan/planReducer';
3
+ import { updateInvoicingCatalogItems } from '../../../../entity/invoicing/invoicingCatalog/invoicingCatalogReducer';
4
4
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
5
5
  import { INVOICING_LIST_PAGE_SIZE, applyListSortParams, withQuery, } from '../../invoicingApiHelper';
6
6
  import { fetchInvoicingCatalogPlanList, updateInvoicingCatalogPlanList, updateInvoicingCatalogPlanListFailure, } from '../invoicingCatalogListViewReducer';
@@ -29,7 +29,7 @@ export const fetchInvoicingCatalogPlanListEpic = (actions$, _state$, zeniAPI) =>
29
29
  if (isSuccessResponse(response)) {
30
30
  const items = response.data?.items ?? [];
31
31
  return [
32
- updateInvoicingPlans(items, 'merge'),
32
+ updateInvoicingCatalogItems(items, 'merge'),
33
33
  updateInvoicingCatalogPlanList({
34
34
  ids: items.map((item) => item.id),
35
35
  nextCursor: response.data?.next_cursor ?? null,
@@ -1,6 +1,6 @@
1
1
  import { of } from 'rxjs';
2
2
  import { catchError, filter, switchMap } from 'rxjs/operators';
3
- import { updateInvoicingPlans } from '../../../../entity/invoicing/plan/planReducer';
3
+ import { updateInvoicingCatalogItems } from '../../../../entity/invoicing/invoicingCatalog/invoicingCatalogReducer';
4
4
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
5
5
  import { INVOICING_LIST_PAGE_SIZE, applyListSortParams, withQuery, } from '../../invoicingApiHelper';
6
6
  import { fetchInvoicingCatalogProductList, updateInvoicingCatalogProductList, updateInvoicingCatalogProductListFailure, } from '../invoicingCatalogListViewReducer';
@@ -29,7 +29,7 @@ export const fetchInvoicingCatalogProductListEpic = (actions$, _state$, zeniAPI)
29
29
  if (isSuccessResponse(response)) {
30
30
  const items = response.data?.items ?? [];
31
31
  return [
32
- updateInvoicingPlans(items, 'merge'),
32
+ updateInvoicingCatalogItems(items, 'merge'),
33
33
  updateInvoicingCatalogProductList({
34
34
  ids: items.map((item) => item.id),
35
35
  nextCursor: response.data?.next_cursor ?? null,