Add setting determining invoice number format (#193)

This commit is contained in:
Tobias Kunze
2016-08-16 21:18:40 +02:00
committed by Raphael Michel
parent 6628d65f9a
commit 4191f93ece
11 changed files with 177 additions and 8 deletions

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-08-16 06:36
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0028_auto_20160816_1242'),
]
operations = [
migrations.AddField(
model_name='invoice',
name='invoice_no_charfield',
field=models.CharField(db_index=True, default='', max_length=19),
preserve_default=False,
),
]

View File

@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-08-16 06:37
from __future__ import unicode_literals
from django.db import migrations
def forwards(apps, schema_editor):
Invoice = apps.get_model('pretixbase', 'Invoice')
for invoice in Invoice.objects.all():
invoice.invoice_no_charfield = '{:05d}'.format(invoice.invoice_no)
invoice.save()
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0028_invoice_invoice_no_charfield'),
]
operations = [
migrations.RunPython(forwards, migrations.RunPython.noop),
]

View File

@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-08-16 06:46
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0029_invoice_no_data'),
]
operations = [
migrations.AlterUniqueTogether(
name='invoice',
unique_together=set([('event', 'invoice_no_charfield')]),
),
migrations.RemoveField(
model_name='invoice',
name='invoice_no',
),
]

View File

@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-08-16 06:48
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0030_auto_20160816_0646'),
]
operations = [
migrations.RenameField(
model_name='invoice',
old_name='invoice_no_charfield',
new_name='invoice_no',
),
migrations.AlterUniqueTogether(
name='invoice',
unique_together=set([('event', 'invoice_no')]),
),
]

View File

@@ -10,10 +10,9 @@ from django.utils.functional import cached_property
def invoice_filename(instance, filename: str) -> str:
secret = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(16))
return 'invoices/{org}/{ev}/{ev}-{no:05d}-{code}-{secret}.pdf'.format(
return 'invoices/{org}/{ev}/{no}-{code}-{secret}.pdf'.format(
org=instance.event.organizer.slug, ev=instance.event.slug,
no=instance.invoice_no, code=instance.order.code,
secret=secret
no=instance.number, code=instance.order.code, secret=secret
)
@@ -47,7 +46,7 @@ class Invoice(models.Model):
"""
order = models.ForeignKey('Order', related_name='invoices', db_index=True)
event = models.ForeignKey('Event', related_name='invoices', db_index=True)
invoice_no = models.PositiveIntegerField(db_index=True)
invoice_no = models.CharField(max_length=19, db_index=True)
is_cancellation = models.BooleanField(default=False)
refers = models.ForeignKey('Invoice', related_name='refered', null=True, blank=True)
invoice_from = models.TextField()
@@ -57,6 +56,20 @@ class Invoice(models.Model):
additional_text = models.TextField(blank=True)
file = models.FileField(null=True, blank=True, upload_to=invoice_filename)
@staticmethod
def _to_numeric_invoice_number(number):
return '{:05d}'.format(int(number))
def _get_numeric_invoice_number(self):
numeric_invoices = Invoice.objects.filter(event=self.event).exclude(invoice_no__contains='-')
return self._to_numeric_invoice_number(numeric_invoices.count() + 1)
def _get_invoice_number_from_order(self):
return '{order}-{count}'.format(
order=self.order.code,
count=Invoice.objects.filter(event=self.event, order=self.order).count() + 1,
)
def save(self, *args, **kwargs):
if not self.order:
raise ValueError('Every invoice needs to be connected to an order')
@@ -64,8 +77,10 @@ class Invoice(models.Model):
self.event = self.order.event
if not self.invoice_no:
for i in range(10):
self.invoice_no = (Invoice.objects.filter(
event=self.event).aggregate(m=Max('invoice_no'))['m'] or 0) + 1
if self.event.settings.get('invoice_numbers_consecutive'):
self.invoice_no = self._get_numeric_invoice_number()
else:
self.invoice_no = self._get_invoice_number_from_order()
try:
return super().save(*args, **kwargs)
except DatabaseError:
@@ -74,12 +89,23 @@ class Invoice(models.Model):
raise
return super().save(*args, **kwargs)
def delete(self, *args, **kwargs):
"""
Deleting an Invoice would allow for the creation of another Invoice object
with the same invoice_no as the deleted one. For various reasons, invoice_no
should be reliably unique for an event.
"""
raise Exception('Invoices cannot be deleted, to guarantee uniqueness of Invoice.invoice_no in any event.')
@property
def number(self):
"""
Returns the invoice number in a human-readable string with the event slug prepended.
"""
return '%s-%05d' % (self.event.slug.upper(), self.invoice_no)
return '{event}-{code}'.format(
event=self.event.slug.upper(),
code=self.invoice_no
)
@cached_property
def canceled(self):

View File

@@ -175,7 +175,7 @@ class Order(LoggedModel):
An order code which is unique among all events of a single organizer,
built by contatenating the event slug and the order code.
"""
return self.event.slug.upper() + self.code
return '{event}-{code}'.format(event=self.event.slug.upper(), code=self.code)
def save(self, *args, **kwargs):
if not self.code:

View File

@@ -37,6 +37,10 @@ DEFAULTS = {
'default': 'False',
'type': bool,
},
'invoice_numbers_consecutive': {
'default': 'True',
'type': bool,
},
'reservation_time': {
'default': '30',
'type': int