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.
@@ -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