# Schema-Wünsche aus Etappe 4 „Geld“ Beim Bau des Finanzmoduls sind Stellen aufgefallen, an denen das Datenmodell etwas anders aussehen müsste. `prisma/schema.prisma` wurde **nicht** angefasst. Sortiert nach Dringlichkeit. --- ## 1. Umbuchungen kennen ihr Gegenkonto nicht **Modell:** `Buchung` Eine Umbuchung zwischen zwei eigenen Konten – Giro aufs Sparbuch – lässt sich derzeit nur als zwei getrennte Buchungen abbilden. Im Modell fehlt die Angabe, wohin das Geld gegangen ist. Für die Rechenprobe ist das folgenlos (Umbuchungen sind weder Einnahme noch Ausgabe und werden herausgerechnet), für den Nutzer aber unbequem: Er muss zweimal erfassen, und niemand prüft, ob beide Seiten zusammenpassen. ```prisma model Buchung { gegenkontoId String? gegenkonto Konto? @relation("Umbuchungsgegenkonto", fields: [gegenkontoId], references: [id], onDelete: SetNull) } ``` Damit ließe sich eine Umbuchung als **eine** Zeile erfassen und beim Bilden der Kontostände auf beiden Seiten berücksichtigen. --- ## 2. Dublettenschutz greift nicht bei Buchungen ohne Konto **Modell:** `Buchung`, `@@unique([kontoId, importReferenz])` In PostgreSQL ist `NULL` nie gleich `NULL`. Die zusammengesetzte Eindeutigkeit schützt deshalb nur, solange `kontoId` gesetzt ist. Bei einem Import ohne Konto – derzeit über die Oberfläche nicht möglich, aber im Modell erlaubt – könnte derselbe Umsatz beliebig oft angelegt werden. Zwei mögliche Auswege: - `kontoId` bei Buchungen mit `importReferenz` verpflichtend machen (Prüfung in der Anwendung, Constraint im Schema nicht ausdrückbar), oder - eine partielle Eindeutigkeit über Roh-SQL ergänzen: ```sql CREATE UNIQUE INDEX buchung_import_ohne_konto ON buchung (betreute_id, import_referenz) WHERE konto_id IS NULL AND import_referenz IS NOT NULL; ``` Die Anwendung erzwingt derzeit ein Konto beim Import; der Schutz hängt also allein an der Oberfläche. --- ## 3. Saldostand und Wertstand haben keinen Papierkorb **Modelle:** `Saldostand`, `Wertstand` Beide Modelle haben kein `geloeschtAm`. Löschen ist damit endgültig – im Gegensatz zu allem anderen im System. Das ist an dieser Stelle besonders unangenehm: Wird ein Wertstand entfernt, lässt sich eine bereits beim Gericht eingereichte Vermögensübersicht nicht mehr nachbilden. Die Oberfläche warnt deshalb ausdrücklich davor, aber ein Papierkorb wäre die bessere Absicherung. ```prisma model Saldostand { geloeschtAm DateTime? } model Wertstand { geloeschtAm DateTime? } ``` Nebenwirkung, die dabei zu bedenken ist: die bestehenden `@@unique([kontoId, stichtag])` bzw. `@@unique([vermoegenspostenId, stichtag])` würden dann auch gelöschte Einträge blockieren. Sie müssten auf partielle Indizes mit `WHERE geloescht_am IS NULL` umgestellt werden. --- ## 4. Wer hat den Stand erfasst? **Modelle:** `Saldostand`, `Wertstand` Beide tragen nur `erstelltAm`, keinen Verweis auf den Benutzer. Bei mehreren Betreuern oder Mitlesern mit Schreibrecht steht im Datensatz selbst nicht, wer die Zahl eingetragen hat. Das Audit-Log beantwortet die Frage, aber nur über eine gesonderte Recherche. ```prisma erstelltVonId String? erstelltVon User? @relation(fields: [erstelltVonId], references: [id], onDelete: SetNull) ``` --- ## 5. Indizes für die Auswertungen **Modelle:** `Buchung`, `Saldostand` Die Berichte lesen Buchungen fast immer entweder nach Konto und Zeitraum oder gefiltert auf fehlende Belege. Dafür gibt es bisher keine passenden Indizes: ```prisma model Buchung { @@index([kontoId, datum]) @@index([betreuteId, belegId]) } ``` Bei den erwarteten Datenmengen (einige tausend Buchungen je betreuter Person) ist das noch nicht spürbar; bei einem Import mehrerer Jahre Kontoauszüge wird es das. --- ## 6. Fremdwährung wird abgelehnt, nicht abgebildet **Modelle:** `Konto`, `Buchung`, `Saldostand`, `Wertstand` Es gibt kein Währungsfeld. Alle Beträge sind Euro-Cent. Der CSV- und CAMT-Import überspringt deshalb Zeilen in fremder Währung und meldet das dem Nutzer, statt umzurechnen – ein umgerechneter Betrag im Kassenbuch wäre in der Rechnungslegung eine Behauptung ohne Beleg. Das ist als bewusste Entscheidung dokumentiert und **kein** Wunsch nach einem Währungsfeld. Es steht hier nur, damit die Einschränkung nicht später für ein Versehen gehalten wird. --- ## 7. Der Betrag einer Buchung sollte positiv sein **Modell:** `Buchung.betragCent` Fachlich ist der Betrag immer positiv; die Richtung steht in `richtung`. Ein negativer Betrag bei `richtung = AUSGABE` würde die Vorzeichen zweimal drehen und die Summen still verfälschen. Die Anwendung prüft das beim Speichern (`betragFeld({ nurPositiv: true })`), in der Datenbank ist es nicht abgesichert. Ein Check-Constraint über Roh-SQL wäre die Ergänzung: ```sql ALTER TABLE buchung ADD CONSTRAINT buchung_betrag_positiv CHECK (betrag_cent > 0); ``` Vor einer solchen Migration müssten Altdaten geprüft werden.