@zeniai/client-epic-state 5.2.41-beta6MM → 5.2.41-beta7MM

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/lib/entity/billPay/billTransaction/billTransactionReducer.d.ts +5 -1
  2. package/lib/entity/billPay/billTransaction/billTransactionReducer.js +17 -2
  3. package/lib/entity/invoicing/coupon/couponPayload.d.ts +8 -0
  4. package/lib/entity/invoicing/coupon/couponPayload.js +8 -0
  5. package/lib/entity/invoicing/coupon/couponState.d.ts +6 -0
  6. package/lib/entity/invoicing/dunningCase/dunningCaseTypes.d.ts +1 -1
  7. package/lib/entity/invoicing/plan/planSelector.d.ts +20 -0
  8. package/lib/entity/invoicing/plan/planSelector.js +42 -0
  9. package/lib/entity/invoicing/product/productPayload.d.ts +8 -1
  10. package/lib/entity/invoicing/product/productPayload.js +4 -1
  11. package/lib/entity/invoicing/product/productState.d.ts +16 -0
  12. package/lib/entity/jeSchedules/jeSchedulesReducer.d.ts +4 -1
  13. package/lib/entity/jeSchedules/jeSchedulesReducer.js +79 -5
  14. package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
  15. package/lib/entity/snackbar/snackbarTypes.js +1 -0
  16. package/lib/entity/task/taskState.d.ts +1 -1
  17. package/lib/epic.d.ts +4 -1
  18. package/lib/epic.js +10 -4
  19. package/lib/esm/entity/billPay/billTransaction/billTransactionReducer.js +16 -1
  20. package/lib/esm/entity/invoicing/coupon/couponPayload.js +8 -0
  21. package/lib/esm/entity/invoicing/plan/planSelector.js +39 -0
  22. package/lib/esm/entity/invoicing/product/productPayload.js +4 -1
  23. package/lib/esm/entity/jeSchedules/jeSchedulesReducer.js +78 -4
  24. package/lib/esm/entity/snackbar/snackbarTypes.js +1 -0
  25. package/lib/esm/epic.js +10 -4
  26. package/lib/esm/index.js +8 -7
  27. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/ignoreActualMatchingSuggestionEpic.js +3 -2
  28. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/fetchFailedJePostingsEpic.js +39 -0
  29. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.js +14 -1
  30. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.js +10 -13
  31. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/initializeJeScheduleLocalDataEpic.js +38 -17
  32. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/retryJeSchedulesEpic.js +17 -1
  33. package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +15 -4
  34. package/lib/esm/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +6 -0
  35. package/lib/esm/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +74 -1
  36. package/lib/esm/view/expenseAutomationView/reducers/reconciliationViewReducer.js +80 -53
  37. package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +10 -0
  38. package/lib/esm/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +6 -2
  39. package/lib/esm/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +49 -14
  40. package/lib/esm/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +28 -11
  41. package/lib/esm/view/invoicing/createInvoice/createInvoiceSelector.js +28 -1
  42. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.js +67 -35
  43. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewReducer.js +22 -11
  44. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +23 -4
  45. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/invoicingCatalogItemFormHelper.js +1 -7
  46. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +19 -5
  47. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/seedInvoicingCatalogItemFormDraftEpic.js +30 -7
  48. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewPayload.js +16 -1
  49. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewReducer.js +22 -11
  50. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewSelector.js +69 -6
  51. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewState.js +22 -0
  52. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/fetchInvoicingCouponDetailPageEpic.js +22 -4
  53. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.js +18 -4
  54. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/seedInvoicingCouponFormDraftEpic.js +29 -6
  55. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.js +28 -12
  56. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.js +37 -2
  57. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/initializeInvoicingCustomerAddressEpic.js +43 -17
  58. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.js +1 -1
  59. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/seedInvoicingCustomerFormDraftEpic.js +29 -6
  60. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.js +30 -14
  61. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +143 -8
  62. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionFormPageEpic.js +4 -1
  63. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/initializeInvoicingSubscriptionAddressEpic.js +44 -18
  64. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +1 -1
  65. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/seedInvoicingSubscriptionFormDraftEpic.js +29 -8
  66. package/lib/esm/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewSelector.js +12 -0
  67. package/lib/esm/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewState.js +6 -6
  68. package/lib/esm/view/invoicing/invoicingFormDraft.js +78 -0
  69. package/lib/esm/view/invoicing/invoicingQboView/invoicingQboViewSelector.js +57 -9
  70. package/lib/esm/view/recommendation/recommendationPayload.js +3 -1
  71. package/lib/esm/view/recommendation/recommendationReducer.js +27 -7
  72. package/lib/esm/view/recommendation/recommendationSelector.js +1 -0
  73. package/lib/esm/view/recommendation/submitRecommendationFeedbackEpic.js +20 -8
  74. package/lib/esm/view/spendManagement/billPay/billDetailView/billDetailViewReducer.js +87 -1
  75. package/lib/esm/view/spendManagement/billPay/billDetailView/billDetailViewSelector.js +1 -0
  76. package/lib/esm/view/spendManagement/billPay/billDetailView/billDetailViewState.js +4 -0
  77. package/lib/esm/view/spendManagement/billPay/billDetailView/epics/fetchBillRecommendationsEpic.js +97 -0
  78. package/lib/esm/view/spendManagement/billPay/billDetailView/epics/fetchRecommendationsForBill.js +85 -0
  79. package/lib/esm/view/spendManagement/billPay/editBillView/editBillViewReducer.js +18 -1
  80. package/lib/esm/view/spendManagement/billPay/editBillView/editBillViewSelector.js +3 -0
  81. package/lib/esm/view/spendManagement/billPay/editBillView/epics/applyBillRecommendationsToLocalStoreEpic.js +95 -0
  82. package/lib/index.d.ts +9 -8
  83. package/lib/index.js +54 -48
  84. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/ignoreActualMatchingSuggestionEpic.js +3 -2
  85. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchFailedJePostingsEpic.d.ts +20 -0
  86. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchFailedJePostingsEpic.js +43 -0
  87. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.d.ts +2 -2
  88. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.js +13 -0
  89. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.js +8 -11
  90. package/lib/view/expenseAutomationView/epics/jeSchedule/initializeJeScheduleLocalDataEpic.js +38 -17
  91. package/lib/view/expenseAutomationView/epics/jeSchedule/retryJeSchedulesEpic.d.ts +2 -2
  92. package/lib/view/expenseAutomationView/epics/jeSchedule/retryJeSchedulesEpic.js +17 -1
  93. package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +15 -4
  94. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
  95. package/lib/view/expenseAutomationView/payload/jeSchedulesPayload.d.ts +18 -0
  96. package/lib/view/expenseAutomationView/payload/transactionCategorizationPayload.d.ts +18 -7
  97. package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +6 -0
  98. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.d.ts +10 -3
  99. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +76 -3
  100. package/lib/view/expenseAutomationView/reducers/reconciliationViewReducer.js +80 -53
  101. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +10 -0
  102. package/lib/view/expenseAutomationView/selectorTypes/jeSchedulesSelectorTypes.d.ts +3 -0
  103. package/lib/view/expenseAutomationView/selectorTypes/transactionsViewSelectorTypes.d.ts +8 -4
  104. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.d.ts +1 -0
  105. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +7 -2
  106. package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +49 -14
  107. package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +27 -10
  108. package/lib/view/expenseAutomationView/types/jeSchedulesViewState.d.ts +16 -0
  109. package/lib/view/expenseAutomationView/types/transactionsViewState.d.ts +8 -5
  110. package/lib/view/invoicing/createInvoice/createInvoiceSelector.d.ts +7 -2
  111. package/lib/view/invoicing/createInvoice/createInvoiceSelector.js +28 -1
  112. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.d.ts +46 -5
  113. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.js +69 -36
  114. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewReducer.d.ts +3 -1
  115. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewReducer.js +22 -11
  116. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.d.ts +17 -0
  117. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +22 -3
  118. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewState.d.ts +2 -1
  119. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/invoicingCatalogItemFormHelper.d.ts +2 -2
  120. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/invoicingCatalogItemFormHelper.js +1 -7
  121. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +17 -3
  122. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/seedInvoicingCatalogItemFormDraftEpic.d.ts +10 -6
  123. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/seedInvoicingCatalogItemFormDraftEpic.js +29 -6
  124. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewPayload.d.ts +5 -1
  125. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewPayload.js +16 -1
  126. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewReducer.d.ts +3 -1
  127. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewReducer.js +22 -11
  128. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewSelector.d.ts +46 -0
  129. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewSelector.js +68 -5
  130. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewState.d.ts +21 -1
  131. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewState.js +24 -1
  132. package/lib/view/invoicing/editInvoicingCouponDetailView/fetchInvoicingCouponDetailPageEpic.d.ts +9 -5
  133. package/lib/view/invoicing/editInvoicingCouponDetailView/fetchInvoicingCouponDetailPageEpic.js +22 -4
  134. package/lib/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.js +18 -4
  135. package/lib/view/invoicing/editInvoicingCouponDetailView/seedInvoicingCouponFormDraftEpic.d.ts +10 -6
  136. package/lib/view/invoicing/editInvoicingCouponDetailView/seedInvoicingCouponFormDraftEpic.js +28 -5
  137. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.d.ts +5 -1
  138. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.js +29 -13
  139. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.d.ts +16 -7
  140. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.js +37 -2
  141. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewState.d.ts +8 -1
  142. package/lib/view/invoicing/editInvoicingCustomerDetailView/initializeInvoicingCustomerAddressEpic.d.ts +15 -10
  143. package/lib/view/invoicing/editInvoicingCustomerDetailView/initializeInvoicingCustomerAddressEpic.js +42 -16
  144. package/lib/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.js +1 -1
  145. package/lib/view/invoicing/editInvoicingCustomerDetailView/seedInvoicingCustomerFormDraftEpic.d.ts +10 -6
  146. package/lib/view/invoicing/editInvoicingCustomerDetailView/seedInvoicingCustomerFormDraftEpic.js +28 -5
  147. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.d.ts +5 -1
  148. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.js +31 -15
  149. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.d.ts +79 -12
  150. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +145 -9
  151. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewState.d.ts +8 -1
  152. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionFormPageEpic.js +4 -1
  153. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/initializeInvoicingSubscriptionAddressEpic.d.ts +15 -11
  154. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/initializeInvoicingSubscriptionAddressEpic.js +42 -16
  155. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +1 -1
  156. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/seedInvoicingSubscriptionFormDraftEpic.d.ts +10 -6
  157. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/seedInvoicingSubscriptionFormDraftEpic.js +28 -7
  158. package/lib/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewSelector.d.ts +10 -0
  159. package/lib/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewSelector.js +12 -0
  160. package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewReducer.d.ts +1 -1
  161. package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewState.d.ts +2 -2
  162. package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewState.js +6 -6
  163. package/lib/view/invoicing/invoicingFormDraft.d.ts +63 -0
  164. package/lib/view/invoicing/invoicingFormDraft.js +86 -0
  165. package/lib/view/invoicing/invoicingQboView/invoicingQboViewSelector.d.ts +49 -0
  166. package/lib/view/invoicing/invoicingQboView/invoicingQboViewSelector.js +59 -10
  167. package/lib/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewReducer.d.ts +1 -1
  168. package/lib/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewState.d.ts +1 -1
  169. package/lib/view/recommendation/recommendationPayload.d.ts +1 -0
  170. package/lib/view/recommendation/recommendationPayload.js +5 -0
  171. package/lib/view/recommendation/recommendationReducer.d.ts +7 -1
  172. package/lib/view/recommendation/recommendationReducer.js +28 -8
  173. package/lib/view/recommendation/recommendationSelector.d.ts +2 -0
  174. package/lib/view/recommendation/recommendationSelector.js +3 -1
  175. package/lib/view/recommendation/recommendationState.d.ts +2 -1
  176. package/lib/view/recommendation/submitRecommendationFeedbackEpic.d.ts +2 -2
  177. package/lib/view/recommendation/submitRecommendationFeedbackEpic.js +18 -6
  178. package/lib/view/spendManagement/billPay/billDetailView/billDetailViewPayload.d.ts +19 -0
  179. package/lib/view/spendManagement/billPay/billDetailView/billDetailViewReducer.d.ts +10 -1
  180. package/lib/view/spendManagement/billPay/billDetailView/billDetailViewReducer.js +88 -2
  181. package/lib/view/spendManagement/billPay/billDetailView/billDetailViewSelector.d.ts +1 -0
  182. package/lib/view/spendManagement/billPay/billDetailView/billDetailViewSelector.js +1 -0
  183. package/lib/view/spendManagement/billPay/billDetailView/billDetailViewState.d.ts +7 -0
  184. package/lib/view/spendManagement/billPay/billDetailView/billDetailViewState.js +4 -0
  185. package/lib/view/spendManagement/billPay/billDetailView/epics/fetchBillRecommendationsEpic.d.ts +37 -0
  186. package/lib/view/spendManagement/billPay/billDetailView/epics/fetchBillRecommendationsEpic.js +104 -0
  187. package/lib/view/spendManagement/billPay/billDetailView/epics/fetchRecommendationsForBill.d.ts +24 -0
  188. package/lib/view/spendManagement/billPay/billDetailView/epics/fetchRecommendationsForBill.js +92 -0
  189. package/lib/view/spendManagement/billPay/editBillView/editBillViewReducer.d.ts +4 -1
  190. package/lib/view/spendManagement/billPay/editBillView/editBillViewReducer.js +19 -2
  191. package/lib/view/spendManagement/billPay/editBillView/editBillViewSelector.d.ts +1 -0
  192. package/lib/view/spendManagement/billPay/editBillView/editBillViewSelector.js +3 -0
  193. package/lib/view/spendManagement/billPay/editBillView/epics/applyBillRecommendationsToLocalStoreEpic.d.ts +18 -0
  194. package/lib/view/spendManagement/billPay/editBillView/epics/applyBillRecommendationsToLocalStoreEpic.js +102 -0
  195. package/package.json +2 -2
