Add a maximum budget to vouchers (#1526)

* Data model changes

* Fix test failures

* Adjustments

* Some tests and API support

* Check when extending orders

* Make things more deterministic, fix style

* Do not apply negative discounts

* Update price_before_voucher on item/subevent changes

* Add tests for price_before_voucher in combination with free price

* Fix InvoiceAddress.DoesNotExist
This commit is contained in:
Raphael Michel
2020-01-03 16:15:17 +01:00
committed by GitHub
parent b738e3bd9d
commit 8e2821b398
20 changed files with 649 additions and 23 deletions

View File

@@ -1388,6 +1388,32 @@ class CartTest(CartTestMixin, TestCase):
self.assertEqual(objs[0].item, self.ticket)
self.assertIsNone(objs[0].variation)
self.assertEqual(objs[0].price, Decimal('21.00'))
self.assertEqual(objs[0].price_before_voucher, Decimal('23.00'))
def test_voucher_free_price_before_voucher_cap(self):
with scopes_disabled():
v = Voucher.objects.create(item=self.ticket, value=Decimal('10.00'), price_mode='percent', event=self.event)
self.ticket.free_price = True
self.ticket.save()
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
'price_%d' % self.ticket.id: '41.00',
'_voucher_code': v.code,
}, follow=True)
self.assertRedirects(response, '/%s/%s/?require_cookie=true' % (self.orga.slug, self.event.slug),
target_status_code=200)
doc = BeautifulSoup(response.rendered_content, "lxml")
self.assertIn('Early-bird', doc.select('.cart .cart-row')[0].select('strong')[0].text)
self.assertIn('1', doc.select('.cart .cart-row')[0].select('.count')[0].text)
self.assertIn('41', doc.select('.cart .cart-row')[0].select('.price')[0].text)
self.assertIn('41', doc.select('.cart .cart-row')[0].select('.price')[1].text)
with scopes_disabled():
objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event))
self.assertEqual(len(objs), 1)
self.assertEqual(objs[0].item, self.ticket)
self.assertIsNone(objs[0].variation)
self.assertEqual(objs[0].price, Decimal('41.00'))
self.assertEqual(objs[0].price_before_voucher, Decimal('41.00'))
def test_voucher_free_price_lower_bound(self):
with scopes_disabled():
@@ -1412,6 +1438,7 @@ class CartTest(CartTestMixin, TestCase):
self.assertEqual(objs[0].item, self.ticket)
self.assertIsNone(objs[0].variation)
self.assertEqual(objs[0].price, Decimal('20.70'))
self.assertEqual(objs[0].price_before_voucher, Decimal('23.00'))
def test_voucher_redemed(self):
with scopes_disabled():
@@ -2438,6 +2465,20 @@ class CartAddonTest(CartTestMixin, TestCase):
assert cp2.expires > now()
assert cp2.addon_to_id == cp1.pk
@classscope(attr='orga')
def test_expand_expired_refresh_voucher(self):
v = Voucher.objects.create(item=self.ticket, value=Decimal('20.00'), event=self.event, price_mode='set',
valid_until=now() + timedelta(days=2), max_usages=999, redeemed=0)
cp1 = CartPosition.objects.create(
expires=now() - timedelta(minutes=10), item=self.ticket, price=Decimal('21.50'),
event=self.event, cart_id=self.session_key, voucher=v
)
self.cm.extend_expired_positions()
self.cm.commit()
cp1.refresh_from_db()
assert cp1.expires > now()
assert cp1.price_before_voucher == Decimal('23.00')
class CartBundleTest(CartTestMixin, TestCase):
@scopes_disabled()
@@ -2490,6 +2531,30 @@ class CartBundleTest(CartTestMixin, TestCase):
assert cp.price == 23 - 1.5
assert cp.addons.count() == 1
assert cp.voucher == v
assert cp.price_before_voucher == 23 - 1.5
a = cp.addons.get()
assert a.item == self.trans
assert a.price == 1.5
assert not a.voucher
@classscope(attr='orga')
def test_discounted_voucher_on_base_product(self):
v = self.event.vouchers.create(code="foo", item=self.ticket, price_mode='subtract', value=Decimal('1.50'))
self.cm.add_new_items([
{
'item': self.ticket.pk,
'variation': None,
'voucher': v.code,
'count': 1
}
])
self.cm.commit()
cp = CartPosition.objects.get(addon_to__isnull=True)
assert cp.item == self.ticket
assert cp.price == 23 - 1.5 - 1.5
assert cp.addons.count() == 1
assert cp.voucher == v
assert cp.price_before_voucher == 23 - 1.5
a = cp.addons.get()
assert a.item == self.trans
assert a.price == 1.5

View File

