Elektronische Rechnungen mit freier und quelloffener Software erzeugen

Ab dem 1. Januar 2025 sind elektronische Rechnungen für alle B2B-Transaktionen in Deutschland verpflichtend. Einige andere Länder im Europäischen Wirtschaftsraum haben bereits ähnliche Maßnahmen umgesetzt oder werden bald folgen. Das hat zur Folge, dass etliche Firmen in Deutschland mit Hochdruck an der Einführung elektronischer Rechnungen arbeiten, und nicht wenige sich dabei mit erheblichen Schwierigkeiten konfrontiert sehen. Dieser Artikel beschreibt, wie sich elektronische Rechnungen in verschiedenen Formaten komplett mit freier und quelloffener Software erzeugen lassen.

Electronic Invoice Creation Process

Table Of Contents

GitHub-Repository

Die Software zur Erzeugung elektronischer Rechnungen ist im GitHub-Repository e-invoice-eu organisiert. Es handelt sich um eine Node.JS-Applikation, genauer gesagt um einen NestJS-Server der lokal installiert und auf jeder Plattform laufen sollte, mit den üblichen Einschränkungen auch unter MS-Windows.

Rechtlicher Hintergrund - EN16931

Die Europäische Union plant die vollständige Digitalisierung von Rechnungen im Verlauf der nächsten Jahre. In diesem Kontext wurde auch die Norm EN16931 veröffentlicht, die Anforderungen an elektronische Rechnungen definiert.

Formate

Zur Zeit werden zwei Formate für Rechnungen gemäß EN16931 akzeptiert. Zum einen ist das UBL (Universal Business Language), zum anderen CII (Cross-Industry Invoice). Bei beiden handelt es sich um XML-Formate.

Eine weitere Ausprägung ist Factur-X bzw. ZUGFeRD. Factur-X wurde in Frankreich entwickelt; ZUGFeRD ist das deutsche Gegenstück. Intern wird für Format CII verwendet, aber das XML-Dokument wird nicht direkt mit den Kunden ausgetauscht, sondern an der PDF angehangen.

Die Möglichkeit, Dokumente an PDFs anzuhängen ist wenig bekannt, und die einzige gängige Software mit graphischer Benutzeroberfläche, die in der Lage ist, PDF-Anhänge darzustellen oder zu extrahieren, ist Adobe Acrobat Reader.

PDF-Anhänge in Adobe Acrobat Reader

Die Funktionalität verbirgt sich hinter einer Büroklammer am rechten Fensterrand. Ein Klick fördert die Liste mit Anhängen zu Tage. In diesem Fall wurde nicht nur die XML-Rechnung sondern auch ein weiteres Dokument mit Zusatzinformationen angehangen.

Business Terms

Die Norm verwendet "business terms", also Geschäftsterminologie, um die Informationen in einer Rechnung zu beschreiben. Die Rechnungsnummer beispielsweise ist Business-Term 1 oder kurz BT-1, der Ländercode der Käuferpostadresse ist BT-40 und so weiter.

Business Rules

Der Standard formuliert auch zahlreiche Business-Rules, also Geschäftsregeln, welche inhaltliche Anforderungen an die Rechnung beschreiben. So fordert zum Beispiel die Business-Rule BR-CO-27, dass die Summe aller Nettosummen der Rechnungspositionen (BT-106) gleich dem Nettorechnungsbetrag (BT-131) sein muss.

Hier ist es mit der Standardisierung dann auch schnell vorbei. Mitgliedsstaaten können ihre eigenen Business-Rules definieren. Eine elektronische Rechnung, die in Deutschland gültig ist, muss nicht zwangsläufig auch in Dänemark gültig sein und akzeptiert werden. Das gilt auch umgekehrt. Zum Beispiel hat Deutschland seine eigenen Standards XRECHNUNG-UBL und XRECHNUNG-CII definiert, und Rechnungen, die an die öffentliche Hand ausgestellt werden, müssen diesen Standards folgen.

Was sind elektronische Rechnungen?

PDF-Dokumenten gelten nicht als elektronische Rechnung im Sinne von EN16931, und Word-Dokumente schon gar nicht. Elektronische Rechnungen müssen maschinenlesbar sein. Dokumente im Format Factur-X/ZUGFeRD gelten als maschinenlesbar, weil die Rechnung im XML-Format aus der PDF-Datei extrahiert werden kann.

Dokumentation

Der Text von EN16931 ist nicht frei verfügbar, jedenfalls nicht als Download im Internet. Glücklicherweise hat OpenPeppol - die Organisation hinter PEPPOL (Pan-European Public Procurement OnLine) - die sogenannten Business Interoperability Specifications (BIS) veröffentlicht. Eine dieser Spezifikationen ist die für UBL-Rechnungen und unter https://docs.peppol.eu/poacc/billing/3.0/syntax/ubl-invoice/tree/ verfügbar.

Diese Dokumentation beschreibt die Struktur und alle Anforderungen an UBL-Rechnungsdaten auf sehr übersichtliche und einfach zu verwendende Art. Das ist einer der Gründe, weshalb e-invoice-eu dieses Format als Basis für das interne Format für Rechnungsdaten verwendet. Der größte Unterschied ist, dass e-invoice-eu JSON bzw. YAML statt XML verwendet, aber die Struktur ist weitestgehend gleich.

Business-Terms und Business-Rules

