Fix: Uebergangs-Defaults (Steuer/Umwandlung) wurden in der Berechnung als 0 gerechnet
Deploy App / deploy (push) Successful in 54s
Deploy App / deploy (push) Successful in 54s
Kapitalbezugssteuer, Grundstueckgewinnsteuer und PK-Umwandlungssatz wurden im UI nur optisch vorbelegt (num(td.x, 8/20/6)), die Berechnung las aber num(td.x) mit Fallback 0. Ein nicht angetupftes Feld fuehrte so zu 0% Steuer bzw. 0 Rente. Defaults sind jetzt zentrale Konstanten (constants.ts) und werden in Berechnung UND UI als Fallback genutzt. Betrifft: PK-Kapital/-Kombi/-Rente, 3a-Bezug, Immobilienverkauf. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,12 @@ import { FieldLabel, MoneyField, NumberField, SelectField } from "@/components/F
|
|||||||
import { formatChf } from "@/lib/format";
|
import { formatChf } from "@/lib/format";
|
||||||
import { api } from "@/lib/api-client";
|
import { api } from "@/lib/api-client";
|
||||||
import { CATEGORY_LABELS, num } from "@/lib/elements";
|
import { CATEGORY_LABELS, num } from "@/lib/elements";
|
||||||
import { PILLAR_3A_MAX_ANNUAL } from "@/lib/constants";
|
import {
|
||||||
|
DEFAULT_CAPITAL_TAX_RATE,
|
||||||
|
DEFAULT_PK_CONVERSION_RATE,
|
||||||
|
DEFAULT_PROPERTY_GAINS_TAX_RATE,
|
||||||
|
PILLAR_3A_MAX_ANNUAL,
|
||||||
|
} from "@/lib/constants";
|
||||||
import type { ElementCategory, PhaseData, TransitionData } from "@/lib/elements";
|
import type { ElementCategory, PhaseData, TransitionData } from "@/lib/elements";
|
||||||
|
|
||||||
export interface CellContext {
|
export interface CellContext {
|
||||||
@@ -336,12 +341,12 @@ export function ElementTransitionFields({
|
|||||||
label="Umwandlungssatz (%)"
|
label="Umwandlungssatz (%)"
|
||||||
help="Jaehrliche Rente = verrentetes Kapital x Umwandlungssatz."
|
help="Jaehrliche Rente = verrentetes Kapital x Umwandlungssatz."
|
||||||
step={0.1}
|
step={0.1}
|
||||||
value={num(td.conversionRate, 6)}
|
value={num(td.conversionRate, DEFAULT_PK_CONVERSION_RATE)}
|
||||||
onChange={(v) => setT({ conversionRate: v })}
|
onChange={(v) => setT({ conversionRate: v })}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(mode === "CAPITAL" || mode === "COMBI") && (
|
{(mode === "CAPITAL" || mode === "COMBI") && (
|
||||||
<NumberField label="Kapitalbezugssteuer (%)" step={0.5} value={num(td.capitalTaxRate, 8)} onChange={(v) => setT({ capitalTaxRate: v })} />
|
<NumberField label="Kapitalbezugssteuer (%)" step={0.5} value={num(td.capitalTaxRate, DEFAULT_CAPITAL_TAX_RATE)} onChange={(v) => setT({ capitalTaxRate: v })} />
|
||||||
)}
|
)}
|
||||||
{mode === "COMBI" && (
|
{mode === "COMBI" && (
|
||||||
<MoneyField
|
<MoneyField
|
||||||
@@ -363,7 +368,7 @@ export function ElementTransitionFields({
|
|||||||
label="Kapitalbezugssteuer (%)"
|
label="Kapitalbezugssteuer (%)"
|
||||||
help="Die Saeule 3a wird bei Pensionierung vollstaendig bezogen."
|
help="Die Saeule 3a wird bei Pensionierung vollstaendig bezogen."
|
||||||
step={0.5}
|
step={0.5}
|
||||||
value={num(td.capitalTaxRate, 8)}
|
value={num(td.capitalTaxRate, DEFAULT_CAPITAL_TAX_RATE)}
|
||||||
onChange={(v) => setT({ capitalTaxRate: v })}
|
onChange={(v) => setT({ capitalTaxRate: v })}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -385,7 +390,7 @@ export function ElementTransitionFields({
|
|||||||
{decision === "SELL" && (
|
{decision === "SELL" && (
|
||||||
<>
|
<>
|
||||||
<MoneyField label="Verkaufspreis (CHF)" value={num(td.salePrice)} onChange={(v) => setT({ salePrice: v })} />
|
<MoneyField label="Verkaufspreis (CHF)" value={num(td.salePrice)} onChange={(v) => setT({ salePrice: v })} />
|
||||||
<NumberField label="Grundstueckgewinnsteuer (%)" step={1} value={num(td.saleTaxRate, 20)} onChange={(v) => setT({ saleTaxRate: v })} />
|
<NumberField label="Grundstueckgewinnsteuer (%)" step={1} value={num(td.saleTaxRate, DEFAULT_PROPERTY_GAINS_TAX_RATE)} onChange={(v) => setT({ saleTaxRate: v })} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ import {
|
|||||||
AHV_COUPLE_CAP_FACTOR,
|
AHV_COUPLE_CAP_FACTOR,
|
||||||
AHV_FULL_CONTRIBUTION_YEARS,
|
AHV_FULL_CONTRIBUTION_YEARS,
|
||||||
AHV_MAX_ANNUAL_SINGLE,
|
AHV_MAX_ANNUAL_SINGLE,
|
||||||
|
DEFAULT_CAPITAL_TAX_RATE,
|
||||||
|
DEFAULT_PK_CONVERSION_RATE,
|
||||||
|
DEFAULT_PROPERTY_GAINS_TAX_RATE,
|
||||||
} from "@/lib/constants";
|
} from "@/lib/constants";
|
||||||
import { num } from "@/lib/elements";
|
import { num } from "@/lib/elements";
|
||||||
import type { ElementCategory } from "@/lib/elements";
|
import type { ElementCategory } from "@/lib/elements";
|
||||||
@@ -459,18 +462,18 @@ export function computePlan(plan: PlanInput): PlanComputed {
|
|||||||
const value = ec.endValue;
|
const value = ec.endValue;
|
||||||
const mode = td.payoutMode ?? "PENSION";
|
const mode = td.payoutMode ?? "PENSION";
|
||||||
if (mode === "CAPITAL") {
|
if (mode === "CAPITAL") {
|
||||||
const net = Math.round(value * (1 - num(td.capitalTaxRate) / 100));
|
const net = Math.round(value * (1 - num(td.capitalTaxRate, DEFAULT_CAPITAL_TAX_RATE) / 100));
|
||||||
outgoing += net;
|
outgoing += net;
|
||||||
carry.value = 0;
|
carry.value = 0;
|
||||||
carry.pkPensionAnnual = 0;
|
carry.pkPensionAnnual = 0;
|
||||||
} else if (mode === "PENSION") {
|
} else if (mode === "PENSION") {
|
||||||
carry.pkPensionAnnual = Math.round((value * num(td.conversionRate)) / 100);
|
carry.pkPensionAnnual = Math.round((value * num(td.conversionRate, DEFAULT_PK_CONVERSION_RATE)) / 100);
|
||||||
carry.value = 0;
|
carry.value = 0;
|
||||||
} else {
|
} else {
|
||||||
const capital = Math.min(value, Math.round(num(td.capitalAmount)));
|
const capital = Math.min(value, Math.round(num(td.capitalAmount)));
|
||||||
const net = Math.round(capital * (1 - num(td.capitalTaxRate) / 100));
|
const net = Math.round(capital * (1 - num(td.capitalTaxRate, DEFAULT_CAPITAL_TAX_RATE) / 100));
|
||||||
outgoing += net;
|
outgoing += net;
|
||||||
carry.pkPensionAnnual = Math.round(((value - capital) * num(td.conversionRate)) / 100);
|
carry.pkPensionAnnual = Math.round(((value - capital) * num(td.conversionRate, DEFAULT_PK_CONVERSION_RATE)) / 100);
|
||||||
carry.value = 0;
|
carry.value = 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -482,7 +485,7 @@ export function computePlan(plan: PlanInput): PlanComputed {
|
|||||||
}
|
}
|
||||||
case "PILLAR_3A": {
|
case "PILLAR_3A": {
|
||||||
if (ownerRetiresNext) {
|
if (ownerRetiresNext) {
|
||||||
const net = Math.round(ec.endValue * (1 - num(td.capitalTaxRate) / 100));
|
const net = Math.round(ec.endValue * (1 - num(td.capitalTaxRate, DEFAULT_CAPITAL_TAX_RATE) / 100));
|
||||||
outgoing += net;
|
outgoing += net;
|
||||||
carry.value = 0;
|
carry.value = 0;
|
||||||
} else {
|
} else {
|
||||||
@@ -506,7 +509,7 @@ export function computePlan(plan: PlanInput): PlanComputed {
|
|||||||
const purchase = Math.round(num(e.phaseValues[phase.id]?.purchasePrice));
|
const purchase = Math.round(num(e.phaseValues[phase.id]?.purchasePrice));
|
||||||
const salePrice = Math.round(num(td.salePrice));
|
const salePrice = Math.round(num(td.salePrice));
|
||||||
const gain = Math.max(0, salePrice - purchase);
|
const gain = Math.max(0, salePrice - purchase);
|
||||||
const tax = gain * (num(td.saleTaxRate) / 100);
|
const tax = gain * (num(td.saleTaxRate, DEFAULT_PROPERTY_GAINS_TAX_RATE) / 100);
|
||||||
outgoing += Math.round(salePrice - carry.mortgage - tax);
|
outgoing += Math.round(salePrice - carry.mortgage - tax);
|
||||||
carry.status = "SOLD";
|
carry.status = "SOLD";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ export const AHV_COUPLE_CAP_FACTOR = 1.5;
|
|||||||
// Beitragsjahr (Ausfalljahr) wird die Rente um 1/44 gekuerzt.
|
// Beitragsjahr (Ausfalljahr) wird die Rente um 1/44 gekuerzt.
|
||||||
export const AHV_FULL_CONTRIBUTION_YEARS = 44;
|
export const AHV_FULL_CONTRIBUTION_YEARS = 44;
|
||||||
|
|
||||||
// Maximaler jaehrlicher Saeule-3a-Beitrag fuer PK-Versicherte (2026). Wird in
|
// Maximaler jaehrlicher Saeule-3a-Beitrag fuer PK-Versicherte (2026).
|
||||||
// 100er-Schritten erfasst (nicht 1'000er wie andere Betraege).
|
|
||||||
export const PILLAR_3A_MAX_ANNUAL = 7258;
|
export const PILLAR_3A_MAX_ANNUAL = 7258;
|
||||||
|
|
||||||
|
// Default-Annahmen fuer Uebergangs-Ereignisse (pro Ereignis editierbar). Werden SOWOHL als
|
||||||
|
// UI-Vorschlag als auch in der Berechnung als Fallback verwendet -- damit ein nicht
|
||||||
|
// angetupfter Wert nicht faelschlich als 0 gerechnet wird.
|
||||||
|
export const DEFAULT_PK_CONVERSION_RATE = 6; // % (jaehrliche Rente = Kapital x Satz)
|
||||||
|
export const DEFAULT_CAPITAL_TAX_RATE = 8; // % Kapitalbezugssteuer (PK-/3a-Kapitalbezug)
|
||||||
|
export const DEFAULT_PROPERTY_GAINS_TAX_RATE = 20; // % Grundstueckgewinnsteuer
|
||||||
|
|||||||
Reference in New Issue
Block a user