@zeniai/client-epic-state 4.19.87-betaVR2 → 4.19.88-beta0ND

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 (42) hide show
  1. package/lib/entity/depositAccount/depositAccount.js +8 -1
  2. package/lib/epic.d.ts +4 -4
  3. package/lib/epic.js +4 -4
  4. package/lib/esm/entity/depositAccount/depositAccount.js +8 -1
  5. package/lib/esm/epic.js +4 -4
  6. package/lib/esm/index.js +4 -3
  7. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic.js +1 -2
  8. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js +1 -2
  9. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +2 -3
  10. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js +1 -4
  11. package/lib/esm/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +11 -4
  12. package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +4 -1
  13. package/lib/esm/view/expenseAutomationView/selectors/missingReceiptsSelector.js +5 -7
  14. package/lib/esm/view/expenseAutomationView/types/missingReceiptsViewState.js +6 -1
  15. package/lib/esm/view/financeStatement/financeStatementSelector.js +5 -0
  16. package/lib/esm/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelector.js +282 -0
  17. package/lib/esm/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelectorTypes.js +1 -0
  18. package/lib/esm/view/profitAndLossClassesView/profitAndLossClassesViewReducer.js +5 -1
  19. package/lib/index.d.ts +8 -6
  20. package/lib/index.js +43 -40
  21. package/lib/tsconfig.typecheck.tsbuildinfo +1 -0
  22. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic.js +1 -2
  23. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js +1 -2
  24. package/lib/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +1 -2
  25. package/lib/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js +1 -4
  26. package/lib/view/expenseAutomationView/payload/missingReceiptsPayload.d.ts +1 -1
  27. package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.d.ts +1 -1
  28. package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +11 -4
  29. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +4 -1
  30. package/lib/view/expenseAutomationView/selectorTypes/missingReceiptsSelectorTypes.d.ts +1 -1
  31. package/lib/view/expenseAutomationView/selectors/missingReceiptsSelector.js +5 -7
  32. package/lib/view/expenseAutomationView/types/missingReceiptsViewState.js +6 -1
  33. package/lib/view/financeStatement/financeStatementSelector.d.ts +2 -0
  34. package/lib/view/financeStatement/financeStatementSelector.js +5 -0
  35. package/lib/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelector.d.ts +13 -0
  36. package/lib/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelector.js +286 -0
  37. package/lib/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelectorTypes.d.ts +57 -0
  38. package/lib/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelectorTypes.js +2 -0
  39. package/lib/view/profitAndLossClassesView/profitAndLossClassesViewReducer.d.ts +4 -2
  40. package/lib/view/profitAndLossClassesView/profitAndLossClassesViewReducer.js +6 -2
  41. package/lib/view/profitAndLossClassesView/profitAndLossClassesViewState.d.ts +2 -0
  42. package/package.json +2 -2
@@ -2,7 +2,14 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.toDepositAccountStatusType = exports.toDepositSubAccounTypeCodeType = void 0;
4
4
  const stringToUnion_1 = require("../../commonStateTypes/stringToUnion");
5
- const ALL_DEPOSIT_SUB_ACCOUNT_TYPE_CODE = ['Zinc', 'Bronze', 'Gold', 'Silver', 'Platinum', 'Checking'];
5
+ const ALL_DEPOSIT_SUB_ACCOUNT_TYPE_CODE = [
6
+ 'Zinc',
7
+ 'Bronze',
8
+ 'Gold',
9
+ 'Silver',
10
+ 'Platinum',
11
+ 'Checking',
12
+ ];
6
13
  const toDepositSubAccounTypeCodeType = (v) => (0, stringToUnion_1.stringToUnion)(v, ALL_DEPOSIT_SUB_ACCOUNT_TYPE_CODE);
7
14
  exports.toDepositSubAccounTypeCodeType = toDepositSubAccounTypeCodeType;
8
15
  const ALL_DEPOSIT_ACCOUNT_STATUS_TYPE = ['Closed', 'Open', 'Frozen'];
package/lib/epic.d.ts CHANGED
@@ -145,14 +145,14 @@ import { ActionType as RetryExpenseAutomationJEScheduleActionType } from './view
145
145
  import { ActionType as BulkUploadReceiptsActionType } from './view/expenseAutomationView/epics/missingReceipts/bulkUploadReceiptsEpic';
146
146
  import { ActionType as ConfirmBulkUploadMatchActionType } from './view/expenseAutomationView/epics/missingReceipts/confirmBulkUploadMatchEpic';
147
147
  import { ActionType as FetchBulkUploadBatchDetailsActionType } from './view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchDetailsEpic';
148
- import { ActionType as FetchMultipleBatchDetailsActionType } from './view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic';
149
- import { ActionType as FetchMoreBatchDetailsActionType } from './view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic';
150
148
  import { ActionType as FetchBulkUploadBatchesActionType } from './view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic';
151
- import { ActionType as FetchExpenseAutomationMissingReceiptsActionType } from './view/expenseAutomationView/epics/missingReceipts/fetchMissingReceiptsEpic';
152
149
  import { ActionType as FetchCompletedTransactionsActionType } from './view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic';
150
+ import { ActionType as FetchExpenseAutomationMissingReceiptsActionType } from './view/expenseAutomationView/epics/missingReceipts/fetchMissingReceiptsEpic';
151
+ import { ActionType as FetchMoreBatchDetailsActionType } from './view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic';
152
+ import { ActionType as FetchMultipleBatchDetailsActionType } from './view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic';
153
153
  import { ActionType as SearchTransactionsForManualMatchActionType } from './view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic';
154
- import { ActionType as WatchBulkUploadBatchStatusActionType } from './view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic';
155
154
  import { ActionType as UploadMissingReceiptSuccessEpicActionType } from './view/expenseAutomationView/epics/missingReceipts/uploadMissingReceiptSuccessEpic';
155
+ import { ActionType as WatchBulkUploadBatchStatusActionType } from './view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic';
156
156
  import { ActionType as BackgroundRefetchReviewTabActionType } from './view/expenseAutomationView/epics/transactionCategorization/backgroundRefetchReviewTabEpic';
157
157
  import { ActionType as FetchExpenseAutomationTransactionCategorizationActionType } from './view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic';
158
158
  import { ActionType as FetchExpenseAutomationTransactionCategorizationViewActionType } from './view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationViewEpic';
package/lib/epic.js CHANGED
@@ -149,14 +149,14 @@ const retryJeSchedulesEpic_1 = require("./view/expenseAutomationView/epics/jeSch
149
149
  const bulkUploadReceiptsEpic_1 = require("./view/expenseAutomationView/epics/missingReceipts/bulkUploadReceiptsEpic");
150
150
  const confirmBulkUploadMatchEpic_1 = require("./view/expenseAutomationView/epics/missingReceipts/confirmBulkUploadMatchEpic");
151
151
  const fetchBulkUploadBatchDetailsEpic_1 = require("./view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchDetailsEpic");
152
- const fetchMultipleBatchDetailsEpic_1 = require("./view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic");
153
- const fetchMoreBatchDetailsEpic_1 = require("./view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic");
154
152
  const fetchBulkUploadBatchesEpic_1 = require("./view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic");
155
- const fetchMissingReceiptsEpic_1 = require("./view/expenseAutomationView/epics/missingReceipts/fetchMissingReceiptsEpic");
156
153
  const fetchCompletedTransactionsEpic_1 = require("./view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic");
154
+ const fetchMissingReceiptsEpic_1 = require("./view/expenseAutomationView/epics/missingReceipts/fetchMissingReceiptsEpic");
155
+ const fetchMoreBatchDetailsEpic_1 = require("./view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic");
156
+ const fetchMultipleBatchDetailsEpic_1 = require("./view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic");
157
157
  const searchTransactionsForManualMatchEpic_1 = require("./view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic");
158
- const watchBulkUploadBatchStatusEpic_1 = require("./view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic");
159
158
  const uploadMissingReceiptSuccessEpic_1 = require("./view/expenseAutomationView/epics/missingReceipts/uploadMissingReceiptSuccessEpic");
159
+ const watchBulkUploadBatchStatusEpic_1 = require("./view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic");
160
160
  const backgroundRefetchReviewTabEpic_1 = require("./view/expenseAutomationView/epics/transactionCategorization/backgroundRefetchReviewTabEpic");
161
161
  const fetchTransactionCategorizationEpic_1 = require("./view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic");
162
162
  const fetchTransactionCategorizationViewEpic_1 = require("./view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationViewEpic");
@@ -1,5 +1,12 @@
1
1
  import { stringToUnion } from '../../commonStateTypes/stringToUnion';
2
- const ALL_DEPOSIT_SUB_ACCOUNT_TYPE_CODE = ['Zinc', 'Bronze', 'Gold', 'Silver', 'Platinum', 'Checking'];
2
+ const ALL_DEPOSIT_SUB_ACCOUNT_TYPE_CODE = [
3
+ 'Zinc',
4
+ 'Bronze',
5
+ 'Gold',
6
+ 'Silver',
7
+ 'Platinum',
8
+ 'Checking',
9
+ ];
3
10
  export const toDepositSubAccounTypeCodeType = (v) => stringToUnion(v, ALL_DEPOSIT_SUB_ACCOUNT_TYPE_CODE);
4
11
  const ALL_DEPOSIT_ACCOUNT_STATUS_TYPE = ['Closed', 'Open', 'Frozen'];
5
12
  export const toDepositAccountStatusType = (v) => stringToUnion(v, ALL_DEPOSIT_ACCOUNT_STATUS_TYPE);
package/lib/esm/epic.js CHANGED
@@ -147,14 +147,14 @@ import { retryJeScheduleEpic as retryExpenseAutomationJEScheduleEpic, } from './
147
147
  import { bulkUploadReceiptsEpic, } from './view/expenseAutomationView/epics/missingReceipts/bulkUploadReceiptsEpic';
148
148
  import { confirmBulkUploadMatchEpic, } from './view/expenseAutomationView/epics/missingReceipts/confirmBulkUploadMatchEpic';
149
149
  import { fetchBulkUploadBatchDetailsEpic, } from './view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchDetailsEpic';
150
- import { fetchMultipleBatchDetailsEpic, } from './view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic';
151
- import { fetchMoreBatchDetailsEpic, } from './view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic';
152
150
  import { fetchBulkUploadBatchesEpic, } from './view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic';
153
- import { fetchMissingReceiptsEpic as fetchExpenseAutomationMissingReceiptsEpic, } from './view/expenseAutomationView/epics/missingReceipts/fetchMissingReceiptsEpic';
154
151
  import { fetchCompletedTransactionsEpic, } from './view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic';
152
+ import { fetchMissingReceiptsEpic as fetchExpenseAutomationMissingReceiptsEpic, } from './view/expenseAutomationView/epics/missingReceipts/fetchMissingReceiptsEpic';
153
+ import { fetchMoreBatchDetailsEpic, } from './view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic';
154
+ import { fetchMultipleBatchDetailsEpic, } from './view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic';
155
155
  import { searchTransactionsForManualMatchEpic, } from './view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic';
156
- import { bulkUploadAutomatchingTimeoutEpic, pollBulkUploadBatchStatusEpic, pusherBatchStatusCompletionEpic, } from './view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic';
157
156
  import { uploadMissingReceiptSuccessEpic, } from './view/expenseAutomationView/epics/missingReceipts/uploadMissingReceiptSuccessEpic';
157
+ import { bulkUploadAutomatchingTimeoutEpic, pollBulkUploadBatchStatusEpic, pusherBatchStatusCompletionEpic, } from './view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic';
158
158
  import { backgroundRefetchReviewTabEpic, } from './view/expenseAutomationView/epics/transactionCategorization/backgroundRefetchReviewTabEpic';
159
159
  import { fetchTransactionCategorizationEpic as fetchExpenseAutomationTransactionCategorizationEpic, } from './view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic';
160
160
  import { fetchTransactionCategorizationViewEpic as fetchExpenseAutomationTransactionCategorizationViewEpic, } from './view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationViewEpic';
package/lib/esm/index.js CHANGED
@@ -163,7 +163,7 @@ import { isReviewTransactionBankTransferType, isReviewTransactionBillPaymentType
163
163
  import { MAX_SELECTION_LIMIT, checkIfAllLineItemsAreCategoryClassFilled, getLineItemsByTransactionIdsFromLocalData, isAnyItemWithUncategorizedExpenseAccount, } from './view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper';
164
164
  import { clearExpenseAutomationFluxAnalysisView, fetchFluxAnalysisView, reviewFluxAnalysisView, updateFluxAnalysisViewPageMetaData, updateFluxAnalysisViewUIState, updateOperatingExpensesIdsForReview, updateSelectedSectionIdsForReview, } from './view/expenseAutomationView/reducers/fluxAnalysisViewReducer';
165
165
  import { clearJeScheduleLocalData as clearExpenseAutomationJEScheduleLocalData, clearExpenseAutomationJESchedulesView, fetchJeSchedulesPage as fetchExpenseAutomationJESchedulesPage, ignoreRecommendedJeSchedule as ignoreExpenseAutomationJESchedule, initializeAccountSettingsView as initializeJeAccountSettingsView, initializeJeScheduleLocalData, removeJeScheduleTransactionKey, retryJeSchedule as retryExpenseAutomationJESchedule, saveAccountSettings as saveJeAccountSettings, saveAccountSettingsLocalData as saveJeAccountSettingsLocalData, updateJeScheduleLocalDataById, updateJeScheduleTransactionKeys, } from './view/expenseAutomationView/reducers/jeSchedulesViewReducer';
166
- import { bulkUploadReceipts, bulkUploadAutomatchingTimedOut, bulkUploadReceiptsFailure, bulkUploadReceiptsSuccess, clearBulkUpload, confirmBulkUploadMatch, confirmBulkUploadMatchFailure, confirmBulkUploadMatchSuccess, fetchBulkUploadBatchDetails, fetchBulkUploadBatchDetailsFailure, fetchBulkUploadBatchDetailsSuccess, storeBatchDetails, fetchMoreBatchDetails, fetchMoreBatchDetailsComplete, fetchMoreBatchDetailsFailure, fetchBulkUploadBatches, fetchBulkUploadBatchesFailure, fetchBulkUploadBatchesSuccess, fetchMissingReceipts as fetchExpenseAutomationMissingReceipts, markMissingReceiptAsDone as markExpenseAutomationMissingReceiptAsDone, pusherBatchStatusUpdate, requestMissingReceiptsTabNavigation, clearMissingReceiptsTabNavigation, setBulkUploadResultsTab, setBulkUploadCompletedSubTab, setBulkUploadSortConfig, searchTransactionsForManualMatch, searchTransactionsForManualMatchSuccess, searchTransactionsForManualMatchFailure, clearManualSearchResults, acknowledgeBulkUploadConfirmMatchComplete, fetchCompletedTransactions, fetchCompletedTransactionsSuccess, fetchCompletedTransactionsFailure, updateBulkUploadProgress, updateMissingReceiptUploadState as updateExpenseAutomationMissingReceiptUploadState, updateMissingReceiptsUIState as updateExpenseAutomationMissingReceiptsUIState, uploadMissingReceiptSuccess as uploadExpenseAutomationMissingReceiptSuccess, } from './view/expenseAutomationView/reducers/missingReceiptsViewReducer';
166
+ import { acknowledgeBulkUploadConfirmMatchComplete, bulkUploadAutomatchingTimedOut, bulkUploadReceipts, bulkUploadReceiptsFailure, bulkUploadReceiptsSuccess, clearBulkUpload, clearManualSearchResults, clearMissingReceiptsTabNavigation, confirmBulkUploadMatch, confirmBulkUploadMatchFailure, confirmBulkUploadMatchSuccess, fetchBulkUploadBatchDetails, fetchBulkUploadBatchDetailsFailure, fetchBulkUploadBatchDetailsSuccess, fetchBulkUploadBatches, fetchBulkUploadBatchesFailure, fetchBulkUploadBatchesSuccess, fetchCompletedTransactions, fetchCompletedTransactionsFailure, fetchCompletedTransactionsSuccess, fetchMissingReceipts as fetchExpenseAutomationMissingReceipts, fetchMoreBatchDetails, fetchMoreBatchDetailsComplete, fetchMoreBatchDetailsFailure, markMissingReceiptAsDone as markExpenseAutomationMissingReceiptAsDone, pusherBatchStatusUpdate, requestMissingReceiptsTabNavigation, searchTransactionsForManualMatch, searchTransactionsForManualMatchFailure, searchTransactionsForManualMatchSuccess, setBulkUploadCompletedSubTab, setBulkUploadResultsTab, setBulkUploadSortConfig, storeBatchDetails, updateBulkUploadProgress, updateMissingReceiptUploadState as updateExpenseAutomationMissingReceiptUploadState, updateMissingReceiptsUIState as updateExpenseAutomationMissingReceiptsUIState, uploadMissingReceiptSuccess as uploadExpenseAutomationMissingReceiptSuccess, } from './view/expenseAutomationView/reducers/missingReceiptsViewReducer';
167
167
  import { deleteAccountStatement, fetchReconciliation as fetchReconciliationView, saveReconciliationDetail as saveExpenseAutomationReconciliationDetail, saveReconciliationReview as saveExpenseAutomationReconciliationReview, setConnectionInProgressForAccount as setConnectionInProgressForAccountReconciliation, setStatementParseInProgress, updateAccountReconciliationLocalData as updateExpenseAutomationAccountReconciliationLocalData, updateSelectedAccountId as updateExpenseAutomationAccountReconciliationSelectedAccountId, updateSelectedTab as updateExpenseAutomationAccountReconciliationSelectedTab, updateReconListScrollPosition as updateExpenseAutomationReconListScrollPosition, updateReviewTabSortState as updateExpenseAutomationReconReviewTabListSortState, updateReviewTabLocalData as updateExpenseAutomationReconReviewTabLocalData, updateReconcileTabListScrollState as updateExpenseAutomationReconcileTabListScrollState, updateReconcileTabListSortState as updateExpenseAutomationReconcileTabListSortState, updateReconcileTabLocalData as updateExpenseAutomationReconcileTabLocalData, updateSelectedDrawerAccountId as updateExpenseAutomationSelectedDrawerAccountId, updateStatementUploadChosen, uploadAccountStatement, } from './view/expenseAutomationView/reducers/reconciliationViewReducer';
168
168
  import { backgroundRefetchReviewTab, clearExpenseAutomationTransactionsView, fetchTransactionCategorization, fetchTransactionCategorizationFailure, fetchTransactionCategorizationView, initializeTransactionCategorizationViewLocalData, markTransactionAsNotMiscategorized, saveTransactionCategorization, saveTransactionCategorizationLocalData, setAllItemsToCategoryClassInLocalDataForCategorization, setEntityRecommendationForLineIdsForCategorization, syncTransactionCategorizationFromDetailSave, updateCurrentSelectedTransactionCategorizationTab, updateSelectedCheckboxTransactionIds, updateSelectedCustomerForTransaction, updateSelectedTransactionId, updateSelectedVendorForTransaction, updateTransactionCategorization, updateTransactionCategorizationSaveStatus, updateTransactionCategorizationUIState, updateTransactionCategorizationUploadReceiptState, uploadTransactionCategorizationReceiptSuccess, } from './view/expenseAutomationView/reducers/transactionsViewReducer';
169
169
  import { getExpenseAutomationFluxAnalysisView } from './view/expenseAutomationView/selectors/fluxAnalysisViewSelector';
@@ -217,7 +217,8 @@ import { changeZeniPersonRoles, deletePerson, fetchAllPeopleRequiredViews, fetch
217
217
  import { getPeople, getPeopleLocalData } from './view/people/peopleSelector';
218
218
  import { fetchProfitAndLoss, fetchProfitAndLossForTimeframe, resetProfitAndLossNodeCollapseState, updateProfitAndLossUIState, } from './view/profitAndLoss/profitAndLossReducer';
219
219
  import { getPandLReportFetchState, getProfitAndLossReport, } from './view/profitAndLoss/profitAndLossSelector';
220
- import { fetchProfitAndLossClassesView, resetProfitAndLossClassesNodeCollapseState, updateAccountViewMode as updateProfitAndLossAccountViewMode, updateClassesToFilterOut as updateProfitAndLossClassesToFilterOut, updateProfitAndLossClassesViewUIState, } from './view/profitAndLossClassesView/profitAndLossClassesViewReducer';
220
+ import { fetchProfitAndLossClassesView, resetProfitAndLossClassesNodeCollapseState, updateAccountViewMode as updateProfitAndLossAccountViewMode, updateClassesToFilterOut as updateProfitAndLossClassesToFilterOut, updateClassViewLayout, updateProfitAndLossClassesViewUIState, } from './view/profitAndLossClassesView/profitAndLossClassesViewReducer';
221
+ import { getProfitAndLossClassesHorizontalView } from './view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelector';
221
222
  import { getProfitAndLossClassesView } from './view/profitAndLossClassesView/profitAndLossClassesViewSelector';
222
223
  import { isPandLReportViewCalculatedSectionID, isPandLReportViewSectionID, } from './view/profitAndLossClassesView/profitAndLossClassesViewSelectorTypes';
223
224
  import { fetchEntityRecommendationsByTransactionId, fetchRecommendationByEntityId, fetchRecommendationByEntityName, } from './view/recommendation/recommendationReducer';
@@ -451,7 +452,7 @@ export { fetchDashboard, getDashboard, updateTreasuryVideoClosed, };
451
452
  export { updateDashboardLayout };
452
453
  export { getPandLWithForecast, } from './view/profitAndLoss/pAndLWithForecast/pAndLWithForecastSelector';
453
454
  export { fetchProfitAndLoss, resetProfitAndLossNodeCollapseState, updateProfitAndLossUIState, getProfitAndLossReport, getPandLReportFetchState, fetchProfitAndLossForTimeframe, };
454
- export { fetchProfitAndLossClassesView, updateProfitAndLossClassesToFilterOut, resetProfitAndLossClassesNodeCollapseState, updateProfitAndLossClassesViewUIState, updateProfitAndLossAccountViewMode, getProfitAndLossClassesView, isPandLReportViewCalculatedSectionID, isPandLReportViewSectionID, };
455
+ export { fetchProfitAndLossClassesView, updateProfitAndLossClassesToFilterOut, resetProfitAndLossClassesNodeCollapseState, updateProfitAndLossClassesViewUIState, updateProfitAndLossAccountViewMode, updateClassViewLayout, getProfitAndLossClassesView, getProfitAndLossClassesHorizontalView, isPandLReportViewCalculatedSectionID, isPandLReportViewSectionID, };
455
456
  export { getAccountingProviderAttachment };
456
457
  export { fetchUserListByType, getUserList };
457
458
  export { fetchAllPeopleRequiredViews, fetchPeoplePage, fetchPeople, deletePerson, resendInvite, changeZeniPersonRoles, invitePeople, inviteZeniPeople, updatePeopleUIState, peopleSaveUpdates, resetUpdateErrorMessage, peopleSaveDataInLocalStore, peopleClearDataInLocalStore, getPeople, getPeopleLocalData, initializeEditPerson, };
@@ -28,7 +28,6 @@ exhaustMap(() => {
28
28
  }
29
29
  const nextPage = unfetchedBatchIds.slice(0, MORE_BATCH_PAGE_SIZE);
30
30
  return concat(fetchBatchDetailsByIds(nextPage, zeniAPI), of(fetchMoreBatchDetailsComplete())).pipe(catchError((error) => of(fetchMoreBatchDetailsFailure({
31
- error: createZeniAPIStatus('Unexpected Error', 'Fetch more batch details errored out: ' +
32
- JSON.stringify(error)),
31
+ error: createZeniAPIStatus('Unexpected Error', 'Fetch more batch details errored out: ' + JSON.stringify(error)),
33
32
  }))));
34
33
  }));
@@ -25,8 +25,7 @@ export function fetchBatchDetailsByIds(batchIds, zeniAPI) {
25
25
  actions.push(storeBatchDetails(toBatchDetails(response.data, zeniAPI.apiEndPoints.fileMicroServiceBaseUrl)));
26
26
  return from(actions);
27
27
  }), catchError((error) => {
28
- console.error(`Failed to fetch batch details for ${batchId}:`, createZeniAPIStatus('Unexpected Error', 'Fetch batch details errored out: ' +
29
- JSON.stringify(error)));
28
+ console.error(`Failed to fetch batch details for ${batchId}:`, createZeniAPIStatus('Unexpected Error', 'Fetch batch details errored out: ' + JSON.stringify(error)));
30
29
  return of(batchDetailFetchFailed({ batchId }));
31
30
  })), 5));
