Verträge aufspüren: wiederkehrende Zahlungen ohne hinterlegten Vertrag #3

Closed
opened 2026-08-22 23:26:56 +00:00 by florian.wilde · 3 comments
Owner

Problem

Am Anfang einer Betreuung weiß niemand, welche Verträge es überhaupt gibt.
Es liegt kein Ordner bereit, in dem alle Versicherungen stehen — die
betreute Person kann es oft selbst nicht mehr sagen, und die Unterlagen
sind über Jahre entstanden.

Die Antwort steht aber schon im System: Wer regelmäßig abbucht, hat einen
Vertrag.
Die Kontoauszüge sind importiert, Buchung.gegenstelle ist
gefüllt — nur wertet das niemand aus. Man scrollt durch hunderte Zeilen und
versucht sich zu merken, welcher Name schon dreimal vorkam.

Solange ich nicht weiß, an wen ich schreiben muss, nützt mir die beste
Briefvorlage nichts.

Was ich will

Unter Vermögen & Kasse eine Ansicht „Wiederkehrende Zahlungen":

  • Buchungen nach Gegenstelle gruppiert, die mehr als einmal in ähnlicher
    Höhe und in erkennbarem Abstand auftreten
  • Je Gruppe: Name, Betrag, Rhythmus (monatlich, vierteljährlich, jährlich),
    erste und letzte Zahlung, Anzahl
  • Deutlich markiert, ob dazu schon ein Vertrag hinterlegt ist
  • Aus einer Zeile heraus: Vertrag anlegen mit vorbelegter Gesellschaft
    und vorbelegtem Beitrag, oder Als geklärt abhaken (Miete, Strom,
    Rundfunkbeitrag sind keine Verträge im Sinne dieses Moduls)

