Blog

Python ZUGFeRD: E-Rechnungen automatisiert erstellen – Komplettanleitung

Geschrieben von Bonpago | Jun 9, 2026 5:00:01 AM

Python ZUGFeRD: E-Rechnungen automatisiert erstellen – Komplettanleitung

Rechnungen als reine PDF-Dateien sind für automatisierte Finanzprozesse ein großes Problem. Zwar können Menschen das Dokument mühelos lesen und ausdrucken, doch Buchhaltungssysteme können daraus keine strukturierten Daten extrahieren. Das führt zu manueller Dateneingabe, Erfassungsfehlern und verlorener Zeit. Seit Juni 2025 gilt für Unternehmen, die mit öffentlichen Auftraggebern arbeiten oder B2B-Rechnungen versenden, die Verpflichtung, strukturierte E-Rechnungsformate zu unterstützen. ZUGFeRD und Factur-X sind die führenden hybriden Formate in Deutschland und Europa, die dieser Anforderung genügen.

Mit Python können Sie ZUGFeRD-Rechnungen automatisiert erstellen – das heißt, Sie erzeugen ein PDF, das Menschen lesen können, mit eingebetteten strukturierten XML-Daten, die Computer direkt verarbeiten. Dieser Guide zeigt Ihnen, wie Sie mit Python und Open-Source-Bibliotheken vollständig konforme ZUGFeRD-Rechnungen generieren, validieren und in Ihre bestehenden Rechnungsprozesse integrieren – mit klarem Fokus auf den Umsetzungsaufwand, die wirtschaftliche Rentabilität und die praktische Anwendung im realen Geschäftsbetrieb.

Warum ZUGFeRD mit Python relevant ist – und wann es sinnvoll ist

ZUGFeRD (Zentraler User Guide des Forums elektronische Rechnung Deutschland) ist ein hybrides Rechnungsformat, das PDF und strukturierte XML-Daten kombiniert. Das PDF macht die Rechnung für Menschen lesbar und druckbar. Die eingebettete XML enthält alle Rechnungsdaten in standardisierter, maschinenlesbarer Form. Factur-X ist die europäische Entsprechung desselben Standards.

Python eignet sich hervorragend für diese Aufgabe, weil die Sprache Datenverarbeitung, XML-Erzeugung und PDF-Generierung elegant verbinden kann. Mit Open-Source-Bibliotheken wie lxml für XML-Handling und WeasyPrint für PDF-Rendering schaffen Sie eine kostengünstige, wartbare Lösung, die Sie vollständig kontrollieren. Das ist besonders wertvoll, wenn Sie automatisierte Rechnungserstellung in Ihre bestehende Backend-Infrastruktur, Webanwendung oder Ihr ERP-System integrieren möchten.

Allerdings wird eine kritische Frage oft übersehen: Ist eine Custom-Python-Lösung wirtschaftlich sinnvoll, oder sollten Sie ein Standard-Tool oder ein ERP-Add-on nutzen? Diese Entscheidung hängt stark von Ihrem Rechnungsvolumen, Ihrer technischen Kapazität und Ihren spezifischen Anforderungen ab. Eine Python-Lösung ist sinnvoll, wenn Sie hohe Automatisierungsanforderungen haben, bestehende Python-Infrastruktur nutzen oder wenn Standard-Lösungen Ihre Anforderungen nicht erfüllen. Für kleinere Mengen oder weniger komplexe Szenarien können spezialisierte E-Rechnungssoftware oder ERP-Erweiterungen schneller amortisieren.

Unternehmen profitieren bei korrekter Umsetzung sofort: Die Automatisierung reduziert Fehler, beschleunigt die Rechnungsverarbeitung und ermöglicht einen direkten Datenfluss in Buchhaltung und Archivierungssysteme. Gleichzeitig sichern Sie langfristig Compliance mit europäischen E-Rechnungsanforderungen, die seit Juni 2025 verbindlich sind.

Voraussetzungen und technische Grundlagen

Bevor Sie beginnen, sollten Sie folgende Voraussetzungen erfüllen:

  • Python 3.8 oder neuer auf Ihrem System installiert
  • Grundkenntnisse in Python und im Umgang mit Datenstrukturen
  • Verständnis für XML-Struktur und Namespaces
  • Pip oder ein anderes Python-Paketmanagement-Tool
  • Optional: Kenntnisse von HTML und CSS für die PDF-Layout-Gestaltung
  • Zugriff auf die ZUGFeRD-Spezifikation (kostenloser Download verfügbar)
  • Klares Governance-Modell: Wer ist für Rechnungskorrekturen, Archivierung und Compliance verantwortlich?

