Skip to main content
Glama

# csv2zugferd Dokumentation

Website: zugferd-tools.de

csv2zugferd ist ein .NET-CLI-Tool, das Rechnungsdaten aus CSV-Dateien liest und daraus ZUGFeRD-konforme XML-Dateien oder PDF-Dateien mit eingebettetem ZUGFeRD-XML erzeugt. Die Struktur der CSV-Datei und das Mapping auf ZUGFeRD-Felder werden über eine YAML-Konfiguration gesteuert.

Demo-Paket

Das Demo-Paket ist der schnellste Einstieg. Es enthält eine fertige Windows-Exe, zwei Beispiel-Rechnungen und eine vollständige YAML-Konfiguration.

Download: GitHub Releases → Demo-ZIP

Datei

Beschreibung

csv2zugferd-win-x64.exe

Windows-64-bit-Exe, self-contained (kein .NET nötig)

config_demo.yml

YAML-Konfiguration für die Demo-CSV-Struktur

demo-2026-0001.csv

Beispiel-Rechnung 1 – Softwarelizenzen mit Rabatt

demo-2026-0001.pdf

PDF-Vorlage zu Rechnung 1

demo-2026-0002.csv

Beispiel-Rechnung 2 – Lizenzen, Service und Schulung

demo-2026-0002.pdf

PDF-Vorlage zu Rechnung 2

create-zugferd-demo.cmd

Startskript – per Doppelklick oder CMD ausführen

Schnellstart:

:: 1. ZIP entpacken, dann:
create-zugferd-demo.cmd
:: Ergebnis: output\DEMO-2026-0001.pdf und output\DEMO-2026-0002.pdf

Die erzeugten Dateien sind valide ZUGFeRD-2.3-Rechnungen (Profil Extended) und können mit dem Mustang-Validator geprüft werden.

Detaillierte Erklärung der config_demo.yml: zugferd-tools.de/docs/schnellstart/

Related MCP server: bort

Eingaben und Ausgaben

Typ

Format

Beschreibung

CSV-Datei

.csv

Rechnungsdaten mit Kopf-, Käufer-, Verkäufer-, Positions- und Summenfeldern

PDF-Vorlage

.pdf

Bestehende Rechnungs-PDF, in die das XML eingebettet wird

Konfiguration

.yml

YAML-Datei mit CSV-Einstellungen und Mapping-Regeln

XML-Ausgabe

.xml

ZUGFeRD XML nach konfiguriertem Profil

PDF-Ausgabe

.pdf

ZUGFeRD PDF mit eingebettetem XML

CLI-Nutzung

dotnet run --project .\csv2zugferd\csv2zugferd.csproj -- --csv .\test\0002486.csv --pdf .\test\2483.pdf --config .\test\config_datasrc.yml --output .\test\output

Option

Kurz

Pflicht

Beschreibung

--csv

-c

Ja

Pfad zur CSV-Datei

--pdf

-p

Ja

Pfad zur PDF-Vorlage

--config

-g

Ja

Pfad zur YAML-Konfiguration

--output

-o

Nein

Ausgabeverzeichnis, Standard ist das aktuelle Verzeichnis

--xml-only

Nein

Nur XML erzeugen, kein PDF

--verbose

-v

Nein

Debug-Ausgabe aktivieren

Die Ausgabedateien werden anhand der Rechnungsnummer benannt, zum Beispiel 0002483.xml und 0002483.pdf.

MCP-Server (Einrichtungsassistent)

Seit Version 1.3.0 stellt csv2zugferd mcp das Tool als MCP-Server (Model Context Protocol) über stdio bereit. Zweck ist die Einrichtung: Ein KI-Assistent in Claude Desktop, Claude Code oder VS Code liest den Kopf einer CSV-Datei, entwirft die YAML-Konfiguration, prüft sie im Probelauf gegen den echten Mapping-Code und speichert sie als Datei. Der Produktivlauf bleibt bei der CLI: lokal, deterministisch, ohne Modell und ohne Cloud-Upload durch csv2zugferd. Die gespeicherte Konfiguration gilt für alle weiteren Rechnungen derselben CSV-Struktur.

Vorbereitung: Für die Einrichtung mit einem KI-Assistenten anonymisierte Beispieldaten verwenden. Die Werkzeuge liefern Zellwerte nur auf Anforderung (sampleRows, includeLineItems, includeParties).

Start und Client-Konfiguration

Der Server wird vom MCP-Client gestartet und liest JSON-RPC von stdin; stdout trägt ausschließlich JSON-RPC, Warnungen gehen zusätzlich nach stderr. Die Logdatei zugferd-*.log liegt nicht im Arbeitsverzeichnis (das bestimmt der Client), sondern unter csv2zugferd\logs im lokalen Anwendungsdatenordner (Windows %LOCALAPPDATA%\csv2zugferd\logs, Linux und macOS ~/.local/share/csv2zugferd/logs); die Umgebungsvariable CSV2ZUGFERD_LOG_DIR setzt ein anderes Verzeichnis. --verbose schaltet die Logdatei auf Debug.

Zwei Wege zum Start: die Windows-Exe aus dem GitHub-Release (csv2zugferd.exe mcp, kein .NET nötig) oder das .NET-Tool-Paket csv2zugferd von NuGet, das MCP-Clients mit dnx csv2zugferd@1.3.1 mcp starten (benötigt das .NET-10-SDK; läuft unter Windows, Linux und macOS). Mit dem Release 1.3.1 erscheint der Server in der Official MCP Registry unter dem Namen de.zugferd-tools/csv2zugferd.

Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "csv2zugferd": {
      "command": "C:\\Tools\\csv2zugferd.exe",
      "args": ["mcp"]
    }
  }
}