Akzeptanzkriterien

  • Die Ansicht listet wiederkehrende Zahlungen, gruppiert nach
    Gegenstelle, absteigend nach Jahresbetrag
  • Der erkannte Rhythmus steht dabei und ist nachvollziehbar
    („4 Zahlungen, etwa alle 3 Monate")
  • Zahlungen, zu denen bereits ein Vertrag besteht, sind als solche
    erkennbar und lassen sich ausblenden
  • „Vertrag anlegen" öffnet das Vertragsformular mit vorbelegter
    Gesellschaft und vorbelegtem Beitrag
  • Eine Gruppe lässt sich dauerhaft als „kein Vertrag" abhaken und
    taucht dann nicht wieder auf
  • Schreibweisen desselben Empfängers werden zusammengefasst, soweit das
    sicher geht (Groß-/Kleinschreibung, Rechtsform, Leerzeichen). Im
    Zweifel lieber getrennt lassen als falsch zusammenwerfen
  • Auf „Heute" erscheint ein Hinweis, wenn es unbearbeitete Kandidaten
    gibt — sonst sieht man die Seite nie wieder an
  • Die Erkennung läuft rein lesend; sie legt von sich aus nichts an

Hinweise zur Umsetzung

  • Buchung.gegenstelle ist vorhanden und wird vom CAMT- und CSV-Import
    gefüllt (src/lib/finanzen/camt.ts, import.ts)
  • Der Verwendungszweck liegt verschlüsselt vor und kann nicht gefiltert
    werden — die Gruppierung muss über die Gegenstelle laufen
  • Für das Abhaken braucht es vermutlich ein kleines eigenes Modell
    (Gegenstelle + Kennzeichnung + Begründung), damit die Entscheidung einen
    Import überlebt

Warum das zuerst kommt

Ohne diese Übersicht ist unklar, an welche Gesellschaften überhaupt
geschrieben werden muss. Die Aufgabe hängt an keiner anderen und kann
sofort beginnen.

Angelegt auf Zuruf im Chat, nicht aus dem Dashboard — bitte in Nebula
nachtragen.

## Problem Am Anfang einer Betreuung weiß niemand, welche Verträge es überhaupt gibt. Es liegt kein Ordner bereit, in dem alle Versicherungen stehen — die betreute Person kann es oft selbst nicht mehr sagen, und die Unterlagen sind über Jahre entstanden. Die Antwort steht aber schon im System: **Wer regelmäßig abbucht, hat einen Vertrag.** Die Kontoauszüge sind importiert, `Buchung.gegenstelle` ist gefüllt — nur wertet das niemand aus. Man scrollt durch hunderte Zeilen und versucht sich zu merken, welcher Name schon dreimal vorkam. Solange ich nicht weiß, an wen ich schreiben muss, nützt mir die beste Briefvorlage nichts. ## Was ich will Unter **Vermögen & Kasse** eine Ansicht **„Wiederkehrende Zahlungen"**: - Buchungen nach Gegenstelle gruppiert, die mehr als einmal in ähnlicher Höhe und in erkennbarem Abstand auftreten - Je Gruppe: Name, Betrag, Rhythmus (monatlich, vierteljährlich, jährlich), erste und letzte Zahlung, Anzahl - Deutlich markiert, ob dazu schon ein Vertrag hinterlegt ist - Aus einer Zeile heraus: **Vertrag anlegen** mit vorbelegter Gesellschaft und vorbelegtem Beitrag, oder **Als geklärt abhaken** (Miete, Strom, Rundfunkbeitrag sind keine Verträge im Sinne dieses Moduls) ## Akzeptanzkriterien - [ ] Die Ansicht listet wiederkehrende Zahlungen, gruppiert nach Gegenstelle, absteigend nach Jahresbetrag - [ ] Der erkannte Rhythmus steht dabei und ist nachvollziehbar („4 Zahlungen, etwa alle 3 Monate") - [ ] Zahlungen, zu denen bereits ein Vertrag besteht, sind als solche erkennbar und lassen sich ausblenden - [ ] „Vertrag anlegen" öffnet das Vertragsformular mit vorbelegter Gesellschaft und vorbelegtem Beitrag - [ ] Eine Gruppe lässt sich dauerhaft als „kein Vertrag" abhaken und taucht dann nicht wieder auf - [ ] Schreibweisen desselben Empfängers werden zusammengefasst, soweit das sicher geht (Groß-/Kleinschreibung, Rechtsform, Leerzeichen). Im Zweifel lieber getrennt lassen als falsch zusammenwerfen - [ ] Auf „Heute" erscheint ein Hinweis, wenn es unbearbeitete Kandidaten gibt — sonst sieht man die Seite nie wieder an - [ ] Die Erkennung läuft rein lesend; sie legt von sich aus nichts an ## Hinweise zur Umsetzung - `Buchung.gegenstelle` ist vorhanden und wird vom CAMT- und CSV-Import gefüllt (`src/lib/finanzen/camt.ts`, `import.ts`) - Der Verwendungszweck liegt verschlüsselt vor und kann nicht gefiltert werden — die Gruppierung muss über die Gegenstelle laufen - Für das Abhaken braucht es vermutlich ein kleines eigenes Modell (Gegenstelle + Kennzeichnung + Begründung), damit die Entscheidung einen Import überlebt ## Warum das zuerst kommt Ohne diese Übersicht ist unklar, an welche Gesellschaften überhaupt geschrieben werden muss. Die Aufgabe hängt an keiner anderen und kann sofort beginnen. _Angelegt auf Zuruf im Chat, nicht aus dem Dashboard — bitte in Nebula nachtragen._
florian.wilde added the in-arbeit label 2026-08-22 23:46:18 +00:00
florian.wilde started working 2026-08-22 23:54:37 +00:00
Author
Owner

🤖 Claude Code hat mit der Umsetzung begonnen.

  • Branch: feat/3-wiederkehrende-zahlungen
  • Verstanden als: Aus den importierten Kontobewegungen soll eine Arbeitsliste werden — wer regelmäßig abbucht, hat vermutlich einen Vertrag, und der fehlt noch in der Akte.
  • Geplantes Vorgehen:
    • Erkennung als reine Funktionen in src/lib/finanzen/wiederkehrend.ts, ohne Datenbank — damit sie sich mit Einheitstests festnageln lässt (Normalisierung der Schreibweisen, Gruppierung, Rhythmus)
    • Kleines Modell für das dauerhafte Abhaken, damit die Entscheidung einen erneuten Import überlebt
    • Ansicht unter Vermögen & Kasse, Hinweis auf „Heute"
    • Vorbelegung des Vertragsformulars aus der Zeile heraus

Zwei Dinge, die ich beim Zuschnitt im Auge behalte und am Ende ausdrücklich berichte:

Jahresbeiträge. Die Erkennung braucht mindestens zwei Zahlungen. Eine jährlich abgebuchte Prämie taucht bei nur einem importierten Jahr genau einmal auf und fällt damit durch das Raster — ausgerechnet bei Versicherungen ist das ein häufiger Fall. Ich prüfe, ob sich einzelne größere Zahlungen ohne Vertrag sinnvoll als getrennter Abschnitt danebenstellen lassen.

Zusammenfassen von Schreibweisen. „Allianz Lebensversicherung AG" und „ALLIANZ LEBENSVERS.AG" sind dieselbe Stelle, „Stadtwerke Bamberg" und „Stadtwerke Bayreuth" nicht. Ich fasse nur zusammen, was sicher ist, und lasse im Zweifel getrennt — zwei Zeilen zu viel sind harmloser als eine falsche Gruppe.

Zeiterfassung läuft.

🤖 **Claude Code hat mit der Umsetzung begonnen.** - Branch: `feat/3-wiederkehrende-zahlungen` - Verstanden als: Aus den importierten Kontobewegungen soll eine Arbeitsliste werden — wer regelmäßig abbucht, hat vermutlich einen Vertrag, und der fehlt noch in der Akte. - Geplantes Vorgehen: - Erkennung als reine Funktionen in `src/lib/finanzen/wiederkehrend.ts`, ohne Datenbank — damit sie sich mit Einheitstests festnageln lässt (Normalisierung der Schreibweisen, Gruppierung, Rhythmus) - Kleines Modell für das dauerhafte Abhaken, damit die Entscheidung einen erneuten Import überlebt - Ansicht unter Vermögen & Kasse, Hinweis auf „Heute" - Vorbelegung des Vertragsformulars aus der Zeile heraus Zwei Dinge, die ich beim Zuschnitt im Auge behalte und am Ende ausdrücklich berichte: **Jahresbeiträge.** Die Erkennung braucht mindestens zwei Zahlungen. Eine jährlich abgebuchte Prämie taucht bei nur einem importierten Jahr genau einmal auf und fällt damit durch das Raster — ausgerechnet bei Versicherungen ist das ein häufiger Fall. Ich prüfe, ob sich einzelne größere Zahlungen ohne Vertrag sinnvoll als getrennter Abschnitt danebenstellen lassen. **Zusammenfassen von Schreibweisen.** „Allianz Lebensversicherung AG" und „ALLIANZ LEBENSVERS.AG" sind dieselbe Stelle, „Stadtwerke Bamberg" und „Stadtwerke Bayreuth" nicht. Ich fasse nur zusammen, was sicher ist, und lasse im Zweifel getrennt — zwei Zeilen zu viel sind harmloser als eine falsche Gruppe. Zeiterfassung läuft.
florian.wilde worked for 47 minutes 2026-08-23 00:42:03 +00:00
florian.wilde added zu-testen and removed in-arbeit labels 2026-08-23 00:43:18 +00:00
Author
Owner

✅ Umsetzung abgeschlossen — bereit zum Testen.

Was geändert wurde

  • Neue Ansicht Vermögen & Kasse → Wiederkehrende Zahlungen: Buchungen nach Empfänger gruppiert, nach Jahresbetrag sortiert, mit Rhythmus, Betragsspanne, Zeitraum und dem Hinweis, ob schon ein Vertrag dazu vorliegt.
  • Aus der Zeile heraus: Vertrag anlegen mit vorbelegter Gesellschaft, Beitrag und Rhythmus — oder kein Vertrag mit Begründung, dauerhaft und rückholbar.
  • Zweiter Abschnitt für einzelne größere Zahlungen ab 150 €. Grund: Die Gruppierung braucht zwei Zahlungen, ein Jahresbeitrag erscheint bei einem Jahr Auszug aber nur einmal.
  • Hinweis auf Heute in der Kachel Unverbuchte Belege, sobald Kandidaten offen sind.
  • Migration 20260823000500_zahlungsgruppe_geklaert (nur eine neue Tabelle).
  • .claude/ ins .gitignore — dort liegen die Worktrees paralleler Agenten.

Geänderte Dateien

  • src/lib/finanzen/wiederkehrend.ts (neu), wiederkehrend-laden.ts (neu)
  • src/app/(app)/finanzen/wiederkehrend/page.tsx (neu)
  • src/components/finanzen/wiederkehrend-aktionen.tsx (neu)
  • src/server/actions/finanzen.ts, src/app/(app)/finanzen/page.tsx, src/app/(app)/vertraege/neu/page.tsx
  • src/lib/dashboard/{laden,typen}.ts, src/components/dashboard/belege-kachel.tsx
  • tests/wiederkehrend.test.ts (neu), e2e/wiederkehrend.spec.ts (neu)
  • Schema, Migration, Changelog, README, Installationsanleitung, Version

So testest du es

  1. git switch feat/3-wiederkehrende-zahlungen und npm run db:deploy
  2. npm run dev, dann Vermögen & Kasse → Buchungen → Import und einen echten Kontoauszug einlesen
  3. Vermögen & Kasse → Wiederkehrende Zahlungen öffnen
  4. Erwartet: Deine regelmäßigen Abbuchungen stehen gruppiert da, die teuerste oben, mit erkanntem Rhythmus
  5. Bei einer davon auf Vertrag anlegen — Gesellschaft und Beitrag sind vorbelegt, Bezeichnung und Art nicht
  6. Bei Miete oder Rundfunkbeitrag auf Kein Vertrag, Begründung eintragen, abhaken. Die Zeile verschwindet, unten steht sie unter „abgehakt" und lässt sich zurückholen
  7. Gegenprobe: Seite neu laden — das Abgehakte bleibt abgehakt
  8. Auf Heute steht in der Kachel Unverbuchte Belege die Zahl der offenen Kandidaten

Aufgewendete Zeit: 00:47
Pull Request: #13


Anmerkungen

Zwei Fehler hat erst der Browsertest gefunden, beide an Typprüfung und Einheitstests vorbei. Einer davon: ein verlorener Backslash machte aus /^\d{1,15}$/ ein /^d{1,15}$/ — die Vorbelegung des Beitrags wurde stillschweigend verschluckt, alles andere funktionierte. Der zweite: Meine Regel „längste Schreibweise gewinnt" wählte die geschriene Variante aus dem Auszug, die dann so ins Vertragsformular gewandert wäre.

Was diese Auswertung nicht findet: Verträge ohne Zahlung im importierten Zeitraum — beitragsfreie Lebensversicherungen, ruhende Verträge, alles vor dem ersten Auszug. Der Kontoauszug ist eine sehr gute Quelle, aber keine vollständige.

Nebenbei aufgefallen (gehört ins Dashboard, nicht Teil dieses PR):

  1. prisma migrate diff will jedes Mal die Trigramm-Indizes löschen. Sie stammen aus prisma/sql/suche.sql und sind Prisma unbekannt. Zum zweiten Mal in Folge musste ich die DROP INDEX-Zeilen von Hand aus der erzeugten Migration entfernen — ein übersehener Durchlauf legt die unscharfe Suche still, ohne dass etwas fehlschlägt. Ein kleines Skript npm run db:migration , das erzeugt und die Zeilen gleich herausnimmt, würde die Falle dauerhaft schließen.
  2. Der Demofall hat keine einzige Buchung. Dadurch lässt sich weder diese Seite noch das Kassenbuch oder die Belegquote im Demofall vorführen, und Tests müssen sich ihre Daten selbst anlegen. Ein Jahr plausibler Kontobewegungen im Seed wäre für Vorführung und Tests gleichermaßen nützlich.

Nach Freigabe: /issue-freigabe 3

✅ **Umsetzung abgeschlossen — bereit zum Testen.** <!-- claude-status branch: feat/3-wiederkehrende-zahlungen pr: 13 zeit_sekunden: 2846 --> **Was geändert wurde** - Neue Ansicht **Vermögen & Kasse → Wiederkehrende Zahlungen**: Buchungen nach Empfänger gruppiert, nach Jahresbetrag sortiert, mit Rhythmus, Betragsspanne, Zeitraum und dem Hinweis, ob schon ein Vertrag dazu vorliegt. - Aus der Zeile heraus: *Vertrag anlegen* mit vorbelegter Gesellschaft, Beitrag und Rhythmus — oder *kein Vertrag* mit Begründung, dauerhaft und rückholbar. - Zweiter Abschnitt für **einzelne größere Zahlungen** ab 150 €. Grund: Die Gruppierung braucht zwei Zahlungen, ein Jahresbeitrag erscheint bei einem Jahr Auszug aber nur einmal. - Hinweis auf **Heute** in der Kachel *Unverbuchte Belege*, sobald Kandidaten offen sind. - Migration `20260823000500_zahlungsgruppe_geklaert` (nur eine neue Tabelle). - `.claude/` ins `.gitignore` — dort liegen die Worktrees paralleler Agenten. **Geänderte Dateien** - `src/lib/finanzen/wiederkehrend.ts` *(neu)*, `wiederkehrend-laden.ts` *(neu)* - `src/app/(app)/finanzen/wiederkehrend/page.tsx` *(neu)* - `src/components/finanzen/wiederkehrend-aktionen.tsx` *(neu)* - `src/server/actions/finanzen.ts`, `src/app/(app)/finanzen/page.tsx`, `src/app/(app)/vertraege/neu/page.tsx` - `src/lib/dashboard/{laden,typen}.ts`, `src/components/dashboard/belege-kachel.tsx` - `tests/wiederkehrend.test.ts` *(neu)*, `e2e/wiederkehrend.spec.ts` *(neu)* - Schema, Migration, Changelog, README, Installationsanleitung, Version **So testest du es** 1. `git switch feat/3-wiederkehrende-zahlungen` und `npm run db:deploy` 2. `npm run dev`, dann **Vermögen & Kasse → Buchungen → Import** und einen echten Kontoauszug einlesen 3. **Vermögen & Kasse → Wiederkehrende Zahlungen** öffnen 4. Erwartet: Deine regelmäßigen Abbuchungen stehen gruppiert da, die teuerste oben, mit erkanntem Rhythmus 5. Bei einer davon auf *Vertrag anlegen* — Gesellschaft und Beitrag sind vorbelegt, Bezeichnung und Art nicht 6. Bei Miete oder Rundfunkbeitrag auf *Kein Vertrag*, Begründung eintragen, abhaken. Die Zeile verschwindet, unten steht sie unter „abgehakt" und lässt sich zurückholen 7. Gegenprobe: Seite neu laden — das Abgehakte bleibt abgehakt 8. Auf **Heute** steht in der Kachel *Unverbuchte Belege* die Zahl der offenen Kandidaten **Aufgewendete Zeit:** 00:47 **Pull Request:** #13 --- **Anmerkungen** **Zwei Fehler hat erst der Browsertest gefunden**, beide an Typprüfung und Einheitstests vorbei. Einer davon: ein verlorener Backslash machte aus `/^\d{1,15}$/` ein `/^d{1,15}$/` — die Vorbelegung des Beitrags wurde stillschweigend verschluckt, alles andere funktionierte. Der zweite: Meine Regel „längste Schreibweise gewinnt" wählte die geschriene Variante aus dem Auszug, die dann so ins Vertragsformular gewandert wäre. **Was diese Auswertung nicht findet:** Verträge ohne Zahlung im importierten Zeitraum — beitragsfreie Lebensversicherungen, ruhende Verträge, alles vor dem ersten Auszug. Der Kontoauszug ist eine sehr gute Quelle, aber keine vollständige. **Nebenbei aufgefallen** (gehört ins Dashboard, nicht Teil dieses PR): 1. **`prisma migrate diff` will jedes Mal die Trigramm-Indizes löschen.** Sie stammen aus `prisma/sql/suche.sql` und sind Prisma unbekannt. Zum zweiten Mal in Folge musste ich die `DROP INDEX`-Zeilen von Hand aus der erzeugten Migration entfernen — ein übersehener Durchlauf legt die unscharfe Suche still, ohne dass etwas fehlschlägt. Ein kleines Skript `npm run db:migration` , das erzeugt und die Zeilen gleich herausnimmt, würde die Falle dauerhaft schließen. 2. **Der Demofall hat keine einzige Buchung.** Dadurch lässt sich weder diese Seite noch das Kassenbuch oder die Belegquote im Demofall vorführen, und Tests müssen sich ihre Daten selbst anlegen. Ein Jahr plausibler Kontobewegungen im Seed wäre für Vorführung und Tests gleichermaßen nützlich. Nach Freigabe: `/issue-freigabe 3`
Author
Owner

🚀 Freigegeben und nach main übernommen.

  • Merge-Commit: 8779cc6
  • Pull Request: #13 (gemergt)
  • Gesamtaufwand: 00:47
  • Veröffentlicht als v1.2.0 — der Tag ist gesetzt, sonst findet update.sh die Fassung nicht.

Zusammen mit #10 freigegeben. Die Kombination beider Branches ist nach dem Merge noch einmal geprüft worden: 537 Tests, 0 Typfehler, Lint sauber.

Beim Ausrollen: Eine Migration läuft mit (20260823000500_zahlungsgruppe_geklaert). Sie legt nur eine neue Tabelle an, bestehende Daten bleiben unberührt.

cd /opt/docker/configs/betreuungs-cockpit
./scripts/update.sh

Issue wird geschlossen.

🚀 **Freigegeben und nach `main` übernommen.** <!-- claude-status merge: 8779cc603e8a2a6a1a0a80f4e0b2f8e8b8c0e6d1 pr: 13 zeit_sekunden: 2846 --> - Merge-Commit: `8779cc6` - Pull Request: #13 (gemergt) - Gesamtaufwand: 00:47 - Veröffentlicht als **v1.2.0** — der Tag ist gesetzt, sonst findet `update.sh` die Fassung nicht. Zusammen mit #10 freigegeben. Die Kombination beider Branches ist nach dem Merge noch einmal geprüft worden: 537 Tests, 0 Typfehler, Lint sauber. **Beim Ausrollen:** Eine Migration läuft mit (`20260823000500_zahlungsgruppe_geklaert`). Sie legt nur eine neue Tabelle an, bestehende Daten bleiben unberührt. ```bash cd /opt/docker/configs/betreuungs-cockpit ./scripts/update.sh ``` Issue wird geschlossen.
florian.wilde removed the zu-testen label 2026-08-23 06:15:46 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Total Time Spent: 47 minutes
florian.wilde
47 minutes
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: florian.wilde/betreuungs-cockpit#3