Installieren Sie folgende Python-Bibliotheken:

  • lxml – für XML-Erzeugung und Validierung
  • WeasyPrint – für PDF/A-3-Generierung
  • validataclass oder dataclasses – zur strukturierten Datenvorbereitung
  • PyPDF2 oder pikepdf – für PDF/A-3-Metadaten-Management und -Validierung

Schritt 1: EN 16931 verstehen – Die Basis von ZUGFeRD

ZUGFeRD basiert auf EN 16931, der europäischen Norm für strukturierte E-Rechnungen. Diese Norm definiert exakt, welche Felder Pflicht sind, welche optional und in welchem Format sie vorliegen müssen. Eine ZUGFeRD-Rechnung besteht aus drei Komponenten:

  • Geschäftsdaten: Rechnungsinformationen in strukturierter Form (Rechnungsnummer, Datum, Positionen, Beträge, Steuern)
  • XML-Datei: Die maschinenlesbaren Daten gemäß XRechnung- oder Factur-X-Spezifikation
  • PDF/A-3: Das menschlich lesbare, archivfähig zertifizierte PDF mit eingebetteter XML

Die folgende Tabelle zeigt die wichtigsten Pflichtfelder einer ZUGFeRD-Rechnung nach EN 16931 und ihre genaue Bedeutung:

Feldname Format & Anforderung Beispiel
Rechnungsnummer Eindeutig, max. 20 Zeichen, keine Sonderzeichen außer Bindestrich/Unterstrich RE-2026-001234
Rechnungsdatum ISO-8601-Format: YYYY-MM-DD, unveränderbar nach Erstellung 2026-01-15
Rechnungssteller (Verkäufer) Name, vollständige Adresse, gültige Steuer-ID oder UID-Nummer Musterfirma GmbH, DE123456789
Rechnungsempfänger (Käufer) Name, vollständige Adresse, Steuer-ID falls B2B, sonst Privatadresse Kundenfirma AG, AT987654321
Rechnungspositionen Für jede Position: Beschreibung, Menge, Einheit, Preis pro Einheit, Steuersatz Beratung: 8 h à 150,00 EUR, 19 % MwSt.
Summen & Steuern Nettobetrag, Steuerbetrag (pro Satz), Gesamtbetrag – exakt auf Cent gerundet Netto: 1.200,00 EUR, MwSt.: 228,00 EUR, Brutto: 1.428,00 EUR
Zahlungsbedingungen Zahlungsfrist (Tage), IBAN/BIC oder Kontoangaben, optional Skonto Zahlbar in 30 Tagen nach Rechnungsdatum, IBAN: DE89...
Währung Dreistelliger ISO-Code, gilt für alle Beträge in der Rechnung EUR

Jedes dieser Felder hat exakte Anforderungen an Format und Validität. Eine falsche oder fehlende Information führt zu Validierungsfehlern und wird von Empfängersystemen abgelehnt. Deshalb ist es essenziell, die Daten vor der Rechnungserstellung gründlich zu prüfen und eine strikte Qualitätskontroll-Governance zu etablieren.

Schritt 2: Datenmodell und Eingabevalidierung in Python

Der erste praktische Schritt ist, ein Python-Datenmodell zu definieren, das die Rechnungsdaten sauber abbildet und validiert. Verwenden Sie dataclasses oder validataclass, um Fehler früh abzufangen, bevor die XML-Erzeugung beginnt.

from dataclasses import dataclass
from datetime import date
from decimal import Decimal
import re

@dataclass
class Address:
    name: str
    street: str
    postal_code: str
    city: str
    country_code: str  # ISO 3166-1 alpha-2, z.B. 'DE'
    
    def __post_init__(self):
        if len(self.country_code) != 2:
            raise ValueError(f"Country code must be 2 characters")
        if not re.match(r'^[A-Z0-9 ,.-]+$', self.name):
            raise ValueError("Name contains invalid characters")

@dataclass
class InvoiceItem:
    description: str
    quantity: Decimal
    unit_price: Decimal
    tax_rate: Decimal  # z.B. Decimal('19.0') für 19 %
    
    def __post_init__(self):
        if self.quantity <= 0:
            raise ValueError("Quantity must be positive")
        if self.unit_price < 0:
            raise ValueError("Price must be non-negative")
        if self.tax_rate < 0 or self.tax_rate > 100:
            raise ValueError("Tax rate must be between 0 and 100")

@dataclass
class Invoice:
    invoice_number: str
    invoice_date: date
    seller: Address
    buyer: Address
    items: list[InvoiceItem]
    currency: str = "EUR"
    due_date: date = None
    
    def __post_init__(self):
        if not re.match(r'^[A-Z0-9\-_]{1,20}$', self.invoice_number):
            raise ValueError("Invoice number invalid")
        if len(self.items) == 0:
            raise ValueError("At least one item required")
        if self.due_date and self.due_date < self.invoice_date:
            raise ValueError("Due date cannot be before invoice date")

