forked from CGM_Public/pretix_original
Compare commits
2 Commits
reldatetim
...
cart-vouch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc298c4202 | ||
|
|
8822d572f5 |
@@ -82,6 +82,9 @@ error_messages = {
|
|||||||
'voucher_expired': _('This voucher is expired.'),
|
'voucher_expired': _('This voucher is expired.'),
|
||||||
'voucher_invalid_item': _('This voucher is not valid for this product.'),
|
'voucher_invalid_item': _('This voucher is not valid for this product.'),
|
||||||
'voucher_invalid_seat': _('This voucher is not valid for this seat.'),
|
'voucher_invalid_seat': _('This voucher is not valid for this seat.'),
|
||||||
|
'voucher_no_match': _('We did not find any position in your cart that we could use this voucher for. If you want '
|
||||||
|
'to add something new to your cart using that voucher, you can do so with the voucher '
|
||||||
|
'redemption option on the bottom of the page.'),
|
||||||
'voucher_item_not_available': _(
|
'voucher_item_not_available': _(
|
||||||
'Your voucher is valid for a product that is currently not for sale.'),
|
'Your voucher is valid for a product that is currently not for sale.'),
|
||||||
'voucher_invalid_subevent': pgettext_lazy('subevent', 'This voucher is not valid for this event date.'),
|
'voucher_invalid_subevent': pgettext_lazy('subevent', 'This voucher is not valid for this event date.'),
|
||||||
@@ -107,10 +110,12 @@ class CartManager:
|
|||||||
AddOperation = namedtuple('AddOperation', ('count', 'item', 'variation', 'price', 'voucher', 'quotas',
|
AddOperation = namedtuple('AddOperation', ('count', 'item', 'variation', 'price', 'voucher', 'quotas',
|
||||||
'addon_to', 'subevent', 'includes_tax', 'bundled', 'seat'))
|
'addon_to', 'subevent', 'includes_tax', 'bundled', 'seat'))
|
||||||
RemoveOperation = namedtuple('RemoveOperation', ('position',))
|
RemoveOperation = namedtuple('RemoveOperation', ('position',))
|
||||||
|
VoucherOperation = namedtuple('VoucherOperation', ('position', 'voucher', 'price'))
|
||||||
ExtendOperation = namedtuple('ExtendOperation', ('position', 'count', 'item', 'variation', 'price', 'voucher',
|
ExtendOperation = namedtuple('ExtendOperation', ('position', 'count', 'item', 'variation', 'price', 'voucher',
|
||||||
'quotas', 'subevent', 'seat'))
|
'quotas', 'subevent', 'seat'))
|
||||||
order = {
|
order = {
|
||||||
RemoveOperation: 10,
|
RemoveOperation: 10,
|
||||||
|
VoucherOperation: 15,
|
||||||
ExtendOperation: 20,
|
ExtendOperation: 20,
|
||||||
AddOperation: 30
|
AddOperation: 30
|
||||||
}
|
}
|
||||||
@@ -419,6 +424,58 @@ class CartManager:
|
|||||||
self._operations.append(op)
|
self._operations.append(op)
|
||||||
return err
|
return err
|
||||||
|
|
||||||
|
def apply_voucher(self, voucher_code: str):
|
||||||
|
if self._operations:
|
||||||
|
raise CartError('Applying a voucher to the whole cart should not be combined with other operations.')
|
||||||
|
try:
|
||||||
|
voucher = self.event.vouchers.get(code__iexact=voucher_code.strip())
|
||||||
|
except Voucher.DoesNotExist:
|
||||||
|
raise CartError(error_messages['voucher_invalid'])
|
||||||
|
voucher_use_diff = Counter()
|
||||||
|
ops = []
|
||||||
|
|
||||||
|
if not voucher.is_active():
|
||||||
|
raise CartError(error_messages['voucher_expired'])
|
||||||
|
|
||||||
|
for p in self.positions:
|
||||||
|
if p.voucher_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not voucher.applies_to(p.item, p.variation):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if voucher.seat and voucher.seat != p.seat:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if voucher.subevent_id and voucher.subevent_id != p.subevent_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if p.is_bundled:
|
||||||
|
continue
|
||||||
|
|
||||||
|
bundled_sum = Decimal('0.00')
|
||||||
|
if not p.addon_to_id:
|
||||||
|
for bundledp in p.addons.all():
|
||||||
|
if bundledp.is_bundled:
|
||||||
|
bundledprice = bundledp.price
|
||||||
|
bundled_sum += bundledprice
|
||||||
|
|
||||||
|
price = self._get_price(p.item, p.variation, voucher, None, p.subevent, bundled_sum=bundled_sum)
|
||||||
|
if price.gross > p.price:
|
||||||
|
continue
|
||||||
|
|
||||||
|
voucher_use_diff[voucher] += 1
|
||||||
|
ops.append((p.price - price.gross, self.VoucherOperation(p, voucher, price)))
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
self._operations += [k[1] for k in ops]\
|
||||||
|
|
||||||
|
if not voucher_use_diff:
|
||||||
|
raise CartError(error_messages['voucher_no_match'])
|
||||||
|
self._voucher_use_diff += voucher_use_diff
|
||||||
|
|
||||||
def add_new_items(self, items: List[dict]):
|
def add_new_items(self, items: List[dict]):
|
||||||
# Fetch items from the database
|
# Fetch items from the database
|
||||||
self._update_items_cache([i['item'] for i in items], [i['variation'] for i in items])
|
self._update_items_cache([i['item'] for i in items], [i['variation'] for i in items])
|
||||||
@@ -762,7 +819,7 @@ class CartManager:
|
|||||||
self._operations.sort(key=lambda a: self.order[type(a)])
|
self._operations.sort(key=lambda a: self.order[type(a)])
|
||||||
seats_seen = set()
|
seats_seen = set()
|
||||||
|
|
||||||
for op in self._operations:
|
for iop, op in enumerate(self._operations):
|
||||||
if isinstance(op, self.RemoveOperation):
|
if isinstance(op, self.RemoveOperation):
|
||||||
if op.position.expires > self.now_dt:
|
if op.position.expires > self.now_dt:
|
||||||
for q in op.position.quotas:
|
for q in op.position.quotas:
|
||||||
@@ -896,6 +953,19 @@ class CartManager:
|
|||||||
op.position.delete()
|
op.position.delete()
|
||||||
else:
|
else:
|
||||||
raise AssertionError("ExtendOperation cannot affect more than one item")
|
raise AssertionError("ExtendOperation cannot affect more than one item")
|
||||||
|
elif isinstance(op, self.VoucherOperation):
|
||||||
|
if vouchers_ok[op.voucher] < 1:
|
||||||
|
if iop == 0:
|
||||||
|
raise CartError(error_messages['voucher_redeemed'])
|
||||||
|
else:
|
||||||
|
# We fail silently if we could only apply the voucher to part of the cart, since that might
|
||||||
|
# be expected
|
||||||
|
continue
|
||||||
|
|
||||||
|
op.position.price = op.price.gross
|
||||||
|
op.position.voucher = op.voucher
|
||||||
|
op.position.save()
|
||||||
|
vouchers_ok[op.voucher] -= 1
|
||||||
|
|
||||||
for p in new_cart_positions:
|
for p in new_cart_positions:
|
||||||
if getattr(p, '_answers', None):
|
if getattr(p, '_answers', None):
|
||||||
@@ -1060,6 +1130,26 @@ def add_items_to_cart(self, event: int, items: List[dict], cart_id: str=None, lo
|
|||||||
raise CartError(error_messages['busy'])
|
raise CartError(error_messages['busy'])
|
||||||
|
|
||||||
|
|
||||||
|
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
|
||||||
|
def apply_voucher(self, event: Event, voucher: str, cart_id: str=None, locale='en') -> None:
|
||||||
|
"""
|
||||||
|
Removes a list of items from a user's cart.
|
||||||
|
:param event: The event ID in question
|
||||||
|
:param voucher: A voucher code
|
||||||
|
:param session: Session ID of a guest
|
||||||
|
"""
|
||||||
|
with language(locale):
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
cm = CartManager(event=event, cart_id=cart_id)
|
||||||
|
cm.apply_voucher(voucher)
|
||||||
|
cm.commit()
|
||||||
|
except LockTimeoutException:
|
||||||
|
self.retry()
|
||||||
|
except (MaxRetriesExceededError, LockTimeoutException):
|
||||||
|
raise CartError(error_messages['busy'])
|
||||||
|
|
||||||
|
|
||||||
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
|
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
|
||||||
def remove_cart_position(self, event: Event, position: int, cart_id: str=None, locale='en') -> None:
|
def remove_cart_position(self, event: Event, position: int, cart_id: str=None, locale='en') -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -246,11 +246,31 @@
|
|||||||
<div class="product">
|
<div class="product">
|
||||||
<strong>{% trans "Total" %}</strong>
|
<strong>{% trans "Total" %}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="count hidden-xs">
|
<div class="count hidden-xs hidden-sm">
|
||||||
<strong>{{ cart.itemcount }}</strong>
|
<strong>{{ cart.itemcount }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 col-xs-6 col-md-offset-3 price">
|
<div class="col-md-3 col-xs-6 col-md-offset-3 price">
|
||||||
<strong>{{ cart.total|money:event.currency }}</strong>
|
<strong>{{ cart.total|money:event.currency }}</strong>
|
||||||
|
|
||||||
|
{% if editable and show_vouchers and not cart.all_with_voucher %}
|
||||||
|
<br>
|
||||||
|
<a class="js-only apply-voucher-toggle" href="#">
|
||||||
|
<span class="fa fa-tag"></span> {% trans "Redeem a voucher" %}
|
||||||
|
</a>
|
||||||
|
<form action="{% eventurl event "presale:event.cart.voucher" cart_namespace=cart_namespace|default_if_none:"" %}"
|
||||||
|
data-asynctask-headline="{% trans "We're applying this voucher to your cart..." %}"
|
||||||
|
method="post" data-asynctask class="apply-voucher">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control" name="voucher" placeholder="{% trans "Voucher code" %}">
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button class="btn btn-primary" type="submit">
|
||||||
|
<span class="fa fa-check"></span>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -18,6 +18,7 @@ import pretix.presale.views.widget
|
|||||||
|
|
||||||
frame_wrapped_urls = [
|
frame_wrapped_urls = [
|
||||||
url(r'^cart/remove$', pretix.presale.views.cart.CartRemove.as_view(), name='event.cart.remove'),
|
url(r'^cart/remove$', pretix.presale.views.cart.CartRemove.as_view(), name='event.cart.remove'),
|
||||||
|
url(r'^cart/voucher$', pretix.presale.views.cart.CartApplyVoucher.as_view(), name='event.cart.voucher'),
|
||||||
url(r'^cart/clear$', pretix.presale.views.cart.CartClear.as_view(), name='event.cart.clear'),
|
url(r'^cart/clear$', pretix.presale.views.cart.CartClear.as_view(), name='event.cart.clear'),
|
||||||
url(r'^cart/answer/(?P<answer>[^/]+)/$',
|
url(r'^cart/answer/(?P<answer>[^/]+)/$',
|
||||||
pretix.presale.views.cart.AnswerDownload.as_view(),
|
pretix.presale.views.cart.AnswerDownload.as_view(),
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ class CartMixin:
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'positions': positions,
|
'positions': positions,
|
||||||
|
'all_with_voucher': all(p.voucher_id for p in positions),
|
||||||
'raw': cartpos,
|
'raw': cartpos,
|
||||||
'total': total,
|
'total': total,
|
||||||
'net_total': net_total,
|
'net_total': net_total,
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ from pretix.base.models import (
|
|||||||
CartPosition, InvoiceAddress, QuestionAnswer, SubEvent, Voucher,
|
CartPosition, InvoiceAddress, QuestionAnswer, SubEvent, Voucher,
|
||||||
)
|
)
|
||||||
from pretix.base.services.cart import (
|
from pretix.base.services.cart import (
|
||||||
CartError, add_items_to_cart, clear_cart, remove_cart_position,
|
CartError, add_items_to_cart, apply_voucher, clear_cart,
|
||||||
|
remove_cart_position,
|
||||||
)
|
)
|
||||||
from pretix.base.views.tasks import AsyncAction
|
from pretix.base.views.tasks import AsyncAction
|
||||||
from pretix.multidomain.urlreverse import eventreverse
|
from pretix.multidomain.urlreverse import eventreverse
|
||||||
@@ -327,6 +328,26 @@ def cart_session(request):
|
|||||||
return request.session['carts'][cart_id]
|
return request.session['carts'][cart_id]
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(allow_frame_if_namespaced, 'dispatch')
|
||||||
|
class CartApplyVoucher(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||||
|
task = apply_voucher
|
||||||
|
known_errortypes = ['CartError']
|
||||||
|
|
||||||
|
def get_success_message(self, value):
|
||||||
|
return _('We applied the voucher to as many products in your cart as we could.')
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
if 'voucher' in request.POST:
|
||||||
|
return self.do(self.request.event.id, request.POST.get('voucher'), get_or_create_cart_id(self.request), translation.get_language())
|
||||||
|
else:
|
||||||
|
if 'ajax' in self.request.GET or 'ajax' in self.request.POST:
|
||||||
|
return JsonResponse({
|
||||||
|
'redirect': self.get_error_url()
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return redirect(self.get_error_url())
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(allow_frame_if_namespaced, 'dispatch')
|
@method_decorator(allow_frame_if_namespaced, 'dispatch')
|
||||||
class CartRemove(EventViewMixin, CartActionMixin, AsyncAction, View):
|
class CartRemove(EventViewMixin, CartActionMixin, AsyncAction, View):
|
||||||
task = remove_cart_position
|
task = remove_cart_position
|
||||||
|
|||||||
@@ -70,4 +70,13 @@ $(function () {
|
|||||||
if ($("#cart-deadline").length) {
|
if ($("#cart-deadline").length) {
|
||||||
cart.init();
|
cart.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$(".apply-voucher").hide();
|
||||||
|
$(".apply-voucher-toggle").click(function (e) {
|
||||||
|
$(".apply-voucher-toggle").hide();
|
||||||
|
$(".apply-voucher").show();
|
||||||
|
$(".apply-voucher input[ŧype=text]").first().focus();
|
||||||
|
e.preventDefault();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -73,6 +73,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.apply-voucher {
|
||||||
|
input {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media(max-width: $screen-sm-max) {
|
@media(max-width: $screen-sm-max) {
|
||||||
.cart-row {
|
.cart-row {
|
||||||
.download-mobile {
|
.download-mobile {
|
||||||
|
|||||||
@@ -1725,6 +1725,178 @@ class CartTest(CartTestMixin, TestCase):
|
|||||||
positions = CartPosition.objects.filter(cart_id=self.session_key, event=self.event)
|
positions = CartPosition.objects.filter(cart_id=self.session_key, event=self.event)
|
||||||
assert positions.count() == 1
|
assert positions.count() == 1
|
||||||
|
|
||||||
|
def test_voucher_apply_matching(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
cp1 = CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||||
|
price=23, expires=now() + timedelta(minutes=10)
|
||||||
|
)
|
||||||
|
cp2 = CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_blue,
|
||||||
|
price=15, expires=now() + timedelta(minutes=10)
|
||||||
|
)
|
||||||
|
v = Voucher.objects.create(
|
||||||
|
event=self.event, item=self.ticket, price_mode='set', value=Decimal('4.00')
|
||||||
|
)
|
||||||
|
response = self.client.post('/%s/%s/cart/voucher' % (self.orga.slug, self.event.slug), {
|
||||||
|
'voucher': v.code,
|
||||||
|
}, follow=True)
|
||||||
|
assert 'alert-success' in response.rendered_content
|
||||||
|
with scopes_disabled():
|
||||||
|
cp1.refresh_from_db()
|
||||||
|
cp2.refresh_from_db()
|
||||||
|
assert cp1.voucher == v
|
||||||
|
assert cp1.price == Decimal('4.00')
|
||||||
|
assert cp2.voucher is None
|
||||||
|
|
||||||
|
def test_voucher_apply_partial_in_price_order(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
cp1 = CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||||
|
price=23, expires=now() + timedelta(minutes=10)
|
||||||
|
)
|
||||||
|
cp2 = CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_blue,
|
||||||
|
price=150, expires=now() + timedelta(minutes=10)
|
||||||
|
)
|
||||||
|
v = Voucher.objects.create(
|
||||||
|
event=self.event, price_mode='set', value=Decimal('4.00'), max_usages=100, redeemed=99
|
||||||
|
)
|
||||||
|
response = self.client.post('/%s/%s/cart/voucher' % (self.orga.slug, self.event.slug), {
|
||||||
|
'voucher': v.code,
|
||||||
|
}, follow=True)
|
||||||
|
assert 'alert-success' in response.rendered_content
|
||||||
|
with scopes_disabled():
|
||||||
|
cp1.refresh_from_db()
|
||||||
|
cp2.refresh_from_db()
|
||||||
|
assert cp1.voucher is None
|
||||||
|
assert cp1.price == Decimal('23.00')
|
||||||
|
assert cp2.voucher == v
|
||||||
|
assert cp2.price == Decimal('4.00')
|
||||||
|
|
||||||
|
def test_voucher_apply_multiple(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
cp1 = CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||||
|
price=23, expires=now() + timedelta(minutes=10)
|
||||||
|
)
|
||||||
|
cp2 = CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_blue,
|
||||||
|
price=150, expires=now() + timedelta(minutes=10)
|
||||||
|
)
|
||||||
|
v = Voucher.objects.create(
|
||||||
|
event=self.event, price_mode='set', quota=self.quota_all, value=Decimal('4.00'), max_usages=100
|
||||||
|
)
|
||||||
|
response = self.client.post('/%s/%s/cart/voucher' % (self.orga.slug, self.event.slug), {
|
||||||
|
'voucher': v.code,
|
||||||
|
}, follow=True)
|
||||||
|
assert 'alert-success' in response.rendered_content
|
||||||
|
with scopes_disabled():
|
||||||
|
cp1.refresh_from_db()
|
||||||
|
cp2.refresh_from_db()
|
||||||
|
assert cp1.voucher == v
|
||||||
|
assert cp1.price == Decimal('4.00')
|
||||||
|
assert cp2.voucher == v
|
||||||
|
assert cp2.price == Decimal('4.00')
|
||||||
|
|
||||||
|
def test_voucher_apply_only_one_per_line(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
cp1 = CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||||
|
price=23, expires=now() + timedelta(minutes=10)
|
||||||
|
)
|
||||||
|
v2 = Voucher.objects.create(
|
||||||
|
event=self.event, price_mode='set', quota=self.quota_all, value=Decimal('4.00'), max_usages=100
|
||||||
|
)
|
||||||
|
cp2 = CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_blue,
|
||||||
|
price=150, expires=now() + timedelta(minutes=10), voucher=v2
|
||||||
|
)
|
||||||
|
v = Voucher.objects.create(
|
||||||
|
event=self.event, price_mode='set', quota=self.quota_all, value=Decimal('4.00'), max_usages=100
|
||||||
|
)
|
||||||
|
response = self.client.post('/%s/%s/cart/voucher' % (self.orga.slug, self.event.slug), {
|
||||||
|
'voucher': v.code,
|
||||||
|
}, follow=True)
|
||||||
|
assert 'alert-success' in response.rendered_content
|
||||||
|
with scopes_disabled():
|
||||||
|
cp1.refresh_from_db()
|
||||||
|
cp2.refresh_from_db()
|
||||||
|
assert cp1.voucher == v
|
||||||
|
assert cp1.price == Decimal('4.00')
|
||||||
|
assert cp2.voucher == v2
|
||||||
|
assert cp2.price == Decimal('150.00')
|
||||||
|
|
||||||
|
def test_voucher_apply_only_positive(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
cp1 = CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||||
|
price=23, expires=now() + timedelta(minutes=10)
|
||||||
|
)
|
||||||
|
cp2 = CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_blue,
|
||||||
|
price=15, expires=now() + timedelta(minutes=10)
|
||||||
|
)
|
||||||
|
v = Voucher.objects.create(
|
||||||
|
event=self.event, price_mode='set', value=Decimal('40.00'), max_usages=100
|
||||||
|
)
|
||||||
|
response = self.client.post('/%s/%s/cart/voucher' % (self.orga.slug, self.event.slug), {
|
||||||
|
'voucher': v.code,
|
||||||
|
}, follow=True)
|
||||||
|
assert 'alert-danger' in response.rendered_content
|
||||||
|
with scopes_disabled():
|
||||||
|
cp1.refresh_from_db()
|
||||||
|
cp2.refresh_from_db()
|
||||||
|
assert cp1.voucher is None
|
||||||
|
assert cp2.voucher is None
|
||||||
|
|
||||||
|
def test_voucher_apply_expired(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
cp1 = CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||||
|
price=23, expires=now() + timedelta(minutes=10)
|
||||||
|
)
|
||||||
|
cp2 = CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_blue,
|
||||||
|
price=15, expires=now() + timedelta(minutes=10)
|
||||||
|
)
|
||||||
|
v = Voucher.objects.create(
|
||||||
|
event=self.event, price_mode='set', value=Decimal('40.00'), max_usages=100,
|
||||||
|
valid_until=now() - timedelta(days=1)
|
||||||
|
)
|
||||||
|
response = self.client.post('/%s/%s/cart/voucher' % (self.orga.slug, self.event.slug), {
|
||||||
|
'voucher': v.code,
|
||||||
|
}, follow=True)
|
||||||
|
assert 'alert-danger' in response.rendered_content
|
||||||
|
with scopes_disabled():
|
||||||
|
cp1.refresh_from_db()
|
||||||
|
cp2.refresh_from_db()
|
||||||
|
assert cp1.voucher is None
|
||||||
|
assert cp2.voucher is None
|
||||||
|
|
||||||
|
def test_voucher_apply_used(self):
|
||||||
|
with scopes_disabled():
|
||||||
|
cp1 = CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||||
|
price=23, expires=now() + timedelta(minutes=10)
|
||||||
|
)
|
||||||
|
cp2 = CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_blue,
|
||||||
|
price=15, expires=now() + timedelta(minutes=10)
|
||||||
|
)
|
||||||
|
v = Voucher.objects.create(
|
||||||
|
event=self.event, price_mode='set', value=Decimal('40.00'), max_usages=100, redeemed=100
|
||||||
|
)
|
||||||
|
response = self.client.post('/%s/%s/cart/voucher' % (self.orga.slug, self.event.slug), {
|
||||||
|
'voucher': v.code,
|
||||||
|
}, follow=True)
|
||||||
|
assert 'alert-danger' in response.rendered_content
|
||||||
|
with scopes_disabled():
|
||||||
|
cp1.refresh_from_db()
|
||||||
|
cp2.refresh_from_db()
|
||||||
|
assert cp1.voucher is None
|
||||||
|
assert cp2.voucher is None
|
||||||
|
|
||||||
|
|
||||||
class CartAddonTest(CartTestMixin, TestCase):
|
class CartAddonTest(CartTestMixin, TestCase):
|
||||||
@scopes_disabled()
|
@scopes_disabled()
|
||||||
@@ -2685,6 +2857,48 @@ class CartBundleTest(CartTestMixin, TestCase):
|
|||||||
assert cp.price == 0
|
assert cp.price == 0
|
||||||
assert b.price == 40
|
assert b.price == 40
|
||||||
|
|
||||||
|
@classscope(attr='orga')
|
||||||
|
def test_voucher_apply_multiple(self):
|
||||||
|
cp = CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||||
|
price=21.5, expires=now() + timedelta(minutes=10)
|
||||||
|
)
|
||||||
|
b = CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.trans, addon_to=cp,
|
||||||
|
price=1.5, expires=now() + timedelta(minutes=10), is_bundled=True
|
||||||
|
)
|
||||||
|
v = Voucher.objects.create(
|
||||||
|
event=self.event, price_mode='set', value=Decimal('4.00'), max_usages=100
|
||||||
|
)
|
||||||
|
|
||||||
|
self.cm.apply_voucher(v.code)
|
||||||
|
self.cm.commit()
|
||||||
|
cp.refresh_from_db()
|
||||||
|
b.refresh_from_db()
|
||||||
|
assert cp.price == Decimal('2.50')
|
||||||
|
assert b.price == Decimal('1.50')
|
||||||
|
|
||||||
|
@classscope(attr='orga')
|
||||||
|
def test_voucher_apply_multiple_reduce_beyond_designated_price(self):
|
||||||
|
cp = CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||||
|
price=21.5, expires=now() + timedelta(minutes=10)
|
||||||
|
)
|
||||||
|
b = CartPosition.objects.create(
|
||||||
|
event=self.event, cart_id=self.session_key, item=self.trans, addon_to=cp,
|
||||||
|
price=1.5, expires=now() + timedelta(minutes=10), is_bundled=True
|
||||||
|
)
|
||||||
|
v = Voucher.objects.create(
|
||||||
|
event=self.event, price_mode='set', value=Decimal('0.00'), max_usages=100
|
||||||
|
)
|
||||||
|
|
||||||
|
self.cm.apply_voucher(v.code)
|
||||||
|
self.cm.commit()
|
||||||
|
cp.refresh_from_db()
|
||||||
|
b.refresh_from_db()
|
||||||
|
assert cp.price == Decimal('0.00')
|
||||||
|
assert b.price == Decimal('1.50')
|
||||||
|
|
||||||
@classscope(attr='orga')
|
@classscope(attr='orga')
|
||||||
def test_extend_base_price_changed(self):
|
def test_extend_base_price_changed(self):
|
||||||
cp = CartPosition.objects.create(
|
cp = CartPosition.objects.create(
|
||||||
|
|||||||
Reference in New Issue
Block a user