|
@@ -5,11 +5,13 @@ import datetime
|
|
|
import random
|
|
|
import uuid
|
|
|
import os
|
|
|
+import re
|
|
|
from decimal import Decimal
|
|
|
|
|
|
from django.db import models
|
|
|
from django.db.models.signals import post_save
|
|
|
from django.dispatch import receiver
|
|
|
+from django.utils.encoding import python_2_unicode_compatible
|
|
|
|
|
|
from coin.offers.models import OfferSubscription
|
|
|
from coin.members.models import Member
|
|
@@ -33,6 +35,58 @@ def invoice_pdf_filename(instance, filename):
|
|
|
instance.number,
|
|
|
uuid.uuid4())
|
|
|
|
|
|
+@python_2_unicode_compatible
|
|
|
+class InvoiceNumber:
|
|
|
+ RE_INVOICE_NUMBER = re.compile(
|
|
|
+ r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<index>\d{6})')
|
|
|
+
|
|
|
+ def __init__(self, date, index):
|
|
|
+ self.date = date
|
|
|
+ self.index = index
|
|
|
+
|
|
|
+ def get_next(self):
|
|
|
+ return InvoiceNumber(self.date, self.index + 1)
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ return '{:%Y-%m}-{:0>6}'.format(self.date, self.index)
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def parse(cls, string):
|
|
|
+ m = cls.RE_INVOICE_NUMBER.match(string)
|
|
|
+ if not m:
|
|
|
+ raise ValueError('Not a valid invoice number: "{}"'.format(string))
|
|
|
+
|
|
|
+ return cls(
|
|
|
+ datetime.date(
|
|
|
+ year=int(m.group('year')),
|
|
|
+ month=int(m.group('month')),
|
|
|
+ day=1),
|
|
|
+ int(m.group('index')))
|
|
|
+
|
|
|
+
|
|
|
+class InvoiceQuerySet(models.QuerySet):
|
|
|
+ def get_next_invoice_number(self, date):
|
|
|
+ last_invoice_number_str = self._get_last_invoice_number(date)
|
|
|
+
|
|
|
+ if last_invoice_number_str is None:
|
|
|
+ # It's the first bill of the month
|
|
|
+ invoice_number = InvoiceNumber(date, 1)
|
|
|
+ else:
|
|
|
+ invoice_number = InvoiceNumber.parse(last_invoice_number_str).get_next()
|
|
|
+
|
|
|
+ return str(invoice_number)
|
|
|
+
|
|
|
+ def _get_last_invoice_number(self, date):
|
|
|
+ return self.with_valid_number().aggregate(
|
|
|
+ models.Max('number'))['number__max']
|
|
|
+
|
|
|
+ def with_valid_number(self):
|
|
|
+ """ Used for number format/logic migration
|
|
|
+
|
|
|
+ - from YYYYMM-RRR-RRR (RRR-RRR being random numbers)
|
|
|
+ - to YYYY-MM-NNNNNN (NNNNNN being sequence)
|
|
|
+ """
|
|
|
+ return self.filter(number__regex=InvoiceNumber.RE_INVOICE_NUMBER.pattern)
|
|
|
|
|
|
class Invoice(models.Model):
|
|
|
|
|
@@ -121,6 +175,10 @@ class Invoice(models.Model):
|
|
|
self.save()
|
|
|
self.generate_pdf()
|
|
|
|
|
|
+ def save(self, *args, **kwargs):
|
|
|
+ self.number = Invoice.objects.get_next_invoice_number(self.date)
|
|
|
+ super(Invoice, self).save(*args, **kwargs)
|
|
|
+
|
|
|
def is_pdf_exists(self):
|
|
|
return (self.validated
|
|
|
and bool(self.pdf)
|
|
@@ -136,6 +194,7 @@ class Invoice(models.Model):
|
|
|
class Meta:
|
|
|
verbose_name = 'facture'
|
|
|
|
|
|
+ objects = InvoiceQuerySet().as_manager()
|
|
|
|
|
|
class InvoiceDetail(models.Model):
|
|
|
|