@zeniai/client-epic-state 5.1.98 → 5.2.0

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 (150) hide show
  1. package/lib/commonStateTypes/accountView/nestedAccountID.d.ts +0 -3
  2. package/lib/commonStateTypes/accountView/nestedAccountID.js +1 -4
  3. package/lib/commonStateTypes/coaBalance/coaBalance.d.ts +0 -5
  4. package/lib/commonStateTypes/timePeriod.d.ts +0 -2
  5. package/lib/commonStateTypes/timePeriod.js +1 -6
  6. package/lib/entity/invoicing/invoicingCommonPayload.d.ts +12 -0
  7. package/lib/entity/invoicing/invoicingCommonPayload.js +18 -0
  8. package/lib/entity/invoicing/invoicingTransaction/invoicingTransactionPayload.d.ts +1 -1
  9. package/lib/entity/toastNotification/epic/pushToastNotificationEpic.d.ts +0 -1
  10. package/lib/entity/toastNotification/epic/pushToastNotificationEpic.js +0 -1
  11. package/lib/entity/transaction/toSupportedTransaction.d.ts +1 -1
  12. package/lib/entity/transaction/toSupportedTransaction.js +0 -1
  13. package/lib/esm/commonStateTypes/accountView/nestedAccountID.js +0 -3
  14. package/lib/esm/commonStateTypes/timePeriod.js +0 -4
  15. package/lib/esm/entity/invoicing/invoicingCommonPayload.js +16 -0
  16. package/lib/esm/entity/toastNotification/epic/pushToastNotificationEpic.js +0 -1
  17. package/lib/esm/entity/transaction/toSupportedTransaction.js +1 -1
  18. package/lib/esm/index.js +12 -11
  19. package/lib/esm/view/invoicing/auditView/auditViewSelector.js +4 -0
  20. package/lib/esm/view/invoicing/couponView/couponViewSelector.js +18 -8
  21. package/lib/esm/view/invoicing/couponView/couponViewState.js +0 -9
  22. package/lib/esm/view/invoicing/createCreditNote/createCreditNoteFormConfig.js +1 -0
  23. package/lib/esm/view/invoicing/createCreditNote/createCreditNotePayload.js +4 -1
  24. package/lib/esm/view/invoicing/createCreditNote/createCreditNoteSelector.js +4 -2
  25. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +7 -6
  26. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/invoicingCatalogItemFormConfig.js +3 -19
  27. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewSelector.js +10 -5
  28. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.js +6 -4
  29. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/invoicingCustomerFormConfig.js +0 -17
  30. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +5 -0
  31. package/lib/esm/view/invoicing/invoiceList/fetchInvoiceListPageEpic.js +2 -1
  32. package/lib/esm/view/invoicing/invoiceList/invoiceListReducer.js +18 -17
  33. package/lib/esm/view/invoicing/invoiceList/invoiceListSelector.js +12 -4
  34. package/lib/esm/view/invoicing/invoiceList/invoiceListState.js +13 -9
  35. package/lib/esm/view/invoicing/invoicingCatalogItemDetailView/invoicingCatalogItemDetailViewSelector.js +5 -1
  36. package/lib/esm/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewSelector.js +2 -1
  37. package/lib/esm/view/invoicing/invoicingConfigView/invoicingConfigViewReducer.js +26 -0
  38. package/lib/esm/view/invoicing/invoicingConfigView/invoicingConfigViewSelector.js +9 -0
  39. package/lib/esm/view/invoicing/invoicingCreditNoteListView/invoicingCreditNoteListViewSelector.js +9 -3
  40. package/lib/esm/view/invoicing/invoicingCreditNoteListView/invoicingCreditNoteListViewState.js +0 -9
  41. package/lib/esm/view/invoicing/invoicingCustomerDetailView/fetchInvoicingCustomerDetailPageEpic.js +2 -2
  42. package/lib/esm/view/invoicing/invoicingCustomerDetailView/invoicingCustomerDetailViewSelector.js +1 -1
  43. package/lib/esm/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewSelector.js +9 -3
  44. package/lib/esm/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewState.js +0 -9
  45. package/lib/esm/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewSelector.js +8 -3
  46. package/lib/esm/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewState.js +1 -9
  47. package/lib/esm/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewSelector.js +11 -3
  48. package/lib/esm/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewState.js +0 -9
  49. package/lib/esm/view/invoicing/invoicingTabHelper.js +27 -0
  50. package/lib/esm/view/invoicing/invoicingTransactionListView/epics/fetchInvoicingTransactionListPageEpic.js +2 -1
  51. package/lib/esm/view/invoicing/invoicingTransactionListView/invoicingTransactionListViewReducer.js +5 -5
  52. package/lib/esm/view/invoicing/invoicingTransactionListView/invoicingTransactionListViewSelector.js +12 -4
  53. package/lib/esm/view/invoicing/invoicingTransactionListView/invoicingTransactionListViewState.js +11 -9
  54. package/lib/esm/view/invoicing/issueCreditNote/issueCreditNoteFormConfig.js +1 -8
  55. package/lib/esm/view/invoicing/issueCreditNote/issueCreditNotePayload.js +5 -1
  56. package/lib/esm/view/invoicing/issueCreditNote/issueCreditNoteSelector.js +5 -2
  57. package/lib/esm/view/invoicing/recordPayment/recordPaymentFormConfig.js +0 -26
  58. package/lib/esm/view/invoicing/recordPayment/recordPaymentSelector.js +26 -6
  59. package/lib/esm/view/invoicing/settingsView/settingsViewSelector.js +7 -1
  60. package/lib/esm/view/profitAndLossProjectView/profitAndLossProjectViewReducer.js +2 -1
  61. package/lib/index.d.ts +13 -12
  62. package/lib/index.js +9 -24
  63. package/lib/view/invoicing/auditView/auditViewSelector.d.ts +4 -0
  64. package/lib/view/invoicing/auditView/auditViewSelector.js +4 -0
  65. package/lib/view/invoicing/couponView/couponViewSelector.d.ts +5 -1
  66. package/lib/view/invoicing/couponView/couponViewSelector.js +17 -7
  67. package/lib/view/invoicing/couponView/couponViewState.d.ts +0 -7
  68. package/lib/view/invoicing/couponView/couponViewState.js +1 -10
  69. package/lib/view/invoicing/createCreditNote/createCreditNoteFormConfig.js +1 -0
  70. package/lib/view/invoicing/createCreditNote/createCreditNotePayload.js +4 -1
  71. package/lib/view/invoicing/createCreditNote/createCreditNoteSelector.d.ts +3 -1
  72. package/lib/view/invoicing/createCreditNote/createCreditNoteSelector.js +4 -2
  73. package/lib/view/invoicing/createCreditNote/createCreditNoteState.d.ts +6 -0
  74. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.d.ts +5 -5
  75. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +6 -5
  76. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/invoicingCatalogItemFormConfig.d.ts +3 -6
  77. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/invoicingCatalogItemFormConfig.js +4 -20
  78. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewSelector.d.ts +9 -7
  79. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewSelector.js +10 -5
  80. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.d.ts +4 -3
  81. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.js +5 -3
  82. package/lib/view/invoicing/editInvoicingCustomerDetailView/invoicingCustomerFormConfig.d.ts +0 -8
  83. package/lib/view/invoicing/editInvoicingCustomerDetailView/invoicingCustomerFormConfig.js +1 -18
  84. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.d.ts +5 -0
  85. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +5 -0
  86. package/lib/view/invoicing/invoiceList/fetchInvoiceListPageEpic.js +2 -1
  87. package/lib/view/invoicing/invoiceList/invoiceListReducer.js +17 -16
  88. package/lib/view/invoicing/invoiceList/invoiceListSelector.d.ts +3 -1
  89. package/lib/view/invoicing/invoiceList/invoiceListSelector.js +11 -3
  90. package/lib/view/invoicing/invoiceList/invoiceListState.d.ts +13 -9
  91. package/lib/view/invoicing/invoiceList/invoiceListState.js +15 -10
  92. package/lib/view/invoicing/invoicingCatalogItemDetailView/invoicingCatalogItemDetailViewSelector.d.ts +9 -1
  93. package/lib/view/invoicing/invoicingCatalogItemDetailView/invoicingCatalogItemDetailViewSelector.js +5 -1
  94. package/lib/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewSelector.d.ts +3 -1
  95. package/lib/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewSelector.js +2 -1
  96. package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewPayload.d.ts +13 -0
  97. package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewReducer.d.ts +1 -1
  98. package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewReducer.js +26 -0
  99. package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewSelector.d.ts +19 -1
  100. package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewSelector.js +11 -1
  101. package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewState.d.ts +13 -0
  102. package/lib/view/invoicing/invoicingCreditNoteListView/invoicingCreditNoteListViewSelector.d.ts +8 -1
  103. package/lib/view/invoicing/invoicingCreditNoteListView/invoicingCreditNoteListViewSelector.js +8 -2
  104. package/lib/view/invoicing/invoicingCreditNoteListView/invoicingCreditNoteListViewState.d.ts +0 -7
  105. package/lib/view/invoicing/invoicingCreditNoteListView/invoicingCreditNoteListViewState.js +1 -10
  106. package/lib/view/invoicing/invoicingCustomerDetailView/fetchInvoicingCustomerDetailPageEpic.js +2 -2
  107. package/lib/view/invoicing/invoicingCustomerDetailView/invoicingCustomerDetailViewSelector.js +1 -1
  108. package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewSelector.d.ts +3 -1
  109. package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewSelector.js +8 -2
  110. package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewState.d.ts +0 -7
  111. package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewState.js +1 -10
  112. package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewReducer.d.ts +1 -1
  113. package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewSelector.d.ts +3 -1
  114. package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewSelector.js +7 -2
  115. package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewState.d.ts +2 -9
  116. package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewState.js +2 -10
  117. package/lib/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewSelector.d.ts +7 -1
  118. package/lib/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewSelector.js +10 -2
  119. package/lib/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewState.d.ts +0 -7
  120. package/lib/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewState.js +1 -10
  121. package/lib/view/invoicing/invoicingTabHelper.d.ts +27 -0
  122. package/lib/view/invoicing/invoicingTabHelper.js +30 -0
  123. package/lib/view/invoicing/invoicingTransactionListView/epics/fetchInvoicingTransactionListPageEpic.js +2 -1
  124. package/lib/view/invoicing/invoicingTransactionListView/invoicingTransactionListViewReducer.d.ts +1 -1
  125. package/lib/view/invoicing/invoicingTransactionListView/invoicingTransactionListViewReducer.js +4 -4
  126. package/lib/view/invoicing/invoicingTransactionListView/invoicingTransactionListViewSelector.d.ts +8 -0
  127. package/lib/view/invoicing/invoicingTransactionListView/invoicingTransactionListViewSelector.js +11 -3
  128. package/lib/view/invoicing/invoicingTransactionListView/invoicingTransactionListViewState.d.ts +10 -10
  129. package/lib/view/invoicing/invoicingTransactionListView/invoicingTransactionListViewState.js +13 -10
  130. package/lib/view/invoicing/issueCreditNote/issueCreditNoteFormConfig.d.ts +0 -2
  131. package/lib/view/invoicing/issueCreditNote/issueCreditNoteFormConfig.js +2 -9
  132. package/lib/view/invoicing/issueCreditNote/issueCreditNotePayload.d.ts +2 -2
  133. package/lib/view/invoicing/issueCreditNote/issueCreditNotePayload.js +5 -1
  134. package/lib/view/invoicing/issueCreditNote/issueCreditNoteSelector.d.ts +3 -1
  135. package/lib/view/invoicing/issueCreditNote/issueCreditNoteSelector.js +4 -1
  136. package/lib/view/invoicing/issueCreditNote/issueCreditNoteState.d.ts +6 -0
  137. package/lib/view/invoicing/recordPayment/recordPaymentFormConfig.d.ts +0 -6
  138. package/lib/view/invoicing/recordPayment/recordPaymentFormConfig.js +1 -27
  139. package/lib/view/invoicing/recordPayment/recordPaymentSelector.d.ts +3 -2
  140. package/lib/view/invoicing/recordPayment/recordPaymentSelector.js +26 -6
  141. package/lib/view/invoicing/settingsView/settingsViewSelector.d.ts +11 -1
  142. package/lib/view/invoicing/settingsView/settingsViewSelector.js +7 -1
  143. package/lib/view/profitAndLossProjectView/profitAndLossProjectViewReducer.js +2 -1
  144. package/package.json +4 -10
  145. package/lib/commonStateTypes/selectorTypes/pickAndMergeForecastSection.d.ts +0 -0
  146. package/lib/commonStateTypes/selectorTypes/pickAndMergeForecastSection.js +0 -1
  147. package/lib/entity/invoicing/coupon/couponConstants.d.ts +0 -11
  148. package/lib/entity/invoicing/coupon/couponConstants.js +0 -29
  149. package/lib/esm/commonStateTypes/selectorTypes/pickAndMergeForecastSection.js +0 -1
  150. package/lib/esm/entity/invoicing/coupon/couponConstants.js +0 -26
