From fe0e1b5e9376a7e5af6030c5b5d40243ec4a1015454e8be3b9bd1ac5323a7362 Mon Sep 17 00:00:00 2001 From: wlfb Date: Mon, 20 Jul 2026 11:23:26 +0200 Subject: [PATCH] feat: deutsche Betragsanzeige (eur-Filter) und deutsche GUI-Labels Co-Authored-By: Claude Fable 5 --- finance/app/formats.py | 30 ++++++++++++++++ finance/app/routers/gui.py | 3 ++ finance/app/templates/_preview_table.html | 6 ++-- finance/app/templates/index.html | 10 +++--- finance/app/templates/planning.html | 39 ++++++++++---------- finance/app/templates/salden.html | 8 ++--- finance/app/templates/transactions.html | 2 +- finance/tests/test_formats.py | 43 +++++++++++++++++++++++ finance/tests/test_gui.py | 32 ++++++++++++++--- 9 files changed, 138 insertions(+), 35 deletions(-) create mode 100644 finance/app/formats.py create mode 100644 finance/tests/test_formats.py diff --git a/finance/app/formats.py b/finance/app/formats.py new file mode 100644 index 0000000..bf13b7d --- /dev/null +++ b/finance/app/formats.py @@ -0,0 +1,30 @@ +"""Deutsche Anzeige-Formate fuer die GUI (Ausbaustufe 5). + +Bewusst ohne locale-Modul: Container-Locales sind nicht garantiert +installiert, und die Ausgabe hinge sonst vom Host ab. Reine +Decimal-Formatierung; die JSON-API bleibt Punkt-Dezimal. +""" +from decimal import Decimal, ROUND_HALF_UP + +DE_LABELS = { + "monthly": "monatlich", + "quarterly": "vierteljährlich", + "yearly": "jährlich", + "annuity": "Annuität", + "bullet": "endfällig", + "percent": "Prozent", + "absolute": "Absolut", + "remove": "Entfällt", + "ende": "Ende", +} + + +def eur(value) -> str: + """Decimal("-2474.5") -> "-2.474,50" (ohne Euro-Zeichen, das setzt das Template).""" + d = Decimal(str(value)).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) + s = f"{d:,.2f}" # US-Trenner: "-2,474.50" + return s.replace(",", "\x00").replace(".", ",").replace("\x00", ".") + + +def de_label(value: str) -> str: + return DE_LABELS.get(value, value) diff --git a/finance/app/routers/gui.py b/finance/app/routers/gui.py index d9ef6c6..da64f84 100644 --- a/finance/app/routers/gui.py +++ b/finance/app/routers/gui.py @@ -10,6 +10,7 @@ from app.auth import COOKIE, session_valid from app.db import get_session from app.engine.loans import add_months from app.engine.recurrence import occurrences +from app.formats import de_label, eur from app.models.tables import (Account, Category, PlannedItem, ProjectionResult, RecurringItem, ScenarioLoan, ScenarioModifier, Transaction) @@ -28,6 +29,8 @@ PAGE_SIZE = 50 templates = Jinja2Templates(directory="app/templates") templates.env.globals["app_version"] = get_version() +templates.env.filters["eur"] = eur +templates.env.filters["de_label"] = de_label router = APIRouter() diff --git a/finance/app/templates/_preview_table.html b/finance/app/templates/_preview_table.html index f0fb513..85bbf33 100644 --- a/finance/app/templates/_preview_table.html +++ b/finance/app/templates/_preview_table.html @@ -8,7 +8,7 @@ {{ tx.booking_date.strftime('%d.%m.%Y') }} {{ tx.purpose }} {{ tx.counterparty }} - {{ '%.2f'|format(tx.amount) }} € + {{ tx.amount|eur }} € {% if tx.is_duplicate %}Duplikat{% endif %} {% else %} @@ -25,7 +25,7 @@ {% else %} Saldo-Differenz – bitte prüfen {% endif %} - · Eröffnungssaldo: {{ '%.2f'|format(statement.opening_balance) if statement.opening_balance is not none else '–' }} € - · Endsaldo: {{ '%.2f'|format(statement.closing_balance) if statement.closing_balance is not none else '–' }} € + · Eröffnungssaldo: {{ statement.opening_balance|eur if statement.opening_balance is not none else '–' }} € + · Endsaldo: {{ statement.closing_balance|eur if statement.closing_balance is not none else '–' }} € · Duplikate: {{ duplicates }}

