mirror of
https://github.com/pretix/pretix.git
synced 2026-05-06 15:24:02 +00:00
Voucher: Add min_usages parameter (#2853)
This commit is contained in:
@@ -110,6 +110,11 @@ error_messages = {
|
||||
'positions have been removed from your cart.'),
|
||||
'price_too_high': _('The entered price is to high.'),
|
||||
'voucher_invalid': _('This voucher code is not known in our database.'),
|
||||
'voucher_min_usages': _('The voucher code "%(voucher)s" can only be used if you select at least %(number)s '
|
||||
'matching products.'),
|
||||
'voucher_min_usages_removed': _('The voucher code "%(voucher)s" can only be used if you select at least '
|
||||
'%(number)s matching products. We have therefore removed some positions from '
|
||||
'your cart that can no longer be purchased like this.'),
|
||||
'voucher_redeemed': _('This voucher code has already been used the maximum number of times allowed.'),
|
||||
'voucher_redeemed_cart': _('This voucher code is currently locked since it is already contained in a cart. This '
|
||||
'might mean that someone else is redeeming this voucher right now, or that you tried '
|
||||
@@ -524,6 +529,15 @@ class CartManager:
|
||||
voucher_use_diff[voucher] += 1
|
||||
ops.append((listed_price - price_after_voucher, self.VoucherOperation(p, voucher, price_after_voucher)))
|
||||
|
||||
for voucher, cnt in list(voucher_use_diff.items()):
|
||||
if 0 < cnt < (voucher.min_usages - voucher.redeemed):
|
||||
raise CartError(
|
||||
_(error_messages['voucher_min_usages']) % {
|
||||
'voucher': voucher.code,
|
||||
'number': (voucher.min_usages - voucher.redeemed),
|
||||
}
|
||||
)
|
||||
|
||||
# If there are not enough voucher usages left for the full cart, let's apply them in the order that benefits
|
||||
# the user the most.
|
||||
ops.sort(key=lambda k: k[0], reverse=True)
|
||||
@@ -915,6 +929,41 @@ class CartManager:
|
||||
)
|
||||
return err
|
||||
|
||||
def _check_min_per_voucher(self):
|
||||
vouchers = Counter()
|
||||
for p in self.positions:
|
||||
vouchers[p.voucher] += 1
|
||||
for op in self._operations:
|
||||
if isinstance(op, self.AddOperation):
|
||||
vouchers[op.voucher] += op.count
|
||||
elif isinstance(op, self.RemoveOperation):
|
||||
vouchers[op.position.voucher] -= 1
|
||||
|
||||
err = None
|
||||
for voucher, count in vouchers.items():
|
||||
if not voucher or count == 0:
|
||||
continue
|
||||
if count < (voucher.min_usages - voucher.redeemed):
|
||||
self._operations = [o for o in self._operations if not (
|
||||
isinstance(o, self.AddOperation) and o.voucher.pk == voucher.pk
|
||||
)]
|
||||
removals = [o.position.pk for o in self._operations if isinstance(o, self.RemoveOperation)]
|
||||
for p in self.positions:
|
||||
if p.voucher_id == voucher.pk and p.pk not in removals:
|
||||
self._operations.append(self.RemoveOperation(position=p))
|
||||
err = _(error_messages['voucher_min_usages_removed']) % {
|
||||
'voucher': voucher.code,
|
||||
'number': (voucher.min_usages - voucher.redeemed),
|
||||
}
|
||||
if not err:
|
||||
raise CartError(
|
||||
_(error_messages['voucher_min_usages']) % {
|
||||
'voucher': voucher.code,
|
||||
'number': (voucher.min_usages - voucher.redeemed),
|
||||
}
|
||||
)
|
||||
return err
|
||||
|
||||
def _perform_operations(self):
|
||||
vouchers_ok = self._get_voucher_availability()
|
||||
quotas_ok = _get_quota_availability(self._quota_diff, self.now_dt)
|
||||
@@ -1171,6 +1220,7 @@ class CartManager:
|
||||
|
||||
err = self._delete_out_of_timeframe()
|
||||
err = self.extend_expired_positions() or err
|
||||
err = err or self._check_min_per_voucher()
|
||||
|
||||
lockfn = NoLockManager
|
||||
if self._require_locking():
|
||||
|
||||
@@ -115,6 +115,8 @@ error_messages = {
|
||||
'server was too busy. Please try again.'),
|
||||
'not_started': _('The booking period for this event has not yet started.'),
|
||||
'ended': _('The booking period has ended.'),
|
||||
'voucher_min_usages': _('The voucher code "%(voucher)s" can only be used if you select at least %(number)s '
|
||||
'matching products.'),
|
||||
'voucher_invalid': _('The voucher code used for one of the items in your cart is not known in our database.'),
|
||||
'voucher_redeemed': _('The voucher code used for one of the items in your cart has already been used the maximum '
|
||||
'number of times allowed. We removed this item from your cart.'),
|
||||
@@ -569,6 +571,7 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
|
||||
products_seen = Counter()
|
||||
q_avail = Counter()
|
||||
v_avail = Counter()
|
||||
v_usages = Counter()
|
||||
v_budget = {}
|
||||
deleted_positions = set()
|
||||
seats_seen = set()
|
||||
@@ -606,6 +609,7 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
|
||||
break
|
||||
|
||||
if cp.voucher:
|
||||
v_usages[cp.voucher] += 1
|
||||
if cp.voucher not in v_avail:
|
||||
redeemed_in_carts = CartPosition.objects.filter(
|
||||
Q(voucher=cp.voucher) & Q(event=event) & Q(expires__gte=now_dt)
|
||||
@@ -717,6 +721,13 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
|
||||
# Sorry, can't let you keep that!
|
||||
delete(cp)
|
||||
|
||||
for voucher, cnt in v_usages.items():
|
||||
if 0 < cnt < voucher.min_usages - voucher.redeemed:
|
||||
raise OrderError(error_messages['voucher_min_usages'], {
|
||||
'voucher': voucher.code,
|
||||
'number': (voucher.min_usages - voucher.redeemed),
|
||||
})
|
||||
|
||||
# Check prices
|
||||
sorted_positions = [cp for cp in sorted_positions if cp.pk and cp.pk not in deleted_positions]
|
||||
old_total = sum(cp.price for cp in sorted_positions)
|
||||
|
||||
Reference in New Issue
Block a user