Claude Desktop mit dem NuGet-Paket statt der Exe:

{
  "mcpServers": {
    "csv2zugferd": {
      "command": "dnx",
      "args": ["csv2zugferd@1.3.1", "mcp"]
    }
  }
}

Claude Code:

claude mcp add csv2zugferd -- C:\Tools\csv2zugferd.exe mcp
# oder mit dem NuGet-Paket:
claude mcp add csv2zugferd -- dnx csv2zugferd@1.3.1 mcp

VS Code (.vscode/mcp.json):

{
  "servers": {
    "csv2zugferd": {
      "type": "stdio",
      "command": "C:\\Tools\\csv2zugferd.exe",
      "args": ["mcp"]
    }
  }
}

Docker

Für Linux-Umgebungen und Kataloge, die den Server selbst bauen, liegt ein Dockerfile im Repository. Das Image ist framework-abhängig auf mcr.microsoft.com/dotnet/runtime:10.0-noble-chiseled-extra (rund 207 MB, ohne Shell) und läuft ohne Root-Rechte:

docker build -t csv2zugferd .
docker run -i --rm -v "/pfad/zu/rechnungen:/data" csv2zugferd

Der Container startet csv2zugferd mcp und spricht JSON-RPC über stdin und stdout. Die Werkzeuge sehen nur den eingehängten Ordner /data (Arbeitsverzeichnis im Container); Pfade in Werkzeugaufrufen lauten entsprechend /data/<datei>. Die Logdatei bleibt im Container (/tmp/csv2zugferd/logs). Dateien, die der Container unter /data anlegt (csv2zugferd_config_file_create, CLI-Ausgaben), gehören auf Linux-Hosts dem Benutzer mit der ID 1654 (app im Image). Mit Argumenten hinter dem Imagenamen läuft statt des Servers die CLI, zum Beispiel docker run --rm -v "/pfad:/data" csv2zugferd --csv /data/rechnung.csv --pdf /data/vorlage.pdf --config /data/config.yml --output /data/out.

Claude Desktop mit dem Container:

{
  "mcpServers": {
    "csv2zugferd": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "-v", "C:\\Rechnungen:/data", "csv2zugferd"]
    }
  }
}

Werkzeuge

Werkzeug

Zweck

Wirkung

csv2zugferd_csv_header_get

Spaltennamen und Zeilenzahl einer CSV lesen, optional bis zu 3 Beispielzeilen (sampleRows)

lesend

csv2zugferd_config_schema_get

Vollständige Konfigurationsreferenz: JSON-Schema aller YAML-Schlüssel mit Beschreibung, Typ, Default und zulässigen Werten plus Semantik je Schlüssel (Wirkung, Modusabhängigkeit, Auflösungsreihenfolge, Rückfall). section liefert einen Teilbaum, z. B. mapping.lineItems

lesend

csv2zugferd_config_get

Konfiguration (Pfad oder YAML-Text) strukturell prüfen: effektive Einstellungen, gemappte Felder mit Quellen, unbekannte Schlüssel, unzulässige Werte mit Rückfall, ignorierte oder fehlende Abschnitte; mit csvPath Spaltenabgleich (fehlende und ungenutzte Spalten)

lesend

csv2zugferd_invoice_get

Probelauf mit dem echten Mapping ohne Ausgabedatei: Rechnungsnummer, Typ, Summen, Steueraufschlüsselung, Warnungen mit Code und Pfad, XML-Größe; optional Positionen, Parteien und PDF-Probelauf in eine temporäre Datei (pdfTemplatePath)

lesend

csv2zugferd_config_file_create

Geprüfte YAML als Datei speichern (UTF-8 ohne BOM); vorhandene Datei nur mit overwrite: true; liefert den CLI-Befehl für den Produktivlauf

schreibend

Die Werkzeuge nutzen denselben Code wie die CLI. csv2zugferd_config_schema_get ersetzt das Lesen dieser README: Alle Konfigurationsmöglichkeiten sind allein über die Werkzeuge abrufbar und prüfbar.

Ressourcen (resources/list): csv2zugferd://docs/config-reference (diese README), csv2zugferd://examples/config-rows und csv2zugferd://examples/config-columns-suffix (Beispielkonfigurationen aus testdata/).

Ablauf der Inbetriebnahme

  1. CSV-Struktur erfassen: csv2zugferd_csv_header_get mit dem Pfad der (anonymisierten) CSV, bei Bedarf sampleRows: 2.

  2. Mapping erarbeiten: csv2zugferd_config_schema_get liefert Schlüssel, Werte und Wirkung; der Assistent entwirft die YAML.

  3. Prüfen und korrigieren: csv2zugferd_config_get mit configYaml und csvPath meldet Tippfehler (unknownKeys), unzulässige Werte (invalidValues) und fehlende Spalten (columnsMissing); csv2zugferd_invoice_get zeigt Summen, Steuergruppen und Warnungen des echten Mappings. Wiederholen, bis das Ergebnis stimmt.

  4. Speichern: csv2zugferd_config_file_create schreibt die YAML und gibt den CLI-Befehl zurück.

  5. Produktivlauf: Den Befehl mit PDF-Vorlage und Ausgabeverzeichnis ausführen, z. B. csv2zugferd --csv rechnung.csv --pdf vorlage.pdf --config config.yml --output out. Dafür ist weder ein Modell noch ein laufender MCP-Server nötig.

