# -*- coding: utf-8 -*- import os.path import sys from decimal import Decimal # Search for included submodule python-drafthorse atoms = os.path.abspath(os.path.dirname(__file__)).split('/') dir = '' while atoms: candidate = os.path.join('/'.join(atoms), 'external/python-drafthorse') if os.path.exists(candidate): dir = candidate break atoms = atoms[:-1] sys.path.insert(0, dir) from drafthorse.models.document import Document from drafthorse.models.accounting import ApplicableTradeTax from drafthorse.models.tradelines import LineItem from .InvoiceObjects import InvoiceTable, InvoiceText, RECHNUNG, GUTSCHRIFT, KORREKTUR, \ VAT_REGULAR, VAT_KLEINUNTERNEHMER, VAT_INNERGEM, PAYMENT_UEBERWEISUNG, PAYMENT_LASTSCHRIFT from drafthorse.models.party import TaxRegistration, URIUniversalCommunication from drafthorse.models.payment import PaymentTerms from drafthorse.models.note import IncludedNote from drafthorse.pdf import attach_xml def InvoiceToXML(invoice): doc = Document() doc.context.guideline_parameter.id = "urn:cen.eu:en16931:2017" doc.header.id = invoice.id # Typecodes: # 380: Handelsrechnungen # 381: Gutschrift # 384: Korrekturrechnung # 389: Eigenrechnung (vom Käufer im Namen des Lieferanten erstellt). # 261: Selbstverfasste Gutschrift. # 386: Vorauszahlungsrechnung # 326: Teilrechnung # 751: Rechnungsinformation - KEINE RECHNUNG if invoice.type == RECHNUNG: doc.header.type_code = "380" elif invoice.type == GUTSCHRIFT: doc.header.type_code = "381" elif invoice.type == KORREKTUR: doc.header.type_code = "384" else: raise TypeError("Unbekannter Rechnungstyp, kann kein XML erstellen") doc.header.issue_date_time = invoice.date # Seller-Address if invoice.seller['trade_name']: pass # FIXME: specified_legal_organization ist in der Library nicht implementiert, pull request ist vorhanden # doc.trade.agreement.seller.specified_legal_organization.trade_name = invoice.seller['trade_name'] doc.trade.agreement.seller.name = invoice.seller['name'] doc.trade.agreement.seller.address.country_id = invoice.seller['address']['country_id'] doc.trade.agreement.seller.address.postcode = invoice.seller['address']['postcode'] doc.trade.agreement.seller.address.city_name = invoice.seller['address']['city_name'] doc.trade.agreement.seller.address.line_one = invoice.seller['address']['line1'] doc.trade.agreement.seller.address.line_two = invoice.seller['address']['line2'] doc.trade.agreement.seller.address.line_three = invoice.seller['address']['line3'] if invoice.seller['phone']: doc.trade.agreement.seller.contact.telephone.number = invoice.seller['phone'] if invoice.seller_vat_id: tax_reg = TaxRegistration() tax_reg.id = ('VA', invoice.seller_vat_id) doc.trade.agreement.seller.tax_registrations.add(tax_reg) if invoice.seller['email']: email = URIUniversalCommunication() email.uri_ID = ('EM', invoice.seller['email']) # FIXME: Typo in der Library ("adress")? doc.trade.agreement.seller.electronic_adress.add(email) # Buyer-Address doc.trade.agreement.buyer.name = invoice.customer['name'] doc.trade.agreement.buyer.address.country_id = invoice.customer['address']['country_id'] doc.trade.agreement.buyer.address.postcode = invoice.customer['address']['postcode'] doc.trade.agreement.buyer.address.city_name = invoice.customer['address']['city_name'] doc.trade.agreement.buyer.address.line_one = invoice.customer['address']['line1'] doc.trade.agreement.buyer.address.line_two = invoice.customer['address']['line2'] doc.trade.agreement.buyer.address.line_three = invoice.customer['address']['line3'] if invoice.buyer_reference: # "Leitweg-ID" in XRechnung doc.trade.agreement.buyer_reference = invoice.buyer_reference if invoice.order_number: doc.trade.agreement.buyer_order.issuer_assigned_id = invoice.order_number if invoice.contract_number: doc.trade.agreement.contract.issuer_assigned_id = invoice.contract_number # Line Items summe_netto = 0.0 summe_brutto = 0.0 summe_bezahlt = 0.0 summe_ust = 0.0 line_id_count = 0 textparts = [] for part in invoice.parts: if isinstance(part, InvoiceText): textparts += part.paragraphs if isinstance(part, InvoiceTable): last_title = None for el in part.entries: if el['type'] == 'title': # Diese Information ist im XML nicht auf diese Weise darstellbar last_title = el['title'] continue line_id_count += 1 li = LineItem() li.document.line_id = f"{line_id_count}" if last_title: title = IncludedNote() title.content.add(last_title) li.document.notes.add(title) li.product.name = el['subject'] if 'desc' in el and el['desc'] != '': li.product.description = el['desc'] if 'period_start' in el and el['period_start']: li.settlement.period.start = el['period_start'] if 'period_end' in el and el['period_end']: li.settlement.period.end = el['period_end'] else: li.settlement.period.end = el['period_start'] # FIXME: Hier sollte der passende Code benutzt werden (z.B. Monat) li.delivery.billed_quantity = (Decimal(el['count']), 'C62') # C62 = ohne Einheit # H87 = Stück # MON = Month # LTR = Liter (1 dm3) # KGM = Kilogram # MTR = Meter # TNE = Tonne li.settlement.trade_tax.type_code = "VAT" if invoice.vat_type == VAT_REGULAR: li.settlement.trade_tax.category_code = "S" elif invoice.vat_type == VAT_KLEINUNTERNEHMER: li.settlement.trade_tax.category_code = "E" elif invoice.vat_type == VAT_INNERGEM: li.settlement.trade_tax.category_code = "K" # FIXME: Typ bei uns nur global gesetzt, nicht pro Artikel # S = Standard VAT rate # Z = Zero rated goods # E = VAT exempt # AE = Reverse charge # K = Intra-Community supply (specific reverse charge) # G = Exempt VAT for Export outside EU # O = Outside VAT scope li.settlement.trade_tax.rate_applicable_percent = Decimal(f"{el['vat'] * 100:.1f}") nettopreis = el['price'] if part.vatType == 'gross': nettopreis = el['price'] / (1 + el['vat']) li.agreement.net.amount = Decimal(f"{nettopreis:.2f}") nettosumme = el['total'] if part.vatType == 'gross': nettosumme = el['total'] / (1 + el['vat']) li.settlement.monetary_summation.total_amount = Decimal(f"{nettosumme:.2f}") summe_netto += nettosumme summe_brutto += el['total'] doc.trade.items.add(li) for pay in part.payments: summe_bezahlt += pay['amount'] for vat, vatdata in part.vat.items(): trade_tax = ApplicableTradeTax() # Steuerbetrag dieses Steuersatzes trade_tax.calculated_amount = Decimal(f"{(vatdata[0] / (vat + 1)) * vat:.2f}") # Nettosumme dieses Steuersatzes trade_tax.basis_amount = Decimal(f"{(vatdata[0] / (vat + 1)):.2f}") trade_tax.type_code = "VAT" if invoice.vat_type == VAT_REGULAR: trade_tax.category_code = "S" elif invoice.vat_type == VAT_KLEINUNTERNEHMER: trade_tax.category_code = "E" trade_tax.exemption_reason = 'Als Kleinunternehmer wird gemäß §19 UStG keine USt in Rechnung gestellt.' elif invoice.vat_type == VAT_INNERGEM: trade_tax.category_code = "K" trade_tax.rate_applicable_percent = Decimal(f"{vat * 100:.1f}") summe_ust += (vatdata[0] / (vat + 1)) * vat doc.trade.settlement.trade_tax.add(trade_tax) for paragraph in textparts: note = IncludedNote() note.content.add(paragraph) doc.header.notes.add(note) rest = summe_brutto - summe_bezahlt if invoice.creditor_reference_id: # Gläubiger-ID für SEPA doc.trade.settlement.creditor_reference_id = invoice.creditor_reference_id doc.trade.settlement.payment_reference = invoice.id doc.trade.settlement.currency_code = 'EUR' if invoice.payment_type: doc.trade.settlement.payment_means.type_code = invoice.payment_type if invoice.seller_bank_data['iban'] and invoice.payment_type == PAYMENT_UEBERWEISUNG: doc.trade.settlement.payment_means.payee_account.account_name = \ invoice.seller_bank_data['kontoinhaber'] or invoice.seller['trade_name'] or invoice.seller['name'] doc.trade.settlement.payment_means.payee_account.iban = invoice.seller_bank_data['iban'] if invoice.seller_bank_data['bic']: doc.trade.settlement.payment_means.payee_institution.bic = invoice.seller_bank_data['bic'] if invoice.buyer_bank_data['iban'] and invoice.payment_type == PAYMENT_LASTSCHRIFT: # Kunden-Bankverbindung bei Lastschrift doc.trade.settlement.payment_means.payer_account.iban = invoice.buyer_bank_data['iban'] terms = PaymentTerms() if invoice.due_date and invoice.payment_type == PAYMENT_UEBERWEISUNG: terms.description = f"Bitte begleichen Sie den Betrag bis zum {invoice.due_date.strftime('%d.%m.%Y')} ohne Abzüge." terms.due = invoice.due_date if invoice.type in [GUTSCHRIFT, KORREKTUR]: terms.description = f"Wir überweisen den Betrag auf Ihr Konto." elif invoice.debit: if invoice.debit_mandate_id: # Mandatsreferenz für Lastschrift terms.debit_mandate_id = invoice.debit_mandate_id terms.description = 'Wir buchen von Ihrem Konto ab.' doc.trade.settlement.terms.add(terms) doc.trade.settlement.monetary_summation.line_total = Decimal(f"{summe_netto:.2f}") 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(f"{summe_netto:.2f}") doc.trade.settlement.monetary_summation.tax_total = (Decimal(f"{summe_ust:.2f}"), "EUR") doc.trade.settlement.monetary_summation.prepaid_total = Decimal(f"{summe_bezahlt:.2f}") doc.trade.settlement.monetary_summation.grand_total = Decimal(f"{summe_brutto:.2f}") doc.trade.settlement.monetary_summation.due_amount = Decimal(f"{rest:.2f}") # Generate XML file xml = doc.serialize(schema="FACTUR-X_EN16931") return xml