]> git.ipfire.org Git - thirdparty/python-drafthorse.git/commitdiff
SImple roundtrip test
authorRaphael Michel <mail@raphaelmichel.de>
Mon, 15 Oct 2018 21:50:42 +0000 (23:50 +0200)
committerRaphael Michel <mail@raphaelmichel.de>
Mon, 15 Oct 2018 21:50:42 +0000 (23:50 +0200)
demo.py
drafthorse/models/accounting.py
drafthorse/models/document.py
drafthorse/models/elements.py
drafthorse/models/product.py
drafthorse/utils.py
sample.xml [deleted file]
tests/samples/easybill_sample.xml [new file with mode: 0644]
tests/test_roundtrip.py [new file with mode: 0644]

diff --git a/demo.py b/demo.py
index d7fe3b63a425d9f18379f9aae2ae6099502d8f6d..ba71c00699f5f560f6a23fb8bc833a6d2f45834b 100644 (file)
--- a/demo.py
+++ b/demo.py
@@ -3,6 +3,7 @@ from datetime import date
 from drafthorse.models.document import Document, IncludedNote
 from drafthorse.utils import prettify
 
+"""
 doc = Document()
 doc.context.guideline_parameter.id = "urn:ferd:CrossIndustryDocument:invoice:1p0:comfort"
 doc.header.id = "RE1337"
@@ -16,12 +17,8 @@ n.content.add("Test Node 2")
 doc.header.notes.add(n)
 
 doc.trade.agreement.seller.name = "Lieferant GmbH"
-
-print(prettify(doc.serialize()))
+"""
 
 samplexml = open("sample.xml", "rb").read()
 doc = Document.parse(samplexml)
-
-print(doc.header.name)
-print(doc.trade.agreement.seller.name)
-print([str(a.content) for a in doc.header.notes.children])
\ No newline at end of file
+print(prettify(doc.serialize()))
index 56a452cf0ece18d764ae521143dac6ef19f36b0b..4ce17b7c6917a0f1791484a1e67a607ad7815ffc 100644 (file)
@@ -21,7 +21,13 @@ class LineApplicableTradeTax(Element):
         tag = "ApplicableTradeTax"
 
 