Antworten, Fehler und Grenzen

  • Erfolgreiche Antworten kommen als structuredContent und als inhaltsgleicher JSON-Text; jedes Werkzeug veröffentlicht ein outputSchema, gegen das die Antwort vor dem Senden geprüft wird.

  • Warnungen sind Objekte {code, path, message} mit stabilem englischem Code (z. B. invalid_date, config_field_ignored, invalid_iban_checksum), vollständigem Konfigurationspfad und deutschem Text.

  • Fachliche Ablehnungen sind Tool-Ergebnisse mit isError: true; die erste Textzeile ist der Code (z. B. csv_file_not_found, config_invalid_yaml, file_exists), _meta.errorCode trägt ihn zusätzlich. Unbekannte Werkzeuge und Schemaverletzungen sind JSON-RPC-Fehler -32602 mit Feldpfad.

  • Lesende Werkzeuge liefern keine Antwort über 900 000 Byte; darüber kommt response_too_large mit den Eingrenzungsargumenten des Werkzeugs (sampleRows: 0, includeLineItems: false, section).

YAML-Grundstruktur

csv:
  separator: ","
  encoding: "UTF-8"
  hasHeader: true
  decimalSeparator: "."
  dateFormat: "dd.MM.yyyy"

zugferd:
  version: "2.3"
  profile: "Extended"
  format: "CII"

mapping:
  invoice: {}
  buyer: {}
  seller: {}
  delivery: {}
  orderReference: {}
  lineItems: {}
  totals: {}
  paymentTerms: {}
  paymentMeans: {}

Jedes Mapping-Feld kann entweder einen CSV-Spaltennamen oder einen festen Wert verwenden:

invoiceNumber:
  column: "INVOICE_NO"

currency:
  value: "EUR"

value ist sinnvoll für Stammdaten oder Standards, die nicht in jeder CSV wiederholt werden sollen. column liest den Wert aus der CSV-Zeile.

Zusätzlich kann ein Mapping-Feld einen Fallback und Regex-Regeln definieren:

unitCode:
  default: "C62"
  rules:
    - when:
        column: "PRODUCT_CODE"
        regex: "^002"
      value: "ANN"
    - when:
        column: "PRODUCT_CODE"
        regex: "^003"
      value: "DAY"
    - when:
        column: "PRODUCT_NAME"
        regex: "(?i)vor-ort-schulung|schulung"
      value: "DAY"

Die Reihenfolge ist: value, column, rules, default. Im columns-Modus werden Regelspalten positionsbezogen aufgelöst, also z. B. PRODUCT_CODE_0, PRODUCT_CODE_1 usw.

Feste Verkäuferdaten

Verkäuferdaten können vollständig in der YAML-Konfiguration festgelegt werden. Das ist der empfohlene Weg, wenn alle Rechnungen vom gleichen Absender erzeugt werden.

seller:
  name:
    value: "Muster Lieferant GmbH"
  id:
    value: "1234567890"
  postalCode:
    value: "80333"
  city:
    value: "Muenchen"
  street:
    value: "Lieferantenstrasse 20"
  country:
    value: "DE"
  taxRegistrations:
    - value: "201/113/40209"
      scheme: "FC"
    - value: "DE123456789"
      scheme: "VA"
  electronicAddress:
    value: "DE123456789"
    scheme: "GermanyVatNumber"

id ist optional und trägt die Verkäuferkennung (BT-29), zum Beispiel die Lieferantennummer, unter der der Käufer den Verkäufer führt. Sie erscheint im XML als ram:ID der SellerTradeParty. Ohne id wird kein Element geschrieben.

Unterstützte Steuer-Schemata:

Scheme

Bedeutung

FC

Steuernummer

VA

Umsatzsteuer-ID

taxRegistrations unterstützt sowohl feste Werte per value als auch CSV-Werte per column.

Rechnungskopf

invoice:
  invoiceNumber:
    column: "INVOICE_NO"
  invoiceDate:
    column: "INVOICE_DUEDATE"
  typeCode:                     # Rechnungstyp (BT-3), ohne Angabe 380
    value: "380"
  currency:
    value: "EUR"
  orderReferenceId:
    column: "SALESORDER_ID"
  referenceOrderNo:
    column: "SALESORDER_ID"
  name:
    value: "WARENRECHNUNG"
  note:
    column: "INVOICE_NOTE"

Pflichtnah sind Rechnungsnummer, Rechnungsdatum und Währung. Wenn die CSV kein echtes Rechnungsdatum enthält, muss eine passende Ersatzspalte bewusst gewählt werden.

Rechnungstyp, Storno und Gutschrift

typeCode setzt den Rechnungstyp (BT-3, rsm:ExchangedDocument/ram:TypeCode). Ohne Angabe wird 380 geschrieben. Zulässig ist eine Zahl nach UNTDID 1001 oder der Name aus der ZUGFeRD-Bibliothek (Invoice, CreditNote, Correction, SelfBilledInvoice, …); ein unbekannter Wert führt zu einem Fehler.

Code

Bedeutung

Einsatz

380

Rechnung

Standard

381

Gutschrift (kaufmännisch), Stornorechnung

vollständiges Storno, Rückerstattung

384

Rechnungskorrektur