Business-Terms und Business-Rules wurden bereits oben beschrieben und werden an vielen Stellen in der Dokumentation erwähnt. Die einschlägigen Business-Rules sind unterhalb der Dokumentation der jeweiligen Elemente aufgelistet. Für das Element /ubl:Invoice/cbc:LineExtensionamount gelten beispielsweise die Business-Rules BR-12, BR-CO-10, BR-DEC-09, UBL-DT-01 und BR-CL-03.

Die oft erwähnten Business-Terms sind leider ungleich schwerer zu finden. Allerdings kann Google hier helfen. Will man beispielsweise herausfinden, was BT-131 ist, kann man die Suchanfrage "site:docs.peppol.eu BT-131" absetzen. Einer der ersten Treffer führt in der Regel zur gewünschten Information.

Kardinalitäten verstehen

Der Standard verwendet "Kardinalitäten", um auszudrücken in welchem Umfang ein bestimmtes Element erforderlich ist. Kardinalitäten werden als zwei Zahlen, die durch zwei Punkte verbunden sind ausgedrückt, zum Beispiel 0..1. Die erste Zahl gibt die Mindestanzahl an Vorkommen an, die zweite die maximale Anzahl. Eine Mindestanzahl von 0 bedeutet, dass es sich um ein optionales Element handelt.

Für die maximale Anzahl kann auch der Buchstabe n verwendet werden, der für jede Ganzzahl größer 0 steht.

Eine Kardinalität 1..1 bedeutet allerdings nicht zwangsläufig, dass ein bestimmtes Element in einem Rechnungsdokument vorhanden sein muss. Das hängt auch von den Elternelementen ab.

Beispiel: Das Element /ubl:Invoice/cac:InvoiceLine/cac:AllowanceCharge/cbc:Amount hat die Kardinalität 1..1. Das Elterelement /ubl:Invoice/cac:InvoiceLine/cac:AllowanceCharge hat aber die Kardinalität 0..n, was bedeutet, dass es optional ist, aber beliebig oft vorhanden sein darf. Das bedeutet, dass cbc:Amount nur verpflichtend ist, wenn die Rechnungsposition ein cac:AllowanceCharge hat.

Die Notwendigkeit, dass ein Element vorhanden sein muss, kann sich auch aus Business-Rules ergeben. Zum Beispiel fordert BR-CO-25, falls der fällige Rechnungsbetrag (BT-115) ist, müssen entweder das Fälligkeitsdatum (BT-9) oder die Zahlungsbedingungen (BT-20) angegeben sein, siehe https://docs.peppol.eu/poacc/billing/3.0/syntax/ubl-invoice/cac-PaymentTerms/cbc-Note/.

Es ist ebenfalls möglich, dass bestimmte Element von Gesetzes wegen vorhanden sein müssen. Es ist zum Beispiel laut Standard ausreichend, dass die Käufer- und Lieferantenaddresse alleine aus dem Ländercode besteht. Allerdings wird dies kaum den gesetzlichen Bestimmungen entsprechen, solange andere Adress-Elemente wie Straße und Stadt fehlen.

Code-Listen

Damit elektronische Rechnungen nicht nur maschinenlesbar sonder auch maschinen-verstehbar sind, müssen gewisse textuelle Informationen standardisiert werden, indem die erlaubten Werte auf Werte aus Code-Listen eingeschränkt werden.

So hat zum Beispiel die in Rechnung gestellte Menge /ubl:Invoice/cac:InvoiceLine/cbc:InvoicedQuantity ein Attribut "@unitCode", dass einen standardisierten Code für die Maßeinheit statt eines Textes wie Stück, Stk., kg oder Stunde angeben muss. Die Dokumentation fordert, dass dieser Code der "UN/ECE-Empfehlung 20 mit der Erweiterung aus der Empfehlung 21" entsprechen muss.

Alle Code-Listen können auf der PEPPOL-Site https://docs.peppol.eu/poacc/billing/3.0/ im Abschnitt "Code lists" gefunden werden. Ein Klick auf den Link Recommendation 20, including Recommendation 21 codes - prefixed with X (UN/ECE) öffnet eine Seite mit der Code-Liste, die alle legalen Werte zeigt. Dort ist ebenfalls ersichtlich, für welche Elemente diese Code-Liste verwendet wird.

"Stück" beispielsweise können mit H87 oder XPP kodiert werden. Viele Einheiten können mit zwei Codes kodiert werden; einmal mit und einmal ohne ein führendes X.

Die Validierung in e-invoice-eu erzwingt übrigens die Verwendung der korrekten Code-Listen.

Status von e-invoice-eu

Meine Firma stellt bereits seit Jahren elektronische Rechnungen aus. Dafür kamen ein selbstgeschriebenes Perl-Modul, LibreOffice, Java, und exiftool zum Einsatz aber dieser Ansatz war nicht sehr flexibel, weshalb ich entschieden habe, die Software mit Node.JS komplett neuzuschreiben, weil JavaScript heute eine bessere Akzeptanz hat.

Zum jetzigen Zeitpunkt kann die Software Rechnungen in den folgenden Formaten erzeugen:

  • UBL (PEPPOL)
  • XRECHNUNG-UBL

Die Roadmap für die nähere Zukunft sieht so aus:

  • CII
  • Factur-X/ZUGFeRD
  • Embedding PDFs in XML
  • Including business rules in validation

Allgemeiner Workflow

