PHP-Entwickler in Nürnberg

PHP-Entwicklung für Unternehmen in Nürnberg mit Schwerpunkt DATEV-Export: Buchungsstapel im Format EXTF laut DATEV Developer Portal, Header, Felder, Zeichensatz und eine Prüfung vor dem Import durch die Steuerberatung. Ein Einzelentwickler, remote, schriftlich.

PHP-Entwickler

Mein Platz in der WordPress-Community

  • Mein wordpress.org-Konto besteht seit 2006.
  • Ich habe zum WordPress-Core in den Versionen 7.0 und 7.1 beigetragen.
  • Ich betreue 30 Plugins auf wordpress.org.
  • Ich organisiere WordCamps und trete dort als Speaker auf.
  • Ich war bei jedem WordCamp Europe von 2017 bis 2026 dabei.

Mein WordPress.org-Profil

#DATEV-Export aus einer PHP-Anwendung: worum es auf dieser Seite geht

Die DATEV eG hat laut ihrem Impressum ihren Sitz in der Paumgartnerstraße in Nürnberg. Laut der Seite Über DATEV wurde sie 1966 von Steuerberatern gegründet und ist heute eine IT-Genossenschaft mit über 40.000 Mitgliedern. Wer in Nürnberg und Mittelfranken eine PHP-Anwendung mit Buchungen betreibt, ein Warenwirtschaftssystem, ein Buchungsportal oder ein Abrechnungsmodul, hört früher oder später von der Steuerkanzlei den Satz: „Schicken Sie uns das als DATEV-Datei.“

Diese Seite beschreibt genau diesen Weg: eine CSV-Datei im DATEV-Format, Kategorie Buchungsstapel, aus PHP erzeugen und prüfen, bevor die Steuerberatung sie in DATEV Rechnungswesen einliest. Alle Dateiregeln stammen aus der Beschreibung im DATEV Developer Portal. Code zeige ich nur dort, wo ich ihn gegen diese Regeln laufen lassen konnte. Ich bin ein einzelner Entwickler in Gdynia, arbeite remote und schriftlich, ohne Termine vor Ort.

Henkerturm, Wasserturm und Brücke über die Pegnitz in Nürnberg

Foto: Uoaei1, CC BY-SA 4.0, Wikimedia Commons.

#Was das Entwicklerportal öffentlich zeigt

Die Beschreibung des DATEV-Formats ist ohne Anmeldung lesbar: Einstieg, Zeichensatz, Header, Buchungsstapel, Stammdatenformate, Import und Export, Änderungsverlauf. Das Portal verweist außerdem auf ein Prüfprogramm für Dateien im DATEV-Format und auf Musterdaten; ob und wie Sie diese Downloads erhalten, sehen Sie im Portal.

Anmeldung brauchen die Online-Schnittstellen. Das Portal beschreibt den Weg so: mit einem DATEV-Konto einloggen, eine Organisation anlegen, eine App erstellen, API-Produkte abonnieren, in einer Sandbox entwickeln und für den Produktivbetrieb einen technischen Prüftermin buchen. Zu den Importwegen gehört laut Portal auch die Online-API „accounting:extf-files“. Deren Einzelheiten stehen hinter dem Login und gehören deshalb nicht auf diese Seite. Hier geht es um die Datei selbst, die jeder Importweg erwartet.

#Der Aufbau der Datei

Das Portal beschreibt das DATEV-Format als CSV-Datei mit festem Dateinamens-Präfix EXTF_ und standardisiertem Satzaufbau. Die Datei hat drei Arten von Zeilen:

  1. Zeile 1: der Header mit den Metadaten für die Verarbeitung.
  2. Zeile 2: die Überschriften der Nutzdaten laut Formatbeschreibung.
  3. Ab Zeile 3: die Nutzdaten, eine Buchung pro Zeile.

Dazu kommen die technischen Regeln aus dem Einstieg des Portals:

  • Alle Felder sind durch Semikolon getrennt.
  • Jede Zeile endet mit CR/LF.
  • Textfelder stehen in Anführungszeichen, Anführungszeichen im Text werden verdoppelt.
  • Steuerzeichen im Text, etwa Zeilenumbrüche, sind nicht erlaubt.
  • Dezimalzahlen haben ein Komma als Trenner (99,99), Tausendertrennzeichen gibt es in der Regel nicht.

Beim Buchungsstapel kommt eine Mengengrenze hinzu: Laut Formatbeschreibung verarbeitet der Import höchstens 99.999 Buchungen pro Datei. Das Portal empfiehlt außerdem, pro Buchungsperiode eine eigene Datei zu erzeugen, damit jede einzeln für ihre Periode verarbeitet werden kann.