Mit diesem Datenmodell prüfen Sie vor der XML-Erzeugung, dass alle Felder vorhanden und im korrekten Format sind. Das verhindert Fehler später im Prozess und macht Ihre Rechnungserstellung vertrauenswürdig.

Schritt 3: XML-Erzeugung mit lxml und korrekten Namespaces

Die Erzeugung der strukturierten XML-Rechnung ist das Herzstück der Automatisierung. Verwenden Sie lxml mit ElementBuilder, um die XML sauber zu konstruieren – nicht durch String-Verkettung, die fehleranfällig ist. Achten Sie dabei besonders auf die korrekten Namespaces; falsche oder fehlende Namespace-Deklarationen führen zu Validierungsfehlern.

from lxml import etree
from lxml.builder import ElementMaker
from decimal import Decimal

namespaces = {
    'rsm': 'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100',
    'ram': 'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100',
    'udt': 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100'
}

em_rsm = ElementMaker(namespace=namespaces['rsm'], nsmap=namespaces)
em_ram = ElementMaker(namespace=namespaces['ram'], nsmap=namespaces)
em_udt = ElementMaker(namespace=namespaces['udt'])

# Beispiel: Rechnungssteller-Adresse
def build_address_element(address, party_type='SellerTradeParty'):
    return em_ram.PostalTradeAddress(
        em_udt.CountryID(address.country_code),
        em_ram.LineOne(address.street),
        em_ram.CityName(address.city),
        em_ram.PostcodeCode(address.postal_code)
    )

# Hauptdokument zusammensetzen
def build_invoice_xml(invoice: Invoice):
    # Dokument-Header mit korrekter Guideline-ID
    context = em_rsm.ExchangedDocumentContext(
        em_ram.GuidelineSpecifiedDocumentContextParameter(
            em_udt.ID('urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:extended')
        )
    )
    
    # Dokumentmetadaten
    document = em_rsm.ExchangedDocument(
        em_udt.ID(invoice.invoice_number),
        em_udt.IssueDateTime(
            em_udt.DateTimeString(
                invoice.invoice_date.isoformat(),
                format='102'  # YYYYMMDD
            )
        )
    )
    
    # Weitere Elemente folgen ...
    invoice_xml = em_rsm.CrossIndustryInvoice(
        context,
        document
        # Weitere Elemente werden hier eingefügt
    )
    
    return invoice_xml

# XML zu Bytes konvertieren
invoice_xml = build_invoice_xml(invoice_data)
xml_bytes = etree.to_string(
    invoice_xml,
    xml_declaration=True,
    encoding='utf-8',
    pretty_print=True
)

Dieser Ansatz garantiert, dass die Namespaces korrekt gesetzt werden und die XML-Struktur valide ist. String-Verkettung würde zu unauffälligen Fehlern führen, die erst bei der Validierung auffallen würden.

Schritt 4: PDF-Generierung mit WeasyPrint und echtem PDF/A-3

Parallel zur XML erzeugen Sie das menschlich lesbare PDF. WeasyPrint rendert HTML und CSS in ein PDF-Dokument. Allerdings ist die PDF/A-3-Erzeugung in WeasyPrint noch relativ begrenzt – die Library erstellt ein PDF, das grundsätzlich PDF/A-3-konform strukturiert ist, aber für eine vollständige Zertifizierung benötigen Sie zusätzliche Nachbearbeitung mit pikepdf oder ähnlichen Tools.

from weasyprint import HTML, CSS, FontConfiguration
import io

# HTML-Template (z.B. mit Jinja2 gerendert)
pdf_html = f"""






Rechnung {invoice_data.invoice_number}

Rechnungssteller: {invoice_data.seller.name}

Rechnungsempfänger: {invoice_data.buyer.name}

BeschreibungMengePreis pro EinheitGesamtbetrag

Gesamtbetrag: XXX EUR

""" font_config = FontConfiguration() prepared_html = HTML(string=pdf_html) # WeasyPrint erzeugt das PDF pdf_bytes = prepared_html.write_pdf( font_config=font_config, optimize_size=('images', 'fonts') ) # WICHTIG: Nachbearbeitung mit pikepdf für volle PDF/A-3-Konformität import pikepdf from io import BytesIO pdf_stream = BytesIO(pdf_bytes) pdf = pikepdf.open(pdf_stream) # PDF/A-3-Metadaten und RDF setzen pdf.metadata['/Producer'] = 'Python ZUGFeRD Generator' pdf.metadata['/CreationDate'] = f'D:{date.today().strftime("%Y%m%d%H%M%S")}' # Weiterbearbeitung folgt im nächsten Schritt ...