Die Zielgruppe für die Software sind kleine und mittelgroße Firmen, die keine ERP-Software sondern eine Tabellenkalkulation zur Erzeugung von Rechnungen verwenden.

Zuordnung der Tabellenkalkulations-Daten

Im ersten Schritt müssen die Daten aus der Tabellenkalkulation auf Rechnungsdaten abgebildet werden. Der entsprechende Endpunkt erwartet daher eine Zuordnungsdatei (Mapping-Datei) und eine Tabellenkalkulationsdatei und erzeugt daraus eine JSON-Datei mit den Rechnungsdaten. Das Schema für diese JSON-Daten ist 100 % äquivalent zur XML-Struktur, die in der PEPPOL-Doku unter https://docs.peppol.eu/poacc/billing/3.0/syntax/ubl-invoice/ beschrieben ist.

Auf dieses Format wird als "internes Format" Bezug genommen.

Elektronische Rechnungen erzeugen

Ein anderer Endpunkt kann dazu genutzt werden, um das eigentliche Rechnungsdokument auf Basis der im internen Format zur Verfügung gestellten Daten entweder als XML oder PDF zu erzeugen. Wenn man keine Tabellenkalkulation zur Erzeugung der Daten im internen Format verwenden will, kann dieser Endpunkt genutzt werden, um das Rechnungsdokument, das an Kunden versendet werden kann, zu erzeugen.

Mappen und Erzeugen auf einmal

Die Umsetzung der Daten und die Erzeung der Rechnung kann auch in einem einzigen Schritt erfolgen. Dies ist die empfohlene Vorgehensweise, wenn eine Tabellenkalkulation für die Rechnungsdaten verwendet wird.

Validierung

Der Service validiert die Eingabedaten gegen Schemata (genauer gesagt JSON schema). Das schließt allerdings lediglich die Prüfung struktureller Restriktionen ein, wie zum Beispiel die Kardinalität von Elementen. Business-Rules werden zur Zeit nicht überprüft. Das wird sich in der Zukunft eventuell ändern, aber derzeit müssen entweder Online-Validatoren verwendet oder ein Offline-Validator installiert werden. Alle verfügbaren Offline-Validatoren erfordern eine Java-Laufzeit-Umgebung und sind dementsprechend langsam.

Das e-invoice-eu-Projekt bietet Wrapper-Skripte um diese Validatoren, siehe https://github.com/gflohr/e-invoice-eu/tree/main/contrib/validators für weitere Informationen.

Es wird dringend empfohlen, den Validierungsschritt nicht zu überspringen, besonders am Anfang. Die Erfahrung zeigt, dass Rechnungen die Validierung anfangs praktisch nie passieren, und es ist daher sehr wahrscheinlich, dass Kunden die Dokumente zurückweisen werden.

Den Dienst Betreiben

Voraussetzungen

Zunächst einmal wird Node.JS und ein Paketmanager wie npm, yarn, pnpm oder bun benötigt. Dieser Artikel geht von der Benutzung von npm aus, aber andere Paketmanager funktionieren genauso.

Wer plant, Factur-X/ZUGFeRD-Dokumente zu erzeugen benötigt ebenfalls LibreOffice. Unter Linux muss Perl auch installiert sein, obwohl Perl fast immer vorinstalliert sein wird.

Für die Offline-Validierung werden eine Java-Laufzeitumgebung und weitere Java-Bibliotheken benötigt, je nachdem, welche Dokumente validiert werden sollen. Siehe https://github.com/gflohr/e-invoice-eu/tree/main/contrib/validators für weitere Informationen

Installation

Zunächst müssen die Abhängigkeiten des Projektes installiert werden:

$ npm install

Wichtig! Wenn bun benutzt wird, muss sichergestellt sein, dass optionale Abhängigkeiten installiert werden. Das geht mit bun install --optional.

Build

Als nächstes muss der Server erzeugt werden:

$ npm run build

Dies erzeugt ein Verzeichnis dist, dass an eine beliebige Stelle kopiert werden kann.This will create a directory dist that you can copy wherever you like.

Starten

Der Server kann in dem Verzeichnis, in dass dist kopiert wurde, gestartet werden:

$ node dist/src/main.js

Man sollte auch eine Datei .env mit dieser Zeile erzeugen:

NODE_ENV=production

Eine eventuelle Fehlermeldung "address already in use" bedeutet, dass der Port 3000 bereits von einer anderen Applikation verwendet wird. In diesem Fall muss ein anderer, noch nicht benutzter Port gewählt werden, indem .env so geändert wird:

NODE_ENV=production
PORT=8080

Testen

Wenn die Adresse http://localhost:3000/api im Browser geöffnet wird, sollte die OpenAPI/Swagger-Dokumentation des Dienstes erscheinen.

Typische Anwendungsfälle

Die folgenden Anwendungsfälle setzen voraus, dass das Repository e-invoice-eu geklont wurde, der Server im Development-Modus mit npm run start gestartet wurde, und man sich in dem Verzeichnis befindet, in das das Repository geklont wurde.

Rechnung aus einer Tabellenkalkulationsdatei erzeugen

Das Repository e-invoice-eu wird mit Beispieldateien einer einer Beispiel-Zuordnungsdatei (Mapping) ausgeliefert. Das lässt sich folgendermaßen ausprobieren:

