Voucher: Add min_usages parameter (#2853)

This commit is contained in:
Raphael Michel
2022-10-20 18:07:24 +02:00
committed by GitHub
parent ba2d908a89
commit d69d70cfb1
13 changed files with 233 additions and 8 deletions

View File

@@ -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():

View File

@@ -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)