drafthorse

repository·master·Indexed 41 days ago

https://github.com/pretix/python-drafthorse

A low-level Python implementation of the ZUGFeRD XML format for digital invoicing in Germany. It provides tools to build ZUGFeRD data structures, generate compliant XML via Document.serialize(), parse XML using Document.parse(), and attach XML data to PDF/A-3 compliant PDF files.

Tokens
2.2K
Snippets
4
Records
4
Agent score
2%

What's inside drafthorse

  1. Generate ZUGFeRD XML files

    master

    To generate a ZUGFeRD XML file, you must build a Document object by populating its hierarchical data model (header, trade, settlement, etc.) and then call serialize().

    Document.serialize() validates the generated XML against the specified schema. If you wish to skip validation, set schema=None.

    from drafthorse.models.document import Document
    # ... populate doc object ...
    xml = doc.serialize(schema="FACTUR-X_EXTENDED")
  2. Complete ZUGFeRD generation example

    master

    This example demonstrates how to instantiate a Document, populate various sections including header, buyer/seller information, line items, and tax details, and finally serialize it to XML.

    from datetime import date, datetime, timedelta, timezone
    from decimal import Decimal
    
    from drafthorse.models.accounting import ApplicableTradeTax
    from drafthorse.models.document import Document
    from drafthorse.models.note import IncludedNote
    from drafthorse.models.party import TaxRegistration
    from drafthorse.models.payment import PaymentMeans, PaymentTerms
    from drafthorse.models.trade import AdvancePayment, IncludedTradeTax
    from drafthorse.models.tradelines import LineItem
    from drafthorse.pdf import attach_xml
    
    # Build data structure
    doc = Document()
    doc.context.guideline_parameter.id = "urn:cen.eu:en16931:2017"
    doc.header.id = "RE1337"
    doc.header.type_code = "380"
    doc.header.issue_date_time = date.today()
    
    doc.header.notes.add(IncludedNote(content="Test Note 1"))
    
    doc.trade.agreement.buyer.name = "Kunde GmbH"
    doc.trade.agreement.buyer.address.country_id = "DE"
    
    doc.trade.settlement.currency_code = "EUR"
    doc.trade.settlement.payment_means.add(PaymentMeans(type_code="ZZZ"))
    
    doc.trade.agreement.seller.name = "Lieferant GmbH"
    doc.trade.agreement.seller.address.country_id = "DE"
    doc.trade.agreement.seller.address.country_subdivision = "Bayern"
    doc.trade.agreement.seller.tax_registrations.add(
        TaxRegistration(id=("VA", "DE000000000"))
    )
    
    advance = AdvancePayment(
        received_date=datetime.now(timezone.utc), paid_amount=Decimal(42)
    )
    advance.included_trade_tax.add(
        IncludedTradeTax(
            calculated_amount=Decimal(0),
            type_code="VAT",
            category_code="E",
            rate_applicable_percent=Decimal(0),
        )
    )
    doc.trade.settlement.advance_payment.add(advance)
    
    li = LineItem()
    li.document.line_id = "1"
    li.product.name = "Rainbow"
    li.agreement.gross.amount = Decimal("1198.8")
    li.agreement.gross.basis_quantity = (Decimal("1.0000"), "C62")  # C62 == unit
    li.agreement.net.amount = Decimal("999")
    li.agreement.net.basis_quantity = (Decimal("1.0000"), "C62")  # C62 == unit
    li.delivery.billed_quantity = (Decimal("1.0000"), "C62")  # C62 == unit
    li.settlement.trade_tax.type_code = "VAT"
    li.settlement.trade_tax.category_code = "S"
    li.settlement.trade_tax.rate_applicable_percent = Decimal("20.00")
    li.settlement.monetary_summation.total_amount = Decimal("999.00")
    doc.trade.items.add(li)
    
    trade_tax = ApplicableTradeTax()
    trade_tax.calculated_amount = Decimal("199.80")
    trade_tax.basis_amount = Decimal("999.00")
    trade_tax.type_code = "VAT"
    trade_tax.category_code = "S"
    trade_tax.rate_applicable_percent = Decimal("20.00")
    doc.trade.settlement.trade_tax.add(trade_tax)
    
    doc.trade.settlement.monetary_summation.line_total = Decimal("999.00")
    doc.trade.settlement.monetary_summation.charge_total = Decimal("0.00")
    doc.trade.settlement.monetary_summation.allowance_total = Decimal("0.00")
    doc.trade.settlement.monetary_summation.tax_basis_total = Decimal("999.00")
    doc.trade.settlement.monetary_summation.tax_total = (Decimal("199.80"), "EUR")
    doc.trade.settlement.monetary_summation.grand_total = Decimal("1198.8")
    doc.trade.settlement.monetary_summation.due_amount = Decimal("1198.8")
    
    terms = PaymentTerms()
    terms.due = datetime.now(timezone.utc) + timedelta(days=30)
    doc.trade.settlement.terms.add(terms)
    
    # Generate XML file
    xml = doc.serialize(schema="FACTUR-X_EXTENDED")
  3. Attach ZUGFeRD XML to an existing PDF

    master

    Use the attach_xml function to embed the generated ZUGFeRD XML into a PDF file.

    Important: The existing PDF must be compliant with the PDF/A-3 standard for this to work correctly. The library does not parse existing PDF files; it only attaches the XML to the byte stream of a provided PDF.

    from drafthorse.pdf import attach_xml
    
    with open("input.pdf", "rb") as original_file:
        new_pdf_bytes = attach_xml(original_file.read(), xml)
    
    with open("output.pdf", "wb") as f:
        f.write(new_pdf_bytes)
  4. Parse ZUGFeRD XML files

    master

    Use Document.parse() to convert ZUGFeRD XML data into a Python object model.

    By default, the parser operates in strict mode, meaning it will raise an error if it encounters any unknown elements. To allow the parser to process as much as possible while ignoring unknown elements, set strict=False.

    from drafthorse.models.document import Document
    samplexml = open("sample.xml", "rb").read()
    doc = Document.parse(samplexml, strict=True)
    print(doc.trade.agreement.seller.name)