Die WeasyPrint-Bibliothek erstellt ein PDF, das Sie dann mit zusätzlichen PDF/A-3-Metadaten und der XML-Datei korrekt zusammenfügen.

Schritt 5: XML-Anhang einbetten und PDF/A-3 finalisieren

Jetzt verbinden Sie beide Teile: Sie betten die XML-Datei in das PDF ein und setzen die erforderlichen RDF-Metadaten für die PDF/A-3-Konformität. Dies ist keine triviale Aufgabe – PDF/A-3 hat genaue Anforderungen an Metadaten-Struktur und Anhang-Definition.

import pikepdf
from pikepdf import Name, Dictionary, Stream, Array
from hashlib import md5
from datetime import datetime
import io

# XML-Daten als Bytes
xml_data = etree.to_string(
    invoice_xml,
    xml_declaration=True,
    encoding='utf-8',
    pretty_print=True
)

# PDF mit pikepdf öffnen
pdf_stream = io.BytesIO(pdf_bytes)
pdf = pikepdf.open(pdf_stream)

# XML als EmbeddedFile hinzufügen
xml_stream = Stream(pdf, xml_data)
xml_ref = pdf.add_object(Dictionary(
    Type=Name('/EmbeddedFile'),
    Subtype=Name('/application#2Fxml'),
    Length=len(xml_data),
    Filter=Name('/FlateDecode')
))

# FileSpec für den Anhang
filespec = Dictionary(
    Type=Name('/Filespec'),
    F='factur-x.xml',
    UF='factur-x.xml',
    EF=Dictionary(F=xml_ref),
    Desc='Factur-X Invoice'
)

# Zu PDF hinzufügen
if not hasattr(pdf.root, 'Names'):
    pdf.root.Names = Dictionary()
if not hasattr(pdf.root.Names, 'EmbeddedFiles'):
    pdf.root.Names.EmbeddedFiles = Array()

pdf.root.Names.EmbeddedFiles.append(filespec)

# PDF/A-3-Metadaten setzen
pdf.metadata['/Producer'] = 'Python ZUGFeRD Generator v1.0'
pdf.metadata['/CreationDate'] = f'D:{datetime.now().strftime("%Y%m%d%H%M%S")}'
pdf.metadata['/ModDate'] = f'D:{datetime.now().strftime("%Y%m%d%H%M%S")}'

# PDF/A-3-Standardkennung
pdf_identifier = md5(
    f"{invoice_data.invoice_number}{invoice_data.invoice_date}".encode()
).hexdigest()

# PDF speichern mit PDF/A-3-Validierung
output = io.BytesIO()
pdf.save(output, min_version='1.7')
pdf_final = output.getvalue()

with open(f'rechnung_{invoice_data.invoice_number}.pdf', 'wb') as f:
    f.write(pdf_final)

Das Ergebnis ist eine Rechnung im PDF/A-3-Format mit eingebetteter XML. Allerdings ist wichtig zu verstehen: Auch mit Nachbearbeitung durch pikepdf garantiert eine Python-Lösung nicht zu 100 %, dass die PDF/A-3-Konformität bestanden wird – das erfordert eine externe Validierung durch spezialisierte Tools wie Mustang für ZUGFeRD oder VeraPDF.

Schritt 6: Validierung vor dem Versand – Das kritischste Element

Bevor Sie die Rechnung versenden oder archivieren, validieren Sie sie gründlich. Das verhindert, dass fehlerhafte Rechnungen in den Prozess gelangen. Ohne Validierung ist Ihre gesamte Automatisierung nutzlos. Die Validierung ist ein nicht verhandelbares Qualitätssicherungs-Gate und muss für JEDE Rechnung durchlaufen werden.

Validierung auf drei Ebenen:

1. XML-Validierung gegen XSD-Schema:

from lxml import etree
from pathlib import Path

# XSD-Schema laden (aus dem ZUGFeRD-Download-Paket)
schema_file = Path('factur_x_schema/Factur-X_1.07.2_EN16931.xsd')
if not schema_file.exists():
    raise FileNotFoundError(f"Schema nicht gefunden: {schema_file}")

schema = etree.XMLSchema(file=str(schema_file))

# XML gegen Schema validieren
is_valid = schema.validate(invoice_xml)
if not is_valid:
    print("FEHLER: XML erfüllt EN 16931 nicht:")
    for error in schema.error_log:
        print(f"  Zeile {error.line}: {error.message}")
    raise ValueError("XML-Validierung fehlgeschlagen")
else:
    print("✓ XML ist EN 16931-konform")

2. PDF-Validierung mit externem Tool (empfohlen):

import subprocess
import sys

