@zeniai/client-epic-state 5.1.82 → 5.1.83-betaRD2

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 (144) hide show
  1. package/lib/entity/jeSchedules/jeScheduleHelper.d.ts +2 -0
  2. package/lib/entity/jeSchedules/jeScheduleHelper.js +7 -0
  3. package/lib/entity/jeSchedules/jeSchedulesPayload.d.ts +34 -1
  4. package/lib/entity/jeSchedules/jeSchedulesPayload.js +68 -0
  5. package/lib/entity/jeSchedules/jeSchedulesReducer.d.ts +4 -2
  6. package/lib/entity/jeSchedules/jeSchedulesReducer.js +26 -2
  7. package/lib/entity/jeSchedules/jeSchedulesSelector.d.ts +10 -2
  8. package/lib/entity/jeSchedules/jeSchedulesSelector.js +28 -3
  9. package/lib/entity/jeSchedules/jeSchedulesState.d.ts +41 -1
  10. package/lib/entity/jeSchedules/jeSchedulesTypes.d.ts +12 -1
  11. package/lib/entity/jeSchedules/jeSchedulesTypes.js +40 -1
  12. package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
  13. package/lib/entity/snackbar/snackbarTypes.js +29 -0
  14. package/lib/entity/tenant/clearAllEpic.d.ts +2 -2
  15. package/lib/entity/tenant/clearAllEpic.js +1 -0
  16. package/lib/entity/transaction/payloadTypes/scheduleTransactionPayload.d.ts +2 -0
  17. package/lib/entity/transaction/payloadTypes/scheduleTransactionPayload.js +2 -0
  18. package/lib/entity/transaction/stateTypes/scheduleTransaction.d.ts +2 -0
  19. package/lib/epic.d.ts +5 -1
  20. package/lib/epic.js +5 -1
  21. package/lib/esm/entity/jeSchedules/jeScheduleHelper.js +6 -0
  22. package/lib/esm/entity/jeSchedules/jeSchedulesPayload.js +68 -1
  23. package/lib/esm/entity/jeSchedules/jeSchedulesReducer.js +26 -2
  24. package/lib/esm/entity/jeSchedules/jeSchedulesSelector.js +26 -2
  25. package/lib/esm/entity/jeSchedules/jeSchedulesTypes.js +34 -0
  26. package/lib/esm/entity/snackbar/snackbarTypes.js +29 -0
  27. package/lib/esm/entity/tenant/clearAllEpic.js +2 -1
  28. package/lib/esm/entity/transaction/payloadTypes/scheduleTransactionPayload.js +2 -0
  29. package/lib/esm/epic.js +5 -1
  30. package/lib/esm/index.js +11 -5
  31. package/lib/esm/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +6 -3
  32. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.js +34 -3
  33. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/newSchedule/initializeNewScheduleEpic.js +21 -0
  34. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/newSchedule/saveNewScheduleEpic.js +110 -0
  35. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/newSchedule/searchTransactionsForNewScheduleEpic.js +73 -0
  36. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +22 -0
  37. package/lib/esm/view/expenseAutomationView/helpers/newScheduleLocalDataHelper.js +140 -0
  38. package/lib/esm/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +163 -28
  39. package/lib/esm/view/expenseAutomationView/selectorTypes/jeNewScheduleViewSelectorTypes.js +1 -0
  40. package/lib/esm/view/expenseAutomationView/selectors/jeNewScheduleViewSelector.js +70 -0
  41. package/lib/esm/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +260 -22
  42. package/lib/esm/view/expenseAutomationView/types/jeSchedulesViewState.js +5 -6
  43. package/lib/esm/view/invoicing/createCreditNote/createCreditNoteEpic.js +24 -4
  44. package/lib/esm/view/invoicing/createInvoice/createInvoiceEpic.js +24 -4
  45. package/lib/esm/view/invoicing/createInvoice/createInvoiceSelector.js +37 -10
  46. package/lib/esm/view/invoicing/discountAction/discountActionEpic.js +33 -9
  47. package/lib/esm/view/invoicing/dunningAction/dunningActionEpic.js +35 -9
  48. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/catalogDetailEditPayload.js +5 -1
  49. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +32 -5
  50. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.js +23 -3
  51. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.js +24 -4
  52. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +24 -4
  53. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/syncInvoicingSubscriptionCouponsEpic.js +20 -3
  54. package/lib/esm/view/invoicing/invoiceAction/invoiceActionEpic.js +60 -17
  55. package/lib/esm/view/invoicing/invoicingConfigView/invoicingConfigViewReducer.js +30 -0
  56. package/lib/esm/view/invoicing/invoicingConfigView/invoicingConfigViewSelector.js +4 -29
  57. package/lib/esm/view/invoicing/invoicingQboView/epics/retryInvoicingQboSyncTaskEpic.js +29 -10
  58. package/lib/esm/view/invoicing/invoicingQboView/epics/startInvoicingQboBackfillEpic.js +28 -5
  59. package/lib/esm/view/invoicing/issueCreditNote/issueCreditNoteEpic.js +24 -4
  60. package/lib/esm/view/invoicing/issueCreditNote/issueCreditNoteSelector.js +22 -14
  61. package/lib/esm/view/invoicing/recordPayment/recordPaymentEpic.js +46 -7
  62. package/lib/esm/view/invoicing/settingsView/settingsViewEpics.js +26 -8
  63. package/lib/esm/view/invoicing/settingsView/settingsViewReducer.js +1 -1
  64. package/lib/esm/view/invoicing/settingsView/settingsViewSelector.js +4 -1
  65. package/lib/esm/view/invoicing/subscriptionAction/subscriptionActionEpic.js +41 -9
  66. package/lib/index.d.ts +18 -13
  67. package/lib/index.js +72 -50
  68. package/lib/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +6 -3
  69. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.d.ts +2 -2
  70. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.js +32 -1
  71. package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/initializeNewScheduleEpic.d.ts +8 -0
  72. package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/initializeNewScheduleEpic.js +25 -0
  73. package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/saveNewScheduleEpic.d.ts +45 -0
  74. package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/saveNewScheduleEpic.js +114 -0
  75. package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/searchTransactionsForNewScheduleEpic.d.ts +8 -0
  76. package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/searchTransactionsForNewScheduleEpic.js +77 -0
  77. package/lib/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.d.ts +6 -0
  78. package/lib/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +26 -0
  79. package/lib/view/expenseAutomationView/helpers/newScheduleLocalDataHelper.d.ts +36 -0
  80. package/lib/view/expenseAutomationView/helpers/newScheduleLocalDataHelper.js +145 -0
  81. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
  82. package/lib/view/expenseAutomationView/payload/jeSchedulesPayload.d.ts +8 -1
  83. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.d.ts +45 -10
  84. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +164 -28
  85. package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.d.ts +1 -1
  86. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.d.ts +2 -2
  87. package/lib/view/expenseAutomationView/selectorTypes/jeNewScheduleViewSelectorTypes.d.ts +25 -0
  88. package/lib/view/expenseAutomationView/selectorTypes/jeNewScheduleViewSelectorTypes.js +2 -0
  89. package/lib/view/expenseAutomationView/selectorTypes/jeSchedulesSelectorTypes.d.ts +34 -8
  90. package/lib/view/expenseAutomationView/selectors/jeNewScheduleViewSelector.d.ts +10 -0
  91. package/lib/view/expenseAutomationView/selectors/jeNewScheduleViewSelector.js +75 -0
  92. package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.d.ts +10 -1
  93. package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +266 -21
  94. package/lib/view/expenseAutomationView/types/completedSubTab.d.ts +1 -1
  95. package/lib/view/expenseAutomationView/types/jeSchedulesViewState.d.ts +59 -8
  96. package/lib/view/expenseAutomationView/types/jeSchedulesViewState.js +8 -8
  97. package/lib/view/invoicing/createCreditNote/createCreditNoteEpic.d.ts +2 -1
  98. package/lib/view/invoicing/createCreditNote/createCreditNoteEpic.js +23 -3
  99. package/lib/view/invoicing/createInvoice/createInvoiceEpic.d.ts +2 -1
  100. package/lib/view/invoicing/createInvoice/createInvoiceEpic.js +23 -3
  101. package/lib/view/invoicing/createInvoice/createInvoiceSelector.d.ts +22 -8
  102. package/lib/view/invoicing/createInvoice/createInvoiceSelector.js +37 -10
  103. package/lib/view/invoicing/discountAction/discountActionEpic.d.ts +2 -1
  104. package/lib/view/invoicing/discountAction/discountActionEpic.js +32 -8
  105. package/lib/view/invoicing/dunningAction/dunningActionEpic.d.ts +2 -1
  106. package/lib/view/invoicing/dunningAction/dunningActionEpic.js +34 -8
  107. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/catalogDetailEditPayload.d.ts +3 -1
  108. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/catalogDetailEditPayload.js +5 -1
  109. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.d.ts +2 -1
  110. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +31 -4
  111. package/lib/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.d.ts +2 -1
  112. package/lib/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.js +22 -2
  113. package/lib/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.d.ts +2 -1
  114. package/lib/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.js +23 -3
  115. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.d.ts +2 -1
  116. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +23 -3
  117. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/syncInvoicingSubscriptionCouponsEpic.d.ts +2 -1
  118. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/syncInvoicingSubscriptionCouponsEpic.js +19 -2
  119. package/lib/view/invoicing/invoiceAction/invoiceActionEpic.d.ts +2 -1
  120. package/lib/view/invoicing/invoiceAction/invoiceActionEpic.js +59 -16
  121. package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewPayload.d.ts +13 -0
  122. package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewReducer.d.ts +1 -1
  123. package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewReducer.js +30 -0
  124. package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewSelector.d.ts +11 -31
  125. package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewSelector.js +4 -29
  126. package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewState.d.ts +15 -0
  127. package/lib/view/invoicing/invoicingQboView/epics/invoicingQboViewActionType.d.ts +2 -1
  128. package/lib/view/invoicing/invoicingQboView/epics/retryInvoicingQboSyncTaskEpic.js +28 -9
  129. package/lib/view/invoicing/invoicingQboView/epics/startInvoicingQboBackfillEpic.js +27 -4
  130. package/lib/view/invoicing/issueCreditNote/issueCreditNoteEpic.d.ts +2 -1
  131. package/lib/view/invoicing/issueCreditNote/issueCreditNoteEpic.js +23 -3
  132. package/lib/view/invoicing/issueCreditNote/issueCreditNoteSelector.d.ts +24 -7
  133. package/lib/view/invoicing/issueCreditNote/issueCreditNoteSelector.js +23 -16
  134. package/lib/view/invoicing/recordPayment/recordPaymentEpic.d.ts +2 -1
  135. package/lib/view/invoicing/recordPayment/recordPaymentEpic.js +45 -6
  136. package/lib/view/invoicing/settingsView/settingsViewEpics.d.ts +2 -1
  137. package/lib/view/invoicing/settingsView/settingsViewEpics.js +25 -7
  138. package/lib/view/invoicing/settingsView/settingsViewReducer.d.ts +10 -1
  139. package/lib/view/invoicing/settingsView/settingsViewReducer.js +1 -1
  140. package/lib/view/invoicing/settingsView/settingsViewSelector.d.ts +4 -2
  141. package/lib/view/invoicing/settingsView/settingsViewSelector.js +4 -1
  142. package/lib/view/invoicing/subscriptionAction/subscriptionActionEpic.d.ts +2 -1
  143. package/lib/view/invoicing/subscriptionAction/subscriptionActionEpic.js +40 -8
  144. package/package.json +1 -1
