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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/lib/entity/billPay/billTransaction/billTransactionReducer.d.ts +5 -1
  2. package/lib/entity/billPay/billTransaction/billTransactionReducer.js +17 -2
  3. package/lib/entity/invoicing/coupon/couponPayload.d.ts +8 -0
  4. package/lib/entity/invoicing/coupon/couponPayload.js +8 -0
  5. package/lib/entity/invoicing/coupon/couponState.d.ts +6 -0
  6. package/lib/entity/invoicing/dunningCase/dunningCaseTypes.d.ts +1 -1
  7. package/lib/entity/invoicing/plan/planSelector.d.ts +20 -0
  8. package/lib/entity/invoicing/plan/planSelector.js +42 -0
  9. package/lib/entity/invoicing/product/productPayload.d.ts +8 -1
  10. package/lib/entity/invoicing/product/productPayload.js +4 -1
  11. package/lib/entity/invoicing/product/productState.d.ts +16 -0
  12. package/lib/entity/jeSchedules/jeSchedulesReducer.d.ts +4 -1
  13. package/lib/entity/jeSchedules/jeSchedulesReducer.js +79 -5
  14. package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
  15. package/lib/entity/snackbar/snackbarTypes.js +1 -0
  16. package/lib/entity/task/taskState.d.ts +1 -1
  17. package/lib/epic.d.ts +4 -1
  18. package/lib/epic.js +10 -4
  19. package/lib/esm/entity/billPay/billTransaction/billTransactionReducer.js +16 -1
  20. package/lib/esm/entity/invoicing/coupon/couponPayload.js +8 -0
  21. package/lib/esm/entity/invoicing/plan/planSelector.js +39 -0
  22. package/lib/esm/entity/invoicing/product/productPayload.js +4 -1
  23. package/lib/esm/entity/jeSchedules/jeSchedulesReducer.js +78 -4
  24. package/lib/esm/entity/snackbar/snackbarTypes.js +1 -0
  25. package/lib/esm/epic.js +10 -4
  26. package/lib/esm/index.js +8 -7
  27. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/ignoreActualMatchingSuggestionEpic.js +3 -2
  28. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/fetchFailedJePostingsEpic.js +39 -0
  29. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.js +14 -1
  30. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.js +10 -13
  31. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/initializeJeScheduleLocalDataEpic.js +38 -17
  32. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/retryJeSchedulesEpic.js +17 -1
  33. package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +15 -4
  34. package/lib/esm/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +6 -0
  35. package/lib/esm/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +74 -1
  36. package/lib/esm/view/expenseAutomationView/reducers/reconciliationViewReducer.js +80 -53
  37. package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +10 -0
  38. package/lib/esm/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +6 -2
  39. package/lib/esm/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +49 -14
  40. package/lib/esm/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +28 -11
  41. package/lib/esm/view/invoicing/createInvoice/createInvoiceSelector.js +28 -1
  42. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.js +67 -35
  43. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewReducer.js +22 -11
  44. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +23 -4
  45. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/invoicingCatalogItemFormHelper.js +1 -7
  46. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +19 -5
  47. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/seedInvoicingCatalogItemFormDraftEpic.js +30 -7
  48. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewPayload.js +16 -1
  49. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewReducer.js +22 -11
  50. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewSelector.js +69 -6
  51. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewState.js +22 -0
  52. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/fetchInvoicingCouponDetailPageEpic.js +22 -4
  53. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.js +18 -4
  54. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/seedInvoicingCouponFormDraftEpic.js +29 -6
  55. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.js +28 -12
  56. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.js +37 -2
  57. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/initializeInvoicingCustomerAddressEpic.js +43 -17
  58. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.js +1 -1
  59. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/seedInvoicingCustomerFormDraftEpic.js +29 -6
  60. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.js +30 -14
  61. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +143 -8
  62. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionFormPageEpic.js +4 -1
  63. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/initializeInvoicingSubscriptionAddressEpic.js +44 -18
  64. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +1 -1
  65. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/seedInvoicingSubscriptionFormDraftEpic.js +29 -8
  66. package/lib/esm/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewSelector.js +12 -0
  67. package/lib/esm/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewState.js +6 -6
  68. package/lib/esm/view/invoicing/invoicingFormDraft.js +78 -0
  69. package/lib/esm/view/invoicing/invoicingQboView/invoicingQboViewSelector.js +57 -9
  70. package/lib/esm/view/recommendation/recommendationPayload.js +3 -1
  71. package/lib/esm/view/recommendation/recommendationReducer.js +27 -7
  72. package/lib/esm/view/recommendation/recommendationSelector.js +1 -0
  73. package/lib/esm/view/recommendation/submitRecommendationFeedbackEpic.js +20 -8
  74. package/lib/esm/view/spendManagement/billPay/billDetailView/billDetailViewReducer.js +87 -1
  75. package/lib/esm/view/spendManagement/billPay/billDetailView/billDetailViewSelector.js +1 -0
  76. package/lib/esm/view/spendManagement/billPay/billDetailView/billDetailViewState.js +4 -0
  77. package/lib/esm/view/spendManagement/billPay/billDetailView/epics/fetchBillRecommendationsEpic.js +97 -0
  78. package/lib/esm/view/spendManagement/billPay/billDetailView/epics/fetchRecommendationsForBill.js +85 -0
  79. package/lib/esm/view/spendManagement/billPay/editBillView/editBillViewReducer.js +18 -1
  80. package/lib/esm/view/spendManagement/billPay/editBillView/editBillViewSelector.js +3 -0
  81. package/lib/esm/view/spendManagement/billPay/editBillView/epics/applyBillRecommendationsToLocalStoreEpic.js +95 -0
  82. package/lib/index.d.ts +9 -8
  83. package/lib/index.js +54 -48
  84. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/ignoreActualMatchingSuggestionEpic.js +3 -2
  85. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchFailedJePostingsEpic.d.ts +20 -0
  86. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchFailedJePostingsEpic.js +43 -0
  87. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.d.ts +2 -2
  88. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulePageEpic.js +13 -0
  89. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.js +8 -11
  90. package/lib/view/expenseAutomationView/epics/jeSchedule/initializeJeScheduleLocalDataEpic.js +38 -17
  91. package/lib/view/expenseAutomationView/epics/jeSchedule/retryJeSchedulesEpic.d.ts +2 -2
  92. package/lib/view/expenseAutomationView/epics/jeSchedule/retryJeSchedulesEpic.js +17 -1
  93. package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +15 -4
  94. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
  95. package/lib/view/expenseAutomationView/payload/jeSchedulesPayload.d.ts +18 -0
  96. package/lib/view/expenseAutomationView/payload/transactionCategorizationPayload.d.ts +18 -7
  97. package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +6 -0
  98. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.d.ts +10 -3
  99. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +76 -3
  100. package/lib/view/expenseAutomationView/reducers/reconciliationViewReducer.js +80 -53
  101. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +10 -0
  102. package/lib/view/expenseAutomationView/selectorTypes/jeSchedulesSelectorTypes.d.ts +3 -0
  103. package/lib/view/expenseAutomationView/selectorTypes/transactionsViewSelectorTypes.d.ts +8 -4
  104. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.d.ts +1 -0
  105. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +7 -2
  106. package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +49 -14
  107. package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +27 -10
  108. package/lib/view/expenseAutomationView/types/jeSchedulesViewState.d.ts +16 -0
  109. package/lib/view/expenseAutomationView/types/transactionsViewState.d.ts +8 -5
  110. package/lib/view/invoicing/createInvoice/createInvoiceSelector.d.ts +7 -2
  111. package/lib/view/invoicing/createInvoice/createInvoiceSelector.js +28 -1
  112. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.d.ts +46 -5
  113. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.js +69 -36
  114. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewReducer.d.ts +3 -1
  115. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewReducer.js +22 -11
  116. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.d.ts +17 -0
  117. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +22 -3
  118. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewState.d.ts +2 -1
  119. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/invoicingCatalogItemFormHelper.d.ts +2 -2
  120. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/invoicingCatalogItemFormHelper.js +1 -7
  121. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +17 -3
  122. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/seedInvoicingCatalogItemFormDraftEpic.d.ts +10 -6
  123. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/seedInvoicingCatalogItemFormDraftEpic.js +29 -6
  124. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewPayload.d.ts +5 -1
  125. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewPayload.js +16 -1
  126. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewReducer.d.ts +3 -1
  127. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewReducer.js +22 -11
  128. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewSelector.d.ts +46 -0
  129. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewSelector.js +68 -5
  130. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewState.d.ts +21 -1
  131. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewState.js +24 -1
  132. package/lib/view/invoicing/editInvoicingCouponDetailView/fetchInvoicingCouponDetailPageEpic.d.ts +9 -5
  133. package/lib/view/invoicing/editInvoicingCouponDetailView/fetchInvoicingCouponDetailPageEpic.js +22 -4
  134. package/lib/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.js +18 -4
  135. package/lib/view/invoicing/editInvoicingCouponDetailView/seedInvoicingCouponFormDraftEpic.d.ts +10 -6
  136. package/lib/view/invoicing/editInvoicingCouponDetailView/seedInvoicingCouponFormDraftEpic.js +28 -5
  137. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.d.ts +5 -1
  138. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.js +29 -13
  139. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.d.ts +16 -7
  140. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.js +37 -2
  141. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewState.d.ts +8 -1
  142. package/lib/view/invoicing/editInvoicingCustomerDetailView/initializeInvoicingCustomerAddressEpic.d.ts +15 -10
  143. package/lib/view/invoicing/editInvoicingCustomerDetailView/initializeInvoicingCustomerAddressEpic.js +42 -16
  144. package/lib/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.js +1 -1
  145. package/lib/view/invoicing/editInvoicingCustomerDetailView/seedInvoicingCustomerFormDraftEpic.d.ts +10 -6
  146. package/lib/view/invoicing/editInvoicingCustomerDetailView/seedInvoicingCustomerFormDraftEpic.js +28 -5
  147. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.d.ts +5 -1
  148. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.js +31 -15
  149. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.d.ts +79 -12
  150. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +145 -9
  151. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewState.d.ts +8 -1
  152. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionFormPageEpic.js +4 -1
  153. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/initializeInvoicingSubscriptionAddressEpic.d.ts +15 -11
  154. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/initializeInvoicingSubscriptionAddressEpic.js +42 -16
  155. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +1 -1
  156. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/seedInvoicingSubscriptionFormDraftEpic.d.ts +10 -6
  157. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/seedInvoicingSubscriptionFormDraftEpic.js +28 -7
  158. package/lib/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewSelector.d.ts +10 -0
  159. package/lib/view/invoicing/invoicingCatalogListView/invoicingCatalogListViewSelector.js +12 -0
  160. package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewReducer.d.ts +1 -1
  161. package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewState.d.ts +2 -2
  162. package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewState.js +6 -6
  163. package/lib/view/invoicing/invoicingFormDraft.d.ts +63 -0
  164. package/lib/view/invoicing/invoicingFormDraft.js +86 -0
  165. package/lib/view/invoicing/invoicingQboView/invoicingQboViewSelector.d.ts +49 -0
  166. package/lib/view/invoicing/invoicingQboView/invoicingQboViewSelector.js +59 -10
  167. package/lib/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewReducer.d.ts +1 -1
  168. package/lib/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewState.d.ts +1 -1
  169. package/lib/view/recommendation/recommendationPayload.d.ts +1 -0
  170. package/lib/view/recommendation/recommendationPayload.js +5 -0
  171. package/lib/view/recommendation/recommendationReducer.d.ts +7 -1
  172. package/lib/view/recommendation/recommendationReducer.js +28 -8
  173. package/lib/view/recommendation/recommendationSelector.d.ts +2 -0
  174. package/lib/view/recommendation/recommendationSelector.js +3 -1
  175. package/lib/view/recommendation/recommendationState.d.ts +2 -1
  176. package/lib/view/recommendation/submitRecommendationFeedbackEpic.d.ts +2 -2
  177. package/lib/view/recommendation/submitRecommendationFeedbackEpic.js +18 -6
  178. package/lib/view/spendManagement/billPay/billDetailView/billDetailViewPayload.d.ts +19 -0
  179. package/lib/view/spendManagement/billPay/billDetailView/billDetailViewReducer.d.ts +10 -1
  180. package/lib/view/spendManagement/billPay/billDetailView/billDetailViewReducer.js +88 -2
  181. package/lib/view/spendManagement/billPay/billDetailView/billDetailViewSelector.d.ts +1 -0
  182. package/lib/view/spendManagement/billPay/billDetailView/billDetailViewSelector.js +1 -0
  183. package/lib/view/spendManagement/billPay/billDetailView/billDetailViewState.d.ts +7 -0
  184. package/lib/view/spendManagement/billPay/billDetailView/billDetailViewState.js +4 -0
  185. package/lib/view/spendManagement/billPay/billDetailView/epics/fetchBillRecommendationsEpic.d.ts +37 -0
  186. package/lib/view/spendManagement/billPay/billDetailView/epics/fetchBillRecommendationsEpic.js +104 -0
  187. package/lib/view/spendManagement/billPay/billDetailView/epics/fetchRecommendationsForBill.d.ts +24 -0
  188. package/lib/view/spendManagement/billPay/billDetailView/epics/fetchRecommendationsForBill.js +92 -0
  189. package/lib/view/spendManagement/billPay/editBillView/editBillViewReducer.d.ts +4 -1
  190. package/lib/view/spendManagement/billPay/editBillView/editBillViewReducer.js +19 -2
  191. package/lib/view/spendManagement/billPay/editBillView/editBillViewSelector.d.ts +1 -0
  192. package/lib/view/spendManagement/billPay/editBillView/editBillViewSelector.js +3 -0
  193. package/lib/view/spendManagement/billPay/editBillView/epics/applyBillRecommendationsToLocalStoreEpic.d.ts +18 -0
  194. package/lib/view/spendManagement/billPay/editBillView/epics/applyBillRecommendationsToLocalStoreEpic.js +102 -0
  195. package/package.json +2 -2
