file. This library can be used to generate or parse the contents of this XML file as well as
attach it to a PDF. We do not support parsing PDF files (for now).
-By low-level, we mean that this library models the ZUGFeRD data model 1:1 without any further
-abstractions or simplifications. You can set and parse all parameters defined in ZUGFeRD 2.1.
+By low-level, we mean that this library tries to model the ZUGFeRD data model 1:1 without any
+further abstractions or simplifications. You can set and parse all parameters defined in ZUGFeRD
+2.2.
-All output is validated against the official XSDs, but no validation of profile levels (basic, comfort, extended) is performed.
+All output is validated against the official XSDs, but no validation of profile levels
+(basic, comfort, extended) is performed.
Usage
-----
Parsing::
- >>> from drafthorse.models.document import Document
- >>> samplexml = open("sample.xml", "rb").read()
- >>> doc = Document.parse(samplexml)
- >>> str(doc.trade.agreement.seller.name)
- 'Lieferant GmbH'
+ from drafthorse.models.document import Document
+ samplexml = open("sample.xml", "rb").read()
+ doc = Document.parse(samplexml)
+ print(doc.trade.agreement.seller.name)
Generating::
- >>> from datetime import date
- >>> from drafthorse.models.document import Document
- >>> from drafthorse.models.note import IncludedNote
-
- >>> doc = Document()
- >>> doc.context.guideline_parameter.id = "urn:ferd:CrossIndustryDocument:invoice:1p0:comfort"
- >>> doc.header.id = "RE1337"
- >>> doc.header.name = "RECHNUNG"
- >>> doc.header.type_code = "380"
- >>> doc.header.issue_date_time.value = date.today()
- >>> doc.header.languages.add("de")
- >>> note = IncludedNote()
- >>> note.content.add("Test Node 1")
- >>> doc.header.notes.add(n)
- >>> doc.trade.agreement.seller.name = "Lieferant GmbH"
-
- >>> xml = doc.serialize()
- >>> xml
- b'<?xml version="1.0" encoding="UTF-8"?><rsm:CrossIndustryDocument …'
+ import os
+ from datetime import date
+ 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.tradelines import LineItem
+ from drafthorse.pdf import attach_xml
+
+ # Build data structure
+ doc = Document()
+ doc.context.guideline_parameter.id = "urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended"
+ doc.header.id = "RE1337"
+ doc.header.type_code = "380"
+ doc.header.name = "RECHNUNG"
+ doc.header.issue_date_time = date.today()
+ doc.header.languages.add("de")
+
+ note = IncludedNote()
+ note.content.add("Test Node 1")
+ doc.header.notes.add(note)
+
+ doc.trade.agreement.seller.name = "Lieferant GmbH"
+ doc.trade.settlement.payee.name = "Kunde GmbH"
+
+ doc.trade.agreement.buyer.name = "Kunde GmbH"
+ doc.trade.settlement.invoicee.name = "Kunde GmbH"
+
+ doc.trade.settlement.currency_code = "EUR"
+ doc.trade.settlement.payment_means.type_code = "ZZZ"
+
+ li = LineItem()
+ li.document.line_id = "1"
+ li.product.name = "Rainbow"
+ li.agreement.gross.amount = Decimal("999.00")
+ li.agreement.gross.basis_quantity = (Decimal("1.0000"), "C62") # C62 == pieces
+ li.agreement.net.amount = Decimal("999.00")
+ li.agreement.net.basis_quantity = (Decimal("999.00"), "EUR")
+ li.delivery.billed_quantity = (Decimal("1.0000"), "C62") # C62 == pieces
+ li.settlement.trade_tax.type_code = "VAT"
+ li.settlement.trade_tax.category_code = "E"
+ li.settlement.trade_tax.rate_applicable_percent = Decimal("0.00")
+ li.settlement.monetary_summation.total_amount = Decimal("999.00")
+ doc.trade.items.add(li)
+
+ trade_tax = ApplicableTradeTax()
+ trade_tax.calculated_amount = Decimal("0.00")
+ trade_tax.basis_amount = Decimal("999.00")
+ trade_tax.type_code = "VAT"
+ trade_tax.category_code = "E"
+ trade_tax.rate_applicable_percent = Decimal("0.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("0.00")
+ doc.trade.settlement.monetary_summation.grand_total = Decimal("999.00")
+ doc.trade.settlement.monetary_summation.due_amount = Decimal("999.00")
+
+ # Generate XML file
+ xml = doc.serialize(schema="FACTUR-X_EXTENDED")
# Attach XML to an existing PDF.
# Note that the existing PDF should be compliant to PDF/A-3!
# You can validate this here: https://www.pdf-online.com/osa/validate.aspx
- >>> new_pdf_bytes = attach_xml(original_pdf_bytes, xml, 'BASIC')
+ with open("input.pdf", "rb") as original_file:
+ new_pdf_bytes = attach_xml(original_file.read(), xml, 'EXTENDED')
+
+ with open("output.pdf", "wb") as f:
+ f.write(new_pdf_bytes)
Development
--- /dev/null
+import os
+from datetime import date
+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.tradelines import LineItem
+from drafthorse.pdf import attach_xml
+
+
+def test_readme_construction_example():
+ doc = Document()
+ doc.context.guideline_parameter.id = "urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended"
+ doc.header.id = "RE1337"
+ doc.header.type_code = "380"
+ doc.header.name = "RECHNUNG"
+ doc.header.issue_date_time = date.today()
+ doc.header.languages.add("de")
+
+ note = IncludedNote()
+ note.content.add("Test Node 1")
+ doc.header.notes.add(note)
+
+ doc.trade.agreement.seller.name = "Lieferant GmbH"
+ doc.trade.settlement.payee.name = "Kunde GmbH"
+
+ doc.trade.agreement.buyer.name = "Kunde GmbH"
+ doc.trade.settlement.invoicee.name = "Kunde GmbH"
+
+ doc.trade.settlement.currency_code = "EUR"
+ doc.trade.settlement.payment_means.type_code = "ZZZ"
+
+ li = LineItem()
+ li.document.line_id = "1"
+ li.product.name = "Rainbow"
+ li.agreement.gross.amount = Decimal("999.00")
+ li.agreement.gross.basis_quantity = (Decimal("1.0000"), "C62") # C62 == pieces
+ li.agreement.net.amount = Decimal("999.00")
+ li.agreement.net.basis_quantity = (Decimal("999.00"), "EUR")
+ li.delivery.billed_quantity = (Decimal("1.0000"), "C62") # C62 == pieces
+ li.settlement.trade_tax.type_code = "VAT"
+ li.settlement.trade_tax.category_code = "E"
+ li.settlement.trade_tax.rate_applicable_percent = Decimal("0.00")
+ li.settlement.monetary_summation.total_amount = Decimal("999.00")
+ doc.trade.items.add(li)
+
+ trade_tax = ApplicableTradeTax()
+ trade_tax.calculated_amount = Decimal("0.00")
+ trade_tax.basis_amount = Decimal("999.00")
+ trade_tax.type_code = "VAT"
+ trade_tax.category_code = "E"
+ trade_tax.rate_applicable_percent = Decimal("0.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("0.00")
+ doc.trade.settlement.monetary_summation.grand_total = Decimal("999.00")
+ doc.trade.settlement.monetary_summation.due_amount = Decimal("999.00")
+
+ xml = doc.serialize(schema="FACTUR-X_EXTENDED")
+ with open(os.path.join(os.path.dirname(__file__), "samples", "Empty.pdf"), "rb") as original_file:
+ assert attach_xml(original_file.read(), xml, 'EXTENDED')