#Zeichensatz: Windows-1252 oder UTF-8 mit BOM

Der Standardzeichensatz für den Import ist laut Portal ISO-8859-1 beziehungsweise Codepage 1252. DATEV Rechnungswesen kann auch Unicode (UTF-8, -16, -32) lesen, aber nur mit Byte Order Mark. Das Portal weist zusätzlich darauf hin, dass Unicode nur beim manuellen Import und über die Online-API funktioniert; die Konsolenanwendung KrStaPv unterstützt es nicht.

Für PHP heißt das: Die Anwendung arbeitet intern in UTF-8, und die Exportdatei wird beim Schreiben umgewandelt. Wenn der Importweg der Kanzlei unklar ist, nehme ich Windows-1252, weil es für alle genannten Wege der Standard ist. Namen mit Zeichen außerhalb dieses Zeichensatzes brauchen dann eine Regel, bevor sie in der Datei stehen, und die lege ich schriftlich mit Ihnen fest.

#Der Header: 31 Felder

Der Header ist laut Portal immer die erste Zeile und muss in seinem Aufbau genau eingehalten werden. Das Portal beschreibt jedes Feld mit einem regulären Ausdruck. Die wichtigsten Felder für einen Export aus einer eigenen Anwendung:

Nr.FeldRegel laut Portal
1Kennzeichen"EXTF" für den Export aus einer Fremdanwendung, "DTVF" für den Export aus einer DATEV-Anwendung
2Versionsnummer700
3 und 4Formatkategorie und Formatname21 und "Buchungsstapel"
5Formatversion13 beim Buchungsstapel
6Erzeugt amZeitstempel YYYYMMDDHHMMSSFFF
11Beraternummer1001 bis 9999999
12Mandantennummer1 bis 99999
13WirtschaftsjahresbeginnYYYYMMDD
14Sachkontenlänge4 bis 8; muss beim Import zur Konfiguration des Mandats in der DATEV-Anwendung passen
15 und 16Datum von, Datum bisBeginn und Ende der Periode des Stapels
17Bezeichnungbis 30 Zeichen, zum Beispiel „Rechnungsausgang 02/2024“
21Festschreibung0 keine, 1 Festschreibung (Standard)
22WKZISO-Währungscode, "EUR" als Standard

Die Felder 11 bis 14 kennt nur die Steuerkanzlei. Ich frage sie schriftlich ab und lege sie als Konfiguration an, nicht als Konstante im Code. Auch die Formatversion 13 ist kein Dauerzustand: Der Änderungsverlauf im Portal nennt die Erhöhung von 12 auf 13 im Februar 2024 (neues Feld 125, abweichendes Skontokonto) und Korrekturen an einigen regulären Ausdrücken im März 2025. Ein Export sollte die Formatversion deshalb an einer Stelle führen und bei einer neuen Version nur dort ändern.

Auf die Festschreibung gehe ich besonders ein. Der Standardwert 1 im Header bedeutet laut Portal, dass der Stapel nach dem Import festgeschrieben wird, und ein Wert 1 im Feld 114 einer einzelnen Buchung schreibt den ganzen Stapel fest. Ob ein Stapel festgeschrieben ankommt, entscheidet die Steuerberatung. Für Testdateien setze ich mit der Kanzlei zusammen 0 fest.

#Die Buchungszeile: Felder, an denen Exporte scheitern

Eine Buchungszeile hat in der aktuellen Formatversion 125 Felder in fester Reihenfolge, auch wenn die meisten leer bleiben. Die folgenden Regeln stammen aus der Beschreibung des Buchungsstapels im Portal:

Nr.FeldRegel laut Portal
1Umsatzpositiv, nicht 0,00, bis zu zehn Stellen vor dem Komma, zwei Nachkommastellen
2Soll-/Haben-Kennzeichen"S" oder "H", bezieht sich auf das Konto in Feld 7
7KontoSach- oder Personenkonto, höchstens 9 Stellen, nicht nur Nullen
8Gegenkontowie Feld 7
9BU-Schlüsselvierstellig in Anführungszeichen
10BelegdatumTTMM; das Jahr kommt aus Header-Feld 13
11Belegfeld 1bis 36 Zeichen aus Buchstaben, Ziffern und $ & % * + - /
13Skontonur bei Zahlungen zulässig, nicht 0
14Buchungstextbis 60 Zeichen
95Auftragsnummerbis 30 Zeichen; bei Rechnungen zu PayPal-Zahlungen die Order-ID oder Transaktions-ID

