Skip to content

Conversation

@nijel
Copy link
Member

@nijel nijel commented Nov 5, 2025

This is a proof of concept, I had to use several libraries to make it work.

Outstanding issues:

  • The VAT validation fails because of VAT amount validation (Money comparison) zfutura/pycheval#30
  • Embedding in PDF does not work for our PDFs with pycheval, using factor-x instead for that.
  • We really need full validation to be part of the tests, needs to be investigated.
  • There are definitely issues with generated XML.

@nijel nijel requested a review from Copilot November 5, 2025 11:50
@nijel nijel added the backlog This is not on the Weblate roadmap for now. Can be prioritized by sponsorship. label Nov 5, 2025
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds EN 16931 (European standard e-invoicing format) XML generation and embeds it into PDF invoices using Factur-X format. The changes enable compliance with European e-invoicing standards by generating structured invoice data alongside the visual PDF representation.

Key changes:

  • Integration of PyCheval library for EN 16931 XML generation and Factur-X for PDF embedding
  • New get_en_16931_xml() and generate_en_16931_xml() methods to create standardized e-invoice XML
  • Modified PDF generation to embed EN 16931 XML using Factur-X format
  • Fixed bug in Money S3 XML generation where country was referenced incorrectly

Reviewed Changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 3 comments.

File Description
weblate_web/invoices/models.py Adds EN 16931 XML generation methods, embeds e-invoice in PDF, fixes country code reference in Money S3 XML
weblate_web/invoices/tests.py Adds validation for generated EN 16931 XML using drafthorse validator
requirements.txt Adds drafthorse, factur-x, and PyCheval dependencies for e-invoicing support

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

billed_total=Money(
self.total_discount, self.get_currency_display()
),
tax_rate=self.vat_rate,
Copy link

Copilot AI Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The discount line item should use self.vat_rate or None for consistency with other line items (line 740). When vat_rate is 0 (falsy), the tax_rate should be None, not 0.

Suggested change
tax_rate=self.vat_rate,
tax_rate=self.vat_rate or None,

Copilot uses AI. Check for mistakes.
pdf_bytes = generate_from_binary(
buffer.getvalue(), generate_xml(self.get_en_16931_xml())
)
# pdf_bytes = embed_invoice_in_pdf(buffer, self.get_en_16931_xml())
Copy link

Copilot AI Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented-out code should be removed. If this is an alternative implementation that might be needed later, it should be documented in a TODO comment or issue tracker instead.

Suggested change
# pdf_bytes = embed_invoice_in_pdf(buffer, self.get_en_16931_xml())

