Fix: Server/Client-Grenze -- Uebergangsregeln nach lib verschoben
Deploy App / deploy (push) Successful in 1m5s

Ursache des 500 (danke fuer die Meldung): lib/decisions.ts importierte
isTransitionAnswered und isCashTransitionAnswered aus der CLIENT-
Komponente ElementDetail. Der PDF-Bericht laeuft serverseitig und brach
deshalb ab: "Attempted to call isCashTransitionAnswered() from the server
but it is on the client."

Die fuenf reinen Uebergangs-Regeln liegen jetzt in lib/transitions.ts;
ElementDetail reicht sie nur noch weiter, damit bestehende Importe
unveraendert bleiben.

Neuer Waechter-Test: kein Modul unter src/lib darf aus src/components
importieren. Gegen den echten Fehler verifiziert -- er schlaegt an.
Weder tsc noch der Build hatten das gemeldet, nur die Produktion.

225 Tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 10:15:54 +02:00
parent 8b50f8d4ae
commit c429624635
4 changed files with 150 additions and 66 deletions
+9 -65
View File
@@ -6,6 +6,15 @@ import { AlertTriangle } from "lucide-react";
import { FieldLabel, MoneyField, NumberField, SelectField, TextField } from "@/components/FormField";
import { formatChf } from "@/lib/format";
import { api } from "@/lib/api-client";
// Die reinen Uebergangs-Regeln liegen in lib/transitions.ts (auch serverseitig nutzbar) und
// werden hier nur weitergereicht, damit bestehende Importe unveraendert bleiben.
export {
withTransitionDefaults,
isTransitionAnswered,
withCashTransitionDefaults,
isCashTransitionAnswered,
cashTransitionSummary,
} from "@/lib/transitions";
import {
buildRateWrites,
changedRateFields,
@@ -229,53 +238,6 @@ export function AhvReviewFields({
);
}
export function withTransitionDefaults(category: ElementCategory, isRetirement: boolean, td: TransitionData): TransitionData {
const out = { ...td };
if (category === "AHV") {
// Ein Speichern der Prüfung markiert sie als erledigt (gleiches Muster wie bei PK/3a).
if (isRetirement && out.reviewed === undefined) out.reviewed = true;
} else 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;
}
// --- Cash-Übergang: einmalige Sonderein-/ausgaben ---
export function withCashTransitionDefaults(ct: CashTransitionData): CashTransitionData {
return ct.mode === undefined ? { ...ct, mode: "NONE" } : { ...ct };
}
export function isCashTransitionAnswered(ct: CashTransitionData): boolean {
return ct.mode !== undefined;
}
// Kurzfassung für die Übergangszelle der Cash-Zeile.
export function cashTransitionSummary(ct: CashTransitionData): string {
const mode = ct.mode;
if (mode === undefined) return "?";
const inn = `+${formatChf(num(ct.inflowAmount))}`;
const out = `${formatChf(num(ct.outflowAmount))}`;
switch (mode) {
case "INFLOW":
return inn;
case "OUTFLOW":
return out;
case "BOTH":
return `${inn} / ${out}`;
default:
return "1:1";
}
}
// Eingabefelder für den Cash-Entscheid. Erfassungs-Konventionen bewusst wie bei den
// laufenden Flows: Zufluss nominal (wie Einkommen), Kosten real (wie Ausgaben).
export function CashTransitionFields({
@@ -389,24 +351,6 @@ export function CashTransitionFields({
);
}
// "Beantwortet" = ein konkreter Entscheid liegt vor (kein offenes Fragezeichen).
export function isTransitionAnswered(category: ElementCategory, isRetirement: boolean, td: TransitionData): boolean {
switch (category) {
case "AHV":
// Nur bei der Pensionierung ist eine Prüfung der Beitragskarriere nötig.
return isRetirement ? td.reviewed === true : true;
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, Übergangs-Review) ---
export function ElementPhaseFields({