d70e6ebd5734e1e8f60c8afddfdc34db014f624a
Bernd Wurst WiP

Bernd Wurst authored 9 months ago

1) # -*- coding: utf-8 -*-
2) # (C) 2011 by Bernd Wurst <bernd@schokokeks.org>
3) 
4) # This file is part of Bib2011.
5) #
6) # Bib2011 is free software: you can redistribute it and/or modify
7) # it under the terms of the GNU General Public License as published by
8) # the Free Software Foundation, either version 3 of the License, or
9) # (at your option) any later version.
10) #
11) # Bib2011 is distributed in the hope that it will be useful,
12) # but WITHOUT ANY WARRANTY; without even the implied warranty of
13) # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14) # GNU General Public License for more details.
15) #
16) # You should have received a copy of the GNU General Public License
17) # along with Bib2011.  If not, see <http://www.gnu.org/licenses/>.
18) 
Bernd Wurst Kontoinhaber im XML einsetzen

Bernd Wurst authored 9 months ago

19) import os.path
20) import sys
Bernd Wurst WiP

Bernd Wurst authored 9 months ago

21) from decimal import Decimal
22) 
23) # Search for included submodule python-drafthorse
24) atoms = os.path.abspath(os.path.dirname(__file__)).split('/')
25) dir = ''
26) while atoms:
27)     candidate = os.path.join('/'.join(atoms), 'external/python-drafthorse')
28)     if os.path.exists(candidate):
29)         dir = candidate
30)         break
31)     atoms = atoms[:-1]
32) sys.path.insert(0, dir)
33) from drafthorse.models.document import Document
34) from drafthorse.models.accounting import ApplicableTradeTax
35) from drafthorse.models.tradelines import LineItem
Bernd Wurst Codestyle-Fixes

Bernd Wurst authored 9 months ago

36) from .InvoiceObjects import InvoiceTable, InvoiceText, RECHNUNG, GUTSCHRIFT, KORREKTUR, \
Bernd Wurst WiP

Bernd Wurst authored 9 months ago

37)     VAT_REGULAR, VAT_KLEINUNTERNEHMER, VAT_INNERGEM, PAYMENT_UEBERWEISUNG, PAYMENT_LASTSCHRIFT
Bernd Wurst WiP

Bernd Wurst authored 9 months ago

38) from drafthorse.models.party import TaxRegistration, URIUniversalCommunication
39) from drafthorse.models.payment import PaymentTerms
40) from drafthorse.models.note import IncludedNote
41) from drafthorse.pdf import attach_xml
42) 
43) def InvoiceToXML(invoice):
44)     doc = Document()
Bernd Wurst WiP

Bernd Wurst authored 9 months ago

45)     doc.context.guideline_parameter.id = "urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:en16931"
Bernd Wurst WiP

Bernd Wurst authored 9 months ago

46)     doc.header.id = invoice.id
47)     # Typecodes:
48)     # 380: Handelsrechnungen
49)     # 381: Gutschrift
50)     # 384: Korrekturrechnung
51)     # 389: Eigenrechnung (vom Käufer im Namen des Lieferanten erstellt).
52)     # 261: Selbstverfasste Gutschrift.
53)     # 386: Vorauszahlungsrechnung
54)     # 326: Teilrechnung
55)     # 751: Rechnungsinformation - KEINE RECHNUNG
56)     if invoice.type == RECHNUNG:
57)         doc.header.type_code = "380"
58)     elif invoice.type == GUTSCHRIFT:
59)         doc.header.type_code = "381"
60)     elif invoice.type == KORREKTUR:
61)         doc.header.type_code = "384"
62)     else:
63)         raise TypeError("Unbekannter Rechnungstyp, kann kein XML erstellen")
64)     doc.header.issue_date_time = invoice.date
65) 
66)     # Seller-Address
67)     if invoice.seller['trade_name']:
68)         pass
69)         # FIXME: specified_legal_organization ist in der Library nicht implementiert, pull request ist vorhanden
Bernd Wurst Kontoinhaber im XML einsetzen

