Fix Uebergang: Entscheide werden zuverlaessig gespeichert; Klick auf Zelle oeffnet Popup
Deploy App / deploy (push) Successful in 1m0s
Deploy App / deploy (push) Successful in 1m0s
- Ursache des "?"-Bugs: sichtbarer Default (Halten/Rente) wurde ohne aktive Auswahl nicht in den Datensatz geschrieben -> beim Speichern blieb der Entscheid leer. Fix: withTransitionDefaults belegt den Entscheid explizit vor (Popup + Review-Panel), sodass ein blosses Speichern den Default persistiert. - Uebergangszellen oeffnen neu ein kleines Popup (TransitionCellDialog) statt des unteren Detail-Panels; offene Zellen sind kraeftig eingefaerbt. - PK und 3a bekommen im normalen Uebergang einen echten Bezugs-Entscheid (Kein Bezug / Bezug + Betrag) inkl. offenem "?"-Status bis entschieden. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,76 @@ function DerivedField({ label, value, help }: { label: string; value: number; he
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PK/3a-Bezugs-Entscheid im normalen Uebergang: Kein Bezug / Bezug (+ Betrag).
|
||||||
|
function WithdrawalDecision({
|
||||||
|
td,
|
||||||
|
setT,
|
||||||
|
max,
|
||||||
|
label,
|
||||||
|
}: {
|
||||||
|
td: TransitionData;
|
||||||
|
setT: (patch: Partial<TransitionData>) => void;
|
||||||
|
max: number;
|
||||||
|
label: string;
|
||||||
|
}) {
|
||||||
|
const mode = td.withdrawalMode ?? "NONE";
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SelectField
|
||||||
|
label="Bezug?"
|
||||||
|
value={mode}
|
||||||
|
onChange={(v: "NONE" | "AMOUNT") => setT(v === "NONE" ? { withdrawalMode: v, withdrawal: 0 } : { withdrawalMode: v })}
|
||||||
|
options={[
|
||||||
|
{ value: "NONE", label: "Kein Bezug" },
|
||||||
|
{ value: "AMOUNT", label: "Bezug" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
{mode === "AMOUNT" && (
|
||||||
|
<MoneyField
|
||||||
|
label={label}
|
||||||
|
help={`Maximal ${formatChf(max)} (Endwert der Vorphase).`}
|
||||||
|
value={num(td.withdrawal)}
|
||||||
|
max={max}
|
||||||
|
onChange={(v) => setT({ withdrawal: v })}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vorbelegung expliziter Entscheide, damit ein blosses "Speichern" den sichtbaren Default
|
||||||
|
// (Halten / Kein Bezug / Rente) auch tatsaechlich persistiert.
|
||||||
|
export function withTransitionDefaults(category: ElementCategory, isRetirement: boolean, td: TransitionData): TransitionData {
|
||||||
|
const out = { ...td };
|
||||||
|
if (category === "REAL_ESTATE" || category === "OTHER_ASSET") {
|
||||||
|
if (out.decision === undefined) out.decision = "HOLD";
|
||||||
|
} else if (category === "PENSION_FUND") {
|
||||||
|
if (isRetirement) {
|
||||||
|
if (out.payoutMode === undefined) out.payoutMode = "PENSION";
|
||||||
|
} else if (out.withdrawalMode === undefined) {
|
||||||
|
out.withdrawalMode = "NONE";
|
||||||
|
}
|
||||||
|
} else if (category === "PILLAR_3A") {
|
||||||
|
if (!isRetirement && out.withdrawalMode === undefined) out.withdrawalMode = "NONE";
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Beantwortet" = ein konkreter Entscheid liegt vor (kein offenes Fragezeichen).
|
||||||
|
export function isTransitionAnswered(category: ElementCategory, isRetirement: boolean, td: TransitionData): boolean {
|
||||||
|
switch (category) {
|
||||||
|
case "REAL_ESTATE":
|
||||||
|
case "OTHER_ASSET":
|
||||||
|
return td.decision !== undefined;
|
||||||
|
case "PENSION_FUND":
|
||||||
|
return isRetirement ? td.payoutMode !== undefined : td.withdrawalMode !== undefined;
|
||||||
|
case "PILLAR_3A":
|
||||||
|
return isRetirement ? true : td.withdrawalMode !== undefined;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Wiederverwendbare Feldgruppen (Detail-Panel, Erstell-Popup, Uebergangs-Review) ---
|
// --- Wiederverwendbare Feldgruppen (Detail-Panel, Erstell-Popup, Uebergangs-Review) ---
|
||||||
|
|
||||||
export function ElementPhaseFields({
|
export function ElementPhaseFields({
|
||||||
@@ -285,15 +355,7 @@ export function ElementTransitionFields({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return <WithdrawalDecision td={td} setT={setT} max={context.carriedEndValue} label="PK-Bezug (CHF)" />;
|
||||||
<MoneyField
|
|
||||||
label="PK-Bezug (CHF)"
|
|
||||||
help={`Optionaler Bezug. Maximal ${formatChf(context.carriedEndValue)} (Endwert der Vorphase).`}
|
|
||||||
value={num(td.withdrawal)}
|
|
||||||
max={context.carriedEndValue}
|
|
||||||
onChange={(v) => setT({ withdrawal: v })}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case "PILLAR_3A":
|
case "PILLAR_3A":
|
||||||
if (context.isRetirementTransition) {
|
if (context.isRetirementTransition) {
|
||||||
return (
|
return (
|
||||||
@@ -306,15 +368,7 @@ export function ElementTransitionFields({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return <WithdrawalDecision td={td} setT={setT} max={context.carriedEndValue} label="3a-Bezug (CHF)" />;
|
||||||
<MoneyField
|
|
||||||
label="3a-Bezug (CHF)"
|
|
||||||
help={`Maximal ${formatChf(context.carriedEndValue)} (Endwert der Vorphase).`}
|
|
||||||
value={num(td.withdrawal)}
|
|
||||||
max={context.carriedEndValue}
|
|
||||||
onChange={(v) => setT({ withdrawal: v })}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case "REAL_ESTATE": {
|
case "REAL_ESTATE": {
|
||||||
const decision = td.decision ?? "HOLD";
|
const decision = td.decision ?? "HOLD";
|
||||||
return (
|
return (
|
||||||
|
|||||||
+107
-39
@@ -23,6 +23,8 @@ import {
|
|||||||
ElementDetail,
|
ElementDetail,
|
||||||
ElementPhaseFields,
|
ElementPhaseFields,
|
||||||
ElementTransitionFields,
|
ElementTransitionFields,
|
||||||
|
isTransitionAnswered,
|
||||||
|
withTransitionDefaults,
|
||||||
type CellContext,
|
type CellContext,
|
||||||
} from "@/components/ElementDetail";
|
} from "@/components/ElementDetail";
|
||||||
import { PhaseDetail } from "@/components/PhaseDetail";
|
import { PhaseDetail } from "@/components/PhaseDetail";
|
||||||
@@ -36,6 +38,7 @@ import {
|
|||||||
num,
|
num,
|
||||||
type ElementCategory,
|
type ElementCategory,
|
||||||
type PhaseData,
|
type PhaseData,
|
||||||
|
type TransitionData,
|
||||||
} from "@/lib/elements";
|
} from "@/lib/elements";
|
||||||
import type { PhaseComputed, PlanComputed } from "@/lib/calculations";
|
import type { PhaseComputed, PlanComputed } from "@/lib/calculations";
|
||||||
import type { ElementInput, PlanInput } from "@/lib/types";
|
import type { ElementInput, PlanInput } from "@/lib/types";
|
||||||
@@ -73,7 +76,6 @@ type Column =
|
|||||||
|
|
||||||
type Selection =
|
type Selection =
|
||||||
| { type: "phaseCell"; elementId: string; phaseId: string }
|
| { type: "phaseCell"; elementId: string; phaseId: string }
|
||||||
| { type: "transitionCell"; elementId: string; fromPhaseId: string }
|
|
||||||
| { type: "phase"; phaseId: string };
|
| { type: "phase"; phaseId: string };
|
||||||
|
|
||||||
export function PlanView({
|
export function PlanView({
|
||||||
@@ -91,6 +93,7 @@ export function PlanView({
|
|||||||
const [showAddPhase, setShowAddPhase] = useState(false);
|
const [showAddPhase, setShowAddPhase] = useState(false);
|
||||||
const [showSettings, setShowSettings] = useState(false);
|
const [showSettings, setShowSettings] = useState(false);
|
||||||
const [reviewFromPhaseId, setReviewFromPhaseId] = useState<string | null>(null);
|
const [reviewFromPhaseId, setReviewFromPhaseId] = useState<string | null>(null);
|
||||||
|
const [editTransition, setEditTransition] = useState<{ elementId: string; fromPhaseId: string } | null>(null);
|
||||||
|
|
||||||
const columns = useMemo<Column[]>(() => {
|
const columns = useMemo<Column[]>(() => {
|
||||||
const cols: Column[] = [];
|
const cols: Column[] = [];
|
||||||
@@ -185,15 +188,20 @@ export function PlanView({
|
|||||||
const ce = computedElement(fromPhase.id, el.id);
|
const ce = computedElement(fromPhase.id, el.id);
|
||||||
if (ce && ce.status !== "ACTIVE") continue;
|
if (ce && ce.status !== "ACTIVE") continue;
|
||||||
const td = el.transitionValues[fromPhase.id] ?? {};
|
const td = el.transitionValues[fromPhase.id] ?? {};
|
||||||
if (el.category === "REAL_ESTATE" || el.category === "OTHER_ASSET") {
|
const retire = toPhase ? isRetirementTransition(el, fromPhase, toPhase) : false;
|
||||||
if (td.decision === undefined) n++;
|
if (!isTransitionAnswered(el.category, retire, td)) n++;
|
||||||
} else if (el.category === "PENSION_FUND" && isRetirementTransition(el, fromPhase, toPhase)) {
|
|
||||||
if (td.payoutMode === undefined) n++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ist der Uebergangs-Entscheid dieses Elements noch offen?
|
||||||
|
function transitionUnanswered(el: ElementInput, fromPhase: PhaseComputed, toPhase: PhaseComputed): boolean {
|
||||||
|
const ce = computedElement(fromPhase.id, el.id);
|
||||||
|
if (ce && ce.status !== "ACTIVE") return false;
|
||||||
|
const retire = isRetirementTransition(el, fromPhase, toPhase);
|
||||||
|
return !isTransitionAnswered(el.category, retire, el.transitionValues[fromPhase.id] ?? {});
|
||||||
|
}
|
||||||
|
|
||||||
function transitionElements(fromPhase: PhaseComputed): ElementInput[] {
|
function transitionElements(fromPhase: PhaseComputed): ElementInput[] {
|
||||||
return plan.elements
|
return plan.elements
|
||||||
.filter((el) => TRANSITION_CATEGORIES.includes(el.category))
|
.filter((el) => TRANSITION_CATEGORIES.includes(el.category))
|
||||||
@@ -352,20 +360,17 @@ export function PlanView({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const canTransition = TRANSITION_CATEGORIES.includes(el.category);
|
const canTransition = TRANSITION_CATEGORIES.includes(el.category);
|
||||||
const isSel =
|
const open = canTransition && transitionUnanswered(el, col.fromPhase, col.toPhase);
|
||||||
selected?.type === "transitionCell" &&
|
|
||||||
selected.elementId === el.id &&
|
|
||||||
selected.fromPhaseId === col.fromPhase.id;
|
|
||||||
return (
|
return (
|
||||||
<td
|
<td
|
||||||
key={`t-${col.fromPhase.id}`}
|
key={`t-${col.fromPhase.id}`}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
canTransition &&
|
canTransition &&
|
||||||
setSelected({ type: "transitionCell", elementId: el.id, fromPhaseId: col.fromPhase.id })
|
setEditTransition({ elementId: el.id, fromPhaseId: col.fromPhase.id })
|
||||||
}
|
}
|
||||||
className={`border-b border-r border-border px-2 py-1.5 text-center text-[11px] ${
|
className={`border-b border-r border-border px-2 py-1.5 text-center text-[11px] ${
|
||||||
canTransition ? "cursor-pointer text-accent" : "text-faint"
|
canTransition ? "cursor-pointer" : "text-faint"
|
||||||
} ${isSel ? "bg-accent-soft" : "bg-accent-soft/40"}`}
|
} ${open ? "bg-accent font-semibold text-accent-fg" : canTransition ? "bg-accent-soft/40 text-accent" : ""}`}
|
||||||
>
|
>
|
||||||
{canTransition ? transitionSummary(el, col.fromPhase, col.toPhase) : "→"}
|
{canTransition ? transitionSummary(el, col.fromPhase, col.toPhase) : "→"}
|
||||||
</td>
|
</td>
|
||||||
@@ -445,6 +450,28 @@ export function PlanView({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
|
|
||||||
|
{editTransition && (() => {
|
||||||
|
const element = plan.elements.find((e) => e.id === editTransition.elementId);
|
||||||
|
const fromPhase = computed.phases.find((p) => p.id === editTransition.fromPhaseId);
|
||||||
|
if (!element || !fromPhase) return null;
|
||||||
|
const toIndex = computed.phases.findIndex((p) => p.id === fromPhase.id) + 1;
|
||||||
|
const toPhase = computed.phases[toIndex];
|
||||||
|
const context = buildTransitionContext(fromPhase, toPhase, element);
|
||||||
|
return (
|
||||||
|
<TransitionCellDialog
|
||||||
|
element={element}
|
||||||
|
fromPhase={fromPhase}
|
||||||
|
toPhase={toPhase}
|
||||||
|
context={context}
|
||||||
|
onClose={() => setEditTransition(null)}
|
||||||
|
onSaved={() => {
|
||||||
|
setEditTransition(null);
|
||||||
|
onChanged();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -484,32 +511,15 @@ export function PlanView({
|
|||||||
const element = plan.elements.find((e) => e.id === selected.elementId);
|
const element = plan.elements.find((e) => e.id === selected.elementId);
|
||||||
if (!element) return null;
|
if (!element) return null;
|
||||||
|
|
||||||
if (selected.type === "phaseCell") {
|
// phaseCell
|
||||||
const phase = computed.phases.find((p) => p.id === selected.phaseId)!;
|
const phase = computed.phases.find((p) => p.id === selected.phaseId)!;
|
||||||
const context = buildPhaseContext(phase, element);
|
const context = buildPhaseContext(phase, element);
|
||||||
return (
|
|
||||||
<ElementDetail
|
|
||||||
element={element}
|
|
||||||
context={context}
|
|
||||||
phaseData={element.phaseValues[phase.id] ?? {}}
|
|
||||||
transitionData={{}}
|
|
||||||
onSaved={onChanged}
|
|
||||||
onDeleteElement={() => deleteElement(element.id)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// transitionCell
|
|
||||||
const fromPhase = computed.phases.find((p) => p.id === selected.fromPhaseId)!;
|
|
||||||
const toIndex = computed.phases.findIndex((p) => p.id === fromPhase.id) + 1;
|
|
||||||
const toPhase = computed.phases[toIndex];
|
|
||||||
const context = buildTransitionContext(fromPhase, toPhase, element);
|
|
||||||
return (
|
return (
|
||||||
<ElementDetail
|
<ElementDetail
|
||||||
element={element}
|
element={element}
|
||||||
context={context}
|
context={context}
|
||||||
phaseData={{}}
|
phaseData={element.phaseValues[phase.id] ?? {}}
|
||||||
transitionData={element.transitionValues[fromPhase.id] ?? {}}
|
transitionData={{}}
|
||||||
onSaved={onChanged}
|
onSaved={onChanged}
|
||||||
onDeleteElement={() => deleteElement(element.id)}
|
onDeleteElement={() => deleteElement(element.id)}
|
||||||
/>
|
/>
|
||||||
@@ -533,10 +543,12 @@ export function PlanView({
|
|||||||
if (isRetirementTransition(el, fromPhase, toPhase)) {
|
if (isRetirementTransition(el, fromPhase, toPhase)) {
|
||||||
return td.payoutMode === "CAPITAL" ? "Kapital" : td.payoutMode === "COMBI" ? "Kombi" : td.payoutMode === "PENSION" ? "Rente" : "?";
|
return td.payoutMode === "CAPITAL" ? "Kapital" : td.payoutMode === "COMBI" ? "Kombi" : td.payoutMode === "PENSION" ? "Rente" : "?";
|
||||||
}
|
}
|
||||||
return num(td.withdrawal) > 0 ? `−${formatChf(num(td.withdrawal))}` : "→";
|
if (td.withdrawalMode === undefined) return "?";
|
||||||
|
return td.withdrawalMode === "AMOUNT" && num(td.withdrawal) > 0 ? `−${formatChf(num(td.withdrawal))}` : "Kein Bezug";
|
||||||
case "PILLAR_3A":
|
case "PILLAR_3A":
|
||||||
if (isRetirementTransition(el, fromPhase, toPhase)) return "Bezug";
|
if (isRetirementTransition(el, fromPhase, toPhase)) return "Bezug";
|
||||||
return num(td.withdrawal) > 0 ? `−${formatChf(num(td.withdrawal))}` : "→";
|
if (td.withdrawalMode === undefined) return "?";
|
||||||
|
return td.withdrawalMode === "AMOUNT" && num(td.withdrawal) > 0 ? `−${formatChf(num(td.withdrawal))}` : "Kein Bezug";
|
||||||
case "OTHER_DEBT":
|
case "OTHER_DEBT":
|
||||||
return num(td.immediateRepayment) > 0 ? "Tilgung" : "→";
|
return num(td.immediateRepayment) > 0 ? "Tilgung" : "→";
|
||||||
default:
|
default:
|
||||||
@@ -881,8 +893,10 @@ function TransitionReviewDialog({
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSaved: () => void;
|
onSaved: () => void;
|
||||||
}) {
|
}) {
|
||||||
const [tds, setTds] = useState<Record<string, import("@/lib/elements").TransitionData>>(() =>
|
const [tds, setTds] = useState<Record<string, TransitionData>>(() =>
|
||||||
Object.fromEntries(elements.map((e) => [e.id, { ...(e.transitionValues[fromPhase.id] ?? {}) }]))
|
Object.fromEntries(
|
||||||
|
elements.map((e) => [e.id, withTransitionDefaults(e.category, isRetirement(e), e.transitionValues[fromPhase.id] ?? {})])
|
||||||
|
)
|
||||||
);
|
);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -951,6 +965,60 @@ function TransitionReviewDialog({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Dialog: einzelner Übergangs-Entscheid (per Klick auf eine Übergangszelle) ---
|
||||||
|
function TransitionCellDialog({
|
||||||
|
element,
|
||||||
|
fromPhase,
|
||||||
|
toPhase,
|
||||||
|
context,
|
||||||
|
onClose,
|
||||||
|
onSaved,
|
||||||
|
}: {
|
||||||
|
element: ElementInput;
|
||||||
|
fromPhase: PhaseComputed;
|
||||||
|
toPhase: PhaseComputed | undefined;
|
||||||
|
context: CellContext;
|
||||||
|
onClose: () => void;
|
||||||
|
onSaved: () => void;
|
||||||
|
}) {
|
||||||
|
const [td, setTd] = useState<TransitionData>(() =>
|
||||||
|
withTransitionDefaults(element.category, context.isRetirementTransition, element.transitionValues[fromPhase.id] ?? {})
|
||||||
|
);
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
setSaving(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
await api.put(`/api/elements/${element.id}/transition/${fromPhase.id}`, td);
|
||||||
|
onSaved();
|
||||||
|
} catch (e) {
|
||||||
|
setError(e instanceof Error ? e.message : "Speichern fehlgeschlagen.");
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogShell title={`Übergang: ${element.name}`} onClose={onClose}>
|
||||||
|
<div className="text-xs text-muted">
|
||||||
|
{CATEGORY_LABELS[element.category]} · {fromPhase.name} → {toPhase?.name ?? "Ende"}
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||||
|
<ElementTransitionFields
|
||||||
|
element={element}
|
||||||
|
context={context}
|
||||||
|
td={td}
|
||||||
|
setT={(patch) => setTd((prev) => ({ ...prev, ...patch }))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{error && <p className="text-sm text-danger">{error}</p>}
|
||||||
|
<DialogActions saving={saving} onConfirm={save} onClose={onClose} confirmLabel="Speichern" />
|
||||||
|
</DialogShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// --- gemeinsame Dialog-Bausteine ---
|
// --- gemeinsame Dialog-Bausteine ---
|
||||||
function DialogShell({
|
function DialogShell({
|
||||||
title,
|
title,
|
||||||
|
|||||||
+3
-1
@@ -74,7 +74,8 @@ export type TransitionDecision = "HOLD" | "SELL";
|
|||||||
export type PkPayoutMode = "CAPITAL" | "PENSION" | "COMBI";
|
export type PkPayoutMode = "CAPITAL" | "PENSION" | "COMBI";
|
||||||
|
|
||||||
export interface TransitionData {
|
export interface TransitionData {
|
||||||
// PENSION_FUND / PILLAR_3A (normaler Uebergang)
|
// PENSION_FUND / PILLAR_3A (normaler Uebergang): expliziter Bezugs-Entscheid.
|
||||||
|
withdrawalMode?: "NONE" | "AMOUNT";
|
||||||
withdrawal?: number;
|
withdrawal?: number;
|
||||||
// PENSION_FUND (Pensions-Uebergang)
|
// PENSION_FUND (Pensions-Uebergang)
|
||||||
payoutMode?: PkPayoutMode;
|
payoutMode?: PkPayoutMode;
|
||||||
@@ -112,6 +113,7 @@ export const phaseDataSchema = z
|
|||||||
|
|
||||||
export const transitionDataSchema = z
|
export const transitionDataSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
withdrawalMode: z.enum(["NONE", "AMOUNT"]).optional(),
|
||||||
withdrawal: nonNeg.optional(),
|
withdrawal: nonNeg.optional(),
|
||||||
payoutMode: z.enum(["CAPITAL", "PENSION", "COMBI"]).optional(),
|
payoutMode: z.enum(["CAPITAL", "PENSION", "COMBI"]).optional(),
|
||||||
capitalAmount: nonNeg.optional(),
|
capitalAmount: nonNeg.optional(),
|
||||||
|
|||||||
Reference in New Issue
Block a user