Drei Berechnungs-Fixes: Phaseninflation, Tilgungsraten, Vorbezugssteuer

- Phase.inflationRate ersatzlos entfernt (inkl. Migration). Die Inflation liegt
  seit V5 plan-weit; das Feld wurde von computePlan nie gelesen und war wirkungslos.
- Amortisation und Tilgung stoppen, sobald Hypothek bzw. Schuld abbezahlt sind:
  Hypothek/Restschuld sind neu laufende Salden, die Rate ist pro Jahr am Restsaldo
  gekappt. Belastet danach weder Cash noch Sparquote.
- Kapitalbezugssteuer greift neu auch bei PK-/3a-Vorbezuegen vor der Pensionierung.
  Der Bezug wird brutto dem Kapital entnommen, netto ins Cash gebucht.

plannedSaveRate ist neu die Rate des ersten Phasenjahres. Drei Regressionstests
ergaenzt (10 -> 13).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 22:22:34 +02:00
parent f768e01d61
commit dfebbeb397
12 changed files with 141 additions and 49 deletions
+2 -9
View File
@@ -154,10 +154,6 @@ export function PlanView({
return !!before?.working && !!after && !after.working;
}
function phaseInflationFor(phaseId: string): number {
return plan.phases.find((p) => p.id === phaseId)?.inflationRate ?? plan.inflationRateDefault;
}
// Baut den Kontext fuer eine Phasenzelle.
function buildPhaseContext(phase: PhaseComputed, element: ElementInput): CellContext {
const ce = computedElement(phase.id, element.id);
@@ -175,7 +171,6 @@ export function PlanView({
carriedEndValue: ce?.endValue ?? 0,
carried: ce?.carried ?? false,
derivedStart: ce?.baseValue ?? 0,
phaseInflation: phaseInflationFor(phase.id),
deflatorStart: phase.cumulativeInflationStart,
};
}
@@ -192,7 +187,6 @@ export function PlanView({
carriedEndValue: ce?.endValue ?? 0,
carried: ce?.carried ?? false,
derivedStart: 0,
phaseInflation: phaseInflationFor(fromPhase.id),
deflatorStart: fromPhase.cumulativeInflationStart,
};
}
@@ -241,7 +235,7 @@ export function PlanView({
.sort((a, b) => a.orderIndex - b.orderIndex);
}
async function handleAddPhase(payload: { name?: string; durationYears?: number; inflationRate?: number | null }) {
async function handleAddPhase(payload: { name?: string; durationYears?: number }) {
await api.post(`/api/plans/${plan.id}/phases`, payload);
setShowAddPhase(false);
onChanged();
@@ -855,7 +849,6 @@ function AddElementDialog({
carriedEndValue: 0,
carried: false,
derivedStart: 0,
phaseInflation: plan.inflationRateDefault,
deflatorStart: firstPhase.cumulativeInflationStart,
};
@@ -954,7 +947,7 @@ function AddPhaseDialog({
}: {
maxDurationYears: number | null;
onClose: () => void;
onCreate: (payload: { name?: string; durationYears?: number; inflationRate?: number | null }) => void;
onCreate: (payload: { name?: string; durationYears?: number }) => void;
}) {
const cap = maxDurationYears;
const [name, setName] = useState("");