Teilkorrektur einer bestehenden Rechnung (Preis, Menge, Steuersatz)

389

Selbstfakturierung (Gutschriftsverfahren, § 14 Abs. 2 UStG)

der Leistungsempfänger rechnet ab; Aussteller und Verkäufer sind vertauscht

326, 386, 875–877

Teil-, Vorauszahlungs- und Bau-Abschlagsrechnungen

werden durchgereicht

Ein Storno ist fachlich keine Gutschrift im Sinne des Gutschriftsverfahrens. Wer „Gutschrift“ meint und selbst der Leistende ist, braucht 381 oder 384, nicht 389.

Rechnung und Storno lassen sich aus einer CSV-Spalte steuern:

invoice:
  typeCode:
    default: "380"
    rules:
      - when:
          column: "Belegart"
          regex: "(?i)^(gutschrift|storno)"
        value: "381"
      - when:
          column: "Belegart"
          regex: "(?i)^korrektur"
        value: "384"
  precedingInvoices:            # Vorgängerrechnung (BT-25/BT-26), eine oder mehrere
    - number:
        column: "Ursprungsrechnung"
      date:
        column: "Ursprungsdatum"

Liefert die CSV keine Belegart, kann die Regel auch auf das Vorzeichen einer Betragsspalte prüfen, zum Beispiel regex: "^-" auf INVOICE_TOTAL.

precedingInvoices erzeugt je Eintrag ein ram:InvoiceReferencedDocument mit Nummer und Datum der Ursprungsrechnung. Einträge ohne Nummer werden übersprungen; das Datum ist optional. Mehrere Einträge sind für Sammelgutschriften oder Schlussrechnungen nach mehreren Abschlägen gedacht. Das Element wird ab Profil BasicWL geschrieben, nicht in Minimum.

Vorzeichen der Beträge:

Typ

Beträge

Hinweis

381

positiv

Das Minus steckt im Code. Negative Beträge bei 381 werden von Validatoren und Empfängern abgelehnt.

380 mit negativen Beträgen

negativ

„Negativrechnung“, laut Factur-X zulässig; manche Empfänger buchen sie als normale Rechnung.

384

positiv oder negativ je Position

Teilkorrektur, Mengen dürfen negativ sein.

Das Tool schreibt die Beträge so, wie die CSV sie liefert. Empfohlen ist 381 mit positiven Beträgen und precedingInvoices.

Weitere Hinweise:

  • name sollte bei Storno und Korrektur auf STORNORECHNUNG bzw. RECHNUNGSKORREKTUR statt GUTSCHRIFT stehen. Das Element wird nur im Profil Extended geschrieben.

  • Ein Storno bekommt eine eigene Rechnungsnummer; das Original wird nie überschrieben (GoBD).

  • Der sichtbare PDF-Teil muss zum XML-Typ passen (kein PDF „Gutschrift“ zu einem XML mit 380).

  • Chorus Pro (Frankreich) akzeptiert nur 380.

Unvollständige Angaben erzeugen eine Warnung im Log, keinen Abbruch:

  • 381 oder 384 ohne precedingInvoices (bei XRechnung und 384: BR-DE-26)

  • Profil XRechnung mit einem Code außerhalb von 326, 380, 381, 384, 389, 875, 876, 877 (BR-DE-17)

  • Profil Minimum mit precedingInvoices

Eine lauffähige Beispielkonfiguration ist testdata/config_creditnote.yml mit testdata/test_creditnote.csv.

Käuferdaten

buyer:
  name:
    column: "ACCOUNT_NAME"
  id:
    column: "ACCOUNT_ID"
  postalCode:
    column: "INVOICE_BILL_CODE"
  city:
    column: "INVOICE_BILL_CITY"
  street:
    column: "INVOICE_BILL_STREET"
  country:
    value: "DE"
  contact:
    column: "CONTACT_LASTNAME"
  taxId:
    column: "BUYER_VAT_ID"
  taxScheme:
    value: "VA"
  electronicAddress:
    column: "BUYER_VAT_ID"
    scheme: "GermanyVatNumber"

id ist optional und trägt die Käuferkennung (BT-46), in der Regel die Kundennummer des Käufers beim Verkäufer. Sie erscheint im XML als ram:ID der BuyerTradeParty. Ohne id wird kein Element geschrieben. Wie alle Felder unterstützt id auch value, default und rules.

Für Länder sollte ein ISO-3166-Alpha-2-Code wie DE verwendet werden. Freitext wie Deutschland wird nicht zuverlässig als ZUGFeRD-Ländercode erkannt.

taxScheme akzeptiert FC (Steuernummer) oder VA (Umsatzsteuer-ID).

Unterstützte Werte für scheme bei electronicAddress:

Scheme

Bedeutung

GermanyVatNumber

Deutsche Umsatzsteuer-ID (EAS 9930)

LuxemburgVatNumber

Luxemburgische Umsatzsteuer-ID (EAS 9938)

ElectronicMailSmtp oder EM

E-Mail-Adresse (EAS EM)

Andere Werte fallen auf GermanyVatNumber zurück. Die vollständige Liste der Kennungen steht in ZUGFeRD-csharp; weitere Werte müssen erst im Mapper ergänzt werden.

Lieferdaten

delivery:
  deliveryNoteNumber:
    column: "DELIVERY_NOTE_NO"
  deliveryNoteDate:
    column: "DELIVERY_NOTE_DATE"
  actualDeliveryDate:
    column: "DELIVERY_DATE"

Alle drei Felder sind optional. deliveryNoteNumber und deliveryNoteDate werden gemeinsam als Lieferscheinreferenz geschrieben. actualDeliveryDate setzt das tatsächliche Lieferdatum (BT-72).

Sind weder Lieferschein noch Lieferdatum konfiguriert, wird das Rechnungsdatum als Leistungsdatum geschrieben und eine Warnung protokolliert. Der Block ApplicableHeaderTradeDelivery ist im ZUGFeRD-Schema Pflicht; ohne Inhalt würde die XML-Datei die Validierung nicht bestehen.

Bestellreferenz

orderReference:
  orderNumber:
    column: "ORDER_NO"
  orderDate:
    column: "ORDER_DATE"

orderNumber entspricht BT-13 (Bestellnummer des Käufers). orderDate ist optional.

Positionsdaten

Das Tool unterstützt zwei CSV-Strukturen. Alle Positionsfelder sind FieldMapping-Einträge; neben den Beispielen unten gibt es sellerAssignedId (Artikelnummer des Verkäufers, BT-155) und buyerAssignedId (Artikelnummer des Käufers, BT-156).

Modus rows

Im rows-Modus entspricht jede CSV-Zeile einer Rechnungsposition. Kopfdaten werden aus der ersten Zeile gelesen.

lineItems:
  mode: "rows"
  fields:
    name:
      column: "Artikel_Name"
    description:
      column: "Artikel_Beschreibung"
    netUnitPrice:
      column: "Einzelpreis_Netto"
    billedQuantity:
      column: "Menge"
    taxPercent:
      column: "MwSt_Prozent"
    unitCode:
      value: "C62"
    taxType:
      value: "VAT"
    taxCategoryCode:
      value: "S"
  allowanceCharge:
    enabled: true
    isDiscount:
      value: true
    chargePercentage:
      column: "Rabatt_Prozent"
    reason:
      column: "Rabatt_Grund"

allowanceCharge ist optional. isDiscount: true erzeugt einen Positionsrabatt (BG-27), false einen Positionszuschlag (BG-28), jeweils als SpecifiedTradeAllowanceCharge in der Positionsabrechnung mit Basisbetrag, Prozentsatz und Grund. Der Zeilennettobetrag (BT-131) ist bereits um den Rabatt gemindert bzw. um den Zuschlag erhöht; die Summen auf Dokumentebene folgen daraus. chargePercentage akzeptiert Werte wie 10, 10.00 oder 10%. Im columns-Modus wird Rabatt stattdessen über discountPercent in fields konfiguriert.

Modus columns

Im columns-Modus steht eine ganze Rechnung in einer CSV-Zeile. Positionen werden über nummerierte Spalten erkannt.

Suffix-Beispiel:

PRODUCT_NAME_0,PRODUCT_QUANTITY_0,PRODUCT_PRICE_0,PRODUCT_NAME_1,PRODUCT_QUANTITY_1,PRODUCT_PRICE_1
Rillsoft Cloud Standard,1,1440.00,,,

Passende YAML:

lineItems:
  mode: "columns"
  columnPattern:
    style: "suffix"
    separator: "_"
    startIndex: 0
    zeroPadding: 0
  fields:
    name:
      column: "PRODUCT_NAME"
    sellerAssignedId:
      column: "PRODUCT_CODE"
    netUnitPrice:
      column: "PRODUCT_PRICE"
    billedQuantity:
      column: "PRODUCT_QUANTITY"
    taxPercent:
      column: "INVOICE_TAX"
    discountPercent:
      column: "PRODUCT_DISCOUNT"
  fixedFields:
    unitCode:
      default: "C62"
      rules:
        - when:
            column: "PRODUCT_CODE"
            regex: "^002"
          value: "ANN"
        - when:
            column: "PRODUCT_CODE"
            regex: "^003"
          value: "DAY"
        - when:
            column: "PRODUCT_NAME"
            regex: "(?i)vor-ort-schulung|schulung"
          value: "DAY"
    taxType:
      value: "VAT"
    taxCategoryCode:
      value: "S"

Im columns-Modus kann ein Feld entweder als nummeriertes Positionsfeld gelesen werden, zum Beispiel PRODUCT_NAME_0, oder als globale Einzelspalte, zum Beispiel INVOICE_TAX.

Bei style: "prefix" steht der Index vor dem Feldnamen; prefix ist der Text vor dem Index, separator trennt Index und Feldname: prefix: "Pos", separator: "_" und startIndex: 1 ergeben Pos1_Artikel, Pos2_Artikel usw. (Beispiel: testdata/config_columns_prefix.yml). Bei style: "suffix" wird prefix nicht verwendet; zeroPadding: 3 erzeugt Artikel_001.

PRODUCT_CODE ist hier eine positionsbezogene Verkäufer-Artikelnummer und wird deshalb als sellerAssignedId gemappt. Es sollte nicht als description verwendet werden.

discountPercent akzeptiert Werte wie 10, 10.00 oder 10%. Der Rabatt wird als rabattierter Netto-Einzelpreis geschrieben. Ein Listenpreis bleibt optional als GrossPriceProductTradePrice erhalten; es wird kein AppliedTradeAllowanceCharge für Positionsrabatte erzeugt.

Wirkung je Modus, Auflösung und Rückfälle

Jedes Mapping-Feld wird in der Reihenfolge value, column, rules, default aufgelöst; der erste nicht leere Treffer gewinnt. Einige Schlüssel wirken nur in einer Betriebsart:

