@zeniai/client-epic-state 4.19.96-beta0ND → 4.19.96-beta2ND

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 (28) hide show
  1. package/lib/commonStateTypes/coaBalance/coaBalanceType.d.ts +2 -0
  2. package/lib/commonStateTypes/coaBalance/coaBalanceType.js +6 -0
  3. package/lib/commonStateTypes/currencyHelper.d.ts +3 -0
  4. package/lib/commonStateTypes/currencyHelper.js +6 -1
  5. package/lib/entity/customer/customerState.d.ts +0 -2
  6. package/lib/entity/customer/customerState.js +0 -5
  7. package/lib/esm/commonStateTypes/coaBalance/coaBalanceType.js +5 -0
  8. package/lib/esm/commonStateTypes/currencyHelper.js +5 -0
  9. package/lib/esm/entity/customer/customerState.js +1 -4
  10. package/lib/esm/index.js +5 -4
  11. package/lib/esm/view/arAgingView/arAgingReport/arAgingReducer.js +1 -1
  12. package/lib/esm/view/arAgingView/arAgingReport/arAgingSelector.js +1 -1
  13. package/lib/esm/view/financeStatement/financeStatementSelector.js +13 -4
  14. package/lib/esm/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelector.js +19 -16
  15. package/lib/esm/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelectorTypes.js +9 -1
  16. package/lib/esm/view/profitAndLossClassesView/profitAndLossHorizontalEnrichment.js +200 -0
  17. package/lib/index.d.ts +6 -5
  18. package/lib/index.js +38 -34
  19. package/lib/tsconfig.typecheck.tsbuildinfo +1 -1
  20. package/lib/view/arAgingView/arAgingReport/arAgingReducer.js +2 -2
  21. package/lib/view/arAgingView/arAgingReport/arAgingSelector.js +5 -5
  22. package/lib/view/financeStatement/financeStatementSelector.js +13 -4
  23. package/lib/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelector.js +19 -16
  24. package/lib/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelectorTypes.d.ts +52 -0
  25. package/lib/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelectorTypes.js +11 -0
  26. package/lib/view/profitAndLossClassesView/profitAndLossHorizontalEnrichment.d.ts +6 -0
  27. package/lib/view/profitAndLossClassesView/profitAndLossHorizontalEnrichment.js +203 -0
  28. package/package.json +1 -1
@@ -5,3 +5,5 @@ export type COABalanceType = 'default' | 'default_with_percent' | 'default_with_
5
5
  export declare const toCOABalanceType: (v: string) => COABalanceType;
6
6
  export declare const toCOABalanceTypeStrict: (v: string) => COABalanceType | undefined;
7
7
  export declare const isCalculatedBalanceType: (id: string) => id is CalculatedBalance;
8
+ /** Removes compare-mode balance types (horizontal P&L-by-class does not use them). */
9
+ export declare function stripCompareAdditionalBalanceTypes(balances: readonly COABalanceType[]): COABalanceType[];
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isCalculatedBalanceType = exports.toCOABalanceTypeStrict = exports.toCOABalanceType = void 0;
4
+ exports.stripCompareAdditionalBalanceTypes = stripCompareAdditionalBalanceTypes;
4
5
  const stringToUnion_1 = require("../stringToUnion");
