Kapitalbezug: Personenzuordnung, Herkunft und Filter fuer bezogene Guthaben
Deploy App / deploy (push) Successful in 1m9s
Deploy App / deploy (push) Successful in 1m9s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+37
-7
@@ -4,10 +4,10 @@
|
||||
| | |
|
||||
|---|---|
|
||||
| **Dokument** | Funktionale und Technische Spezifikation FPT |
|
||||
| **Version** | 0.33 |
|
||||
| **Version** | 0.34 |
|
||||
| **Datum** | 2026-07-25 |
|
||||
| **Status** | Lebendes Dokument |
|
||||
| **Codestand** | Arbeitsstand nach `0761f6b` inkl. Modul-Review 4 (Matrix) (Branch `main`) |
|
||||
| **Codestand** | Arbeitsstand nach `0953880` inkl. Modul-Review 4 (Nachbesserungen) (Branch `main`) |
|
||||
| **Ersetzt** | `FDD_TDD_FPT.docx` (v1–v5) im Ordner `Info Dateien` – diese sind ab Version 0.1 dieses Dokuments obsolet |
|
||||
| **Geltungsbereich** | Gesamter Code im Verzeichnis `FPT` |
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
| Version | Datum | Autor | Änderung |
|
||||
|---|---|---|---|
|
||||
| 0.34 | 2026-07-25 | Claude (Opus 5) | **Modul-Review 4, Nachbesserungen: die Übergangs-Entscheide bis ans Ende durchgezogen.** (1) **Zuordnung überall dort, wo Elemente über ihren Namen angeboten werden.** Zwei Personen nennen ihre Guthaben typischerweise gleich («Säule 3a», «ETF»); ohne die Person wählt man im Dropdown blind. Betroffen waren das **Ziel der Anlage-Quote** beim Kapitalbezug (dort mit hoher Folgewirkung: Ein Fehlgriff leitet das Alterskapital in das Depot der falschen Person) und die Zeilen im Dialog **«Kapital verteilen»**. Die Klartext-Zuordnung liegt neu als `ownerLabel` in `src/lib/elements.ts` und wird von allen drei Stellen genutzt. (2) **Herkunft des umgeleiteten Alterskapitals wird ausgewiesen.** Fliessen PK **und** 3a in dasselbe Vermögens-Element, stand dort bisher nur eine Summe – ob wirklich beide angekommen sind, liess sich nicht prüfen. `Carry` und `ElementPhaseComputed` führen neu `capitalInSources` bzw. `capitalFromTransferSources` mit: Betrag **je Quelle**, benannt mit Element **und** Person. Sichtbar am Ziel-Element und im Dialog «Kapital verteilen». Das Feld heisst neu **«Zusatzinvestition aus Kapitalbezug»** (vorher «Davon aus Kapitalbezug (PK/3a)» – irreführend, weil es kein Anteil an der manuell erfassten Zusatzinvestition ist, sondern ein zweiter, davon unabhängiger Betrag). (3) **Der Dialog «Kapital verteilen» zeigt das bereits Zugeteilte.** Vorher stand dort eine **0**, obwohl die Quote geflossen war – das Feld führt nur den manuell erfassten Teil. Neu erscheint darüber eine read-only Zeile mit dem aus dem Bezugs-Entscheid stammenden Betrag samt Aufschlüsselung, darunter das editierbare Feld und die Summe beider. (4) **Bezogene Vorsorge-Guthaben werden in beiden Verteil-Dialogen nicht mehr angeboten.** Nach der Pensionierung ignoriert die Rechnung Beiträge und Zusatzeinlagen in PK und Säule 3a – die Dialoge boten sie trotzdem an, inklusive eines aus der Vorphase geerbten 3a-Beitrags, der dort als aktive Rate erschien. Der Filter prüfte nur den `status` (`ACTIVE`), und der bleibt nach dem Bezug bestehen. Neu setzt die Rechnung selbst das Kennzeichen `acceptsCapital: false`; die Dialoge lesen es, statt die Regel ein zweites Mal nachzubauen. 4 Tests ergänzt (288 → 292). |
|
||||
| 0.33 | 2026-07-25 | Claude (Opus 5) | **Modul-Review 4 (Matrix: Phasen und Elemente).** (1) **Kapitalverwendung neu am Vorsorge-Element** (Punkt C aus Roadmap Nr. 44): Die Prozent-Aufteilung des bezogenen Alterskapitals hing am **Cash-Übergang** – dem falschen Ort, denn mit zwei Guthaben (PK und 3a) liess sie sich dort gar nicht getrennt beantworten. Sie steht jetzt beim **Bezugs-Entscheid** der Pensionskasse (nur bei Kapitalbezug) bzw. der **Säule 3a**. Beide Dialoge führen neu **brutto → Steuersatz → netto** und darunter die Verteilung. Der zugeteilte Betrag fliesst über den regulären Weg (`Carry.capitalIn` → Zusatzeinlage der Folgephase) und ist damit **überall sichtbar**: am Ziel-Element, in der Cash-Brücke als Investition und im «Kapital verteilen»-Dialog. Vorher erhöhte er still den Bestand, weshalb Element und Dialog eine **0** zeigten. Die **Säule 3a** ist am Pensions-Übergang neu ein **offener Entscheid** (Steuersatz und Verwendung); vorher galt sie als automatisch beantwortet. (2) **Phasendauer: die Folgephase gleicht aus** (Kap. 3.3.2). Bis 0.32 prüfte die Kappung nur die **bearbeitete** Phase – wurde Phase 1 von 10 auf 12 Jahre verlängert, überspannte danach Phase 2 die Pensionierung, und die tragende Invariante aus Roadmap Nr. 44 kippte. Neu trägt die Folgephase die Differenz (Gesamtdauer bleibt gleich, wie beim Verschieben des Pensionsalters); passt sie nicht, wird blockiert; vorher erscheint eine Rückfrage. Neue reine Funktion `planDurationChange`. (3) **Element und Phase direkt bedienbar:** In der Matrix tragen Element-Zeile und Phasenkopf neu **Stift** (umbenennen, beim Element inkl. **Zuordnung**) und **Papierkorb**; das Expand-Symbol ist **immer** sichtbar statt nur bei Mouseover. `PATCH /api/elements/<id>` nimmt dafür neu auch `ownerRole` (bleibt für AHV/PK/3a personengebunden). (4) **Hilfetexte** werden über ein **Portal** gezeichnet – in scrollenden Dialogen schnitt der Container sie vorher ab; sie klappen nach oben, wenn unten kein Platz ist. (5) **Verteil-Dialoge:** Zeilen zeigen die **Zuordnung** (Person A/B/Gemeinsam) und sind nach **«vom Cash»/«ins Cash»** gruppiert; die Vorbelegung nutzt neu den **effektiven** Wert inklusive Vererbung aus der Vorphase – ein geerbter 3a-Beitrag erschien vorher als 0. (6) **Matrix:** alle Phasenspalten **gleich breit**, bei vielen Phasen wird horizontal gescrollt; **«Alle auf-/zuklappen»**; eine zugeklappte Kategorie zeigt je Phase die **Summe** ihrer Elemente. (7) **Phasen-Detailansicht** nutzt die neue Aufteilungs-Grafik (Fläche + Ring) statt der alten Balken. (8) **Übersicht:** «Leer starten» steht neu auch im leeren Zustand zur Wahl. (9) Nebenbei: dritte vom Umlaut-Sweep verstümmelte Hex-Farbe (`#7c3äd`) repariert, das Phasen-Panel nutzt den eigenen Bestätigungs-Dialog statt `window.confirm`. 10 Tests ergänzt (278 → 288). |
|
||||
| 0.32 | 2026-07-25 | Claude (Opus 5) | **Modul-Review 3, Nachbesserungen – darunter ein gravierender Rechenfehler bei den effektiven Werten.** (1) **Immobilien-Bugfix (Kap. 3.9):** Der Ist-Wizard belegte den Immobilienwert mit dem **Eigenkapital** vor (`ElementYearPoint.value`), während Erfassung und Rechenkern den **Verkehrswert** erwarten. Der Rechenkern setzte den vorbelegten Wert als Verkehrswert ein, liess die Hypothek aber stehen – das Eigenkapital brach im Ist-Jahr schlagartig ein, typischerweise ins Negative. Sichtbar wurde das als **negative Gesamt-Abweichung, obwohl nur ein Lohn erhöht** wurde, und als «wegbrechendes» Wohneigentum in der Vermögensaufteilung. Neu wird `propertyValue` vorbelegt; das Feld ist als «Verkehrswert + Restschuld» beschriftet. Drei Regressionstests. (2) **Ist-Datensätze bearbeitbar:** Ein Klick auf die Zeile (oder «Bearbeiten») öffnet den erfassten Satz erneut; neuer Endpunkt `PUT /api/plans/<id>/actuals/<setId>`. Beim Bearbeiten überschreiben die Planwerte die erfassten Zahlen nicht mehr. (3) **Ring-Klick in der Vermögensaufteilung repariert:** Recharts 3 reicht im Klick-Parameter **kein `activePayload`** mehr durch (nur noch `activeIndex`) – der Handler feuerte nie, der Ring zeigte immer das Planende. (4) **Seitenleiste sauber dreistufig:** Ebene 1 Pläne, Ebene 2 die vier Bereiche (Szenarien, Effektive Werte, Analysen, Berichte) mit **bündigen Symbolen**, Ebene 3 nur die Szenarien – verschachtelt nach Herkunft. (5) Die **Szenario-Liste** zeigt neben der Version deren **Kommentar**. |
|
||||
| 0.31 | 2026-07-25 | Claude (Opus 5) | **Modul-Review 3 (Plan-/Szenario-Struktur, Dashboard, Grafiken).** (1) **Versionierung startet bei 0.1** statt 1.0 (Kap. 3.8): Ein Szenario läuft in der 0er-Reihe (0.1, 0.2, … 0.137), bis eine **Hauptversion** gesetzt wird – erst dann entsteht 1.0. Vorher begann jedes Szenario bereits bei 1.0, wodurch die Hauptversion ihre Bedeutung verlor. Die Szenario-Liste zeigt neu die **echte** Version statt «1.x», dazu eine Spalte **Phasen**. (2) **Plan-Dashboard:** Kacheln sind **anklickbar** und führen in ihren Bereich, neu inkl. **Berichte**; der Plan lässt sich über ein Stift-Symbol **umbenennen**; die Ist-Abweichung nennt das **Jahr** des jüngsten Ist-Datensatzes und ist bei einer positiven Abweichung **grün** statt rot. (3) **Szenario-Liste:** Ein Klick auf die **Zeile** öffnet die Matrix (der «Matrix»-Knopf entfällt), dazu je Zeile **Kopie** und **Löschen**; die Kopiervorlage ist damit frei wählbar und nicht mehr auf das Basisszenario festgelegt. Nach einer Löschung lädt die Liste neu (zeigte vorher den alten Stand). (4) **Seitenleiste:** Szenarien werden wieder **verschachtelt** dargestellt (Tiefe = Herkunftskette); «Effektive Werte», «Analysen» und «Berichte» stehen neu **bündig zum Knoten «Szenarien»** statt auf Höhe der einzelnen Szenarien. (5) **CSV-Export vollständig neu** (Kap. 3.6.5, neues Modul `csv.ts`): vier Blöcke – Kopf, Lebensphasen, **die ganze Matrix** (Elemente × Phasen mit Beginn/Ende und den Übergangs-Entscheiden im Klartext) und **Jahreswerte**; mit BOM, damit Excel die Umlaute erkennt. Vorher enthielt die Datei kein einziges finanzielles Element. (6) **Grafiken:** Ein **Szenario-Wähler** gilt neu für **alle drei** Grafiken (vorher nur der Vermögensverlauf, und der nur additiv). Der **Vergleichs-Fehler** ist behoben: `WealthChart` benutzte den Szenario-**Namen** als Datenschlüssel, wodurch sich gleichnamige Szenarien gegenseitig überschrieben (Legende zeigte beide, der Chart nur eine) – neu die **ID**; die stille Deckelung auf vier Serien entfällt. Die **Legende** ist eigenständig, erlaubt eine **freie Farbwahl je Serie** und erklärt den Linienstil (gestrichelt = Plan, durchgezogen = effektiv). Die **Vermögensaufteilung** ist neu eine **gestapelte Fläche über alle Planjahre** plus ein **Ring** für die relative Aufteilung zu einem wählbaren Zeitpunkt (vorher gestapelte Balken je Phase mit schräger Beschriftung). **Alle Diagrammfarben** kommen aus neuen Theme-Tokens (`--chart-1` … `--chart-6`, `--chart-grid`) statt fester Hex-Werte. (7) **Dokumentation nachgezogen:** Die Kapitel 2.1, 3.2.2–3.2.7 und 3.10 beschrieben noch den Stand **vor V7** (Grundprofil am Szenario, `parentPlanId`, `Scenario.startYear`, `window.confirm`, drei Sidebar-Unterpunkte). (8) Nebenbei: verstümmelte Hex-Farbe `--danger-soft` im Warm-Schema repariert, deutsche Plural-/Umlautfehler in den Übersichts-Kacheln, Dateiname des CSV-Exports transliteriert Umlaute statt sie zu `_` zu machen. 8 Tests ergänzt (267 → 275). |
|
||||
@@ -1190,6 +1191,11 @@ bestehende Felder** über die bestehenden Endpunkte – an der Berechnung änder
|
||||
| Immobilie | `extraAmortization` | dem **Übergang davor** |
|
||||
| Sonstige Schulden | `immediateRepayment` | dem **Übergang davor** |
|
||||
|
||||
Jede Zeile nennt neben dem Namen die **Zuordnung** (Person A / Person B / Gemeinsam) – ohne sie
|
||||
sind zwei gleichnamige Guthaben nicht unterscheidbar. **Bezogene** PK- und 3a-Guthaben erscheinen
|
||||
gar nicht mehr (Kennzeichen `acceptsCapital`, siehe 3.12.5): Die Rechnung ignoriert dort jede
|
||||
Einlage, ein Eingabefeld wäre also folgenlos.
|
||||
|
||||
Dass zwei verschiedene Objekte beschrieben werden, ist eine Folge des Datenmodells: Die
|
||||
Zusatzeinlage ist ein Phasenwert, Sonderamortisation und Sofort-Tilgung sind Übergangs-Entscheide.
|
||||
Beide zehren aber vom selben Topf. Betragsfelder werden dabei in die bestehenden Daten
|
||||
@@ -1200,6 +1206,12 @@ Was nicht verteilt wird, **bleibt automatisch auf dem Cash** – dafür braucht
|
||||
ist das Verhalten des Modells. Wird mehr verteilt als vorhanden, startet die Folgephase mit
|
||||
negativem Cash; der Dialog weist das als Liquiditätslücke aus.
|
||||
|
||||
**Bereits zugeteiltes Alterskapital steht read-only darüber.** Das Eingabefeld führt nur den
|
||||
hier erfassten Teil; was aus der Prozent-Quote des Bezugs-Entscheids stammt
|
||||
([3.12.5](#3125-punkt-c-verwendung-des-bezogenen-alterskapitals)), liegt daneben und ist nach
|
||||
Quelle aufgeschlüsselt. Bis 0.33 fehlte diese Zeile: Man sah eine **0** und hielt die Quote für
|
||||
wirkungslos.
|
||||
|
||||
**«Sparquote verteilen»** (bzw. **«Bezug verteilen»** bei Verzehr) verteilt die laufende Quote auf
|
||||
jährliche Raten: `annualContribution` (3a, Sonstiges Vermögen), `annualWithdrawal` (Sonstiges
|
||||
Vermögen), `amortization` (Immobilie), `annualRepayment` (Schulden).
|
||||
@@ -1208,6 +1220,10 @@ Vermögen), `amortization` (Immobilie), `annualRepayment` (Schulden).
|
||||
> das Cash-Konto nicht ([4.6.3](#463-pension_fund)) – er lässt sich also gar nicht aus der Quote
|
||||
> verteilen.
|
||||
|
||||
Auch hier gilt der Filter über `acceptsCapital`: Eine **bezogene Säule 3a** nimmt keine Einzahlung
|
||||
mehr auf und erscheint deshalb nicht. Bis 0.33 stand sie in der Liste – mitsamt dem aus der
|
||||
Vorphase geerbten Beitrag, der als aktive Rate aussah, obwohl die Rechnung ihn längst verwarf.
|
||||
|
||||
Der Dialog weist **drei** Bezugsgrössen aus: Quote im ersten Jahr, im letzten Jahr und –
|
||||
entscheidend – die **absolute Quote über die ganze Phase**. Letztere ist die Grösse, gegen die
|
||||
sich eine flache Jahresrate sinnvoll verteilen lässt (siehe [9.25](#925-die-quote-ist-kein-fester-betrag)).
|
||||
@@ -1973,9 +1989,23 @@ eine Zusatzeinlage, und beide laufen dadurch korrekt durch die zwei Wasserfall-B
|
||||
|
||||
**Der zugeteilte Betrag ist überall sichtbar.** Er wandert über `Carry.capitalIn` in die
|
||||
**Zusatzeinlage der Folgephase** und erscheint dadurch am Ziel-Element (als eigene, read-only
|
||||
Zeile «Davon aus Kapitalbezug»), in der Cash-Brücke als **Investition** und im Dialog «Kapital
|
||||
verteilen». Bis 0.32 erhöhte die Verteilung direkt den internen Bestand – Element und Dialog
|
||||
zeigten deshalb eine **0**, obwohl das Geld geflossen war.
|
||||
Zeile «Zusatzinvestition aus Kapitalbezug»), in der Cash-Brücke als **Investition** und im
|
||||
Dialog «Kapital verteilen» (dort über dem Eingabefeld, das nur den **manuell** erfassten Teil
|
||||
führt). Bis 0.32 erhöhte die Verteilung direkt den internen Bestand – Element und Dialog
|
||||
zeigten deshalb eine **0**, obwohl das Geld geflossen war; bis 0.33 fehlte sie im Dialog.
|
||||
|
||||
**Die Herkunft wird mitgeführt.** `Carry.capitalIn` hat als Gegenstück `capitalInSources` –
|
||||
Betrag je Bezug, benannt mit Element **und** Person; am Ziel-Element liegt das Ergebnis als
|
||||
`capitalFromTransferSources`. Ohne diese Aufschlüsselung ist bei zwei Guthaben (PK und 3a, ggf.
|
||||
beider Personen) im selben Ziel nicht prüfbar, ob alles angekommen ist – der Grund, weshalb
|
||||
das **Ziel-Dropdown** die Zuordnung nennen muss: Zwei gleichnamige Depots sind sonst nicht
|
||||
unterscheidbar, und ein Fehlgriff leitet das Alterskapital an die falsche Person.
|
||||
|
||||
**Bezogene Guthaben nehmen nichts mehr auf.** Nach der Pensionierung ignoriert die Rechnung
|
||||
Beiträge und Zusatzeinlagen in PK und Säule 3a. Damit die Verteil-Dialoge diese Regel nicht ein
|
||||
zweites Mal nachbauen (und dabei abweichen), setzt die Rechnung das Kennzeichen
|
||||
`ElementPhaseComputed.acceptsCapital = false`; die Dialoge blenden solche Zeilen aus. Der
|
||||
frühere Filter auf `status === "ACTIVE"` griff nicht: Der Status bleibt nach dem Bezug bestehen.
|
||||
|
||||
**Die Säule 3a ist am Pensions-Übergang ein offener Entscheid.** Der Bezug selbst steht fest
|
||||
(sie wird immer ausbezahlt), zu entscheiden sind Steuersatz und Verwendung. Vorher galt der
|
||||
@@ -3994,14 +4024,14 @@ Include `src/**/*.test.ts`). Es gibt **keine** Komponenten-, API- oder E2E-Tests
|
||||
| `report.test.ts` | 9 | Zusammenfassung, Basis-Angabe zu JEDER Kennzahl, Szenario-Deckelung, Plan/Ist-Block, Haftungsausschluss, gültige PDF-Datei |
|
||||
| `livesim.test.ts` | 15 | Element-Regler bewegt genau ein Element; Aufschlüsselung deckt sich mit dem Sammelregler; neutrale Stellung verändert den Plan nicht; Ruinmeldung |
|
||||
| `phaseplan.test.ts` | 14 | Ableitung der Lebensabschnitte aus den Pensionierungszeitpunkten (Einzel/Paar/bereits pensioniert); letzter Teil immer offen |
|
||||
| `bridges.test.ts` | 17 | Vermögens- und Cash-Brücke gehen über acht Plankonstellationen ohne Restgrösse auf; Umbuchungen bleiben aus der Vermögensbrücke heraus; Kapitalverwendung nach Quote (Punkt C) |
|
||||
| `bridges.test.ts` | 21 | Vermögens- und Cash-Brücke gehen über acht Plankonstellationen ohne Restgrösse auf; Umbuchungen bleiben aus der Vermögensbrücke heraus; Kapitalverwendung nach Quote (Punkt C) inkl. Herkunft je Quelle und `acceptsCapital` |
|
||||
| `retirement.test.ts` | 16 | Spielraum und Sperren je Person, Verschiebung ohne Änderung der Gesamtdauer, Wegfall einer Phase, Zusammenführung der Übergangs-Entscheide |
|
||||
| `server-boundary.test.ts` | 1 | statischer Wächter: kein Modul unter `src/lib` importiert aus `src/components` |
|
||||
| `diff.test.ts` | 9 | Abweichungs-Erkennung gegen das Eltern-Szenario |
|
||||
| `migrations.test.ts` | 3 | spielt alle Migrationen gegen echtes PostgreSQL (PGlite) ein; prüft zusätzlich die V7-**Datenübernahme** (Basisszenario gewinnt, Pensionsalter bleiben szenario-eigen) |
|
||||
| `rate-limit.test.ts` | 6 | Fixed-Window: erlaubt bis Limit, blockt danach, startet nach Fensterablauf neu, trennt je Schlüssel; Client-IP aus X-Forwarded-For / X-Real-IP |
|
||||
| `csv.test.ts` | 7 | BOM, alle vier Blöcke, jedes Element als Zeile, Beginn-/Ende-/Übergangsspalten, Entscheid im Klartext, ein Eintrag je Planjahr, Maskierung von `;` und `"` |
|
||||
| **Total** | **288** | |
|
||||
| **Total** | **292** | |
|
||||
|
||||
## 8.2 Testfälle
|
||||
|
||||
|
||||
Reference in New Issue
Block a user