Allow to cancel an order without creating a cancellation invoice

This commit is contained in:
Raphael Michel
2021-02-05 15:31:46 +01:00
parent f1a98b5c30
commit bba103156c
6 changed files with 47 additions and 10 deletions

View File

@@ -327,7 +327,7 @@ def deny_order(order, comment='', user=None, send_mail: bool=True, auth=None):
def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device=None, oauth_application=None, def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device=None, oauth_application=None,
cancellation_fee=None, keep_fees=None): cancellation_fee=None, keep_fees=None, cancel_invoice=True):
""" """
Mark this order as canceled Mark this order as canceled
:param order: The order to change :param order: The order to change
@@ -351,9 +351,10 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device
if not order.cancel_allowed(): if not order.cancel_allowed():
raise OrderError(_('You cannot cancel this order.')) raise OrderError(_('You cannot cancel this order.'))
invoices = [] invoices = []
i = order.invoices.filter(is_cancellation=False).last() if cancel_invoice:
if i and not i.refered.exists(): i = order.invoices.filter(is_cancellation=False).last()
invoices.append(generate_cancellation(i)) if i and not i.refered.exists():
invoices.append(generate_cancellation(i))
for position in order.positions.all(): for position in order.positions.all():
for gc in position.issued_gift_cards.all(): for gc in position.issued_gift_cards.all():
@@ -403,7 +404,7 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device
order.cancellation_date = now() order.cancellation_date = now()
order.save(update_fields=['status', 'cancellation_date', 'total']) order.save(update_fields=['status', 'cancellation_date', 'total'])
if i: if cancel_invoice and i:
invoices.append(generate_invoice(order)) invoices.append(generate_invoice(order))
else: else:
with order.event.lock(): with order.event.lock():
@@ -2152,11 +2153,12 @@ def _try_auto_refund(order, manual_refund=False, allow_partial=False, source=Ord
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,)) @app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,))
@scopes_disabled() @scopes_disabled()
def cancel_order(self, order: int, user: int=None, send_mail: bool=True, api_token=None, oauth_application=None, def cancel_order(self, order: int, user: int=None, send_mail: bool=True, api_token=None, oauth_application=None,
device=None, cancellation_fee=None, try_auto_refund=False, refund_as_giftcard=False, comment=None): device=None, cancellation_fee=None, try_auto_refund=False, refund_as_giftcard=False, comment=None,
cancel_invoice=True):
try: try:
try: try:
ret = _cancel_order(order, user, send_mail, api_token, device, oauth_application, ret = _cancel_order(order, user, send_mail, api_token, device, oauth_application,
cancellation_fee) cancellation_fee, cancel_invoice=cancel_invoice)
if try_auto_refund: if try_auto_refund:
_try_auto_refund(order, refund_as_giftcard=refund_as_giftcard, _try_auto_refund(order, refund_as_giftcard=refund_as_giftcard,
comment=comment) comment=comment)

View File

@@ -5,7 +5,7 @@ from urllib.parse import urlencode
from django import forms from django import forms
from django.apps import apps from django.apps import apps
from django.conf import settings from django.conf import settings
from django.db.models import Exists, F, Max, Model, OuterRef, Q, QuerySet from django.db.models import Exists, F, Max, Model, OuterRef, Q, QuerySet, Count
from django.db.models.functions import Coalesce, ExtractWeekDay from django.db.models.functions import Coalesce, ExtractWeekDay
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils.formats import date_format, localize from django.utils.formats import date_format, localize
@@ -153,6 +153,7 @@ class OrderFilterForm(FilterForm):
(Order.STATUS_CANCELED, _('Canceled (fully)')), (Order.STATUS_CANCELED, _('Canceled (fully)')),
('cp', _('Canceled (fully or with paid fee)')), ('cp', _('Canceled (fully or with paid fee)')),
('rc', _('Cancellation requested')), ('rc', _('Cancellation requested')),
('cni', _('Fully canceled but invoice not canceled')),
)), )),
(_('Payment process'), ( (_('Payment process'), (
(Order.STATUS_EXPIRED, _('Expired')), (Order.STATUS_EXPIRED, _('Expired')),
@@ -264,6 +265,18 @@ class OrderFilterForm(FilterForm):
status=Order.STATUS_PAID, status=Order.STATUS_PAID,
pending_sum_t__gt=0 pending_sum_t__gt=0
) )
elif s == 'cni':
i = Invoice.objects.filter(
order=OuterRef('pk'),
is_cancellation=False,
refered__isnull=True,
).order_by().values('order').annotate(k=Count('id')).values('k')
qs = qs.annotate(
icnt=i
).filter(
icnt__gt=0,
status=Order.STATUS_CANCELED,
)
elif s == 'pa': elif s == 'pa':
qs = qs.filter( qs = qs.filter(
status=Order.STATUS_PENDING, status=Order.STATUS_PENDING,

View File

@@ -117,6 +117,11 @@ class CancelForm(ConfirmPaymentForm):
'in your cancellation fee if you want to keep them. Please always enter a gross value, ' 'in your cancellation fee if you want to keep them. Please always enter a gross value, '
'tax will be calculated automatically.'), 'tax will be calculated automatically.'),
) )
cancel_invoice = forms.BooleanField(
label=_('Generate cancellation for invoice'),
initial=True,
required=False
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -130,6 +135,8 @@ class CancelForm(ConfirmPaymentForm):
self.fields['cancellation_fee'].max_value = prs self.fields['cancellation_fee'].max_value = prs
else: else:
del self.fields['cancellation_fee'] del self.fields['cancellation_fee']
if not self.instance.invoices.exists():
del self.fields['cancel_invoice']
def clean_cancellation_fee(self): def clean_cancellation_fee(self):
val = self.cleaned_data['cancellation_fee'] or Decimal('0.00') val = self.cleaned_data['cancellation_fee'] or Decimal('0.00')

View File

@@ -23,13 +23,16 @@
<input type="hidden" name="status" value="c"/> <input type="hidden" name="status" value="c"/>
{% bootstrap_form_errors form %} {% bootstrap_form_errors form %}
{% bootstrap_field form.send_email layout='' %} {% bootstrap_field form.send_email layout='' %}
{% if form.cancel_invoice %}
{% bootstrap_field form.cancel_invoice layout='' %}
{% endif %}
{% if form.cancellation_fee %} {% if form.cancellation_fee %}
{% bootstrap_field form.cancellation_fee layout='' %} {% bootstrap_field form.cancellation_fee layout='' %}
{% endif %} {% endif %}
<div class="row checkout-button-row"> <div class="row checkout-button-row">
<div class="col-md-4"> <div class="col-md-4">
<a class="btn btn-block btn-default btn-lg" <a class="btn btn-block btn-default btn-lg"
href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}"> href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}">
{% trans "No, take me back" %} {% trans "No, take me back" %}
</a> </a>
</div> </div>

