Phase 9: Kassenverwaltung (#14)
This commit was merged in pull request #14.
This commit is contained in:
+105
-1
@@ -21,7 +21,111 @@ Die aufgewendete Zeit wandert nach jedem Abschnitt in die Spalte
|
||||
| Phase 7 — Benachrichtigungen und Termine | 3:10 h | 1 | 04.09.2026 |
|
||||
| Phase 8 — Lokale KI | 3:40 h | 1 | 04.09.2026 |
|
||||
| Aufräumarbeiten (Issues #10–#12) | 1:20 h | 1 | 04.09.2026 |
|
||||
| **Summe** | **24:20 h** | **11** | |
|
||||
| Phase 9 — Kassenverwaltung | 4:10 h | 1 | 04.09.2026 |
|
||||
| **Summe** | **28:30 h** | **12** | |
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-04 · Phase 9 — Kassenverwaltung
|
||||
|
||||
**12:40 – 16:50 · 4:10 h · Version 0.10.0**
|
||||
|
||||
### Was entstanden ist
|
||||
|
||||
Neun Modelle, drei Trigger, zwölf CHECKs, sechs Router, fünf Ansichten und
|
||||
ein Kassenjahr im Seed.
|
||||
|
||||
Der Reihe nach, wie es CLAUDE.md vorgibt: Schema, Migration, Serverlogik, UI,
|
||||
Tests.
|
||||
|
||||
| Bereich | Umfang |
|
||||
|---|---|
|
||||
| Datenmodell | `Kassenkonto`, `Buchungskategorie`, `Buchung`, `Beitragssatz`, `Beitrag`, `Rechnung`, `Rechnungsposition`, `Kassenabschluss`, `Abschlussposten` |
|
||||
| Absicherung | RLS für alle neun Tabellen, drei Trigger, zwölf CHECKs, ein REVOKE |
|
||||
| Bibliothek | `lib/kasse/betrag.ts`, `bankimport.ts`, `vorschlag.ts`, `texte.ts`, `dokumente.ts` |
|
||||
| Router | Konten, Kategorien, Buchungen, Beiträge, Rechnungen, Abschluss |
|
||||
| Oberfläche | Übersicht, Kassenbuch, Beiträge, Rechnungen, Abschluss mit Auswertung |
|
||||
| Hintergrund | Rechnungs-PDF, Kassenprüfbericht, Meldung überfälliger Posten |
|
||||
| Tests | 67 Unit-Tests (13 gegen eine echte Datenbank), 13 E2E-Tests |
|
||||
|
||||
### Entscheidungen
|
||||
|
||||
**Die Sperren sitzen in der Datenbank, nicht im Router.** Die ganze Phase
|
||||
hängt an einem Satz: was geprüft ist, ändert sich nicht mehr. Ein
|
||||
Zustandscheck in `buchung.aendern` erfüllt das nicht — er gilt für genau
|
||||
diesen einen Weg. Ein zweiter Router, ein Skript, ein Import, und der geprüfte
|
||||
Bestand ist ein anderer, ohne dass irgendwo etwas auffällt. Also drei Trigger:
|
||||
festgeschriebene Buchung unveränderbar, geprüfter Zeitraum zu, festgeschriebener
|
||||
Abschluss fest.
|
||||
|
||||
**Jeder Trigger hat genau eine Ausnahme, und jede hat einen Grund.** Die
|
||||
Buchung lässt den Verweis auf ihre Gegenbuchung eintragen — eine Stornierung
|
||||
entsteht später, sie einzutragen ist keine Änderung des Vorgangs, sondern
|
||||
seine Fortschreibung. Der Abschluss lässt den Prüfbericht nachtragen, denn das
|
||||
PDF entsteht im Hintergrund und damit nach dem Festschreiben. Ohne diese
|
||||
Ausnahme gäbe es nie einen Bericht.
|
||||
|
||||
**Der Bestand wird gerechnet, nie gespeichert.** Ein gespeicherter Bestand
|
||||
neben einer nachgetragenen Buchung wäre falsch, ohne falsch auszusehen — und
|
||||
genau danach sucht ein Kassenprüfer. Bei den Größenordnungen einer
|
||||
Feuerwehrkasse kostet das Rechnen nichts.
|
||||
|
||||
**Der Kategorievorschlag geht dem eigenen Bestand vor der Stichwortliste
|
||||
nach.** Wenn eine Wehr Diesel seit Jahren unter „Fahrzeuge" bucht statt unter
|
||||
„Kraftstoff", ist das ihre Entscheidung, und sie soll dabei bleiben dürfen.
|
||||
Die Stichwortliste greift erst, wenn es noch keinen Bestand gibt. Unter der
|
||||
Hälfte gemeinsamer Wörter schweigt der Vorschlag: ein falscher wird
|
||||
übernommen, ohne gelesen zu werden.
|
||||
|
||||
**Der Bankimport erzeugt Entwürfe, keine Buchungen.** Und jeder Umsatz trägt
|
||||
eine Kennung, damit ein zweimal eingelesener Auszug als solcher erkannt wird.
|
||||
Ohne das wäre es ein doppelt gebuchter Monat, und der fiele erst beim
|
||||
Kassenabschluss auf — also drei Monate später.
|
||||
|
||||
**Kein XML-Parser für CAMT.** Gebraucht werden sechs Felder aus einer flachen
|
||||
Struktur, die Datei kommt von der eigenen Bank. Der Parser ist kürzer als die
|
||||
Einbindung, und in einer Anwendung ohne Netzzugang ist jede Abhängigkeit
|
||||
weniger eine Sorge weniger. Namensräume werden ignoriert — genau das, was ein
|
||||
Parser hier auch täte.
|
||||
|
||||
**In der Prüfmaske stehen die gezählten Felder leer.** Der Buchbestand ist zu
|
||||
sehen, weil der Prüfer ihn für die Differenz braucht. Vorbelegt wird nichts:
|
||||
wer erst das Buch sieht und dann zählt, zählt, bis es passt.
|
||||
|
||||
### Wo es gehakt hat
|
||||
|
||||
| Problem | Ursache | Lösung |
|
||||
|---|---|---|
|
||||
| Der Abschluss ließ sich nicht mit seinem Bericht versehen | Der Trigger sperrte auch `berichtDateiId` — das PDF entsteht aber erst nach dem Festschreiben | Ausnahme für genau dieses Feld, wie beim Stornierungsverweis der Buchung |
|
||||
| Die Rechnungsnummer war ein Wettlauf | Höchste lesen, dann schreiben: zwei gleichzeitige Rechnungen lesen dieselbe Nummer | Zusammenstoß erkennen und die nächste nehmen; gefunden hat es der parallele E2E-Lauf über zwei Projekte |
|
||||
| Eine Organisation ließ sich nicht mehr löschen | Der DELETE-Trigger der Buchung kannte den Aufbewahrungsschalter nicht, den Audit-Log und Prüfprotokoll haben | `app.audit_bereinigung` auch dort — ohne ihn wäre die DSGVO-Löschung nicht durchführbar |
|
||||
| Der Datenbanktest ließ sich nur einmal ausführen | Dieselbe Ursache: das Aufräumen scheiterte an den festgeschriebenen Buchungen, und der Rest lief im nächsten Lauf gegen einen geprüften Zeitraum | siehe oben |
|
||||
| E2E: „Barkasse" fand sieben Elemente | Das Wort steht auch in Verwendungszwecken | über die Kartenüberschrift angesprochen |
|
||||
| E2E: die erste Maifest-Zeile war die falsche | Absteigend sortiert steht das laufende Jahr oben, und das ist nicht festgeschrieben | eine Buchung genommen, die es nur im Vorjahr gibt — und die Gegenprobe dazu |
|
||||
| Prisma wollte zum zehnten Mal die Trigramm-Indizes aus Phase 1 löschen | Sie stehen in einer Roh-SQL-Migration, nicht im Schema | 30 DropIndex-Blöcke entfernt, Kommentar in die Migration |
|
||||
| Der volle E2E-Lauf meldete quer durch alle Module Fehler | `npm run build` während eines laufenden `next dev`: der Produktionslauf überschreibt `.next`, und der Entwicklungsserver antwortet danach nur noch mit 500 | Entwicklungsserver beenden, `.next` löschen, neu laufen lassen — 239 grün |
|
||||
| Ein E2E-Test aus Phase 0 wurde rot | Er sicherte zu, dass die Kasse **kein** Verweis ist — die Zusicherung ist mit dieser Phase abgelaufen | umgeschrieben: die Kasse ist jetzt ein Verweis. Ein künftiges Modul zum Ausgrauen gibt es nicht mehr |
|
||||
|
||||
### Abweichung von der Vorgabe
|
||||
|
||||
Keine. `Phase-9.md` ist vollständig umgesetzt, einschließlich CAMT.053, MT940,
|
||||
Nummernkreis, Kassenprüfbericht und Export.
|
||||
|
||||
Der Beleg-Anhang an einer Buchung nutzt den `Belegentwurf` aus Phase 8 mit —
|
||||
er hat dafür einen `buchungId` bekommen. Fotografieren und Erfassen war
|
||||
bereits da; neu ist nur, dass daraus eine Buchung wird.
|
||||
|
||||
### Nachweise
|
||||
|
||||
- `npm run typecheck` — ohne Ausgabe
|
||||
- `npm run lint` — ohne Befund
|
||||
- `npm run test` — 562 Tests grün, davon 13 neu gegen eine echte Datenbank
|
||||
- `npm run test:e2e` — 239 Tests grün über Desktop und Tablet, davon 26 neue
|
||||
- `npm run build` — fünf neue Routen unter `/kasse`
|
||||
- `npm run db:seed` — Kassenjahr angelegt, Abschluss festgeschrieben
|
||||
- Rechnungs-PDF (41 kB) und Kassenprüfbericht (45 kB) einmal von Hand über
|
||||
Gotenberg erzeugt und in MinIO abgelegt — die beiden Vorlagen haben sonst
|
||||
keinen automatischen Nachweis
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user