Refs #145 -- Vouchers that grant discounts

This commit is contained in:
Raphael Michel
2016-11-29 15:04:49 +01:00
parent fdbe71ff63
commit eea6a5e9da
14 changed files with 234 additions and 74 deletions

View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.3 on 2016-11-29 13:30
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0047_auto_20161126_1300'),
]
operations = [
migrations.AddField(
model_name='voucher',
name='price_mode',
field=models.CharField(choices=[('none', 'No effect'), ('set', 'Set product price to'), ('subtract', 'Subtract from product price'), ('percent', 'Reduce product price by (%)')], default='set', max_length=100, verbose_name='Price mode'),
),
migrations.RenameField(
model_name='voucher',
old_name='price',
new_name='value',
),
migrations.AlterField(
model_name='voucher',
name='value',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Voucher value'),
),
]

View File

@@ -1,3 +1,5 @@
from decimal import Decimal
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
@@ -5,6 +7,7 @@ from django.utils.crypto import get_random_string
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from ..decimal import round_decimal
from .base import LoggedModel
from .event import Event
from .items import Item, ItemVariation, Quota
@@ -59,6 +62,13 @@ class Voucher(LoggedModel):
* You need to either select a quota or an item
* If you select an item that has variations but do not select a variation, you cannot set block_quota
"""
PRICE_MODES = (
('none', _('No effect')),
('set', _('Set product price to')),
('subtract', _('Subtract from product price')),
('percent', _('Reduce product price by (%)')),
)
event = models.ForeignKey(
Event,
on_delete=models.CASCADE,
@@ -98,10 +108,15 @@ class Voucher(LoggedModel):
"If activated, a holder of this voucher code can buy tickets, even if there are none left."
)
)
price = models.DecimalField(
verbose_name=_("Set product price to"),
price_mode = models.CharField(
verbose_name=_("Price mode"),
max_length=100,
choices=PRICE_MODES,
default='set'
)
value = models.DecimalField(
verbose_name=_("Voucher value"),
decimal_places=2, max_digits=10, null=True, blank=True,
help_text=_('If empty, the product will cost its normal price.')
)
item = models.ForeignKey(
Item, related_name='vouchers',
@@ -208,3 +223,19 @@ class Voucher(LoggedModel):
if self.valid_until and self.valid_until < now():
return False
return True
def calculate_price(self, original_price: Decimal) -> Decimal:
"""
Returns how the price given in original_price would be modified if this
voucher is applied, i.e. replaced by a different price or reduced by a
certain percentage. If the voucher does not modify the price, the
original price will be returned.
"""
if self.value:
if self.price_mode == 'set':
return self.value
elif self.price_mode == 'subtract':
return original_price - self.value
elif self.price_mode == 'percent':
return round_decimal(original_price * (Decimal('100.00') - self.value) / Decimal('100.00'))
return original_price

View File

@@ -168,11 +168,10 @@ def _add_new_items(event: Event, items: List[dict],
err = err or error_messages['in_part']
quota_ok = min(quota_ok, avail[1])
if voucher and voucher.price is not None:
price = voucher.price
else:
price = item.default_price if variation is None else (
variation.default_price if variation.default_price is not None else item.default_price)
price = item.default_price if variation is None else (
variation.default_price if variation.default_price is not None else item.default_price)
if voucher:
price = voucher.calculate_price(price)
if item.free_price and 'price' in i and i['price'] is not None and i['price'] != "":
custom_price = i['price']

View File

@@ -233,8 +233,7 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
err = err or error_messages['voucher_expired']
cp.delete()
continue
if cp.voucher.price is not None:
price = cp.voucher.price
price = cp.voucher.calculate_price(price)
if price != cp.price and not (cp.item.free_price and cp.price > price):
positions[i] = cp

View File

@@ -22,8 +22,8 @@ class VoucherForm(I18nModelForm):
model = Voucher
localized_fields = '__all__'
fields = [
'code', 'valid_until', 'block_quota', 'allow_ignore_quota', 'price', 'tag',
'comment', 'max_usages'
'code', 'valid_until', 'block_quota', 'allow_ignore_quota', 'value', 'tag',
'comment', 'max_usages', 'price_mode'
]
widgets = {
'valid_until': forms.DateTimeInput(attrs={'class': 'datetimepicker'}),
@@ -187,8 +187,8 @@ class VoucherBulkForm(VoucherForm):
model = Voucher
localized_fields = '__all__'
fields = [
'valid_until', 'block_quota', 'allow_ignore_quota', 'price', 'tag', 'comment',
'max_usages'
'valid_until', 'block_quota', 'allow_ignore_quota', 'value', 'tag', 'comment',
'max_usages', 'price_mode'
]
widgets = {
'valid_until': forms.DateTimeInput(attrs={'class': 'datetimepicker'}),

View File

@@ -33,7 +33,15 @@
{% bootstrap_field form.valid_until layout="horizontal" %}
{% bootstrap_field form.block_quota layout="horizontal" %}
{% bootstrap_field form.allow_ignore_quota layout="horizontal" %}
{% bootstrap_field form.price layout="horizontal" %}
<div class="form-group">
<label class="col-md-3 control-label" for="id_tag">{% trans "Price effect" %}</label>
<div class="col-md-5">
{% bootstrap_field form.price_mode show_label=False form_group_class="" %}
</div>
<div class="col-md-4">
{% bootstrap_field form.value show_label=False form_group_class="" %}
</div>
</div>
{% bootstrap_field form.itemvar layout="horizontal" %}
<div class="form-group">
<div class="col-md-9 col-md-offset-3">

View File

@@ -27,7 +27,15 @@
{% bootstrap_field form.valid_until layout="horizontal" %}
{% bootstrap_field form.block_quota layout="horizontal" %}
{% bootstrap_field form.allow_ignore_quota layout="horizontal" %}
{% bootstrap_field form.price layout="horizontal" %}
<div class="form-group">
<label class="col-md-3 control-label" for="id_tag">{% trans "Price effect" %}</label>
<div class="col-md-5">
{% bootstrap_field form.price_mode show_label=False form_group_class="" %}
</div>
<div class="col-md-4">
{% bootstrap_field form.value show_label=False form_group_class="" %}
</div>
</div>
{% bootstrap_field form.itemvar layout="horizontal" %}
<div class="form-group">
<div class="col-md-9 col-md-offset-3">

View File

@@ -59,7 +59,7 @@ class VoucherList(EventPermissionRequiredMixin, ListView):
headers = [
_('Voucher code'), _('Valid until'), _('Product'), _('Reserve quota'), _('Bypass quota'),
_('Price'), _('Tag'), _('Redeemed'), _('Maximum usages')
_('Price effect'), _('Value'), _('Tag'), _('Redeemed'), _('Maximum usages')
]
writer.writerow(headers)
@@ -77,7 +77,8 @@ class VoucherList(EventPermissionRequiredMixin, ListView):
prod,
_("Yes") if v.block_quota else _("No"),
_("Yes") if v.allow_ignore_quota else _("No"),
str(v.price) if v.price else "",
v.get_price_mode_display(),
str(v.value) if v.value else "",
v.tag,
str(v.redeemed),
str(v.max_usages)

View File

@@ -198,20 +198,16 @@ class RedeemView(EventViewMixin, TemplateView):
item.cached_availability = (Quota.AVAILABILITY_OK, 1)
else:
item.cached_availability = item.check_quotas()
if self.voucher.price is not None:
item.price = self.voucher.price
else:
item.price = item.default_price
item.price = self.voucher.calculate_price(item.default_price)
else:
for var in item.available_variations:
if self.voucher.allow_ignore_quota or self.voucher.block_quota:
var.cached_availability = (Quota.AVAILABILITY_OK, 1)
else:
var.cached_availability = list(var.check_quotas())
if self.voucher.price is not None:
var.price = self.voucher.price
else:
var.price = var.default_price if var.default_price is not None else item.default_price
var.price = self.voucher.calculate_price(
var.default_price if var.default_price is not None else item.default_price
)
if len(item.available_variations) > 0:
item.min_price = min([v.price for v in item.available_variations])

View File

@@ -1,6 +1,7 @@
import datetime
import sys
from datetime import timedelta
from decimal import Decimal
import pytest
from django.conf import settings
@@ -406,6 +407,29 @@ class QuotaTestCase(BaseQuotaTestCase):
v.clean()
class VoucherTestCase(BaseQuotaTestCase):
def test_calculate_price_none(self):
v = Voucher.objects.create(event=self.event, price_mode='none', value=Decimal('10.00'))
v.calculate_price(Decimal('23.42')) == Decimal('23.42')
def test_calculate_price_set_empty(self):
v = Voucher.objects.create(event=self.event, price_mode='set')
v.calculate_price(Decimal('23.42')) == Decimal('23.42')
def test_calculate_price_set(self):
v = Voucher.objects.create(event=self.event, price_mode='set', value=Decimal('10.00'))
v.calculate_price(Decimal('23.42')) == Decimal('10.00')
def test_calculate_price_subtract(self):
v = Voucher.objects.create(event=self.event, price_mode='subtract', value=Decimal('10.00'))
v.calculate_price(Decimal('23.42')) == Decimal('13.42')
def test_calculate_price_percent(self):
v = Voucher.objects.create(event=self.event, price_mode='percent', value=Decimal('23.00'))
v.calculate_price(Decimal('100.00')) == Decimal('77.00')
class OrderTestCase(BaseQuotaTestCase):
def setUp(self):
super().setUp()

View File

@@ -80,9 +80,9 @@ class VoucherFormTest(SoupTest):
def test_csv(self):
self.event.vouchers.create(item=self.ticket, code='ABCDEFG')
doc = self.client.get('/control/event/%s/%s/vouchers/?download=yes' % (self.orga.slug, self.event.slug))
assert doc.content.strip() == '"Voucher code","Valid until","Product","Reserve quota","Bypass quota","Price",' \
'"Tag","Redeemed","Maximum usages"\r\n"ABCDEFG","","Early-bird ticket","No",' \
'"No","","","0","1"'.encode('utf-8')
assert doc.content.strip() == '"Voucher code","Valid until","Product","Reserve quota","Bypass quota",' \
'"Price effect","Value","Tag","Redeemed","Maximum usages"\r\n"ABCDEFG","",' \
'"Early-bird ticket","No","No","Set product price to","","","0","1"'.encode('utf-8')
def test_filter_status_valid(self):
v = self.event.vouchers.create(item=self.ticket)

View File

@@ -488,7 +488,7 @@ class CartTest(CartTestMixin, TestCase):
event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_red,
price=14, expires=now() + timedelta(minutes=10)
)
v = Voucher.objects.create(item=self.shirt, variation=self.shirt_red, price=Decimal('10.00'), event=self.event)
v = Voucher.objects.create(item=self.shirt, variation=self.shirt_red, value=Decimal('10.00'), 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)
@@ -594,7 +594,7 @@ class CartTest(CartTestMixin, TestCase):
self.assertEqual(len(objs), 0)
def test_voucher_price(self):
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event)
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event)
self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
'_voucher_code': v.code,
@@ -605,8 +605,76 @@ class CartTest(CartTestMixin, TestCase):
self.assertIsNone(objs[0].variation)
self.assertEqual(objs[0].price, Decimal('12.00'))
def test_voucher_price_percent(self):
v = Voucher.objects.create(item=self.ticket, value=Decimal('10.00'), price_mode='percent', event=self.event)
self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
'_voucher_code': v.code,
}, follow=True)
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('20.70'))
def test_voucher_price_subtract(self):
v = Voucher.objects.create(item=self.ticket, value=Decimal('10.00'), price_mode='subtract', event=self.event)
self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
'_voucher_code': v.code,
}, follow=True)
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('13.00'))
def test_voucher_free_price(self):
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: '21.00',
'_voucher_code': v.code,
}, follow=True)
self.assertRedirects(response, '/%s/%s/' % (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('21', doc.select('.cart .cart-row')[0].select('.price')[0].text)
self.assertIn('21', doc.select('.cart .cart-row')[0].select('.price')[1].text)
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('21.00'))
def test_voucher_free_price_lower_bound(self):
v = Voucher.objects.create(item=self.ticket, value=Decimal('10.00'), price_mode='percent', event=self.event)
self.ticket.free_price = False
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: '20.00',
'_voucher_code': v.code,
}, follow=True)
self.assertRedirects(response, '/%s/%s/' % (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('20.70', doc.select('.cart .cart-row')[0].select('.price')[0].text)
self.assertIn('20.70', doc.select('.cart .cart-row')[0].select('.price')[1].text)
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('20.70'))
def test_voucher_redemed(self):
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event, redeemed=1)
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, redeemed=1)
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
'_voucher_code': v.code,
@@ -616,7 +684,7 @@ class CartTest(CartTestMixin, TestCase):
self.assertFalse(CartPosition.objects.filter(cart_id=self.session_key, event=self.event).exists())
def test_voucher_expired(self):
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event,
valid_until=now() - timedelta(days=2))
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
@@ -638,7 +706,7 @@ class CartTest(CartTestMixin, TestCase):
def test_voucher_quota_empty(self):
self.quota_tickets.size = 0
self.quota_tickets.save()
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event)
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event)
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
'_voucher_code': v.code,
@@ -650,7 +718,7 @@ class CartTest(CartTestMixin, TestCase):
def test_voucher_quota_ignore(self):
self.quota_tickets.size = 0
self.quota_tickets.save()
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event,
allow_ignore_quota=True)
self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
@@ -665,7 +733,7 @@ class CartTest(CartTestMixin, TestCase):
def test_voucher_quota_block(self):
self.quota_tickets.size = 1
self.quota_tickets.save()
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event,
block_quota=True)
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
@@ -684,7 +752,7 @@ class CartTest(CartTestMixin, TestCase):
self.assertEqual(objs[0].price, Decimal('12.00'))
def test_voucher_doubled(self):
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event)
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event)
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
'_voucher_code': v.code,
@@ -758,7 +826,7 @@ class CartTest(CartTestMixin, TestCase):
self.assertEqual(len(objs), 0)
def test_voucher_multiuse_ok(self):
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event,
max_usages=2, redeemed=0)
self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '2',
@@ -769,7 +837,7 @@ class CartTest(CartTestMixin, TestCase):
assert all(cp.voucher == v for cp in positions)
def test_voucher_multiuse_multiprod_ok(self):
v = Voucher.objects.create(quota=self.quota_all, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(quota=self.quota_all, value=Decimal('12.00'), event=self.event,
max_usages=2, redeemed=0)
self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
@@ -781,7 +849,7 @@ class CartTest(CartTestMixin, TestCase):
assert all(cp.voucher == v for cp in positions)
def test_voucher_multiuse_partially(self):
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event,
max_usages=2, redeemed=1)
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '2',
@@ -793,7 +861,7 @@ class CartTest(CartTestMixin, TestCase):
assert not positions.exists()
def test_voucher_multiuse_multiprod_partially(self):
v = Voucher.objects.create(quota=self.quota_all, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(quota=self.quota_all, value=Decimal('12.00'), event=self.event,
max_usages=2, redeemed=1)
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
@@ -807,7 +875,7 @@ class CartTest(CartTestMixin, TestCase):
assert all(cp.voucher == v for cp in positions)
def test_voucher_multiuse_redeemed(self):
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event,
max_usages=2, redeemed=2)
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '2',
@@ -819,7 +887,7 @@ class CartTest(CartTestMixin, TestCase):
assert not positions.exists()
def test_voucher_multiuse_multiprod_redeemed(self):
v = Voucher.objects.create(quota=self.quota_all, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(quota=self.quota_all, value=Decimal('12.00'), event=self.event,
max_usages=2, redeemed=2)
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
@@ -832,7 +900,7 @@ class CartTest(CartTestMixin, TestCase):
assert not positions.exists()
def test_voucher_multiuse_redeemed_in_my_cart(self):
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event,
max_usages=2, redeemed=1)
CartPosition.objects.create(
expires=now() - timedelta(minutes=10), item=self.ticket, voucher=v, price=Decimal('12.00'),
@@ -848,7 +916,7 @@ class CartTest(CartTestMixin, TestCase):
assert positions.count() == 1
def test_voucher_multiuse_redeemed_in_other_cart(self):
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event,
max_usages=2, redeemed=1)
CartPosition.objects.create(
expires=now() + timedelta(minutes=10), item=self.ticket, voucher=v, price=Decimal('12.00'),
@@ -864,7 +932,7 @@ class CartTest(CartTestMixin, TestCase):
assert not positions.exists()
def test_voucher_multiuse_redeemed_in_other_expired_cart(self):
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event,
max_usages=2, redeemed=1)
CartPosition.objects.create(
expires=now() - timedelta(minutes=10), item=self.ticket, voucher=v, price=Decimal('12.00'),

View File

@@ -294,7 +294,7 @@ class CheckoutTestCase(TestCase):
self.assertEqual(cr1.price, 24)
def test_voucher(self):
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event,
valid_until=now() + timedelta(days=2))
cr1 = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
@@ -312,7 +312,7 @@ class CheckoutTestCase(TestCase):
self.assertEqual(Voucher.objects.get(pk=v.pk).redeemed, 1)
def test_voucher_required(self):
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event,
valid_until=now() + timedelta(days=2))
self.ticket.require_voucher = True
self.ticket.save()
@@ -341,7 +341,7 @@ class CheckoutTestCase(TestCase):
assert doc.select(".alert-danger")
def test_voucher_price_changed(self):
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event,
valid_until=now() + timedelta(days=2))
cr1 = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
@@ -355,20 +355,8 @@ class CheckoutTestCase(TestCase):
cr1 = CartPosition.objects.get(id=cr1.id)
self.assertEqual(cr1.price, Decimal('12.00'))
def test_voucher_expired(self):
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
valid_until=now() - timedelta(days=2))
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, "lxml")
self.assertIn("expired", doc.select(".alert-danger")[0].text)
def test_voucher_redeemed(self):
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event,
valid_until=now() + timedelta(days=2), redeemed=1)
CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
@@ -380,7 +368,7 @@ class CheckoutTestCase(TestCase):
self.assertIn("has already been", doc.select(".alert-danger")[0].text)
def test_voucher_multiuse_redeemed(self):
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event,
valid_until=now() + timedelta(days=2), max_usages=3, redeemed=3)
CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
@@ -392,7 +380,7 @@ class CheckoutTestCase(TestCase):
self.assertIn("has already been", doc.select(".alert-danger")[0].text)
def test_voucher_multiuse_partially(self):
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event,
valid_until=now() + timedelta(days=2), max_usages=3, redeemed=2)
CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
@@ -409,7 +397,7 @@ class CheckoutTestCase(TestCase):
assert CartPosition.objects.filter(cart_id=self.session_key).count() == 1
def test_voucher_multiuse_ok(self):
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event,
valid_until=now() + timedelta(days=2), max_usages=3, redeemed=1)
CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
@@ -430,7 +418,7 @@ class CheckoutTestCase(TestCase):
assert v.redeemed == 3
def test_voucher_multiuse_in_other_cart_expired(self):
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event,
valid_until=now() + timedelta(days=2), max_usages=3, redeemed=1)
CartPosition.objects.create(
event=self.event, cart_id='other', item=self.ticket,
@@ -455,7 +443,7 @@ class CheckoutTestCase(TestCase):
assert v.redeemed == 3
def test_voucher_multiuse_in_other_cart(self):
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event,
valid_until=now() + timedelta(days=2), max_usages=3, redeemed=1)
CartPosition.objects.create(
event=self.event, cart_id='other', item=self.ticket,
@@ -478,7 +466,7 @@ class CheckoutTestCase(TestCase):
def test_voucher_ignore_quota(self):
self.quota_tickets.size = 0
self.quota_tickets.save()
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event,
valid_until=now() + timedelta(days=2), allow_ignore_quota=True)
cr1 = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
@@ -496,7 +484,7 @@ class CheckoutTestCase(TestCase):
def test_voucher_block_quota(self):
self.quota_tickets.size = 1
self.quota_tickets.save()
v = Voucher.objects.create(item=self.ticket, price=Decimal('12.00'), event=self.event,
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event,
valid_until=now() + timedelta(days=2), block_quota=True)
cr1 = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
@@ -523,7 +511,7 @@ class CheckoutTestCase(TestCase):
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,
v = Voucher.objects.create(quota=self.quota_tickets, value=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,

View File

@@ -285,19 +285,27 @@ class VoucherRedeemItemDisplayTest(EventTestMixin, SoupTest):
html = self.client.get('/%s/%s/redeem?voucher=%s' % (self.orga.slug, self.event.slug, self.v.code))
assert "_voucher_item" in html.rendered_content
def test_special_price(self):
self.v.price = Decimal("10.00")
def test_voucher_price(self):
self.v.value = Decimal("10.00")
self.v.save()
html = self.client.get('/%s/%s/redeem?voucher=%s' % (self.orga.slug, self.event.slug, self.v.code))
assert "Early-bird" in html.rendered_content
assert "10.00" in html.rendered_content
def test_special_price_variations(self):
def test_voucher_price_percentage(self):
self.v.value = Decimal("10.00")
self.v.price_mode = 'percent'
self.v.save()
html = self.client.get('/%s/%s/redeem?voucher=%s' % (self.orga.slug, self.event.slug, self.v.code))
assert "Early-bird" in html.rendered_content
assert "10.80" in html.rendered_content
def test_voucher_price_variations(self):
var1 = ItemVariation.objects.create(item=self.item, value='Red', default_price=14, position=1)
var2 = ItemVariation.objects.create(item=self.item, value='Black', position=2)
self.q.variations.add(var1)
self.q.variations.add(var2)
self.v.price = Decimal("10.00")
self.v.value = Decimal("10.00")
self.v.save()
html = self.client.get('/%s/%s/redeem?voucher=%s' % (self.orga.slug, self.event.slug, self.v.code))
assert "Early-bird" in html.rendered_content