@zeniai/client-epic-state 5.2.70 → 5.2.72

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 (159) hide show
  1. package/lib/entity/actualMatching/actualMatchingReducer.d.ts +3 -1
  2. package/lib/entity/actualMatching/actualMatchingReducer.js +11 -2
  3. package/lib/entity/aiCfo/aiCfoState.d.ts +2 -2
  4. package/lib/entity/billPay/billTransaction/billTransactionState.d.ts +2 -2
  5. package/lib/entity/chargeCardTransaction/chargeCardTransaction.d.ts +1 -1
  6. package/lib/entity/depositAccountTransaction/depositAccountTransaction.d.ts +1 -1
  7. package/lib/entity/invoicing/{product/productPayload.d.ts → invoicingCatalog/invoicingCatalogPayload.d.ts} +44 -6
  8. package/lib/entity/invoicing/{product/productPayload.js → invoicingCatalog/invoicingCatalogPayload.js} +50 -6
  9. package/lib/entity/invoicing/invoicingCatalog/invoicingCatalogReducer.d.ts +12 -0
  10. package/lib/entity/invoicing/{plan/planReducer.js → invoicingCatalog/invoicingCatalogReducer.js} +20 -21
  11. package/lib/entity/invoicing/{plan/planSelector.d.ts → invoicingCatalog/invoicingCatalogSelector.d.ts} +5 -6
  12. package/lib/entity/invoicing/{plan/planSelector.js → invoicingCatalog/invoicingCatalogSelector.js} +12 -12
  13. package/lib/entity/invoicing/{product/productState.d.ts → invoicingCatalog/invoicingCatalogState.d.ts} +42 -6
  14. package/lib/entity/jeSchedules/jeSchedulesPayload.d.ts +2 -0
  15. package/lib/entity/jeSchedules/jeSchedulesPayload.js +2 -0
  16. package/lib/entity/jeSchedules/jeSchedulesState.d.ts +2 -0
  17. package/lib/entity/jeSchedules/jeSchedulesTypes.d.ts +4 -4
  18. package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
  19. package/lib/entity/snackbar/snackbarTypes.js +3 -0
  20. package/lib/epic.d.ts +4 -2
  21. package/lib/epic.js +5 -1
  22. package/lib/esm/entity/actualMatching/actualMatchingReducer.js +9 -1
  23. package/lib/esm/entity/invoicing/{product/productPayload.js → invoicingCatalog/invoicingCatalogPayload.js} +46 -3
  24. package/lib/esm/entity/invoicing/{plan/planReducer.js → invoicingCatalog/invoicingCatalogReducer.js} +19 -20
  25. package/lib/esm/entity/invoicing/{plan/planSelector.js → invoicingCatalog/invoicingCatalogSelector.js} +8 -8
  26. package/lib/esm/entity/jeSchedules/jeSchedulesPayload.js +2 -0
  27. package/lib/esm/entity/snackbar/snackbarTypes.js +3 -0
  28. package/lib/esm/epic.js +5 -1
  29. package/lib/esm/index.js +9 -12
  30. package/lib/esm/reducer.js +4 -7
  31. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingMatchEpic.js +4 -2
  32. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/ignoreActualMatchingSuggestionEpic.js +50 -0
  33. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/matchAndReverseAccrualsEpic.js +26 -13
  34. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/searchMatchTransactionsEpic.js +2 -23
  35. package/lib/esm/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +89 -38
  36. package/lib/esm/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +68 -62
  37. package/lib/esm/view/expenseAutomationView/types/actualMatchingViewState.js +15 -0
  38. package/lib/esm/view/invoicing/createInvoice/createInvoiceEpic.js +9 -1
  39. package/lib/esm/view/invoicing/createInvoice/createInvoiceSelector.js +1 -1
  40. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +5 -3
  41. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/fetchInvoicingCatalogItemDetailEpic.js +2 -2
  42. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/runInvoicingCatalogPlanActionEpic.js +2 -2
  43. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/runInvoicingCatalogProductActionEpic.js +4 -4
  44. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +3 -7
  45. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/seedInvoicingCatalogItemFormDraftEpic.js +2 -2
  46. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.js +5 -1
  47. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +5 -1
  48. package/lib/esm/view/invoicing/invoiceList/invoiceListReducer.js +8 -1
  49. package/lib/esm/view/invoicing/invoicingCatalogListView/epics/fetchInvoicingCatalogPlanListEpic.js +2 -2
  50. package/lib/esm/view/invoicing/invoicingCatalogListView/epics/fetchInvoicingCatalogProductListEpic.js +2 -2
  51. package/lib/esm/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewSelector.js +55 -22
  52. package/lib/esm/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewState.js +1 -20
  53. package/lib/esm/view/invoicing/settingsView/settingsViewSelector.js +18 -9
  54. package/lib/esm/view/invoicing/settingsView/submitInvoicingBrandingFormEpic.js +11 -2
  55. package/lib/esm/view/onboardingView/customerView/epic/customerDetails/updateOnboardingCustomerViewEpic.js +31 -11
  56. package/lib/esm/view/onboardingView/customerView/onboardingCustomerViewReducer.js +2 -1
  57. package/lib/esm/view/spendManagement/billPay/editBillView/editBillViewSelector.js +4 -30
  58. package/lib/esm/view/spendManagement/commonSetup/epic/setup/resetKycKybEpic.js +37 -0
  59. package/lib/esm/view/spendManagement/commonSetup/setupViewReducer.js +21 -1
  60. package/lib/esm/view/spendManagement/commonSetup/setupViewSelector.js +2 -1
  61. package/lib/index.d.ts +15 -19
  62. package/lib/index.js +65 -63
  63. package/lib/reducer.d.ts +1 -2
  64. package/lib/reducer.js +4 -7
  65. package/lib/rootStateTypes.d.ts +3 -5
  66. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingMatchEpic.d.ts +2 -2
  67. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingMatchEpic.js +3 -1
  68. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/ignoreActualMatchingSuggestionEpic.d.ts +10 -0
  69. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/ignoreActualMatchingSuggestionEpic.js +54 -0
  70. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/matchAndReverseAccrualsEpic.js +24 -11
  71. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/searchMatchTransactionsEpic.js +1 -22
  72. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
  73. package/lib/view/expenseAutomationView/payload/actualMatchingPayload.d.ts +12 -1
  74. package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.d.ts +19 -8
  75. package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +90 -39
  76. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.d.ts +2 -2
  77. package/lib/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.d.ts +24 -10
  78. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.d.ts +7 -4
  79. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +71 -62
  80. package/lib/view/expenseAutomationView/types/actualMatchingViewState.d.ts +19 -4
  81. package/lib/view/expenseAutomationView/types/actualMatchingViewState.js +18 -1
  82. package/lib/view/invoicing/createInvoice/createInvoiceEpic.d.ts +3 -2
  83. package/lib/view/invoicing/createInvoice/createInvoiceEpic.js +8 -0
  84. package/lib/view/invoicing/createInvoice/createInvoiceSelector.d.ts +2 -2
  85. package/lib/view/invoicing/createInvoice/createInvoiceSelector.js +2 -2
  86. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.d.ts +8 -9
  87. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.d.ts +2 -2
  88. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +5 -3
  89. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/fetchInvoicingCatalogItemDetailEpic.d.ts +2 -2
  90. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/fetchInvoicingCatalogItemDetailEpic.js +2 -2
  91. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/invoicingCatalogItemFormHelper.d.ts +3 -4
  92. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/runInvoicingCatalogPlanActionEpic.d.ts +2 -2
  93. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/runInvoicingCatalogPlanActionEpic.js +2 -2
  94. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/runInvoicingCatalogProductActionEpic.d.ts +2 -3
  95. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/runInvoicingCatalogProductActionEpic.js +4 -4
  96. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.d.ts +2 -3
  97. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +3 -7
  98. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/seedInvoicingCatalogItemFormDraftEpic.js +2 -2
  99. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.d.ts +3 -1
  100. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.js +6 -2
  101. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.d.ts +2 -2
  102. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.d.ts +3 -1
  103. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +5 -1
  104. package/lib/view/invoicing/invoiceList/invoiceListReducer.d.ts +3 -1
  105. package/lib/view/invoicing/invoiceList/invoiceListReducer.js +9 -2
  106. package/lib/view/invoicing/invoicingCatalogListView/epics/fetchInvoicingCatalogListPageEpic.d.ts +2 -2
  107. package/lib/view/invoicing/invoicingCatalogListView/epics/fetchInvoicingCatalogPlanListEpic.d.ts +2 -2
  108. package/lib/view/invoicing/invoicingCatalogListView/epics/fetchInvoicingCatalogPlanListEpic.js +2 -2
  109. package/lib/view/invoicing/invoicingCatalogListView/epics/fetchInvoicingCatalogProductListEpic.d.ts +2 -2
  110. package/lib/view/invoicing/invoicingCatalogListView/epics/fetchInvoicingCatalogProductListEpic.js +2 -2
  111. package/lib/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewPayload.d.ts +3 -3
  112. package/lib/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewSelector.d.ts +33 -15
  113. package/lib/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewSelector.js +57 -23
  114. package/lib/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewState.d.ts +0 -15
  115. package/lib/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewState.js +0 -22
  116. package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewReducer.d.ts +1 -1
  117. package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewState.d.ts +1 -1
  118. package/lib/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewReducer.d.ts +1 -1
  119. package/lib/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewState.d.ts +1 -1
  120. package/lib/view/invoicing/settingsView/settingsViewSelector.d.ts +12 -3
  121. package/lib/view/invoicing/settingsView/settingsViewSelector.js +17 -8
  122. package/lib/view/invoicing/settingsView/submitInvoicingBrandingFormEpic.js +11 -2
  123. package/lib/view/onboardingView/customerView/epic/customerDetails/updateOnboardingCustomerViewEpic.js +31 -11
  124. package/lib/view/onboardingView/customerView/onboardingCustomerViewReducer.d.ts +3 -2
  125. package/lib/view/onboardingView/customerView/onboardingCustomerViewReducer.js +2 -1
  126. package/lib/view/onboardingView/customerView/onboardingCustomerViewState.d.ts +4 -0
  127. package/lib/view/scheduleView/scheduleListView/scheduleListTypes.d.ts +1 -1
  128. package/lib/view/spendManagement/billPay/billList/billListReducer.d.ts +5 -5
  129. package/lib/view/spendManagement/billPay/billList/billListState.d.ts +1 -1
  130. package/lib/view/spendManagement/billPay/editBillView/editBillViewReducer.d.ts +2 -2
  131. package/lib/view/spendManagement/billPay/editBillView/editBillViewSelector.d.ts +1 -1
  132. package/lib/view/spendManagement/billPay/editBillView/editBillViewSelector.js +4 -30
  133. package/lib/view/spendManagement/commonSetup/epic/setup/resetKycKybEpic.d.ts +8 -0
  134. package/lib/view/spendManagement/commonSetup/epic/setup/resetKycKybEpic.js +41 -0
  135. package/lib/view/spendManagement/commonSetup/setupViewReducer.d.ts +1 -1
  136. package/lib/view/spendManagement/commonSetup/setupViewReducer.js +22 -2
  137. package/lib/view/spendManagement/commonSetup/setupViewSelector.d.ts +1 -0
  138. package/lib/view/spendManagement/commonSetup/setupViewSelector.js +2 -1
  139. package/lib/view/spendManagement/commonSetup/setupViewState.d.ts +1 -0
  140. package/lib/view/spendManagement/commonSetup/types/businessVerification.d.ts +2 -1
  141. package/lib/view/spendManagement/reimbursement/editRemiView/editRemiViewReducer.d.ts +2 -2
  142. package/lib/view/spendManagement/reimbursement/remiListView/remiListReducer.d.ts +5 -5
  143. package/lib/view/spendManagement/reimbursement/remiListView/remiListState.d.ts +2 -2
  144. package/package.json +1 -1
  145. package/lib/entity/invoicing/plan/planPayload.d.ts +0 -40
  146. package/lib/entity/invoicing/plan/planPayload.js +0 -51
  147. package/lib/entity/invoicing/plan/planReducer.d.ts +0 -14
  148. package/lib/entity/invoicing/plan/planState.d.ts +0 -43
  149. package/lib/entity/invoicing/product/productReducer.d.ts +0 -9
  150. package/lib/entity/invoicing/product/productReducer.js +0 -50
  151. package/lib/entity/invoicing/product/productSelector.d.ts +0 -4
  152. package/lib/entity/invoicing/product/productSelector.js +0 -17
  153. package/lib/entity/invoicing/product/productState.js +0 -2
  154. package/lib/esm/entity/invoicing/plan/planPayload.js +0 -47
  155. package/lib/esm/entity/invoicing/product/productReducer.js +0 -43
  156. package/lib/esm/entity/invoicing/product/productSelector.js +0 -10
  157. package/lib/esm/entity/invoicing/product/productState.js +0 -1
  158. /package/lib/entity/invoicing/{plan/planState.js → invoicingCatalog/invoicingCatalogState.js} +0 -0
  159. /package/lib/esm/entity/invoicing/{plan/planState.js → invoicingCatalog/invoicingCatalogState.js} +0 -0
@@ -1,3 +1,4 @@
1
+ import { createSelector } from '@reduxjs/toolkit';
1
2
  import orderBy from 'lodash/orderBy';
2
3
  import { toMonthYearPeriodId, } from '../../../commonStateTypes/timePeriod';
3
4
  import { getActualMatchingMatchById } from '../../../entity/actualMatching/actualMatchingSelector';
@@ -5,7 +6,6 @@ import { generateJEOneTimeAccrualKey } from '../../../entity/jeSchedules/jeSched
5
6
  import { getJEOneTimeAccrualByKey, } from '../../../entity/jeSchedules/jeSchedulesSelector';
6
7
  import { getCurrentTenant } from '../../../entity/tenant/tenantSelector';
7
8
  import { date } from '../../../zeniDayJS';
8
- import { actualMatchingTransactionLineKey, } from '../types/actualMatchingViewState';
9
9
  export function getSelectedMonthYearForCurrentTenant(state) {
10
10
  const tenantId = getCurrentTenant(state)?.tenantId;
11
11
  if (tenantId == null) {
@@ -34,27 +34,6 @@ function toMatchModalAccrual(member) {
34
34
  jeOneTimeAccrualKey: generateJEOneTimeAccrualKey(member.accrualId),
35
35
  };
36
36
  }
37
- function uniqueMatchModalAccruals(pinned, searched) {
38
- const byId = new Map();
39
- pinned.forEach((row) => byId.set(row.accrualId, row));
40
- searched.forEach((row) => {
41
- if (!byId.has(row.accrualId)) {
42
- byId.set(row.accrualId, row);
43
- }
44
- });
45
- return Array.from(byId.values());
46
- }
47
- function uniqueTransactionSearchResults(pinned, searched) {
48
- const byKey = new Map();
49
- pinned.forEach((row) => byKey.set(actualMatchingTransactionLineKey(row.transactionType, row.transactionIntegrationId, row.lineId), row));
50
- searched.forEach((row) => {
51
- const key = actualMatchingTransactionLineKey(row.transactionType, row.transactionIntegrationId, row.lineId);
52
- if (!byKey.has(key)) {
53
- byKey.set(key, row);
54
- }
55
- });
56
- return Array.from(byKey.values());
57
- }
58
37
  export function parseActualMatchingMatchId(matchId) {
59
38
  const separator = matchId.indexOf(':');
60
39
  if (separator <= 0 || separator === matchId.length - 1) {
@@ -115,13 +94,18 @@ function collectGroupedMembers(members) {
115
94
  estimatedAmount: member.estimatedAmount,
116
95
  jeDebitAccountName: member.jeDebitAccountName,
117
96
  memo: member.memo,
97
+ reversalTransactionId: member.reversalTransactionId,
98
+ reversalTransactionIntegrationId: member.reversalTransactionIntegrationId,
118
99
  serviceMonth: member.serviceMonth,
100
+ transactionId: member.transactionId,
101
+ transactionIntegrationId: member.transactionIntegrationId,
119
102
  }));
120
103
  }
