feat: Geld, an das man nicht herankommt, zaehlt nicht mehr als verfuegbar
Eine Saeule 3a ist gesperrt – bis zur Rente, zur Selbstaendigkeit oder zur Auswanderung. Bisher zaehlte ihr Stand in dieselbe Summe wie das Girokonto, und die Antwort auf "wie viel habe ich" war um genau diesen Betrag zu hoch. Das Geld steht da und ist doch nicht da. Konten lassen sich jetzt als gebunden kennzeichnen. "Gesamt" zeigt dann nur noch, was wirklich ausgegeben werden kann; darunter steht "zusaetzlich CHF 2'004.61 gebunden" – aufklappbar, mit jedem Konto einzeln. Eine Summe, die man nicht nachrechnen kann, muss man glauben. Verschwiegen wird nichts: Der Saldo bleibt am Konto stehen, und Vermoegensverlauf wie Nettovermoegen rechnen weiter damit. Es nicht ausgeben zu koennen heisst nicht, es nicht zu besitzen – das ist der ganze Unterschied, und ein Test haelt ihn fest. Ein eigenes Kennzeichen, keine Ableitung aus dem Kontotyp: Ein frei verkaeufliches Depot und eine gesperrte Saeule 3a kommen beide als "depot" herein. Am Typ laesst sich das nicht ablesen – am ausdruecklichen Eintrag schon. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
35c5d2c84a
commit
76dbd3b481
@@ -34,6 +34,7 @@ function emptyForm() {
|
||||
opening_balance: "",
|
||||
opening_date: "",
|
||||
credit_limit: "",
|
||||
is_locked: false,
|
||||
savings_category_id: "",
|
||||
identifiers: [],
|
||||
};
|
||||
@@ -260,6 +261,7 @@ export default function Accounts() {
|
||||
opening_date: acc.opening_date || "",
|
||||
credit_limit: acc.credit_limit_cents != null
|
||||
? centsToInput(acc.credit_limit_cents) : "",
|
||||
is_locked: !!acc.is_locked,
|
||||
savings_category_id: acc.savings_category_id ? String(acc.savings_category_id) : "",
|
||||
identifiers: acc.identifiers.map((i) => ({ kind: i.kind, value: i.value })),
|
||||
});
|
||||
@@ -289,6 +291,8 @@ export default function Accounts() {
|
||||
// Girokonto macht, soll den Rahmen nicht unsichtbar mitschleppen.
|
||||
credit_limit_cents: form.type === "kreditkarte"
|
||||
? (parseAmountToCents(form.credit_limit) ?? null) : null,
|
||||
// Eine Kreditkarte hat kein Guthaben, das gebunden sein koennte.
|
||||
is_locked: form.type === "kreditkarte" ? false : !!form.is_locked,
|
||||
savings_category_id: form.savings_category_id ? Number(form.savings_category_id) : null,
|
||||
identifiers: form.identifiers
|
||||
.filter((i) => i.value.trim())
|
||||
@@ -336,9 +340,17 @@ export default function Accounts() {
|
||||
// Archivierte bleiben draussen – das Backend rechnet ebenso
|
||||
// (balances.net_worth_cents, dashboard.py). Zwei Zahlen fuer dieselbe Sache
|
||||
// sind eine Zahl zu viel.
|
||||
const total = accounts
|
||||
.filter((a) => !a.is_archived)
|
||||
//
|
||||
// Gesperrtes Geld ebenfalls: „Gesamt" beantwortet die Frage „wie viel habe
|
||||
// ich zur Verfuegung", und eine Saeule 3a gehoert darauf nicht. Es wird
|
||||
// daneben ausgewiesen, nicht verschwiegen – es gehoert dem Nutzer, nur eben
|
||||
// noch nicht.
|
||||
const aktiv = accounts.filter((a) => !a.is_archived);
|
||||
const total = aktiv
|
||||
.filter((a) => !a.is_locked)
|
||||
.reduce((s, a) => s + a.balance_base_cents, 0);
|
||||
const gebundene = aktiv.filter((a) => a.is_locked);
|
||||
const gebunden = gebundene.reduce((s, a) => s + a.balance_base_cents, 0);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -357,6 +369,25 @@ export default function Accounts() {
|
||||
<span className="muted">{t("accounts.total")}</span>
|
||||
<strong><Money cents={total} currency={baseCurrency} /></strong>
|
||||
</div>
|
||||
{/* Aufklappbar, weil eine Summe, die man nicht nachrechnen kann, man
|
||||
glauben muss. Darunter steht jedes gebundene Konto einzeln. */}
|
||||
{gebundene.length > 0 && (
|
||||
<details className="gebunden-zeile">
|
||||
<summary className="muted small">
|
||||
{t("accounts.lockedTotal", {
|
||||
value: formatMoney(gebunden, baseCurrency),
|
||||
count: gebundene.length,
|
||||
})}
|
||||
</summary>
|
||||
{gebundene.map((a) => (
|
||||
<div className="gebunden-posten" key={a.id}>
|
||||
<span className="grow ellipsis">{a.name}</span>
|
||||
<Money cents={a.balance_cents} currency={a.currency}
|
||||
baseCents={a.balance_base_cents} baseCurrency={baseCurrency} />
|
||||
</div>
|
||||
))}
|
||||
</details>
|
||||
)}
|
||||
<div className="account-list">
|
||||
{accounts.map((a, i) => (
|
||||
<div className={`account-row${aufschluss[a.id] ? " mit-aufschluss" : ""}`}
|
||||
@@ -370,6 +401,12 @@ export default function Accounts() {
|
||||
Certain eine Auszugszeile diesem Konto zuordnet. */}
|
||||
{a.identifiers.length > 0
|
||||
&& ` · ${t("accounts.identShort", { count: a.identifiers.length })}`}
|
||||
{a.is_locked && (
|
||||
<span className="badge" style={{ marginLeft: 6 }}
|
||||
title={t("accounts.lockedHint")}>
|
||||
{t("accounts.lockedBadge")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Kreditrahmen account={a} t={t} />
|
||||
{/* Bei angebundenen Konten der Saldo, den die Bank fuehrt.
|
||||
@@ -515,6 +552,20 @@ export default function Accounts() {
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Nicht aus dem Kontotyp abgeleitet: Ein frei verkaeufliches Depot
|
||||
und eine gesperrte Saeule 3a kommen beide als „depot" herein.
|
||||
Bei einer Kreditkarte gibt es nichts zu binden. */}
|
||||
{form.type !== "kreditkarte" && (
|
||||
<>
|
||||
<label className="checkline">
|
||||
<input type="checkbox" checked={form.is_locked}
|
||||
onChange={(e) => setForm({ ...form, is_locked: e.target.checked })} />
|
||||
<span>{t("accounts.locked")}</span>
|
||||
</label>
|
||||
<p className="muted small">{t("accounts.lockedHint")}</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
<label>
|
||||
<span>{t("accounts.savingsCategory")}</span>
|
||||
<CategorySelect
|
||||
|
||||
Reference in New Issue
Block a user