# Changelog Alle nennenswerten Änderungen an FLORIAN. Format nach [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), Versionierung nach [Semantic Versioning](https://semver.org/lang/de/). Die Regeln dazu stehen in [docs/VERSIONIERUNG.md](docs/VERSIONIERUNG.md). ## [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). Bis dahin gab es das Skript `npm run lint`, aber nichts, was es hätte prüfen können — ein Werkzeug, das aussieht, als prüfe es etwas, und nichts prüft, ist schlechter als gar keines. Bewusst **ohne** Formatierungsregeln: die macht Prettier. Dazu `npm run lint:fix`. - **Eine eigene Wehr für die E2E-Tests.** `npm run db:test-reset` legt `ff-teststelle` neu an — vollständig, mit denselben Beispieldaten, aber unter eigener Kennung. Ein Riegel im Skript bricht ab, wenn die Testkennung mit der der Beispielwehr übereinstimmt. - `tests/e2e/wehr.ts` — Zugangsdaten und Anmeldung an einer Stelle statt achtmal wortgleich in acht Dateien. Schlägt die Anmeldung fehl, sagt die Meldung, dass die Testwehr fehlt, statt zweihundertmal „Timeout". ### Behoben - **Die E2E-Tests schrieben in die Beispielwehr.** Nach fünf Phasen standen dort 120 Atemschutz-Überwachungen statt einer, 115 Geräte statt 45 und 39 Einsätze statt 8. Zweimal fielen Tests aus, weil ein gesuchter Eintrag von Seite 1 gerutscht war; bei jedem Lauf unterschieden sich alle 72 Bildschirmfotos. Der Bestand ist einmal zurückgesetzt, die Tests laufen jetzt gegen die eigene Wehr. - **`
` auf der Einsatzseite** — die Meldung stand seit
Phase 3 in der Konsole und ist der klassische Auslöser für einen
Hydration-Fehler. Die Wurzel war das `Badge`: es rendert jetzt ein ``
statt eines `