View File

@@ -170,6 +170,10 @@
</span> </span>
{% endif %} {% endif %}
{{ o.total|money:request.event.currency }} {{ o.total|money:request.event.currency }}
{% if o.status == "c" and o.icnt %}
<br>
<span class="label label-warning">{% trans "INVOICE NOT CANCELED" %}</span>
{% endif %}
</td> </td>
<td class="text-right flip">{{ o.pcnt|default_if_none:"0" }}</td> <td class="text-right flip">{{ o.pcnt|default_if_none:"0" }}</td>
<td class="text-right flip">{% include "pretixcontrol/orders/fragment_order_status.html" with order=o %}</td> <td class="text-right flip">{% include "pretixcontrol/orders/fragment_order_status.html" with order=o %}</td>

View File

@@ -151,6 +151,11 @@ class OrderList(OrderSearchMixin, EventPermissionRequiredMixin, PaginationMixin,
s = OrderPosition.objects.filter( s = OrderPosition.objects.filter(
order=OuterRef('pk') order=OuterRef('pk')
).order_by().values('order').annotate(k=Count('id')).values('k') ).order_by().values('order').annotate(k=Count('id')).values('k')
i = Invoice.objects.filter(
order=OuterRef('pk'),
is_cancellation=False,
refered__isnull=True,
).order_by().values('order').annotate(k=Count('id')).values('k')
annotated = { annotated = {
o['pk']: o o['pk']: o
for o in for o in
@@ -158,10 +163,11 @@ class OrderList(OrderSearchMixin, EventPermissionRequiredMixin, PaginationMixin,
pk__in=[o.pk for o in ctx['orders']] pk__in=[o.pk for o in ctx['orders']]
).annotate( ).annotate(
pcnt=Subquery(s, output_field=IntegerField()), pcnt=Subquery(s, output_field=IntegerField()),
icnt=Subquery(i, output_field=IntegerField()),
has_cancellation_request=Exists(CancellationRequest.objects.filter(order=OuterRef('pk'))) has_cancellation_request=Exists(CancellationRequest.objects.filter(order=OuterRef('pk')))
).values( ).values(
'pk', 'pcnt', 'is_overpaid', 'is_underpaid', 'is_pending_with_full_payment', 'has_external_refund', 'pk', 'pcnt', 'is_overpaid', 'is_underpaid', 'is_pending_with_full_payment', 'has_external_refund',
'has_pending_refund', 'has_cancellation_request', 'computed_payment_refund_sum' 'has_pending_refund', 'has_cancellation_request', 'computed_payment_refund_sum', 'icnt'
) )
} }
@@ -177,6 +183,7 @@ class OrderList(OrderSearchMixin, EventPermissionRequiredMixin, PaginationMixin,
o.has_pending_refund = annotated.get(o.pk)['has_pending_refund'] o.has_pending_refund = annotated.get(o.pk)['has_pending_refund']
o.has_cancellation_request = annotated.get(o.pk)['has_cancellation_request'] o.has_cancellation_request = annotated.get(o.pk)['has_cancellation_request']
o.computed_payment_refund_sum = annotated.get(o.pk)['computed_payment_refund_sum'] o.computed_payment_refund_sum = annotated.get(o.pk)['computed_payment_refund_sum']
o.icnt = annotated.get(o.pk)['icnt']
o.sales_channel_obj = scs[o.sales_channel] o.sales_channel_obj = scs[o.sales_channel]
if ctx['page_obj'].paginator.count < 1000: if ctx['page_obj'].paginator.count < 1000:
@@ -1134,6 +1141,7 @@ class OrderTransition(OrderView):
try: try:
cancel_order(self.order.pk, user=self.request.user, cancel_order(self.order.pk, user=self.request.user,
send_mail=self.mark_canceled_form.cleaned_data['send_email'], send_mail=self.mark_canceled_form.cleaned_data['send_email'],
cancel_invoice=self.mark_canceled_form.cleaned_data.get('cancel_invoice', True),
cancellation_fee=self.mark_canceled_form.cleaned_data.get('cancellation_fee')) cancellation_fee=self.mark_canceled_form.cleaned_data.get('cancellation_fee'))
except OrderError as e: except OrderError as e:
messages.error(self.request, str(e)) messages.error(self.request, str(e))