docs: A13 – höchstens 8 benannte Tortensegmente (Nutzerentscheidung)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,9 +16,9 @@
|
|||||||
- **Geld = `decimal.Decimal`** überall. Einzige Ausnahme: Winkel/Koordinaten in `engine/pie.py` (reine Geometrie) dürfen `float` sein; `Slice.value`/`Slice.share` bleiben `Decimal`.
|
- **Geld = `decimal.Decimal`** überall. Einzige Ausnahme: Winkel/Koordinaten in `engine/pie.py` (reine Geometrie) dürfen `float` sein; `Slice.value`/`Slice.share` bleiben `Decimal`.
|
||||||
- **Deutsch** für GUI-Texte, Meldungen, Kommentare, Commit-Messages; Monatsanzeige `MM.JJJJ`.
|
- **Deutsch** für GUI-Texte, Meldungen, Kommentare, Commit-Messages; Monatsanzeige `MM.JJJJ`.
|
||||||
- **Engine bleibt rein:** `engine/breakdown.py` und `engine/pie.py` importieren nur Standardbibliothek und `app.engine.*` — kein `app.models`, `app.services`, `app.reserved`.
|
- **Engine bleibt rein:** `engine/breakdown.py` und `engine/pie.py` importieren nur Standardbibliothek und `app.engine.*` — kein `app.models`, `app.services`, `app.reserved`.
|
||||||
- **Namen/Konstanten (exakt):** `PLAN_NAME = f"{SYSTEM_PREFIX}Plan"` (= „System-Plan", in `services/breakdown.py`); `FREI = "Frei verfügbar"` (`engine/breakdown.py`); `UEBRIGE = "Übrige"`, `MIN_SHARE = Decimal("0.03")`, `MAX_NAMED = 6` (`engine/pie.py`); Kategorie Kredite = `services.planning_chart.KREDITRATEN` („System-Kreditraten"), ohne Kategorie = `services.planning_chart.UNKATEGORISIERT` („unkategorisiert").
|
- **Namen/Konstanten (exakt):** `PLAN_NAME = f"{SYSTEM_PREFIX}Plan"` (= „System-Plan", in `services/breakdown.py`); `FREI = "Frei verfügbar"` (`engine/breakdown.py`); `UEBRIGE = "Übrige"`, `MIN_SHARE = Decimal("0.03")`, `MAX_NAMED = 8` (`engine/pie.py`); Kategorie Kredite = `services.planning_chart.KREDITRATEN` („System-Kreditraten"), ohne Kategorie = `services.planning_chart.UNKATEGORISIERT` („unkategorisiert").
|
||||||
- **Rundung:** Monatsbeträge je Zeile `quantize(Decimal("0.01"), ROUND_HALF_UP)`; Prozente `quantize(Decimal("0.1"), ROUND_HALF_UP)`; Summen aus gerundeten Zeilen.
|
- **Rundung:** Monatsbeträge je Zeile `quantize(Decimal("0.01"), ROUND_HALF_UP)`; Prozente `quantize(Decimal("0.1"), ROUND_HALF_UP)`; Summen aus gerundeten Zeilen.
|
||||||
- **Tortenpalette** (dataviz-Referenzpalette, Slots 1–6, Validator auf `#ffffff`: adjacent CVD ΔE ≥ 9.1, normal ≥ 19.6 — PASS; Kontrast-WARN wird durch Legende + Tabelle entlastet): `--seg-1:#2a78d6; --seg-2:#eb6834; --seg-3:#1baf7a; --seg-4:#eda100; --seg-5:#e87ba4; --seg-6:#008300; --seg-uebrige:#8a8984; --seg-frei:#d6d5cf`. Höchstens **6 benannte farbige Segmente** (dataviz: Torte ≤ 6 Segmente), danach „Übrige" — Ergänzung zur Spec-3-%-Regel.
|
- **Tortenpalette** (dataviz-Referenzpalette, alle 8 Slots, Validator auf `#ffffff`: adjacent CVD ΔE ≥ 9.1, normal ≥ 19.6 — PASS; kreisförmige Nachbarschaften ebenfalls geprüft: s8↔s1 ΔE 21.6/32.3, s7/s8↔Übrige ≥ 8.8/18.9, s8↔Frei 25.1/31.3, Übrige↔Frei 24.3 — PASS; Kontrast-WARN für s3/s4/s5 wird durch Legende + Tabelle entlastet): `--seg-1:#2a78d6; --seg-2:#eb6834; --seg-3:#1baf7a; --seg-4:#eda100; --seg-5:#e87ba4; --seg-6:#008300; --seg-7:#4a3aa7; --seg-8:#e34948; --seg-uebrige:#8a8984; --seg-frei:#d6d5cf`. **Nutzerentscheidung (2026-09-29): höchstens 8 benannte farbige Segmente + „Übrige" + „Frei verfügbar" = max. 10 Segmente** — Ergänzung zur Spec-3-%-Regel.
|
||||||
- **Kein Dark-Mode:** Die App hat keinen (`style.css` ohne `prefers-color-scheme`); Palette nur für den weißen Hintergrund.
|
- **Kein Dark-Mode:** Die App hat keinen (`style.css` ohne `prefers-color-scheme`); Palette nur für den weißen Hintergrund.
|
||||||
- **UX-Regel:** Formular immer sichtbar; alle Fehler inline, Status 200, dasselbe Template.
|
- **UX-Regel:** Formular immer sichtbar; alle Fehler inline, Status 200, dasselbe Template.
|
||||||
- **Keine Schemaänderung**, keine Migration, keine View, Grafana unverändert.
|
- **Keine Schemaänderung**, keine Migration, keine View, Grafana unverändert.
|
||||||
@@ -566,8 +566,8 @@ git commit -m "feat: Engine breakdown – Monatsbeträge je Posten, Summen, Kate
|
|||||||
|
|
||||||
**Interfaces:**
|
**Interfaces:**
|
||||||
- Produces (alle in `app.engine.pie`):
|
- Produces (alle in `app.engine.pie`):
|
||||||
- `UEBRIGE = "Übrige"`, `MIN_SHARE = Decimal("0.03")`, `MAX_NAMED = 6`
|
- `UEBRIGE = "Übrige"`, `MIN_SHARE = Decimal("0.03")`, `MAX_NAMED = 8`
|
||||||
- `Slice` (frozen dataclass: `label: str`, `value: Decimal`, `share: Decimal` (Prozent, 1 NK), `path: str | None` (SVG-`d` im Einheitskreis, Mittelpunkt 0,0, Start 12 Uhr, im Uhrzeigersinn; `None` = Vollkreis), `color: str` ∈ {`"s1"`…`"s6"`, `"uebrige"`, `"frei"`}, `merged: bool`, `members: tuple[str, ...]`, `start: float`, `end: float` (Anteile 0…1))
|
- `Slice` (frozen dataclass: `label: str`, `value: Decimal`, `share: Decimal` (Prozent, 1 NK), `path: str | None` (SVG-`d` im Einheitskreis, Mittelpunkt 0,0, Start 12 Uhr, im Uhrzeigersinn; `None` = Vollkreis), `color: str` ∈ {`"s1"`…`"s8"`, `"uebrige"`, `"frei"`}, `merged: bool`, `members: tuple[str, ...]`, `start: float`, `end: float` (Anteile 0…1))
|
||||||
- `pie_slices(values: list[tuple[str, Decimal]], rest: Decimal | None = None, rest_label: str = "", min_share: Decimal = MIN_SHARE, max_named: int = MAX_NAMED) -> list[Slice]`
|
- `pie_slices(values: list[tuple[str, Decimal]], rest: Decimal | None = None, rest_label: str = "", min_share: Decimal = MIN_SHARE, max_named: int = MAX_NAMED) -> list[Slice]`
|
||||||
|
|
||||||
Regeln: Werte ≤ 0 entfallen; Basis = Summe der Werte + `rest` (falls > 0); sortiert absteigend nach Wert, bei Gleichstand nach Label; benannt werden höchstens `max_named` Werte mit Anteil ≥ `min_share` (Farben `s1`…); die übrigen: genau einer ⇒ bleibt mit eigenem Label, Farbe `"uebrige"`, `merged=False`; zwei oder mehr ⇒ ein Segment `UEBRIGE` (Farbe `"uebrige"`, `merged=True`, `members` in Größenreihenfolge); `rest` (> 0) wird nie zusammengefasst, kommt als letztes Segment mit Label `rest_label` und Farbe `"frei"`; genau ein Segment ⇒ `path=None`.
|
Regeln: Werte ≤ 0 entfallen; Basis = Summe der Werte + `rest` (falls > 0); sortiert absteigend nach Wert, bei Gleichstand nach Label; benannt werden höchstens `max_named` Werte mit Anteil ≥ `min_share` (Farben `s1`…); die übrigen: genau einer ⇒ bleibt mit eigenem Label, Farbe `"uebrige"`, `merged=False`; zwei oder mehr ⇒ ein Segment `UEBRIGE` (Farbe `"uebrige"`, `merged=True`, `members` in Größenreihenfolge); `rest` (> 0) wird nie zusammengefasst, kommt als letztes Segment mit Label `rest_label` und Farbe `"frei"`; genau ein Segment ⇒ `path=None`.
|
||||||
@@ -623,10 +623,18 @@ def test_einzelner_kleiner_wert_behaelt_namen():
|
|||||||
assert [(s.label, s.color, s.merged) for s in sl] == [("A", "s1", False), ("B", "uebrige", False)]
|
assert [(s.label, s.color, s.merged) for s in sl] == [("A", "s1", False), ("B", "uebrige", False)]
|
||||||
|
|
||||||
|
|
||||||
def test_hoechstens_sechs_benannte_segmente():
|
def test_hoechstens_acht_benannte_segmente():
|
||||||
|
sl = pie_slices([(f"K{i}", D("10")) for i in range(10)], rest=D("5"),
|
||||||
|
rest_label="Frei verfügbar")
|
||||||
|
assert [s.color for s in sl] == ["s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8",
|
||||||
|
"uebrige", "frei"]
|
||||||
|
assert sl[-2].merged and sl[-2].members == ("K8", "K9")
|
||||||
|
|
||||||
|
|
||||||
|
def test_acht_kategorien_ohne_uebrige():
|
||||||
sl = pie_slices([(f"K{i}", D("10")) for i in range(8)])
|
sl = pie_slices([(f"K{i}", D("10")) for i in range(8)])
|
||||||
assert [s.color for s in sl] == ["s1", "s2", "s3", "s4", "s5", "s6", "uebrige"]
|
assert [s.color for s in sl] == [f"s{i}" for i in range(1, 9)]
|
||||||
assert sl[-1].merged and sl[-1].members == ("K6", "K7")
|
assert not any(s.merged for s in sl)
|
||||||
|
|
||||||
|
|
||||||
def test_rest_wird_nie_zusammengefasst():
|
def test_rest_wird_nie_zusammengefasst():
|
||||||
@@ -662,7 +670,7 @@ from decimal import ROUND_HALF_UP, Decimal
|
|||||||
|
|
||||||
UEBRIGE = "Übrige"
|
UEBRIGE = "Übrige"
|
||||||
MIN_SHARE = Decimal("0.03")
|
MIN_SHARE = Decimal("0.03")
|
||||||
MAX_NAMED = 6
|
MAX_NAMED = 8
|
||||||
PCT = Decimal("0.1")
|
PCT = Decimal("0.1")
|
||||||
|
|
||||||
|
|
||||||
@@ -672,7 +680,7 @@ class Slice:
|
|||||||
value: Decimal
|
value: Decimal
|
||||||
share: Decimal # Prozent der Tortenbasis, eine Nachkommastelle
|
share: Decimal # Prozent der Tortenbasis, eine Nachkommastelle
|
||||||
path: str | None # SVG-d; None = Vollkreis (einziges Segment)
|
path: str | None # SVG-d; None = Vollkreis (einziges Segment)
|
||||||
color: str # "s1".."s6", "uebrige", "frei"
|
color: str # "s1".."s8", "uebrige", "frei"
|
||||||
merged: bool
|
merged: bool
|
||||||
members: tuple[str, ...]
|
members: tuple[str, ...]
|
||||||
start: float # Anteil 0..1
|
start: float # Anteil 0..1
|
||||||
@@ -736,7 +744,7 @@ git add finance/app/engine/pie.py finance/tests/test_pie.py
|
|||||||
git commit -m "feat: Engine pie – Tortensegmente mit Übrige-Regel und SVG-Pfaden (A13-T3)"
|
git commit -m "feat: Engine pie – Tortensegmente mit Übrige-Regel und SVG-Pfaden (A13-T3)"
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 6: Fable-Gate** — Geometrie-Faktencheck: für 3–4 eigene Wertelisten Endpunkte der Pfade nachrechnen (12 Uhr Start, Uhrzeigersinn, `large-arc` ab > 50 %), Anteilssumme ≈ 100 % (Rundung), Übrige-Regel (3 %, max. 6, Einzelner behält Namen), `rest` nie zusammengefasst; ein erzeugtes SVG (Wegwerfdatei im Scratchpad, `viewBox="-1.05 -1.05 2.1 2.1"`) rendern und ansehen (z. B. per `rsvg-convert` oder Browser-Container), keine Lücken/Überlappungen.
|
- [ ] **Step 6: Fable-Gate** — Geometrie-Faktencheck: für 3–4 eigene Wertelisten Endpunkte der Pfade nachrechnen (12 Uhr Start, Uhrzeigersinn, `large-arc` ab > 50 %), Anteilssumme ≈ 100 % (Rundung), Übrige-Regel (3 %, max. 8 benannte, Einzelner behält Namen, max. 10 Segmente), `rest` nie zusammengefasst; ein erzeugtes SVG (Wegwerfdatei im Scratchpad, `viewBox="-1.05 -1.05 2.1 2.1"`) rendern und ansehen (z. B. per `rsvg-convert` oder Browser-Container), keine Lücken/Überlappungen.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1343,10 +1351,11 @@ def aufteilung_page(request: Request, monat: str | None = None,
|
|||||||
|
|
||||||
```css
|
```css
|
||||||
/* Aufteilung (Ausbaustufe 13). Tortenfarben: dataviz-Referenzpalette,
|
/* Aufteilung (Ausbaustufe 13). Tortenfarben: dataviz-Referenzpalette,
|
||||||
Slots 1-6 (adjacent CVD-validiert auf weißem Grund); Übrige/Frei neutral. */
|
Slots 1-8 (Nachbarpaare inkl. Kreisschluss und Grautöne CVD-validiert auf
|
||||||
|
weißem Grund); Übrige/Frei neutral. Max. 8 benannte + Übrige + Frei. */
|
||||||
.aufteilung {
|
.aufteilung {
|
||||||
--seg-1: #2a78d6; --seg-2: #eb6834; --seg-3: #1baf7a;
|
--seg-1: #2a78d6; --seg-2: #eb6834; --seg-3: #1baf7a; --seg-4: #eda100;
|
||||||
--seg-4: #eda100; --seg-5: #e87ba4; --seg-6: #008300;
|
--seg-5: #e87ba4; --seg-6: #008300; --seg-7: #4a3aa7; --seg-8: #e34948;
|
||||||
--seg-uebrige: #8a8984; --seg-frei: #d6d5cf;
|
--seg-uebrige: #8a8984; --seg-frei: #d6d5cf;
|
||||||
}
|
}
|
||||||
.kennzahlen { display: flex; flex-wrap: wrap; gap: 1.5rem; margin-bottom: 1rem; }
|
.kennzahlen { display: flex; flex-wrap: wrap; gap: 1.5rem; margin-bottom: 1rem; }
|
||||||
@@ -1367,6 +1376,8 @@ def aufteilung_page(request: Request, monat: str | None = None,
|
|||||||
.seg.s4 { fill: var(--seg-4); } .swatch.s4 { background: var(--seg-4); }
|
.seg.s4 { fill: var(--seg-4); } .swatch.s4 { background: var(--seg-4); }
|
||||||
.seg.s5 { fill: var(--seg-5); } .swatch.s5 { background: var(--seg-5); }
|
.seg.s5 { fill: var(--seg-5); } .swatch.s5 { background: var(--seg-5); }
|
||||||
.seg.s6 { fill: var(--seg-6); } .swatch.s6 { background: var(--seg-6); }
|
.seg.s6 { fill: var(--seg-6); } .swatch.s6 { background: var(--seg-6); }
|
||||||
|
.seg.s7 { fill: var(--seg-7); } .swatch.s7 { background: var(--seg-7); }
|
||||||
|
.seg.s8 { fill: var(--seg-8); } .swatch.s8 { background: var(--seg-8); }
|
||||||
.seg.uebrige { fill: var(--seg-uebrige); } .swatch.uebrige { background: var(--seg-uebrige); }
|
.seg.uebrige { fill: var(--seg-uebrige); } .swatch.uebrige { background: var(--seg-uebrige); }
|
||||||
.seg.frei { fill: var(--seg-frei); } .swatch.frei { background: var(--seg-frei); box-shadow: inset 0 0 0 1px #aaa; }
|
.seg.frei { fill: var(--seg-frei); } .swatch.frei { background: var(--seg-frei); box-shadow: inset 0 0 0 1px #aaa; }
|
||||||
.legende { list-style: none; padding: 0; margin: 0.75rem 0 0; font-size: 0.9rem; }
|
.legende { list-style: none; padding: 0; margin: 0.75rem 0 0; font-size: 0.9rem; }
|
||||||
@@ -1738,7 +1749,8 @@ git commit -m "feat: Aufteilung – Drill-down in Kategorie per Klick auf Torten
|
|||||||
Einnahmen und Ausgaben. Das Tortendiagramm zeigt die Einkünfte (Kosten je
|
Einnahmen und Ausgaben. Das Tortendiagramm zeigt die Einkünfte (Kosten je
|
||||||
Kategorie plus „Frei verfügbar"); übersteigen die Kosten die Einkünfte,
|
Kategorie plus „Frei verfügbar"); übersteigen die Kosten die Einkünfte,
|
||||||
zeigt es die Gesamtkosten. Kleine Kategorien (unter 3 %, bzw. ab der
|
zeigt es die Gesamtkosten. Kleine Kategorien (unter 3 %, bzw. ab der
|
||||||
siebten) sind zu „Übrige" zusammengefasst. Ein Klick auf ein Segment oder
|
neunten) sind zu „Übrige" zusammengefasst – höchstens 8 benannte
|
||||||
|
Kategorien plus „Übrige" und „Frei verfügbar". Ein Klick auf ein Segment oder
|
||||||
einen Legendeneintrag zeigt darunter die Posten dieser Kategorie.
|
einen Legendeneintrag zeigt darunter die Posten dieser Kategorie.
|
||||||
</li>
|
</li>
|
||||||
```
|
```
|
||||||
@@ -1746,7 +1758,7 @@ git commit -m "feat: Aufteilung – Drill-down in Kategorie per Klick auf Torten
|
|||||||
- [ ] **Step 2: `docs/ARCHITEKTUR.md`** —
|
- [ ] **Step 2: `docs/ARCHITEKTUR.md`** —
|
||||||
- Komponenten-Diagramm: `T_pages` um `aufteilung.html` ergänzen; `R_gui`-Label um `aufteilung_page, aufteilung_kategorie_fragment`; `S_projection`-Label um `load_scenario_inputs`; neue Knoten `S_breakdown["breakdown.py<br/>compute_breakdown"]` (SERVICES), `E_breakdown["breakdown.py<br/>breakdown_rows, summarize,<br/>category_rows, share"]` und `E_pie["pie.py<br/>pie_slices"]` (ENGINE); Kanten `R_gui --> S_breakdown`, `R_gui --> E_breakdown`, `R_gui --> E_pie`, `S_breakdown --> S_projection`, `S_breakdown --> E_breakdown`, `S_breakdown --> E_loans`, `S_breakdown --> S_doublecount`, `E_breakdown --> E_scenario`, `E_breakdown --> E_recurrence`.
|
- Komponenten-Diagramm: `T_pages` um `aufteilung.html` ergänzen; `R_gui`-Label um `aufteilung_page, aufteilung_kategorie_fragment`; `S_projection`-Label um `load_scenario_inputs`; neue Knoten `S_breakdown["breakdown.py<br/>compute_breakdown"]` (SERVICES), `E_breakdown["breakdown.py<br/>breakdown_rows, summarize,<br/>category_rows, share"]` und `E_pie["pie.py<br/>pie_slices"]` (ENGINE); Kanten `R_gui --> S_breakdown`, `R_gui --> E_breakdown`, `R_gui --> E_pie`, `S_breakdown --> S_projection`, `S_breakdown --> E_breakdown`, `S_breakdown --> E_loans`, `S_breakdown --> S_doublecount`, `E_breakdown --> E_scenario`, `E_breakdown --> E_recurrence`.
|
||||||
- Abschnitt 4 „Szenario-Engine ist rein": Satz „nur `scenario.py` importiert innerhalb der Engine …" korrigieren zu „`scenario.py` und `breakdown.py` importieren innerhalb der Engine (`loans.py`/`recurrence.py`/`scenario.py`)"; `services/projection_service.py::load_scenario_inputs` als gemeinsame Ladefunktion nennen (`None` = System-Plan).
|
- Abschnitt 4 „Szenario-Engine ist rein": Satz „nur `scenario.py` importiert innerhalb der Engine …" korrigieren zu „`scenario.py` und `breakdown.py` importieren innerhalb der Engine (`loans.py`/`recurrence.py`/`scenario.py`)"; `services/projection_service.py::load_scenario_inputs` als gemeinsame Ladefunktion nennen (`None` = System-Plan).
|
||||||
- Neuer Konzeptabsatz **„Aufteilung (Ausbaustufe 13)."**: `/aufteilung` (GET-Formular, Auswahl in der URL), `services/breakdown.py::compute_breakdown` (`PLAN_NAME` „System-Plan" = `load_scenario_inputs(session, None)`), Rechenregeln aus `engine/breakdown.py` (Normalisierung per `recurrence.STEP`, Einmalposten/Kreditraten voll im Fälligkeitsmonat, Auszahlung nie), Torte serverseitig aus `engine/pie.py::pie_slices` (`MIN_SHARE` 3 %, `MAX_NAMED` 6, `UEBRIGE`), Drill-down `GET /aufteilung/kategorie` mit `hx-swap-oob` der Haupttorte und `sammel=1` für die Sammelgruppe; keine Tabelle/View, Grafana unberührt.
|
- Neuer Konzeptabsatz **„Aufteilung (Ausbaustufe 13)."**: `/aufteilung` (GET-Formular, Auswahl in der URL), `services/breakdown.py::compute_breakdown` (`PLAN_NAME` „System-Plan" = `load_scenario_inputs(session, None)`), Rechenregeln aus `engine/breakdown.py` (Normalisierung per `recurrence.STEP`, Einmalposten/Kreditraten voll im Fälligkeitsmonat, Auszahlung nie), Torte serverseitig aus `engine/pie.py::pie_slices` (`MIN_SHARE` 3 %, `MAX_NAMED` 8, `UEBRIGE`), Drill-down `GET /aufteilung/kategorie` mit `hx-swap-oob` der Haupttorte und `sammel=1` für die Sammelgruppe; keine Tabelle/View, Grafana unberührt.
|
||||||
- Nur Namen verwenden, die im Code existieren.
|
- Nur Namen verwenden, die im Code existieren.
|
||||||
|
|
||||||
- [ ] **Step 3: Version** — `finance/VERSION` → `0.13.0`; `.venv/bin/python -m pytest -q` grün.
|
- [ ] **Step 3: Version** — `finance/VERSION` → `0.13.0`; `.venv/bin/python -m pytest -q` grün.
|
||||||
|
|||||||
@@ -33,7 +33,9 @@
|
|||||||
- **„Frei verfügbar" als Tabellenzeile** im `<tfoot>` (bei Unterdeckung
|
- **„Frei verfügbar" als Tabellenzeile** im `<tfoot>` (bei Unterdeckung
|
||||||
„Unterdeckung", rot). Darunter „Gesamte Einkünfte".
|
„Unterdeckung", rot). Darunter „Gesamte Einkünfte".
|
||||||
- **Kategorien < 3 %** der Tortenbasis werden nur in der Torte zu „Übrige"
|
- **Kategorien < 3 %** der Tortenbasis werden nur in der Torte zu „Übrige"
|
||||||
zusammengefasst.
|
zusammengefasst. Zusätzlich (Nachtrag 2026-09-29): **höchstens 8 benannte
|
||||||
|
Kategorien** + „Übrige" + „Frei verfügbar" = max. 10 Segmente
|
||||||
|
(8-Farben-Referenzpalette, validiert).
|
||||||
- **Endfällige Kredite**: Die Schlussrate enthält die volle Tilgung und
|
- **Endfällige Kredite**: Die Schlussrate enthält die volle Tilgung und
|
||||||
erscheint im letzten Monat voll (konsequent zu „Einmalposten voll").
|
erscheint im letzten Monat voll (konsequent zu „Einmalposten voll").
|
||||||
- **Drill-down**: Klick auf ein Kostensegment (oder dessen Legendeneintrag)
|
- **Drill-down**: Klick auf ein Kostensegment (oder dessen Legendeneintrag)
|
||||||
|
|||||||
Reference in New Issue
Block a user