PyCheval 0.1.0__tar.gz → 0.2.0__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 (28) hide show
  1. pycheval-0.2.0/PKG-INFO +123 -0
  2. pycheval-0.2.0/README.md +105 -0
  3. pycheval-0.2.0/pyproject.toml +77 -0
  4. {pycheval-0.1.0 → pycheval-0.2.0}/src/pycheval/__init__.py +6 -1
  5. pycheval-0.2.0/src/pycheval/_test_data.py +176 -0
  6. {pycheval-0.1.0 → pycheval-0.2.0}/src/pycheval/const.py +9 -0
  7. {pycheval-0.1.0 → pycheval-0.2.0}/src/pycheval/exc.py +4 -0
  8. {pycheval-0.1.0 → pycheval-0.2.0}/src/pycheval/format.py +2 -2
  9. {pycheval-0.1.0 → pycheval-0.2.0}/src/pycheval/generate.py +10 -157
  10. pycheval-0.2.0/src/pycheval/locale/de/LC_MESSAGES/pycheval.mo +0 -0
  11. {pycheval-0.1.0 → pycheval-0.2.0}/src/pycheval/locale/de/LC_MESSAGES/pycheval.po +164 -104
  12. {pycheval-0.1.0 → pycheval-0.2.0}/src/pycheval/model.py +51 -10
  13. {pycheval-0.1.0 → pycheval-0.2.0}/src/pycheval/money.py +21 -13
  14. {pycheval-0.1.0 → pycheval-0.2.0}/src/pycheval/parse.py +11 -10
  15. pycheval-0.2.0/src/pycheval/pdf_common.py +23 -0
  16. pycheval-0.2.0/src/pycheval/pdf_embed.py +233 -0
  17. {pycheval-0.1.0 → pycheval-0.2.0}/src/pycheval/pdf_extract.py +1 -22
  18. {pycheval-0.1.0 → pycheval-0.2.0}/src/pycheval/pdf_parse.py +2 -1
  19. {pycheval-0.1.0 → pycheval-0.2.0}/src/pycheval/types.py +4 -1
  20. pycheval-0.1.0/PKG-INFO +0 -93
  21. pycheval-0.1.0/README.md +0 -70
  22. pycheval-0.1.0/pyproject.toml +0 -54
  23. {pycheval-0.1.0 → pycheval-0.2.0}/LICENSE +0 -0
  24. {pycheval-0.1.0 → pycheval-0.2.0}/src/pycheval/_locale.py +0 -0
  25. {pycheval-0.1.0 → pycheval-0.2.0}/src/pycheval/countries.py +0 -0
  26. {pycheval-0.1.0 → pycheval-0.2.0}/src/pycheval/py.typed +0 -0
  27. {pycheval-0.1.0 → pycheval-0.2.0}/src/pycheval/quantities.py +0 -0
  28. {pycheval-0.1.0 → pycheval-0.2.0}/src/pycheval/type_codes.py +0 -0