32
31
  }
@@ -1,5 +1,5 @@
1
1
  import { EMPTY, from, merge, of } from 'rxjs';
2
- import { catchError, debounceTime, filter, mergeMap, switchMap } from 'rxjs/operators';
2
+ import { catchError, debounceTime, filter, mergeMap, switchMap, } from 'rxjs/operators';
3
3
  import { convertToPeriod } from '../../../../commonStateTypes/timePeriod';
4
4
  import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
5
5
  import { updateTransactions } from '../../../../entity/transaction/transactionReducer';
@@ -25,8 +25,7 @@ export const searchTransactionsForManualMatchEpic = (actions$, state$, zeniAPI)
25
25
  action.payload.query.trim().length >= 2), debounceTime(300));
26
26
  return merge(pagination$, newSearchTooShortOrClear$, newSearchDebounced$).pipe(switchMap((action) => {
27
27
  const { query, pageToken } = action.payload;
28
- if (pageToken == null &&
29
- query.trim().length < 2) {
28
+ if (pageToken == null && query.trim().length < 2) {
30
29
  return EMPTY;
31
30
  }
32
31
  const state = state$.value;
@@ -29,12 +29,9 @@ export const bulkUploadAutomatchingTimeoutEpic = (actions$, state$) => actions$.
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;
32
- if (bulk.phase !== 'matching' ||
33
- bulk.currentBatchId !== batchId) {
32
+ if (bulk.phase !== 'matching' || bulk.currentBatchId !== batchId) {
34
33
  return EMPTY;
35
34
  }
36
- // eslint-disable-next-line no-console
37
- console.warn('[bulk-upload] Automatching wait exceeded timeout; releasing UI', { batchId });
38
35
  return from([
39
36
  bulkUploadAutomatchingTimedOut(batchId),
40
37
  fetchBulkUploadBatches({ cacheOverride: true }),
@@ -323,8 +323,7 @@ const expenseAutomationMissingReceiptsView = createSlice({
323
323
  * dismiss the Unmatched skeleton.
324
324
  */
325
325
  const currentId = draft.bulkUpload.currentBatchId;
326
- if (draft.bulkUpload.phase === 'matching' &&
327
- currentId != null) {
326
+ if (draft.bulkUpload.phase === 'matching' && currentId != null) {
328
327
  const existing = draft.bulkUpload.batchDetailsById[currentId];
329
328
  if (existing?.status === 'completed') {
330
329
  draft.bulkUpload.phase = 'completed';
@@ -345,7 +344,15 @@ const expenseAutomationMissingReceiptsView = createSlice({
345
344
  };
346
345
  },
347
346
  prepare(batchId, attachmentId, transactionId, transactionType, fileName) {
348
- return { payload: { batchId, attachmentId, transactionId, transactionType, fileName } };
347
+ return {
348
+ payload: {
349
+ batchId,
350
+ attachmentId,
351
+ transactionId,
352
+ transactionType,
353
+ fileName,
354
+ },
355
+ };
349
356
  },
350
357
  },
351
358
  confirmBulkUploadMatchSuccess(draft, action) {
@@ -492,7 +499,7 @@ const expenseAutomationMissingReceiptsView = createSlice({
492
499
  },
493
500
  },
494
501
  fetchCompletedTransactionsSuccess(draft, action) {
495
- const { transactionIds, nextPageToken, totalCount, selectedPeriod, isInitialLoad } = action.payload;
502
+ const { transactionIds, nextPageToken, totalCount, selectedPeriod, isInitialLoad, } = action.payload;
496
503
  const periodId = toMonthYearPeriodId(selectedPeriod);
497
504
  const cacheKey = getCompletedTransactionsCacheKey(periodId, draft.bulkUpload.sortKey, draft.bulkUpload.sortOrder, draft.bulkUpload.completedSubTab);
498
505
  if (isInitialLoad) {
@@ -712,7 +712,10 @@ const expenseAutomationTransactionsView = createSlice({
712
712
  uploadTransactionCategorizationReceiptSuccess: {
713
713
  reducer(draft, action) {
714
714
  const { transactionPayload, selectedTab } = action.payload;
715
- draft.transactionCategorizationView[selectedTab].uploadReceiptStatusById[transactionPayload.transaction_id] = { fetchState: 'Completed', error: undefined };
715
+ draft.transactionCategorizationView[selectedTab].uploadReceiptStatusById[transactionPayload.transaction_id] = {
716
+ fetchState: 'Completed',
717
+ error: undefined,
718
+ };
716
719
  },
717
720
  prepare(selectedTab, transactionPayload, updateType = 'merge') {
718
721
  return { payload: { selectedTab, transactionPayload, updateType } };
@@ -2,9 +2,9 @@ import orderBy from 'lodash/orderBy';
2
2
  import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
3
3
  import { getCurrentTenant } from '../../../entity/tenant/tenantSelector';
4
4
  import { getSupportedTransactionById, getSupportedTransactionsByIds, } from '../../../entity/transaction/transactionSelector';
5
- import { getAllSteps } from '../selectorTypes/expenseAutomationViewSelectorTypes';
6
5
  import { isBatchListStatusCompleted } from '../payload/missingReceiptsPayload';
7
6
  import { getCompletedTransactionsCacheKey } from '../reducers/missingReceiptsViewReducer';
7
+ import { getAllSteps } from '../selectorTypes/expenseAutomationViewSelectorTypes';
8
8
  import { isUnmatchedTabFileStatus, } from '../types/missingReceiptsViewState';
9
9
  export function getExpenseAutomationMissingReceiptsView(state) {
10
10
  const { expenseAutomationMissingReceiptsViewState, expenseAutomationViewState, transactionState, monthEndCloseChecksState, } = state;
@@ -50,12 +50,12 @@ export function getExpenseAutomationMissingReceiptsView(state) {
50
50
  const completionStatus = missingReceiptsCompletionStatus != null
51
51
  ? missingReceiptsCompletionStatus
52
52
  : 'incomplete';
53
- const { bulkUpload, pendingMissingReceiptsTabNavigation, } = expenseAutomationMissingReceiptsViewState;
53
+ const { bulkUpload, pendingMissingReceiptsTabNavigation } = expenseAutomationMissingReceiptsViewState;
54
54
  const currentBatchStatus = bulkUpload.currentBatchId != null
55
55
  ? bulkUpload.batchStatusById[bulkUpload.currentBatchId]
56
56
  : undefined;
57
57
  const batchList = monthYearPeriodId != null
58
- ? bulkUpload.batchListByPeriod[monthYearPeriodId] ?? []
58
+ ? (bulkUpload.batchListByPeriod[monthYearPeriodId] ?? [])
59
59
  : [];
60
60
  /**
61
61
  * Primary "Unmatched" batch = first completed batch in platform list order, preferring the
@@ -104,7 +104,7 @@ export function getExpenseAutomationMissingReceiptsView(state) {
104
104
  }
105
105
  switch (bulkUpload.sortKey) {
106
106
  case 'vendor':
107
- return (matchedTransaction.vendorName ?? matchedTransaction.description)?.toLowerCase() ?? null;
107
+ return ((matchedTransaction.vendorName ?? matchedTransaction.description)?.toLowerCase() ?? null);
108
108
  case 'category': {
109
109
  const line = matchedTransaction.lines?.[0];
110
110
  return line?.account?.accountName?.toLowerCase() ?? null;
@@ -296,9 +296,7 @@ function resolveBatchFile(file, batchId, transactionState) {
296
296
  filePreviewUrl: file.filePreviewUrl,
297
297
  status: file.status,
298
298
  matchedTransaction,
299
- candidates: candidates != null && candidates.length > 0
300
- ? candidates
301
- : undefined,
299
+ candidates: candidates != null && candidates.length > 0 ? candidates : undefined,
302
300
  matchSource: file.matchSource,
303
301
  };
304
302
  }
@@ -28,7 +28,12 @@ const UNMATCHED_TAB_FILE_STATUSES = ['possible_matches', 'no_match'];
28
28
  export function isUnmatchedTabFileStatus(status) {
29
29
  return UNMATCHED_TAB_FILE_STATUSES.includes(status);
30
30
  }
31
- const BULK_UPLOAD_PHASES = ['idle', 'uploading', 'matching', 'completed'];
31
+ const BULK_UPLOAD_PHASES = [
32
+ 'idle',
33
+ 'uploading',
34
+ 'matching',
35
+ 'completed',
36
+ ];
32
37
  export const toBulkUploadPhase = (v) => stringToUnion(v.trim().toLowerCase(), BULK_UPLOAD_PHASES);
33
38
  const BULK_UPLOAD_RESULTS_TABS = ['completed', 'unmatched'];
34
39
  export const toBulkUploadResultsTab = (v) => stringToUnion(v.trim().toLowerCase(), BULK_UPLOAD_RESULTS_TABS);
@@ -8,6 +8,7 @@ import { extractThisPeriod } from '../../commonStateTypes/viewAndReport/thisPeri
8
8
  import { getBalanceSheet } from '../balanceSheet/balanceSheetSelector';
9
9
  import { getCashFlow } from '../cashFlow/cashFlowSelector';
10
10
  import { getProfitAndLossReport, } from '../profitAndLoss/profitAndLossSelector';
11
+ import { getProfitAndLossClassesHorizontalView } from '../profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelector';
11
12
  import { getProfitAndLossClassesView } from '../profitAndLossClassesView/profitAndLossClassesViewSelector';
12
13
  function getDateRangeForFinanceReports(profitAndLossState, balanceSheetState, cashFlowState) {
13
14
  const datesSorted = [
@@ -60,6 +61,9 @@ export const getFinanceStatement = createSelector((state) => state.accountState,
60
61
  const profitAndLossReport = getProfitAndLossReport(filter, undefined, accountState, accountGroupState, sectionAccountsViewState, profitAndLossState, forecastState);
61
62
  const classFilter = newBalancesFilterClassesView(filter.firstMonthOfFY, filter.timeframe, filter.balancesRange.numberOfPeriods, filter.balancesRange.thisPeriod, filter.balancesRange.orderBy, filter.additionalBalances, profitAndLossClassesViewState.uiState.classesToFilterOut);
62
63
  const profitAndLossByClassReport = getProfitAndLossClassesView(classFilter, accountState, classState, sectionsState, profitAndLossClassesViewState);
64
+ const profitAndLossByClassHorizontalReport = profitAndLossClassesViewState.uiState.classViewLayout === 'horizontal'
65
+ ? getProfitAndLossClassesHorizontalView(classFilter, accountState, classState, sectionsState, profitAndLossClassesViewState, thisPeriod)
66
+ : getProfitAndLossClassesHorizontalView(classFilter, accountState, classState, sectionsState, profitAndLossClassesViewState, undefined);
63
67
  const balanceSheetReport = getBalanceSheet(filter, accountState, accountGroupState, sectionAccountsViewState, balanceSheetState);
64
68
  const cashFlowReport = getCashFlow(filter, accountState, accountGroupState, sectionAccountsViewState, cashFlowState);
65
69
  const allReports = [
@@ -107,6 +111,7 @@ export const getFinanceStatement = createSelector((state) => state.accountState,
107
111
  dataAvailable,
108
112
  profitAndLossReport,
109
113
  profitAndLossByClassReport,
114
+ profitAndLossByClassHorizontalReport,
110
115
  balanceSheetReport,
111
116
  cashFlowReport,
112
117
  filter,
@@ -0,0 +1,282 @@
1
+ import { toAmountWC } from '../../commonStateTypes/amount';
2
+ import { getSectionClassesViewReport } from '../../entity/sectionClassesViewV2/sectionClassesViewSelector';
3
+ import { ALL_PANDL_REPORT_VIEW_SECTION_IDS, } from './profitAndLossClassesViewSelectorTypes';
4
+ const DEFAULT_CURRENCY = { currencyCode: 'USD', currencySymbol: '$' };
5
+ export const profitAndLossHorizontalIdsInOrder = [
6
+ 'income',
7
+ 'cogs',
8
+ 'grossProfit',
9
+ 'expenses',
10
+ 'earningsBeforeInterestAndTax',
11
+ 'otherIncome',
12
+ 'otherExpenses',
13
+ 'netOtherIncome',
14
+ 'netIncome',
15
+ ];
16
+ const SECTION_TITLES = {
17
+ income: 'Income',
18
+ cogs: 'Cost of Goods Sold',
19
+ grossProfit: 'Gross Profit',
20
+ expenses: 'Expenses',
21
+ earningsBeforeInterestAndTax: 'Net Operating Income',
22
+ otherIncome: 'Other Income',
23
+ otherExpenses: 'Other Expenses',
24
+ netOtherIncome: 'Net Other Income',
25
+ netIncome: 'Net Income',
26
+ };
27
+ export const getProfitAndLossClassesHorizontalView = (filter, accountState, classState, sectionsState, profitAndLossClassesViewState, selectedPeriod) => {
28
+ const fetchState = reduceFetchState([
29
+ profitAndLossClassesViewState.fetchState.month,
30
+ profitAndLossClassesViewState.fetchState.quarter,
31
+ profitAndLossClassesViewState.fetchState.year,
32
+ ]);
33
+ const reportId = 'profit_and_loss_by_classes';
34
+ // Build class columns from the class hierarchy (top-level only)
35
+ const classColumns = buildClassColumns(profitAndLossClassesViewState.classHierarchy, profitAndLossClassesViewState.uiState.classesToFilterOut);
36
+ const sections = {};
37
+ const calculatedSections = {};
38
+ if (fetchState.fetchState === 'Completed' && selectedPeriod != null) {
39
+ // Get section reports using existing selector infrastructure
40
+ const sectionReports = {};
41
+ ALL_PANDL_REPORT_VIEW_SECTION_IDS.forEach((sectionId) => {
42
+ sectionReports[sectionId] = getSectionClassesViewReport(accountState, classState, sectionsState, { reportId, sectionId }, filter);
43
+ });
44
+ // Pivot each section from class→account to account→class
45
+ const sectionIds = [
46
+ 'income',
47
+ 'cogs',
48
+ 'expenses',
49
+ 'otherIncome',
50
+ 'otherExpenses',
51
+ ];
52
+ sectionIds.forEach((sectionId) => {
53
+ const sectionReport = sectionReports[sectionId];
54
+ if (sectionReport != null) {
55
+ sections[sectionId] = pivotSectionToHorizontal(sectionReport, classColumns, selectedPeriod, SECTION_TITLES[sectionId] ?? sectionId);
56
+ }
57
+ });
58
+ // Calculate derived sections
59
+ const incomeSection = sections['income'];
60
+ const cogsSection = sections['cogs'];
61
+ const expensesSection = sections['expenses'];
62
+ const otherIncomeSection = sections['otherIncome'];
63
+ const otherExpensesSection = sections['otherExpenses'];
64
+ // Gross Profit = Income - COGS
65
+ if (incomeSection != null && cogsSection != null) {
66
+ calculatedSections['grossProfit'] = computeCalculatedSection(incomeSection.classTotals, cogsSection.classTotals, incomeSection.sectionTotal, cogsSection.sectionTotal, 'subtract', SECTION_TITLES['grossProfit']);
67
+ }
68
+ // EBIT = Gross Profit - Expenses
69
+ const grossProfit = calculatedSections['grossProfit'];
70
+ if (grossProfit != null && expensesSection != null) {
71
+ calculatedSections['earningsBeforeInterestAndTax'] =
72
+ computeCalculatedSection(grossProfit.classTotals, expensesSection.classTotals, grossProfit.sectionTotal, expensesSection.sectionTotal, 'subtract', SECTION_TITLES['earningsBeforeInterestAndTax']);
73
+ }
74
+ // Net Other Income = Other Income - Other Expenses
75
+ if (otherIncomeSection != null && otherExpensesSection != null) {
76
+ calculatedSections['netOtherIncome'] = computeCalculatedSection(otherIncomeSection.classTotals, otherExpensesSection.classTotals, otherIncomeSection.sectionTotal, otherExpensesSection.sectionTotal, 'subtract', SECTION_TITLES['netOtherIncome']);
77
+ }
78
+ // Net Income = EBIT + Net Other Income
79
+ const ebit = calculatedSections['earningsBeforeInterestAndTax'];
80
+ const netOtherIncome = calculatedSections['netOtherIncome'];
81
+ if (ebit != null && netOtherIncome != null) {
82
+ calculatedSections['netIncome'] = computeCalculatedSection(ebit.classTotals, netOtherIncome.classTotals, ebit.sectionTotal, netOtherIncome.sectionTotal, 'add', SECTION_TITLES['netIncome']);
83
+ }
84
+ }
85
+ // Build synthetic header COABalances for rendering class names as column headers
86
+ const headerBalances = buildHeaderBalances(classColumns, filter.timeframe, selectedPeriod);
87
+ return {
88
+ reportId,
89
+ reportTitle: 'Profit and Loss by Class',
90
+ firstMonthOfFY: profitAndLossClassesViewState.firstMonthOfFY,
91
+ fetchState: fetchState.fetchState,
92
+ bookCloseDate: profitAndLossClassesViewState.bookCloseDate,
93
+ dataAvailable: profitAndLossClassesViewState.dataAvailable,
94
+ selectedPeriod,
95
+ classColumns,
96
+ sections,
97
+ calculatedSections,
98
+ idsInOrder: profitAndLossHorizontalIdsInOrder,
99
+ uiState: profitAndLossClassesViewState.uiState,
100
+ headerBalances,
101
+ };
102
+ };
103
+ /**
104
+ * Build column definitions from class hierarchy (top-level classes only).
105
+ */
106
+ function buildClassColumns(classHierarchy, classesToFilterOut) {
107
+ return classHierarchy
108
+ .filter((c) => !classesToFilterOut.some((filteredId) => filteredId[0] === c.classId[0]))
109
+ .map((c) => ({
110
+ classId: c.classId[0],
111
+ className: c.className,
112
+ nestedClassId: c.classId,
113
+ qboId: c.qboId,
114
+ }));
115
+ }
116
+ /**
117
+ * Pivot a section from class→account→balances to account→class→balance (single period).
118
+ */
119
+ function pivotSectionToHorizontal(section, classColumns, selectedPeriod, title) {
120
+ // Map: accountId -> { accountName, classId -> balance }
121
+ const accountMap = new Map();
122
+ // Also track class totals
123
+ const classTotalMap = new Map();
124
+ classColumns.forEach((col) => classTotalMap.set(col.classId, 0));
125
+ // Iterate over each class in the section
126
+ section.classes.forEach((classReport) => {
127
+ const classId = classReport.id[0];
128
+ // Only process classes that are in our columns
129
+ if (!classColumns.some((col) => col.classId === classId)) {
130
+ return;
131
+ }
132
+ // Get the class-level balance for this period (total for the class in this section)
133
+ const classBalance = findBalanceForPeriod(classReport.childrenBalancesTotal.balances, selectedPeriod);
134
+ classTotalMap.set(classId, classBalance);
135
+ // Iterate through accounts in this class
136
+ collectAccountsFromClass(classReport, classId, selectedPeriod, accountMap);
137
+ });
138
+ // Build account rows
139
+ const accounts = [];
140
+ accountMap.forEach((accountData, accountId) => {
141
+ const classBalances = classColumns.map((col) => ({
142
+ classId: col.classId,
143
+ balance: toAmountWC(accountData.classBalances.get(col.classId) ?? 0, DEFAULT_CURRENCY),
144
+ }));
145
+ const rowTotal = classBalances.reduce((sum, cb) => sum + cb.balance.amount, 0);
146
+ accounts.push({
147
+ accountId,
148
+ accountName: accountData.accountName,
149
+ classBalances,
150
+ rowTotal: toAmountWC(rowTotal, DEFAULT_CURRENCY),
151
+ });
152
+ });
153
+ // Build class totals
154
+ const classTotals = classColumns.map((col) => ({
155
+ classId: col.classId,
156
+ balance: toAmountWC(classTotalMap.get(col.classId) ?? 0, DEFAULT_CURRENCY),
157
+ }));
158
+ const sectionTotal = classTotals.reduce((sum, ct) => sum + ct.balance.amount, 0);
159
+ return {
160
+ title,
161
+ accounts,
162
+ classTotals,
163
+ sectionTotal: toAmountWC(sectionTotal, DEFAULT_CURRENCY),
164
+ };
165
+ }
166
+ /**
167
+ * Recursively collect account balances from a NestedClassReportV2 into accountMap.
168
+ */
169
+ function collectAccountsFromClass(classReport, classId, selectedPeriod, accountMap) {
170
+ // Process accounts directly on this class
171
+ classReport.class.accounts.forEach((nestedAccount) => {
172
+ collectAccountsFromNestedAccount(nestedAccount, classId, selectedPeriod, accountMap);
173
+ });
174
+ }
175
+ /**
176
+ * Recursively collect accounts from NestedAccountReport (handles sub-accounts).
177
+ */
178
+ function collectAccountsFromNestedAccount(nestedAccount, classId, selectedPeriod, accountMap) {
179
+ const accountId = nestedAccount.account.accountId;
180
+ const accountName = nestedAccount.account.accountName;
181
+ const balance = findBalanceForPeriod(nestedAccount.account.balancesInTimeframe.balances, selectedPeriod);
182
+ if (!accountMap.has(accountId)) {
183
+ accountMap.set(accountId, {
184
+ accountName,
185
+ classBalances: new Map(),
186
+ });
187
+ }
188
+ const accountData = accountMap.get(accountId);
189
+ if (accountData == null) {
190
+ return;
191
+ }
192
+ const existing = accountData.classBalances.get(classId) ?? 0;
193
+ accountData.classBalances.set(classId, existing + balance);
194
+ // Process sub-accounts
195
+ nestedAccount.children.forEach((child) => {
196
+ collectAccountsFromNestedAccount(child, classId, selectedPeriod, accountMap);
197
+ });
198
+ }
199
+ /**
200
+ * Find the balance amount for a specific period from a COABalance array.
201
+ */
202
+ function findBalanceForPeriod(balances, selectedPeriod) {
203
+ for (const bal of balances) {
204
+ const b = bal;
205
+ if (b.startDate != null && b.endDate != null) {
206
+ if (b.startDate.isSame(selectedPeriod.startDate, 'day') &&
207
+ b.endDate.isSame(selectedPeriod.endDate, 'day')) {
208
+ return b.balance?.amount ?? 0;
209
+ }
210
+ }
211
+ }
212
+ return 0;
213
+ }
214
+ /**
215
+ * Compute a calculated section by adding or subtracting two sets of class totals.
216
+ */
217
+ function computeCalculatedSection(aTotals, bTotals, aTotal, bTotal, operation, title) {
218
+ const classTotals = aTotals.map((a) => {
219
+ const bMatch = bTotals.find((b) => b.classId === a.classId);
220
+ const bAmount = bMatch?.balance.amount ?? 0;
221
+ const resultAmount = operation === 'add'
222
+ ? a.balance.amount + bAmount
223
+ : a.balance.amount - bAmount;
224
+ return {
225
+ classId: a.classId,
226
+ balance: toAmountWC(resultAmount, DEFAULT_CURRENCY),
227
+ };
228
+ });
229
+ const sectionTotalAmount = operation === 'add'
230
+ ? aTotal.amount + bTotal.amount
231
+ : aTotal.amount - bTotal.amount;
232
+ return {
233
+ title,
234
+ classTotals,
235
+ sectionTotal: toAmountWC(sectionTotalAmount, DEFAULT_CURRENCY),
236
+ };
237
+ }
238
+ /**
239
+ * Build synthetic COABalance array for header rendering.
240
+ * One entry per class column + one for "Total".
241
+ */
242
+ function buildHeaderBalances(classColumns, timeframe, selectedPeriod) {
243
+ if (selectedPeriod == null) {
244
+ return [];
245
+ }
246
+ const { endDate, startDate } = selectedPeriod;
247
+ const balances = classColumns.map((col) => ({
248
+ balance: toAmountWC(0, DEFAULT_CURRENCY),
249
+ balanceTimeFrame: timeframe,
250
+ endDate,
251
+ startDate,
252
+ type: 'default',
253
+ label: col.className,
254
+ }));
255
+ // Add a "Total" column
256
+ balances.push({
257
+ balance: toAmountWC(0, DEFAULT_CURRENCY),
258
+ balanceTimeFrame: timeframe,
259
+ endDate,
260
+ startDate,
261
+ type: 'default',
262
+ label: 'Total',
263
+ });
264
+ return balances;
265
+ }
266
+ function reduceFetchState(fetchStateList) {
267
+ const isAnyProgress = fetchStateList.some((s) => s.fetchState === 'In-Progress');
268
+ const isAllCompleted = fetchStateList.every((s) => s.fetchState === 'Completed');
269
+ const isAnyError = fetchStateList.some((s) => s.fetchState === 'Error');
270
+ const error = fetchStateList.find((s) => s.error != null)?.error;
271
+ let fetchState = 'Not-Started';
272
+ if (isAnyProgress) {
273
+ fetchState = 'In-Progress';
274
+ }
275
+ else if (isAllCompleted) {
276
+ fetchState = 'Completed';
277
+ }
278
+ else if (isAnyError) {
279
+ fetchState = 'Error';
280
+ }
281
+ return { fetchState, error };
282
+ }