Belegfeld 1 ist die Rechnungs- oder Belegnummer und laut Portal der „Schlüssel“ für den Ausgleich offener Rechnungen. Leerzeichen, Umlaute, Punkt, Komma, Semikolon und Doppelpunkt sind unzulässig. Eine Rechnungsnummer wie „RE 2026.0815“ passt deshalb nicht. Ich ändere sie nicht stillschweigend im Export, denn eine umgebaute Nummer gleicht in der Kanzlei keine offene Rechnung mehr aus. Entweder erzeugt die Anwendung Rechnungsnummern von Anfang an in dieser Form, oder der Export lehnt die Zeile ab und nennt sie im Fehlerbericht.

Beim Umsatz gilt: Das Vorzeichen steckt im Soll-/Haben-Kennzeichen, der Betrag selbst ist immer positiv. Wie PHP mit Beträgen sicher umgeht, steht auf der Seite PHP-Entwickler Frankfurt. Für den Export genügt: Beträge liegen als Cent in einer Ganzzahl und werden erst beim Schreiben in die Komma-Form gebracht.

function datevAmount(int $cents): string
{
    return intdiv($cents, 100) . ',' . sprintf('%02d', $cents % 100);
}

$csv = implode("\r\n", $lines) . "\r\n";
file_put_contents($path, mb_convert_encoding($csv, 'Windows-1252', 'UTF-8'));

datevAmount(10018) ergibt 100,18, datevAmount(5) ergibt 0,05. Mit intdiv() bleibt die Rechnung bei ganzen Zahlen, und mb_convert_encoding() wandelt die fertige Datei in den Standardzeichensatz um.

#So prüfe ich eine Datei, bevor die Steuerberatung sie importiert

Ein Import in DATEV ist kein Testlauf, den man beliebig oft wiederholt. Deshalb prüft der Export seine eigene Datei, bevor jemand sie hochlädt. Die Prüfung liest die fertige Datei wieder ein, statt dem Code zu vertrauen, der sie geschrieben hat. Das folgende Skript prüft eine Auswahl der Regeln aus dem Portal: Zeilenende, Zeichensatz, die wichtigsten Header-Felder, die Zahl der Spalten und sieben Felder jeder Buchung.

<?php
declare(strict_types=1);

/** @return list<string> */
function checkExtf(string $path): array
{
    $errors = [];
    $raw = file_get_contents($path);
    if ($raw === false || $raw === '') {
        return ['Datei nicht lesbar'];
    }
    // Windows-1252 ist der Standard, UTF-8 gilt nur mit BOM.
    if (!str_starts_with($raw, "\xEF\xBB\xBF")
        && mb_check_encoding($raw, 'UTF-8') && preg_match('/[\x80-\xFF]/', $raw)) {
        $errors[] = 'UTF-8 ohne BOM';
    }
    if (preg_match('/(?<!\r)\n/', $raw)) {
        $errors[] = 'Zeilenende ohne CR/LF';
    }
    $lines = preg_split('/\r\n/', rtrim($raw, "\r\n"));
    $header = str_getcsv($lines[0], ';', '"', '');
    $rules = [
        1  => '/^(EXTF|DTVF)$/',
        2  => '/^700$/',
        3  => '/^(16|20|21|46|48|65)$/',
        5  => '/^(2|4|5|13)$/',
        13 => '/^20\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])$/',
        14 => '/^[4-8]$/',
    ];
    foreach ($rules as $nr => $re) {
        if (!preg_match($re, $header[$nr - 1] ?? '')) {
            $errors[] = "Header Feld $nr: " . ($header[$nr - 1] ?? 'fehlt');
        }
    }
    if (count($lines) - 2 > 99999) {
        $errors[] = 'mehr als 99.999 Buchungen in einer Datei';
    }
    $checks = [
        1  => '/^(?!0{1,10},00)\d{1,10},\d{2}$/',
        2  => '/^(S|H)$/',
        7  => '/^(?!0{1,9}$)\d{1,9}$/',
        8  => '/^(?!0{1,9}$)\d{1,9}$/',
        10 => '/^\d{4}$/',
        11 => '/^[\w$&%*+\-\/]{0,36}$/',
        14 => '/^.{0,60}$/u',
    ];
    foreach (array_slice($lines, 2) as $i => $line) {
        $f = str_getcsv($line, ';', '"', '');
        $n = $i + 3;
        if (count($f) !== 125) {
            $errors[] = "Zeile $n: " . count($f) . ' Felder statt 125';
            continue;
        }
        foreach ($checks as $nr => $re) {
            if (!preg_match($re, $f[$nr - 1])) {
                $errors[] = "Zeile $n Feld $nr: {$f[$nr - 1]}";
            }
        }
    }
    return $errors;
}

Getestet habe ich es gegen eine Datei mit dem Beispiel-Header aus dem Portal. Eine gültige Buchungszeile ergibt keine Meldung. Eine Zeile mit Betrag 0,00, Belegfeld „Rg 1.2“ und Belegdatum 31 ergibt drei Meldungen mit Zeile und Feldnummer, und eine Datei mit LF statt CR/LF wird als solche gemeldet. Die Muster stammen aus den Ausdrücken des Portals. Den Rest der Regeln, zum Beispiel für Steuerschlüssel oder EU-Felder, prüft dieses Skript nicht. Dafür ist das Prüfprogramm im Portal da.

Mein Ablauf für eine Exportstrecke in der Praxis:

  1. Die Kanzlei nennt schriftlich Beraternummer, Mandantennummer, Wirtschaftsjahresbeginn, Sachkontenlänge und Sachkontenrahmen, und den Importweg.
  2. Die Anwendung erzeugt eine Datei für eine Buchungsperiode mit synthetischen Testdaten, Festschreibung 0.
  3. Das Skript prüft die Datei, danach das Prüfprogramm aus dem Portal.
  4. Die Kanzlei importiert die Testdatei in einen Testmandanten oder liest sie in DATEV ein und sieht nach, ob Konten, BU-Schlüssel und Belegnummern stimmen.
  5. Erst dann kommt echter Datenbestand, wieder Periode für Periode.

#Was die Steuerberatung vorab liefern muss

Die Steuerberatung legt fest, was in die Felder gehört, die der Code nicht erraten kann. Ich schicke vor dem Start diese Liste:

  • Berater- und Mandantennummer, Beginn des Wirtschaftsjahres
  • Sachkontenlänge und Sachkontenrahmen des Mandanten
  • Konten für Erlöse, Forderungen, Zahlungsdienstleister, Gebühren und Erstattungen
  • BU-Schlüssel für jeden Steuerfall, den Ihr Geschäft hat, auch Reverse-Charge und EU-Lieferungen
  • Ob Rechnungen als Beleglink an der Buchung hängen sollen
  • Ob Stapel festgeschrieben ankommen sollen oder nicht
  • Importweg: manuell, KrStaPv oder Online-API, und damit der erlaubte Zeichensatz

Steuerlich entscheide ich nichts. Welcher BU-Schlüssel zu welchem Geschäftsvorfall gehört, ist Sache der Kanzlei. Ich setze die Vorgabe um und halte sie in einer Tabelle fest, die Kanzlei und Entwickler gemeinsam lesen können.

#Exportdateien mit Personendaten und das BayLDA

Eine Buchungsdatei enthält Personenkonten, Belegnummern und Buchungstexte, die Namen von Kundschaft enthalten können. Für Unternehmen in Bayern überwacht das Bayerische Landesamt für Datenschutzaufsicht (BayLDA) das Datenschutzrecht im nicht-öffentlichen Bereich.

Für die Frage, ob mit der Steuerberatung ein Vertrag zur Auftragsverarbeitung nötig ist, hat das BayLDA eine Auslegungshilfe (Stand 14. Januar 2020) veröffentlicht. Danach ist die Tätigkeit von Steuerberaterinnen und Steuerberatern mangels Weisungsgebundenheit regelmäßig keine Auftragsverarbeitung nach Art. 28 DSGVO, sondern eigenverantwortlich. Das betrifft Sie und Ihre Kanzlei.

Meine Rolle ist eine andere. Sobald ich auf einem Server mit echten Buchungsdaten arbeite, kann Art. 28 DSGVO für mich gelten. Deshalb arbeite ich bei der Entwicklung mit synthetischen Daten. Brauche ich Zugriff auf echte Daten, klären wir den Vertrag vorher schriftlich. Rechtsberatung gebe ich nicht; die liefert Ihre Datenschutzberatung.

#Shops mit WooCommerce

Läuft Ihre Buchhaltung über DATEV und der Shop auf WooCommerce, gehört das Format zu einem größeren Thema: Steuerschlüssel, OSS-Umsätze, Zahlungsabgleich. Das beschreibt die Seite WooCommerce und DATEV. Die Dateiregeln auf dieser Seite gelten für jede PHP-Anwendung, ob mit Laravel, Symfony oder ohne Framework.