@@ -0,0 +1,70 @@
1
+ import { getAccountIdsForTypes } from '../../../entity/account/accountSelector';
2
+ import { getScheduleTransactionById } from '../../../entity/transaction/transactionSelector';
3
+ import { getAccountIdsForLabel, getAccountList, getNestedAccountListHierarchy, } from '../../accountList/accountListSelector';
4
+ import { getClassList, getNestedClassListHierarchy, } from '../../classList/classListSelector';
5
+ import { getAccountsTypesForScheduleDetails } from '../../scheduleView/scheduleDetailView/scheduleDetailLocalDataHelper';
6
+ import { filterNestedAccountHierarchyById } from '../../scheduleView/scheduleDetailView/scheduleDetailSelector';
7
+ import { getAccountLabelForScheduleType, getAccountsTypesForScheduleEpics, } from '../../scheduleView/scheduleListView/scheduleListHelper';
8
+ /**
9
+ * The Base Transaction search is shared across schedule types (one search
10
+ * box, not one per prepaid_expenses/fixed_assets) — split out from
11
+ * getNewScheduleView so a consumer can read it without supplying an
12
+ * otherwise-irrelevant scheduleType.
13
+ */
14
+ export const getNewScheduleSearchTransactionsView = (state) => {
15
+ const { searchTransactions } = state.expenseAutomationJESchedulesViewState.newSchedule;
16
+ const searchTransactionsResults = searchTransactions.searchResults
17
+ .map((key) => getScheduleTransactionById(state.transactionState, key))
18
+ .filter((transaction) => transaction != null);
19
+ return {
20
+ searchTransactionsResults,
21
+ searchTransactionsFetchState: {
22
+ fetchState: searchTransactions.fetchState,
23
+ error: searchTransactions.error,
24
+ },
25
+ searchTransactionsString: searchTransactions.searchString,
26
+ };
27
+ };
28
+ export const getNewScheduleView = (state, scheduleType) => {
29
+ const { accountState, accountListState, classState, classListState } = state;
30
+ const { newSchedule } = state.expenseAutomationJESchedulesViewState;
31
+ const searchTransactionsView = getNewScheduleSearchTransactionsView(state);
32
+ const accountType = getAccountsTypesForScheduleEpics();
33
+ const newScheduleAccountTypes = getAccountsTypesForScheduleDetails(scheduleType);
34
+ const newScheduleAccountIds = getAccountIdsForTypes(accountState, newScheduleAccountTypes);
35
+ const allAccountList = getAccountList(accountState, accountListState, accountType.accountListKey);
36
+ const filteredAccounts = allAccountList.accounts.filter((account) => newScheduleAccountIds.includes(account.accountId));
37
+ const accountListNestedAccountHierarchy = filterNestedAccountHierarchyById(getNestedAccountListHierarchy(accountListState, accountType.accountListKey), filteredAccounts.map((account) => account.accountId));
38
+ const allClassList = getClassList(classState, classListState);
39
+ const classListNestedAccountHierarchy = getNestedClassListHierarchy(classListState);
40
+ let allDepreciationAccountList;
41
+ let allDepreciationAccountListNestedAccountHierarchy;
42
+ if (scheduleType === 'fixed_assets') {
43
+ const depreciationCategoryIds = getAccountIdsForTypes(accountState, [
44
+ 'fixed_assets',
45
+ 'other_assets',
46
+ ]);
47
+ const accountLabel = getAccountLabelForScheduleType('fixed_assets');
48
+ const fixedAssetLabelAccountIds = accountLabel != null
49
+ ? getAccountIdsForLabel(accountListState, accountType.accountListKey, accountLabel)
50
+ : [];
51
+ allDepreciationAccountList = allAccountList.accounts.filter((account) => depreciationCategoryIds.includes(account.accountId) &&
52
+ !fixedAssetLabelAccountIds.includes(account.accountId));
53
+ allDepreciationAccountListNestedAccountHierarchy =
54
+ filterNestedAccountHierarchyById(getNestedAccountListHierarchy(accountListState, accountType.accountListKey), allDepreciationAccountList.map((account) => account.accountId));
55
+ }
56
+ return {
57
+ scheduleType,
58
+ accountListNestedAccountHierarchy,
59
+ allAccountList: filteredAccounts,
60
+ allClassList: allClassList.classes,
61
+ allDepreciationAccountList,
62
+ allDepreciationAccountListNestedAccountHierarchy,
63
+ classListNestedAccountHierarchy,
64
+ newScheduleLocalData: newSchedule.newScheduleState[scheduleType],
65
+ saveStatus: newSchedule.saveStatus,
66
+ ...searchTransactionsView,
67
+ fetchState: newSchedule.fetchState,
68
+ error: newSchedule.error,
69
+ };
70
+ };
@@ -3,7 +3,8 @@ import orderBy from 'lodash/orderBy';
3
3
  import { reduceAnyFetchState } from '../../../commonStateTypes/reduceFetchState';
4
4
  import { LAST_SCHEDULE_DAY_OF_MONTH, toMonthYearPeriodId, } from '../../../commonStateTypes/timePeriod';
5
5
  import { getAccountIdsForTypes } from '../../../entity/account/accountSelector';
6
- import { getJEScheduledTransactionByJEScheduleTransactionKey, } from '../../../entity/jeSchedules/jeSchedulesSelector';
6
+ import { getJEAccruedScheduleByJEScheduleKey, getJEOneTimeAccrualByKey, getJEScheduledTransactionByJEScheduleTransactionKey, } from '../../../entity/jeSchedules/jeSchedulesSelector';
7
+ import { ALL_SCHEDULES_TYPES, } from '../../../entity/jeSchedules/jeSchedulesTypes';
7
8
  import { getCurrentTenant } from '../../../entity/tenant/tenantSelector';
8
9
  import { getAccountIdsForLabel, getAccountList, getNestedAccountListHierarchy, getUncategorizedAccounts, } from '../../accountList/accountListSelector';
9
10
  import { getClassList, getNestedClassListHierarchy, } from '../../classList/classListSelector';
@@ -11,8 +12,118 @@ import { getAccountsTypesForScheduleDetails } from '../../scheduleView/scheduleD
11
12
  import { filterNestedAccountHierarchyById } from '../../scheduleView/scheduleDetailView/scheduleDetailSelector';
12
13
  import { getAccountLabelForScheduleType, getAccountsTypesForScheduleList, } from '../../scheduleView/scheduleListView/scheduleListHelper';
13
14
  import { getAllSteps } from '../selectorTypes/expenseAutomationViewSelectorTypes';
15
+ import { ALL_JE_PAGE_TABS, getJESchedulePeriodTabKey, } from '../types/jeSchedulesViewState';
14
16
  import { getJEAccountSettingsView } from './jeAccountSettingsViewSelector';
