- Thema
- VB6
- SDK-Mitglieder
- FillZUGFeRD_Structure() GetItemTradeTax() Invoice_TradeItem InvoiceCurrencyCode Licensekey ReadTradeItemCSV() TradeItemsCount
- Business Terms
- BT-151 Code der Umsatzsteuerkategorie des in Rechnung BT-152 Umsatzsteuersatz für den in Rechnung gestellten Artikel BT-131 Nettobetrag der Rechnungsposition BT-5 Code für die Rechnungswährung BG-25 Rechnungsposition BG-30 Umsatzsteuerinformationen auf der Ebene der Rechnungsposition BT-126 Kennung der Rechnungsposition BT-129 In Rechnung gestellte Menge
- Codebeispiele
- VB6 / VBScript (COM), VB.NET
- Veröffentlicht
Wer eine ZUGFeRD- oder XRechnung in Microsoft Access, Excel oder einer anderen VBA-Umgebung einliest, will für jede Rechnungsposition meist dieselben vier Werte: Umsatzsteuersatz, Steuerkategorie, Nettobetrag und Währung. Der naheliegende Weg über Invoice_TradeItem(i).TradeSettlement funktioniert in VB.NET, scheitert aber unter VBA. Dieser Artikel zeigt, warum das so ist und mit welchen Aufrufen der Aloaha ZUGFeRD SDK Sie die Werte über COM zuverlässig bekommen.
Das Problem: verschachtelte .NET-Strukturen über COM
Ein typischer erster Versuch in VBA sieht so aus:
' funktioniert unter VBA NICHT
Kategorie = alo.Invoice_TradeItem(i).TradeSettlement.TradeTax.CategoryCode
Steuersatz = alo.Invoice_TradeItem(i).TradeSettlement.TradeTax.RateApplicablePercent
Betrag = alo.Invoice_TradeItem(i).TradeSettlement.Monetary_Summation.LineTotalAmount.Amount
Waehrung = alo.Invoice_TradeItem(i).TradeSettlement.Monetary_Summation.LineTotalAmount.CurrencyID
Invoice_TradeItem liefert zwar ein Objekt zurück, aber TradeSettlement und die Ebenen darunter sind .NET-Structures (Werttypen), keine Klassen. Die COM-Schnittstelle, über die VBA mit der SDK spricht, kann solche verschachtelten Werttypen nicht als Objekte durchreichen. Je nach Office-Version endet der Zugriff mit einem Laufzeitfehler, einem leeren Wert oder „Objekt unterstützt diese Eigenschaft oder Methode nicht“. In VB.NET und C# tritt das Problem nicht auf, dort ist der Zugriff über die Struktur völlig in Ordnung.
Für COM-Clients bietet die SDK deshalb flache Aufrufe, die nur Zeichenketten und Zahlen übergeben.
Die passenden Aufrufe für VBA
| Wert | EN-16931-Feld | Aufruf in VBA |
|---|---|---|
| Anzahl der Positionen | BG-25 (Rechnungsposition) | alo.TradeItemsCount |
| Umsatzsteuersatz der Position | BT-152 | alo.GetItemTradeTax i, Steuersatz, Kategorie |
| Steuerkategorie der Position (S, Z, E, AE, K, G, O, L, M) | BT-151 | alo.GetItemTradeTax i, Steuersatz, Kategorie |
| Nettobetrag der Position | BT-131 | Spalte totalamount aus alo.ReadTradeItemCSV(vbTab) |
| Währung | BT-5 | alo.InvoiceCurrencyCode |
Eine eigene Währung pro Position gibt es in der EN 16931 nicht: Alle Positionsbeträge stehen immer in der Rechnungswährung BT-5. InvoiceCurrencyCode reicht deshalb für jede Zeile.
Rechnung laden
FillZUGFeRD_Structure liest wahlweise eine XML-Datei (ZUGFeRD, Factur-X, XRechnung) oder ein ZUGFeRD-PDF; aus dem PDF holt die SDK die eingebettete XML selbst heraus.
Dim alo As Object
Set alo = CreateObject("Aloaha_ZUGFeRD.XML")
alo.Licensekey = "IHR-LIZENZSCHLUESSEL"
If Not alo.FillZUGFeRD_Structure("C:\Rechnungen\eingang.pdf", "IHR-LIZENZSCHLUESSEL") Then
MsgBox "Rechnung konnte nicht gelesen werden."
Exit Sub
End If
Steuersatz und Steuerkategorie je Position
GetItemTradeTax füllt die übergebenen Variablen per Referenz (ByRef). Wichtig: Die Variablen müssen als String deklariert sein, sonst kann VBA sie nicht als Referenz übergeben. Der Index ist 0-basiert – die erste Position hat den Index 0, die letzte TradeItemsCount - 1.
Dim i As Long
Dim Steuersatz As String, Kategorie As String
For i = 0 To alo.TradeItemsCount - 1
Steuersatz = "": Kategorie = ""
alo.GetItemTradeTax i, Steuersatz, Kategorie
Debug.Print "Position " & i + 1, Kategorie, Steuersatz
Next i
Optional liefert derselbe Aufruf auch Text und Code des Befreiungsgrunds (entsprechend BT-120 und BT-121 auf Rechnungsebene), falls die Position steuerbefreit ist:
Dim Grund As String, GrundCode As String
alo.GetItemTradeTax i, Steuersatz, Kategorie, Grund, GrundCode
Nettobetrag der Position über ReadTradeItemCSV
ReadTradeItemCSV gibt alle Positionen als Text zurück: in der ersten Zeile die Spaltennamen, danach eine Zeile pro Position, getrennt durch vbCrLf. Den Nettobetrag der Position (BT-131) finden Sie in der Spalte totalamount. Suchen Sie die Spalte über ihren Namen statt über eine feste Nummer – dann bleibt Ihr Code auch bei künftigen zusätzlichen Spalten richtig.
Dim Zeilen() As String, Kopf() As String, Felder() As String
Dim k As Long, colBetrag As Long
Dim Waehrung As String
Waehrung = alo.InvoiceCurrencyCode
Zeilen = Split(alo.ReadTradeItemCSV(vbTab), vbCrLf)
Kopf = Split(Zeilen(0), vbTab)
colBetrag = -1
For k = 0 To UBound(Kopf)
If Kopf(k) = "totalamount" Then colBetrag = k
Next k
For i = 1 To UBound(Zeilen)
If Len(Zeilen(i)) > 0 Then
Felder = Split(Zeilen(i), vbTab)
Debug.Print "Position " & i, Val(Felder(colBetrag)), Waehrung
End If
Next i
Drei Hinweise dazu:
- Tabulator als Trennzeichen. Standard ist das Komma. Weil Artikelbezeichnungen und Beschreibungen selbst Kommas enthalten können, ist
vbTabsicherer. - Dezimalpunkt. Beträge und Prozentsätze stehen so in der Liste, wie sie in der XML stehen, also mit Punkt (
119.00).Val()wertet den Punkt unabhängig von den Ländereinstellungen richtig aus;CDbl()oderCCur()würden auf einem deutschen System falsch rechnen. - Textspalten sind HTML-kodiert. Freitextfelder wie
productname,descriptionoderItemNoteenthalten Sonderzeichen in kodierter Form (&statt&). Für Beträge, Sätze und Codes spielt das keine Rolle.
Weitere Spalten der Positionsliste
Die Liste enthält deutlich mehr als nur den Betrag. Die wichtigsten Spalten:
| Spalte | Inhalt | Feld |
|---|---|---|
LineID | Positionsnummer | BT-126 |
AssignedID | Artikelnummer des Verkäufers | BT-155 |
productname | Artikelbezeichnung | BT-153 |
quantity, unit | Menge und Einheit | BT-129, BT-130 |
netprice | Nettopreis | BT-146 |
grossprice | Bruttopreis vor Preisnachlass | BT-148 |
totalamount | Nettobetrag der Position | BT-131 |
taxpercent | Umsatzsteuersatz | BT-152 |
taxcategorycode | Steuerkategorie | BT-151 |
DeliveryNote | Lieferscheinnummer der Position | – |
Steuersatz und Kategorie stehen also auch in der Liste. Wenn Sie ohnehin alle Positionsdaten in eine Access-Tabelle übernehmen, reicht ReadTradeItemCSV allein; GetItemTradeTax lohnt sich, wenn Sie gezielt nur die Steuerdaten einer Position brauchen oder den Befreiungsgrund benötigen.
Komplettbeispiel für Access
Das folgende Beispiel liest eine Rechnung ein und schreibt jede Position mit Betrag, Steuersatz, Kategorie und Währung in eine Tabelle tblPositionen.
Public Sub PositionenEinlesen(ByVal Pfad As String)
Dim alo As Object
Dim rs As DAO.Recordset
Dim Zeilen() As String, Kopf() As String, Felder() As String
Dim i As Long, k As Long
Dim colPos As Long, colBetrag As Long, colSatz As Long, colKat As Long
Dim Waehrung As String
Set alo = CreateObject("Aloaha_ZUGFeRD.XML")
alo.Licensekey = "IHR-LIZENZSCHLUESSEL"
If Not alo.FillZUGFeRD_Structure(Pfad, "IHR-LIZENZSCHLUESSEL") Then Exit Sub
If alo.TradeItemsCount = 0 Then Exit Sub
Waehrung = alo.InvoiceCurrencyCode
Zeilen = Split(alo.ReadTradeItemCSV(vbTab), vbCrLf)
Kopf = Split(Zeilen(0), vbTab)
For k = 0 To UBound(Kopf)
Select Case Kopf(k)
Case "LineID": colPos = k
Case "totalamount": colBetrag = k
Case "taxpercent": colSatz = k
Case "taxcategorycode": colKat = k
End Select
Next k
Set rs = CurrentDb.OpenRecordset("tblPositionen", dbOpenDynaset)
For i = 1 To UBound(Zeilen)
If Len(Zeilen(i)) > 0 Then
Felder = Split(Zeilen(i), vbTab)
rs.AddNew
rs!PositionNr = Felder(colPos)
rs!Nettobetrag = Val(Felder(colBetrag))
rs!Steuersatz = Val(Felder(colSatz))
rs!Steuerkategorie = Felder(colKat)
rs!Waehrung = Waehrung
rs.Update
End If
Next i
rs.Close
End Sub
Ersetzen Sie IHR-LIZENZSCHLUESSEL durch Ihren Lizenzschlüssel. Für einen Test genügt eine Testlizenz.
Und in VB.NET oder C#?
Dort können Sie weiterhin direkt auf die Struktur zugreifen, zum Beispiel alo.Invoice_TradeItem(i).TradeSettlement.TradeTax.CategoryCode. Die hier gezeigten Aufrufe funktionieren aber ebenso, und wer Code zwischen VBA und .NET teilt, fährt mit ihnen einheitlich.
Häufige Fragen
Warum kann ich in VBA nicht auf Invoice_TradeItem(i).TradeSettlement zugreifen?
TradeSettlement ist eine .NET-Structure, also ein Werttyp. Über COM lassen sich solche verschachtelten Werttypen nicht als Objekte an VBA weitergeben. Nutzen Sie stattdessen GetItemTradeTax und ReadTradeItemCSV.
Ist der Positionsindex 0- oder 1-basiert?
0-basiert. Die Schleife läuft von 0 bis TradeItemsCount - 1. In der Liste von ReadTradeItemCSV ist Zeile 0 die Kopfzeile, die erste Position steht also in Zeile 1.
Woher bekomme ich die Währung einer einzelnen Position?
Aus der Rechnungswährung BT-5 über InvoiceCurrencyCode. Die EN 16931 kennt keine abweichende Währung pro Position; alle Positionsbeträge stehen in der Rechnungswährung.
Warum rechnet CDbl mit den Beträgen falsch?
Die Beträge stehen mit Dezimalpunkt in der Rechnung. CDbl und CCur richten sich nach den Ländereinstellungen und lesen den Punkt auf deutschen Systemen als Tausendertrennzeichen. Val() wertet den Punkt immer als Dezimaltrennzeichen.
Kann ich statt einer XML auch ein ZUGFeRD-PDF einlesen?
Ja. FillZUGFeRD_Structure erkennt die Endung .pdf und liest die eingebettete XML aus dem PDF selbst aus.
Business Terms in diesem Artikel: BT-151 BT-152 BT-131 BT-5 BG-25 BG-30 BT-126 BT-129 BT-146