#Ablauf mit mir

  1. Sie schreiben mir, welche Anwendung die Buchungen erzeugt und welche DATEV-Produkte die Kanzlei nutzt.
  2. Ich stelle meine Rückfragen schriftlich und schicke eine erste Einschätzung mit Umfang, Annahmen und offenen Punkten.
  3. Nach Ihrer schriftlichen Freigabe baue ich den Export auf einer Testumgebung und liefere die Testdatei zusammen mit dem Prüfbericht.
  4. Die Kanzlei importiert die Testdatei und meldet zurück, was nicht stimmt.
  5. Danach folgt der Echtbetrieb periodenweise, mit dokumentiertem Fehlerbericht pro Lauf.
  6. Optional betreue ich den Export später weiter, etwa bei einer neuen Formatversion. Die Stunden gelten pro Monat und werden nicht übertragen.

#Was ich von Ihnen brauche

  • Die Angaben der Kanzlei aus der Liste oben, möglichst schriftlich von ihr selbst
  • Zugang zum Repository der Anwendung oder einen Auszug der Stelle, an der Buchungen entstehen
  • Eine Beispielrechnung je Steuerfall und ein Beispiel, wie die Kanzlei die Zahlen heute erhält
  • Die PHP-Version und das Framework, falls vorhanden
  • Die Vorgaben zu Konten und Steuerschlüsseln von Ihrer Steuerberatung, und die Bedienung von DATEV selbst zeigt Ihnen die Kanzlei

#Häufige Fragen

#Wie viele Buchungen darf eine Datei enthalten?

Laut Formatbeschreibung höchstens 99.999 Buchungen pro Buchungsstapel. Das Portal empfiehlt außerdem eine Datei pro Buchungsperiode.

#Welchen Zeichensatz muss die Datei haben?

Der Standard ist ISO-8859-1 oder Windows-1252. UTF-8 und andere Unicode-Formate liest DATEV nur mit Byte Order Mark, und nur beim manuellen Import oder über die Online-API. Die Konsolenanwendung KrStaPv unterstützt kein Unicode.

#Warum darf in Belegfeld 1 kein Leerzeichen stehen?

Laut Portal sind dort nur Buchstaben, Ziffern und die Zeichen $ & % * + - / erlaubt, bis 36 Stellen. Das Feld dient als Schlüssel für den Ausgleich offener Rechnungen, deshalb ändere ich Rechnungsnummern nicht heimlich, sondern lehne ungültige Zeilen im Export ab.

#Muss ich ein DATEV-Entwicklerkonto haben?

Für die Datei selbst nicht, die Beschreibung ist öffentlich. Die Online-Schnittstellen setzen laut Portal eine Anmeldung, eine Organisation und eine App voraus. Welchen Weg die Kanzlei nutzt, klären wir zuerst.

#Brauche ich einen Vertrag zur Auftragsverarbeitung mit meiner Steuerberatung?

Nach der Auslegungshilfe des BayLDA vom 14. Januar 2020 in der Regel nicht, weil Steuerberaterinnen und Steuerberater eigenverantwortlich arbeiten. Für mich als Entwickler mit Zugriff auf echte Daten kann die Frage anders ausfallen. Die Entscheidung trifft Ihre Datenschutzberatung.

#Welche Dateien kann DATEV Rechnungswesen aus meiner PHP-Anwendung lesen?

Laut Portal ist das DATEV-Format eine CSV-Datei mit dem Dateinamens-Präfix EXTF_ und einem festen Satzaufbau. Der Buchungsstapel ist nur eines der Formate. Dazu kommen wiederkehrende Buchungen, Debitoren und Kreditoren, Zahlungsbedingungen, Sachkontenbeschriftungen und diverse Adressen.

#Weitere Leistungen in Nürnberg

#Schreiben Sie mir

Muss Ihre PHP-Anwendung Buchungen an die Steuerkanzlei liefern, und die Datei wird abgelehnt oder gibt es sie bisher nur als Excel-Liste? Schreiben Sie mir über das Kontaktformular: welche Anwendung die Buchungen erzeugt, welche DATEV-Produkte die Kanzlei nutzt, welche PHP-Version läuft und bis wann Sie das brauchen. Die erste schriftliche Einschätzung zum Export kostet Sie nichts. Ich antworte schriftlich an Werktagen.

Brauchen Sie die Leistung: PHP-Entwickler in Nürnberg?

Beschreiben Sie Ihr Projekt per E-Mail. Antwort innerhalb von 48 Stunden.

Projekt beschreiben

Weitere WordPress-Dienste und Wissensbasis entdecken