$ curl -v -X POST \
    http://localhost:3000/api/invoice/transform-and-create/UBL \
    -F mapping=@contrib/mappings/default-invoice.yaml \
    -F data=@contrib/templates/1234567890-consulting/default-invoice.ods

Dies ruft den Alles-auf-Einmal-Endpunkt auf, der die Rechnungs-Tabelle contrib/templates/1234567890-consulting/default-invoice.ods mit der Zuordnungsdatei contrib/mappings/default-invoice.yaml in ein UBL-Dokument transformiert. Das Ausgabeformat wird als Pfadparameter nach dem eigentlichen Endpunkt /api/invoice/transform-and-create angegeben. Zur Zeit werden lediglich die Format UBL und XRECHNUNG-UBL unterstützt.

Die erzeugte Rechnung ist zur Zeit noch nicht valide, weil die Eingabedaten in der Tabelle noch Unsinn für Entwicklungszwecke enthalten.

Rechnung in das interne Format transformieren

Es ist ebenfalls möglich, lediglich Daten in das interne Format zu transformieren:

$ curl -v -X POST \
    http://localhost:3000/api/mapping/transform \
    -F mapping=@contrib/mappings/default-invoice.yaml \
    -F data=@contrib/templates/1234567890-consulting/default-invoice.ods

Rechung aus Daten im internen Format erzeugen

Dies ist noch nicht implementiert, aber wird folgendermaßen funktionieren:

$ curl -v -X POST \
    http://localhost:3000/api/invoice/transform/UBL \
    -F data=@contrib/templates/1234567890-consulting/default-invoice.ods

Die Swagger/OpenAPI-Dokumentation lesen

Dafür muss die Adresse http://localhost:3000/api im Browser geöffnet werden.

Zuordnungsschema herunterladen

Das JSON-Schema für Mappings (mit denen Tabellendaten auf Rechnungsdaten abgebildet werden) ist unter http://localhost:3000/api/schema/mapping verfügbar.

Rechnungsschema herunterladen

Das JSON-Schema für das interne Rechnungsformat ist unter http://localhost:3000/api/schema/invoice verfügbar.

Einfach mal loslegen

Wer den Service für das eigene Business verwenden will, sollte unbedingt mit den zur Verfügung gestellten Beispieldateien beginnen. Diese können dann an die eigenen Bedüfnisse angepasst werden.

Die Tabelle

Eine Beispiel-Tabelle gibt es in contrib/templates/1234567890-consulting/default-invoice.ods.

Formate

Die Beispieltabelle wird im Open-Document-Format mit der Dateinamenserweiterung .ods zur Verfügung gestellt. Es können aber auch Excel-Dateien mit der Erweiterung .xlsx verwendet werden. Genau gesagt, kann jedes Format, das von der Bibliothek SheetJS unterstützt wird, verwendet werden. Siehe dazu die Dateiformatsseite der SheetJS-Dokumentation

Druckbereiche

Die Tabelle ist mit ziemlich vielen zusätzlichen Informationen für die elektronische Rechnung verunstaltet. Es ist wichtig, diese zusätzlichen Daten in der PDF- bzw. Papierversion zu verstecken, indem Druckbereiche definiert werden.

Wer lediglich XML-Dokumente erzeugen will, kann dies natürlich ignorieren. Will mann allerdings in der Zukunft eines der hybriden Factur-X/ZUGFeRD-Formate verwenden, sollte man auch ein Auge auf das Design des Dokuments haben. Und auch mit XML ist es möglich, eine PDF-Version der Rechnung als base64-kodierten String einzubetten.

Stellt man hybride Rechnungen aus, fordert zumindest die deutsche Finanzverwaltung, dass die PDF- und XML-Versionen inhaltsgleiche Mehrstücke darstellen. Das bedeutet, das beiden Dokumente die exakt gleichen steuerrelevanten Informationen enthalten müssen. Dies muss ebenfalls beachtet werden.

Rechnungsnummer

Ich organisiere meine Rechnungen in Verzeichnissen, deren Namen mit der Rechnungsnummer beginnen. In der Vergangenheit bin ich des öfteren in die Bredouille gekommen, weil ich versehentlich Rechnungsnummern doppelt verwendet oder übersprungen hatte. Deshalb verwende ich mittlerweile eine Formel in der Tabellenkalkulation, um die Rechnungsnummer aus dem Verzeichnisnamen zu ermitteln:

Rechnungsnummer extrahieren

In der oberen Zelle wird zunächst der Verzeichnisteil des Dateinamens extrahiert. In der unteren Zelle werden die ersten 10 Zeichen dieses Namens mit folgender Formel extrahiert:

=LEFT(R1, 10)

Wird eine andere Dateisystemsstruktur verwendet, oder haben die eigenen Rechnungsnummern eine andere Länge, muss die Formel selbstverständlich angepasst werden.

In der Druckversion soll der String "Invoice № NNNNNNNNNN" angezeigt werden, aber für die elektronische Rechnung muss die Rechnungsnummer isoliert zur Verfügung steht. Deshalb wird die angezeigte Rechnungsnummer mit der Formel =CONCATENATE("Invoice № ", R2) erzeugt.

Codelisten

Wie oben erwähnt, müssen viele Daten mit Codelisten kodiert werden, zum Beispiel Maßeinheiten oder Steuerkategorien. Speziell Steuerkategorien müssen auch noch zu Steuersätzen zugeordnet werden.

