Include investment holding values in net worth and account balances
- Net worth report, balance sheet, and daily snapshots now add holding
market values (falling back to cost basis) to investment-type account
balances (investment, pension, stocks_shares_isa, crypto_wallet)
- Accounts list shows total value for investment accounts with a
breakdown line ("£X cash + £Y holdings") when both are non-zero
- Add Holding modal gains a "Debit account for this purchase" toggle
that creates a matching withdrawal transaction, enabling proper cash
flow tracking for users who fund their brokerage via transfer first
- Both simple (holdings-only) and full cash-flow workflows produce
correct net worth figures without double-counting
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
cdc1e67321
commit
312594f3d2
5 changed files with 139 additions and 11 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { X, Search, Loader2 } from "lucide-react";
|
||||
import { searchAssets, createHolding, addInvestmentTransaction, AssetSearchResult } from "@/api/investments";
|
||||
import { createTransaction } from "@/api/transactions";
|
||||
import { useUiStore } from "@/store/uiStore";
|
||||
import { format } from "date-fns";
|
||||
|
||||
|
|
@ -28,6 +29,7 @@ export default function AddHoldingModal({ accounts, onClose, onSuccess }: Props)
|
|||
);
|
||||
|
||||
const [priceMode, setPriceMode] = useState<"per_share" | "total">("per_share");
|
||||
const [recordCash, setRecordCash] = useState(false);
|
||||
const [form, setForm] = useState({
|
||||
account_id: investAccounts[0]?.id ?? "",
|
||||
quantity: "",
|
||||
|
|
@ -76,6 +78,18 @@ export default function AddHoldingModal({ accounts, onClose, onSuccess }: Props)
|
|||
currency: form.currency,
|
||||
date: form.date,
|
||||
});
|
||||
if (recordCash) {
|
||||
const totalSpent = qty * price + (parseFloat(form.fees) || 0);
|
||||
await createTransaction({
|
||||
account_id: form.account_id,
|
||||
type: "investment",
|
||||
amount: -totalSpent,
|
||||
currency: form.currency,
|
||||
date: form.date,
|
||||
description: `Buy ${selected.symbol} × ${qty}`,
|
||||
status: "cleared",
|
||||
});
|
||||
}
|
||||
onSuccess();
|
||||
} catch (e: any) {
|
||||
const detail = e?.response?.data?.detail;
|
||||
|
|
@ -237,6 +251,25 @@ export default function AddHoldingModal({ accounts, onClose, onSuccess }: Props)
|
|||
/>
|
||||
</div>
|
||||
|
||||
{/* Cash purchase toggle */}
|
||||
<label className="flex items-start gap-3 cursor-pointer select-none p-3 rounded-lg border border-border hover:bg-secondary/40 transition-colors">
|
||||
<div className="relative mt-0.5 shrink-0">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={recordCash}
|
||||
onChange={(e) => setRecordCash(e.target.checked)}
|
||||
className="sr-only"
|
||||
/>
|
||||
<div className={`w-9 h-5 rounded-full transition-colors ${recordCash ? "bg-primary" : "bg-secondary border border-input"}`}>
|
||||
<div className={`w-3.5 h-3.5 rounded-full bg-white shadow transition-transform mt-0.5 ${recordCash ? "translate-x-4.5 ml-0.5" : "translate-x-0.5 ml-0.5"}`} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium">Debit account for this purchase</p>
|
||||
<p className="text-xs text-muted-foreground mt-0.5">Records a matching withdrawal from the account. Enable this if you transferred cash to this account first and want to track the balance accurately.</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
{error && <p className="text-destructive text-sm bg-destructive/10 rounded-md px-3 py-2">{error}</p>}
|
||||
|
||||
<div className="flex gap-3 pt-1">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue