mikrowerk-edi-invoicing 0.3.1__tar.gz → 0.6.5__tar.gz
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.3.1/mikrowerk_edi_invoicing.egg-info → mikrowerk_edi_invoicing-0.6.5}/PKG-INFO +5 -3
- mikrowerk_edi_invoicing-0.6.5/edi_invoice_parser/__init__.py +20 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/dom_elements_helper.py +10 -3
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/delivery.py +4 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/elements.py +2 -1
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/payment.py +8 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/references.py +6 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/xml_cii_dom_parser.py +47 -43
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cross_industry_invoice_mapper.py +21 -5
- mikrowerk_edi_invoicing-0.6.5/edi_invoice_parser/model/__init__.py +17 -0
- mikrowerk_edi_invoicing-0.6.5/edi_invoice_parser/model/trade_document_types.py +159 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/model/xml_abstract_x_rechnung_parser.py +2 -2
- mikrowerk_edi_invoicing-0.6.5/edi_invoice_parser/tests/__init__.py +3 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/tests/test_parse_x_rechnung.py +10 -8
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/ubl_sax_parser/xml_ubl_sax_parser.py +49 -38
- mikrowerk_edi_invoicing-0.6.5/edi_invoice_parser/util/timer_helper.py +11 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5/mikrowerk_edi_invoicing.egg-info}/PKG-INFO +5 -3
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/mikrowerk_edi_invoicing.egg-info/SOURCES.txt +4 -2
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/mikrowerk_edi_invoicing.egg-info/requires.txt +1 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/setup.py +5 -9
- mikrowerk_edi_invoicing-0.3.1/edi_invoice_parser/__init__.py +0 -17
- mikrowerk_edi_invoicing-0.3.1/edi_invoice_parser/model/__init__.py +0 -4
- mikrowerk_edi_invoicing-0.3.1/edi_invoice_parser/model/x_rechnung.py +0 -263
- mikrowerk_edi_invoicing-0.3.1/edi_invoice_parser/tests/__init__.py +0 -3
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/LICENSE +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/README.md +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/__init__.py +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/__init__.py +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/accounting.py +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/container.py +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/document.py +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/fields.py +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/note.py +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/party.py +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/product.py +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/trade.py +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/tradelines.py +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/pdf.py +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/utils.py +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/xmp_schema.py +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/tests/test_iban_handling.py +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/ubl_sax_parser/__init__.py +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/util/__init__.py +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/util/file_helper.py +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/mikrowerk_edi_invoicing.egg-info/dependency_links.txt +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/mikrowerk_edi_invoicing.egg-info/top_level.txt +0 -0
- {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/setup.cfg +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: mikrowerk_edi_invoicing
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Parser for EDI invoices in CII or UBL format
|
|
3
|
+
Version: 0.6.5
|
|
4
|
+
Summary: Parser for EDI invoices in CII or UBL format or plain pdf with LLM support
|
|
5
5
|
Author: Mikrowerk a Gammadata Division
|
|
6
6
|
Author-email: info@mikrowerk.com
|
|
7
7
|
License: GNU Affero General Public License v3
|
|
@@ -22,12 +22,14 @@ Requires-Dist: factur-x==3.6
|
|
|
22
22
|
Requires-Dist: jsonpickle~=4.0.1
|
|
23
23
|
Requires-Dist: parameterized
|
|
24
24
|
Requires-Dist: schwifty
|
|
25
|
+
Requires-Dist: google-genai
|
|
25
26
|
Dynamic: author
|
|
26
27
|
Dynamic: author-email
|
|
27
28
|
Dynamic: classifier
|
|
28
29
|
Dynamic: description
|
|
29
30
|
Dynamic: description-content-type
|
|
30
31
|
Dynamic: license
|
|
32
|
+
Dynamic: license-file
|
|
31
33
|
Dynamic: requires-dist
|
|
32
34
|
Dynamic: summary
|
|
33
35
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from .cross_industry_invoice_mapper import parse_and_map_x_rechnung, check_if_parser_is_available
|
|
2
|
+
from .model.trade_document_types import TradeDocument, TradeParty, TradePartyAddress, TradeCurrency, TradePartyContact, \
|
|
3
|
+
TradeLine, TradePaymentMeans, AppliedTradeTax, BankAccount, FinancialCard, ubl_doc_codes
|
|
4
|
+
|
|
5
|
+
__all__ = ["parse_and_map_x_rechnung",
|
|
6
|
+
"check_if_parser_is_available",
|
|
7
|
+
"TradeDocument",
|
|
8
|
+
"TradeParty",
|
|
9
|
+
"TradePartyAddress",
|
|
10
|
+
"TradeCurrency",
|
|
11
|
+
"TradePartyContact",
|
|
12
|
+
"TradeLine",
|
|
13
|
+
"TradePaymentMeans",
|
|
14
|
+
"AppliedTradeTax",
|
|
15
|
+
"BankAccount",
|
|
16
|
+
"FinancialCard",
|
|
17
|
+
"ubl_doc_codes"
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
version = "0.6.5"
|
|
@@ -23,12 +23,19 @@ def get_value(self) -> any:
|
|
|
23
23
|
return self._value
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def get_float_value(self) -> any:
|
|
27
|
+
return float(self._value) if self._value is not None else None
|
|
28
|
+
|
|
29
|
+
|
|
26
30
|
def get_value_from_amount(self) -> any:
|
|
27
|
-
return self._amount
|
|
31
|
+
return float(self._amount) if self._amount is not None else None
|
|
28
32
|
|
|
29
33
|
|
|
30
34
|
def get_currency(self) -> (any, any):
|
|
31
|
-
|
|
35
|
+
if self._amount:
|
|
36
|
+
return float(self._amount), self._amount
|
|
37
|
+
else:
|
|
38
|
+
return self._amount, self._amount
|
|
32
39
|
|
|
33
40
|
|
|
34
41
|
def get_currencies(self) -> [(any, any)]:
|
|
@@ -114,7 +121,7 @@ DirectDateTimeElement.get_string = get_string_from_date
|
|
|
114
121
|
DateTimeElement.get_string = get_string_from_date
|
|
115
122
|
DateTimeElement.get_value = get_value
|
|
116
123
|
DecimalElement.get_string = get_string_from_value
|
|
117
|
-
DecimalElement.get_value =
|
|
124
|
+
DecimalElement.get_value = get_float_value
|
|
118
125
|
IndicatorElement.get_string = get_string_from_value
|
|
119
126
|
QuantityElement.get_string = get_string_from_quantity
|
|
120
127
|
QuantityElement.get_value = get_value_from_amount
|
|
@@ -9,6 +9,7 @@ from .party import (
|
|
|
9
9
|
from .references import (
|
|
10
10
|
DeliveryNoteReferencedDocument,
|
|
11
11
|
DespatchAdviceReferencedDocument,
|
|
12
|
+
ReceivingAdviceReferencedDocument
|
|
12
13
|
)
|
|
13
14
|
|
|
14
15
|
|
|
@@ -59,6 +60,9 @@ class TradeDelivery(Element):
|
|
|
59
60
|
delivery_note = Field(
|
|
60
61
|
DeliveryNoteReferencedDocument, required=False, profile=EXTENDED
|
|
61
62
|
)
|
|
63
|
+
receiving_advice_ref_document = Field(
|
|
64
|
+
ReceivingAdviceReferencedDocument, required=False, profile=EXTENDED
|
|
65
|
+
)
|
|
62
66
|
|
|
63
67
|
class Meta:
|
|
64
68
|
namespace = NS_RAM
|
|
@@ -356,7 +356,8 @@ class DateTimeElement(StringElement):
|
|
|
356
356
|
if len(root) != 1:
|
|
357
357
|
raise TypeError("Date containers should have one child")
|
|
358
358
|
if root[0].tag != "{%s}%s" % (self._date_time_namespace, "DateTimeString"):
|
|
359
|
-
raise TypeError("Tag %s not recognized" % root[0].tag)
|
|
359
|
+
# raise TypeError("Tag %s not recognized" % root[0].tag)
|
|
360
|
+
print("TypeError: Tag %s not recognized" % root[0].tag)
|
|
360
361
|
self._format = root[0].attrib["format"]
|
|
361
362
|
if self._format == "102":
|
|
362
363
|
self._value = datetime.strptime(root[0].text, "%Y%m%d").date()
|
|
@@ -27,6 +27,12 @@ class PayerFinancialAccount(Element):
|
|
|
27
27
|
namespace = NS_RAM
|
|
28
28
|
tag = "PayerPartyDebtorFinancialAccount"
|
|
29
29
|
|
|
30
|
+
class PayerSpecifiedDebtorFinancialInstitution(Element):
|
|
31
|
+
bic = StringField(NS_RAM, "BICID")
|
|
32
|
+
|
|
33
|
+
class Meta:
|
|
34
|
+
namespace = NS_RAM
|
|
35
|
+
tag = "PayerSpecifiedDebtorFinancialInstitution"
|
|
30
36
|
|
|
31
37
|
class PayeeFinancialAccount(Element):
|
|
32
38
|
iban = StringField(NS_RAM, "IBANID")
|
|
@@ -55,6 +61,8 @@ class PaymentMeans(Element):
|
|
|
55
61
|
payer_account = Field(PayerFinancialAccount)
|
|
56
62
|
payee_account = Field(PayeeFinancialAccount)
|
|
57
63
|
payee_institution = Field(PayeeFinancialInstitution)
|
|
64
|
+
debtor_financial_institution = Field(PayerSpecifiedDebtorFinancialInstitution)
|
|
65
|
+
|
|
58
66
|
|
|
59
67
|
class Meta:
|
|
60
68
|
namespace = NS_RAM
|
|
@@ -139,6 +139,12 @@ class DeliveryNoteReferencedDocument(ReferencedDocument):
|
|
|
139
139
|
tag = "DeliveryNoteReferencedDocument"
|
|
140
140
|
|
|
141
141
|
|
|
142
|
+
class ReceivingAdviceReferencedDocument(ReferencedDocument):
|
|
143
|
+
class Meta:
|
|
144
|
+
namespace = NS_RAM
|
|
145
|
+
tag = "ReceivingAdviceReferencedDocument"
|
|
146
|
+
|
|
147
|
+
|
|
142
148
|
class LineDeliveryNoteReferencedDocument(ReferencedDocument):
|
|
143
149
|
class Meta:
|
|
144
150
|
namespace = NS_RAM
|
|
@@ -4,45 +4,46 @@ This implements a mapper from a drafthorse parsed x-rechnung-xml to the internal
|
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
|
|
6
6
|
from .models.document import Document
|
|
7
|
-
|
|
7
|
+
from ..model.trade_document_types import (TradeDocument, TradeParty, TradeCurrency, TradeLine, TradePaymentMeans,
|
|
8
|
+
AppliedTradeTax, TradePartyAddress, TradePartyContact, BankAccount,
|
|
9
|
+
FinancialCard)
|
|
8
10
|
from ..model.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
11
|
|
|
13
12
|
|
|
14
13
|
class XRechnungCIIXMLParser(XMLAbstractXRechnungParser):
|
|
15
14
|
|
|
16
15
|
@classmethod
|
|
17
|
-
def parse_and_map_x_rechnung(cls, _xml: any) ->
|
|
16
|
+
def parse_and_map_x_rechnung(cls, _xml: any) -> TradeDocument:
|
|
18
17
|
doc = Document.parse(_xml)
|
|
19
18
|
return cls().map_to_x_rechnung(doc)
|
|
20
19
|
|
|
21
20
|
@classmethod
|
|
22
|
-
def map_to_x_rechnung(cls, doc: any) ->
|
|
21
|
+
def map_to_x_rechnung(cls, doc: any) -> TradeDocument:
|
|
23
22
|
"""
|
|
24
23
|
:param doc: Element, the parsed dom root element
|
|
25
24
|
:return:
|
|
26
25
|
"""
|
|
27
26
|
|
|
28
|
-
return
|
|
27
|
+
return TradeDocument(
|
|
29
28
|
name=f"{cls().TYPE_CODES.get(doc.header.type_code.get_string(), 'Unknown doc type ')} {doc.header.id.get_string()}",
|
|
29
|
+
doc_type_name=f"{cls().TYPE_CODES.get(doc.header.type_code.get_string(), 'Unknown doc type ')}",
|
|
30
30
|
doc_id=doc.header.id.get_string(),
|
|
31
|
-
doc_type_code=doc.header.type_code.get_string(),
|
|
31
|
+
doc_type_code=int(doc.header.type_code.get_string()) if doc.header.type_code else None,
|
|
32
32
|
issued_date_time=doc.header.issue_date_time.get_value(),
|
|
33
33
|
notes=doc.header.notes.get_string_elements("\n"),
|
|
34
34
|
languages=doc.header.languages.get_string_elements(";"),
|
|
35
|
-
|
|
35
|
+
receiver_reference=doc.trade.agreement.buyer_reference.get_string(),
|
|
36
|
+
order_reference=doc.trade.agreement.buyer_order.issuer_assigned_id.get_string(),
|
|
36
37
|
currency_code=doc.trade.settlement.currency_code.get_string(),
|
|
37
38
|
line_total_amount=doc.trade.settlement.monetary_summation.line_total.get_value(),
|
|
38
39
|
charge_total_amount=doc.trade.settlement.monetary_summation.charge_total.get_value(),
|
|
39
40
|
allowance_total_amount=doc.trade.settlement.monetary_summation.allowance_total.get_value(),
|
|
40
|
-
tax_basis_total_amount=
|
|
41
|
+
tax_basis_total_amount=TradeCurrency.from_currency_tuple(
|
|
41
42
|
doc.trade.settlement.monetary_summation.tax_basis_total.get_currency()),
|
|
42
|
-
tax_total_amount=[
|
|
43
|
+
tax_total_amount=[TradeCurrency.from_currency_tuple(tpl) for tpl in
|
|
43
44
|
doc.trade.settlement.monetary_summation.tax_total_other_currency.get_currencies()],
|
|
44
45
|
# list of currency
|
|
45
|
-
grand_total_amount=
|
|
46
|
+
grand_total_amount=TradeCurrency.from_currency_tuple(
|
|
46
47
|
doc.trade.settlement.monetary_summation.grand_total.get_currency()),
|
|
47
48
|
total_prepaid_amount=doc.trade.settlement.monetary_summation.prepaid_total.get_value(),
|
|
48
49
|
due_payable_amount=doc.trade.settlement.monetary_summation.due_amount.get_value(),
|
|
@@ -50,11 +51,12 @@ class XRechnungCIIXMLParser(XMLAbstractXRechnungParser):
|
|
|
50
51
|
payment_means=cls().map_payment_means(
|
|
51
52
|
doc.trade.settlement.payment_means) if doc.trade.settlement.payment_means else None,
|
|
52
53
|
payment_terms=doc.trade.settlement.terms.get_string_elements("\n"),
|
|
53
|
-
|
|
54
|
+
sender=cls().map_trade_party(doc.trade.agreement.seller) if hasattr(doc.trade.agreement,
|
|
54
55
|
"seller") else None,
|
|
55
56
|
invoicee=cls().map_trade_party(doc.trade.agreement.invoicee) if hasattr(doc.trade.agreement,
|
|
56
57
|
"invoicee") else None,
|
|
57
|
-
|
|
58
|
+
receiver=cls().map_trade_party(doc.trade.agreement.buyer) if hasattr(doc.trade.agreement,
|
|
59
|
+
"buyer") else None,
|
|
58
60
|
payee=cls().map_trade_party(doc.trade.agreement.payee) if hasattr(doc.trade.agreement, "payee") else None,
|
|
59
61
|
trade_line_items=cls().map_trade_line_items(doc.trade.items) if hasattr(doc.trade, "items") else None,
|
|
60
62
|
applicable_trade_taxes=cls().map_trade_taxes(doc.trade.settlement.trade_tax) if hasattr(
|
|
@@ -63,9 +65,9 @@ class XRechnungCIIXMLParser(XMLAbstractXRechnungParser):
|
|
|
63
65
|
)
|
|
64
66
|
|
|
65
67
|
@classmethod
|
|
66
|
-
def map_trade_party(cls, trade_party: any) ->
|
|
68
|
+
def map_trade_party(cls, trade_party: any) -> TradeParty:
|
|
67
69
|
_global_id_schema, _global_id = cls().map_first_id(trade_party.global_id)
|
|
68
|
-
return
|
|
70
|
+
return TradeParty(
|
|
69
71
|
name=trade_party.name.get_string(),
|
|
70
72
|
description=trade_party.description.get_string(),
|
|
71
73
|
global_id=_global_id,
|
|
@@ -76,8 +78,8 @@ class XRechnungCIIXMLParser(XMLAbstractXRechnungParser):
|
|
|
76
78
|
trade_party, 'tax_registrations') else None,
|
|
77
79
|
fiscal_registration_number=cls().map_tax_registration(trade_party.tax_registrations, 'FC') if hasattr(
|
|
78
80
|
trade_party, 'tax_registrations') else None,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
address=cls().map_trade_address(trade_party.address) if hasattr(trade_party, 'address') else None,
|
|
82
|
+
contact=cls().map_trade_contact(trade_party.contact) if hasattr(trade_party, 'contact') else None,
|
|
81
83
|
id=trade_party.id.get_string() if hasattr(trade_party, 'id') else None,
|
|
82
84
|
)
|
|
83
85
|
|
|
@@ -110,8 +112,8 @@ class XRechnungCIIXMLParser(XMLAbstractXRechnungParser):
|
|
|
110
112
|
return None
|
|
111
113
|
|
|
112
114
|
@staticmethod
|
|
113
|
-
def map_trade_address(trade_address: any) ->
|
|
114
|
-
return
|
|
115
|
+
def map_trade_address(trade_address: any) -> TradePartyAddress:
|
|
116
|
+
return TradePartyAddress(
|
|
115
117
|
city_name=trade_address.city_name.get_string(),
|
|
116
118
|
country_id=trade_address.country_id.get_string(),
|
|
117
119
|
country_subdivision_id=trade_address.country_subdivision.get_string(),
|
|
@@ -122,8 +124,8 @@ class XRechnungCIIXMLParser(XMLAbstractXRechnungParser):
|
|
|
122
124
|
)
|
|
123
125
|
|
|
124
126
|
@staticmethod
|
|
125
|
-
def map_trade_contact(trade_contact: any) ->
|
|
126
|
-
return
|
|
127
|
+
def map_trade_contact(trade_contact: any) -> TradePartyContact:
|
|
128
|
+
return TradePartyContact(
|
|
127
129
|
name=trade_contact.person_name.get_string(),
|
|
128
130
|
email=trade_contact.email.get_string(),
|
|
129
131
|
telephone=trade_contact.telephone.get_string(),
|
|
@@ -132,24 +134,26 @@ class XRechnungCIIXMLParser(XMLAbstractXRechnungParser):
|
|
|
132
134
|
)
|
|
133
135
|
|
|
134
136
|
@staticmethod
|
|
135
|
-
def map_bank_account(payment_means: any) ->
|
|
136
|
-
return
|
|
137
|
-
iban="".join(payment_means.payee_account.iban.get_string().split()) if hasattr(payment_means,
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
def map_bank_account(payment_means: any) -> BankAccount:
|
|
138
|
+
return BankAccount(
|
|
139
|
+
iban="".join(payment_means.payee_account.iban.get_string().split()) if (hasattr(payment_means,
|
|
140
|
+
'payee_account')
|
|
141
|
+
and payment_means.payee_account.iban.get_string()) else None,
|
|
142
|
+
bic=payment_means.payee_institution.bic.get_string() if (hasattr(payment_means,
|
|
143
|
+
'payee_institution')
|
|
144
|
+
and payment_means.payee_institution.bic) else None,
|
|
141
145
|
)
|
|
142
146
|
|
|
143
147
|
@staticmethod
|
|
144
|
-
def map_financial_card(financial_card: any) ->
|
|
145
|
-
return
|
|
148
|
+
def map_financial_card(financial_card: any) -> FinancialCard:
|
|
149
|
+
return FinancialCard(
|
|
146
150
|
id=financial_card.id.get_string(),
|
|
147
151
|
cardholder_name=financial_card.cardholder_name.get_string(),
|
|
148
152
|
)
|
|
149
153
|
|
|
150
154
|
@classmethod
|
|
151
|
-
def map_payment_means(cls, payment_means: any) ->
|
|
152
|
-
return
|
|
155
|
+
def map_payment_means(cls, payment_means: any) -> TradePaymentMeans:
|
|
156
|
+
return TradePaymentMeans(
|
|
153
157
|
information=payment_means.information.get_string(),
|
|
154
158
|
type_code=payment_means.type_code.get_string(),
|
|
155
159
|
payee_account=cls().map_bank_account(payment_means) if hasattr(payment_means, 'payee_account') else None,
|
|
@@ -158,8 +162,8 @@ class XRechnungCIIXMLParser(XMLAbstractXRechnungParser):
|
|
|
158
162
|
)
|
|
159
163
|
|
|
160
164
|
@staticmethod
|
|
161
|
-
def map_trade_tax(trade_tax: any) ->
|
|
162
|
-
return
|
|
165
|
+
def map_trade_tax(trade_tax: any) -> AppliedTradeTax:
|
|
166
|
+
return AppliedTradeTax(
|
|
163
167
|
type_code=trade_tax.type_code.get_string(),
|
|
164
168
|
name=f"{trade_tax.type_code.get_string()} {trade_tax.rate_applicable_percent.get_value()}",
|
|
165
169
|
category_code=trade_tax.category_code.get_string(),
|
|
@@ -169,33 +173,33 @@ class XRechnungCIIXMLParser(XMLAbstractXRechnungParser):
|
|
|
169
173
|
)
|
|
170
174
|
|
|
171
175
|
@classmethod
|
|
172
|
-
def map_trade_taxes(cls, trade_taxes: any) -> [
|
|
176
|
+
def map_trade_taxes(cls, trade_taxes: any) -> [TradeLine]:
|
|
173
177
|
res = []
|
|
174
178
|
for child in trade_taxes.children:
|
|
175
179
|
res.append(cls().map_trade_tax(child))
|
|
176
180
|
return res
|
|
177
181
|
|
|
178
182
|
@classmethod
|
|
179
|
-
def map_trade_line(cls, trade_line: any) ->
|
|
180
|
-
return
|
|
183
|
+
def map_trade_line(cls, trade_line: any) -> TradeLine:
|
|
184
|
+
return TradeLine(
|
|
181
185
|
name=trade_line.product.name.get_string(),
|
|
182
186
|
description=trade_line.product.description.get_string(),
|
|
183
187
|
line_id=trade_line.document.line_id.get_string(),
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
188
|
+
unit_price=trade_line.agreement.net.amount.get_value(),
|
|
189
|
+
unit_price_gross=trade_line.agreement.gross.amount.get_value(),
|
|
190
|
+
quantity=trade_line.delivery.billed_quantity.get_value(),
|
|
187
191
|
global_product_id=trade_line.product.global_id.get_string(),
|
|
188
|
-
|
|
192
|
+
total_amount=trade_line.settlement.monetary_summation.total_amount.get_value(),
|
|
189
193
|
total_allowance_charge=trade_line.settlement.monetary_summation.total_allowance_charge.get_value(),
|
|
190
194
|
quantity_unit_code=trade_line.delivery.billed_quantity._unit_code,
|
|
191
195
|
seller_assigned_id=trade_line.product.seller_assigned_id.get_string(),
|
|
192
196
|
buyer_assigned_id=trade_line.product.buyer_assigned_id.get_string(),
|
|
193
197
|
global_product_scheme_id=trade_line.product.global_id._scheme_id,
|
|
194
|
-
|
|
198
|
+
tax=cls().map_trade_tax(trade_line.settlement.trade_tax)
|
|
195
199
|
)
|
|
196
200
|
|
|
197
201
|
@classmethod
|
|
198
|
-
def map_trade_line_items(cls, trade_line_items: any) -> [
|
|
202
|
+
def map_trade_line_items(cls, trade_line_items: any) -> [TradeLine]:
|
|
199
203
|
res = []
|
|
200
204
|
for child in trade_line_items.children:
|
|
201
205
|
res.append(cls().map_trade_line(child))
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
"""
|
|
2
2
|
This implements a mapper from a drafthorse parsed x-rechnung-xml to the internal XRechnung object
|
|
3
3
|
"""
|
|
4
|
+
import logging
|
|
4
5
|
from lxml import etree
|
|
5
6
|
|
|
6
|
-
from .model.
|
|
7
|
+
from .model.trade_document_types import TradeDocument
|
|
7
8
|
from .cii_dom_parser import XRechnungCIIXMLParser
|
|
8
9
|
from .ubl_sax_parser.xml_ubl_sax_parser import XRechnungUblXMLParser
|
|
10
|
+
from .model.xml_abstract_x_rechnung_parser import XMLAbstractXRechnungParser
|
|
9
11
|
|
|
12
|
+
_logger = logging.getLogger(__name__)
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
|
|
15
|
+
def parse_and_map_x_rechnung(_xml: bytes) -> TradeDocument:
|
|
12
16
|
"""
|
|
13
17
|
|
|
14
18
|
Args:
|
|
@@ -17,12 +21,24 @@ def parse_and_map_x_rechnung(_xml: bytes) -> XRechnung:
|
|
|
17
21
|
Returns: XRechnung
|
|
18
22
|
|
|
19
23
|
"""
|
|
24
|
+
_parser = get_xml_parser_for_doc_type(_xml)
|
|
25
|
+
if _parser is None:
|
|
26
|
+
raise ValueError('xml format not supported for any parser"')
|
|
27
|
+
return _parser.parse_and_map_x_rechnung(_xml)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_xml_parser_for_doc_type(_xml: bytes) -> XMLAbstractXRechnungParser:
|
|
20
31
|
_parser = None
|
|
21
32
|
tree = etree.fromstring(_xml)
|
|
22
33
|
if tree.tag == '{urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100}CrossIndustryInvoice':
|
|
23
34
|
_parser = XRechnungCIIXMLParser()
|
|
24
35
|
elif tree.tag == '{urn:oasis:names:specification:ubl:schema:xsd:Invoice-2}Invoice':
|
|
25
36
|
_parser = XRechnungUblXMLParser()
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return _parser
|
|
37
|
+
else:
|
|
38
|
+
_logger.warning(f'No parser found, unsupported XML tag: {tree.tag}')
|
|
39
|
+
return _parser
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def check_if_parser_is_available(_xml: bytes) -> bool:
|
|
43
|
+
return get_xml_parser_for_doc_type(_xml) is not None
|
|
44
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .trade_document_types import TradeDocument, TradeParty, TradePartyAddress, TradeCurrency, TradePartyContact, \
|
|
2
|
+
TradeLine, TradePaymentMeans, AppliedTradeTax, BankAccount, FinancialCard, ubl_doc_codes
|
|
3
|
+
from .xml_abstract_x_rechnung_parser import XMLAbstractXRechnungParser
|
|
4
|
+
|
|
5
|
+
__all__ = ["XMLAbstractXRechnungParser",
|
|
6
|
+
"TradeDocument",
|
|
7
|
+
"TradeParty",
|
|
8
|
+
"TradePartyAddress",
|
|
9
|
+
"TradeCurrency",
|
|
10
|
+
"TradePartyContact",
|
|
11
|
+
"TradeLine",
|
|
12
|
+
"TradePaymentMeans",
|
|
13
|
+
"AppliedTradeTax",
|
|
14
|
+
"BankAccount",
|
|
15
|
+
"FinancialCard",
|
|
16
|
+
"ubl_doc_codes"
|
|
17
|
+
]
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
import datetime
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
UBL Document Type (XML Root) Description UNCL 1001 Code (Example)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
ubl_doc_codes = {
|
|
9
|
+
"ApplicationResponse": ('ApplicationResponse', 431, 'Response to an application/message'),
|
|
10
|
+
"Catalogue": ('Catalogue', 71, 'Product catalogue'),
|
|
11
|
+
"CatalogueRequest": ('CatalogueRequest', 171, 'Catalogue request'),
|
|
12
|
+
"CreditNote": ('CreditNote', 381, 'Commercial Credit note'),
|
|
13
|
+
"DebitNote": ('DebitNote', 383, 'Debit note'),
|
|
14
|
+
"DespatchAdvice": ('DespatchAdvice', 250, 'Despatch advice (Advance Ship Notice)'),
|
|
15
|
+
"Invoice": ('Invoice', 380, 'Commercial Invoice'),
|
|
16
|
+
"Order": ('Order', 220, 'Order'),
|
|
17
|
+
"OrderChange": ('OrderChange', 222, 'Order change'),
|
|
18
|
+
"OrderResponse": ('OrderResponse', 255, 'Order response (confirmation/rejection)'),
|
|
19
|
+
"Quotation": ('Quotation', 83, 'Quotation'),
|
|
20
|
+
"RequestForQuotation": ('RequestForQuotation', 135, 'Request for quotation'),
|
|
21
|
+
"RemittanceAdvice": ('RemittanceAdvice', 256, 'Remittance advice'),
|
|
22
|
+
"Statement": ('Statement', 86, 'Account statement / Balance confirmation'),
|
|
23
|
+
"UtilityStatement": ('Utility statement', 490, 'Statement (electricity, gas, etc.)'),
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class TradePartyAddress:
|
|
29
|
+
post_code: str = None
|
|
30
|
+
city_name: str = None
|
|
31
|
+
country_id: str = None
|
|
32
|
+
country_subdivision_id: str = None
|
|
33
|
+
address_line_1: str = None
|
|
34
|
+
address_line_2: str = None
|
|
35
|
+
address_line_3: str = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class TradePartyContact:
|
|
40
|
+
name: str = None
|
|
41
|
+
department_name: str = None
|
|
42
|
+
telephone: str = None
|
|
43
|
+
fax: str = None
|
|
44
|
+
email: str = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class TradeParty:
|
|
49
|
+
name: str= None
|
|
50
|
+
description: str = None # 'Description'
|
|
51
|
+
global_id: int = 0 # 'Global ID'
|
|
52
|
+
global_id_schema: str = None # 'Global Schema'
|
|
53
|
+
id: str = None # 'id'
|
|
54
|
+
address: TradePartyAddress | None = None
|
|
55
|
+
contact: TradePartyContact | None = None
|
|
56
|
+
email: str = None # 'Email'
|
|
57
|
+
vat_registration_number: str | None = None
|
|
58
|
+
fiscal_registration_number: str | None = None
|
|
59
|
+
legal_registration_number: str | None = None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class AppliedTradeTax:
|
|
64
|
+
name: str = None
|
|
65
|
+
type_code: str = None
|
|
66
|
+
category_code: str = None
|
|
67
|
+
applicable_percent: float = None
|
|
68
|
+
basis_amount: float = None
|
|
69
|
+
calculated_amount: float = None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class TradeLine:
|
|
74
|
+
line_id: int = 0
|
|
75
|
+
article_code: str | None = None
|
|
76
|
+
name: str | None = None
|
|
77
|
+
description: str | None = None
|
|
78
|
+
quantity: float = None
|
|
79
|
+
quantity_unit_code: str = None
|
|
80
|
+
unit_price: float = None
|
|
81
|
+
unit_price_gross: float = None
|
|
82
|
+
tax: AppliedTradeTax | None = None
|
|
83
|
+
total_amount_net: float = None
|
|
84
|
+
total_amount: float = None
|
|
85
|
+
total_allowance_charge: float = None
|
|
86
|
+
global_product_id: str = None # 'Global Product ID')
|
|
87
|
+
global_product_scheme_id: str = None # 'Global Product Scheme ID')
|
|
88
|
+
seller_assigned_id: str = None # 'Seller Assigned ID')
|
|
89
|
+
buyer_assigned_id: str = None # 'Buyer Assigned ID')
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass
|
|
93
|
+
class TradeCurrency:
|
|
94
|
+
amount: float
|
|
95
|
+
currency_code: str
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def from_currency_tuple(currency_tuple: tuple) -> 'TradeCurrency':
|
|
99
|
+
return TradeCurrency(*currency_tuple)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class BankAccount:
|
|
104
|
+
iban: str | None = None
|
|
105
|
+
bic: str = None
|
|
106
|
+
name: str = None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class FinancialCard:
|
|
111
|
+
id: str
|
|
112
|
+
cardholder_name: str | None = None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass
|
|
116
|
+
class TradePaymentMeans:
|
|
117
|
+
id: str = None
|
|
118
|
+
type_code: str = None
|
|
119
|
+
information: str = None
|
|
120
|
+
financial_card: FinancialCard = None
|
|
121
|
+
payee_account: BankAccount = None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass
|
|
125
|
+
class TradeDocument:
|
|
126
|
+
"""
|
|
127
|
+
Model of a Trade Document
|
|
128
|
+
"""
|
|
129
|
+
name: str = None
|
|
130
|
+
doc_type_code: int = 0 # Document Type Code: ubl_doc_codes
|
|
131
|
+
doc_type_name: str = None
|
|
132
|
+
doc_id: str = None
|
|
133
|
+
project: str = None
|
|
134
|
+
issued_date_time: datetime = None # 'Date'
|
|
135
|
+
delivered_date_time: datetime = None # 'Delivered Date'
|
|
136
|
+
languages: str = None # 'Languages'
|
|
137
|
+
notes: str = None # 'Notes'
|
|
138
|
+
sender_reference: str = None # 'Buyer Reference'
|
|
139
|
+
receiver_reference: str | None = None
|
|
140
|
+
dispatch_reference: str | None = None
|
|
141
|
+
order_reference: str | None = None
|
|
142
|
+
sender: TradeParty = None
|
|
143
|
+
receiver: TradeParty = None
|
|
144
|
+
payee: TradeParty = None
|
|
145
|
+
invoicee: TradeParty = None
|
|
146
|
+
currency_code: str = None # 'Currency Code'
|
|
147
|
+
payment_means: TradePaymentMeans = None
|
|
148
|
+
payment_terms: str | None = None # 'Payment Terms'
|
|
149
|
+
due_date_time: datetime = None
|
|
150
|
+
line_total_amount: float = None # 'Line Total Amount'
|
|
151
|
+
charge_total_amount: float = None # 'Charge Total Amount'
|
|
152
|
+
allowance_total_amount: float = None # 'Allowance Total Amount'
|
|
153
|
+
tax_basis_total_amount: TradeCurrency = None
|
|
154
|
+
tax_total_amount: [TradeCurrency] = None # 'Tax Grand Total Amount'
|
|
155
|
+
grand_total_amount: TradeCurrency = None # 'Grand Total Amount'
|
|
156
|
+
total_prepaid_amount: float = None # 'Total Prepaid Amount'
|
|
157
|
+
due_payable_amount: float = None # 'Due Payable Amount'
|
|
158
|
+
trade_line_items: [TradeLine] = None
|
|
159
|
+
applicable_trade_taxes: [AppliedTradeTax] = None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
|
|
3
|
-
from ..model.
|
|
3
|
+
from ..model.trade_document_types import TradeDocument
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class XMLAbstractXRechnungParser(ABC):
|
|
@@ -17,5 +17,5 @@ class XMLAbstractXRechnungParser(ABC):
|
|
|
17
17
|
|
|
18
18
|
@staticmethod
|
|
19
19
|
@abstractmethod
|
|
20
|
-
def parse_and_map_x_rechnung(_xml: any) ->
|
|
20
|
+
def parse_and_map_x_rechnung(_xml: any) -> TradeDocument:
|
|
21
21
|
pass
|
|
@@ -6,7 +6,7 @@ from facturx import get_facturx_xml_from_pdf
|
|
|
6
6
|
|
|
7
7
|
from . import get_checked_file_path
|
|
8
8
|
from edi_invoice_parser.cross_industry_invoice_mapper import parse_and_map_x_rechnung
|
|
9
|
-
from . import
|
|
9
|
+
from . import TradeDocument
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class XRechnungEinfachTestCase(unittest.TestCase):
|
|
@@ -24,21 +24,23 @@ class XRechnungEinfachTestCase(unittest.TestCase):
|
|
|
24
24
|
('xml', 'e_invoicing_EN16931/CII_business_example_02.xml'),
|
|
25
25
|
('xml', 'e_invoicing_EN16931/CII_business_example_Z.xml'),
|
|
26
26
|
('xml', 'e_invoicing_EN16931/CII_example1.xml'),
|
|
27
|
-
|
|
27
|
+
('xml', 'e_invoicing_EN16931/CII_example2.xml'), # throws error
|
|
28
28
|
('xml', 'e_invoicing_EN16931/CII_example3.xml'),
|
|
29
29
|
('xml', 'e_invoicing_EN16931/CII_example4.xml'),
|
|
30
|
-
|
|
30
|
+
('xml', 'e_invoicing_EN16931/CII_example5.xml'), # throws error
|
|
31
31
|
('xml', 'e_invoicing_EN16931/CII_example6.xml'),
|
|
32
32
|
('xml', 'e_invoicing_EN16931/CII_example7.xml'),
|
|
33
33
|
('xml', 'e_invoicing_EN16931/CII_example8.xml'),
|
|
34
34
|
('xml', 'e_invoicing_EN16931/CII_example9.xml'),
|
|
35
35
|
('xml', 'ubl/RG00343552.xml'),
|
|
36
|
+
('xml', 'ubl/RG00350365.xml'),
|
|
36
37
|
('xml', 'ubl/ubl_invoice_example.xml'),
|
|
37
38
|
('xml', 'ubl/UBL-Invoice-2.1-Example.xml'),
|
|
38
39
|
('pdf', 'zugferd/BASIC-WL_Einfach/BASIC-WL_Einfach.pdf'),
|
|
39
40
|
# ('pdf', 'zugferd/XRECHNUNG_Einfach/XRECHNUNG_Einfach.pdf'), # needs some checks, why test failed
|
|
40
41
|
# ("pdf", "zugferd/XRECHNUNG_Elektron/XRECHNUNG_Elektron.pdf"), # needs some checks, why test failed
|
|
41
|
-
('pdf', 'odoo_generated/INV_2025_00001.pdf')
|
|
42
|
+
('pdf', 'odoo_generated/INV_2025_00001.pdf'),
|
|
43
|
+
|
|
42
44
|
])
|
|
43
45
|
def test_x_rechnung_files(self, file_type, file_path):
|
|
44
46
|
print(f"start testing with file: {file_path}")
|
|
@@ -49,10 +51,10 @@ class XRechnungEinfachTestCase(unittest.TestCase):
|
|
|
49
51
|
else:
|
|
50
52
|
raise AssertionError(f'File type {file_type} not supported')
|
|
51
53
|
assert _parsed is not None
|
|
52
|
-
res_dict = _parsed.map_to_dict()
|
|
53
|
-
print(jsonpickle.dumps(
|
|
54
|
+
# res_dict = _parsed.map_to_dict()
|
|
55
|
+
print(jsonpickle.dumps(_parsed))
|
|
54
56
|
|
|
55
|
-
def _parse_xml(self, filepath) ->
|
|
57
|
+
def _parse_xml(self, filepath) -> TradeDocument:
|
|
56
58
|
_file_path, _exists, _is_dir = get_checked_file_path(filepath, __file__)
|
|
57
59
|
self.assertTrue(_exists)
|
|
58
60
|
print(f"\n_parse_xml: file_path={_file_path}")
|
|
@@ -62,7 +64,7 @@ class XRechnungEinfachTestCase(unittest.TestCase):
|
|
|
62
64
|
self.assertIsNotNone(res)
|
|
63
65
|
return res
|
|
64
66
|
|
|
65
|
-
def _parse_pdf(self, filepath) ->
|
|
67
|
+
def _parse_pdf(self, filepath) -> TradeDocument:
|
|
66
68
|
_file_path, _exists, _is_dir = get_checked_file_path(filepath, __file__)
|
|
67
69
|
self.assertTrue(_exists)
|
|
68
70
|
print(f"\n_parse_pdf: file_path={_file_path}")
|