Files
FPT/src/lib/decisions.ts
T
admGitAICDS c429624635
Deploy App / deploy (push) Successful in 1m5s
Fix: Server/Client-Grenze -- Uebergangsregeln nach lib verschoben
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>
2026-07-21 10:15:54 +02:00

85 lines
3.4 KiB
TypeScript

// Offene Übergangs-Entscheide (SPEZIFIKATION 3.5.4).
//
// Die Zählung lag bisher lokal in `PlanView`. Sie wird jetzt auch vom Bericht gebraucht --
// und zwei Umsetzungen derselben Regel driften garantiert auseinander. Deshalb hier als
// reine Funktion, die beide benutzen.
// WICHTIG: aus lib/transitions, NICHT aus der Client-Komponente ElementDetail. Ein Import
// von dort macht diese Datei serverseitig unbenutzbar ("Attempted to call ... from the
// server but it is on the client") -- genau daran scheiterte der PDF-Bericht.
import { isCashTransitionAnswered, isTransitionAnswered } from "@/lib/transitions";
import type { ElementCategory } from "@/lib/elements";
import type { ElementInput, PlanInput } from "@/lib/types";
import type { PhaseComputed, PlanComputed } from "@/lib/calculations";
export const TRANSITION_CATEGORIES: ElementCategory[] = [
"AHV",
"PENSION_FUND",
"PILLAR_3A",
"REAL_ESTATE",
"OTHER_ASSET",
"OTHER_DEBT",
];
// Wechselt der Besitzer dieses Elements an DIESER Grenze in die Pension? Entscheidet, ob
// z. B. ein PK-Bezug überhaupt zur Wahl steht.
export function isRetirementTransition(
element: ElementInput,
fromPhase: PhaseComputed,
toPhase: PhaseComputed
): boolean {
if (!element.ownerRole || element.ownerRole === "HOUSEHOLD") return false;
const before = fromPhase.persons.find((p) => p.role === element.ownerRole);
const after = toPhase.persons.find((p) => p.role === element.ownerRole);
return !!before?.working && !!after && !after.working;
}
// Steht an dieser Grenze für dieses Element gar kein Entscheid an (bereits verkauft/getilgt,
// AHV ausserhalb des Pensions-Übergangs, PK/3a einer bereits pensionierten Person)?
export function transitionInactive(
computed: PlanComputed,
el: ElementInput,
fromPhase: PhaseComputed,
toPhase?: PhaseComputed
): boolean {
const ce = computed.phases.find((p) => p.id === fromPhase.id)?.elements.find((e) => e.elementId === el.id);
if (ce && ce.status !== "ACTIVE") return true;
if (el.category === "AHV") {
return !(toPhase && isRetirementTransition(el, fromPhase, toPhase));
}
if (el.category === "PENSION_FUND" || el.category === "PILLAR_3A") {
if (el.ownerRole && el.ownerRole !== "HOUSEHOLD") {
const owner = fromPhase.persons.find((p) => p.role === el.ownerRole);
if (owner && !owner.working) return true;
}
}
return false;
}
// Offene Entscheide an EINER Phasengrenze. Der Cash-Entscheid zählt mit.
export function openTransitionCount(
plan: PlanInput,
computed: PlanComputed,
fromPhase: PhaseComputed,
toPhase: PhaseComputed
): number {
const cash = plan.phases.find((p) => p.id === fromPhase.id)?.cashTransition ?? {};
let n = isCashTransitionAnswered(cash) ? 0 : 1;
for (const el of plan.elements) {
if (!TRANSITION_CATEGORIES.includes(el.category)) continue;
if (transitionInactive(computed, el, fromPhase, toPhase)) continue;
const td = el.transitionValues[fromPhase.id] ?? {};
if (!isTransitionAnswered(el.category, isRetirementTransition(el, fromPhase, toPhase), td)) n++;
}
return n;
}
// Summe über alle Phasengrenzen -- die aktionierbarste Kennzahl des ganzen Werkzeugs.
export function totalOpenDecisions(plan: PlanInput, computed: PlanComputed): number {
let n = 0;
for (let i = 0; i < computed.phases.length - 1; i++) {
n += openTransitionCount(plan, computed, computed.phases[i], computed.phases[i + 1]);
}
return n;
}