Automate transition: carried-over securities auto-appear in next phase, sale proceeds tracked as incoming capital with allocation status indicators
Deploy App / deploy (push) Successful in 1m27s
Deploy App / deploy (push) Successful in 1m27s
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Phase" ADD COLUMN "incomingCapital" DOUBLE PRECISION NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Security" ADD COLUMN "carriedBaseValue" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||||
|
ADD COLUMN "carriedFromSecurityId" TEXT;
|
||||||
+27
-17
@@ -104,14 +104,17 @@ model Plan {
|
|||||||
|
|
||||||
// Ein Lebensabschnitt innerhalb eines Plans
|
// Ein Lebensabschnitt innerhalb eines Plans
|
||||||
model Phase {
|
model Phase {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
planId String
|
planId String
|
||||||
plan Plan @relation(fields: [planId], references: [id], onDelete: Cascade)
|
plan Plan @relation(fields: [planId], references: [id], onDelete: Cascade)
|
||||||
sequenceNumber Int
|
sequenceNumber Int
|
||||||
name String
|
name String
|
||||||
durationYears Int
|
durationYears Int
|
||||||
inflationRate Float?
|
inflationRate Float?
|
||||||
incomeMode IncomeMode @default(HOUSEHOLD)
|
incomeMode IncomeMode @default(HOUSEHOLD)
|
||||||
|
// Aus Verkaeufen im Uebergang aus der Vorphase verfuegbares Startkapital (wird beim
|
||||||
|
// Speichern des Uebergangs automatisch gesetzt, siehe PhaseTransition).
|
||||||
|
incomingCapital Float @default(0)
|
||||||
|
|
||||||
incomeEntries IncomeEntry[]
|
incomeEntries IncomeEntry[]
|
||||||
expenseEntries ExpenseEntry[]
|
expenseEntries ExpenseEntry[]
|
||||||
@@ -152,16 +155,23 @@ model ExpenseEntry {
|
|||||||
|
|
||||||
// Eine Wertschrift innerhalb einer Phase (inkl. jaehrlichem Sparbeitrag)
|
// Eine Wertschrift innerhalb einer Phase (inkl. jaehrlichem Sparbeitrag)
|
||||||
model Security {
|
model Security {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
phaseId String
|
phaseId String
|
||||||
phase Phase @relation(fields: [phaseId], references: [id], onDelete: Cascade)
|
phase Phase @relation(fields: [phaseId], references: [id], onDelete: Cascade)
|
||||||
name String
|
name String
|
||||||
startValue Float
|
startValue Float
|
||||||
expectedReturn Float
|
expectedReturn Float
|
||||||
annualContribution Float @default(0)
|
annualContribution Float @default(0)
|
||||||
ownerTag OwnerTag @default(HOUSEHOLD)
|
ownerTag OwnerTag @default(HOUSEHOLD)
|
||||||
// Steuersatz auf Verkaufsgewinn bei Uebernahme in PhaseTransitionItem (Default 0%, siehe Kap. 9)
|
// Steuersatz auf Verkaufsgewinn bei Uebernahme in PhaseTransitionItem (Default 0%, siehe Kap. 9)
|
||||||
saleTaxRate Float @default(0)
|
saleTaxRate Float @default(0)
|
||||||
|
// Baseline-Wert bei automatischer Uebernahme aus der Vorphase (0 bei manuell angelegten
|
||||||
|
// Wertschriften). Dient dazu, im UI zu erkennen, wie viel vom verfuegbaren Startkapital
|
||||||
|
// bereits (on top of der Uebernahme) zugewiesen wurde.
|
||||||
|
carriedBaseValue Float @default(0)
|
||||||
|
// Verweist auf die Wertschrift der Vorphase, aus der automatisch uebernommen wurde
|
||||||
|
// (nur intern zur Deduplizierung bei wiederholtem Speichern des Uebergangs, kein FK).
|
||||||
|
carriedFromSecurityId String?
|
||||||
|
|
||||||
transitionItems PhaseTransitionItem[]
|
transitionItems PhaseTransitionItem[]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const securitySchema = z.object({
|
|||||||
annualContribution: z.number(),
|
annualContribution: z.number(),
|
||||||
ownerTag: z.enum(["PERSON_A", "PERSON_B", "HOUSEHOLD"]),
|
ownerTag: z.enum(["PERSON_A", "PERSON_B", "HOUSEHOLD"]),
|
||||||
saleTaxRate: z.number().min(0).max(100),
|
saleTaxRate: z.number().min(0).max(100),
|
||||||
|
carriedBaseValue: z.number().default(0),
|
||||||
});
|
});
|
||||||
const realEstateSchema = z.object({
|
const realEstateSchema = z.object({
|
||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { prisma } from "@/lib/db";
|
import { prisma } from "@/lib/db";
|
||||||
|
import { computeSecurityYearlyValues } from "@/lib/calculations";
|
||||||
|
|
||||||
const transitionItemSchema = z.object({
|
const transitionItemSchema = z.object({
|
||||||
positionType: z.enum(["SECURITY", "REAL_ESTATE"]),
|
positionType: z.enum(["SECURITY", "REAL_ESTATE"]),
|
||||||
@@ -50,6 +51,9 @@ export async function GET(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Speichert die Entscheidungen (Uebernehmen/Verkaufen) fuer jede Position der Vorphase.
|
// Speichert die Entscheidungen (Uebernehmen/Verkaufen) fuer jede Position der Vorphase.
|
||||||
|
// Uebernommene Wertschriften werden automatisch als neue Wertschrift in der Folgephase
|
||||||
|
// angelegt (Startwert = Endwert dieser Phase). Verkaufte Positionen fliessen als
|
||||||
|
// "verfuegbares Startkapital" (Phase.incomingCapital) in die Folgephase ein.
|
||||||
export async function PUT(
|
export async function PUT(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
{ params }: { params: Promise<{ phaseId: string }> }
|
{ params }: { params: Promise<{ phaseId: string }> }
|
||||||
@@ -94,8 +98,74 @@ export async function PUT(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const securityById = new Map(phase.securities.map((s) => [s.id, s]));
|
||||||
|
const realEstateById = new Map(phase.realEstates.map((re) => [re.id, re]));
|
||||||
|
|
||||||
|
let incomingCapital = 0;
|
||||||
|
const securitiesToCarry: { source: (typeof phase.securities)[number]; endValue: number }[] = [];
|
||||||
|
|
||||||
|
for (const item of parsed.data.items) {
|
||||||
|
if (item.positionType === "SECURITY" && item.securityId) {
|
||||||
|
const security = securityById.get(item.securityId);
|
||||||
|
if (!security) continue;
|
||||||
|
const endValue = computeSecurityYearlyValues(
|
||||||
|
security.startValue,
|
||||||
|
security.expectedReturn,
|
||||||
|
security.annualContribution,
|
||||||
|
phase.durationYears
|
||||||
|
)[phase.durationYears];
|
||||||
|
|
||||||
|
if (item.decision === "CARRY_OVER") {
|
||||||
|
securitiesToCarry.push({ source: security, endValue });
|
||||||
|
} else {
|
||||||
|
const gain = Math.max(0, endValue - security.startValue);
|
||||||
|
const tax = gain * (security.saleTaxRate / 100);
|
||||||
|
incomingCapital += endValue - tax;
|
||||||
|
}
|
||||||
|
} else if (item.positionType === "REAL_ESTATE" && item.realEstateId) {
|
||||||
|
const realEstate = realEstateById.get(item.realEstateId);
|
||||||
|
if (!realEstate || item.decision !== "SELL") continue;
|
||||||
|
const salePrice = item.salePrice ?? 0;
|
||||||
|
const gain = Math.max(0, salePrice - realEstate.marketValue);
|
||||||
|
const tax = gain * (realEstate.saleTaxRate / 100);
|
||||||
|
incomingCapital += salePrice - tax;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const transition = await prisma.$transaction(async (tx) => {
|
const transition = await prisma.$transaction(async (tx) => {
|
||||||
await tx.phaseTransition.deleteMany({ where: { fromPhaseId: phaseId } });
|
await tx.phaseTransition.deleteMany({ where: { fromPhaseId: phaseId } });
|
||||||
|
|
||||||
|
// Vorherige automatisch uebernommene Wertschriften aus einem frueheren Speichern
|
||||||
|
// dieses Uebergangs entfernen, damit sie nicht dupliziert werden. Manuell vom
|
||||||
|
// Benutzer angelegte Wertschriften (carriedFromSecurityId = null) bleiben unberuehrt.
|
||||||
|
await tx.security.deleteMany({
|
||||||
|
where: {
|
||||||
|
phaseId: nextPhase.id,
|
||||||
|
carriedFromSecurityId: { in: phase.securities.map((s) => s.id) },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const { source, endValue } of securitiesToCarry) {
|
||||||
|
await tx.security.create({
|
||||||
|
data: {
|
||||||
|
phaseId: nextPhase.id,
|
||||||
|
name: source.name,
|
||||||
|
startValue: endValue,
|
||||||
|
carriedBaseValue: endValue,
|
||||||
|
expectedReturn: source.expectedReturn,
|
||||||
|
annualContribution: 0,
|
||||||
|
ownerTag: source.ownerTag,
|
||||||
|
saleTaxRate: source.saleTaxRate,
|
||||||
|
carriedFromSecurityId: source.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await tx.phase.update({
|
||||||
|
where: { id: nextPhase.id },
|
||||||
|
data: { incomingCapital },
|
||||||
|
});
|
||||||
|
|
||||||
return tx.phaseTransition.create({
|
return tx.phaseTransition.create({
|
||||||
data: {
|
data: {
|
||||||
fromPhaseId: phaseId,
|
fromPhaseId: phaseId,
|
||||||
@@ -114,5 +184,5 @@ export async function PUT(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return NextResponse.json({ transition });
|
return NextResponse.json({ transition, incomingCapital });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export async function POST(
|
|||||||
durationYears: phase.durationYears,
|
durationYears: phase.durationYears,
|
||||||
inflationRate: phase.inflationRate,
|
inflationRate: phase.inflationRate,
|
||||||
incomeMode: phase.incomeMode,
|
incomeMode: phase.incomeMode,
|
||||||
|
incomingCapital: phase.incomingCapital,
|
||||||
incomeEntries: {
|
incomeEntries: {
|
||||||
create: phase.incomeEntries.map((e) => ({
|
create: phase.incomeEntries.map((e) => ({
|
||||||
personId: e.personId,
|
personId: e.personId,
|
||||||
@@ -73,6 +74,7 @@ export async function POST(
|
|||||||
annualContribution: s.annualContribution,
|
annualContribution: s.annualContribution,
|
||||||
ownerTag: s.ownerTag,
|
ownerTag: s.ownerTag,
|
||||||
saleTaxRate: s.saleTaxRate,
|
saleTaxRate: s.saleTaxRate,
|
||||||
|
carriedBaseValue: s.carriedBaseValue,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
realEstates: {
|
realEstates: {
|
||||||
|
|||||||
@@ -220,7 +220,12 @@ export function AppShell({ initialHousehold }: { initialHousehold: HouseholdInpu
|
|||||||
onChanged={refreshCurrent}
|
onChanged={refreshCurrent}
|
||||||
/>
|
/>
|
||||||
{nextPhase && (
|
{nextPhase && (
|
||||||
<TransitionPanel phase={phase} computed={computedPhase} nextPhaseName={nextPhase.name} />
|
<TransitionPanel
|
||||||
|
phase={phase}
|
||||||
|
computed={computedPhase}
|
||||||
|
nextPhaseName={nextPhase.name}
|
||||||
|
onChanged={refreshCurrent}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ export function PhaseCard({
|
|||||||
</button>
|
</button>
|
||||||
{expanded && (
|
{expanded && (
|
||||||
<PhaseForm
|
<PhaseForm
|
||||||
|
key={`${phase.id}:${phase.securities.length}:${phase.incomingCapital}`}
|
||||||
household={household}
|
household={household}
|
||||||
phase={phase}
|
phase={phase}
|
||||||
onSaved={() => {
|
onSaved={() => {
|
||||||
|
|||||||
@@ -58,6 +58,13 @@ export function PhaseForm({ household, phase, onSaved, onCancel }: Props) {
|
|||||||
const savingsQuota = totalIncome - totalExpense;
|
const savingsQuota = totalIncome - totalExpense;
|
||||||
const allocated = securities.reduce((s, sec) => s + sec.annualContribution, 0);
|
const allocated = securities.reduce((s, sec) => s + sec.annualContribution, 0);
|
||||||
const overAllocated = allocated > savingsQuota;
|
const overAllocated = allocated > savingsQuota;
|
||||||
|
const savingsRemaining = savingsQuota - allocated > 0.5;
|
||||||
|
|
||||||
|
const allocatedStartCapital = securities.reduce(
|
||||||
|
(s, sec) => s + Math.max(0, sec.startValue - sec.carriedBaseValue),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const startCapitalRemaining = phase.incomingCapital - allocatedStartCapital > 0.5;
|
||||||
|
|
||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
@@ -193,16 +200,9 @@ export function PhaseForm({ household, phase, onSaved, onCancel }: Props) {
|
|||||||
label="+ Ausgabenposten"
|
label="+ Ausgabenposten"
|
||||||
onClick={() => setExpenseEntries((prev) => [...prev, { id: tempId(), label: null, amount: 0 }])}
|
onClick={() => setExpenseEntries((prev) => [...prev, { id: tempId(), label: null, amount: 0 }])}
|
||||||
/>
|
/>
|
||||||
<div
|
<div className="rounded-md bg-zinc-50 px-3 py-2 text-sm text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300">
|
||||||
className={`rounded-md px-3 py-2 text-sm ${
|
|
||||||
overAllocated
|
|
||||||
? "bg-amber-50 text-amber-800 dark:bg-amber-950 dark:text-amber-300"
|
|
||||||
: "bg-zinc-50 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Verfuegbare Sparquote (CHF/Jahr): <strong>{savingsQuota.toLocaleString("de-CH")}</strong>
|
Verfuegbare Sparquote (CHF/Jahr): <strong>{savingsQuota.toLocaleString("de-CH")}</strong>
|
||||||
{" "}— zugewiesen an Wertschriften: {allocated.toLocaleString("de-CH")}
|
{" "}(Details und Verteilung siehe Wertschriften weiter unten)
|
||||||
{overAllocated && " ⚠ Die zugewiesenen Sparbeitraege uebersteigen die verfuegbare Sparquote."}
|
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
@@ -258,10 +258,36 @@ export function PhaseForm({ household, phase, onSaved, onCancel }: Props) {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
setSecurities((prev) => [
|
setSecurities((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
{ id: tempId(), name: "", startValue: 0, expectedReturn: 0, annualContribution: 0, ownerTag: "HOUSEHOLD", saleTaxRate: 0 },
|
{
|
||||||
|
id: tempId(),
|
||||||
|
name: "",
|
||||||
|
startValue: 0,
|
||||||
|
expectedReturn: 0,
|
||||||
|
annualContribution: 0,
|
||||||
|
ownerTag: "HOUSEHOLD",
|
||||||
|
saleTaxRate: 0,
|
||||||
|
carriedBaseValue: 0,
|
||||||
|
},
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<div className="flex flex-col gap-1.5 rounded-md bg-zinc-50 px-3 py-2 text-sm dark:bg-zinc-800">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<StatusDot ok={!startCapitalRemaining} />
|
||||||
|
Verfuegbares Startkapital (aus Verkaeufen der Vorphase): <strong>{phase.incomingCapital.toLocaleString("de-CH")}</strong> CHF
|
||||||
|
{" "}— zugewiesen: {allocatedStartCapital.toLocaleString("de-CH")}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<StatusDot ok={!savingsRemaining} />
|
||||||
|
Verfuegbare Sparquote (CHF/Jahr): <strong>{savingsQuota.toLocaleString("de-CH")}</strong>
|
||||||
|
{" "}— zugewiesen: {allocated.toLocaleString("de-CH")}
|
||||||
|
</div>
|
||||||
|
{overAllocated && (
|
||||||
|
<div className="text-amber-700 dark:text-amber-400">
|
||||||
|
⚠ Die zugewiesenen Sparbeitraege uebersteigen die verfuegbare Sparquote.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
{/* Immobilien */}
|
{/* Immobilien */}
|
||||||
@@ -461,6 +487,15 @@ function AddButton({ label, onClick }: { label: string; onClick: () => void }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function StatusDot({ ok }: { ok: boolean }) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
title={ok ? "Vollstaendig verteilt" : "Noch nicht vollstaendig verteilt"}
|
||||||
|
className={`inline-block h-2.5 w-2.5 shrink-0 rounded-full ${ok ? "bg-emerald-500" : "bg-red-500"}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function RemoveButton({ onClick }: { onClick: () => void }) {
|
function RemoveButton({ onClick }: { onClick: () => void }) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -21,10 +21,12 @@ export function TransitionPanel({
|
|||||||
phase,
|
phase,
|
||||||
computed,
|
computed,
|
||||||
nextPhaseName,
|
nextPhaseName,
|
||||||
|
onChanged,
|
||||||
}: {
|
}: {
|
||||||
phase: PhaseInput;
|
phase: PhaseInput;
|
||||||
computed: PhaseComputed;
|
computed: PhaseComputed;
|
||||||
nextPhaseName: string;
|
nextPhaseName: string;
|
||||||
|
onChanged: () => void;
|
||||||
}) {
|
}) {
|
||||||
const [items, setItems] = useState<ItemDraft[]>([]);
|
const [items, setItems] = useState<ItemDraft[]>([]);
|
||||||
const [loaded, setLoaded] = useState(false);
|
const [loaded, setLoaded] = useState(false);
|
||||||
@@ -131,6 +133,7 @@ export function TransitionPanel({
|
|||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
setSaved(true);
|
setSaved(true);
|
||||||
|
onChanged();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e instanceof Error ? e.message : "Speichern fehlgeschlagen.");
|
setError(e instanceof Error ? e.message : "Speichern fehlgeschlagen.");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -198,8 +201,8 @@ export function TransitionPanel({
|
|||||||
<div className="rounded-md bg-white px-3 py-2 text-sm dark:bg-zinc-900">
|
<div className="rounded-md bg-white px-3 py-2 text-sm dark:bg-zinc-900">
|
||||||
Verfuegbares Startkapital fuer neue Phase (aus Verkaeufen): <strong>{totalAvailableCapital.toLocaleString("de-CH")} CHF</strong>
|
Verfuegbares Startkapital fuer neue Phase (aus Verkaeufen): <strong>{totalAvailableCapital.toLocaleString("de-CH")} CHF</strong>
|
||||||
<p className="mt-1 text-xs text-zinc-500">
|
<p className="mt-1 text-xs text-zinc-500">
|
||||||
Dieser Betrag kann anschliessend frei auf neue oder bestehende Wertschriften der Folgephase verteilt werden
|
Wird beim Speichern automatisch in "{nextPhaseName}" als verfuegbares Startkapital hinterlegt.
|
||||||
(Startwert der jeweiligen Wertschrift in "{nextPhaseName}" manuell anpassen).
|
Uebernommene Wertschriften erscheinen dort automatisch mit ihrem Endwert als neuer Startwert.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{error && <p className="text-sm text-red-600 dark:text-red-400">{error}</p>}
|
{error && <p className="text-sm text-red-600 dark:text-red-400">{error}</p>}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export function toPlanInput(plan: PlanWithRelations): PlanInput {
|
|||||||
durationYears: phase.durationYears,
|
durationYears: phase.durationYears,
|
||||||
inflationRate: phase.inflationRate,
|
inflationRate: phase.inflationRate,
|
||||||
incomeMode: phase.incomeMode,
|
incomeMode: phase.incomeMode,
|
||||||
|
incomingCapital: phase.incomingCapital,
|
||||||
incomeEntries: phase.incomeEntries.map((e) => ({
|
incomeEntries: phase.incomeEntries.map((e) => ({
|
||||||
id: e.id,
|
id: e.id,
|
||||||
personId: e.personId,
|
personId: e.personId,
|
||||||
@@ -68,6 +69,7 @@ export function toPlanInput(plan: PlanWithRelations): PlanInput {
|
|||||||
annualContribution: s.annualContribution,
|
annualContribution: s.annualContribution,
|
||||||
ownerTag: s.ownerTag,
|
ownerTag: s.ownerTag,
|
||||||
saleTaxRate: s.saleTaxRate,
|
saleTaxRate: s.saleTaxRate,
|
||||||
|
carriedBaseValue: s.carriedBaseValue,
|
||||||
})),
|
})),
|
||||||
realEstates: phase.realEstates.map((re) => ({
|
realEstates: phase.realEstates.map((re) => ({
|
||||||
id: re.id,
|
id: re.id,
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ export interface SecurityInput {
|
|||||||
annualContribution: number;
|
annualContribution: number;
|
||||||
ownerTag: OwnerTag;
|
ownerTag: OwnerTag;
|
||||||
saleTaxRate: number;
|
saleTaxRate: number;
|
||||||
|
// Baseline-Wert bei automatischer Uebernahme aus der Vorphase (0 bei manuell angelegten
|
||||||
|
// Wertschriften). Siehe PhaseInput.incomingCapital fuer den Kontext.
|
||||||
|
carriedBaseValue: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RealEstateInput {
|
export interface RealEstateInput {
|
||||||
@@ -81,6 +84,9 @@ export interface PhaseInput {
|
|||||||
durationYears: number;
|
durationYears: number;
|
||||||
inflationRate: number | null;
|
inflationRate: number | null;
|
||||||
incomeMode: IncomeMode;
|
incomeMode: IncomeMode;
|
||||||
|
// Aus Verkaeufen im Uebergang aus der Vorphase verfuegbares Startkapital (automatisch
|
||||||
|
// gesetzt beim Speichern des Uebergangs der Vorphase).
|
||||||
|
incomingCapital: number;
|
||||||
incomeEntries: IncomeEntryInput[];
|
incomeEntries: IncomeEntryInput[];
|
||||||
expenseEntries: ExpenseEntryInput[];
|
expenseEntries: ExpenseEntryInput[];
|
||||||
securities: SecurityInput[];
|
securities: SecurityInput[];
|
||||||
|
|||||||
Reference in New Issue
Block a user