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.
Files changed (47) hide show
  1. {mikrowerk_edi_invoicing-0.3.1/mikrowerk_edi_invoicing.egg-info → mikrowerk_edi_invoicing-0.6.5}/PKG-INFO +5 -3
  2. mikrowerk_edi_invoicing-0.6.5/edi_invoice_parser/__init__.py +20 -0
  3. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/dom_elements_helper.py +10 -3
  4. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/delivery.py +4 -0
  5. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/elements.py +2 -1
  6. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/payment.py +8 -0
  7. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/references.py +6 -0
  8. {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
  9. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cross_industry_invoice_mapper.py +21 -5
  10. mikrowerk_edi_invoicing-0.6.5/edi_invoice_parser/model/__init__.py +17 -0
  11. mikrowerk_edi_invoicing-0.6.5/edi_invoice_parser/model/trade_document_types.py +159 -0
  12. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/model/xml_abstract_x_rechnung_parser.py +2 -2
  13. mikrowerk_edi_invoicing-0.6.5/edi_invoice_parser/tests/__init__.py +3 -0
  14. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/tests/test_parse_x_rechnung.py +10 -8
  15. {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
  16. mikrowerk_edi_invoicing-0.6.5/edi_invoice_parser/util/timer_helper.py +11 -0
  17. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5/mikrowerk_edi_invoicing.egg-info}/PKG-INFO +5 -3
  18. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/mikrowerk_edi_invoicing.egg-info/SOURCES.txt +4 -2
  19. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/mikrowerk_edi_invoicing.egg-info/requires.txt +1 -0
  20. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/setup.py +5 -9
  21. mikrowerk_edi_invoicing-0.3.1/edi_invoice_parser/__init__.py +0 -17
  22. mikrowerk_edi_invoicing-0.3.1/edi_invoice_parser/model/__init__.py +0 -4
  23. mikrowerk_edi_invoicing-0.3.1/edi_invoice_parser/model/x_rechnung.py +0 -263
  24. mikrowerk_edi_invoicing-0.3.1/edi_invoice_parser/tests/__init__.py +0 -3
  25. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/LICENSE +0 -0
  26. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/README.md +0 -0
  27. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/__init__.py +0 -0
  28. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/__init__.py +0 -0
  29. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/accounting.py +0 -0
  30. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/container.py +0 -0
  31. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/document.py +0 -0
  32. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/fields.py +0 -0
  33. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/note.py +0 -0
  34. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/party.py +0 -0
  35. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/product.py +0 -0
  36. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/trade.py +0 -0
  37. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/models/tradelines.py +0 -0
  38. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/pdf.py +0 -0
  39. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/utils.py +0 -0
  40. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/cii_dom_parser/xmp_schema.py +0 -0
  41. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/tests/test_iban_handling.py +0 -0
  42. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/ubl_sax_parser/__init__.py +0 -0
  43. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/util/__init__.py +0 -0
  44. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/edi_invoice_parser/util/file_helper.py +0 -0
  45. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/mikrowerk_edi_invoicing.egg-info/dependency_links.txt +0 -0
  46. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/mikrowerk_edi_invoicing.egg-info/top_level.txt +0 -0
  47. {mikrowerk_edi_invoicing-0.3.1 → mikrowerk_edi_invoicing-0.6.5}/setup.cfg +0 -0
@@ -1,7 +1,7 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: mikrowerk_edi_invoicing
3
- Version: 0.3.1
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
- return self._amount, self._currency
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 = 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) -> XRechnung:
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) -> XRechnung:
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 XRechnung(
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
- buyer_reference=doc.trade.agreement.buyer_reference.get_string(),
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=XRechnungCurrency.from_currency_tuple(
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=[XRechnungCurrency.from_currency_tuple(tpl) for tpl in
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=XRechnungCurrency.from_currency_tuple(
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
- seller=cls().map_trade_party(doc.trade.agreement.seller) if hasattr(doc.trade.agreement,
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
- buyer=cls().map_trade_party(doc.trade.agreement.buyer) if hasattr(doc.trade.agreement, "buyer") else None,
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) -> XRechnungTradeParty:
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 XRechnungTradeParty(
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
- postal_address=cls().map_trade_address(trade_party.address) if hasattr(trade_party, 'address') else None,
80
- trade_contact=cls().map_trade_contact(trade_party.contact) if hasattr(trade_party, 'contact') else None,
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) -> XRechnungTradeAddress:
114
- return XRechnungTradeAddress(
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) -> XRechnungTradeContact:
126
- return XRechnungTradeContact(
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) -> XRechnungBankAccount:
136
- return XRechnungBankAccount(
137
- iban="".join(payment_means.payee_account.iban.get_string().split()) if hasattr(payment_means,
138
- 'payee_account') else None,
139
- bic=payment_means.payee_institution.bic.get_string() if hasattr(payment_means,
140
- 'payee_institution') else None,
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) -> XRechnungFinancialCard:
145
- return XRechnungFinancialCard(
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) -> XRechnungPaymentMeans:
152
- return XRechnungPaymentMeans(
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) -> XRechnungAppliedTradeTax:
162
- return XRechnungAppliedTradeTax(
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) -> [XRechnungTradeLine]:
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) -> XRechnungTradeLine:
180
- return XRechnungTradeLine(
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
- price_unit=trade_line.agreement.net.amount.get_value(),
185
- price_unit_gross=trade_line.agreement.gross.amount.get_value(),
186
- quantity_billed=trade_line.delivery.billed_quantity.get_value(),
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
- total_amount_net=trade_line.settlement.monetary_summation.total_amount.get_value(),
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
- trade_tax=cls().map_trade_tax(trade_line.settlement.trade_tax)
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) -> [XRechnungTradeLine]:
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.x_rechnung import XRechnung
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
- def parse_and_map_x_rechnung(_xml: bytes) -> XRechnung:
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
- if _parser is None:
27
- raise ValueError(f'xml format not supported: "{tree.tag}"')
28
- return _parser.parse_and_map_x_rechnung(_xml)
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.x_rechnung import XRechnung
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) -> XRechnung:
20
+ def parse_and_map_x_rechnung(_xml: any) -> TradeDocument:
21
21
  pass
@@ -0,0 +1,3 @@
1
+ from ..util.file_helper import get_checked_file_path
2
+ from ..model.trade_document_types import TradeDocument
3
+ __all__ = ["get_checked_file_path", "TradeDocument"]
@@ -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 XRechnung
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
- # # ('xml', 'e_invoicing_EN16931/CII_example2.xml'), # throws error
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
- # # ('xml', 'e_invoicing_EN16931/CII_example5.xml'), # throws error
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(res_dict))
54
+ # res_dict = _parsed.map_to_dict()
55
+ print(jsonpickle.dumps(_parsed))
54
56
 
55
- def _parse_xml(self, filepath) -> XRechnung:
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) -> XRechnung:
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}")