Alle Tests grün, trotzdem nicht zu gebrauchen
Inhaltsverzeichnis
Alle Tests grün, trotzdem nicht zu gebrauchen
Ein Tag, zwei Roadmap-Phasen, derselbe Lerneffekt zweimal hintereinander: Eine Testsuite mit 270 grünen Tests beweist, dass ein Feature funktioniert — nicht, dass es fertig ist. Und “fertig” heißt beim Nextcloud-ERP inzwischen: erst der echte Server, dann der echte erste Blick eines Menschen darauf.
Fünf Belegtypen, ein PDF-Weg — und zwei Bugs, die nur der echte Server zeigte
Ausgangsfrage: Kann das ERP-Add-on aus Angeboten, Aufträgen, Lieferscheinen, Rechnungen und Gutschriften automatisch PDFs erzeugen, ordentlich abgelegt und möglichst löschgeschützt? Bisher gab es nur für Rechnungen überhaupt eine automatische Dokumentablage — und die war bewusst nur eine druckfähige HTML-Datei, kein echtes PDF, um nicht “nebenbei” eine neue Composer-Abhängigkeit mitzuentscheiden. Jetzt gibt es einen gemeinsamen PDF-Dienst auf Basis von Dompdf, der beim jeweiligen Fixieren-Schritt jedes Belegtyps automatisch ein Dokument mit Zeitstempel im Dateinamen in den passenden Projektordner schreibt: Angebot beim Versenden, Auftrag beim Bestätigen, Lieferschein/Rechnung/Gutschrift beim Ausstellen. Zum Löschschutz gilt: Echte, rechtssichere Unveränderbarkeit kann eine selbst gehostete Lösung gegen einen Admin mit Serverzugriff ohnehin nicht garantieren — praktisch geht ein Nextcloud Group Folder mit verweigerter Löschberechtigung für normale Nutzer, dafür gibt es jetzt eine dokumentierte, bewusst manuelle Admin-Anleitung statt einer fragilen Automatisierung über die interne API einer Fremd-App.
Der eigentlich interessante Teil kam erst danach. Der erste curl-Test gegen die echte, laufende Instanz scheiterte mit “Class Dompdf\Options not found” — obwohl composer install sauber durchlief und alle 270 PHPUnit-Tests grün waren. Der Grund: Nextclouds App-Loader sucht ausschließlich nach <app>/composer/autoload.php, nicht dem normalen vendor/-Verzeichnis. PHPUnit dagegen lädt seinen eigenen vendor/autoload.php über einen eigenen Bootstrap-Mechanismus — die Testsuite hätte diesen Fehler also nie gemerkt, ganz egal wie viele Tests grün gewesen wären. Ein Einzeiler in der Application-Klasse hat es behoben, aber nur, weil überhaupt gegen die echte Instanz getestet wurde.
Direkt danach der nächste, ganz anders gelagerte Bug am selben Symptom: Ausgerechnet der Auftrags-PDF-Export blieb weiter stumm, ohne jede Fehlermeldung. Ursache: Der Auftrags-Controller erbt — anders als die vier anderen Belegtyp-Controller — nicht von der gemeinsamen Basisklasse, sondern hat seine eigene, lokale Berechtigungsprüfung, die bislang void statt den angemeldeten Nutzer zurückgab. Weil die PDF-Erzeugung bewusst “best effort” ist und niemals den eigentlichen Vorgang fehlschlagen lässt, war der Fehler komplett unsichtbar, bis jeder der fünf Belegtypen einzeln durchgeklickt wurde. Fünf strukturell fast identische Controller, aber genau der eine mit einem abweichenden Vererbungsmuster hatte den Bug — und war beim Testen zufällig zuletzt an der Reihe. Ein guter Grund, bei “gleichartigen” Komponenten trotzdem jede einzeln zu verifizieren, statt von einer stellvertretend auf alle zu schließen. Nebenbei kam noch ein alter, unabhängiger Tippfehler ans Licht: Die Gutschrift-Entity hatte ihr projectId-Feld nie als Ganzzahl-Typ registriert, wodurch es nach dem Laden aus der Datenbank als Text zurückkam — ein stiller Fehler, der schon lange bestand, aber erst jetzt auffiel, weil es der erste Codepfad war, der das Feld tatsächlich typgebunden weiterreichte.
Noch am selben Tag kamen zwei weitere Runden Nutzerfeedback obendrauf. Erstens: Für Angebot und Auftrag gab es gar keinen erkennbaren Auslöser für die PDF-Erzeugung — sie hing nur am generischen Status-Dropdown plus “Speichern”, während Rechnung, Lieferschein und Gutschrift von Anfang an einen eigenen “… ausstellen”-Button hatten. Der eigene Playwright-Klicktest hatte diesen Weg zuvor bereits bestätigt — funktional also “richtig”. Trotzdem war er für jemanden, der die Codebasis nicht kennt, schlicht nicht auffindbar. Nachgezogen wurde ein gleichwertiger Button, bewusst “PDF erstellen” benannt statt zum Beispiel “Angebot versenden” — ein “versenden”-Button hätte fälschlich suggeriert, dass die App die Zustellung an den Kunden selbst übernimmt, obwohl sie nur das Dokument erzeugt. Zweitens die Nachfrage, ob sich das PDF nicht auch direkt im Angebot-Screen anzeigen lässt statt nur zu verlinken. Der bisherige Link riss in die Files-App; ein neuer schlanker Endpunkt liefert das PDF jetzt mit “inline” statt “attachment” aus, wodurch es sich per eingebettetem Frame direkt in allen vier Belegansichten mit eigenem Dokument zeigen lässt — der externe Link bleibt zusätzlich für Vollbild und Druck bestehen. Kurz hakte dabei noch ein scheinbar kaputter Endpunkt, der hartnäckig die App-Startseite statt des Dokuments auslieferte, obwohl Routing und Controller korrekt waren: Der Server cacht seine kompilierte Routentabelle im Arbeitsspeicher, eine neue Route wird erst nach einem Prozess-Neustart sichtbar.
“So ist das Angebot nicht zu gebrauchen” — vom Praxistest zur neuen Roadmap-Phase
Direkt nachdem der PDF-Export stand, kam der erste echte Praxistest — und ein klares Urteil: “Das Angebot ist so nicht zu gebrauchen.” Es fehlten die Basics: Firmenname, Kundenanschrift, Datum, Bindefrist. Dazu zwei weitere Wünsche: Positionsgruppen sollen auch im PDF auftauchen, nicht nur in der Web-Ansicht, und einmal angelegte Positionen sollen sich korrigieren statt nur löschen lassen — dazu ein Rabattkonzept, pro Position und auf den gesamten Beleg.
Aus dem Feedback wurde eine komplette neue Roadmap-Phase. Ein neues Firmenprofil (Name, Anschrift, USt-IdNr., Kontakt, eine freie Fußzeile für alles andere wie die Bankverbindung) lässt sich unter Einstellungen pflegen und erscheint jetzt als Absenderkopf in jedem erzeugten Beleg-PDF. Die Kundenanschrift wird direkt aus dem verknüpften Nextcloud-Kontakt gezogen, keine doppelte Datenhaltung. Positionsgruppen erscheinen jetzt als Zwischenüberschriften im PDF, genau wie in der Web-Ansicht, und einmal angelegte Positionen (Menge, Preis, MwSt., Rabatt, Beschreibung) lassen sich direkt bearbeiten statt löschen und neu anlegen zu müssen. Statt die neue Kopf-, Kunden- und Gruppen-Logik in jedem der fünf Belegtypen einzeln nachzubauen, gibt es jetzt einen einzigen gemeinsamen HTML-Baustein, den alle fünf nutzen — eine Änderung an einer Stelle wirkt sich sofort konsistent auf alle Belegtypen aus.
Beim Rabatt fiel die Entscheidung bewusst gegen die naheliegende Abkürzung: Ein Rabatt als negative Freitext-Position einzutragen hätte die MwSt.-Berechnung verwässert und wäre im PDF nicht als das erkennbar gewesen, was er ist. Also gibt es jetzt ein eigenes Feld, das sich sowohl auf eine einzelne Position als auch auf den gesamten Beleg setzen lässt — mehr Aufwand, aber sauberer. Interessant wurde es bei der Berechnung: Ein Belegrabatt bei gemischten MwSt.-Sätzen ist keine einfache Bruttosumme-Rechnung. Mischt ein Beleg Positionen mit 19 % und 7 % MwSt., muss ein Gesamtrabatt anteilig auf beide Steuersatz-Blöcke wirken, bevor die MwSt. berechnet wird — sonst stimmt die MwSt.-Aufteilung am Ende nicht mehr mit der tatsächlichen Bemessungsgrundlage überein. Die Verifikation ging diesmal einen Schritt weiter als sonst: Statt nur zu prüfen, ob überhaupt ein PDF entsteht, wurden die erzeugten PDF-Bytes tatsächlich dekomprimiert und der enthaltene Text ausgelesen — so ließ sich schwarz auf weiß bestätigen, dass Firmenname, Kundenanschrift, Datum, Bindefrist, Gruppentitel und Rabattzeilen wirklich im fertigen Dokument stehen, nicht nur in der Datenbank.
Fazit
Beide Geschichten laufen auf denselben blinden Fleck hinaus: Der auffällige, laute Fehler ist selten der interessante. Eine Testsuite mit eigenem Bootstrap-Mechanismus kann einen Produktionsfehler komplett verstecken, egal wie grün sie sonst ist. Und “es funktioniert technisch” ist nicht dasselbe wie “es ist tatsächlich benutzbar” — der erste echte Blick eines Menschen auf das Ergebnis fand sofort, was jede automatisierte Prüfung vorher übersehen hatte: fehlender Inhalt, nicht fehlende Funktion. Zuverlässigkeit zeigt sich selten im ersten Testlauf, sondern darin, ob man nach dem ersten Fix noch einen zweiten, ganz anderen Blick riskiert — gegen den echten Server, und dann gegen einen echten, ahnungslosen Nutzer.
Repo: github.com/KayBeckmann/Nextcloud-ERP (MIT-Lizenz).