@@ -1,5 +1,6 @@
1
1
  import { createSlice } from '@reduxjs/toolkit';
2
2
  import { clearAll } from '../../../rootActions';
3
+ import { applyInvoicingFormDraftEdit, applyInvoicingFormDraftFailure, applyInvoicingFormDraftSaved, applyInvoicingFormDraftSeed, } from '../invoicingFormDraft';
3
4
  import { initialInvoicingCatalogItemFormLocalData } from './invoicingCatalogItemFormHelper';
4
5
  export const initialState = {
5
6
  fetchState: 'Not-Started',
@@ -21,7 +22,7 @@ const editInvoicingCatalogItemDetailView = createSlice({
21
22
  updateInvoicingCatalogItemFormDraft(draft, action) {
22
23
  const { id, localData } = action.payload;
23
24
  if (id != null) {
24
- draft.editDraftById[id] = localData;
25
+ applyInvoicingFormDraftEdit(draft.editDraftById, id, localData);
25
26
  }
26
27
  else {
27
28
  draft.newItemDraft = localData;
@@ -42,6 +43,9 @@ const editInvoicingCatalogItemDetailView = createSlice({
42
43
  draft.error = undefined;
43
44
  draft.savedItemId = action.payload.itemId;
44
45
  draft.savedKind = action.payload.kind;
46
+ // An edit is refetched on success; keep the saved values on screen until
47
+ // that lands, and let it replace them.
48
+ applyInvoicingFormDraftSaved(draft.editDraftById, action.payload.itemId);
45
49
  },
46
50
  submitInvoicingCatalogItemFormFailure(draft, action) {
47
51
  draft.fetchState = 'Error';
@@ -64,6 +68,7 @@ const editInvoicingCatalogItemDetailView = createSlice({
64
68
  fetchState: 'Error',
65
69
  error: action.payload.error,
66
70
  };
71
+ applyInvoicingFormDraftFailure(draft.editDraftById, action.payload.id, initialInvoicingCatalogItemFormLocalData);
67
72
  },
68
73
  fetchInvoicingCatalogItemDetailPage: {
69
74
  reducer() {
@@ -79,14 +84,10 @@ const editInvoicingCatalogItemDetailView = createSlice({
79
84
  };
80
85
  },
81
86
  },
82
- /**
83
- * Seed the draft from the loaded record, once. Skipped when a draft already
84
- * exists so a late-arriving refetch cannot overwrite in-progress edits.
85
- */
87
+ /** Fill the draft from the loaded record — see `applyInvoicingFormDraftSeed`. */
86
88
  seedInvoicingCatalogItemFormDraft(draft, action) {
87
- if (draft.editDraftById[action.payload.id] == null) {
88
- draft.editDraftById[action.payload.id] = action.payload.localData;
89
- }
89
+ const { id, localData } = action.payload;
90
+ applyInvoicingFormDraftSeed(draft.editDraftById, id, localData);
90
91
  },
91
92
  runInvoicingCatalogPlanAction(draft, action) {
92
93
  draft.actionStateById[action.payload.id] = {
@@ -125,13 +126,23 @@ const editInvoicingCatalogItemDetailView = createSlice({
125
126
  error: action.payload.error,
126
127
  };
127
128
  },
128
- resetEditInvoicingCatalogItemDetailView(draft) {
129
+ /**
130
+ * Discard a form's draft — the catalog item's own when `id` is given,
131
+ * the create draft otherwise. Scoped to the one form on purpose: another
132
+ * catalog item's half-finished edit is not this form's to throw away.
133
+ * Clearing every draft at once is what `clearAll` is for.
134
+ */
135
+ resetEditInvoicingCatalogItemDetailView(draft, action) {
129
136
  draft.fetchState = 'Not-Started';
130
137
  draft.error = undefined;
131
138
  draft.savedItemId = undefined;
132
139
  draft.savedKind = undefined;
133
- draft.editDraftById = {};
134
- draft.newItemDraft = { ...initialInvoicingCatalogItemFormLocalData };
140
+ const id = action.payload?.id;
141
+ if (id == null) {
142
+ draft.newItemDraft = { ...initialInvoicingCatalogItemFormLocalData };
143
+ return;
144
+ }
145
+ delete draft.editDraftById[id];
135
146
  },
136
147
  },
137
148
  extraReducers: (builder) => {
@@ -2,7 +2,8 @@ import { createSelector } from '@reduxjs/toolkit';
2
2
  import { reduceAllFetchState } from '../../../commonStateTypes/reduceFetchState';
3
3
  import { getInvoicingPlanById, getInvoicingPlansByIds, } from '../../../entity/invoicing/plan/planSelector';
4
4
  import { getInvoicingConfigView, invoicingFieldHelpForGroup, } from '../invoicingConfigView/invoicingConfigViewSelector';
5
- import { getInvoicingQboView, isInvoicingAccountingPickerDataLoading, isInvoicingQboConnected, } from '../invoicingQboView/invoicingQboViewSelector';
5
+ import { getInvoicingFormDraftLocalData, isInvoicingFormDraftPending, } from '../invoicingFormDraft';
6
+ import { INVOICING_DEFAULT_INCOME_ACCOUNT_TYPES, INVOICING_INCOME_ACCOUNT_MAPPING_SLOT, NO_INVOICING_ACCOUNT_HIERARCHY, NO_INVOICING_ACCOUNT_IDS, NO_INVOICING_CLASS_HIERARCHY, getInvoicingQboAccountOptions, getInvoicingQboView, invoicingQboAccountIdsForMappingSlot, isInvoicingAccountingPickerDataLoading, isInvoicingQboConnected, } from '../invoicingQboView/invoicingQboViewSelector';
6
7
  import { INVOICING_CATALOG_APPLICABILITY_VALUES, INVOICING_CATALOG_PRICING_MODEL_VALUES, INVOICING_CATALOG_PRORATION_VALUES, INVOICING_CATALOG_TIER_TYPE_VALUES, initialInvoicingCatalogItemFormLocalData, } from './invoicingCatalogItemFormHelper';
7
8
  export const getEditInvoicingCatalogItemDetailViewState = (state) => ({
8
9
  fetchState: state.editInvoicingCatalogItemDetailViewState.fetchState,
@@ -43,6 +44,21 @@ const NOT_STARTED = {
43
44
  error: undefined,
44
45
  fetchState: 'Not-Started',
45
46
  };
47
+ /**
48
+ * The QuickBooks ids a price point's income account may be picked from.
49
+ *
50
+ * Memoized, and collapsing to one shared empty array while QBO is disconnected:
51
+ * the pages turn this into their picker options and re-seed their form defaults
52
+ * when those change, so a fresh array on every store change would reset the form
53
+ * under the user — including mid-save.
54
+ */
55
+ const getInvoicingCatalogIncomeAccountIds = createSelector([
56
+ getInvoicingQboAccountOptions,
57
+ (state) => getInvoicingConfigView(state).qboAccountMappingKindOptions,
58
+ isInvoicingQboConnected,
59
+ ], (accounts, qboAccountMappingKindOptions, isQboConnected) => isQboConnected
60
+ ? invoicingQboAccountIdsForMappingSlot(accounts, qboAccountMappingKindOptions, INVOICING_INCOME_ACCOUNT_MAPPING_SLOT, INVOICING_DEFAULT_INCOME_ACCOUNT_TYPES)
61
+ : NO_INVOICING_ACCOUNT_IDS);
46
62
  export const getInvoicingCatalogItemFormView = (state, id) => {
47
63
  const { editDraftById, error, fetchState, newItemDraft, savedItemId, savedKind, } = state.editInvoicingCatalogItemDetailViewState;
48
64
  const plan = id != null ? getInvoicingPlanById(state.invoicingPlanState, id) : undefined;
@@ -52,7 +68,7 @@ export const getInvoicingCatalogItemFormView = (state, id) => {
52
68
  // lands. Until then the page is showing its skeleton, so the blank shape here
53
69
  // is never rendered.
54
70
  const localData = id != null
55
- ? (editDraftById[id] ?? initialInvoicingCatalogItemFormLocalData)
71
+ ? getInvoicingFormDraftLocalData(editDraftById, id, initialInvoicingCatalogItemFormLocalData)
56
72
  : newItemDraft;
57
73
  const allApplicableItemOptions = getInvoicingApplicableCatalogItemOptions(state);
58
74
  const applicableItemOptions = id != null
@@ -90,9 +106,12 @@ export const getInvoicingCatalogItemFormView = (state, id) => {
90
106
  catalogProductLifecycleActionOptions,
91
107
  recordState,
92
108
  fieldHelp: invoicingFieldHelpForGroup(state, 'catalog'),
109
+ isFormLoading: isInvoicingAccountingPickerDataLoading(state) ||
110
+ isInvoicingFormDraftPending(editDraftById, id),
93
111
  isQboPickerDataLoading: isInvoicingAccountingPickerDataLoading(state),
94
- qboAccountHierarchy: qboView?.nestedAccountHierarchy ?? [],
95
- qboClassHierarchy: qboView?.nestedClassHierarchy ?? [],
112
+ qboAccountHierarchy: qboView?.nestedAccountHierarchy ?? NO_INVOICING_ACCOUNT_HIERARCHY,
113
+ qboClassHierarchy: qboView?.nestedClassHierarchy ?? NO_INVOICING_CLASS_HIERARCHY,
114
+ qboIncomeAccountIds: getInvoicingCatalogIncomeAccountIds(state),
96
115
  applicabilityValues: INVOICING_CATALOG_APPLICABILITY_VALUES,
97
116
  applicableItemOptions,
98
117
  localData,
@@ -3,13 +3,7 @@
3
3
  * for pricing model, tier type, proration or applicability yet, so these stay
4
4
  * here until it does. Everything else on this form comes from `/1.0/config`.
5
5
  */
6
- export const INVOICING_CATALOG_PRICING_MODEL_VALUES = [
7
- 'flat_fee',
8
- 'per_unit',
9
- 'tiered',
10
- 'volume',
11
- 'stairstep',
12
- ];
6
+ export const INVOICING_CATALOG_PRICING_MODEL_VALUES = ['flat_fee', 'per_unit', 'tiered', 'volume', 'stairstep'];
13
7
  export const INVOICING_CATALOG_TIER_TYPE_VALUES = [
14
8
  'per_unit',
15
9
  'flat_fee',
@@ -1,4 +1,4 @@
1
- import { from } from 'rxjs';
1
+ import { forkJoin, from } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { mergeInvoicingPlanSubFamily, updateInvoicingPlans, } from '../../../entity/invoicing/plan/planReducer';
4
4
  import { updateInvoicingProducts } from '../../../entity/invoicing/product/productReducer';
@@ -7,7 +7,7 @@ import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
7
7
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
8
8
  import { addInvoicingCatalogItem } from '../invoicingCatalogListView/invoicingCatalogListViewReducer';
9
9
  import { getInvoicingCatalogAccountingLookup } from '../invoicingQboView/invoicingQboViewSelector';
10
- import { localDataToSaveInvoicingCatalogItemPayload, } from './editInvoicingCatalogItemDetailViewPayload';
10
+ import { localDataToCreateInvoicingCatalogProductPayloads, localDataToSaveInvoicingCatalogItemPayload, } from './editInvoicingCatalogItemDetailViewPayload';
11
11
  import { submitInvoicingCatalogItemForm, submitInvoicingCatalogItemFormFailure, submitInvoicingCatalogItemFormSuccess, } from './editInvoicingCatalogItemDetailViewReducer';
12
12
  export const saveInvoicingCatalogItemEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter((action) => submitInvoicingCatalogItemForm.match(action)), mergeMap((action) => {
13
13
  const { id } = action.payload;
@@ -17,8 +17,9 @@ export const saveInvoicingCatalogItemEpic = (actions$, state$, zeniAPI) => actio
17
17
  // class become the records the service stores. Price points ride along
18
18
  // nested, so the detail page and the create/edit form share one request.
19
19
  const viewState = state.editInvoicingCatalogItemDetailViewState;
20
- const localData = (id != null ? viewState.editDraftById[id] : viewState.newItemDraft) ??
21
- viewState.newItemDraft;
20
+ const localData = (id != null
21
+ ? viewState.editDraftById[id]?.localData
22
+ : viewState.newItemDraft) ?? viewState.newItemDraft;
22
23
  const data = localDataToSaveInvoicingCatalogItemPayload(localData, id == null, accountingLookup);
23
24
  const baseUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0`;
24
25
  const path = id != null
@@ -80,7 +81,20 @@ export const saveInvoicingCatalogItemEpic = (actions$, state$, zeniAPI) => actio
80
81
  }));
81
82
  return resultActions;
82
83
  };
83
- return completeSave([]);
84
+ // Price points added on an edit form cannot ride in the update:
85
+ // it addresses them by id and they have none yet. They are created
86
+ // against the item that was just saved, and their responses join
87
+ // the same success — one of them failing is the whole save
88
+ // failing, since the item on screen would otherwise claim price
89
+ // points the service never stored. A create needs none of this:
90
+ // its price points are nested in the item body.
91
+ const newProductPayloads = id != null
92
+ ? localDataToCreateInvoicingCatalogProductPayloads(localData, savedItem.id, accountingLookup)
93
+ : [];
94
+ if (newProductPayloads.length === 0) {
95
+ return completeSave([]);
96
+ }
97
+ return forkJoin(newProductPayloads.map((productPayload) => zeniAPI.postAndGetJSON(`${baseUrl}/catalog/products`, productPayload))).pipe(mergeMap((productResponses) => completeSave(productResponses)));
84
98
  }
85
99
  return from([
86
100
  submitInvoicingCatalogItemFormFailure(response.status),
@@ -1,16 +1,39 @@
1
1
  import { EMPTY, of } from 'rxjs';
2
2
  import { filter, mergeMap, withLatestFrom } from 'rxjs/operators';
3
3
  import { getInvoicingPlanById } from '../../../entity/invoicing/plan/planSelector';
4
- import { seedInvoicingCatalogItemFormDraft, updateInvoicingCatalogItemDetail, } from './editInvoicingCatalogItemDetailViewReducer';
4
+ import { fetchInvoicingCatalogItemDetailPage, seedInvoicingCatalogItemFormDraft, updateInvoicingCatalogItemDetail, } from './editInvoicingCatalogItemDetailViewReducer';
5
5
  import { entityToInvoicingCatalogItemFormLocalData } from './invoicingCatalogItemFormHelper';
6
6
  /**
7
- * Seeds the edit draft from the record the moment the detail fetch lands, so
8
- * the form has its values before it mounts. The reducer ignores the seed when a
9
- * draft already exists, which is what stops a background refetch from
10
- * overwriting edits in progress.
7
+ * Seeds the edit draft from the record, so the form has its values before it
8
+ * mounts. The reducer ignores a seed once the draft holds the record, which is
9
+ * what stops a background refetch from overwriting edits in progress.
10
+ *
11
+ * Opening the page counts as a trigger, not just the detail response: the page
12
+ * fetch is cache-guarded, so re-opening a form for a record already loaded this
13
+ * session issues no request at all — and a draft cleared in between (by a save
14
+ * or a cancel) would have nothing left to seed it.
11
15
  */
12
- export const seedInvoicingCatalogItemFormDraftEpic = (actions$, state$) => actions$.pipe(filter(updateInvoicingCatalogItemDetail.match), withLatestFrom(state$), mergeMap(([action, state]) => {
13
- const { id } = action.payload;
16
+ export const seedInvoicingCatalogItemFormDraftEpic = (actions$, state$) => actions$.pipe(filter((action) => updateInvoicingCatalogItemDetail.match(action) ||
17
+ fetchInvoicingCatalogItemDetailPage.match(action)), withLatestFrom(state$), mergeMap(([action, state]) => {
18
+ const id = action.payload.id;
19
+ if (id == null) {
20
+ return EMPTY;
21
+ }
22
+ // The entity alone does not say the whole record is loaded — the list
23
+ // endpoints merge their own list-shaped rows into the same store. Only a
24
+ // completed detail load does, which is also exactly when the page's
25
+ // cache-guarded fetch issues no request and this is the sole seed.
26
+ // A forced refetch is the caller asking for fresh data: the response will
27
+ // seed, and seeding first from the entity as it stands would mark the
28
+ // draft loaded and lock that response out.
29
+ if (fetchInvoicingCatalogItemDetailPage.match(action) &&
30
+ (action.payload.cacheOverride === true ||
31
+ state.editInvoicingCatalogItemDetailViewState.recordStateById[id]
32
+ ?.fetchState !== 'Completed')) {
33
+ return EMPTY;
34
+ }
35
+ // Nothing to seed from on a cold open; the detail response triggers this
36
+ // again once it lands.
14
37
  const plan = getInvoicingPlanById(state.invoicingPlanState, id);
15
38
  if (plan == null) {
16
39
  return EMPTY;
@@ -1,9 +1,13 @@
1
+ import { toAccountPayload } from '../../../entity/account/accountPayload';
1
2
  /**
2
3
  * Build the create/update discount request body from the form draft (snake_case
3
4
  * matching the `/discounts` schema). On create the coupon id is part of the
4
5
  * body; on edit it is the URL param, so it is omitted here.
6
+ *
7
+ * `accountByQboId` resolves the discounts-account picker back to the account
8
+ * record the body carries, the same way the catalog and QBO settings bodies do.
5
9
  */
6
- export const localDataToSaveInvoicingCouponPayload = (localData, isCreate) => {
10
+ export const localDataToSaveInvoicingCouponPayload = (localData, isCreate, accountByQboId) => {
7
11
  const data = {
8
12
  apply_on: localData.applyOn,
9
13
  discount_type: localData.discountType,
@@ -54,5 +58,16 @@ export const localDataToSaveInvoicingCouponPayload = (localData, isCreate) => {
54
58
  if (invoiceNotes !== '') {
55
59
  data.invoice_notes = invoiceNotes;
56
60
  }
61
+ // Omitted rather than sent as `null` when there is none: the service reads a
62
+ // blank discounts account as "not given" and leaves what it has, which is
63
+ // what keeps a form that never showed the picker — QBO disconnected — from
64
+ // clearing an account set elsewhere.
65
+ const discountsAccountId = localData.discountsAccountId.trim();
66
+ const discountsAccount = discountsAccountId !== ''
67
+ ? accountByQboId?.(discountsAccountId)
68
+ : undefined;
69
+ if (discountsAccount != null) {
70
+ data.discounts_account = toAccountPayload(discountsAccount);
71
+ }
57
72
  return data;
58
73
  };
@@ -1,5 +1,6 @@
1
1
  import { createSlice } from '@reduxjs/toolkit';
2
2
  import { clearAll } from '../../../rootActions';
3
+ import { applyInvoicingFormDraftEdit, applyInvoicingFormDraftFailure, applyInvoicingFormDraftSaved, applyInvoicingFormDraftSeed, } from '../invoicingFormDraft';
3
4
  import { initialInvoicingCouponFormLocalData, } from './editInvoicingCouponDetailViewState';
4
5
  export const initialState = {
5
6
  fetchState: 'Not-Started',
@@ -20,7 +21,7 @@ const editInvoicingCouponDetailView = createSlice({
20
21
  updateInvoicingCouponFormDraft(draft, action) {
21
22
  const { id, localData } = action.payload;
22
23
  if (id != null) {
23
- draft.editDraftById[id] = localData;
24
+ applyInvoicingFormDraftEdit(draft.editDraftById, id, localData);
24
25
  }
25
26
  else {
26
27
  draft.newCouponDraft = localData;
@@ -50,6 +51,9 @@ const editInvoicingCouponDetailView = createSlice({
50
51
  draft.fetchState = 'Completed';
51
52
  draft.error = undefined;
52
53
  draft.savedCouponId = action.payload.couponId;
54
+ // An edit is refetched on success; keep the saved values on screen until
55
+ // that lands, and let it replace them.
56
+ applyInvoicingFormDraftSaved(draft.editDraftById, action.payload.couponId);
53
57
  },
54
58
  submitInvoicingCouponFormFailure(draft, action) {
55
59
  draft.fetchState = 'Error';
@@ -85,15 +89,12 @@ const editInvoicingCouponDetailView = createSlice({
85
89
  fetchState: 'Error',
86
90
  error: action.payload.status,
87
91
  };
92
+ applyInvoicingFormDraftFailure(draft.editDraftById, action.payload.id, initialInvoicingCouponFormLocalData);
88
93
  },
89
- /**
90
- * Seed the draft from the loaded record, once. Skipped when a draft already
91
- * exists so a late refetch cannot overwrite in-progress edits.
92
- */
94
+ /** Fill the draft from the loaded record — see `applyInvoicingFormDraftSeed`. */
93
95
  seedInvoicingCouponFormDraft(draft, action) {
94
- if (draft.editDraftById[action.payload.id] == null) {
95
- draft.editDraftById[action.payload.id] = action.payload.localData;
96
- }
96
+ const { id, localData } = action.payload;
97
+ applyInvoicingFormDraftSeed(draft.editDraftById, id, localData);
97
98
  },
98
99
  runInvoicingDiscountAction(draft, action) {
99
100
  draft.actionStateById[action.payload.id] = {
@@ -113,12 +114,22 @@ const editInvoicingCouponDetailView = createSlice({
113
114
  error: action.payload.error,
114
115
  };
115
116
  },
116
- resetEditInvoicingCouponDetailView(draft) {
117
+ /**
118
+ * Discard a form's draft — the coupon's own when `id` is given,
119
+ * the create draft otherwise. Scoped to the one form on purpose: another
120
+ * coupon's half-finished edit is not this form's to throw away.
121
+ * Clearing every draft at once is what `clearAll` is for.
122
+ */
123
+ resetEditInvoicingCouponDetailView(draft, action) {
117
124
  draft.fetchState = 'Not-Started';
118
125
  draft.error = undefined;
119
126
  draft.savedCouponId = undefined;
120
- draft.editDraftById = {};
121
- draft.newCouponDraft = { ...initialInvoicingCouponFormLocalData };
127
+ const id = action.payload?.id;
128
+ if (id == null) {
129
+ draft.newCouponDraft = { ...initialInvoicingCouponFormLocalData };
130
+ return;
131
+ }
132
+ delete draft.editDraftById[id];
122
133
  },
123
134
  },
124
135
  extraReducers: (builder) => {
@@ -1,13 +1,32 @@
1
+ import { createSelector } from '@reduxjs/toolkit';
1
2
  import { reduceAllFetchState } from '../../../commonStateTypes/reduceFetchState';
2
3
  import { getInvoicingCouponById } from '../../../entity/invoicing/coupon/couponSelector';
3
4
  import { toInvoicingFormOption, } from '../../../entity/invoicing/invoicingCommonPayload';
4
5
  import { getInvoicingConfigView, invoicingFieldHelpForGroup, } from '../invoicingConfigView/invoicingConfigViewSelector';
5
- import { initialInvoicingCouponFormLocalData, } from './editInvoicingCouponDetailViewState';
6
+ import { getInvoicingFormDraftLocalData, isInvoicingFormDraftPending, } from '../invoicingFormDraft';
7
+ import { INVOICING_DEFAULT_INCOME_ACCOUNT_TYPES, INVOICING_DISCOUNTS_ACCOUNT_MAPPING_SLOT, NO_INVOICING_ACCOUNT_HIERARCHY, NO_INVOICING_ACCOUNT_IDS, getInvoicingQboAccountOptions, getInvoicingQboView, invoicingQboAccountByQboId, invoicingQboAccountIdsForMappingSlot, isInvoicingAccountingPickerDataLoading, isInvoicingQboConnected, } from '../invoicingQboView/invoicingQboViewSelector';
8
+ import { getInvoicingSettings, getInvoicingSettingsFetchState, } from '../settingsView/settingsViewSelector';
9
+ import { initialInvoicingCouponFormLocalData, invoicingCouponDiscountsAccountIdForCreate, } from './editInvoicingCouponDetailViewState';
6
10
  /**
7
11
  * Bundled view for the discount create/edit form: the save fetch state/error,
8
12
  * the id of the coupon last saved, and — when a `couponId` is passed (edit
9
13
  * mode) — the existing coupon entity to prefill the form.
10
14
  */
15
+ /**
16
+ * The QuickBooks ids the discounts account may be picked from.
17
+ *
18
+ * Memoized, and collapsing to one shared empty array while QBO is disconnected:
19
+ * the form turns this into its picker options and re-seeds its defaults when
20
+ * those change, so a fresh array on every store change would reset the form
21
+ * under the user until their first edit.
22
+ */
23
+ const getInvoicingCouponDiscountsAccountIds = createSelector([
24
+ getInvoicingQboAccountOptions,
25
+ (state) => getInvoicingConfigView(state).qboAccountMappingKindOptions,
26
+ isInvoicingQboConnected,
27
+ ], (accounts, qboAccountMappingKindOptions, isQboConnected) => isQboConnected
28
+ ? invoicingQboAccountIdsForMappingSlot(accounts, qboAccountMappingKindOptions, INVOICING_DISCOUNTS_ACCOUNT_MAPPING_SLOT, INVOICING_DEFAULT_INCOME_ACCOUNT_TYPES)
29
+ : NO_INVOICING_ACCOUNT_IDS);
11
30
  const NOT_STARTED = {
12
31
  error: undefined,
13
32
  fetchState: 'Not-Started',
@@ -23,6 +42,9 @@ export const getEditInvoicingCouponDetailViewState = (state, couponId) => {
23
42
  fetchState: 'Not-Started',
24
43
  })
25
44
  : { error: undefined, fetchState: 'Completed' };
45
+ const coupon = couponId != null
46
+ ? getInvoicingCouponById(state.invoicingCouponState, couponId)
47
+ : undefined;
26
48
  return {
27
49
  actionState: couponId != null
28
50
  ? (state.editInvoicingCouponDetailViewState.actionStateById[couponId] ??
@@ -36,9 +58,9 @@ export const getEditInvoicingCouponDetailViewState = (state, couponId) => {
36
58
  { error: undefined, fetchState: configFetchState },
37
59
  ]),
38
60
  recordState,
39
- coupon: couponId != null
40
- ? getInvoicingCouponById(state.invoicingCouponState, couponId)
41
- : undefined,
61
+ isFormLoading: isInvoicingFormDraftPending(state.editInvoicingCouponDetailViewState.editDraftById, couponId),
62
+ coupon,
63
+ discountsAccountName: invoicingQboAccountByQboId(state, coupon?.discountsAccountId)?.accountName,
42
64
  savedCouponId,
43
65
  fetchState,
44
66
  error,
@@ -58,16 +80,57 @@ export const getInvoicingCouponFormView = (state, couponId) => {
58
80
  // `seedInvoicingCouponFormDraftEpic` when the detail load lands. Until then
59
81
  // the page is showing its skeleton.
60
82
  const localData = couponId != null
61
- ? (editDraftById[couponId] ?? initialInvoicingCouponFormLocalData)
83
+ ? getInvoicingFormDraftLocalData(editDraftById, couponId, initialInvoicingCouponFormLocalData)
62
84
  : newCouponDraft;
63
85
  const { billingPeriodUnitOptions, discountApplyOnOptions, discountDurationTypeOptions, discountTypeOptions, } = getInvoicingConfigView(state);
86
+ const isQboConnected = isInvoicingQboConnected(state);
87
+ const qboView = isQboConnected ? getInvoicingQboView(state) : undefined;
88
+ const isQboPickerDataLoading = isInvoicingAccountingPickerDataLoading(state);
89
+ const settingsFetchState = getInvoicingSettingsFetchState(state).fetchState;
90
+ // Only a create reads the settings, and only until they arrive: an `Error`
91
+ // is a create that starts on no account, not a page that never renders.
92
+ const isCreateSeedPending = couponId == null &&
93
+ (settingsFetchState === 'Not-Started' ||
94
+ settingsFetchState === 'In-Progress');
64
95
  return {
65
96
  ...base,
66
97
  applyOnOptions: discountApplyOnOptions.map(toInvoicingFormOption),
67
98
  discountTypeOptions: discountTypeOptions.map(toInvoicingFormOption),
68
99
  durationTypeOptions: discountDurationTypeOptions.map(toInvoicingFormOption),
69
- localData,
100
+ isQboConnected,
101
+ isQboPickerDataLoading,
102
+ // Widened past the record and config the detail page waits on: the form
103
+ // also renders from the account list, and seeds a create from the settings
104
+ // discounts account. A form that mounted before those landed would offer an
105
+ // empty picker and save a coupon with no account of its own — not wrong,
106
+ // since blank means "use the settings account", but not what the user was
107
+ // shown either. Widened here rather than on the base view, so the detail
108
+ // page does not start waiting on lists it never renders.
109
+ //
110
+ // Only while they are still coming, though, and the settings only on a
111
+ // create: a coupon saves fine without a discounts account, so neither is
112
+ // allowed to hold the form behind a skeleton once it has failed.
113
+ pageFetchState: reduceAllFetchState([
114
+ base.pageFetchState,
115
+ {
116
+ error: undefined,
117
+ fetchState: isQboPickerDataLoading || isCreateSeedPending
118
+ ? 'In-Progress'
119
+ : 'Completed',
120
+ },
121
+ ]),
122
+ localData: couponId != null
123
+ ? localData
124
+ : {
125
+ ...localData,
126
+ // Seeded from settings only while the draft has none of its own, so
127
+ // the picker shows the account a create would post against before
128
+ // the user touches it, and stops seeding the moment they pick.
129
+ discountsAccountId: invoicingCouponDiscountsAccountIdForCreate(localData.discountsAccountId, getInvoicingSettings(state)?.qbo?.account_mappings),
130
+ },
70
131
  mode: couponId != null ? 'edit' : 'create',
71
132
  periodUnitOptions: billingPeriodUnitOptions.map(toInvoicingFormOption),
133
+ qboAccountHierarchy: qboView?.nestedAccountHierarchy ?? NO_INVOICING_ACCOUNT_HIERARCHY,
134
+ qboIncomeAccountIds: getInvoicingCouponDiscountsAccountIds(state),
72
135
  };
73
136
  };
@@ -1,9 +1,11 @@
1
+ import { getInvoicingQboAccountMappingId, } from '../../../entity/invoicing/invoicingCommonPayload';
1
2
  export const initialInvoicingCouponFormLocalData = {
2
3
  applyOn: 'invoice_amount',
3
4
  couponCode: '',
4
5
  couponId: '',
5
6
  discountPercentage: '',
6
7
  discountQuantity: '',
8
+ discountsAccountId: '',
7
9
  discountType: 'percentage',
8
10
  durationType: 'one_time',
9
11
  invoiceName: '',
@@ -29,6 +31,10 @@ export const entityToInvoicingCouponFormLocalData = (coupon) => {
29
31
  ? String(coupon.discountPercentage)
30
32
  : '',
31
33
  discountQuantity: coupon.discountQuantity != null ? String(coupon.discountQuantity) : '',
34
+ // The coupon's own account, never the current setting: an edit shows what
35
+ // this coupon will post against, which is what was stored when it was
36
+ // saved even if the settings default has moved since.
37
+ discountsAccountId: coupon.discountsAccountId ?? '',
32
38
  discountType: coupon.discountType ?? 'percentage',
33
39
  durationType: coupon.durationType ?? 'one_time',
34
40
  invoiceName: coupon.invoiceName ?? '',
@@ -41,3 +47,19 @@ export const entityToInvoicingCouponFormLocalData = (coupon) => {
41
47
  validTill: coupon.validTill != null ? coupon.validTill.format('YYYY-MM-DD') : '',
42
48
  };
43
49
  };
50
+ /**
51
+ * The discounts account a create starts from: the draft's own pick, or the QBO
52
+ * settings discounts account while the user has not made one. Read by both the
53
+ * form view and the save, so what the picker shows is what the request sends —
54
+ * a create that leaves the picker alone still stores the settings account on
55
+ * the coupon rather than relying on the setting staying put.
56
+ *
57
+ * Create only. An edit shows the coupon's stored account, so a coupon saved
58
+ * without one keeps showing none.
59
+ */
60
+ export const invoicingCouponDiscountsAccountIdForCreate = (discountsAccountId, accountMappings) => {
61
+ const pickedAccountId = discountsAccountId.trim();
62
+ return pickedAccountId !== ''
63
+ ? pickedAccountId
64
+ : (getInvoicingQboAccountMappingId(accountMappings ?? undefined, 'discounts_account') ?? '');
65
+ };
@@ -1,6 +1,8 @@
1
1
  import { EMPTY, from } from 'rxjs';
2
2
  import { filter, mergeMap, withLatestFrom } from 'rxjs/operators';
3
3
  import { fetchInvoicingConfig } from '../invoicingConfigView/invoicingConfigViewReducer';
4
+ import { fetchInvoicingAccountingPickers, fetchInvoicingQboConnection, } from '../invoicingQboView/invoicingQboViewReducer';
5
+ import { fetchInvoicingSettings } from '../settingsView/settingsViewReducer';
4
6
  import { fetchInvoicingCouponDetail, fetchInvoicingCouponDetailPage, } from './editInvoicingCouponDetailViewReducer';
5
7
  /**
6
8
  * Fetch unless the endpoint is already loaded or in flight — or always when the
@@ -10,10 +12,12 @@ const shouldFetch = (fetchStateAndError, cacheOverride) => cacheOverride ||
10
12
  (fetchStateAndError.fetchState !== 'Completed' &&
11
13
  fetchStateAndError.fetchState !== 'In-Progress');
12
14
  /**
13
- * Orchestrates the discount detail page: one action fans out to the coupon
14
- * record and the invoicing config behind its dropdowns and field help, each
15
- * skipped when already cached. The page reads a single reduced fetch state off
16
- * the view, so the form mounts once with real values.
15
+ * Orchestrates the discount pages: one action fans out to the coupon record,
16
+ * the invoicing config behind its dropdowns and field help, and the QuickBooks
17
+ * connection, account list and settings the form's Discounts account picker
18
+ * renders and seeds from — each skipped when already cached. The page reads a
19
+ * single reduced fetch state off the view, so the form mounts once with real
20
+ * values.
17
21
  */
18
22
  export const fetchInvoicingCouponDetailPageEpic = (actions$, state$) => actions$.pipe(filter(fetchInvoicingCouponDetailPage.match), withLatestFrom(state$), mergeMap(([action, state]) => {
19
23
  const { cacheOverride = false, id } = action.payload;
@@ -32,5 +36,19 @@ export const fetchInvoicingCouponDetailPageEpic = (actions$, state$) => actions$
32
36
  if (shouldFetch({ error: configState.error, fetchState: configState.fetchState }, cacheOverride)) {
33
37
  pageActions.push(fetchInvoicingConfig());
34
38
  }
39
+ const qboState = state.invoicingQboViewState;
40
+ if (shouldFetch(qboState.connectionFetchState, cacheOverride)) {
41
+ pageActions.push(fetchInvoicingQboConnection());
42
+ }
43
+ // The picker epic tops up the shared account list and skips what the
44
+ // store already holds, so this is safe to ask for on every open.
45
+ pageActions.push(fetchInvoicingAccountingPickers());
46
+ const settingsState = state.invoicingSettingsViewState;
47
+ if (shouldFetch({ error: settingsState.error, fetchState: settingsState.fetchState }, cacheOverride)) {
48
+ // A create seeds its Discounts account from the settings mapping, so
49
+ // the settings are page data here rather than a settings-screen
50
+ // concern.
51
+ pageActions.push(fetchInvoicingSettings());
52
+ }
35
53
  return pageActions.length > 0 ? from(pageActions) : EMPTY;
36
54
  }));
@@ -5,8 +5,11 @@ import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiE
5
5
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
6
6
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
7
7
  import { addInvoicingCouponToList } from '../couponView/couponViewReducer';
8
+ import { invoicingQboAccountByQboId } from '../invoicingQboView/invoicingQboViewSelector';
9
+ import { getInvoicingSettings } from '../settingsView/settingsViewSelector';
8
10
  import { localDataToSaveInvoicingCouponPayload, } from './editInvoicingCouponDetailViewPayload';
9
11
  import { saveInvoicingCoupon, submitInvoicingCouponForm, submitInvoicingCouponFormFailure, submitInvoicingCouponFormSuccess, } from './editInvoicingCouponDetailViewReducer';
12
+ import { invoicingCouponDiscountsAccountIdForCreate } from './editInvoicingCouponDetailViewState';
10
13
  export const saveInvoicingCouponEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter((action) => submitInvoicingCouponForm.match(action) ||
11
14
  saveInvoicingCoupon.match(action)), mergeMap((action) => {
12
15
  const { id } = action.payload;
@@ -14,11 +17,22 @@ export const saveInvoicingCouponEpic = (actions$, state$, zeniAPI) => actions$.p
14
17
  const data = saveInvoicingCoupon.match(action)
15
18
  ? action.payload.data
16
19
  : (() => {
17
- const viewState = state$.value.editInvoicingCouponDetailViewState;
18
- const localData = (id != null
19
- ? viewState.editDraftById[id]
20
+ const state = state$.value;
21
+ const viewState = state.editInvoicingCouponDetailViewState;
22
+ const draft = (id != null
23
+ ? viewState.editDraftById[id]?.localData
20
24
  : viewState.newCouponDraft) ?? viewState.newCouponDraft;
21
- return localDataToSaveInvoicingCouponPayload(localData, id == null);
25
+ // The create default is resolved here as well as in the form view,
26
+ // so a create that never touched the picker still persists the
27
+ // settings account on the coupon instead of leaving it to whatever
28
+ // the setting says the day an invoice is raised.
29
+ const localData = id == null
30
+ ? {
31
+ ...draft,
32
+ discountsAccountId: invoicingCouponDiscountsAccountIdForCreate(draft.discountsAccountId, getInvoicingSettings(state)?.qbo?.account_mappings),
33
+ }
34
+ : draft;
35
+ return localDataToSaveInvoicingCouponPayload(localData, id == null, (qboId) => invoicingQboAccountByQboId(state, qboId));
22
36
  })();
23
37
  const baseUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0`;
24
38
  const path = id != null