Diese Codelisten lege ich normalerweise in separten Tabs ab. Der Tab "Tax" (also Steuer) enthält zum Beispiel fünf Spalte. Die erste Spalte enthält den Code, der in der Druck-Version der Rechnung angezeigt wird, also prinzipiell die Werte aus der entsprechenden Codeliste Duty or tax or fee category code (Subset of UNCL5305) mit der Ausnahme der Kategorie "S", wo der Steuersatz als Suffix zugefügt wurde.

Wo auch immer Steuerinformationen benötigt werden, wurden Datengültigkeitsbereiche definiert, die auf diese Liste von Steuerkategorien zeigen. In LibreOffice lässt sich dies erreichen, indem man die entsprechenden Zellen auswählt, und dann den Bereich mit dem Menüeintrag Daten -> Güligkeit... auswählt. Klickt man danach in die Zelle, lassen sich die Werte aus einem Drop-Down auswählen.

Allein, S19 oder S7 sind keine gültigen Codes. Die Zahlen 19 und 7 wurden lediglich als Hinweis auf den Steuersatz angehängt. Der korrekte Code für beide lautet S. Im Tab "Tax" findet sich dieser Code in Spalte C. Wie kann dieser jetzt in die relevanten Zellen kopiert werden? Dies passiert mithilfe der Formel VLOOKUP (SVERWEIS in der deutschen Version, aber VLOOKUP funktioniert ebenfalls).

Beispiel:

=VLOOKUP(H23, Tax.$A$2:$C$12, 3, 0)

Diese Formel macht einen vertikalen Lookup oder senkrechten Verweis. Im Detail funktioniert das so:

  1. Funktion: VLOOKUP steht für "vertikalen Lookup" oder "senkrechten Verweis." Sie sucht nach einem Wert in der ersten Spalte eines Bereichs und liefert einen Wert in der selben Zeile in der angegebenen Spalte zurück.

  2. Lookup-Wert: $H23 - Dies ist der Wert, nach dem gesucht wird. Das Dollarzeichen vor dem Spaltenbuchstaben ($H) zeigt an, dass die Spaltenreferenz absolut ist und sich nicht ändert, wenn die Formel in eine andere Zelle kopiert wird. Die Zeilennummer (23) dagegen ist relativ, so dass die Referenz sich zu $H24, $H25, etc. ändert, wenn die Formel nach unten kopiert wird.

  3. Tabellen-Array: Tax.$A$2:$C$12 - Dies gibt den Bereich an, in dem die VLOOKUP-Funktion sucht. Die Verwendung von $A$2:$C$12 bedeutet, dass sowohl die Spalten (A und C) als auch die Zeilennummern (2 und 12) absolute Referenzen sind. Wenn die Formel kopiert wird, bezieht sich diese Referenz auf exakt diese Zellen, was wichtig ist, damit die Formel immer auf die gewünschten Daten zeigt.

  4. Spalten-Index: 3 - Damit wird angegeben, dass VLOOKUP einen Wert aus der dritten Spalte des definierten Bereichs zurückgibt. In diesem Fall wird sie einen Wert aus Spalte C zurückgeben, weil der Bereich mit Spalte A beginnt. Die Spalte C des Tabs Tax enthält den korrekten Code aus der Code-Liste.

  5. Bereichs-Lookup: 0 - Dies gibt an, dass ein exakter Treffer gewünscht ist. Der Wert 0 (oder FALSE in einigen anderen Excel-Kontexten) weist die Funktion an, einen exakten Treffer für den Wert in $H23 zu finden.

Die Übertragung der Steuerrate in Prozent passiert auf exakt die gleiche Weise, ebenso wie die Zuordnung der Maßeinheiten zu Unit-Codes.

All diese Felder kann man natürlich auch von Hand befüllen, was aber fehlerträchtig ist. Es lohnt sich also, sich zu bemühen, die Formel stattdessen zu verstehen.

Der Rest der Tabelle sollte selbsterklärend sein, weil nur Standard-Features benutzt werden. Bis man mit der Datei vertraut ist, sollte man allerdings vor der Bearbeitung immer auf den aktuellen Wert der Zelle schauen, damit nicht unbeabsichtigt Formeln überschrieben werden.

Mapping-Definition

Zum Beispiel-Rechnungstemplate gibt es eine begleitende Mapping Datei im YAML-Format, contrib/mappings/default-invoice.yaml. Da die YAML-Syntax eine Obermenge der JSON-Syntax ist, kann man auf Wunsch auf JSON verwenden. YAML hat allerdings den Vorzug, dass es Kommentare in der Datei erlaubt.

Sinn der Mapping-Datei ist die Übertragung der tabellarischen Daten aus der Tabellenkalkulation in die Baumstruktur der Rechnungsdaten im internen Format.

Allgemeiner Aufbau

meta:
  sectionColumn:
    Invoice: L
ubl:Invoice:
  \# ... omitted for brevity

Der erste Abschnitt der Datei ist der meta-Abschnitt, der zusätzliche Informationen für die Zuordnung enthält. Im Moment gibt es nur die Eigenschaft sectionColumn für Section-Spalten, deren Sinn weiter unten erklärt wird.

Der zweite Abschnitt ist ubl:Invoice. Das JSON-Schema für diesen Abschnitt ist identisch zum Rechnungsdaten-Schema, dem "internen Format", mit dem einzigen Unterschied, dass die Werte der Blattknoten des Baums nicht (notwendigerweise) echte Werte sind, sondern in der Regel Referenzen auf Tabellenzellen, die dem Service mitteilen, wo eine bestimmte Information gefunden werden kann.