@@ -3000,3 +3000,140 @@ class CheckoutSeatingTest(BaseCheckoutTestCase, TestCase):
with self.assertRaises(OrderError):
_perform_order(self.event, 'manual', [self.cp1.pk], 'admin@example.org', 'en', None, {}, 'web')
assert not CartPosition.objects.filter(pk=self.cp1.pk).exists()
class CheckoutVoucherBudgetTest(BaseCheckoutTestCase, TestCase):
@scopes_disabled()
def setUp(self):
super().setUp()
self.v = Voucher.objects.create(item=self.ticket, value=Decimal('21.50'), event=self.event, price_mode='set',
valid_until=now() + timedelta(days=2), max_usages=999, redeemed=0)
self.cp1 = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price_before_voucher=23, price=21.5, expires=now() + timedelta(minutes=10), voucher=self.v
)
self.cp2 = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price_before_voucher=23, price=21.5, expires=now() + timedelta(minutes=10), voucher=self.v
)
@scopes_disabled()
def test_no_budget(self):
oid = _perform_order(self.event, 'manual', [self.cp1.pk, self.cp2.pk], 'admin@example.org', 'en', None, {},
'web')
o = Order.objects.get(pk=oid)
op = o.positions.first()
assert op.item == self.ticket
assert op.price_before_voucher == Decimal('23.00')
@scopes_disabled()
def test_budget_exceeded_for_second_order(self):
self.v.budget = Decimal('1.50')
self.v.save()
oid = _perform_order(self.event, 'manual', [self.cp1.pk], 'admin@example.org', 'en', None, {},
'web')
o = Order.objects.get(pk=oid)
op = o.positions.first()
assert op.item == self.ticket
with self.assertRaises(OrderError):
_perform_order(self.event, 'manual', [self.cp2.pk], 'admin@example.org', 'en', None, {},
'web')
self.cp2.refresh_from_db()
assert self.cp2.price == Decimal('23.00')
@scopes_disabled()
def test_budget_exceeded_between_positions(self):
self.v.budget = Decimal('1.50')
self.v.save()
with self.assertRaises(OrderError):
_perform_order(self.event, 'manual', [self.cp1.pk, self.cp2.pk], 'admin@example.org', 'en', None, {},
'web')
self.cp1.refresh_from_db()
assert self.cp1.price == Decimal('21.50')
self.cp2.refresh_from_db()
assert self.cp2.price == Decimal('23.00')
@scopes_disabled()
def test_budget_exceeded_in_first_position(self):
self.v.budget = Decimal('1.00')
self.v.save()
with self.assertRaises(OrderError):
_perform_order(self.event, 'manual', [self.cp1.pk, self.cp2.pk], 'admin@example.org', 'en', None, {},
'web')
self.cp1.refresh_from_db()
assert self.cp1.price == Decimal('22.00')
self.cp2.refresh_from_db()
assert self.cp2.price == Decimal('23.00')
@scopes_disabled()
def test_budget_exceeded_in_second_position(self):
self.v.budget = Decimal('2.50')
self.v.save()
with self.assertRaises(OrderError):
_perform_order(self.event, 'manual', [self.cp1.pk, self.cp2.pk], 'admin@example.org', 'en', None, {},
'web')
self.cp1.refresh_from_db()
assert self.cp1.price == Decimal('21.50')
self.cp2.refresh_from_db()
assert self.cp2.price == Decimal('22.00')
@scopes_disabled()
def test_budget_exceeded_during_price_change(self):
self.v.budget = Decimal('2.50')
self.v.value = Decimal('21.00')
self.v.save()
self.cp1.expires = now() - timedelta(hours=1)
self.cp1.save()
self.cp2.expires = now() - timedelta(hours=1)
self.cp2.save()
with self.assertRaises(OrderError):
_perform_order(self.event, 'manual', [self.cp1.pk, self.cp2.pk], 'admin@example.org', 'en', None, {},
'web')
self.cp1.refresh_from_db()
assert self.cp1.price == Decimal('21.00')
self.cp2.refresh_from_db()
assert self.cp2.price == Decimal('22.50')
@scopes_disabled()
def test_budget_exceeded_expired_cart(self):
self.v.budget = Decimal('0.00')
self.v.value = Decimal('21.00')
self.v.save()
self.cp1.expires = now() - timedelta(hours=1)
self.cp1.save()
self.cp2.expires = now() - timedelta(hours=1)
self.cp2.save()
with self.assertRaises(OrderError):
_perform_order(self.event, 'manual', [self.cp1.pk, self.cp2.pk], 'admin@example.org', 'en', None, {},
'web')
self.cp1.refresh_from_db()
assert self.cp1.price == Decimal('23.00')
self.cp2.refresh_from_db()
assert self.cp2.price == Decimal('23.00')
@scopes_disabled()
def test_budget_overbooked_expired_cart(self):
self.v.budget = Decimal('1.50')
self.v.value = Decimal('21.50')
self.v.save()
self.cp1.expires = now() - timedelta(hours=1)
self.cp1.save()
self.cp2.expires = now() - timedelta(hours=1)
self.cp2.save()
oid = _perform_order(self.event, 'manual', [self.cp1.pk], 'admin@example.org', 'en', None, {},
'web')
o = Order.objects.get(pk=oid)
op = o.positions.first()
assert op.item == self.ticket
self.v.budget = Decimal('1.00')
self.v.save()
with self.assertRaises(OrderError):
_perform_order(self.event, 'manual', [self.cp2.pk], 'admin@example.org', 'en', None, {},
'web')
self.cp2.refresh_from_db()
assert self.cp2.price == Decimal('23.00')