Lesbare Wasserfaelle, Verkaufspreis-Abgleich, Erklaerung wirkungsloser Treiber
Deploy App / deploy (push) Successful in 59s
Deploy App / deploy (push) Successful in 59s
Die Wasserfall-Zahlen waren korrekt (residual exakt 0), die Darstellung nicht lesbar. Neu als eigene liegende HTML/CSS-Darstellung statt Recharts: - Verbindungslinien zwischen den Balken (ohne sie zerfaellt der Wasserfall in unverbundene Rechtecke) - Wertbeschriftung an jedem Schritt - Zwischenstand und Veraenderung optisch unterschieden - Abschnitte "Am Uebergang" / "Innerhalb der Phase" - aufklappbare Tabelle mit laufendem Zwischenstand - Nullposten werden nicht gezeichnet - Restposten neu als Fehlermeldung statt beilaeufiger Rundungsnotiz Verkaufspreis einer Immobilie wird beim Wechsel auf "Verkaufen" mit dem modellierten Verkehrswert vorbelegt (nur wenn noch keiner erfasst ist); Verkehrswert und Abweichung werden ausgewiesen, ab 10 % rot abgesetzt. Die beiden Groessen bleiben bewusst entkoppelt -- ein Verkauf unter Verkehrswert ist ein realer Fall. Tornado erklaert Nullbalken statt sie stumm zu zeigen. Wichtigster Fall: Wird die Immobilie vor Planende verkauft, ist die Wertsteigerung nachweislich wirkungslos, weil der Erloes am erfassten Verkaufspreis haengt und nicht am Verkehrswert. 11 Tests ergaenzt (92 -> 103), darunter residual === 0 ueber sieben Plankonstellationen. SPEZIFIKATION auf 0.12, neue Kapitel 3.5.8, 4.13.5, 4.14.2.1, 9.22. Keine Aenderung an der Berechnung. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,9 @@ export interface CellContext {
|
||||
derivedStart: number; // fortgeschriebener Basiswert (read-only Anzeige)
|
||||
derivedMortgage: number; // nur Immobilie: fortgeschriebene Resthypothek zu Phasenbeginn
|
||||
mortgageEnd: number; // nur Immobilie: Resthypothek am Phasenende (fuer die Sonderamortisation)
|
||||
// nur Immobilie: modellierter VERKEHRSWERT am Phasenende (Eigenkapital + Resthypothek),
|
||||
// inkl. aufgelaufener Wertsteigerung. Vorbelegung und Vergleichswert fuer den Verkaufspreis.
|
||||
propertyValueEnd: number;
|
||||
deflatorStart: number; // Kaufkraft-Deflator zu Phasenbeginn (real <-> nominal, erstes Jahr)
|
||||
// Warnhinweis: Anzahl Phasen NACH dieser (Aenderungen schreiben sich dorthin fort).
|
||||
laterPhaseCount: number;
|
||||
@@ -769,12 +772,24 @@ export function ElementTransitionFields({
|
||||
return <WithdrawalDecision td={td} setT={setT} max={context.carriedEndValue} label="3a-Bezug (CHF)" />;
|
||||
case "REAL_ESTATE": {
|
||||
const decision = td.decision ?? "HOLD";
|
||||
const marktwert = Math.round(context.propertyValueEnd);
|
||||
const preis = num(td.salePrice);
|
||||
// Abweichung zwischen erfasstem Verkaufspreis und modelliertem Verkehrswert. Beide
|
||||
// Groessen sind unabhaengig erfassbar -- ohne diesen Vergleich koennte man 2 %
|
||||
// Wertsteigerung annehmen und trotzdem zum Kaufpreis verkaufen, ohne es zu merken.
|
||||
const abweichung = preis - marktwert;
|
||||
const abweichungPct = marktwert > 0 ? (abweichung / marktwert) * 100 : 0;
|
||||
const deutlich = marktwert > 0 && Math.abs(abweichungPct) >= 10;
|
||||
return (
|
||||
<>
|
||||
<SelectField
|
||||
label="Entscheidung"
|
||||
value={decision === "SELL" ? "SELL" : "HOLD"}
|
||||
onChange={(v: "HOLD" | "SELL") => setT({ decision: v })}
|
||||
onChange={(v: "HOLD" | "SELL") =>
|
||||
// Beim Wechsel auf "Verkaufen" den Verkaufspreis mit dem modellierten
|
||||
// Verkehrswert vorbelegen -- aber nur, wenn noch keiner erfasst ist.
|
||||
setT(v === "SELL" && td.salePrice === undefined ? { decision: v, salePrice: marktwert } : { decision: v })
|
||||
}
|
||||
options={[
|
||||
{ value: "HOLD", label: "Halten" },
|
||||
{ value: "SELL", label: "Verkaufen" },
|
||||
@@ -782,7 +797,42 @@ export function ElementTransitionFields({
|
||||
/>
|
||||
{decision === "SELL" && (
|
||||
<>
|
||||
<MoneyField label="Verkaufspreis (CHF)" value={num(td.salePrice)} onChange={(v) => setT({ salePrice: v })} />
|
||||
<MoneyField
|
||||
label="Verkaufspreis (CHF)"
|
||||
help="Vorbelegt mit dem modellierten Verkehrswert am Phasenende. Bewusst änderbar – der erfasste Preis ist massgebend, nicht der modellierte Wert."
|
||||
value={preis}
|
||||
onChange={(v) => setT({ salePrice: v })}
|
||||
/>
|
||||
<DerivedField
|
||||
label="Modellierter Verkehrswert (Phasenende)"
|
||||
value={marktwert}
|
||||
help="Kaufpreis zuzüglich der bis hierhin aufgelaufenen Wertsteigerung. Nur zum Vergleich – für den Erlös zählt der oben erfasste Verkaufspreis."
|
||||
/>
|
||||
{marktwert > 0 && (
|
||||
<p
|
||||
className={`col-span-full rounded-lg px-3 py-2 text-xs ${
|
||||
deutlich ? "border border-danger bg-danger-soft text-danger" : "bg-surface-2 text-muted"
|
||||
}`}
|
||||
>
|
||||
{Math.abs(abweichung) < 1 ? (
|
||||
<>Verkaufspreis und modellierter Verkehrswert stimmen überein.</>
|
||||
) : (
|
||||
<>
|
||||
Der Verkaufspreis liegt <strong>{formatChf(Math.abs(abweichung))} CHF</strong> (
|
||||
{abweichung > 0 ? "+" : "−"}
|
||||
{Math.abs(Math.round(abweichungPct * 10) / 10)} %) {abweichung > 0 ? "über" : "unter"} dem
|
||||
modellierten Verkehrswert.
|
||||
{deutlich && (
|
||||
<>
|
||||
{" "}
|
||||
Das ist eine deutliche Abweichung – prüfe, ob sie gewollt ist oder ob die angenommene
|
||||
Wertsteigerung nicht zum erwarteten Verkaufspreis passt.
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
<NumberField label="Grundstueckgewinnsteuer (%)" step={1} value={num(td.saleTaxRate, DEFAULT_PROPERTY_GAINS_TAX_RATE)} onChange={(v) => setT({ saleTaxRate: v })} />
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user