121
104
  function collectLeftoverMembers(leftovers) {
122
105
  if (leftovers == null || leftovers.length === 0) {
123
106
  return undefined;
124
107
  }
108
+ // Leftover accruals did not post, so they have no journal-entry ids to open.
125
109
  return leftovers.map((member) => ({
126
110
  accrualId: member.accrualId,
127
111
  estimatedAmount: member.estimatedAmount,
@@ -227,6 +211,7 @@ function emptyKpiSummary() {
227
211
  actualsReceived: { count: 0, expectedCount: 0, vendorsPending: 0 },
228
212
  autoReversed: { count: 0, percent: 0, clearedAmount: 0 },
229
213
  pendingReview: { count: 0, amount: 0, estimatedMinutesToClear: 0 },
214
+ reversed: { count: 0 },
230
215
  variance: { percent: 0, averagePercentPerMatch: 0, netAmount: 0 },
231
216
  };
232
217
  }
@@ -282,6 +267,7 @@ export function buildActualMatchingKpiSummary(rows, displayCurrencyCode) {
282
267
  }
283
268
  const autoReversed = kpiRows.filter((row) => row.kind === 'auto_reversed');
284
269
  const pendingReview = kpiRows.filter((row) => row.kind === 'pending_review');
270
+ const reversed = kpiRows.filter((row) => isReversedMatchKind(row.kind));
285
271
  const receivedCount = kpiRows.length;
286
272
  const rowsWithActual = kpiRows.filter((row) => row.actualAmount?.amount != null);
287
273
  const accruedTotal = rowsWithActual.reduce((sum, row) => sum + (row.accruedAmount?.amount ?? row.estimatedAmount.amount), 0);
@@ -307,6 +293,9 @@ export function buildActualMatchingKpiSummary(rows, displayCurrencyCode) {
307
293
  amount: sumRowAmounts(pendingReview, displayCurrencyCode),
308
294
  estimatedMinutesToClear: pendingReview.length * ESTIMATED_MINUTES_PER_PENDING_MATCH,
309
295
  },
296
+ reversed: {
297
+ count: reversed.length,
298
+ },
310
299
  variance: {
311
300
  percent: accruedTotal === 0
312
301
  ? 0
@@ -319,23 +308,66 @@ export function buildActualMatchingKpiSummary(rows, displayCurrencyCode) {
319
308
  },
320
309
  };
321
310
  }
322
- export function getExpenseAutomationActualMatchingMatchView(state, matchId) {
323
- const view = state.expenseAutomationActualMatchingViewState;
311
+ function toActualMatchingMatchView(match) {
324
312
  return {
325
- match: getActualMatchingMatchById(state.actualMatchingState, matchId),
326
- detailFetchState: view.detailFetchStateByMatchId[matchId] ?? NOT_STARTED,
327
- refreshStatus: view.detailRefreshStatusByMatchId[matchId] ?? NOT_STARTED,
328
- undoReversalStatus: view.undoReversalStatusByMatchId[matchId] ?? NOT_STARTED,
329
- lastMatchResults: view.lastMatchResultsByMatchId?.[matchId],
330
- lastUndoResults: view.lastUndoResultsByMatchId?.[matchId],
313
+ ...match,
314
+ members: match.members.map(toMatchModalAccrual),
315
+ leftovers: match.leftovers == null
316
+ ? undefined
317
+ : match.leftovers.map(toMatchModalAccrual),
331
318
  };
332
319
  }
320
+ const matchViewSelectorById = new Map();
321
+ export function getExpenseAutomationActualMatchingMatchView(state, matchId) {
322
+ let selector = matchViewSelectorById.get(matchId);
323
+ if (selector == null) {
324
+ selector = createSelector([
325
+ (root) => root.expenseAutomationActualMatchingViewState
326
+ .detailFetchStateByMatchId[matchId] ?? NOT_STARTED,
327
+ (root) => root.expenseAutomationActualMatchingViewState.ignoreStatusByMatchId[matchId] ?? NOT_STARTED,
328
+ (root) => root.expenseAutomationActualMatchingViewState
329
+ .lastMatchResultsByMatchId?.[matchId],
330
+ (root) => root.expenseAutomationActualMatchingViewState
331
+ .lastUndoResultsByMatchId?.[matchId],
332
+ (root) => root.expenseAutomationActualMatchingViewState
333
+ .detailRefreshStatusByMatchId[matchId] ?? NOT_STARTED,
334
+ (root) => root.expenseAutomationActualMatchingViewState
335
+ .undoReversalStatusByMatchId[matchId] ?? NOT_STARTED,
336
+ (root) => getActualMatchingMatchById(root.actualMatchingState, matchId),
337
+ ], (detailFetchState, ignoreStatus, lastMatchResults, lastUndoResults, refreshStatus, undoReversalStatus, match) => ({
338
+ detailFetchState,
339
+ ignoreStatus,
340
+ lastMatchResults,
341
+ lastUndoResults,
342
+ match: match == null ? undefined : toActualMatchingMatchView(match),
343
+ refreshStatus,
344
+ undoReversalStatus,
345
+ }));
346
+ matchViewSelectorById.set(matchId, selector);
347
+ }
348
+ return selector(state);
349
+ }
333
350
  export function getCloseMatchAfterUndo(state) {
334
- return state.expenseAutomationActualMatchingViewState.closeMatchAfterUndo;
351
+ return state.expenseAutomationActualMatchingViewState
352
+ .closeMatchAfterUndoMatchIds[0];
353
+ }
354
+ export function getCloseMatchAfterUndoMatchId(state) {
355
+ return getCloseMatchAfterUndo(state);
356
+ }
357
+ export function getCloseMatchAfterIgnoreMatchId(state) {
358
+ return state.expenseAutomationActualMatchingViewState
359
+ .closeMatchAfterIgnoreMatchIds[0];
360
+ }
361
+ export function getActualMatchingMatchModalConfirmStatus(state, matchId) {
362
+ if (matchId != null && matchId !== '') {
363
+ return (state.expenseAutomationActualMatchingViewState.confirmStatusByMatchId[matchId] ?? NOT_STARTED);
364
+ }
365
+ return state.expenseAutomationActualMatchingViewState.matchModal
366
+ .confirmStatus;
335
367
  }
336
368
  export function getExpenseAutomationActualMatchingView(state) {
337
369
  const { expenseAutomationActualMatchingViewState } = state;
338
- const { matchIdsByPeriod, fetchState, error, refreshStatus, uiState, matchModal, } = expenseAutomationActualMatchingViewState;
370
+ const { matchIdsByPeriod, fetchState, error, refreshStatus, uiState, matchModal, ignoreStatusByMatchId, undoReversalStatusByMatchId, } = expenseAutomationActualMatchingViewState;
339
371
  const selectedPeriod = getSelectedMonthYearForCurrentTenant(state);
340
372
  const monthYearPeriodId = selectedPeriod != null ? toMonthYearPeriodId(selectedPeriod) : undefined;
341
373
  const matchIds = monthYearPeriodId != null ? matchIdsByPeriod[monthYearPeriodId] : undefined;
@@ -351,32 +383,6 @@ export function getExpenseAutomationActualMatchingView(state) {
351
383
  const searchedAccruals = matchModal.accrualSearch.results
352
384
  .map((key) => getJEOneTimeAccrualByKey(key, state.jeSchedulesState))
353
385
  .filter((accrual) => accrual != null);
354
- const openedMatch = matchModal.openedFromMatchId != null
355
- ? getActualMatchingMatchById(state.actualMatchingState, matchModal.openedFromMatchId)
356
- : undefined;
357
- const pinnedAccruals = [
358
- ...(openedMatch?.members ?? []),
359
- ...(openedMatch?.leftovers ?? []),
360
- ].map(toMatchModalAccrual);
361
- const firstMember = openedMatch?.members[0];
362
- const cleared = firstMember?.cleared;
363
- const suggestion = firstMember?.matchSuggestion;
364
- const stamp = cleared ?? suggestion;
365
- const consumedLineIds = cleared?.consumedLineIds;
366
- const prefill = openedMatch == null
367
- ? undefined
368
- : {
369
- selectedAccrualIds: [
370
- ...openedMatch.members.map((member) => member.accrualId),
371
- ...(openedMatch.leftovers ?? []).map((leftover) => leftover.accrualId),
372
- ],
373
- actualAmount: openedMatch.actualAmount.amount,
374
- matchedTransactionId: stamp?.matchedTransactionId,
375
- matchedLineId: cleared == null ? suggestion?.matchedLineId : undefined,
376
- ...(consumedLineIds != null && consumedLineIds.length > 0
377
- ? { consumedLineIds }
378
- : {}),
379
- };
380
386
  // `fetchState` is a flat slice field that survives a period switch. Until this
381
387
  // month's ids land, leftover Completed/Error is the previous month. Report
382
388
  // Not-Started so chrome matches Review's "cache slot missing" skeleton, not
@@ -394,16 +400,16 @@ export function getExpenseAutomationActualMatchingView(state) {
394
400
  kpiSummary,
395
401
  uiState,
396
402
  refreshStatus,
403
+ ignoreStatusByMatchId,
404
+ undoReversalStatusByMatchId,
397
405
  hasLoadedForSelectedPeriod: matchIds != null,
398
406
  matchModal: {
399
407
  isOpen: matchModal.isOpen,
400
- openedFromMatchId: matchModal.openedFromMatchId,
401
- prefill,
402
408
  confirmStatus: matchModal.confirmStatus,
403
409
  lastBatchResults: matchModal.lastBatchResults,
404
410
  transactionSearch: {
405
411
  query: matchModal.transactionSearch.query,
406
- results: uniqueTransactionSearchResults(matchModal.pinnedTransactionResults, matchModal.transactionSearch.results),
412
+ results: matchModal.transactionSearch.results,
407
413
  fetchState: {
408
414
  fetchState: matchModal.transactionSearch.fetchState,
409
415
  error: matchModal.transactionSearch.error,
@@ -413,7 +419,7 @@ export function getExpenseAutomationActualMatchingView(state) {
413
419
  },
414
420
  accrualSearch: {
415
421
  query: matchModal.accrualSearch.query,
416
- results: uniqueMatchModalAccruals(pinnedAccruals, searchedAccruals),
422
+ results: searchedAccruals,
417
423
  fetchState: {
418
424
  fetchState: matchModal.accrualSearch.fetchState,
419
425
  error: matchModal.accrualSearch.error,
@@ -13,6 +13,12 @@ const ACTUAL_MATCHING_SORT_KEYS = [
13
13
  'variance',
14
14
  ];
15
15
  export const toActualMatchingSortKey = (v) => stringToUnion(v, ACTUAL_MATCHING_SORT_KEYS);
16
+ const ACTUAL_MATCHING_STATUS_FILTERS = [
17
+ 'all',
18
+ 'pending_review',
19
+ 'reversed',
20
+ ];
21
+ export const toActualMatchingStatusFilter = (v) => stringToUnion(v, ACTUAL_MATCHING_STATUS_FILTERS);
16
22
  export const MATCH_TRANSACTION_SEARCH_PAGE_SIZE = 50;
17
23
  export const MATCH_ACCRUAL_SEARCH_LIMIT = 50;
18
24
  export const MATCH_SEARCH_DEBOUNCE_MS = 300;
@@ -33,6 +39,15 @@ const UNDO_REVERSAL_RESULT_STATUSES = [
33
39
  'error',
34
40
  ];
35
41
  export const toUndoReversalResultStatus = (v) => stringToUnion(v, UNDO_REVERSAL_RESULT_STATUSES);
42
+ const ACTUAL_MATCHING_IGNORE_REASONS = [
43
+ 'wrong_bill',
44
+ 'wrong_period',
45
+ 'amount_off',
46
+ 'already_handled',
47
+ 'other_ignore',
48
+ ];
49
+ export const toActualMatchingIgnoreReasonType = (v) => stringToUnion(v, ACTUAL_MATCHING_IGNORE_REASONS);
50
+ export const ALL_ACTUAL_MATCHING_IGNORE_REASONS = ACTUAL_MATCHING_IGNORE_REASONS;
36
51
  export function actualMatchingTransactionLineKey(transactionType, transactionIntegrationId, lineId) {
37
52
  return `${transactionType}:${transactionIntegrationId}:${lineId}`;
38
53
  }
@@ -6,7 +6,8 @@ import { updateInvoices } from '../../../entity/invoicing/invoice/invoiceReducer
6
6
  import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiError';
7
7
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
8
8
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
9
- import { addInvoiceToList } from '../invoiceList/invoiceListReducer';
9
+ import { invalidateInvoicingCustomerDetail } from '../editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer';
10
+ import { addInvoiceToList, invalidateInvoiceListForCustomer, } from '../invoiceList/invoiceListReducer';
10
11
  import { createInvoice, createInvoiceFailure, createInvoiceSuccess, } from './createInvoiceReducer';
11
12
  export const createInvoiceEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(createInvoice.match), mergeMap((action) => {
12
13
  const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/invoices`;
@@ -23,6 +24,13 @@ export const createInvoiceEpic = (actions$, _state$, zeniAPI) => actions$.pipe(f
23
24
  id: response.data.id,
24
25
  status: response.data.status,
25
26
  }),
27
+ // The customer's page caches their invoices and totals.
28
+ invalidateInvoicingCustomerDetail({
29
+ id: response.data.customer_id,
30
+ }),
31
+ invalidateInvoiceListForCustomer({
32
+ customerId: response.data.customer_id,
33
+ }),
26
34
  createInvoiceSuccess({ invoiceId: response.data.id }),
27
35
  openSnackbar({
28
36
  messageSection: 'invoicing_invoice_saved',
@@ -1,5 +1,5 @@
1
1
  import { getAddressByAddressId } from '../../../entity/address/addressSelector';
2
- import { invoicingPricePointDisplayAmount } from '../../../entity/invoicing/plan/planSelector';
2
+ import { invoicingPricePointDisplayAmount } from '../../../entity/invoicing/invoicingCatalog/invoicingCatalogSelector';
3
3
  import { getInvoicingCatalogListView, getInvoicingCatalogPrices, } from '../invoicingCatalogListView/invoicingCatalogListViewSelector';
4
4
  import { invoicingFieldHelpForGroup } from '../invoicingConfigView/invoicingConfigViewSelector';
5
5
  import { getInvoicingCustomerListView } from '../invoicingCustomerListView/invoicingCustomerListViewSelector';
@@ -1,6 +1,6 @@
1
1
  import { createSelector } from '@reduxjs/toolkit';
2
2
  import { reduceAllFetchState } from '../../../commonStateTypes/reduceFetchState';
3
- import { getInvoicingPlanById, getInvoicingPlansByIds, } from '../../../entity/invoicing/plan/planSelector';
3
+ import { getInvoicingCatalogItemById, getInvoicingCatalogItemsByIds, } from '../../../entity/invoicing/invoicingCatalog/invoicingCatalogSelector';
4
4
  import { getInvoicingConfigView, invoicingFieldHelpForGroup, } from '../invoicingConfigView/invoicingConfigViewSelector';
5
5
  import { getInvoicingFormDraftLocalData, isInvoicingFormDraftPending, } from '../invoicingFormDraft';
6
6
  import { INVOICING_DEFAULT_INCOME_ACCOUNT_TYPES, INVOICING_INCOME_ACCOUNT_MAPPING_SLOT, NO_INVOICING_ACCOUNT_HIERARCHY, NO_INVOICING_ACCOUNT_IDS, NO_INVOICING_CLASS_HIERARCHY, getInvoicingQboAccountOptions, getInvoicingQboView, invoicingQboAccountIdsForMappingSlot, isInvoicingAccountingPickerDataLoading, isInvoicingQboConnected, } from '../invoicingQboView/invoicingQboViewSelector';
@@ -36,7 +36,7 @@ const buildApplicableCatalogItemOptions = (plans) => {
36
36
  * Kept referentially stable across unrelated dispatches; the form view filters
37
37
  * out the item being edited (see `getInvoicingCatalogItemFormView`).
38
38
  */
39
- export const getInvoicingApplicableCatalogItemOptions = createSelector((state) => state.invoicingPlanState, (state) => state.invoicingCatalogListViewState, (planState, listViewState) => buildApplicableCatalogItemOptions(getInvoicingPlansByIds(planState, [
39
+ export const getInvoicingApplicableCatalogItemOptions = createSelector((state) => state.invoicingCatalogState, (state) => state.invoicingCatalogListViewState, (catalogState, listViewState) => buildApplicableCatalogItemOptions(getInvoicingCatalogItemsByIds(catalogState, [
40
40
  ...listViewState.planIds,
41
41
  ...listViewState.productIds,
42
42
  ])));
@@ -61,7 +61,9 @@ const getInvoicingCatalogIncomeAccountIds = createSelector([
61
61
  : NO_INVOICING_ACCOUNT_IDS);
62
62
  export const getInvoicingCatalogItemFormView = (state, id) => {
63
63
  const { editDraftById, error, fetchState, newItemDraft, savedItemId, savedKind, } = state.editInvoicingCatalogItemDetailViewState;
64
- const plan = id != null ? getInvoicingPlanById(state.invoicingPlanState, id) : undefined;
64
+ const plan = id != null
65
+ ? getInvoicingCatalogItemById(state.invoicingCatalogState, id)
66
+ : undefined;
65
67
  const kind = plan != null ? 'plan' : undefined;
66
68
  // No entity fallback: the draft is the one source of form values, seeded from
67
69
  // the record by `seedInvoicingCatalogItemFormDraftEpic` when the detail load
@@ -1,6 +1,6 @@
1
1
  import { of } from 'rxjs';
2
2
  import { catchError, filter, switchMap } from 'rxjs/operators';
3
- import { updateInvoicingPlans } from '../../../entity/invoicing/plan/planReducer';
3
+ import { updateInvoicingCatalogItems } from '../../../entity/invoicing/invoicingCatalog/invoicingCatalogReducer';
4
4
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
5
5
  import { fetchInvoicingCatalogItemDetail, updateInvoicingCatalogItemDetail, updateInvoicingCatalogItemDetailFailure, } from './editInvoicingCatalogItemDetailViewReducer';
6
6
  export const fetchInvoicingCatalogItemDetailEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchInvoicingCatalogItemDetail.match), switchMap((action) => {
@@ -9,7 +9,7 @@ export const fetchInvoicingCatalogItemDetailEpic = (actions$, _state$, zeniAPI)
9
9
  return zeniAPI.getJSON(apiUrl).pipe(switchMap((response) => {
10
10
  if (isSuccessResponse(response) && response.data != null) {
11
11
  return [
12
- updateInvoicingPlans([response.data], 'merge'),
12
+ updateInvoicingCatalogItems([response.data], 'merge'),
13
13
  updateInvoicingCatalogItemDetail({ id: response.data.id, kind }),
14
14
  ];
15
15
  }
@@ -1,6 +1,6 @@
1
1
  import { of } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
- import { updateInvoicingPlans } from '../../../entity/invoicing/plan/planReducer';
3
+ import { updateInvoicingCatalogItems } from '../../../entity/invoicing/invoicingCatalog/invoicingCatalogReducer';
4
4
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
5
5
  import { runInvoicingCatalogPlanAction, runInvoicingCatalogPlanActionFailure, runInvoicingCatalogPlanActionSuccess, } from './editInvoicingCatalogItemDetailViewReducer';
6
6
  export const runInvoicingCatalogPlanActionEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(runInvoicingCatalogPlanAction.match), mergeMap((action) => {
@@ -12,7 +12,7 @@ export const runInvoicingCatalogPlanActionEpic = (actions$, _state$, zeniAPI) =>
12
12
  .pipe(mergeMap((response) => {
13
13
  if (isSuccessResponse(response) && response.data != null) {
14
14
  return [
15
- updateInvoicingPlans([response.data], 'merge'),
15
+ updateInvoicingCatalogItems([response.data], 'merge'),
16
16
  runInvoicingCatalogPlanActionSuccess({ id }),
17
17
  ];
18
18
  }
@@ -1,7 +1,6 @@
1
1
  import { of } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
- import { mergeInvoicingPlanSubFamily } from '../../../entity/invoicing/plan/planReducer';
4
- import { updateInvoicingProducts } from '../../../entity/invoicing/product/productReducer';
3
+ import { mergeInvoicingCatalogPricePoint } from '../../../entity/invoicing/invoicingCatalog/invoicingCatalogReducer';
5
4
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
6
5
  import { runInvoicingCatalogProductAction, runInvoicingCatalogProductActionFailure, runInvoicingCatalogProductActionSuccess, } from './editInvoicingCatalogItemDetailViewReducer';
7
6
  export const runInvoicingCatalogProductActionEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(runInvoicingCatalogProductAction.match), mergeMap((action) => {
@@ -16,9 +15,10 @@ export const runInvoicingCatalogProductActionEpic = (actions$, _state$, zeniAPI)
16
15
  .pipe(mergeMap((response) => {
17
16
  if (isSuccessResponse(response) && response.data != null) {
18
17
  return [
19
- updateInvoicingProducts([response.data], 'merge'),
20
18
  ...(parentItemId != null
21
- ? [mergeInvoicingPlanSubFamily(parentItemId, response.data)]
19
+ ? [
20
+ mergeInvoicingCatalogPricePoint(parentItemId, response.data),
21
+ ]
22
22
  : []),
23
23
  runInvoicingCatalogProductActionSuccess({ id: stateKey }),
24
24
  ];
@@ -1,7 +1,6 @@
1
1
  import { forkJoin, from } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
- import { mergeInvoicingPlanSubFamily, updateInvoicingPlans, } from '../../../entity/invoicing/plan/planReducer';
4
- import { updateInvoicingProducts } from '../../../entity/invoicing/product/productReducer';
3
+ import { mergeInvoicingCatalogPricePoint, updateInvoicingCatalogItems, } from '../../../entity/invoicing/invoicingCatalog/invoicingCatalogReducer';
5
4
  import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiError';
6
5
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
7
6
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
@@ -51,11 +50,8 @@ export const saveInvoicingCatalogItemEpic = (actions$, state$, zeniAPI) => actio
51
50
  }
52
51
  const savedProducts = productResponses.flatMap((productResponse) => productResponse.data != null ? [productResponse.data] : []);
53
52
  const resultActions = [
54
- updateInvoicingPlans([savedItem], 'merge'),
55
- ...(savedProducts.length > 0
56
- ? [updateInvoicingProducts(savedProducts, 'merge')]
57
- : []),
58
- ...savedProducts.map((product) => mergeInvoicingPlanSubFamily(savedItem.id, product)),
53
+ updateInvoicingCatalogItems([savedItem], 'merge'),
54
+ ...savedProducts.map((product) => mergeInvoicingCatalogPricePoint(savedItem.id, product)),
59
55
  submitInvoicingCatalogItemFormSuccess({
60
56
  itemId: savedItem.id,
61
57
  kind: 'plan',
@@ -1,6 +1,6 @@
1
1
  import { EMPTY, of } from 'rxjs';
2
2
  import { filter, mergeMap, withLatestFrom } from 'rxjs/operators';
3
- import { getInvoicingPlanById } from '../../../entity/invoicing/plan/planSelector';
3
+ import { getInvoicingCatalogItemById } from '../../../entity/invoicing/invoicingCatalog/invoicingCatalogSelector';
4
4
  import { fetchInvoicingCatalogItemDetailPage, seedInvoicingCatalogItemFormDraft, updateInvoicingCatalogItemDetail, } from './editInvoicingCatalogItemDetailViewReducer';
5
5
  import { entityToInvoicingCatalogItemFormLocalData } from './invoicingCatalogItemFormHelper';
6
6
  /**
@@ -34,7 +34,7 @@ export const seedInvoicingCatalogItemFormDraftEpic = (actions$, state$) => actio
34
34
  }
35
35
  // Nothing to seed from on a cold open; the detail response triggers this
36
36
  // again once it lands.
37
- const plan = getInvoicingPlanById(state.invoicingPlanState, id);
37
+ const plan = getInvoicingCatalogItemById(state.invoicingCatalogState, id);
38
38
  if (plan == null) {
39
39
  return EMPTY;
40
40
  }
@@ -95,6 +95,10 @@ const editInvoicingCustomerDetailView = createSlice({
95
95
  error: undefined,
96
96
  };
97
97
  },
98
+ // Makes the page epic refetch a record that changed server-side (e.g. a new subscription).
99
+ invalidateInvoicingCustomerDetail(draft, action) {
100
+ delete draft.recordStateById[action.payload.id];
101
+ },
98
102
  fetchInvoicingCustomerDetail(draft, action) {
99
103
  draft.recordStateById[action.payload.id] = {
100
104
  fetchState: 'In-Progress',
@@ -198,5 +202,5 @@ const editInvoicingCustomerDetailView = createSlice({
198
202
  });
199
203
  },
200
204
  });
201
- export const { fetchInvoicingPaymentLinkEmailPreview, fetchInvoicingPaymentLinkEmailPreviewPage, updateInvoicingPaymentLinkEmailPreview, updateInvoicingPaymentLinkEmailPreviewFailure, addPromotionalCreditsFailure, addPromotionalCreditsSuccess, fetchInvoicingCustomerDetail, fetchInvoicingCustomerDetailPage, seedInvoicingCustomerFormDraft, updateInvoicingCustomerDetail, resetPromotionalCreditAction, submitAddPromotionalCredits, updateInvoicingCustomerDetailFailure, updateInvoicingCustomerFormDraft, submitInvoicingCustomerForm, submitInvoicingCustomerFormSuccess, submitInvoicingCustomerFormFailure, resetEditInvoicingCustomerDetailView, initializeInvoicingCustomerAddress, claimInvoicingCustomerBillingAddress, } = editInvoicingCustomerDetailView.actions;
205
+ export const { fetchInvoicingPaymentLinkEmailPreview, fetchInvoicingPaymentLinkEmailPreviewPage, updateInvoicingPaymentLinkEmailPreview, updateInvoicingPaymentLinkEmailPreviewFailure, addPromotionalCreditsFailure, addPromotionalCreditsSuccess, fetchInvoicingCustomerDetail, fetchInvoicingCustomerDetailPage, invalidateInvoicingCustomerDetail, seedInvoicingCustomerFormDraft, updateInvoicingCustomerDetail, resetPromotionalCreditAction, submitAddPromotionalCredits, updateInvoicingCustomerDetailFailure, updateInvoicingCustomerFormDraft, submitInvoicingCustomerForm, submitInvoicingCustomerFormSuccess, submitInvoicingCustomerFormFailure, resetEditInvoicingCustomerDetailView, initializeInvoicingCustomerAddress, claimInvoicingCustomerBillingAddress, } = editInvoicingCustomerDetailView.actions;
202
206
  export default editInvoicingCustomerDetailView.reducer;
@@ -7,6 +7,8 @@ import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiE
7
7
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
8
8
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
9
9
  import { resetNewAddressDataInLocalStore } from '../../addressView/addressViewReducer';
10
+ import { invalidateInvoicingCustomerDetail } from '../editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer';
11
+ import { invalidateInvoiceListForCustomer } from '../invoiceList/invoiceListReducer';
10
12
  import { addInvoicingSubscriptionToList } from '../invoicingSubscriptionListView/invoicingSubscriptionListViewReducer';
11
13
  import { localDataToSaveInvoicingSubscriptionPayload, } from './editInvoicingSubscriptionDetailViewPayload';
12
14
  import { submitInvoicingSubscriptionForm, submitInvoicingSubscriptionFormFailure, submitInvoicingSubscriptionFormSuccess, syncInvoicingSubscriptionCoupons, } from './editInvoicingSubscriptionDetailViewReducer';
@@ -86,7 +88,9 @@ export const saveInvoicingSubscriptionEpic = (actions$, state$, zeniAPI) => acti
86
88
  resultActions.push(addInvoicingSubscriptionToList({
87
89
  id: saved.id,
88
90
  status: saved.status,
89
- }));
91
+ }),
92
+ // The service may bill the first invoice on create.
93
+ invalidateInvoicingCustomerDetail({ id: saved.customer_id }), invalidateInvoiceListForCustomer({ customerId: saved.customer_id }));
90
94
  }
91
95
  resultActions.push(openSnackbar({
92
96
  messageSection: 'invoicing_subscription_saved',
@@ -124,6 +124,13 @@ const invoiceList = createSlice({
124
124
  draft.kpisFetchState.fetchState = 'Error';
125
125
  draft.kpisFetchState.error = action.payload;
126
126
  },
127
+ // Counts are fetched under the same customer filter, so they go stale with the tabs.
128
+ invalidateInvoiceListForCustomer(draft, action) {
129
+ if (draft.filters.customerId === action.payload.customerId) {
130
+ draft.invoiceByTab = makeInvoiceByTab();
131
+ draft.countsFetchState = { fetchState: 'Not-Started', error: undefined };
132
+ }
133
+ },
127
134
  updateInvoiceListFilters(draft, action) {
128
135
  draft.filters = action.payload;
129
136
  },
@@ -166,5 +173,5 @@ const invoiceList = createSlice({
166
173
  });
167
174
  },
168
175
  });
169
- export const { fetchInvoiceList, fetchInvoiceListPage, updateInvoiceList, updateInvoiceListFailure, fetchInvoiceCounts, updateInvoiceCounts, updateInvoiceCountsFailure, fetchInvoiceKPIs, updateInvoiceKPIs, updateInvoiceKPIsFailure, updateInvoiceListFilters, setInvoiceActiveTab, addInvoiceToList, } = invoiceList.actions;
176
+ export const { fetchInvoiceList, fetchInvoiceListPage, updateInvoiceList, updateInvoiceListFailure, fetchInvoiceCounts, updateInvoiceCounts, updateInvoiceCountsFailure, fetchInvoiceKPIs, updateInvoiceKPIs, updateInvoiceKPIsFailure, updateInvoiceListFilters, setInvoiceActiveTab, addInvoiceToList, invalidateInvoiceListForCustomer, } = invoiceList.actions;
170
177
  export default invoiceList.reducer;
@@ -1,6 +1,6 @@
1
1
  import { of } from 'rxjs';
2
2
  import { catchError, filter, switchMap } from 'rxjs/operators';
3
- import { updateInvoicingPlans } from '../../../../entity/invoicing/plan/planReducer';
3
+ import { updateInvoicingCatalogItems } from '../../../../entity/invoicing/invoicingCatalog/invoicingCatalogReducer';
4
4
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
5
5
  import { INVOICING_LIST_PAGE_SIZE, applyListSortParams, withQuery, } from '../../invoicingApiHelper';
6
6
  import { fetchInvoicingCatalogPlanList, updateInvoicingCatalogPlanList, updateInvoicingCatalogPlanListFailure, } from '../invoicingCatalogListViewReducer';
@@ -29,7 +29,7 @@ export const fetchInvoicingCatalogPlanListEpic = (actions$, _state$, zeniAPI) =>
29
29
  if (isSuccessResponse(response)) {
30
30
  const items = response.data?.items ?? [];
31
31
  return [
32
- updateInvoicingPlans(items, 'merge'),
32
+ updateInvoicingCatalogItems(items, 'merge'),
33
33
  updateInvoicingCatalogPlanList({
34
34
  ids: items.map((item) => item.id),
35
35
  nextCursor: response.data?.next_cursor ?? null,
@@ -1,6 +1,6 @@
1
1
  import { of } from 'rxjs';
2
2
  import { catchError, filter, switchMap } from 'rxjs/operators';
3
- import { updateInvoicingPlans } from '../../../../entity/invoicing/plan/planReducer';
3
+ import { updateInvoicingCatalogItems } from '../../../../entity/invoicing/invoicingCatalog/invoicingCatalogReducer';
4
4
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
5
5
  import { INVOICING_LIST_PAGE_SIZE, applyListSortParams, withQuery, } from '../../invoicingApiHelper';
6
6
  import { fetchInvoicingCatalogProductList, updateInvoicingCatalogProductList, updateInvoicingCatalogProductListFailure, } from '../invoicingCatalogListViewReducer';
@@ -29,7 +29,7 @@ export const fetchInvoicingCatalogProductListEpic = (actions$, _state$, zeniAPI)
29
29
  if (isSuccessResponse(response)) {
30
30
  const items = response.data?.items ?? [];
31
31
  return [
32
- updateInvoicingPlans(items, 'merge'),
32
+ updateInvoicingCatalogItems(items, 'merge'),
33
33
  updateInvoicingCatalogProductList({
34
34
  ids: items.map((item) => item.id),
35
35
  nextCursor: response.data?.next_cursor ?? null,
@@ -1,7 +1,5 @@
1
1
  import { createSelector } from '@reduxjs/toolkit';
2
- import { getInvoicingPlansByIds } from '../../../entity/invoicing/plan/planSelector';
3
- import { runningInvoicingActionId } from '../runningInvoicingActionId';
4
- import { INVOICING_PRODUCT_COLUMNS, } from './invoicingCatalogListViewState';
2
+ import { getInvoicingCatalogItemsByIds, invoicingPricePointDisplayAmount, } from '../../../entity/invoicing/invoicingCatalog/invoicingCatalogSelector';
5
3
  /**
6
4
  * Flatten catalog items into the price rows the pickers render.
7
5
  *
@@ -22,28 +20,63 @@ export const getInvoicingCatalogPrices = (items) => items.flatMap((item) => (ite
22
20
  priceCode: subFamily.priceCode,
23
21
  productCode: item.productCode,
24
22
  })));
23
+ const lowestStartingTier = (tiers) => tiers.reduce((lowest, tier) => lowest == null || tier.startingUnit < lowest.startingUnit ? tier : lowest, undefined);
24
+ export const getInvoicingCatalogPlanRows = (items) => items.flatMap((item) => {
25
+ const pricePoints = item.subFamilies ?? [];
26
+ if (pricePoints.length === 0) {
27
+ return [
28
+ {
29
+ id: item.id,
30
+ item,
31
+ period: item.period,
32
+ periodUnit: item.periodUnit,
33
+ price: item.price,
34
+ productFamily: item.name,
35
+ status: item.status,
36
+ tierCount: item.tiers?.length ?? 0,
37
+ firstTier: lowestStartingTier(item.tiers ?? []),
38
+ },
39
+ ];
40
+ }
41
+ return pricePoints.map((pricePoint) => ({
42
+ id: pricePoint.id,
43
+ item,
44
+ period: pricePoint.period,
45
+ periodUnit: pricePoint.periodUnit,
46
+ planName: pricePoint.name,
47
+ price: invoicingPricePointDisplayAmount(pricePoint),
48
+ pricingModel: pricePoint.pricingModel,
49
+ productFamily: item.name,
50
+ status: pricePoint.status,
51
+ tierCount: pricePoint.tiers?.length ?? 0,
52
+ firstTier: lowestStartingTier(pricePoint.tiers ?? []),
53
+ }));
54
+ });
25
55
  /**
26
56
  * Bundled read view for the single catalog page (plans + products tabs).
27
57
  * Memoized over the plan + product entity slices and the list-view slice so the
28
58
  * denormalized arrays keep referential identity. The base `fetchState`/`error`
29
59
  * track the plan list; the product list has its own `productFetchState`.
30
60
  */
31
- export const getInvoicingCatalogListView = createSelector((state) => state.invoicingPlanState, (state) => state.invoicingCatalogListViewState, (state) => state.invoicingConfigViewState.catalogItemLifecycleActionOptions, (state) => state.editInvoicingCatalogItemDetailViewState.actionStateById, (planState, listViewState, catalogItemLifecycleActionOptions, actionStateById) => ({
32
- catalogItemLifecycleActionOptions,
33
- runningActionId: runningInvoicingActionId(actionStateById),
34
- plans: getInvoicingPlansByIds(planState, listViewState.planIds),
35
- planSort: listViewState.planSort,
36
- productColumns: INVOICING_PRODUCT_COLUMNS,
37
- products: getInvoicingPlansByIds(planState, listViewState.productIds),
38
- productSort: listViewState.productSort,
39
- count: listViewState.counts,
40
- countsFetchState: listViewState.countsFetchState,
41
- planFilters: listViewState.planFilters,
42
- planNextCursor: listViewState.planNextCursor,
43
- productFilters: listViewState.productFilters,
44
- productNextCursor: listViewState.productNextCursor,
45
- productFetchState: listViewState.productFetchState,
46
- fetchState: listViewState.fetchState,
47
- error: listViewState.error,
48
- version: 1.0,
49
- }));
61
+ export const getInvoicingCatalogListView = createSelector((state) => state.invoicingCatalogState, (state) => state.invoicingCatalogListViewState, (catalogState, listViewState) => {
62
+ const plans = getInvoicingCatalogItemsByIds(catalogState, listViewState.planIds);
63
+ const products = getInvoicingCatalogItemsByIds(catalogState, listViewState.productIds);
64
+ return {
65
+ planRows: getInvoicingCatalogPlanRows(plans),
66
+ plans,
67
+ planSort: listViewState.planSort,
68
+ productRows: getInvoicingCatalogPlanRows(products),
69
+ products,
70
+ productSort: listViewState.productSort,
71
+ count: listViewState.counts,
72
+ countsFetchState: listViewState.countsFetchState,
73
+ planFilters: listViewState.planFilters,
74
+ planNextCursor: listViewState.planNextCursor,
75
+ productFilters: listViewState.productFilters,
76
+ productNextCursor: listViewState.productNextCursor,
77
+ productFetchState: listViewState.productFetchState,
78
+ fetchState: listViewState.fetchState,
79
+ error: listViewState.error,
80
+ version: 1.0,
81
+ };
82
+ });
@@ -1,20 +1 @@
1
- import { stringToUnion } from '../../../commonStateTypes/stringToUnion';
2
- export const ALL_INVOICING_PRODUCT_SORT_KEYS = [
3
- 'product_name',
4
- 'product_code',
5
- 'type',
6
- 'pricing',
7
- 'usage',
8
- 'updated',
9
- 'status',
10
- ];
11
- export const toInvoicingProductSortKey = (v) => stringToUnion(v, ALL_INVOICING_PRODUCT_SORT_KEYS);
12
- export const INVOICING_PRODUCT_COLUMNS = [
13
- { key: 'product_name', sortable: true },
14
- { key: 'product_code', sortable: true },
15
- { key: 'type', sortable: true },
16
- { key: 'pricing', sortable: false },
17
- { key: 'usage', sortable: true },
18
- { key: 'updated', sortable: true },
19
- { key: 'status', sortable: true },
20
- ];
1
+ export {};