From e32e7e2a500f82e520579a59e060a2e18be27837 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Mon, 14 Nov 2022 16:55:39 +0100 Subject: [PATCH] Add clever handling of plus button in cart with voucher (#2893) --- src/pretix/base/services/cart.py | 30 +++++++++++++++++-- .../pretixpresale/event/fragment_cart.html | 4 +++ src/pretix/presale/views/cart.py | 8 +++-- src/tests/presale/test_cart.py | 25 ++++++++++++++++ 4 files changed, 63 insertions(+), 4 deletions(-) diff --git a/src/pretix/base/services/cart.py b/src/pretix/base/services/cart.py index 04fbb7e6ac..1c39168a2f 100644 --- a/src/pretix/base/services/cart.py +++ b/src/pretix/base/services/cart.py @@ -195,7 +195,7 @@ class CartManager: AddOperation = namedtuple('AddOperation', ('count', 'item', 'variation', 'voucher', 'quotas', 'addon_to', 'subevent', 'bundled', 'seat', 'listed_price', 'price_after_voucher', 'custom_price_input', - 'custom_price_input_is_net')) + 'custom_price_input_is_net', 'voucher_ignored')) RemoveOperation = namedtuple('RemoveOperation', ('position',)) VoucherOperation = namedtuple('VoucherOperation', ('position', 'voucher', 'price_after_voucher')) ExtendOperation = namedtuple('ExtendOperation', ('position', 'count', 'item', 'variation', 'voucher', @@ -330,12 +330,16 @@ class CartManager: (isinstance(op, self.ExtendOperation) and op.position.is_bundled) ): if op.item.require_voucher and op.voucher is None: + if getattr(op, 'voucher_ignored', False): + raise CartError(error_messages['voucher_redeemed']) raise CartError(error_messages['voucher_required']) if ( (op.item.hide_without_voucher or (op.variation and op.variation.hide_without_voucher)) and (op.voucher is None or not op.voucher.show_hidden_items) ): + if getattr(op, 'voucher_ignored', False): + raise CartError(error_messages['voucher_redeemed']) raise CartError(error_messages['voucher_required']) if not op.item.is_available() or (op.variation and not op.variation.is_available()): @@ -480,7 +484,7 @@ class CartManager: self._check_item_constraints(op) if cp.voucher: - self._voucher_use_diff[cp.voucher] += 1 + self._voucher_use_diff[cp.voucher] += 2 self._operations.append(op) return err @@ -586,6 +590,7 @@ class CartManager: item = self._items_cache[i['item']] variation = self._variations_cache[i['variation']] if i['variation'] is not None else None voucher = None + voucher_ignored = False if i.get('voucher'): try: @@ -595,6 +600,24 @@ class CartManager: else: voucher_use_diff[voucher] += i['count'] + if i.get('voucher_ignore_if_redeemed', False): + # This is a special case handling for when a user clicks "+" on an existing line in their cart + # that has a voucher attached. If the voucher still has redemptions left, we'll add another line + # with the same voucher, but if it does not we silently continue as if there was no voucher, + # leading to either a higher-priced ticket or an error. Still, this leads to less error cases + # than either of the possible default assumptions. + predicted_redeemed_after = ( + voucher.redeemed + + CartPosition.objects.filter(voucher=voucher, expires__gte=self.now_dt).count() + + self._voucher_use_diff[voucher] + + voucher_use_diff[voucher] + ) + if predicted_redeemed_after > voucher.max_usages: + i.pop('voucher') + voucher_ignored = True + voucher = None + voucher_use_diff[voucher] -= i['count'] + # Fetch all quotas. If there are no quotas, this item is not allowed to be sold. quotas = list(item.quotas.filter(subevent=subevent) if variation is None else variation.quotas.filter(subevent=subevent)) @@ -641,6 +664,7 @@ class CartManager: price_after_voucher=bundle.designated_price, custom_price_input=None, custom_price_input_is_net=False, + voucher_ignored=False, ) self._check_item_constraints(bop, operations) bundled.append(bop) @@ -670,6 +694,7 @@ class CartManager: price_after_voucher=price_after_voucher, custom_price_input=custom_price, custom_price_input_is_net=self.event.settings.display_net_prices, + voucher_ignored=voucher_ignored, ) self._check_item_constraints(op, operations) operations.append(op) @@ -801,6 +826,7 @@ class CartManager: price_after_voucher=listed_price, custom_price_input=custom_price, custom_price_input_is_net=self.event.settings.display_net_prices, + voucher_ignored=False, ) self._check_item_constraints(op, operations) operations.append(op) diff --git a/src/pretix/presale/templates/pretixpresale/event/fragment_cart.html b/src/pretix/presale/templates/pretixpresale/event/fragment_cart.html index 8bff4b518a..b8fbce1788 100644 --- a/src/pretix/presale/templates/pretixpresale/event/fragment_cart.html +++ b/src/pretix/presale/templates/pretixpresale/event/fragment_cart.html @@ -267,6 +267,10 @@ data-asynctask-text="{% blocktrans with time=event.settings.reservation_time %}Once the items are in your cart, you will have {{ time }} minutes to complete your purchase.{% endblocktrans %}" method="post" data-asynctask> + {% if line.voucher and not line.voucher.seat %} + + + {% endif %} {% csrf_token %} {% if line.variation %}