Compare commits

..

1 Commits

Author SHA1 Message Date
Raphael Michel
2a61c21feb Drop support for Python 3.9 2026-01-08 11:16:58 +01:00
6 changed files with 17 additions and 49 deletions

View File

@@ -23,13 +23,13 @@ jobs:
name: Tests
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11"]
python-version: ["3.10", "3.11", "3.13"]
database: [sqlite, postgres]
exclude:
- database: sqlite
python-version: "3.9"
- database: sqlite
python-version: "3.10"
- database: sqlite
python-version: "3.11"
services:
postgres:
image: postgres:15

View File

@@ -286,11 +286,6 @@ gross prices stay the same.
**This is less confusing to consumers and the end result is still compliant to EN 16931, so we recommend this for e-invoicing when (primarily) consumers are involved.**
Some gross prices **cannot** stay the same. For example, it is impossible to represent €15.00 incl. 19%, since a net
price of €12.60 would lead to €14.99 gross and a net price of €12.61 would lead to €15.01 gross. Since this algorithm
is supposed to be consumer-friendly, it has a bias for choosing €14.99 in this case, even if €15.01 would be a little
less of a difference.
The main downside is that it might be confusing when selling to business customers, since the prices of the identical tickets appear to be different.
Full computation for the example above:

View File

@@ -3,7 +3,7 @@ name = "pretix"
dynamic = ["version"]
description = "Reinventing presales, one ticket at a time"
readme = "README.rst"
requires-python = ">=3.9"
requires-python = ">=3.10"
license = {file = "LICENSE"}
keywords = ["tickets", "web", "shop", "ecommerce"]
authors = [

View File

@@ -284,27 +284,12 @@ def apply_rounding(rounding_mode: Literal["line", "sum_by_net", "sum_by_net_keep
# e.g. 99.99 at 19% is impossible since 84.03 + 19% = 100.00 and 84.02 + 19% = 99.98
target_gross_total = round_decimal((target_net_total * (1 + tax_rate / 100)), currency)
if target_net_total and target_gross_total > gross_total:
target_net_total -= min((target_gross_total - gross_total), len(lines) * minimum_unit)
try_target_gross_total = round_decimal((target_net_total * (1 + tax_rate / 100)), currency)
if try_target_gross_total <= gross_total:
target_gross_total = try_target_gross_total
diff_gross = target_gross_total - gross_total
diff_net = target_net_total - net_total
diff_gross_sgn = -1 if diff_gross < 0 else 1
diff_net_sgn = -1 if diff_net < 0 else 1
for l in lines:
if diff_gross and diff_net:
apply_diff = diff_gross_sgn * minimum_unit
l.price = l.gross_price_before_rounding + apply_diff
l.price_includes_rounding_correction = apply_diff
l.tax_value = l.tax_value_before_rounding
l.tax_value_includes_rounding_correction = Decimal("0.00")
changed.append(l)
diff_gross -= apply_diff
diff_net -= apply_diff
elif diff_gross:
if diff_gross:
apply_diff = diff_gross_sgn * minimum_unit
l.price = l.gross_price_before_rounding + apply_diff
l.price_includes_rounding_correction = apply_diff
@@ -327,11 +312,6 @@ def apply_rounding(rounding_mode: Literal["line", "sum_by_net", "sum_by_net_keep
l.tax_value_includes_rounding_correction = Decimal("0.00")
changed.append(l)
# Double-check that result is consistent in computing gross from net
new_net_total = sum(l.price - l.tax_value for l in lines)
new_gross_total = sum(l.price for l in lines)
assert new_gross_total == round_decimal((new_net_total * (1 + tax_rate / 100)), currency)
elif rounding_mode == "line":
for l in lines:
if l.price_includes_rounding_correction or l.tax_value_includes_rounding_correction:

View File

@@ -871,8 +871,7 @@ class TaxSettingsForm(EventSettingsValidationMixin, SettingsForm):
"Recommended for e-invoicing when you primarily sell to consumers. "
"The gross or net price of some products may be changed automatically to ensure correct "
"rounding of the order total. The system attempts to keep gross prices as configured whenever "
"possible. Gross prices may still change if they are impossible to derive from a rounded net price, "
"but the system will prefer rounding them down instead of up."
"possible. Gross prices may still change if they are impossible to derive from a rounded net price."
),
}
self.fields["tax_rounding"].choices = (

View File

@@ -134,12 +134,17 @@ def test_revert_net_keep_gross_rounding_to_single_line(sample_lines):
assert l.tax_rate == Decimal("19.00")
def test_rounding_of_impossible_gross_price():
@pytest.mark.django_db
@pytest.mark.parametrize("rounding_mode", [
"sum_by_net",
"sum_by_net_keep_gross",
])
def test_rounding_of_impossible_gross_price(rounding_mode):
l = OrderPosition(
price=Decimal("23.00"),
)
l._calculate_tax(tax_rule=TaxRule(rate=Decimal("7.00")), invoice_address=InvoiceAddress())
apply_rounding("sum_by_net", "EUR", [l])
apply_rounding(rounding_mode, "EUR", [l])
assert l.price == Decimal("23.01")
assert l.price_includes_rounding_correction == Decimal("0.01")
assert l.tax_value == Decimal("1.51")
@@ -147,19 +152,6 @@ def test_rounding_of_impossible_gross_price():
assert l.tax_rate == Decimal("7.00")
def test_rounding_of_impossible_gross_price_keep_gross():
l = OrderPosition(
price=Decimal("23.00"),
)
l._calculate_tax(tax_rule=TaxRule(rate=Decimal("7.00")), invoice_address=InvoiceAddress())
apply_rounding("sum_by_net_keep_gross", "EUR", [l])
assert l.price == Decimal("22.99")
assert l.price_includes_rounding_correction == Decimal("-0.01")
assert l.tax_value == Decimal("1.50")
assert l.tax_value_includes_rounding_correction == Decimal("0.00")
assert l.tax_rate == Decimal("7.00")
@pytest.mark.django_db
def test_round_down():
lines = [OrderPosition(
@@ -244,8 +236,10 @@ def test_do_not_touch_free(rounding_mode):
)
l2._calculate_tax(tax_rule=TaxRule(rate=Decimal("7.00")), invoice_address=InvoiceAddress())
apply_rounding(rounding_mode, "EUR", [l1, l2])
assert l2.price == Decimal("23.01") or l2.price == Decimal("22.99")
assert l2.price_includes_rounding_correction != Decimal("0.00") or l2.tax_value_includes_rounding_correction != Decimal("0.00")
assert l2.price == Decimal("23.01")
assert l2.price_includes_rounding_correction == Decimal("0.01")
assert l2.tax_value == Decimal("1.51")
assert l2.tax_value_includes_rounding_correction == Decimal("0.01")
assert l2.tax_rate == Decimal("7.00")
assert l1.price == Decimal("0.00")
assert l1.price_includes_rounding_correction == Decimal("0.00")