17
+ import { getNewScheduleView } from './jeNewScheduleViewSelector';
18
+ // Matches the AI-automation user addresses webc's schedule list row already
19
+ // checks (JEScheduleReadOnlyListRow/JEScheduleDraftListRow "Method" column,
20
+ // strings.ts aiBotEmail/botEmail) to determine whether a schedule was
21
+ // created by the AI accountant vs. a human.
22
+ const AI_BOT_EMAIL = 'ai@zeni.ai';
23
+ const BOT_EMAIL = 'bot@zeni.ai';
24
+ // A one-time accrual has no jeScheduleType of its own — it is not a JE schedule. It is still an
25
+ // accrued expense, so it counts and filters under that type on the page's type tabs.
26
+ const ONE_TIME_ACCRUAL_SCHEDULE_TYPE = 'accrued_expenses';
27
+ // Which of the two list buckets an accrual belongs in. `draft` still needs a human to post it and
28
+ // `posting_blocked` needs attention, so both are review work; everything else is already on the
29
+ // books or in a transient claim.
30
+ const ONE_TIME_ACCRUAL_REVIEW_STATUSES = [
31
+ 'draft',
32
+ 'posting_blocked',
33
+ ];
34
+ export function getJEScheduleRowType(schedule) {
35
+ return isOneTimeAccrualRow(schedule)
36
+ ? ONE_TIME_ACCRUAL_SCHEDULE_TYPE
37
+ : schedule.jeScheduleType;
38
+ }
39
+ export function isPendingReviewRow(schedule) {
40
+ return isOneTimeAccrualRow(schedule)
41
+ ? ONE_TIME_ACCRUAL_REVIEW_STATUSES.includes(schedule.accrualStatus)
42
+ : schedule.status.code === 'draft';
43
+ }
44
+ // Belongs on the Existing Schedules tab. For an accrual the endpoint has already filtered to
45
+ // posted, so this only has to agree with that rather than re-derive it.
46
+ export function isExistingTabRow(schedule) {
47
+ return isOneTimeAccrualRow(schedule)
48
+ ? schedule.accrualStatus === 'posted'
49
+ : schedule.status.code === 'ongoing';
50
+ }
51
+ // Exported (rather than kept as a closure inside the selector) so it can be
52
+ // unit-tested directly — confidenceScore is a separate, more narrowly
53
+ // populated field that can be null on a schedule the bot user created,
54
+ // which previously caused these schedules to display as "AI Accountant" in
55
+ // the list but count under "Manual" in the KPI summary.
56
+ export const isAiAccountantSchedule = (schedule) => {
57
+ // A one-time accrual records who made it as `origin`, which is authoritative — no email
58
+ // sniffing needed, and the JE bot is often absent from the user entity entirely.
59
+ if (isOneTimeAccrualRow(schedule)) {
60
+ return schedule.origin === 'agent';
61
+ }
62
+ return (schedule.createdByUser?.email === AI_BOT_EMAIL ||
63
+ schedule.createdByUser?.email === BOT_EMAIL);
64
+ };
65
+ // Accrued schedules are structurally distinct — no baseTransaction, carrying recurringAmount and
66
+ // memo in its place — so anything reading baseTransaction has to branch on this first.
67
+ //
68
+ // Discriminates on jeAccruedScheduleKey, which only JEAccruedSchedule carries. jeScheduleType
69
+ // cannot be used: it is a data field present on BOTH row shapes, so an accrued-typed row that
70
+ // arrived through the prepaid/fixed DTO (failed_je_postings, legacy fetches) would be
71
+ // misclassified and then crash on recurringAmount / jeAccruedScheduleKey.
72
+ export function isAccruedScheduleRow(schedule) {
73
+ return 'jeAccruedScheduleKey' in schedule;
74
+ }
75
+ // Same structural approach as isAccruedScheduleRow, and for the same reason: jeOneTimeAccrualKey
76
+ // exists only on this shape. Note the two guards are NOT interchangeable — a one-time accrual is
77
+ // not an accrued *schedule*, and routing it into the accrued branch would read recurringAmount,
78
+ // render postedCount/lifecycleTotal as a lifecycle and startDate–endDate as a period, none of
79
+ // which it has.
80
+ export function isOneTimeAccrualRow(schedule) {
81
+ return 'jeOneTimeAccrualKey' in schedule;
82
+ }
83
+ function getScheduleAmount(schedule) {
84
+ if (isOneTimeAccrualRow(schedule)) {
85
+ return schedule.estimatedAmount.amount;
86
+ }
87
+ if (isAccruedScheduleRow(schedule)) {
88
+ return schedule.recurringAmount.amount;
89
+ }
90
+ return schedule.baseTransaction.amount.amount;
91
+ }
92
+ // A one-time accrual shares almost no sortable field with a schedule, so it gets its own accessor
93
+ // rather than a guard on each of the dozen cases below. Keys with no meaning for a single-month
94
+ // accrual -- amortizationPeriod, remainingMonths, runningBalance, jePostingDate, scheduleCategory --
95
+ // deliberately fall through to undefined, which sorts them together at one end.
96
+ function getOneTimeAccrualSortValue(accrual, sortKey) {
97
+ switch (sortKey) {
98
+ case 'vendor':
99
+ return accrual.vendorName.toLowerCase();
100
+ case 'category':
101
+ return accrual.jeDebitAccountName?.toLowerCase();
102
+ case 'class':
103
+ return accrual.jeDebitAccountingClassName?.toLowerCase();
104
+ case 'depCategory':
105
+ return accrual.jeCreditAccountName?.toLowerCase();
106
+ case 'type':
107
+ return ONE_TIME_ACCRUAL_SCHEDULE_TYPE;
108
+ case 'totalAmount':
109
+ return accrual.estimatedAmount.amount;
110
+ case 'transactionDate':
111
+ return accrual.postingDate?.valueOf();
112
+ case 'memo':
113
+ return accrual.memo.toLowerCase();
114
+ case 'startMonth':
115
+ return accrual.serviceMonth;
116
+ default:
117
+ return undefined;
118
+ }
119
+ }
15
120
  function getJEScheduleSortAccessor(sortKey) {
121
+ const scheduleAccessor = getScheduleRowSortAccessor(sortKey);
122
+ return (schedule) => isOneTimeAccrualRow(schedule)
123
+ ? getOneTimeAccrualSortValue(schedule, sortKey)
124
+ : scheduleAccessor(schedule);
125
+ }
126
+ function getScheduleRowSortAccessor(sortKey) {
16
127
  switch (sortKey) {
17
128
  case 'vendor':
18
129
  return (s) => s.vendor.name?.toLowerCase();
@@ -23,7 +134,9 @@ function getJEScheduleSortAccessor(sortKey) {
23
134
  case 'depCategory':
24
135
  return (s) => s.jeCredit.account?.accountName?.toLowerCase();
25
136
  case 'scheduleCategory':
26
- return (s) => s.baseTransaction.account?.accountName?.toLowerCase();
137
+ return (s) => isAccruedScheduleRow(s)
138
+ ? undefined
139
+ : s.baseTransaction.account?.accountName?.toLowerCase();
27
140
  case 'startMonth':
28
141
  return (s) => s.startDate?.valueOf();
29
142
  case 'type':
@@ -51,11 +164,13 @@ function getJEScheduleSortAccessor(sortKey) {
51
164
  return s.balanceAsOfToday?.amount ?? null;
52
165
  };
53
166
  case 'totalAmount':
54
- return (s) => s.baseTransaction.amount.amount;
167
+ return (s) => getScheduleAmount(s);
55
168
  case 'transactionDate':
56
- return (s) => s.baseTransaction.date.valueOf();
169
+ return (s) => s.createTime?.valueOf();
57
170
  case 'memo':
58
- return (s) => s.baseTransaction.memo?.toLowerCase();
171
+ return (s) => isAccruedScheduleRow(s)
172
+ ? s.memo.toLowerCase()
173
+ : s.baseTransaction.memo?.toLowerCase();
59
174
  default:
60
175
  return (s) => s.startDate?.valueOf();
61
176
  }
@@ -67,9 +182,15 @@ function sortJEScheduledTransactions(transactions, sortKey, sortOrder) {
67
182
  export function getExpenseAutomationJESchedulesView(state) {
68
183
  const jeScheduledTransaction = [];
69
184
  const failedJeScheduledTransaction = [];
185
+ const accruedSchedules = [];
186
+ const oneTimeAccruals = [];
70
187
  const { accountState, accountListState, classState, classListState, expenseAutomationJESchedulesViewState, expenseAutomationViewState, monthEndCloseChecksState, monthEndCloseChecksViewState, } = state;
71
- const { jeScheduleTransactionKeysByPeriod, failedJeScheduleTransactionKeysByPeriod, jeScheduleLocalDataById, postStatusById, ignoreStatusById, uiState, error, fetchState, refreshStatus, } = expenseAutomationJESchedulesViewState;
188
+ const { jeScheduleTransactionKeysByPeriodAndTab, jeScheduleAccruedKeysByPeriodAndTab, jeScheduleOneTimeAccrualKeysByPeriodAndTab, failedJeScheduleTransactionKeysByPeriodAndTab, jeScheduleLocalDataById, postStatusById, ignoreStatusById, uiState, error, fetchState, refreshStatus, newSchedule, } = expenseAutomationJESchedulesViewState;
72
189
  const accountSettingsView = getJEAccountSettingsView(state);
190
+ const newScheduleView = newSchedule.selectedDetailScheduleType === 'fixed_assets' ||
191
+ newSchedule.selectedDetailScheduleType === 'prepaid_expenses'
192
+ ? getNewScheduleView(state, newSchedule.selectedDetailScheduleType)
193
+ : undefined;
73
194
  const currentTenant = getCurrentTenant(state);
74
195
  const { selectedPeriodByTenantId } = expenseAutomationViewState;
75
196
  const selectedPeriod = selectedPeriodByTenantId[currentTenant.tenantId];
@@ -97,27 +218,48 @@ export function getExpenseAutomationJESchedulesView(state) {
97
218
  const filteredDepAccountListNestedAccountHierarchy = filterNestedAccountHierarchyById(depAccountListNestedAccountHierarchy, filteredDepAccounts.map((account) => account.accountId));
98
219
  const classListNestedAccountHierarchy = getNestedClassListHierarchy(classListState);
99
220
  const monthYearPeriodId = selectedPeriod != null ? toMonthYearPeriodId(selectedPeriod) : null;
100
- const jeScheduleTransactionKeysForSelectedPeriod = monthYearPeriodId != null
101
- ? jeScheduleTransactionKeysByPeriod[monthYearPeriodId]
102
- : null;
103
- const failedJeScheduleTransactionKeysForSelectedPeriod = monthYearPeriodId != null
104
- ? failedJeScheduleTransactionKeysByPeriod[monthYearPeriodId]
221
+ const selectedPeriodTabKey = monthYearPeriodId != null
222
+ ? getJESchedulePeriodTabKey(uiState.selectedPageTab, monthYearPeriodId)
105
223
  : null;
106
- const jeScheduleTransactionKeys = jeScheduleTransactionKeysForSelectedPeriod ?? [];
107
- const failedJeScheduleTransactionKeys = failedJeScheduleTransactionKeysForSelectedPeriod ?? [];
224
+ const jeScheduleTransactionKeysForSelectedTab = selectedPeriodTabKey != null
225
+ ? jeScheduleTransactionKeysByPeriodAndTab[selectedPeriodTabKey]
226
+ : undefined;
227
+ const failedJeScheduleTransactionKeysForSelectedTab = selectedPeriodTabKey != null
228
+ ? failedJeScheduleTransactionKeysByPeriodAndTab[selectedPeriodTabKey]
229
+ : undefined;
230
+ // Distinguishes "this tab has never been fetched for this period" from "fetched and genuinely
231
+ // empty". Without it the page cannot tell a first visit from a real empty state, and renders an
232
+ // empty table where it should render a skeleton.
233
+ const hasLoadedForSelectedTab = jeScheduleTransactionKeysForSelectedTab != null;
234
+ const jeScheduleTransactionKeys = jeScheduleTransactionKeysForSelectedTab ?? [];
235
+ const failedJeScheduleTransactionKeys = failedJeScheduleTransactionKeysForSelectedTab ?? [];
236
+ const jeScheduleAccruedKeys = (selectedPeriodTabKey != null
237
+ ? jeScheduleAccruedKeysByPeriodAndTab[selectedPeriodTabKey]
238
+ : undefined) ?? [];
239
+ const jeScheduleOneTimeAccrualKeys = (selectedPeriodTabKey != null
240
+ ? jeScheduleOneTimeAccrualKeysByPeriodAndTab[selectedPeriodTabKey]
241
+ : undefined) ?? [];
242
+ jeScheduleOneTimeAccrualKeys.forEach((jeOneTimeAccrualKey) => {
243
+ const accrual = getJEOneTimeAccrualByKey(jeOneTimeAccrualKey, state.jeSchedulesState);
244
+ if (accrual != null) {
245
+ oneTimeAccruals.push(accrual);
246
+ }
247
+ });
108
248
  jeScheduleTransactionKeys.forEach((jeScheduleKey) => {
109
249
  const jeScheduleDetails = getJEScheduledTransactionByJEScheduleTransactionKey(jeScheduleKey, state);
110
250
  jeScheduledTransaction.push(jeScheduleDetails);
111
251
  });
252
+ jeScheduleAccruedKeys.forEach((jeAccruedScheduleKey) => {
253
+ const accruedScheduleDetails = getJEAccruedScheduleByJEScheduleKey(jeAccruedScheduleKey, state);
254
+ if (accruedScheduleDetails != null) {
255
+ accruedSchedules.push(accruedScheduleDetails);
256
+ }
257
+ });
112
258
  failedJeScheduleTransactionKeys.forEach((jeScheduleKey) => {
113
259
  const jeScheduleDetails = getJEScheduledTransactionByJEScheduleTransactionKey(jeScheduleKey, state);
114
260
  failedJeScheduledTransaction.push(jeScheduleDetails);
115
261
  });
116
- const { sortKey, sortOrder } = uiState;
117
- const draftSchedules = sortJEScheduledTransactions(jeScheduledTransaction.filter((schedule) => schedule.status.code === 'draft'), sortKey, sortOrder);
118
- const ongoingSchedules = sortJEScheduledTransactions(jeScheduledTransaction.filter((schedule) => schedule.status.code === 'ongoing'), sortKey, sortOrder);
119
- const completedSchedules = sortJEScheduledTransactions(jeScheduledTransaction.filter((schedule) => schedule.status.code === 'completed' ||
120
- schedule.status.code === 'marked_as_completed'), sortKey, sortOrder);
262
+ const { sortKey, sortOrder, selectedJETypeFilter } = uiState;
121
263
  const unsortedResolveSchedules = [];
122
264
  failedJeScheduledTransaction.forEach((schedule) => {
123
265
  schedule.scheduledJournalEntry.forEach((journalEntry) => {
@@ -129,10 +271,87 @@ export function getExpenseAutomationJESchedulesView(state) {
129
271
  });
130
272
  const resolveSchedules = sortJEScheduledTransactions(unsortedResolveSchedules, sortKey, sortOrder);
131
273
  const validResolveForPending = unsortedResolveSchedules.filter((r) => jeScheduleLocalDataById[r.scheduledJournalEntry.scheduledJournalEntryID] != null);
274
+ const matchesTypeFilter = (schedule) => selectedJETypeFilter == null ||
275
+ getJEScheduleRowType(schedule) === selectedJETypeFilter;
276
+ const isCreatedStatus = (schedule) => {
277
+ // An accrual is "created" once it is not review work: posted, reversing, reversed, or in a
278
+ // transient posting claim.
279
+ if (isOneTimeAccrualRow(schedule)) {
280
+ return !isPendingReviewRow(schedule);
281
+ }
282
+ return (schedule.status.code === 'ongoing' ||
283
+ schedule.status.code === 'completed' ||
284
+ schedule.status.code === 'marked_as_completed');
285
+ };
286
+ const allScheduleRows = [
287
+ ...jeScheduledTransaction,
288
+ ...accruedSchedules,
289
+ ...oneTimeAccruals,
290
+ ];
132
291
  const pendingReviewSchedules = sortJEScheduledTransactions([
133
- ...jeScheduledTransaction.filter((schedule) => schedule.status.code === 'draft'),
292
+ ...allScheduleRows.filter((schedule) => isPendingReviewRow(schedule) && matchesTypeFilter(schedule)),
134
293
  ...validResolveForPending,
135
294
  ], sortKey, sortOrder);
295
+ const createdSchedules = sortJEScheduledTransactions(allScheduleRows.filter((schedule) => isCreatedStatus(schedule) && matchesTypeFilter(schedule)), sortKey, sortOrder);
296
+ // KPI summary — derived from unfiltered schedules so counts are always accurate
297
+ const allSchedules = allScheduleRows;
298
+ const sumAmount = (schedules) => schedules.reduce((total, schedule) => total + (getScheduleAmount(schedule) ?? 0), 0);
299
+ const pendingReviewTotal = sumAmount(pendingReviewSchedules);
300
+ const createdTotal = sumAmount(createdSchedules);
301
+ const aiAccountantSchedules = allSchedules.filter(isAiAccountantSchedule);
302
+ const manualSchedules = allSchedules.filter((schedule) => !isAiAccountantSchedule(schedule));
303
+ const pendingUnfiltered = allSchedules.filter((schedule) => isPendingReviewRow(schedule));
304
+ const kpiSummary = {
305
+ aiAccountant: {
306
+ count: aiAccountantSchedules.length,
307
+ amount: sumAmount(aiAccountantSchedules),
308
+ },
309
+ manual: {
310
+ count: manualSchedules.length,
311
+ amount: sumAmount(manualSchedules),
312
+ },
313
+ needsReview: {
314
+ count: pendingUnfiltered.length,
315
+ amount: sumAmount(pendingUnfiltered),
316
+ },
317
+ total: {
318
+ count: allSchedules.length,
319
+ amount: sumAmount(allSchedules),
320
+ },
321
+ };
322
+ // Each tab's badge counts from that tab's own bucket. A tab that has not been fetched yet
323
+ // reports undefined rather than 0, so the UI can hide the badge instead of asserting "none".
324
+ const scheduleCountsByTab = {};
325
+ ALL_JE_PAGE_TABS.forEach((pageTab) => {
326
+ const periodTabKey = monthYearPeriodId != null
327
+ ? getJESchedulePeriodTabKey(pageTab, monthYearPeriodId)
328
+ : null;
329
+ const transactionKeys = periodTabKey != null
330
+ ? jeScheduleTransactionKeysByPeriodAndTab[periodTabKey]
331
+ : undefined;
332
+ if (transactionKeys == null) {
333
+ return;
334
+ }
335
+ const accruedKeys = (periodTabKey != null
336
+ ? jeScheduleAccruedKeysByPeriodAndTab[periodTabKey]
337
+ : undefined) ?? [];
338
+ const oneTimeAccrualKeys = (periodTabKey != null
339
+ ? jeScheduleOneTimeAccrualKeysByPeriodAndTab[periodTabKey]
340
+ : undefined) ?? [];
341
+ const rowsForTab = [
342
+ ...transactionKeys.map((transactionKey) => getJEScheduledTransactionByJEScheduleTransactionKey(transactionKey, state)),
343
+ ...accruedKeys
344
+ .map((accruedKey) => getJEAccruedScheduleByJEScheduleKey(accruedKey, state))
345
+ .filter((accruedRow) => accruedRow != null),
346
+ ...oneTimeAccrualKeys
347
+ .map((accrualKey) => getJEOneTimeAccrualByKey(accrualKey, state.jeSchedulesState))
348
+ .filter((accrualRow) => accrualRow != null),
349
+ ];
350
+ scheduleCountsByTab[pageTab] =
351
+ pageTab === 'existing_schedules'
352
+ ? rowsForTab.filter((row) => isExistingTabRow(row)).length
353
+ : rowsForTab.filter((row) => isPendingReviewRow(row) || isCreatedStatus(row)).length;
354
+ });
136
355
  const allSteps = monthYearPeriodId != null
137
356
  ? getAllSteps(monthEndCloseChecksState, monthYearPeriodId, currentTenant.tenantId)
138
357
  : [];
@@ -145,6 +364,20 @@ export function getExpenseAutomationJESchedulesView(state) {
145
364
  { fetchState, error },
146
365
  monthEndFetchState,
147
366
  ]);
367
+ const countByType = (schedules) => {
368
+ const counts = ALL_SCHEDULES_TYPES.reduce((acc, type) => ({ ...acc, [type]: 0 }), {});
369
+ schedules.forEach((schedule) => {
370
+ const scheduleType = getJEScheduleRowType(schedule);
371
+ if (scheduleType != null) {
372
+ counts[scheduleType] += 1;
373
+ }
374
+ });
375
+ return counts;
376
+ };
377
+ const jeScheduleTypeCounts = {
378
+ active: countByType(allSchedules.filter((schedule) => isPendingReviewRow(schedule) || isCreatedStatus(schedule))),
379
+ ongoing: countByType(allSchedules.filter((schedule) => isExistingTabRow(schedule))),
380
+ };
148
381
  return {
149
382
  uncategorizedAccounts,
150
383
  allDepreciationAccountList: filteredDepAccounts,
@@ -153,12 +386,17 @@ export function getExpenseAutomationJESchedulesView(state) {
153
386
  allAccountList: filteredAccounts,
154
387
  accountListNestedAccountHierarchy,
155
388
  allClassList: allClasses,
156
- completedSchedules,
157
- draftSchedules,
158
- ongoingSchedules,
389
+ createdSchedules,
390
+ createdTotal,
391
+ hasLoadedForSelectedTab,
392
+ jeScheduleTypeCounts,
393
+ scheduleCountsByTab,
394
+ kpiSummary,
159
395
  pendingReviewSchedules,
396
+ pendingReviewTotal,
160
397
  resolveSchedules: resolveSchedules,
161
398
  accountSettingsView,
399
+ newScheduleView,
162
400
  postStatusById: postStatusById,
163
401
  ignoreStatusById: ignoreStatusById,
164
402
  fetchState: reducedFetchStatus.fetchState,
@@ -16,9 +16,8 @@ const JE_SCHEDULE_SORT_KEYS = [
16
16
  'memo',
17
17
  ];
18
18
  export const toJEScheduleSortKey = (v) => stringToUnion(v, JE_SCHEDULE_SORT_KEYS);
19
- const JE_SCHEDULE_MAIN_TABS = [
20
- 'pending_review',
21
- 'ongoing',
22
- 'completed',
23
- ];
24
- export const toJEScheduleMainTab = (v) => stringToUnion(v, JE_SCHEDULE_MAIN_TABS);
19
+ const JE_PAGE_TABS = ['schedules', 'existing_schedules'];
20
+ export const toJEPageTab = (v) => stringToUnion(v, JE_PAGE_TABS);
21
+ export const ALL_JE_PAGE_TABS = JE_PAGE_TABS;
22
+ export const getJESchedulePeriodTabKey = (selectedPageTab, monthYearPeriodId) => `${selectedPageTab}_${monthYearPeriodId}`;
23
+ export const MIN_NEW_SCHEDULE_SEARCH_LENGTH = 2;
@@ -1,6 +1,7 @@
1
- import { of } from 'rxjs';
1
+ import { from } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { updateInvoicingCreditNotes } from '../../../entity/invoicing/creditNote/creditNoteReducer';
4
+ import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
4
5
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
5
6
  import { localDataToCreateCreditNotePayload, } from './createCreditNotePayload';
6
7
  import { createCreditNoteFailure, createCreditNoteSuccess, submitCreateCreditNote, } from './createCreditNoteReducer';
@@ -15,9 +16,28 @@ export const createCreditNoteEpic = (actions$, state$, zeniAPI) => actions$.pipe
15
16
  return [
16
17
  updateInvoicingCreditNotes([response.data], 'merge'),
17
18
  createCreditNoteSuccess({ creditNoteId: response.data.id }),
19
+ openSnackbar({
20
+ messageSection: 'invoicing_credit_note_saved',
21
+ messageText: 'success',
22
+ type: 'success',
23
+ }),
18
24
  ];
19
25
  }
20
- return of(createCreditNoteFailure(response.status));
21
- }), catchError((error) => of(createCreditNoteFailure(createZeniAPIStatus('Unexpected Error', 'Create credit note REST API call errored out ' +
22
- JSON.stringify(error))))));
26
+ return from([
27
+ createCreditNoteFailure(response.status),
28
+ openSnackbar({
29
+ messageSection: 'invoicing_credit_note_saved',
30
+ messageText: 'failed',
31
+ type: 'error',
32
+ }),
33
+ ]);
34
+ }), catchError((error) => from([
35
+ createCreditNoteFailure(createZeniAPIStatus('Unexpected Error', 'Create credit note REST API call errored out ' +
36
+ JSON.stringify(error))),
37
+ openSnackbar({
38
+ messageSection: 'invoicing_credit_note_saved',
39
+ messageText: 'failed',
40
+ type: 'error',
41
+ }),
42
+ ])));
23
43
  }));
