mirror of
https://github.com/pretix/pretix.git
synced 2026-05-04 15:04:03 +00:00
Resolved two more edge cases in quota handling
Scenario 1) Blocking voucher is used in a CartPosition. Previously too much was subtracted from the quota. Scenario 2) When two quotas are assigned to a product and one of them is sold out, blocking vouchers for the other quota should not enable to buy the product.
This commit is contained in:
@@ -554,6 +554,10 @@ class Quota(LoggedModel):
|
||||
|
||||
return CartPosition.objects.filter(
|
||||
Q(expires__gte=now()) &
|
||||
~Q(
|
||||
Q(voucher__isnull=False) & Q(voucher__block_quota=True)
|
||||
& Q(Q(voucher__valid_until__isnull=True) | Q(voucher__valid_until__gte=now()))
|
||||
) &
|
||||
self._position_lookup
|
||||
).distinct().count()
|
||||
|
||||
|
||||
@@ -222,8 +222,13 @@ def _check_positions(event: Event, dt: datetime, positions: List[CartPosition]):
|
||||
|
||||
quota_ok = True
|
||||
|
||||
if not cp.voucher or not (cp.voucher.allow_ignore_quota or cp.voucher.block_quota):
|
||||
ignore_all_quotas = cp.expires >= dt or (
|
||||
cp.voucher and (cp.voucher.allow_ignore_quota or (cp.voucher.block_quota and cp.voucher.quota is None)))
|
||||
|
||||
if not ignore_all_quotas:
|
||||
for quota in quotas:
|
||||
if cp.voucher and cp.voucher.block_quota and cp.voucher.quota_id == quota.pk:
|
||||
continue
|
||||
avail = quota.availability()
|
||||
if avail[0] != Quota.AVAILABILITY_OK:
|
||||
# This quota is sold out/currently unavailable, so do not sell this at all
|
||||
|
||||
@@ -240,6 +240,44 @@ class QuotaTestCase(BaseQuotaTestCase):
|
||||
block_quota=True)
|
||||
self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
|
||||
|
||||
def test_blocking_voucher_in_cart(self):
|
||||
self.quota.items.add(self.item1)
|
||||
v = Voucher.objects.create(quota=self.quota, event=self.event, valid_until=now() + timedelta(days=5),
|
||||
block_quota=True)
|
||||
CartPosition.objects.create(event=self.event, item=self.item1, price=2,
|
||||
expires=now() + timedelta(days=3), voucher=v)
|
||||
self.assertEqual(self.quota.count_blocking_vouchers(), 1)
|
||||
self.assertEqual(self.quota.count_in_cart(), 0)
|
||||
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
|
||||
|
||||
def test_blocking_voucher_in_cart_inifinitely_valid(self):
|
||||
self.quota.items.add(self.item1)
|
||||
v = Voucher.objects.create(quota=self.quota, event=self.event, block_quota=True)
|
||||
CartPosition.objects.create(event=self.event, item=self.item1, price=2,
|
||||
expires=now() + timedelta(days=3), voucher=v)
|
||||
self.assertEqual(self.quota.count_blocking_vouchers(), 1)
|
||||
self.assertEqual(self.quota.count_in_cart(), 0)
|
||||
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
|
||||
|
||||
def test_blocking_expired_voucher_in_cart(self):
|
||||
self.quota.items.add(self.item1)
|
||||
v = Voucher.objects.create(quota=self.quota, event=self.event, valid_until=now() - timedelta(days=5),
|
||||
block_quota=True)
|
||||
CartPosition.objects.create(event=self.event, item=self.item1, price=2,
|
||||
expires=now() + timedelta(days=3), voucher=v)
|
||||
self.assertEqual(self.quota.count_blocking_vouchers(), 0)
|
||||
self.assertEqual(self.quota.count_in_cart(), 1)
|
||||
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
|
||||
|
||||
def test_nonblocking_voucher_in_cart(self):
|
||||
self.quota.items.add(self.item1)
|
||||
v = Voucher.objects.create(quota=self.quota, event=self.event)
|
||||
CartPosition.objects.create(event=self.event, item=self.item1, price=2,
|
||||
expires=now() + timedelta(days=3), voucher=v)
|
||||
self.assertEqual(self.quota.count_blocking_vouchers(), 0)
|
||||
self.assertEqual(self.quota.count_in_cart(), 1)
|
||||
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
|
||||
|
||||
|
||||
class OrderTestCase(BaseQuotaTestCase):
|
||||
def setUp(self):
|
||||
|
||||
@@ -600,6 +600,15 @@ class CartTest(CartTestMixin, TestCase):
|
||||
objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event))
|
||||
self.assertEqual(len(objs), 0)
|
||||
|
||||
def test_voucher_quota_other_quota_full(self):
|
||||
quota2 = self.event.quotas.create(name='Test', size=0)
|
||||
quota2.variations.add(self.shirt_red)
|
||||
v = Voucher.objects.create(quota=self.quota_shirts, event=self.event)
|
||||
self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
|
||||
'variation_%d_%d_voucher' % (self.shirt.id, self.shirt_red.id): v.code,
|
||||
}, follow=True)
|
||||
self.assertEqual(CartPosition.objects.filter(cart_id=self.session_key, event=self.event).count(), 0)
|
||||
|
||||
def test_hide_without_voucher(self):
|
||||
v = Voucher.objects.create(item=self.shirt, event=self.event)
|
||||
self.shirt.hide_without_voucher = True
|
||||
|
||||
@@ -409,6 +409,24 @@ class CheckoutTestCase(TestCase):
|
||||
self.assertEqual(Order.objects.count(), 1)
|
||||
self.assertEqual(OrderPosition.objects.count(), 1)
|
||||
|
||||
def test_voucher_block_quota_other_quota_full(self):
|
||||
self.quota_tickets.size = 0
|
||||
self.quota_tickets.save()
|
||||
q2 = self.event.quotas.create(name='Testquota', size=0)
|
||||
q2.items.add(self.ticket)
|
||||
v = Voucher.objects.create(quota=self.quota_tickets, price=Decimal('12.00'), event=self.event,
|
||||
valid_until=now() - timedelta(days=2), block_quota=True)
|
||||
CartPosition.objects.create(
|
||||
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||
price=12, expires=now() + timedelta(minutes=10), voucher=v
|
||||
)
|
||||
self._set_session('payment', 'banktransfer')
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
doc = BeautifulSoup(response.rendered_content)
|
||||
self.assertEqual(len(doc.select(".alert-danger")), 1)
|
||||
self.assertFalse(Order.objects.exists())
|
||||
|
||||
def test_voucher_double(self):
|
||||
self.quota_tickets.size = 2
|
||||
self.quota_tickets.save()
|
||||
@@ -536,3 +554,60 @@ class CheckoutTestCase(TestCase):
|
||||
doc = BeautifulSoup(response.rendered_content)
|
||||
self.assertGreaterEqual(len(doc.select(".alert-danger")), 1)
|
||||
self.assertFalse(CartPosition.objects.filter(id=cr1.id).exists())
|
||||
|
||||
def test_confirm_expired_with_blocking_voucher_unavailable(self):
|
||||
self.quota_tickets.size = 0
|
||||
self.quota_tickets.save()
|
||||
v = Voucher.objects.create(quota=self.quota_tickets, event=self.event, block_quota=True)
|
||||
CartPosition.objects.create(
|
||||
event=self.event, cart_id=self.session_key, item=self.ticket, voucher=v,
|
||||
price=23, expires=now() - timedelta(minutes=10)
|
||||
)
|
||||
self._set_session('payment', 'banktransfer')
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
doc = BeautifulSoup(response.rendered_content)
|
||||
self.assertEqual(len(doc.select(".thank-you")), 1)
|
||||
|
||||
def test_confirm_expired_with_non_blocking_voucher_unavailable(self):
|
||||
self.quota_tickets.size = 0
|
||||
self.quota_tickets.save()
|
||||
v = Voucher.objects.create(quota=self.quota_tickets, event=self.event)
|
||||
cr1 = CartPosition.objects.create(
|
||||
event=self.event, cart_id=self.session_key, item=self.ticket, voucher=v,
|
||||
price=23, expires=now() - timedelta(minutes=10)
|
||||
)
|
||||
self._set_session('payment', 'banktransfer')
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
doc = BeautifulSoup(response.rendered_content)
|
||||
self.assertGreaterEqual(len(doc.select(".alert-danger")), 1)
|
||||
self.assertFalse(CartPosition.objects.filter(id=cr1.id).exists())
|
||||
|
||||
def test_confirm_not_expired_with_blocking_voucher_unavailable(self):
|
||||
self.quota_tickets.size = 0
|
||||
self.quota_tickets.save()
|
||||
v = Voucher.objects.create(quota=self.quota_tickets, event=self.event, block_quota=True)
|
||||
CartPosition.objects.create(
|
||||
event=self.event, cart_id=self.session_key, item=self.ticket, voucher=v,
|
||||
price=23, expires=now() + timedelta(minutes=10)
|
||||
)
|
||||
self._set_session('payment', 'banktransfer')
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
doc = BeautifulSoup(response.rendered_content)
|
||||
self.assertEqual(len(doc.select(".thank-you")), 1)
|
||||
|
||||
def test_confirm_not_expired_with_non_blocking_voucher_unavailable(self):
|
||||
self.quota_tickets.size = 0
|
||||
self.quota_tickets.save()
|
||||
v = Voucher.objects.create(quota=self.quota_tickets, event=self.event)
|
||||
CartPosition.objects.create(
|
||||
event=self.event, cart_id=self.session_key, item=self.ticket, voucher=v,
|
||||
price=23, expires=now() + timedelta(minutes=10)
|
||||
)
|
||||
self._set_session('payment', 'banktransfer')
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
doc = BeautifulSoup(response.rendered_content)
|
||||
self.assertEqual(len(doc.select(".thank-you")), 1)
|
||||
|
||||
Reference in New Issue
Block a user