Copilot uses AI. Check for mistakes.
line_items=line_items,
type_code=type_code,
seller=TradeParty(
name="Weblate s.r.o",
Copy link

Copilot AI Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Company name is missing a period after 'r.o'. It should be 'Weblate s.r.o.' (with a trailing period) for proper Czech company designation.

Suggested change
name="Weblate s.r.o",
name="Weblate s.r.o.",

Copilot uses AI. Check for mistakes.
@codecov
Copy link

codecov bot commented Nov 5, 2025

❌ 38 Tests Failed:

Tests completed Failed Passed Skipped
132 38 94 0
View the top 3 failed test(s) by shortest run time
weblate_web.invoices.tests.InvoiceTestCase::test_invoice_kinds
Stack Traces | 0.01s run time
self = <weblate_web.invoices.tests.InvoiceTestCase testMethod=test_invoice_kinds>

    def test_invoice_kinds(self) -> None:
        for kind in InvoiceKind.values:
            invoice = self.create_invoice(kind=InvoiceKind(kind))
>           self.validate_invoice(invoice)

weblate_web/invoices/tests.py:244: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate_web/invoices/tests.py:116: in validate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:567: in generate_files
    self.generate_en_16931_xml()
weblate_web/invoices/models.py:842: in generate_en_16931_xml
    invoice = self.get_en_16931_xml()
              ^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/invoices/models.py:802: in get_en_16931_xml
    return EN16931Invoice(
<string>:44: in __init__
    ???
.../hostedtoolcache/Python/3.12.12........./x64/lib/python3.12........./site-packages/pycheval/model.py:465: in __post_init__
    super().__post_init__()
.../hostedtoolcache/Python/3.12.12........./x64/lib/python3.12........./site-packages/pycheval/model.py:423: in __post_init__
    super().__post_init__()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = EN16931Invoice(invoice_number='0025000001', type_code=<DocumentTypeCode.INVOICING_DATA_SHEET: 130>, invoice_date=datet...d=None, rounding_amount=None, seller_order_id=None, referenced_docs=[], procuring_project=None, tax_currency_code=None)

    def __post_init__(self) -> None:
        if not self.type_code.is_invoice_type:
>           raise ModelError(f"Invalid invoice type code: {self.type_code}.")
E           pycheval.exc.ModelError: Invalid invoice type code: 130.

.../hostedtoolcache/Python/3.12.12........./x64/lib/python3.12........./site-packages/pycheval/model.py:372: ModelError
weblate_web.invoices.tests.InvoiceTestCase::test_total_vat
Stack Traces | 0.01s run time
self = <weblate_web.invoices.tests.InvoiceTestCase testMethod=test_total_vat>

    def test_total_vat(self) -> None:
        invoice = self.create_invoice(vat_rate=21, customer_reference="PO123456")
        self.assertEqual(invoice.total_amount, 121)
>       self.validate_invoice(invoice)

weblate_web/invoices/tests.py:166: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate_web/invoices/tests.py:116: in validate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:567: in generate_files
    self.generate_en_16931_xml()
weblate_web/invoices/models.py:842: in generate_en_16931_xml
    invoice = self.get_en_16931_xml()
              ^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/invoices/models.py:734: in get_en_16931_xml
    tax = Tax(
<string>:11: in __init__
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Tax(calculated_amount=Money('21', 'EUR'), basis_amount=Money('100', 'EUR'), rate_percent=21, category_code=<TaxCategor...e.STANDARD_RATE: 'S'>, exemption_reason=None, exemption_reason_code=None, tax_point_date=None, due_date_type_code=None)

    def __post_init__(self) -> None:
        if (
            self.due_date_type_code is not None
            and not self.due_date_type_code.is_invoice_due_date
        ):
            raise ModelError(
                "Invalid due date type code: {self.due_date_type_code}."
            )
        if (
            self.rate_percent is not None
            and self.basis_amount * self.rate_percent / Decimal(100)
            != self.calculated_amount
        ):
>           raise ModelError(
                f"Calculated amount {self.calculated_amount} does not match "
                f"basis amount {self.basis_amount} and tax rate "
                f"{self.rate_percent}\u2009%."
            )
E           pycheval.exc.ModelError: Calculated amount EUR 21 does not match basis amount EUR 100 and tax rate 21 %.

.../hostedtoolcache/Python/3.12.12.../x64/lib/python3.12.../site-packages/pycheval/model.py:792: ModelError
weblate_web.invoices.tests.InvoiceTestCase::test_total_vat_note
Stack Traces | 0.01s run time
self = <weblate_web.invoices.tests.InvoiceTestCase testMethod=test_total_vat_note>

    def test_total_vat_note(self) -> None:
        invoice = self.create_invoice(
            vat_rate=21, customer_reference="PO123456", customer_note="Test note\n" * 3
        )
        self.assertEqual(invoice.total_amount, 121)
>       self.validate_invoice(invoice)

weblate_web/invoices/tests.py:173: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate_web/invoices/tests.py:116: in validate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:567: in generate_files
    self.generate_en_16931_xml()
weblate_web/invoices/models.py:842: in generate_en_16931_xml
    invoice = self.get_en_16931_xml()
              ^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/invoices/models.py:734: in get_en_16931_xml
    tax = Tax(
<string>:11: in __init__
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Tax(calculated_amount=Money('21', 'EUR'), basis_amount=Money('100', 'EUR'), rate_percent=21, category_code=<TaxCategor...e.STANDARD_RATE: 'S'>, exemption_reason=None, exemption_reason_code=None, tax_point_date=None, due_date_type_code=None)

    def __post_init__(self) -> None:
        if (
            self.due_date_type_code is not None
            and not self.due_date_type_code.is_invoice_due_date
        ):
            raise ModelError(
                "Invalid due date type code: {self.due_date_type_code}."
            )
        if (
            self.rate_percent is not None
            and self.basis_amount * self.rate_percent / Decimal(100)
            != self.calculated_amount
        ):
>           raise ModelError(
                f"Calculated amount {self.calculated_amount} does not match "
                f"basis amount {self.basis_amount} and tax rate "
                f"{self.rate_percent}\u2009%."
            )
E           pycheval.exc.ModelError: Calculated amount EUR 21 does not match basis amount EUR 100 and tax rate 21 %.

.../hostedtoolcache/Python/3.12.12.../x64/lib/python3.12.../site-packages/pycheval/model.py:792: ModelError
weblate_web.invoices.tests.InvoiceTestCase::test_discount_vat
Stack Traces | 0.011s run time
self = <weblate_web.invoices.tests.InvoiceTestCase testMethod=test_discount_vat>

    def test_discount_vat(self) -> None:
        invoice = self.create_invoice(
            discount=Discount.objects.create(description="Test discount", percents=50),
            vat_rate=21,
        )
        self.assertEqual(invoice.total_amount, Decimal("60.50"))
>       self.validate_invoice(invoice)

weblate_web/invoices/tests.py:226: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate_web/invoices/tests.py:116: in validate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:567: in generate_files
    self.generate_en_16931_xml()
weblate_web/invoices/models.py:842: in generate_en_16931_xml
    invoice = self.get_en_16931_xml()
              ^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/invoices/models.py:734: in get_en_16931_xml
    tax = Tax(
<string>:11: in __init__
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Tax(calculated_amount=Money('10.5', 'EUR'), basis_amount=Money('50', 'EUR'), rate_percent=21, category_code=<TaxCatego...e.STANDARD_RATE: 'S'>, exemption_reason=None, exemption_reason_code=None, tax_point_date=None, due_date_type_code=None)

    def __post_init__(self) -> None:
        if (
            self.due_date_type_code is not None
            and not self.due_date_type_code.is_invoice_due_date
        ):
            raise ModelError(
                "Invalid due date type code: {self.due_date_type_code}."
            )
        if (
            self.rate_percent is not None
            and self.basis_amount * self.rate_percent / Decimal(100)
            != self.calculated_amount
        ):
>           raise ModelError(
                f"Calculated amount {self.calculated_amount} does not match "
                f"basis amount {self.basis_amount} and tax rate "
                f"{self.rate_percent}\u2009%."
            )
E           pycheval.exc.ModelError: Calculated amount EUR 10.5 does not match basis amount EUR 50 and tax rate 21 %.

.../hostedtoolcache/Python/3.12.12.../x64/lib/python3.12.../site-packages/pycheval/model.py:792: ModelError
weblate_web.payments.tests.BackendTest::test_pay
Stack Traces | 0.017s run time
self = <weblate_web.payments.tests.BackendTest testMethod=test_pay>

    def test_pay(self) -> None:
        backend = get_backend("pay")(self.payment)
        self.assertIsNone(backend.initiate(None, "", ""))
        self.check_payment(Payment.PENDING)
>       self.assertTrue(backend.complete(None))
                        ^^^^^^^^^^^^^^^^^^^^^^

weblate_web/payments/tests.py:263: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate_web/payments/backends.py:357: in complete
    self.success()
weblate_web/payments/backends.py:450: in success
    self.generate_invoice()
.../hostedtoolcache/Python/3.12.12....../x64/lib/python3.12/contextlib.py:81: in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
weblate_web/payments/backends.py:423: in generate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:567: in generate_files
    self.generate_en_16931_xml()
weblate_web/invoices/models.py:842: in generate_en_16931_xml
    invoice = self.get_en_16931_xml()
              ^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/invoices/models.py:734: in get_en_16931_xml
    tax = Tax(
<string>:11: in __init__
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Tax(calculated_amount=Money('21', 'EUR'), basis_amount=Money('100', 'EUR'), rate_percent=21, category_code=<TaxCategor...e.STANDARD_RATE: 'S'>, exemption_reason=None, exemption_reason_code=None, tax_point_date=None, due_date_type_code=None)

    def __post_init__(self) -> None:
        if (
            self.due_date_type_code is not None
            and not self.due_date_type_code.is_invoice_due_date
        ):
            raise ModelError(
                "Invalid due date type code: {self.due_date_type_code}."
            )
        if (
            self.rate_percent is not None
            and self.basis_amount * self.rate_percent / Decimal(100)
            != self.calculated_amount
        ):
>           raise ModelError(
                f"Calculated amount {self.calculated_amount} does not match "
                f"basis amount {self.basis_amount} and tax rate "
                f"{self.rate_percent}\u2009%."
            )
E           pycheval.exc.ModelError: Calculated amount EUR 21 does not match basis amount EUR 100 and tax rate 21 %.

.../hostedtoolcache/Python/3.12.12....../x64/lib/python3.12.../site-packages/pycheval/model.py:792: ModelError
weblate_web.payments.tests.BackendTest::test_pending
Stack Traces | 0.018s run time
self = <weblate_web.payments.tests.BackendTest testMethod=test_pending>

    def test_pending(self) -> None:
        backend = get_backend("pending")(self.payment)
        self.assertIsNotNone(backend.initiate(None, "", ""))
        self.check_payment(Payment.PENDING)
>       self.assertTrue(backend.complete(None))
                        ^^^^^^^^^^^^^^^^^^^^^^

weblate_web/payments/tests.py:281: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate_web/payments/backends.py:357: in complete
    self.success()
weblate_web/payments/backends.py:450: in success
    self.generate_invoice()
.../hostedtoolcache/Python/3.12.12....../x64/lib/python3.12/contextlib.py:81: in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
weblate_web/payments/backends.py:423: in generate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:567: in generate_files
    self.generate_en_16931_xml()
weblate_web/invoices/models.py:842: in generate_en_16931_xml
    invoice = self.get_en_16931_xml()
              ^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/invoices/models.py:734: in get_en_16931_xml
    tax = Tax(
<string>:11: in __init__
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Tax(calculated_amount=Money('21', 'EUR'), basis_amount=Money('100', 'EUR'), rate_percent=21, category_code=<TaxCategor...e.STANDARD_RATE: 'S'>, exemption_reason=None, exemption_reason_code=None, tax_point_date=None, due_date_type_code=None)

    def __post_init__(self) -> None:
        if (
            self.due_date_type_code is not None
            and not self.due_date_type_code.is_invoice_due_date
        ):
            raise ModelError(
                "Invalid due date type code: {self.due_date_type_code}."
            )
        if (
            self.rate_percent is not None
            and self.basis_amount * self.rate_percent / Decimal(100)
            != self.calculated_amount
        ):
>           raise ModelError(
                f"Calculated amount {self.calculated_amount} does not match "
                f"basis amount {self.basis_amount} and tax rate "
                f"{self.rate_percent}\u2009%."
            )
E           pycheval.exc.ModelError: Calculated amount EUR 21 does not match basis amount EUR 100 and tax rate 21 %.

.../hostedtoolcache/Python/3.12.12....../x64/lib/python3.12.../site-packages/pycheval/model.py:792: ModelError
weblate_web.payments.tests.BackendTest::test_proforma
Stack Traces | 0.02s run time
self = <weblate_web.payments.tests.BackendTest testMethod=test_proforma>

    @responses.activate
    @override_settings(
        FIO_TOKEN="test-token",  # noqa: S106
    )
    def test_proforma(self) -> None:
        mock_vies()
        cnb_mock_rates()
        backend = get_backend("fio-bank")(self.payment)
>       self.assertIsNotNone(backend.initiate(None, "", "/complete/"))
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

weblate_web/payments/tests.py:306: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate_web/payments/backends.py:332: in initiate
    result = self.perform(request, back_url, complete_url)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/payments/backends.py:531: in perform
    self.generate_invoice(proforma=True)
.../hostedtoolcache/Python/3.12.12....../x64/lib/python3.12/contextlib.py:81: in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
weblate_web/payments/backends.py:423: in generate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:567: in generate_files
    self.generate_en_16931_xml()
weblate_web/invoices/models.py:842: in generate_en_16931_xml
    invoice = self.get_en_16931_xml()
              ^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/invoices/models.py:734: in get_en_16931_xml
    tax = Tax(
<string>:11: in __init__
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Tax(calculated_amount=Money('21', 'EUR'), basis_amount=Money('100', 'EUR'), rate_percent=21, category_code=<TaxCategor...e.STANDARD_RATE: 'S'>, exemption_reason=None, exemption_reason_code=None, tax_point_date=None, due_date_type_code=None)

    def __post_init__(self) -> None:
        if (
            self.due_date_type_code is not None
            and not self.due_date_type_code.is_invoice_due_date
        ):
            raise ModelError(
                "Invalid due date type code: {self.due_date_type_code}."
            )
        if (
            self.rate_percent is not None
            and self.basis_amount * self.rate_percent / Decimal(100)
            != self.calculated_amount
        ):
>           raise ModelError(
                f"Calculated amount {self.calculated_amount} does not match "
                f"basis amount {self.basis_amount} and tax rate "
                f"{self.rate_percent}\u2009%."
            )
E           pycheval.exc.ModelError: Calculated amount EUR 21 does not match basis amount EUR 100 and tax rate 21 %.

.../hostedtoolcache/Python/3.12.12....../x64/lib/python3.12.../site-packages/pycheval/model.py:792: ModelError
weblate_web.payments.tests.ThePay2Test::test_pay_recurring
Stack Traces | 0.022s run time
self = <weblate_web.payments.tests.ThePay2Test testMethod=test_pay_recurring>

    @responses.activate
    def test_pay_recurring(self) -> None:
        # Modify backend payment as it has a copy
        self.backend.payment.repeat = Payment.objects.create(
            customer=self.customer,
            amount=100,
            description="Test Item",
            backend=self.backend_name,
        )
        self.backend.payment.save()
        responses.post(
            f"https://demo.api.thepay..../projects/42/payments/{self.backend.payment.repeat.pk}/savedauthorization?merchant_id=00000000-0000-0000-0000-000000000000",
            json={
                "state": "paid",
                "parent": {"recurring_payments_available": True},
            },
        )
        self.assertIsNone(self.backend.initiate(None, "", ""))
        self.check_payment(Payment.PENDING)
>       self.assertTrue(self.backend.complete(None))
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^

weblate_web/payments/tests.py:623: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate_web/payments/backends.py:357: in complete
    self.success()
weblate_web/payments/backends.py:450: in success
    self.generate_invoice()
.../hostedtoolcache/Python/3.12.12....../x64/lib/python3.12/contextlib.py:81: in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
weblate_web/payments/backends.py:423: in generate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:567: in generate_files
    self.generate_en_16931_xml()
weblate_web/invoices/models.py:842: in generate_en_16931_xml
    invoice = self.get_en_16931_xml()
              ^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/invoices/models.py:734: in get_en_16931_xml
    tax = Tax(
<string>:11: in __init__
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Tax(calculated_amount=Money('21', 'EUR'), basis_amount=Money('100', 'EUR'), rate_percent=21, category_code=<TaxCategor...e.STANDARD_RATE: 'S'>, exemption_reason=None, exemption_reason_code=None, tax_point_date=None, due_date_type_code=None)

    def __post_init__(self) -> None:
        if (
            self.due_date_type_code is not None
            and not self.due_date_type_code.is_invoice_due_date
        ):
            raise ModelError(
                "Invalid due date type code: {self.due_date_type_code}."
            )
        if (
            self.rate_percent is not None
            and self.basis_amount * self.rate_percent / Decimal(100)
            != self.calculated_amount
        ):
>           raise ModelError(
                f"Calculated amount {self.calculated_amount} does not match "
                f"basis amount {self.basis_amount} and tax rate "
                f"{self.rate_percent}\u2009%."
            )
E           pycheval.exc.ModelError: Calculated amount EUR 21 does not match basis amount EUR 100 and tax rate 21 %.

.../hostedtoolcache/Python/3.12.12....../x64/lib/python3.12.../site-packages/pycheval/model.py:792: ModelError
weblate_web.payments.tests.ThePay2Test::test_pay
Stack Traces | 0.024s run time
self = <weblate_web.payments.tests.ThePay2Test testMethod=test_pay>

    @responses.activate
    def test_pay(self) -> None:
        thepay_mock_create_payment()
        thepay_mock_payment(self.payment.pk)
        response = self.backend.initiate(None, "", "")
        self.assertIsNotNone(response)
        self.assertRedirects(
            response,
            "https://gate.thepay.cz/12345/pay",
            fetch_redirect_response=False,
        )
        self.check_payment(Payment.PENDING)
>       self.assertTrue(self.backend.complete(None))
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^

weblate_web/payments/tests.py:463: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate_web/payments/backends.py:357: in complete
    self.success()
weblate_web/payments/backends.py:450: in success
    self.generate_invoice()
.../hostedtoolcache/Python/3.12.12....../x64/lib/python3.12/contextlib.py:81: in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
weblate_web/payments/backends.py:423: in generate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:567: in generate_files
    self.generate_en_16931_xml()
weblate_web/invoices/models.py:842: in generate_en_16931_xml
    invoice = self.get_en_16931_xml()
              ^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/invoices/models.py:734: in get_en_16931_xml
    tax = Tax(
<string>:11: in __init__
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Tax(calculated_amount=Money('21', 'EUR'), basis_amount=Money('100', 'EUR'), rate_percent=21, category_code=<TaxCategor...e.STANDARD_RATE: 'S'>, exemption_reason=None, exemption_reason_code=None, tax_point_date=None, due_date_type_code=None)

    def __post_init__(self) -> None:
        if (
            self.due_date_type_code is not None
            and not self.due_date_type_code.is_invoice_due_date
        ):
            raise ModelError(
                "Invalid due date type code: {self.due_date_type_code}."
            )
        if (
            self.rate_percent is not None
            and self.basis_amount * self.rate_percent / Decimal(100)
            != self.calculated_amount
        ):
>           raise ModelError(
                f"Calculated amount {self.calculated_amount} does not match "
                f"basis amount {self.basis_amount} and tax rate "
                f"{self.rate_percent}\u2009%."
            )
E           pycheval.exc.ModelError: Calculated amount EUR 21 does not match basis amount EUR 100 and tax rate 21 %.

.../hostedtoolcache/Python/3.12.12....../x64/lib/python3.12.../site-packages/pycheval/model.py:792: ModelError
weblate_web.invoices.tests.InvoiceTestCase::test_package_usd
Stack Traces | 0.034s run time
self = <weblate_web.invoices.tests.InvoiceTestCase testMethod=test_package_usd>

    def test_package_usd(self) -> None:
        invoice = self.create_invoice_package(currency=Currency.USD)
        self.assertEqual(
            invoice.total_amount,
            round(Decimal(100) * invoice.exchange_rate_eur * Decimal("1.1"), 0),
        )
>       self.validate_invoice(invoice)

weblate_web/invoices/tests.py:239: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate_web/invoices/tests.py:116: in validate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:568: in generate_files
    self.generate_pdf()
weblate_web/invoices/models.py:850: in generate_pdf
    render_pdf(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def render_pdf(
        *,
        html: str,
        output: Path,
        attachments: list[Attachment] | None = None,
        pdf_variant: str | None = None,
    ) -> None:
        font_config = FontConfiguration()
    
        renderer = HTML(
            string=html,
            url_fetcher=url_fetcher,
        )
        fonts_css = finders.find("pdf/fonts.css")
        if fonts_css is None:
            raise ValueError("Could not load fonts CSS")
        font_style = CSS(
            filename=fonts_css,
            font_config=font_config,
            url_fetcher=url_fetcher,
        )
        if attachments:
>           renderer.metadata.attachments = attachments
            ^^^^^^^^^^^^^^^^^
E           AttributeError: 'HTML' object has no attribute 'metadata'

weblate_web/pdf.py:92: AttributeError
weblate_web.payments.tests.BackendTest::test_invoice_in_text
Stack Traces | 0.042s run time
self = <weblate_web.payments.tests.BackendTest testMethod=test_invoice_in_text>

    @responses.activate
    @override_settings(
        FIO_TOKEN="test-token",  # noqa: S106
    )
    def test_invoice_in_text(self) -> None:
        # Czech bank notation
>       self.test_invoice_bank(format_string="PROFORMA{}PAYMENT")

weblate_web/payments/tests.py:396: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../hostedtoolcache/Python/3.12.12............/x64/lib/python3.12.../site-packages/responses/__init__.py:232: in wrapper
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12............/x64/lib/python3.12.../django/test/utils.py:456: in inner
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
weblate_web/payments/tests.py:368: in test_invoice_bank
    FioBank.fetch_payments()
weblate_web/payments/backends.py:645: in fetch_payments
    backend.success()
weblate_web/payments/backends.py:453: in success
    self.send_notification("payment_completed")
weblate_web/payments/backends.py:436: in send_notification
    self.payment.customer.send_notification(
weblate_web/payments/models.py:294: in send_notification
    email = send_notification(notification, recipients, invoice=invoice, **kwargs)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/payments/utils.py:108: in send_notification
    email.attach(invoice.filename, invoice.path.read_bytes(), "application/pdf")
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12............/x64/lib/python3.12/pathlib.py:1019: in read_bytes
    with self.open(mode='rb') as f:
         ^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = PosixPath('.../website/invoices/Weblate_Invoice_1025000001.pdf')
mode = 'rb', buffering = -1, encoding = None, errors = None, newline = None

    def open(self, mode='r', buffering=-1, encoding=None,
             errors=None, newline=None):
        """
        Open the file pointed to by this path and return a file object, as
        the built-in open() function does.
        """
        if "b" not in mode:
            encoding = io.text_encoding(encoding)
>       return io.open(self, mode, buffering, encoding, errors, newline)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       FileNotFoundError: [Errno 2] No such file or directory: '.../website/invoices/Weblate_Invoice_1025000001.pdf'

.../hostedtoolcache/Python/3.12.12............/x64/lib/python3.12/pathlib.py:1013: FileNotFoundError
weblate_web.payments.tests.BackendTest::test_invoice_vs
Stack Traces | 0.042s run time
self = <weblate_web.payments.tests.BackendTest testMethod=test_invoice_vs>

    @responses.activate
    @override_settings(
        FIO_TOKEN="test-token",  # noqa: S106
    )
    def test_invoice_vs(self) -> None:
        # Czech bank notation
>       self.test_invoice_bank(format_string="VS{}/SS/KS")

weblate_web/payments/tests.py:388: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../hostedtoolcache/Python/3.12.12............/x64/lib/python3.12.../site-packages/responses/__init__.py:232: in wrapper
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12............/x64/lib/python3.12.../django/test/utils.py:456: in inner
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
weblate_web/payments/tests.py:368: in test_invoice_bank
    FioBank.fetch_payments()
weblate_web/payments/backends.py:645: in fetch_payments
    backend.success()
weblate_web/payments/backends.py:453: in success
    self.send_notification("payment_completed")
weblate_web/payments/backends.py:436: in send_notification
    self.payment.customer.send_notification(
weblate_web/payments/models.py:294: in send_notification
    email = send_notification(notification, recipients, invoice=invoice, **kwargs)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/payments/utils.py:108: in send_notification
    email.attach(invoice.filename, invoice.path.read_bytes(), "application/pdf")
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12............/x64/lib/python3.12/pathlib.py:1019: in read_bytes
    with self.open(mode='rb') as f:
         ^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = PosixPath('.../website/invoices/Weblate_Invoice_1025000001.pdf')
mode = 'rb', buffering = -1, encoding = None, errors = None, newline = None

    def open(self, mode='r', buffering=-1, encoding=None,
             errors=None, newline=None):
        """
        Open the file pointed to by this path and return a file object, as
        the built-in open() function does.
        """
        if "b" not in mode:
            encoding = io.text_encoding(encoding)
>       return io.open(self, mode, buffering, encoding, errors, newline)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       FileNotFoundError: [Errno 2] No such file or directory: '.../website/invoices/Weblate_Invoice_1025000001.pdf'

.../hostedtoolcache/Python/3.12.12............/x64/lib/python3.12/pathlib.py:1013: FileNotFoundError
weblate_web.invoices.tests.InvoiceTestCase::test_total
Stack Traces | 0.062s run time
self = <weblate_web.invoices.tests.InvoiceTestCase testMethod=test_total>

    def test_total(self) -> None:
        invoice = self.create_invoice(vat="CZ8003280318")
        self.assertEqual(invoice.total_amount, 100)
>       self.validate_invoice(invoice)

weblate_web/invoices/tests.py:161: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate_web/invoices/tests.py:116: in validate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:568: in generate_files
    self.generate_pdf()
weblate_web/invoices/models.py:850: in generate_pdf
    render_pdf(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def render_pdf(
        *,
        html: str,
        output: Path,
        attachments: list[Attachment] | None = None,
        pdf_variant: str | None = None,
    ) -> None:
        font_config = FontConfiguration()
    
        renderer = HTML(
            string=html,
            url_fetcher=url_fetcher,
        )
        fonts_css = finders.find("pdf/fonts.css")
        if fonts_css is None:
            raise ValueError("Could not load fonts CSS")
        font_style = CSS(
            filename=fonts_css,
            font_config=font_config,
            url_fetcher=url_fetcher,
        )
        if attachments:
>           renderer.metadata.attachments = attachments
            ^^^^^^^^^^^^^^^^^
E           AttributeError: 'HTML' object has no attribute 'metadata'

weblate_web/pdf.py:92: AttributeError
weblate_web.invoices.tests.InvoiceTestCase::test_total_items
Stack Traces | 0.063s run time
self = <weblate_web.invoices.tests.InvoiceTestCase testMethod=test_total_items>

    def test_total_items(self) -> None:
        invoice = self.create_invoice()
        invoice.invoiceitem_set.create(
            description="Other item", unit_price=1000, quantity=4
        )
        self.assertEqual(invoice.total_amount, 4100)
>       self.validate_invoice(invoice)

weblate_web/invoices/tests.py:181: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate_web/invoices/tests.py:116: in validate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:568: in generate_files
    self.generate_pdf()
weblate_web/invoices/models.py:850: in generate_pdf
    render_pdf(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def render_pdf(
        *,
        html: str,
        output: Path,
        attachments: list[Attachment] | None = None,
        pdf_variant: str | None = None,
    ) -> None:
        font_config = FontConfiguration()
    
        renderer = HTML(
            string=html,
            url_fetcher=url_fetcher,
        )
        fonts_css = finders.find("pdf/fonts.css")
        if fonts_css is None:
            raise ValueError("Could not load fonts CSS")
        font_style = CSS(
            filename=fonts_css,
            font_config=font_config,
            url_fetcher=url_fetcher,
        )
        if attachments:
>           renderer.metadata.attachments = attachments
            ^^^^^^^^^^^^^^^^^
E           AttributeError: 'HTML' object has no attribute 'metadata'

weblate_web/pdf.py:92: AttributeError
weblate_web.invoices.tests.InvoiceTestCase::test_total_items_hour
Stack Traces | 0.063s run time
self = <weblate_web.invoices.tests.InvoiceTestCase testMethod=test_total_items_hour>

    def test_total_items_hour(self) -> None:
        invoice = self.create_invoice()
        invoice.invoiceitem_set.create(
            description="Other item",
            unit_price=1000,
            quantity=1,
            quantity_unit=QuantityUnit.HOURS,
        )
        self.assertEqual(invoice.total_amount, 1100)
>       self.validate_invoice(invoice)

weblate_web/invoices/tests.py:203: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate_web/invoices/tests.py:116: in validate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:568: in generate_files
    self.generate_pdf()
weblate_web/invoices/models.py:850: in generate_pdf
    render_pdf(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def render_pdf(
        *,
        html: str,
        output: Path,
        attachments: list[Attachment] | None = None,
        pdf_variant: str | None = None,
    ) -> None:
        font_config = FontConfiguration()
    
        renderer = HTML(
            string=html,
            url_fetcher=url_fetcher,
        )
        fonts_css = finders.find("pdf/fonts.css")
        if fonts_css is None:
            raise ValueError("Could not load fonts CSS")
        font_style = CSS(
            filename=fonts_css,
            font_config=font_config,
            url_fetcher=url_fetcher,
        )
        if attachments:
>           renderer.metadata.attachments = attachments
            ^^^^^^^^^^^^^^^^^
E           AttributeError: 'HTML' object has no attribute 'metadata'

weblate_web/pdf.py:92: AttributeError
weblate_web.invoices.tests.InvoiceTestCase::test_total_items_hours
Stack Traces | 0.063s run time
self = <weblate_web.invoices.tests.InvoiceTestCase testMethod=test_total_items_hours>

    def test_total_items_hours(self) -> None:
        invoice = self.create_invoice()
        invoice.invoiceitem_set.create(
            description="Other item",
            unit_price=1000,
            quantity=4,
            quantity_unit=QuantityUnit.HOURS,
        )
        self.assertEqual(invoice.total_amount, 4100)
>       self.validate_invoice(invoice)

weblate_web/invoices/tests.py:192: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate_web/invoices/tests.py:116: in validate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:568: in generate_files
    self.generate_pdf()
weblate_web/invoices/models.py:850: in generate_pdf
    render_pdf(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def render_pdf(
        *,
        html: str,
        output: Path,
        attachments: list[Attachment] | None = None,
        pdf_variant: str | None = None,
    ) -> None:
        font_config = FontConfiguration()
    
        renderer = HTML(
            string=html,
            url_fetcher=url_fetcher,
        )
        fonts_css = finders.find("pdf/fonts.css")
        if fonts_css is None:
            raise ValueError("Could not load fonts CSS")
        font_style = CSS(
            filename=fonts_css,
            font_config=font_config,
            url_fetcher=url_fetcher,
        )
        if attachments:
>           renderer.metadata.attachments = attachments
            ^^^^^^^^^^^^^^^^^
E           AttributeError: 'HTML' object has no attribute 'metadata'

weblate_web/pdf.py:92: AttributeError
weblate_web.invoices.tests.InvoiceTestCase::test_pay_link
Stack Traces | 0.064s run time
self = <weblate_web.invoices.tests.InvoiceTestCase testMethod=test_pay_link>

    @override_settings(PAYMENT_DEBUG=True)
    def test_pay_link(self) -> None:
        invoice = self.create_invoice_package()
>       self.validate_invoice(invoice)

weblate_web/invoices/tests.py:249: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate_web/invoices/tests.py:116: in validate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:568: in generate_files
    self.generate_pdf()
weblate_web/invoices/models.py:850: in generate_pdf
    render_pdf(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def render_pdf(
        *,
        html: str,
        output: Path,
        attachments: list[Attachment] | None = None,
        pdf_variant: str | None = None,
    ) -> None:
        font_config = FontConfiguration()
    
        renderer = HTML(
            string=html,
            url_fetcher=url_fetcher,
        )
        fonts_css = finders.find("pdf/fonts.css")
        if fonts_css is None:
            raise ValueError("Could not load fonts CSS")
        font_style = CSS(
            filename=fonts_css,
            font_config=font_config,
            url_fetcher=url_fetcher,
        )
        if attachments:
>           renderer.metadata.attachments = attachments
            ^^^^^^^^^^^^^^^^^
E           AttributeError: 'HTML' object has no attribute 'metadata'

weblate_web/pdf.py:92: AttributeError
weblate_web.invoices.tests.InvoiceTestCase::test_discount_negative
Stack Traces | 0.065s run time
self = <weblate_web.invoices.tests.InvoiceTestCase testMethod=test_discount_negative>

    def test_discount_negative(self) -> None:
        invoice = self.create_invoice(
            discount=Discount.objects.create(description="Test discount", percents=50)
        )
        invoice.invoiceitem_set.create(description="Prepaid amount", unit_price=-10)
        self.assertEqual(invoice.total_amount, 40)
>       self.validate_invoice(invoice)

weblate_web/invoices/tests.py:218: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate_web/invoices/tests.py:116: in validate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:568: in generate_files
    self.generate_pdf()
weblate_web/invoices/models.py:850: in generate_pdf
    render_pdf(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def render_pdf(
        *,
        html: str,
        output: Path,
        attachments: list[Attachment] | None = None,
        pdf_variant: str | None = None,
    ) -> None:
        font_config = FontConfiguration()
    
        renderer = HTML(
            string=html,
            url_fetcher=url_fetcher,
        )
        fonts_css = finders.find("pdf/fonts.css")
        if fonts_css is None:
            raise ValueError("Could not load fonts CSS")
        font_style = CSS(
            filename=fonts_css,
            font_config=font_config,
            url_fetcher=url_fetcher,
        )
        if attachments:
>           renderer.metadata.attachments = attachments
            ^^^^^^^^^^^^^^^^^
E           AttributeError: 'HTML' object has no attribute 'metadata'

weblate_web/pdf.py:92: AttributeError
weblate_web.invoices.tests.InvoiceTestCase::test_package
Stack Traces | 0.066s run time
self = <weblate_web.invoices.tests.InvoiceTestCase testMethod=test_package>

    def test_package(self) -> None:
        invoice = self.create_invoice_package()
        self.assertEqual(invoice.total_amount, Decimal(100))
>       self.validate_invoice(invoice)

weblate_web/invoices/tests.py:231: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate_web/invoices/tests.py:116: in validate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:568: in generate_files
    self.generate_pdf()
weblate_web/invoices/models.py:850: in generate_pdf
    render_pdf(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def render_pdf(
        *,
        html: str,
        output: Path,
        attachments: list[Attachment] | None = None,
        pdf_variant: str | None = None,
    ) -> None:
        font_config = FontConfiguration()
    
        renderer = HTML(
            string=html,
            url_fetcher=url_fetcher,
        )
        fonts_css = finders.find("pdf/fonts.css")
        if fonts_css is None:
            raise ValueError("Could not load fonts CSS")
        font_style = CSS(
            filename=fonts_css,
            font_config=font_config,
            url_fetcher=url_fetcher,
        )
        if attachments:
>           renderer.metadata.attachments = attachments
            ^^^^^^^^^^^^^^^^^
E           AttributeError: 'HTML' object has no attribute 'metadata'

weblate_web/pdf.py:92: AttributeError
weblate_web.crm.tests.CRMTestCase::test_service
Stack Traces | 0.094s run time
self = <weblate_web.crm.tests.CRMTestCase testMethod=test_service>

    def test_service(self):
        Package.objects.create(name="community", price=0)
        customer = Customer.objects.create(user_id=-1, name="TEST CUSTOMER")
        payment = Payment.objects.create(customer=customer, amount=1)
        service = Service.objects.create(customer=customer)
        expires = timezone.now() + timedelta(days=1)
        subscription1 = service.subscription_set.create(
            package=Package.objects.create(
                name="x1",
                verbose="pkg1",
                price=42,
                category=PackageCategory.PACKAGE_SHARED,
            ),
            expires=expires,
            payment=payment.pk,
        )
        subscription2 = service.subscription_set.create(
            package=Package.objects.create(
                name="x2",
                verbose="pkg2",
                price=99,
                category=PackageCategory.PACKAGE_SHARED,
            ),
            expires=expires,
            payment=payment.pk,
        )
        self.assertTrue(subscription1.enabled)
        self.assertTrue(subscription2.enabled)
        response = self.client.get(service.get_absolute_url())
        self.assertContains(response, "pkg1")
        self.assertContains(response, "pkg2")
    
        # Test renewal quote
>       response = self.client.post(
            service.get_absolute_url(),
            {"quote": 1, "subscription": subscription1.pk},
            follow=True,
        )

weblate_web/crm/tests.py:96: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:1153: in post
    response = super().post(
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:499: in post
    return self.generic(
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:671: in generic
    return self.request(**r)
           ^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:1087: in request
    self.check_exception(response)
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:802: in check_exception
    raise exc_value
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../core/handlers/exception.py:55: in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../core/handlers/base.py:197: in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../views/generic/base.py:105: in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/utils/decorators.py:48: in _wrapper
    return bound_method(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../contrib/auth/decorators.py:59: in _view_wrapper
    return view_func(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/crm/views.py:57: in dispatch
    return super().dispatch(request, *args, **kwargs)  # type:ignore[misc]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../views/generic/base.py:144: in dispatch
    return handler(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/crm/views.py:147: in post
    invoice = subscription.create_invoice(
weblate_web/models.py:1188: in create_invoice
    return self._create_invoice(
weblate_web/models.py:1141: in _create_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:567: in generate_files
    self.generate_en_16931_xml()
weblate_web/invoices/models.py:842: in generate_en_16931_xml
    invoice = self.get_en_16931_xml()
              ^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/invoices/models.py:829: in get_en_16931_xml
    address=PostalAddress(
<string>:10: in __init__
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = PostalAddress(country_code='', country_subdivision=None, post_code='', city='', line_one='', line_two=None, line_three=None)

    def __post_init__(self) -> None:
        if not validate_iso_3166_1_alpha_2(self.country_code):
>           raise ModelError("Invalid ISO 3166-1 alpha-2 country code.")
E           pycheval.exc.ModelError: Invalid ISO 3166-1 alpha-2 country code.

.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../site-packages/pycheval/model.py:325: ModelError
weblate_web.payments.tests.BackendTest::test_invoice_bank
Stack Traces | 0.096s run time
self = <weblate_web.payments.tests.BackendTest testMethod=test_invoice_bank>
format_string = '{}'

    @responses.activate
    @override_settings(
        FIO_TOKEN="test-token",  # noqa: S106
    )
    def test_invoice_bank(self, format_string="{}") -> None:
        mock_vies()
        customer = Customer.objects.create(**CUSTOMER)
        invoice = Invoice.objects.create(
            customer=customer,
            kind=InvoiceKind.INVOICE,
            category=InvoiceCategory.HOSTING,
            vat_rate=21,
        )
        invoice.invoiceitem_set.create(
            description="Test item",
            unit_price=100,
        )
    
        responses.add(responses.GET, FIO_API, body=json.dumps(FIO_TRASACTIONS))
        FioBank.fetch_payments()
        self.assertFalse(invoice.paid_payment_set.exists())
        self.assertEqual(len(mail.outbox), 0)
    
        received = deepcopy(FIO_TRASACTIONS)
        transaction = received["accountStatement"]["transactionList"]["transaction"]  # type: ignore[index]
        payment_message = format_string.format(invoice.number)
        transaction[0]["column16"]["value"] = payment_message
        transaction[1]["column16"]["value"] = payment_message
        transaction[1]["column1"]["value"] = int(invoice.total_amount)
        responses.replace(responses.GET, FIO_API, body=json.dumps(received))
>       FioBank.fetch_payments()

weblate_web/payments/tests.py:368: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate_web/payments/backends.py:645: in fetch_payments
    backend.success()
weblate_web/payments/backends.py:453: in success
    self.send_notification("payment_completed")
weblate_web/payments/backends.py:436: in send_notification
    self.payment.customer.send_notification(
weblate_web/payments/models.py:294: in send_notification
    email = send_notification(notification, recipients, invoice=invoice, **kwargs)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/payments/utils.py:108: in send_notification
    email.attach(invoice.filename, invoice.path.read_bytes(), "application/pdf")
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12....../x64/lib/python3.12/pathlib.py:1019: in read_bytes
    with self.open(mode='rb') as f:
         ^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = PosixPath('.../website/invoices/Weblate_Invoice_1025000001.pdf')
mode = 'rb', buffering = -1, encoding = None, errors = None, newline = None

    def open(self, mode='r', buffering=-1, encoding=None,
             errors=None, newline=None):
        """
        Open the file pointed to by this path and return a file object, as
        the built-in open() function does.
        """
        if "b" not in mode:
            encoding = io.text_encoding(encoding)
>       return io.open(self, mode, buffering, encoding, errors, newline)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       FileNotFoundError: [Errno 2] No such file or directory: '.../website/invoices/Weblate_Invoice_1025000001.pdf'

.../hostedtoolcache/Python/3.12.12....../x64/lib/python3.12/pathlib.py:1013: FileNotFoundError
weblate_web.invoices.tests.InvoiceTestCase::test_discount
Stack Traces | 0.112s run time
self = <weblate_web.invoices.tests.InvoiceTestCase testMethod=test_discount>

    def test_discount(self) -> None:
        invoice = self.create_invoice(
            discount=Discount.objects.create(description="Test discount", percents=50)
        )
        self.assertEqual(invoice.total_amount, 50)
>       self.validate_invoice(invoice)

weblate_web/invoices/tests.py:210: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate_web/invoices/tests.py:116: in validate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:568: in generate_files
    self.generate_pdf()
weblate_web/invoices/models.py:850: in generate_pdf
    render_pdf(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def render_pdf(
        *,
        html: str,
        output: Path,
        attachments: list[Attachment] | None = None,
        pdf_variant: str | None = None,
    ) -> None:
        font_config = FontConfiguration()
    
        renderer = HTML(
            string=html,
            url_fetcher=url_fetcher,
        )
        fonts_css = finders.find("pdf/fonts.css")
        if fonts_css is None:
            raise ValueError("Could not load fonts CSS")
        font_style = CSS(
            filename=fonts_css,
            font_config=font_config,
            url_fetcher=url_fetcher,
        )
        if attachments:
>           renderer.metadata.attachments = attachments
            ^^^^^^^^^^^^^^^^^
E           AttributeError: 'HTML' object has no attribute 'metadata'

weblate_web/pdf.py:92: AttributeError
weblate_web.crm.tests.CRMTestCase::test_customer_quote
Stack Traces | 0.118s run time
self = <weblate_web.crm.tests.CRMTestCase testMethod=test_customer_quote>

    @responses.activate
    def test_customer_quote(self):
        cnb_mock_rates()
        Package.objects.create(name="community", price=0)
        package = Package.objects.create(name="x1", verbose="pkg1", price=42)
        customer = Customer.objects.create(user_id=-1, name="TEST CUSTOMER")
    
        response = self.client.get(customer.get_absolute_url())
        self.assertContains(response, "Invoice new service")
    
>       response = self.client.post(
            customer.get_absolute_url(),
            {
                "package": package.id,
                "customer_reference": "PO123456",
                "currency": 1,
                "kind": 90,
            },
        )

weblate_web/crm/tests.py:175: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:1153: in post
    response = super().post(
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:499: in post
    return self.generic(
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:671: in generic
    return self.request(**r)
           ^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:1087: in request
    self.check_exception(response)
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:802: in check_exception
    raise exc_value
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../core/handlers/exception.py:55: in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../core/handlers/base.py:197: in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../views/generic/base.py:105: in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/utils/decorators.py:48: in _wrapper
    return bound_method(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../contrib/auth/decorators.py:59: in _view_wrapper
    return view_func(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/crm/views.py:57: in dispatch
    return super().dispatch(request, *args, **kwargs)  # type:ignore[misc]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../views/generic/base.py:144: in dispatch
    return handler(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/crm/views.py:294: in post
    invoice = Subscription.new_subscription_invoice(
weblate_web/models.py:1165: in new_subscription_invoice
    return cls._create_invoice(
weblate_web/models.py:1141: in _create_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:567: in generate_files
    self.generate_en_16931_xml()
weblate_web/invoices/models.py:842: in generate_en_16931_xml
    invoice = self.get_en_16931_xml()
              ^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/invoices/models.py:829: in get_en_16931_xml
    address=PostalAddress(
<string>:10: in __init__
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = PostalAddress(country_code='', country_subdivision=None, post_code='', city='', line_one='', line_two=None, line_three=None)

    def __post_init__(self) -> None:
        if not validate_iso_3166_1_alpha_2(self.country_code):
>           raise ModelError("Invalid ISO 3166-1 alpha-2 country code.")
E           pycheval.exc.ModelError: Invalid ISO 3166-1 alpha-2 country code.

.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../site-packages/pycheval/model.py:325: ModelError
weblate_web.tests.PaymentTest::test_fosdem_donation
Stack Traces | 0.197s run time
self = <weblate_web.tests.PaymentTest testMethod=test_fosdem_donation>

    @override_settings(**THEPAY2_MOCK_SETTINGS)
    @responses.activate
    def test_fosdem_donation(self) -> None:
        thepay_mock_create_payment()
    
        response = self.client.get("/fosdem/donate/", follow=True)
        self.assertContains(response, "Please provide your billing")
        payment = Payment.objects.all().get()
        self.assertEqual(payment.amount, 30)
        self.assertEqual(payment.state, Payment.NEW)
        customer_url = reverse("payment-customer", kwargs={"pk": payment.uuid})
        payment_url = reverse("payment", kwargs={"pk": payment.uuid})
        self.assertRedirects(response, customer_url)
        response = self.client.post(customer_url, TEST_CUSTOMER, follow=True)
        self.assertContains(response, "Please choose payment method")
        response = self.client.post(payment_url, {"method": "thepay2-card"})
        self.assertEqual(response.status_code, 302)
        self.assertTrue(response.url.startswith("https://gate.thepay.cz/"))  # type: ignore[attr-defined]
    
        payment.refresh_from_db()
        self.assertEqual(payment.state, Payment.PENDING)
    
        # Perform the payment
        thepay_mock_payment(payment.pk)
    
        # Back to our web
>       response = self.client.get(payment.get_complete_url())
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

weblate_web/tests.py:1389: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../django/test/client.py:1124: in get
    response = super().get(
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../django/test/client.py:475: in get
    return self.generic(
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../django/test/client.py:671: in generic
    return self.request(**r)
           ^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../django/test/client.py:1087: in request
    self.check_exception(response)
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../django/test/client.py:802: in check_exception
    raise exc_value
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../core/handlers/exception.py:55: in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../core/handlers/base.py:197: in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../views/generic/base.py:105: in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/views.py:587: in dispatch
    backend.complete(self.request)
weblate_web/payments/backends.py:357: in complete
    self.success()
weblate_web/payments/backends.py:450: in success
    self.generate_invoice()
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12/contextlib.py:81: in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
weblate_web/payments/backends.py:423: in generate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:567: in generate_files
    self.generate_en_16931_xml()
weblate_web/invoices/models.py:842: in generate_en_16931_xml
    invoice = self.get_en_16931_xml()
              ^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/invoices/models.py:734: in get_en_16931_xml
    tax = Tax(
<string>:11: in __init__
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Tax(calculated_amount=Money('5.207', 'EUR'), basis_amount=Money('24.793', 'EUR'), rate_percent=21, category_code=<TaxC...e.STANDARD_RATE: 'S'>, exemption_reason=None, exemption_reason_code=None, tax_point_date=None, due_date_type_code=None)

    def __post_init__(self) -> None:
        if (
            self.due_date_type_code is not None
            and not self.due_date_type_code.is_invoice_due_date
        ):
            raise ModelError(
                "Invalid due date type code: {self.due_date_type_code}."
            )
        if (
            self.rate_percent is not None
            and self.basis_amount * self.rate_percent / Decimal(100)
            != self.calculated_amount
        ):
>           raise ModelError(
                f"Calculated amount {self.calculated_amount} does not match "
                f"basis amount {self.basis_amount} and tax rate "
                f"{self.rate_percent}\u2009%."
            )
E           pycheval.exc.ModelError: Calculated amount EUR 5.207 does not match basis amount EUR 24.793 and tax rate 21 %.

.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../site-packages/pycheval/model.py:792: ModelError
weblate_web.payments.tests.BackendTest::test_invoice_url
Stack Traces | 0.255s run time
self = <weblate_web.payments.tests.BackendTest testMethod=test_invoice_url>

    @responses.activate
    @override_settings(
        FIO_TOKEN="test-token",  # noqa: S106
    )
    def test_invoice_url(self) -> None:
        mock_vies()
        customer = Customer.objects.create(**CUSTOMER)
        invoice = Invoice.objects.create(
            customer=customer,
            kind=InvoiceKind.INVOICE,
            category=InvoiceCategory.HOSTING,
            vat_rate=21,
        )
        invoice.invoiceitem_set.create(
            description="Test item",
            unit_price=100,
        )
    
        url = cast("str", invoice.get_payment_url())
        self.assertIsNotNone(url)
    
        # Trigger payment what creates an empty payment object
        self.client.get(url, follow=True)
    
        received = deepcopy(FIO_TRASACTIONS)
        transaction = received["accountStatement"]["transactionList"]["transaction"]  # type: ignore[index]
        transaction[0]["column16"]["value"] = invoice.number
        transaction[1]["column16"]["value"] = invoice.number
        transaction[1]["column1"]["value"] = int(invoice.total_amount)
        responses.add(responses.GET, FIO_API, body=json.dumps(received))
>       FioBank.fetch_payments()

weblate_web/payments/tests.py:428: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
weblate_web/payments/backends.py:645: in fetch_payments
    backend.success()
weblate_web/payments/backends.py:453: in success
    self.send_notification("payment_completed")
weblate_web/payments/backends.py:436: in send_notification
    self.payment.customer.send_notification(
weblate_web/payments/models.py:294: in send_notification
    email = send_notification(notification, recipients, invoice=invoice, **kwargs)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/payments/utils.py:108: in send_notification
    email.attach(invoice.filename, invoice.path.read_bytes(), "application/pdf")
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12....../x64/lib/python3.12/pathlib.py:1019: in read_bytes
    with self.open(mode='rb') as f:
         ^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = PosixPath('.../website/invoices/Weblate_Invoice_1025000001.pdf')
mode = 'rb', buffering = -1, encoding = None, errors = None, newline = None

    def open(self, mode='r', buffering=-1, encoding=None,
             errors=None, newline=None):
        """
        Open the file pointed to by this path and return a file object, as
        the built-in open() function does.
        """
        if "b" not in mode:
            encoding = io.text_encoding(encoding)
>       return io.open(self, mode, buffering, encoding, errors, newline)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       FileNotFoundError: [Errno 2] No such file or directory: '.../website/invoices/Weblate_Invoice_1025000001.pdf'

.../hostedtoolcache/Python/3.12.12....../x64/lib/python3.12/pathlib.py:1013: FileNotFoundError
weblate_web.tests.PaymentTest::test_recurring
Stack Traces | 0.352s run time
self = <weblate_web.tests.PaymentTest testMethod=test_recurring>

    @responses.activate
    @override_settings(PAYMENT_DEBUG=True)
    def test_recurring(self) -> None:
        donation = self.create_donation(years=0)
        payment = cast("Payment", donation.payment_obj)
        self.assertIsNotNone(payment)
        # No recurring payments for now
        self.assertEqual(payment.payment_set.count(), 0)
    
        # Trigger payment and process it
>       call_command("recurring_payments")

weblate_web/tests.py:1348: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../hostedtoolcache/Python/3.12.12............/x64/lib/python3.12.../core/management/__init__.py:194: in call_command
    return command.execute(*args, **defaults)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12............/x64/lib/python3.12.../core/management/base.py:460: in execute
    output = self.handle(*args, **options)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../management/commands/recurring_payments.py:44: in handle
    self.handle_donations()
.../management/commands/recurring_payments.py:214: in handle_donations
    cls.peform_payment(
.../management/commands/recurring_payments.py:166: in peform_payment
    repeated.trigger_recurring()
weblate_web/payments/models.py:590: in trigger_recurring
    backend.complete(None)
weblate_web/payments/backends.py:357: in complete
    self.success()
weblate_web/payments/backends.py:450: in success
    self.generate_invoice()
.../hostedtoolcache/Python/3.12.12............/x64/lib/python3.12/contextlib.py:81: in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
weblate_web/payments/backends.py:423: in generate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:567: in generate_files
    self.generate_en_16931_xml()
weblate_web/invoices/models.py:842: in generate_en_16931_xml
    invoice = self.get_en_16931_xml()
              ^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/invoices/models.py:829: in get_en_16931_xml
    address=PostalAddress(
<string>:10: in __init__
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = PostalAddress(country_code='', country_subdivision=None, post_code='', city='', line_one='', line_two=None, line_three=None)

    def __post_init__(self) -> None:
        if not validate_iso_3166_1_alpha_2(self.country_code):
>           raise ModelError("Invalid ISO 3166-1 alpha-2 country code.")
E           pycheval.exc.ModelError: Invalid ISO 3166-1 alpha-2 country code.

.../hostedtoolcache/Python/3.12.12............/x64/lib/python3.12.../site-packages/pycheval/model.py:325: ModelError
weblate_web.tests.ExpiryTest::test_expiring_recurring_donate
Stack Traces | 0.358s run time
self = <weblate_web.tests.ExpiryTest testMethod=test_expiring_recurring_donate>

    def test_expiring_recurring_donate(self) -> None:
        self.create_donation(years=0, days=-2)
        RecurringPaymentsCommand.notify_expiry(force_summary=True)
        self.assert_notifications()
>       RecurringPaymentsCommand.handle_donations()

weblate_web/tests.py:1700: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../management/commands/recurring_payments.py:214: in handle_donations
    cls.peform_payment(
.../management/commands/recurring_payments.py:166: in peform_payment
    repeated.trigger_recurring()
weblate_web/payments/models.py:590: in trigger_recurring
    backend.complete(None)
weblate_web/payments/backends.py:357: in complete
    self.success()
weblate_web/payments/backends.py:450: in success
    self.generate_invoice()
.../hostedtoolcache/Python/3.12.12....../x64/lib/python3.12/contextlib.py:81: in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
weblate_web/payments/backends.py:423: in generate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:567: in generate_files
    self.generate_en_16931_xml()
weblate_web/invoices/models.py:842: in generate_en_16931_xml
    invoice = self.get_en_16931_xml()
              ^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/invoices/models.py:829: in get_en_16931_xml
    address=PostalAddress(
<string>:10: in __init__
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = PostalAddress(country_code='', country_subdivision=None, post_code='', city='', line_one='', line_two=None, line_three=None)

    def __post_init__(self) -> None:
        if not validate_iso_3166_1_alpha_2(self.country_code):
>           raise ModelError("Invalid ISO 3166-1 alpha-2 country code.")
E           pycheval.exc.ModelError: Invalid ISO 3166-1 alpha-2 country code.

.../hostedtoolcache/Python/3.12.12....../x64/lib/python3.12.../site-packages/pycheval/model.py:325: ModelError
weblate_web.tests.ExpiryTest::test_expiring_recurring_subscription
Stack Traces | 0.4s run time
self = <weblate_web.tests.ExpiryTest testMethod=test_expiring_recurring_subscription>

    def test_expiring_recurring_subscription(self) -> None:
        self.create_service(years=0, days=-2)
        RecurringPaymentsCommand.notify_expiry(force_summary=True)
        self.assert_notifications()
>       RecurringPaymentsCommand.handle_subscriptions()

weblate_web/tests.py:1736: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../management/commands/recurring_payments.py:192: in handle_subscriptions
    cls.peform_payment(
.../management/commands/recurring_payments.py:166: in peform_payment
    repeated.trigger_recurring()
weblate_web/payments/models.py:590: in trigger_recurring
    backend.complete(None)
weblate_web/payments/backends.py:357: in complete
    self.success()
weblate_web/payments/backends.py:450: in success
    self.generate_invoice()
.../hostedtoolcache/Python/3.12.12....../x64/lib/python3.12/contextlib.py:81: in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
weblate_web/payments/backends.py:423: in generate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:567: in generate_files
    self.generate_en_16931_xml()
weblate_web/invoices/models.py:842: in generate_en_16931_xml
    invoice = self.get_en_16931_xml()
              ^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/invoices/models.py:829: in get_en_16931_xml
    address=PostalAddress(
<string>:10: in __init__
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = PostalAddress(country_code='', country_subdivision=None, post_code='', city='', line_one='', line_two=None, line_three=None)

    def __post_init__(self) -> None:
        if not validate_iso_3166_1_alpha_2(self.country_code):
>           raise ModelError("Invalid ISO 3166-1 alpha-2 country code.")
E           pycheval.exc.ModelError: Invalid ISO 3166-1 alpha-2 country code.

.../hostedtoolcache/Python/3.12.12....../x64/lib/python3.12.../site-packages/pycheval/model.py:325: ModelError
weblate_web.tests.PaymentsTest::test_pay
Stack Traces | 0.486s run time
self = <weblate_web.tests.PaymentsTest testMethod=test_pay>

    @override_settings(PAYMENT_DEBUG=True)
    def test_pay(self) -> None:
        payment, url, _dummy = self.prepare_payment()
>       response = self.client.post(url, {"method": "pay"})
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

weblate_web/tests.py:984: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:1153: in post
    response = super().post(
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:499: in post
    return self.generic(
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:671: in generic
    return self.request(**r)
           ^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:1087: in request
    self.check_exception(response)
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:802: in check_exception
    raise exc_value
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../core/handlers/exception.py:55: in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../core/handlers/base.py:197: in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../views/generic/base.py:105: in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/views.py:431: in dispatch
    return super().dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../views/generic/base.py:144: in dispatch
    return handler(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../views/generic/edit.py:151: in post
    return self.form_valid(form)
           ^^^^^^^^^^^^^^^^^^^^^
weblate_web/views.py:474: in form_valid
    backend.complete(self.request)
weblate_web/payments/backends.py:357: in complete
    self.success()
weblate_web/payments/backends.py:450: in success
    self.generate_invoice()
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12/contextlib.py:81: in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
weblate_web/payments/backends.py:423: in generate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:567: in generate_files
    self.generate_en_16931_xml()
weblate_web/invoices/models.py:842: in generate_en_16931_xml
    invoice = self.get_en_16931_xml()
              ^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/invoices/models.py:734: in get_en_16931_xml
    tax = Tax(
<string>:11: in __init__
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Tax(calculated_amount=Money('21', 'EUR'), basis_amount=Money('100', 'EUR'), rate_percent=21, category_code=<TaxCategor...e.STANDARD_RATE: 'S'>, exemption_reason=None, exemption_reason_code=None, tax_point_date=None, due_date_type_code=None)

    def __post_init__(self) -> None:
        if (
            self.due_date_type_code is not None
            and not self.due_date_type_code.is_invoice_due_date
        ):
            raise ModelError(
                "Invalid due date type code: {self.due_date_type_code}."
            )
        if (
            self.rate_percent is not None
            and self.basis_amount * self.rate_percent / Decimal(100)
            != self.calculated_amount
        ):
>           raise ModelError(
                f"Calculated amount {self.calculated_amount} does not match "
                f"basis amount {self.basis_amount} and tax rate "
                f"{self.rate_percent}\u2009%."
            )
E           pycheval.exc.ModelError: Calculated amount EUR 21 does not match basis amount EUR 100 and tax rate 21 %.

.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../site-packages/pycheval/model.py:792: ModelError
weblate_web.tests.PaymentsTest::test_pending
Stack Traces | 0.682s run time
self = <weblate_web.tests.PaymentsTest testMethod=test_pending>

    @override_settings(PAYMENT_DEBUG=True)
    def test_pending(self) -> None:
        payment, url, _dummy = self.prepare_payment()
        response = self.client.post(url, {"method": "pending"})
        complete_url = reverse("payment-complete", kwargs={"pk": payment.pk})
        self.assertRedirects(
            response,
            "https://cihar.com/?url=http://localhost:1234" + complete_url,
            fetch_redirect_response=False,
        )
        self.check_payment(payment, Payment.PENDING)
>       response = self.client.get(complete_url)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

weblate_web/tests.py:1043: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../django/test/client.py:1124: in get
    response = super().get(
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../django/test/client.py:475: in get
    return self.generic(
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../django/test/client.py:671: in generic
    return self.request(**r)
           ^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../django/test/client.py:1087: in request
    self.check_exception(response)
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../django/test/client.py:802: in check_exception
    raise exc_value
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../core/handlers/exception.py:55: in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../core/handlers/base.py:197: in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../views/generic/base.py:105: in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/views.py:587: in dispatch
    backend.complete(self.request)
weblate_web/payments/backends.py:357: in complete
    self.success()
weblate_web/payments/backends.py:450: in success
    self.generate_invoice()
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12/contextlib.py:81: in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
weblate_web/payments/backends.py:423: in generate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:567: in generate_files
    self.generate_en_16931_xml()
weblate_web/invoices/models.py:842: in generate_en_16931_xml
    invoice = self.get_en_16931_xml()
              ^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/invoices/models.py:734: in get_en_16931_xml
    tax = Tax(
<string>:11: in __init__
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Tax(calculated_amount=Money('21', 'EUR'), basis_amount=Money('100', 'EUR'), rate_percent=21, category_code=<TaxCategor...e.STANDARD_RATE: 'S'>, exemption_reason=None, exemption_reason_code=None, tax_point_date=None, due_date_type_code=None)

    def __post_init__(self) -> None:
        if (
            self.due_date_type_code is not None
            and not self.due_date_type_code.is_invoice_due_date
        ):
            raise ModelError(
                "Invalid due date type code: {self.due_date_type_code}."
            )
        if (
            self.rate_percent is not None
            and self.basis_amount * self.rate_percent / Decimal(100)
            != self.calculated_amount
        ):
>           raise ModelError(
                f"Calculated amount {self.calculated_amount} does not match "
                f"basis amount {self.basis_amount} and tax rate "
                f"{self.rate_percent}\u2009%."
            )
E           pycheval.exc.ModelError: Calculated amount EUR 21 does not match basis amount EUR 100 and tax rate 21 %.

.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../site-packages/pycheval/model.py:792: ModelError
weblate_web.tests.PaymentTest::test_donation_workflow_card
Stack Traces | 0.812s run time
self = <weblate_web.tests.PaymentTest testMethod=test_donation_workflow_card>
reward = 0

    @override_settings(**THEPAY2_MOCK_SETTINGS)
    @responses.activate
    def test_donation_workflow_card(self, reward=0) -> None:  # noqa: PLR0915
        self.login()
        thepay_mock_create_payment()
        response = self.client.post(
            ".../en/donate/new/",
            {"recurring": "y", "amount": 1000, "reward": reward},
            follow=True,
        )
        self.assertContains(response, "Please provide your billing")
        payment = Payment.objects.all().get()
        self.assertEqual(payment.amount, 1000)
        self.assertEqual(payment.state, Payment.NEW)
        customer_url = reverse("payment-customer", kwargs={"pk": payment.uuid})
        payment_url = reverse("payment", kwargs={"pk": payment.uuid})
        self.assertRedirects(response, customer_url)
        response = self.client.post(customer_url, TEST_CUSTOMER, follow=True)
        self.assertContains(response, "Please choose payment method")
        response = self.client.post(payment_url, {"method": "thepay2-card"})
        self.assertEqual(response.status_code, 302)
        self.assertTrue(response.url.startswith("https://gate.thepay.cz/"))  # type: ignore[attr-defined]
    
        payment.refresh_from_db()
        self.assertEqual(payment.state, Payment.PENDING)
    
        # Perform the payment
        thepay_mock_payment(payment.pk)
    
        # Back to our web
>       response = self.client.get(payment.get_complete_url(), follow=True)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

weblate_web/tests.py:1212: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../django/test/client.py:1124: in get
    response = super().get(
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../django/test/client.py:475: in get
    return self.generic(
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../django/test/client.py:671: in generic
    return self.request(**r)
           ^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../django/test/client.py:1087: in request
    self.check_exception(response)
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../django/test/client.py:802: in check_exception
    raise exc_value
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../core/handlers/exception.py:55: in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../core/handlers/base.py:197: in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../views/generic/base.py:105: in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/views.py:587: in dispatch
    backend.complete(self.request)
weblate_web/payments/backends.py:357: in complete
    self.success()
weblate_web/payments/backends.py:450: in success
    self.generate_invoice()
.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12/contextlib.py:81: in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
weblate_web/payments/backends.py:423: in generate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:567: in generate_files
    self.generate_en_16931_xml()
weblate_web/invoices/models.py:842: in generate_en_16931_xml
    invoice = self.get_en_16931_xml()
              ^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/invoices/models.py:734: in get_en_16931_xml
    tax = Tax(
<string>:11: in __init__
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Tax(calculated_amount=Money('173.554', 'EUR'), basis_amount=Money('826.446', 'EUR'), rate_percent=21, category_code=<T...e.STANDARD_RATE: 'S'>, exemption_reason=None, exemption_reason_code=None, tax_point_date=None, due_date_type_code=None)

    def __post_init__(self) -> None:
        if (
            self.due_date_type_code is not None
            and not self.due_date_type_code.is_invoice_due_date
        ):
            raise ModelError(
                "Invalid due date type code: {self.due_date_type_code}."
            )
        if (
            self.rate_percent is not None
            and self.basis_amount * self.rate_percent / Decimal(100)
            != self.calculated_amount
        ):
>           raise ModelError(
                f"Calculated amount {self.calculated_amount} does not match "
                f"basis amount {self.basis_amount} and tax rate "
                f"{self.rate_percent}\u2009%."
            )
E           pycheval.exc.ModelError: Calculated amount EUR 173.554 does not match basis amount EUR 826.446 and tax rate 21 %.

.../hostedtoolcache/Python/3.12.12............................../x64/lib/python3.12.../site-packages/pycheval/model.py:792: ModelError
weblate_web.tests.PaymentTest::test_donation_workflow_bank
Stack Traces | 0.825s run time
self = <weblate_web.tests.PaymentTest testMethod=test_donation_workflow_bank>

    def test_donation_workflow_bank(self) -> None:
        self.login()
        response = self.client.post(
            ".../en/donate/new/",
            {"recurring": "y", "amount": 10, "reward": 0},
            follow=True,
        )
        self.assertContains(response, "Please provide your billing")
        payment = Payment.objects.all().get()
        self.assertEqual(payment.state, Payment.NEW)
        customer_url = reverse("payment-customer", kwargs={"pk": payment.uuid})
        payment_url = reverse("payment", kwargs={"pk": payment.uuid})
        self.assertRedirects(response, customer_url)
        response = self.client.post(customer_url, TEST_CUSTOMER, follow=True)
        self.assertContains(response, "Please choose payment method")
>       response = self.client.post(payment_url, {"method": "fio-bank"}, follow=True)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

weblate_web/tests.py:1294: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:1153: in post
    response = super().post(
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:499: in post
    return self.generic(
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:671: in generic
    return self.request(**r)
           ^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:1087: in request
    self.check_exception(response)
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:802: in check_exception
    raise exc_value
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../core/handlers/exception.py:55: in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../core/handlers/base.py:197: in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../views/generic/base.py:105: in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/views.py:431: in dispatch
    return super().dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../views/generic/base.py:144: in dispatch
    return handler(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../views/generic/edit.py:151: in post
    return self.form_valid(form)
           ^^^^^^^^^^^^^^^^^^^^^
weblate_web/views.py:460: in form_valid
    result = backend.initiate(
weblate_web/payments/backends.py:332: in initiate
    result = self.perform(request, back_url, complete_url)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/payments/backends.py:531: in perform
    self.generate_invoice(proforma=True)
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12/contextlib.py:81: in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
weblate_web/payments/backends.py:423: in generate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:567: in generate_files
    self.generate_en_16931_xml()
weblate_web/invoices/models.py:842: in generate_en_16931_xml
    invoice = self.get_en_16931_xml()
              ^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/invoices/models.py:734: in get_en_16931_xml
    tax = Tax(
<string>:11: in __init__
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Tax(calculated_amount=Money('1.735', 'EUR'), basis_amount=Money('8.264', 'EUR'), rate_percent=21, category_code=<TaxCa...e.STANDARD_RATE: 'S'>, exemption_reason=None, exemption_reason_code=None, tax_point_date=None, due_date_type_code=None)

    def __post_init__(self) -> None:
        if (
            self.due_date_type_code is not None
            and not self.due_date_type_code.is_invoice_due_date
        ):
            raise ModelError(
                "Invalid due date type code: {self.due_date_type_code}."
            )
        if (
            self.rate_percent is not None
            and self.basis_amount * self.rate_percent / Decimal(100)
            != self.calculated_amount
        ):
>           raise ModelError(
                f"Calculated amount {self.calculated_amount} does not match "
                f"basis amount {self.basis_amount} and tax rate "
                f"{self.rate_percent}\u2009%."
            )
E           pycheval.exc.ModelError: Calculated amount EUR 1.735 does not match basis amount EUR 8.264 and tax rate 21 %.

.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../site-packages/pycheval/model.py:792: ModelError
weblate_web.tests.PaymentTest::test_service_workflow_card
Stack Traces | 0.859s run time
self = <weblate_web.tests.PaymentTest testMethod=test_service_workflow_card>

    @override_settings(**THEPAY2_MOCK_SETTINGS)
    @responses.activate
    def test_service_workflow_card(self) -> None:  # noqa: PLR0915
        self.login()
        thepay_mock_create_payment()
        Package.objects.create(name="community", verbose="Community support", price=0)
        Package.objects.create(name="extended", verbose="Extended support", price=42)
        response = self.client.get(".../en/subscription/new/?plan=extended", follow=True)
        self.assertContains(response, "Please provide your billing")
        payment = Payment.objects.all().get()
        self.assertEqual(payment.state, Payment.NEW)
        customer_url = reverse("payment-customer", kwargs={"pk": payment.uuid})
        payment_url = reverse("payment", kwargs={"pk": payment.uuid})
        self.assertRedirects(response, customer_url)
        response = self.client.post(customer_url, TEST_CUSTOMER, follow=True)
        self.assertContains(response, "Please choose payment method")
        response = self.client.post(payment_url, {"method": "thepay2-card"})
        self.assertEqual(response.status_code, 302)
        self.assertTrue(response.url.startswith("https://gate.thepay.cz/"))  # type: ignore[attr-defined]
    
        payment.refresh_from_db()
        self.assertEqual(payment.state, Payment.PENDING)
    
        # Perform the payment
        thepay_mock_payment(payment.pk)
    
        # Back to our web
>       response = self.client.get(payment.get_complete_url(), follow=True)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

weblate_web/tests.py:1093: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../hostedtoolcache/Python/3.12.12.........................../x64/lib/python3.12.../django/test/client.py:1124: in get
    response = super().get(
.../hostedtoolcache/Python/3.12.12.........................../x64/lib/python3.12.../django/test/client.py:475: in get
    return self.generic(
.../hostedtoolcache/Python/3.12.12.........................../x64/lib/python3.12.../django/test/client.py:671: in generic
    return self.request(**r)
           ^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12.........................../x64/lib/python3.12.../django/test/client.py:1087: in request
    self.check_exception(response)
.../hostedtoolcache/Python/3.12.12.........................../x64/lib/python3.12.../django/test/client.py:802: in check_exception
    raise exc_value
.../hostedtoolcache/Python/3.12.12.........................../x64/lib/python3.12.../core/handlers/exception.py:55: in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12.........................../x64/lib/python3.12.../core/handlers/base.py:197: in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12.........................../x64/lib/python3.12.../views/generic/base.py:105: in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/views.py:587: in dispatch
    backend.complete(self.request)
weblate_web/payments/backends.py:357: in complete
    self.success()
weblate_web/payments/backends.py:450: in success
    self.generate_invoice()
.../hostedtoolcache/Python/3.12.12.........................../x64/lib/python3.12/contextlib.py:81: in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
weblate_web/payments/backends.py:423: in generate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:568: in generate_files
    self.generate_pdf()
weblate_web/invoices/models.py:850: in generate_pdf
    render_pdf(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def render_pdf(
        *,
        html: str,
        output: Path,
        attachments: list[Attachment] | None = None,
        pdf_variant: str | None = None,
    ) -> None:
        font_config = FontConfiguration()
    
        renderer = HTML(
            string=html,
            url_fetcher=url_fetcher,
        )
        fonts_css = finders.find("pdf/fonts.css")
        if fonts_css is None:
            raise ValueError("Could not load fonts CSS")
        font_style = CSS(
            filename=fonts_css,
            font_config=font_config,
            url_fetcher=url_fetcher,
        )
        if attachments:
>           renderer.metadata.attachments = attachments
            ^^^^^^^^^^^^^^^^^
E           AttributeError: 'HTML' object has no attribute 'metadata'

weblate_web/pdf.py:92: AttributeError
weblate_web.tests.ServiceTest::test_hosted_pay_yearly
Stack Traces | 0.883s run time
self = <weblate_web.tests.ServiceTest testMethod=test_hosted_pay_yearly>

    @responses.activate
    def test_hosted_pay_yearly(self) -> None:
        mock_vies()
        cnb_mock_rates()
        with override("en"):
            self.login()
            service = self.create_service(
                years=0, days=3, recurring="", package="test:test-1-m"
            )
            response = self.client.post(
                reverse("subscription-pay", kwargs={"pk": service.pk}),
                {"switch_yearly": 1},
                follow=True,
            )
            payment_url = response.redirect_chain[0][0].split("localhost:1234")[-1]
            payment_edit_url = response.redirect_chain[1][0]
            self.assertTrue(payment_url.startswith("/en/payment/"))
            response = self.client.post(payment_edit_url, TEST_CUSTOMER, follow=True)
            self.assertRedirects(response, payment_url)
>           response = self.client.post(payment_url, {"method": "pay"}, follow=True)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

weblate_web/tests.py:1821: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../django/test/client.py:1153: in post
    response = super().post(
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../django/test/client.py:499: in post
    return self.generic(
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../django/test/client.py:671: in generic
    return self.request(**r)
           ^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../django/test/client.py:1087: in request
    self.check_exception(response)
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../django/test/client.py:802: in check_exception
    raise exc_value
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../core/handlers/exception.py:55: in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../core/handlers/base.py:197: in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../views/generic/base.py:105: in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/views.py:431: in dispatch
    return super().dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../views/generic/base.py:144: in dispatch
    return handler(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../views/generic/edit.py:151: in post
    return self.form_valid(form)
           ^^^^^^^^^^^^^^^^^^^^^
weblate_web/views.py:474: in form_valid
    backend.complete(self.request)
weblate_web/payments/backends.py:357: in complete
    self.success()
weblate_web/payments/backends.py:450: in success
    self.generate_invoice()
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12/contextlib.py:81: in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
weblate_web/payments/backends.py:423: in generate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:568: in generate_files
    self.generate_pdf()
weblate_web/invoices/models.py:850: in generate_pdf
    render_pdf(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def render_pdf(
        *,
        html: str,
        output: Path,
        attachments: list[Attachment] | None = None,
        pdf_variant: str | None = None,
    ) -> None:
        font_config = FontConfiguration()
    
        renderer = HTML(
            string=html,
            url_fetcher=url_fetcher,
        )
        fonts_css = finders.find("pdf/fonts.css")
        if fonts_css is None:
            raise ValueError("Could not load fonts CSS")
        font_style = CSS(
            filename=fonts_css,
            font_config=font_config,
            url_fetcher=url_fetcher,
        )
        if attachments:
>           renderer.metadata.attachments = attachments
            ^^^^^^^^^^^^^^^^^
E           AttributeError: 'HTML' object has no attribute 'metadata'

weblate_web/pdf.py:92: AttributeError
weblate_web.tests.ServiceTest::test_hosted_pay
Stack Traces | 0.887s run time
self = <weblate_web.tests.ServiceTest testMethod=test_hosted_pay>

    @responses.activate
    def test_hosted_pay(self) -> None:
        mock_vies()
        cnb_mock_rates()
        with override("en"):
            self.login()
            service = self.create_service(
                years=0, days=3, recurring="", package="test:test-1-m"
            )
            hosted = service.hosted_subscriptions
            self.assertEqual(
                hosted[0].expires.date(),
                timezone.now().date() + timedelta(days=3),
            )
    
            response = self.client.post(
                reverse("subscription-pay", kwargs={"pk": service.pk}),
                follow=True,
            )
            payment_url = response.redirect_chain[0][0].split("localhost:1234")[-1]
            payment_edit_url = response.redirect_chain[1][0]
            self.assertTrue(payment_url.startswith("/en/payment/"))
            response = self.client.post(payment_edit_url, TEST_CUSTOMER, follow=True)
            self.assertRedirects(response, payment_url)
>           response = self.client.post(payment_url, {"method": "pay"}, follow=True)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

weblate_web/tests.py:1788: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../django/test/client.py:1153: in post
    response = super().post(
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../django/test/client.py:499: in post
    return self.generic(
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../django/test/client.py:671: in generic
    return self.request(**r)
           ^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../django/test/client.py:1087: in request
    self.check_exception(response)
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../django/test/client.py:802: in check_exception
    raise exc_value
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../core/handlers/exception.py:55: in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../core/handlers/base.py:197: in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../views/generic/base.py:105: in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/views.py:431: in dispatch
    return super().dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../views/generic/base.py:144: in dispatch
    return handler(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../views/generic/edit.py:151: in post
    return self.form_valid(form)
           ^^^^^^^^^^^^^^^^^^^^^
weblate_web/views.py:474: in form_valid
    backend.complete(self.request)
weblate_web/payments/backends.py:357: in complete
    self.success()
weblate_web/payments/backends.py:450: in success
    self.generate_invoice()
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12/contextlib.py:81: in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
weblate_web/payments/backends.py:423: in generate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:568: in generate_files
    self.generate_pdf()
weblate_web/invoices/models.py:850: in generate_pdf
    render_pdf(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def render_pdf(
        *,
        html: str,
        output: Path,
        attachments: list[Attachment] | None = None,
        pdf_variant: str | None = None,
    ) -> None:
        font_config = FontConfiguration()
    
        renderer = HTML(
            string=html,
            url_fetcher=url_fetcher,
        )
        fonts_css = finders.find("pdf/fonts.css")
        if fonts_css is None:
            raise ValueError("Could not load fonts CSS")
        font_style = CSS(
            filename=fonts_css,
            font_config=font_config,
            url_fetcher=url_fetcher,
        )
        if attachments:
>           renderer.metadata.attachments = attachments
            ^^^^^^^^^^^^^^^^^
E           AttributeError: 'HTML' object has no attribute 'metadata'

weblate_web/pdf.py:92: AttributeError
weblate_web.tests.ServiceTest::test_decicated_new
Stack Traces | 0.889s run time
self = <weblate_web.tests.ServiceTest testMethod=test_decicated_new>

    @override_settings(ZAMMAD_TOKEN="test")  # noqa: S106
    @responses.activate
    def test_decicated_new(self) -> None:
        mock_vies()
        cnb_mock_rates()
        self.create_packages()
        responses.add(
            responses.POST,
            "https://care.weblate..../api/v1/tickets",
            json={
                "id": 19,
                "group_id": 2,
                "priority_id": 2,
                "state_id": 1,
                "organization_id": None,
                "number": "22019",
                "title": "Help me!",
                "owner_id": 1,
                "customer_id": 10,
                "note": None,
                "first_response_at": None,
                "first_response_escalation_at": None,
                "first_response_in_min": None,
                "first_response_diff_in_min": None,
                "close_at": None,
                "close_escalation_at": None,
                "close_in_min": None,
                "close_diff_in_min": None,
                "update_escalation_at": None,
                "update_in_min": None,
                "update_diff_in_min": None,
                "last_contact_at": None,
                "last_contact_agent_at": None,
                "last_contact_customer_at": None,
                "last_owner_update_at": None,
                "create_article_type_id": 10,
                "create_article_sender_id": 1,
                "article_count": 1,
                "escalation_at": None,
                "pending_time": None,
                "type": None,
                "time_unit": None,
                "preferences": {},
                "updated_by_id": 3,
                "created_by_id": 3,
                "created_at": "2021-11-08T14:17:41.913Z",
                "updated_at": "2021-11-08T14:17:41.994Z",
                "article_ids": [30],
                "ticket_time_accounting_ids": [],
            },
        )
    
        with override("en"):
            self.login()
            response = self.client.get(
                reverse("subscription-new"),
                {"plan": "test:test-1"},
                follow=True,
            )
            payment_url = response.redirect_chain[0][0].split("localhost:1234")[-1]
            payment_edit_url = response.redirect_chain[1][0]
            self.assertTrue(payment_url.startswith("/en/payment/"))
            response = self.client.post(payment_edit_url, TEST_CUSTOMER, follow=True)
            self.assertRedirects(response, payment_url)
>           response = self.client.post(payment_url, {"method": "pay"}, follow=True)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

weblate_web/tests.py:1899: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../django/test/client.py:1153: in post
    response = super().post(
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../django/test/client.py:499: in post
    return self.generic(
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../django/test/client.py:671: in generic
    return self.request(**r)
           ^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../django/test/client.py:1087: in request
    self.check_exception(response)
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../django/test/client.py:802: in check_exception
    raise exc_value
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../core/handlers/exception.py:55: in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../core/handlers/base.py:197: in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../views/generic/base.py:105: in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/views.py:431: in dispatch
    return super().dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../views/generic/base.py:144: in dispatch
    return handler(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../views/generic/edit.py:151: in post
    return self.form_valid(form)
           ^^^^^^^^^^^^^^^^^^^^^
weblate_web/views.py:474: in form_valid
    backend.complete(self.request)
weblate_web/payments/backends.py:357: in complete
    self.success()
weblate_web/payments/backends.py:450: in success
    self.generate_invoice()
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12/contextlib.py:81: in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
weblate_web/payments/backends.py:423: in generate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:568: in generate_files
    self.generate_pdf()
weblate_web/invoices/models.py:850: in generate_pdf
    render_pdf(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def render_pdf(
        *,
        html: str,
        output: Path,
        attachments: list[Attachment] | None = None,
        pdf_variant: str | None = None,
    ) -> None:
        font_config = FontConfiguration()
    
        renderer = HTML(
            string=html,
            url_fetcher=url_fetcher,
        )
        fonts_css = finders.find("pdf/fonts.css")
        if fonts_css is None:
            raise ValueError("Could not load fonts CSS")
        font_style = CSS(
            filename=fonts_css,
            font_config=font_config,
            url_fetcher=url_fetcher,
        )
        if attachments:
>           renderer.metadata.attachments = attachments
            ^^^^^^^^^^^^^^^^^
E           AttributeError: 'HTML' object has no attribute 'metadata'

weblate_web/pdf.py:92: AttributeError
weblate_web.tests.ServiceTest::test_hosted_upgrade
Stack Traces | 0.89s run time
self = <weblate_web.tests.ServiceTest testMethod=test_hosted_upgrade>

    @responses.activate
    def test_hosted_upgrade(self) -> None:
        mock_vies()
        cnb_mock_rates()
        with override("en"):
            self.login()
            service = self.create_service(
                years=0, days=3, recurring="", package="test:test-1"
            )
            suggestions = service.get_suggestions()
            self.assertEqual(len(suggestions), 1)
            self.assertEqual(suggestions[0][0], "test:test-2")
            params: dict[str, str | int] = {
                "plan": "test:test-2",
                "service": service.pk,
            }
            response = self.client.get(reverse("subscription-new"), params, follow=True)
            payment_url = response.redirect_chain[0][0].split("localhost:1234")[-1]
            payment_edit_url = response.redirect_chain[1][0]
            self.assertTrue(payment_url.startswith("/en/payment/"))
            response = self.client.post(payment_edit_url, TEST_CUSTOMER, follow=True)
            self.assertRedirects(response, payment_url)
>           response = self.client.post(payment_url, {"method": "pay"}, follow=True)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

weblate_web/tests.py:1925: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../django/test/client.py:1153: in post
    response = super().post(
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../django/test/client.py:499: in post
    return self.generic(
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../django/test/client.py:671: in generic
    return self.request(**r)
           ^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../django/test/client.py:1087: in request
    self.check_exception(response)
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../django/test/client.py:802: in check_exception
    raise exc_value
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../core/handlers/exception.py:55: in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../core/handlers/base.py:197: in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../views/generic/base.py:105: in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/views.py:431: in dispatch
    return super().dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../views/generic/base.py:144: in dispatch
    return handler(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12.../views/generic/edit.py:151: in post
    return self.form_valid(form)
           ^^^^^^^^^^^^^^^^^^^^^
weblate_web/views.py:474: in form_valid
    backend.complete(self.request)
weblate_web/payments/backends.py:357: in complete
    self.success()
weblate_web/payments/backends.py:450: in success
    self.generate_invoice()
.../hostedtoolcache/Python/3.12.12................................./x64/lib/python3.12/contextlib.py:81: in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
weblate_web/payments/backends.py:423: in generate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:568: in generate_files
    self.generate_pdf()
weblate_web/invoices/models.py:850: in generate_pdf
    render_pdf(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def render_pdf(
        *,
        html: str,
        output: Path,
        attachments: list[Attachment] | None = None,
        pdf_variant: str | None = None,
    ) -> None:
        font_config = FontConfiguration()
    
        renderer = HTML(
            string=html,
            url_fetcher=url_fetcher,
        )
        fonts_css = finders.find("pdf/fonts.css")
        if fonts_css is None:
            raise ValueError("Could not load fonts CSS")
        font_style = CSS(
            filename=fonts_css,
            font_config=font_config,
            url_fetcher=url_fetcher,
        )
        if attachments:
>           renderer.metadata.attachments = attachments
            ^^^^^^^^^^^^^^^^^
E           AttributeError: 'HTML' object has no attribute 'metadata'

weblate_web/pdf.py:92: AttributeError
weblate_web.tests.PaymentTest::test_donation_workflow_card_reward
Stack Traces | 0.945s run time
self = <weblate_web.tests.PaymentTest testMethod=test_donation_workflow_card_reward>

    def test_donation_workflow_card_reward(self) -> None:
>       self.test_donation_workflow_card(2)

weblate_web/tests.py:1180: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/utils.py:456: in inner
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../site-packages/responses/__init__.py:232: in wrapper
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
weblate_web/tests.py:1212: in test_donation_workflow_card
    response = self.client.get(payment.get_complete_url(), follow=True)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:1124: in get
    response = super().get(
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:475: in get
    return self.generic(
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:671: in generic
    return self.request(**r)
           ^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:1087: in request
    self.check_exception(response)
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../django/test/client.py:802: in check_exception
    raise exc_value
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../core/handlers/exception.py:55: in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../core/handlers/base.py:197: in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../views/generic/base.py:105: in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/views.py:587: in dispatch
    backend.complete(self.request)
weblate_web/payments/backends.py:357: in complete
    self.success()
weblate_web/payments/backends.py:450: in success
    self.generate_invoice()
.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12/contextlib.py:81: in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
weblate_web/payments/backends.py:423: in generate_invoice
    invoice.generate_files()
weblate_web/invoices/models.py:567: in generate_files
    self.generate_en_16931_xml()
weblate_web/invoices/models.py:842: in generate_en_16931_xml
    invoice = self.get_en_16931_xml()
              ^^^^^^^^^^^^^^^^^^^^^^^
weblate_web/invoices/models.py:734: in get_en_16931_xml
    tax = Tax(
<string>:11: in __init__
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Tax(calculated_amount=Money('173.554', 'EUR'), basis_amount=Money('826.446', 'EUR'), rate_percent=21, category_code=<T...e.STANDARD_RATE: 'S'>, exemption_reason=None, exemption_reason_code=None, tax_point_date=None, due_date_type_code=None)

    def __post_init__(self) -> None:
        if (
            self.due_date_type_code is not None
            and not self.due_date_type_code.is_invoice_due_date
        ):
            raise ModelError(
                "Invalid due date type code: {self.due_date_type_code}."
            )
        if (
            self.rate_percent is not None
            and self.basis_amount * self.rate_percent / Decimal(100)
            != self.calculated_amount
        ):
>           raise ModelError(
                f"Calculated amount {self.calculated_amount} does not match "
                f"basis amount {self.basis_amount} and tax rate "
                f"{self.rate_percent}\u2009%."
            )
E           pycheval.exc.ModelError: Calculated amount EUR 173.554 does not match basis amount EUR 826.446 and tax rate 21 %.

.../hostedtoolcache/Python/3.12.12..................................../x64/lib/python3.12.../site-packages/pycheval/model.py:792: ModelError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@nijel nijel force-pushed the en16931 branch 2 times, most recently from ad6ee0e to a5473ae Compare November 5, 2025 12:46
This is a proof of concept, I had to use several libraries to make it
work.

Outstanding issues:

- The VAT validation fails because of zfutura/pycheval#30
- We really need full validation to be part of the tests, needs to be
  investigated.
- There are definitely issues with generated XML.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backlog This is not on the Weblate roadmap for now. Can be prioritized by sponsorship.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants