From: Marko Luther Date: Sun, 10 Mar 2024 15:28:48 +0000 (+0100) Subject: fixes Issue #20, Issue #22, Issue #26 and Issue #29. It replaces PR #21 and PR #23... X-Git-Tag: 2.4.0~5 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=cdfd45a07ac252452aa128536506b412bb593e53;p=thirdparty%2Fpython-drafthorse.git fixes Issue #20, Issue #22, Issue #26 and Issue #29. It replaces PR #21 and PR #23 and contains alignments to the v2.2 specification (#27) * - corrects the NS_QDT name according to the zugferd22 specification - extends elements.py:DateTimeElement to allow the adjustment of the inner DateTimes namespace - updates references.py:ReferencedDocument to use NS_QDT for its DateTimeElement - corrects profiles in reference.py for various attributes according to the zugferd22 schemata - adds test zugferd_2p2_EN16931_ElektronischeAdresse2.xml as a variation of the official zugferd22 sample EN16931_ElektronischeAdresse.xml adding a FormattedIssueDateTime to the BuyerOrderReferencedDocument - moves SellerOrderReferencedDocument from trade.py to references.py - fixes DateTimeField namespace of AdvancePayment.received_date * This commit also adds the class ProductInstance (IndividualTradeProductInstance) to product.py and adds the missing fields "id" (IDField) and "instance" (ProductInstance) to product.py:TradeProduct. This commit also adds class PayerTradeParty (PayerTradeParty) to party.py and adds the missing field "payer" (PayerTradeParty) to trade.py:TradeSettlement. This commit also removes the unused (and not needed) party.py:LineApplicableTradeTax class. This commit also corrects profile and required tags according to the v2.2 specification as follows: - references.py:LineAdditionalReferencedDocument .name profile COMFORT => BASIC (was COMFORT) - tradelines.py:LineSettlement .trade_tax profile COMFORT => BASIC .period profile COMFORT => BASIC .allowance_charge profile COMFORT => BASIC .monetary_summation profile COMFORT => BASIC .additional_referenced_document profile EXTENDED => COMFORT .accounting_account profile EXTENDED => COMFORT - product.py:ProductCharacteristic .type_code required => optional .description profile EXTENDED => COMFORT .value profile EXTENDED => COMFORT - product.py:ProductClassification .class_code required => optional; profile EXTENDED => COMFORT .value required => optional - product.py:TradeProduct .name profile COMFORT => BASIC .characteristics EXTENDED => COMFORT .classifications EXTENDED => COMFORT .origins EXTENDED => COMFORT - trade.py:TradeSettlement .tax_currency_code profile COMFORT => BASIC .invoicer profile COMFORT => EXTENDED .invoicee profile COMFORT => EXTENDED .payee profile COMFORT => BASIC .allowance_charge profile COMFORT => BASIC .service_charge profile COMFORT => EXTENDED .terms profile COMFORT => BASIC .accounting_account EXTENDED => BASIC - accounting.py:ApplicableTradeTax .exemption_reason COMFORT => BASIC .category_code COMFORT => BASIC .exemption_reason_code EXTENDED => BASIC * fixes profile annotations in TradeAllowanceCharge * SpecifiedTradeAllowanceCharge:ReasonCode in COMFORT --- diff --git a/README.rst b/README.rst index 357809d..b540a41 100644 --- a/README.rst +++ b/README.rst @@ -66,6 +66,14 @@ Generating:: doc.trade.settlement.currency_code = "EUR" doc.trade.settlement.payment_means.type_code = "ZZZ" + doc.trade.agreement.seller.address.country_id = "DE" + doc.trade.agreement.seller.address.country_subdivision = "Bayern" + + doc.trade.agreement.seller_order.issue_date_time = datetime.now(timezone.utc) + doc.trade.agreement.buyer_order.issue_date_time = datetime.now(timezone.utc) + doc.trade.settlement.advance_payment.received_date = datetime.now(timezone.utc) + doc.trade.agreement.customer_order.issue_date_time = datetime.now(timezone.utc) + li = LineItem() li.document.line_id = "1" li.product.name = "Rainbow" diff --git a/drafthorse/models/__init__.py b/drafthorse/models/__init__.py index 59a4c23..b4a25e2 100644 --- a/drafthorse/models/__init__.py +++ b/drafthorse/models/__init__.py @@ -4,7 +4,7 @@ NS_A = "urn:un:unece:uncefact:data:standard:QualifiedDataType:100" NS_RAM = ( "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" ) -NS_QDT = "urn:un:unece:uncefact:data:standard:QualifiedDataType:10" +NS_QDT = "urn:un:unece:uncefact:data:standard:QualifiedDataType:100" BASIC = "BASIC" COMFORT = "COMFORT" EXTENDED = "EXTENDED" diff --git a/drafthorse/models/accounting.py b/drafthorse/models/accounting.py index 0bae75d..0d2aa56 100644 --- a/drafthorse/models/accounting.py +++ b/drafthorse/models/accounting.py @@ -28,47 +28,6 @@ class BillingSpecifiedPeriod(Element): tag = "BillingSpecifiedPeriod" -class SellerOrderReferencedDocument(Element): - issuer_ID = StringField(NS_RAM, "IssuerAssignedID", profile=COMFORT) - issue_date_time = DateTimeField( - NS_RAM, "FormattedIssueDateTime", required=True, profile=EXTENDED - ) - - class Meta: - namespace = NS_RAM - tag = "SellerOrderReferencedDocument" - - -class LineApplicableTradeTax(Element): - calculated_amount = DecimalField( - 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)", - ) - category_code = StringField( - NS_RAM, - "CategoryCode", - required=False, - profile=COMFORT, - _d="Steuerkategorie (Wert)", - ) - rate_applicable_percent = DecimalField( - NS_RAM, "RateApplicablePercent", required=True, profile=BASIC - ) - - class Meta: - namespace = NS_RAM - tag = "ApplicableTradeTax" - - class ApplicableTradeTax(Element): calculated_amount = DecimalField( NS_RAM, "CalculatedAmount", required=True, profile=BASIC, _d="Steuerbetrag" @@ -80,17 +39,8 @@ class ApplicableTradeTax(Element): NS_RAM, "ExemptionReason", required=False, - profile=COMFORT, - _d="Grund der Steuerbefreiung (Freitext)", - ) - tax_point_date = DateTimeField( - NS_RAM, "TaxPointDate", required=False, profile=COMFORT - ) - due_date_type_code = StringField( - NS_RAM, - "DueDateTypeCode", - required=False, profile=BASIC, + _d="Grund der Steuerbefreiung (Freitext)", ) basis_amount = DecimalField( NS_RAM, @@ -117,16 +67,25 @@ class ApplicableTradeTax(Element): NS_RAM, "CategoryCode", required=False, - profile=COMFORT, + profile=BASIC, _d="Steuerkategorie (Wert)", ) exemption_reason_code = StringField( NS_RAM, "ExemptionReasonCode", required=False, - profile=EXTENDED, + profile=BASIC, _d="Grund der Steuerbefreiung (Code)", ) + tax_point_date = DateTimeField( + NS_RAM, "TaxPointDate", required=False, profile=COMFORT + ) + due_date_type_code = StringField( + NS_RAM, + "DueDateTypeCode", + required=False, + profile=BASIC, + ) rate_applicable_percent = DecimalField( NS_RAM, "RateApplicablePercent", required=True, profile=BASIC ) @@ -254,14 +213,14 @@ class TradeAllowanceCharge(Element): NS_RAM, "CalculationPercent", required=False, - profile=EXTENDED, + profile=COMFORT, _d="Rabatt in Prozent", ) basis_amount = DecimalField( # TODO: Should be deprecated? NS_RAM, "BasisAmount", required=False, - profile=EXTENDED, + profile=COMFORT, _d="Basisbetrag des Rabatts", ) basis_quantity = QuantityField( @@ -278,7 +237,7 @@ class TradeAllowanceCharge(Element): profile=COMFORT, _d="Betrag des Zu-/Abschlags", ) - reason_code = StringField(NS_RAM, "ReasonCode", required=False, profile=EXTENDED) + reason_code = StringField(NS_RAM, "ReasonCode", required=False, profile=COMFORT) reason = StringField(NS_RAM, "Reason", required=False, profile=COMFORT) trade_tax = MultiField(CategoryTradeTax, required=False, profile=COMFORT) diff --git a/drafthorse/models/elements.py b/drafthorse/models/elements.py index 8478208..80a6820 100644 --- a/drafthorse/models/elements.py +++ b/drafthorse/models/elements.py @@ -80,7 +80,7 @@ class Element(metaclass=BaseElementMeta): if ( not hasattr(self, key) and not key.startswith("_") - and not key in ("required",) + and key not in ("required",) ): raise AttributeError( f"Element {type(self)} has no attribute '{key}'. If you set it, it would not be included in the output." @@ -301,7 +301,8 @@ class IDElement(StringElement): def to_etree(self): node = self._etree_node() node.text = self._text - node.attrib["schemeID"] = self._scheme_id + if self._scheme_id != "": + node.attrib["schemeID"] = self._scheme_id return node def from_etree(self, root): @@ -319,14 +320,17 @@ class IDElement(StringElement): class DateTimeElement(StringElement): - def __init__(self, namespace, tag, value=None, format="102"): + def __init__( + self, namespace, tag, value=None, format="102", date_time_namespace=NS_UDT + ): super().__init__(namespace, tag) self._value = value self._format = format + self._date_time_namespace = date_time_namespace def to_etree(self): t = self._etree_node() - node = ET.Element("{%s}%s" % (NS_UDT, "DateTimeString")) + node = ET.Element("{%s}%s" % (self._date_time_namespace, "DateTimeString")) if self._value: if self._format == "102": node.text = self._value.strftime("%Y%m%d") @@ -344,7 +348,7 @@ class DateTimeElement(StringElement): def from_etree(self, root): if len(root) != 1: raise TypeError("Date containers should have one child") - if root[0].tag != "{%s}%s" % (NS_UDT, "DateTimeString"): + if root[0].tag != "{%s}%s" % (self._date_time_namespace, "DateTimeString"): raise TypeError("Tag %s not recognized" % root[0].tag) self._format = root[0].attrib["format"] if self._format == "102": diff --git a/drafthorse/models/fields.py b/drafthorse/models/fields.py index c215a54..5efed5d 100644 --- a/drafthorse/models/fields.py +++ b/drafthorse/models/fields.py @@ -1,6 +1,6 @@ from decimal import Decimal -from . import BASIC +from . import BASIC, NS_UDT from .container import ( Container, CurrencyContainer, @@ -119,10 +119,16 @@ class IDField(Field): if instance._data.get(self.name, None) is None: instance._data[self.name] = self.initialize() - if not isinstance(value, (tuple, list)): - raise TypeError("Please pass a 2-tuple of including scheme ID and ID.") - instance._data[self.name]._text = value[1] - instance._data[self.name]._scheme_id = value[0] + if isinstance(value, (tuple, list)): + if len(value) == 2: + instance._data[self.name]._text = value[1] + instance._data[self.name]._scheme_id = value[0] + else: + raise TypeError( + "Please pass a 2-tuple of including scheme ID and ID, or just an ID." + ) + else: + instance._data[self.name]._text = value class CurrencyField(Field): @@ -208,7 +214,9 @@ class BinaryObjectField(Field): instance._data[self.name] = self.initialize() if not isinstance(value, (tuple, list)): - raise TypeError("Please pass a 2-tuple of including amount and unit code.") + raise TypeError( + "Please pass a 3-tuple of mimeCode, filename and base64-encoded binary." + ) instance._data[self.name]._text = value[2] instance._data[self.name]._mime_code = value[0] instance._data[self.name]._filename = value[1] @@ -238,13 +246,21 @@ class IndicatorField(Field): class DateTimeField(Field): def __init__( - self, namespace, tag, default=False, required=False, profile=BASIC, _d=None + self, + namespace, + tag, + default=False, + required=False, + profile=BASIC, + _d=None, + date_time_namespace=NS_UDT, ): from .elements import DateTimeElement super().__init__(DateTimeElement, default, required, profile, _d) self.namespace = namespace self.tag = tag + self._date_time_namespace = date_time_namespace def __set__(self, instance, value): if instance._data.get(self.name, None) is None: @@ -252,7 +268,9 @@ class DateTimeField(Field): instance._data[self.name]._value = value def initialize(self): - return self.cls(self.namespace, self.tag) + return self.cls( + self.namespace, self.tag, date_time_namespace=self._date_time_namespace + ) class DirectDateTimeField(Field): diff --git a/drafthorse/models/party.py b/drafthorse/models/party.py index 415cbac..efc96bc 100644 --- a/drafthorse/models/party.py +++ b/drafthorse/models/party.py @@ -117,6 +117,12 @@ class PayeeTradeParty(TradeParty): tag = "PayeeTradeParty" +class PayerTradeParty(TradeParty): + class Meta: + namespace = NS_RAM + tag = "PayerTradeParty" + + class InvoicerTradeParty(TradeParty): class Meta: namespace = NS_RAM diff --git a/drafthorse/models/product.py b/drafthorse/models/product.py index 5a0ff93..9a49cd5 100644 --- a/drafthorse/models/product.py +++ b/drafthorse/models/product.py @@ -13,11 +13,11 @@ class ProductCharacteristic(Element): type_code = StringField( NS_RAM, "TypeCode", - required=True, + required=False, profile=EXTENDED, _d="Art der Produkteigenschaft", ) - description = StringField(NS_RAM, "Description", required=True, profile=EXTENDED) + description = StringField(NS_RAM, "Description", required=True, profile=COMFORT) value_measure = QuantityField( NS_RAM, "ValueMeasure", @@ -25,7 +25,7 @@ class ProductCharacteristic(Element): profile=EXTENDED, _d="Numerische Messgröße", ) - value = StringField(NS_RAM, "Value", required=False, profile=EXTENDED) + value = StringField(NS_RAM, "Value", required=False, profile=COMFORT) class Meta: namespace = NS_RAM @@ -34,15 +34,26 @@ class ProductCharacteristic(Element): class ProductClassification(Element): class_code = ClassificationField( - NS_RAM, "ClassCode", required=True, profile=EXTENDED + NS_RAM, "ClassCode", required=False, profile=COMFORT ) - value = StringField(NS_RAM, "ClassName", required=True, profile=EXTENDED) + value = StringField(NS_RAM, "ClassName", required=False, profile=EXTENDED) class Meta: namespace = NS_RAM tag = "DesignatedProductClassification" +class ProductInstance(Element): + batch_id = IDField(NS_RAM, "BatchID", required=False, profile=EXTENDED) + serial_id = StringField( + NS_RAM, "SupplierAssignedSerialID", required=False, profile=EXTENDED + ) + + class Meta: + namespace = NS_RAM + tag = "IndividualTradeProductInstance" + + class OriginCountry(Element): id = StringField( NS_RAM, "ID", required=True, profile=EXTENDED, _d="Land der Produktherkunft" @@ -73,22 +84,20 @@ class ReferencedProduct(Element): class TradeProduct(Element): - global_id = IDField(NS_RAM, "GlobalID", required=False, profile=COMFORT) + id = IDField(NS_RAM, "ID", required=False, profile=EXTENDED) + global_id = IDField(NS_RAM, "GlobalID", required=False) 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) + name = StringField(NS_RAM, "Name", required=False) 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) + characteristics = MultiField(ProductCharacteristic, required=False, profile=COMFORT) + classifications = MultiField(ProductClassification, required=False, profile=COMFORT) + instance = MultiField(ProductInstance, required=False, profile=EXTENDED) + origins = MultiField(OriginCountry, required=False, profile=COMFORT) included_products = MultiField(ReferencedProduct, required=False, profile=EXTENDED) class Meta: diff --git a/drafthorse/models/references.py b/drafthorse/models/references.py index e1a73a1..94fef61 100644 --- a/drafthorse/models/references.py +++ b/drafthorse/models/references.py @@ -1,6 +1,6 @@ -from . import COMFORT, EXTENDED, NS_RAM +from . import BASIC, COMFORT, NS_RAM, NS_QDT from .elements import Element -from .fields import BinaryObjectField, DirectDateTimeField, StringField +from .fields import BinaryObjectField, DateTimeField, StringField, IDField class ProcuringProjectType(Element): @@ -13,14 +13,24 @@ class ProcuringProjectType(Element): class ReferencedDocument(Element): - date_time_string = DirectDateTimeField( - NS_RAM, "DateTimeString", required=False, profile=COMFORT + issuer_assigned_id = IDField( + NS_RAM, "IssuerAssignedID", required=False, profile=BASIC ) - issuer_assigned_id = StringField( - NS_RAM, "IssuerAssignedID", required=False, profile=COMFORT + issue_date_time = DateTimeField( + NS_RAM, + "FormattedIssueDateTime", + required=True, + profile=BASIC, + date_time_namespace=NS_QDT, ) +class SellerOrderReferencedDocument(ReferencedDocument): + class Meta: + namespace = NS_RAM + tag = "SellerOrderReferencedDocument" + + class BuyerOrderReferencedDocument(ReferencedDocument): class Meta: namespace = NS_RAM @@ -33,18 +43,12 @@ class ContractReferencedDocument(ReferencedDocument): tag = "ContractReferencedDocument" -class AdditionalReferencedDocument(Element): - issuer_assigned_id = StringField( - NS_RAM, "IssuerAssignedID", required=False, profile=COMFORT - ) - uri_id = StringField(NS_RAM, "URIID", required=False, profile=EXTENDED) - date_time_string = DirectDateTimeField( - NS_RAM, "DateTimeString", required=False, profile=COMFORT - ) - type_code = StringField(NS_RAM, "TypeCode", profile=EXTENDED, required=True) +class AdditionalReferencedDocument(ReferencedDocument): + uri_id = StringField(NS_RAM, "URIID", required=False, profile=COMFORT) + type_code = StringField(NS_RAM, "TypeCode", profile=COMFORT, required=True) name = StringField(NS_RAM, "Name", profile=COMFORT, required=False) attached_object = BinaryObjectField( - NS_RAM, "AttachmentBinaryObject", required=False, profile=EXTENDED + NS_RAM, "AttachmentBinaryObject", required=False, profile=COMFORT ) class Meta: @@ -52,15 +56,8 @@ class AdditionalReferencedDocument(Element): tag = "AdditionalReferencedDocument" -class InvoiceReferencedDocument(Element): - issuer_assigned_id = StringField( - NS_RAM, "IssuerAssignedID", required=False, profile=COMFORT - ) - - date_time_string = DirectDateTimeField( - NS_RAM, "DateTimeString", required=True, profile=COMFORT - ) - type_code = StringField(NS_RAM, "TypeCode", profile=EXTENDED, required=False) +class InvoiceReferencedDocument(ReferencedDocument): + type_code = StringField(NS_RAM, "TypeCode", profile=COMFORT, required=False) class Meta: namespace = NS_RAM @@ -80,7 +77,7 @@ class DespatchAdviceReferencedDocument(ReferencedDocument): class LineUltimateCustomerOrderReferencedDocument(ReferencedDocument): - line_id = StringField(NS_RAM, "LineID", required=False, profile=EXTENDED) + line_id = StringField(NS_RAM, "LineID", required=False, profile=COMFORT) class Meta: namespace = NS_RAM @@ -88,7 +85,7 @@ class LineUltimateCustomerOrderReferencedDocument(ReferencedDocument): class LineBuyerOrderReferencedDocument(ReferencedDocument): - line_id = StringField(NS_RAM, "LineID", required=False, profile=EXTENDED) + line_id = StringField(NS_RAM, "LineID", required=False, profile=COMFORT) class Meta: namespace = NS_RAM @@ -96,7 +93,7 @@ class LineBuyerOrderReferencedDocument(ReferencedDocument): class LineContractReferencedDocument(ReferencedDocument): - line_id = StringField(NS_RAM, "LineID", required=False, profile=EXTENDED) + line_id = StringField(NS_RAM, "LineID", required=False, profile=COMFORT) class Meta: namespace = NS_RAM @@ -104,7 +101,7 @@ class LineContractReferencedDocument(ReferencedDocument): class LineDespatchAdviceReferencedDocument(ReferencedDocument): - line_id = StringField(NS_RAM, "LineID", required=False, profile=EXTENDED) + line_id = StringField(NS_RAM, "LineID", required=False, profile=COMFORT) class Meta: namespace = NS_RAM @@ -112,28 +109,24 @@ class LineDespatchAdviceReferencedDocument(ReferencedDocument): class LineReceivingAdviceReferencedDocument(ReferencedDocument): - line_id = StringField(NS_RAM, "LineID", required=False, profile=EXTENDED) + line_id = StringField(NS_RAM, "LineID", required=False, profile=COMFORT) class Meta: namespace = NS_RAM tag = "ReceivingAdviceReferencedDocument" -class LineAdditionalReferencedDocument(Element): - issuer_assigned_id = StringField( - NS_RAM, "IssuerAssignedID", required=False, profile=COMFORT - ) - uri_id = StringField(NS_RAM, "URIID", required=False, profile=EXTENDED) - line_id = StringField(NS_RAM, "LineID", required=False, profile=EXTENDED) - type_code = StringField(NS_RAM, "TypeCode", required=False, profile=EXTENDED) - name = StringField(NS_RAM, "Name", required=False, profile=EXTENDED) - date_time_string = DirectDateTimeField( - NS_RAM, "FormattedIssueDateTime", required=False, profile=COMFORT - ) +class LineAdditionalReferencedDocument(ReferencedDocument): + uri_id = StringField(NS_RAM, "URIID", required=False, profile=COMFORT) + line_id = StringField(NS_RAM, "LineID", required=False, profile=COMFORT) + type_code = StringField(NS_RAM, "TypeCode", required=False, profile=COMFORT) + name = StringField(NS_RAM, "Name", required=False, profile=COMFORT) reference_type_code = StringField( - NS_RAM, "ReferenceTypeCode", profile=EXTENDED, required=True + NS_RAM, "ReferenceTypeCode", profile=COMFORT, required=True + ) + attached_object = BinaryObjectField( + NS_RAM, "AttachmentBinaryObject", required=False, profile=COMFORT ) - # todo: AttachmentBinaryObject class Meta: namespace = NS_RAM diff --git a/drafthorse/models/trade.py b/drafthorse/models/trade.py index 3044dac..99c9302 100644 --- a/drafthorse/models/trade.py +++ b/drafthorse/models/trade.py @@ -1,11 +1,10 @@ -from . import BASIC, COMFORT, EXTENDED, NS_RAM, NS_RSM +from . import BASIC, COMFORT, EXTENDED, NS_RAM, NS_RSM, NS_QDT from .accounting import ( ApplicableTradeTax, AppliedTradeTax, BillingSpecifiedPeriod, MonetarySummation, ReceivableAccountingAccount, - SellerOrderReferencedDocument, TradeAllowanceCharge, ) from .delivery import TradeDelivery @@ -17,6 +16,7 @@ from .party import ( InvoiceeTradeParty, InvoicerTradeParty, PayeeTradeParty, + PayerTradeParty, SellerTaxRepresentativeTradeParty, SellerTradeParty, ) @@ -28,6 +28,7 @@ from .payment import ( from .references import ( AdditionalReferencedDocument, BuyerOrderReferencedDocument, + SellerOrderReferencedDocument, ContractReferencedDocument, InvoiceReferencedDocument, ProcuringProjectType, @@ -147,7 +148,9 @@ class IncludedTradeTax(Element): class AdvancePayment(Element): paid_amount = DecimalField(NS_RAM, "PaidAmount") - received_date = DateTimeField(NS_RAM, "FormattedReceivedDateTime") + received_date = DateTimeField( + NS_RAM, "FormattedReceivedDateTime", date_time_namespace=NS_QDT + ) included_trade_tax = MultiField(IncludedTradeTax) class Meta: @@ -159,47 +162,52 @@ class TradeSettlement(Element): creditor_reference_id = StringField(NS_RAM, "CreditorReferenceID") payment_reference = StringField(NS_RAM, "PaymentReference") tax_currency_code = StringField( - NS_RAM, "TaxCurrencyCode", required=False, profile=COMFORT + NS_RAM, "TaxCurrencyCode", required=False, profile=BASIC ) currency_code = StringField(NS_RAM, "InvoiceCurrencyCode") issuer_reference = StringField(NS_RAM, "InvoiceIssuerReference", profile=EXTENDED) invoicer = Field( - InvoicerTradeParty, required=False, profile=COMFORT, _d="Rechnungsaussteller" + InvoicerTradeParty, required=False, profile=EXTENDED, _d="Rechnungsaussteller" ) invoicee = Field( - InvoiceeTradeParty, required=False, profile=COMFORT, _d="Rechnungsempfänger" + InvoiceeTradeParty, required=False, profile=EXTENDED, _d="Rechnungsempfänger" ) payee = Field( - PayeeTradeParty, required=False, profile=COMFORT, _d="Zahlungsempfänger" + PayeeTradeParty, required=False, profile=BASIC, _d="Zahlungsempfänger" + ) + payer = Field( + PayerTradeParty, required=False, profile=EXTENDED, _d="Zahlungspflichtiger" ) - invoice_currency = Field(TaxApplicableTradeCurrencyExchange) + invoice_currency = Field(TaxApplicableTradeCurrencyExchange, profile=EXTENDED) payment_means = Field(PaymentMeans) trade_tax = MultiField(ApplicableTradeTax) period = Field(BillingSpecifiedPeriod, required=False, profile=BASIC) allowance_charge = MultiField( TradeAllowanceCharge, required=False, - profile=COMFORT, + profile=BASIC, _d="Schalter für Zu-/Abschlag", ) - service_charge = MultiField(LogisticsServiceCharge, required=False, profile=COMFORT) - terms = MultiField(PaymentTerms, required=False, profile=COMFORT) + service_charge = MultiField( + LogisticsServiceCharge, required=False, profile=EXTENDED + ) + terms = MultiField(PaymentTerms, required=False, profile=BASIC) monetary_summation = Field( MonetarySummation, required=True, profile=BASIC, _d="Detailinformation zu Belegsummen", ) + invoice_referenced_document = Field( + InvoiceReferencedDocument, required=False, profile=BASIC + ) accounting_account = Field( ReceivableAccountingAccount, required=False, - profile=EXTENDED, + profile=BASIC, _d="Detailinformationen zur Buchungsreferenz", ) advance_payment = MultiField(AdvancePayment, required=False, profile=EXTENDED) - invoice_referenced_document = Field( - InvoiceReferencedDocument, required=False, profile=BASIC - ) class Meta: namespace = NS_RAM diff --git a/drafthorse/models/tradelines.py b/drafthorse/models/tradelines.py index 1e0c121..9b43c9a 100644 --- a/drafthorse/models/tradelines.py +++ b/drafthorse/models/tradelines.py @@ -150,25 +150,24 @@ class LineSummation(Element): class LineSettlement(Element): - trade_tax = Field(ApplicableTradeTax, required=False, profile=COMFORT) - period = Field(BillingSpecifiedPeriod, required=False, profile=COMFORT) + trade_tax = Field(ApplicableTradeTax, required=False) + period = Field(BillingSpecifiedPeriod, required=False) allowance_charge = MultiField( TradeAllowanceCharge, required=False, - profile=COMFORT, _d="Schalter für Zu-/Abschlag", ) - monetary_summation = Field(LineSummation, required=False, profile=COMFORT) + monetary_summation = Field(LineSummation, required=False, profile=BASIC) invoice_referenced_document = Field( InvoiceReferencedDocument, required=False, profile=EXTENDED ) additional_referenced_document = Field( - LineAdditionalReferencedDocument, required=False, profile=EXTENDED + LineAdditionalReferencedDocument, required=False, profile=COMFORT ) accounting_account = Field( ReceivableAccountingAccount, required=False, - profile=EXTENDED, + profile=COMFORT, _d="Detailinformationen zur Buchungsreferenz", ) diff --git a/setup.py b/setup.py index 3434eaf..b88013d 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -import sys from codecs import open from os import path from setuptools import find_packages, setup diff --git a/tests/samples/zugferd_2p1_EN16931_Einfach-LineObjectIdentifier.xml b/tests/samples/zugferd_2p1_EN16931_Einfach-LineObjectIdentifier.xml new file mode 100644 index 0000000..cd0f53d --- /dev/null +++ b/tests/samples/zugferd_2p1_EN16931_Einfach-LineObjectIdentifier.xml @@ -0,0 +1,248 @@ + + + + + + + + + + urn:cen.eu:en16931:2017 + + + + 471102 + 380 + + 20180305 + + + Rechnung gemäß Bestellung vom 01.03.2018. + + + Lieferant GmbH +Lieferantenstraße 20 +80333 München +Deutschland +Geschäftsführer: Hans Muster +Handelsregisternummer: H A 123 + + REG + + + + + + 1 + + + 4012345001235 + TB100A4 + Trennblätter A4 + + + + 9.9000 + + + 9.9000 + + + + 20.0000 + + + + VAT + S + 19.00 + + + 198.00 + + + + + + 2 + + + 4000050986428 + ARNR2 + Joghurt Banane + + + + 5.5000 + + + 5.5000 + + + + 50.0000 + + + + VAT + S + 7.00 + + + 275.00 + + + D200KKRG + 130 + + + + + + 549910 + 4000001123452 + Lieferant GmbH + + 80333 + Lieferantenstraße 20 + München + DE + + + 201/113/40209 + + + DE123456789 + + + + GE2020211 + Kunden AG Mitte + + 69876 + Kundenstraße 15 + Frankfurt + DE + + + + + + + 20180305 + + + + + EUR + + 19.25 + VAT + 275.00 + S + 7.00 + + + 37.62 + VAT + 198.00 + S + 19.00 + + + Zahlbar innerhalb 30 Tagen netto bis 04.04.2018, 3% Skonto innerhalb 10 Tagen bis 15.03.2018 + + + 473.00 + 0.00 + 0.00 + 473.00 + 56.87 + 529.87 + 0.00 + 529.87 + + + + \ No newline at end of file diff --git a/tests/samples/zugferd_2p2_EN16931_ElektronischeAdresse2.xml b/tests/samples/zugferd_2p2_EN16931_ElektronischeAdresse2.xml new file mode 100644 index 0000000..a7f2db4 --- /dev/null +++ b/tests/samples/zugferd_2p2_EN16931_ElektronischeAdresse2.xml @@ -0,0 +1,384 @@ + + + + + + + + + + urn:cen.eu:en16931:2017 + + + + 9314110911/00/M/00/N + 387 + + 20181001 + + + MUSTER-Autovermietung GMBH +Musterstr. 99 +99199 MUSTERHAUSEN +Geschäftsführung: +Maxima Musterfrau +USt-IdNr: DE136695976 +Telefon: +49 711-50885524 +www.musterlieferant.de +HRB Nr. 372876 +Amtsgericht Musterstadt +GLN 4304171000002 + + REG + + + Bei Rückfragen: +Telefon: +49 711-50885524 +E-Mail : info@muster-autovermietung.de + + + + Übergabe am 29.09.2018/ 10:35 + Ort: Frankfurt a. M. + km: 17791 + + + + Rückgabe am 01.10.2018/ 10:19 + Ort: Frankfurt a. M. + km: 18664 + + + + Übernahme: Frankfurt + Datum: 01.10.2018 + Zeit: 10:19 + km/out: 177791 + km/in: 18664 + km gefahren: 873 + Kennzeichen: M-MM 0000 + CO2: 150 + Bruttolistenpreis: 68300 + + + + + + + 1 + + + Miettage + + Fahrzeug-Kennzeichen + M-MM 0000 + + + + + 86.5500 + + + + 2.0000 + + + + VAT + S + 19.00 + + + 173.10 + + + + + + 2 + + Inklusiv-Kilometer waren: 873 + + + + Navigationssystem - Garantie + + + + 5.0400 + + + + 2.0000 + + + + VAT + S + 19.00 + + + 10.08 + + + + + + 3 + + + Vollkasko + + + + 23.1000 + + + + 2.0000 + + + + VAT + S + 19.00 + + + 46.20 + + + + + + 4 + + + minimale Selbstbeteiligung + + + + 15.5500 + + + + 2.0000 + + + + VAT + S + 19.00 + + + 31.10 + + + + + + 5 + + + Personen-Unfallversicherung + + + + 7.9800 + + + + 2.0000 + + + + VAT + S + 19.00 + + + 15.96 + + + + + + 6 + + + Choice Upgrade + + + + 5.0400 + + + + 2.0000 + + + + VAT + S + 19.00 + + + 10.08 + + + + + + 549910 + 4333741000005 + MUSTER-Autovermietung + + 99199 + Musterstr. 99 + MUSTERHAUSEN + DE + + + 1234567890128 + + + DE136695976 + + + + 9314110911 + MUSTER-KUNDE GMBH + + 40235 + KUNDENWEG 88 + DUESSELDORF + DE + + + 04 0 11 000 - 12345 12345 - 35 + + + + B123456789 + + 20180204 + + + + + + + 20180929 + + + + L87654321012345 + + + + EUR + + 54.44 + VAT + 286.52 + S + 19.00 + + + + Die Leistung wurde erbracht im Zeitraum zwischen Übergabe und Rückgabe. Der Rechnungsbetrag wird über Ihre MasterCard-Kreditkarte abgebucht. Dies ist keine Aufforderung zur Zahlung. Rechnung für Ihre Unterlagen. + + + + 286.52 + 0.00 + 0.00 + 286.52 + 54.44 + 340.96 + 0.00 + 340.96 + + + + \ No newline at end of file diff --git a/tests/test_mininal.py b/tests/test_mininal.py index 0bd7f53..aeaa34b 100644 --- a/tests/test_mininal.py +++ b/tests/test_mininal.py @@ -1,5 +1,5 @@ import os -from datetime import date +from datetime import date, datetime, timezone from decimal import Decimal from drafthorse.models.accounting import ApplicableTradeTax @@ -36,6 +36,11 @@ def test_readme_construction_example(): doc.trade.agreement.seller.address.country_id = "DE" doc.trade.agreement.seller.address.country_subdivision = "Bayern" + doc.trade.agreement.seller_order.issue_date_time = datetime.now(timezone.utc) + doc.trade.agreement.buyer_order.issue_date_time = datetime.now(timezone.utc) + doc.trade.settlement.advance_payment.received_date = datetime.now(timezone.utc) + doc.trade.agreement.customer_order.issue_date_time = datetime.now(timezone.utc) + li = LineItem() li.document.line_id = "1" li.product.name = "Rainbow"