@@ -1,8 +1,9 @@
1
- import { of } from 'rxjs';
1
+ import { from } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { updateAddresses } from '../../../entity/address/addressReducer';
4
4
  import { extractInvoicingInvoiceAddresses } from '../../../entity/invoicing/invoice/invoicePayload';
5
5
  import { updateInvoices } from '../../../entity/invoicing/invoice/invoiceReducer';
6
+ import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
6
7
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
7
8
  import { addInvoiceToList } from '../invoiceList/invoiceListReducer';
8
9
  import { createInvoice, createInvoiceFailure, createInvoiceSuccess, } from './createInvoiceReducer';
@@ -22,9 +23,28 @@ export const createInvoiceEpic = (actions$, _state$, zeniAPI) => actions$.pipe(f
22
23
  status: response.data.status,
23
24
  }),
24
25
  createInvoiceSuccess({ invoiceId: response.data.id }),
26
+ openSnackbar({
27
+ messageSection: 'invoicing_invoice_saved',
28
+ messageText: 'success',
29
+ type: 'success',
30
+ }),
25
31
  ];
26
32
  }
27
- return of(createInvoiceFailure(response.status));
28
- }), catchError((error) => of(createInvoiceFailure(createZeniAPIStatus('Unexpected Error', 'Create invoice REST API call errored out ' +
29
- JSON.stringify(error))))));
33
+ return from([
34
+ createInvoiceFailure(response.status),
35
+ openSnackbar({
36
+ messageSection: 'invoicing_invoice_saved',
37
+ messageText: 'failed',
38
+ type: 'error',
39
+ }),
40
+ ]);
41
+ }), catchError((error) => from([
42
+ createInvoiceFailure(createZeniAPIStatus('Unexpected Error', 'Create invoice REST API call errored out ' +
43
+ JSON.stringify(error))),
44
+ openSnackbar({
45
+ messageSection: 'invoicing_invoice_saved',
46
+ messageText: 'failed',
47
+ type: 'error',
48
+ }),
49
+ ])));
30
50
  }));
