diff --git a/src/pretix/base/services/cancelevent.py b/src/pretix/base/services/cancelevent.py
index c686678acb..89d0d82b6f 100644
--- a/src/pretix/base/services/cancelevent.py
+++ b/src/pretix/base/services/cancelevent.py
@@ -24,6 +24,7 @@ from decimal import Decimal
from django.db import transaction
from django.db.models import Count, Exists, IntegerField, OuterRef, Q, Subquery
+from django.utils.crypto import get_random_string
from django.utils.translation import gettext
from i18nfield.strings import LazyI18nString
@@ -41,6 +42,7 @@ from pretix.base.services.orders import (
)
from pretix.base.services.tasks import ProfiledEventTask
from pretix.base.services.tax import split_fee_for_taxes
+from pretix.base.templatetags.money import money_filter
from pretix.celery_app import app
from pretix.helpers import OF_SELF
from pretix.helpers.format import format_map
@@ -112,7 +114,7 @@ def cancel_event(self, event: Event, subevent: int, auto_refund: bool,
manual_refund: bool=False, send: bool=False, send_subject: dict=None, send_message: dict=None,
send_waitinglist: bool=False, send_waitinglist_subject: dict={}, send_waitinglist_message: dict={},
user: int=None, refund_as_giftcard: bool=False, giftcard_expires=None, giftcard_conditions=None,
- subevents_from: str=None, subevents_to: str=None):
+ subevents_from: str=None, subevents_to: str=None, dry_run=False):
send_subject = LazyI18nString(send_subject)
send_message = LazyI18nString(send_message)
send_waitinglist_subject = LazyI18nString(send_waitinglist_subject)
@@ -161,32 +163,72 @@ def cancel_event(self, event: Event, subevent: int, auto_refund: bool,
has_subevent=True, has_other_subevent=False, has_blocked=False
)
- for se in subevents:
- se.log_action(
- 'pretix.subevent.canceled', user=user,
- )
- se.active = False
- se.save(update_fields=['active'])
- se.log_action(
- 'pretix.subevent.changed', user=user, data={'active': False, '_source': 'cancel_event'}
- )
+ if not dry_run:
+ for se in subevents:
+ se.log_action(
+ 'pretix.subevent.canceled', user=user,
+ data={
+ "auto_refund": auto_refund,
+ "keep_fee_fixed": keep_fee_fixed,
+ "keep_fee_per_ticket": keep_fee_per_ticket,
+ "keep_fee_percentage": keep_fee_percentage,
+ "keep_fees": keep_fees,
+ "manual_refund": manual_refund,
+ "send": send,
+ "send_subject": send_subject,
+ "send_message": send_message,
+ "send_waitinglist": send_waitinglist,
+ "send_waitinglist_subject": send_waitinglist_subject,
+ "send_waitinglist_message": send_waitinglist_message,
+ "refund_as_giftcard": refund_as_giftcard,
+ "giftcard_expires": str(giftcard_expires),
+ "giftcard_conditions": giftcard_conditions,
+ }
+ )
+ se.active = False
+ se.save(update_fields=['active'])
+ se.log_action(
+ 'pretix.subevent.changed', user=user, data={'active': False, '_source': 'cancel_event'}
+ )
else:
subevents = None
subevent_ids = set()
orders_to_change = orders_to_cancel.filter(has_blocked=True)
orders_to_cancel = orders_to_cancel.filter(has_blocked=False)
- event.log_action(
- 'pretix.event.canceled', user=user,
- )
- for i in event.items.filter(active=True):
- i.active = False
- i.save(update_fields=['active'])
- i.log_action(
- 'pretix.event.item.changed', user=user, data={'active': False, '_source': 'cancel_event'}
+ if not dry_run:
+ event.log_action(
+ 'pretix.event.canceled', user=user,
+ data={
+ "auto_refund": auto_refund,
+ "keep_fee_fixed": keep_fee_fixed,
+ "keep_fee_per_ticket": keep_fee_per_ticket,
+ "keep_fee_percentage": keep_fee_percentage,
+ "keep_fees": keep_fees,
+ "manual_refund": manual_refund,
+ "send": send,
+ "send_subject": send_subject,
+ "send_message": send_message,
+ "send_waitinglist": send_waitinglist,
+ "send_waitinglist_subject": send_waitinglist_subject,
+ "send_waitinglist_message": send_waitinglist_message,
+ "refund_as_giftcard": refund_as_giftcard,
+ "giftcard_expires": str(giftcard_expires),
+ "giftcard_conditions": giftcard_conditions,
+ }
)
+
+ for i in event.items.filter(active=True):
+ i.active = False
+ i.save(update_fields=['active'])
+ i.log_action(
+ 'pretix.event.item.changed', user=user, data={'active': False, '_source': 'cancel_event'}
+ )
failed = 0
- total = orders_to_cancel.count() + orders_to_change.count()
+ refund_total = Decimal("0.00")
+ cancel_full_total = orders_to_cancel.count()
+ cancel_partial_total = orders_to_change.count()
+ total = cancel_full_total + cancel_partial_total
qs_wl = event.waitinglistentries.filter(voucher__isnull=True).select_related('subevent')
if subevents:
qs_wl = qs_wl.filter(subevent__in=subevents)
@@ -199,6 +241,7 @@ def cancel_event(self, event: Event, subevent: int, auto_refund: bool,
)
for o in orders_to_cancel.only('id', 'total').iterator():
+ payment_refund_sum = o.payment_refund_sum # cache to avoid multiple computations
try:
fee = Decimal('0.00')
fee_sum = Decimal('0.00')
@@ -217,20 +260,24 @@ def cancel_event(self, event: Event, subevent: int, auto_refund: bool,
for p in o.positions.all():
if p.addon_to_id is None:
fee += min(p.price, Decimal(keep_fee_per_ticket))
- fee = round_decimal(min(fee, o.payment_refund_sum), event.currency)
+ fee = round_decimal(min(fee, payment_refund_sum), event.currency)
- _cancel_order(o.pk, user, send_mail=False, cancellation_fee=fee, keep_fees=keep_fee_objects)
- refund_amount = o.payment_refund_sum
+ if dry_run:
+ refund_total += max(Decimal("0.00"), min(payment_refund_sum, o.total - fee))
+ else:
+ _cancel_order(o.pk, user, send_mail=False, cancellation_fee=fee, keep_fees=keep_fee_objects)
+ refund_amount = payment_refund_sum
+ refund_amount += refund_total
- try:
- if auto_refund or manual_refund:
- _try_auto_refund(o.pk, auto_refund=auto_refund, manual_refund=manual_refund, allow_partial=True,
- source=OrderRefund.REFUND_SOURCE_ADMIN, refund_as_giftcard=refund_as_giftcard,
- giftcard_expires=giftcard_expires, giftcard_conditions=giftcard_conditions,
- comment=gettext('Event canceled'))
- finally:
- if send:
- _send_mail(o, send_subject, send_message, subevent, refund_amount, user, o.positions.all())
+ try:
+ if auto_refund or manual_refund:
+ _try_auto_refund(o.pk, auto_refund=auto_refund, manual_refund=manual_refund, allow_partial=True,
+ source=OrderRefund.REFUND_SOURCE_ADMIN, refund_as_giftcard=refund_as_giftcard,
+ giftcard_expires=giftcard_expires, giftcard_conditions=giftcard_conditions,
+ comment=gettext('Event canceled'))
+ finally:
+ if send:
+ _send_mail(o, send_subject, send_message, subevent, refund_amount, user, o.positions.all())
counter += 1
if not self.request.called_directly and counter % max(10, total // 100) == 0:
@@ -247,12 +294,16 @@ def cancel_event(self, event: Event, subevent: int, auto_refund: bool,
for o in orders_to_change.values_list('id', flat=True).iterator():
with transaction.atomic():
- o = event.orders.select_for_update(of=OF_SELF).get(pk=o)
+ if dry_run:
+ o = event.orders.get(pk=o)
+ else:
+ o = event.orders.select_for_update(of=OF_SELF).get(pk=o)
total = Decimal('0.00')
fee = Decimal('0.00')
positions = []
ocm = OrderChangeManager(o, user=user, notify=False)
+ payment_refund_sum = o.payment_refund_sum # cache to avoid multiple computations
for p in o.positions.all():
if (not event.has_subevents or p.subevent_id in subevent_ids) and not p.blocked:
total += p.price
@@ -267,7 +318,7 @@ def cancel_event(self, event: Event, subevent: int, auto_refund: bool,
fee += Decimal(keep_fee_fixed)
if keep_fee_percentage:
fee += Decimal(keep_fee_percentage) / Decimal('100.00') * total
- fee = round_decimal(min(fee, o.payment_refund_sum), event.currency)
+ fee = round_decimal(min(fee, payment_refund_sum), event.currency)
if fee:
tax_rule_zero = TaxRule.zero()
if event.settings.tax_rule_cancellation == "default":
@@ -298,17 +349,21 @@ def cancel_event(self, event: Event, subevent: int, auto_refund: bool,
)
ocm.add_fee(f)
- ocm.commit()
- refund_amount = o.payment_refund_sum - o.total
+ if dry_run:
+ refund_total += max(payment_refund_sum - (o.total + ocm._totaldiff), Decimal("0.00"))
+ else:
+ ocm.commit()
+ refund_amount = payment_refund_sum - o.total
+ refund_total += refund_amount
- if auto_refund or manual_refund:
- _try_auto_refund(o.pk, auto_refund=auto_refund, manual_refund=manual_refund, allow_partial=True,
- source=OrderRefund.REFUND_SOURCE_ADMIN, refund_as_giftcard=refund_as_giftcard,
- giftcard_expires=giftcard_expires, giftcard_conditions=giftcard_conditions,
- comment=gettext('Event canceled'))
+ if auto_refund or manual_refund:
+ _try_auto_refund(o.pk, auto_refund=auto_refund, manual_refund=manual_refund, allow_partial=True,
+ source=OrderRefund.REFUND_SOURCE_ADMIN, refund_as_giftcard=refund_as_giftcard,
+ giftcard_expires=giftcard_expires, giftcard_conditions=giftcard_conditions,
+ comment=gettext('Event canceled'))
- if send:
- _send_mail(o, send_subject, send_message, subevent, refund_amount, user, positions)
+ if send:
+ _send_mail(o, send_subject, send_message, subevent, refund_amount, user, positions)
counter += 1
if not self.request.called_directly and counter % max(10, total // 100) == 0:
@@ -319,7 +374,8 @@ def cancel_event(self, event: Event, subevent: int, auto_refund: bool,
if send_waitinglist:
for wle in qs_wl:
- _send_wle_mail(wle, send_waitinglist_subject, send_waitinglist_message, wle.subevent)
+ if not dry_run:
+ _send_wle_mail(wle, send_waitinglist_subject, send_waitinglist_message, wle.subevent)
counter += 1
if not self.request.called_directly and counter % max(10, total // 100) == 0:
@@ -327,4 +383,30 @@ def cancel_event(self, event: Event, subevent: int, auto_refund: bool,
state='PROGRESS',
meta={'value': round(counter / total * 100 if total else 0, 2)}
)
- return failed
+
+ confirmation_code = None
+ if dry_run and user and refund_total > Decimal('100.00'):
+ confirmation_code = get_random_string(8, allowed_chars="01234567890")
+ mail(
+ user.email,
+ subject=gettext('Bulk-refund confirmation'),
+ template='pretixbase/email/cancel_confirm.txt',
+ context={
+ "event": str(event),
+ "amount": money_filter(refund_total, event.currency),
+ "confirmation_code": confirmation_code,
+ },
+ locale=user.locale,
+ )
+
+ return {
+ "dry_run": dry_run,
+ "id": self.request.id,
+ "failed": failed,
+ "refund_total": refund_total,
+ "cancel_full_total": cancel_full_total,
+ "cancel_partial_total": cancel_partial_total,
+ "confirmation_code": confirmation_code,
+ "args": self.request.args,
+ "kwargs": self.request.kwargs,
+ }
diff --git a/src/pretix/base/templates/pretixbase/email/cancel_confirm.txt b/src/pretix/base/templates/pretixbase/email/cancel_confirm.txt
new file mode 100644
index 0000000000..5e3043156a
--- /dev/null
+++ b/src/pretix/base/templates/pretixbase/email/cancel_confirm.txt
@@ -0,0 +1,10 @@
+{% load i18n %}
+{% trans "You have requested us to cancel an event which includes a larger bulk-refund:" %}
+
+{% trans "Event" %}: {{ event }}
+
+{% trans "Estimated refund amount" %}: **{{ amount }}**
+
+{% trans "Please confirm that you want to proceed by coping the following confirmation code into the cancellation form:" %}
+
+**{{ confirmation_code }}**
diff --git a/src/pretix/control/forms/orders.py b/src/pretix/control/forms/orders.py
index bdcbafd14f..9d2dc82d67 100644
--- a/src/pretix/control/forms/orders.py
+++ b/src/pretix/control/forms/orders.py
@@ -1030,3 +1030,27 @@ class EventCancelForm(FormPlaceholderMixin, forms.Form):
if self.event.has_subevents and not d['subevent'] and not d['all_subevents'] and not d.get('subevents_from'):
raise ValidationError(_('Please confirm that you want to cancel ALL dates in this event series.'))
return d
+
+
+class EventCancelConfirmForm(forms.Form):
+ confirm = forms.BooleanField(
+ label=_("I understand that this is not reversible and want to continue"),
+ required=True,
+ )
+ confirmation_code = forms.CharField(
+ label=_("Confirmation code"),
+ help_text=_("We have just emailed you a confirmation code to enter to confirm this action"),
+ required=True,
+ )
+
+ def __init__(self, *args, **kwargs):
+ self.code = kwargs.pop("confirmation_code")
+ super().__init__(*args, **kwargs)
+ if not self.code:
+ del self.fields["confirmation_code"]
+
+ def clean_confirmation_code(self):
+ val = self.cleaned_data['confirmation_code']
+ if val != self.code:
+ raise ValidationError(_('The confirmation code is incorrect.'))
+ return val
diff --git a/src/pretix/control/templates/pretixcontrol/orders/cancel.html b/src/pretix/control/templates/pretixcontrol/orders/cancel.html
index 47c91ecb9e..2e8c9ce71b 100644
--- a/src/pretix/control/templates/pretixcontrol/orders/cancel.html
+++ b/src/pretix/control/templates/pretixcontrol/orders/cancel.html
@@ -79,9 +79,15 @@
{% bootstrap_field form.send_waitinglist_message layout="horizontal" %}
-
+ {% if dry_run_supported %}
+
+ {% else %}
+
+ {% endif %}
{% endblock %}
diff --git a/src/pretix/control/templates/pretixcontrol/orders/cancel_confirm.html b/src/pretix/control/templates/pretixcontrol/orders/cancel_confirm.html
new file mode 100644
index 0000000000..b7e6208618
--- /dev/null
+++ b/src/pretix/control/templates/pretixcontrol/orders/cancel_confirm.html
@@ -0,0 +1,85 @@
+{% extends "pretixcontrol/event/base.html" %}
+{% load i18n %}
+{% load eventsignal %}
+{% load bootstrap3 %}
+{% load money %}
+{% block title %}{% trans "Cancel event" %}{% endblock %}
+{% block content %}
+ {% trans "Cancel event" %}
+
+{% endblock %}
diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py
index 130d4e78c4..feb3b64014 100644
--- a/src/pretix/control/urls.py
+++ b/src/pretix/control/urls.py
@@ -460,6 +460,7 @@ urlpatterns = [
re_path(r'^orders/search$', orders.OrderSearch.as_view(), name='event.orders.search'),
re_path(r'^dangerzone/$', event.DangerZone.as_view(), name='event.dangerzone'),
re_path(r'^cancel/$', orders.EventCancel.as_view(), name='event.cancel'),
+ re_path(r'^cancel/(?P[^/]+)/$', orders.EventCancelConfirm.as_view(), name='event.cancel.confirm'),
re_path(r'^shredder/$', shredder.StartShredView.as_view(), name='event.shredder.start'),
re_path(r'^shredder/export$', shredder.ShredExportView.as_view(), name='event.shredder.export'),
re_path(r'^shredder/download/(?P[^/]+)/$', shredder.ShredDownloadView.as_view(), name='event.shredder.download'),
diff --git a/src/pretix/control/views/orders.py b/src/pretix/control/views/orders.py
index 9e5d8382c5..c403cec839 100644
--- a/src/pretix/control/views/orders.py
+++ b/src/pretix/control/views/orders.py
@@ -42,6 +42,7 @@ from datetime import datetime, time, timedelta
from decimal import Decimal, DecimalException
from urllib.parse import quote, urlencode
+from celery.result import AsyncResult
from django import forms
from django.conf import settings
from django.contrib import messages
@@ -122,8 +123,8 @@ from pretix.control.forms.filter import (
RefundFilterForm,
)
from pretix.control.forms.orders import (
- CancelForm, CommentForm, DenyForm, EventCancelForm, ExporterForm,
- ExtendForm, MarkPaidForm, OrderContactForm, OrderFeeAddForm,
+ CancelForm, CommentForm, DenyForm, EventCancelConfirmForm, EventCancelForm,
+ ExporterForm, ExtendForm, MarkPaidForm, OrderContactForm, OrderFeeAddForm,
OrderFeeAddFormset, OrderFeeChangeForm, OrderLocaleForm, OrderMailForm,
OrderPositionAddForm, OrderPositionAddFormset, OrderPositionChangeForm,
OrderPositionMailForm, OrderRefundForm, OtherOperationsForm,
@@ -2975,10 +2976,99 @@ class EventCancel(EventPermissionRequiredMixin, AsyncAction, FormView):
send_waitinglist_subject=form.cleaned_data.get('send_waitinglist_subject').data,
send_waitinglist_message=form.cleaned_data.get('send_waitinglist_message').data,
user=self.request.user.pk,
+ dry_run=settings.HAS_CELERY,
+ )
+
+ def get_context_data(self, **kwargs):
+ return super().get_context_data(
+ dry_run_supported=settings.HAS_CELERY,
)
def get_success_message(self, value):
- if value == 0:
+ if value["dry_run"]:
+ return None
+ elif value["failed"] == 0:
+ return _('All orders have been canceled.')
+ else:
+ return _('The orders have been canceled. An error occurred with {count} orders, please '
+ 'check all uncanceled orders.').format(count=value)
+
+ def get_success_url(self, value):
+ if settings.HAS_CELERY:
+ return reverse('control:event.cancel.confirm', kwargs={
+ 'organizer': self.request.organizer.slug,
+ 'event': self.request.event.slug,
+ 'task': value["id"],
+ })
+ else:
+ return reverse('control:event.cancel', kwargs={
+ 'organizer': self.request.organizer.slug,
+ 'event': self.request.event.slug,
+ })
+
+ def get_error_url(self):
+ return reverse('control:event.cancel', kwargs={
+ 'organizer': self.request.organizer.slug,
+ 'event': self.request.event.slug,
+ })
+
+ def get_error_message(self, exception):
+ if isinstance(exception, str):
+ return exception
+ return super().get_error_message(exception)
+
+ def form_invalid(self, form):
+ messages.error(self.request, _('Your input was not valid.'))
+ return super().form_invalid(form)
+
+
+class EventCancelConfirm(EventPermissionRequiredMixin, AsyncAction, FormView):
+ template_name = 'pretixcontrol/orders/cancel_confirm.html'
+ permission = 'can_change_orders'
+ form_class = EventCancelConfirmForm
+ task = cancel_event
+ known_errortypes = ['OrderError']
+
+ @cached_property
+ def dryrun_result(self):
+ res = AsyncResult(self.kwargs.get("task"))
+ if not res.ready():
+ raise Http404()
+ if not res.successful():
+ raise Http404()
+ data = res.info
+ if not data.get("dry_run"):
+ raise Http404()
+ if data.get("args")[0] != self.request.event.pk:
+ raise Http404()
+ return data
+
+ def get(self, request, *args, **kwargs):
+ if 'async_id' in request.GET and settings.HAS_CELERY:
+ return self.get_result(request)
+ return FormView.get(self, request, *args, **kwargs)
+
+ def get_form_kwargs(self):
+ k = super().get_form_kwargs()
+ k['confirmation_code'] = self.dryrun_result["confirmation_code"]
+ return k
+
+ def form_valid(self, form):
+ return self.do(
+ *self.dryrun_result["args"],
+ **{
+ **self.dryrun_result["kwargs"],
+ "dry_run": False,
+ },
+ )
+
+ def get_context_data(self, **kwargs):
+ return super().get_context_data(
+ dryrun_result=self.dryrun_result,
+ )
+
+ def get_success_message(self, value):
+ if value["failed"] == 0:
return _('All orders have been canceled.')
else:
return _('The orders have been canceled. An error occurred with {count} orders, please '
diff --git a/src/pretix/static/pretixcontrol/js/ui/main.js b/src/pretix/static/pretixcontrol/js/ui/main.js
index a78a9acfe5..a1928bbcaf 100644
--- a/src/pretix/static/pretixcontrol/js/ui/main.js
+++ b/src/pretix/static/pretixcontrol/js/ui/main.js
@@ -348,7 +348,7 @@ var form_handlers = function (el) {
dependency.on("change", update);
});
- el.find("div[data-display-dependency], textarea[data-display-dependency], input[data-display-dependency], select[data-display-dependency]").each(function () {
+ el.find("div[data-display-dependency], textarea[data-display-dependency], input[data-display-dependency], select[data-display-dependency], button[data-display-dependency]").each(function () {
var dependent = $(this),
dependency = findDependency($(this).attr("data-display-dependency"), this),
update = function (ev) {
@@ -373,10 +373,11 @@ var form_handlers = function (el) {
enabled = !enabled;
}
var $toggling = dependent;
- if (dependent.attr("data-disable-dependent")) {
+ if (dependent.is("[data-disable-dependent]")) {
$toggling.attr('disabled', !enabled).trigger("change");
}
- if (dependent.get(0).tagName.toLowerCase() !== "div") {
+ const tagName = dependent.get(0).tagName.toLowerCase()
+ if (tagName !== "div" && tagName !== "button") {
$toggling = dependent.closest('.form-group');
}
if (ev) {
diff --git a/src/tests/base/test_cancelevent.py b/src/tests/base/test_cancelevent.py
index 79509aef0f..dbb97c1530 100644
--- a/src/tests/base/test_cancelevent.py
+++ b/src/tests/base/test_cancelevent.py
@@ -63,6 +63,15 @@ class EventCancelTests(TestCase):
generate_invoice(self.order)
djmail.outbox = []
+ def _cancel_with_dryrun(self, *args, expected_refunds, **kwargs):
+ dry_run = cancel_event(
+ *args, **kwargs, dry_run=True
+ )
+ assert dry_run["refund_total"] == expected_refunds
+ cancel_event(
+ *args, **kwargs,
+ )
+
@classscope(attr='o')
def test_cancel_send_mail(self):
gc = self.o.issued_gift_cards.create(currency="EUR")
@@ -74,11 +83,11 @@ class EventCancelTests(TestCase):
)
self.order.status = Order.STATUS_PAID
self.order.save()
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=None,
auto_refund=True, keep_fee_fixed="0.00", keep_fee_percentage="0.00", keep_fee_per_ticket="",
send=True, send_subject="Event canceled", send_message="Event canceled :-( {refund_amount}",
- user=None
+ user=None, expected_refunds=Decimal("46.00")
)
assert len(djmail.outbox) == 1
self.order.refresh_from_db()
@@ -114,11 +123,11 @@ class EventCancelTests(TestCase):
self.op1.blocked = ["admin"]
self.op1.save()
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=None,
auto_refund=True, keep_fee_fixed="0.00", keep_fee_percentage="0.00", keep_fee_per_ticket="",
send=True, send_subject="Event canceled", send_message="Event canceled :-(",
- user=None
+ user=None, expected_refunds=Decimal("23.00")
)
self.op1.refresh_from_db()
@@ -147,11 +156,11 @@ class EventCancelTests(TestCase):
self.order.status = Order.STATUS_PAID
self.order.save()
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=None,
auto_refund=True, keep_fee_fixed="0.00", keep_fee_percentage="0.00", keep_fee_per_ticket="",
send=True, send_subject="Event canceled", send_message="Event canceled :-(",
- user=None
+ user=None, expected_refunds=Decimal("46.00")
)
r = self.order.refunds.get()
@@ -175,11 +184,11 @@ class EventCancelTests(TestCase):
self.order.status = Order.STATUS_PAID
self.order.save()
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=None,
auto_refund=False, keep_fee_fixed="0.00", keep_fee_percentage="0.00", keep_fee_per_ticket="",
send=True, send_subject="Event canceled", send_message="Event canceled :-(",
- user=None
+ user=None, expected_refunds=Decimal("46.00")
)
self.order.refresh_from_db()
@@ -198,11 +207,11 @@ class EventCancelTests(TestCase):
self.order.status = Order.STATUS_PAID
self.order.save()
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=None,
auto_refund=True, keep_fee_fixed="0.00", keep_fee_percentage="0.00", keep_fee_per_ticket="2.00",
send=False, send_subject="Event canceled", send_message="Event canceled :-(",
- user=None
+ user=None, expected_refunds=Decimal("42.00")
)
r = self.order.refunds.get()
@@ -226,11 +235,11 @@ class EventCancelTests(TestCase):
self.order.status = Order.STATUS_PAID
self.order.save()
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=None,
auto_refund=True, keep_fee_fixed="0.00", keep_fee_percentage="0.00", keep_fee_per_ticket="2.00",
send=False, send_subject="Event canceled", send_message="Event canceled :-(",
- user=None
+ user=None, expected_refunds=Decimal("44.00")
)
r = self.order.refunds.get()
@@ -252,11 +261,11 @@ class EventCancelTests(TestCase):
self.order.status = Order.STATUS_PAID
self.order.save()
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=None,
auto_refund=True, keep_fee_fixed="0.00", keep_fee_percentage="0.00", keep_fee_per_ticket="2.00",
send=False, send_subject="Event canceled", send_message="Event canceled :-(",
- user=None
+ user=None, expected_refunds=Decimal("44.00")
)
r = self.order.refunds.get()
@@ -276,11 +285,11 @@ class EventCancelTests(TestCase):
self.order.status = Order.STATUS_PAID
self.order.save()
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=None,
auto_refund=True, keep_fee_fixed="10.00", keep_fee_percentage="10.00", keep_fee_per_ticket="",
send=False, send_subject="Event canceled", send_message="Event canceled :-(",
- user=None
+ user=None, expected_refunds=Decimal("31.40")
)
r = self.order.refunds.get()
@@ -304,11 +313,11 @@ class EventCancelTests(TestCase):
self.order.status = Order.STATUS_PENDING
self.order.save()
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=None,
auto_refund=True, keep_fee_fixed="10.00", keep_fee_percentage="10.00", keep_fee_per_ticket="",
send=False, send_subject="Event canceled", send_message="Event canceled :-(",
- user=None
+ user=None, expected_refunds=Decimal("12.00")
)
assert not self.order.refunds.exists()
@@ -335,10 +344,11 @@ class EventCancelTests(TestCase):
self.order.status = Order.STATUS_PAID
self.order.save()
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=None,
auto_refund=True, keep_fee_fixed="0.00", keep_fee_percentage="10.00", keep_fees=[OrderFee.FEE_TYPE_PAYMENT], keep_fee_per_ticket="",
- send=False, send_subject="Event canceled", send_message="Event canceled :-(", user=None
+ send=False, send_subject="Event canceled", send_message="Event canceled :-(", user=None,
+ expected_refunds=Decimal("36.90")
)
r = self.order.refunds.get()
assert r.state == OrderRefund.REFUND_STATE_DONE
@@ -371,11 +381,11 @@ class EventCancelTests(TestCase):
self.order.status = Order.STATUS_PAID
self.order.save()
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=None,
auto_refund=True, keep_fee_fixed="0.00", keep_fee_percentage="10.00", keep_fees=[OrderFee.FEE_TYPE_PAYMENT], keep_fee_per_ticket="",
send=False, send_subject="Event canceled", send_message="Event canceled :-(",
- user=None
+ user=None, expected_refunds=Decimal("39.40")
)
r = self.order.refunds.get()
assert r.amount == Decimal('39.40')
@@ -400,11 +410,11 @@ class EventCancelTests(TestCase):
self.order.status = Order.STATUS_PAID
self.order.save()
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=None, manual_refund=True,
auto_refund=True, keep_fee_fixed="0.00", keep_fee_percentage="0.00", keep_fee_per_ticket="",
send=False, send_subject="Event canceled", send_message="Event canceled :-(",
- user=None
+ user=None, expected_refunds=Decimal("46.00")
)
assert self.order.refunds.count() == 2
@@ -436,11 +446,11 @@ class EventCancelTests(TestCase):
self.order.status = Order.STATUS_PAID
self.order.save()
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=None, manual_refund=False,
auto_refund=True, keep_fee_fixed="0.00", keep_fee_percentage="0.00", keep_fee_per_ticket="",
send=False, send_subject="Event canceled", send_message="Event canceled :-(",
- user=None
+ user=None, expected_refunds=Decimal("46.00")
)
assert self.order.refunds.count() == 1
@@ -467,11 +477,11 @@ class EventCancelTests(TestCase):
self.order.status = Order.STATUS_PAID
self.order.save()
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=None, manual_refund=True,
auto_refund=False, keep_fee_fixed="0.00", keep_fee_percentage="0.00", keep_fee_per_ticket="",
send=False, send_subject="Event canceled", send_message="Event canceled :-(",
- user=None
+ user=None, expected_refunds=Decimal("46.00")
)
assert self.order.refunds.count() == 1
@@ -511,17 +521,26 @@ class SubEventCancelTests(TestCase):
generate_invoice(self.order)
djmail.outbox = []
+ def _cancel_with_dryrun(self, *args, expected_refunds, **kwargs):
+ dry_run = cancel_event(
+ *args, **kwargs, dry_run=True
+ )
+ assert dry_run["refund_total"] == expected_refunds
+ cancel_event(
+ *args, **kwargs,
+ )
+
@classscope(attr='o')
def test_cancel_partially_send_mail_attendees(self):
self.op1.attendee_email = 'foo@example.com'
self.op1.save()
self.op2.attendee_email = 'foo@example.org'
self.op2.save()
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=self.se1.pk,
auto_refund=True, keep_fee_fixed="0.00", keep_fee_percentage="0.00", keep_fee_per_ticket="",
send=True, send_subject="Event canceled", send_message="Event canceled :-(",
- user=None
+ user=None, expected_refunds=Decimal("0.00")
)
assert len(djmail.outbox) == 2
self.order.refresh_from_db()
@@ -532,19 +551,19 @@ class SubEventCancelTests(TestCase):
def test_cancel_subevent_range(self):
self.op2.subevent = self.se1
self.op2.save()
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=None, subevents_from=self.se1.date_from - timedelta(days=3), subevents_to=self.se1.date_from - timedelta(days=2),
auto_refund=True, keep_fee_fixed="0.00", keep_fee_percentage="0.00", keep_fee_per_ticket="",
send=True, send_subject="Event canceled", send_message="Event canceled :-(",
- user=None
+ user=None, expected_refunds=Decimal("0.00")
)
self.order.refresh_from_db()
assert self.order.status == Order.STATUS_PENDING
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=None, subevents_from=self.se1.date_from - timedelta(days=3), subevents_to=self.se1.date_from + timedelta(days=2),
auto_refund=True, keep_fee_fixed="0.00", keep_fee_percentage="0.00", keep_fee_per_ticket="",
send=True, send_subject="Event canceled", send_message="Event canceled :-(",
- user=None
+ user=None, expected_refunds=Decimal("0.00")
)
self.order.refresh_from_db()
assert self.order.status == Order.STATUS_CANCELED
@@ -553,11 +572,11 @@ class SubEventCancelTests(TestCase):
def test_cancel_simple_order(self):
self.op2.subevent = self.se1
self.op2.save()
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=self.se1.pk,
auto_refund=True, keep_fee_fixed="0.00", keep_fee_percentage="0.00", keep_fee_per_ticket="",
send=True, send_subject="Event canceled", send_message="Event canceled :-(",
- user=None
+ user=None, expected_refunds=Decimal("0.00")
)
self.order.refresh_from_db()
assert self.order.status == Order.STATUS_CANCELED
@@ -567,11 +586,11 @@ class SubEventCancelTests(TestCase):
self.op2.subevent = self.se1
self.op2.blocked = ["admin"]
self.op2.save()
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=self.se1.pk,
auto_refund=True, keep_fee_fixed="0.00", keep_fee_percentage="0.00", keep_fee_per_ticket="",
send=True, send_subject="Event canceled", send_message="Event canceled :-(",
- user=None
+ user=None, expected_refunds=Decimal("0.00")
)
self.order.refresh_from_db()
assert self.order.status == Order.STATUS_PENDING
@@ -582,11 +601,11 @@ class SubEventCancelTests(TestCase):
@classscope(attr='o')
def test_cancel_all_subevents(self):
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=None,
auto_refund=True, keep_fee_fixed="0.00", keep_fee_percentage="0.00", keep_fee_per_ticket="",
send=True, send_subject="Event canceled", send_message="Event canceled :-(",
- user=None
+ user=None, expected_refunds=Decimal("0.00")
)
self.order.refresh_from_db()
assert self.order.status == Order.STATUS_CANCELED
@@ -602,11 +621,12 @@ class SubEventCancelTests(TestCase):
)
self.order.status = Order.STATUS_PAID
self.order.save()
- cancel_event(
+ self.order.refresh_from_db()
+ self._cancel_with_dryrun(
self.event.pk, subevent=self.se1.pk,
auto_refund=True, keep_fee_fixed="0.00", keep_fee_percentage="0.00", keep_fee_per_ticket="",
send=True, send_subject="Event canceled", send_message="Event canceled :-( {refund_amount}",
- user=None
+ user=None, expected_refunds=Decimal("23.00")
)
self.order.refresh_from_db()
assert self.order.status == Order.STATUS_PAID
@@ -614,20 +634,20 @@ class SubEventCancelTests(TestCase):
@classscope(attr='o')
def test_cancel_mixed_order_range(self):
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=None, subevents_from=self.se1.date_from - timedelta(days=3), subevents_to=self.se1.date_from - timedelta(days=2),
auto_refund=True, keep_fee_fixed="0.00", keep_fee_percentage="0.00", keep_fee_per_ticket="",
send=True, send_subject="Event canceled", send_message="Event canceled :-( {refund_amount}",
- user=None
+ user=None, expected_refunds=Decimal("0.00")
)
self.order.refresh_from_db()
assert self.order.status == Order.STATUS_PENDING
assert self.order.positions.count() == 2
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=None, subevents_from=self.se1.date_from - timedelta(days=3), subevents_to=self.se1.date_from + timedelta(days=2),
auto_refund=True, keep_fee_fixed="0.00", keep_fee_percentage="0.00", keep_fee_per_ticket="",
send=True, send_subject="Event canceled", send_message="Event canceled :-( {refund_amount}",
- user=None
+ user=None, expected_refunds=Decimal("0.00")
)
self.order.refresh_from_db()
assert self.order.status == Order.STATUS_PENDING
@@ -651,11 +671,11 @@ class SubEventCancelTests(TestCase):
self.order.status = Order.STATUS_PAID
self.order.save()
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=self.se1.pk,
auto_refund=True, keep_fee_fixed="0.00", keep_fee_percentage="10.00", keep_fee_per_ticket="",
send=False, send_subject="Event canceled", send_message="Event canceled :-(",
- user=None
+ user=None, expected_refunds=Decimal("16.20")
)
r = self.order.refunds.get()
assert r.state == OrderRefund.REFUND_STATE_DONE
@@ -682,11 +702,11 @@ class SubEventCancelTests(TestCase):
self.order.status = Order.STATUS_PAID
self.order.save()
- cancel_event(
+ self._cancel_with_dryrun(
self.event.pk, subevent=self.se1.pk,
auto_refund=True, keep_fee_fixed="0.00", keep_fee_percentage="0.00", keep_fee_per_ticket="2.00",
send=False, send_subject="Event canceled", send_message="Event canceled :-(",
- user=None
+ user=None, expected_refunds=Decimal("21.00")
)
r = self.order.refunds.get()
assert r.state == OrderRefund.REFUND_STATE_DONE