@@ -102,32 +102,27 @@ const expenseAutomationReconciliationView = createSlice({
102
102
  draft.selectedTab = 'review';
103
103
  draft.selectedAccountId = action.payload.accountId;
104
104
  }
105
- const accountReconciliationRecords = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(action.payload.selectedPeriod)];
106
- const { transactionFetchState } = accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId];
107
- if (transactionFetchState.fetchState !== 'Completed' ||
105
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(action.payload.selectedPeriod)]?.reconciliationByAccountID[action.payload.accountId];
106
+ if (accountRecon == null) {
107
+ return;
108
+ }
109
+ if (accountRecon.transactionFetchState.fetchState !== 'Completed' ||
108
110
  action.payload.cacheOverride === true) {
109
- const data = accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId];
110
111
  if (action.payload.refreshViewInBackground === true) {
111
- accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId] = {
112
- ...data,
113
- refreshStatus: {
114
- fetchState: 'In-Progress',
115
- error: undefined,
116
- },
117
- accountId: action.payload.accountId,
118
- reconciliationId: undefined,
112
+ accountRecon.refreshStatus = {
113
+ fetchState: 'In-Progress',
114
+ error: undefined,
119
115
  };
116
+ accountRecon.accountId = action.payload.accountId;
117
+ accountRecon.reconciliationId = undefined;
120
118
  }
