c429624635
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>
85 lines
3.4 KiB
TypeScript
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;
|
|
}
|