mirror of
https://github.com/pretix/pretix.git
synced 2026-08-13 11:17:01 +00:00
Discounts: Fix edge case in computation (#4126)
* Add new test case for discounts: Two discounts: - "For every 1 item1, you get three item2 for 10 % off." - "For every 1 item1, you get five item3 for 10 % off." Cart: 2x item1, 2x item2, 6x item3 Expected result: 2x item1 full price, 2x item2 discounted, 5x item3 discounted, 1x item3 full price * Fix discount calculation bug * Update src/pretix/base/models/discount.py Co-authored-by: Raphael Michel <michel@rami.io> * Update src/pretix/base/models/discount.py Co-authored-by: Richard Schreiber <schreiber@rami.io> --------- Co-authored-by: Raphael Michel <michel@rami.io> Co-authored-by: Richard Schreiber <schreiber@rami.io>
This commit is contained in:
co-authored by
Raphael Michel
Richard Schreiber
parent
d2b96b2425
commit
541b8f5bd6
@@ -23,6 +23,7 @@
|
||||
from collections import defaultdict
|
||||
from decimal import Decimal
|
||||
from itertools import groupby
|
||||
from math import ceil
|
||||
from typing import Dict, Optional, Tuple
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
@@ -272,7 +273,7 @@ class Discount(LoggedModel):
|
||||
|
||||
# Prevent over-consuming of items, i.e. if our discount is "buy 2, get 1 free", we only
|
||||
# want to match multiples of 3
|
||||
n_groups = min(len(condition_idx_group) // self.condition_min_count, len(benefit_idx_group))
|
||||
n_groups = min(len(condition_idx_group) // self.condition_min_count, ceil(len(benefit_idx_group) / self.benefit_only_apply_to_cheapest_n_matches))
|
||||
consume_idx = condition_idx_group[:n_groups * self.condition_min_count]
|
||||
benefit_idx = benefit_idx_group[:n_groups * self.benefit_only_apply_to_cheapest_n_matches]
|
||||
else:
|
||||
|
||||
@@ -51,6 +51,11 @@ def item2(event):
|
||||
return event.items.create(name='Ticket II', default_price=Decimal('50.00'))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def item3(event):
|
||||
return event.items.create(name='Ticket III', default_price=Decimal('42.00'))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def voucher(event):
|
||||
return event.vouchers.create()
|
||||
@@ -1338,3 +1343,66 @@ def test_multiple_discounts_with_benefit_condition_overlap(event, item, item2):
|
||||
|
||||
new_prices = [p for p, d in apply_discounts(event, 'web', positions)]
|
||||
assert sorted(new_prices) == sorted(expected)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@scopes_disabled()
|
||||
def test_multiple_discounts_with_same_condition(event, item, item2, item3):
|
||||
# "For every 1 item1, you get three item2 for 10 % off." + "For every 1 item1, you get five item3 for 10 % off."
|
||||
d1 = Discount(
|
||||
event=event,
|
||||
condition_min_count=1,
|
||||
condition_all_products=False,
|
||||
benefit_only_apply_to_cheapest_n_matches=3,
|
||||
benefit_discount_matching_percent=10,
|
||||
benefit_same_products=False,
|
||||
position=1,
|
||||
)
|
||||
d1.save()
|
||||
d1.condition_limit_products.add(item)
|
||||
d1.benefit_limit_products.add(item2)
|
||||
|
||||
d2 = Discount(
|
||||
event=event,
|
||||
condition_min_count=1,
|
||||
condition_all_products=False,
|
||||
benefit_only_apply_to_cheapest_n_matches=5,
|
||||
benefit_discount_matching_percent=10,
|
||||
benefit_same_products=False,
|
||||
position=2,
|
||||
)
|
||||
d2.save()
|
||||
d2.condition_limit_products.add(item)
|
||||
d2.benefit_limit_products.add(item3)
|
||||
|
||||
positions = (
|
||||
(item3.pk, None, Decimal('42.00'), False, False, Decimal('0.00')),
|
||||
(item3.pk, None, Decimal('42.00'), False, False, Decimal('0.00')),
|
||||
(item3.pk, None, Decimal('42.00'), False, False, Decimal('0.00')),
|
||||
(item3.pk, None, Decimal('42.00'), False, False, Decimal('0.00')),
|
||||
(item3.pk, None, Decimal('42.00'), False, False, Decimal('0.00')),
|
||||
(item3.pk, None, Decimal('42.00'), False, False, Decimal('0.00')),
|
||||
(item2.pk, None, Decimal('50.00'), False, False, Decimal('0.00')),
|
||||
(item2.pk, None, Decimal('50.00'), False, False, Decimal('0.00')),
|
||||
(item.pk, None, Decimal('23.00'), False, False, Decimal('0.00')),
|
||||
(item.pk, None, Decimal('23.00'), False, False, Decimal('0.00')),
|
||||
)
|
||||
expected = (
|
||||
# both item1 remain full price
|
||||
Decimal('23.00'),
|
||||
Decimal('23.00'),
|
||||
# 5 item3 discounted
|
||||
Decimal('37.80'),
|
||||
Decimal('37.80'),
|
||||
Decimal('37.80'),
|
||||
Decimal('37.80'),
|
||||
Decimal('37.80'),
|
||||
# 2 item2 discounted
|
||||
Decimal('45.00'),
|
||||
Decimal('45.00'),
|
||||
# 1 item3 remains untouched
|
||||
Decimal('42.00'),
|
||||
)
|
||||
|
||||
new_prices = [p for p, d in apply_discounts(event, 'web', positions)]
|
||||
assert sorted(new_prices) == sorted(expected)
|
||||
|
||||
Reference in New Issue
Block a user