121
119
  else {
122
- accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId] = {
123
- ...data,
124
- transactionFetchState: {
125
- fetchState: 'In-Progress',
126
- error: undefined,
127
- },
128
- accountId: action.payload.accountId,
129
- reconciliationId: undefined,
120
+ accountRecon.transactionFetchState = {
121
+ fetchState: 'In-Progress',
122
+ error: undefined,
130
123
  };
124
+ accountRecon.accountId = action.payload.accountId;
125
+ accountRecon.reconciliationId = undefined;
131
126
  }
132
127
  }
133
128
  }
@@ -179,14 +174,17 @@ const expenseAutomationReconciliationView = createSlice({
179
174
  }
180
175
  }
181
176
  else {
182
- const accountReconciliationRecords = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(action.payload.selectedPeriod)];
177
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(action.payload.selectedPeriod)]?.reconciliationByAccountID[action.payload.accountId];
178
+ if (accountRecon == null) {
179
+ return;
180
+ }
183
181
  if (action.payload.refreshViewInBackground === true) {
184
- accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId].refreshStatus.error = action.payload.error;
185
- accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId].refreshStatus.fetchState = 'Error';
182
+ accountRecon.refreshStatus.error = action.payload.error;
183
+ accountRecon.refreshStatus.fetchState = 'Error';
186
184
  }
187
185
  else {
188
- accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId].transactionFetchState.error = action.payload.error;
189
- accountReconciliationRecords.reconciliationByAccountID[action.payload.accountId].transactionFetchState.fetchState = 'Error';
186
+ accountRecon.transactionFetchState.error = action.payload.error;
187
+ accountRecon.transactionFetchState.fetchState = 'Error';
190
188
  }
191
189
  }
192
190
  },
@@ -391,8 +389,11 @@ const expenseAutomationReconciliationView = createSlice({
391
389
  },
392
390
  setStatementParseInProgress: (draft, action) => {
393
391
  const { accountId, selectedPeriod, statementParseInProgress } = action.payload;
394
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].statementParseInProgress =
395
- statementParseInProgress;
392
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
393
+ if (accountRecon == null) {
394
+ return;
395
+ }
396
+ accountRecon.statementParseInProgress = statementParseInProgress;
396
397
  },
