Die neue Statusprüfung ließ nur „COMPLETED" durch. Deutsche Bank-Exporte
schreiben aber „ABGESCHLOSSEN", wodurch keine einzige Zeile mehr buchbar war.
Statt einer Positivliste jetzt eine Sperrliste (storniert/ausstehend/abgelehnt/
fehlgeschlagen, jeweils deutsch und englisch); unbekannte Status werden gebucht,
damit keine echten Buchungen still verloren gehen.
Test mit echtem deutschem Revolut-Export inkl. reiner Gebührenzeile
(Betrag 0.00, Gebühr 10.99): Endsaldo trifft die Kontostand-Spalte. 81 grün.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Das Revolut-Preset kannte die Spalten Gebühr/Währung/Status laut Kommentar,
der Parser hat sie aber nie gelesen. Folgen: Gebühren fehlten im Saldo,
stornierte (REVERTED) und schwebende Zeilen wurden mitgebucht, und Zeilen
fremder Währungs-Pockets landeten 1:1 im Konto – der Saldo lief dadurch zu hoch.
- Gebührenspalte mindert den Betrag (immer als Kosten)
- nur Zeilen mit Status COMPLETED (oder ohne Statusspalte) sind buchbar
- Zeilen in anderer Währung als das Zielkonto werden nicht gebucht
- Grund wird in der Prüfansicht als Badge angezeigt (storniert / Währung)
- Gebühr/Währung/Status in der Spaltenzuordnung wählbar; Revolut-Preset ergänzt
- Test mit echtem Revolut-Format (Gebühr, Storno, Fremdwährung), 80 grün
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Die Liste lud nur die 200 neuesten Buchungen, wodurch ältere Monate fehlten
(bei 254 importierten Zeilen reichte die Ansicht nur bis Juni zurück). Da die
Tabelle im Browser filtert und sortiert, werden nun standardmäßig alle Buchungen
geladen. Verifiziert am echten Auszug: 254 von 254, ab 05.01.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bisher wurde eine zweite Buchung mit gleichem Tag/Betrag/Text (z. B. zwei
Gutschriften à 324 am selben Tag) fälschlich als Duplikat verworfen. Jetzt gilt
eine Zeile nur als Duplikat, wenn bereits GENAU SO VIELE identische Buchungen in
der DB stehen – ein erneuter Import derselben Datei bleibt weiterhin Duplikat-sicher.
- csv_import.detect_rows: existing/seen als Counter (Multiset) statt Set
- Tests angepasst + Re-Import-Test ergänzt; an echtem Raiffeisen-Auszug verifiziert
(254 Zeilen, 0 fälschliche Duplikate, Saldo == Bank-Endsaldo)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Enthält der Buchungstext den Erkennungs-Text (Art „Text") eines ANDEREN
Kontos, wird auch ein Eingang als Transfer VON diesem Konto gesetzt – nicht
nur die Ausgabe. Richtung ergibt sich aus dem Vorzeichen.
- Gegenbuchungs-Erkennung: existiert schon ein Transfer zwischen denselben
Konten mit passendem Betrag (±5 Tage), wird die Gegenseite als „Gegenbuchung"
markiert und nicht angehakt – so zählt dieselbe Bewegung beim Import beider
Auszüge nicht doppelt (überschreibbar in der Prüfansicht).
- Tests: Eingang übers Konto-Tag, Gegenbuchung übersprungen (78 grün)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
_set_identifiers glich die Liste per clear()+neu-Anlegen ab; SQLAlchemy fügte
dabei die (unveränderten) Identifikatoren vor dem Löschen ein und verletzte so
UNIQUE(account_id, kind, value_norm) -> Internal Server Error beim Speichern.
Jetzt als Diff: unveränderte behalten, nur fehlende hinzufügen / überzählige
entfernen. Betrifft alle Identifikator-Arten (IBAN/Nummer/Text).
Regressionstest: Umbenennen mit unverändertem Identifikator + Entfernen (76 grün).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Transfer-Regel kann direkt ein Zielkonto tragen (target_account_id); ohne ->
weiterhin Auto-Erkennung aus dem Buchungstext (Konto-Texte)
- Migration 0010; Endpunkt aktualisiert bestehendes Wort statt Konflikt
- Import bevorzugt das feste Zielkonto, sonst _guess_target_account
- Konten-Seite: Transfer-Erkennung mit Zielkonto-Auswahl + Anzeige am Chip
- Test: festes Zielkonto greift ohne Konto-Texte (75 grün)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Neue Seite „Mein Konto" (/konto, für alle Nutzer): eigenes Passwort ändern
(inkl. Richtlinien-Anzeige) + Zwei-Faktor verwalten; erreichbar über den
Nutzer-Chip in der Seitenleisten-Fußzeile
- Einstellungen: 2FA-Karte entfernt (liegt jetzt bei „Mein Konto"), Karten in
ein mehrspaltiges Raster gruppiert
- Menüpunkt „Einstellungen" unter die Überschrift „System" verschoben (für alle
Nutzer sichtbar; „Benutzer" bleibt Admin-only)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Bank-Vorlagen und eigene Vorlagen in EINEM Dropdown (optgroups, typ-getaggte
Werte preset:/tpl:)
- Speichern überschreibt bei gleichem Namen (Rückfrage); geladene Vorlage belegt
den Namen vor
- Spaltenvorschau wird automatisch erzeugt (kein „Datei einlesen"-Klick mehr)
- Gegen-IBAN in „Weitere Spalten (optional)" verschoben + erklärt; Grundzuordnung
zeigt nur Datum/Betrag/Empfänger/Zweck
- Beleg-Modus zeigt die hochgeladenen PDF-Seiten als Bild + extrahierten Text
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Neuer Parser erkennt mehrzeilige Auszüge (N26 & Co.): Beschreibung → Value
Date → Buchungsdatum → Betrag stehen auf eigenen Zeilen; Betrags-Zeile schließt
eine Buchung ab, Kopf-/Fußzeilen (Name/Adresse/eigene IBAN/Seitenzahl/
Spaltenköpfe) werden gefiltert, Gegen-IBAN und Zweck werden übernommen
- Alter Ein-Zeilen-Heuristik-Parser bleibt als Fallback
- /documents/preview liefert zusätzlich die Seiten als PNG-Data-URLs
(PyMuPDF get_pixmap, kein poppler nötig)
- Tests: N26-artige mehrzeilige Fixture + Seiten-Rendering (74 grün);
am echten 34-Seiten-Auszug 207 Buchungen erkannt
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Budget-Donut: Mittelbetrag passt sich der Länge an (kein Hineinlaufen in den
Ring bei 4-stelligen Beträgen)
- Vermögensverlauf: Y-Achse ohne Währungssymbol (formatAmount) + etwas mehr
linker Rand → „CHF …" wird nicht mehr abgeschnitten
- Karten-Optionen: im Anordnen-Modus je Karte einstellbar; Budget-Donut und
Budget-Fortschritt können nach Gruppen ODER Kategorien aufschlüsseln
(?group_by=), Auswahl wird im Layout (props) mitgespeichert
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- CategorizationRule bekommt `kind` (expense|income|transfer); Unique jetzt
(owner, match_field, pattern, kind) statt ohne kind
- Konfliktprüfung/Anlage (add/update Keyword, Transfer-Keyword, gelernte Regeln)
arbeiten kind-bewusst: dasselbe Wort je einmal pro Art möglich
- Migration 0009 mit Backfill (kind aus Kategorie-Art bzw. is_transfer)
- Tests angepasst: quer über Arten erlaubt, gleiche Art weiterhin Konflikt (71 grün)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Der Schlagwort-Knopf sah bei jeder Kategorie gleich aus - man musste jede
einzeln oeffnen, um zu sehen, wo ueberhaupt etwas hinterlegt ist. Belegte
Kategorien tragen den Knopf jetzt in Akzentfarbe und mit der Anzahl daneben,
der Tooltip nennt sie ausgeschrieben.
Die Zahl kommt als keyword_count an der Kategorie mit, ermittelt in einer
Aggregat-Abfrage fuer die ganze Liste. Ein Abruf je Kategorie waere dafuer
unverhaeltnismaessig gewesen. Gefuellt wird das Feld nur beim Auflisten;
beim Anlegen und Aendern einer Gruppe bleibt es bei 0, wo es ohnehin nichts
aussagt.
Das Popup laedt beim Schliessen die Liste neu, damit die Zahl nach dem
Bearbeiten stimmt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
In der Passwortrichtlinie stand das Haekchen mittig und der Text ganz rechts.
Ursache war die globale Regel "input, select { width: 100% }": sie erwischt
auch Ankreuzfelder und zieht sie ueber die volle Breite. Vier Stellen im
Code umgingen das bereits einzeln (.checklist, .dt-pop-opt, .checkline,
.datatable td) - eine fuenfte Umgehung waere die falsche Antwort gewesen.
Ankreuz- und Auswahlfelder sind jetzt zentral ausgenommen, die vier
Einzelfaelle entfallen. Nebeneffekt: sie tragen ueberall dieselbe
Akzentfarbe (bisher nur an zwei Stellen) und sitzen in Tabellenzellen
mittig, statt durch das geerbte margin-top um 5px nach unten gedrueckt zu
werden.
Die Karte selbst nutzt jetzt .checkline, das vorhandene Muster fuer
"Ankreuzfeld plus Text"; .field-inline richtet an der Grundlinie aus und
war hier von vornherein das falsche.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bisher oeffnete der Doppelklick ein Eingabefeld im Chip selbst. Dort war nur
Platz fuer das Wort - der Betrag liess sich gar nicht aendern, weil die
Inline-Bearbeitung bewusst nur { word } geschickt hat.
Jetzt laedt der Doppelklick Wort und Betrag in dieselbe Zeile, in der auch
neue Schlagwoerter entstehen. Der Knopf wechselt auf ein Haekchen, daneben
steht Abbrechen (auch per Escape), und der bearbeitete Chip ist markiert.
Gespeichert wird per PATCH mit ausdruecklichem amount_cents, damit ein
geleertes Betragsfeld den Betrag auch wirklich loescht; ein geparstes 0 wird
zu null, weil das Schema gt=0 verlangt. Ueber POST ginge es nicht: das
antwortet bei vorhandenem Wort mit geaendertem Betrag mit 409.
Tests decken jetzt ab, dass Betrag mitgeaendert wird, beim Weglassen stehen
bleibt und bei ausdruecklichem null verschwindet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Im Downloads-Ordner lagen "certain-finance-export_....json" und
"certain-finance-backup_....json" nebeneinander, ohne dass man sah, welche
Datei sich zurueckladen laesst. Alle Downloads heissen jetzt
certain-finance_<art>_<datum>_<uhrzeit>.<endung>, wobei die Art es sagt:
vollbackup, vollbackup-system, datenuebersicht, buchungen, datenbank. Das
persoenliche Vollbackup traegt zusaetzlich den Benutzernamen.
Den Namen vergibt jetzt allein der Server im Content-Disposition-Kopf; das
Frontend liest ihn von dort. Vorher erfanden beide Seiten ihn getrennt und
kamen auf unterschiedliche Ergebnisse (Bindestrich gegen Unterstrich, UTC
gegen Ortszeit). Zeitstempel ist Ortszeit: wer abends um 20 Uhr exportiert,
soll keine Datei von 18 Uhr finden.
Passend dazu heisst der Knopf "Alle Daten (JSON)" nun "Datenuebersicht
(JSON)", und der Hinweis darueber sagt ausdruecklich, dass sich diese
Dateien nicht zurueckladen lassen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zwei-Faktor per TOTP mit zehn Einmal-Wiederherstellungscodes. Der
Zwischenzustand "Passwort stimmt, Code fehlt" haengt an der vorhandenen
Sitzungstabelle (pending_2fa, zehn Minuten Laufzeit) und gilt nirgends als
angemeldet; nach dem Code wird das Cookie getauscht statt weiterverwendet.
Der Wiederverwendungsschutz merkt sich das tatsaechlich getroffene
Zeitfenster, nicht die aktuelle Uhrzeit - sonst liesse sich ein Code aus dem
naechsten Fenster gleich darauf nochmal einloesen.
Passwortrichtlinie (Laenge, Zeichenklassen, MFA-Pflicht) in der neuen
globalen Tabelle app_settings, durchgesetzt beim Anlegen, Zuruecksetzen und
Aendern von Passwoertern. Bestehende Passwoerter lassen sich nicht gegen eine
neue Richtlinie pruefen - der Hash gibt nichts her. Beim Anmelden liegt das
Passwort aber kurz im Klartext vor: dort wird geprueft und bei Verstoss ein
Wechsel verlangt, mit passendem Grund fuer den Hinweistext.
Nebenbei geschlossen: must_change_password war bisher nur im Frontend
erzwungen, wer die API direkt ansprach kam vorbei. enforce_account_state
haengt jetzt am /api-Sammelrouter und laesst nur noch durch, was man zum
Erfuellen der Pflicht braucht.
Das JSON-Backup liess sich bisher grundsaetzlich nicht zurueckladen: keine
Version, Kategorien nur ueber mehrdeutige Namen, sieben Tabellen fehlten.
Daneben steht nun ein Format v2, das Beziehungen ueber lokale IDs abbildet,
je Benutzer oder systemweit. Zurueckgeladen wird nach einer Pruefansicht,
wahlweise ersetzend oder ergaenzend; vor dem Ersetzen legt der Server einen
Schnappschuss der Datenbank an. Der alte Ansichts-Export bleibt fuer
Tabellenkalkulationen bestehen und wird beim Hochladen ausdruecklich
abgewiesen, statt halb zu raten.
Frontend: wiederverwendbares Passwortfeld mit Augensymbol, animierte
Anforderungsliste, 2FA-Seite, Admin-Karte fuer die Richtlinie und Karte zum
Zurueckladen. Neue Bewegung ist in prefers-reduced-motion hinterlegt; Secret
und Codes sind markierbar, sonst verhinderte das globale user-select das
Kopieren.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Portierte DataTable-Komponente (aus Vorrania): Spalten per Drag breiter ziehen
und verschieben, Klick-Sortierung, Filterzeile je Spalte, Spalten ein-/ausblenden;
Breiten/Reihenfolge/Filter pro Tabelle im Browser gemerkt (localStorage)
- Zu langer Zellinhalt wird mit „…" abgeschnitten (Titel bleibt sichtbar, Excel-artig)
- Umgestellt: Buchungen, Import-Prüfansicht (inkl. interaktiver Zellen), Import-
Verlauf, Benutzer
- Neue Skeleton-Komponente für den Ladezustand
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Regeln:
- Schlagwörter mit optionaler Betragsbedingung (z. B. „SV = 5.00 → Restaurant");
Betrags-Regeln gewinnen vor betragslosen
- Transfer-Schlagwörter (Text → Transfer) auf der Konten-Seite
- Konto-Identifikator-Art „Text": erkennt das Zielkonto eines Transfers aus dem
Buchungstext (längster passender Text gewinnt)
- Migration 0007, Endpunkte, Tests (Betrag/Transfer/Konflikt), gesamt 30 grün
Optik:
- App-Icon: Münzhaufen (aus Nutzer-SVG) weiß auf grünem Grund, klein gehalten
- Farbschema auf pastelliges Grün (Akzent hell/dunkel + Favicon)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gruppen erscheinen nur noch als dezente Zwischenüberschrift; die Kategorien
laufen als schlichte Zeilen mit dünnem Trenner durch (eine Karte).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Manuelle Schlagwörter je Kategorie (Popup mit Chips, Doppelklick zum Bearbeiten)
- Treffer über Empfänger ODER Verwendungszweck; längstes Schlagwort gewinnt;
Kind-Filter (Ausgabe/Einnahme); wirken nur bei künftigen Importen
- Lernende Korrektur-Erkennung: ab 5 Anwendungen und >= 1/3 Korrekturen
erscheint eine Prüf-Meldung in den Einstellungen (Behalten/Entfernen)
- Neue CategorySelect-Komponente (Baum + Suche, per Portal) ersetzt die
nativen optgroup-Selects in Buchungen und Import-Prüfansicht
- Migration 0006 (source/applied_count/overridden_count/confirmed, match_field any)
- Endpunkte, i18n DE/EN, Tests (test_keywords: 7 neu, gesamt 27 grün)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Backup & Export:
- routers/backup: /backup/db (Admin, konsistentes SQLite-Online-Backup),
/export/transactions.csv|json und /export/all.json (owner-gescoped).
- Einstellungen: Bereich „Backup & Export" mit Download-Buttons. docs/BACKUP.md
(Wiederherstellung + LXC-Sicherung). pytest für Export + Admin-Schutz.
Import-Verbesserungen (Nutzer-Feedback zum Ablauf):
- Klarer Hinweis, dass die Zuordnungstabelle nur Beispielzeilen zeigt und
„Vorschau erstellen" die vollständige Prüfliste + Buchen-Button erzeugt.
- Zusammenfassung (erkannt/gebucht) und Warnung, wenn keine gültige Zeile erkannt
wurde (Hinweis auf Format/Bank-Vorlage).
- Fix: Raiffeisen-Preset hat has_header=true (der Export hat eine Kopfzeile).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- services/csv_import: PRESETS-Liste (aus echten Beispiel-Exporten des Nutzers),
GET /imports/presets.
- Frontend: Preset-Auswahl im Import füllt Format/Spalten automatisch.
Beide Presets per TestClient gegen echte Zeilenformate verifiziert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ein 100%-breites select drängte das danebenliegende Eingabefeld auf 0.
Select auf feste Breite, .row-Kinder dürfen schrumpfen (min-width:0).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ein eigener Dienst-Benutzer kommt nicht an Code/venv unter /root (Modus 700).
install.sh setzt User/Group der Unit jetzt auf den Eigentümer des Repos – bei
Klon als root nach /root also root. Kein useradd mehr.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Deployment-Fixes (Dienst startete nicht -> 203/EXEC "uvicorn not found"):
- Unit ohne ProtectHome/ProtectSystem: Repo/venv dürfen unter /root oder /home
liegen (ProtectHome=true blendete diese Pfade für den Dienst aus).
- install.sh: Python-3.11+-Prüfung mit klarer Meldung; prüft nach der Installation,
dass uvicorn wirklich in der venv liegt.
- EZB-Kursabruf läuft beim Start jetzt im Hintergrund (blockiert den Start nie).
Phase-3-Backend (Dashboard):
- Modell + Migration 0004: dashboard_layouts (JSON-Layout je Nutzer).
- routers/dashboard: Layout speichern/laden/zurücksetzen + Kartendaten
(Budget-Donut, Budget-Fortschritt, Einnahmen/Ausgaben, Kontostände,
Vermögensverlauf, letzte Buchungen).
- Frontend-Infrastruktur: Chart-Komponenten (Donut/Line), palette, API-Methoden,
react-grid-layout. (Dashboard-Seite folgt im nächsten Commit.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>