// 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; }