diff --git a/src/pretix/api/serializers/event.py b/src/pretix/api/serializers/event.py index 34afa9ef5d..7c1c739d45 100644 --- a/src/pretix/api/serializers/event.py +++ b/src/pretix/api/serializers/event.py @@ -597,6 +597,7 @@ class EventSettingsSerializer(serializers.Serializer): 'invoice_numbers_consecutive', 'invoice_numbers_prefix', 'invoice_numbers_prefix_cancellations', + 'invoice_numbers_counter_length', 'invoice_attendee_name', 'invoice_include_expire_date', 'invoice_address_explanation_text', diff --git a/src/pretix/base/models/invoices.py b/src/pretix/base/models/invoices.py index e617dc8d05..1692ac2895 100644 --- a/src/pretix/base/models/invoices.py +++ b/src/pretix/base/models/invoices.py @@ -116,8 +116,8 @@ class Invoice(models.Model): objects = ScopedManager(organizer='event__organizer') @staticmethod - def _to_numeric_invoice_number(number): - return '{:05d}'.format(int(number)) + def _to_numeric_invoice_number(number, places): + return ('{:0%dd}' % places).format(int(number)) @property def full_invoice_from(self): @@ -173,7 +173,7 @@ class Invoice(models.Model): ] return '\n'.join([p.strip() for p in parts if p and p.strip()]) - def _get_numeric_invoice_number(self): + def _get_numeric_invoice_number(self, c_length): numeric_invoices = Invoice.objects.filter( event__organizer=self.event.organizer, prefix=self.prefix, @@ -182,7 +182,7 @@ class Invoice(models.Model): ).aggregate( max=Max('numeric_number') )['max'] or 0 - return self._to_numeric_invoice_number(numeric_invoices + 1) + return self._to_numeric_invoice_number(numeric_invoices + 1, c_length) def _get_invoice_number_from_order(self): return '{order}-{count}'.format( @@ -209,7 +209,7 @@ class Invoice(models.Model): self.prefix += 'TEST-' for i in range(10): if self.event.settings.get('invoice_numbers_consecutive'): - self.invoice_no = self._get_numeric_invoice_number() + self.invoice_no = self._get_numeric_invoice_number(self.event.settings.invoice_numbers_counter_length) else: self.invoice_no = self._get_invoice_number_from_order() try: diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py index e176198a06..a73b8823c3 100644 --- a/src/pretix/base/settings.py +++ b/src/pretix/base/settings.py @@ -309,6 +309,16 @@ DEFAULTS = { help_text=_("The expiration date will not be shown if the invoice is generated after the order is paid."), ) }, + 'invoice_numbers_counter_length': { + 'default': '5', + 'type': int, + 'form_class': forms.IntegerField, + 'serializer_class': serializers.IntegerField, + 'form_kwargs': dict( + label=_("Minimum length of invoice number after prefix"), + help_text=_("The part of your invoice number after your prefix will be filled up with leading zeros up to this length, e.g. INV-001 or INV-00001."), + ) + }, 'invoice_numbers_consecutive': { 'default': 'True', 'type': bool, diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index d0e6750852..350063623c 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -677,6 +677,7 @@ class InvoiceSettingsForm(SettingsForm): 'invoice_numbers_consecutive', 'invoice_numbers_prefix', 'invoice_numbers_prefix_cancellations', + 'invoice_numbers_counter_length', 'invoice_address_explanation_text', 'invoice_email_attachment', 'invoice_address_from_name', diff --git a/src/pretix/control/templates/pretixcontrol/event/invoicing.html b/src/pretix/control/templates/pretixcontrol/event/invoicing.html index 85409aa64c..41a300065f 100644 --- a/src/pretix/control/templates/pretixcontrol/event/invoicing.html +++ b/src/pretix/control/templates/pretixcontrol/event/invoicing.html @@ -19,6 +19,7 @@ {% bootstrap_field form.invoice_numbers_consecutive layout="control" %} {% bootstrap_field form.invoice_numbers_prefix layout="control" %} {% bootstrap_field form.invoice_numbers_prefix_cancellations layout="control" %} + {% bootstrap_field form.invoice_numbers_counter_length layout="control" %}
{% trans "Address form" %} diff --git a/src/tests/base/test_invoices.py b/src/tests/base/test_invoices.py index f0d371b192..2a22411ff9 100644 --- a/src/tests/base/test_invoices.py +++ b/src/tests/base/test_invoices.py @@ -369,6 +369,12 @@ def test_invoice_numbers(env): inv8 = generate_invoice(order) inv23 = generate_invoice(order2) + event.settings.set('invoice_numbers_counter_length', 6) + inv24 = generate_invoice(order) + event.settings.set('invoice_numbers_counter_length', 1) + inv25 = generate_invoice(order) + inv26 = generate_invoice(order) + # expected behaviour for switching between numbering formats or dealing with gaps assert inv1.invoice_no == '00001' assert inv2.invoice_no == '00002' @@ -384,6 +390,9 @@ def test_invoice_numbers(env): assert inv22.invoice_no == '{}-2'.format(order2.code) # but consecutively in this mode assert inv23.invoice_no == '00007' + assert inv24.invoice_no == '000008' + assert inv25.invoice_no == '9' + assert inv26.invoice_no == '10' # test Invoice.number, too assert inv1.number == '{}-00001'.format(event.slug.upper()) @@ -441,13 +450,14 @@ def test_invoice_number_prefixes(env): event2.settings.set('invoice_numbers_prefix', 'inv_') event2.settings.set('invoice_numbers_prefix_cancellations', 'crd_') event2.settings.set('invoice_numbers_consecutive', True) + event2.settings.set('invoice_numbers_counter_length', 4) i = generate_invoice(order2) - assert i.number == 'inv_00001' - assert generate_cancellation(i).number == 'crd_00001' + assert i.number == 'inv_0001' + assert generate_cancellation(i).number == 'crd_0001' event2.settings.set('invoice_numbers_prefix', 'inv_%Y%m%d_') i = generate_invoice(order2) - assert i.number == 'inv_%s_00001' % now().date().strftime('%Y%m%d') + assert i.number == 'inv_%s_0001' % now().date().strftime('%Y%m%d') # Test database uniqueness check with pytest.raises(DatabaseError):