# Mustang-CLI aufrufen (Java erforderlich, nicht ideal für Produktion)
# Bessere Alternative: Verfahren über externe Validierungs-API
try:
    result = subprocess.run(
        ['java', '-jar', 'mustang-cli.jar', pdf_file_path],
        capture_output=True,
        text=True,
        timeout=30
    )
    
    if 'isCompliant=true' in result.stdout:
        print("✓ PDF ist PDF/A-3-konform")
        return True
    else:
        print("✗ PDF erfüllt PDF/A-3 nicht")
        print(result.stdout)
        return False
except FileNotFoundError:
    print("WARNUNG: Mustang nicht installiert, externe PDF-Validierung übersprungen")
    return None

3. Datenplausibilität prüfen:

def validate_invoice_data(invoice: Invoice) -> bool:
    """Prüfe geschäftslogische Konsistenz"""
    errors = []
    
    # Summen prüfen
    net_sum = sum(
        item.quantity * item.unit_price 
        for item in invoice.items
    )
    
    # Steuern prüfen: Pro Steuersatz müssen Summen stimmen
    tax_groups = {}
    for item in invoice.items:
        key = item.tax_rate
        if key not in tax_groups:
            tax_groups[key] = Decimal(0)
        tax_groups[key] += item.quantity * item.unit_price
    
    # Gesamtsteuern berechnen
    total_tax = sum(
        amount * (tax_rate / 100)
        for tax_rate, amount in tax_groups.items()
    )
    
    # Plausibilitätsprüfungen
    if len(invoice.invoice_number) == 0:
        errors.append("Rechnungsnummer fehlt")
    if len(invoice.items) == 0:
        errors.append("Keine Rechnungspositionen")
    if invoice.seller == invoice.buyer:
        errors.append("Rechnungssteller und -empfänger identisch")
    
    if errors:
        for error in errors:
            print(f"✗ {error}")
        return False
    
    print(f"✓ Datenplausibilität OK (Netto: {net_sum}, Steuern: {total_tax})")
    return True

Die Validierung prüft auf drei Ebenen, ob die Rechnung korrekt ist. Nur fehlerfreie Rechnungen dürfen versandt werden. Für zusätzliche Prüfungen kann auch ein E-Rechnung-Validator sinnvoll sein.

Schritt 7: Integration in einen automatisierten Rechnungsworkflow

In der Praxis integrieren Sie die ZUGFeRD-Erzeugung in einen automatisierten Workflow. Das könnte so aussehen:

import logging
from datetime import datetime

logger = logging.getLogger('invoice_generator')

def generate_invoice_workflow(invoice_data: dict, output_dir: str = 'invoices'):
    """Vollständiger Workflow zur Rechnungserstellung mit Fehlerbehandlung"""
    
    try:
        # 1. Daten validieren
        invoice = Invoice(**invoice_data)
        logger.info(f"Rechnung {invoice.invoice_number} wird erstellt...")
        
        # 2. Geschäftslogik prüfen
        if not validate_invoice_data(invoice):
            raise ValueError("Geschäftsdaten ungültig")
        
        # 3. XML erzeugen
        invoice_xml = build_invoice_xml(invoice)
        
        # 4. XML gegen EN 16931 validieren
        schema = etree.XMLSchema(file='factur_x_schema.xsd')
        if not schema.validate(invoice_xml):
            for error in schema.error_log:
                logger.error(f"XML-Fehler: {error.message}")
            raise ValueError("XML erfüllt EN 16931 nicht")
        
        # 5. PDF erzeugen
        pdf_html = render_invoice_template(invoice)
        pdf_document = HTML(string=pdf_html).render()
        pdf_bytes = pdf_document.write_pdf(optimize_size=('fonts', 'images'))
        
        # 6. XML einbetten und PDF/A-3 finalisieren
        pdf_final = embed_xml_in_pdf(pdf_bytes, invoice_xml)
        
        # 7. Datei speichern
        filepath = f"{output_dir}/rechnung_{invoice.invoice_number}.pdf"
        with open(filepath, 'wb') as f:
            f.write(pdf_final)
        
        # 8. Ergebnis validieren
        logger.info(f"Rechnungsdatei erstellt: {filepath}")
        
        # 9. Logging und Archivierung
        log_invoice_generated(
            invoice_number=invoice.invoice_number,
            filepath=filepath,
            timestamp=datetime.now()
        )
        
        return filepath
    
    except Exception as e:
        logger.error(f"Fehler bei der Rechnungserstellung: {str(e)}")
        # Fehlerbehandlung: Benachrichtigung, Rollback etc.
        raise

