Mapping

Mapping-Definition bilden Daten aus Tabellenzellen auf Rechnungsdatenfelder ab.

Praktische Erwägungen

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.

Format

Das verwendet Format ist YAML. YAML ist syntaktisch eine Obermenge von JSON, so dass man auch JSON verwenden kann.

Allgemeine Struktur

Die allgemeine Struktur eines Mappings ist ein Objekt (auch Hash, Dictionary oder assoziatives Array) mit den benannten Eigenschaften meta und ubl:Invoice.

Das meta-Objekt

Die Schlüssel des meta-Objekts enthalten Hilfen für das eigentliche Mapping.

Das Objekt 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.

Das Array 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.

Das Objekt 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:

Hartkodierte Wwerte

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.

Tab-Referenzen

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.

Sektions-Referenzen

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.

Zellenreferenzen

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.

Formeln

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.

Überraschende Arrays

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.

Beispiele

Das Verzeichnis contrib/templates enthält Beispiele für Tabellendateien. Das Verzeichnis contrib/mappings enthält die entsprechenden Mappings für diese Tabellen.

Code-Listen

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.

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.