Phase 9: Kassenverwaltung
Kassenbuch, Mitgliedsbeitraege, Rechnungen an Dritte, Kassenpruefung und die Auswertungen fuer die Jahreshauptversammlung. Die drei Festlegungen aus Phase-9.md sitzen in der Datenbank, nicht im Router: eine festgeschriebene Buchung ist unveraenderbar, ein geprueftes Jahr nimmt kein Datum mehr an, und gerechnet wird mit Prisma.Decimal. Korrigiert wird ueber eine Gegenbuchung mit Verweis - beide Buchungen bleiben stehen, damit im Kassenbuch zu sehen ist, dass etwas berichtigt wurde. Neun Modelle, drei Trigger, zwoelf CHECKs, sechs Router, fuenf Ansichten. Dazu der Bankimport fuer CAMT.053 und MT940 ohne Fremdbibliothek, der Kategorievorschlag aus dem eigenen Buchungsbestand und die Bruecke von Phase 8: aus einem gegengezeichneten Beleg wird eine Buchung, das Foto haengt danach an ihr. Nebenbei behoben: die Rechnungsnummer war ein Wettlauf, und eine Organisation liess sich nicht mehr loeschen, sobald sie eine festgeschriebene Buchung hatte - der Trigger kannte den Aufbewahrungsschalter nicht. 562 Unit-Tests und 239 E2E-Tests gruen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d70acad4cd
commit
4165334c5a
+158
-1
@@ -6,11 +6,168 @@ Alle nennenswerten Änderungen an FLORIAN. Format nach
|
||||
|
||||
Die Regeln dazu stehen in [docs/VERSIONIERUNG.md](docs/VERSIONIERUNG.md).
|
||||
|
||||
## [Unveröffentlicht]
|
||||
## [0.10.0] — 2026-09-04
|
||||
|
||||
Phase 9: Kassenverwaltung. Kassenbuch, Mitgliedsbeiträge, Rechnungen an
|
||||
Dritte, Kassenprüfung und die Auswertungen für die Jahreshauptversammlung.
|
||||
|
||||
**Eine festgeschriebene Buchung ist unveränderbar.** Nicht, weil ein Zweig im
|
||||
Router es so vorsieht, sondern weil die Datenbank es ablehnt. Korrigiert wird
|
||||
über eine Gegenbuchung mit Verweis; beide Buchungen bleiben stehen, und im
|
||||
Kassenbuch ist zu sehen, dass etwas berichtigt wurde. Genau das will ein
|
||||
Kassenprüfer sehen — eine verschwundene Buchung dagegen kann er nicht prüfen.
|
||||
|
||||
**Der Kassenabschluss schließt seinen Zeitraum.** Danach wird keine Buchung
|
||||
mit einem Datum darin mehr angenommen, auch keine nachträglich hineindatierte.
|
||||
Ohne diese zweite Hälfte stimmte der geprüfte Bestand nach einem
|
||||
Datumswechsel nicht mehr — und niemand hätte es bemerkt.
|
||||
|
||||
**Gerechnet wird mit `Prisma.Decimal`, nirgends mit Fließkomma.** `0.1 + 0.2`
|
||||
ergibt in JavaScript 0.30000000000000004. In einer Kasse ist das kein
|
||||
Rundungsfehler, sondern eine Differenz in der Prüfung, die niemand erklären
|
||||
kann.
|
||||
|
||||
### Hinzugefügt
|
||||
|
||||
**Datenmodell**
|
||||
|
||||
- `Kassenkonto` (bar, Bank, Rücklage) mit Anfangsbestand. Der laufende Bestand
|
||||
wird **gerechnet und nie gespeichert**: ein gespeicherter Bestand neben
|
||||
einer nachgetragenen Buchung wäre falsch, ohne falsch auszusehen.
|
||||
- `Buchungskategorie` als zweistufiger Baum, mit Kontonummer für den Export an
|
||||
die Kämmerei.
|
||||
- `Buchung` mit Datum, Richtung, immer positivem Betrag, Verwendungszweck,
|
||||
Belegnummer, Beleg-Datei und dem Verweis auf die Gegenbuchung.
|
||||
- `Beitragssatz` und `Beitrag` (offen, bezahlt, erlassen, gemahnt) mit
|
||||
Mahnstufe und Erlassbegründung.
|
||||
- `Rechnung` und `Rechnungsposition` mit Nummernkreis je Jahr. Die Menge hat
|
||||
drei Nachkommastellen — Einsatzstunden werden viertelstündlich abgerechnet.
|
||||
- `Kassenabschluss` und `Abschlussposten` mit Bestand laut Buch, gezähltem
|
||||
Bestand, Differenz, zwei Prüfern und dem Bericht als PDF.
|
||||
|
||||
**Was die Datenbank durchsetzt** (Migration `20260904140000`)
|
||||
|
||||
- RLS für alle neun Tabellen, wie in jeder Phase zuvor.
|
||||
- Trigger `buchung_unveraenderlich` — festgeschriebene Buchungen lassen sich
|
||||
weder ändern noch löschen. Änderbar bleibt genau eines: der Verweis auf die
|
||||
Gegenbuchung. Eine Stornierung entsteht später; sie einzutragen ist keine
|
||||
Änderung des Vorgangs, sondern seine Fortschreibung.
|
||||
- Trigger `buchung_zeitraum_offen` — ein geprüfter Zeitraum nimmt weder eine
|
||||
neue Buchung an noch das Hineindatieren einer vorhandenen.
|
||||
- Trigger `abschluss_unveraenderlich` — ein festgeschriebener Abschluss bleibt,
|
||||
wie er ist. Nachtragen lässt sich nur der Prüfbericht, denn das PDF entsteht
|
||||
im Hintergrund und damit nach dem Festschreiben.
|
||||
- `REVOKE DELETE ON kassenabschluss` für die Anwendung.
|
||||
- CHECKs, die verhindern, was sich hinterher nicht mehr erkennen ließe: ein
|
||||
Betrag von null oder darunter, eine Buchung an eine Person **und** einen
|
||||
Dritten zugleich, ein erlassener Beitrag ohne Begründung, ein Abschluss mit
|
||||
einer Differenz, die nicht zu seinen eigenen Zahlen passt, eine Rechnung,
|
||||
die fällig wird, bevor sie geschrieben wurde.
|
||||
|
||||
**Kassenbuch**
|
||||
|
||||
- Buchungsmaske mit **Kategorievorschlag**: zuerst aus den eigenen früheren
|
||||
Buchungen der Wehr, erst danach aus einer kurzen Stichwortliste. Unter der
|
||||
Hälfte gemeinsamer Wörter schweigt der Vorschlag — ein falscher wird
|
||||
übernommen, ohne gelesen zu werden.
|
||||
- Summen über **alle** Treffer, nicht nur über die angezeigte Seite.
|
||||
- Festschreiben als Sammelaktion bis zu einem Stichtag.
|
||||
- Gegenbuchen mit Pflichtbegründung; das Buchungsdatum ist immer heute, damit
|
||||
ein geprüfter Zeitraum zubleibt.
|
||||
- **Aus einem gegengezeichneten Beleg wird eine Buchung.** Die Brücke zu
|
||||
Phase 8: fotografiert und ausgefüllt wird am Terminal oder am Handy,
|
||||
gebucht wird in der Kasse — mit Konto und Kategorie, die keine Maschine
|
||||
entscheiden kann. Das Foto hängt danach an der Buchung, und genau das
|
||||
verlangt eine Kassenprüfung: zu jeder Ausgabe ein Beleg, an der Buchung
|
||||
und nicht in einem Ordner daneben. Ein Entwurf lässt sich nicht buchen.
|
||||
|
||||
**Kontoauszug einlesen**
|
||||
|
||||
- CAMT.053 und MT940, beides ohne zusätzliche Abhängigkeit. Erkannt wird am
|
||||
Inhalt, nicht an der Dateiendung.
|
||||
- Es entstehen **Entwürfe, keine fertigen Buchungen**.
|
||||
- Jeder Umsatz trägt eine Kennung — die der Bank, sonst eine aus Datum, Betrag
|
||||
und Zweck gebildete. Ein zweimal eingelesener Auszug wird dadurch als solcher
|
||||
erkannt, statt einen Monat doppelt zu buchen.
|
||||
- Was nicht gelesen werden konnte, steht mit Grund daneben, statt still zu
|
||||
verschwinden.
|
||||
|
||||
**Beiträge**
|
||||
|
||||
- Sätze je Abteilung, gültig ab einem Jahr und nicht rückwirkend änderbar.
|
||||
- Beitragslauf mit **Vorschau**: wie viele Beiträge entstehen, welche Summe —
|
||||
und wer keinen passenden Satz hat. Der Lauf ist wiederholbar, ohne dass
|
||||
etwas doppelt entsteht.
|
||||
- Zahlungseingang erzeugt zugleich die Buchung. Ein Häkchen ohne Geld in der
|
||||
Kasse wäre eine Behauptung.
|
||||
- Mahnen als Sammelaktion, Erlassen nur mit Begründung.
|
||||
- Rückstandsliste über alle Jahre — die Ansicht, in der auffällt, wer seit
|
||||
drei Jahren offen steht.
|
||||
|
||||
**Rechnungen**
|
||||
|
||||
- Fortlaufende Nummer je Jahr, lückenlos: eine fehlende Nummer muss in der
|
||||
Kassenprüfung erklärt werden.
|
||||
- Der Betrag ist die Summe der Positionen, gerechnet und nicht eingegeben.
|
||||
- Bis zum Versenden ein Entwurf, danach unveränderbar. Korrigiert wird über
|
||||
Storno und eine neue Rechnung — die stornierte bleibt mit Grund stehen.
|
||||
- PDF über Gotenberg, im Hintergrund erzeugt.
|
||||
|
||||
**Kassenprüfung und Auswertung**
|
||||
|
||||
- Prüfmaske: links der Bestand laut Buch, rechts der gezählte. In dieser
|
||||
Reihenfolge — wer erst das Buch sieht und dann zählt, zählt, bis es passt.
|
||||
- Ohne Prüfer und Prüfdatum lässt sich nichts festschreiben; eine Differenz
|
||||
ohne Erläuterung ebenso wenig.
|
||||
- Kassenprüfbericht als PDF mit vier Unterschriftsfeldern.
|
||||
- Jahresauswertung nach Kategorie und Monat, mit Vorjahresvergleich.
|
||||
- CSV-Export mit Semikolon und BOM — was Excel in Deutschland ohne Rückfrage
|
||||
richtig öffnet.
|
||||
|
||||
**Tests**
|
||||
|
||||
- 67 neue Unit-Tests: Geldrechnen, beide Auszugsformate, Kategorievorschlag,
|
||||
und dreizehn gegen eine echte Datenbank, die die Trigger und CHECKs prüfen.
|
||||
Sie laufen über den Systemzugang: kommt nicht einmal der Eigentümer der
|
||||
Datenbank daran vorbei, kommt niemand vorbei.
|
||||
- 13 E2E-Tests über Desktop und Tablet.
|
||||
|
||||
**Seed**
|
||||
|
||||
- Ein vollständiges Kassenjahr: drei Konten, der Standardkontenrahmen,
|
||||
24 Buchungen über zwei Jahre, die Mitgliedsbeiträge mit Rückständen, zwei
|
||||
Rechnungen und die festgeschriebene Kassenprüfung des Vorjahres. Die Zahlen
|
||||
des Abschlusses sind die Summe der Buchungen und nicht erfunden.
|
||||
|
||||
### Geändert
|
||||
|
||||
- Ein E2E-Test aus Phase 0 sicherte zu, dass die Kasse in der Navigation
|
||||
**kein** Verweis ist — sie war ja noch nicht da. Die Zusicherung ist mit
|
||||
dieser Phase abgelaufen und prüft jetzt das Gegenteil. Ein künftiges Modul
|
||||
zum Ausgrauen gibt es nicht mehr; kommt später eines dazu, gehört sie
|
||||
zurück.
|
||||
|
||||
### Behoben
|
||||
|
||||
- **Die Rechnungsnummer war ein Wettlauf.** Höchste lesen und dann schreiben:
|
||||
schreiben zwei Leute gleichzeitig eine Rechnung, lesen beide dieselbe
|
||||
höchste Nummer, und die zweite läuft in den Eindeutigkeitsfehler. Gefunden
|
||||
hat das der parallele E2E-Lauf. Der seltene Zusammenstoß wird jetzt erkannt
|
||||
und die nächste Nummer genommen — lückenlos bleibt sie dabei.
|
||||
- **Eine Organisation ließ sich nicht mehr löschen**, sobald sie eine
|
||||
festgeschriebene Buchung hatte: der Trigger lehnte auch das Löschen im
|
||||
Aufbewahrungslauf ab. Er kennt jetzt denselben Schalter wie das Audit-Log
|
||||
und das Prüfprotokoll (`app.audit_bereinigung`). Ohne ihn wäre die
|
||||
DSGVO-Löschung einer ausscheidenden Wehr nicht durchführbar gewesen.
|
||||
|
||||
---
|
||||
|
||||
## [0.9.1] — 2026-09-04
|
||||
|
||||
Aufräumarbeiten vor Phase 9. Drei Punkte, die sich über mehrere Phasen
|
||||
angesammelt hatten und in jeder Phase Zeit gekostet haben.
|
||||
|
||||
|
||||
### Hinzugefügt
|
||||
|
||||
- **ESLint** hat endlich eine Konfiguration (`eslint.config.mjs`, Flat Config).
|
||||
|
||||
Reference in New Issue
Block a user