Schlüssel

Wirkt nur bei

lineItems.columnPattern, lineItems.fixedFields

lineItems.mode: columns

lineItems.fields.unitCode, taxType, taxCategoryCode, lineItems.allowanceCharge

lineItems.mode: rows (im columns-Modus gelten fixedFields)

totals.lineTotalAmount … totals.totalPrepaidAmount

totals.mode: manual

paymentMeans.directDebit

typeCode 59 oder 49

Zwei Schlüssel werden aus Kompatibilitätsgründen akzeptiert, haben aber keine Wirkung: lineItems.columnsFields (Positionsfelder gehören nach lineItems.fields) und lineItems.fields.discountReason (ein Rabattgrund wird nur im rows-Modus über lineItems.allowanceCharge.reason geschrieben). Die CLI protokolliert ihre Verwendung als Warnung, csv2zugferd_config_get meldet sie als config_field_ignored.

Unbekannte Werte brechen die Verarbeitung nicht ab, sondern fallen mit Warnung zurück: zugferd.version → 2.3, zugferd.profile → Extended, zugferd.format → CII, lineItems.mode → rows, columnPattern.style → suffix, totals.mode → manual, Währung → EUR, Land → DE, Einheit → C62, Steuerart → VAT, Steuerkategorie → S, Steuernummernschema → VA, electronicAddress.scheme → GermanyVatNumber. Ein unbekannter invoice.typeCode oder paymentMeans.typeCode ist dagegen ein Fehler. Unbekannte YAML-Schlüssel (Tippfehler) werden beim Laden ignoriert; csv2zugferd_config_get listet sie unter unknownKeys. Der Spaltenabgleich von csv2zugferd_config_get ist strukturell: Er vergleicht konfigurierte Spaltennamen (im columns-Modus einschließlich der indexierten Namen) mit dem CSV-Kopf, liest aber keine Zellwerte; ob ein Wert leer ist oder eine Regel greift, zeigt erst csv2zugferd_invoice_get.

Summen

Summen können automatisch berechnet oder manuell aus der CSV gelesen werden.

Automatisch:

totals:
  mode: "auto"

Manuell:

totals:
  mode: "manual"
  lineTotalAmount:
    column: "INVOICE_SUBTOTAL"
  allowanceTotalAmount:
    value: "0"
  taxBasisAmount:
    column: "INVOICE_SUBTOTAL"
  taxTotalAmount:
    column: "INVOICE_TAXTOTAL"
  grandTotalAmount:
    column: "INVOICE_TOTAL"
  totalPrepaidAmount:
    value: "0"

Im manuellen Modus werden nur die Rechnungssummen aus der CSV übernommen. Die Steueraufschlüsselung je Steuersatz (BG-23) wird weiterhin aus den Positionen abgeleitet: Die Steuerbasis je Satz ist die Summe der Positionsnettobeträge, der Steuerbetrag je Satz wird daraus berechnet. Eine Differenz zwischen der berechneten Steuersumme und taxTotalAmount wird der letzten Steuergruppe zugeschlagen, damit die Summe der Gruppen exakt taxTotalAmount ergibt (BR-CO-14). Bei nur einem Steuersatz entspricht der Steuerbetrag der Gruppe damit genau dem CSV-Wert. Rundungsabweichungen im Cent-Bereich zwischen Steuerbasis × Steuersatz und dem übernommenen Steuerbetrag sind zulässig (BR-CO-17 sieht eine Toleranz vor). Zeilennettobeträge (BT-131) werden in beiden Modi aus Einzelpreis × Menge berechnet und kaufmännisch auf zwei Nachkommastellen gerundet; alle Kopfsummen und Steuerbasen summieren diese gerundeten Werte (BR-CO-10, BR-S-08). Im manuellen Modus muss lineTotalAmount der Summe der gerundeten Zeilennettobeträge entsprechen.

Für Rabatt-Szenarien mit Positionsdaten ist auto bevorzugt, weil BT-131, Steuerbasis und Gesamtsummen dann konsistent aus den Positionen berechnet werden.

Zahlungsbedingungen

paymentTerms:
  description:
    value: "Zahlbar ohne Abzug bis zum Faelligkeitsdatum"
  dueDate:
    column: "INVOICE_DUEDATE"

Eine Beschreibung ist optional, aber in echten Rechnungen meistens sinnvoll.

Zahlungsmittel und Bankverbindung

Der Abschnitt paymentMeans erzeugt die Zahlungsanweisungen nach EN 16931 (BG-16) inklusive Bankverbindung des Verkäufers und SEPA-Lastschriftdaten. Alle Felder unterstützen column, value, default und rules.

Überweisung

paymentMeans:
  typeCode:
    value: "58"                 # SEPA-Überweisung
  information:
    value: "SEPA-Überweisung"
  paymentReference:             # Verwendungszweck (BT-83)
    column: "INVOICE_NO"
  sellerAccounts:               # ein oder mehrere Konten des Verkäufers
    - iban:
        value: "DE02 1203 0000 0000 2020 51"
      bic:
        value: "BYLADEM1001"
      name:
        value: "Muster GmbH"

SEPA-Lastschrift

