Die Beispieldateien im Verzeichnis
contrib
funktionieren alle. Es ist sehr empfehlenswert, diese als Ausgangspunkte für eigene Rechnungstemplates und Mappings zu benutzen, bis man mit den verwendeten Konzepten genügend vertraut ist. Das ist einfacher, als es auf den ersten Blick scheint, wenn man erst einmal den Dreh heraus hat.
Wer die Software benutzt, um Rechnungen direkt aus JSON-Daten zu erzeugen, kann diesen Abschnitt einfach überspringen. Mappings werden nur benötigt, um E-Rechnungen aus Tabellendaten zu generieren.
meta
-Objekt
ubl:Invoice
Das verwendet Format ist YAML. YAML ist syntaktisch eine Obermenge von JSON, so dass man auch JSON verwenden kann.
Die allgemeine Struktur eines Mappings ist ein Objekt (auch Hash, Dictionary oder assoziatives Array) mit den benannten Eigenschaften meta
und ubl:Invoice
.
meta
-ObjektDie Schlüssel des meta
-Objekts enthalten Hilfen für das eigentliche Mapping.
meta.sectionColumn
(Sektionsspalte)Die Schlüssel dieses Objektes sind die Namen von Tabs in den Rechnungsdateien. Der Wert sind jewals Spaltennamen, die auf Folgen von Großbuchstaben A
bis Z
bestehen.
Die Bedeutung dieses Schlüssels wird später klarer.
meta.empty
Zellen, die leer sind, werden nicht gemappt. Das Zielelement wird für sie einfach nicht generiert.
Leider können einige leere Zellen nicht als solche erkannt werden. Findet in LibreOffice eine Formel VLOOKUP
eine leere Textzelle, wird eine Zahlenzelle mit einem Zeilenumbruch als Wert generiert. Vermutlich existieren noch mehr solche Bugs oder Eigentümlichkeiten.
Wenn man in einen solchen Fall hineingerät (uns ist das mit dem Beispieltemplate, das mit E-Invoice-EU ausgeliefert wird passiert), kann man ein Array meta.empty
definieren dessen Element String sind, die als leere Werte betrachtet werden sollen. Man kann dann einfach einen dieser String in die Zelle schreiben, die das Problem verursacht. Offensichlicherweise sollte man natürlich Strings verwenden, die nicht in einer regulären Rechnung vorkommen.
ubl:Invoice
Sagten wir, dass ein Mapping Tabellenzellen auf Rechnungsdaten abbildet? Es ist eher umgekehrt. Das Mapping bildet eine Referenz in die Rechnungsdaten auf eine Referenz zu einer Zelle, in der der Wert gefunden werden kann, ab. Statt einer Referenz auf eine Zelle kann auch ein hartkodierter Wert verwendet werden.
Die Struktur entspricht exakt der Struktur der Rechnungsdaten, siehe Internes Format. Nur sind alle Werte entweder Referenzen auf eine Tabellenzelle oder ein hartkodierter Wert.
Ein weiterer Unterschied besteht darin, dass Daten innerhalb eines Arrays direkt auf das erste Element des Arrays abgebildet werden. Auch das wird später noch klarer.
Sehen wir uns einen Auszug eines Mappings an:
meta:
sectionColumn:
Invoice: K
emtpy: ['[:empty:]', '%no-no-nothing%'],
ubl:Invoice:
cbc:ID: =Invoice.D7
cbc:IssueDate: =F7
cac:InvoiceLine:
section: :Line
cbc:ID: =Invoice:Line.A1
cbc:Note: =Invoice.M3
cac:Item:
cbcName: =Line.B1
cac:Price:
cbc:PriceAmount: =Invoice:Line.F1
cbc:PriceAmount@currencyCode: EUR
Zeile 3: Die Sektionsspalte im Tab Invoice
ist Spalte K
.
Zeile 4: Zellen mit den Werten [:empty:]
und %no-no-nothing%
sollen als leer betrachtet werden.
Zeile 6: Die Rechnungsnummer findet sich in Zelle D7 des Tabs Invoice
.
Zeile 7: Das Ausgabedatum findet sich in Zelle F7. Und in welchem Tab? Im ersten Tab, weil kein Tabname angegeben wurde.
Zeile 8: Die Rechnungspositionen sind eigentlich ein Array, aber …
Zeile 9: … der Beginn jeder Rechnungsposition ist mit dem String „Line“ in der Sektionsspalte (meta.sectionColumn
, siehe oben!) dieses Tabs markiert.
Zeile 10: Alles, was in diesem Abschnitt folgt, kann jetzt relativ zur entsprechenden Zeile der entsprechenden Rechnungsposition angegeben werden. Die ID dieser Rechnungsposition findet sich in Spalte A der ersten Zeile der Sektion, die mit „Line“ markiert ist.
Zeile 11: Wird die Sektion dagegen nicht angegeben, ist die Zeilennummer (3) relativ zum gesamten Tab zu interpretieren.
Zeile 13: Wie weiter oben erwähnt, kann der Name des Tabs weggelassen werden, wenn es sich um das erste Tab in der Datei handelt. In diesem Fall haben wir eine Sektion „Line“ aber kein Tab. Die Zellreferenz =Line.B1
bezieht sich daher auf Spalte B
der ersten Zeile der Sektion Line
im ersten Tab der Datei.
Zeile 15: Wird ein String ohne führendes Gleichheitszeichen (=
) benutzt, wird er als hartkodierter Wert interpretiert. Im vorliegenden Fall ist das Attribut currencyCode
von cbc:PriceAmount
immer „EUR“ und wird nicht aus der Tabelle gelesen.
Beschreiben wir das auf etwas formalere Weise:
Alles, was nicht mit einem Gleichheitszeichen (=
) beginnt, ist ein hartkodierter Wert. Es muss aber ein String sein. Das bedeutet, dass Zahlen in Anführungszeichen gesetzt werden müssen:
ubl:Invoice:
cbc:InvoiceTypeCode: '380'
Weil YAML so funktioniert, wie es funktioniert, müssen auch die Strings „null“, „true“, „false“, „yes“ und „no“ in Anführungszeichen gesetzt werden. Im Besonderen muss der Wert für cbc:ChargeIndicator
innerhalb von cac:AllowanceCharge
-Sektionen einer der Strings „true“ oderr „false“ sein, und nicht ein boolescher Wert true
or false
.
Wird kein Tabname angegeben, so wird das erste Tab in der Datei ausgewählt. Enthält ein Tabname einen Doppelpunkt (:
) oder Punkt (.
), muss er in einfache Anführungszeichen eingeschlossen werden:
cbc:DueDate: ='Fancy.SheetName'.D3
Allerdings erlauben die meisten Tabellenkalkulationen ohnehin keine Doppelpunkte in den Namen von Tabs.
Sektionsreferenzen beginnen immer mit einem Doppelpunkt (:
). Ein Sektionsname darf keinen Doppelpunkt (:
) oder Punkt (.
) enthalten. Escapen oder Quoten ist nicht möglich. Es müssen Namen ohne diese Zeichen gewählt werden.
Eine Sektionsreferenz ohne führende Tab-Referenz muss mit einem Doppelpunkt :
beginnen. Das ist notwendig, damit sie von Tab-Referenzen unterschieden werden kann.
Beispiel:
ubl:Invoice:
cac:InvoiceLine:
section: :Line
cbc:ID: =:Line.A1
cbc:Note: =SomeSheet.Q23
Sehen wir uns die Referenzen für cbc:ID
und cbc:Note
genauer an. Die ID referenziert eine Zeile in der Sektion Line
des ersten Tabs. Die Referenz für cbc:Note
bezieht sich auf die Zelle Q23
im Tab SomeSheet
.
Um die Dinge noch etwas komplizierter zu machen, ist auch möglich, diese „Schleifen“ in Rechnungen zu verschachteln.
Es gibt zum Beispiel für Zu- und Abschläge das Array-Element ubl:Invoice/cac:AllowanceCharge
auf Dokumentenebe. Daneben gibt es auch ein entsprechendes Element
ubl:Invoice/cac:InvoiceLine/cac:AllowanceCharge
auf Positionsebene, und sogar noch ein weiteres auf Preisebene. Wie sollte das in einer Tabelle strukturiert werden? Nehmen wir an, dass die Zu- oder Abschläge aus Spalte H
kommen, der Betrag der Rechnungsposition aus Spalte I
, und Sektionsspalte ist K
. Die Tabelle sollte dann so aussehen:
| Zeile | … | H | I | J | K | | — | — | ——— | —— | — | ————— | | 20 | | 120.00 | | | Line | | 21 | | | 3.05 | | ACLine | | 22 | | 75.50 | | | Line | | 23 | | 180.00 | | | Line | | 24 | | | 1.23 | | ACLine | | 25 | | | | | | | 26 | | | 7.50 | | ACLine | | 27 | | | | | Footer |
Diese Rechnung hat drei Rechnungspositionen, die in den Zeilen 20, 22 und 23 beginnen.
Die erste Position hat eine Positionssumme von 120,00 und einen Zu- oder Abschlag von 3,05. Es müssen immer positive Werte verwendet werden. Das Vorzeichen ergibt sich implizit aus dem Element
cbc:ChargeIndicator
.
Die zweite Position hat eine Positionssumme von 75,50 und keine Zu- oder Abschläge. Die dritte Position hat eine Summe von 180,00 und zwei Zu- oder Abschläge in Höhe von 1,23 und 7,50.
Wo immer eine Tabellenzeile mit „Line“ markiert ist, fängt eine Sektion für eine Rechnungsposition
(cac:InvoiceLine
) an. Sie endet vor der nächsten Zeile, die mit „Line“ markiert ist oder am Ende der Tabelle. Auf dieser Ebene werden nur Zeilen in diesem Bereich in Betracht gezogen, und sie können sogar verschachtelt sein. So ist zum Beispiel Zeile 25 Teil der ersten Sektion ACLine
innerhalb der zweiten Sektion Line
.
Ein kleiner Stolperstein kann die letzte Line
-Sektion sein, die in Tabellenzeile 23 anfängt. Weil keine weitere Sektionsmarkierung folgt, erstreckt sich diese Line
-Sektion bis zur letzten Zeile der Tabelle. Würde man alle Sektionen für Zu- und Abschläge auf allen Ebenen gleich benennen, zum Beispiel mit ZA
, würden sie alle von der letzten Line
-Sektion konsumiert. Dementsprechend wäre es nicht möglich, Zu- oder Abschläge auf Dokumentenebene, nach der letzten Line
-Sektion anzugeben.
Wir nehmen mit: Sektionsnamen auf unterschiedlichen Ebenen sollten unterschiedlich sein, selbst, wenn sie sich auf den gleichen Sachverhalt beziehen.
Referenzen auf Tabellenzellen bestehen aus einem oder mehreren Großbuchstaben A-Z
, gefolgt von einer vorzeichenlosen Dezimalziffer 1-9
. Das kennen wir von unserer Tabellenkalkulation.
Wie bitte? Formeln werden in Mappings nicht unterstützt. Sie können aber in der Tabelle verwendet werden, weil der Parser das Resultat der Berechnung verwendet und nicht die Formel selbst.
Bei einigen Elementen handelt es sich um Arrays, obwohl man das nicht erwarten würde. Zum Beispiel ist die Steuersummierung
/ubl:Invoice/cac:TaxTotal
ein Array mit ein bis zwei Elementen. Allerdings wird man hier fast immer nur ein Element verwenden. In diesem Fall kann man sich die Sektion schenken inklusive des Schlüssel section
schenken, weil es keinen Bedarf gibt. Es ist gut möglich, dass man dieses Feature benutzt, ohne es zu merken.
Das Verzeichnis
contrib/templates
enthält Beispiele für Tabellendateien. Das Verzeichnis
contrib/mappings
enthält die entsprechenden Mappings für diese Tabellen.
Ein Problem mit Code-Listen ist die Tatsache, dass die Werte oft nicht verständlich sind. Es liegt nicht unbedingt auf der Hand, dass der Einheiten-Code HUR
für Stunden steht.
Die Beispiel-Tamplates in contrib/templates
versuchen das etwas benutzerfreundlicher zu machen. Dazu werden Datenbereich (data ranges) und die Funktion VLOOKUP
verwendet. Die grundsätzliche Idee ist, dass Benutzerinnen und Benutzer einen menschlich lesbaren Wert aus einer Auswahlbox wählen, der dann mit Hilfe der Funktion VLOOKUP
aus einer anderswo definierten Tabelle in einen maschinen-lesbaren Code übersetzt wird.
Stattdessen kann man auch einfach zwei Zellen, die nichts miteinander zu tun haben, verwenden; eine für den menschenlesbaren Wert, der gedruckt wird, und eine für den mascheinenlesbaren Code. Das vereinfacht die Tabelle, macht den Prozess aber fehleranfälliger.