@@ -0,0 +1,123 @@
1
+ Metadata-Version: 2.3
2
+ Name: PyCheval
3
+ Version: 0.2.0
4
+ Summary: Factur-X/ZUGFeRD parsing and generation library for Python
5
+ License: Apache-2.0
6
+ Keywords: factur-x,zugferd,invoice,billing
7
+ Author: Sebastian Rittau
8
+ Author-email: sebastian.rittau@zfutura.de
9
+ Requires-Python: >= 3.11
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Topic :: File Formats
13
+ Classifier: Topic :: Office/Business :: Financial :: Accounting
14
+ Requires-Dist: pypdf (>=5.4.0,<6)
15
+ Project-URL: GitHub, https://github.com/zfutura/pycheval
16
+ Description-Content-Type: text/markdown
17
+
18
+ # PyCheval – Factur-X/ZUGFeRD parsing and generation library for Python
19
+
20
+ [![GitHub](https://img.shields.io/github/release/zfutura/pycheval/all.svg)](https://github.com/zfutura/pycheval/releases/)
21
+ ![Supported Python Versions](https://img.shields.io/pypi/pyversions/pycheval)
22
+ [![Apache 2.0 License](https://img.shields.io/github/license/zfutura/pycheval)](https://github.com/zfutura/pycheval/blob/main/LICENSE)
23
+ [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/zfutura/pycheval/test-and-lint.yml)](https://github.com/zfutura/pycheval/actions/workflows/test-and-lint)
24
+
25
+ Factur-X (also called ZUGFeRD in Germany) is a Franco-German standard for
26
+ electronic invoices. Structured XML data is embedded in PDF-A/3 files,
27
+ allowing invoices to be processed automatically while still being displayed in
28
+ standard PDF readers. Factur-X supports EN 16931, the European standard for
29
+ electronic invoicing.
30
+
31
+ See the [Factur-X website (French)](https://www.factur-x.org/) or
32
+ [FeRD website (German)](https://www.ferd-net.de/) for more information.
33
+
34
+ This library supports reading and writing PDF and XML files according to
35
+ Factur-X Version 1.07.3 (aka ZUGFeRD 2.3.3). The following Factur-X profiles
36
+ are currently supported:
37
+
38
+ - Minimum
39
+ - Basic WL
40
+ - Basic
41
+ - EN 16931 (Comfort)
42
+
43
+ Extended and XRechnung profiles are not yet supported.
44
+
45
+ **Warning**: This library is still in early development. The API may change
46
+ frequently, and not all features are implemented yet.
47
+
48
+ ## Usage
49
+
50
+ ### Installation
51
+
52
+ You can install PyCheval from PyPI:
53
+
54
+ ```bash
55
+ pip install PyCheval
56
+ ```
57
+
58
+ ### Generating Factur-X
59
+
60
+ PyCheval supports several Factur-X profile levels, each with different levels of detail and complexity. First, you need to create an instance of the appropriate profile class. Then, you can pass that instance to one of the generation functions.
61
+
62
+ ```python
63
+ from datetime import date
64
+ from pycheval import EN16931Invoice, Money, generate_xml
65
+
66
+ invoice = EN16931Invoice(
67
+ invoice_number="2021-123",
68
+ invoice_date=date(2021, 4, 13),
69
+ grand_total=Money("100.00", "EUR"),
70
+ ... # See the class documentation for all required and optional fields.
71
+ )
72
+ xml_string = generate_xml(invoice)
73
+ ```
74
+
75
+ To embed the generated XML into a PDF, you can use the `embed_invoice_in_pdf` function:
76
+
77
+ ```python
78
+ from pathlib import Path
79
+ from pycheval import embed_invoice_in_pdf
80
+
81
+ invoice = ...
82
+ pdf_bytes = embed_invoice_in_pdf("invoice.pdf", invoice)
83
+ Path("invoice_with_facturx.pdf").write_bytes(pdf_bytes)
84
+ ```
85
+
86
+ ### Parsing Factur-X PDF files
87
+
88
+ PyCheval can parse Factur-X PDF files and extract the embedded invoice data. The parser will return an instance of the appropriate profile class.
89
+
90
+ ```python
91
+ from pycheval import parse_pdf
92
+
93
+ invoice = parse_pdf("invoice.pdf") # Returns MinimumInvoice or a subclass
94
+ ```
95
+
96
+ ### Printing invoices
97
+
98
+ To display a formatted Factur-X invoice in the terminal, use the `format_invoice_as_text()` function:
99
+
100
+ ```python
101
+ from pycheval import format_invoice_as_text
102
+
103
+ invoice = EN16931Invoice(...)
104
+ print(format_invoice_as_text(invoice))
105
+ ```
106
+
107
+ ### License and Warranty
108
+
109
+ **Copyright © ZFutura GmbH**
110
+
111
+ This project is licensed under the [Apache License 2.0](LICENSE).
112
+
113
+ **Disclaimer**: The software is provided "as is", without warranty of any kind.
114
+ The authors are not liable for any damages arising from the use of this
115
+ software. In particular, the authors do not guarantee that invoices generated
116
+ or parsed by this library will be valid or compliant with any standards, nor
117
+ that they are suitable for any specific purpose.
118
+
119
+ **Important**: It is the user's responsibility to ensure that generated
120
+ invoices meet all legal and regulatory requirements for their jurisdiction.
121
+
122
+ See the [LICENSE](LICENSE) file for the complete disclaimer and license terms.
123
+
@@ -0,0 +1,105 @@
1
+ # PyCheval – Factur-X/ZUGFeRD parsing and generation library for Python
2
+
3
+ [![GitHub](https://img.shields.io/github/release/zfutura/pycheval/all.svg)](https://github.com/zfutura/pycheval/releases/)
4
+ ![Supported Python Versions](https://img.shields.io/pypi/pyversions/pycheval)
5
+ [![Apache 2.0 License](https://img.shields.io/github/license/zfutura/pycheval)](https://github.com/zfutura/pycheval/blob/main/LICENSE)
6
+ [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/zfutura/pycheval/test-and-lint.yml)](https://github.com/zfutura/pycheval/actions/workflows/test-and-lint)
7
+
8
+ Factur-X (also called ZUGFeRD in Germany) is a Franco-German standard for
9
+ electronic invoices. Structured XML data is embedded in PDF-A/3 files,
10
+ allowing invoices to be processed automatically while still being displayed in
11
+ standard PDF readers. Factur-X supports EN 16931, the European standard for
12
+ electronic invoicing.
13
+
14
+ See the [Factur-X website (French)](https://www.factur-x.org/) or
15
+ [FeRD website (German)](https://www.ferd-net.de/) for more information.
16
+
17
+ This library supports reading and writing PDF and XML files according to
18
+ Factur-X Version 1.07.3 (aka ZUGFeRD 2.3.3). The following Factur-X profiles
19
+ are currently supported:
20
+
21
+ - Minimum
22
+ - Basic WL
23
+ - Basic
24
+ - EN 16931 (Comfort)
25
+
26
+ Extended and XRechnung profiles are not yet supported.
27
+
28
+ **Warning**: This library is still in early development. The API may change
29
+ frequently, and not all features are implemented yet.
30
+
31
+ ## Usage
32
+
33
+ ### Installation
34
+
35
+ You can install PyCheval from PyPI:
36
+
37
+ ```bash
38
+ pip install PyCheval
39
+ ```
40
+
41
+ ### Generating Factur-X
42
+
43
+ PyCheval supports several Factur-X profile levels, each with different levels of detail and complexity. First, you need to create an instance of the appropriate profile class. Then, you can pass that instance to one of the generation functions.
44
+
45
+ ```python
46
+ from datetime import date
47
+ from pycheval import EN16931Invoice, Money, generate_xml
48
+
49
+ invoice = EN16931Invoice(
50
+ invoice_number="2021-123",
51
+ invoice_date=date(2021, 4, 13),
52
+ grand_total=Money("100.00", "EUR"),
53
+ ... # See the class documentation for all required and optional fields.
54
+ )
55
+ xml_string = generate_xml(invoice)
56
+ ```
57
+
58
+ To embed the generated XML into a PDF, you can use the `embed_invoice_in_pdf` function:
59
+
60
+ ```python
61
+ from pathlib import Path
62
+ from pycheval import embed_invoice_in_pdf
63
+
64
+ invoice = ...
65
+ pdf_bytes = embed_invoice_in_pdf("invoice.pdf", invoice)
66
+ Path("invoice_with_facturx.pdf").write_bytes(pdf_bytes)
67
+ ```
68
+
69
+ ### Parsing Factur-X PDF files
70
+
71
+ PyCheval can parse Factur-X PDF files and extract the embedded invoice data. The parser will return an instance of the appropriate profile class.
72
+
73
+ ```python
74
+ from pycheval import parse_pdf
75
+
76
+ invoice = parse_pdf("invoice.pdf") # Returns MinimumInvoice or a subclass
77
+ ```
78
+
79
+ ### Printing invoices
80
+
81
+ To display a formatted Factur-X invoice in the terminal, use the `format_invoice_as_text()` function:
82
+
83
+ ```python
84
+ from pycheval import format_invoice_as_text
85
+
86
+ invoice = EN16931Invoice(...)
87
+ print(format_invoice_as_text(invoice))
88
+ ```
89
+
90
+ ### License and Warranty
91
+
92
+ **Copyright © ZFutura GmbH**
93
+
94
+ This project is licensed under the [Apache License 2.0](LICENSE).
95
+
96
+ **Disclaimer**: The software is provided "as is", without warranty of any kind.
97
+ The authors are not liable for any damages arising from the use of this
98
+ software. In particular, the authors do not guarantee that invoices generated
99
+ or parsed by this library will be valid or compliant with any standards, nor
100
+ that they are suitable for any specific purpose.
101
+
102
+ **Important**: It is the user's responsibility to ensure that generated
103
+ invoices meet all legal and regulatory requirements for their jurisdiction.
104
+
105
+ See the [LICENSE](LICENSE) file for the complete disclaimer and license terms.
@@ -0,0 +1,77 @@
1
+ [project]
2
+ name = "PyCheval"
3
+ version = "0.2.0"
4
+ description = "Factur-X/ZUGFeRD parsing and generation library for Python"
5
+ authors = [{ name = "Sebastian Rittau", email = "sebastian.rittau@zfutura.de" }]
6
+ readme = "README.md"
7
+ license = "Apache-2.0"
8
+ keywords = ["factur-x", "zugferd", "invoice", "billing"]
9
+ classifiers = [
10
+ "Development Status :: 3 - Alpha",
11
+ "Intended Audience :: Developers",
12
+ "Topic :: File Formats",
13
+ "Topic :: Office/Business :: Financial :: Accounting",
14
+ ]
15
+ requires-python = ">= 3.11"
16
+ dependencies = [
17
+ "pypdf >= 5.4.0, < 6",
18
+ ]
19
+
20
+ [project.urls]
21
+ GitHub = "https://github.com/zfutura/pycheval"
22
+
23
+ [tool.poetry]
24
+ include = [{ path = "src/pycheval/locale/**/*.mo", format = ["sdist", "wheel"] }]
25
+ exclude = ["src/pycheval/test_*"]
26
+
27
+ [tool.poetry.group.dev.dependencies]
28
+ babel = "~=2.17.0"
29
+ mypy = "~=1.17.0"
30
+ poethepoet = "^0.36.0"
31
+ pytest = "^8.4.1"
32
+ ruff = "^0.12.1"
33
+
34
+ [tool.poe.tasks.lint]
35
+ cmd = "ruff check src"
36
+ help = "Lint the source code with ruff"
37
+
38
+ [tool.poe.tasks.test]
39
+ cmd = "pytest src"
40
+ help = "Run unit and integration tests with pytest"
41
+
42
+ [tool.poe.tasks.typecheck]
43
+ cmd = "mypy src"
44
+ help = "Type check the source code with mypy"
45
+
46
+ [tool.poe.tasks.i18n-extract]
47
+ cmd = "pybabel extract -o messages.pot src"
48
+ help = "Extract messages for translation"
49
+
50
+ [tool.poe.tasks.i18n-init]
51
+ cmd = "pybabel init -D pycheval -i messages.pot -d src/pycheval/locale -l "
52
+ help = "Initialize translation files for a new language"
53
+
54
+ [tool.poe.tasks.i18n-update]
55
+ cmd = "pybabel update -D pycheval -i messages.pot -d src/pycheval/locale"
56
+ help = "Update translation files from the template"
57
+
58
+ [tool.poe.tasks.i18n-compile]
59
+ cmd = "pybabel compile -D pycheval -d src/pycheval/locale"
60
+ help = "Compile translation files"
61
+
62
+ [tool.ruff]
63
+ target-version = "py312"
64
+ line-length = 79
65
+
66
+ [tool.ruff.lint]
67
+ select = ["E", "F", "B", "I"]
68
+
69
+ [tool.ruff.lint.isort]
70
+ combine-as-imports = true
71
+
72
+ [tool.mypy]
73
+ strict = true
74
+
75
+ [build-system]
76
+ requires = ["poetry-core"]
77
+ build-backend = "poetry.core.masonry.api"
@@ -3,12 +3,17 @@ from typing import Final
3
3
  from .exc import * # noqa: F403
4
4
  from .format import format_invoice_as_text as format_invoice_as_text
5
5
  from .generate import (
6
- generate as generate,
7
6
  generate_et as generate_et,
7
+ generate_xml as generate_xml,
8
8
  )
9
9
  from .model import * # noqa: F403
10
10
  from .money import Money as Money
11
11
  from .parse import parse_xml as parse_xml
12
+ from .pdf_common import FileRelationship as FileRelationship
13
+ from .pdf_embed import (
14
+ embed_facturx_file_in_pdf as embed_facturx_file_in_pdf,
15
+ embed_invoice_in_pdf as embed_invoice_in_pdf,
16
+ )
12
17
  from .pdf_parse import parse_pdf as parse_pdf
13
18
 
14
19
  FACTURX_VERSION: Final = "1.0.07"
@@ -0,0 +1,176 @@
1
+ import datetime
2
+ from decimal import Decimal
3
+ from typing import Final
4
+
5
+ from .model import (
6
+ EN16931Invoice,
7
+ EN16931LineItem,
8
+ IncludedNote,
9
+ LineAllowance,
10
+ LineCharge,
11
+ LineItem,
12
+ PaymentMeans,
13
+ PaymentTerms,
14
+ PostalAddress,
15
+ ProductCharacteristic,
16
+ ProductClassification,
17
+ ReferenceDocument,
18
+ Tax,
19
+ TradeContact,
20
+ TradeParty,
21
+ )
22
+ from .money import Money
23
+ from .quantities import QuantityCode
24
+ from .type_codes import (
25
+ AllowanceChargeCode,
26
+ DocumentTypeCode,
27
+ ItemTypeCode,
28
+ PaymentMeansCode,
29
+ ReferenceQualifierCode,
30
+ TaxCategoryCode,
31
+ TextSubjectCode,
32
+ )
33
+
34
+ TEST_PNG = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\n\x00\x00\x00\n\x08\x02\x00\x00\x00\x02PX\xea\x00\x00\x00&IDAT\x18\xd3c\xfc\xff\xff?\x03n\xc0\xc4\xc0\xc0\xc0\xc0\xc8\x88E\x86\x91\x91\x81\x81\x81\x91\x08\xdd\x83X\x9al\x8f\x01\x00\xdf\x94\t\x11%\xfb\xb4\xe3\x00\x00\x00\x00IEND\xaeB`\x82" # noqa: E501
35
+
36
+ TEST_SELLER: Final = TradeParty(
37
+ "Seller GmbH",
38
+ PostalAddress(
39
+ "DE", None, "44331", "Test City", "Test Street 42", None, None
40
+ ),
41
+ "seller@example.com",
42
+ vat_id="DE123456789",
43
+ tax_number="123/456/789",
44
+ ids=["SELLER-123"],
45
+ global_ids=[("123456789", "0088")],
46
+ description="being formed",
47
+ legal_id=("DE123456789", "0088"),
48
+ trading_business_name="Great Business",
49
+ contact=TradeContact(
50
+ "Max Mustermann",
51
+ "Sales",
52
+ phone="+49 123 4567890",
53
+ email="foo@example.com",
54
+ ),
55
+ )
56
+
57
+ TEST_BUYER: Final = TradeParty(
58
+ "Buyer AG",
59
+ PostalAddress("DE"),
60
+ )
61
+
62
+ TEST_EN16931_INVOICE: Final = EN16931Invoice(
63
+ "INV-12345",
64
+ DocumentTypeCode.INVOICE,
65
+ datetime.date(2024, 8, 20),
66
+ TEST_SELLER,
67
+ TEST_BUYER,
68
+ "EUR",
69
+ tax_basis_total_amount=Money("10090.00", "EUR"),
70
+ tax_total_amounts=[Money("1917.10", "EUR")],
71
+ grand_total_amount=Money("12007.10", "EUR"),
72
+ due_payable_amount=Money("12007.10", "EUR"),
73
+ line_total_amount=Money("10090.00", "EUR"),
74
+ tax=[
75
+ Tax(
76
+ Money("1917.10", "EUR"),
77
+ Money("10090.00", "EUR"),
78
+ Decimal(19),
79
+ TaxCategoryCode.STANDARD_RATE,
80
+ ),
81
+ ],
82
+ delivery_date=datetime.date(2024, 8, 21),
83
+ notes=[
84
+ IncludedNote("This is a test invoice."),
85
+ IncludedNote(
86
+ "This is seller note.", TextSubjectCode.COMMENTS_BY_SELLER
87
+ ),
88
+ ],
89
+ line_items=[
90
+ LineItem(
91
+ "1",
92
+ "Fixed amount item\nWith multiple lines",
93
+ Money("10000.00", "EUR"),
94
+ (Decimal(1), QuantityCode.PIECE),
95
+ Money("10000.00", "EUR"),
96
+ Decimal("19"),
97
+ ),
98
+ EN16931LineItem(
99
+ "2",
100
+ "Hourly item",
101
+ Money("30.00", "EUR"),
102
+ (Decimal(3), QuantityCode.HOUR),
103
+ Money("90.00", "EUR"),
104
+ Decimal("19"),
105
+ global_id=("9781529044195", "0160"),
106
+ basis_quantity=(Decimal(1), QuantityCode.HOUR),
107
+ description="This is a line item description.",
108
+ note=IncludedNote("This is a line item note."),
109
+ seller_assigned_id="ISBN-44",
110
+ buyer_assigned_id="TT-123",
111
+ product_characteristics=[
112
+ ProductCharacteristic("color", "red"),
113
+ ],
114
+ product_classifications=[
115
+ ProductClassification("1234-5679", list_id=ItemTypeCode.ISSN),
116
+ ProductClassification(
117
+ "9781529044195",
118
+ list_id=ItemTypeCode.ISBN,
119
+ list_version_id="99",
120
+ ),
121
+ ],
122
+ origin_country="DE",
123
+ buyer_order_line_id="BUY-DOC",
124
+ gross_unit_price=(
125
+ Money("40.00", "EUR"),
126
+ (Decimal(1), QuantityCode.HOUR),
127
+ ),
128
+ gross_allowance_or_charge=LineAllowance(
129
+ Money("10.00", "EUR"),
130
+ reason_code=AllowanceChargeCode.AHEAD_OF_SCHEDULE,
131
+ ),
132
+ charges=[
133
+ LineCharge(
134
+ Money("0.05", "EUR"), reason="Complexity surcharge"
135
+ ),
136
+ ],
137
+ allowances=[
138
+ LineAllowance(
139
+ Money("1.00", "EUR"),
140
+ reason_code=AllowanceChargeCode.AHEAD_OF_SCHEDULE,
141
+ reason="Ahead of schedule",
142
+ basis_amount=Money("20.00", "EUR"),
143
+ percent=Decimal("5"),
144
+ ),
145
+ ],
146
+ billing_period=(
147
+ datetime.date(2024, 8, 1),
148
+ datetime.date(2024, 8, 31),
149
+ ),
150
+ doc_ref=("REFDOC-1", None),
151
+ ),
152
+ ],
153
+ buyer_reference="BUYER-1234",
154
+ seller_order_id="SELL-DOC",
155
+ buyer_order_id="BUY-DOC",
156
+ contract_id="CONTRACT-123",
157
+ referenced_docs=[
158
+ ReferenceDocument("REFDOC-1", DocumentTypeCode.INVOICING_DATA_SHEET),
159
+ ReferenceDocument(
160
+ "REFDOC-2",
161
+ DocumentTypeCode.RELATED_DOCUMENT,
162
+ "Test ref doc",
163
+ "https://example.com/refdoc.txt",
164
+ attachment=(TEST_PNG, "image/png", "refdoc.png"),
165
+ reference_type_code=ReferenceQualifierCode.PRICE_LIST_VERSION,
166
+ ),
167
+ ],
168
+ procuring_project=("PROJ-123", "Project X"),
169
+ sepa_reference="ABC-dddd",
170
+ payment_means=[
171
+ PaymentMeans(PaymentMeansCode.BANK_PAYMENT),
172
+ ],
173
+ payment_terms=PaymentTerms(
174
+ due_date=datetime.date(2024, 9, 3),
175
+ ),
176
+ )
@@ -16,3 +16,12 @@ URN_XRECHNUNG_PROFILE = "urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:sta
16
16
  NS_CII = "urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
17
17
  NS_RAM = "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" # noqa: E501
18
18
  NS_UDT = "urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
19
+
20
+ ALLOWED_ATTACHMENT_MIME_TYPES = (
21
+ "application/pdf",
22
+ "image/png",
23
+ "image/jpeg",
24
+ "text/csv",
25
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
26
+ "application/vnd.oasis.opendocument.spreadsheet",
27
+ )
@@ -6,6 +6,10 @@ class PDFError(FacturXError):
6
6
  """Base class for PDF processing exceptions."""
7
7
 
8
8
 
9
+ class InsufficientPDFError(PDFError):
10
+ """Raised when a PDF file is insufficient for Factur-X."""
11
+
12
+
9
13
  class PDFParseError(FacturXError):
10
14
  """Raise when a PDF file cannot be processed."""
11
15
 
@@ -473,8 +473,8 @@ def format_trade_party(trade_party: TradeParty) -> str:
473
473
  lines.append(_format_global_id(gid))
474
474
  if trade_party.legal_id:
475
475
  lines.append(_format_global_id(trade_party.legal_id))
476
- for contact in trade_party.contacts:
477
- lines.append(format_trade_contact(contact))
476
+ if trade_party.contact is not None:
477
+ lines.append(format_trade_contact(trade_party.contact))
478
478
 
479
479
  return "\n".join(lines)
480
480