From: Raphael Michel Date: Mon, 15 Oct 2018 21:50:42 +0000 (+0200) Subject: SImple roundtrip test X-Git-Tag: 1.0.0~19 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d43afaf515802dc4db3c958f4ebaadcbdc33177a;p=thirdparty%2Fpython-drafthorse.git SImple roundtrip test --- diff --git a/demo.py b/demo.py index d7fe3b6..ba71c00 100644 --- 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())) diff --git a/drafthorse/models/accounting.py b/drafthorse/models/accounting.py index 56a452c..4ce17b7 100644 --- a/drafthorse/models/accounting.py +++ b/drafthorse/models/accounting.py @@ -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 diff --git a/drafthorse/models/document.py b/drafthorse/models/document.py index 7aa4113..c1c5b08 100644 --- a/drafthorse/models/document.py +++ b/drafthorse/models/document.py @@ -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 diff --git a/drafthorse/models/elements.py b/drafthorse/models/elements.py index 2d9101f..c7fc2c2 100644 --- a/drafthorse/models/elements.py +++ b/drafthorse/models/elements.py @@ -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"" + 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) diff --git a/drafthorse/models/product.py b/drafthorse/models/product.py index d93d053..1617c0e 100644 --- a/drafthorse/models/product.py +++ b/drafthorse/models/product.py @@ -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) diff --git a/drafthorse/utils.py b/drafthorse/utils.py index 4ad6e3d..085ea2f 100644 --- a/drafthorse/utils.py +++ b/drafthorse/utils.py @@ -5,9 +5,26 @@ from xml.dom import minidom logger = logging.getLogger("drafthorse") +def minify(xml): + try: + from lxml import etree + return b"" + 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"" + 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 index 9705d8d..0000000 --- a/sample.xml +++ /dev/null @@ -1,157 +0,0 @@ - - - - - urn:ferd:CrossIndustryDocument:invoice:1p0:comfort - - - - RE1337 - RECHNUNG - 380 - - 20130305 - - - Test Node 1 - - - Test Node 2 - - - easybill GmbH - Düsselstr. 21 - 41564 Kaarst - - Geschäftsführer: - Christian Szardenings - Ronny Keyser - - REG - - - - - - Lieferant GmbH - - 80333 - Lieferantenstraße 20 - München - DE - - - 201/113/40209 - - - DE123456789 - - - - Kunden AG Mitte - - 69876 - Hans Muster - Kundenstraße 15 - Frankfurt - DE - - - - - - - 20130305 - - - - - 2013-471102 - EUR - - 31 - Überweisung - - DE08700901001234567890 - - - - - GENODEF1M04 - - - - - - 19.25 - VAT - 275.00 - 7.00 - - - 37.62 - VAT - 198.00 - 19.00 - - - 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. - - - 20130404 - - - - 198.00 - 0.00 - 0.00 - 198.00 - 37.62 - 235.62 - - - - - 1 - - Testcontent in einem LineDocument - - - - - 9.9000 - - - false - - 1.8000 - - - - 9.9000 - - - - 20.0000 - - - - VAT - S - 19.00 - - - 198.00 - - - - TB100A4 - Trennblätter A4 - - - - \ No newline at end of file diff --git a/tests/samples/easybill_sample.xml b/tests/samples/easybill_sample.xml new file mode 100644 index 0000000..d64bb8f --- /dev/null +++ b/tests/samples/easybill_sample.xml @@ -0,0 +1,157 @@ + + + + + false + + + urn:ferd:CrossIndustryDocument:invoice:1p0:comfort + + + + RE1337 + RECHNUNG + 380 + + 20130305 + + + false + + + Test Node 1 + + + Test Node 2 + + + easybill GmbH + Düsselstr. 21 + 41564 Kaarst + + Geschäftsführer: + Christian Szardenings + Ronny Keyser + + REG + + + + + + Lieferant GmbH + + 80333 + Lieferantenstraße 20 + München + DE + + + 201/113/40209 + + + DE123456789 + + + + Kunden AG Mitte + + 69876 + Hans Muster + Kundenstraße 15 + Frankfurt + DE + + + + + + + 20130305 + + + + + 2013-471102 + EUR + + 31 + Überweisung + + DE08700901001234567890 + + + GENODEF1M04 + + + + 19.25 + VAT + 275.00 + 7.00 + + + 37.62 + VAT + 198.00 + 19.00 + + + 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. + + + 20130404 + + + + 198.00 + 0.00 + 0.00 + 198.00 + 37.62 + 235.62 + + + + + 1 + + Testcontent in einem LineDocument + + + + + 9.9000 + + + false + + 1.8000 + + + + 9.9000 + + + + 20.0000 + + + + VAT + S + 19.00 + + + 198.00 + + + + TB100A4 + Trennblätter A4 + + + + + diff --git a/tests/test_roundtrip.py b/tests/test_roundtrip.py new file mode 100644 index 0000000..c4bb25f --- /dev/null +++ b/tests/test_roundtrip.py @@ -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