@zeniai/client-epic-state 4.19.88-beta11ND → 4.19.88-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.
- package/lib/entity/reimbursement/reimbursementPayload.d.ts +0 -2
- package/lib/entity/reimbursement/reimbursementPayload.js +0 -7
- package/lib/entity/reimbursement/reimbursementState.d.ts +0 -2
- package/lib/esm/entity/reimbursement/reimbursementPayload.js +0 -7
- package/lib/esm/index.js +3 -2
- package/lib/esm/view/financeStatement/financeStatementSelector.js +5 -0
- package/lib/esm/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelector.js +281 -0
- package/lib/esm/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelectorTypes.js +1 -0
- package/lib/esm/view/profitAndLossClassesView/profitAndLossClassesViewReducer.js +5 -1
- package/lib/index.d.ts +5 -3
- package/lib/index.js +28 -25
- package/lib/tsconfig.typecheck.tsbuildinfo +1 -1
- package/lib/view/financeStatement/financeStatementSelector.d.ts +2 -0
- package/lib/view/financeStatement/financeStatementSelector.js +5 -0
- package/lib/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelector.d.ts +13 -0
- package/lib/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelector.js +285 -0
- package/lib/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelectorTypes.d.ts +57 -0
- package/lib/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelectorTypes.js +2 -0
- package/lib/view/profitAndLossClassesView/profitAndLossClassesViewReducer.d.ts +4 -2
- package/lib/view/profitAndLossClassesView/profitAndLossClassesViewReducer.js +6 -2
- package/lib/view/profitAndLossClassesView/profitAndLossClassesViewState.d.ts +2 -0
- package/package.json +1 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { URLPayload } from '../../commonPayloadTypes/urlPayload';
|
|
2
2
|
import { SegregatedReimbursementLineWithRecommendation } from '../../view/spendManagement/reimbursement/editRemiView/editRemiViewState';
|
|
3
3
|
import { AccountBasePayload } from '../account/accountPayload';
|
|
4
|
-
import { CustomerBasePayload } from '../customer/customerPayload';
|
|
5
4
|
import { BillAccountPayload, BillPaymentMethodPayload, BillPaymentStatusPayload, BillStagePayload, BillStatusPayload, BulkOperationDetailPayload, DeletionInfoPayload } from '../billPay/billTransaction/billTransactionPayload';
|
|
6
5
|
import { ClassBasePayload } from '../class/classPayload';
|
|
7
6
|
import { MerchantBasePayload } from '../merchant/merchantPayload';
|
|
@@ -69,7 +68,6 @@ interface ReimbursementTypePayload {
|
|
|
69
68
|
export interface LineDetailPayload {
|
|
70
69
|
class: ClassBasePayload;
|
|
71
70
|
account?: AccountBasePayload;
|
|
72
|
-
customer?: CustomerBasePayload;
|
|
73
71
|
}
|
|
74
72
|
interface ReimbursementInfoPayload {
|
|
75
73
|
actual_deposit_date: string | null;
|
|
@@ -6,7 +6,6 @@ const amount_1 = require("../../commonStateTypes/amount");
|
|
|
6
6
|
const stringToUnion_1 = require("../../commonStateTypes/stringToUnion");
|
|
7
7
|
const zeniDayJS_1 = require("../../zeniDayJS");
|
|
8
8
|
const accountPayload_1 = require("../account/accountPayload");
|
|
9
|
-
const customerPayload_1 = require("../customer/customerPayload");
|
|
10
9
|
const billTransactionPayload_1 = require("../billPay/billTransaction/billTransactionPayload");
|
|
11
10
|
const classPayload_1 = require("../class/classPayload");
|
|
12
11
|
const merchantPayload_1 = require("../merchant/merchantPayload");
|
|
@@ -172,9 +171,6 @@ const toLineDetailPayload = (lineDetail) => ({
|
|
|
172
171
|
account: lineDetail.account != null
|
|
173
172
|
? (0, accountPayload_1.toAccountPayload)(lineDetail.account)
|
|
174
173
|
: undefined,
|
|
175
|
-
customer: lineDetail.customer != null
|
|
176
|
-
? (0, customerPayload_1.toCustomerBasePayload)(undefined, lineDetail.customer)
|
|
177
|
-
: undefined,
|
|
178
174
|
});
|
|
179
175
|
exports.toLineDetailPayload = toLineDetailPayload;
|
|
180
176
|
const toLineDetail = (payload) => ({
|
|
@@ -184,9 +180,6 @@ const toLineDetail = (payload) => ({
|
|
|
184
180
|
account: payload.account?.account_id != null
|
|
185
181
|
? (0, accountPayload_1.mapAccountBasePayloadToAccountBase)(payload.account)
|
|
186
182
|
: undefined,
|
|
187
|
-
customer: payload.customer?.customer_id != null
|
|
188
|
-
? (0, customerPayload_1.toCustomerBase)(payload.customer, false)
|
|
189
|
-
: undefined,
|
|
190
183
|
});
|
|
191
184
|
const toDistanceMilage = (payload) => ({
|
|
192
185
|
unit: payload.unit,
|
|
@@ -6,7 +6,6 @@ import { ZeniUrl as Url } from '../../zeniUrl';
|
|
|
6
6
|
import { AccountBase } from '../account/accountState';
|
|
7
7
|
import { BillPaymentStatus, BillStage, BillStatus, BulkProcessingDetail, DeletionInfo } from '../billPay/billTransaction/billTransactionState';
|
|
8
8
|
import { ClassBase } from '../class/classState';
|
|
9
|
-
import { CustomerBase } from '../customer/customerState';
|
|
10
9
|
import { MerchantBase } from '../merchant/merchant';
|
|
11
10
|
import { Attachment } from '../transaction/stateTypes/attachment';
|
|
12
11
|
import { ReimbursementTransactionType } from './reimbursementPayload';
|
|
@@ -59,7 +58,6 @@ export interface DistanceMilage {
|
|
|
59
58
|
export interface LineDetail {
|
|
60
59
|
account?: AccountBase;
|
|
61
60
|
class?: ClassBase;
|
|
62
|
-
customer?: CustomerBase;
|
|
63
61
|
}
|
|
64
62
|
export interface ReimbursementInfo {
|
|
65
63
|
billPaymentMethodType: RemiPaymentMethod;
|
|
@@ -3,7 +3,6 @@ import { toAmount } from '../../commonStateTypes/amount';
|
|
|
3
3
|
import { stringToUnion } from '../../commonStateTypes/stringToUnion';
|
|
4
4
|
import { date, dateNow } from '../../zeniDayJS';
|
|
5
5
|
import { mapAccountBasePayloadToAccountBase, toAccountPayload, } from '../account/accountPayload';
|
|
6
|
-
import { toCustomerBase, toCustomerBasePayload, } from '../customer/customerPayload';
|
|
7
6
|
import { toBillPaymentMethod, toBillPaymentStatus, toBillStage, toBillStatus, toBulkProcessingDetail, toDeletionInfo, } from '../billPay/billTransaction/billTransactionPayload';
|
|
8
7
|
import { mapClassBasePayloadToClassBase, toClassBasePayload, } from '../class/classPayload';
|
|
9
8
|
import { mapMerchantBasePayloadToMerchantBase, toMerchantBasePayload, } from '../merchant/merchantPayload';
|
|
@@ -167,9 +166,6 @@ export const toLineDetailPayload = (lineDetail) => ({
|
|
|
167
166
|
account: lineDetail.account != null
|
|
168
167
|
? toAccountPayload(lineDetail.account)
|
|
169
168
|
: undefined,
|
|
170
|
-
customer: lineDetail.customer != null
|
|
171
|
-
? toCustomerBasePayload(undefined, lineDetail.customer)
|
|
172
|
-
: undefined,
|
|
173
169
|
});
|
|
174
170
|
const toLineDetail = (payload) => ({
|
|
175
171
|
class: payload.class.accounting_class_id != null
|
|
@@ -178,9 +174,6 @@ const toLineDetail = (payload) => ({
|
|
|
178
174
|
account: payload.account?.account_id != null
|
|
179
175
|
? mapAccountBasePayloadToAccountBase(payload.account)
|
|
180
176
|
: undefined,
|
|
181
|
-
customer: payload.customer?.customer_id != null
|
|
182
|
-
? toCustomerBase(payload.customer, false)
|
|
183
|
-
: undefined,
|
|
184
177
|
});
|
|
185
178
|
const toDistanceMilage = (payload) => ({
|
|
186
179
|
unit: payload.unit,
|
package/lib/esm/index.js
CHANGED
|
@@ -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, };
|
|
@@ -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,
|
package/lib/esm/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelector.js
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
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, 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, 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
|
+
// Sum all sliced period balances (same slice as vertical columns; multi-period = range total)
|
|
133
|
+
const classBalance = sumBalancesInSlice(classReport.childrenBalancesTotal.balances);
|
|
134
|
+
classTotalMap.set(classId, classBalance);
|
|
135
|
+
// Iterate through accounts in this class
|
|
136
|
+
collectAccountsFromClass(classReport, classId, 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, accountMap) {
|
|
170
|
+
// Process accounts directly on this class
|
|
171
|
+
classReport.class.accounts.forEach((nestedAccount) => {
|
|
172
|
+
collectAccountsFromNestedAccount(nestedAccount, classId, accountMap);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Recursively collect accounts from NestedAccountReport (handles sub-accounts).
|
|
177
|
+
*/
|
|
178
|
+
function collectAccountsFromNestedAccount(nestedAccount, classId, accountMap) {
|
|
179
|
+
const accountId = nestedAccount.account.accountId;
|
|
180
|
+
const accountName = nestedAccount.account.accountName;
|
|
181
|
+
const balance = sumBalancesInSlice(nestedAccount.account.balancesInTimeframe.balances);
|
|
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, accountMap);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Total amount for the current report slice. `balances` is already narrowed by
|
|
201
|
+
* `getCOABalances` / `sliceCOABalances` to the selected range; summing matches
|
|
202
|
+
* multi-period selections and avoids mismatches when `thisPeriod` does not
|
|
203
|
+
* align with a row (e.g. slice fallback) or when several period rows are shown.
|
|
204
|
+
*/
|
|
205
|
+
function sumBalancesInSlice(balances) {
|
|
206
|
+
let sum = 0;
|
|
207
|
+
for (const bal of balances) {
|
|
208
|
+
const b = bal;
|
|
209
|
+
sum += b.balance?.amount ?? 0;
|
|
210
|
+
}
|
|
211
|
+
return sum;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Compute a calculated section by adding or subtracting two sets of class totals.
|
|
215
|
+
*/
|
|
216
|
+
function computeCalculatedSection(aTotals, bTotals, aTotal, bTotal, operation, title) {
|
|
217
|
+
const classTotals = aTotals.map((a) => {
|
|
218
|
+
const bMatch = bTotals.find((b) => b.classId === a.classId);
|
|
219
|
+
const bAmount = bMatch?.balance.amount ?? 0;
|
|
220
|
+
const resultAmount = operation === 'add'
|
|
221
|
+
? a.balance.amount + bAmount
|
|
222
|
+
: a.balance.amount - bAmount;
|
|
223
|
+
return {
|
|
224
|
+
classId: a.classId,
|
|
225
|
+
balance: toAmountWC(resultAmount, DEFAULT_CURRENCY),
|
|
226
|
+
};
|
|
227
|
+
});
|
|
228
|
+
const sectionTotalAmount = operation === 'add'
|
|
229
|
+
? aTotal.amount + bTotal.amount
|
|
230
|
+
: aTotal.amount - bTotal.amount;
|
|
231
|
+
return {
|
|
232
|
+
title,
|
|
233
|
+
classTotals,
|
|
234
|
+
sectionTotal: toAmountWC(sectionTotalAmount, DEFAULT_CURRENCY),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Build synthetic COABalance array for header rendering.
|
|
239
|
+
* One entry per class column + one for "Total".
|
|
240
|
+
*/
|
|
241
|
+
function buildHeaderBalances(classColumns, timeframe, selectedPeriod) {
|
|
242
|
+
if (selectedPeriod == null) {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
const { endDate, startDate } = selectedPeriod;
|
|
246
|
+
const balances = classColumns.map((col) => ({
|
|
247
|
+
balance: toAmountWC(0, DEFAULT_CURRENCY),
|
|
248
|
+
balanceTimeFrame: timeframe,
|
|
249
|
+
endDate,
|
|
250
|
+
startDate,
|
|
251
|
+
type: 'default',
|
|
252
|
+
label: col.className,
|
|
253
|
+
}));
|
|
254
|
+
// Add a "Total" column
|
|
255
|
+
balances.push({
|
|
256
|
+
balance: toAmountWC(0, DEFAULT_CURRENCY),
|
|
257
|
+
balanceTimeFrame: timeframe,
|
|
258
|
+
endDate,
|
|
259
|
+
startDate,
|
|
260
|
+
type: 'default',
|
|
261
|
+
label: 'Total',
|
|
262
|
+
});
|
|
263
|
+
return balances;
|
|
264
|
+
}
|
|
265
|
+
function reduceFetchState(fetchStateList) {
|
|
266
|
+
const isAnyProgress = fetchStateList.some((s) => s.fetchState === 'In-Progress');
|
|
267
|
+
const isAllCompleted = fetchStateList.every((s) => s.fetchState === 'Completed');
|
|
268
|
+
const isAnyError = fetchStateList.some((s) => s.fetchState === 'Error');
|
|
269
|
+
const error = fetchStateList.find((s) => s.error != null)?.error;
|
|
270
|
+
let fetchState = 'Not-Started';
|
|
271
|
+
if (isAnyProgress) {
|
|
272
|
+
fetchState = 'In-Progress';
|
|
273
|
+
}
|
|
274
|
+
else if (isAllCompleted) {
|
|
275
|
+
fetchState = 'Completed';
|
|
276
|
+
}
|
|
277
|
+
else if (isAnyError) {
|
|
278
|
+
fetchState = 'Error';
|
|
279
|
+
}
|
|
280
|
+
return { fetchState, error };
|
|
281
|
+
}
|
package/lib/esm/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelectorTypes.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -23,6 +23,7 @@ export const initialState = {
|
|
|
23
23
|
nodeCollapseState: {},
|
|
24
24
|
scrollPosition: undefined,
|
|
25
25
|
accountViewMode: 'account_group',
|
|
26
|
+
classViewLayout: 'vertical',
|
|
26
27
|
classesToFilterOut: [],
|
|
27
28
|
},
|
|
28
29
|
firstMonthOfFY: 1,
|
|
@@ -94,6 +95,9 @@ const profitAndLossClassesView = createSlice({
|
|
|
94
95
|
updateAccountViewMode(draft, action) {
|
|
95
96
|
draft.uiState.accountViewMode = action.payload.viewMode;
|
|
96
97
|
},
|
|
98
|
+
updateClassViewLayout(draft, action) {
|
|
99
|
+
draft.uiState.classViewLayout = action.payload.classViewLayout;
|
|
100
|
+
},
|
|
97
101
|
resetProfitAndLossClassesNodeCollapseState(draft) {
|
|
98
102
|
draft.uiState.nodeCollapseState = {};
|
|
99
103
|
},
|
|
@@ -102,7 +106,7 @@ const profitAndLossClassesView = createSlice({
|
|
|
102
106
|
},
|
|
103
107
|
},
|
|
104
108
|
});
|
|
105
|
-
export const { fetchProfitAndLossClassesView, fetchProfitAndLossForTimeframeClassesView, updateProfitAndLossForTimeframeClassesView, updateProfitAndLossForTimeframeClassesViewFailure, updateClassesToFilterOut, updateProfitAndLossClassesViewUIState, updateAccountViewMode, resetProfitAndLossClassesNodeCollapseState, clearProfitAndLossClassesView, } = profitAndLossClassesView.actions;
|
|
109
|
+
export const { fetchProfitAndLossClassesView, fetchProfitAndLossForTimeframeClassesView, updateProfitAndLossForTimeframeClassesView, updateProfitAndLossForTimeframeClassesViewFailure, updateClassesToFilterOut, updateProfitAndLossClassesViewUIState, updateAccountViewMode, updateClassViewLayout, resetProfitAndLossClassesNodeCollapseState, clearProfitAndLossClassesView, } = profitAndLossClassesView.actions;
|
|
106
110
|
export default profitAndLossClassesView.reducer;
|
|
107
111
|
const doUpdatedProfitAndLossClassesView = (draft, timeframe, pandLReport) => {
|
|
108
112
|
const report = mapReportPayloadV2ToReportV2(pandLReport.report);
|
package/lib/index.d.ts
CHANGED
|
@@ -346,10 +346,12 @@ import { PeopleViewSortKey } from './view/people/peopleTypes';
|
|
|
346
346
|
import { fetchProfitAndLoss, fetchProfitAndLossForTimeframe, resetProfitAndLossNodeCollapseState, updateProfitAndLossUIState } from './view/profitAndLoss/profitAndLossReducer';
|
|
347
347
|
import { ProfitAndLossReport, getPandLReportFetchState, getProfitAndLossReport } from './view/profitAndLoss/profitAndLossSelector';
|
|
348
348
|
import { ProfitAndLossState, ProfitAndLossUIState } from './view/profitAndLoss/profitAndLossState';
|
|
349
|
-
import { fetchProfitAndLossClassesView, resetProfitAndLossClassesNodeCollapseState, updateAccountViewMode as updateProfitAndLossAccountViewMode, updateClassesToFilterOut as updateProfitAndLossClassesToFilterOut, updateProfitAndLossClassesViewUIState } from './view/profitAndLossClassesView/profitAndLossClassesViewReducer';
|
|
349
|
+
import { fetchProfitAndLossClassesView, resetProfitAndLossClassesNodeCollapseState, updateAccountViewMode as updateProfitAndLossAccountViewMode, updateClassesToFilterOut as updateProfitAndLossClassesToFilterOut, updateClassViewLayout, updateProfitAndLossClassesViewUIState } from './view/profitAndLossClassesView/profitAndLossClassesViewReducer';
|
|
350
|
+
import { getProfitAndLossClassesHorizontalView } from './view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelector';
|
|
351
|
+
import { ClassColumnDefinition, HorizontalAccountRow, HorizontalCalculatedSection, HorizontalClassBalance, HorizontalSectionReport, ProfitAndLossByClassHorizontalReport } from './view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelectorTypes';
|
|
350
352
|
import { getProfitAndLossClassesView } from './view/profitAndLossClassesView/profitAndLossClassesViewSelector';
|
|
351
353
|
import { PandLReportViewCalculatedSectionID, PandLReportViewSectionID, ProfitAndLossClassesViewReport, isPandLReportViewCalculatedSectionID, isPandLReportViewSectionID } from './view/profitAndLossClassesView/profitAndLossClassesViewSelectorTypes';
|
|
352
|
-
import { ProfitAndLossClassesViewUIState } from './view/profitAndLossClassesView/profitAndLossClassesViewState';
|
|
354
|
+
import { ClassViewLayout, ProfitAndLossClassesViewUIState } from './view/profitAndLossClassesView/profitAndLossClassesViewState';
|
|
353
355
|
import { fetchEntityRecommendationsByTransactionId, fetchRecommendationByEntityId, fetchRecommendationByEntityName } from './view/recommendation/recommendationReducer';
|
|
354
356
|
import { clearReferrals, fetchReferrals, fetchRewardsPlan, resendReferralInvite, saveReferralFormDataInLocalStore, sendReferralInvite, updateReferViewed, updateReferralListSortUiState } from './view/referralView/referralReducer';
|
|
355
357
|
import { ReferralListSelectorView, RewardsPlanCardReport, getInviteFormView, getReferralListView, getRewardsPlanCard } from './view/referralView/referralSelector';
|
|
@@ -664,7 +666,7 @@ export { fetchDashboard, getDashboard, DashboardReport, updateTreasuryVideoClose
|
|
|
664
666
|
export { updateDashboardLayout };
|
|
665
667
|
export { PandLWithForecastView, getPandLWithForecast, } from './view/profitAndLoss/pAndLWithForecast/pAndLWithForecastSelector';
|
|
666
668
|
export { fetchProfitAndLoss, resetProfitAndLossNodeCollapseState, updateProfitAndLossUIState, ProfitAndLossState, ProfitAndLossUIState, getProfitAndLossReport, ProfitAndLossReport, getPandLReportFetchState, fetchProfitAndLossForTimeframe, };
|
|
667
|
-
export { fetchProfitAndLossClassesView, updateProfitAndLossClassesToFilterOut, resetProfitAndLossClassesNodeCollapseState, updateProfitAndLossClassesViewUIState, updateProfitAndLossAccountViewMode, ProfitAndLossClassesViewUIState, ProfitAndLossClassesViewReport, getProfitAndLossClassesView, PandLReportViewSectionID, PandLReportViewCalculatedSectionID, isPandLReportViewCalculatedSectionID, isPandLReportViewSectionID, };
|
|
669
|
+
export { fetchProfitAndLossClassesView, updateProfitAndLossClassesToFilterOut, resetProfitAndLossClassesNodeCollapseState, updateProfitAndLossClassesViewUIState, updateProfitAndLossAccountViewMode, updateClassViewLayout, ProfitAndLossClassesViewUIState, ProfitAndLossClassesViewReport, getProfitAndLossClassesView, getProfitAndLossClassesHorizontalView, PandLReportViewSectionID, PandLReportViewCalculatedSectionID, isPandLReportViewCalculatedSectionID, isPandLReportViewSectionID, ClassViewLayout, ProfitAndLossByClassHorizontalReport, ClassColumnDefinition, HorizontalAccountRow, HorizontalClassBalance, HorizontalSectionReport, HorizontalCalculatedSection, };
|
|
668
670
|
export { AccountingProviderAttachmentSelector, getAccountingProviderAttachment };
|
|
669
671
|
export { fetchUserListByType, getUserList, UserListSelectorView };
|
|
670
672
|
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, };
|