paymentMeans:
  typeCode:
    value: "59"                 # SEPA-Lastschrift
  paymentReference:
    column: "INVOICE_NO"
  directDebit:
    creditorId:                 # Gläubiger-ID (BT-90)
      value: "DE98ZZZ09999999999"
    mandateReference:           # Mandatsreferenz (BT-89)
      column: "MANDATE_ID"
    buyerIban:                  # zu belastendes Konto des Käufers (BT-91)
      column: "CUSTOMER_IBAN"
    buyerBic:
      column: "CUSTOMER_BIC"

Überweisung und Lastschrift lassen sich pro Rechnung über eine Regel umschalten:

typeCode:
  default: "58"
  rules:
    - when:
        column: "PAYMENT_METHOD"
        regex: "(?i)^lastschrift$"
      value: "59"

directDebit wird nur bei typeCode 59 oder 49 ausgewertet.

Feld

EN 16931

Hinweis

typeCode

BT-81

Zahl nach UNTDID 4461 (30, 58, 59, …) oder Name (SEPACreditTransfer, SEPADirectDebit, …)

information

BT-82

Freitext zur Zahlungsart

paymentReference

BT-83

Verwendungszweck

sellerAccounts[].iban

BT-84

Leerzeichen werden entfernt

sellerAccounts[].name

BT-85

Kontoinhaber

sellerAccounts[].bic

BT-86

directDebit.mandateReference

BT-89

directDebit.creditorId

BT-90

directDebit.buyerIban

BT-91

wird im Log maskiert

Code

Bedeutung

30

Überweisung (nicht SEPA)

58

SEPA-Überweisung

59

SEPA-Lastschrift

49

Lastschrift (nicht SEPA)

Unvollständige Angaben brechen die Verarbeitung nicht ab, sondern erzeugen eine Warnung im Log:

  • Überweisung (30, 58) ohne IBAN des Verkäufers (BR-61)

  • SEPA-Lastschrift ohne Gläubiger-ID, Mandatsreferenz oder IBAN des Käufers

  • Profil XRechnung ohne paymentMeans (BR-DE-1)

  • IBAN mit ungültiger Prüfsumme

Ein unbekannter typeCode führt dagegen zu einem Fehler.

Konfiguration für test\0002486.csv

Die Datei test\0002486.csv verwendet:

Eigenschaft

Wert

Trennzeichen

Komma

Dezimaltrennzeichen

Punkt

Datumsformat

dd.MM.yyyy

Positionsmodus

columns

Positionsmuster

Suffix, Startindex 0, zum Beispiel PRODUCT_NAME_0

Steuer

globale Spalte INVOICE_TAX

Produktcode

positionsbezogen, zum Beispiel PRODUCT_CODE_0

Rabatt

positionsbezogen, zum Beispiel PRODUCT_DISCOUNT_0

Summen

automatisch aus Positionen berechnet

Die passende Konfiguration liegt unter test\config_datasrc.yml.

Verarbeitung

  1. YAML-Konfiguration laden.

  2. CSV mit Separator, Encoding, Header- und Zahlenformat lesen.

  3. Rechnungskopf, Käufer, Verkäufer, Lieferung und Bestellung aus erster Zeile oder festen Werten mappen.

  4. Positionen je nach Modus aus Zeilen oder nummerierten Spalten erzeugen.

  5. Summen automatisch berechnen oder aus CSV übernehmen.

  6. InvoiceDescriptor als ZUGFeRD-XML speichern.

  7. Optional das XML in eine PDF-Vorlage einbetten.

Externe Validierung mit Mustang-CLI

Die Unit-Tests prüfen Mapping und Summen. Ob das erzeugte XML den ZUGFeRD-Regeln entspricht (Schema, EN16931- und XRechnung-Schematron), prüft zusätzlich das Mustang-CLI. Diese Tests laufen nicht bei dotnet test, sondern nur auf ausdrückliche Anforderung, weil sie Java 17 und das Mustang-Jar benötigen.

# einmalig: Jar nach tools/Mustang-CLI.jar laden (nicht im Git)
.\tools\Get-MustangCli.ps1

# alle Testdaten-Konfigurationen exportieren und validieren
dotnet test --filter Category=ExternalValidation

Alternativ kann der Jar-Pfad über die Umgebungsvariable MUSTANG_CLI_JAR gesetzt werden. Fehlt das Jar, melden die Tests Inconclusive statt eines Fehlers. Für eine manuelle Gegenprobe einzelner Dateien eignet sich der Browser-Upload auf e-rechnungs-checker.de.

Hinweise

  • Paketversionen werden zentral in Directory.Packages.props verwaltet.

  • Das Target Framework steht zentral in Directory.Build.props.

  • Markdown-Dateien im Repository sollen UTF-8 mit BOM verwenden.

  • country sollte als ISO-Code gesetzt werden, zum Beispiel DE.

  • Verkäufer-Stammdaten gehören in die YAML-Konfiguration, wenn sie für alle Rechnungen identisch sind.

Haftungsausschluss

csv2zugferd wird ohne Gewähr bereitgestellt. Die erzeugten XML- und PDF-Dateien müssen vor produktiver Nutzung fachlich und technisch geprüft werden. Das Tool ersetzt keine steuerliche, rechtliche oder fachliche Beratung.

Für die Richtigkeit, Vollständigkeit und steuerliche Bewertung der Eingabe- und Ausgabedaten bleibt der Anwender verantwortlich. Die Bereitstellung erfolgt zu den Bedingungen der Lizenz Apache-2.0, die einen Gewährleistungs- und Haftungsausschluss enthält.

Available Tools

5 tools
csv2zugferd_config_file_createSave configuration fileA
DestructiveIdempotent