@@ -1,25 +1,52 @@
1
+ import { getAddressByAddressId } from '../../../entity/address/addressSelector';
1
2
  import { getInvoicingCatalogListView, getInvoicingCatalogPrices, } from '../invoicingCatalogListView/invoicingCatalogListViewSelector';
2
3
  import { getInvoicingCustomerListView } from '../invoicingCustomerListView/invoicingCustomerListViewSelector';
3
4
  import { getInvoicingSettings } from '../settingsView/settingsViewSelector';
5
+ /**
6
+ * Display name for a customer option: company, else the full name, else the
7
+ * email, else the raw id.
8
+ */
9
+ const customerDisplayName = (customer) => {
10
+ const fullName = [customer.firstName, customer.lastName]
11
+ .filter((part) => part != null && part.trim() !== '')
12
+ .join(' ')
13
+ .trim();
14
+ const company = customer.company?.trim() ?? '';
15
+ const email = customer.email?.trim() ?? '';
16
+ return company !== ''
17
+ ? company
18
+ : fullName !== ''
19
+ ? fullName
20
+ : email !== ''
21
+ ? email
22
+ : customer.id;
23
+ };
4
24
  /**
5
25
  * Single bundled view for the create-invoice screen — web-ui consumes only this.
6
26
  * Carries the form draft (`localData`), the option sources the form needs
7
- * (customers, catalog plans/products, settings), the create-invoice fetch
8
- * state/error, and the id of the invoice last created (`savedInvoiceId`, to
9
- * navigate on success). Returns raw entity lists; the screen maps them to its
10
- * option shapes.
27
+ * (customer picker rows, catalog plan names, products, settings), the
28
+ * create-invoice fetch state/error, and the id of the invoice last created
29
+ * (`savedInvoiceId`, to navigate on success).
11
30
  */