Bernd Wurst authored 9 months ago

70)         # doc.trade.agreement.seller.specified_legal_organization.trade_name = invoice.seller['trade_name']
Bernd Wurst WiP

Bernd Wurst authored 9 months ago

71)     doc.trade.agreement.seller.name = invoice.seller['name']
72)     doc.trade.agreement.seller.address.country_id = invoice.seller['address']['country_id']
73)     doc.trade.agreement.seller.address.postcode = invoice.seller['address']['postcode']
74)     doc.trade.agreement.seller.address.city_name = invoice.seller['address']['city_name']
75)     doc.trade.agreement.seller.address.line_one = invoice.seller['address']['line1']
76)     doc.trade.agreement.seller.address.line_two = invoice.seller['address']['line2']
77)     doc.trade.agreement.seller.address.line_three = invoice.seller['address']['line3']
78)     if invoice.seller_vat_id:
79)         tax_reg = TaxRegistration()
80)         tax_reg.id = ('VA', invoice.seller_vat_id)
81)         doc.trade.agreement.seller.tax_registrations.add(tax_reg)
82)     if invoice.seller['email']:
83)         email = URIUniversalCommunication()
84)         email.uri_ID = ('EM', invoice.seller['email'])
85)         # FIXME: Typo in der Library ("adress")?
86)         doc.trade.agreement.seller.electronic_adress.add(email)
87) 
88)     # Buyer-Address
89)     doc.trade.agreement.buyer.name = invoice.customer['name']
90)     doc.trade.agreement.buyer.address.country_id = invoice.customer['address']['country_id']
91)     doc.trade.agreement.buyer.address.postcode = invoice.customer['address']['postcode']
92)     doc.trade.agreement.buyer.address.city_name = invoice.customer['address']['city_name']
93)     doc.trade.agreement.buyer.address.line_one = invoice.customer['address']['line1']
94)     doc.trade.agreement.buyer.address.line_two = invoice.customer['address']['line2']
95)     doc.trade.agreement.buyer.address.line_three = invoice.customer['address']['line3']
96) 
97)     # Line Items
98)     summe_netto = 0.0
99)     summe_brutto = 0.0
100)     summe_bezahlt = 0.0
101)     summe_ust = 0.0
102)     line_id_count = 0
103)     textparts = []
104)     for part in invoice.parts:
Bernd Wurst Codestyle-Fixes

Bernd Wurst authored 9 months ago

105)         if isinstance(part, InvoiceText):
Bernd Wurst WiP

Bernd Wurst authored 9 months ago

106)             textparts += part.paragraphs
Bernd Wurst Codestyle-Fixes

Bernd Wurst authored 9 months ago

107)         if isinstance(part, InvoiceTable):
Bernd Wurst WiP

Bernd Wurst authored 9 months ago

108)             for el in part.entries:
109)                 line_id_count += 1
110)                 li = LineItem()
111)                 li.document.line_id = f"{line_id_count}"
112)                 li.product.name = el['subject']
113)                 if 'desc' in el and el['desc'] != '':
114)                     desc = li.product.description = el['desc']
115) 
116)                 if 'period_start' in el and el['period_start']:
117)                     if 'period_end' in el and el['period_end']:
118)                         li.settlement.period.start = el['period_start']
119)                         li.settlement.period.end = el['period_end']
120)                     else:
121)                         li.delivery.event.occurrence = el['period_start']
122) 
123)                 # FIXME: Hier sollte der passende Code benutzt werden (z.B. Monat)
Bernd Wurst Kleine Korrekturen, mache B...

Bernd Wurst authored 9 months ago

124)                 li.delivery.billed_quantity = (Decimal(el['count']), 'C62')
125)                 # C62 = ohne Einheit
126)                 # H87 = Stück
127)                 # MON = Month
Bernd Wurst WiP

Bernd Wurst authored 9 months ago

