fix: "davon ... fest gebunden" war irrefuehrend – und nicht nachpruefbar
Der Hinweis stand unter der Zeile und begann mit "davon". Die naechste Zahl
darueber ist der Ist-Betrag, also las es sich als "von den 17.66 sind 113.72
gebunden" – und wo die Vertraege mehr kosten als geplant, ergab es vollends
keinen Sinn. Die Rechnung stimmte; der Satz war falsch.
Jetzt steht schlicht da, was es ist: "4 Vertraege kosten CHF 113.72 im Monat".
Kein "davon", kein Bezug auf eine Zahl, zu der es keinen gibt. Kostet mehr als
geplant, sagt die Zeile das ausdruecklich, statt es der Rechnung zu ueberlassen.
Vor allem aber laesst sich die Summe jetzt AUFKLAPPEN: darunter steht jeder
einzelne Vertrag mit seinem Monatswert, und bei laengerem Intervall auch der
urspruengliche Betrag ("CHF 190.00 jaehrlich · 16.84 EUR"). Eine Summe, die man
nicht nachrechnen kann, muss man glauben – und wenn sie einem krumm vorkommt,
hilft nur Nachsehen.
Die Liste kommt aus derselben Quelle wie die Summe (monatlich_base_cents vom
Server). Stimmten die beiden nicht ueberein, waere das ein echter Fehler – und
man saehe ihn sofort.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ab5d18e0d0
commit
01ae12ffea
+64
-18
@@ -44,6 +44,10 @@ export default function Budget() {
|
||||
const [accounts, setAccounts] = useState([]);
|
||||
const [template, setTemplate] = useState({ lines: [], income: [] });
|
||||
const [incomeModal, setIncomeModal] = useState(null);
|
||||
// Die Vertraege – fuer die Aufschluesselung unter einer Kategoriezeile.
|
||||
const [abos, setAbos] = useState([]);
|
||||
// Welche Kategorie gerade aufgeklappt ist.
|
||||
const [offen, setOffen] = useState(null);
|
||||
|
||||
const base = views?.[0]?.base_currency || "EUR";
|
||||
const fmt = (c) => formatMoney(c, base);
|
||||
@@ -62,8 +66,10 @@ export default function Budget() {
|
||||
}
|
||||
async function loadStamm() {
|
||||
try {
|
||||
const [g, a] = await Promise.all([api.listCategories(), api.listAccounts()]);
|
||||
setGroups(g); setAccounts(a);
|
||||
const [g, a, v] = await Promise.all([
|
||||
api.listCategories(), api.listAccounts(), api.listSubscriptions(),
|
||||
]);
|
||||
setGroups(g); setAccounts(a); setAbos(v.subscriptions);
|
||||
} catch (err) { toast(err.message, "error"); }
|
||||
}
|
||||
|
||||
@@ -118,6 +124,13 @@ export default function Budget() {
|
||||
} catch (err) { toast(err.message, "error"); }
|
||||
}
|
||||
|
||||
function vertraegeVon(catId) {
|
||||
return abos.filter((s) => s.is_active && s.category_id === catId);
|
||||
}
|
||||
function zeigeVertraege(catId) {
|
||||
setOffen(catId);
|
||||
}
|
||||
|
||||
async function saveTemplate(catId, cents) {
|
||||
try { await api.setTemplateCategory(catId, cents); loadTemplate(); } catch (err) { toast(err.message, "error"); }
|
||||
}
|
||||
@@ -224,23 +237,56 @@ export default function Budget() {
|
||||
? saveZuschlag(v.label, c, cents)
|
||||
: saveMonth(v.label, c.category_id, cents))} />
|
||||
</div>
|
||||
{/* Die Rechnung offenlegen. Ohne sie stuende im Feld eine Zahl,
|
||||
die nicht der Soll-Betrag ist – und niemand wuesste, warum. */}
|
||||
{berechnet ? (
|
||||
<div className="budget-rechnung muted small">
|
||||
{t("budget.fromContracts", {
|
||||
betrag: fmt(c.aus_vertraegen_cents), count: c.vertraege_anzahl,
|
||||
})}
|
||||
{" + "}{fmt(c.zuschlag_cents)}{" = "}
|
||||
<strong>{fmt(c.planned_cents)}</strong>
|
||||
{/* Die Rechnung offenlegen – und zwar aufklappbar bis auf den
|
||||
einzelnen Vertrag. Eine Summe, die man nicht nachrechnen
|
||||
kann, muss man glauben; und wenn sie einem krumm vorkommt,
|
||||
hilft nur Nachsehen. */}
|
||||
{(berechnet || c.vertraege_anzahl > 0) && (
|
||||
<div className="budget-rechnung small">
|
||||
<button type="button" className="budget-rechnung-kopf"
|
||||
onClick={() => zeigeVertraege(
|
||||
offen === c.category_id ? null : c.category_id)}>
|
||||
<Icon name={offen === c.category_id ? "chevron-down" : "chevron-right"}
|
||||
size={13} />
|
||||
<span className="muted">
|
||||
{t("budget.contractsCost", {
|
||||
betrag: fmt(c.aus_vertraegen_cents), count: c.vertraege_anzahl,
|
||||
})}
|
||||
{berechnet && (
|
||||
<>
|
||||
{" + "}{fmt(c.zuschlag_cents)}{" = "}
|
||||
<strong>{fmt(c.planned_cents)}</strong>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
{/* Mehr gebunden als geplant ist keine Rechenpanne,
|
||||
sondern eine Aussage – aber nur, wenn sie dasteht. */}
|
||||
{!berechnet && c.aus_vertraegen_cents > c.planned_cents && (
|
||||
<span className="muted warn">{t("budget.moreThanPlanned")}</span>
|
||||
)}
|
||||
</button>
|
||||
{offen === c.category_id && (
|
||||
<ul className="budget-vertragsliste muted">
|
||||
{vertraegeVon(c.category_id).map((s) => (
|
||||
<li key={s.id}>
|
||||
<span className="ellipsis">{s.name}</span>
|
||||
<span className="budget-vertrag-betrag">
|
||||
{s.interval_months > 1 && (
|
||||
<span className="muted">
|
||||
{formatMoney(s.amount_cents, s.currency)}
|
||||
{" "}{t("abos.every." + s.interval_months)}{" · "}
|
||||
</span>
|
||||
)}
|
||||
{s.monatlich_base_cents == null
|
||||
? t("budget.noRateShort")
|
||||
: fmt(s.monatlich_base_cents)}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
) : c.vertraege_anzahl > 0 ? (
|
||||
<div className="budget-rechnung muted small">
|
||||
{t("budget.boundByContracts", {
|
||||
betrag: fmt(c.aus_vertraegen_cents), count: c.vertraege_anzahl,
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user