-class ApplicableTradeTax(LineApplicableTradeTax):
+class ApplicableTradeTax(Element):
+    calculated_amount = CurrencyField(NS_RAM, "CalculatedAmount", required=True,
+                                      profile=BASIC, _d="Steuerbetrag")
+    type_code = StringField(NS_RAM, "TypeCode", required=True, profile=BASIC,
+                            _d="Steuerart (Code)")
+    exemption_reason = StringField(NS_RAM, "ExemptionReason", required=False,
+                                   profile=COMFORT, _d="Grund der Steuerbefreiung (Freitext)")
     basis_amount = CurrencyField(NS_RAM, "BasisAmount", required=True,
                                  profile=BASIC, _d="Basisbetrag der Steuerberechnung")
     line_total_basis_amount = CurrencyField(NS_RAM, "LineTotalBasisAmount",
@@ -30,6 +36,10 @@ class ApplicableTradeTax(LineApplicableTradeTax):
     allowance_charge_basis_amount = CurrencyField(NS_RAM, "AllowanceChargeBasisAmount",
                                                   required=False, profile=EXTENDED,
                                                   _d="Gesamtbetrag Zu- und Abschläge des Steuersatzes")
+    category_code = StringField(NS_RAM, "CategoryCode", required=False,
+                                profile=COMFORT, _d="Steuerkategorie (Wert)")
+    applicable_percent = DecimalField(NS_RAM, "ApplicablePercent",
+                                      required=True, profile=BASIC)
 
     class Meta:
         namespace = NS_RAM
index 7aa411346debaab621717e53ec5413b8ec1296eb..c1c5b08c1fb54959e26bb99876414244245f2594 100644 (file)
@@ -22,16 +22,16 @@ class BusinessDocumentContextParameter(Element):
 
     class Meta:
         namespace = NS_RAM
-        tag = "BusinessSpecifiedDocumentContextParameter"
+        tag = "BusinessProcessSpecifiedDocumentContextParameter"
 
 
 class DocumentContext(Element):
     test_indicator = IndicatorField(NS_RAM, "TestIndicator", required=False,
                                     profile=BASIC, _d="Testkennzeichen")
-    guideline_parameter = Field(GuidelineDocumentContextParameter, required=True,
-                                profile=BASIC, _d="Anwendungsempfehlung")
     business_parameter = Field(BusinessDocumentContextParameter, required=False,
                                profile=EXTENDED, _d="Geschäftsprozess, Wert")
+    guideline_parameter = Field(GuidelineDocumentContextParameter, required=True,
+                                profile=BASIC, _d="Anwendungsempfehlung")
 
     class Meta:
         namespace = NS_FERD_1p0
index 2d9101f2ac9d48ad4751b927bf1a6b77e904b59e..c7fc2c22ac14c3c51157d387c99e9efde3c06776 100644 (file)
@@ -43,7 +43,7 @@ class Element(metaclass=BaseElementMeta):
 
     def to_etree(self):
         node = self._etree_node()
-        for v in self._data.values():
+        for k, v in self._data.items():
             if v is not None:
                 v.append_to(node)
         return node
@@ -51,13 +51,14 @@ class Element(metaclass=BaseElementMeta):
     def get_tag(self):
         return "{%s}%s" % (self.Meta.namespace, self.Meta.tag)
 
-    def append_to(self, node):
-        node.append(self.to_etree())
+    def append_to(self, node, required=False):
+        el = self.to_etree()
+        if required or list(el) or el.text:
+            node.append(el)
 
     def serialize(self):
         xml = b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + ET.tostring(self.to_etree(), "utf-8")
-        validate_xml(xmlout=xml, schema="ZUGFeRD1p0")
-        return xml
+        return validate_xml(xmlout=xml, schema="ZUGFeRD1p0")
 
     def from_etree(self, root):
         if hasattr(self, 'Meta') and hasattr(self.Meta, 'namespace') and root.tag != "{%s}%s" % (self.Meta.namespace, self.Meta.tag):
@@ -108,13 +109,13 @@ class StringElement(Element):
 
 
 class DecimalElement(StringElement):
-    def __init__(self, namespace, tag, value=0):
+    def __init__(self, namespace, tag, value=None):
         super().__init__(namespace, tag)
         self.value = value
 
     def to_etree(self):
         node = self._etree_node()
-        node.text = str(self.value)
+        node.text = str(self.value) if self.value is not None else ""
         return node
 
     def __str__(self):
@@ -263,7 +264,7 @@ class DateTimeElement(StringElement):
 
 
 class IndicatorElement(StringElement):
-    def __init__(self, namespace, tag, value=None):
+    def __init__(self, namespace, tag, value=False):
         super().__init__(namespace, tag)
         self.value = value
 
@@ -273,8 +274,9 @@ class IndicatorElement(StringElement):
     def to_etree(self):
         t = self._etree_node()
         node = ET.Element("{%s}%s" % (NS_UDT, "Indicator"))
-        node.text = self.value
+        node.text = str(self.value).lower()
         t.append(node)
+        return t
 
     def __str__(self):
         return "{}".format(self.value)
index d93d05338454010e6af701ec707b0f870a31ce12..1617c0e3d43332a8779938e57509db49c5e529c7 100644 (file)
@@ -53,13 +53,13 @@ class ReferencedProduct(Element):
 
 
 class TradeProduct(Element):
-    name = StringField(NS_RAM, "Name", required=False, profile=COMFORT)
-    description = StringField(NS_RAM, "Description", required=False, profile=COMFORT)
     global_id = IDField(NS_RAM, "GlobalID", required=False, profile=COMFORT)
     seller_assigned_id = StringField(NS_RAM, "SellerAssignedID", required=False,
                                      profile=COMFORT)
     buyer_assigned_id = StringField(NS_RAM, "BuyerAssignedID", required=False,
                                     profile=COMFORT)
+    name = StringField(NS_RAM, "Name", required=False, profile=COMFORT)
+    description = StringField(NS_RAM, "Description", required=False, profile=COMFORT)
     characteristics = MultiField(ProductCharacteristic, required=False, profile=EXTENDED)
     classifications = MultiField(ProductClassification, required=False, profile=EXTENDED)
     origins = MultiField(OriginCountry, required=False, profile=EXTENDED)
index 4ad6e3d38b597cab1bc23b428a2e43218d6d3058..085ea2f562ffcba5e52cdcfbb805859c25c3c1e8 100644 (file)
@@ -5,9 +5,26 @@ from xml.dom import minidom
 logger = logging.getLogger("drafthorse")
 
 
+def minify(xml):
+    try:
+        from lxml import etree
+        return b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + etree.tostring(etree.fromstring(xml))
+    except ImportError:
+        logger.warning("Could not minify output as LXML is not installed.")
+        return xml
+
+
 def prettify(xml):
-    reparsed = minidom.parseString(xml)
-    return reparsed.toprettyxml(indent="\t")
+    try:
+        from lxml import etree
+    except ImportError:
+        reparsed = minidom.parseString(xml)
+        return reparsed.toprettyxml(indent="\t")
+    else:
+        parser = etree.XMLParser(remove_blank_text=True)
+        return b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + etree.tostring(
+            etree.fromstring(xml, parser), pretty_print=True
+        )
 
 
 def validate_xml(xmlout, schema):
diff --git a/sample.xml b/sample.xml
deleted file mode 100644 (file)
index 9705d8d..0000000
+++ /dev/null
@@ -1,157 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<rsm:CrossIndustryDocument xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-                           xmlns:rsm="urn:ferd:CrossIndustryDocument:invoice:1p0"
-                           xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:12"
-                           xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:15">
-    <rsm:SpecifiedExchangedDocumentContext>
-        <ram:GuidelineSpecifiedDocumentContextParameter>
-            <ram:ID>urn:ferd:CrossIndustryDocument:invoice:1p0:comfort</ram:ID>
-        </ram:GuidelineSpecifiedDocumentContextParameter>
-    </rsm:SpecifiedExchangedDocumentContext>
-    <rsm:HeaderExchangedDocument>
-        <ram:ID>RE1337</ram:ID>
-        <ram:Name>RECHNUNG</ram:Name>
-        <ram:TypeCode>380</ram:TypeCode>
-        <ram:IssueDateTime>
-            <udt:DateTimeString format="102">20130305</udt:DateTimeString>
-        </ram:IssueDateTime>
-        <ram:IncludedNote>
-            <ram:Content>Test Node 1</ram:Content>
-        </ram:IncludedNote>
-        <ram:IncludedNote>
-            <ram:Content>Test Node 2</ram:Content>
-        </ram:IncludedNote>
-        <ram:IncludedNote>
-            <ram:Content>easybill GmbH
-                Düsselstr. 21
-                41564 Kaarst
-
-                Geschäftsführer:
-                Christian Szardenings
-                Ronny Keyser
-            </ram:Content>
-            <ram:SubjectCode>REG</ram:SubjectCode>
-        </ram:IncludedNote>
-    </rsm:HeaderExchangedDocument>
-    <rsm:SpecifiedSupplyChainTradeTransaction>
-        <ram:ApplicableSupplyChainTradeAgreement>
-            <ram:SellerTradeParty>
-                <ram:Name>Lieferant GmbH</ram:Name>
-                <ram:PostalTradeAddress>
-                    <ram:PostcodeCode>80333</ram:PostcodeCode>
-                    <ram:LineOne>Lieferantenstraße 20</ram:LineOne>
-                    <ram:CityName>München</ram:CityName>
-                    <ram:CountryID>DE</ram:CountryID>
-                </ram:PostalTradeAddress>
-                <ram:SpecifiedTaxRegistration>
-                    <ram:ID schemeID="FC">201/113/40209</ram:ID>
-                </ram:SpecifiedTaxRegistration>
-                <ram:SpecifiedTaxRegistration>
-                    <ram:ID schemeID="VA">DE123456789</ram:ID>
-                </ram:SpecifiedTaxRegistration>
-            </ram:SellerTradeParty>
-            <ram:BuyerTradeParty>
-                <ram:Name>Kunden AG Mitte</ram:Name>
-                <ram:PostalTradeAddress>
-                    <ram:PostcodeCode>69876</ram:PostcodeCode>
-                    <ram:LineOne>Hans Muster</ram:LineOne>
-                    <ram:LineTwo>Kundenstraße 15</ram:LineTwo>
-                    <ram:CityName>Frankfurt</ram:CityName>
-                    <ram:CountryID>DE</ram:CountryID>
-                </ram:PostalTradeAddress>
-            </ram:BuyerTradeParty>
-        </ram:ApplicableSupplyChainTradeAgreement>
-        <ram:ApplicableSupplyChainTradeDelivery>
-            <ram:ActualDeliverySupplyChainEvent>
-                <ram:OccurrenceDateTime>
-                    <udt:DateTimeString format="102">20130305</udt:DateTimeString>
-                </ram:OccurrenceDateTime>
-            </ram:ActualDeliverySupplyChainEvent>
-        </ram:ApplicableSupplyChainTradeDelivery>
-        <ram:ApplicableSupplyChainTradeSettlement>
-            <ram:PaymentReference>2013-471102</ram:PaymentReference>
-            <ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>
-            <ram:SpecifiedTradeSettlementPaymentMeans>
-                <ram:TypeCode>31</ram:TypeCode>
-                <ram:Information>Überweisung</ram:Information>
-                <ram:PayeePartyCreditorFinancialAccount>
-                    <ram:IBANID>DE08700901001234567890</ram:IBANID>
-                    <ram:AccountName></ram:AccountName>
-                    <ram:ProprietaryID></ram:ProprietaryID>
-                </ram:PayeePartyCreditorFinancialAccount>
-                <ram:PayeeSpecifiedCreditorFinancialInstitution>
-                    <ram:BICID>GENODEF1M04</ram:BICID>
-                    <ram:GermanBankleitzahlID></ram:GermanBankleitzahlID>
-                    <ram:Name></ram:Name>
-                </ram:PayeeSpecifiedCreditorFinancialInstitution>
-            </ram:SpecifiedTradeSettlementPaymentMeans>
-            <ram:ApplicableTradeTax>
-                <ram:CalculatedAmount currencyID="EUR">19.25</ram:CalculatedAmount>
-                <ram:TypeCode>VAT</ram:TypeCode>
-                <ram:BasisAmount currencyID="EUR">275.00</ram:BasisAmount>
-                <ram:ApplicablePercent>7.00</ram:ApplicablePercent>
-            </ram:ApplicableTradeTax>
-            <ram:ApplicableTradeTax>
-                <ram:CalculatedAmount currencyID="EUR">37.62</ram:CalculatedAmount>
-                <ram:TypeCode>VAT</ram:TypeCode>
-                <ram:BasisAmount currencyID="EUR">198.00</ram:BasisAmount>
-                <ram:ApplicablePercent>19.00</ram:ApplicablePercent>
-            </ram:ApplicableTradeTax>
-            <ram:SpecifiedTradePaymentTerms>
-                <ram:Description>Zahlbar innerhalb von 20 Tagen (bis zum 05.10.2016) unter Abzug von 3% Skonto
-                    (Zahlungsbetrag = 1.766,03 €). Bis zum 29.09.2016 ohne Abzug.
-                </ram:Description>
-                <ram:DueDateDateTime>
-                    <udt:DateTimeString format="102">20130404</udt:DateTimeString>
-                </ram:DueDateDateTime>
-            </ram:SpecifiedTradePaymentTerms>
-            <ram:SpecifiedTradeSettlementMonetarySummation>
-                <ram:LineTotalAmount currencyID="EUR">198.00</ram:LineTotalAmount>
-                <ram:ChargeTotalAmount currencyID="EUR">0.00</ram:ChargeTotalAmount>
-                <ram:AllowanceTotalAmount currencyID="EUR">0.00</ram:AllowanceTotalAmount>
-                <ram:TaxBasisTotalAmount currencyID="EUR">198.00</ram:TaxBasisTotalAmount>
-                <ram:TaxTotalAmount currencyID="EUR">37.62</ram:TaxTotalAmount>
-                <ram:GrandTotalAmount currencyID="EUR">235.62</ram:GrandTotalAmount>
-            </ram:SpecifiedTradeSettlementMonetarySummation>
-        </ram:ApplicableSupplyChainTradeSettlement>
-        <ram:IncludedSupplyChainTradeLineItem>
-            <ram:AssociatedDocumentLineDocument>
-                <ram:LineID>1</ram:LineID>
-                <ram:IncludedNote>
-                    <ram:Content>Testcontent in einem LineDocument</ram:Content>
-                </ram:IncludedNote>
-            </ram:AssociatedDocumentLineDocument>
-            <ram:SpecifiedSupplyChainTradeAgreement>
-                <ram:GrossPriceProductTradePrice>
-                    <ram:ChargeAmount currencyID="EUR">9.9000</ram:ChargeAmount>
-                    <ram:AppliedTradeAllowanceCharge>
-                        <ram:ChargeIndicator>
-                            <udt:Indicator>false</udt:Indicator>
-                        </ram:ChargeIndicator>
-                        <ram:ActualAmount currencyID="EUR">1.8000</ram:ActualAmount>
-                    </ram:AppliedTradeAllowanceCharge>
-                </ram:GrossPriceProductTradePrice>
-                <ram:NetPriceProductTradePrice>
-                    <ram:ChargeAmount currencyID="EUR">9.9000</ram:ChargeAmount>
-                </ram:NetPriceProductTradePrice>
-            </ram:SpecifiedSupplyChainTradeAgreement>
-            <ram:SpecifiedSupplyChainTradeDelivery>
-                <ram:BilledQuantity unitCode="C62">20.0000</ram:BilledQuantity>
-            </ram:SpecifiedSupplyChainTradeDelivery>
-            <ram:SpecifiedSupplyChainTradeSettlement>
-                <ram:ApplicableTradeTax>
-                    <ram:TypeCode>VAT</ram:TypeCode>
-                    <ram:CategoryCode>S</ram:CategoryCode>
-                    <ram:ApplicablePercent>19.00</ram:ApplicablePercent>
-                </ram:ApplicableTradeTax>
-                <ram:SpecifiedTradeSettlementMonetarySummation>
-                    <ram:LineTotalAmount currencyID="EUR">198.00</ram:LineTotalAmount>
-                </ram:SpecifiedTradeSettlementMonetarySummation>
-            </ram:SpecifiedSupplyChainTradeSettlement>
-            <ram:SpecifiedTradeProduct>
-                <ram:SellerAssignedID>TB100A4</ram:SellerAssignedID>
-                <ram:Name>Trennblätter A4</ram:Name>
-            </ram:SpecifiedTradeProduct>
-        </ram:IncludedSupplyChainTradeLineItem>
-    </rsm:SpecifiedSupplyChainTradeTransaction>
-</rsm:CrossIndustryDocument>
\ No newline at end of file
diff --git a/tests/samples/easybill_sample.xml b/tests/samples/easybill_sample.xml
new file mode 100644 (file)
index 0000000..d64bb8f
--- /dev/null
@@ -0,0 +1,157 @@
+<?xml version="1.0" ?>
+<rsm:CrossIndustryDocument xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:12" xmlns:rsm="urn:ferd:CrossIndustryDocument:invoice:1p0" xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:15">
+       <rsm:SpecifiedExchangedDocumentContext>
+               <ram:TestIndicator>
+                       <udt:Indicator>false</udt:Indicator>
+               </ram:TestIndicator>
+               <ram:GuidelineSpecifiedDocumentContextParameter>
+                       <ram:ID>urn:ferd:CrossIndustryDocument:invoice:1p0:comfort</ram:ID>
+               </ram:GuidelineSpecifiedDocumentContextParameter>
+       </rsm:SpecifiedExchangedDocumentContext>
+       <rsm:HeaderExchangedDocument>
+               <ram:ID>RE1337</ram:ID>
+               <ram:Name>RECHNUNG</ram:Name>
+               <ram:TypeCode>380</ram:TypeCode>
+               <ram:IssueDateTime>
+                       <udt:DateTimeString format="102">20130305</udt:DateTimeString>
+               </ram:IssueDateTime>
+               <ram:CopyIndicator>
+                       <udt:Indicator>false</udt:Indicator>
+               </ram:CopyIndicator>
+               <ram:IncludedNote>
+                       <ram:Content>Test Node 1</ram:Content>
+               </ram:IncludedNote>
+               <ram:IncludedNote>
+                       <ram:Content>Test Node 2</ram:Content>
+               </ram:IncludedNote>
+               <ram:IncludedNote>
+                       <ram:Content>easybill GmbH
+                Düsselstr. 21
+                41564 Kaarst
+
+                Geschäftsführer:
+                Christian Szardenings
+                Ronny Keyser
+            </ram:Content>
+                       <ram:SubjectCode>REG</ram:SubjectCode>
+               </ram:IncludedNote>
+       </rsm:HeaderExchangedDocument>
+       <rsm:SpecifiedSupplyChainTradeTransaction>
+               <ram:ApplicableSupplyChainTradeAgreement>
+                       <ram:SellerTradeParty>
+                               <ram:Name>Lieferant GmbH</ram:Name>
+                               <ram:PostalTradeAddress>
+                                       <ram:PostcodeCode>80333</ram:PostcodeCode>
+                                       <ram:LineOne>Lieferantenstraße 20</ram:LineOne>
+                                       <ram:CityName>München</ram:CityName>
+                                       <ram:CountryID>DE</ram:CountryID>
+                               </ram:PostalTradeAddress>
+                               <ram:SpecifiedTaxRegistration>
+                                       <ram:ID schemeID="FC">201/113/40209</ram:ID>
+                               </ram:SpecifiedTaxRegistration>
+                               <ram:SpecifiedTaxRegistration>
+                                       <ram:ID schemeID="VA">DE123456789</ram:ID>
+                               </ram:SpecifiedTaxRegistration>
+                       </ram:SellerTradeParty>
+                       <ram:BuyerTradeParty>
+                               <ram:Name>Kunden AG Mitte</ram:Name>
+                               <ram:PostalTradeAddress>
+                                       <ram:PostcodeCode>69876</ram:PostcodeCode>
+                                       <ram:LineOne>Hans Muster</ram:LineOne>
+                                       <ram:LineTwo>Kundenstraße 15</ram:LineTwo>
+                                       <ram:CityName>Frankfurt</ram:CityName>
+                                       <ram:CountryID>DE</ram:CountryID>
+                               </ram:PostalTradeAddress>
+                       </ram:BuyerTradeParty>
+               </ram:ApplicableSupplyChainTradeAgreement>
+               <ram:ApplicableSupplyChainTradeDelivery>
+                       <ram:ActualDeliverySupplyChainEvent>
+                               <ram:OccurrenceDateTime>
+                                       <udt:DateTimeString format="102">20130305</udt:DateTimeString>
+                               </ram:OccurrenceDateTime>
+                       </ram:ActualDeliverySupplyChainEvent>
+               </ram:ApplicableSupplyChainTradeDelivery>
+               <ram:ApplicableSupplyChainTradeSettlement>
+                       <ram:PaymentReference>2013-471102</ram:PaymentReference>
+                       <ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>
+                       <ram:SpecifiedTradeSettlementPaymentMeans>
+                               <ram:TypeCode>31</ram:TypeCode>
+                               <ram:Information>Überweisung</ram:Information>
+                               <ram:PayeePartyCreditorFinancialAccount>
+                                       <ram:IBANID>DE08700901001234567890</ram:IBANID>
+                               </ram:PayeePartyCreditorFinancialAccount>
+                               <ram:PayeeSpecifiedCreditorFinancialInstitution>
+                                       <ram:BICID>GENODEF1M04</ram:BICID>
+                               </ram:PayeeSpecifiedCreditorFinancialInstitution>
+                       </ram:SpecifiedTradeSettlementPaymentMeans>
+                       <ram:ApplicableTradeTax>
+                               <ram:CalculatedAmount currencyID="EUR">19.25</ram:CalculatedAmount>
+                               <ram:TypeCode>VAT</ram:TypeCode>
+                               <ram:BasisAmount currencyID="EUR">275.00</ram:BasisAmount>
+                               <ram:ApplicablePercent>7.00</ram:ApplicablePercent>
+                       </ram:ApplicableTradeTax>
+                       <ram:ApplicableTradeTax>
+                               <ram:CalculatedAmount currencyID="EUR">37.62</ram:CalculatedAmount>
+                               <ram:TypeCode>VAT</ram:TypeCode>
+                               <ram:BasisAmount currencyID="EUR">198.00</ram:BasisAmount>
+                               <ram:ApplicablePercent>19.00</ram:ApplicablePercent>
+                       </ram:ApplicableTradeTax>
+                       <ram:SpecifiedTradePaymentTerms>
+                               <ram:Description>Zahlbar innerhalb von 20 Tagen (bis zum 05.10.2016) unter Abzug von 3% Skonto
+                    (Zahlungsbetrag = 1.766,03 €). Bis zum 29.09.2016 ohne Abzug.
+                </ram:Description>
+                               <ram:DueDateDateTime>
+                                       <udt:DateTimeString format="102">20130404</udt:DateTimeString>
+                               </ram:DueDateDateTime>
+                       </ram:SpecifiedTradePaymentTerms>
+                       <ram:SpecifiedTradeSettlementMonetarySummation>
+                               <ram:LineTotalAmount currencyID="EUR">198.00</ram:LineTotalAmount>
+                               <ram:ChargeTotalAmount currencyID="EUR">0.00</ram:ChargeTotalAmount>
+                               <ram:AllowanceTotalAmount currencyID="EUR">0.00</ram:AllowanceTotalAmount>
+                               <ram:TaxBasisTotalAmount currencyID="EUR">198.00</ram:TaxBasisTotalAmount>
+                               <ram:TaxTotalAmount currencyID="EUR">37.62</ram:TaxTotalAmount>
+                               <ram:GrandTotalAmount currencyID="EUR">235.62</ram:GrandTotalAmount>
+                       </ram:SpecifiedTradeSettlementMonetarySummation>
+               </ram:ApplicableSupplyChainTradeSettlement>
+               <ram:IncludedSupplyChainTradeLineItem>
+                       <ram:AssociatedDocumentLineDocument>
+                               <ram:LineID>1</ram:LineID>
+                               <ram:IncludedNote>
+                                       <ram:Content>Testcontent in einem LineDocument</ram:Content>
+                               </ram:IncludedNote>
+                       </ram:AssociatedDocumentLineDocument>
+                       <ram:SpecifiedSupplyChainTradeAgreement>
+                               <ram:GrossPriceProductTradePrice>
+                                       <ram:ChargeAmount currencyID="EUR">9.9000</ram:ChargeAmount>
+                                       <ram:AppliedTradeAllowanceCharge>
+                                               <ram:ChargeIndicator>
+                                                       <udt:Indicator>false</udt:Indicator>
+                                               </ram:ChargeIndicator>
+                                               <ram:ActualAmount currencyID="EUR">1.8000</ram:ActualAmount>
+                                       </ram:AppliedTradeAllowanceCharge>
+                               </ram:GrossPriceProductTradePrice>
+                               <ram:NetPriceProductTradePrice>
+                                       <ram:ChargeAmount currencyID="EUR">9.9000</ram:ChargeAmount>
+                               </ram:NetPriceProductTradePrice>
+                       </ram:SpecifiedSupplyChainTradeAgreement>
+                       <ram:SpecifiedSupplyChainTradeDelivery>
+                               <ram:BilledQuantity unitCode="C62">20.0000</ram:BilledQuantity>
+                       </ram:SpecifiedSupplyChainTradeDelivery>
+                       <ram:SpecifiedSupplyChainTradeSettlement>
+                               <ram:ApplicableTradeTax>
+                                       <ram:TypeCode>VAT</ram:TypeCode>
+                                       <ram:CategoryCode>S</ram:CategoryCode>
+                                       <ram:ApplicablePercent>19.00</ram:ApplicablePercent>
+                               </ram:ApplicableTradeTax>
+                               <ram:SpecifiedTradeSettlementMonetarySummation>
+                                       <ram:LineTotalAmount currencyID="EUR">198.00</ram:LineTotalAmount>
+                               </ram:SpecifiedTradeSettlementMonetarySummation>
+                       </ram:SpecifiedSupplyChainTradeSettlement>
+                       <ram:SpecifiedTradeProduct>
+                               <ram:SellerAssignedID>TB100A4</ram:SellerAssignedID>
+                               <ram:Name>Trennblätter A4</ram:Name>
+                       </ram:SpecifiedTradeProduct>
+               </ram:IncludedSupplyChainTradeLineItem>
+       </rsm:SpecifiedSupplyChainTradeTransaction>
+</rsm:CrossIndustryDocument>
+
diff --git a/tests/test_roundtrip.py b/tests/test_roundtrip.py
new file mode 100644 (file)
index 0000000..c4bb25f
--- /dev/null
@@ -0,0 +1,18 @@
+import os
+
+import pytest
+
+from drafthorse.models.document import Document
+from drafthorse.utils import prettify
+
+samples = [
+    'easybill_sample.xml',
+]
+
+
+@pytest.mark.parametrize("filename", samples)
+def test_sample_roundtrip(filename):
+    origxml = prettify(open(os.path.join(os.path.dirname(__file__), 'samples', filename), 'r').read())
+    doc = Document.parse(origxml)
+    generatedxml = prettify(doc.serialize())
+    assert generatedxml.decode().strip() == origxml.decode().strip()
\ No newline at end of file