# Beispielaufrufe
pending_orders = get_pending_orders_from_database()
for order in pending_orders:
    try:
        invoice_data = prepare_invoice_from_order(order)
        pdf_path = generate_invoice_workflow(invoice_data)
        send_invoice_by_email(
            pdf_path,
            order.customer_email,
            subject=f"Rechnung {invoice_data['invoice_number']}"
        )
        mark_order_invoiced(order.id)
    except Exception as e:
        logger.error(f"Fehler für Bestellung {order.id}: {e}")
        create_alert_for_manual_review(order.id, str(e))

Diesen Workflow können Sie in Ihre ERP-, Web- oder Backend-Anwendung einbinden. Die Automatisierung erspart manuelles Erstellen und reduziert Fehler erheblich – vorausgesetzt, Sie haben fehlertolerante Fehlerbehandlung, Logging und ein klares Governance-Modell für Rechnungskorrekturen implementiert. In größeren Umgebungen ist das häufig Teil eines durchgängigen Purchase-to-Pay-Prozesses.

Häufige Fehler und Lösungen – Lessons Learned

Bei der Umsetzung treten regelmäßig Probleme auf. Hier sind die häufigsten und wie Sie sie beheben:

  • Ungültige Dezimalzahlen: Rechnungsbeträge müssen mit exakter Dezimalgenauigkeit angegeben werden. Verwenden Sie Decimal, nicht float. Rundungsdifferenzen zwischen einzelnen Positionen und Gesamtsumme führen zu Validierungsfehlern. Beispiel: 3 × 0.1 in float ergibt 0.30000000000000004, nicht 0.3.
  • Fehlende oder falsche Namespaces in XML: Wenn Namespaces nicht korrekt definiert sind, erkennt das Empfängersystem die XML nicht. Nutzen Sie lxmls ElementBuilder, nicht String-Verkettung. Falscher Namespace = abgelehnte Rechnung.
  • PDF ist kein echtes PDF/A-3: WeasyPrint erzeugt grundsätzlich kein PDF/A-3 ohne Nachbearbeitung. Sie benötigen pikepdf oder ähnliche Tools. Unbearbeitete PDFs von WeasyPrint scheitern bei der PDF/A-3-Validierung.
  • Fonts nicht eingebettet: PDF/A erfordert eingebettete Schriften. optimize_size=('fonts') sorgt dafür, dass WeasyPrint das beachtet. Ohne eingebettete Fonts ist das PDF nicht archivierbar.
  • Inkonsistenz zwischen PDF und XML: Wenn Sie PDF und XML einzeln erstellen, können Beträge oder Positionen unterschiedlich sein. Das führt zu Fehlern bei der automatisierten Verarbeitung. Generieren Sie beide immer aus derselben Datenquelle.
  • Ungültige Zeichenkodierung: Immer UTF-8 verwenden, nie andere Kodierungen. Nicht-ASCII-Zeichen (Umlaute, Sonderzeichen) führen zu Fehlern, wenn nicht UTF-8 verwendet wird.
  • Datum im falschen Format: ZUGFeRD akzeptiert nur ISO 8601 (YYYY-MM-DD). Andere Formate führen zu Validierungsfehlern. Keine Variationen wie DD.MM.YYYY oder 15-JAN-2026.
  • Signatur-Sicherheitsmissverständnis: ZUGFeRD selbst beinhaltet keine digitale Signatur. Ein Rechnungsempfänger könnte theoretisch die XML im PDF nachträglich ändern. Abhilfe: Signieren Sie das PDF selbst oder versenden Sie es über sichere Kanäle. Dies ist ein Governance-Problem, nicht nur ein technisches.
  • Fehlerhafte Rechnungsnummern-Verwaltung: Wenn Sie Rechnungsnummern doppelt vergeben oder lückenhafte Nummernkreise erzeugen, sind Ihre Rechnungen ungültig. Ein striktes Nummernkreis-Management ist notwendig.
  • Fehlende Archivierungsstrategie: Generierte Rechnungen müssen unveränderbar archiviert werden. Ohne Backup-Strategie und Langzeitarchivierungssystem verstoßen Sie gegen GoBD-Anforderungen; Hinweise zum E-Rechnung-Archivieren helfen bei der Umsetzung.

Hier ist eine praktische Checkliste für die Qualitätssicherung vor dem Produktiveinsatz:

