Refs #145 -- Multi-use vouchers

This commit is contained in:
Raphael Michel
2016-11-27 00:02:28 +01:00
parent 6c2ecd153c
commit db6fb51fc6
18 changed files with 470 additions and 104 deletions

View File

@@ -4,8 +4,9 @@ from datetime import datetime
from decimal import Decimal
from typing import Tuple
from django.conf import settings
from django.db import models
from django.db.models import Q
from django.db.models import F, Func, Q, Sum
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
@@ -577,12 +578,18 @@ class Quota(LoggedModel):
from pretix.base.models import Voucher
now_dt = now_dt or now()
if 'sqlite3' in settings.DATABASES['default']['ENGINE']:
func = 'MAX'
else:
func = 'GREATEST'
return Voucher.objects.filter(
Q(block_quota=True) &
Q(redeemed=False) &
Q(Q(valid_until__isnull=True) | Q(valid_until__gte=now_dt)) &
Q(Q(self._position_lookup) | Q(quota=self))
).values('id').distinct().count()
).values('id').aggregate(
free=Sum(Func(F('max_usages') - F('redeemed'), 0, function=func))
)['free'] or 0
def count_in_cart(self, now_dt: datetime=None) -> int:
from pretix.base.models import CartPosition
@@ -617,9 +624,9 @@ class Quota(LoggedModel):
return (
( # Orders for items which do not have any variations
Q(variation__isnull=True) &
Q(item__quotas__in=[self])
Q(item__quotas=self)
) | ( # Orders for items which do have any variations
Q(variation__quotas__in=[self])
Q(variation__quotas=self)
)
)

View File

@@ -7,6 +7,7 @@ from typing import List, Union
import pytz
from django.conf import settings
from django.db import models
from django.db.models import F
from django.utils.crypto import get_random_string
from django.utils.timezone import make_aware, now
from django.utils.translation import ugettext_lazy as _
@@ -459,6 +460,8 @@ class OrderPosition(AbstractPosition):
@classmethod
def transform_cart_positions(cls, cp: List, order) -> list:
from . import Voucher
ops = []
for cartpos in cp:
op = OrderPosition(order=order)
@@ -471,8 +474,7 @@ class OrderPosition(AbstractPosition):
answ.cartposition = None
answ.save()
if cartpos.voucher:
cartpos.voucher.redeemed = True
cartpos.voucher.save()
Voucher.objects.filter(pk=cartpos.voucher.pk).update(redeemed=F('redeemed') + 1)
cartpos.delete()
return ops

View File

@@ -30,7 +30,9 @@ class Voucher(LoggedModel):
:type event: Event
:param code: The secret voucher code
:type code: str
:param redeemed: Whether or not this voucher has already been redeemed
:param max_usages: The number of times this voucher can be redeemed
:type max_usages: int
:param redeemed: The number of times this voucher already has been redeemed
:type redeemed: bool
:param valid_until: The expiration date of this voucher (optional)
:type valid_until: datetime
@@ -68,10 +70,14 @@ class Voucher(LoggedModel):
max_length=255, default=generate_code,
db_index=True,
)
redeemed = models.BooleanField(
max_usages = models.PositiveIntegerField(
verbose_name=_("Maximum usages"),
help_text=_("Number of times this voucher can be redeemed."),
default=1
)
redeemed = models.PositiveIntegerField(
verbose_name=_("Redeemed"),
default=False,
db_index=True
default=0
)
valid_until = models.DateTimeField(
blank=True, null=True, db_index=True,
@@ -197,7 +203,7 @@ class Voucher(LoggedModel):
Returns True if a voucher has not yet been redeemed, but is still
within its validity (if valid_until is set).
"""
if self.redeemed:
if self.redeemed >= self.max_usages:
return False
if self.valid_until and self.valid_until < now():
return False