diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index a62ed30c0e..d1348c67de 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -240,15 +240,17 @@ class Order(LoggedModel): has_pending_refund=Exists(pending_refund), ).annotate( pending_sum_t=F('total') - Coalesce(F('payment_sum'), 0) + Coalesce(F('refund_sum'), 0), - pending_sum_rc=-1 * F('payment_sum') + Coalesce(F('refund_sum'), 0), + pending_sum_rc=-1 * Coalesce(F('payment_sum'), 0) + Coalesce(F('refund_sum'), 0), ).annotate( is_overpaid=Case( When(~Q(status__in=(Order.STATUS_REFUNDED, Order.STATUS_CANCELED)) & Q(pending_sum_t__lt=0), then=Value('1')), When(Q(status__in=(Order.STATUS_REFUNDED, Order.STATUS_CANCELED)) & Q(pending_sum_rc__lt=0), then=Value('1')), - When(Q(status__in=(Order.STATUS_EXPIRED, Order.STATUS_PENDING)) & Q(pending_sum_t__lt=0), - then=Value('1')), + default=Value('0'), + output_field=models.IntegerField() + ), + is_pending_with_full_payment=Case( When(Q(status__in=(Order.STATUS_EXPIRED, Order.STATUS_PENDING)) & Q(pending_sum_t__lte=0) & Q(require_approval=False), then=Value('1')), diff --git a/src/pretix/control/forms/filter.py b/src/pretix/control/forms/filter.py index 0db1497437..3c59ddc4ad 100644 --- a/src/pretix/control/forms/filter.py +++ b/src/pretix/control/forms/filter.py @@ -209,6 +209,7 @@ class EventOrderFilterForm(OrderFilterForm): ('pa', _('Approval pending')), ('overpaid', _('Overpaid')), ('underpaid', _('Underpaid')), + ('pendingpaid', _('Pending (but fully paid)')) ), required=False, ) @@ -276,9 +277,11 @@ class EventOrderFilterForm(OrderFilterForm): qs = qs.filter( Q(~Q(status__in=(Order.STATUS_REFUNDED, Order.STATUS_CANCELED)) & Q(pending_sum_t__lt=0)) | Q(Q(status__in=(Order.STATUS_REFUNDED, Order.STATUS_CANCELED)) & Q(pending_sum_rc__lt=0)) - | Q(Q(status__in=(Order.STATUS_EXPIRED, Order.STATUS_PENDING)) & Q(pending_sum_t__lt=0)) - | Q(Q(status__in=(Order.STATUS_EXPIRED, Order.STATUS_PENDING)) & Q(pending_sum_t__lte=0) - & Q(require_approval=False)) + ) + elif fdata.get('status') == 'pendingpaid': + qs = qs.filter( + Q(status__in=(Order.STATUS_EXPIRED, Order.STATUS_PENDING)) & Q(pending_sum_t__lte=0) + & Q(require_approval=False) ) elif fdata.get('status') == 'underpaid': qs = qs.filter( diff --git a/src/pretix/control/templates/pretixcontrol/event/index.html b/src/pretix/control/templates/pretixcontrol/event/index.html index 0059716859..2f71270283 100644 --- a/src/pretix/control/templates/pretixcontrol/event/index.html +++ b/src/pretix/control/templates/pretixcontrol/event/index.html @@ -43,6 +43,17 @@ class="btn btn-primary">{% trans "Show orders pending approval" %} {% endif %} + {% if has_pending_orders_with_full_payment %} +
+ {% blocktrans trimmed %} + This event contains fully paid orders that are not marked as paid, probably + because no quota was left at the time their payment arrived. You should review the cases and consider + either refunding the customer or creating more space. + {% endblocktrans %} + {% trans "Show affected orders" %} +
+ {% endif %} {% if actions|length > 0 %}
diff --git a/src/pretix/control/templates/pretixcontrol/orders/index.html b/src/pretix/control/templates/pretixcontrol/orders/index.html index ec95d12892..8b6c04a609 100644 --- a/src/pretix/control/templates/pretixcontrol/orders/index.html +++ b/src/pretix/control/templates/pretixcontrol/orders/index.html @@ -137,6 +137,8 @@ {% trans "OVERPAID" %} {% elif o.is_underpaid %} {% trans "UNDERPAID" %} + {% elif o.is_pending_with_full_payment %} + {% trans "FULLY PAID" %} {% endif %} {{ o.total|money:request.event.currency }} diff --git a/src/pretix/control/views/dashboards.py b/src/pretix/control/views/dashboards.py index 1b95a50408..103fa02060 100644 --- a/src/pretix/control/views/dashboards.py +++ b/src/pretix/control/views/dashboards.py @@ -270,9 +270,9 @@ def event_index(request, organizer, event): ctx['has_overpaid_orders'] = Order.annotate_overpayments(request.event.orders).filter( Q(~Q(status__in=(Order.STATUS_REFUNDED, Order.STATUS_CANCELED)) & Q(pending_sum_t__lt=0)) | Q(Q(status__in=(Order.STATUS_REFUNDED, Order.STATUS_CANCELED)) & Q(pending_sum_rc__lt=0)) - | Q(Q(status__in=(Order.STATUS_EXPIRED, Order.STATUS_PENDING)) & Q(pending_sum_t__lt=0)) - | Q(Q(status__in=(Order.STATUS_EXPIRED, Order.STATUS_PENDING)) & Q(pending_sum_t__lte=0) - & Q(require_approval=False)) + ).exists() + ctx['has_pending_orders_with_full_payment'] = Order.annotate_overpayments(request.event.orders).filter( + Q(status__in=(Order.STATUS_EXPIRED, Order.STATUS_PENDING)) & Q(pending_sum_t__lte=0) & Q(require_approval=False) ).exists() ctx['has_pending_refunds'] = OrderRefund.objects.filter( order__event=request.event, diff --git a/src/tests/base/test_models.py b/src/tests/base/test_models.py index 56bc5cb538..5c859a8611 100644 --- a/src/tests/base/test_models.py +++ b/src/tests/base/test_models.py @@ -1018,7 +1018,8 @@ class OrderTestCase(BaseQuotaTestCase): assert self.order.pending_sum == Decimal('0.00') o = Order.annotate_overpayments(Order.objects.all()).first() assert not o.is_underpaid - assert o.is_overpaid + assert not o.is_overpaid + assert not o.is_pending_with_full_payment assert not o.has_pending_refund assert not o.has_external_refund