diff --git a/packages/desktop-client/src/components/reports/ReportOptions.ts b/packages/desktop-client/src/components/reports/ReportOptions.ts index 25fc25551824a428fea548a43b3b2c1f64c1fe0c..6fe42586be10cbb16a5c5fa01a7028fb178cb233 100644 --- a/packages/desktop-client/src/components/reports/ReportOptions.ts +++ b/packages/desktop-client/src/components/reports/ReportOptions.ts @@ -35,6 +35,8 @@ const balanceTypeOptions = [ { description: 'Payment', format: 'totalDebts' as const }, { description: 'Deposit', format: 'totalAssets' as const }, { description: 'Net', format: 'totalTotals' as const }, + { description: 'Net Payment', format: 'netDebts' as const }, + { description: 'Net Deposit', format: 'netAssets' as const }, ]; const groupByOptions = [ diff --git a/packages/desktop-client/src/components/reports/ReportSummary.tsx b/packages/desktop-client/src/components/reports/ReportSummary.tsx index 9e25d548cfe5bd6361695548f05067442c8b21f5..88968e67a0a668f416a95cfa0b80e2a351ac6b05 100644 --- a/packages/desktop-client/src/components/reports/ReportSummary.tsx +++ b/packages/desktop-client/src/components/reports/ReportSummary.tsx @@ -6,7 +6,10 @@ import { integerToCurrency, amountToInteger, } from 'loot-core/src/shared/util'; -import { type DataEntity } from 'loot-core/src/types/models/reports'; +import { + type balanceTypeOpType, + type DataEntity, +} from 'loot-core/src/types/models/reports'; import { theme, styles } from '../../style'; import { Text } from '../common/Text'; @@ -19,7 +22,7 @@ type ReportSummaryProps = { startDate: string; endDate: string; data: DataEntity; - balanceTypeOp: 'totalDebts' | 'totalAssets' | 'totalTotals'; + balanceTypeOp: balanceTypeOpType; interval: string; intervalsCount: number; }; @@ -33,9 +36,13 @@ export function ReportSummary({ intervalsCount, }: ReportSummaryProps) { const net = - Math.abs(data.totalDebts) > Math.abs(data.totalAssets) - ? 'PAYMENT' - : 'DEPOSIT'; + balanceTypeOp === 'netAssets' + ? 'DEPOSIT' + : balanceTypeOp === 'netDebts' + ? 'PAYMENT' + : Math.abs(data.totalDebts) > Math.abs(data.totalAssets) + ? 'PAYMENT' + : 'DEPOSIT'; const average = amountToInteger(data[balanceTypeOp]) / intervalsCount; return ( <View diff --git a/packages/desktop-client/src/components/reports/disabledList.ts b/packages/desktop-client/src/components/reports/disabledList.ts index eb3e709718f9211d7e53b981d0c465be7ab3700e..4042ad77ac1550f3a7f16d94386d3f021fc5e1cc 100644 --- a/packages/desktop-client/src/components/reports/disabledList.ts +++ b/packages/desktop-client/src/components/reports/disabledList.ts @@ -63,7 +63,7 @@ const totalGraphOptions: graphOptions[] = [ description: 'BarGraph', disabledSplit: [], defaultSplit: 'Category', - disabledType: ['Net'], + disabledType: [], defaultType: 'Payment', }, { @@ -88,7 +88,7 @@ const timeGraphOptions: graphOptions[] = [ description: 'TableGraph', disabledSplit: ['Interval'], defaultSplit: 'Category', - disabledType: [], + disabledType: ['Net Payment', 'Net Deposit'], defaultType: 'Payment', disableLegend: true, disableLabel: true, @@ -97,14 +97,14 @@ const timeGraphOptions: graphOptions[] = [ description: 'StackedBarGraph', disabledSplit: ['Interval'], defaultSplit: 'Category', - disabledType: ['Net'], + disabledType: [], defaultType: 'Payment', }, { description: 'LineGraph', disabledSplit: ['Interval'], defaultSplit: 'Category', - disabledType: ['Net'], + disabledType: [], defaultType: 'Payment', disableLegend: false, disableLabel: true, diff --git a/packages/desktop-client/src/components/reports/graphs/AreaGraph.tsx b/packages/desktop-client/src/components/reports/graphs/AreaGraph.tsx index 03a5934b332a38b2da0570f10e444422479e1138..60c315d2a243c09a9900ea55e260768c179c4bf0 100644 --- a/packages/desktop-client/src/components/reports/graphs/AreaGraph.tsx +++ b/packages/desktop-client/src/components/reports/graphs/AreaGraph.tsx @@ -16,7 +16,10 @@ import { amountToCurrency, amountToCurrencyNoDecimal, } from 'loot-core/src/shared/util'; -import { type DataEntity } from 'loot-core/src/types/models/reports'; +import { + type balanceTypeOpType, + type DataEntity, +} from 'loot-core/src/types/models/reports'; import { usePrivacyMode } from '../../../hooks/usePrivacyMode'; import { useResponsive } from '../../../ResponsiveProvider'; @@ -33,6 +36,8 @@ type PayloadItem = { date: string; totalAssets: number | string; totalDebts: number | string; + netAssets: number | string; + netDebts: number | string; totalTotals: number | string; }; }; @@ -40,7 +45,7 @@ type PayloadItem = { type CustomTooltipProps = { active?: boolean; payload?: PayloadItem[]; - balanceTypeOp: 'totalAssets' | 'totalTotals' | 'totalDebts'; + balanceTypeOp: balanceTypeOpType; }; const CustomTooltip = ({ @@ -74,10 +79,22 @@ const CustomTooltip = ({ )} {['totalDebts', 'totalTotals'].includes(balanceTypeOp) && ( <AlignedText - left="Debt:" + left="Debts:" right={amountToCurrency(payload[0].payload.totalDebts)} /> )} + {['netAssets'].includes(balanceTypeOp) && ( + <AlignedText + left="Net Assets:" + right={amountToCurrency(payload[0].payload.netAssets)} + /> + )} + {['netDebts'].includes(balanceTypeOp) && ( + <AlignedText + left="Net Debts:" + right={amountToCurrency(payload[0].payload.netDebts)} + /> + )} {['totalTotals'].includes(balanceTypeOp) && ( <AlignedText left="Net:" @@ -132,7 +149,7 @@ const customLabel = ({ type AreaGraphProps = { style?: CSSProperties; data: DataEntity; - balanceTypeOp: 'totalAssets' | 'totalTotals' | 'totalDebts'; + balanceTypeOp: balanceTypeOpType; compact?: boolean; viewLabels: boolean; }; diff --git a/packages/desktop-client/src/components/reports/graphs/BarGraph.tsx b/packages/desktop-client/src/components/reports/graphs/BarGraph.tsx index e6f1a0a6eb25b4cd8fb8fe92875cf5b47cdee7cc..37ca3cef86f46d1e790ede15750218ce330232a8 100644 --- a/packages/desktop-client/src/components/reports/graphs/BarGraph.tsx +++ b/packages/desktop-client/src/components/reports/graphs/BarGraph.tsx @@ -19,7 +19,10 @@ import { amountToCurrency, amountToCurrencyNoDecimal, } from 'loot-core/src/shared/util'; -import { type DataEntity } from 'loot-core/src/types/models/reports'; +import { + type balanceTypeOpType, + type DataEntity, +} from 'loot-core/src/types/models/reports'; import { type RuleConditionEntity } from 'loot-core/types/models/rule'; import { useAccounts } from '../../../hooks/useAccounts'; @@ -50,6 +53,8 @@ type PayloadItem = { name: string; totalAssets: number | string; totalDebts: number | string; + netAssets: number | string; + netDebts: number | string; totalTotals: number | string; networth: number | string; totalChange: number | string; @@ -60,7 +65,7 @@ type PayloadItem = { type CustomTooltipProps = { active?: boolean; payload?: PayloadItem[]; - balanceTypeOp?: 'totalAssets' | 'totalDebts' | 'totalTotals'; + balanceTypeOp?: balanceTypeOpType; yAxis?: string; }; @@ -96,10 +101,22 @@ const CustomTooltip = ({ )} {['totalDebts', 'totalTotals'].includes(balanceTypeOp) && ( <AlignedText - left="Debt:" + left="Debts:" right={amountToCurrency(payload[0].payload.totalDebts)} /> )} + {['netAssets'].includes(balanceTypeOp) && ( + <AlignedText + left="Net Assets:" + right={amountToCurrency(payload[0].payload.netAssets)} + /> + )} + {['netDebts'].includes(balanceTypeOp) && ( + <AlignedText + left="Net Debts:" + right={amountToCurrency(payload[0].payload.netDebts)} + /> + )} {['totalTotals'].includes(balanceTypeOp) && ( <AlignedText left="Net:" @@ -137,7 +154,7 @@ type BarGraphProps = { data: DataEntity; filters: RuleConditionEntity[]; groupBy: string; - balanceTypeOp: 'totalAssets' | 'totalDebts' | 'totalTotals'; + balanceTypeOp: balanceTypeOpType; compact?: boolean; viewLabels: boolean; showHiddenCategories?: boolean; @@ -167,11 +184,15 @@ export function BarGraph({ const labelsMargin = viewLabels ? 30 : 0; const getVal = obj => { - if (balanceTypeOp === 'totalDebts') { - return -1 * obj.totalDebts; - } else { + if (balanceTypeOp === 'totalTotals' && groupBy === 'Interval') { return obj.totalAssets; } + + if (['totalDebts', 'netDebts'].includes(balanceTypeOp)) { + return -1 * obj[balanceTypeOp]; + } + + return obj[balanceTypeOp]; }; const longestLabelLength = data[splitData] diff --git a/packages/desktop-client/src/components/reports/graphs/DonutGraph.tsx b/packages/desktop-client/src/components/reports/graphs/DonutGraph.tsx index 3eab0f182d4993fe9d3cabf2574313a4317569e0..d6b02490b9f806662159d2deaf45c2eaa61f9bd8 100644 --- a/packages/desktop-client/src/components/reports/graphs/DonutGraph.tsx +++ b/packages/desktop-client/src/components/reports/graphs/DonutGraph.tsx @@ -4,7 +4,10 @@ import React, { useState } from 'react'; import { PieChart, Pie, Cell, Sector, ResponsiveContainer } from 'recharts'; import { amountToCurrency } from 'loot-core/src/shared/util'; -import { type DataEntity } from 'loot-core/src/types/models/reports'; +import { + type balanceTypeOpType, + type DataEntity, +} from 'loot-core/src/types/models/reports'; import { type RuleConditionEntity } from 'loot-core/types/models/rule'; import { useAccounts } from '../../../hooks/useAccounts'; @@ -181,7 +184,7 @@ type DonutGraphProps = { data: DataEntity; filters: RuleConditionEntity[]; groupBy: string; - balanceTypeOp: 'totalAssets' | 'totalDebts' | 'totalTotals'; + balanceTypeOp: balanceTypeOpType; compact?: boolean; viewLabels: boolean; showHiddenCategories?: boolean; @@ -209,7 +212,7 @@ export function DonutGraph({ const [pointer, setPointer] = useState(''); const getVal = obj => { - if (balanceTypeOp === 'totalDebts') { + if (['totalDebts', 'netDebts'].includes(balanceTypeOp)) { return -1 * obj[balanceTypeOp]; } else { return obj[balanceTypeOp]; diff --git a/packages/desktop-client/src/components/reports/graphs/LineGraph.tsx b/packages/desktop-client/src/components/reports/graphs/LineGraph.tsx index d47575f88b9adf09a0c37f909990f51bd62a5408..14c53ad28e13d9ab4c43ecfb04055fdf12a8fb7d 100644 --- a/packages/desktop-client/src/components/reports/graphs/LineGraph.tsx +++ b/packages/desktop-client/src/components/reports/graphs/LineGraph.tsx @@ -16,7 +16,10 @@ import { amountToCurrency, amountToCurrencyNoDecimal, } from 'loot-core/src/shared/util'; -import { type DataEntity } from 'loot-core/types/models/reports'; +import { + type balanceTypeOpType, + type DataEntity, +} from 'loot-core/types/models/reports'; import { type RuleConditionEntity } from 'loot-core/types/models/rule'; import { useAccounts } from '../../../hooks/useAccounts'; @@ -115,7 +118,7 @@ type LineGraphProps = { filters: RuleConditionEntity[]; groupBy: string; compact?: boolean; - balanceTypeOp: 'totalAssets' | 'totalDebts' | 'totalTotals'; + balanceTypeOp: balanceTypeOpType; showHiddenCategories?: boolean; showOffBudget?: boolean; interval?: string; diff --git a/packages/desktop-client/src/components/reports/graphs/StackedBarGraph.tsx b/packages/desktop-client/src/components/reports/graphs/StackedBarGraph.tsx index 81463d147d85cdffe1bb60aaecb6e2e7b89a0683..309fa8618dc884e7299dc91e69231e5d7f4a49e3 100644 --- a/packages/desktop-client/src/components/reports/graphs/StackedBarGraph.tsx +++ b/packages/desktop-client/src/components/reports/graphs/StackedBarGraph.tsx @@ -17,7 +17,10 @@ import { amountToCurrency, amountToCurrencyNoDecimal, } from 'loot-core/src/shared/util'; -import { type DataEntity } from 'loot-core/src/types/models/reports'; +import { + type balanceTypeOpType, + type DataEntity, +} from 'loot-core/src/types/models/reports'; import { type RuleConditionEntity } from 'loot-core/types/models/rule'; import { useAccounts } from '../../../hooks/useAccounts'; @@ -144,7 +147,7 @@ type StackedBarGraphProps = { groupBy: string; compact?: boolean; viewLabels: boolean; - balanceTypeOp: 'totalAssets' | 'totalDebts' | 'totalTotals'; + balanceTypeOp: balanceTypeOpType; showHiddenCategories?: boolean; showOffBudget?: boolean; interval?: string; @@ -194,6 +197,7 @@ export function StackedBarGraph({ data={data.intervalData} margin={{ top: 0, right: 0, left: leftMargin, bottom: 10 }} style={{ cursor: pointer }} + stackOffset="sign" //stacked by sign > {(!isNarrowWidth || !compact) && ( <Tooltip diff --git a/packages/desktop-client/src/components/reports/graphs/showActivity.ts b/packages/desktop-client/src/components/reports/graphs/showActivity.ts index 892664b8126caf67a0d38bfba5b551418cfb3a26..085cc806d57dbc1f0a03ad070306cb9d576933e8 100644 --- a/packages/desktop-client/src/components/reports/graphs/showActivity.ts +++ b/packages/desktop-client/src/components/reports/graphs/showActivity.ts @@ -4,6 +4,7 @@ import * as monthUtils from 'loot-core/src/shared/months'; import { type AccountEntity } from 'loot-core/types/models/account'; import { type CategoryEntity } from 'loot-core/types/models/category'; import { type CategoryGroupEntity } from 'loot-core/types/models/category-group'; +import { type balanceTypeOpType } from 'loot-core/types/models/reports'; import { type RuleConditionEntity } from 'loot-core/types/models/rule'; import { ReportOptions } from '../ReportOptions'; @@ -12,7 +13,7 @@ type showActivityProps = { navigate: NavigateFunction; categories: { list: CategoryEntity[]; grouped: CategoryGroupEntity[] }; accounts: AccountEntity[]; - balanceTypeOp: 'totalAssets' | 'totalDebts' | 'totalTotals'; + balanceTypeOp: balanceTypeOpType; filters: RuleConditionEntity[]; showHiddenCategories: boolean; showOffBudget: boolean; @@ -50,7 +51,7 @@ export function showActivity({ 'FromDate') as 'dayFromDate' | 'monthFromDate' | 'yearFromDate'); const isDateOp = interval === 'Weekly' || type !== 'time'; - const conditions = [ + const filterConditions = [ ...filters, id && { field, op: 'is', value: id, type: 'id' }, { @@ -66,8 +67,9 @@ export function showActivity({ options: { date: true }, }, !( - balanceTypeOp === 'totalTotals' && - (type === 'totals' || type === 'time') + ['netAssets', 'netDebts'].includes(balanceTypeOp) || + (balanceTypeOp === 'totalTotals' && + (type === 'totals' || type === 'time')) ) && { field: 'amount', op: 'gte', @@ -96,7 +98,7 @@ export function showActivity({ navigate('/accounts', { state: { goBack: true, - conditions, + filterConditions, }, }); } diff --git a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTable.tsx b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTable.tsx index 0da32fb5ce4acfb0586d05588d77d6bbea183455..6db7986aa3009b10e5c7d166a65e6dd5ae28639d 100644 --- a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTable.tsx +++ b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTable.tsx @@ -9,6 +9,7 @@ import React, { import { type GroupedEntity, type DataEntity, + type balanceTypeOpType, } from 'loot-core/src/types/models/reports'; import { type RuleConditionEntity } from 'loot-core/types/models/rule'; @@ -28,7 +29,7 @@ type ReportTableProps = { totalScrollRef: RefObject<HTMLDivElement>; handleScroll: UIEventHandler<HTMLDivElement>; groupBy: string; - balanceTypeOp: 'totalDebts' | 'totalTotals' | 'totalAssets'; + balanceTypeOp: balanceTypeOpType; data: DataEntity; filters?: RuleConditionEntity[]; mode: string; diff --git a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableHeader.tsx b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableHeader.tsx index 2dea557dea7d957d77ce53e7a4c494f6cbf0b2ef..926beef7ecc1680824fe198394c2840de37f6eaf 100644 --- a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableHeader.tsx +++ b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableHeader.tsx @@ -1,6 +1,9 @@ import React, { type RefObject, type UIEventHandler } from 'react'; -import { type IntervalEntity } from 'loot-core/src/types/models/reports'; +import { + type balanceTypeOpType, + type IntervalEntity, +} from 'loot-core/src/types/models/reports'; import { theme } from '../../../../style'; import { type CSSProperties } from '../../../../style/types'; @@ -12,7 +15,7 @@ type ReportTableHeaderProps = { groupBy: string; interval: string; data: IntervalEntity[]; - balanceTypeOp: 'totalDebts' | 'totalTotals' | 'totalAssets'; + balanceTypeOp: balanceTypeOpType; headerScrollRef: RefObject<HTMLDivElement>; handleScroll: UIEventHandler<HTMLDivElement>; compact: boolean; diff --git a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableList.tsx b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableList.tsx index f3ba6bad8a2a5b2b793f8ccca643dc39695ae766..e26b51367d5a7e1b42bdf456a53604bbe60104f4 100644 --- a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableList.tsx +++ b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableList.tsx @@ -38,6 +38,8 @@ export function ReportTableList({ date: interval.date, totalAssets: interval.totalAssets, totalDebts: interval.totalDebts, + netAssets: interval.netAssets, + netDebts: interval.netDebts, totalTotals: interval.totalTotals, intervalData: [], categories: [], diff --git a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableRow.tsx b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableRow.tsx index c18cf54c9cac902ba1ad8626457776d8876010b7..11a8dea359e847ee7a7fac48187fddd7f3ec4e33 100644 --- a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableRow.tsx +++ b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableRow.tsx @@ -5,7 +5,10 @@ import { amountToInteger, integerToCurrency, } from 'loot-core/src/shared/util'; -import { type GroupedEntity } from 'loot-core/types/models/reports'; +import { + type balanceTypeOpType, + type GroupedEntity, +} from 'loot-core/types/models/reports'; import { type RuleConditionEntity } from 'loot-core/types/models/rule'; import { useAccounts } from '../../../../hooks/useAccounts'; @@ -20,7 +23,7 @@ import { showActivity } from '../showActivity'; type ReportTableRowProps = { item: GroupedEntity; - balanceTypeOp: 'totalAssets' | 'totalDebts' | 'totalTotals'; + balanceTypeOp: balanceTypeOpType; groupBy: string; mode: string; filters?: RuleConditionEntity[]; diff --git a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableTotals.tsx b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableTotals.tsx index 8690fc709e5acfcd23deca2a35a81cc3609e8d48..ed192f0c4eb5e625fe591f488bd4e141ac6cb891 100644 --- a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableTotals.tsx +++ b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableTotals.tsx @@ -85,6 +85,8 @@ export function ReportTableTotals({ intervalData: data.intervalData, totalAssets: data.totalAssets, totalDebts: data.totalDebts, + netAssets: data.netAssets, + netDebts: data.netDebts, totalTotals: data.totalTotals, }; diff --git a/packages/desktop-client/src/components/reports/reports/CustomReport.tsx b/packages/desktop-client/src/components/reports/reports/CustomReport.tsx index 2fb2a375c1b0039f2f114f292eed77ab16f200a5..edeba3fbf2286b2355706e87192e4729d5a5185e 100644 --- a/packages/desktop-client/src/components/reports/reports/CustomReport.tsx +++ b/packages/desktop-client/src/components/reports/reports/CustomReport.tsx @@ -8,6 +8,7 @@ import * as monthUtils from 'loot-core/src/shared/months'; import { amountToCurrency } from 'loot-core/src/shared/util'; import { type CategoryEntity } from 'loot-core/types/models/category'; import { + type balanceTypeOpType, type CustomReportEntity, type DataEntity, } from 'loot-core/types/models/reports'; @@ -248,7 +249,7 @@ export function CustomReport() { } }, [interval, startDate, endDate, firstDayOfWeekIdx]); - const balanceTypeOp: 'totalAssets' | 'totalDebts' | 'totalTotals' = + const balanceTypeOp: balanceTypeOpType = ReportOptions.balanceTypeMap.get(balanceType) || 'totalDebts'; const payees = usePayees(); const accounts = useAccounts(); @@ -692,7 +693,7 @@ export function CustomReport() { right={ <Text> <PrivacyFilter blurIntensity={5}> - {amountToCurrency(Math.abs(data[balanceTypeOp]))} + {amountToCurrency(data[balanceTypeOp])} </PrivacyFilter> </Text> } diff --git a/packages/desktop-client/src/components/reports/spreadsheets/calculateLegend.ts b/packages/desktop-client/src/components/reports/spreadsheets/calculateLegend.ts index 7ada7dec8d9077646e665ef7a3d6b4d41405e97c..93a80014fee46a24275772c62dc8611a0b6aa525 100644 --- a/packages/desktop-client/src/components/reports/spreadsheets/calculateLegend.ts +++ b/packages/desktop-client/src/components/reports/spreadsheets/calculateLegend.ts @@ -2,6 +2,7 @@ import { type LegendEntity, type IntervalEntity, type GroupedEntity, + type balanceTypeOpType, } from 'loot-core/src/types/models/reports'; import { theme } from '../../../style'; @@ -12,7 +13,7 @@ export function calculateLegend( calcDataFiltered: GroupedEntity[], groupBy: string, graphType?: string, - balanceTypeOp?: 'totalAssets' | 'totalDebts' | 'totalTotals', + balanceTypeOp?: balanceTypeOpType, ): LegendEntity[] { const colorScale = getColorScale('qualitative'); const chooseData = diff --git a/packages/desktop-client/src/components/reports/spreadsheets/custom-spreadsheet.ts b/packages/desktop-client/src/components/reports/spreadsheets/custom-spreadsheet.ts index 0ad83924bf9d4542c523ec9fc615e62b9e0f5ce9..86a2fd6ebeb51694238717eb2100cc490e39702a 100644 --- a/packages/desktop-client/src/components/reports/spreadsheets/custom-spreadsheet.ts +++ b/packages/desktop-client/src/components/reports/spreadsheets/custom-spreadsheet.ts @@ -13,6 +13,7 @@ import { type CategoryGroupEntity, } from 'loot-core/src/types/models'; import { + type balanceTypeOpType, type DataEntity, type GroupedEntity, type IntervalEntity, @@ -46,7 +47,7 @@ export type createCustomSpreadsheetProps = { showHiddenCategories: boolean; showUncategorized: boolean; groupBy?: string; - balanceTypeOp?: 'totalAssets' | 'totalDebts' | 'totalTotals'; + balanceTypeOp?: balanceTypeOpType; payees?: PayeeEntity[]; accounts?: AccountEntity[]; graphType?: string; @@ -153,11 +154,16 @@ export function createCustomSpreadsheet({ let totalAssets = 0; let totalDebts = 0; + let netAssets = 0; + let netDebts = 0; const intervalData = intervals.reduce( (arr: IntervalEntity[], intervalItem, index) => { let perIntervalAssets = 0; let perIntervalDebts = 0; + let perIntervalNetAssets = 0; + let perIntervalNetDebts = 0; + let perIntervalTotals = 0; const stacked: Record<string, number> = {}; groupByList.map(item => { @@ -193,20 +199,43 @@ export function createCustomSpreadsheet({ .reduce((a, v) => (a = a + v.amount), 0); perIntervalDebts += intervalDebts; + const netAmounts = intervalAssets + intervalDebts; + if (balanceTypeOp === 'totalAssets') { stackAmounts += intervalAssets; } if (balanceTypeOp === 'totalDebts') { - stackAmounts += intervalDebts; + stackAmounts += Math.abs(intervalDebts); + } + if (balanceTypeOp === 'netAssets') { + stackAmounts += netAmounts > 0 ? netAmounts : 0; + } + if (balanceTypeOp === 'netDebts') { + stackAmounts = netAmounts < 0 ? Math.abs(netAmounts) : 0; + } + if (balanceTypeOp === 'totalTotals') { + stackAmounts += netAmounts; } if (stackAmounts !== 0) { - stacked[item.name] = integerToAmount(Math.abs(stackAmounts)); + stacked[item.name] = integerToAmount(stackAmounts); } + perIntervalNetAssets = + netAmounts > 0 + ? perIntervalNetAssets + netAmounts + : perIntervalNetAssets; + perIntervalNetDebts = + netAmounts < 0 + ? perIntervalNetDebts + netAmounts + : perIntervalNetDebts; + perIntervalTotals += netAmounts; + return null; }); totalAssets += perIntervalAssets; totalDebts += perIntervalDebts; + netAssets += perIntervalNetAssets; + netDebts += perIntervalNetDebts; arr.push({ date: d.format( @@ -219,9 +248,11 @@ export function createCustomSpreadsheet({ index + 1 === intervals.length ? endDate : monthUtils.subDays(intervals[index + 1], 1), - totalDebts: integerToAmount(perIntervalDebts), totalAssets: integerToAmount(perIntervalAssets), - totalTotals: integerToAmount(perIntervalDebts + perIntervalAssets), + totalDebts: integerToAmount(perIntervalDebts), + netAssets: integerToAmount(perIntervalNetAssets), + netDebts: integerToAmount(perIntervalNetDebts), + totalTotals: integerToAmount(perIntervalTotals), }); return arr; @@ -262,8 +293,10 @@ export function createCustomSpreadsheet({ legend, startDate, endDate, - totalDebts: integerToAmount(totalDebts), totalAssets: integerToAmount(totalAssets), + totalDebts: integerToAmount(totalDebts), + netAssets: integerToAmount(netAssets), + netDebts: integerToAmount(netDebts), totalTotals: integerToAmount(totalAssets + totalDebts), }); setDataCheck?.(true); diff --git a/packages/desktop-client/src/components/reports/spreadsheets/filterEmptyRows.ts b/packages/desktop-client/src/components/reports/spreadsheets/filterEmptyRows.ts index b133a67da132b18b08b07bac120fdc6df71b644c..922696e9cf7b1563a808fc61c9ce0a90983e5cff 100644 --- a/packages/desktop-client/src/components/reports/spreadsheets/filterEmptyRows.ts +++ b/packages/desktop-client/src/components/reports/spreadsheets/filterEmptyRows.ts @@ -1,4 +1,7 @@ -import { type GroupedEntity } from 'loot-core/src/types/models/reports'; +import { + type balanceTypeOpType, + type GroupedEntity, +} from 'loot-core/src/types/models/reports'; export function filterEmptyRows({ showEmpty, @@ -7,7 +10,7 @@ export function filterEmptyRows({ }: { showEmpty: boolean; data: GroupedEntity; - balanceTypeOp?: 'totalAssets' | 'totalDebts' | 'totalTotals'; + balanceTypeOp?: balanceTypeOpType; }): boolean { let showHide: boolean; if (balanceTypeOp === 'totalTotals') { diff --git a/packages/desktop-client/src/components/reports/spreadsheets/grouped-spreadsheet.ts b/packages/desktop-client/src/components/reports/spreadsheets/grouped-spreadsheet.ts index ed3048d291d245501d28c844c3f847c595610e22..414bb93a8b2b10a4751523e5f4ca3dbc2abbb9c5 100644 --- a/packages/desktop-client/src/components/reports/spreadsheets/grouped-spreadsheet.ts +++ b/packages/desktop-client/src/components/reports/spreadsheets/grouped-spreadsheet.ts @@ -111,11 +111,16 @@ export function createGroupedSpreadsheet({ group => { let totalAssets = 0; let totalDebts = 0; + let netAssets = 0; + let netDebts = 0; const intervalData = intervals.reduce( (arr: IntervalEntity[], intervalItem) => { let groupedAssets = 0; let groupedDebts = 0; + let groupedNetAssets = 0; + let groupedNetDebts = 0; + let groupedTotals = 0; if (!group.categories) { return []; @@ -151,16 +156,32 @@ export function createGroupedSpreadsheet({ ) .reduce((a, v) => (a = a + v.amount), 0); groupedDebts += intervalDebts; + + const intervalTotals = intervalAssets + intervalDebts; + + groupedNetAssets = + intervalTotals > 0 + ? groupedNetAssets + intervalTotals + : groupedNetAssets; + groupedNetDebts = + intervalTotals < 0 + ? groupedNetDebts + intervalTotals + : groupedNetDebts; + groupedTotals += intervalTotals; }); totalAssets += groupedAssets; totalDebts += groupedDebts; + netAssets += groupedNetAssets; + netDebts += groupedNetDebts; arr.push({ date: intervalItem, totalAssets: integerToAmount(groupedAssets), totalDebts: integerToAmount(groupedDebts), - totalTotals: integerToAmount(groupedDebts + groupedAssets), + netAssets: integerToAmount(groupedNetAssets), + netDebts: integerToAmount(groupedNetDebts), + totalTotals: integerToAmount(groupedTotals), }); return arr; @@ -191,6 +212,8 @@ export function createGroupedSpreadsheet({ name: group.name, totalAssets: integerToAmount(totalAssets), totalDebts: integerToAmount(totalDebts), + netAssets: integerToAmount(netAssets), + netDebts: integerToAmount(netDebts), totalTotals: integerToAmount(totalAssets + totalDebts), intervalData, categories: diff --git a/packages/desktop-client/src/components/reports/spreadsheets/recalculate.ts b/packages/desktop-client/src/components/reports/spreadsheets/recalculate.ts index 3f4bc44b7b488dbe772107e09e3995f1ef69ac6a..d0dc1e1c9f9f12356129abf321dba914df5c24d4 100644 --- a/packages/desktop-client/src/components/reports/spreadsheets/recalculate.ts +++ b/packages/desktop-client/src/components/reports/spreadsheets/recalculate.ts @@ -73,14 +73,18 @@ export function recalculate({ .reduce((a, v) => (a = a + v.amount), 0); totalDebts += intervalDebts; + const intervalTotals = intervalAssets + intervalDebts; + const change = last - ? intervalAssets + intervalDebts - amountToInteger(last.totalTotals) + ? intervalTotals - amountToInteger(last.totalTotals) : 0; arr.push({ totalAssets: integerToAmount(intervalAssets), totalDebts: integerToAmount(intervalDebts), - totalTotals: integerToAmount(intervalAssets + intervalDebts), + netAssets: intervalTotals > 0 ? integerToAmount(intervalTotals) : 0, + netDebts: intervalTotals < 0 ? integerToAmount(intervalTotals) : 0, + totalTotals: integerToAmount(intervalTotals), change, intervalStartDate: index === 0 ? startDate : intervalItem, intervalEndDate: @@ -94,12 +98,16 @@ export function recalculate({ [], ); + const totalTotals = totalAssets + totalDebts; + return { id: item.id || '', name: item.name, totalAssets: integerToAmount(totalAssets), totalDebts: integerToAmount(totalDebts), - totalTotals: integerToAmount(totalAssets + totalDebts), + netAssets: totalTotals > 0 ? integerToAmount(totalTotals) : 0, + netDebts: totalTotals < 0 ? integerToAmount(totalTotals) : 0, + totalTotals: integerToAmount(totalTotals), intervalData, }; } diff --git a/packages/loot-core/src/types/models/reports.d.ts b/packages/loot-core/src/types/models/reports.d.ts index d7e04a5f81680c420345509b192c5d082f38601d..da645278614fa14b328e0369f65a1d500c46b26a 100644 --- a/packages/loot-core/src/types/models/reports.d.ts +++ b/packages/loot-core/src/types/models/reports.d.ts @@ -25,6 +25,13 @@ export interface CustomReportEntity { tombstone?: boolean; } +export type balanceTypeOpType = + | 'totalAssets' + | 'totalDebts' + | 'totalTotals' + | 'netAssets' + | 'netDebts'; + export type SpendingMonthEntity = Record< string | number, { @@ -68,6 +75,8 @@ export interface DataEntity { endDate?: string; totalDebts: number; totalAssets: number; + netAssets: number; + netDebts: number; totalTotals: number; } @@ -84,6 +93,8 @@ export type IntervalEntity = { intervalEndDate?: string; totalAssets: number; totalDebts: number; + netAssets: number; + netDebts: number; totalTotals: number; }; @@ -95,6 +106,8 @@ export interface GroupedEntity { totalAssets: number; totalDebts: number; totalTotals: number; + netAssets: number; + netDebts: number; categories?: GroupedEntity[]; } diff --git a/upcoming-release-notes/2871.md b/upcoming-release-notes/2871.md new file mode 100644 index 0000000000000000000000000000000000000000..6488461265f98d0d8138fa78c92d59e71dbefe2f --- /dev/null +++ b/upcoming-release-notes/2871.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [carkom] +--- + +Custom reports - rework "net" numbers to work more intuitively and allow for greater customization