Zellenreferenzen

Referenzen auf Zellen sehen exakt so aus, wie man es von einer Tabellenkalkulation gewohnt ist. Sie beginnen immer mit einem Gleichheitszeichen, gefolgt vom Namen der Zelle.

Beispiel:

ubl:Invoice:
  \# ...
  cbc:ID: =R2
  cbc:IssueDate: =Invoice.I6
  \# ...

Die erste Referenz =R2 bedeutet, dass sich der Wert von cbc:ID in Spalte R, Zeile 2 der Tabelle befindet.

Die zweite Referenz =Invoice.I6 verwendet auch den Namen des Tabs. Wird der Name des Tabs weggelassen, wird die Zelle im ersten Tab der Datei gesucht. Hält man alle Rechnungsdaten in einem einzigen Tab, kann man dieses Feature also komplett vergessen, und immer einfach den Zellennamen vewenden.

Konstante Werte

Alles, was nicht mit einem Gleichheitszeichen beginnt, ist ein konstanter Wert. Das ist praktisch, um Dinge hartzukodieren:

ubl:Invoice:
  \# ...
  cbc:Name: Acme Ltd.
  cbc:DocumentTypeCode: "380"
  \# ...

Der cbc:Name im obigen Beispiel wird unverändert in die Rechnungsdaten kopiert.

Es ist zu beachten, dass nur Strings, also Zeichenketten als konstante Werte erlaubt sind. Zahlen müssen deshalb in Anführungszeichen gesetzt werden, so wie bei cbc:DocumentTypeCode oben.

Will man einen Konstante verwenden, die mit einem Gleichheitszeichen beginnt, muss man ein (gerades) einfaches Anführungszeichen ' voranstellen, dass bei der Übertragung entfernt wird. Will man eine Konstante, die mit einem solchen einfachen Anführungszeichen beginnt, verwenden, muss man am Anfang zwei Anführungszeichen verwenden, zum Beispiel ''Wert in Anführungszeichen'.

