Phase 9: Kassenverwaltung (#14)
This commit was merged in pull request #14.
This commit is contained in:
+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