128)                 # LTR = Liter (1 dm3)
129)                 # KGM = Kilogram
130)                 # MTR = Meter
131)                 # TNE = Tonne
132) 
133)                 li.settlement.trade_tax.type_code = "VAT"
134)                 if invoice.vat_type == VAT_REGULAR:
135)                     li.settlement.trade_tax.category_code = "S"
136)                 elif invoice.vat_type == VAT_KLEINUNTERNEHMER:
137)                     li.settlement.trade_tax.category_code = "E"
138)                 elif invoice.vat_type == VAT_INNERGEM:
139)                     li.settlement.trade_tax.category_code = "K"
140)                 # FIXME: Typ bei uns nur global gesetzt, nicht pro Artikel
141)                 # S = Standard VAT rate
142)                 # Z = Zero rated goods
143)                 # E = VAT exempt
144)                 # AE = Reverse charge
145)                 # K = Intra-Community supply (specific reverse charge)
146)                 # G = Exempt VAT for Export outside EU
147)                 # O = Outside VAT scope
Bernd Wurst Kontoinhaber im XML einsetzen

Bernd Wurst authored 9 months ago

148)                 li.settlement.trade_tax.rate_applicable_percent = Decimal(f"{el['vat'] * 100:.1f}")
Bernd Wurst WiP

Bernd Wurst authored 9 months ago

149) 
150)                 nettopreis = el['price']
151)                 if part.vatType == 'gross':
152)                     nettopreis = el['price'] / (1 + el['vat'])
153)                 li.agreement.net.amount = Decimal(f"{nettopreis:.2f}")
154) 
155)                 nettosumme = el['total']
156)                 if part.vatType == 'gross':
157)                     nettosumme = el['total'] / (1 + el['vat'])
158)                 li.settlement.monetary_summation.total_amount = Decimal(f"{nettosumme:.2f}")
159) 
160)                 summe_netto += nettosumme
161)                 summe_brutto += el['total']
162)                 doc.trade.items.add(li)
163) 
164)             for pay in part.payments:
165)                 summe_bezahlt += pay['amount']
166) 
167)             for vat, vatdata in part.vat.items():
168)                 trade_tax = ApplicableTradeTax()
169)                 # Steuerbetrag dieses Steuersatzes
170)                 trade_tax.calculated_amount = Decimal(f"{(vatdata[0] / (vat + 1)) * vat:.2f}")
171)                 # Nettosumme dieses Steuersatzes
172)                 trade_tax.basis_amount = Decimal(f"{(vatdata[0] / (vat + 1)):.2f}")
173)                 trade_tax.type_code = "VAT"
174)                 if invoice.vat_type == VAT_REGULAR:
175)                     trade_tax.category_code = "S"
176)                 elif invoice.vat_type == VAT_KLEINUNTERNEHMER:
177)                     trade_tax.category_code = "E"
178)                     trade_tax.exemption_reason = 'Als Kleinunternehmer wird gemäß §19 UStG keine USt in Rechnung gestellt.'
179)                 elif invoice.vat_type == VAT_INNERGEM:
180)                     trade_tax.category_code = "K"
Bernd Wurst Kontoinhaber im XML einsetzen

Bernd Wurst authored 9 months ago

181)                 trade_tax.rate_applicable_percent = Decimal(f"{vat * 100:.1f}")
Bernd Wurst WiP

Bernd Wurst authored 9 months ago

182)                 summe_ust += (vatdata[0] / (vat + 1)) * vat
183)                 doc.trade.settlement.trade_tax.add(trade_tax)
184) 
185)     for paragraph in textparts:
186)         note = IncludedNote()
187)         note.content.add(paragraph)
188)         doc.header.notes.add(note)
189) 
190)     rest = summe_brutto - summe_bezahlt
191) 
192)     if invoice.creditor_reference_id:
193)         # Gläubiger-ID für SEPA
194)         doc.trade.settlement.creditor_reference_id = invoice.creditor_reference_id
195)     doc.trade.settlement.payment_reference = invoice.id
196)     doc.trade.settlement.currency_code = 'EUR'
Bernd Wurst WiP