Prüfpunkt Status Kritikalität
Alle Rechnungsdaten (Beträge, Daten, Nummern) auf Format und Vollständigkeit prüfen KRITISCH
XML gegen das offizielle EN-16931-XSD-Schema validieren KRITISCH
PDF mit Mustang oder einem Verifikationstool auf PDF/A-3 prüfen KRITISCH
Testrechnung mit Testkunden tatsächlich verarbeiten lassen (technischer Testlauf) KRITISCH
Sicherstellen, dass die Datei korrekt versandt wird (E-Mail-Encoding, MIME-Type prüfen) HOCH
Empfängersystem kann die Rechnung auslesen und verarbeitet die XML-Daten korrekt HOCH
Archivierungssystem kann die Datei langfristig speichern und später wieder laden HOCH
Logging und Fehlerbehandlung sind implementiert und getestet HOCH
Backup-Strategie für generierte Rechnungen existiert HOCH
Datenschutz und Zugriffsschutz sind konfiguriert (Verschlüsselung, Berechtigungen) HOCH
Nummernkreis-Verwaltung ist eindeutig und lückenlos KRITISCH
Governance für Rechnungskorrekturen und Stornierungen definiert HOCH
Performance und Skalierbarkeit unter Last getestet (100+ Rechnungen pro Minute?) MITTEL
Notfall-Recovery-Verfahren dokumentiert (Was tun, wenn das System ausfällt?) HOCH

Wirtschaftlichkeit und Governance – Das Entscheidungskriterium

Einer der größten Fehler ist technische Euphorie ohne wirtschaftliche Bewertung. Bevor Sie sich auf eine Python-Eigenentwicklung einlassen, beantworten Sie diese Fragen ehrlich:

  • Rechnungsvolumen: Wie viele Rechnungen erzeugen Sie pro Monat? Unter 500/Monat ist eine Custom-Lösung oft wirtschaftlich unrentabel. Ab 5.000+/Monat wird Eigenentwicklung interessant. Das Rechnungsvolumen ist der Primärtreiber für die Wirtschaftlichkeit einer Eigenentwicklung.
  • Spezialanforderungen: Benötigen Sie Funktionen, die Standard-Tools nicht bieten (z. B. komplexe Rabattkaskaden, Währungskonversionen, spezielle Reporting-Anforderungen)? Diese sind oft Gründe für eine Eigenentwicklung.
  • Technische Kapazität: Haben Sie Entwickler im Haus, die eine solche Lösung bauen und über Jahre wartbar halten können? Das Risiko von Wartungsengpässen wird oft unterschätzt.
  • Entwicklungskosten: Einmalige Entwicklung + laufende Wartung + Schulung + Test-Infrastruktur können schnell 50.000 EUR übersteigen. Kann ein kommerzielles Tool (2.000–10.000 EUR/Jahr) günstiger sein? Eine detaillierte Kostenrechnung ist essenziell.
  • Compliance-Risiko: Wer trägt die Verantwortung, wenn Ihre Eigenentwicklung fehlerhaft ist und Rechnungen nicht mehr gültig sind? Das Risiko ist oft höher als erwartet. Haftungs- und Garantiefragen müssen geklärt sein.
  • Governance und Support: Ein Standard-Tool hat einen Hersteller, der Support bietet. Bei einer Eigenentwicklung sind Sie auf sich selbst gestellt – das ist sowohl ein Vorteil (Flexibilität) als auch ein Risiko (Wartung). Der Support-Aufwand muss realistisch geplant werden.
  • Skalierbarkeit: Wird Ihr Rechnungsvolumen über die Zeit wachsen? Eine Python-Lösung muss von Anfang an skalierbar designt sein, sonst wird die Wartung exponentiell teuer. Benchmarking und Load-Testing sind Anforderungen, keine Optional-Features.
  • Compliance-Updates: Wenn sich gesetzliche Anforderungen ändern (neue EN-16931-Versionen, neue Steuersätze, neue Feldanforderungen), muss der Code angepasst werden. Bei Standard-Tools passiert das automatisch, bei Eigenentwicklung fällt das auf Sie als Entwicklerteam zurück.

Die ehrliche Antwort: Für die meisten Unternehmen ist eine spezialisierte Rechnungssoftware oder ein ERP-Add-on wirtschaftlicher. Eine Python-Lösung macht Sinn, wenn Sie sehr hohe Volumina haben, spezialisierte Anforderungen oder wenn Sie bereits ein Python-basiertes Backend haben und die Integration enger erfolgen soll. Wenn Sie bei Strategie, Auswahl oder Umsetzung Unterstützung benötigen, kann eine E-Rechnung-Beratung helfen. Aber: Die Gesamtbetriebskosten (Total Cost of Ownership) einer Eigenentwicklung sind fast immer höher als auf den ersten Blick angenommen.

Zusammenfassung der Schritte

