Phase quick-261002-fm5 Plan 01: Finanzbuchhaltung, Kantinenabrechnung und Handelsware
Zwei neue Module in der neuen Seitenleisten-Gruppe „Finanzbuchhaltung“: Kantinenabrechnung (Kantinen-CSV prüfen, DATEV-Lohndatei im ASCII-Format erzeugen) und Handelsware (Excel-Umsätze Erlöskonten zuordnen, Kontenliste pflegen, DATEV-Buchungsdatei als TXT erzeugen). Beide laufen über Aktivierung im Marktplatz plus Freigabe. Beraternummer, Mandantennummer, Lohnart, Standard-Erlöskonto und Startwert Gegenkonto sind je Mandant leer, bis ein Administrator sie einträgt. Nichts wurde gepusht.
Commits
Aufgabe
Commit
Inhalt
1 (Tracer)
1f85277
Kategorie accounting, Kantinenabrechnung durchgängig (API, Migration, Web, Zugriffsinventar)
2
44c1d43
Handelsware API: Prisma-Modelle mit Zeilenschutz, XLSX/TXT/CSV-Funktionen, Dienst, Controller
Biome:biome lint auf allen neuen/geänderten Dateien ohne Befund. biome check --write (Format + Importsortierung) wurde auf die neuen Dateien angewandt. Bestehende, nicht von mir angefasste Dateien (z. B. apps/api/src/proxmox) sind schon vorher nicht formatrein; das habe ich nicht angefasst.
RLS-Gates:rls-coverage und rls-access-inventory grün, Fundstellentabelle, Bereichszeilen, Summenzeile und Paarzählung (92 Paare) im Dokument nachgeführt.
Migrationen: beide lokal über die Container-IP angewandt, prisma migrate status „Database schema is up to date“ (54 Migrationen), prisma migrate diff Schema gegen DB: „No difference detected“.
Grep-Gates: keine Zahlen 1387819 / 10001 / 9005 in Kantine-Code, -Tests, -Web oder -Migration; keine 8000 in Handelsware-Code, -Tests, -Web, -Migration, auch nicht in den Testdateien.
Lokaler Stack:docker compose up -d --build api web gebaut, API healthy. Logs: Kantine-DATEV module seeded in registry, Handelsware-DATEV module seeded in registry, alle Routen gemappt, kein Migrationsfehler. Zeilen in Module: kantine-datev und handelsware-datev, beide Kategorie accounting.
Browser-Prüfung: nicht gemacht, liegt beim Orchestrator. Ich habe mich nicht angemeldet und keine Zugangsdaten gelesen. Das heißt: die Seiten laufen bisher nur gegen die Komponententests und gegen den gebauten Stack (Start, Routen, Seed), nicht gegen echte Klicks.
Testdateien für die Browser-Prüfung
Alle in /home/vicolab/projects/tessera-ctl/.planning/quick/261002-fm5-finanzbuchhaltung-module-kantinenabrechn/testdata/, alles erfundene Daten, mit den Pipeline-Funktionen gegengeprüft:
Datei
Zweck
Erwartung in der Vorschau
kantine-cp1252-fehler.csv
Windows-1252, CRLF, Umlaute, ein ungültiger Betrag, zwei Abrechnungsmonate
4 Zeilen, 03/2026, 82,54 EUR, kein Fehler. Datei bei Einstellungen z. B. 1234567 / 12345 / 1111: LuG_1234567_12345_03_2026.sic
HWA 0326 Test.xlsx
B1 = 2026, gemischt Zahl und deutscher Text, ein negativer Wert, ein unbekanntes Produkt
Datumsvorschlag 3103, Dateiname HWA_0326.txt
Konten.csv
Windows-1252, Name;Gegenkonto;Konto
enthält 4 Konten, „Neues Produkt Saft“ fehlt absichtlich. Nach dem Import dieser Liste (Standard-Erlöskonto z. B. 4711, Startwert 2000) bekommt das Produkt Gegenkonto 2014 und die Markierung „neu“
Ablauf-Vorschlag: Kantine-Einstellungen leer lassen und Hinweis prüfen, dann Werte eintragen. Handelsware: erst Einstellungen setzen, dann Konten.csv im Reiter Konten importieren, danach die XLSX hochladen, „neu“ prüfen, Konten-Reiter vor und nach dem Download vergleichen.
Abweichungen vom Plan
[Regel 1 – Fehler] Zeilennummern aus der echten Datei. Die Vorlage nutzt Index + 2; bei Leerzeilen oder übersprungenen Zeilen zeigt das eine falsche Zeile. Der Parser gibt jetzt die echte 1-basierte Dateizeile mit (line), der Validator nutzt sie und fällt ohne sie auf Index + 2 zurück (Test deckt beides ab). Commit 1f85277.
[Regel 3 – blockierend] sheetRows = 10 002 statt MAX_ROWS + 1. Mit MAX_ROWS + 1 wäre die 10 001. Datenzeile nie sichtbar, „zu viele Zeilen“ also nicht erkennbar. Test prüft genau 10 000 (ok) und 10 001 (Fehler). Commit 44c1d43.
[Regel 2 – fehlende Absicherung] XLSX-Signaturprüfung. SheetJS wirft bei Müll-Bytes nicht, es liest sie als Text. Ohne Prüfung des ZIP-/OLE-Kopfes wäre „garbage bytes → invalidFile“ nicht erfüllbar. Commit 44c1d43.
[Regel 2] Tabulator und Zeilenumbruch in Buchungstext und Kopftext werden durch ein Leerzeichen ersetzt, sonst würden sie die Spalten der TXT-Datei zerreißen. Gleiches gilt für Kontennamen über das DTO (Tabulator abgelehnt). Commit 44c1d43.
[Regel 2] Dateinamen-Dekodierung (apps/api/src/accounting/decode-upload-filename.ts): multer liefert UTF-8-Namen als latin1-gelesen; der Helfer kehrt das um, ohne einen schon richtigen Namen zu beschädigen. Der Plan nannte nur die Umwandlung; ein blindes Buffer.from(name, 'latin1') hätte Namen mit Zeichen über 255 zerstört. Commit 44c1d43.
Zusätzliche gemeinsame Web-Bausteine (nicht in files_modified): accounting-request.ts (Anfrage, Fehlerklasse), file-drop-area.tsx, tab-bar.tsx. Sie ersetzen eine zweifach kopierte Ablagefläche, Reiterleiste und Fehlerbehandlung. Die Ablage importiert keine Texte aus dem Zertifikat-Manager, wie verlangt.
Umlaut-Wächter: das Wort „neues“ (korrektes Deutsch) stand nicht auf der Allowlist und ließ die volle Web-Suite rot werden; ergänzt in umlaut-dictionary.ts (nur die Teilmenge der Aufgabe 1 hatte das nicht gezeigt, die volle Suite schon).
Doku: Im Inhaltsverzeichnis der Anwenderanleitung fehlte Proxmox, ich habe es mit ergänzt; „fünf Module“ wurde zu „sieben“. Die alte Klassen-Tabelle im Zugriffsdokument (Zahl 83) war schon vorher veraltet (gezählt waren 89); ich habe sie nicht umgeschrieben, sondern wie bisher einen Nachtragsabsatz mit nachgezählten Werten (90, dann 92 Paare) ergänzt.
TDD: Aufgabe 2 und die Kantine-Funktionen sind mit den Tests zusammen entstanden und in je einem atomaren Commit gelandet; es gibt keine getrennten RED-Commits. Die Tests laufen gegen die finale Implementierung.
Auth-Gates
Keine. Es wurde kein Login benötigt; ich habe keine Zugangsdaten gelesen oder verwendet (die Lesesperre für .env hat zudem gegriffen, als ich die Admin-Angaben nachsehen wollte, und ich habe es dabei belassen).
Offene Fragen und Hinweise für Sie
Kodierung der Handelsware-TXT: Ich habe UTF-8 beibehalten, wie in der Vorlage. DATEV-Importe erwarten oft Windows-1252 (ANSI). Enthalten Produktnamen Umlaute, kann DATEV sie falsch anzeigen. Das sollte die Kollegin am echten Import prüfen; die Umstellung wäre eine Zeile in handelsware-datev.service.ts (Ausgabe) plus mimeType.
Export schickt die Datei erneut: Statt einer JSON-Liste aller Zeilen sendet der Export die Original-XLSX als Multipart plus buchungsdatum und newAccounts (JSON-Text). Der Server liest alle Zeilen selbst neu und berechnet die Zuordnung in derselben Transaktion; die TXT kann so nicht von der Arbeitsmappe abweichen, und die JSON-Grenze von Express (100 kB) kappt große Listen nicht.
„Startwert“-Bedeutung: Er ist die erste Nummer, die vergeben wird, wenn die Kontenliste leer ist. Ist die Liste nicht leer, gilt höchstes vorhandenes Gegenkonto + 1 (wie die Vorlage mit festem 8000). Das ist eine Entscheidung von mir, bitte bestätigen.
Wer was ändert: Nur Administratoren ändern die Einstellungen (beide Module). Die Kontenliste der Handelsware dürfen alle Benutzer mit Modulzugriff pflegen.
xlsx 0.18.5: Das Lesen der hochgeladenen Excel-Dateien nutzt die bereits vorhandene xlsx-Abhängigkeit der API (0.18.5). Bekannte Hinweise (Prototype Pollution, ReDoS), die nur in Versionen behoben sind, die nicht in der npm-Registry stehen, sind laut Bedrohungsmodell T-FM5-05 bewusst akzeptiert: nur angemeldete, freigegebene interne Benutzer, Formeln/HTML/Formate abgeschaltet, nur erstes Blatt, Spalten A/B, 5 MB und 10 000 Zeilen Grenze. Ein Austausch der Bibliothek ist eine eigene Entscheidung.
Kantine: Betragsformat wie in der Vorlage: „1.234,56“ und „-5,00“ werden als Fehler gemeldet (die Vorlage akzeptiert nur Ziffern mit optionalem Komma). Das ist so gewollt, könnte aber bei Kantinenexporten mit Tausenderpunkt hinderlich sein.
Desktop-Download: Der Download läuft über denselben Blob-Mechanismus wie der Zertifikat-Manager (vom Desktop-Client seit 1.9.2 gespeichert). Im Desktop-Client selbst nicht getestet.
Icons:utensils und shopping-bag habe ich den Lucide-Pfaden nach gezeichnet; optisch noch nicht gesehen.
Known Stubs
Keine. Es gibt keine Platzhalter oder fest eingebauten leeren Werte, die in die Oberfläche fließen. Die Einstellungsfelder sind absichtlich leer (kein Standardwert), das ist Teil der Anforderung und kein Stub.
Threat Flags
Keine neue Angriffsfläche außerhalb des Bedrohungsmodells. Umgesetzt: T-FM5-01 (keine Speicherung/Protokollierung der Kantinenzeilen; Test, dass die Vorschau keine Namen enthält), T-FM5-02 (Rollen-Metadaten per Test), T-FM5-03 (RLS plus Inventar), T-FM5-04 (5 MB, 1 MB für Konten-CSV, 10 000 Zeilen, newAccounts begrenzt), T-FM5-05 (akzeptiert, siehe oben), T-FM5-06 (Neuberechnung in der Transaktion, 409, P2002 → 409), T-FM5-07 (Apostroph vor =+-@, Rundlauf-Test).
Self-Check: PASSED
Dateien vorhanden: Migrationen, Dienste, Controller, Seiten, Testdaten (4 Dateien) — geprüft per ls und Lauf der Funktionen gegen die Testdaten.
Marktplatz: beide Module unter „Finanzbuchhaltung“, aktiviert; Seitenleiste zeigt neue Kategorie.
Kantine: Hinweis bei leeren Einstellungen; kantine-cp1252-fehler.csv → 4 Zeilen, 03/2026, 71,74 €, Warnung zwei Monate, Fehler Zeile 4, Download gesperrt. Einstellungen 1234567/12345/1111 gespeichert; kantine-gueltig-utf8.csv → LuG_1234567_12345_03_2026.sic, 11 Spalten, CRLF, Inhalt identisch zur Vorlage (Betrag 0 → -0.00 wie in der Vorlage).
Handelsware: Einstellungen 4711/2000; Konten.csv (cp1252) importiert, Umlaute korrekt; HWA 0326 Test.xlsx → Datum 3103, Kopftext 2026, „Neues Produkt Saft“ neu mit 2014/4711; Download HWA_0326.txt (UTF-8, CRLF); neues Konto erst danach in der Kontenliste.