forked from CGM_Public/pretix_original
107 lines
4.2 KiB
Python
107 lines
4.2 KiB
Python
import random
|
|
import string
|
|
from datetime import date
|
|
from decimal import Decimal
|
|
|
|
from django.db import DatabaseError, models
|
|
from django.db.models import Max
|
|
|
|
|
|
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(
|
|
org=instance.event.organizer.slug, ev=instance.event.slug,
|
|
no=instance.invoice_no, code=instance.order.code,
|
|
secret=secret
|
|
)
|
|
|
|
|
|
class Invoice(models.Model):
|
|
"""
|
|
Represents an invoice that is issued because of an order. Because invoices are legally required
|
|
not to change, this object duplicates a lot of data (e.g. the invoice address).
|
|
|
|
:param order: The associated order
|
|
:type order: Order
|
|
:param event: The event this belongs to (for convenience)
|
|
:type event: Event
|
|
:param invoice_no: The human-readable, event-unique invoice number
|
|
:type invoice_no: int
|
|
:param is_cancellation: Whether or not this is a cancellation instead of an invoice
|
|
:type is_cancellation: bool
|
|
:param refers: A link to another invoice this invoice refers to, e.g. the cancelled invoice in a cancellation
|
|
:type refers: Invoice
|
|
:param invoice_from: The sender address
|
|
:type invoice_from: str
|
|
:param invoice_to: The receiver address
|
|
:type invoice_to: str
|
|
:param date: The invoice date
|
|
:type date: date
|
|
:param locale: The locale in which the invoice should be printed
|
|
:type locale: str
|
|
:param additional_text: Additional text for the invoice
|
|
:type additional_text: str
|
|
:param file: The filename of the rendered invoice
|
|
:type file: File
|
|
"""
|
|
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)
|
|
is_cancellation = models.BooleanField(default=False)
|
|
refers = models.ForeignKey('Invoice', related_name='refered', null=True, blank=True)
|
|
invoice_from = models.TextField()
|
|
invoice_to = models.TextField()
|
|
date = models.DateField(default=date.today)
|
|
locale = models.CharField(max_length=50, default='en')
|
|
additional_text = models.TextField(blank=True)
|
|
file = models.FileField(null=True, blank=True, upload_to=invoice_filename)
|
|
|
|
def save(self, *args, **kwargs):
|
|
if not self.order:
|
|
raise ValueError('Every invoice needs to be connected to an order')
|
|
if not self.event:
|
|
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
|
|
try:
|
|
return super().save(*args, **kwargs)
|
|
except DatabaseError:
|
|
# Suppress duplicate key errors and try again
|
|
if i == 9:
|
|
raise
|
|
return super().save(*args, **kwargs)
|
|
|
|
@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)
|
|
|
|
class Meta:
|
|
unique_together = ('event', 'invoice_no')
|
|
|
|
|
|
class InvoiceLine(models.Model):
|
|
"""
|
|
One position listed on an Invoice.
|
|
|
|
:param invoice: The invoice this belongs to
|
|
:type invoice: Invoice
|
|
:param description: The item description
|
|
:type description: str
|
|
:param gross_value: The gross value
|
|
:type gross_value: decimal.Decimal
|
|
:param tax_value: The included tax (as an absolute value)
|
|
:type tax_value: decimal.Decimal
|
|
:param tax_rate: The applied tax rate in percent
|
|
:type tax_rate: decimal.Decimal
|
|
"""
|
|
invoice = models.ForeignKey('Invoice', related_name='lines')
|
|
description = models.TextField()
|
|
gross_value = models.DecimalField(max_digits=10, decimal_places=2)
|
|
tax_value = models.DecimalField(max_digits=10, decimal_places=2, default=Decimal('0.00'))
|
|
tax_rate = models.DecimalField(max_digits=7, decimal_places=2, default=Decimal('0.00'))
|