397
398
  setStatementProcessingDismissedToBackground: (draft, action) => {
398
399
  const { accountId, selectedPeriod, dismissedToBackground } = action.payload;
@@ -476,15 +477,23 @@ const expenseAutomationReconciliationView = createSlice({
476
477
  },
477
478
  uploadAccountStatementSuccess: (draft, action) => {
478
479
  const { accountId, selectedPeriod } = action.payload;
479
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].statementUploadStatus = {
480
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
481
+ if (accountRecon == null) {
482
+ return;
483
+ }
484
+ accountRecon.statementUploadStatus = {
480
485
  fetchState: 'Completed',
481
486
  error: undefined,
482
487
  };
483
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].statementParseInProgress = true;
488
+ accountRecon.statementParseInProgress = true;
484
489
  },
485
490
  uploadAccountStatementFailure: (draft, action) => {
486
491
  const { accountId, selectedPeriod } = action.payload;
487
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].statementUploadStatus = {
492
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
493
+ if (accountRecon == null) {
494
+ return;
495
+ }
496
+ accountRecon.statementUploadStatus = {
488
497
  fetchState: 'Error',
489
498
  error: action.payload.error,
490
499
  };
@@ -677,23 +686,34 @@ const expenseAutomationReconciliationView = createSlice({
677
686
  },
678
687
  reparseStatementFailure: (draft, action) => {
679
688
  const { accountId, selectedPeriod } = action.payload;
680
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].reparseStatementStatus = {
689
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
690
+ if (accountRecon == null) {
691
+ return;
692
+ }
693
+ accountRecon.reparseStatementStatus = {
681
694
  fetchState: 'Error',
682
695
  error: action.payload.error,
683
696
  };
684
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].statementProcessingFailed = true;
697
+ accountRecon.statementProcessingFailed = true;
685
698
  },
686
699
  resetReparseStatementStatus: (draft, action) => {
687
700
  const { accountId, selectedPeriod } = action.payload;
688
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].reparseStatementStatus = {
701
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
702
+ if (accountRecon == null) {
703
+ return;
704
+ }
705
+ accountRecon.reparseStatementStatus = {
689
706
  fetchState: 'Not-Started',
690
707
  error: undefined,
691
708
  };
692
709
  },
693
710
  updateParsedStatementData: (draft, action) => {
694
711
  const { accountId, selectedPeriod, parsedStatementData } = action.payload;
695
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].parsedStatementData =
696
- parsedStatementData;
712
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
713
+ if (accountRecon == null) {
714
+ return;
715
+ }
716
+ accountRecon.parsedStatementData = parsedStatementData;
697
717
  },
698
718
  updateStatementUpdateLocalData: (draft, action) => {
699
719
  const { accountId, selectedPeriod, statementUpdateLocalData, statementUploadId, } = action.payload;
@@ -819,17 +839,21 @@ const expenseAutomationReconciliationView = createSlice({
819
839
  },
820
840
  updateAccountReconciliationLocalData: (draft, action) => {
821
841
  const { accountId, selectedPeriod, localData } = action.payload;
822
- const oldLocalData = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].localData;
842
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
843
+ if (accountRecon == null) {
844
+ return;
845
+ }
846
+ const oldLocalData = accountRecon.localData;
823
847
  if (oldLocalData == null) {
824
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].localData = localData;
848
+ accountRecon.localData = localData;
825
849
  }
826
850
  else {
827
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].localData = {
851
+ accountRecon.localData = {
828
852
  ...oldLocalData,
829
853
  ...localData,
830
854
  };
831
855
  }
832
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId].localData = {
856
+ accountRecon.localData = {
833
857
  ...oldLocalData,
834
858
  ...localData,
835
859
  };
@@ -1119,7 +1143,10 @@ function updateAllReconciliation(draft, accounts, selectedPeriod, reconciliation
1119
1143
  }
1120
1144
  }
1121
1145
  function updateReconciliationByAccountID(draft, accounts, selectedPeriod, accountId, reconciliationData, refreshViewInBackground) {
1122
- const oldRecord = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId];
1146
+ const accountRecon = draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)]?.reconciliationByAccountID[accountId];
1147
+ if (accountRecon == null) {
1148
+ return;
1149
+ }
1123
1150
  const oldConnectionStatus = draft.accountConnectionStatusByAccountID[accountId];
1124
1151
  draft.accountConnectionStatusByAccountID[accountId] = {
1125
1152
  status: toBankStatusCodeType(reconciliationData.bank_status.code),
@@ -1129,21 +1156,17 @@ function updateReconciliationByAccountID(draft, accounts, selectedPeriod, accoun
1129
1156
  : false,
1130
1157
  paymentAccountId: accounts.payment_account_id ?? undefined,
1131
1158
  };
1132
- const statementParseInProgress = resolveStatementParseInProgressAfterFetch(oldRecord, reconciliationData);
1159
+ const statementParseInProgress = resolveStatementParseInProgressAfterFetch(accountRecon, reconciliationData);
1160
+ accountRecon.reconciliationId =
1161
+ reconciliationData.reconciliation_id ?? undefined;
1162
+ accountRecon.statementParseInProgress = statementParseInProgress;
1133
1163
  if (refreshViewInBackground === true) {
1134
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId] = {
1135
- ...oldRecord,
1136
- reconciliationId: reconciliationData.reconciliation_id ?? undefined,
1137
- refreshStatus: { fetchState: 'Completed', error: undefined },
1138
- statementParseInProgress,
1139
- };
1164
+ accountRecon.refreshStatus = { fetchState: 'Completed', error: undefined };
1140
1165
  }
1141
1166
  else {
1142
- draft.accountReconciliationRecordsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)].reconciliationByAccountID[accountId] = {
1143
- ...oldRecord,
1144
- statementParseInProgress,
1145
- reconciliationId: reconciliationData.reconciliation_id ?? undefined,
1146
- transactionFetchState: { fetchState: 'Completed', error: undefined },
1167
+ accountRecon.transactionFetchState = {
1168
+ fetchState: 'Completed',
1169
+ error: undefined,
1147
1170
  };
1148
1171
  }
1149
1172
  }
@@ -1173,7 +1196,11 @@ const updateExcludedAccountExclusionInfo = (draft, account) => {
1173
1196
  };
1174
1197
  };