Die Erstellung von ZUGFeRD-Rechnungen mit Python folgt einem klaren Ablauf:

  1. EN 16931 verstehen: Die europäische Norm ist die Basis. Lesen Sie die Spezifikation gründlich.
  2. Datenmodell definieren: Strukturieren Sie Rechnungsinformationen in Python-Klassen und validieren Sie Eingaben früh.
  3. XML generieren: Nutzen Sie lxml mit ElementBuilder, um strukturierte Rechnungsdaten gemäß EN 16931 zu erzeugen. Namespaces sind kritisch.
  4. PDF rendern: WeasyPrint erstellt aus HTML/CSS ein PDF.
  5. PDF/A-3 finalisieren: Nutzen Sie pikepdf, um XML einzubetten und PDF/A-3-Metadaten zu setzen.
  6. Validieren: Prüfen Sie XML gegen das XSD-Schema und das Gesamt-PDF mit externen Tools.
  7. Automatisieren: Integrieren Sie den Prozess mit Fehlerbehandlung, Logging und Governance in Ihren Workflow.
  8. Archivieren: Speichern Sie jede generierte Rechnung unveränderlich für die Langzeitaufbewahrung.

Mit diesem Ansatz schaffen Sie eine zuverlässige, konforme und skalierbare Rechnungserzeugung – aber nur, wenn Sie die technische UND wirtschaftliche Seite sorgfältig planen.

Häufig gestellte Fragen

Muss ich ZUGFeRD-Rechnungen versenden?

Seit Juni 2025 gilt die Verpflichtung für Rechnungen an öffentliche Auftraggeber in Deutschland: Diese müssen im XRechnung-Format oder in kompatiblen Formaten wie ZUGFeRD übermittelt werden. Für E-Rechnung im B2B-Bereich zwischen privaten Unternehmen ist es nicht zwingend, aber zunehmend erwartet und wirtschaftlich sinnvoll. Die technische Vorbereitung ist definitiv zu empfehlen, um zukunftssicher zu sein.

Kann ich weiterhin reine PDF-Rechnungen versenden?

Ja, aber nur, wenn Sie nicht an öffentliche Auftraggeber Rechnungen stellen. ZUGFeRD ist eine hybride Form – das PDF bleibt lesbar und druckbar. Sie verlieren also keine Rückwärtskompatibilität, gewinnen aber strukturierte Daten obendrauf.

Welche Python-Versionen werden unterstützt?

lxml und WeasyPrint funktionieren mit Python 3.8+. Verwenden Sie möglichst eine aktuelle Version (3.11 oder neuer), um von Sicherheitsupdates zu profitieren und eine bessere Performance zu erreichen.

Kann ich das selbst bauen oder sollte ich ein Tool nutzen?

Das hängt stark von Ihrer Situation ab. Wenn Sie technische Ressourcen haben und hohe Automatisierungsanforderungen, ist eine Python-Lösung wartbar und flexibel. Wenn Sie keine Entwicklung im Haus haben oder das Volumen niedrig ist, sind kommerzielle Lösungen (spezialisierte Faktura-Systeme, ERP-Add-ons, LibreOffice-Erweiterungen) oft wirtschaftlicher und risikoärmer.

Wie sicher sind die erzeugten Rechnungen?

ZUGFeRD selbst beinhaltet keine digitale Signatur – das ist ein Design-Schwachpunkt: Theoretisch könnte jemand die XML im PDF nachträglich ändern, ohne dass es bemerkt wird. Abhilfe: Signieren Sie das PDF selbst digital oder versenden Sie es über sichere Kanäle. Für solche Prozesse spielt auch Informationssicherheit eine wichtige Rolle. Die Rechnungssicherheit ist in der deutschen E-Rechnungspraxis noch nicht vollständig gelöst. Das ist ein Governance- und Prozessthema, nicht nur technisch.

Können unterschiedliche ZUGFeRD-Profile (Basis, Kern, Erweiterung) mit derselben Python-Lösung erzeugt werden?

Ja, aber die XML-Struktur unterscheidet sich je nach Profil. Ein einfacheres Profil benötigt weniger Felder, ein erweitertes mehr. Ihre Python-Lösung muss über Parameter steuern können, welches Profil erzeugt wird. Für den Anfang (EN 16931-konform) reicht ein Basis- oder Kern-Profil aus.

Was passiert, wenn die Validierung fehlschlägt?

Fehlerhafte Rechnungen können von Empfängersystemen nicht automatisch verarbeitet werden. Sie landen dann manuell beim Sachbearbeiter – das soll gerade vermieden werden. Daher: Immer lokal und vollständig validieren, bevor Sie die Rechnung versenden. Nur fehlerfreie Rechnungen versenden. Implementieren Sie Fehlerbehandlung und Alerts, damit Fehler sofort bemerkt werden.

Wie lange soll ich Rechnungen archivieren?

In Deutschland beträgt die Aufbewahrungsfrist für Geschäftsunterlagen (inkl. Rechnungen) gemäß GoBD zehn Jahre. PDF/A-3 ist für diese Dauer geeignet, da das Format archivierbar ist. Sie sollten nicht nur die Dateien speichern, sondern auch Metadaten (Erstellungsdatum, Versender etc.) dokumentieren.