Netto/Brutto-Klarstellung fuer die AHV + erweitertes Immobilien-Modul
Deploy App / deploy (push) Successful in 59s
Deploy App / deploy (push) Successful in 59s
Roadmap Nr. 9 (reduziert): Einkommen ist neu explizit als NETTOLOHN definiert (Label + Hilfetext). Bisher stand nirgends, ob netto oder brutto gemeint ist -- fuer den Cash-Fluss egal (beide Konventionen heben sich auf), aber seit der einkommensabhaengigen AHV haengt eine Rente daran. Die AHV bemisst sich am Bruttolohn, deshalb rechnet das Tool intern mit AHV_GROSS_FROM_NET_FACTOR = 1.12 hoch. Ohne das war die Rente um bis zu ~1'900/Jahr zu tief (Details: SPEZ 9.13). Der Faktor ist hergeleitet und dokumentiert (AHV/IV/EO 5.3% + ALV 1.1% + NBU ~1% + PK ~2-5% auf den koordinierten Lohn) -- fix vertretbar, weil das mdJE selbst ein Karriere-Durchschnitt ist. Keine Aufschluesselung, kein sichtbares Feld (kommt mit Roadmap Nr. 41 als erklaerte Konstante). Roadmap Nr. 8: Immobilie neu mit Hypothekarzins (% der Restschuld, Zinsbetrag sinkt mit der Amortisation, read-only "Beginn -> Ende") und Wertsteigerung. WICHTIG: Die Wertsteigerung wirkt auf die LIEGENSCHAFT, nicht auf das Eigenkapital. 1% von 1 Mio sind 10'000/Jahr, also 10% eines Eigenkapitals von 100'000 -- das ist der Hebel. Auf dem EK gerechnet waeren es 1'000 (Beispiel: 304'622 statt 210'462). Kaufpreis und Verkehrswert laufen deshalb getrennt; die Grundstueckgewinnsteuer bemisst sich weiterhin am urspruenglichen Kaufpreis. Doppelzaehlung: Schalter interestHandling auf der Immobilie, Default INCLUDED -- bestehende Plaene haben die Zinsen in den Ausgaben und aendern sich nicht. Keine Steuerschaetzung (Begruendung: SPEZ 9.14). Sechs Regressionstests (30 -> 36). Spezifikation auf v0.5. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,7 @@ export interface CellContext {
|
||||
carriedEndValue: number; // Endwert des Elements in der (Vor-)Phase, fuer Bezugs-Maxima
|
||||
carried: boolean; // Phase >= 2: Basiswert wird aus der Vorphase fortgeschrieben
|
||||
derivedStart: number; // fortgeschriebener Basiswert (read-only Anzeige)
|
||||
derivedMortgage: number; // nur Immobilie: fortgeschriebene Resthypothek zu Phasenbeginn
|
||||
deflatorStart: number; // Kaufkraft-Deflator zu Phasenbeginn (real <-> nominal, erstes Jahr)
|
||||
// Warnhinweis: Anzahl Phasen NACH dieser (Aenderungen schreiben sich dorthin fort).
|
||||
laterPhaseCount: number;
|
||||
@@ -148,7 +149,8 @@ export function AhvReviewFields({
|
||||
}) {
|
||||
const brauchtVorgeschichte = career.yearsBeforePlan > 0;
|
||||
// Vorbelegung mit dem geplanten Durchschnitt: besser als 0, wenn der Nutzer nichts weiss.
|
||||
const avgBefore = typeof td.avgIncomeBefore === "number" ? td.avgIncomeBefore : Math.round(career.plannedAvgIncome);
|
||||
const avgBefore =
|
||||
typeof td.avgIncomeBefore === "number" ? td.avgIncomeBefore : Math.round(career.plannedAvgGrossIncome);
|
||||
const gapBefore = Math.max(0, Math.round(num(td.gapYearsBefore)));
|
||||
const mdJE = ahvMdje(career, brauchtVorgeschichte ? avgBefore : 0, gapBefore);
|
||||
const rente = ahvAnnualPension(mdJE, career.gapYearsInPlan + gapBefore);
|
||||
@@ -157,14 +159,16 @@ export function AhvReviewFields({
|
||||
<>
|
||||
<p className="col-span-full rounded-lg bg-surface-2 px-3 py-2 text-xs text-muted">
|
||||
Die AHV-Rente haengt vom <strong className="text-fg">massgebenden durchschnittlichen Jahreseinkommen</strong>{" "}
|
||||
ueber die ganze Beitragsdauer (ab Alter 21) ab. Alle Betraege sind REAL (heutige Kaufkraft) –
|
||||
die AHV wertet vergangene Einkommen auf und indexiert die Schwellen, was sich real weitgehend aufhebt.
|
||||
ueber die ganze Beitragsdauer (ab Alter 21) ab. Massgebend ist der <strong className="text-fg">Bruttolohn</strong>;
|
||||
das Tool rechnet die im Plan erfassten Nettoeinkommen dafuer automatisch hoch. Alle Betraege sind
|
||||
REAL (heutige Kaufkraft) – die AHV wertet vergangene Einkommen auf und indexiert die Schwellen,
|
||||
was sich real weitgehend aufhebt.
|
||||
</p>
|
||||
|
||||
<DerivedField
|
||||
label="Geplantes Durchschnittseinkommen (aus dem Plan)"
|
||||
value={Math.round(career.plannedAvgIncome)}
|
||||
help={`Reales Mittel ueber ${career.planYears} Beitragsjahre der Erwerbsphasen dieses Plans. Nur Einkommen, die dieser Person zugeordnet sind.`}
|
||||
label="Geplantes Bruttoeinkommen (aus dem Plan)"
|
||||
value={Math.round(career.plannedAvgGrossIncome)}
|
||||
help={`Reales Mittel ueber ${career.planYears} Beitragsjahre der Erwerbsphasen dieses Plans, hochgerechnet vom Netto- auf den Bruttolohn. Nur Einkommen, die dieser Person zugeordnet sind.`}
|
||||
/>
|
||||
<DerivedField
|
||||
label="Beitragsjahre im Plan"
|
||||
@@ -179,8 +183,8 @@ export function AhvReviewFields({
|
||||
davor kennt das Tool nicht – bitte ergaenzen.
|
||||
</p>
|
||||
<MoneyField
|
||||
label="Durchschnittseinkommen vor Planbeginn (real)"
|
||||
help="In HEUTIGER Kaufkraft. Am besten aus der AHV-Rentenvorausberechnung uebernehmen – dort ist die Aufwertung bereits enthalten. Die Rohbetraege im Kontoauszug sind historisch-nominal und daher zu tief."
|
||||
label="Durchschnittliches Bruttoeinkommen vor Planbeginn (real)"
|
||||
help="BRUTTO und in HEUTIGER Kaufkraft. Am besten aus der AHV-Rentenvorausberechnung uebernehmen – dort ist die Aufwertung bereits enthalten. Die Rohbetraege im Kontoauszug sind historisch-nominal und daher zu tief."
|
||||
value={avgBefore}
|
||||
onChange={(v) => setT({ avgIncomeBefore: v })}
|
||||
/>
|
||||
@@ -416,11 +420,11 @@ export function ElementPhaseFields({
|
||||
<>
|
||||
<p className="col-span-2 rounded-lg bg-surface-2 px-3 py-2 text-xs text-muted">
|
||||
{isIncome
|
||||
? "Einkommen wird NOMINAL erfasst (die Zahl auf dem Lohnausweis). Der reale Wert (heutige Kaufkraft) wird nur zur Info angezeigt."
|
||||
? "Bitte das NETTO-Einkommen erfassen: der Betrag, der nach allen Lohnabzuegen (AHV/ALV, Pensionskasse, NBU) tatsaechlich aufs Konto kommt – nicht der Bruttolohn. Erfasst wird NOMINAL; der reale Wert (heutige Kaufkraft) erscheint nur zur Info. Fuer die AHV-Rente rechnet das Tool intern auf den Bruttolohn zurueck."
|
||||
: "Ausgaben werden REAL erfasst (in heutiger Kaufkraft). Die Inflation (plan-weit) rechnet daraus automatisch die nominalen Ausgaben – nur zur Info."}
|
||||
</p>
|
||||
<MoneyField
|
||||
label={isIncome ? "Jahreseinkommen NOMINAL (erstes Jahr)" : "Jahresausgaben REAL (erstes Jahr, heutige Kaufkraft)"}
|
||||
label={isIncome ? "Jahreseinkommen NETTO, nominal (erstes Jahr)" : "Jahresausgaben REAL (erstes Jahr, heutige Kaufkraft)"}
|
||||
help={carried ? "Vorbelegt mit dem fortgeschriebenen Wert der Vorphase; hier bewusst aenderbar." : undefined}
|
||||
value={baseValue}
|
||||
onChange={(v) => setP({ amount: v })}
|
||||
@@ -457,8 +461,8 @@ export function ElementPhaseFields({
|
||||
bitte hier erfassen (REAL, heutige Kaufkraft).
|
||||
</p>
|
||||
<MoneyField
|
||||
label="Durchschnittseinkommen der Beitragsjahre (real)"
|
||||
help="Am besten aus der AHV-Rentenvorausberechnung bzw. der Rentenverfuegung. Ab 90'720 gibt es die Maximalrente."
|
||||
label="Durchschnittliches Bruttoeinkommen der Beitragsjahre (real)"
|
||||
help="BRUTTO und in heutiger Kaufkraft. Am besten aus der AHV-Rentenvorausberechnung bzw. der Rentenverfuegung. Ab 90'720 gibt es die Maximalrente."
|
||||
value={num(pd.avgIncomeBefore)}
|
||||
onChange={(v) => setP({ avgIncomeBefore: v })}
|
||||
/>
|
||||
@@ -559,34 +563,75 @@ export function ElementPhaseFields({
|
||||
<NumberField label="Erwartete Rendite (%/Jahr)" step={0.1} value={num(pd.expectedReturn)} onChange={(v) => setP({ expectedReturn: v })} />
|
||||
</>
|
||||
);
|
||||
case "REAL_ESTATE":
|
||||
if (carried) {
|
||||
return (
|
||||
<>
|
||||
<DerivedField label="Startwert Netto (fortgeschrieben)" value={context.derivedStart} help="Kaufpreis minus fortgeschriebene Resthypothek." />
|
||||
<MoneyField
|
||||
label="Amortisation (CHF/Jahr)"
|
||||
help="Jaehrliche Reduktion der Hypothek. Zaehlt gegen die Sparquote."
|
||||
value={num(pd.amortization)}
|
||||
|
||||
onChange={(v) => setP({ amortization: v })}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
case "REAL_ESTATE": {
|
||||
// Zinsbetrag zu Phasenbeginn und -ende: die Restschuld sinkt mit der Amortisation,
|
||||
// der Zinsbetrag also mit. Am Nullpunkt gekappt (analog zur Berechnung).
|
||||
const hypStart = carried ? context.derivedMortgage : num(pd.mortgage);
|
||||
const hypEnde = Math.max(0, hypStart - num(pd.amortization) * context.durationYears);
|
||||
const zinsStart = Math.round((hypStart * num(pd.interestRate)) / 100);
|
||||
const zinsEnde = Math.round((hypEnde * num(pd.interestRate)) / 100);
|
||||
const handling = pd.interestHandling ?? "INCLUDED";
|
||||
return (
|
||||
<>
|
||||
<MoneyField label="Kaufpreis (CHF)" value={num(pd.purchasePrice)} onChange={(v) => setP({ purchasePrice: v })} />
|
||||
<MoneyField label="Hypothek (CHF)" value={num(pd.mortgage)} onChange={(v) => setP({ mortgage: v })} />
|
||||
{carried ? (
|
||||
<>
|
||||
<DerivedField
|
||||
label="Startwert Netto (fortgeschrieben)"
|
||||
value={context.derivedStart}
|
||||
help="Verkehrswert (inkl. bisheriger Wertsteigerung) minus fortgeschriebene Resthypothek."
|
||||
/>
|
||||
<DerivedField label="Resthypothek (fortgeschrieben)" value={hypStart} help="Aus der Vorphase fortgeschrieben." />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<MoneyField label="Kaufpreis (CHF)" value={num(pd.purchasePrice)} onChange={(v) => setP({ purchasePrice: v })} />
|
||||
<MoneyField label="Hypothek (CHF)" value={num(pd.mortgage)} onChange={(v) => setP({ mortgage: v })} />
|
||||
</>
|
||||
)}
|
||||
<MoneyField
|
||||
label="Amortisation (CHF/Jahr)"
|
||||
help="Jaehrliche Reduktion der Hypothek. Zaehlt gegen die Sparquote."
|
||||
help="Jaehrliche Reduktion der Hypothek. Zaehlt gegen die Sparquote und endet, sobald die Hypothek abbezahlt ist."
|
||||
value={num(pd.amortization)}
|
||||
|
||||
onChange={(v) => setP({ amortization: v })}
|
||||
/>
|
||||
<NumberField
|
||||
label="Hypothekarzins (%/Jahr)"
|
||||
help="Zinssatz auf der Restschuld. Der Zinsbetrag sinkt automatisch mit der Amortisation."
|
||||
step={0.1}
|
||||
value={num(pd.interestRate)}
|
||||
onChange={(v) => setP({ interestRate: v })}
|
||||
/>
|
||||
<div>
|
||||
<FieldLabel
|
||||
label="Hypothekarzins-Betrag (Beginn → Ende)"
|
||||
help="Restschuld x Zinssatz, im ersten und im letzten Jahr dieser Phase. Wird automatisch berechnet."
|
||||
/>
|
||||
<div className="w-full rounded-lg border border-dashed border-border bg-surface-2 px-2.5 py-1.5 text-sm text-muted">
|
||||
{formatChf(zinsStart)} <span className="text-faint">→</span> {formatChf(zinsEnde)}
|
||||
</div>
|
||||
</div>
|
||||
<NumberField
|
||||
label="Geschaetzte Wertsteigerung (%/Jahr)"
|
||||
help="Wirkt auf den Wert der LIEGENSCHAFT, nicht auf das Eigenkapital. 1% von 1 Mio sind 10'000 pro Jahr – bei 100'000 Eigenkapital also 10% darauf (Hebel)."
|
||||
step={0.1}
|
||||
value={num(pd.valueGrowth)}
|
||||
onChange={(v) => setP({ valueGrowth: v })}
|
||||
/>
|
||||
<div className="sm:col-span-2">
|
||||
<SelectField
|
||||
label="Sind die Hypothekarzinsen bereits in den Ausgaben enthalten?"
|
||||
help="Damit die Zinsen nicht doppelt zaehlen. Standard: bereits enthalten – so verhalten sich bestehende Plaene unveraendert."
|
||||
value={handling}
|
||||
onChange={(v: "INCLUDED" | "ADD") => setP({ interestHandling: v })}
|
||||
options={[
|
||||
{ value: "INCLUDED", label: "Ja – bereits im Ausgaben-Element beruecksichtigt" },
|
||||
{ value: "ADD", label: "Nein – bitte zu den Ausgaben dazuzaehlen" },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
case "OTHER_ASSET":
|
||||
return (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user