Ein Sportfachgeschäft arbeitet seit rund zwei Jahrzehnten mit einer selbst gewachsenen Warenwirtschaft: Artikel, Kunden, Belege, Kasse, Skiverleih, Werkstatt. Kontor ersetzt sie bei gleichem Funktionsumfang und gleicher Arbeitsweise, erfüllt dabei aber die heutigen Anforderungen an Kassenführung, Nachvollziehbarkeit und Aufbewahrung.
Das Altsystem stammt aus den frühen 2000er-Jahren und ist seither um Kasse, Skiverleih, Werkstatt, Tennisservice, Reklamation und Onlinehandel erweitert worden. Vier Punkte gaben den Ausschlag für einen Neubau statt einer weiteren Erweiterung.
| Punkt | Sachlage |
|---|---|
| Rechtliche Anforderungen | Kassensicherungsverordnung, GoBD, DSFinV-K, DSGVO, Pflichtangaben auf Belegen, Aufbewahrungsfristen. Unveränderbarkeit und Nachvollziehbarkeit setzen einen entsprechenden Datenbankaufbau voraus und lassen sich nicht nachrüsten. |
| Gewachsene Struktur | Jeder Geschäftsbereich hat über die Jahre eigene Masken und ein eigenes Belegverständnis bekommen. |
| Datenbestand | Rund zwanzig Jahre Artikel-, Kunden- und Belegdaten. Der Neubau muss sie vollständig und nachweisbar übernehmen. |
| Bedienung | Belegarten, Kürzel und Reihenfolgen bleiben unverändert, damit eingearbeitete Mitarbeiter ohne Schulung weiterarbeiten können. |
Zu den Angaben auf dieser Seite. Kontor arbeitet mit den Geschäftsdaten eines realen Betriebs. Diese Seite enthält deshalb keine Umsätze, keine Kunden-, Artikel-, Beleg- oder Bonzahlen, keine Lieferanten, keine Preise und keine Bildschirmfotos aus dem Bestand. Sämtliche Zahlen beziehen sich auf den Programmcode.
Alle Bereiche laufen über einen gemeinsamen Einstieg, mit gemeinsamer Rechteprüfung und gemeinsamen Bausteinen für Listen, Formulare und Drucke. Die Übersicht ordnet sie nach ihrer Rolle: Stammdaten, laufende Vorgänge, Kasse und Nachweisführung.
Ein Verkauf durchläuft sieben Stationen. Zwei davon – die Prüfung der Buchungsberechtigung und die Signatur – sind mit den Anforderungen aus § 146a AO hinzugekommen.
Beide Fragen werden getrennt beantwortet und können unterschiedlich ausfallen. Jede Antwort stammt aus genau einer Klasse; zuvor beantworteten elf Stellen im Code dieselbe Frage mit teils abweichenden Bedingungen.
| Frage | Grundlage | Wirkung |
|---|---|---|
| Erfüllt die Kasse § 146a Abs. 1 AO? | Seriennummer eingetragen, Zugangsdaten zur Gegenstelle vorhanden, Gegenstelle antwortet, Kasse angemeldet. | Bestimmt den Konformitätsstatus der Kasse. Eine eingetragene Seriennummer ohne Zugangsdaten gilt als nicht konform. |
| Darf dieser Vorgang gebucht werden? | Trainingsbuchung, freigegebener Notverkauf oder konforme Kasse. | Entscheidet über den einzelnen Vorgang. Eine Freigabe erlaubt den Verkauf und ändert den Konformitätsstatus nicht. |
Der übrige Teil der Anwendung ist für den Schreibtisch gebaut. An der Kasse trägt derselbe Maßstab nicht: Gelesen wird im Stehen, schräg von der Seite, mit einem wartenden Kunden gegenüber, und getroffen wird mit dem Finger. Das Kassenfenster hat deshalb einen eigenen Satz an Maßen, und er steht an genau einer Stelle im Stylesheet.
Beispieldaten. Artikelnamen, Preise und Beträge sind für diese Darstellung erfunden. Es sind keine Daten aus dem Betrieb abgebildet.
| Entscheidung | Begründung |
|---|---|
| Eine Eingabefläche | Die große Zifferntastatur ist die einzige Stelle, an der Zahlen entstehen. Sie bearbeitet je nach Auswahl den Preis einer Position oder den Bar- beziehungsweise Kartenbetrag, und eine Zeile darüber sagt jederzeit, was sie gerade bearbeitet. Wer nebenbei kassiert, soll nicht suchen müssen, welches Feld gerade den Fokus hat. |
| 44 Pixel als Untergrenze | Jede Trefferfläche ist mindestens so groß. Größer wird sie nur mit Begründung: Das Betragsfeld wird blind getroffen, das Scanfeld muss den Barcode sichtbar aufnehmen, und die Taste Kassieren darf nicht danebengehen. |
| Farbe als Aussage | Violett steht für einen Nachlass, Petrol für eine Dienstleistung, Blau für den Gutschein, Grün für den Abschluss des Vorgangs. Ein von Hand gesetzter Preis trägt denselben violetten Rahmen wie der Nachlass, weil beides dasselbe bedeutet: Hier hat jemand eingegriffen. |
| Drei Themen je Konto | Hell, dunkel und eine gedämpfte Fassung dazwischen. Die Wahl hängt am Benutzerkonto, nicht am Gerät: An derselben Kasse arbeiten morgens und abends verschiedene Leute, und die Beleuchtung im Laden ist nicht dieselbe. |
| Kein Farbwert im Kassen-Stylesheet | Alle Farben kommen aus benannten Merkmalen einer zentralen Datei. Sonst gäbe es eine Maske, die im dunklen Thema falsch aussieht, und die Kontrastprüfung fände sie nie. |
Geprüft wird das nicht nach Gefühl: Ein Werkzeug liest die Farbmerkmale beider Themen aus dem Stylesheet und rechnet die Vordergrund- und Hintergrundpaare nach WCAG 2.1 durch. Fließtext muss 4,5:1 erreichen, große Schrift und Bedienelemente 3:1. Der Rückgabewert entscheidet, nicht der Blick auf den Bildschirm.
Der überwiegende Teil des Aufwands entfällt auf die Nachweisführung, nicht auf den Kassiervorgang selbst.
| Anforderung | Umsetzung |
|---|---|
| Einzelaufzeichnung und Sicherungseinrichtung § 146a AO | Jeder Vorgang wird signiert. Die Anbindung ist als austauschbare Schnittstelle mit vier Ausprägungen umgesetzt: örtlich, über Netz, als Dienst, sowie eine Attrappe für Tests. Jeder Aufruf wird protokolliert. |
| Ausfalldokumentation | Bei Störung der Signatureinheit wird weiter kassiert. Der Ausfall wird erfasst und auf dem Beleg vermerkt. |
| Unveränderbarkeit GoBD | Gebuchte Belege sind gesperrt. Korrekturen erfolgen ausschließlich über Storno mit Gegenbuchung. Durchgesetzt wird das nicht mehr nur im Programm, sondern in der Datenbank: Bons, Bonpositionen, Abschlüsse, Signaturvermerke und das Protokoll selbst tragen Auslöser, die ein UPDATE oder DELETE zurückweisen. Wer eine Zeile ändern will, bekommt keinen Hinweis, sondern einen Fehler. |
| Nachweis, dass nichts geändert wurde GoBD Rz. 107 ff. | Über dem Protokoll liegt eine Hashkette: Jede Zeile trägt den Hash ihrer Vorgängerin. Zwei Kassen, die gleichzeitig buchen, ließen die Kette früher gabeln und erzeugten einen Fehlalarm; das war die eigentliche Gefahr, denn eine Prüfung, die regelmäßig grundlos anschlägt, wird nicht mehr gelesen. Heute vergibt die Datenbank die Kettenposition, und ein Endsiegel schließt die Kette ab, damit auch das Abschneiden der letzten Zeilen auffällt. |
| Kassenbuch § 146 Abs. 4 AO | Entnahmen, Einlagen und Bankgänge tragen eine eigene Nummer, den Vermerk Eigen- oder Fremdbeleg, die Herkunft der Zeile und den Namen des Erfassers. Ein Vertipper wird nicht überschrieben, sondern durch eine Gegenbewegung berichtigt. Dazu der Kassensturz: eine Zwischenzählung, die protokolliert wird, aber keinen Tag abschließt und keinem Bon eine Abschlussnummer gibt. |
| Digitale Schnittstelle DSFinV-K | Vollständige Lieferung aus 20 CSV-Dateien mit fester Benennung, dazu index.xml als Beschreibung und die zugehörige DTD. Die Klammer bildet der Kassenabschluss, nicht ein Zeitraum. Vor der Abgabe prüft das Werkzeug Satzarten, Spalten, Summen und Verweise der eigenen Ausgabe. |
| Verfahrensdokumentation | Wird zusammen mit dem Quelltext geführt und beschreibt Abläufe, Zuständigkeiten und Kontrollen. |
| Aufbewahrung | Jahresarchiv als CSV je Tabelle mit Satzbeschreibung und Prüfsummen. Die Löschung im Bestand erfolgt erst nach Unversehrtheitsprüfung, zeilengenauem Abgleich, vorliegender jüngerer Sicherung und geprüftem Rückweg. |
| Pflichtangaben auf Belegen § 14 UStG | Gemeinsames Layout für Werkstattauftrag, Rechnung, Leihvertrag und Lieferschein: Anschriftfeld nach DIN 5008, Umsatzsteuer nach Sätzen aufgeschlüsselt, Pflichtangaben in der Fußzeile. Fehlende Angaben werden auf dem Beleg ausgewiesen. |
| Datenschutz | Rechtestufen je Modul, zentral im Router geprüft. Kundensätze ohne Inhalt werden stillgelegt statt gelöscht, solange aufbewahrungspflichtige Belege daran hängen. |
Eine Selbstprüfung hält den Datenbestand gegen diese Anforderungen. Jeder Befund führt die Vorschrift, eine Frist und die Herkunft dieser Frist auf. Nicht eindeutig einzuordnende Befunde werden als offen gekennzeichnet. Ein eigener Abschnitt benennt, was die Selbstprüfung nicht feststellen kann.
Der übernommene Bestand stammt aus einem System ohne Eingabeprüfung. Die Prüfungen arbeiten deshalb zweistufig: Befund erheben, Beleglage gegenprüfen, und erst bei eindeutigem Ergebnis berichtigen.
Verkaufspreise werden gegen die tatsächliche Beleglage gehalten – wie oft und zu welchem Preis ein Artikel berechnet und eingekauft wurde. Berichtigt wird nur, wo Belege und Artikelstamm übereinstimmen. Abweichungen mit kaufmännischem Ermessen werden gemeldet.
Einzelne Kassenpositionen enthalten im Preisfeld Werte aus einem Barcode. Erkannt werden sie über den Abstand zum Artikelstamm und zu anderen Verkäufen desselben Artikels.
Namen, Namensteilung, Anreden, Anschriften und Rufnummern werden vereinheitlicht, Dubletten zusammengeführt. Sätze ohne Inhalt werden stillgelegt, nicht gelöscht.
Alle Warnungen des Systems sind an einer Stelle erfasst, mit Herkunft, Schwere und Bedingung. Zu jeder gehört ein Nachweis, dass sie unter der einen Bedingung anschlägt und unter der anderen nicht. Das Register weist aus, dass es eine Momentaufnahme ist.
Eigene Betriebsart für die Einrichtung einer Kasse. Übungsbuchungen sind gekennzeichnet, erreichen weder Bestand noch Auswertungen und lassen sich am Ende in einem Zug entfernen.
Bestandsmeldungen an das Onlinesystem laufen über eine Warteschlange: Meldungen zum selben Artikel werden verdichtet, gesendet wird gedrosselt und mit Wiederholung. Widersprüche werden erfasst statt überschrieben; je Feld ist festgelegt, welches System es führt.
Alle Angaben beziehen sich auf den Programmcode.
Diese Angaben werden von einem Werkzeug am laufenden System gemessen und als Dokument geschrieben. Ein zweiter Aufruf prüft per Rückgabewert, ob das Dokument noch zum System passt.
Serverseitig gerendertes HTML auf PHP 8.1 und MySQL beziehungsweise MariaDB. Kein Framework, kein Frontend-Build, keine Fremdpakete.
| Schicht | Inhalt |
|---|---|
| Einstieg | Front-Controller mit Routentabelle. Jede Adresse trägt Modul und erforderliche Rechtestufe; die Prüfung erfolgt zentral im Router. |
| Kern | Router, Datenbankhülle, Anmeldung, Sitzung, CSRF-Schutz, Ansichten, Blätterung, Protokoll, eigener PSR-4-Autoloader. |
| Fachlogik | 172 Dienstklassen nach Sachgebiet: Kasse, Kassensicherung, digitale Schnittstelle, Kundenkartei, Datenqualität, Anbindung nach außen, Altdatenübernahme, Auslieferung. |
| Ansichten | 161 Vorlagen, jede Ausgabe maskiert. Druckansichten statt PDF-Erzeugung. |
| Datenbank | 92 Tabellen, durchgängig vorbereitete Anweisungen, 96 versionierte Schemaschritte mit eigenem Einspielwerkzeug. 24 Auslöser in der Datenbank weisen Änderungen an Aufzeichnungen zurück. |
| Werkzeuge | 93 Kommandozeilenprogramme für Übernahme, Abgleiche, Diagnose, Sicherung, Archiv, Startfreigabe und Auslieferung. |
| Entscheidung | Begründung |
|---|---|
| Kein Framework, kein Build | Zielumgebung ist gewöhnliches Webhosting mit PHP und MySQL. Ohne Fremdpakete entfällt eine alternde Abhängigkeitskette, und der Quelltext bleibt ohne Wiederherstellung eines Buildvorgangs lauffähig. |
| Serverseitige Blätterung | Jede Liste hat Blätterung, eigene Zählabfrage und passende Indizes; ausgeliefert werden 50 Zeilen je Seite. Der Aufwand bleibt damit unabhängig vom Datenwachstum. |
| Sperre nach dem Buchen | Belege werden in einer Transaktion gebucht und anschließend gesperrt. Wiederholtes Absenden bleibt wirkungslos. |
| Nummernvergabe mit Sperre | Belegnummern über einen Nummernkreis mit SELECT … FOR UPDATE, damit gleichzeitige Vorgänge keine doppelten Nummern erhalten. |
| Ein Schreibweg | Auch die Kassiermaske schreibt Bons über denselben geprüften Dienst wie die Kassenanbindung. Transaktion, Idempotenz, Bestandsbuchung, Steueraufteilung und Protokoll liegen damit an einer Stelle. |
| Rechte als Stufen | Je Modul eine Stufe statt einer Freitextmatrix. Menüpunkte ohne Berechtigung werden ausgeblendet. |
| Lange Läufe als eigener Prozess | Sicherung und Datenprüfung melden ihren Fortschritt an die Maske; die Oberfläche wartet nicht auf das Ergebnis. |
| Geprüfte Auslieferung | Vor dem Versand eines Releasepakets werden Prüfsumme, Archivinhalt, Pfade, ein Probeentpacken und die Dateiänderungen ausgewertet. |
| Prüfung | Umfang |
|---|---|
| Startfreigabe | Prüfpunkte über Umgebung, Konfiguration, Datenbank, Zugänge, Datenübernahme, Kasse und Betrieb. Rein lesend, je Punkt mit Ergebnis, Messwert und Begründung. Ein Erfolgsrückgabewert entsteht nur bei vollständiger Freigabe. |
| Automatisierte Tests | 132 Testklassen zu Kassieren, Kassensicherung, digitaler Schnittstelle, Belegen, Bestand, Gutscheinen, Inventur, Warengruppen, Rechten, Steuersätzen, Nummernkreisen, Migrationen, Sicherung, Archiv, Datenübernahme, Kundenanlage und Auslieferung. Eigener Testläufer ohne Fremdpakete. |
| Schutz der Testumgebung | Drei Prüfungen vor dem Verbindungsaufbau: Der Datenbankname muss auf _test enden, die aufgebaute Verbindung wird gegen den erwarteten Namen gehalten, und eine bereits gefüllte Datenbank wird nur angefasst, wenn sie eine vom Testaufbau angelegte Kennzeichentabelle trägt. |
| Kontrastprüfung | Die Farbwerte des hellen und des dunklen Themas werden aus der Stilvorlage gelesen und nach WCAG 2.1 durchgerechnet, getrennt für Fließtext, große Schrift und Bedienelemente. |
| Prozesslandkarte | Alle Abläufe des Hauses als Diagramm, in dem die Linienart die Aussage trägt: durchgezogen heißt belegt, gestrichelt heißt ungeprüft, rot heißt gesperrt. Hinter jeder roten Kante steht eine Frage, die sich zur Laufzeit beantworten lässt. Ein Test hält die Karte gegen diese Antworten, und zwar in beide Richtungen: Was rot gezeichnet ist, muss zu sein, und jede gemessene Sperre muss in der Karte stehen. Eine Karte, die einen offenen Weg als gesperrt zeichnet, ist schlimmer als gar keine. |
| Belastbarkeitsbericht | Hält je Modul fest, worauf es sich stützt und ob diese Grundlage trägt. Nicht belastbare Bereiche sind in der Oberfläche abgeschaltet. |
Das System ist nicht im Echtbetrieb. Die Umstellung erfolgt nach einer vollständigen Startfreigabe.
Aufnahme des tatsächlichen Funktionsumfangs und der genutzten Masken vor Beginn der Entwicklung.
Front-Controller mit Rechteprüfung, Belege über einen gemeinsamen Codepfad, Artikel, Kunden, Kasse, Verleih, Werkstatt, Inventur, Auswertungen, Verwaltung, Sicherung, Jahresarchiv.
TSE-Anbindung mit vier Ausprägungen, zentrales Konformitätsurteil, Kassiersperre je Vorgang, Ausfalldokumentation, DSFinV-K-Lieferung mit eigener Gegenprüfung.
Bons, Positionen, Abschlüsse, Signaturvermerke und das Protokoll tragen Auslöser gegen Änderung und Löschung. Über dem Protokoll liegt eine Hashkette mit Endsiegel, deren Position die Datenbank vergibt. Vorher konnte die Prüfung eine Änderung nur melden, nicht verhindern.
Bargeldbewegungen mit Nummer, Belegart und Gegenbuchung statt Änderung. Der Kassensturz zählt zwischendurch, ohne den Tag abzuschließen. Befunde bekommen einen Verlauf und einen Abschluss, statt entweder spurlos zu verschwinden oder dauerhaft rot zu blinken.
Eigener Maßstab für die Theke, drei Themen je Benutzerkonto, alle Farben als benannte Merkmale an einer Stelle, dazu eine Kontrastprüfung, die mit einem Rückgabewert antwortet.
Preisprüfung, Erkennung unmöglicher Beträge, Kundenkartei, Warnungsregister, Betriebsart zum Einrichten der Kassen, geprüfte Auslieferung.
Phasenweise und wiederaufsetzbar, mit Abgleich gegen die Berichte des Altsystems. Widersprüche im Altbestand werden gekennzeichnet übernommen.
Der Betrieb verkauft auch über Portale; die Bestände dafür führt bis heute eine fremde Software. Der lesende Zugriff auf deren Bestand steht, der Mengenabgleich ebenfalls. Zurückgeschrieben wird noch nichts: Vier voneinander unabhängige Riegel halten den Schreibweg zu, solange der Nullpunkt nicht steht. Die vollständige Ablösung ist bewusst nicht Teil dieses Vorhabens, sondern ein eigenes.
Seriennummern sind eingetragen, die Verbindung zur Gegenstelle steht aus. Bis dahin weist das System aus, dass nicht signiert wird.
Nach vollständiger Startfreigabe.
Wie etwas gebaut ist, warum es so gebaut ist, ob es sich auf einen anderen Fall übertragen lässt – schreib einfach. Die Nachricht geht als E-Mail an mich, sonst passiert damit nichts.