diff --git a/src/pretix/base/migrations/0149_order_cancellation_date.py b/src/pretix/base/migrations/0149_order_cancellation_date.py new file mode 100644 index 0000000000..b454705a89 --- /dev/null +++ b/src/pretix/base/migrations/0149_order_cancellation_date.py @@ -0,0 +1,46 @@ +# Generated by Django 3.0.4 on 2020-03-25 14:40 + +from django.db import migrations, models +from django.db.models import OuterRef, Count, Subquery, Q +from django.utils.timezone import now + + +def fill_cancellation_date(apps, schema_editor): + Order = apps.get_model('pretixbase', 'Order') + LogEntry = apps.get_model('pretixbase', 'LogEntry') + OrderPosition = apps.get_model('pretixbase', 'OrderPosition') + + s = OrderPosition.all.filter( + order=OuterRef('pk'), + canceled=False, + ).order_by().values('order').annotate(k=Count('id')).values('k') + for o in Order.objects.annotate( + pcnt=Subquery(s) + ).filter( + Q(pcnt=0) | Q(pcnt__isnull=True) | Q(status="c") + ).values('id').iterator(): + le = LogEntry.objects.filter( + content_type__model="order", + object_id=o['id'], + action_type='pretix.event.order.canceled' + ).order_by('-datetime').only('datetime').first() + if le: + Order.objects.filter(pk=o['id']).update( + cancellation_date=le.datetime, + last_modified=now() + ) + + +class Migration(migrations.Migration): + dependencies = [ + ('pretixbase', '0148_cancellationrequest'), + ] + + operations = [ + migrations.AddField( + model_name='order', + name='cancellation_date', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.RunPython(fill_cancellation_date, migrations.RunPython.noop) + ] diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index 11924c4ce3..2c6fc368d8 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -152,6 +152,9 @@ class Order(LockModel, LoggedModel): datetime = models.DateTimeField( verbose_name=_("Date"), db_index=True ) + cancellation_date = models.DateTimeField( + null=True, blank=True + ) expires = models.DateTimeField( verbose_name=_("Expiration date") ) diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py index 77cba45de4..54cdc61241 100644 --- a/src/pretix/base/services/orders.py +++ b/src/pretix/base/services/orders.py @@ -109,10 +109,11 @@ def reactivate_order(order: Order, force: bool=False, user: User=None, auth=None order.status = Order.STATUS_PAID else: order.status = Order.STATUS_PENDING + order.cancellation_date = None 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.save(update_fields=['expires', 'status', 'cancellation_date']) order.log_action( 'pretix.event.order.reactivated', user=user, @@ -389,14 +390,16 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device raise OrderError(_('The cancellation fee cannot be higher than the payment credit of this order.')) order.status = Order.STATUS_PAID order.total = cancellation_fee - order.save(update_fields=['status', 'total']) + order.cancellation_date = now() + order.save(update_fields=['status', 'cancellation_date', 'total']) if i: invoices.append(generate_invoice(order)) else: with order.event.lock(): order.status = Order.STATUS_CANCELED - order.save(update_fields=['status']) + order.cancellation_date = now() + order.save(update_fields=['status', 'cancellation_date']) for position in order.positions.all(): if position.voucher: diff --git a/src/pretix/control/templates/pretixcontrol/order/index.html b/src/pretix/control/templates/pretixcontrol/order/index.html index 6c67458a0b..91da906cf8 100644 --- a/src/pretix/control/templates/pretixcontrol/order/index.html +++ b/src/pretix/control/templates/pretixcontrol/order/index.html @@ -158,6 +158,10 @@
{{ order.code }}
{% trans "Order date" %}
{{ order.datetime|date:"SHORT_DATETIME_FORMAT" }}
+ {% if order.cancellation_date %} +
{% trans "Cancellation date" %}
+
{{ order.cancellation_date|date:"SHORT_DATETIME_FORMAT" }}
+ {% endif %} {% if sales_channel %}
{% trans "Sales channel" %}
{{ sales_channel.verbose_name }}
diff --git a/src/tests/base/test_orders.py b/src/tests/base/test_orders.py index c67782395f..2066cfe309 100644 --- a/src/tests/base/test_orders.py +++ b/src/tests/base/test_orders.py @@ -606,6 +606,7 @@ class OrderCancelTests(TestCase): def test_cancel_unpaid(self): cancel_order(self.order.pk) self.order.refresh_from_db() + assert self.order.cancellation_date assert self.order.status == Order.STATUS_CANCELED assert self.order.all_logentries().last().action_type == 'pretix.event.order.canceled' assert self.order.invoices.count() == 2 @@ -2497,6 +2498,7 @@ class OrderReactivateTest(TestCase): code='FOO', event=self.event, email='dummy@dummy.test', status=Order.STATUS_CANCELED, locale='en', datetime=now(), expires=now() + timedelta(days=1), + cancellation_date=now(), total=Decimal('46.00'), ) self.ticket = Item.objects.create(event=self.event, name='Early-bird ticket', @@ -2549,6 +2551,7 @@ class OrderReactivateTest(TestCase): 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 + assert not self.order.cancellation_date @classscope(attr='o') def test_reactivate_sold_out(self):