forked from CGM_Public/pretix_original
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(
|
return CartPosition.objects.filter(
|
||||||
Q(expires__gte=now()) &
|
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
|
self._position_lookup
|
||||||
).distinct().count()
|
).distinct().count()
|
||||||
|
|
||||||
|
|||||||
@@ -222,8 +222,13 @@ def _check_positions(event: Event, dt: datetime, positions: List[CartPosition]):
|
|||||||
|
|
||||||
quota_ok = True
|
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:
|
for quota in quotas:
|
||||||
|
if cp.voucher and cp.voucher.block_quota and cp.voucher.quota_id == quota.pk:
|
||||||
|
continue
|
||||||
avail = quota.availability()
|
avail = quota.availability()
|
||||||
if avail[0] != Quota.AVAILABILITY_OK:
|
if avail[0] != Quota.AVAILABILITY_OK:
|
||||||
# This quota is sold out/currently unavailable, so do not sell this at all
|
# This quota is sold out/currently unavailable, so do not sell this at all
|
||||||
|
|||||||
@@ -240,6 +240,44 @@ class QuotaTestCase(BaseQuotaTestCase):
|
|||||||
block_quota=True)
|
block_quota=True)
|
||||||
self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
|
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):
|
class OrderTestCase(BaseQuotaTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
@@ -600,6 +600,15 @@ class CartTest(CartTestMixin, TestCase):
|
|||||||
objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event))
|
objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event))
|
||||||
self.assertEqual(len(objs), 0)
|
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):
|
def test_hide_without_voucher(self):
|
||||||
v = Voucher.objects.create(item=self.shirt, event=self.event)
|
v = Voucher.objects.create(item=self.shirt, event=self.event)
|
||||||
self.shirt.hide_without_voucher = True
|
self.shirt.hide_without_voucher = True
|
||||||
|
|||||||
@@ -409,6 +409,24 @@ class CheckoutTestCase(TestCase):
|
|||||||
self.assertEqual(Order.objects.count(), 1)
|
self.assertEqual(Order.objects.count(), 1)
|
||||||
self.assertEqual(OrderPosition.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):
|
def test_voucher_double(self):
|
||||||
self.quota_tickets.size = 2
|
self.quota_tickets.size = 2
|
||||||
self.quota_tickets.save()
|
self.quota_tickets.save()
|
||||||
@@ -536,3 +554,60 @@ class CheckoutTestCase(TestCase):
|
|||||||
doc = BeautifulSoup(response.rendered_content)
|
doc = BeautifulSoup(response.rendered_content)
|
||||||
self.assertGreaterEqual(len(doc.select(".alert-danger")), 1)
|
self.assertGreaterEqual(len(doc.select(".alert-danger")), 1)
|
||||||
self.assertFalse(CartPosition.objects.filter(id=cr1.id).exists())
|
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