5
6
  const ALL_COA_BALANCE_FIELDS = [
6
7
  'default',
@@ -33,3 +34,8 @@ const toCOABalanceTypeStrict = (v) => (0, stringToUnion_1.stringToUnionStrict)(v
33
34
  exports.toCOABalanceTypeStrict = toCOABalanceTypeStrict;
34
35
  const isCalculatedBalanceType = (id) => ALL_CALCULATED_BALANCES.find((sectionId) => id === sectionId) != null;
35
36
  exports.isCalculatedBalanceType = isCalculatedBalanceType;
37
+ /** Removes compare-mode balance types (horizontal P&L-by-class does not use them). */
38
+ function stripCompareAdditionalBalanceTypes(balances) {
39
+ return balances.filter((balanceType) => balanceType !== 'this_period_vs_last_period' &&
40
+ balanceType !== 'this_period_vs_last_period_percent');
41
+ }
@@ -1 +1,4 @@
1
+ import { Currency } from './amount';
1
2
  export declare const isUsdCurrency: (currencyCode: string) => currencyCode is "USD";
3
+ /** Default home-currency fallback when tenant/report currency is unset. */
4
+ export declare const defaultCustomerCurrency: Currency;
@@ -1,7 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isUsdCurrency = void 0;
3
+ exports.defaultCustomerCurrency = exports.isUsdCurrency = void 0;
4
4
  const isUsdCurrency = (currencyCode) => {
5
5
  return currencyCode === 'USD';
6
6
  };
7
7
  exports.isUsdCurrency = isUsdCurrency;
8
+ /** Default home-currency fallback when tenant/report currency is unset. */
9
+ exports.defaultCustomerCurrency = {
10
+ currencyCode: 'USD',
11
+ currencySymbol: '$',
12
+ };
@@ -1,4 +1,3 @@
1
- import { Currency } from '../../commonStateTypes/amount';
2
1
  import { ID } from '../../commonStateTypes/common';
3
2
  import { ZeniDate } from '../../zeniDayJS';
4
3
  import { ZeniUrl as Url } from '../../zeniUrl';
@@ -47,4 +46,3 @@ export interface Customer extends CustomerBase {
47
46
  export interface CustomerState {
48
47
  byCustomerId: Record<ID, Customer>;
49
48
  }
50
- export declare const defaultCustomerCurrency: Currency;
@@ -1,7 +1,2 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.defaultCustomerCurrency = void 0;
4
- exports.defaultCustomerCurrency = {
5
- currencyCode: 'USD',
6
- currencySymbol: '$',
7
- };
@@ -27,3 +27,8 @@ const ALL_CALCULATED_BALANCES = [
27
27
  export const toCOABalanceType = (v) => stringToUnion(v, ALL_COA_BALANCE_FIELDS);
28
28
  export const toCOABalanceTypeStrict = (v) => stringToUnionStrict(v, ALL_COA_BALANCE_FIELDS);
29
29
  export const isCalculatedBalanceType = (id) => ALL_CALCULATED_BALANCES.find((sectionId) => id === sectionId) != null;
30
+ /** Removes compare-mode balance types (horizontal P&L-by-class does not use them). */
31
+ export function stripCompareAdditionalBalanceTypes(balances) {
32
+ return balances.filter((balanceType) => balanceType !== 'this_period_vs_last_period' &&
33
+ balanceType !== 'this_period_vs_last_period_percent');
34
+ }
@@ -1,3 +1,8 @@
1
1
  export const isUsdCurrency = (currencyCode) => {
2
2
  return currencyCode === 'USD';
3
3
  };
4
+ /** Default home-currency fallback when tenant/report currency is unset. */
5
+ export const defaultCustomerCurrency = {
6
+ currencyCode: 'USD',
7
+ currencySymbol: '$',
8
+ };
@@ -1,4 +1 @@
1
- export const defaultCustomerCurrency = {
2
- currencyCode: 'USD',
3
- currencySymbol: '$',
4
- };
1
+ export {};
package/lib/esm/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { isAllowedValueWithCode, isAllowedValueWithID, } from './commonStateTypes/allowedValue';
2
2
  import { toAmount } from './commonStateTypes/amount';
3
+ import { defaultCustomerCurrency } from './commonStateTypes/currencyHelper';
3
4
  import { getTimeframeTickTag, isIncompleteMonthPresentInTimeframeTick, } from './commonStateTypes/bookCloseDateHelper';
4
5
  import { toCOABalanceTimeframe, } from './commonStateTypes/coaBalance/coaBalance';
5
- import { isCalculatedBalanceType, toCOABalanceType, toCOABalanceTypeStrict, } from './commonStateTypes/coaBalance/coaBalanceType';
6
+ import { isCalculatedBalanceType, stripCompareAdditionalBalanceTypes, toCOABalanceType, toCOABalanceTypeStrict, } from './commonStateTypes/coaBalance/coaBalanceType';
6
7
  import { newBalancesFilter, newBalancesFilterForDateRange, toCOABalancesOrderStrict, } from './commonStateTypes/coaBalance/coaBalancesFilter';
7
8
  import { newBalancesFilterClassesView, } from './commonStateTypes/coaBalance/coaBalancesFilterClassesView';
8
9
  import { getNumberOfPeriods } from './commonStateTypes/dataAvailable';
@@ -40,7 +41,6 @@ import { getControllersName, getFirstControllerEmail, getFirstControllerId, } fr
40
41
  import { getCompanyByCompanyId, getCompanyInfoForAllCockpitCompaniesInState, getControllersForCompany, } from './entity/company/companySelector';
41
42
  import { ALL_ACCOUNTING_CONNECTION_CREATION_MODES, isCompanyEligibleForTreasury, } from './entity/company/companyStateTypes';
42
43
  import { updateCompaniesHealth, updateCompanyHealth, } from './entity/companyHealthMetric/companyHealthMetricReducer';
43
- import { defaultCustomerCurrency, } from './entity/customer/customerState';
44
44
  import { CustomerIncomeTrend } from './entity/customerIncome/customerIncomeTrend';
45
45
  import { getDepositAccountLimitForChargeCardByDepositAccountId } from './entity/depositAccount/depositAccountSelector';
46
46
  import { mapAttachmentFilePayloadToFile, mapAttachmentFileToAttachmentFilePayload, mapFilePayloadToFile, removeAttachmentFiles, updateAttachmentFiles, updateFiles, } from './entity/file/fileReducer';
@@ -219,6 +219,7 @@ import { fetchProfitAndLoss, fetchProfitAndLossForTimeframe, resetProfitAndLossN
219
219
  import { getPandLReportFetchState, getProfitAndLossReport, } from './view/profitAndLoss/profitAndLossSelector';
220
220
  import { fetchProfitAndLossClassesView, resetProfitAndLossClassesNodeCollapseState, updateAccountViewMode as updateProfitAndLossAccountViewMode, updateClassesToFilterOut as updateProfitAndLossClassesToFilterOut, updateClassViewLayout, updateProfitAndLossClassesViewUIState, } from './view/profitAndLossClassesView/profitAndLossClassesViewReducer';
221
221
  import { getProfitAndLossClassesHorizontalView } from './view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelector';
222
+ import { HORIZONTAL_CLASS_TOTAL_BALANCE_ID, isHorizontalAccountReportData, } from './view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelectorTypes';
222
223
  import { getProfitAndLossClassesView } from './view/profitAndLossClassesView/profitAndLossClassesViewSelector';
223
224
  import { isPandLReportViewCalculatedSectionID, isPandLReportViewSectionID, } from './view/profitAndLossClassesView/profitAndLossClassesViewSelectorTypes';
224
225
  import { fetchEntityRecommendationsByTransactionId, fetchRecommendationByEntityId, fetchRecommendationByEntityName, } from './view/recommendation/recommendationReducer';
@@ -414,7 +415,7 @@ export { toReportID, toReportFormatStrict, isCashFlowOrBalanceSheetReport, isDas
414
415
  export { toCOABalanceTimeframe, };
415
416
  export { toCOABalancesOrderStrict, newBalancesFilter, newBalancesFilterForDateRange, };
416
417
  export { newBalancesFilterClassesView };
417
- export { toCOABalanceTypeStrict, toCOABalanceType as toCOABlanaceType, isCalculatedBalanceType, };
418
+ export { toCOABalanceTypeStrict, toCOABalanceType as toCOABlanaceType, isCalculatedBalanceType, stripCompareAdditionalBalanceTypes, };
418
419
  export { stringToUnion, stringToUnionStrict };
419
420
  export { fetchMonthEndCloseChecks, fetchMonthClosePerformanceTrend, getMonthEndCloseChecksViewByTenantId, ALL_MONTH_END_CLOSE_CHECKS_FREQUENCY, };
420
421
  export {
@@ -452,7 +453,7 @@ export { fetchDashboard, getDashboard, updateTreasuryVideoClosed, };
452
453
  export { updateDashboardLayout };
453
454
  export { getPandLWithForecast, } from './view/profitAndLoss/pAndLWithForecast/pAndLWithForecastSelector';
454
455
  export { fetchProfitAndLoss, resetProfitAndLossNodeCollapseState, updateProfitAndLossUIState, getProfitAndLossReport, getPandLReportFetchState, fetchProfitAndLossForTimeframe, };
455
- export { fetchProfitAndLossClassesView, updateProfitAndLossClassesToFilterOut, resetProfitAndLossClassesNodeCollapseState, updateProfitAndLossClassesViewUIState, updateProfitAndLossAccountViewMode, updateClassViewLayout, getProfitAndLossClassesView, getProfitAndLossClassesHorizontalView, isPandLReportViewCalculatedSectionID, isPandLReportViewSectionID, };
456
+ export { fetchProfitAndLossClassesView, updateProfitAndLossClassesToFilterOut, resetProfitAndLossClassesNodeCollapseState, updateProfitAndLossClassesViewUIState, updateProfitAndLossAccountViewMode, updateClassViewLayout, getProfitAndLossClassesView, getProfitAndLossClassesHorizontalView, isPandLReportViewCalculatedSectionID, isPandLReportViewSectionID, HORIZONTAL_CLASS_TOTAL_BALANCE_ID, isHorizontalAccountReportData, };
456
457
  export { getAccountingProviderAttachment };
457
458
  export { fetchUserListByType, getUserList };
458
459
  export { fetchAllPeopleRequiredViews, fetchPeoplePage, fetchPeople, deletePerson, resendInvite, changeZeniPersonRoles, invitePeople, inviteZeniPeople, updatePeopleUIState, peopleSaveUpdates, resetUpdateErrorMessage, peopleSaveDataInLocalStore, peopleClearDataInLocalStore, getPeople, getPeopleLocalData, initializeEditPerson, };
@@ -3,7 +3,7 @@ import { mapReportPayloadV2ToReportV2 } from '../../../commonPayloadTypes/v2/vie
3
3
  import { getAgingBalancesKey, toAgingPeriod, } from '../../../commonStateTypes/agingBalance/agingBalance';
4
4
  import { getNestedCustomerHierarchy } from '../../../commonStateTypes/customerView/getNestedCustomerHierarchy';
5
5
  import { toCustomer } from '../../../entity/customer/customerPayload';
6
- import { defaultCustomerCurrency } from '../../../entity/customer/customerState';
6
+ import { defaultCustomerCurrency } from '../../../commonStateTypes/currencyHelper';
7
7
  import { dateNow } from '../../../zeniDayJS';
8
8
  import { toArAgingDataByCustomerId, toArAgingTotals, } from './arAgingPayload';
9
9
  export const inititalUIState = {
@@ -1,8 +1,8 @@
1
1
  import { createSelector } from '@reduxjs/toolkit';
2
2
  import orderBy from 'lodash/orderBy';
3
3
  import { getAgingBalancesKey, } from '../../../commonStateTypes/agingBalance/agingBalance';
4
+ import { defaultCustomerCurrency } from '../../../commonStateTypes/currencyHelper';
4
5
  import { getCustomer } from '../../../entity/customer/customerSelector';
5
- import { defaultCustomerCurrency, } from '../../../entity/customer/customerState';
6
6
  export const getArAgingReport = createSelector((state) => state.arAgingState, (state) => state.customerState, (arAgingState, customerState) => {
7
7
  const { reportId, reportTitle, fetchState, uiState, firstMonthOfFY, agingPeriods, } = arAgingState;
8
8
  const { searchString, sortOrder, sortKey, selectedDate } = uiState;
@@ -1,4 +1,5 @@
1
1
  import { createSelector } from '@reduxjs/toolkit';
2
+ import { stripCompareAdditionalBalanceTypes } from '../../commonStateTypes/coaBalance/coaBalanceType';
2
3
  import { newBalancesFilter, newBalancesFilterForDateRange, } from '../../commonStateTypes/coaBalance/coaBalancesFilter';
3
4
  import { newBalancesFilterClassesView } from '../../commonStateTypes/coaBalance/coaBalancesFilterClassesView';
4
5
  import { getNumberOfPeriods, } from '../../commonStateTypes/dataAvailable';
@@ -55,9 +56,15 @@ export const getFinanceStatement = createSelector((state) => state.accountState,
55
56
  if (thisPeriod == null && balancesInTimeframe.length > 0) {
56
57
  thisPeriod = toTimeframeTick(balancesInTimeframe[balancesInTimeframe.length - 1]);
57
58
  }
58
- const filter = newBalancesFilter(firstMonthOfFY, financeStatementState.timeframe, Math.min(getNumberOfPeriods(firstMonthOfFY, financeStatementState.timeframe, dataAvailable) ?? financeStatementState.selectedCOABalancesRange.numberOfPeriods, financeStatementState.selectedCOABalancesRange.numberOfPeriods), thisPeriod, 'ascending_date', financeStatementState.isAdditionalBalancesShown === true
59
+ const rawAdditionalBalances = financeStatementState.isAdditionalBalancesShown === true
59
60
  ? financeStatementState.selectedAdditionalBalances
60
- : []);
61
+ : [];
62
+ const useHorizontalClassesLayout = profitAndLossClassesViewState.uiState.classViewLayout === 'horizontal' &&
63
+ financeStatementState.selectedReportId === 'profit_and_loss_by_classes';
64
+ const additionalBalancesForFilter = useHorizontalClassesLayout
65
+ ? stripCompareAdditionalBalanceTypes(rawAdditionalBalances)
66
+ : rawAdditionalBalances;
67
+ const filter = newBalancesFilter(firstMonthOfFY, financeStatementState.timeframe, Math.min(getNumberOfPeriods(firstMonthOfFY, financeStatementState.timeframe, dataAvailable) ?? financeStatementState.selectedCOABalancesRange.numberOfPeriods, financeStatementState.selectedCOABalancesRange.numberOfPeriods), thisPeriod, 'ascending_date', additionalBalancesForFilter);
61
68
  const profitAndLossReport = getProfitAndLossReport(filter, undefined, accountState, accountGroupState, sectionAccountsViewState, profitAndLossState, forecastState);
62
69
  const classFilter = newBalancesFilterClassesView(filter.firstMonthOfFY, filter.timeframe, filter.balancesRange.numberOfPeriods, filter.balancesRange.thisPeriod, filter.balancesRange.orderBy, filter.additionalBalances, profitAndLossClassesViewState.uiState.classesToFilterOut);
63
70
  const profitAndLossByClassReport = getProfitAndLossClassesView(classFilter, accountState, classState, sectionsState, profitAndLossClassesViewState);
@@ -117,7 +124,9 @@ export const getFinanceStatement = createSelector((state) => state.accountState,
117
124
  filter,
118
125
  selectedReportId: financeStatementState.selectedReportId,
119
126
  downloadState: financeStatementState.downloadState,
120
- isCompareModeOn: financeStatementState.selectedAdditionalBalances.includes('this_period_vs_last_period') ||
121
- financeStatementState.selectedAdditionalBalances.includes('this_period_vs_last_period_percent'),
127
+ isCompareModeOn: useHorizontalClassesLayout
128
+ ? false
129
+ : financeStatementState.selectedAdditionalBalances.includes('this_period_vs_last_period') ||
130
+ financeStatementState.selectedAdditionalBalances.includes('this_period_vs_last_period_percent'),
122
131
  };
123
132
  });
@@ -1,11 +1,9 @@
1
1
  import { toAmountWC } from '../../commonStateTypes/amount';
2
+ import { getSummationFreeRangeTotalBalance } from '../../commonStateTypes/coaBalance/additionalBalances/getSummationBalance';
2
3
  import { reduceFetchStateParallelTimeframes } from '../../commonStateTypes/reduceFetchState';
4
+ import { defaultCustomerCurrency } from '../../commonStateTypes/currencyHelper';
3
5
  import { getSectionClassesViewReport } from '../../entity/sectionClassesViewV2/sectionClassesViewSelector';
4
- /** If `ProfitAndLossClassesViewState.currency` is unset (e.g. tests). */
5
- const FALLBACK_REPORT_CURRENCY = {
6
- currencyCode: 'USD',
7
- currencySymbol: '$',
8
- };
6
+ import { enrichProfitAndLossByClassHorizontalReport } from './profitAndLossHorizontalEnrichment';
9
7
  export const profitAndLossHorizontalIdsInOrder = [
10
8
  'income',
11
9
  'cogs',
@@ -49,7 +47,7 @@ export const getProfitAndLossClassesHorizontalView = (filter, accountState, clas
49
47
  const classColumns = buildClassColumns(profitAndLossClassesViewState.classHierarchy, filter.classesToFilterOut);
50
48
  const sections = {};
51
49
  const calculatedSections = {};
52
- let reportCurrency = FALLBACK_REPORT_CURRENCY;
50
+ let reportCurrency = defaultCustomerCurrency;
53
51
  if (fetchState.fetchState === 'Completed' && selectedPeriod != null) {
54
52
  // Get section reports using existing selector infrastructure
55
53
  const sectionReports = {};
@@ -57,7 +55,7 @@ export const getProfitAndLossClassesHorizontalView = (filter, accountState, clas
57
55
  sectionReports[sectionId] = getSectionClassesViewReport(accountState, classState, sectionsState, { reportId, sectionId }, filter);
58
56
  });
59
57
  reportCurrency =
60
- profitAndLossClassesViewState.currency ?? FALLBACK_REPORT_CURRENCY;
58
+ profitAndLossClassesViewState.currency ?? defaultCustomerCurrency;
61
59
  // Pivot each section from class→account to account→class
62
60
  HORIZONTAL_PANDL_SECTION_IDS.forEach((sectionId) => {
63
61
  const sectionReport = sectionReports[sectionId];
@@ -94,7 +92,7 @@ export const getProfitAndLossClassesHorizontalView = (filter, accountState, clas
94
92
  }
95
93
  // Build synthetic header COABalances for rendering class names as column headers
96
94
  const headerBalances = buildHeaderBalances(classColumns, filter.timeframe, selectedPeriod, reportCurrency);
97
- return {
95
+ const report = {
98
96
  reportId,
99
97
  reportTitle: 'Profit and Loss by Class',
100
98
  firstMonthOfFY: profitAndLossClassesViewState.firstMonthOfFY,
@@ -109,6 +107,8 @@ export const getProfitAndLossClassesHorizontalView = (filter, accountState, clas
109
107
  uiState: profitAndLossClassesViewState.uiState,
110
108
  headerBalances,
111
109
  };
110
+ enrichProfitAndLossByClassHorizontalReport(report);
111
+ return report;
112
112
  };
113
113
  /**
114
114
  * Build column definitions from class hierarchy (top-level classes only).
@@ -166,6 +166,7 @@ function pivotSectionToHorizontal(section, classColumns, title, currency) {
166
166
  accounts.push({
167
167
  accountId,
168
168
  accountName: accountData.accountName,
169
+ accountType: accountData.accountType,
169
170
  depth: accountData.depth,
170
171
  classBalances,
171
172
  rowTotal: toAmountWC(rowTotal, currency),
@@ -231,6 +232,7 @@ function collectAccountsFromNestedAccount(nestedAccount, classId, accountMap, de
231
232
  if (!accountMap.has(accountId)) {
232
233
  accountMap.set(accountId, {
233
234
  accountName,
235
+ accountType: nestedAccount.account.accountType,
234
236
  depth,
235
237
  classBalances: new Map(),
236
238
  });
@@ -240,6 +242,9 @@ function collectAccountsFromNestedAccount(nestedAccount, classId, accountMap, de
240
242
  return;
241
243
  }
242
244
  accountData.depth = Math.max(accountData.depth, depth);
245
+ if (accountData.accountType == null && nestedAccount.account.accountType != null) {
246
+ accountData.accountType = nestedAccount.account.accountType;
247
+ }
243
248
  const existing = accountData.classBalances.get(classId) ?? 0;
244
249
  accountData.classBalances.set(classId, existing + balance);
245
250
  // Process sub-accounts
@@ -248,17 +253,15 @@ function collectAccountsFromNestedAccount(nestedAccount, classId, accountMap, de
248
253
  });
249
254
  }
250
255
  /**
251
- * Total amount for the current report slice. `balances` is already narrowed by
252
- * `getCOABalances` / `sliceCOABalances` to the selected range; summing matches
253
- * multi-period selections and avoids mismatches when `thisPeriod` does not
254
- * align with a row (e.g. slice fallback) or when several period rows are shown.
256
+ * Total `balance.amount` for the current COA slice (same aggregation as
257
+ * `getSummationFreeRangeTotalBalance`, used for class totals and account rows).
255
258
  */
256
259
  function sumBalancesInSlice(balances) {
257
- let sum = 0;
258
- for (const bal of balances) {
259
- sum += bal.balance?.amount ?? 0;
260
+ if (balances.length === 0) {
261
+ return 0;
260
262
  }
261
- return sum;
263
+ const summed = getSummationFreeRangeTotalBalance([...balances]);
264
+ return summed?.balance.amount ?? 0;
262
265
  }
263
266
  /**
264
267
  * Compute a calculated section by adding or subtracting two sets of class totals.
@@ -1 +1,9 @@
1
- export {};
1
+ /** Synthetic COABalance `id` for the horizontal class report Total column (transaction routing). */
2
+ export const HORIZONTAL_CLASS_TOTAL_BALANCE_ID = '__horizontal_total__';
3
+ export function isHorizontalAccountReportData(data) {
4
+ return (typeof data === 'object' &&
5
+ data !== null &&
6
+ 'depth' in data &&
7
+ typeof data.depth === 'number' &&
8
+ !('filter' in data));
9
+ }
@@ -0,0 +1,200 @@
1
+ import { defaultCustomerCurrency } from '../../commonStateTypes/currencyHelper';
2
+ import { HORIZONTAL_CLASS_TOTAL_BALANCE_ID, } from './profitAndLossClassesByClassHorizontalSelectorTypes';
3
+ /**
4
+ * Populates `sectionHeaderBalancesSlice`, and per-account `accountReportData`, so the
5
+ * web layer only maps selector output to tree nodes.
6
+ */
7
+ export function enrichProfitAndLossByClassHorizontalReport(report) {
8
+ if (report.fetchState !== 'Completed' || report.selectedPeriod == null) {
9
+ return;
10
+ }
11
+ for (const section of Object.values(report.sections)) {
12
+ if (section != null) {
13
+ enrichHorizontalSection(report, section);
14
+ }
15
+ }
16
+ for (const calc of Object.values(report.calculatedSections)) {
17
+ if (calc != null) {
18
+ enrichHorizontalCalculatedSection(report, calc);
19
+ }
20
+ }
21
+ }
22
+ function enrichHorizontalSection(report, section) {
23
+ const classAmounts = report.classColumns.map((col) => {
24
+ const total = section.classTotals.find((ct) => ct.classId === col.classId);
25
+ return total?.balance.amount ?? 0;
26
+ });
27
+ section.sectionHeaderBalancesSlice = buildHorizontalClassColumnBalancesSlice(report, classAmounts, section.sectionTotal.amount);
28
+ const childrenRowIndicesByParent = buildHorizontalAccountChildIndicesByParent(section.accounts);
29
+ const childrenRecord = {};
30
+ childrenRowIndicesByParent.forEach((childIndices, parentIndex) => {
31
+ childrenRecord[parentIndex] = childIndices;
32
+ });
33
+ section.treeLayout = {
34
+ rootAccountRowIndices: section.accounts
35
+ .map((_, i) => findHorizontalAccountParentIndex(section.accounts, i) === -1 ? i : -1)
36
+ .filter((i) => i >= 0),
37
+ childrenRowIndicesByParent: childrenRecord,
38
+ };
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
+ });
45
+ row.accountReportData = {
46
+ accountId: row.accountId,
47
+ accountName: row.accountName,
48
+ accountType: row.accountType,
49
+ depth: row.depth,
50
+ balancesInTimeframe: buildHorizontalClassColumnBalancesSlice(report, classRowAmounts, row.rowTotal.amount),
51
+ horizontalTreeView: buildHorizontalTreeView(section.accounts, i),
52
+ };
53
+ }
54
+ }
55
+ function enrichHorizontalCalculatedSection(report, calcSection) {
56
+ const classAmounts = report.classColumns.map((col) => {
57
+ const total = calcSection.classTotals.find((ct) => ct.classId === col.classId);
58
+ return total?.balance.amount ?? 0;
59
+ });
60
+ calcSection.sectionHeaderBalancesSlice = buildHorizontalClassColumnBalancesSlice(report, classAmounts, calcSection.sectionTotal.amount);
61
+ }
62
+ function buildHorizontalClassColumnBalancesSlice(report, classAmounts, totalAmount) {
63
+ const fallback = defaultCustomerCurrency;
64
+ const firstHeader = report.headerBalances[0];
65
+ const currencyCode = firstHeader?.balance.currencyCode ?? fallback.currencyCode;
66
+ const currencySymbol = firstHeader?.balance.currencySymbol ?? fallback.currencySymbol;
67
+ const timeframe = firstHeader?.balanceTimeFrame ?? 'month';
68
+ const emptySlice = {
69
+ balances: [],
70
+ additionalBalances: [],
71
+ filter: {
72
+ firstMonthOfFY: report.firstMonthOfFY,
73
+ timeframe,
74
+ balancesRange: { numberOfPeriods: 0, orderBy: 'ascending_date' },
75
+ additionalBalances: [],
76
+ },
77
+ };
78
+ if (report.selectedPeriod == null) {
79
+ return emptySlice;
80
+ }
81
+ const { startDate, endDate } = report.selectedPeriod;
82
+ const balances = report.classColumns.map((col, i) => ({
83
+ balance: {
84
+ amount: classAmounts[i] ?? 0,
85
+ currencyCode,
86
+ currencySymbol,
87
+ },
88
+ balanceTimeFrame: timeframe,
89
+ startDate,
90
+ endDate,
91
+ type: 'default',
92
+ id: col.classId,
93
+ label: col.className,
94
+ }));
95
+ balances.push({
96
+ balance: {
97
+ amount: totalAmount,
98
+ currencyCode,
99
+ currencySymbol,
100
+ },
101
+ balanceTimeFrame: timeframe,
102
+ startDate,
103
+ endDate,
104
+ type: 'default',
105
+ id: HORIZONTAL_CLASS_TOTAL_BALANCE_ID,
106
+ label: 'Total',
107
+ });
108
+ return {
109
+ balances,
110
+ additionalBalances: [],
111
+ filter: {
112
+ firstMonthOfFY: report.firstMonthOfFY,
113
+ timeframe,
114
+ balancesRange: {
115
+ numberOfPeriods: balances.length,
116
+ orderBy: 'ascending_date',
117
+ },
118
+ additionalBalances: [],
119
+ },
120
+ };
121
+ }
122
+ function findHorizontalAccountParentIndex(dfsOrderedAccountRows, childRowIndex) {
123
+ const childDepth = dfsOrderedAccountRows[childRowIndex].depth;
124
+ if (childDepth <= 2) {
125
+ return -1;
126
+ }
127
+ for (let earlierRowIndex = childRowIndex - 1; earlierRowIndex >= 0; earlierRowIndex--) {
128
+ const earlierDepth = dfsOrderedAccountRows[earlierRowIndex].depth;
129
+ if (earlierDepth < childDepth) {
130
+ if (earlierDepth === childDepth - 1) {
131
+ return earlierRowIndex;
132
+ }
133
+ return -1;
134
+ }
135
+ }
136
+ return -1;
137
+ }
138
+ function getHorizontalAccountSiblingIndices(dfsOrderedAccountRows, rowIndex) {
139
+ const parentRowIndex = findHorizontalAccountParentIndex(dfsOrderedAccountRows, rowIndex);
140
+ const rowDepth = dfsOrderedAccountRows[rowIndex].depth;
141
+ const siblings = [];
142
+ for (let i = 0; i < dfsOrderedAccountRows.length; i++) {
143
+ if (dfsOrderedAccountRows[i].depth === rowDepth &&
144
+ findHorizontalAccountParentIndex(dfsOrderedAccountRows, i) ===
145
+ parentRowIndex) {
146
+ siblings.push(i);
147
+ }
148
+ }
149
+ return siblings;
150
+ }
151
+ function horizontalAccountHasChildRows(dfsOrderedAccountRows, parentRowIndex) {
152
+ return dfsOrderedAccountRows.some((_, rowIndex) => findHorizontalAccountParentIndex(dfsOrderedAccountRows, rowIndex) ===
153
+ parentRowIndex);
154
+ }
155
+ function buildHorizontalSubAccountAncestorMetadata(dfsOrderedAccountRows, currentRowIndex) {
156
+ const ancestorRowIndicesRootToLeaf = [];
157
+ let walkRowIndex = currentRowIndex;
158
+ while (walkRowIndex >= 0) {
159
+ ancestorRowIndicesRootToLeaf.unshift(walkRowIndex);
160
+ walkRowIndex = findHorizontalAccountParentIndex(dfsOrderedAccountRows, walkRowIndex);
161
+ }
162
+ return ancestorRowIndicesRootToLeaf.map((rowIndex) => {
163
+ const siblingIndices = getHorizontalAccountSiblingIndices(dfsOrderedAccountRows, rowIndex);
164
+ return {
165
+ depth: Math.max(0, dfsOrderedAccountRows[rowIndex].depth - 1),
166
+ hasSibling: siblingIndices.length > 1,
167
+ hasChildren: horizontalAccountHasChildRows(dfsOrderedAccountRows, rowIndex),
168
+ isLastNode: rowIndex === siblingIndices[siblingIndices.length - 1],
169
+ name: dfsOrderedAccountRows[rowIndex].accountName,
170
+ };
171
+ });
172
+ }
173
+ function buildHorizontalAccountChildIndicesByParent(dfsOrderedAccountRows) {
174
+ const childIndicesByParentRowIndex = new Map();
175
+ for (let rowIndex = 0; rowIndex < dfsOrderedAccountRows.length; rowIndex++) {
176
+ const parentRowIndex = findHorizontalAccountParentIndex(dfsOrderedAccountRows, rowIndex);
177
+ if (parentRowIndex >= 0) {
178
+ let childRowIndices = childIndicesByParentRowIndex.get(parentRowIndex);
179
+ if (childRowIndices === undefined) {
180
+ childRowIndices = [];
181
+ childIndicesByParentRowIndex.set(parentRowIndex, childRowIndices);
182
+ }
183
+ childRowIndices.push(rowIndex);
184
+ }
185
+ }
186
+ return childIndicesByParentRowIndex;
187
+ }
188
+ function buildHorizontalTreeView(dfsOrderedAccountRows, currentRowIndex) {
189
+ const treeDepth = Math.max(0, dfsOrderedAccountRows[currentRowIndex].depth - 1);
190
+ const siblingIndices = getHorizontalAccountSiblingIndices(dfsOrderedAccountRows, currentRowIndex);
191
+ const hasChildren = horizontalAccountHasChildRows(dfsOrderedAccountRows, currentRowIndex);
192
+ return {
193
+ subAccountAncestorMetadata: buildHorizontalSubAccountAncestorMetadata(dfsOrderedAccountRows, currentRowIndex),
194
+ treeDepth,
195
+ hasChildren,
196
+ hasSiblings: siblingIndices.length > 1,
197
+ isLeaf: !hasChildren,
198
+ isLastNode: currentRowIndex === siblingIndices[siblingIndices.length - 1],
199
+ };
200
+ }
package/lib/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import { NestedAccountHierarchyForReport, NestedAccountID, NestedAccountIDHierar
2
2
  import { AgingBalance, AgingPeriod } from './commonStateTypes/agingBalance/agingBalance';
3
3
  import { AllowedValue, AllowedValueWithCode, AllowedValueWithID, isAllowedValueWithCode, isAllowedValueWithID } from './commonStateTypes/allowedValue';
4
4
  import { Amount, Currency, toAmount } from './commonStateTypes/amount';
5
+ import { defaultCustomerCurrency } from './commonStateTypes/currencyHelper';
5
6
  import { AnimationFileName } from './commonStateTypes/animations';
6
7
  import { Balance, BalanceChange } from './commonStateTypes/balance';
7
8
  import { BalanceKind, BalancesTimeseries, TrendTimeseries } from './commonStateTypes/balanceTimeseries';
@@ -9,7 +10,7 @@ import { TimeFrameTickTag, getTimeframeTickTag, isIncompleteMonthPresentInTimefr
9
10
  import { NestedClassHierarchyForReport, NestedClassID, NestedClassIDHierarchy } from './commonStateTypes/classesView/nestedClassID';
10
11
  import { COABalance, COABalanceGrouped, COABalanceGroupedByPeriod, COABalanceMonthQuarterTimeframe, COABalanceTimeFrame, COABalanceWithPercent, toCOABalanceTimeframe } from './commonStateTypes/coaBalance/coaBalance';
11
12
  import { COABalanceByAdditionalPeriodGrouped } from './commonStateTypes/coaBalance/coaBalanceByAdditionalPeriod';
12
- import { COABalanceType, CalculatedBalance, CalculatedCompareBalance, CalculatedTotalBalance, isCalculatedBalanceType, toCOABalanceType, toCOABalanceTypeStrict } from './commonStateTypes/coaBalance/coaBalanceType';
13
+ import { COABalanceType, CalculatedBalance, CalculatedCompareBalance, CalculatedTotalBalance, isCalculatedBalanceType, stripCompareAdditionalBalanceTypes, toCOABalanceType, toCOABalanceTypeStrict } from './commonStateTypes/coaBalance/coaBalanceType';
13
14
  import { COABalancesFilter, COABalancesOrder, COABalancesRange, COABalancesRangeWithThisPeriod, COABalancesSlice, newBalancesFilter, newBalancesFilterForDateRange, toCOABalancesOrderStrict } from './commonStateTypes/coaBalance/coaBalancesFilter';
14
15
  import { COABalancesFilterClassesView, newBalancesFilterClassesView } from './commonStateTypes/coaBalance/coaBalancesFilterClassesView';
15
16
  import { AccountViewMode, CompletionStatusType, FetchState, FetchStateAndError, FetchedState, ID, Mode, UpdateType } from './commonStateTypes/common';
@@ -70,7 +71,7 @@ import { AdditionalChurnFactor, AdditionalChurnFactors, CompanyHealthMetric, Com
70
71
  import { ConnectedAccount } from './entity/connectedAccount/connectedAccount';
71
72
  import { Country } from './entity/countryList/countryListState';
72
73
  import { CountryWithCurrency } from './entity/currency/currency';
73
- import { Customer, CustomerBase, defaultCustomerCurrency } from './entity/customer/customerState';
74
+ import { Customer, CustomerBase } from './entity/customer/customerState';
74
75
  import { CustomerIncomeTrend } from './entity/customerIncome/customerIncomeTrend';
75
76
  import { CustomerSatisfactionInfo } from './entity/customerSatisfaction/customerSatisfactionSelectorReducerTypes';
76
77
  import { CustomerRatingCodeType, TeamRatingCodeType } from './entity/customerSatisfaction/customerSatisfactionState';
@@ -348,7 +349,7 @@ import { ProfitAndLossReport, getPandLReportFetchState, getProfitAndLossReport }
348
349
  import { ProfitAndLossState, ProfitAndLossUIState } from './view/profitAndLoss/profitAndLossState';
349
350
  import { fetchProfitAndLossClassesView, resetProfitAndLossClassesNodeCollapseState, updateAccountViewMode as updateProfitAndLossAccountViewMode, updateClassesToFilterOut as updateProfitAndLossClassesToFilterOut, updateClassViewLayout, updateProfitAndLossClassesViewUIState } from './view/profitAndLossClassesView/profitAndLossClassesViewReducer';
350
351
  import { getProfitAndLossClassesHorizontalView } from './view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelector';
351
- import { ClassColumnDefinition, HorizontalAccountRow, HorizontalCalculatedSection, HorizontalClassBalance, HorizontalSectionReport, ProfitAndLossByClassHorizontalReport } from './view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelectorTypes';
352
+ import { ClassColumnDefinition, HORIZONTAL_CLASS_TOTAL_BALANCE_ID, HorizontalAccountReportData, HorizontalAccountRow, HorizontalCalculatedSection, HorizontalClassBalance, HorizontalSectionReport, HorizontalSectionTreeLayout, ProfitAndLossByClassHorizontalReport, isHorizontalAccountReportData } from './view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelectorTypes';
352
353
  import { getProfitAndLossClassesView } from './view/profitAndLossClassesView/profitAndLossClassesViewSelector';
353
354
  import { PandLReportViewCalculatedSectionID, PandLReportViewSectionID, ProfitAndLossClassesViewReport, isPandLReportViewCalculatedSectionID, isPandLReportViewSectionID } from './view/profitAndLossClassesView/profitAndLossClassesViewSelectorTypes';
354
355
  import { ClassViewLayout, ProfitAndLossClassesViewUIState } from './view/profitAndLossClassesView/profitAndLossClassesViewState';
@@ -619,7 +620,7 @@ export { COABalance, COABalanceWithPercent, COABalanceTimeFrame, COABalanceGroup
619
620
  export { COABalanceByAdditionalPeriodGrouped };
620
621
  export { COABalancesOrder, toCOABalancesOrderStrict, COABalancesRange, COABalancesRangeWithThisPeriod, COABalancesFilter, newBalancesFilter, newBalancesFilterForDateRange, COABalancesSlice, };
621
622
  export { newBalancesFilterClassesView, COABalancesFilterClassesView };
622
- export { CalculatedCompareBalance, CalculatedTotalBalance, CalculatedBalance, COABalanceType, toCOABalanceTypeStrict, toCOABalanceType as toCOABlanaceType, isCalculatedBalanceType, };
623
+ export { CalculatedCompareBalance, CalculatedTotalBalance, CalculatedBalance, COABalanceType, toCOABalanceTypeStrict, toCOABalanceType as toCOABlanaceType, isCalculatedBalanceType, stripCompareAdditionalBalanceTypes, };
623
624
  export { stringToUnion, stringToUnionStrict };
624
625
  export { TransactionsOrder, COABalancesSliceOrder, EntityOrder, Section, SectionWithAccountOnly, CalculatedSection, };
625
626
  export { ClassesViewSelectorReportV2 };
@@ -666,7 +667,7 @@ export { fetchDashboard, getDashboard, DashboardReport, updateTreasuryVideoClose
666
667
  export { updateDashboardLayout };
667
668
  export { PandLWithForecastView, getPandLWithForecast, } from './view/profitAndLoss/pAndLWithForecast/pAndLWithForecastSelector';
668
669
  export { fetchProfitAndLoss, resetProfitAndLossNodeCollapseState, updateProfitAndLossUIState, ProfitAndLossState, ProfitAndLossUIState, getProfitAndLossReport, ProfitAndLossReport, getPandLReportFetchState, fetchProfitAndLossForTimeframe, };
669
- export { fetchProfitAndLossClassesView, updateProfitAndLossClassesToFilterOut, resetProfitAndLossClassesNodeCollapseState, updateProfitAndLossClassesViewUIState, updateProfitAndLossAccountViewMode, updateClassViewLayout, ProfitAndLossClassesViewUIState, ProfitAndLossClassesViewReport, getProfitAndLossClassesView, getProfitAndLossClassesHorizontalView, PandLReportViewSectionID, PandLReportViewCalculatedSectionID, isPandLReportViewCalculatedSectionID, isPandLReportViewSectionID, ClassViewLayout, ProfitAndLossByClassHorizontalReport, ClassColumnDefinition, HorizontalAccountRow, HorizontalClassBalance, HorizontalSectionReport, HorizontalCalculatedSection, };
670
+ export { fetchProfitAndLossClassesView, updateProfitAndLossClassesToFilterOut, resetProfitAndLossClassesNodeCollapseState, updateProfitAndLossClassesViewUIState, updateProfitAndLossAccountViewMode, updateClassViewLayout, ProfitAndLossClassesViewUIState, ProfitAndLossClassesViewReport, getProfitAndLossClassesView, getProfitAndLossClassesHorizontalView, PandLReportViewSectionID, PandLReportViewCalculatedSectionID, isPandLReportViewCalculatedSectionID, isPandLReportViewSectionID, ClassViewLayout, ProfitAndLossByClassHorizontalReport, ClassColumnDefinition, HORIZONTAL_CLASS_TOTAL_BALANCE_ID, HorizontalAccountReportData, HorizontalAccountRow, HorizontalClassBalance, HorizontalSectionReport, HorizontalSectionTreeLayout, HorizontalCalculatedSection, isHorizontalAccountReportData, };
670
671
  export { AccountingProviderAttachmentSelector, getAccountingProviderAttachment };
671
672
  export { fetchUserListByType, getUserList, UserListSelectorView };
672
673
  export { fetchAllPeopleRequiredViews, fetchPeoplePage, fetchPeople, deletePerson, resendInvite, changeZeniPersonRoles, invitePeople, inviteZeniPeople, updatePeopleUIState, peopleSaveUpdates, resetUpdateErrorMessage, peopleSaveDataInLocalStore, peopleClearDataInLocalStore, PeopleSelectorView, PeopleLocalDataView, Person, getPeople, getPeopleLocalData, PeopleState, PersonUpdateType, PeoplePageMode, ZeniPersonUserRoleType, PeopleUIState, PeopleLocalData, initializeEditPerson, };