@@ -1,22 +1,5 @@
1
1
  /** `AddressType` key used when the customer form opens the shared Address screen. */
2
2
  export const INVOICING_CUSTOMER_BILLING_ADDRESS_TYPE = 'invoicing_customer_billing_address';
3
- /**
4
- * Canonical option value sets for the customer form. CES owns the values;
5
- * web-components maps each value to a localized label (same split as the
6
- * invoicing list column/tab config).
7
- */
8
- export const INVOICING_CUSTOMER_TYPE_VALUES = [
9
- 'business',
10
- 'residential',
11
- ];
12
- export const INVOICING_TAXABILITY_VALUES = ['taxable', 'exempt'];
13
- export const INVOICING_NET_TERM_DAY_VALUES = [
14
- '0',
15
- '15',
16
- '30',
17
- '45',
18
- '60',
19
- ];
20
3
  export const initialInvoicingCustomerFormLocalData = {
21
4
  autoCollection: true,
22
5
  company: '',
@@ -2,11 +2,16 @@ import { getInvoicingSubscriptionById } from '../../../entity/invoicing/invoicin
2
2
  import { getFormattedAddress } from '../../addressView/addressViewSelector';
3
3
  import { getInvoicingCouponListItems } from '../couponView/couponViewSelector';
4
4
  import { getInvoicingCatalogListView, getInvoicingCatalogPrices, } from '../invoicingCatalogListView/invoicingCatalogListViewSelector';
5
+ import { getInvoicingConfigView } from '../invoicingConfigView/invoicingConfigViewSelector';
5
6
  import { getInvoicingCustomerListView } from '../invoicingCustomerListView/invoicingCustomerListViewSelector';
6
7
  import { INVOICING_SUBSCRIPTION_BILLING_FREQUENCY_VALUES, INVOICING_SUBSCRIPTION_SHIPPING_ADDRESS_TYPE, entityToInvoicingSubscriptionFormLocalData, } from './invoicingSubscriptionFormConfig';
7
8
  export const getEditInvoicingSubscriptionDetailViewState = (state, invoicingSubscriptionID) => {
8
9
  const { error, fetchState, savedSubscriptionId } = state.editInvoicingSubscriptionDetailViewState;
10
+ const { subscriptionCommercialEditAllowedFromStatuses, subscriptionLifecycleActionOptions, subscriptionMetadataEditAllowedFromStatuses, } = getInvoicingConfigView(state);
9
11
  return {
12
+ subscriptionCommercialEditAllowedFromStatuses,
13
+ subscriptionLifecycleActionOptions,
14
+ subscriptionMetadataEditAllowedFromStatuses,
10
15
  subscription: invoicingSubscriptionID != null
11
16
  ? getInvoicingSubscriptionById(state.invoicingSubscriptionState, invoicingSubscriptionID)
12
17
  : undefined,
@@ -1,6 +1,7 @@
1
1
  import { concat, from } from 'rxjs';
2
2
  import { filter, mergeMap } from 'rxjs/operators';
3
3
  import { fetchInvoiceCounts, fetchInvoiceKPIs, fetchInvoiceList, fetchInvoiceListPage, } from './invoiceListReducer';
4
+ import { makeInvoiceTabState } from './invoiceListState';
4
5
  /**
5
6
  * Fetch unless the endpoint is already loaded or in flight — or always when the
6
7
  * caller forces a refresh via `cacheOverride` (e.g. on a filter/sort change).
@@ -16,7 +17,7 @@ const shouldFetch = (fetchStateAndError, cacheOverride) => cacheOverride ||
16
17
  export const fetchInvoiceListPageEpic = (actions$, state$) => actions$.pipe(filter(fetchInvoiceListPage.match), mergeMap((action) => {
17
18
  const { cacheOverride, filters, sort } = action.payload;
18
19
  const state = state$.value.invoicingInvoiceListState;
19
- const activeTabState = state.invoiceByTab[state.activeTab];
20
+ const activeTabState = state.invoiceByTab[state.activeTab] ?? makeInvoiceTabState();
20
21
  const pageActions = [];
21
22
  if (shouldFetch({ fetchState: activeTabState.fetchState, error: activeTabState.error }, cacheOverride)) {
22
23
  pageActions.push(fetchInvoiceList({ filters, sort }));
@@ -1,6 +1,6 @@
1
1
  import { createSlice } from '@reduxjs/toolkit';
2
2
  import { clearAll } from '../../../rootActions';
3
- import { ALL_INVOICING_INVOICE_TAB_IDS, makeInvoiceByTab, } from './invoiceListState';
3
+ import { invoiceTabStateFor, makeInvoiceByTab, } from './invoiceListState';
4
4
  /**
5
5
  * Whether two invoice-list queries share the same base (tab-independent) query.
6
6
  * The status tab is server-side per-tab, so it is excluded here — when the base
@@ -15,13 +15,17 @@ export const sameInvoiceListQuery = (a, b) => a.filters.customerId === b.filters
15
15
  a.sort?.field === b.sort?.field &&
16
16
  a.sort?.dir === b.sort?.dir;
17
17
  /**
18
- * Narrow an invoice status to a status tab id when one exists. Statuses like
19
- * `draft` / `posted` / `pending` have no dedicated tab, so a newly created
20
- * invoice in one of those states only lands in the `all` tab.
18
+ * The status tab a newly created invoice belongs in always its own status.
19
+ *
20
+ * This used to gate on `ALL_INVOICING_INVOICE_TAB_IDS`, which left a status the
21
+ * config added but this build's union does not know getting its count
22
+ * incremented while the id landed only in `all`: the tab rendered with a
23
+ * non-zero count and an empty list until a refetch. Per-tab cache entries are
24
+ * created on demand now, so every status is a valid key and the add path agrees
25
+ * with the count path. A status with no rendered tab just caches an entry nobody
26
+ * reads.
21
27
  */
22
- const statusToTabId = (status) => ALL_INVOICING_INVOICE_TAB_IDS.includes(status)
23
- ? status
24
- : undefined;
28
+ const statusToTabId = (status) => status;
25
29
  export const initialState = {
26
30
  activeTab: 'all',
27
31
  invoiceByTab: makeInvoiceByTab(),
@@ -37,7 +41,7 @@ const invoiceList = createSlice({
37
41
  reducers: {
38
42
  fetchInvoiceList: {
39
43
  reducer(draft, action) {
40
- const tab = draft.invoiceByTab[draft.activeTab];
44
+ const tab = invoiceTabStateFor(draft.invoiceByTab, draft.activeTab);
41
45
  if (!action.payload.keepExistingListItems) {
42
46
  tab.invoiceIds = [];
43
47
  tab.nextCursor = null;
@@ -80,7 +84,7 @@ const invoiceList = createSlice({
80
84
  },
81
85
  },
82
86
  updateInvoiceList(draft, action) {
83
- const tab = draft.invoiceByTab[draft.activeTab];
87
+ const tab = invoiceTabStateFor(draft.invoiceByTab, draft.activeTab);
84
88
  const newIds = action.payload.invoices.map((invoice) => invoice.id);
85
89
  tab.invoiceIds = action.payload.keepExistingListItems
86
90
  ? [...new Set([...tab.invoiceIds, ...newIds])]
@@ -90,7 +94,7 @@ const invoiceList = createSlice({
90
94
  tab.error = undefined;
91
95
  },
92
96
  updateInvoiceListFailure(draft, action) {
93
- const tab = draft.invoiceByTab[draft.activeTab];
97
+ const tab = invoiceTabStateFor(draft.invoiceByTab, draft.activeTab);
94
98
  tab.fetchState = 'Error';
95
99
  tab.error = action.payload;
96
100
  },
@@ -136,17 +140,14 @@ const invoiceList = createSlice({
136
140
  // are left untouched and refetched lazily.
137
141
  addInvoiceToList(draft, action) {
138
142
  const { id, status } = action.payload;
139
- const allTab = draft.invoiceByTab['all'];
143
+ const allTab = invoiceTabStateFor(draft.invoiceByTab, 'all');
140
144
  if (allTab.invoiceIds.includes(id)) {
141
145
  return;
142
146
  }
143
147
  allTab.invoiceIds = [id, ...allTab.invoiceIds];
144
- const statusTab = statusToTabId(status);
145
- if (statusTab != null) {
146
- const tab = draft.invoiceByTab[statusTab];
147
- if (!tab.invoiceIds.includes(id)) {
148
- tab.invoiceIds = [id, ...tab.invoiceIds];
149
- }
148
+ const statusTab = invoiceTabStateFor(draft.invoiceByTab, statusToTabId(status));
149
+ if (!statusTab.invoiceIds.includes(id)) {
150
+ statusTab.invoiceIds = [id, ...statusTab.invoiceIds];
150
151
  }
151
152
  draft.counts.all += 1;
152
153
  draft.counts[status] = (draft.counts[status] ?? 0) + 1;
@@ -1,13 +1,17 @@
1
1
  import { createSelector } from '@reduxjs/toolkit';
2
- import { INVOICING_INVOICE_TABS, } from './invoiceListState';
2
+ import { invoicingTabIdsFromConfig } from '../invoicingTabHelper';
3
+ import { ALL_INVOICING_INVOICE_TAB_IDS, makeInvoiceTabState, } from './invoiceListState';
3
4
  /**
4
5
  * Bundled read view for the invoices list page: the denormalized invoice rows
5
6
  * plus list counts, KPIs and their fetch states. Memoized so the `invoices`
6
7
  * array (and the wrapping view object) keep referential identity until the
7
8
  * invoice entity slice or the list-view slice actually change.
8
9
  */
9
- export const getInvoiceListView = createSelector((state) => state.invoicingInvoiceState, (state) => state.invoicingInvoiceListState, (invoiceState, listViewState) => {
10
- const activeTabState = listViewState.invoiceByTab[listViewState.activeTab];
10
+ export const getInvoiceListView = createSelector((state) => state.invoicingInvoiceState, (state) => state.invoicingInvoiceListState, (state) => state.invoicingConfigViewState.invoiceStatusOptions, (invoiceState, listViewState, statusOptions) => {
11
+ // Config-driven tabs: a status this build's union does not know has no
12
+ // cache entry until it is fetched, so default rather than crash.
13
+ const activeTabState = listViewState.invoiceByTab[listViewState.activeTab] ??
14
+ makeInvoiceTabState();
11
15
  return {
12
16
  activeTab: listViewState.activeTab,
13
17
  invoices: activeTabState.invoiceIds
@@ -19,7 +23,11 @@ export const getInvoiceListView = createSelector((state) => state.invoicingInvoi
19
23
  kpis: listViewState.kpis,
20
24
  kpisFetchState: listViewState.kpisFetchState,
21
25
  nextCursor: activeTabState.nextCursor,
22
- tabIds: INVOICING_INVOICE_TABS,
26
+ statusOptions,
27
+ tabIds: invoicingTabIdsFromConfig({
28
+ allTabIds: ALL_INVOICING_INVOICE_TAB_IDS,
29
+ statusOptions,
30
+ }),
23
31
  fetchState: activeTabState.fetchState,
24
32
  error: activeTabState.error,
25
33
  version: 1.0,
@@ -8,21 +8,25 @@ export const ALL_INVOICING_INVOICE_TAB_IDS = [
8
8
  'voided',
9
9
  ];
10
10
  export const toInvoicingInvoiceTabId = (v) => stringToUnion(v, ALL_INVOICING_INVOICE_TAB_IDS);
11
- /**
12
- * Ordered filter-tab id set owned by CES so the invoice-list tabs live next to
13
- * the model rather than being hardcoded in web-components. Tab labels and the
14
- * per-tab count lookup stay in the view layer; this only carries the id set and
15
- * display order.
16
- */
17
- export const INVOICING_INVOICE_TABS = [
18
- ...ALL_INVOICING_INVOICE_TAB_IDS,
19
- ];
20
11
  export const makeInvoiceTabState = () => ({
21
12
  invoiceIds: [],
22
13
  nextCursor: null,
23
14
  fetchState: 'Not-Started',
24
15
  error: undefined,
25
16
  });
17
+ /**
18
+ * The cache entry for a tab, created on first use. The tab set is config-driven,
19
+ * so a status this build's union does not know still needs somewhere to cache.
20
+ */
21
+ export const invoiceTabStateFor = (invoiceByTab, tabId) => {
22
+ const existing = invoiceByTab[tabId];
23
+ if (existing != null) {
24
+ return existing;
25
+ }
26
+ const created = makeInvoiceTabState();
27
+ invoiceByTab[tabId] = created;
28
+ return created;
29
+ };
26
30
  export const makeInvoiceByTab = () => ALL_INVOICING_INVOICE_TAB_IDS.reduce((acc, tabId) => {
27
31
  acc[tabId] = makeInvoiceTabState();
28
32
  return acc;
@@ -2,13 +2,17 @@ import { getInvoicingPlanById } from '../../../entity/invoicing/plan/planSelecto
2
2
  import { getInvoicingConfigView } from '../invoicingConfigView/invoicingConfigViewSelector';
3
3
  export const getInvoicingCatalogItemDetail = (state, _kind, id) => {
4
4
  const { error, fetchState } = state.invoicingCatalogItemDetailViewState;
5
- const { catalogItemLifecycleActionOptions, catalogProductLifecycleActionOptions, catalogTierTypeOptionsByPricingModel, fetchState: configFetchState, } = getInvoicingConfigView(state);
5
+ const { billingPeriodUnitOptions, catalogItemLifecycleActionOptions, catalogItemTypeOptions, catalogProductLifecycleActionOptions, catalogTierTypeOptionsByPricingModel, catalogTrialEndActionOptions, catalogTrialUnitOptions, fetchState: configFetchState, } = getInvoicingConfigView(state);
6
6
  return {
7
7
  catalogItemLifecycleActionOptions,
8
8
  catalogProductLifecycleActionOptions,
9
9
  catalogTierTypeOptionsByPricingModel,
10
10
  configFetchState,
11
+ periodUnitOptions: billingPeriodUnitOptions,
11
12
  plan: getInvoicingPlanById(state.invoicingPlanState, id),
13
+ trialEndActionOptions: catalogTrialEndActionOptions,
14
+ trialUnitOptions: catalogTrialUnitOptions,
15
+ typeOptions: catalogItemTypeOptions,
12
16
  fetchState,
13
17
  error,
14
18
  version: 1.0,
@@ -15,7 +15,8 @@ export const getInvoicingCatalogPrices = (items) => items.flatMap((item) => (ite
15
15
  * denormalized arrays keep referential identity. The base `fetchState`/`error`
16
16
  * track the plan list; the product list has its own `productFetchState`.
17
17
  */
18
- export const getInvoicingCatalogListView = createSelector((state) => state.invoicingPlanState, (state) => state.invoicingCatalogListViewState, (planState, listViewState) => ({
18
+ export const getInvoicingCatalogListView = createSelector((state) => state.invoicingPlanState, (state) => state.invoicingCatalogListViewState, (state) => state.invoicingConfigViewState.catalogItemLifecycleActionOptions, (planState, listViewState, catalogItemLifecycleActionOptions) => ({
19
+ catalogItemLifecycleActionOptions,
19
20
  plans: getInvoicingPlansByIds(planState, listViewState.planIds),
20
21
  productColumns: INVOICING_PRODUCT_COLUMNS,
21
22
  products: getInvoicingPlansByIds(planState, listViewState.productIds),
@@ -23,6 +23,19 @@ export const initialState = {
23
23
  netTermDayOptions: [],
24
24
  paymentGatewayOptions: [],
25
25
  taxabilityOptions: [],
26
+ cardStatusOptions: [],
27
+ catalogItemStatusOptions: [],
28
+ catalogItemTypeOptions: [],
29
+ creditNoteReasonCodeOptions: [],
30
+ creditNoteStatusOptions: [],
31
+ creditNoteTypeOptions: [],
32
+ customerStatusOptions: [],
33
+ discountStatusOptions: [],
34
+ dunningCaseStatusOptions: [],
35
+ invoiceStatusOptions: [],
36
+ subscriptionStatusOptions: [],
37
+ transactionStatusOptions: [],
38
+ transactionTypeOptions: [],
26
39
  enableFetchState: { fetchState: 'Not-Started', error: undefined },
27
40
  subscriptionLifecycleActionOptions: [],
28
41
  subscriptionCommercialEditAllowedFromStatuses: [],
@@ -76,6 +89,19 @@ export function mapInvoicingConfigPayloadToState(payload) {
76
89
  netTermDayOptions: (payload.net_term_day_options ?? []).map(toInvoicingNamedOption),
77
90
  paymentGatewayOptions: (payload.payment_gateway_options ?? []).map(toInvoicingNamedOption),
78
91
  taxabilityOptions: (payload.taxability_options ?? []).map(toInvoicingNamedOption),
92
+ cardStatusOptions: (payload.card_status_options ?? []).map(toInvoicingNamedOption),
93
+ catalogItemStatusOptions: (payload.catalog_item_status_options ?? []).map(toInvoicingNamedOption),
94
+ catalogItemTypeOptions: (payload.catalog_item_type_options ?? []).map(toInvoicingNamedOption),
95
+ creditNoteReasonCodeOptions: (payload.credit_note_reason_code_options ?? []).map(toInvoicingNamedOption),
96
+ creditNoteStatusOptions: (payload.credit_note_status_options ?? []).map(toInvoicingNamedOption),
97
+ creditNoteTypeOptions: (payload.credit_note_type_options ?? []).map(toInvoicingNamedOption),
98
+ customerStatusOptions: (payload.customer_status_options ?? []).map(toInvoicingNamedOption),
99
+ discountStatusOptions: (payload.discount_status_options ?? []).map(toInvoicingNamedOption),
100
+ dunningCaseStatusOptions: (payload.dunning_case_status_options ?? []).map(toInvoicingNamedOption),
101
+ invoiceStatusOptions: (payload.invoice_status_options ?? []).map(toInvoicingNamedOption),
102
+ subscriptionStatusOptions: (payload.subscription_status_options ?? []).map(toInvoicingNamedOption),
103
+ transactionStatusOptions: (payload.transaction_status_options ?? []).map(toInvoicingNamedOption),
104
+ transactionTypeOptions: (payload.transaction_type_options ?? []).map(toInvoicingNamedOption),
79
105
  createdAt: payload.created_at ?? undefined,
80
106
  updatedAt: payload.updated_at ?? undefined,
81
107
  dunningScheduleActionOptions: payload.dunning_schedule_action_options.map((option) => ({
@@ -8,3 +8,12 @@ export const getInvoicingConfigView = (state) => {
8
8
  status: deriveInvoicingOnboardingStatus(configState.isInvoicingTosAccepted, configState.isInvoicingEnabled),
9
9
  };
10
10
  };
11
+ export const getInvoicingOnboardingGateView = (state) => {
12
+ const { acceptTermsFetchState, enableFetchState, fetchState } = state.invoicingConfigViewState;
13
+ return {
14
+ acceptTermsState: acceptTermsFetchState,
15
+ enableState: enableFetchState,
16
+ fetchState,
17
+ status: deriveInvoicingOnboardingStatus(state.invoicingConfigViewState.isInvoicingTosAccepted, state.invoicingConfigViewState.isInvoicingEnabled),
18
+ };
19
+ };
@@ -1,12 +1,13 @@
1
1
  import { createSelector } from '@reduxjs/toolkit';
2
2
  import { getInvoicingCreditNotesByIds } from '../../../entity/invoicing/creditNote/creditNoteSelector';
3
- import { INVOICING_CREDIT_NOTE_COLUMNS, INVOICING_CREDIT_NOTE_TABS, } from './invoicingCreditNoteListViewState';
3
+ import { invoicingTabIdsFromConfig } from '../invoicingTabHelper';
4
+ import { ALL_INVOICING_CREDIT_NOTE_TAB_IDS, INVOICING_CREDIT_NOTE_COLUMNS, } from './invoicingCreditNoteListViewState';
4
5
  /**
5
6
  * Memoized so the denormalized `creditNotes` array (and the wrapping view
6
7
  * object) keep referential identity until the credit-note entity slice or the
7
8
  * list-view slice actually change.
8
9
  */
9
- export const getInvoicingCreditNoteListView = createSelector((state) => state.invoicingCreditNoteState, (state) => state.invoicingCreditNoteListViewState, (creditNoteState, listViewState) => {
10
+ export const getInvoicingCreditNoteListView = createSelector((state) => state.invoicingCreditNoteState, (state) => state.invoicingCreditNoteListViewState, (state) => state.invoicingConfigViewState.creditNoteStatusOptions, (state) => state.invoicingConfigViewState.creditNoteTypeOptions, (creditNoteState, listViewState, statusOptions, typeOptions) => {
10
11
  const { activeTab } = listViewState;
11
12
  const allCreditNotes = getInvoicingCreditNotesByIds(creditNoteState, listViewState.ids);
12
13
  // The list API ignores `status`, so it returns every status; filter to the
@@ -22,7 +23,12 @@ export const getInvoicingCreditNoteListView = createSelector((state) => state.in
22
23
  countsFetchState: listViewState.countsFetchState,
23
24
  filters: listViewState.filters,
24
25
  nextCursor: listViewState.nextCursor,
25
- tabIds: INVOICING_CREDIT_NOTE_TABS,
26
+ statusOptions,
27
+ typeOptions,
28
+ tabIds: invoicingTabIdsFromConfig({
29
+ allTabIds: ALL_INVOICING_CREDIT_NOTE_TAB_IDS,
30
+ statusOptions,
31
+ }),
26
32
  fetchState: listViewState.fetchState,
27
33
  error: listViewState.error,
28
34
  sort: listViewState.sort,
@@ -28,12 +28,3 @@ export const ALL_INVOICING_CREDIT_NOTE_TAB_IDS = [
28
28
  'voided',
29
29
  ];
30
30
  export const toInvoicingCreditNoteTabId = (v) => stringToUnion(v, ALL_INVOICING_CREDIT_NOTE_TAB_IDS);
31
- /**
32
- * Ordered filter-tab id set owned by CES so the credit-notes-list tabs live next
33
- * to the model rather than being hardcoded in web-components. Tab labels and the
34
- * per-tab count lookup stay in the view layer; this only carries the id set and
35
- * display order.
36
- */
37
- export const INVOICING_CREDIT_NOTE_TABS = [
38
- ...ALL_INVOICING_CREDIT_NOTE_TAB_IDS,
39
- ];
@@ -34,8 +34,8 @@ export const fetchInvoicingCustomerDetailPageEpic = (actions$, state$) => action
34
34
  const invoiceListIsForCustomer = invoiceListState.filters.customerId === id;
35
35
  if (!invoiceListIsForCustomer ||
36
36
  shouldFetch({
37
- fetchState: invoiceListTabState.fetchState,
38
- error: invoiceListTabState.error,
37
+ fetchState: invoiceListTabState?.fetchState ?? 'Not-Started',
38
+ error: invoiceListTabState?.error,
39
39
  }, cacheOverride)) {
40
40
  pageActions.push(fetchInvoiceList({ filters: { customerId: id } }));
41
41
  }
@@ -10,7 +10,7 @@ export const getInvoicingCustomerDetail = (state, invoicingCustomerID) => {
10
10
  const settings = getInvoicingSettings(state);
11
11
  return {
12
12
  invoicingCustomer: getInvoicingCustomerById(invoicingCustomerState, invoicingCustomerID),
13
- invoices: getInvoicesByIds(state.invoicingInvoiceState, state.invoicingInvoiceListState.invoiceByTab[state.invoicingInvoiceListState.activeTab].invoiceIds),
13
+ invoices: getInvoicesByIds(state.invoicingInvoiceState, state.invoicingInvoiceListState.invoiceByTab[state.invoicingInvoiceListState.activeTab]?.invoiceIds ?? []),
14
14
  setupIntent: getInvoicingSetupIntent(state),
15
15
  setupIntentFetchState: getInvoicingSetupIntentFetchState(state),
16
16
  plaidLinkToken: getInvoicingPlaidLinkToken(state),
@@ -1,13 +1,14 @@
1
1
  import { createSelector } from '@reduxjs/toolkit';
2
2
  import { getInvoicingCustomersByIds } from '../../../entity/invoicing/invoicingCustomer/invoicingCustomerSelector';
3
- import { INVOICING_CUSTOMER_COLUMNS, INVOICING_CUSTOMER_TABS, } from './invoicingCustomerListViewState';
3
+ import { invoicingTabIdsFromConfig } from '../invoicingTabHelper';
4
+ import { ALL_INVOICING_CUSTOMER_TAB_IDS, INVOICING_CUSTOMER_COLUMNS, } from './invoicingCustomerListViewState';
4
5
  /**
5
6
  * Memoized so the denormalized `customers` array (and the wrapping view object)
6
7
  * keep referential identity until the customer entity slice or the list-view
7
8
  * slice actually change. Without this, the connected list page re-rendered on
8
9
  * every unrelated redux action because a fresh array was built each call.
9
10
  */
10
- export const getInvoicingCustomerListView = createSelector((state) => state.invoicingCustomerState, (state) => state.invoicingCustomerListViewState, (customerState, listViewState) => {
11
+ export const getInvoicingCustomerListView = createSelector((state) => state.invoicingCustomerState, (state) => state.invoicingCustomerListViewState, (state) => state.invoicingConfigViewState.customerStatusOptions, (customerState, listViewState, statusOptions) => {
11
12
  const { activeTab } = listViewState;
12
13
  const allCustomers = getInvoicingCustomersByIds(customerState, listViewState.ids);
13
14
  // The list API ignores `status`, so it returns every status; filter to the
@@ -28,7 +29,12 @@ export const getInvoicingCustomerListView = createSelector((state) => state.invo
28
29
  filters: listViewState.filters,
29
30
  count: listViewState.counts,
30
31
  countsFetchState: listViewState.countsFetchState,
31
- tabIds: INVOICING_CUSTOMER_TABS,
32
+ statusOptions,
33
+ tabIds: invoicingTabIdsFromConfig({
34
+ allTabIds: ALL_INVOICING_CUSTOMER_TAB_IDS,
35
+ localTabIds: ['trial', 'at_risk'],
36
+ statusOptions,
37
+ }),
32
38
  sort: listViewState.sort,
33
39
  };
34
40
  });
@@ -23,12 +23,3 @@ export const ALL_INVOICING_CUSTOMER_TAB_IDS = [
23
23
  'at_risk',
24
24
  ];
25
25
  export const toInvoicingCustomerTabId = (v) => stringToUnion(v, ALL_INVOICING_CUSTOMER_TAB_IDS);
26
- /**
27
- * Ordered filter-tab id set owned by CES so the customer-list tabs live next to
28
- * the model rather than being hardcoded in web-components. Tab labels and the
29
- * per-tab count lookup stay in the view layer; this only carries the id set and
30
- * display order.
31
- */
32
- export const INVOICING_CUSTOMER_TABS = [
33
- ...ALL_INVOICING_CUSTOMER_TAB_IDS,
34
- ];
@@ -1,12 +1,13 @@
1
1
  import { createSelector } from '@reduxjs/toolkit';
2
2
  import { getInvoicingDunningCasesByIds } from '../../../entity/invoicing/dunningCase/dunningCaseSelector';
3
- import { INVOICING_DUNNING_CASE_COLUMNS, INVOICING_DUNNING_CASE_TABS, } from './invoicingDunningCaseListViewState';
3
+ import { invoicingTabIdsFromConfig } from '../invoicingTabHelper';
4
+ import { ALL_INVOICING_DUNNING_CASE_TAB_IDS, INVOICING_DUNNING_CASE_COLUMNS, } from './invoicingDunningCaseListViewState';
4
5
  /**
5
6
  * Memoized so the denormalized `dunningCases` array (and the wrapping view
6
7
  * object) keep referential identity until the dunning-case entity slice or the
7
8
  * list-view slice actually change.
8
9
  */
9
- export const getInvoicingDunningCaseListView = createSelector((state) => state.invoicingDunningCaseState, (state) => state.invoicingDunningCaseListViewState, (dunningCaseState, listViewState) => {
10
+ export const getInvoicingDunningCaseListView = createSelector((state) => state.invoicingDunningCaseState, (state) => state.invoicingDunningCaseListViewState, (state) => state.invoicingConfigViewState.dunningCaseStatusOptions, (dunningCaseState, listViewState, statusOptions) => {
10
11
  const { activeTab } = listViewState;
11
12
  const allDunningCases = getInvoicingDunningCasesByIds(dunningCaseState, listViewState.ids);
12
13
  // The list API ignores `status`, so it returns every status; filter to the
@@ -24,7 +25,11 @@ export const getInvoicingDunningCaseListView = createSelector((state) => state.i
24
25
  nextCursor: listViewState.nextCursor,
25
26
  fetchState: listViewState.fetchState,
26
27
  error: listViewState.error,
27
- tabIds: INVOICING_DUNNING_CASE_TABS,
28
+ statusOptions,
29
+ tabIds: invoicingTabIdsFromConfig({
30
+ allTabIds: ALL_INVOICING_DUNNING_CASE_TAB_IDS,
31
+ statusOptions,
32
+ }),
28
33
  sort: listViewState.sort,
29
34
  version: 1.0,
30
35
  };
@@ -25,16 +25,8 @@ export const ALL_INVOICING_DUNNING_CASE_TAB_IDS = [
25
25
  'Retrying',
26
26
  'Scheduled',
27
27
  'Manual Review',
28
+ 'Paused',
28
29
  'Resolved',
29
30
  'Cancelled',
30
31
  ];
31
32
  export const toInvoicingDunningCaseTabId = (v) => stringToUnion(v, ALL_INVOICING_DUNNING_CASE_TAB_IDS);
32
- /**
33
- * Ordered filter-tab id set owned by CES so the dunning-cases-list tabs live
34
- * next to the model rather than being hardcoded in web-components. Tab labels
35
- * and the per-tab count lookup stay in the view layer; this only carries the id
36
- * set and display order.
37
- */
38
- export const INVOICING_DUNNING_CASE_TABS = [
39
- ...ALL_INVOICING_DUNNING_CASE_TAB_IDS,
40
- ];
@@ -1,6 +1,7 @@
1
1
  import { createSelector } from '@reduxjs/toolkit';
2
2
  import { getInvoicingSubscriptionsByIds } from '../../../entity/invoicing/invoicingSubscription/invoicingSubscriptionSelector';
3
- import { INVOICING_SUBSCRIPTION_COLUMNS, INVOICING_SUBSCRIPTION_TABS, } from './invoicingSubscriptionListViewState';
3
+ import { invoicingTabIdsFromConfig } from '../invoicingTabHelper';
4
+ import { ALL_INVOICING_SUBSCRIPTION_TAB_IDS, INVOICING_SUBSCRIPTION_COLUMNS, } from './invoicingSubscriptionListViewState';
4
5
  /**
5
6
  * Memoized so the denormalized `subscriptions` array (and the wrapping view
6
7
  * object) keep referential identity until the subscription entity slice or the
@@ -8,7 +9,7 @@ import { INVOICING_SUBSCRIPTION_COLUMNS, INVOICING_SUBSCRIPTION_TABS, } from './
8
9
  * re-rendered on every unrelated redux action because a fresh array was built
9
10
  * each call.
10
11
  */
11
- export const getInvoicingSubscriptionListView = createSelector((state) => state.invoicingSubscriptionState, (state) => state.invoicingSubscriptionListViewState, (subscriptionState, listViewState) => {
12
+ export const getInvoicingSubscriptionListView = createSelector((state) => state.invoicingSubscriptionState, (state) => state.invoicingSubscriptionListViewState, (state) => state.invoicingConfigViewState.subscriptionStatusOptions, (state) => state.invoicingConfigViewState, (subscriptionState, listViewState, statusOptions, configState) => {
12
13
  const { activeTab } = listViewState;
13
14
  const allSubscriptions = getInvoicingSubscriptionsByIds(subscriptionState, listViewState.ids);
14
15
  // The list API ignores `status`, so it returns every status; filter to the
@@ -26,7 +27,14 @@ export const getInvoicingSubscriptionListView = createSelector((state) => state.
26
27
  filters: listViewState.filters,
27
28
  count: listViewState.counts,
28
29
  countsFetchState: listViewState.countsFetchState,
29
- tabIds: INVOICING_SUBSCRIPTION_TABS,
30
+ subscriptionCommercialEditAllowedFromStatuses: configState.subscriptionCommercialEditAllowedFromStatuses,
31
+ subscriptionLifecycleActionOptions: configState.subscriptionLifecycleActionOptions,
32
+ subscriptionMetadataEditAllowedFromStatuses: configState.subscriptionMetadataEditAllowedFromStatuses,
33
+ statusOptions,
34
+ tabIds: invoicingTabIdsFromConfig({
35
+ allTabIds: ALL_INVOICING_SUBSCRIPTION_TAB_IDS,
36
+ statusOptions,
37
+ }),
30
38
  sort: listViewState.sort,
31
39
  };
32
40
  });
@@ -32,12 +32,3 @@ export const ALL_INVOICING_SUBSCRIPTION_TAB_IDS = [
32
32
  'cancelled',
33
33
  ];
34
34
  export const toInvoicingSubscriptionTabId = (v) => stringToUnion(v, ALL_INVOICING_SUBSCRIPTION_TAB_IDS);
35
- /**
36
- * Ordered filter-tab id set owned by CES so the subscription-list tabs live next
37
- * to the model rather than being hardcoded in web-components. Tab labels and the
38
- * per-tab count lookup stay in the view layer; this only carries the id set and
39
- * display order.
40
- */
41
- export const INVOICING_SUBSCRIPTION_TABS = [
42
- ...ALL_INVOICING_SUBSCRIPTION_TAB_IDS,
43
- ];
@@ -0,0 +1,27 @@
1
+ import { invoicingOptionCodes, } from '../../entity/invoicing/invoicingCommonPayload';
2
+ /** Every list's leading "show everything" tab; FE-owned, never a backend status. */
3
+ const ALL_TAB_ID = 'all';
4
+ /**
5
+ * Build a list's filter-tab ids from the config option list rather than a
6
+ * hardcoded array, so adding a status backend-side widens the tabs.
7
+ *
8
+ * `all` always leads and is never taken from config. Codes config sends that the
9
+ * tab-id union does not know still render: the union types state, the per-tab
10
+ * cache and URL parsing, but it is a frontend snapshot of a backend enum, and
11
+ * dropping unknown codes would silently hide a status the backend already has —
12
+ * the exact drift this is meant to end.
13
+ */
14
+ export function invoicingTabIdsFromConfig({ allTabIds, localTabIds = [], statusOptions, }) {
15
+ // Nullish as well as empty: a partial test state or a draft persisted before
16
+ // these lists existed can leave it undefined.
17
+ if (statusOptions == null || statusOptions.length === 0) {
18
+ return [...allTabIds];
19
+ }
20
+ const configTabIds = invoicingOptionCodes(statusOptions)
21
+ .filter((code) => code !== ALL_TAB_ID)
22
+ // A config code outside the union is still a valid tab at runtime — the
23
+ // caches key off the string — so it is admitted rather than dropped.
24
+ .map((code) => code);
25
+ const ordered = [ALL_TAB_ID, ...configTabIds, ...localTabIds];
26
+ return Array.from(new Set(ordered));
27
+ }
@@ -1,6 +1,7 @@
1
1
  import { concat, from } from 'rxjs';
2
2
  import { filter, mergeMap } from 'rxjs/operators';
3
3
  import { fetchInvoicingTransactionList, fetchInvoicingTransactionListPage, } from '../invoicingTransactionListViewReducer';
4
+ import { makeInvoicingTransactionTabState } from '../invoicingTransactionListViewState';
4
5
  /**
5
6
  * Fetch unless the endpoint is already loaded or in flight — or always when the
6
7
  * caller forces a refresh via `cacheOverride` (e.g. on a filter/sort change).
@@ -16,7 +17,7 @@ const shouldFetch = (fetchStateAndError, cacheOverride) => cacheOverride ||
16
17
  export const fetchInvoicingTransactionListPageEpic = (actions$, state$) => actions$.pipe(filter(fetchInvoicingTransactionListPage.match), mergeMap((action) => {
17
18
  const { cacheOverride, filters, sort } = action.payload;
18
19
  const state = state$.value.invoicingTransactionListViewState;
19
- const activeTabState = state.byTab[state.activeTab];
20
+ const activeTabState = state.byTab[state.activeTab] ?? makeInvoicingTransactionTabState();
20
21
  const pageActions = [];
21
22
  if (shouldFetch({ fetchState: activeTabState.fetchState, error: activeTabState.error }, cacheOverride)) {
22
23
  pageActions.push(fetchInvoicingTransactionList({ filters, sort }));
@@ -1,6 +1,6 @@
1
1
  import { createSlice } from '@reduxjs/toolkit';
2
2
  import { clearAll } from '../../../rootActions';
3
- import { makeInvoicingTransactionIdsByTab, } from './invoicingTransactionListViewState';
3
+ import { invoicingTransactionTabStateFor, makeInvoicingTransactionIdsByTab, } from './invoicingTransactionListViewState';
4
4
  /**
5
5
  * Whether two transaction-list queries share the same base (tab-independent)
6
6
  * query. When the base query changes (search / customer / status / sort) every
@@ -22,7 +22,7 @@ const invoicingTransactionListView = createSlice({
22
22
  reducers: {
23
23
  fetchInvoicingTransactionList: {
24
24
  reducer(draft, action) {
25
- const tab = draft.byTab[draft.activeTab];
25
+ const tab = invoicingTransactionTabStateFor(draft.byTab, draft.activeTab);
26
26
  if (!action.payload.keepExistingListItems) {
27
27
  tab.ids = [];
28
28
  tab.nextCursor = null;
@@ -65,7 +65,7 @@ const invoicingTransactionListView = createSlice({
65
65
  },
66
66
  },
67
67
  updateInvoicingTransactionList(draft, action) {
68
- const tab = draft.byTab[draft.activeTab];
68
+ const tab = invoicingTransactionTabStateFor(draft.byTab, draft.activeTab);
69
69
  tab.ids = action.payload.keepExistingListItems
70
70
  ? [...new Set([...tab.ids, ...action.payload.ids])]
71
71
  : action.payload.ids;
@@ -74,7 +74,7 @@ const invoicingTransactionListView = createSlice({
74
74
  tab.error = undefined;
75
75
  },
76
76
  updateInvoicingTransactionListFailure(draft, action) {
77
- const tab = draft.byTab[draft.activeTab];
77
+ const tab = invoicingTransactionTabStateFor(draft.byTab, draft.activeTab);
78
78
  tab.fetchState = 'Error';
79
79
  tab.error = action.payload;
80
80
  },
@@ -94,7 +94,7 @@ const invoicingTransactionListView = createSlice({
94
94
  addInvoicingTransactionToList(draft, action) {
95
95
  const id = action.payload;
96
96
  const prependTo = (tabId) => {
97
- const tab = draft.byTab[tabId];
97
+ const tab = invoicingTransactionTabStateFor(draft.byTab, tabId);
98
98
  if (!tab.ids.includes(id)) {
99
99
  tab.ids = [id, ...tab.ids];
100
100
  }
@@ -1,13 +1,15 @@
1
1
  import { createSelector } from '@reduxjs/toolkit';
2
2
  import { getInvoicingTransactionsByIds } from '../../../entity/invoicing/invoicingTransaction/invoicingTransactionSelector';
3
- import { INVOICING_TRANSACTION_COLUMNS, INVOICING_TRANSACTION_TABS, } from './invoicingTransactionListViewState';
3
+ import { invoicingTabIdsFromConfig } from '../invoicingTabHelper';
4
+ import { ALL_INVOICING_TRANSACTION_TAB_IDS, INVOICING_TRANSACTION_COLUMNS, makeInvoicingTransactionTabState, } from './invoicingTransactionListViewState';
4
5
  /**
5
6
  * Memoized so the denormalized `transactions` array (and the wrapping view
6
7
  * object) keep referential identity until the transaction entity slice or the
7
8
  * list-view slice actually change.
8
9
  */
9
- export const getInvoicingTransactionListView = createSelector((state) => state.invoicingTransactionState, (state) => state.invoicingTransactionListViewState, (transactionState, listViewState) => {
10
- const activeTabState = listViewState.byTab[listViewState.activeTab];
10
+ export const getInvoicingTransactionListView = createSelector((state) => state.invoicingTransactionState, (state) => state.invoicingTransactionListViewState, (state) => state.invoicingConfigViewState.transactionTypeOptions, (state) => state.invoicingConfigViewState.transactionStatusOptions, (transactionState, listViewState, typeOptions, statusOptions) => {
11
+ const activeTabState = listViewState.byTab[listViewState.activeTab] ??
12
+ makeInvoicingTransactionTabState();
11
13
  return {
12
14
  activeTab: listViewState.activeTab,
13
15
  columns: INVOICING_TRANSACTION_COLUMNS,
@@ -16,7 +18,13 @@ export const getInvoicingTransactionListView = createSelector((state) => state.i
16
18
  nextCursor: activeTabState.nextCursor,
17
19
  fetchState: activeTabState.fetchState,
18
20
  error: activeTabState.error,
19
- tabIds: INVOICING_TRANSACTION_TABS,
21
+ statusOptions,
22
+ typeOptions,
23
+ // Tabs are types on this list, so the type list drives membership.
24
+ tabIds: invoicingTabIdsFromConfig({
25
+ allTabIds: ALL_INVOICING_TRANSACTION_TAB_IDS,
26
+ statusOptions: typeOptions,
27
+ }),
20
28
  sort: listViewState.sort,
21
29
  version: 1.0,
22
30
  };