mirror of
https://github.com/pretix/pretix.git
synced 2026-05-05 15:14:04 +00:00
Allow to reactivate canceled orders (#1601)
This commit is contained in:
@@ -44,7 +44,7 @@ from pretix.base.services.mail import SendMailException
|
||||
from pretix.base.services.orders import (
|
||||
OrderChangeManager, OrderError, _order_placed_email,
|
||||
_order_placed_email_attendee, approve_order, cancel_order, deny_order,
|
||||
extend_order, mark_order_expired, mark_order_refunded,
|
||||
extend_order, mark_order_expired, mark_order_refunded, reactivate_order,
|
||||
)
|
||||
from pretix.base.services.pricing import get_price
|
||||
from pretix.base.services.tickets import generate
|
||||
@@ -261,6 +261,29 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
)
|
||||
return self.retrieve(request, [], **kwargs)
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
def reactivate(self, request, **kwargs):
|
||||
|
||||
order = self.get_object()
|
||||
if order.status != Order.STATUS_CANCELED:
|
||||
return Response(
|
||||
{'detail': 'The order is not allowed to be reactivated.'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
try:
|
||||
reactivate_order(
|
||||
order,
|
||||
user=request.user if request.user.is_authenticated else None,
|
||||
auth=request.auth if isinstance(request.auth, (Device, TeamAPIToken, OAuthAccessToken)) else None,
|
||||
)
|
||||
except OrderError as e:
|
||||
return Response(
|
||||
{'detail': str(e)},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return self.retrieve(request, [], **kwargs)
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
def approve(self, request, **kwargs):
|
||||
send_mail = request.data.get('send_email', True)
|
||||
|
||||
@@ -125,6 +125,10 @@ def register_default_webhook_events(sender, **kwargs):
|
||||
'pretix.event.order.canceled',
|
||||
_('Order canceled'),
|
||||
),
|
||||
ParametrizedOrderWebhookEvent(
|
||||
'pretix.event.order.reactivated',
|
||||
_('Order reactivated'),
|
||||
),
|
||||
ParametrizedOrderWebhookEvent(
|
||||
'pretix.event.order.expired',
|
||||
_('Order expired'),
|
||||
|
||||
@@ -4,6 +4,7 @@ import json
|
||||
import logging
|
||||
import os
|
||||
import string
|
||||
from collections import Counter
|
||||
from datetime import datetime, time, timedelta
|
||||
from decimal import Decimal
|
||||
from typing import Any, Dict, List, Union
|
||||
@@ -694,16 +695,19 @@ class Order(LockModel, LoggedModel):
|
||||
|
||||
return self._is_still_available(count_waitinglist=count_waitinglist, force=force)
|
||||
|
||||
def _is_still_available(self, now_dt: datetime=None, count_waitinglist=True, force=False) -> Union[bool, str]:
|
||||
def _is_still_available(self, now_dt: datetime=None, count_waitinglist=True, force=False,
|
||||
check_voucher_usage=False) -> Union[bool, str]:
|
||||
error_messages = {
|
||||
'unavailable': _('The ordered product "{item}" is no longer available.'),
|
||||
'seat_unavailable': _('The seat "{seat}" is no longer available.'),
|
||||
'voucher_budget': _('The voucher "{voucher}" no longer has sufficient budget.'),
|
||||
'voucher_usages': _('The voucher "{voucher}" has been used in the meantime.'),
|
||||
}
|
||||
now_dt = now_dt or now()
|
||||
positions = self.positions.all().select_related('item', 'variation', 'seat', 'voucher')
|
||||
quota_cache = {}
|
||||
v_budget = {}
|
||||
v_usage = Counter()
|
||||
try:
|
||||
for i, op in enumerate(positions):
|
||||
if op.seat:
|
||||
@@ -722,6 +726,13 @@ class Order(LockModel, LoggedModel):
|
||||
))
|
||||
v_budget[op.voucher] -= disc
|
||||
|
||||
if op.voucher and check_voucher_usage:
|
||||
v_usage[op.voucher.pk] += 1
|
||||
if v_usage[op.voucher.pk] + op.voucher.redeemed > op.voucher.max_usages:
|
||||
raise Quota.QuotaExceededException(error_messages['voucher_usages'].format(
|
||||
voucher=op.voucher.code
|
||||
))
|
||||
|
||||
quotas = list(op.quotas)
|
||||
if len(quotas) == 0:
|
||||
raise Quota.QuotaExceededException(error_messages['unavailable'].format(
|
||||
|
||||
@@ -223,6 +223,12 @@ def register_default_notification_types(sender, **kwargs):
|
||||
_('Order canceled'),
|
||||
_('Order {order.code} has been canceled.')
|
||||
),
|
||||
ParametrizedOrderNotificationType(
|
||||
sender,
|
||||
'pretix.event.order.reactivated',
|
||||
_('Order reactivated'),
|
||||
_('Order {order.code} has been reactivated.')
|
||||
),
|
||||
ParametrizedOrderNotificationType(
|
||||
sender,
|
||||
'pretix.event.order.expired',
|
||||
|
||||
@@ -94,6 +94,53 @@ def mark_order_paid(*args, **kwargs):
|
||||
raise NotImplementedError("This method is no longer supported since pretix 1.17.")
|
||||
|
||||
|
||||
def reactivate_order(order: Order, force: bool=False, user: User=None, auth=None):
|
||||
"""
|
||||
Reactivates a canceled order. If ``force`` is not set to ``True``, this will fail if there is not
|
||||
enough quota.
|
||||
"""
|
||||
if order.status != Order.STATUS_CANCELED:
|
||||
raise OrderError('The order was not canceled.')
|
||||
|
||||
with order.event.lock() as now_dt:
|
||||
is_available = force or order._is_still_available(now_dt, count_waitinglist=False, check_voucher_usage=True)
|
||||
if is_available is True:
|
||||
if order.payment_refund_sum >= order.total:
|
||||
order.status = Order.STATUS_PAID
|
||||
else:
|
||||
order.status = Order.STATUS_PENDING
|
||||
order.set_expires(now(),
|
||||
order.event.subevents.filter(id__in=[p.subevent_id for p in order.positions.all()]))
|
||||
with transaction.atomic():
|
||||
order.save(update_fields=['expires', 'status'])
|
||||
order.log_action(
|
||||
'pretix.event.order.reactivated',
|
||||
user=user,
|
||||
auth=auth,
|
||||
data={
|
||||
'expires': order.expires,
|
||||
}
|
||||
)
|
||||
for position in order.positions.all():
|
||||
if position.voucher:
|
||||
Voucher.objects.filter(pk=position.voucher.pk).update(redeemed=Greatest(0, F('redeemed') + 1))
|
||||
|
||||
for gc in position.issued_gift_cards.all():
|
||||
gc = GiftCard.objects.select_for_update().get(pk=gc.pk)
|
||||
gc.transactions.create(value=position.price, order=order)
|
||||
break
|
||||
else:
|
||||
raise OrderError(is_available)
|
||||
|
||||
order_approved.send(order.event, order=order)
|
||||
if order.status == Order.STATUS_PAID:
|
||||
order_paid.send(order.event, order=order)
|
||||
|
||||
num_invoices = order.invoices.filter(is_cancellation=False).count()
|
||||
if num_invoices > 0 and order.invoices.filter(is_cancellation=True).count() >= num_invoices and invoice_qualified(order):
|
||||
generate_invoice(order)
|
||||
|
||||
|
||||
def extend_order(order: Order, new_date: datetime, force: bool=False, user: User=None, auth=None):
|
||||
"""
|
||||
Extends the deadline of an order. If the order is already expired, the quota will be checked to
|
||||
@@ -117,9 +164,10 @@ def extend_order(order: Order, new_date: datetime, force: bool=False, user: User
|
||||
'state_change': was_expired
|
||||
}
|
||||
)
|
||||
|
||||
if was_expired:
|
||||
num_invoices = order.invoices.filter(is_cancellation=False).count()
|
||||
if num_invoices > 0 and order.invoices.filter(is_cancellation=True).count() >= num_invoices:
|
||||
if num_invoices > 0 and order.invoices.filter(is_cancellation=True).count() >= num_invoices and invoice_qualified(order):
|
||||
generate_invoice(order)
|
||||
|
||||
if order.status == Order.STATUS_PENDING:
|
||||
@@ -277,6 +325,7 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device
|
||||
:param order: The order to change
|
||||
:param user: The user that performed the change
|
||||
"""
|
||||
# If new actions are added to this function, make sure to add the reverse operation to reactivate_order()
|
||||
with transaction.atomic():
|
||||
if isinstance(order, int):
|
||||
order = Order.objects.select_for_update().get(pk=order)
|
||||
|
||||
@@ -341,6 +341,16 @@ as the first argument.
|
||||
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
|
||||
order_reactivated = EventPluginSignal(
|
||||
providing_args=["order"]
|
||||
)
|
||||
"""
|
||||
This signal is sent out every time a canceled order is reactivated. The order object is given
|
||||
as the first argument.
|
||||
|
||||
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
|
||||
order_expired = EventPluginSignal(
|
||||
providing_args=["order"]
|
||||
)
|
||||
|
||||
@@ -191,6 +191,7 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
||||
'pretix.event.order.paid': _('The order has been marked as paid.'),
|
||||
'pretix.event.order.refunded': _('The order has been refunded.'),
|
||||
'pretix.event.order.canceled': _('The order has been canceled.'),
|
||||
'pretix.event.order.reactivated': _('The order has been reactivated.'),
|
||||
'pretix.event.order.deleted': _('The test mode order {code} has been deleted.'),
|
||||
'pretix.event.order.placed': _('The order has been created.'),
|
||||
'pretix.event.order.placed.require_approval': _('The order requires approval before it can continue to be processed.'),
|
||||
|
||||
@@ -59,6 +59,10 @@
|
||||
<a href="{% url "control:event.order.transition" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}?status=c" class="btn btn-default">
|
||||
{% trans "Cancel order" %}
|
||||
</a>
|
||||
{% elif order.status == 'c' %}
|
||||
<a href="{% url "control:event.order.reactivate" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}" class="btn btn-default">
|
||||
{% trans "Reactivate order" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}
|
||||
{% trans "Reactivate order" %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<h1>
|
||||
{% trans "Reactivate order" %}
|
||||
<a class="btn btn-link btn-lg"
|
||||
href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}">
|
||||
{% blocktrans trimmed with order=order.code %}
|
||||
Back to order {{ order }}
|
||||
{% endblocktrans %}
|
||||
</a>
|
||||
</h1>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
By reactivating the order, you reverse its cancellation and transform this back into a pending or paid order.
|
||||
This is only possible as long as all products in the order are still available.
|
||||
If the order is pending payment, the expiry date will be reset.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<form method="post" class="form-horizontal" href="">
|
||||
{% csrf_token %}
|
||||
<div class="form-group submit-group">
|
||||
<a class="btn btn-default btn-lg"
|
||||
href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}">
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
<button class="btn btn-danger btn-save btn-lg" type="submit">
|
||||
{% trans "Reactivate" %}
|
||||
</button>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -222,6 +222,8 @@ urlpatterns = [
|
||||
name='event.order.checkvatid'),
|
||||
url(r'^orders/(?P<code>[0-9A-Z]+)/extend$', orders.OrderExtend.as_view(),
|
||||
name='event.order.extend'),
|
||||
url(r'^orders/(?P<code>[0-9A-Z]+)/reactivate$', orders.OrderReactivate.as_view(),
|
||||
name='event.order.reactivate'),
|
||||
url(r'^orders/(?P<code>[0-9A-Z]+)/contact$', orders.OrderContactChange.as_view(),
|
||||
name='event.order.contact'),
|
||||
url(r'^orders/(?P<code>[0-9A-Z]+)/locale', orders.OrderLocaleChange.as_view(),
|
||||
|
||||
@@ -57,7 +57,7 @@ from pretix.base.services.mail import SendMailException, render_mail
|
||||
from pretix.base.services.orders import (
|
||||
OrderChangeManager, OrderError, approve_order, cancel_order, deny_order,
|
||||
extend_order, mark_order_expired, mark_order_refunded,
|
||||
notify_user_changed_order,
|
||||
notify_user_changed_order, reactivate_order,
|
||||
)
|
||||
from pretix.base.services.stats import order_overview
|
||||
from pretix.base.services.tickets import generate
|
||||
@@ -1261,6 +1261,42 @@ class OrderExtend(OrderView):
|
||||
data=self.request.POST if self.request.method == "POST" else None)
|
||||
|
||||
|
||||
class OrderReactivate(OrderView):
|
||||
permission = 'can_change_orders'
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
try:
|
||||
reactivate_order(
|
||||
self.order,
|
||||
user=self.request.user
|
||||
)
|
||||
messages.success(self.request, _('The order has been reactivated.'))
|
||||
except OrderError as e:
|
||||
messages.error(self.request, str(e))
|
||||
return self._redirect_here()
|
||||
except LockTimeoutException:
|
||||
messages.error(self.request, _('We were not able to process the request completely as the '
|
||||
'server was too busy.'))
|
||||
return self._redirect_back()
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if self.order.status != Order.STATUS_CANCELED:
|
||||
messages.error(self.request, _('This action is only allowed for canceled orders.'))
|
||||
return self._redirect_back()
|
||||
return super().dispatch(request, *kwargs, **kwargs)
|
||||
|
||||
def _redirect_here(self):
|
||||
return redirect('control:event.order.reactivate',
|
||||
event=self.request.event.slug,
|
||||
organizer=self.request.event.organizer.slug,
|
||||
code=self.order.code)
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
return render(self.request, 'pretixcontrol/order/reactivate.html', {
|
||||
'order': self.order,
|
||||
})
|
||||
|
||||
|
||||
class OrderChange(OrderView):
|
||||
permission = 'can_change_orders'
|
||||
template_name = 'pretixcontrol/order/change.html'
|
||||
|
||||
@@ -1097,6 +1097,29 @@ def test_order_mark_paid_locked(token_client, organizer, event, order):
|
||||
assert order.status == Order.STATUS_EXPIRED
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_order_reactivate(token_client, organizer, event, order, quota):
|
||||
order.status = Order.STATUS_CANCELED
|
||||
order.save()
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/orders/{}/reactivate/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
)
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.data['status'] == Order.STATUS_PENDING
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_order_reactivate_invalid(token_client, organizer, event, order):
|
||||
resp = token_client.post(
|
||||
'/api/v1/organizers/{}/events/{}/orders/{}/reactivate/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
)
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_order_mark_canceled_pending(token_client, organizer, event, order):
|
||||
djmail.outbox = []
|
||||
|
||||
@@ -23,7 +23,8 @@ from pretix.base.reldate import RelativeDate, RelativeDateWrapper
|
||||
from pretix.base.services.invoices import generate_invoice
|
||||
from pretix.base.services.orders import (
|
||||
OrderChangeManager, OrderError, _create_order, approve_order, cancel_order,
|
||||
deny_order, expire_orders, send_download_reminders, send_expiry_warnings,
|
||||
deny_order, expire_orders, reactivate_order, send_download_reminders,
|
||||
send_expiry_warnings,
|
||||
)
|
||||
from pretix.base.signals import register_sales_channels
|
||||
from pretix.plugins.banktransfer.payment import BankTransfer
|
||||
@@ -1457,7 +1458,7 @@ class OrderChangeManagerTests(TestCase):
|
||||
self.ocm.commit()
|
||||
ops = list(self.order.positions.all())
|
||||
for op in ops:
|
||||
assert op.price == Decimal('23.01') # sic. we can't really avoid it.
|
||||
assert op.price == Decimal('23.01') # sic. we can't really avoid it.
|
||||
assert op.tax_value == Decimal('1.51')
|
||||
assert op.tax_rate == Decimal('7.00')
|
||||
|
||||
@@ -2169,7 +2170,8 @@ class OrderChangeManagerTests(TestCase):
|
||||
def test_clear_out_order(self):
|
||||
self.order.status = Order.STATUS_PAID
|
||||
self.order.save()
|
||||
self.order.payments.create(amount=self.order.total, state=OrderPayment.PAYMENT_STATE_CONFIRMED, provider='manual')
|
||||
self.order.payments.create(amount=self.order.total, state=OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||
provider='manual')
|
||||
cancel_order(self.order, cancellation_fee=Decimal('5.00'))
|
||||
self.order.refresh_from_db()
|
||||
assert self.order.total == Decimal('5.00')
|
||||
@@ -2187,7 +2189,8 @@ class OrderChangeManagerTests(TestCase):
|
||||
self.order.total = Decimal('51.1')
|
||||
self.order.save()
|
||||
|
||||
self.order.payments.create(state=OrderPayment.PAYMENT_STATE_PENDING, amount=Decimal('48.5'), fee=fee, provider="banktransfer")
|
||||
self.order.payments.create(state=OrderPayment.PAYMENT_STATE_PENDING, amount=Decimal('48.5'), fee=fee,
|
||||
provider="banktransfer")
|
||||
prov = self.ocm._get_payment_provider()
|
||||
prov.settings.set('_fee_percent', Decimal('10.00'))
|
||||
prov.settings.set('_fee_reverse_calc', False)
|
||||
@@ -2205,7 +2208,8 @@ class OrderChangeManagerTests(TestCase):
|
||||
self.order.total = Decimal('50.60')
|
||||
self.order.save()
|
||||
|
||||
self.order.payments.create(state=OrderPayment.PAYMENT_STATE_PENDING, amount=Decimal('48.5'), fee=fee, provider="banktransfer")
|
||||
self.order.payments.create(state=OrderPayment.PAYMENT_STATE_PENDING, amount=Decimal('48.5'), fee=fee,
|
||||
provider="banktransfer")
|
||||
prov = self.ocm._get_payment_provider()
|
||||
prov.settings.set('_fee_percent', Decimal('10.00'))
|
||||
prov.settings.set('_fee_reverse_calc', False)
|
||||
@@ -2224,7 +2228,8 @@ class OrderChangeManagerTests(TestCase):
|
||||
self.order.total = Decimal('50.60')
|
||||
self.order.save()
|
||||
|
||||
self.order.payments.create(state=OrderPayment.PAYMENT_STATE_PENDING, amount=Decimal('48.5'), fee=fee, provider="banktransfer")
|
||||
self.order.payments.create(state=OrderPayment.PAYMENT_STATE_PENDING, amount=Decimal('48.5'), fee=fee,
|
||||
provider="banktransfer")
|
||||
prov = self.ocm._get_payment_provider()
|
||||
prov.settings.set('_fee_percent', Decimal('10.00'))
|
||||
prov.settings.set('_fee_reverse_calc', False)
|
||||
@@ -2463,3 +2468,118 @@ def test_issue_when_paid_and_changed(event):
|
||||
op2 = order.positions.last()
|
||||
gc2 = op2.issued_gift_cards.get()
|
||||
assert gc2.value == op2.price
|
||||
|
||||
|
||||
class OrderReactivateTest(TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.o = Organizer.objects.create(name='Dummy', slug='dummy')
|
||||
with scope(organizer=self.o):
|
||||
self.event = Event.objects.create(organizer=self.o, name='Dummy', slug='dummy', date_from=now(),
|
||||
plugins='tests.testdummy')
|
||||
self.order = Order.objects.create(
|
||||
code='FOO', event=self.event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_CANCELED, locale='en',
|
||||
datetime=now(), expires=now() + timedelta(days=1),
|
||||
total=Decimal('46.00'),
|
||||
)
|
||||
self.ticket = Item.objects.create(event=self.event, name='Early-bird ticket',
|
||||
default_price=Decimal('23.00'), admission=True)
|
||||
self.op1 = OrderPosition.objects.create(
|
||||
order=self.order, item=self.ticket, variation=None,
|
||||
price=Decimal("23.00"), attendee_name_parts={'full_name': "Peter"}, positionid=1
|
||||
)
|
||||
self.op2 = OrderPosition.objects.create(
|
||||
order=self.order, item=self.ticket, variation=None,
|
||||
price=Decimal("23.00"), attendee_name_parts={'full_name': "Dieter"}, positionid=2
|
||||
)
|
||||
self.stalls = Item.objects.create(event=self.event, name='Stalls',
|
||||
default_price=Decimal('23.00'), admission=True)
|
||||
self.plan = SeatingPlan.objects.create(
|
||||
name="Plan", organizer=self.o, layout="{}"
|
||||
)
|
||||
self.event.seat_category_mappings.create(
|
||||
layout_category='Stalls', product=self.stalls
|
||||
)
|
||||
self.quota = self.event.quotas.create(name='Test', size=None)
|
||||
self.quota.items.add(self.stalls)
|
||||
self.quota.items.add(self.ticket)
|
||||
self.seat_a1 = self.event.seats.create(name="A1", product=self.stalls, seat_guid="A1")
|
||||
generate_invoice(self.order)
|
||||
djmail.outbox = []
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_paid(self):
|
||||
self.order.status = Order.STATUS_PAID
|
||||
self.order.save()
|
||||
with pytest.raises(OrderError):
|
||||
reactivate_order(self.order)
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_reactivate_unpaid(self):
|
||||
e = self.order.expires
|
||||
reactivate_order(self.order)
|
||||
self.order.refresh_from_db()
|
||||
assert self.order.status == Order.STATUS_PENDING
|
||||
assert self.order.all_logentries().last().action_type == 'pretix.event.order.reactivated'
|
||||
assert self.order.invoices.count() == 3
|
||||
assert self.order.expires > e > now()
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_reactivate_paid(self):
|
||||
self.order.payments.create(state=OrderPayment.PAYMENT_STATE_CONFIRMED, amount=48.5)
|
||||
reactivate_order(self.order)
|
||||
self.order.refresh_from_db()
|
||||
assert self.order.status == Order.STATUS_PAID
|
||||
assert self.order.all_logentries().last().action_type == 'pretix.event.order.reactivated'
|
||||
assert self.order.invoices.count() == 3
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_reactivate_sold_out(self):
|
||||
self.quota.size = 0
|
||||
self.quota.save()
|
||||
with pytest.raises(OrderError):
|
||||
reactivate_order(self.order)
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_reactivate_seat_taken(self):
|
||||
self.op1.item = self.stalls
|
||||
self.op1.seat = self.seat_a1
|
||||
self.op1.save()
|
||||
self.seat_a1.blocked = True
|
||||
self.seat_a1.save()
|
||||
with pytest.raises(OrderError):
|
||||
reactivate_order(self.order)
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_reactivate_voucher_ok(self):
|
||||
self.op1.voucher = self.event.vouchers.create(code="FOO", item=self.ticket, redeemed=0, max_usages=1)
|
||||
self.op1.save()
|
||||
reactivate_order(self.order)
|
||||
v = self.op1.voucher
|
||||
v.refresh_from_db()
|
||||
assert v.redeemed == 1
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_reactivate_voucher_budget(self):
|
||||
self.op1.voucher = self.event.vouchers.create(code="FOO", item=self.ticket, budget=Decimal('0.00'))
|
||||
self.op1.price_before_voucher = self.op1.price * 2
|
||||
self.op1.save()
|
||||
with pytest.raises(OrderError):
|
||||
reactivate_order(self.order)
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_reactivate_voucher_used(self):
|
||||
self.op1.voucher = self.event.vouchers.create(code="FOO", item=self.ticket, redeemed=1, max_usages=1)
|
||||
self.op1.save()
|
||||
with pytest.raises(OrderError):
|
||||
reactivate_order(self.order)
|
||||
v = self.op1.voucher
|
||||
v.refresh_from_db()
|
||||
assert v.redeemed == 1
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_reactivate_gift_card(self):
|
||||
gc = self.o.issued_gift_cards.create(currency="EUR", issued_in=self.op1)
|
||||
reactivate_order(self.order)
|
||||
assert gc.value == 23
|
||||
|
||||
@@ -574,6 +574,37 @@ def test_order_resend_link(client, env):
|
||||
assert 'FOO' in mail.outbox[0].body
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_order_reactivate_not_canceled(client, env):
|
||||
with scopes_disabled():
|
||||
o = Order.objects.get(id=env[2].id)
|
||||
o.status = Order.STATUS_PAID
|
||||
o.save()
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
response = client.get('/control/event/dummy/dummy/orders/FOO/reactivate', follow=True)
|
||||
assert 'alert-danger' in response.content.decode()
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/reactivate', follow=True)
|
||||
assert 'alert-danger' in response.content.decode()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_order_reactivate(client, env):
|
||||
with scopes_disabled():
|
||||
q = Quota.objects.create(event=env[0], size=3)
|
||||
q.items.add(env[3])
|
||||
o = Order.objects.get(id=env[2].id)
|
||||
o.status = Order.STATUS_CANCELED
|
||||
o.save()
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
response = client.post('/control/event/dummy/dummy/orders/FOO/reactivate', {
|
||||
}, follow=True)
|
||||
print(response.content.decode())
|
||||
assert 'alert-success' in response.content.decode()
|
||||
with scopes_disabled():
|
||||
o = Order.objects.get(id=env[2].id)
|
||||
assert o.status == Order.STATUS_PENDING
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_order_extend_not_pending(client, env):
|
||||
with scopes_disabled():
|
||||
|
||||
@@ -102,6 +102,7 @@ event_urls = [
|
||||
"orders/ABC/resend",
|
||||
"orders/ABC/invoice",
|
||||
"orders/ABC/extend",
|
||||
"orders/ABC/reactivate",
|
||||
"orders/ABC/change",
|
||||
"orders/ABC/contact",
|
||||
"orders/ABC/comment",
|
||||
@@ -274,6 +275,7 @@ event_permission_urls = [
|
||||
("can_view_orders", "orders/", 200),
|
||||
("can_view_orders", "orders/FOO/", 200),
|
||||
("can_change_orders", "orders/FOO/extend", 200),
|
||||
("can_change_orders", "orders/FOO/reactivate", 302),
|
||||
("can_change_orders", "orders/FOO/contact", 200),
|
||||
("can_change_orders", "orders/FOO/transition", 405),
|
||||
("can_change_orders", "orders/FOO/checkvatid", 405),
|
||||
|
||||
Reference in New Issue
Block a user