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.
This commit is contained in:
Raphael Michel
2026-01-26 09:30:10 +01:00
parent 5c8e785a6f
commit 15c2c39ac4
8 changed files with 46 additions and 4 deletions
+1
View File
@@ -806,6 +806,7 @@ class EventSettingsSerializer(SettingsSerializer):
'invoice_reissue_after_modify',
'invoice_include_free',
'invoice_generate',
'invoice_generate_only_business',
'invoice_period',
'invoice_numbers_consecutive',
'invoice_numbers_prefix',
+1 -1
View File
@@ -2031,7 +2031,7 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
else:
order = Order.objects.select_for_update(of=OF_SELF).get(pk=inv.order_id)
c = generate_cancellation(inv)
if inv.order.status != Order.STATUS_CANCELED:
if invoice_qualified(order):
inv = generate_invoice(order)
else:
inv = c
+13 -2
View File
@@ -521,9 +521,20 @@ def invoice_pdf_task(invoice: int):
def invoice_qualified(order: Order):
if order.total == Decimal('0.00') or order.require_approval or \
order.sales_channel.identifier not in order.event.settings.get('invoice_generate_sales_channels'):
if order.total == Decimal('0.00'):
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
+9
View File
@@ -1225,6 +1225,15 @@ DEFAULTS = {
'default': json.dumps(['web']),
'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': {
'default': '',
'type': str,
+1
View File
@@ -939,6 +939,7 @@ class InvoiceSettingsForm(EventSettingsValidationMixin, SettingsForm):
'invoice_show_payments',
'invoice_reissue_after_modify',
'invoice_generate',
'invoice_generate_only_business',
'invoice_period',
'invoice_attendee_name',
'invoice_event_location',
@@ -12,6 +12,7 @@
<legend>{% trans "Invoice generation" %}</legend>
{% bootstrap_field form.invoice_generate 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_organizer layout="control" %}
{% bootstrap_field form.invoice_language layout="control" %}
+1 -1
View File
@@ -1742,7 +1742,7 @@ class OrderInvoiceReissue(OrderView):
messages.error(self.request, _('The invoice has been cleaned of personal data.'))
else:
c = generate_cancellation(inv)
if order.status not in (Order.STATUS_CANCELED, Order.STATUS_EXPIRED):
if invoice_qualified(order):
inv = generate_invoice(order)
else:
inv = c
+19
View File
@@ -612,6 +612,25 @@ def test_sales_channels_qualify(env):
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 is_addon(item):
is_addon, id, price = item