1175
1198
  const updateReconciliationByAccountIDOnIncludeAccount = (draft, accountId, reconciliation, monthYearPeriodId) => {
1176
- draft.accountReconciliationRecordsBySelectedPeriod[monthYearPeriodId].reconciliationByAccountID[accountId] = {
1199
+ const accountReconciliationRecords = draft.accountReconciliationRecordsBySelectedPeriod[monthYearPeriodId];
1200
+ if (accountReconciliationRecords == null) {
1201
+ return;
1202
+ }
1203
+ accountReconciliationRecords.reconciliationByAccountID[accountId] = {
1177
1204
  accountId,
1178
1205
  reconciliationId: reconciliation.reconciliation_id ?? undefined,
1179
1206
  transactionFetchState: { fetchState: 'Not-Started', error: undefined },
@@ -373,6 +373,16 @@ const expenseAutomationTransactionsView = createSlice({
373
373
  if (cached != null) {
374
374
  autoCat.uiState.pageToken = cached.pageToken;
375
375
  autoCat.uiState.totalCount = { ...cached.totalCount };
376
+ // Keep the stored badge in step with the restored sub-tab: the selector
377
+ // falls back to it for any period this sub-tab has not loaded. Merged,
378
+ // not replaced -- the snapshot only holds periods this sub-tab actually
379
+ // loaded, while the badge map also carries periods filled from a Review
380
+ // response's `other_tab_total_count`. Overwriting would drop those and
381
+ // leave the badge at 0 for any period this sub-tab never visited.
382
+ draft.parentTotalCountByTab.autoCategorized = {
383
+ ...draft.parentTotalCountByTab.autoCategorized,
384
+ ...cached.totalCount,
385
+ };
376
386
  autoCat.uiState.scrollPosition = cached.scrollPosition;
377
387
  autoCat.transactionIdsBySelectedPeriod = {
378
388
  ...cached.transactionIdsBySelectedPeriod,
@@ -333,6 +333,7 @@ function toActualMatchingMatchView(match) {
333
333
  : match.leftovers.map(toMatchModalAccrual),
334
334
  };
335
335
  }
336
+ const EMPTY_ACTUAL_MATCHING_ROWS = [];
336
337
  export function getExpenseAutomationActualMatchingMatchView(state, matchId) {
337
338
  const view = state.expenseAutomationActualMatchingViewState;
338
339
  const match = getActualMatchingMatchById(state.actualMatchingState, matchId);
@@ -353,6 +354,9 @@ export function getCloseMatchAfterIgnoreMatchId(state) {
353
354
  return state.expenseAutomationActualMatchingViewState
354
355
  .closeMatchAfterIgnoreMatchId;
355
356
  }
357
+ export function getActualMatchingMatchModalConfirmStatus(state) {
358
+ return state.expenseAutomationActualMatchingViewState.matchModal.confirmStatus;
359
+ }
356
360
  export function getExpenseAutomationActualMatchingView(state) {
357
361
  const { expenseAutomationActualMatchingViewState } = state;
358
362
  const { matchIdsByPeriod, fetchState, error, refreshStatus, uiState, matchModal, ignoreStatusByMatchId, undoReversalStatusByMatchId, } = expenseAutomationActualMatchingViewState;
@@ -369,10 +373,10 @@ export function getExpenseAutomationActualMatchingView(state) {
369
373
  const includeReversed = uiState.statusFilter !== 'pending_review';
370
374
  const reversed = includeReversed
371
375
  ? sortActualMatchingRows(rows.filter((row) => isReversedMatchKind(row.kind)), uiState)
372
- : [];
376
+ : EMPTY_ACTUAL_MATCHING_ROWS;
373
377
  const pendingReview = includePendingReview
374
378
  ? sortActualMatchingRows(rows.filter((row) => row.kind === 'pending_review'), uiState)
375
- : [];
379
+ : EMPTY_ACTUAL_MATCHING_ROWS;
376
380
  const kpiSummary = buildActualMatchingKpiSummary(allRows, getTenantDisplayCurrency(state)?.currencyCode);
377
381
  const searchedAccruals = matchModal.accrualSearch.results
378
382
  .map((key) => getJEOneTimeAccrualByKey(key, state.jeSchedulesState))
@@ -210,7 +210,7 @@ export function getExpenseAutomationJESchedulesView(state) {
210
210
  const accruedSchedules = [];
211
211
  const oneTimeAccruals = [];
212
212
  const { accountState, accountListState, classState, classListState, expenseAutomationJESchedulesViewState, expenseAutomationViewState, monthEndCloseChecksState, } = state;
213
- const { jeScheduleTransactionKeysByPeriodAndTab, jeScheduleAccruedKeysByPeriodAndTab, jeScheduleOneTimeAccrualKeysByPeriodAndTab, failedJeScheduleTransactionKeysByPeriodAndTab, jeScheduleLocalDataById, postStatusById, ignoreStatusById, revertStatusById, oneTimeAccrualActionStatusById, uiState, error, fetchState, refreshStatus, newSchedule, } = expenseAutomationJESchedulesViewState;
213
+ const { jeScheduleTransactionKeysByPeriodAndTab, jeScheduleAccruedKeysByPeriodAndTab, jeScheduleOneTimeAccrualKeysByPeriodAndTab, failedJePostings, jeScheduleLocalDataById, postStatusById, ignoreStatusById, revertStatusById, oneTimeAccrualActionStatusById, uiState, error, fetchState, refreshStatus, newSchedule, } = expenseAutomationJESchedulesViewState;
214
214
  const accountSettingsView = getJEAccountSettingsView(state);
215
215
  const newScheduleView = newSchedule.selectedDetailScheduleType === 'fixed_assets' ||
216
216
  newSchedule.selectedDetailScheduleType === 'prepaid_expenses'
@@ -249,15 +249,11 @@ export function getExpenseAutomationJESchedulesView(state) {
249
249
  const jeScheduleTransactionKeysForSelectedTab = selectedPeriodTabKey != null
250
250
  ? jeScheduleTransactionKeysByPeriodAndTab[selectedPeriodTabKey]
251
251
  : undefined;
252
- const failedJeScheduleTransactionKeysForSelectedTab = selectedPeriodTabKey != null
253
- ? failedJeScheduleTransactionKeysByPeriodAndTab[selectedPeriodTabKey]
254
- : undefined;
255
252
  // Distinguishes "this tab has never been fetched for this period" from "fetched and genuinely
256
253
  // empty". Without it the page cannot tell a first visit from a real empty state, and renders an
257
254
  // empty table where it should render a skeleton.
258
255
  const hasLoadedForSelectedTab = jeScheduleTransactionKeysForSelectedTab != null;
259
256
  const jeScheduleTransactionKeys = jeScheduleTransactionKeysForSelectedTab ?? [];
260
- const failedJeScheduleTransactionKeys = failedJeScheduleTransactionKeysForSelectedTab ?? [];
261
257
  const jeScheduleAccruedKeys = (selectedPeriodTabKey != null
262
258
  ? jeScheduleAccruedKeysByPeriodAndTab[selectedPeriodTabKey]
263
259
  : undefined) ?? [];
@@ -280,24 +276,57 @@ export function getExpenseAutomationJESchedulesView(state) {
280
276
  accruedSchedules.push(accruedScheduleDetails);
281
277
  }
282
278
  });
283
- failedJeScheduleTransactionKeys.forEach((jeScheduleKey) => {
284
- const jeScheduleDetails = getJEScheduledTransactionByJEScheduleTransactionKey(jeScheduleKey, state);
285
- failedJeScheduledTransaction.push(jeScheduleDetails);
279
+ // Sourced from the tenant-wide bucket, not the per-period one: a failed posting belongs to no
280
+ // close month, and windowing it is what kept older failures off the only tab that can resolve
281
+ // them.
282
+ failedJePostings.jeScheduleTransactionKeys.forEach((jeScheduleKey) => {
283
+ // getJEScheduledTransactionByJEScheduleTransactionKey throws three different ways: it
284
+ // destructures the result of recordGet without a null check, and it raises explicitly for a
285
+ // missing vendor and for a missing base transaction. Catching rather than pre-checking each
286
+ // one keeps this correct if a fourth is added, and none of the three is recoverable here --
287
+ // the row cannot be drawn either way.
288
+ //
289
+ // This matters more for this bucket than anywhere else on the page. It is tenant-wide, so it
290
+ // names schedules the close-month list never loaded, and the group is defined by deleted
291
+ // vendors and deleted categories -- precisely the rows whose entities are most likely to be
292
+ // missing. Unguarded, one such key takes down the whole JE tab instead of dropping one row.
293
+ try {
294
+ failedJeScheduledTransaction.push(getJEScheduledTransactionByJEScheduleTransactionKey(jeScheduleKey, state));
295
+ }
296
+ catch {
297
+ // Intentionally swallowed: a failed posting we cannot resolve is one missing row, and the
298
+ // alternative is a blank page for every other row too.
299
+ }
286
300
  });
301
+ // What the endpoint last reported as failing. The entity store keeps every month a schedule
302
+ // has and an entry resolved by someone else keeps its old `error` array, so `error` alone would
303
+ // leave a Retry button on a posting that is no longer failing.
304
+ const failedScheduledJournalEntryIDs = new Set(failedJePostings.scheduledJournalEntryIDs);
287
305
  const { sortKey, sortOrder, selectedJETypeFilter } = uiState;
306
+ const matchesTypeFilter = (schedule) => selectedJETypeFilter == null ||
307
+ getJEScheduleRowType(schedule) === selectedJETypeFilter;
288
308
  const unsortedResolveSchedules = [];
289
309
  failedJeScheduledTransaction.forEach((schedule) => {
290
- schedule.scheduledJournalEntry.forEach((journalEntry) => {
310
+ // Only the entries that actually carry an error. The schedule's entry array is shared state:
311
+ // opening a schedule's detail page or Quick View replaces it with every month the schedule
312
+ // has, posted ones included, so fanning out over all of them turned one failed posting into a
313
+ // row per month -- and a month that never failed has nothing to resolve, so it rendered with
314
+ // an empty action column.
315
+ //
316
+ // Intersected with the ids the endpoint actually returned, not just `error`: the entry array
317
+ // is a cache of every month, and an entry whose failure someone else resolved keeps its stale
318
+ // error array until something overwrites it. Only what the last fetch named is still failing.
319
+ schedule.scheduledJournalEntry
320
+ .filter((journalEntry) => journalEntry.error.length > 0 &&
321
+ failedScheduledJournalEntryIDs.has(journalEntry.scheduledJournalEntryID))
322
+ .forEach((journalEntry) => {
291
323
  unsortedResolveSchedules.push({
292
324
  ...schedule,
293
325
  scheduledJournalEntry: journalEntry,
294
326
  });
295
327
  });
296
328
  });
297
- const resolveSchedules = sortJEScheduledTransactions(unsortedResolveSchedules, sortKey, sortOrder);
298
- const validResolveForPending = unsortedResolveSchedules.filter((r) => jeScheduleLocalDataById[r.scheduledJournalEntry.scheduledJournalEntryID] != null);
299
- const matchesTypeFilter = (schedule) => selectedJETypeFilter == null ||
300
- getJEScheduleRowType(schedule) === selectedJETypeFilter;
329
+ const resolveSchedules = sortJEScheduledTransactions(unsortedResolveSchedules.filter(matchesTypeFilter), sortKey, sortOrder);
301
330
  const isCreatedStatus = (schedule) => {
302
331
  // An accrual is "created" once it is not review work: posted, reversing, reversed, or in a
303
332
  // transient posting claim.
@@ -315,7 +344,6 @@ export function getExpenseAutomationJESchedulesView(state) {
315
344
  ];
316
345
  const pendingReviewSchedules = sortJEScheduledTransactions([
317
346
  ...allScheduleRows.filter((schedule) => isPendingReviewRow(schedule) && matchesTypeFilter(schedule)),
318
- ...validResolveForPending,
319
347
  ], sortKey, sortOrder);
320
348
  const createdSchedules = sortJEScheduledTransactions(allScheduleRows.filter((schedule) => isCreatedStatus(schedule) && matchesTypeFilter(schedule)), sortKey, sortOrder);
321
349
  // KPI summary — derived from unfiltered schedules so counts are always accurate
@@ -434,6 +462,13 @@ export function getExpenseAutomationJESchedulesView(state) {
434
462
  kpiSummary,
435
463
  pendingReviewSchedules,
436
464
  pendingReviewTotal,
465
+ failedPostings: resolveSchedules,
466
+ // Exposed so a consumer can tell "still loading" and "the lookup failed" from "nothing has
467
+ // failed". The bucket keeps its rows on failure precisely so an error can be shown beside
468
+ // them, which is only possible if the state travels with them.
469
+ failedPostingsFetchState: failedJePostings.fetchState,
470
+ // Deprecated alias of failedPostings. Still read by the section's footer-form effect and by
471
+ // app-ui analytics; remove once both are repointed.
437
472
  resolveSchedules: resolveSchedules,
438
473
  accountSettingsView,
439
474
  newScheduleView,
@@ -1,6 +1,6 @@
1
1
  import { createSelector } from '@reduxjs/toolkit';
2
2
  import omit from 'lodash/omit';
3
- import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
3
+ import { toMonthYearPeriodId, } from '../../../commonStateTypes/timePeriod';
4
4
  import { getCurrentTenant, getIsAccountingProjectsEnabled, } from '../../../entity/tenant/tenantSelector';
5
5
  import { getTransactionWithCOT } from '../../../entity/transaction/transactionHelper';
6
6
  import { getSupportedTransactionsByIds } from '../../../entity/transaction/transactionSelector';
@@ -172,18 +172,35 @@ export function getExpenseAutomationTransactionView(state) {
172
172
  review: monthYearPeriodId != null ? totalCountByReview[monthYearPeriodId] : 0,
173
173
  autoCategorized: monthYearPeriodId != null ? totalCountByAutoCat[monthYearPeriodId] : 0,
174
174
  };
175
- // Parent-tab badge counts. Sourced from `parent_tab_total_count` in the
176
- // listing response and used exclusively by the navbar / tab strip badges.
177
- // Independent of which Completed sub-tab is active so the badge stays
178
- // stable across sub-tab switches.
175
+ // Tab badge counts, used exclusively by the navbar / tab strip badges. Both
176
+ // tabs are counted under the filters applied to the list, so a badge always
177
+ // equals the number of rows that tab shows.
178
+ //
179
+ // The ACTIVE tab prefers its own list count: the two are the same number (the
180
+ // backend's parent_tab_total_count mirrors total_count), but `uiState.totalCount`
181
+ // is restored by every cache path that skips a fetch -- switching back to an
182
+ // already-loaded period, or to a cached Completed sub-tab -- whereas the stored
183
+ // badge is only written when a response lands, so on those paths it would keep
184
+ // showing whatever was fetched last.
185
+ //
186
+ // The `in` check is load-bearing, not defensive: `totalCount` is a per-period
187
+ // map typed as `Record<MonthYearPeriodId, number>`, so indexing it is typed
188
+ // `number` but is `undefined` for any period the active (sub-)tab has not
189
+ // loaded -- the window between switching to a fresh sub-tab and its response
190
+ // landing. Falling back to the stored badge there keeps a real number in the
191
+ // label instead of blanking it.
179
192
  const parentTotalCountByTab = expenseAutomationTransactionsViewState.parentTotalCountByTab;
193
+ const storedBadgeByTab = (tab) => monthYearPeriodId != null
194
+ ? (parentTotalCountByTab[tab][monthYearPeriodId] ?? 0)
195
+ : 0;
196
+ const badgeForTab = (tab, listCountByPeriod) => selectedTransactionCategorizationTab === tab &&
197
+ monthYearPeriodId != null &&
198
+ monthYearPeriodId in listCountByPeriod
199
+ ? listCountByPeriod[monthYearPeriodId]
200
+ : storedBadgeByTab(tab);
180
201
  const parentTabTotalCountByTab = {
181
- review: monthYearPeriodId != null
182
- ? (parentTotalCountByTab.review[monthYearPeriodId] ?? 0)
183
- : 0,
184
- autoCategorized: monthYearPeriodId != null
185
- ? (parentTotalCountByTab.autoCategorized[monthYearPeriodId] ?? 0)
186
- : 0,
202
+ review: badgeForTab('review', totalCountByReview),
203
+ autoCategorized: badgeForTab('autoCategorized', totalCountByAutoCat),
187
204
  };
188
205
  const fetchStateByTab = {
189
206
  review: {
@@ -1,8 +1,35 @@
1
1
  import { getAddressByAddressId } from '../../../entity/address/addressSelector';
2
+ import { invoicingPricePointDisplayAmount } from '../../../entity/invoicing/plan/planSelector';
2
3
  import { getInvoicingCatalogListView, getInvoicingCatalogPrices, } from '../invoicingCatalogListView/invoicingCatalogListViewSelector';
3
4
  import { invoicingFieldHelpForGroup } from '../invoicingConfigView/invoicingConfigViewSelector';
4
5
  import { getInvoicingCustomerListView } from '../invoicingCustomerListView/invoicingCustomerListViewSelector';
5
6
  import { getInvoicingSettings } from '../settingsView/settingsViewSelector';
7
+ /**
8
+ * Pricing models whose single-unit amount is a true per-unit rate — the only
9
+ * ones a Create Invoice line may be prefilled from. Typed against the entity
10
+ * union so a misspelt member fails to compile; a model added to the union
11
+ * still has to be classified here by hand (it is left out, so it fails closed:
12
+ * no prefill). The complement is `isTieredCatalogPricingModel`.
13
+ */
14
+ const PER_UNIT_RATE_PRICING_MODELS = [
15
+ 'per_unit',
16
+ 'flat_fee',
17
+ ];
18
+ /**
19
+ * Whether a price point's single-unit amount is a per-unit rate. A missing
20
+ * model means per-unit: the service defaults `pricing_model` to `per_unit`
21
+ * and older records may omit it.
22
+ */
23
+ const hasPerUnitRate = (pricingModel) => pricingModel == null ||
24
+ pricingModel === '' ||
25
+ PER_UNIT_RATE_PRICING_MODELS.some((model) => model === pricingModel);
26
+ /**
27
+ * The amount to prefill on a Create Invoice line for a price point, or
28
+ * `undefined` when there is no safe per-unit rate to prefill.
29
+ */
30
+ const planNamePrefillPrice = (pricePoint) => hasPerUnitRate(pricePoint.pricingModel)
31
+ ? invoicingPricePointDisplayAmount(pricePoint)?.amount
32
+ : undefined;
6
33
  /**
7
34
  * Display name for a customer option: company, else the full name, else the
8
35
  * email, else the raw id.
@@ -47,7 +74,7 @@ export const getCreateInvoiceFormView = (state) => {
47
74
  .filter((subFamily) => (subFamily.name ?? '') !== '')
48
75
  .map((subFamily) => ({
49
76
  planName: plan.name ?? '',
50
- price: subFamily.price?.amount,
77
+ price: planNamePrefillPrice(subFamily),
51
78
  subFamilyName: subFamily.name ?? '',
52
79
  }))),
53
80
  products: getInvoicingCatalogPrices(catalogListView.products),
@@ -3,7 +3,6 @@ import { toClassBasePayload } from '../../../entity/class/classPayload';
3
3
  import { blankInvoicingCatalogSubFamily, isTieredCatalogPricingModel, } from './invoicingCatalogItemFormHelper';
4
4
  const CURRENCY_CODE = 'USD';
5
5
  const trimOrUndefined = (value) => value.trim() !== '' ? value.trim() : undefined;
6
- const trimOrNull = (value) => value.trim() !== '' ? value.trim() : null;
7
6
  const toProductCode = (value) => value
8
7
  .trim()
9
8
  .toLowerCase()
@@ -91,13 +90,24 @@ const buildSubFamilyPayload = (localData, subFamily, clearEmptyValues, lookup) =
91
90
  /** `unit` is the item's unit of measure, not a currency; `CURRENCY_CODE` above prices Price points. */
92
91
  const DEFAULT_CATALOG_ITEM_UNIT = 'USD';
93
92
  /**
94
- * Build the parent catalog-item request body from the form draft. Creates omit
95
- * both parent and price point IDs; edits retain returned price point IDs.
93
+ * Build the parent catalog-item request body from the form draft. Every field
94
+ * the form holds is sent, so a value cleared on the form is cleared on the
95
+ * item: the service reads an absent key as "leave it alone", which is why
96
+ * `description` travels as an empty string and `applicable_items` as an empty
97
+ * list rather than being dropped. Fields the form does not collect
98
+ * (`external_name`, `item_family_id`, `metadata`, …) stay off the body so an
99
+ * edit cannot overwrite them with a value nobody chose.
96
100
  */
97
101
  export const localDataToSaveInvoicingCatalogItemPayload = (localData, isCreate, lookup) => {
98
- const description = trimOrUndefined(localData.description);
99
102
  const productCode = trimOrUndefined(localData.productCode) ?? toProductCode(localData.name);
100
103
  const itemPayload = {
104
+ // `applicable_items` only means anything while the item is restricted;
105
+ // sending the empty list otherwise is what clears a restriction that was
106
+ // just lifted.
107
+ applicable_items: localData.itemApplicability === 'restricted'
108
+ ? localData.applicableItems
109
+ : [],
110
+ description: trimOrUndefined(localData.description) ?? '',
101
111
  enabled_for_checkout: true,
102
112
  enabled_in_portal: true,
103
113
  is_giftable: false,
@@ -107,41 +117,63 @@ export const localDataToSaveInvoicingCatalogItemPayload = (localData, isCreate,
107
117
  name: localData.name.trim(),
108
118
  product_code: productCode,
109
119
  type: localData.catalogType,
120
+ // The form no longer collects a unit; an item that already has one keeps it.
121
+ unit: trimOrUndefined(localData.unit) ?? DEFAULT_CATALOG_ITEM_UNIT,
110
122
  };
111
- if (description != null) {
112
- itemPayload.description = description;
113
- }
114
- // The form no longer collects a unit; an item that already has one keeps it.
115
- itemPayload.unit =
116
- trimOrUndefined(localData.unit) ?? DEFAULT_CATALOG_ITEM_UNIT;
117
- if (localData.itemApplicability === 'restricted' &&
118
- localData.applicableItems.length > 0) {
119
- itemPayload.applicable_items = localData.applicableItems;
123
+ // The external id is assigned by the billing service and shown read-only, so
124
+ // it is echoed back only when the item already has one: sending the blank a
125
+ // create starts with would clear an id assigned in the meantime.
126
+ const externalId = trimOrUndefined(localData.externalId);
127
+ if (externalId != null) {
128
+ itemPayload.external_id = externalId;
120
129
  }
121
130
  const subFamilies = localData.subFamilies.length > 0
122
131
  ? localData.subFamilies
123
132
  : [blankInvoicingCatalogSubFamily()];
124
- itemPayload.sub_family_details = isCreate
125
- ? subFamilies.map((subFamily) => buildSubFamilyPayload(localData, subFamily, false, lookup))
126
- : subFamilies.map((subFamily) => {
127
- const priceCode = trimOrUndefined(subFamily.priceCode) ?? toProductCode(subFamily.name);
128
- const detail = {
129
- description: trimOrNull(subFamily.description),
130
- invoice_notes: trimOrNull(subFamily.invoiceNotes),
131
- name: subFamily.name.trim(),
132
- price_code: priceCode,
133
- show_description_in_invoices: subFamily.showDescriptionInInvoices,
134
- };
135
- // Edits send a narrower body than creates, but the accounting mapping
136
- // is editable on this form, so leaving it out dropped the account and
137
- // class the user had just picked. `null` rather than omitted when it is
138
- // empty: an absent key on an update reads as "leave it alone", which
139
- // would make a cleared mapping impossible to save — the same reason
140
- // `description` and `invoice_notes` go through `trimOrNull`.
141
- detail.accounting_detail =
142
- buildAccountingDetail(subFamily, lookup) ?? null;
143
- const id = trimOrUndefined(subFamily.id);
144
- return id != null ? { ...detail, id } : detail;
145
- });
133
+ if (isCreate) {
134
+ itemPayload.sub_families = subFamilies.map((subFamily) => buildSubFamilyPayload(localData, subFamily, false, lookup));
135
+ return itemPayload;
136
+ }
137
+ // Only price points the service has already stored travel in an update: it
138
+ // addresses them by id, and a blank one has nothing to address. A price point
139
+ // added on the form is created by its own request once the item is saved,
140
+ // rather than riding along as a row the update cannot place.
141
+ itemPayload.sub_families = subFamilies
142
+ .filter((subFamily) => subFamily.id !== '')
143
+ .map((subFamily) => {
144
+ // Edits send the same body a create does. The narrower hand-built one
145
+ // this replaces had to be kept in step by hand and was not: it dropped
146
+ // `accounting_detail` once, and `billing_cycles`, `price`,
147
+ // `pricing_model`, `period`, the trial fields, `proration_type`,
148
+ // `free_quantity`, `is_taxable` and `tiers` were never added at all, so
149
+ // editing any of them looked saved and changed nothing.
150
+ const detail = buildSubFamilyPayload(localData, subFamily, true, lookup);
151
+ // `null` rather than omitted when empty: an absent key on an update
152
+ // reads as "leave it alone", which would make a cleared mapping
153
+ // impossible to save — the same reason `description` and
154
+ // `invoice_notes` are cleared rather than dropped above.
155
+ detail.accounting_detail = detail.accounting_detail ?? null;
156
+ // Never blank: the filter above leaves only price points the service has
157
+ // already stored, which is the only kind an update can address.
158
+ return { ...detail, id: subFamily.id };
159
+ });
146
160
  return itemPayload;
147
161
  };
162
+ /**
163
+ * The bodies that create the price points added on an edit form, one per
164
+ * request to `/catalog/products`.
165
+ *
166
+ * An update cannot carry them: it addresses price points by id, and these have
167
+ * none until the service assigns one. They are bound to their item by
168
+ * `item_id`, so this needs the saved item's id and runs after the item itself
169
+ * is saved.
170
+ *
171
+ * Empty values are left off rather than sent as `null`: there is nothing on a
172
+ * price point that does not exist yet to clear.
173
+ */
174
+ export const localDataToCreateInvoicingCatalogProductPayloads = (localData, itemId, lookup) => localData.subFamilies
175
+ .filter((subFamily) => subFamily.id === '')
176
+ .map((subFamily) => ({
177
+ ...buildSubFamilyPayload(localData, subFamily, false, lookup),
178
+ item_id: itemId,
179
+ }));