Rework core model: financial elements as plan-wide entities across phases; derived phase types (Erwerb/Pension/Misch) with retirement-capped durations and per-plan retirement age; AHV (gap years + couple ceiling), PK payout/annuity, 3a, real estate, other assets/debts; horizontal timeline with retirement markers; phase x element matrix with detail panel; savings/consumption quota + available-capital key figures with red status
Deploy App / deploy (push) Successful in 1m57s
Deploy App / deploy (push) Successful in 1m57s
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
// Typ- und Validierungs-Layer fuer die finanziellen Elemente. Die kategorie- und
|
||||
// kontextspezifischen Felder liegen in der DB als JSON; hier werden sie typisiert und
|
||||
// (an der API-Grenze) mit Zod validiert.
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
export type ElementCategory =
|
||||
| "INCOME"
|
||||
| "EXPENSE"
|
||||
| "AHV"
|
||||
| "PENSION_FUND"
|
||||
| "PILLAR_3A"
|
||||
| "REAL_ESTATE"
|
||||
| "OTHER_ASSET"
|
||||
| "OTHER_DEBT";
|
||||
|
||||
export type OwnerRole = "PERSON_A" | "PERSON_B" | "HOUSEHOLD";
|
||||
|
||||
// Kategorien, deren Element zwingend genau einer Person zugeordnet ist.
|
||||
export const PERSON_ONLY_CATEGORIES: ElementCategory[] = ["INCOME", "AHV", "PENSION_FUND", "PILLAR_3A"];
|
||||
|
||||
// Kategorien, die gemeinsam ODER pro Person erfasst werden koennen.
|
||||
export const OWNER_OPTIONAL_CATEGORIES: ElementCategory[] = ["EXPENSE", "REAL_ESTATE", "OTHER_ASSET", "OTHER_DEBT"];
|
||||
|
||||
export const CATEGORY_LABELS: Record<ElementCategory, string> = {
|
||||
INCOME: "Einkommen",
|
||||
EXPENSE: "Ausgaben",
|
||||
AHV: "AHV",
|
||||
PENSION_FUND: "Pensionskasse",
|
||||
PILLAR_3A: "Saeule 3a",
|
||||
REAL_ESTATE: "Immobilie",
|
||||
OTHER_ASSET: "Sonstiges Vermoegen",
|
||||
OTHER_DEBT: "Sonstige Schulden",
|
||||
};
|
||||
|
||||
// Reihenfolge der Kategorien in der Matrix (Gruppierung der Zeilen).
|
||||
export const CATEGORY_ORDER: ElementCategory[] = [
|
||||
"INCOME",
|
||||
"EXPENSE",
|
||||
"AHV",
|
||||
"PENSION_FUND",
|
||||
"PILLAR_3A",
|
||||
"REAL_ESTATE",
|
||||
"OTHER_ASSET",
|
||||
"OTHER_DEBT",
|
||||
];
|
||||
|
||||
// --- Roh-Payloads (JSON in der DB) ---
|
||||
// Bewusst tolerant getippt (alle Felder optional): die Berechnung liest defensiv mit
|
||||
// Defaults, das UI zeigt je nach Kontext nur die relevanten Felder.
|
||||
|
||||
export interface PhaseData {
|
||||
// INCOME / EXPENSE
|
||||
amount?: number;
|
||||
// AHV
|
||||
gapYears?: number;
|
||||
// PENSION_FUND / PILLAR_3A / OTHER_ASSET
|
||||
currentValue?: number;
|
||||
startValue?: number;
|
||||
expectedReturn?: number;
|
||||
annualContribution?: number;
|
||||
// REAL_ESTATE
|
||||
purchasePrice?: number;
|
||||
mortgage?: number;
|
||||
amortization?: number;
|
||||
// OTHER_DEBT
|
||||
annualRepayment?: number;
|
||||
}
|
||||
|
||||
export type TransitionDecision = "HOLD" | "SELL";
|
||||
export type PkPayoutMode = "CAPITAL" | "PENSION" | "COMBI";
|
||||
|
||||
export interface TransitionData {
|
||||
// PENSION_FUND / PILLAR_3A (normaler Uebergang)
|
||||
withdrawal?: number;
|
||||
// PENSION_FUND (Pensions-Uebergang)
|
||||
payoutMode?: PkPayoutMode;
|
||||
capitalAmount?: number;
|
||||
conversionRate?: number;
|
||||
// PENSION_FUND (Kapital) / PILLAR_3A (Pensions-Uebergang) / REAL_ESTATE
|
||||
capitalTaxRate?: number;
|
||||
saleTaxRate?: number;
|
||||
// REAL_ESTATE / OTHER_ASSET
|
||||
decision?: TransitionDecision;
|
||||
salePrice?: number;
|
||||
// OTHER_DEBT
|
||||
immediateRepayment?: number;
|
||||
}
|
||||
|
||||
// --- Zod-Schemas (nachsichtig: unbekannte Felder werden verworfen) ---
|
||||
|
||||
const nonNeg = z.number().min(0);
|
||||
|
||||
export const phaseDataSchema = z
|
||||
.object({
|
||||
amount: nonNeg.optional(),
|
||||
gapYears: z.number().int().min(0).optional(),
|
||||
currentValue: nonNeg.optional(),
|
||||
startValue: nonNeg.optional(),
|
||||
expectedReturn: z.number().min(-50).max(100).optional(),
|
||||
annualContribution: nonNeg.optional(),
|
||||
purchasePrice: nonNeg.optional(),
|
||||
mortgage: nonNeg.optional(),
|
||||
amortization: nonNeg.optional(),
|
||||
annualRepayment: nonNeg.optional(),
|
||||
})
|
||||
.strip();
|
||||
|
||||
export const transitionDataSchema = z
|
||||
.object({
|
||||
withdrawal: nonNeg.optional(),
|
||||
payoutMode: z.enum(["CAPITAL", "PENSION", "COMBI"]).optional(),
|
||||
capitalAmount: nonNeg.optional(),
|
||||
conversionRate: z.number().min(0).max(20).optional(),
|
||||
capitalTaxRate: z.number().min(0).max(100).optional(),
|
||||
saleTaxRate: z.number().min(0).max(100).optional(),
|
||||
decision: z.enum(["HOLD", "SELL"]).optional(),
|
||||
salePrice: nonNeg.optional(),
|
||||
immediateRepayment: nonNeg.optional(),
|
||||
})
|
||||
.strip();
|
||||
|
||||
export function num(value: number | undefined | null, fallback = 0): number {
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
||||
}
|
||||
Reference in New Issue
Block a user