forked from CGM_Public/pretix_original
Invoices: Allow issuing invoices only to businesses (Z#23220397) (#5807)
* Invoices: Allow issuing invoices only to businesses In situations where every invoice has a significant accounting cost and consumers usually do not need invoices, this can save a lot of money or effort. * Improve backend UI if not qualified for invoice
This commit is contained in:
@@ -806,6 +806,7 @@ class EventSettingsSerializer(SettingsSerializer):
|
|||||||
'invoice_reissue_after_modify',
|
'invoice_reissue_after_modify',
|
||||||
'invoice_include_free',
|
'invoice_include_free',
|
||||||
'invoice_generate',
|
'invoice_generate',
|
||||||
|
'invoice_generate_only_business',
|
||||||
'invoice_period',
|
'invoice_period',
|
||||||
'invoice_numbers_consecutive',
|
'invoice_numbers_consecutive',
|
||||||
'invoice_numbers_prefix',
|
'invoice_numbers_prefix',
|
||||||
|
|||||||
@@ -2031,7 +2031,7 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
else:
|
else:
|
||||||
order = Order.objects.select_for_update(of=OF_SELF).get(pk=inv.order_id)
|
order = Order.objects.select_for_update(of=OF_SELF).get(pk=inv.order_id)
|
||||||
c = generate_cancellation(inv)
|
c = generate_cancellation(inv)
|
||||||
if inv.order.status != Order.STATUS_CANCELED:
|
if invoice_qualified(order):
|
||||||
inv = generate_invoice(order)
|
inv = generate_invoice(order)
|
||||||
else:
|
else:
|
||||||
inv = c
|
inv = c
|
||||||
|
|||||||
@@ -521,9 +521,20 @@ def invoice_pdf_task(invoice: int):
|
|||||||
|
|
||||||
|
|
||||||
def invoice_qualified(order: Order):
|
def invoice_qualified(order: Order):
|
||||||
if order.total == Decimal('0.00') or order.require_approval or \
|
if order.total == Decimal('0.00'):
|
||||||
order.sales_channel.identifier not in order.event.settings.get('invoice_generate_sales_channels'):
|
|
||||||
return False
|
return False
|
||||||
|
if order.require_approval:
|
||||||
|
return False
|
||||||
|
if order.sales_channel.identifier not in order.event.settings.invoice_generate_sales_channels:
|
||||||
|
return False
|
||||||
|
if order.status in (Order.STATUS_CANCELED, Order.STATUS_EXPIRED):
|
||||||
|
return False
|
||||||
|
if order.event.settings.invoice_generate_only_business:
|
||||||
|
try:
|
||||||
|
ia = order.invoice_address
|
||||||
|
return ia.is_business
|
||||||
|
except InvoiceAddress.DoesNotExist:
|
||||||
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1225,6 +1225,15 @@ DEFAULTS = {
|
|||||||
'default': json.dumps(['web']),
|
'default': json.dumps(['web']),
|
||||||
'type': list
|
'type': list
|
||||||
},
|
},
|
||||||
|
'invoice_generate_only_business': {
|
||||||
|
'default': 'False',
|
||||||
|
'type': bool,
|
||||||
|
'form_class': forms.BooleanField,
|
||||||
|
'serializer_class': serializers.BooleanField,
|
||||||
|
'form_kwargs': dict(
|
||||||
|
label=_("Only issue invoices to business customers"),
|
||||||
|
)
|
||||||
|
},
|
||||||
'invoice_address_from': {
|
'invoice_address_from': {
|
||||||
'default': '',
|
'default': '',
|
||||||
'type': str,
|
'type': str,
|
||||||
|
|||||||
@@ -939,6 +939,7 @@ class InvoiceSettingsForm(EventSettingsValidationMixin, SettingsForm):
|
|||||||
'invoice_show_payments',
|
'invoice_show_payments',
|
||||||
'invoice_reissue_after_modify',
|
'invoice_reissue_after_modify',
|
||||||
'invoice_generate',
|
'invoice_generate',
|
||||||
|
'invoice_generate_only_business',
|
||||||
'invoice_period',
|
'invoice_period',
|
||||||
'invoice_attendee_name',
|
'invoice_attendee_name',
|
||||||
'invoice_event_location',
|
'invoice_event_location',
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
<legend>{% trans "Invoice generation" %}</legend>
|
<legend>{% trans "Invoice generation" %}</legend>
|
||||||
{% bootstrap_field form.invoice_generate layout="control" %}
|
{% bootstrap_field form.invoice_generate layout="control" %}
|
||||||
{% bootstrap_field form.invoice_generate_sales_channels layout="control" %}
|
{% bootstrap_field form.invoice_generate_sales_channels layout="control" %}
|
||||||
|
{% bootstrap_field form.invoice_generate_only_business layout="control" %}
|
||||||
{% bootstrap_field form.invoice_email_attachment layout="control" %}
|
{% bootstrap_field form.invoice_email_attachment layout="control" %}
|
||||||
{% bootstrap_field form.invoice_email_organizer layout="control" %}
|
{% bootstrap_field form.invoice_email_organizer layout="control" %}
|
||||||
{% bootstrap_field form.invoice_language layout="control" %}
|
{% bootstrap_field form.invoice_language layout="control" %}
|
||||||
|
|||||||
@@ -353,7 +353,7 @@
|
|||||||
data-toggle="tooltip"
|
data-toggle="tooltip"
|
||||||
title="{% trans 'Generate a cancellation document for this invoice and create a new invoice with a new invoice number.' %}"
|
title="{% trans 'Generate a cancellation document for this invoice and create a new invoice with a new invoice number.' %}"
|
||||||
{% endif %}>
|
{% endif %}>
|
||||||
{% if order.status == "c" %}
|
{% if order.status == "c" or not invoice_qualified %}
|
||||||
{% trans "Generate cancellation" %}
|
{% trans "Generate cancellation" %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans "Cancel and reissue" %}
|
{% trans "Cancel and reissue" %}
|
||||||
|
|||||||
@@ -509,9 +509,10 @@ class OrderView(EventPermissionRequiredMixin, DetailView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
ctx = super().get_context_data(**kwargs)
|
ctx = super().get_context_data(**kwargs)
|
||||||
ctx['can_generate_invoice'] = invoice_qualified(self.order) and (
|
ctx['invoice_qualified'] = invoice_qualified(self.order)
|
||||||
|
ctx['can_generate_invoice'] = ctx['invoice_qualified'] and (
|
||||||
self.request.event.settings.invoice_generate in ('admin', 'user', 'paid', 'user_paid', 'True')
|
self.request.event.settings.invoice_generate in ('admin', 'user', 'paid', 'user_paid', 'True')
|
||||||
) and self.order.status in (Order.STATUS_PAID, Order.STATUS_PENDING) and (
|
) and (
|
||||||
not self.order.invoices.exists()
|
not self.order.invoices.exists()
|
||||||
or self.order.invoices.filter(is_cancellation=True).count() >= self.order.invoices.filter(is_cancellation=False).count()
|
or self.order.invoices.filter(is_cancellation=True).count() >= self.order.invoices.filter(is_cancellation=False).count()
|
||||||
)
|
)
|
||||||
@@ -1742,14 +1743,15 @@ class OrderInvoiceReissue(OrderView):
|
|||||||
messages.error(self.request, _('The invoice has been cleaned of personal data.'))
|
messages.error(self.request, _('The invoice has been cleaned of personal data.'))
|
||||||
else:
|
else:
|
||||||
c = generate_cancellation(inv)
|
c = generate_cancellation(inv)
|
||||||
if order.status not in (Order.STATUS_CANCELED, Order.STATUS_EXPIRED):
|
if invoice_qualified(order):
|
||||||
inv = generate_invoice(order)
|
inv = generate_invoice(order)
|
||||||
|
messages.success(self.request, _('The invoice has been reissued.'))
|
||||||
else:
|
else:
|
||||||
inv = c
|
inv = c
|
||||||
|
messages.success(self.request, _('The invoice has been canceled.'))
|
||||||
order.log_action('pretix.event.order.invoice.reissued', user=self.request.user, data={
|
order.log_action('pretix.event.order.invoice.reissued', user=self.request.user, data={
|
||||||
'invoice': inv.pk
|
'invoice': inv.pk
|
||||||
})
|
})
|
||||||
messages.success(self.request, _('The invoice has been reissued.'))
|
|
||||||
return redirect(self.get_order_url())
|
return redirect(self.get_order_url())
|
||||||
|
|
||||||
def get(self, *args, **kwargs): # NOQA
|
def get(self, *args, **kwargs): # NOQA
|
||||||
|
|||||||
@@ -612,6 +612,25 @@ def test_sales_channels_qualify(env):
|
|||||||
assert invoice_qualified(order) is False
|
assert invoice_qualified(order) is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_business_only(env):
|
||||||
|
event, order = env
|
||||||
|
event.settings.set('invoice_generate', 'admin')
|
||||||
|
event.settings.set('invoice_generate_only_business', True)
|
||||||
|
order.total = Decimal('42.00')
|
||||||
|
|
||||||
|
ia = InvoiceAddress.objects.create(company='Acme Company', street='221B Baker Street', is_business=True,
|
||||||
|
zipcode='12345', city='London', country_old='England', country='', order=order)
|
||||||
|
|
||||||
|
assert invoice_qualified(order) is True
|
||||||
|
|
||||||
|
ia.is_business = False
|
||||||
|
ia.save()
|
||||||
|
|
||||||
|
# Order with default Sales Channel (web)
|
||||||
|
assert invoice_qualified(order) is False
|
||||||
|
|
||||||
|
|
||||||
def test_addon_aware_groupby():
|
def test_addon_aware_groupby():
|
||||||
def is_addon(item):
|
def is_addon(item):
|
||||||
is_addon, id, price = item
|
is_addon, id, price = item
|
||||||
|
|||||||
Reference in New Issue
Block a user