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>
@@ -1,10 +1,8 @@
|
||||
# Anleitung
|
||||
|
||||
Bedienung von FLORIAN, Stand Phase 8 (Version 0.9.0). Die Bilder zeigen die
|
||||
Bedienung von FLORIAN, Stand Phase 9 (Version 0.10.0). Die Bilder zeigen die
|
||||
Beispielwehr aus dem Seed und werden mit `npm run screenshots` neu erzeugt.
|
||||
|
||||
> Die Kassenverwaltung kommt in Phase 9 dazu. Siehe [ROADMAP.md](ROADMAP.md).
|
||||
|
||||
---
|
||||
|
||||
## 1. Anmelden
|
||||
@@ -1680,9 +1678,9 @@ Prüfen Sie gegen das Foto, das links daneben steht. Dann **Gegenzeichnen**.
|
||||
Ohne Vision-Modell funktioniert alles bis auf das Auslesen: der Beleg wird
|
||||
fotografiert und abgelegt, die Angaben tragen Sie von Hand ein.
|
||||
|
||||
Die Buchung daraus kommt mit der Kasse in Phase 9. Hier entsteht erst einmal ein
|
||||
sauber erfasster Beleg mit Foto — und der ist schon mehr wert als ein
|
||||
Schuhkarton.
|
||||
Ist der Beleg gegengezeichnet, steht in der Liste der erledigten Belege ein
|
||||
Knopf **Buchen**. Er führt in einem Schritt ins Kassenbuch — zu wählen sind
|
||||
nur Konto und Kategorie. Siehe [Kapitel 48](#48-buchen).
|
||||
|
||||
### Das KI-Protokoll
|
||||
|
||||
@@ -1704,7 +1702,291 @@ Datensatz, seit dem ersten Tag.
|
||||
|
||||
---
|
||||
|
||||
## 47. Rollen und was sie dürfen
|
||||
## 47. Die Kasse
|
||||
|
||||
Unter **Kasse** stehen alle Konten der Wehr mit ihrem heutigen Bestand.
|
||||
|
||||

|
||||
|
||||
Drei Arten von Konten:
|
||||
|
||||
| Art | Wofür |
|
||||
|---|---|
|
||||
| **Barkasse** | Die Kassette im Gerätehaus. Wechselgeld für Feste, Kleinausgaben. |
|
||||
| **Bankkonto** | Das Girokonto der Wehr. Mit IBAN, damit sich Kontoauszüge zuordnen lassen. |
|
||||
| **Rücklage** | Angespartes für eine Beschaffung. Wird nicht laufend bewegt. |
|
||||
|
||||
**Der Bestand wird gerechnet, nicht gespeichert.** Anfangsbestand plus alle
|
||||
Buchungen. Wer nachträglich eine Buchung einträgt, sieht den Bestand sofort
|
||||
richtig — es gibt keinen zweiten Wert, der danebenläuft.
|
||||
|
||||
### Beim ersten Mal
|
||||
|
||||
Steht die Kasse leer, bietet die Übersicht an, den **Standardkontenrahmen**
|
||||
anzulegen: rund zwanzig Kategorien, wie sie eine Feuerwehrkasse braucht —
|
||||
Kraftstoff, Reparatur, Schutzkleidung, Ausbildung, Verpflegung auf der einen
|
||||
Seite; Zuschüsse, Spenden, Mitgliedsbeiträge, Veranstaltungen, Kostenersatz
|
||||
auf der anderen.
|
||||
|
||||
Sie lassen sich danach umbenennen, ergänzen und um eigene Unterkategorien
|
||||
erweitern. Wer eine eigene Gliederung hat, legt sie stattdessen von Hand an.
|
||||
|
||||
---
|
||||
|
||||
## 48. Buchen
|
||||
|
||||

|
||||
|
||||
Oben stehen drei Summen: Einnahmen, Ausgaben, Saldo. Sie beziehen sich auf
|
||||
**alle** Treffer der eingestellten Auswahl, nicht nur auf die angezeigten
|
||||
Zeilen — eine Summe unter einer abgeschnittenen Liste bedeutete nichts.
|
||||
|
||||
### Eine Buchung erfassen
|
||||
|
||||
Der Betrag ist immer positiv; ob es eine Einnahme oder eine Ausgabe ist, steht
|
||||
mit den beiden Knöpfen daneben. Eine „negative Ausgabe" gibt es nicht: sie
|
||||
wäre eine Einnahme in Verkleidung und in keiner Auswertung als solche zu
|
||||
erkennen.
|
||||
|
||||
Sobald ein Verwendungszweck dasteht, schlägt FLORIAN eine Kategorie vor.
|
||||
Zuerst aus den **eigenen früheren Buchungen** der Wehr — steht darunter „zuletzt
|
||||
so gebucht", ist dieselbe Sache schon einmal so einsortiert worden. Erst wenn
|
||||
es dazu nichts gibt, greift eine Stichwortliste.
|
||||
|
||||
Der Vorschlag ist ein Angebot, kein Automatismus: er wird erst übernommen,
|
||||
wenn er angeklickt wird. Und ist er unsicher, erscheint gar keiner. Ein
|
||||
falscher Vorschlag wäre schlechter als keiner — er wird übernommen, ohne
|
||||
gelesen zu werden.
|
||||
|
||||
### Entwurf und festgeschrieben
|
||||
|
||||
Eine neue Buchung ist zunächst ein **Entwurf**. Sie lässt sich ändern und
|
||||
verwerfen, solange der Kassier sie durchsieht.
|
||||
|
||||
Mit **Festschreiben** wird sie unveränderbar. Das ist der Zustand, den eine
|
||||
Kassenprüfung erwartet, und dahinter führt kein Weg zurück — auch nicht über
|
||||
einen Umweg, die Datenbank lehnt jede Änderung ab.
|
||||
|
||||
Ein Schloss in der Zeile zeigt, welche Buchungen festgeschrieben sind.
|
||||
|
||||
### Einen Beleg buchen
|
||||
|
||||
Wurde eine Quittung unter **Belege** fotografiert und gegengezeichnet, muss
|
||||
sie nicht noch einmal abgetippt werden. In der Liste der erledigten Belege
|
||||
steht dort ein Knopf **Buchen**.
|
||||
|
||||
Zu wählen sind nur Konto und Kategorie — alles andere steht schon fest.
|
||||
Betrag, Datum und Händler kommen aus dem gegengezeichneten Beleg, und das
|
||||
Foto hängt danach an der Buchung. Das ist der Zustand, den eine
|
||||
Kassenprüfung erwartet: zu jeder Ausgabe ein Beleg, und zwar an der Buchung
|
||||
und nicht in einem Ordner daneben.
|
||||
|
||||
Ein Beleg, der noch nicht gegengezeichnet ist, lässt sich nicht buchen. Er
|
||||
ist bis dahin ein von einer Maschine ausgefülltes Formular.
|
||||
|
||||
### Wenn etwas falsch war
|
||||
|
||||
Bei einem Entwurf: ändern oder verwerfen.
|
||||
|
||||
Bei einer festgeschriebenen Buchung: **Gegenbuchen**. Es entsteht eine zweite
|
||||
Buchung mit derselben Summe in die andere Richtung, mit Verweis auf die erste
|
||||
und mit dem eingegebenen Grund im Verwendungszweck. Beide bleiben stehen.
|
||||
|
||||
Das sieht nach mehr Aufwand aus, als eine Zahl zu korrigieren, und ist genau
|
||||
der Punkt: eine verschwundene Buchung kann ein Kassenprüfer nicht prüfen. Eine
|
||||
berichtigte kann er nachvollziehen.
|
||||
|
||||
Das Datum der Gegenbuchung ist immer **heute**. Läge es im geprüften Zeitraum,
|
||||
würde die Datenbank sie ablehnen — und das zu Recht.
|
||||
|
||||
---
|
||||
|
||||
## 49. Kontoauszug einlesen
|
||||
|
||||
Statt fünfzig Umsätze abzutippen, lässt sich der Auszug aus dem Online-Banking
|
||||
einlesen. Zwei Formate:
|
||||
|
||||
| Format | Datei |
|
||||
|---|---|
|
||||
| **CAMT.053** | `.xml` oder `.camt` |
|
||||
| **MT940** | `.txt` oder `.sta` |
|
||||
|
||||
Beides bietet fast jedes Online-Banking zum Herunterladen an, meist unter
|
||||
„Umsatzexport" oder „Umsätze exportieren". Welches von beiden, spielt keine
|
||||
Rolle — FLORIAN erkennt es am Inhalt.
|
||||
|
||||
### Was dabei passiert — und was nicht
|
||||
|
||||
Es entstehen **Entwürfe**, keine fertigen Buchungen. Jeder Posten muss danach
|
||||
noch durchgesehen und die Kategorie geprüft werden.
|
||||
|
||||
Umsätze, die schon in der Kasse stehen, sind grau hinterlegt und mit „steht
|
||||
schon in der Kasse" gekennzeichnet. Sie sind nicht vorausgewählt. Das ist der
|
||||
eigentliche Wert der Sache: ein zweimal eingelesener Auszug wäre sonst ein
|
||||
doppelt gebuchter Monat, und der fällt erst beim Kassenabschluss auf — also
|
||||
drei Monate später.
|
||||
|
||||
Umsätze, die nicht gelesen werden konnten, stehen mit Grund darüber. Sie
|
||||
verschwinden nicht stillschweigend.
|
||||
|
||||
---
|
||||
|
||||
## 50. Mitgliedsbeiträge
|
||||
|
||||

|
||||
|
||||
### Sätze
|
||||
|
||||
Ein Beitragssatz gilt **ab einem Jahr** und wahlweise für eine Abteilung. Ein
|
||||
Satz ohne Abteilung gilt für alle, die keinen eigenen haben.
|
||||
|
||||
Rückwirkend wird nichts geändert. Wer den Beitrag erhöht, legt einen neuen
|
||||
Satz ab dem kommenden Jahr an — sonst stimmten die Beiträge der vergangenen
|
||||
Jahre nicht mehr, und die sind bereits geprüft.
|
||||
|
||||
### Der Beitragslauf
|
||||
|
||||
Einmal im Jahr, meist im Januar. Vorher zeigt FLORIAN, was entstehen wird:
|
||||
|
||||
- wie viele Beiträge und über welche Summe
|
||||
- wie viele Mitglieder für dieses Jahr schon einen haben (die bleiben unberührt)
|
||||
- **wer keinen passenden Satz hat**
|
||||
|
||||
Der letzte Punkt ist der wichtige. Das sind die Mitglieder, für die niemand
|
||||
entschieden hat, was sie zahlen — entweder fehlt ein Satz für ihre Abteilung
|
||||
oder es gibt keinen allgemeinen.
|
||||
|
||||
Der Lauf lässt sich wiederholen. Wer im Februar drei Neue aufgenommen hat,
|
||||
startet ihn noch einmal; doppelt entsteht nichts.
|
||||
|
||||
### Zahlungen und Mahnungen
|
||||
|
||||
Ein Zahlungseingang erzeugt zugleich die **Buchung** im Kassenbuch. Ein
|
||||
Häkchen ohne Geld in der Kasse wäre eine Behauptung.
|
||||
|
||||
Gemahnt wird als Sammelaktion: die offenen Beiträge auswählen, mahnen. Die
|
||||
Mahnstufe zählt hoch. Ab der fünften Stufe hört FLORIAN auf — was danach
|
||||
kommt, entscheidet die Kommandantschaft und nicht die Software.
|
||||
|
||||
Ein **Erlass** braucht eine Begründung. Ohne sie wird er nicht gespeichert:
|
||||
ein erlassener Beitrag ohne Grund ist in der Kassenprüfung eine offene Frage,
|
||||
und in fünf Jahren weiß es niemand mehr.
|
||||
|
||||
### Rückstände
|
||||
|
||||
Ganz oben steht die Rückstandsliste über **alle Jahre**, nach Person
|
||||
gruppiert. Sie ist die einzige Ansicht, in der auffällt, wer seit drei Jahren
|
||||
offen steht — in der Jahresliste sieht das aus wie ein einzelner offener
|
||||
Beitrag.
|
||||
|
||||
---
|
||||
|
||||
## 51. Rechnungen an Dritte
|
||||
|
||||

|
||||
|
||||
Kostenersatz nach Art. 28 BayFwG (Fehlalarm einer Brandmeldeanlage, Öleinsatz
|
||||
mit Verursacher), Vermietung des Schulungsraums, Absicherung einer
|
||||
Veranstaltung.
|
||||
|
||||
### Nummer und Betrag
|
||||
|
||||
Die **Rechnungsnummer** vergibt FLORIAN fortlaufend je Jahr, lückenlos. Eine
|
||||
fehlende Nummer muss in der Kassenprüfung erklärt werden, deshalb wird auch
|
||||
eine stornierte Rechnung nicht gelöscht, sondern bleibt mit Grund stehen.
|
||||
|
||||
Der **Betrag** ist die Summe der Positionen und wird nicht eingegeben. Ein von
|
||||
Hand eingetippter Gesamtbetrag neben Positionen, die etwas anderes ergeben,
|
||||
ist der Fehler, den niemand bemerkt, bis der Empfänger anruft.
|
||||
|
||||
Die Menge hat drei Nachkommastellen: Einsatzstunden werden vielerorts
|
||||
viertelstündlich abgerechnet, und 0,25 × 47,50 € muss auf den Cent stimmen.
|
||||
|
||||
### Entwurf, versendet, bezahlt
|
||||
|
||||
Bis zum **Versenden** ist eine Rechnung ein Entwurf und lässt sich vollständig
|
||||
ändern. Danach nicht mehr: was beim Empfänger liegt, muss dem entsprechen, was
|
||||
hier steht.
|
||||
|
||||
Beim Versenden entsteht das PDF im Hintergrund. Es steht kurz darauf zum
|
||||
Herunterladen bereit.
|
||||
|
||||
Der Zahlungseingang erzeugt — wie beim Beitrag — zugleich die Buchung. Wurde
|
||||
weniger überwiesen als berechnet, lässt sich der abweichende Betrag eintragen.
|
||||
|
||||
Für eine Korrektur: stornieren und neu schreiben. Eine bezahlte Rechnung wird
|
||||
nicht storniert; dort ist zuerst die Buchung gegenzubuchen.
|
||||
|
||||
---
|
||||
|
||||
## 52. Kassenprüfung
|
||||
|
||||

|
||||
|
||||
Einmal im Jahr, meist vor der Jahreshauptversammlung. Zwei Kassenprüfer zählen
|
||||
die Kasse und vergleichen mit dem Buch.
|
||||
|
||||
### Der Ablauf
|
||||
|
||||
1. **Zeitraum wählen.** In der Regel das abgelaufene Kalenderjahr.
|
||||
2. **Zählen.** In der Maske stehen links die Bestände laut Buch und rechts
|
||||
leere Felder für das Gezählte. Die Reihenfolge ist Absicht: wer erst das
|
||||
Buch sieht und dann zählt, zählt, bis es passt.
|
||||
3. **Differenz erklären**, falls eine bleibt.
|
||||
4. **Prüfer und Prüfdatum eintragen.**
|
||||
5. **Festschreiben.**
|
||||
|
||||
Steht ein Hinweis auf noch nicht festgeschriebene Buchungen im Zeitraum: erst
|
||||
durchsehen. Das Festschreiben des Abschlusses nimmt sie mit.
|
||||
|
||||
### Was das Festschreiben bewirkt
|
||||
|
||||
Der Zeitraum ist danach **zu**. Es lässt sich keine Buchung mehr mit einem
|
||||
Datum darin anlegen, und keine vorhandene mehr dorthin verschieben. Wer nach
|
||||
der Prüfung noch eine Quittung findet, bucht sie mit dem heutigen Datum.
|
||||
|
||||
Der Abschluss selbst ist ebenfalls unveränderbar — Zahlen, Prüfer und
|
||||
Erläuterung stehen fest. Nachgetragen wird nur der Prüfbericht, weil das PDF
|
||||
im Hintergrund entsteht.
|
||||
|
||||
Ohne Prüfer und Prüfdatum lässt sich nichts festschreiben: ohne beides ist es
|
||||
kein Abschluss, sondern eine Notiz. Und eine Differenz ohne Erläuterung
|
||||
ebenfalls nicht — sonst steht in fünf Jahren „stimmt nicht" im Bericht und
|
||||
niemand weiß mehr, warum.
|
||||
|
||||
### Der Prüfbericht
|
||||
|
||||
Entsteht als PDF mit vier Unterschriftsfeldern: beide Prüfer, der Kassier und
|
||||
der Kommandant. Er lässt sich ausdrucken, unterschreiben und abheften — die
|
||||
Papierform bleibt für die Entlastung in der Versammlung die maßgebliche.
|
||||
|
||||
---
|
||||
|
||||
## 53. Auswertungen und Export
|
||||
|
||||
Unter **Kasse → Abschluss → Auswertung** stehen die Zahlen für die
|
||||
Jahreshauptversammlung:
|
||||
|
||||
- Einnahmen und Ausgaben je Kategorie, absteigend nach Betrag
|
||||
- der Verlauf über zwölf Monate
|
||||
- das Vorjahr daneben — eine Zahl ohne Vergleich sagt nichts
|
||||
|
||||
Das ist die Ansicht, in der auffällt, dass sich die Kraftstoffkosten verdoppelt
|
||||
haben.
|
||||
|
||||
### Export
|
||||
|
||||
**Kassenbuch als CSV** lädt alle Buchungen eines Jahres herunter: Datum,
|
||||
Konto, Richtung, Betrag, Ober- und Unterkategorie, Kontonummer,
|
||||
Verwendungszweck, Belegnummer, Gegenüber und ob festgeschrieben.
|
||||
|
||||
Die Datei ist für den Steuerberater oder die Kämmerei gedacht und öffnet in
|
||||
Excel ohne Rückfrage richtig: Semikolon als Trennzeichen, Komma als
|
||||
Dezimaltrennzeichen, UTF-8 mit Kennung.
|
||||
|
||||
---
|
||||
|
||||
## 54. Rollen und was sie dürfen
|
||||
|
||||
Neun Rollen, jede mit einem festen Satz Rechte. Die Zuordnung steht zentral in
|
||||
`src/lib/authz.ts` und wird beim Seed in die Datenbank gespiegelt — eine Wehr
|
||||
@@ -1716,7 +1998,7 @@ kann sie also nachträglich zuschneiden, ohne dass jemand Code ändert.
|
||||
| **Kommandant** | alles innerhalb der eigenen Wehr, einschließlich Rollen vergeben und Protokoll einsehen |
|
||||
| **Gerätewart** | Fahrzeuge und Geräte anlegen, ändern, aussondern; Prüfungen planen und durchführen |
|
||||
| **Atemschutzgerätewart** | zusätzlich Atemschutzgeräte, Träger und die Überwachung nach FwDV 7 |
|
||||
| **Kassier** | Buchungen und Kassenabschluss (Modul in Phase 9), Belege erfassen und gegenzeichnen |
|
||||
| **Kassier** | Kassenbuch, Beiträge, Rechnungen und Kassenabschluss; Belege erfassen und gegenzeichnen |
|
||||
| **Schriftführer** | Mitglieder pflegen, Einsätze und Berichte erfassen, den Terminkalender führen |
|
||||
| **Ausbilder** | Übungen und Übungstermine planen, Teilnahme, Anwesenheit und Qualifikationen erfassen |
|
||||
| **Mitglied** | eigene Daten, Listen lesen, Termine sehen und sich zurückmelden, eigene Benachrichtigungswege einrichten, im Wiki lesen |
|
||||
@@ -1738,7 +2020,7 @@ sind nur die Textvorlagen, und die hängen an einem eigenen Recht.
|
||||
|
||||
---
|
||||
|
||||
## 48. Anmeldung über authentik einrichten
|
||||
## 55. Anmeldung über authentik einrichten
|
||||
|
||||
authentik läuft **getrennt** von FLORIAN und ist nicht Teil dieses Stacks.
|
||||
|
||||
@@ -1791,7 +2073,7 @@ ganze Wehr aus.
|
||||
|
||||
---
|
||||
|
||||
## 49. PIN und Ausweiskarte verwalten
|
||||
## 56. PIN und Ausweiskarte verwalten
|
||||
|
||||
Wie sich am Terminal angemeldet wird, steht in Kapitel 24. Hier geht es darum,
|
||||
was dahinter liegt.
|
||||
@@ -1814,7 +2096,7 @@ Huber), PIN `471203`.
|
||||
|
||||
---
|
||||
|
||||
## 50. Was protokolliert wird
|
||||
## 57. Was protokolliert wird
|
||||
|
||||
Jede schreibende Änderung erzeugt einen Eintrag mit Zeitpunkt, Person,
|
||||
betroffenem Datensatz und den geänderten Feldern im Vorher-Nachher-Vergleich.
|
||||
@@ -1839,7 +2121,7 @@ jeweiligen Detailseite sichtbar.
|
||||
|
||||
---
|
||||
|
||||
## 51. Wenn etwas nicht geht
|
||||
## 58. Wenn etwas nicht geht
|
||||
|
||||
| Beobachtung | Wahrscheinliche Ursache | Was hilft |
|
||||
|---|---|---|
|
||||
|
||||
@@ -39,8 +39,8 @@ Faktoren, die in die Schätzung eingehen:
|
||||
| 6 | Kartenmodule | 14–18 PT | 3,5–4,5 h | **2:50 h** |
|
||||
| 7 | Benachrichtigungen und Termine | 10–13 PT | 2,5–3,5 h | **3:10 h** |
|
||||
| 8 | Lokale KI | 9–12 PT | 2,5–3 h | **3:40 h** |
|
||||
| 9 | Kassenverwaltung | 8–11 PT | 2–3 h | offen |
|
||||
| | **Summe** | **123–164 PT** | **28–37 h** | |
|
||||
| 9 | Kassenverwaltung | 8–11 PT | 2–3 h | **4:10 h** |
|
||||
| | **Summe** | **123–164 PT** | **28–37 h** | **28:30 h** |
|
||||
|
||||
123–164 Personentage entsprechen etwa **6 bis 8 Monaten Vollzeitarbeit** einer
|
||||
einzelnen Person. Zum Vergleich: eine kommerzielle Feuerwehrverwaltung kostet
|
||||
@@ -422,6 +422,16 @@ Die Unveränderlichkeit festgeschriebener Buchungen kostet hier fast nichts —
|
||||
das Muster steht seit Phase 0 mit dem Audit-Log-Trigger und lässt sich
|
||||
übernehmen.
|
||||
|
||||
**Tatsächlich: 4:10 h** — die längste Phase bisher. Beide Schätzungen oben
|
||||
haben sich bestätigt und zugleich verschoben: MT940 kostete weniger als
|
||||
gedacht, weil beide Formate ohne Fremdbibliothek auskommen. Dafür war die
|
||||
Unveränderlichkeit nicht das Übernehmen eines Musters, sondern drei
|
||||
Trigger mit drei genau begründeten Ausnahmen — und die Ausnahmen sind der
|
||||
Teil, der Zeit gekostet hat. Zwei davon sind erst im Test aufgefallen:
|
||||
der Prüfbericht, der nach dem Festschreiben entsteht, und die
|
||||
DSGVO-Löschung einer Wehr, die an ihren eigenen festgeschriebenen Buchungen
|
||||
scheiterte.
|
||||
|
||||
---
|
||||
|
||||
## Was hier nicht drin ist
|
||||
|
||||
@@ -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
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ Phase 5 ████████████████████ fertig
|
||||
Phase 6 ████████████████████ fertig 0.7.0
|
||||
Phase 7 ████████████████████ fertig 0.8.0
|
||||
Phase 8 ████████████████████ fertig 0.9.0
|
||||
Phase 9 ░░░░░░░░░░░░░░░░░░░░ als Nächstes 0.10.0
|
||||
Phase 9 ████████████████████ fertig 0.10.0
|
||||
Freigabe ░░░░░░░░░░░░░░░░░░░░ offen 1.0.0
|
||||
```
|
||||
|
||||
@@ -234,7 +234,7 @@ Dafür bleibt **alles andere ohne Modell vollständig benutzbar**: Wiki lesen un
|
||||
schreiben, Wortsuche, Belege ablegen, beide Protokolle. Wo etwas fehlt, steht
|
||||
ein Satz, der sagt was fehlt und wie es ohne geht.
|
||||
|
||||
## Phase 9 — Kassenverwaltung · als Nächstes
|
||||
## Phase 9 — Kassenverwaltung · fertig
|
||||
|
||||
Am 03.09.2026 ergänzt. Die Rolle `KASSIER` war seit Phase 0 angelegt, ein Modul
|
||||
dazu fehlte in der ursprünglichen Planung. Spezifikation: `Phase-9.md`.
|
||||
@@ -253,7 +253,11 @@ Kassenabschluss friert seinen Zeitraum ein, danach nimmt die Buchungsmaske
|
||||
kein Datum daraus mehr an. Beträge durchgängig als `DECIMAL(12,2)`, nie als
|
||||
Fließkommazahl.
|
||||
|
||||
**Danach kann die Wehr:** ihre Kasse führen und den Kassenprüfbericht auf
|
||||
Beides sitzt in Triggern und nicht im Router: ein vergessener Zustandscheck,
|
||||
ein Skript oder ein Import würden sonst reichen, um den geprüften Bestand zu
|
||||
ändern, ohne dass irgendwo etwas auffällt.
|
||||
|
||||
**Die Wehr kann jetzt:** ihre Kasse führen und den Kassenprüfbericht auf
|
||||
Knopfdruck erzeugen.
|
||||
|
||||
## 1.0.0 — Freigabe
|
||||
@@ -276,7 +280,7 @@ Am 03.09.2026 entschieden:
|
||||
|
||||
| Punkt | Entscheidung |
|
||||
|---|---|
|
||||
| Kassenmodul | Wird als **Phase 9** ergänzt. Spezifikation in `Phase-9.md`, Aufwand 8–11 PT. |
|
||||
| Kassenmodul | Als **Phase 9** ergänzt und mit 0.10.0 umgesetzt. Spezifikation in `Phase-9.md`. |
|
||||
| `Berechtigung` ohne `organisationId` | Bleibt so. Die Tabelle ist der systemweite Katalog dessen, was die Software kann, für alle Wehren identisch — mandantenbezogen wird es erst über `RolleBerechtigung`. Für die Anwendungsrolle nur lesbar. |
|
||||
| Löschausnahme im Audit-Log | Bleibt so. `UPDATE` ist dauerhaft gesperrt, `DELETE` nur mit `SET LOCAL app.audit_bereinigung = 'an'` im Aufbewahrungslauf des Workers. Ohne diese Ausnahme wäre das Löschkonzept nach DSGVO nicht umsetzbar. |
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 224 KiB |
|
Before Width: | Height: | Size: 226 KiB After Width: | Height: | Size: 226 KiB |
|
Before Width: | Height: | Size: 726 KiB After Width: | Height: | Size: 727 KiB |
|
Before Width: | Height: | Size: 742 KiB After Width: | Height: | Size: 222 KiB |
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 193 KiB |
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 183 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 213 KiB After Width: | Height: | Size: 297 KiB |
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 391 KiB After Width: | Height: | Size: 257 KiB |
|
Before Width: | Height: | Size: 317 KiB After Width: | Height: | Size: 263 KiB |
|
Before Width: | Height: | Size: 270 KiB After Width: | Height: | Size: 270 KiB |
|
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 262 KiB After Width: | Height: | Size: 237 KiB |
|
Before Width: | Height: | Size: 229 KiB After Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 199 KiB After Width: | Height: | Size: 199 KiB |
|
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 200 KiB |
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 310 KiB |
|
After Width: | Height: | Size: 317 KiB |
|
After Width: | Height: | Size: 937 KiB |
|
After Width: | Height: | Size: 149 KiB |
|
After Width: | Height: | Size: 408 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 189 KiB |
|
After Width: | Height: | Size: 129 KiB |