Es ist wichtig, dass die geraden Anführungszeichen nicht mit Backticks \`` oder anderen Anführungszeichen wieoder’` vertauscht werden, besonders beim Kopieren und Einfügen aus anderer Software.

Sections

Außer, wenn die Rechnung lediglich eine Position hat, werden einige Zellen keine fixe Position haben. Zum Beispiel werden alle Summen und Zwischensummen der Rechnung nach unten verschoben, wenn weitere Positionen zugefügt werden. Dies wird durch das Konzept von Sections in der Tabelle unterstützt.

Eine Spalte in jedem Tab muss für die Namen der Sections reserviert werden. Das wird durch das Objekt /meta/sectionColumns im Mapping angegeben, dessen Schlüssel die Namen der Tabs sind. Die entsprechenden Werte sind die Namen der Section-Spalten.

Groß- und Kleinschreibung bei Section-Namen muss beachtet werden!

Die Section, mit der Summe aller Rechnungspositionen kann zum Beispiel mit Subtotal. Dieser Name wird in die Zeile, in der die Section anfängt in die Section-Spalte eingetragen.

Defininition einer Section

Für Zellenreferenzen muss jetzt eine leicht veränderte Syntax verwendet werden. Section-Namen beginnen immer mit einem Doppelpunkt : (weil Doppelpunkte nicht als Teil eines Tabnamens erlaubt sind):

cbc:Amount: =:Subtotal.J1

Diese Referenz wird als erste Zeile in der Section Subtotal interpretiert. Sind die Daten auf mehrere Tabs verteilt, kann man auch =Invoice:Subtotal.J1 verwenden, um anzugeben, dass sich die Zelle im Tab Invoice befindet.

Zugeordnete Sections

Im Allgemeinen dürfen Section-Namen nur genau einmal pro Tab verwendet werden. Eine Ausnahme stellen zugeordnete Sections dar.

Einige Teile der Rechnung können wiederholt auftreten. Ein typisches Beispiel dafür sind die Rechnungspositionen, weil jede Rechnung eine Zeile pro in Rechnung gestellte Leistung enthält. Ein Mapping dafür kann ungefähr so aussehen:

ubl:Invoice:
  \# ...
  cac:InvoiceLine:
    section: :InvoiceLine
    cbc:ID: =:InvoiceLine.A1
    cbc:InvoicedQuantity: =:InvoiceLine.E1
    cbc:InvoicedQuantity@unitCode: =:InvoiceLine.M1
    cbc:LineExtensionAmount: =:InvoiceLine.J1
    cbc:LineExtensionAmount@currencyID: EUR

Das Element /ubl:Invoice/cac:InvoiceLine wird einmal pro Rechnungsposition erzeugt. Es handelt sich also um eine Liste bzw. um ein Array. Für solche Arrays muss eine spezielle Eigenschaft section mit dem Namen der Section, diesem Array zugeordnet ist, definiert werden. Ein Tabname kann optional mit section: Invoice:InvoiceLine zugefügt werden. Es muss sichergestellt sein, dass das Tab einen entsprechenden Eintrag für die Section-Spate hat.

Referenzen mit zugeordneten Sections funktionieren mehr oder weniger wie mit normalen Sections, nur dass die Zeilennummer nicht relativ zur ersten Section, sondern relativ zur aktuellen Section interpretiert wird.

Meistens werden Referenzen innerhalb solcher Listen immer die gleiche Section referenzieren. Man kann allerdings auch eine andere Section referenzieren, allerdings nur, wenn es keine zugeordnete Section ist. Genauso kann man natürlich auch Informationen aus dem Rechnungskopf (normalerweise kein Teil einer Section) oder Konstanten referenzieren.

Verschachtelte zugeordnete Sections

Sections können beliebig tief verschachtelt werden. Ein wichtiges Beispiel dafür sind Zu- und Abschläge auf Positionsebene. Genau wie die Positionszeile selber, kann es eine beliebige Anzahl an Zu- und Abschlägen für jede Position geben.

Es muss lediglich darauf geachtet werden, dass für jede Ebene ein eigener Section-Name verwendet wird. Zu- und Abschläge können auf Dokumentenebene, auf Positionsebene und auf Preisebene auftauchen. Verwendet man alle drei Type, müssen auch unterschiedliche Section-Namen für jeden Typ verwendet werden.

Beispiel:

Definition verschachtelter Sections

In diesem Beispiel wurde der Name ACInvoiceLine für Zu- und Abschläge auf Positionseben gewählt. Zur ersten Zeile gibt es zwei Zu- oder Abschläge mit einem Beträgen von 33,50 € und 12,28 €. Zur zweiten Zeile gibt es einen Zu- oder Abschlag in Höhe von 23,04 €. Zu den restlichen Zeilen gibt es keine Zu- oder Abschläge. Der relevante Teil des Mappings hierfür sieht folgendermaßen aus:

ubl:Invoice:
  \# ...
  cac:InvoiceLine:
    section: :InvoiceLine
    cbc:LineExtensionAmount: =:InvoiceLine.J1
    cac:AllowanceCharge:
      section: :ACInvoiceLine
      cbc:Amount: :ACInvoiceLine.J1

Sowohl die Zeilensumme cbc:LineExtensionAmount als auch die Beträge der Zu- und Abschläge cbc:AllowancheCharge/cbc:Amount stehen in Spalte J, aber die Zeilensumme steht in der ersten Zeile der jeweiligen Section für die Position, und der Betrag des Zu- oder Abschläge in der ersten Zeile der Section für die Zu- und Abwschläge, die sich innerhalb der Section für die Position befindet.

Attribute

Einige Elemente haben (XML)-Attribute. So haben zum Beispiel die meisten Geldbeträge ein verpflichtendes Attribut currencyID. Für die Definition einer Referenz für solche Attribut wird der Attributname mit einem führenden Klammeraffen @ an den Elementnamen angehangen:

cbc:Amount: :ACInvoiceLine.J1
  cbc:Amount@currencyID: EUR

Alternative Mappings

Das Mapping der Daten aus der Tabellenkalkulation auf die Rechnungsdaten ist zugegebenermaßen etwas kompliziert.

Ist man der Auffassung, dass es zu kompliziert ist, oder aber die Features für den eigenen Anwendungsfall nicht ausreichen, steht es es einem frei, eine eigene Version zu bauen. So lange es gelingt, Rechnungsdaten im internen Format zu produzieren, kann der Mappingschritt auch entfallen.

Häufige Mapping-Probleme

Obwohl das Format der Rechnungsdaten gut dokumentiert ist, werfen einige Felder Fragen auf.

Dokumententyp-Code

Der /ubl:Invoice/cbc:DocumentTypeCode muss der Liste Invoice type code (UNCL1001 subset) entnommen werden. Der meistverwendete Code ist 380 für Handelsrechnungen.

Für Gutschriften (selbst-ausgestellte Rechnungen) schlägt die Dokumentation von Factur-X/ZUGFeRD den Typ-Code 389 vor. Dieser Code ist allerdings in der PEPPOL-Liste nicht enthalten.

Das gleiche Problem existiert für Rechnungskorrekturen, für die in der Dokumentation von Factur-X der Code 384 vorgeschlagen wird, der ebenfalls in der PEPPOL-Liste fehlt.

Wer etwas zur Aufklärung dieses Sachverhalts beitragen kann, wird gebeten, einen Kommentar zu hinterlassen.

Käuferreferenz (Buyer Reference)

Das Feld /ubl:Invoice/cbc:BuyerReference ist eigentlich ein optionales Feld. Die Business-Regel PEPPOL-EN16931-R003 fordert allerdings, dass entweder eine Käuferreferenz oder aber eine Bestellreferenz /ubl:Invoice/cac:OrderReference/cbc:ID angegeben werden müssen.

Die Bestellreferenz ist normalerweise eine vom Kunden verwendete Bestellnummer für bestellten Leistungen.

Die Käuferreferenz hat dagegen eine spezielle Semantik in Rechnungen für die öffentliche Hand. Der deutsche Standard XRECHNUNG schreibt vor, dass dieses Feld vorhanden und mit der sogenannten Leitweg-ID gefüllt sein muss. Diese Leitweg-ID findet man oft im Internet. Ansonsten fragt man einfach den Kunden, der die eigene ID kennen wird.

Endpoint-IDs

Sowohl Lieferant als auch Kunde müssen durch Endpunkt-IDs identifiziert werden. Die entsprechenden Felder sind /ubl:Invoice/cac:AccountingSupplierParty/cac:Party/cbc:EndpointID (BT-34) und /ubl:Invoice/cac:AccountingCustomerParty/cac:Party/cbc:EndpointID (BT-49). Eine sichere Wahl ist die jeweilige USt-ID mit der @schemeID 9930 für deutsche USt-IDs. Für andere Länder kann die @schemeID der Code-Liste CEF Electronic Address Scheme (EAS). entnommen werden. Andere populäre Optionen sind die bereits erwähnte Leitweg-ID (@schemeID 0204) oder der EAN Location Code (@schemeID 0088), auch bekannt als Global Location Number GLN.

Die Dokumentation zu Factur-X/ZUGFeRD erlaubt hier auch Mail-Adressen mit der @schemeID EM. Hier gibt es leider wieder eine Diskrepanz zwischen dieser Dokumentation und der Dokumentation von PEPPOL, denn EM fehlt in der PEPPOL-Liste. Wer dazu etwas weiß, wird ebenfalls gebeten, einen Kommentar zu hinterlassen.

Für größere Kunden ist es durchaus üblich, dass sie genaue Vorgaben machen, welche Endpunkt-IDs mit welcher @schemeID zu verwenden sind. Dies wird normalerweise vom Kunden mitgeteilt. Ansonsten ist die USt-ID aber ein guter Standardwert.

IBAN und BIC

Sowohl IBAN als auch BIC/SWIFT-Code sind eigentlich optional. Viele Kunden werden aber auf der Angabe bestehen, damit die Bezahlung organisiert werden kann. Auch Kontoinhaber und Zahlungstyp werden benötigt. Die entsprechenden Feler sind ubl:Invoice/cac:PaymentMeans/cac:CardAccount/cbc:HolderName für den Namen des Kontoinhabers, ubl:Invoice/cac:PaymentMeans/cbc:PaymentMeansCode für den Zahlungstyp-Code (eine Banküberweisung hat den Code 30), ubl:Invoice/cac:PaymentMeans/cac:PayeeFinancialAccount/cbc:ID für die IBAN, und ` ubl:Invoice/cac:PaymentMeans/cac:PayeeFinancialAccount/cac:FinancialInstitutionBranch/cbc:ID für den BIC/SWIFT-Code.