Validates configYaml (it must deserialize as a csv2zugferd configuration) and writes it to configPath as UTF-8 without BOM. An existing file is replaced only with overwrite: true; otherwise the call is rejected with file_exists and nothing is written. Returns the full path, the size and the csv2zugferd CLI command that uses the saved file for production runs. Does not run the mapping (use csv2zugferd_invoice_get first) and does not create XML or PDF files.

Inputs: configPath (required), configYaml (required), csvPath, overwrite. Returns: Saved configuration file.

csv2zugferd_config_getCheck configurationA
Read-onlyIdempotent

Loads a YAML configuration from configPath or configYaml and reports its structure: effective settings and modes, every mapped field with its sources, unknown keys (typos), values outside the allowed sets with the fallback the CLI would apply, and ignored or missing sections. With csvPath the referenced columns are compared with the CSV header (missing and unused columns). Does not read cell values and does not calculate totals; use csv2zugferd_invoice_get for a trial run. Read-only, writes no files.

Inputs: csvPath, configPath, configYaml. Returns: Structural check of a configuration.

csv2zugferd_config_schema_getGet configuration referenceA
Read-onlyIdempotent

Returns the reference of the csv2zugferd YAML configuration: a JSON Schema of every key with description, type, default and allowed values, plus semantics per key (effect, mode dependency, resolution order value > column > rules > default, fallback for unknown values). Use it before drafting a configuration; no README is needed. The complete reference is about 100 KB; pass section (for example mapping.lineItems) to get one subtree with its definitions. Does not check a concrete configuration (use csv2zugferd_config_get). Read-only.

Inputs: section. Returns: Complete reference of the YAML configuration, or the requested subtree.

csv2zugferd_csv_header_getRead CSV headerA
Read-onlyIdempotent

Reads the header row of a CSV file and returns the column names in file order plus the number of data rows. Optionally returns up to 3 sample rows (sampleRows, default 0) so the mapping can be drafted from real values. Does not validate a configuration (use csv2zugferd_config_get) and does not map an invoice (use csv2zugferd_invoice_get). Read-only, writes no files.

Inputs: csvPath (required), encoding, separator, sampleRows. Returns: Header of the CSV file.

csv2zugferd_invoice_getTrial run invoice mappingA
Read-onlyIdempotent

Runs the real csv2zugferd mapping for one CSV file with the given configuration (configPath or configYaml) without writing any output file: reads the CSV, maps header, parties, line items and totals, builds the ZUGFeRD XML in memory and returns invoice number, type, totals, tax breakdown, warnings with codes and paths, and the XML size. includeLineItems and includeParties (default false) add details; pdfTemplatePath also embeds the XML into a temporary PDF copy to check format and template. Does not create the XML or PDF for production; that is done by the csv2zugferd CLI (csv2zugferd_config_file_create returns the command). Read-only for the user's files; a temporary PDF is created and deleted only with pdfTemplatePath.

Inputs: csvPath (required), configPath, configYaml, includeParties, +2 more. Returns: Result of the trial run (same code path as the CLI up to writing the XML).

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

    TDQS

    A4.4/5.0

    Scored across 5 tools

    Disambiguation5/5

    Each tool targets a distinct step of the workflow: writing a config file, inspecting a config, fetching the schema reference, reading a CSV header, and doing an in-memory trial run. The descriptions explicitly disambiguate with cross-references ('use csv2zugferd_invoice_get first', 'use csv2zugferd_config_get'). No two tools overlap in purpose.

    Naming Consistency4/5

    All names use snake_case with a shared csv2zugferd_ prefix and a resource_then_action ordering (config_get, csv_header_get, invoice_get). The one deviation is csv2zugferd_config_file_create, which nests an extra noun (config_file) while config_get does not, making resource granularity slightly uneven.

    Tool Count5/5

    Five tools map cleanly onto five distinct workflow stages (schema reference → CSV inspection → config authoring → config validation → trial run), with no filler. The surface is well scoped for a config/validation assistant around a CLI.

    Completeness4/5

    The surface covers the full prepare-validate-preview lifecycle including schema lookup, header inspection, config write, config lint and a dry-run mapping, which is coherent for the stated read-only purpose. The absence of any tool to actually emit production XML/PDF (delegated to the CLI) and no way to remove or list saved config files are minor gaps agents can work around.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables AI assistants to locally parse, validate, audit, explain, generate, and convert XRechnung and ZUGFeRD/Factur-X e-invoices using official rule sets, fully offline with no API keys required.
      Apache 2.0
    • F
      license
      B
      quality
      B
      maintenance
      Enables an AI assistant to read and manage a local project-management database over stdio: listing and creating projects, tasks, expenses, chats and people, and pulling money, margin, deadline and overdue summaries. It runs on the same service layer and SQLite file as the bundled web UI, so figures shown to the assistant and to the human always agree.
      14
      -
    • A
      license
      A
      quality
      B
      maintenance
      Read and check European e-invoices (Factur-X, ZUGFeRD, XRechnung, UBL, CII) from an AI assistant: contents in plain text plus an EN 16931 consistency check.
      2
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Enables AI assistants and agents to create, validate, read and convert German and EU e-invoices in ZUGFeRD, XRechnung and Factur-X formats, including extracting structured data from scanned or PDF invoices and embedding XML into PDF/A-3 files. It can run as a hosted remote server or locally, with each request authenticated by the caller's own API token.
      10
      20 npm
      MIT