Einmalige Sonderein-/ausgaben am Cash-Uebergang (Roadmap Nr. 1)
Deploy App / deploy (push) Successful in 1m51s
Deploy App / deploy (push) Successful in 1m51s
Der Cash-Uebergang zwischen zwei Phasen ist neu ein eigener Entscheid: 1:1 uebernehmen / einmaliger Zufluss / einmalige Kosten / beides. Die Betraege gehen direkt aufs Cash-Konto und bleiben aus der Spar-/Verzehrquote heraus. - Zufluss NOMINAL erfasst, real angezeigt (wie Einkommen), optionaler Steuersatz (Default 0 %). Kosten REAL erfasst, nominal angezeigt (wie Ausgaben). Umrechnung ueber den Bestands-Deflator an der Phasengrenze. - Entscheid startet unbeantwortet und zaehlt im "offen"-Badge mit; eine neue Phase erzeugt damit automatisch einen offenen Cash-Entscheid am neuen Uebergang. - Eigene Kopf-Kennzahlen statt Vermischung mit Kapitalzufluss/-investitionen: eine Erbschaft ist kein Verkaufserloes, ein Poolbau keine Investition. - Cash ist kein FinancialElement -> der Entscheid haengt als JSON an der Von-Phase (neue Spalte Phase.cashTransition + Migration). Szenario-Kopie nimmt ihn mit. Fuenf Regressionstests ergaenzt (13 -> 18). Spezifikation auf v0.3. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+44
-1
@@ -61,6 +61,13 @@ export interface PhaseComputed {
|
||||
plannedWithdrawRate: number; // geplante Verzehrrate: Bezugsraten aus Sonstigem Vermoegen
|
||||
capitalInflow: number; // Kapitalzufluss: PK-/3a-Bezuege + Verkaeufe (aus dem Uebergang in diese Phase)
|
||||
capitalInvest: number; // Kapitalinvestitionen: Zusatz-/Neuinvestitionen + sofortige Tilgungen
|
||||
// Einmalige Sonderein-/ausgaben aus dem Uebergang in DIESE Phase (nominal, netto nach Steuer).
|
||||
// Bewusst getrennt von capitalInflow/capitalInvest: eine Erbschaft ist kein Verkaufserloes,
|
||||
// ein Poolbau keine Kapitalinvestition.
|
||||
oneOffInflow: number;
|
||||
oneOffInflowLabel: string | null;
|
||||
oneOffOutflow: number;
|
||||
oneOffOutflowLabel: string | null;
|
||||
cashStart: number;
|
||||
cashEnd: number;
|
||||
cashNegative: boolean; // Cash faellt in dieser Phase (irgendwann) unter 0 -> Liquiditaetsluecke
|
||||
@@ -147,6 +154,10 @@ export function computePlan(plan: PlanInput): PlanComputed {
|
||||
// Aus dem Uebergang der Vorphase in DIESE Phase fliessende Groessen (Kopf-Kennzahlen).
|
||||
let incomingInflow = 0; // Brutto-Zufluss: Verkaeufe + PK-/3a-Bezuege
|
||||
let incomingImmediateRepay = 0; // sofortige Schuldentilgungen (Abfluss)
|
||||
let incomingOneOffInflow = 0; // einmaliger Sonderzufluss (netto nach Steuer)
|
||||
let incomingOneOffInflowLabel: string | null = null;
|
||||
let incomingOneOffOutflow = 0; // einmalige Sonderkosten (nominal)
|
||||
let incomingOneOffOutflowLabel: string | null = null;
|
||||
let ruinAge: number | null = null;
|
||||
|
||||
for (let i = 0; i < phases.length; i++) {
|
||||
@@ -500,6 +511,10 @@ export function computePlan(plan: PlanInput): PlanComputed {
|
||||
plannedWithdrawRate: plannedWithdrawTotal,
|
||||
capitalInflow: Math.round(incomingInflow),
|
||||
capitalInvest: Math.round(investmentsFromCash + incomingImmediateRepay),
|
||||
oneOffInflow: Math.round(incomingOneOffInflow),
|
||||
oneOffInflowLabel: incomingOneOffInflowLabel,
|
||||
oneOffOutflow: Math.round(incomingOneOffOutflow),
|
||||
oneOffOutflowLabel: incomingOneOffOutflowLabel,
|
||||
cashStart: Math.round(cashStart),
|
||||
cashEnd,
|
||||
cashNegative,
|
||||
@@ -516,6 +531,30 @@ export function computePlan(plan: PlanInput): PlanComputed {
|
||||
// --- Uebergang: Carry aktualisieren, Cash der Folgephase bilden ---
|
||||
let txInflow = 0;
|
||||
let txImmediateRepay = 0;
|
||||
|
||||
// Einmalige Sonderein-/ausgaben auf dem Cash-Konto. Nur sinnvoll, wenn eine Folgephase
|
||||
// existiert -- nach der letzten Phase gibt es keinen Uebergang. Der Wechselkurs zwischen
|
||||
// real und nominal ist an dieser Grenze `cumulativeInflation` (Bestands-Deflator am
|
||||
// Phasenende), denn Cash ist ein Bestand.
|
||||
let txOneOffInflow = 0;
|
||||
let txOneOffOutflow = 0;
|
||||
let txOneOffInflowLabel: string | null = null;
|
||||
let txOneOffOutflowLabel: string | null = null;
|
||||
if (nextPhase) {
|
||||
const ct = phase.cashTransition ?? {};
|
||||
const mode = ct.mode ?? "NONE";
|
||||
if (mode === "INFLOW" || mode === "BOTH") {
|
||||
// Zufluss ist NOMINAL erfasst (wie Einkommen); Steuer mindert den Netto-Zufluss.
|
||||
const gross = Math.round(num(ct.inflowAmount));
|
||||
txOneOffInflow = Math.round(gross * (1 - num(ct.inflowTaxRate, 0) / 100));
|
||||
txOneOffInflowLabel = ct.inflowLabel?.trim() || null;
|
||||
}
|
||||
if (mode === "OUTFLOW" || mode === "BOTH") {
|
||||
// Kosten sind REAL erfasst (wie Ausgaben) -> mit der kumulierten Inflation aufwerten.
|
||||
txOneOffOutflow = Math.round(num(ct.outflowAmount) * cumulativeInflation);
|
||||
txOneOffOutflowLabel = ct.outflowLabel?.trim() || null;
|
||||
}
|
||||
}
|
||||
for (const e of orderedElements) {
|
||||
const carry = carries.get(e.id)!;
|
||||
const ec = ecById.get(e.id)!;
|
||||
@@ -616,9 +655,13 @@ export function computePlan(plan: PlanInput): PlanComputed {
|
||||
carry.hasCarry = true;
|
||||
}
|
||||
|
||||
cashCarryIn = cashEnd + txInflow - txImmediateRepay;
|
||||
cashCarryIn = cashEnd + txInflow + txOneOffInflow - txImmediateRepay - txOneOffOutflow;
|
||||
incomingInflow = txInflow;
|
||||
incomingImmediateRepay = txImmediateRepay;
|
||||
incomingOneOffInflow = txOneOffInflow;
|
||||
incomingOneOffInflowLabel = txOneOffInflowLabel;
|
||||
incomingOneOffOutflow = txOneOffOutflow;
|
||||
incomingOneOffOutflowLabel = txOneOffOutflowLabel;
|
||||
yearsBefore += duration;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user