mikrowerk-edi-invoicing 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mikrowerk_edi_invoicing-0.1.0.dist-info/LICENSE +661 -0
- mikrowerk_edi_invoicing-0.1.0.dist-info/METADATA +35 -0
- mikrowerk_edi_invoicing-0.1.0.dist-info/RECORD +18 -0
- mikrowerk_edi_invoicing-0.1.0.dist-info/WHEEL +5 -0
- mikrowerk_edi_invoicing-0.1.0.dist-info/top_level.txt +4 -0
- model/__init__.py +3 -0
- model/x_rechnung.py +260 -0
- tests/__init__.py +0 -0
- tests/test_iban_handling.py +24 -0
- tests/test_parse_x_rechnung.py +82 -0
- util/__init__.py +0 -0
- util/file_helper.py +24 -0
- x_mapper/__init__.py +8 -0
- x_mapper/cross_industry_invoice_mapper.py +20 -0
- x_mapper/drafthorse_elements_helper.py +132 -0
- x_mapper/xml_abstract_x_rechnung_parser.py +21 -0
- x_mapper/xml_cii_dom_parser.py +203 -0
- x_mapper/xml_ubl_sax_parser.py +268 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This implements a mapper from a drafthorse parsed x-rechnung-xml to the internal XRechnung object
|
|
3
|
+
"""
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
from drafthorse.models.document import Document
|
|
7
|
+
|
|
8
|
+
from .xml_abstract_x_rechnung_parser import XMLAbstractXRechnungParser
|
|
9
|
+
from ..model.x_rechnung import (XRechnung, XRechnungCurrency, XRechnungTradeParty, XRechnungTradeAddress,
|
|
10
|
+
XRechnungTradeContact, XRechnungPaymentMeans, XRechnungFinancialCard,
|
|
11
|
+
XRechnungTradeLine, XRechnungAppliedTradeTax, XRechnungBankAccount)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class XRechnungCIIXMLParser(XMLAbstractXRechnungParser):
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def parse_and_map_x_rechnung(cls, _xml: any) -> XRechnung:
|
|
19
|
+
doc = Document.parse(_xml)
|
|
20
|
+
return cls().map_to_x_rechnung(doc)
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def map_to_x_rechnung(cls, doc: any) -> XRechnung:
|
|
24
|
+
"""
|
|
25
|
+
:param doc: Element, the parsed dom root element
|
|
26
|
+
:return:
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
return XRechnung(
|
|
30
|
+
name=f"{cls().TYPE_CODES.get(doc.header.type_code.get_string(), 'Unknown doc type ')} {doc.header.id.get_string()}",
|
|
31
|
+
doc_id=doc.header.id.get_string(),
|
|
32
|
+
doc_type_code=doc.header.type_code.get_string(),
|
|
33
|
+
issued_date_time=doc.header.issue_date_time.get_value(),
|
|
34
|
+
notes=doc.header.notes.get_string_elements("\n"),
|
|
35
|
+
languages=doc.header.languages.get_string_elements(";"),
|
|
36
|
+
buyer_reference=doc.trade.agreement.buyer_reference.get_string(),
|
|
37
|
+
currency_code=doc.trade.settlement.currency_code.get_string(),
|
|
38
|
+
line_total_amount=doc.trade.settlement.monetary_summation.line_total.get_value(),
|
|
39
|
+
charge_total_amount=doc.trade.settlement.monetary_summation.charge_total.get_value(),
|
|
40
|
+
allowance_total_amount=doc.trade.settlement.monetary_summation.allowance_total.get_value(),
|
|
41
|
+
tax_basis_total_amount=XRechnungCurrency.from_currency_tuple(
|
|
42
|
+
doc.trade.settlement.monetary_summation.tax_basis_total.get_currency()),
|
|
43
|
+
tax_total_amount=[XRechnungCurrency.from_currency_tuple(tpl) for tpl in
|
|
44
|
+
doc.trade.settlement.monetary_summation.tax_total_other_currency.get_currencies()],
|
|
45
|
+
# list of currency
|
|
46
|
+
grand_total_amount=XRechnungCurrency.from_currency_tuple(
|
|
47
|
+
doc.trade.settlement.monetary_summation.grand_total.get_currency()),
|
|
48
|
+
total_prepaid_amount=doc.trade.settlement.monetary_summation.prepaid_total.get_value(),
|
|
49
|
+
due_payable_amount=doc.trade.settlement.monetary_summation.due_amount.get_value(),
|
|
50
|
+
delivered_date_time=datetime.now(),
|
|
51
|
+
payment_means=cls().map_payment_means(
|
|
52
|
+
doc.trade.settlement.payment_means) if doc.trade.settlement.payment_means else None,
|
|
53
|
+
payment_terms=doc.trade.settlement.terms.get_string_elements("\n"),
|
|
54
|
+
seller=cls().map_trade_party(doc.trade.agreement.seller) if hasattr(doc.trade.agreement,
|
|
55
|
+
"seller") else None,
|
|
56
|
+
invoicee=cls().map_trade_party(doc.trade.agreement.invoicee) if hasattr(doc.trade.agreement,
|
|
57
|
+
"invoicee") else None,
|
|
58
|
+
buyer=cls().map_trade_party(doc.trade.agreement.buyer) if hasattr(doc.trade.agreement, "buyer") else None,
|
|
59
|
+
payee=cls().map_trade_party(doc.trade.agreement.payee) if hasattr(doc.trade.agreement, "payee") else None,
|
|
60
|
+
trade_line_items=cls().map_trade_line_items(doc.trade.items) if hasattr(doc.trade, "items") else None,
|
|
61
|
+
applicable_trade_taxes=cls().map_trade_taxes(doc.trade.settlement.trade_tax) if hasattr(
|
|
62
|
+
doc.trade.settlement,
|
|
63
|
+
"trade_tax") else None
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def map_trade_party(cls, trade_party: any) -> XRechnungTradeParty:
|
|
68
|
+
_global_id_schema, _global_id = cls().map_first_id(trade_party.global_id)
|
|
69
|
+
return XRechnungTradeParty(
|
|
70
|
+
name=trade_party.name.get_string(),
|
|
71
|
+
description=trade_party.description.get_string(),
|
|
72
|
+
global_id=_global_id,
|
|
73
|
+
global_id_schema=_global_id_schema,
|
|
74
|
+
email=cls().map_electronic_address(trade_party.electronic_address, 'EM') if hasattr(trade_party,
|
|
75
|
+
"electronic_address") else None,
|
|
76
|
+
vat_registration_number=cls().map_tax_registration(trade_party.tax_registrations, 'VA') if hasattr(
|
|
77
|
+
trade_party, 'tax_registrations') else None,
|
|
78
|
+
fiscal_registration_number=cls().map_tax_registration(trade_party.tax_registrations, 'FC') if hasattr(
|
|
79
|
+
trade_party, 'tax_registrations') else None,
|
|
80
|
+
postal_address=cls().map_trade_address(trade_party.address) if hasattr(trade_party, 'address') else None,
|
|
81
|
+
trade_contact=cls().map_trade_contact(trade_party.contact) if hasattr(trade_party, 'contact') else None,
|
|
82
|
+
id=trade_party.id.get_string() if hasattr(trade_party, 'id') else None,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def map_first_id(global_id: any) -> (str, str):
|
|
87
|
+
if global_id is not None and hasattr(global_id, "children") and len(global_id.children) > 0:
|
|
88
|
+
for child in global_id.children:
|
|
89
|
+
return child
|
|
90
|
+
else:
|
|
91
|
+
return None, None
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def map_electronic_address(electronic_address: any, schema_id: str) -> str | None:
|
|
95
|
+
if electronic_address is not None and hasattr(electronic_address, "children") and len(
|
|
96
|
+
electronic_address.children) > 0:
|
|
97
|
+
for child in electronic_address.children:
|
|
98
|
+
if child.uri_ID._scheme_id == schema_id:
|
|
99
|
+
return child.uri_ID._text
|
|
100
|
+
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
def map_tax_registration(tax_reg: any, schema_id: str) -> str | None:
|
|
105
|
+
if tax_reg is not None and hasattr(tax_reg, "children") and len(
|
|
106
|
+
tax_reg.children) > 0:
|
|
107
|
+
for child in tax_reg.children:
|
|
108
|
+
if child.id._scheme_id == schema_id and child.id._text:
|
|
109
|
+
return "".join(child.id._text.split())
|
|
110
|
+
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def map_trade_address(trade_address: any) -> XRechnungTradeAddress:
|
|
115
|
+
return XRechnungTradeAddress(
|
|
116
|
+
city_name=trade_address.city_name.get_string(),
|
|
117
|
+
country_id=trade_address.country_id.get_string(),
|
|
118
|
+
country_subdivision_id=trade_address.country_subdivision.get_string(),
|
|
119
|
+
address_line_1=trade_address.line_one.get_string(),
|
|
120
|
+
address_line_2=trade_address.line_two.get_string(),
|
|
121
|
+
address_line_3=trade_address.line_three.get_string(),
|
|
122
|
+
post_code=trade_address.postcode.get_string(),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def map_trade_contact(trade_contact: any) -> XRechnungTradeContact:
|
|
127
|
+
return XRechnungTradeContact(
|
|
128
|
+
name=trade_contact.person_name.get_string(),
|
|
129
|
+
email=trade_contact.email.get_string(),
|
|
130
|
+
telephone=trade_contact.telephone.get_string(),
|
|
131
|
+
department_name=trade_contact.department_name.get_string(),
|
|
132
|
+
fax=trade_contact.fax.get_string(),
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def map_bank_account(payment_means: any) -> XRechnungBankAccount:
|
|
137
|
+
return XRechnungBankAccount(
|
|
138
|
+
iban="".join(payment_means.payee_account.iban.get_string().split()) if hasattr(payment_means,
|
|
139
|
+
'payee_account') else None,
|
|
140
|
+
bic=payment_means.payee_institution.bic.get_string() if hasattr(payment_means,
|
|
141
|
+
'payee_institution') else None,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
@staticmethod
|
|
145
|
+
def map_financial_card(financial_card: any) -> XRechnungFinancialCard:
|
|
146
|
+
return XRechnungFinancialCard(
|
|
147
|
+
id=financial_card.id.get_string(),
|
|
148
|
+
cardholder_name=financial_card.cardholder_name.get_string(),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
@classmethod
|
|
152
|
+
def map_payment_means(cls, payment_means: any) -> XRechnungPaymentMeans:
|
|
153
|
+
return XRechnungPaymentMeans(
|
|
154
|
+
information=payment_means.information.get_string(),
|
|
155
|
+
type_code=payment_means.type_code.get_string(),
|
|
156
|
+
payee_account=cls().map_bank_account(payment_means) if hasattr(payment_means, 'payee_account') else None,
|
|
157
|
+
financial_card=cls().map_financial_card(payment_means.financial_card) if hasattr(payment_means,
|
|
158
|
+
'financial_card') else None,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
@staticmethod
|
|
162
|
+
def map_trade_tax(trade_tax: any) -> XRechnungAppliedTradeTax:
|
|
163
|
+
return XRechnungAppliedTradeTax(
|
|
164
|
+
type_code=trade_tax.type_code.get_string(),
|
|
165
|
+
name=f"{trade_tax.type_code.get_string()} {trade_tax.rate_applicable_percent.get_value()}",
|
|
166
|
+
category_code=trade_tax.category_code.get_string(),
|
|
167
|
+
basis_amount=trade_tax.basis_amount.get_value(),
|
|
168
|
+
calculated_amount=trade_tax.calculated_amount.get_value(),
|
|
169
|
+
applicable_percent=trade_tax.rate_applicable_percent.get_value()
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
@classmethod
|
|
173
|
+
def map_trade_taxes(cls, trade_taxes: any) -> [XRechnungTradeLine]:
|
|
174
|
+
res = []
|
|
175
|
+
for child in trade_taxes.children:
|
|
176
|
+
res.append(cls().map_trade_tax(child))
|
|
177
|
+
return res
|
|
178
|
+
|
|
179
|
+
@classmethod
|
|
180
|
+
def map_trade_line(cls, trade_line: any) -> XRechnungTradeLine:
|
|
181
|
+
return XRechnungTradeLine(
|
|
182
|
+
name=trade_line.product.name.get_string(),
|
|
183
|
+
description=trade_line.product.description.get_string(),
|
|
184
|
+
line_id=trade_line.document.line_id.get_string(),
|
|
185
|
+
price_unit=trade_line.agreement.net.amount.get_value(),
|
|
186
|
+
price_unit_gross=trade_line.agreement.gross.amount.get_value(),
|
|
187
|
+
quantity_billed=trade_line.delivery.billed_quantity.get_value(),
|
|
188
|
+
global_product_id=trade_line.product.global_id.get_string(),
|
|
189
|
+
total_amount_net=trade_line.settlement.monetary_summation.total_amount.get_value(),
|
|
190
|
+
total_allowance_charge=trade_line.settlement.monetary_summation.total_allowance_charge.get_value(),
|
|
191
|
+
quantity_unit_code=trade_line.delivery.billed_quantity._unit_code,
|
|
192
|
+
seller_assigned_id=trade_line.product.seller_assigned_id.get_string(),
|
|
193
|
+
buyer_assigned_id=trade_line.product.buyer_assigned_id.get_string(),
|
|
194
|
+
global_product_scheme_id=trade_line.product.global_id._scheme_id,
|
|
195
|
+
trade_tax=cls().map_trade_tax(trade_line.settlement.trade_tax)
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
@classmethod
|
|
199
|
+
def map_trade_line_items(cls, trade_line_items: any) -> [XRechnungTradeLine]:
|
|
200
|
+
res = []
|
|
201
|
+
for child in trade_line_items.children:
|
|
202
|
+
res.append(cls().map_trade_line(child))
|
|
203
|
+
return res
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""
|
|
2
|
+
implementation of an ubl xml parser based on sax
|
|
3
|
+
"""
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
import io
|
|
6
|
+
from collections import deque
|
|
7
|
+
import xml.sax as sax
|
|
8
|
+
from decimal import Decimal
|
|
9
|
+
|
|
10
|
+
from ..model.x_rechnung import (XRechnung, XRechnungTradeParty, XRechnungTradeAddress, XRechnungTradeContact,
|
|
11
|
+
XRechnungPaymentMeans, XRechnungBankAccount, XRechnungCurrency, XRechnungTradeLine,
|
|
12
|
+
XRechnungAppliedTradeTax)
|
|
13
|
+
from .xml_abstract_x_rechnung_parser import XMLAbstractXRechnungParser
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class UblSaxHandler(sax.ContentHandler):
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self.x_rechnung: XRechnung = XRechnung()
|
|
19
|
+
self.content = ""
|
|
20
|
+
self.stack = deque()
|
|
21
|
+
self.current_attributes = None
|
|
22
|
+
self.current_trade_party = XRechnungTradeParty()
|
|
23
|
+
self.current_trade_address: XRechnungTradeAddress = XRechnungTradeAddress()
|
|
24
|
+
self.current_trade_contact: XRechnungTradeContact = XRechnungTradeContact()
|
|
25
|
+
self.current_payment_means: XRechnungPaymentMeans = XRechnungPaymentMeans()
|
|
26
|
+
self.current_payment_means_list: [XRechnungPaymentMeans] = []
|
|
27
|
+
self.current_currency: XRechnungCurrency | None = None
|
|
28
|
+
self.current_trade_line: XRechnungTradeLine | None = None
|
|
29
|
+
self.trade_line_list: [XRechnungTradeLine] = []
|
|
30
|
+
self.current_trade_tax: XRechnungAppliedTradeTax | None = None
|
|
31
|
+
self.applicable_trade_taxes: [XRechnungAppliedTradeTax] = []
|
|
32
|
+
|
|
33
|
+
def startDocument(self):
|
|
34
|
+
print("------------------------------ Start ---------------------------------------------------")
|
|
35
|
+
self.x_rechnung = XRechnung()
|
|
36
|
+
self.stack = deque()
|
|
37
|
+
|
|
38
|
+
def endDocument(self):
|
|
39
|
+
self.x_rechnung.name = (
|
|
40
|
+
f"{XMLAbstractXRechnungParser.TYPE_CODES.get(self.x_rechnung.doc_type_code, 'Unknown doc type ')}"
|
|
41
|
+
f" {self.x_rechnung.doc_id}")
|
|
42
|
+
print("------------------------------- End --------------------------------------------------")
|
|
43
|
+
|
|
44
|
+
def startElementNS(self, name, qname, attrs):
|
|
45
|
+
_ns, _tag_name = name
|
|
46
|
+
self.current_attributes = attrs
|
|
47
|
+
self.stack.append((_tag_name, attrs))
|
|
48
|
+
self.content = ""
|
|
49
|
+
match _tag_name:
|
|
50
|
+
case "Party":
|
|
51
|
+
self.current_trade_party = XRechnungTradeParty()
|
|
52
|
+
case "PostalAddress":
|
|
53
|
+
self.current_trade_address = XRechnungTradeAddress()
|
|
54
|
+
case "Contact":
|
|
55
|
+
self.current_trade_contact = XRechnungTradeContact()
|
|
56
|
+
case "PaymentMeans":
|
|
57
|
+
self.current_payment_means = XRechnungPaymentMeans()
|
|
58
|
+
self.current_payment_means.payee_account = XRechnungBankAccount()
|
|
59
|
+
case "InvoiceLine":
|
|
60
|
+
self.current_trade_line = XRechnungTradeLine()
|
|
61
|
+
case "TaxSubtotal":
|
|
62
|
+
self.current_trade_tax = XRechnungAppliedTradeTax()
|
|
63
|
+
case "ClassifiedTaxCategory":
|
|
64
|
+
self.current_trade_tax = XRechnungAppliedTradeTax()
|
|
65
|
+
|
|
66
|
+
_path = '/'.join([tag for tag, _attrs in self.stack])
|
|
67
|
+
|
|
68
|
+
print(f">>>>>>>>>>>>>>> start: {_ns} {_tag_name} >>>>>>>>>>>>>>>>")
|
|
69
|
+
print(_path)
|
|
70
|
+
for k, v in attrs.items():
|
|
71
|
+
print(f"{k}: {v}")
|
|
72
|
+
|
|
73
|
+
def endElementNS(self, name, qname):
|
|
74
|
+
_ns, _tag_name = name
|
|
75
|
+
_path = '/'.join([tag for tag, attrs in self.stack])
|
|
76
|
+
_close_name, attrs = self.stack.pop()
|
|
77
|
+
_content = self.content.strip()
|
|
78
|
+
|
|
79
|
+
# parse any matching closing tags
|
|
80
|
+
if "/Party" in _path:
|
|
81
|
+
self.handle_party(_path, _tag_name, _content, attrs)
|
|
82
|
+
if "/PaymentMeans" in _path:
|
|
83
|
+
self.handle_payment_means(_path, _tag_name, _content, attrs)
|
|
84
|
+
if "/InvoiceLine" in _path:
|
|
85
|
+
self.handle_invoice_line(_path, _tag_name, _content, attrs)
|
|
86
|
+
if "/ClassifiedTaxCategory" in _path:
|
|
87
|
+
self.handle_trade_tax(_path, _tag_name, _content, attrs)
|
|
88
|
+
if "/TaxTotal/TaxSubtotal" in _path:
|
|
89
|
+
self.handle_trade_tax(_path, _tag_name, _content, attrs)
|
|
90
|
+
|
|
91
|
+
# collect results when closing a main tag
|
|
92
|
+
match _tag_name:
|
|
93
|
+
case "PostalAddress":
|
|
94
|
+
self.current_trade_party.postal_address = self.current_trade_address
|
|
95
|
+
case "Contact":
|
|
96
|
+
self.current_trade_party.trade_contact = self.current_trade_contact
|
|
97
|
+
case "PaymentMeans":
|
|
98
|
+
self.current_payment_means_list.append(self.current_payment_means)
|
|
99
|
+
case "InvoiceLine":
|
|
100
|
+
self.current_trade_line.trade_tax = self.current_trade_tax
|
|
101
|
+
self.trade_line_list.append(self.current_trade_line)
|
|
102
|
+
|
|
103
|
+
if _path.endswith("Invoice/TaxTotal/TaxSubtotal"):
|
|
104
|
+
self.applicable_trade_taxes.append(self.current_trade_tax)
|
|
105
|
+
|
|
106
|
+
# invoice top level properties
|
|
107
|
+
self.handle_invoice(_path, _tag_name, _content, attrs)
|
|
108
|
+
|
|
109
|
+
print(f"content:'{_content}'")
|
|
110
|
+
self.content = ''
|
|
111
|
+
print(_path)
|
|
112
|
+
print(f"<<<<<<<<<<<<<<< end: {_ns} {_tag_name} <<<<<<<<<<<<<<<")
|
|
113
|
+
|
|
114
|
+
def characters(self, content):
|
|
115
|
+
if content and len(content) > 0:
|
|
116
|
+
self.content += content
|
|
117
|
+
|
|
118
|
+
def handle_invoice(self, path: str, tag, content: str, attrs=None):
|
|
119
|
+
match path:
|
|
120
|
+
case "Invoice/ID":
|
|
121
|
+
self.x_rechnung.doc_id = content
|
|
122
|
+
case "Invoice/IssueDate":
|
|
123
|
+
self.x_rechnung.issued_date_time = datetime.fromisoformat(content)
|
|
124
|
+
case "Invoice/InvoiceTypeCode":
|
|
125
|
+
self.x_rechnung.doc_type_code = content
|
|
126
|
+
case "Invoice/DocumentCurrencyCode":
|
|
127
|
+
self.x_rechnung.currency_code = content
|
|
128
|
+
case "Invoice/BuyerReference":
|
|
129
|
+
self.x_rechnung.buyer_reference = content
|
|
130
|
+
case "Invoice/OrderReference/ID":
|
|
131
|
+
self.x_rechnung.order_reference = content
|
|
132
|
+
case "Invoice/OrderReference/SalesOrderID":
|
|
133
|
+
self.x_rechnung.sales_order_reference = content
|
|
134
|
+
case "Invoice/DespatchDocumentReference/ID":
|
|
135
|
+
self.x_rechnung.dispatch_document_reference = content
|
|
136
|
+
case "Invoice/AccountingSupplierParty":
|
|
137
|
+
self.x_rechnung.seller = self.current_trade_party
|
|
138
|
+
case "Invoice/AccountingCustomerParty":
|
|
139
|
+
self.x_rechnung.buyer = self.current_trade_party
|
|
140
|
+
case "Invoice/PaymentMeans":
|
|
141
|
+
if self.current_payment_means_list and len(self.current_payment_means_list) > 0:
|
|
142
|
+
self.x_rechnung.payment_means = self.current_payment_means_list[0]
|
|
143
|
+
case "Invoice/PaymentTerms/Note":
|
|
144
|
+
self.x_rechnung.payment_terms = content
|
|
145
|
+
case "Invoice/AllowanceCharge/Amount":
|
|
146
|
+
self.x_rechnung.allowance_total_amount = Decimal(content)
|
|
147
|
+
case "Invoice/TaxTotal/TaxAmount":
|
|
148
|
+
self.x_rechnung.tax_total_amount = [XRechnungCurrency(Decimal(content),
|
|
149
|
+
attrs.get('currencyID', 'EUR'))]
|
|
150
|
+
case "Invoice/TaxTotal/TaxSubtotal/TaxableAmount":
|
|
151
|
+
self.x_rechnung.tax_basis_total_amount = XRechnungCurrency(Decimal(content),
|
|
152
|
+
attrs.get('currencyID', 'EUR'))
|
|
153
|
+
case "Invoice/LegalMonetaryTotal/TaxInclusiveAmount":
|
|
154
|
+
self.x_rechnung.grand_total_amount = XRechnungCurrency(Decimal(content),
|
|
155
|
+
attrs.get('currencyID', 'EUR'))
|
|
156
|
+
case "Invoice/LegalMonetaryTotal/PrepaidAmount":
|
|
157
|
+
self.x_rechnung.total_prepaid_amount = Decimal(content)
|
|
158
|
+
case "Invoice/LegalMonetaryTotal/PayableAmount":
|
|
159
|
+
self.x_rechnung.due_payable_amount = Decimal(content)
|
|
160
|
+
case "Invoice/InvoiceLine":
|
|
161
|
+
self.x_rechnung.trade_line_items = self.trade_line_list
|
|
162
|
+
case "Invoice/TaxTotal":
|
|
163
|
+
self.x_rechnung.applicable_trade_taxes = self.applicable_trade_taxes
|
|
164
|
+
|
|
165
|
+
def handle_party(self, path: str, tag: str, content: str, attr=None):
|
|
166
|
+
if "/PostalAddress" in path:
|
|
167
|
+
self.handle_postal_address(path, tag, content)
|
|
168
|
+
elif "/Contact" in path:
|
|
169
|
+
self.handle_contact(path, tag, content)
|
|
170
|
+
elif path.endswith("Party/EndpointID"):
|
|
171
|
+
if attr.get((None, "schemeID"), None) == "EM":
|
|
172
|
+
self.current_trade_party.email = content
|
|
173
|
+
elif path.endswith("Party/PartyName/Name"):
|
|
174
|
+
self.current_trade_party.name = content
|
|
175
|
+
elif path.endswith("/Party/PartyTaxScheme/CompanyID"):
|
|
176
|
+
self.current_trade_party.vat_registration_number = content
|
|
177
|
+
elif path.endswith("/Party/PartyLegalEntity/CompanyID"):
|
|
178
|
+
self.current_trade_party.legal_registration_number = content
|
|
179
|
+
elif path.endswith("/Party/PartyLegalEntity/RegistrationName"):
|
|
180
|
+
self.current_trade_party.name = content
|
|
181
|
+
elif path.endswith("/Party/PartyIdentification/ID"):
|
|
182
|
+
self.current_trade_party.global_id = content
|
|
183
|
+
self.current_trade_party.global_id_schema = attr.get((None, "schemeID"), "")
|
|
184
|
+
|
|
185
|
+
def handle_postal_address(self, path: str, tag: str, content: str, attr=None):
|
|
186
|
+
if path.endswith("/Party/PostalAddress/StreetName"):
|
|
187
|
+
self.current_trade_address.address_line_1 = content
|
|
188
|
+
elif path.endswith("/Party/PostalAddress/AdditionalStreetName"):
|
|
189
|
+
self.current_trade_address.address_line_2 = content
|
|
190
|
+
elif path.endswith("/Party/PostalAddress/BuildingNumber"):
|
|
191
|
+
self.current_trade_address.address_line_3 = content
|
|
192
|
+
elif path.endswith("/Party/PostalAddress/CityName"):
|
|
193
|
+
self.current_trade_address.city_name = content
|
|
194
|
+
elif path.endswith("/Party/PostalAddress/PostalZone"):
|
|
195
|
+
self.current_trade_address.post_code = content
|
|
196
|
+
elif path.endswith("/Party/PostalAddress/Country/IdentificationCode"):
|
|
197
|
+
self.current_trade_address.country_id = content
|
|
198
|
+
|
|
199
|
+
def handle_contact(self, path: str, tag: str, content: str, attr=None):
|
|
200
|
+
if path.endswith("/Party/Contact/Name"):
|
|
201
|
+
self.current_trade_contact.name = content
|
|
202
|
+
elif path.endswith("/Party/Contact/ElectronicMail"):
|
|
203
|
+
self.current_trade_contact.email = content
|
|
204
|
+
elif path.endswith("/Party/Contact/Telephone"):
|
|
205
|
+
self.current_trade_contact.telephone = content
|
|
206
|
+
|
|
207
|
+
def handle_payment_means(self, path: str, tag: str, content: str, attr=None):
|
|
208
|
+
if path.endswith("/PaymentMeans/PaymentMeansCode"):
|
|
209
|
+
self.current_payment_means.type_code = content
|
|
210
|
+
elif path.endswith("/PaymentMeans/PaymentID"):
|
|
211
|
+
self.current_payment_means.id = content
|
|
212
|
+
elif path.endswith("/PaymentMeans/PayeeFinancialAccount/ID"):
|
|
213
|
+
self.current_payment_means.payee_account.iban = content
|
|
214
|
+
elif path.endswith("/PaymentMeans/PayeeFinancialAccount/FinancialInstitutionBranch/ID"):
|
|
215
|
+
self.current_payment_means.payee_account.bic = content
|
|
216
|
+
|
|
217
|
+
def handle_invoice_line(self, path: str, tag: str, content: str, attr=None):
|
|
218
|
+
if path.endswith("/InvoiceLine/ID"):
|
|
219
|
+
self.current_trade_line.line_id = content
|
|
220
|
+
elif path.endswith("/InvoiceLine/Note"):
|
|
221
|
+
self.current_trade_line.note = content
|
|
222
|
+
elif path.endswith("/InvoiceLine/InvoicedQuantity"):
|
|
223
|
+
self.current_trade_line.quantity_billed = float(content)
|
|
224
|
+
self.current_trade_line.quantity_unit_code = attr.get((None, 'unitCode'), None) if attr else None
|
|
225
|
+
elif path.endswith("/InvoiceLine/LineExtensionAmount"):
|
|
226
|
+
self.current_trade_line.total_amount_net = float(content)
|
|
227
|
+
elif path.endswith("/InvoiceLine/Item/Description"):
|
|
228
|
+
self.current_trade_line.description = content
|
|
229
|
+
elif path.endswith("/InvoiceLine/Item/Name"):
|
|
230
|
+
self.current_trade_line.name = content
|
|
231
|
+
elif path.endswith("/InvoiceLine/Item/SellersItemIdentification/ID"):
|
|
232
|
+
self.current_trade_line.seller_assigned_id = content
|
|
233
|
+
elif path.endswith("/InvoiceLine/Item/BuyersItemIdentification/ID"):
|
|
234
|
+
self.current_trade_line.buyer_assigned_id = content
|
|
235
|
+
elif path.endswith("/InvoiceLine/Item/ItemInstance/LotIdentification/LotNumberID"):
|
|
236
|
+
self.current_trade_line.lot_number_id = content
|
|
237
|
+
elif path.endswith("/InvoiceLine/Item/ItemInstance/LotIdentification/ExpiryDate"):
|
|
238
|
+
self.current_trade_line.expiry_date = datetime.fromisoformat(content)
|
|
239
|
+
elif path.endswith("/InvoiceLine/Price/PriceAmount"):
|
|
240
|
+
self.current_trade_line.price_unit = float(content)
|
|
241
|
+
|
|
242
|
+
def handle_trade_tax(self, path: str, tag: str, content: str, attr=None):
|
|
243
|
+
if path.endswith("/ClassifiedTaxCategory/ID") or path.endswith("/TaxCategory/ID"):
|
|
244
|
+
self.current_trade_tax.category_code = content
|
|
245
|
+
elif path.endswith("/ClassifiedTaxCategory/TaxScheme/ID") or path.endswith("/TaxCategory/TaxScheme/ID"):
|
|
246
|
+
self.current_trade_tax.type_code = content
|
|
247
|
+
self.current_trade_tax.name = content
|
|
248
|
+
elif path.endswith("/ClassifiedTaxCategory/Percent") or path.endswith("/TaxCategory/Percent"):
|
|
249
|
+
self.current_trade_tax.applicable_percent = float(content)
|
|
250
|
+
elif path.endswith("/TaxSubtotal/TaxableAmount"):
|
|
251
|
+
self.current_trade_tax.basis_amount = float(content)
|
|
252
|
+
elif path.endswith("/TaxSubtotal/TaxAmount"):
|
|
253
|
+
self.current_trade_tax.calculated_amount = float(content)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class XRechnungUblXMLParser(XMLAbstractXRechnungParser):
|
|
257
|
+
@classmethod
|
|
258
|
+
def parse_and_map_x_rechnung(cls, _xml: bytes) -> XRechnung:
|
|
259
|
+
# create an XMLReader
|
|
260
|
+
parser = sax.make_parser()
|
|
261
|
+
# turn off namespaces
|
|
262
|
+
parser.setFeature(sax.handler.feature_namespaces, 1)
|
|
263
|
+
# override the default ContextHandler
|
|
264
|
+
handler = UblSaxHandler()
|
|
265
|
+
parser.setContentHandler(handler)
|
|
266
|
+
parser.parse(io.BytesIO(_xml))
|
|
267
|
+
x_rechnung = handler.x_rechnung
|
|
268
|
+
return x_rechnung
|