12
31
  export const getCreateInvoiceFormView = (state) => {
13
32
  const createInvoiceState = state.invoicingCreateInvoiceState;
14
33
  const catalogListView = getInvoicingCatalogListView(state);
15
- const plans = getInvoicingCatalogPrices(catalogListView.plans);
16
34
  return {
17
- customers: getInvoicingCustomerListView(state).customers,
35
+ customers: getInvoicingCustomerListView(state).customers.map((customer) => ({
36
+ billingAddress: customer.billingAddressId != null
37
+ ? getAddressByAddressId(state.addressState, customer.billingAddressId)
38
+ : undefined,
39
+ email: customer.email,
40
+ id: customer.id,
41
+ name: customerDisplayName(customer),
42
+ })),
18
43
  localData: createInvoiceState.formDraft,
19
- planNames: plans
20
- .map((plan) => plan.name ?? '')
21
- .filter((name) => name !== ''),
22
- plans,
44
+ planNames: catalogListView.plans.flatMap((plan) => (plan.subFamilies ?? [])
45
+ .filter((subFamily) => (subFamily.name ?? '') !== '')
46
+ .map((subFamily) => ({
47
+ planName: plan.name ?? '',
48
+ subFamilyName: subFamily.name ?? '',
49
+ }))),
23
50
  products: getInvoicingCatalogPrices(catalogListView.products),
24
51
  savedInvoiceId: createInvoiceState.createdInvoiceId,
25
52
  settings: getInvoicingSettings(state),