Zu- und Abschläge

Zu- und Abschläge gibt es auf Dokumentenebene, auf Positionsebene und auf Preisebene. Sie sind jeweils in einer Gruppe cac:AllowanceCharge definiert, zum Beispiel /ubl:Invoice/cac:AllowanceCharge für Zu- und Abschläge. Entgegen der Gepflogenheiten auf Papierrechnungen, haben jedoch auch Abschläge einen positiven Betrag. Ob es sich um einen Zu- oder um einen Abschlag handelt, ergibt sich jeweils aus dem Wert des Feldes cbc:ChargeIndicator innerhalb der Gruppe, z. B. /ubl:Invoice/cac:AllowanceCharge/cbc:ChargeIndicator, dass true für Zuschläge und false für Abschläge enthalten muss.

Damit Abschläge in der Print-Version mit negativem Vorzeichen dargestellt werden, muss in der Tabellenkalkulation etwas getrickst werden. Im Beispiel-Template wird das demnächst gezeigt werden.

Customization-ID und Profile-ID

Die Felder /ubl:Invoice/cbc:CustomizationID und /ubl:Invoice/cbc:ProfileID sind im Standard Pflichtfelder. In Mappings oder Rechnungs-Eingabe-Daten für e-invoice-eu sind sie dagegen optional, weil ihre Werte sich aus dem gewählten Rechnungsformat ergeben. Lässt man die Werte weg, was die empfohlene Vorgehensweise ist, werden die jeweils aktuellen Werte verwendet. Wenn man weiß, was man tut, kann man sich allerdings darüber hinwegsetzen und seine eigenen Werte für die Customization- und Profile-ID übergeben.

Zusammenfassung

Die hier angegebenen Informationen sollten ausreichen, um die erste elektronische Rechnung zu erzeugen. Am Projekt e-invoice-eu wird noch intensiv entwickelt. Informationen über neue Features gibt es auf der GitHub-Seite von e-invoice-eu und hier.

Leider bin ich nicht im Stande, feste Daten für die Einführung bestimmter Features zu nennen. Anfragen für kommerziellen Support und Beratungsleistungen rund um elektronische Rechnungen können jedoch an die Mailadresse info@cantanea.com gestellt werden.

Leave a comment

JSON.stringify() missbrauchen

Tücken von JavaScript `for...in`-Schleifen

Elektronische Rechnungen mit freier und quelloffener Software erzeugen

Dynamische Angular-Konfiguration

ImageMagick für Perl kompilieren

Angular Tour of heroes als Standalone-App

Diese Website verwendet Cookies und ähnliche Technologien, um gewisse Funktionalität zu ermöglichen, die Benutzbarkeit zu erhöhen und Inhalt entsprechend ihren Interessen zu liefern. Über die technisch notwendigen Cookies hinaus können abhängig von ihrem Zweck Analyse- und Marketing-Cookies zum Einsatz kommen. Sie können ihre Zustimmung zu den vorher erwähnten Cookies erklären, indem sie auf "Zustimmen und weiter" klicken. Hier können sie Detaileinstellungen vornehmen oder ihre Zustimmung - auch teilweise - mit Wirkung für die Zukunft zurücknehmen. Für weitere Informationen lesen sie bitte unsere Datenschutzerklärung.