diff --git a/finance/app/templates/index.html b/finance/app/templates/index.html index bcd81c8..c47a71d 100644 --- a/finance/app/templates/index.html +++ b/finance/app/templates/index.html @@ -11,7 +11,7 @@ {% endif %}
- Gesamtsaldo: {{ '%.2f'|format(total) }} € + Gesamtsaldo: {{ total|eur }} €
@@ -26,10 +26,10 @@ - + @@ -68,7 +68,7 @@ - + {% else %} diff --git a/finance/app/templates/planning.html b/finance/app/templates/planning.html index 2877281..e77d5e1 100644 --- a/finance/app/templates/planning.html +++ b/finance/app/templates/planning.html @@ -13,8 +13,8 @@ {% for r in recurring %} - - + + - - + + - + - - + + - + - - + + '; rows.forEach(function (r) { html += '' - + '' - + '' - + '' - + ''; + + '' + + '' + + '' + + ''; }); html += '
{{ account.bank }}{{ '%.2f'|format(balance) }} €{{ balance|eur }} € {% if account.anchor_date is not none and account.anchor_balance is not none %} - Anker: {{ '%.2f'|format(account.anchor_balance) }} € am {{ account.anchor_date.strftime('%d.%m.%Y') }} + Anker: {{ account.anchor_balance|eur }} € am {{ account.anchor_date.strftime('%d.%m.%Y') }} {% else %} kein Anker {% endif %} @@ -47,7 +47,7 @@ + value="{{ account.anchor_balance|eur if account.anchor_balance is not none else '' }}" required>
{{ item.date.strftime('%d.%m.%Y') }} {{ item.name }}{{ '%.2f'|format(item.amount) }} €{{ item.amount|eur }} €
Keine anstehenden Posten.
{{ r.name }}{{ '%.2f'|format(r.amount) }} €{{ r.rhythm }}{{ r.amount|eur }} €{{ r.rhythm|de_label }} {{ r.due_day }} {{ category_names.get(r.category_id, '–') }} @@ -39,7 +39,7 @@ @@ -64,8 +64,8 @@ {% for s in suggestions %}
{{ s.name }}{{ '%.2f'|format(s.amount) }} €{{ s.rhythm }}{{ s.amount|eur }} €{{ s.rhythm|de_label }} {{ s.due_day }}
{{ p.name }}{{ '%.2f'|format(p.amount) }} €{{ p.amount|eur }} € {{ p.due.strftime('%d.%m.%Y') }} {{ category_names.get(p.category_id, '–') }} @@ -143,11 +143,11 @@ {% for l in loans %}
{{ l.name }}{{ '%.2f'|format(l.principal) }} €{{ '%.2f'|format(l.annual_rate_pct) }} %{{ l.principal|eur }} €{{ l.annual_rate_pct|eur }} % {{ l.term_months }} Monate {{ l.payout_date.strftime('%d.%m.%Y') }}{{ l.repayment_type }}{{ l.repayment_type|de_label }} Auszahlungsdatum @@ -227,8 +227,8 @@ {% if m.target_type == "category" %}Kategorie: {{ category_names.get(m.target_id, m.target_id) }} {% else %}Posten: {{ recurring_names.get(m.target_id, m.target_id) }}{% endif %} {{ m.kind }}{{ '%.2f'|format(m.value) }}{{ m.kind|de_label }}{{ m.value|eur }} @@ -266,7 +266,7 @@ @@ -281,7 +281,7 @@ {% if row.result %}

- Tiefpunkt: {{ '%.2f'|format(row.result.low_point_balance) }} € am {{ row.result.low_point_date.strftime('%d.%m.%Y') }}
+ Tiefpunkt: {{ row.result.low_point_balance|eur }} € am {{ row.result.low_point_date.strftime('%d.%m.%Y') }}
{% if row.result.below_zero_date %}Unterschreitet 0 € ab {{ row.result.below_zero_date.strftime('%d.%m.%Y') }}
{% endif %} {% if row.result.below_threshold_date %}Unterschreitet Warnschwelle ab {{ row.result.below_threshold_date.strftime('%d.%m.%Y') }}
{% endif %} Kurven in Grafana ansehen. @@ -343,6 +343,9 @@ var parts = iso.split('-'); return parts[2] + '.' + parts[1] + '.' + parts[0]; } + function fmtEur(n) { + return Number(n).toLocaleString('de-DE', {minimumFractionDigits: 2, maximumFractionDigits: 2}); + } function loadLoanSchedule(loanId) { if (loadedSchedules[loanId]) return; loadedSchedules[loanId] = true; @@ -353,10 +356,10 @@ + '

TilgungRestschuld
' + formatIsoDate(r.due) + '' + Number(r.payment).toFixed(2) + ' €' + Number(r.interest).toFixed(2) + ' €' + Number(r.principal).toFixed(2) + ' €' + Number(r.remaining).toFixed(2) + ' €
' + fmtEur(r.payment) + ' €' + fmtEur(r.interest) + ' €' + fmtEur(r.principal) + ' €' + fmtEur(r.remaining) + ' €
'; document.getElementById('schedule-' + loanId).innerHTML = html; diff --git a/finance/app/templates/salden.html b/finance/app/templates/salden.html index c3b78d4..670f558 100644 --- a/finance/app/templates/salden.html +++ b/finance/app/templates/salden.html @@ -17,14 +17,14 @@ {% for account, balance in balances %} {{ account.name }} - {{ '%.2f'|format(balance) }} € + {{ balance|eur }} € {% else %} Keine Konten angelegt. {% endfor %} Gesamt - {{ '%.2f'|format(total) }} € + {{ total|eur }} € @@ -48,9 +48,9 @@ {{ row['month'].strftime('%m.%Y') }} {% for account, value in row['values'] %} - {{ '%.2f'|format(value) }} € + {{ value|eur }} € {% endfor %} - {{ '%.2f'|format(row['total']) }} € + {{ row['total']|eur }} € {% else %} Keine bestätigten Buchungen vorhanden. diff --git a/finance/app/templates/transactions.html b/finance/app/templates/transactions.html index 65d411c..d83a832 100644 --- a/finance/app/templates/transactions.html +++ b/finance/app/templates/transactions.html @@ -53,7 +53,7 @@ {{ account_names.get(tx.account_id, tx.account_id) }} {{ tx.purpose }} {{ tx.counterparty }} - {{ '%.2f'|format(tx.amount) }} + {{ tx.amount|eur }}