Money tracker: accounts can be left out of total wealth (include_in_total)
All checks were successful
Deploy production / deploy (push) Successful in 2m32s
All checks were successful
Deploy production / deploy (push) Successful in 2m32s
Adds money.accounts.include_in_total (default true). Excluded accounts keep their own balance and line, but are left out of net worth, invested, this month, the projection and the total/All accounts lines.
This commit is contained in:
@@ -15,10 +15,24 @@ export function accountColor(account: { color?: string | null }, index: number):
|
||||
return account.color || palette[index % palette.length];
|
||||
}
|
||||
|
||||
/** Accounts kept out of net worth and total wealth (`includeInTotal: false`). */
|
||||
export function excludedIds(accounts: { id: number; includeInTotal?: boolean }[]): Set<number> {
|
||||
return new Set(
|
||||
accounts.filter((account) => account.includeInTotal === false).map((account) => account.id)
|
||||
);
|
||||
}
|
||||
|
||||
export function balanceSeries(
|
||||
accounts: { id: number; name: string; openingBalanceCents: number; color?: string | null }[],
|
||||
accounts: {
|
||||
id: number;
|
||||
name: string;
|
||||
openingBalanceCents: number;
|
||||
color?: string | null;
|
||||
includeInTotal?: boolean;
|
||||
}[],
|
||||
transactions: { accountId: number; date: string; amountCents: number }[]
|
||||
): ChartSeries[] {
|
||||
const excluded = excludedIds(accounts);
|
||||
const ordered = [...transactions].sort((a, b) => a.date.localeCompare(b.date));
|
||||
const balances = new Map(accounts.map((account) => [account.id, account.openingBalanceCents]));
|
||||
const accountPoints = new Map(accounts.map((account) => [account.id, [] as ChartPoint[]]));
|
||||
@@ -36,7 +50,10 @@ export function balanceSeries(
|
||||
accountPoints.get(account.id)!.push({ date, value: balances.get(account.id)! });
|
||||
combinedPoints.push({
|
||||
date,
|
||||
value: [...balances.values()].reduce((sum, balance) => sum + balance, 0)
|
||||
value: [...balances].reduce(
|
||||
(sum, [id, balance]) => (excluded.has(id) ? sum : sum + balance),
|
||||
0
|
||||
)
|
||||
});
|
||||
}
|
||||
return [
|
||||
@@ -86,7 +103,12 @@ export function forecastSeries(
|
||||
return [{ label: 'Projected total', color: '#0070f3', points }];
|
||||
}
|
||||
|
||||
type ProjectionAccount = { id: number; kind: string; openingBalanceCents: number };
|
||||
type ProjectionAccount = {
|
||||
id: number;
|
||||
kind: string;
|
||||
openingBalanceCents: number;
|
||||
includeInTotal?: boolean;
|
||||
};
|
||||
type ProjectionMovement = { date: string; amountCents: number; accountId: number | null };
|
||||
type ProjectionRecurring = {
|
||||
amountCents: number;
|
||||
@@ -114,6 +136,8 @@ export function wealthProjection(
|
||||
settings: ProjectionSettings | null,
|
||||
today: string
|
||||
): ChartSeries[] {
|
||||
const excluded = excludedIds(accounts);
|
||||
const counted = (accountId: number | null) => accountId === null || !excluded.has(accountId);
|
||||
const accountKind = new Map(accounts.map((account) => [account.id, account.kind]));
|
||||
const investmentId = settings?.investmentAccountId ?? null;
|
||||
const validInvestmentId =
|
||||
@@ -121,6 +145,7 @@ export function wealthProjection(
|
||||
let spendable = 0;
|
||||
let invested = 0;
|
||||
for (const account of accounts) {
|
||||
if (excluded.has(account.id)) continue;
|
||||
const value = balances.get(account.id) ?? 0;
|
||||
if (account.kind === 'investment') invested += value;
|
||||
else spendable += value;
|
||||
@@ -129,7 +154,9 @@ export function wealthProjection(
|
||||
const totalPoints: ChartPoint[] = [{ date: today, value: spendable + invested }];
|
||||
const spendablePoints: ChartPoint[] = [{ date: today, value: spendable }];
|
||||
const investedPoints: ChartPoint[] = [{ date: today, value: invested }];
|
||||
const movements = [...futureTransactions, ...forecasts].filter((entry) => entry.date > today);
|
||||
const movements = [...futureTransactions, ...forecasts].filter(
|
||||
(entry) => entry.date > today && counted(entry.accountId)
|
||||
);
|
||||
const latestMovement = movements.reduce(
|
||||
(latest, entry) => (entry.date > latest ? entry.date : latest),
|
||||
''
|
||||
@@ -162,6 +189,7 @@ export function wealthProjection(
|
||||
}
|
||||
if (settings?.includeRecurring !== false && index > 0) {
|
||||
for (const item of recurring) {
|
||||
if (!counted(item.accountId)) continue;
|
||||
if ((item.startsOn && item.startsOn > date) || (item.endsOn && item.endsOn < first))
|
||||
continue;
|
||||
const value = Math.round(monthlyCents(item.amountCents, item.cadence));
|
||||
@@ -215,6 +243,7 @@ export function wealthTimeline(
|
||||
kind: string;
|
||||
openingBalanceCents: number;
|
||||
color?: string | null;
|
||||
includeInTotal?: boolean;
|
||||
}[],
|
||||
actualTransactions: { accountId: number; date: string; amountCents: number }[],
|
||||
projection: ChartSeries[],
|
||||
@@ -229,7 +258,7 @@ export function wealthTimeline(
|
||||
date,
|
||||
value: accounts.reduce(
|
||||
(sum, account, accountIndex) =>
|
||||
(account.kind === 'investment') === investment
|
||||
account.includeInTotal !== false && (account.kind === 'investment') === investment
|
||||
? sum + accountLines[accountIndex].points[index].value
|
||||
: sum,
|
||||
0
|
||||
|
||||
Reference in New Issue
Block a user