@zeniai/client-epic-state 4.19.99-betaRD21 → 4.19.99-betaRD4

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.

Potentially problematic release.


This version of @zeniai/client-epic-state might be problematic. Click here for more details.

Files changed (107) hide show
  1. package/lib/entity/aiAccountantCustomer/aiAccountantCustomerPayload.d.ts +44 -0
  2. package/lib/entity/aiAccountantCustomer/aiAccountantCustomerPayload.js +53 -0
  3. package/lib/entity/aiAccountantCustomer/aiAccountantCustomerReducer.d.ts +10 -0
  4. package/lib/entity/aiAccountantCustomer/aiAccountantCustomerReducer.js +49 -0
  5. package/lib/entity/aiAccountantCustomer/aiAccountantCustomerSelector.d.ts +48 -0
  6. package/lib/entity/aiAccountantCustomer/aiAccountantCustomerSelector.js +66 -0
  7. package/lib/entity/aiAccountantCustomer/aiAccountantCustomerState.d.ts +52 -0
  8. package/lib/entity/aiAccountantCustomer/aiAccountantCustomerState.js +36 -0
  9. package/lib/entity/billPay/billTransaction/billTransactionState.d.ts +2 -2
  10. package/lib/entity/chargeCard/chargeCard.d.ts +1 -1
  11. package/lib/entity/depositAccountTransaction/depositAccountTransaction.d.ts +1 -1
  12. package/lib/entity/entityApprovalStatus/entityApprovalStatusState.d.ts +2 -2
  13. package/lib/entity/jeSchedules/jeSchedulesPayload.d.ts +6 -9
  14. package/lib/entity/jeSchedules/jeSchedulesPayload.js +2 -36
  15. package/lib/entity/jeSchedules/jeSchedulesSelector.d.ts +2 -4
  16. package/lib/entity/jeSchedules/jeSchedulesSelector.js +2 -5
  17. package/lib/entity/jeSchedules/jeSchedulesState.d.ts +2 -26
  18. package/lib/entity/jeSchedules/jeSchedulesTypes.d.ts +1 -1
  19. package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
  20. package/lib/entity/tenant/clearAllEpic.d.ts +3 -1
  21. package/lib/entity/tenant/clearAllEpic.js +4 -0
  22. package/lib/entity/transaction/payloadTypes/scheduleTransactionPayload.d.ts +1 -4
  23. package/lib/entity/transaction/payloadTypes/scheduleTransactionPayload.js +0 -1
  24. package/lib/entity/transaction/stateTypes/scheduleTransaction.d.ts +0 -1
  25. package/lib/entity/userRole/userRoleType.d.ts +2 -2
  26. package/lib/epic.d.ts +5 -1
  27. package/lib/epic.js +5 -1
  28. package/lib/esm/entity/aiAccountantCustomer/aiAccountantCustomerPayload.js +47 -0
  29. package/lib/esm/entity/aiAccountantCustomer/aiAccountantCustomerReducer.js +45 -0
  30. package/lib/esm/entity/aiAccountantCustomer/aiAccountantCustomerSelector.js +62 -0
  31. package/lib/esm/entity/aiAccountantCustomer/aiAccountantCustomerState.js +30 -0
  32. package/lib/esm/entity/jeSchedules/jeSchedulesPayload.js +2 -36
  33. package/lib/esm/entity/jeSchedules/jeSchedulesSelector.js +2 -5
  34. package/lib/esm/entity/tenant/clearAllEpic.js +4 -0
  35. package/lib/esm/entity/transaction/payloadTypes/scheduleTransactionPayload.js +0 -1
  36. package/lib/esm/epic.js +5 -1
  37. package/lib/esm/index.js +7 -0
  38. package/lib/esm/reducer.js +6 -0
  39. package/lib/esm/view/aiAccountantView/aiAccountantViewPayload.js +2 -0
  40. package/lib/esm/view/aiAccountantView/aiAccountantViewReducer.js +147 -0
  41. package/lib/esm/view/aiAccountantView/aiAccountantViewSelector.js +61 -0
  42. package/lib/esm/view/aiAccountantView/aiAccountantViewState.js +11 -0
  43. package/lib/esm/view/aiAccountantView/epics/cancelAiAccountantOnboardingEpic.js +26 -0
  44. package/lib/esm/view/aiAccountantView/epics/fetchAiAccountantCustomersEpic.js +27 -0
  45. package/lib/esm/view/aiAccountantView/epics/fetchAiAccountantJobsEpic.js +47 -0
  46. package/lib/esm/view/aiAccountantView/epics/triggerAiAccountantJobEpic.js +39 -0
  47. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.js +2 -2
  48. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/bulkUploadReceiptsEpic.js +2 -2
  49. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js +3 -4
  50. package/lib/esm/view/expenseAutomationView/expenseAutomationViewSelector.js +1 -0
  51. package/lib/esm/view/expenseAutomationView/selectors/fluxAnalysisViewSelector.js +2 -10
  52. package/lib/esm/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +3 -13
  53. package/lib/esm/view/expenseAutomationView/selectors/missingReceiptsSelector.js +3 -11
  54. package/lib/esm/view/expenseAutomationView/selectors/reconciliationViewSelector.js +9 -22
  55. package/lib/esm/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +6 -12
  56. package/lib/esm/view/expenseAutomationView/types/jeSchedulesViewState.js +0 -1
  57. package/lib/esm/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelector.js +8 -3
  58. package/lib/esm/view/profitAndLossClassesView/profitAndLossHorizontalEnrichment.js +63 -144
  59. package/lib/esm/view/transactionDetail/transactionDetailSelector.js +2 -9
  60. package/lib/index.d.ts +10 -2
  61. package/lib/index.js +30 -2
  62. package/lib/reducer.d.ts +6 -0
  63. package/lib/reducer.js +6 -0
  64. package/lib/tsconfig.typecheck.tsbuildinfo +1 -1
  65. package/lib/view/aiAccountantView/aiAccountantViewPayload.d.ts +32 -0
  66. package/lib/view/aiAccountantView/aiAccountantViewPayload.js +8 -0
  67. package/lib/view/aiAccountantView/aiAccountantViewReducer.d.ts +29 -0
  68. package/lib/view/aiAccountantView/aiAccountantViewReducer.js +151 -0
  69. package/lib/view/aiAccountantView/aiAccountantViewSelector.d.ts +13 -0
  70. package/lib/view/aiAccountantView/aiAccountantViewSelector.js +67 -0
  71. package/lib/view/aiAccountantView/aiAccountantViewState.d.ts +27 -0
  72. package/lib/view/aiAccountantView/aiAccountantViewState.js +13 -0
  73. package/lib/view/aiAccountantView/epics/cancelAiAccountantOnboardingEpic.d.ts +20 -0
  74. package/lib/view/aiAccountantView/epics/cancelAiAccountantOnboardingEpic.js +30 -0
  75. package/lib/view/aiAccountantView/epics/fetchAiAccountantCustomersEpic.d.ts +23 -0
  76. package/lib/view/aiAccountantView/epics/fetchAiAccountantCustomersEpic.js +31 -0
  77. package/lib/view/aiAccountantView/epics/fetchAiAccountantJobsEpic.d.ts +33 -0
  78. package/lib/view/aiAccountantView/epics/fetchAiAccountantJobsEpic.js +51 -0
  79. package/lib/view/aiAccountantView/epics/triggerAiAccountantJobEpic.d.ts +15 -0
  80. package/lib/view/aiAccountantView/epics/triggerAiAccountantJobEpic.js +43 -0
  81. package/lib/view/companyView/types/cockpitTypes.d.ts +6 -5
  82. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.js +2 -2
  83. package/lib/view/expenseAutomationView/epics/missingReceipts/bulkUploadReceiptsEpic.js +1 -1
  84. package/lib/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js +3 -4
  85. package/lib/view/expenseAutomationView/expenseAutomationViewSelector.js +1 -0
  86. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
  87. package/lib/view/expenseAutomationView/payload/jeSchedulesPayload.d.ts +1 -1
  88. package/lib/view/expenseAutomationView/selectors/fluxAnalysisViewSelector.js +2 -10
  89. package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +3 -13
  90. package/lib/view/expenseAutomationView/selectors/missingReceiptsSelector.js +3 -11
  91. package/lib/view/expenseAutomationView/selectors/reconciliationViewSelector.js +9 -22
  92. package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +5 -11
  93. package/lib/view/expenseAutomationView/types/jeSchedulesViewState.d.ts +1 -1
  94. package/lib/view/expenseAutomationView/types/jeSchedulesViewState.js +0 -1
  95. package/lib/view/expenseAutomationView/types/missingReceiptsViewState.d.ts +1 -1
  96. package/lib/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelector.js +8 -3
  97. package/lib/view/profitAndLossClassesView/profitAndLossHorizontalEnrichment.js +63 -144
  98. package/lib/view/reviewCompanyView/reviewCompanyViewState.d.ts +1 -1
  99. package/lib/view/spendManagement/billPay/billList/billListReducer.d.ts +1 -1
  100. package/lib/view/spendManagement/billPay/billList/billListState.d.ts +1 -1
  101. package/lib/view/spendManagement/chargeCards/chargeCardDetail/chargeCardDetailReducer.d.ts +4 -4
  102. package/lib/view/spendManagement/reimbursement/remiListView/remiListReducer.d.ts +1 -1
  103. package/lib/view/spendManagement/reimbursement/remiListView/remiListState.d.ts +3 -3
  104. package/lib/view/subscriptionView/types/subscriptionTypes.d.ts +1 -1
  105. package/lib/view/transactionDetail/transactionDetailSelector.d.ts +0 -1
  106. package/lib/view/transactionDetail/transactionDetailSelector.js +2 -9
  107. package/package.json +1 -1
@@ -0,0 +1,39 @@
1
+ import { from, of } from 'rxjs';
2
+ import { catchError, filter, mergeMap } from 'rxjs/operators';
3
+ import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
4
+ import { fetchAiAccountantCustomers, triggerAiAccountantJob, triggerAiAccountantJobFailure, triggerAiAccountantJobSuccess, } from '../aiAccountantViewReducer';
5
+ export const triggerAiAccountantJobEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(triggerAiAccountantJob.match), mergeMap((action) => {
6
+ const { operationType, params, triggeredBy } = action.payload;
7
+ const rootState = state$.value;
8
+ const selectedTenantIds = rootState.aiAccountantViewState.selectedTenantIdsForJobTrigger;
9
+ const entityState = rootState.aiAccountantCustomerState;
10
+ const customers = selectedTenantIds
11
+ .map((tenantId) => {
12
+ const customer = entityState.customerByTenantId[tenantId];
13
+ return customer != null
14
+ ? { company_id: customer.companyId, tenant_id: customer.tenantId }
15
+ : undefined;
16
+ })
17
+ .filter((entry) => entry != null);
18
+ const base = `${zeniAPI.apiEndPoints.tenantMicroServiceBaseUrl}/1.0/cockpit-panels`;
19
+ const url = `${base}/ai-accountant/jobs`;
20
+ return zeniAPI
21
+ .postAndGetJSON(url, {
22
+ customers,
23
+ operation_type: operationType,
24
+ params: params ?? {},
25
+ triggered_by: triggeredBy ?? 'cockpit-ui',
26
+ })
27
+ .pipe(mergeMap((response) => {
28
+ if (isSuccessResponse(response)) {
29
+ return from([
30
+ triggerAiAccountantJobSuccess(),
31
+ fetchAiAccountantCustomers(),
32
+ ]);
33
+ }
34
+ else {
35
+ return of(triggerAiAccountantJobFailure(response.status));
36
+ }
37
+ }), catchError((error) => of(triggerAiAccountantJobFailure(createZeniAPIStatus('Unexpected error', 'AI Accountant trigger job failed: ' +
38
+ JSON.stringify(error))))));
39
+ }));
@@ -21,13 +21,13 @@ export const fetchJeSchedulesEpic = (actions$, state$, zeniAPI) => actions$.pipe
21
21
  if (isSuccessResponse(response) && response.data != null) {
22
22
  const updateActions = [];
23
23
  updateActions.push(updateJESchedules([
24
- ...response.data.je_schedules,
24
+ ...response.data.draft_je_schedules,
25
25
  ...response.data.failed_je_postings.je_schedules,
26
26
  ]), updateJESchedulesDetails([...response.data.failed_je_postings.je_schedules], response.data.failed_je_postings.scheduled_journal_entries), initializeJeScheduleLocalData([
27
27
  ...response.data.failed_je_postings.je_schedules,
28
28
  ]));
29
29
  updateActions.push(fetchJeSchedulesSuccess({
30
- jeSchedules: [...response.data.je_schedules],
30
+ jeSchedules: [...response.data.draft_je_schedules],
31
31
  failedJeSchedules: [
32
32
  ...response.data.failed_je_postings.je_schedules,
33
33
  ],
@@ -1,9 +1,9 @@
1
1
  import { concat, of, timer } from 'rxjs';
2
- import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
2
+ import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
4
4
  import { BULK_UPLOAD_BAR_COMPLETE_HOLD_MS } from '../../bulkUploadTiming';
5
5
  import { bulkUploadReceipts, bulkUploadReceiptsFailure, bulkUploadReceiptsSuccess, updateBulkUploadProgress, } from '../../reducers/missingReceiptsViewReducer';
6
- export const bulkUploadReceiptsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(bulkUploadReceipts.match), switchMap((action) => {
6
+ export const bulkUploadReceiptsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(bulkUploadReceipts.match), mergeMap((action) => {
7
7
  const { files } = action.payload;
8
8
  const formData = new FormData();
9
9
  for (const file of files) {
@@ -25,7 +25,7 @@ export const pusherBatchStatusCompletionEpic = (actions$) => actions$.pipe(filte
25
25
  * force `phase` to `completed` (dismiss automatching banner) and refresh the batch list.
26
26
  * Cancels when the batch completes normally, bulk state is cleared, or another upload supersedes.
27
27
  */
28
- export const bulkUploadAutomatchingTimeoutEpic = (actions$, state$) => actions$.pipe(filter(bulkUploadReceiptsSuccess.match), switchMap((action) => {
28
+ export const bulkUploadAutomatchingTimeoutEpic = (actions$, state$) => actions$.pipe(filter(bulkUploadReceiptsSuccess.match), mergeMap((action) => {
29
29
  const { batchId } = action.payload;
30
30
  return timer(BULK_UPLOAD_AUTOMATCHING_TIMEOUT_MS).pipe(mergeMap(() => {
31
31
  const bulk = state$.value.expenseAutomationMissingReceiptsViewState.bulkUpload;
@@ -44,11 +44,10 @@ export const bulkUploadAutomatchingTimeoutEpic = (actions$, state$) => actions$.
44
44
  * error snackbar per upload session (first failure only) so the user is not spammed every
45
45
  * interval; Pusher or a later successful poll still completes the flow.
46
46
  */
47
- export const pollBulkUploadBatchStatusEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(bulkUploadReceiptsSuccess.match), switchMap((action) => {
47
+ export const pollBulkUploadBatchStatusEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(bulkUploadReceiptsSuccess.match), mergeMap((action) => {
48
48
  const { batchId } = action.payload;
49
49
  let pollErrorNotified = false;
50
- return timer(FALLBACK_FIRST_DELAY_MS, FALLBACK_POLL_INTERVAL_MS).pipe(takeUntil(merge(actions$.pipe(filter(clearBulkUpload.match)), actions$.pipe(filter(pusherBatchStatusUpdate.match), filter((action) => action.payload.batchId === batchId &&
51
- action.payload.status === 'completed')), actions$.pipe(filter(fetchBulkUploadBatchDetailsSuccess.match), filter((action) => action.payload.batchId === batchId)), actions$.pipe(filter(bulkUploadAutomatchingTimedOut.match), filter((action) => action.payload.batchId === batchId)))), switchMap(() => zeniAPI
50
+ return timer(FALLBACK_FIRST_DELAY_MS, FALLBACK_POLL_INTERVAL_MS).pipe(takeUntil(merge(actions$.pipe(filter(clearBulkUpload.match)), actions$.pipe(filter(pusherBatchStatusUpdate.match), filter((action) => action.payload.batchId === batchId)), actions$.pipe(filter(fetchBulkUploadBatchDetailsSuccess.match), filter((action) => action.payload.batchId === batchId)), actions$.pipe(filter(bulkUploadAutomatchingTimedOut.match), filter((action) => action.payload.batchId === batchId)))), switchMap(() => zeniAPI
52
51
  .getJSON(`${zeniAPI.apiEndPoints.communicationAgentMicroServiceBaseUrl}/1.0/batches/${batchId}`)
53
52
  .pipe(mergeMap((response) => {
54
53
  /** Same batch-details URL; polls until `status` is completed (Pusher may win first). */
@@ -91,6 +91,7 @@ export function getExpenseAutomationView(state) {
91
91
  const fetchStateVariables = [
92
92
  expenseAutomationMissingReceiptsViewState,
93
93
  expenseAutomationJESchedulesViewState,
94
+ monthEndCloseChecksView,
94
95
  expenseAutomationTransactionsView,
95
96
  expenseAutomationReconciliationViewState,
96
97
  ];
@@ -1,4 +1,3 @@
1
- import { reduceAnyFetchState } from '../../../commonStateTypes/reduceFetchState';
2
1
  import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
3
2
  import { getAccountBase } from '../../../entity/account/accountSelector';
4
3
  import { getClassById } from '../../../entity/class/classSelector';
@@ -7,7 +6,7 @@ import { getUserByUserId } from '../../../entity/user/userSelector';
7
6
  import { getVendorByVendorId } from '../../../entity/vendor/vendorSelector';
8
7
  import { getAllSteps } from '../selectorTypes/expenseAutomationViewSelectorTypes';
9
8
  export function getExpenseAutomationFluxAnalysisView(state) {
10
- const { expenseAutomationFluxAnalysisViewState, monthEndCloseChecksState, monthEndCloseChecksViewState, } = state;
9
+ const { expenseAutomationFluxAnalysisViewState, monthEndCloseChecksState } = state;
11
10
  const { fetchState, error, refreshStatus, uiState, reviewOperatingExpenseIdsByPeriod, selectedSectionIdsByPeriod, groupsBySelectedPeriod, fluxAnalysisEntities, bulkReviewStatus, currency, totalBalancesAndVariance, } = expenseAutomationFluxAnalysisViewState;
12
11
  const fetchStateAndError = {
13
12
  fetchState,
@@ -92,17 +91,10 @@ export function getExpenseAutomationFluxAnalysisView(state) {
92
91
  },
93
92
  };
94
93
  const completionStatus = fluxCompletionStatus != null ? fluxCompletionStatus : 'incomplete';
95
- const monthEndFetchState = monthYearPeriodId != null
96
- ? (monthEndCloseChecksViewState.monthEndCloseChecksViewByTenantId[currentTenant.tenantId]?.[monthYearPeriodId] ?? { fetchState: 'Not-Started', error: undefined })
97
- : { fetchState: 'Not-Started', error: undefined };
98
- const reducedFetchStatus = reduceAnyFetchState([
99
- fetchStateAndError,
100
- monthEndFetchState,
101
- ]);
102
94
  return {
103
95
  sections,
104
96
  totalBalancesAndVariance,
105
- fetchStatus: reducedFetchStatus,
97
+ fetchStatus: fetchStateAndError,
106
98
  bulkReviewStatus,
107
99
  reviewOperatingExpensesIds,
108
100
  selectedSectionIds,
@@ -1,6 +1,5 @@
1
1
  import omit from 'lodash/omit';
2
2
  import orderBy from 'lodash/orderBy';
3
- import { reduceAnyFetchState } from '../../../commonStateTypes/reduceFetchState';
4
3
  import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
5
4
  import { getAccountIdsForTypes } from '../../../entity/account/accountSelector';
6
5
  import { getJEScheduledTransactionByJEScheduleTransactionKey, } from '../../../entity/jeSchedules/jeSchedulesSelector';
@@ -20,8 +19,6 @@ function getJEScheduleSortAccessor(sortKey) {
20
19
  return (s) => s.jeDebit.account?.accountName?.toLowerCase();
21
20
  case 'class':
22
21
  return (s) => s.jeDebit.accountingClass?.className?.toLowerCase();
23
- case 'depCategory':
24
- return (s) => s.jeCredit.account?.accountName?.toLowerCase();
25
22
  case 'scheduleCategory':
26
23
  return (s) => s.jeScheduleType;
27
24
  case 'startMonth':
@@ -53,7 +50,7 @@ function sortJEScheduledTransactions(transactions, sortKey, sortOrder) {
53
50
  export function getExpenseAutomationJESchedulesView(state) {
54
51
  const jeScheduledTransaction = [];
55
52
  const failedJeScheduledTransaction = [];
56
- const { accountState, accountListState, classState, classListState, expenseAutomationJESchedulesViewState, expenseAutomationViewState, monthEndCloseChecksState, monthEndCloseChecksViewState, } = state;
53
+ const { accountState, accountListState, classState, classListState, expenseAutomationJESchedulesViewState, expenseAutomationViewState, monthEndCloseChecksState, } = state;
57
54
  const { jeScheduleTransactionKeysByPeriod, failedJeScheduleTransactionKeysByPeriod, jeScheduleLocalDataById, postStatusById, ignoreStatusById, uiState, error, fetchState, refreshStatus, } = expenseAutomationJESchedulesViewState;
58
55
  const accountSettingsView = getJEAccountSettingsView(state);
59
56
  const currentTenant = getCurrentTenant(state);
@@ -119,13 +116,6 @@ export function getExpenseAutomationJESchedulesView(state) {
119
116
  : [];
120
117
  const jeCompletionStatus = allSteps.find((step) => step.step === 'je_schedules')?.completionStatus;
121
118
  const completionStatus = jeCompletionStatus != null ? jeCompletionStatus : 'incomplete';
122
- const monthEndFetchState = monthYearPeriodId != null
123
- ? (monthEndCloseChecksViewState.monthEndCloseChecksViewByTenantId[currentTenant.tenantId]?.[monthYearPeriodId] ?? { fetchState: 'Not-Started', error: undefined })
124
- : { fetchState: 'Not-Started', error: undefined };
125
- const reducedFetchStatus = reduceAnyFetchState([
126
- { fetchState, error },
127
- monthEndFetchState,
128
- ]);
129
119
  return {
130
120
  uncategorizedAccounts,
131
121
  allDepreciationAccountList: filteredDepAccounts,
@@ -141,10 +131,10 @@ export function getExpenseAutomationJESchedulesView(state) {
141
131
  accountSettingsView,
142
132
  postStatusById: postStatusById,
143
133
  ignoreStatusById: ignoreStatusById,
144
- fetchState: reducedFetchStatus.fetchState,
134
+ fetchState: fetchState,
145
135
  jeScheduleLocalDataById: jeScheduleLocalDataById,
146
136
  uiState: uiState,
147
- error: reducedFetchStatus.error,
137
+ error: error,
148
138
  refreshStatus,
149
139
  completionStatus,
150
140
  };
@@ -1,5 +1,4 @@
1
1
  import orderBy from 'lodash/orderBy';
2
- import { reduceAnyFetchState } from '../../../commonStateTypes/reduceFetchState';
3
2
  import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
4
3
  import { getCurrentTenant } from '../../../entity/tenant/tenantSelector';
5
4
  import { getSupportedTransactionById, getSupportedTransactionsByIds, } from '../../../entity/transaction/transactionSelector';
@@ -8,7 +7,7 @@ import { getCompletedTransactionsCacheKey } from '../reducers/missingReceiptsVie
8
7
  import { getAllSteps } from '../selectorTypes/expenseAutomationViewSelectorTypes';
9
8
  import { isUnmatchedTabFileStatus, } from '../types/missingReceiptsViewState';
10
9
  export function getExpenseAutomationMissingReceiptsView(state) {
11
- const { expenseAutomationMissingReceiptsViewState, expenseAutomationViewState, transactionState, monthEndCloseChecksState, monthEndCloseChecksViewState, } = state;
10
+ const { expenseAutomationMissingReceiptsViewState, expenseAutomationViewState, transactionState, monthEndCloseChecksState, } = state;
12
11
  const currentTenant = getCurrentTenant(state);
13
12
  const { selectedPeriodByTenantId } = expenseAutomationViewState;
14
13
  const selectedPeriod = selectedPeriodByTenantId[currentTenant.tenantId];
@@ -176,16 +175,9 @@ export function getExpenseAutomationMissingReceiptsView(state) {
176
175
  vendorLogo: result.vendorLogo ?? transaction?.logo?.href,
177
176
  };
178
177
  });
179
- const monthEndFetchState = monthYearPeriodId != null
180
- ? (monthEndCloseChecksViewState.monthEndCloseChecksViewByTenantId[currentTenant.tenantId]?.[monthYearPeriodId] ?? { fetchState: 'Not-Started', error: undefined })
181
- : { fetchState: 'Not-Started', error: undefined };
182
- const reducedFetchStatus = reduceAnyFetchState([
183
- { fetchState, error },
184
- monthEndFetchState,
185
- ]);
186
178
  return {
187
- fetchState: reducedFetchStatus.fetchState,
188
- error: reducedFetchStatus.error,
179
+ fetchState,
180
+ error,
189
181
  uploadReceiptStatusById,
190
182
  uiState,
191
183
  refreshStatus,
@@ -13,7 +13,7 @@ import { getAccountList, getNestedAccountListHierarchy, } from '../../accountLis
13
13
  import { getClassList, getNestedClassListHierarchy, } from '../../classList/classListSelector';
14
14
  import { initialActionFetchState, initialReconciliationTabsState, initialState, } from '../reducers/reconciliationViewReducer';
15
15
  import { getAllSteps } from '../selectorTypes/expenseAutomationViewSelectorTypes';
16
- export const getExpenseAutomationReconciliationView = createSelector((state) => state, (state) => getCurrentTenant(state), (state) => state.expenseAutomationReconciliationViewState, (state) => state.expenseAutomationViewState, (state) => state.plaidAccountViewState, (state) => state.accountListState, (state) => state.accountState, (state) => state.classListState, (state) => state.classState, (state) => state.monthEndCloseChecksState, (state) => state.vendorState, (state) => state.monthEndCloseChecksViewState, (state, currentTenant, reconciliationViewState, expenseAutomationViewState, plaidAccountViewState, accountListState, accountState, classListState, classState, monthEndCloseChecksState, vendorState, monthEndCloseChecksViewState) => {
16
+ export const getExpenseAutomationReconciliationView = createSelector((state) => state, (state) => getCurrentTenant(state), (state) => state.expenseAutomationReconciliationViewState, (state) => state.expenseAutomationViewState, (state) => state.plaidAccountViewState, (state) => state.accountListState, (state) => state.accountState, (state) => state.classListState, (state) => state.classState, (state) => state.monthEndCloseChecksState, (state) => state.vendorState, (state, currentTenant, reconciliationViewState, expenseAutomationViewState, plaidAccountViewState, accountListState, accountState, classListState, classState, monthEndCloseChecksState, vendorState) => {
17
17
  const { selectedPeriodByTenantId } = expenseAutomationViewState;
18
18
  const reconcileTabUiState = reconciliationViewState.reconTabsState.reconcile.uiState;
19
19
  const reviewTabUiState = reconciliationViewState.reconTabsState.review.uiState;
@@ -35,13 +35,6 @@ export const getExpenseAutomationReconciliationView = createSelector((state) =>
35
35
  }
36
36
  return acc;
37
37
  }, []);
38
- const monthYearPeriodId = selectedPeriod != null ? toMonthYearPeriodId(selectedPeriod) : null;
39
- const monthEndFetchState = monthYearPeriodId != null
40
- ? (monthEndCloseChecksViewState.monthEndCloseChecksViewByTenantId[currentTenant.tenantId]?.[monthYearPeriodId] ?? {
41
- fetchState: 'Not-Started',
42
- error: undefined,
43
- })
44
- : { fetchState: 'Not-Started', error: undefined };
45
38
  if (accountReconForMonthYearPeriod == null) {
46
39
  return {
47
40
  bankAccounts: [],
@@ -51,13 +44,10 @@ export const getExpenseAutomationReconciliationView = createSelector((state) =>
51
44
  classListNestedAccountHierarchy: [],
52
45
  allAccountList: [],
53
46
  allClassList: [],
54
- fetchStatus: reduceAnyFetchState([
55
- {
56
- fetchState: reconciliationViewState.fetchState,
57
- error: reconciliationViewState.error,
58
- },
59
- monthEndFetchState,
60
- ]),
47
+ fetchStatus: {
48
+ fetchState: reconciliationViewState.fetchState,
49
+ error: reconciliationViewState.error,
50
+ },
61
51
  plaidConnectionDetails: plaidAccountViewState['expense_automation_reconcile']
62
52
  .plaidConnectionDetails,
63
53
  completionStatus: 'incomplete',
@@ -280,13 +270,10 @@ export const getExpenseAutomationReconciliationView = createSelector((state) =>
280
270
  bankAccounts: uniqueBankAccounts,
281
271
  creditCards: uniqueCreditCards,
282
272
  accountReconciliationsByAccountID: reconList,
283
- fetchStatus: reduceAnyFetchState([
284
- {
285
- fetchState: reconciliationViewState.fetchState,
286
- error: reconciliationViewState.error,
287
- },
288
- monthEndFetchState,
289
- ]),
273
+ fetchStatus: {
274
+ fetchState: reconciliationViewState.fetchState,
275
+ error: reconciliationViewState.error,
276
+ },
290
277
  plaidConnectionDetails: plaidAccountViewState['expense_automation_reconcile']
291
278
  .plaidConnectionDetails,
292
279
  reconciliationTabsState: reconciliationViewState.reconTabsState,
@@ -1,5 +1,5 @@
1
1
  import omit from 'lodash/omit';
2
- import { reduceAllFetchState, reduceAnyFetchState, } from '../../../commonStateTypes/reduceFetchState';
2
+ import { reduceAllFetchState } from '../../../commonStateTypes/reduceFetchState';
3
3
  import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
4
4
  import { getCurrentTenant } from '../../../entity/tenant/tenantSelector';
5
5
  import { getTransactionWithCOT } from '../../../entity/transaction/transactionHelper';
@@ -8,7 +8,7 @@ import { getAccountList, getNestedAccountListHierarchy, getUncategorizedAccounts
8
8
  import { getClassList, getNestedClassListHierarchy, } from '../../classList/classListSelector';
9
9
  import { getAllSteps } from '../selectorTypes/expenseAutomationViewSelectorTypes';
10
10
  export function getExpenseAutomationTransactionView(state) {
11
- const { accountState, classState, classListState, accountListState, expenseAutomationTransactionsViewState, expenseAutomationViewState, transactionState, monthEndCloseChecksState, monthEndCloseChecksViewState, } = state;
11
+ const { accountState, classState, classListState, accountListState, expenseAutomationTransactionsViewState, expenseAutomationViewState, transactionState, monthEndCloseChecksState, } = state;
12
12
  const { selectedTransactionCategorizationTab } = expenseAutomationTransactionsViewState;
13
13
  const { uiState, refreshStatus, saveStatus, markAsNotMiscategorizedStatus, transactionIdsBySelectedPeriod, transactionReviewLocalDataById, selectedCheckBoxTransactionIds, transactionIdsWithUnsavedData, uploadReceiptStatusById, selectedTransactionId, selectedTransactionLineId, } = expenseAutomationTransactionsViewState.transactionCategorizationView[selectedTransactionCategorizationTab];
14
14
  const uncategorizedIncomeExpense = getUncategorizedAccounts(accountState, accountListState, 'accountList');
@@ -41,16 +41,10 @@ export function getExpenseAutomationTransactionView(state) {
41
41
  .filter((transaction) => transaction != null);
42
42
  const transactions = getSupportedTransactionsByIds(transactionState, transactionIds);
43
43
  const transactionsWithCOT = transactions.map((transaction) => getTransactionWithCOT(transaction));
44
- const monthEndFetchState = monthYearPeriodId != null
45
- ? (monthEndCloseChecksViewState.monthEndCloseChecksViewByTenantId[currentTenant.tenantId]?.[monthYearPeriodId] ?? { fetchState: 'Not-Started', error: undefined })
46
- : { fetchState: 'Not-Started', error: undefined };
47
- const fetchStatus = reduceAnyFetchState([
48
- reduceAllFetchState([
49
- accountList,
50
- classList,
51
- expenseAutomationTransactionsViewState.transactionCategorizationView[selectedTransactionCategorizationTab],
52
- ]),
53
- monthEndFetchState,
44
+ const fetchStatus = reduceAllFetchState([
45
+ accountList,
46
+ classList,
47
+ expenseAutomationTransactionsViewState.transactionCategorizationView[selectedTransactionCategorizationTab],
54
48
  ]);
55
49
  const totalCountByReview = expenseAutomationTransactionsViewState.transactionCategorizationView.review
56
50
  .uiState.totalCount;
@@ -3,7 +3,6 @@ const JE_SCHEDULE_SORT_KEYS = [
3
3
  'vendor',
4
4
  'category',
5
5
  'class',
6
- 'depCategory',
7
6
  'scheduleCategory',
8
7
  'startMonth',
9
8
  'type',
@@ -49,11 +49,16 @@ export const getProfitAndLossClassesHorizontalView = (filter, accountState, clas
49
49
  const calculatedSections = {};
50
50
  let reportCurrency = defaultCustomerCurrency;
51
51
  if (fetchState.fetchState === 'Completed' && selectedPeriod != null) {
52
+ // Get section reports using existing selector infrastructure
53
+ const sectionReports = {};
54
+ HORIZONTAL_PANDL_SECTION_IDS.forEach((sectionId) => {
55
+ sectionReports[sectionId] = getSectionClassesViewReport(accountState, classState, sectionsState, { reportId, sectionId }, filter);
56
+ });
52
57
  reportCurrency =
53
58
  profitAndLossClassesViewState.currency ?? defaultCustomerCurrency;
54
- // Fetch each section and pivot class→account to account→class in one pass
59
+ // Pivot each section from class→account to account→class
55
60
  HORIZONTAL_PANDL_SECTION_IDS.forEach((sectionId) => {
56
- const sectionReport = getSectionClassesViewReport(accountState, classState, sectionsState, { reportId, sectionId }, filter);
61
+ const sectionReport = sectionReports[sectionId];
57
62
  if (sectionReport != null) {
58
63
  sections[sectionId] = pivotSectionToHorizontal(sectionReport, classColumns, SECTION_TITLES[sectionId] ?? sectionId, reportCurrency);
59
64
  }
@@ -256,7 +261,7 @@ function sumBalancesInSlice(balances) {
256
261
  if (balances.length === 0) {
257
262
  return 0;
258
263
  }
259
- const summed = getSummationFreeRangeTotalBalance(balances);
264
+ const summed = getSummationFreeRangeTotalBalance([...balances]);
260
265
  return summed?.balance.amount ?? 0;
261
266
  }
262
267
  /**
@@ -25,31 +25,30 @@ function enrichHorizontalSection(report, section) {
25
25
  return total?.balance.amount ?? 0;
26
26
  });
27
27
  section.sectionHeaderBalancesSlice = buildHorizontalClassColumnBalancesSlice(report, classAmounts, section.sectionTotal.amount);
28
- const parentIndices = computeHorizontalAccountParentIndices(section.accounts);
29
- const childrenRowIndicesByParent = buildHorizontalAccountChildIndicesByParent(parentIndices);
28
+ const childrenRowIndicesByParent = buildHorizontalAccountChildIndicesByParent(section.accounts);
30
29
  const childrenRecord = {};
31
30
  childrenRowIndicesByParent.forEach((childIndices, parentIndex) => {
32
31
  childrenRecord[parentIndex] = childIndices;
33
32
  });
34
33
  section.treeLayout = {
35
34
  rootAccountRowIndices: section.accounts
36
- .map((_, i) => (parentIndices[i] === -1 ? i : -1))
35
+ .map((_, i) => findHorizontalAccountParentIndex(section.accounts, i) === -1 ? i : -1)
37
36
  .filter((i) => i >= 0),
38
37
  childrenRowIndicesByParent: childrenRecord,
39
38
  };
40
- const siblingsPerRow = buildSiblingIndicesPerRow(section.accounts, parentIndices);
41
- const hasChildFlags = computeHorizontalHasChildFlags(parentIndices);
42
- for (let accountIndex = 0; accountIndex < section.accounts.length; accountIndex++) {
43
- const row = section.accounts[accountIndex];
44
- // Same column order as `report.classColumns` (see `pivotSectionToHorizontal`).
45
- const classRowAmounts = row.classBalances.map((classBalance) => classBalance.balance.amount);
39
+ for (let i = 0; i < section.accounts.length; i++) {
40
+ const row = section.accounts[i];
41
+ const classRowAmounts = report.classColumns.map((col) => {
42
+ const cb = row.classBalances.find((b) => b.classId === col.classId);
43
+ return cb?.balance.amount ?? 0;
44
+ });
46
45
  row.accountReportData = {
47
46
  accountId: row.accountId,
48
47
  accountName: row.accountName,
49
48
  accountType: row.accountType,
50
49
  depth: row.depth,
51
50
  balancesInTimeframe: buildHorizontalClassColumnBalancesSlice(report, classRowAmounts, row.rowTotal.amount),
52
- horizontalTreeView: buildHorizontalTreeViewFromPrecomputed(section.accounts, accountIndex, parentIndices, siblingsPerRow, hasChildFlags),
51
+ horizontalTreeView: buildHorizontalTreeView(section.accounts, i),
53
52
  };
54
53
  }
55
54
  }
@@ -81,9 +80,9 @@ function buildHorizontalClassColumnBalancesSlice(report, classAmounts, totalAmou
81
80
  return emptySlice;
82
81
  }
83
82
  const { startDate, endDate } = report.selectedPeriod;
84
- const balances = report.classColumns.map((col, columnIndex) => ({
83
+ const balances = report.classColumns.map((col, i) => ({
85
84
  balance: {
86
- amount: classAmounts[columnIndex] ?? 0,
85
+ amount: classAmounts[i] ?? 0,
87
86
  currencyCode,
88
87
  currencySymbol,
89
88
  },
@@ -121,157 +120,64 @@ function buildHorizontalClassColumnBalancesSlice(report, classAmounts, totalAmou
121
120
  },
122
121
  };
123
122
  }
124
- /**
125
- * Parent index per DFS row — O(n). Matches the scan in the former
126
- * `findHorizontalAccountParentIndex` (depth &lt;= 2 rows have no parent).
127
- */
128
- function computeHorizontalAccountParentIndices(rows) {
129
- const accountRowCount = rows.length;
130
- const parentIndex = new Array(accountRowCount).fill(-1);
131
- const stack = [];
132
- for (let rowIndex = 0; rowIndex < accountRowCount; rowIndex++) {
133
- const accountRow = rows[rowIndex];
134
- if (accountRow == null) {
135
- continue;
136
- }
137
- const depth = accountRow.depth;
138
- while (stack.length > 0) {
139
- const topIndex = stack[stack.length - 1];
140
- if (topIndex == null) {
141
- break;
142
- }
143
- const topRow = rows[topIndex];
144
- if (topRow == null) {
145
- stack.pop();
146
- continue;
147
- }
148
- if (topRow.depth < depth) {
149
- break;
150
- }
151
- stack.pop();
152
- }
153
- if (depth <= 2) {
154
- parentIndex[rowIndex] = -1;
155
- }
156
- else if (stack.length === 0) {
157
- parentIndex[rowIndex] = -1;
158
- }
159
- else {
160
- const peek = stack[stack.length - 1];
161
- if (peek == null) {
162
- parentIndex[rowIndex] = -1;
163
- }
164
- else {
165
- const parentRow = rows[peek];
166
- parentIndex[rowIndex] =
167
- parentRow != null && parentRow.depth === depth - 1 ? peek : -1;
123
+ function findHorizontalAccountParentIndex(dfsOrderedAccountRows, childRowIndex) {
124
+ const childDepth = dfsOrderedAccountRows[childRowIndex].depth;
125
+ if (childDepth <= 2) {
126
+ return -1;
127
+ }
128
+ for (let earlierRowIndex = childRowIndex - 1; earlierRowIndex >= 0; earlierRowIndex--) {
129
+ const earlierDepth = dfsOrderedAccountRows[earlierRowIndex].depth;
130
+ if (earlierDepth < childDepth) {
131
+ if (earlierDepth === childDepth - 1) {
132
+ return earlierRowIndex;
168
133
  }
134
+ return -1;
169
135
  }
170
- stack.push(rowIndex);
171
136
  }
172
- return parentIndex;
137
+ return -1;
173
138
  }
174
- /** Sibling row indices (same parent + depth), in traversal order — O(n). */
175
- function buildSiblingIndicesPerRow(rows, parentIndices) {
176
- const accountRowCount = rows.length;
177
- const keyToIndices = new Map();
178
- for (let rowIndex = 0; rowIndex < accountRowCount; rowIndex++) {
179
- const parentRowIndex = parentIndices[rowIndex];
180
- const accountRow = rows[rowIndex];
181
- if (parentRowIndex == null || accountRow == null) {
182
- continue;
183
- }
184
- const key = `${parentRowIndex}:${accountRow.depth}`;
185
- let siblingGroup = keyToIndices.get(key);
186
- if (siblingGroup == null) {
187
- siblingGroup = [];
188
- keyToIndices.set(key, siblingGroup);
139
+ function getHorizontalAccountSiblingIndices(dfsOrderedAccountRows, rowIndex) {
140
+ const parentRowIndex = findHorizontalAccountParentIndex(dfsOrderedAccountRows, rowIndex);
141
+ const rowDepth = dfsOrderedAccountRows[rowIndex].depth;
142
+ const siblings = [];
143
+ for (let i = 0; i < dfsOrderedAccountRows.length; i++) {
144
+ if (dfsOrderedAccountRows[i].depth === rowDepth &&
145
+ findHorizontalAccountParentIndex(dfsOrderedAccountRows, i) ===
146
+ parentRowIndex) {
147
+ siblings.push(i);
189
148
  }
190
- siblingGroup.push(rowIndex);
191
149
  }
192
- const siblingsPerRow = new Array(accountRowCount);
193
- for (let rowIndex = 0; rowIndex < accountRowCount; rowIndex++) {
194
- const parentRowIndex = parentIndices[rowIndex];
195
- const accountRow = rows[rowIndex];
196
- if (parentRowIndex == null || accountRow == null) {
197
- siblingsPerRow[rowIndex] = [rowIndex];
198
- continue;
199
- }
200
- const key = `${parentRowIndex}:${accountRow.depth}`;
201
- const group = keyToIndices.get(key);
202
- siblingsPerRow[rowIndex] = group != null ? group : [rowIndex];
203
- }
204
- return siblingsPerRow;
150
+ return siblings;
205
151
  }
206
- function computeHorizontalHasChildFlags(parentIndices) {
207
- const rowCount = parentIndices.length;
208
- const flags = new Array(rowCount).fill(false);
209
- for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
210
- const parentRowIndex = parentIndices[rowIndex];
211
- if (parentRowIndex != null &&
212
- parentRowIndex >= 0 &&
213
- parentRowIndex < rowCount) {
214
- flags[parentRowIndex] = true;
215
- }
216
- }
217
- return flags;
152
+ function horizontalAccountHasChildRows(dfsOrderedAccountRows, parentRowIndex) {
153
+ return dfsOrderedAccountRows.some((_, rowIndex) => findHorizontalAccountParentIndex(dfsOrderedAccountRows, rowIndex) ===
154
+ parentRowIndex);
218
155
  }
219
- function buildHorizontalSubAccountAncestorMetadataFromPrecomputed(dfsOrderedAccountRows, currentRowIndex, parentIndices, siblingsPerRow, hasChildFlags) {
220
- const chain = [];
156
+ function buildHorizontalSubAccountAncestorMetadata(dfsOrderedAccountRows, currentRowIndex) {
157
+ const ancestorRowIndicesRootToLeaf = [];
221
158
  let walkRowIndex = currentRowIndex;
222
159
  while (walkRowIndex >= 0) {
223
- chain.unshift(walkRowIndex);
224
- const parentRowIndex = parentIndices[walkRowIndex];
225
- if (parentRowIndex == null) {
226
- break;
227
- }
228
- walkRowIndex = parentRowIndex;
160
+ ancestorRowIndicesRootToLeaf.unshift(walkRowIndex);
161
+ walkRowIndex = findHorizontalAccountParentIndex(dfsOrderedAccountRows, walkRowIndex);
229
162
  }
230
- return chain.map((rowIndex) => {
231
- const accountRow = dfsOrderedAccountRows[rowIndex];
232
- const siblingIndices = siblingsPerRow[rowIndex];
233
- const hasChild = hasChildFlags[rowIndex];
234
- if (accountRow == null || siblingIndices == null) {
235
- return {
236
- depth: 0,
237
- hasSibling: false,
238
- hasChildren: hasChild === true,
239
- isLastNode: true,
240
- name: '',
241
- };
242
- }
163
+ return ancestorRowIndicesRootToLeaf.map((rowIndex) => {
164
+ const siblingIndices = getHorizontalAccountSiblingIndices(dfsOrderedAccountRows, rowIndex);
243
165
  return {
244
- depth: accountRow.depth - 2,
166
+ depth: dfsOrderedAccountRows[rowIndex].depth - 2,
245
167
  hasSibling: siblingIndices.length > 1,
246
- hasChildren: hasChild === true,
168
+ hasChildren: horizontalAccountHasChildRows(dfsOrderedAccountRows, rowIndex),
247
169
  isLastNode: rowIndex === siblingIndices[siblingIndices.length - 1],
248
- name: accountRow.accountName,
170
+ name: dfsOrderedAccountRows[rowIndex].accountName,
249
171
  };
250
172
  });
251
173
  }
252
- function buildHorizontalTreeViewFromPrecomputed(dfsOrderedAccountRows, currentRowIndex, parentIndices, siblingsPerRow, hasChildFlags) {
253
- const siblingIndices = siblingsPerRow[currentRowIndex];
254
- const accountRow = dfsOrderedAccountRows[currentRowIndex];
255
- const hasChildrenFlag = hasChildFlags[currentRowIndex];
256
- const treeDepth = accountRow != null ? Math.max(0, accountRow.depth - 2) : 0;
257
- const hasChildren = hasChildrenFlag === true;
258
- const siblings = siblingIndices ?? [currentRowIndex];
259
- return {
260
- subAccountAncestorMetadata: buildHorizontalSubAccountAncestorMetadataFromPrecomputed(dfsOrderedAccountRows, currentRowIndex, parentIndices, siblingsPerRow, hasChildFlags),
261
- treeDepth,
262
- hasChildren,
263
- hasSiblings: siblings.length > 1,
264
- isLeaf: !hasChildren,
265
- isLastNode: currentRowIndex === siblings[siblings.length - 1],
266
- };
267
- }
268
- function buildHorizontalAccountChildIndicesByParent(parentIndices) {
174
+ function buildHorizontalAccountChildIndicesByParent(dfsOrderedAccountRows) {
269
175
  const childIndicesByParentRowIndex = new Map();
270
- for (let rowIndex = 0; rowIndex < parentIndices.length; rowIndex++) {
271
- const parentRowIndex = parentIndices[rowIndex];
272
- if (parentRowIndex != null && parentRowIndex >= 0) {
176
+ for (let rowIndex = 0; rowIndex < dfsOrderedAccountRows.length; rowIndex++) {
177
+ const parentRowIndex = findHorizontalAccountParentIndex(dfsOrderedAccountRows, rowIndex);
178
+ if (parentRowIndex >= 0) {
273
179
  let childRowIndices = childIndicesByParentRowIndex.get(parentRowIndex);
274
- if (childRowIndices == null) {
180
+ if (childRowIndices === undefined) {
275
181
  childRowIndices = [];
276
182
  childIndicesByParentRowIndex.set(parentRowIndex, childRowIndices);
277
183
  }
@@ -280,3 +186,16 @@ function buildHorizontalAccountChildIndicesByParent(parentIndices) {
280
186
  }
281
187
  return childIndicesByParentRowIndex;
282
188
  }
189
+ function buildHorizontalTreeView(dfsOrderedAccountRows, currentRowIndex) {
190
+ const treeDepth = Math.max(0, dfsOrderedAccountRows[currentRowIndex].depth - 2);
191
+ const siblingIndices = getHorizontalAccountSiblingIndices(dfsOrderedAccountRows, currentRowIndex);
192
+ const hasChildren = horizontalAccountHasChildRows(dfsOrderedAccountRows, currentRowIndex);
193
+ return {
194
+ subAccountAncestorMetadata: buildHorizontalSubAccountAncestorMetadata(dfsOrderedAccountRows, currentRowIndex),
195
+ treeDepth,
196
+ hasChildren,
197
+ hasSiblings: siblingIndices.length > 1,
198
+ isLeaf: !hasChildren,
199
+ isLastNode: currentRowIndex === siblingIndices[siblingIndices.length - 1],
200
+ };
201
+ }