Bernd Wurst authored 9 months ago

197)     if invoice.payment_type:
198)         doc.trade.settlement.payment_means.type_code = invoice.payment_type
199) 
200)     if invoice.seller_bank_data['iban'] and invoice.payment_type == PAYMENT_UEBERWEISUNG:
Bernd Wurst Kontoinhaber im XML einsetzen

Bernd Wurst authored 9 months ago

201)         doc.trade.settlement.payment_means.payee_account.account_name = \
202)             invoice.seller_bank_data['kontoinhaber'] or invoice.seller['trade_name'] or invoice.seller['name']
Bernd Wurst WiP

Bernd Wurst authored 9 months ago

203)         doc.trade.settlement.payment_means.payee_account.iban = invoice.seller_bank_data['iban']
Bernd Wurst Kleine Korrekturen, mache B...

Bernd Wurst authored 9 months ago

204)         if invoice.seller_bank_data['bic']:
205)             doc.trade.settlement.payment_means.payee_institution.bic = invoice.seller_bank_data['bic']
Bernd Wurst WiP

Bernd Wurst authored 9 months ago

206)     if invoice.buyer_bank_data['iban'] and invoice.payment_type == PAYMENT_LASTSCHRIFT:
Bernd Wurst WiP

Bernd Wurst authored 9 months ago

207)         # Kunden-Bankverbindung bei Lastschrift
208)         doc.trade.settlement.payment_means.payer_account.iban = invoice.buyer_bank_data['iban']
209) 
210)     terms = PaymentTerms()
Bernd Wurst WiP

Bernd Wurst authored 9 months ago

211)     if invoice.due_date and invoice.payment_type == PAYMENT_UEBERWEISUNG:
Bernd Wurst WiP

Bernd Wurst authored 9 months ago

212)         terms.description = f"Bitte begleichen Sie den Betrag bis zum {invoice.due_date.strftime('%d.%m.%Y')} ohne Abzüge."
213)         terms.due = invoice.due_date
Bernd Wurst Kleine Korrekturen, mache B...

Bernd Wurst authored 9 months ago

214)     if invoice.type in [GUTSCHRIFT, KORREKTUR]:
Bernd Wurst WiP

Bernd Wurst authored 9 months ago

215)         terms.description = f"Wir überweisen den Betrag auf Ihr Konto."
216)     elif invoice.debit:
Bernd Wurst WiP

Bernd Wurst authored 9 months ago

217)         if invoice.debit_mandate_id:
218)             # Mandatsreferenz für Lastschrift
219)             terms.debit_mandate_id = invoice.debit_mandate_id
220)         terms.description = 'Wir buchen von Ihrem Konto ab.'
221)     doc.trade.settlement.terms.add(terms)
222) 
223)     doc.trade.settlement.monetary_summation.line_total = Decimal(f"{summe_netto:.2f}")
224)     doc.trade.settlement.monetary_summation.charge_total = Decimal("0.00")
225)     doc.trade.settlement.monetary_summation.allowance_total = Decimal("0.00")
226)     doc.trade.settlement.monetary_summation.tax_basis_total = Decimal(f"{summe_netto:.2f}")
227)     doc.trade.settlement.monetary_summation.tax_total = (Decimal(f"{summe_ust:.2f}"), "EUR")
228)     doc.trade.settlement.monetary_summation.prepaid_total = Decimal(f"{summe_bezahlt:.2f}")
229)     doc.trade.settlement.monetary_summation.grand_total = Decimal(f"{summe_brutto:.2f}")
230)     doc.trade.settlement.monetary_summation.due_amount = Decimal(f"{rest:.2f}")
231) 
232)     # Generate XML file
Bernd Wurst WiP

Bernd Wurst authored 9 months ago

233)     xml = doc.serialize(schema="FACTUR-X_EN16931")