123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- # -*- coding: utf-8 -*-
- import datetime
- import random
- import uuid
- from decimal import Decimal
- from django.db import models
- from django.db.models.signals import post_save
- from django.dispatch import receiver
- from coin.offers.models import OfferSubscription
- from coin.members.models import Member
- from coin.html2pdf import render_as_pdf
- def next_invoice_number():
- "Détermine un numéro de facture aléatoire"
- return u'%s%02i-%i-%i' % (datetime.date.today().year,
- datetime.date.today().month,
- random.randrange(100, 999),
- random.randrange(100, 999))
- class Invoice(models.Model):
- INVOICES_STATUS_CHOICES = (
- ('open', u'A payer'),
- ('closed', u'Reglée'),
- ('trouble', u'Litige')
- )
- validated = models.BooleanField(default=False, verbose_name='Facture validée')
- number = models.CharField(max_length=25,
- default=next_invoice_number,
- unique=True,
- verbose_name='Numéro')
- status = models.CharField(max_length=50, choices=INVOICES_STATUS_CHOICES,
- default='open',
- verbose_name='Statut')
- date = models.DateField(default=datetime.date.today, null=True)
- date_due = models.DateField(
- default=(datetime.date(datetime.date.today().year,
- datetime.date.today().month + 1, 1) -
- datetime.timedelta(days=1)),
- null=True,
- verbose_name=u'Date d\'échéance de paiement')
- member = models.ForeignKey(Member, null=True, blank=True, default=None,
- related_name='invoices',
- verbose_name='Membre',
- on_delete=models.SET_NULL)
- pdf = models.FileField(upload_to='invoices',
- null=True, blank=True,
- verbose_name=u'Facture en PDF')
- def amount(self):
- "Calcul le montant de la facture en fonction des éléments de détails"
- total = Decimal('0.0')
- for detail in self.details.all():
- total += detail.total()
- return total.quantize(Decimal('0.01'))
- amount.short_description = 'Montant'
- def amount_paid(self):
- """
- Calcul le montant payé de la facture en fonction des éléments
- de paiements
- """
- total = Decimal('0.0')
- for payment in self.payments.all():
- total += payment.amount
- return total.quantize(Decimal('0.01'))
- amount_paid.short_description = 'Montant payé'
- def amount_remaining_to_pay(self):
- """
- Calcul le montant restant à payer
- """
- return self.amount() - self.amount_paid()
- amount_remaining_to_pay.short_description = 'Reste à payer'
- def has_owner(self, username):
- "Check if passed username (ex gmajax) is owner of the invoice"
- return (self.member.username == username)
- def generate_pdf(self):
- "Make and store a pdf file for the invoice"
- pdf_file = render_as_pdf('billing/invoice_pdf.html', {"invoice": self})
- self.pdf.save(u'%s_%s.pdf' % (self.number, uuid.uuid4()), pdf_file)
- def validate(self):
- """
- Switch invoice to validate mode. This set to False the draft field
- and generate the pdf
- """
- # if not self.is_validated():
- self.validated = True
- self.save()
- self.generate_pdf()
- def is_validated(self):
- return self.validated and bool(self.pdf)
- def __unicode__(self):
- return u'#%s %0.2f€ %s' % (self.number, self.amount(), self.date_due)
- class Meta:
- verbose_name = 'facture'
- class InvoiceDetail(models.Model):
- label = models.CharField(max_length=100)
- amount = models.DecimalField(max_digits=5, decimal_places=2,
- verbose_name='Montant')
- quantity = models.DecimalField(null=True, verbose_name=u'Quantité',
- default=1.0, decimal_places=2, max_digits=4)
- tax = models.DecimalField(null=True, default=0.0, decimal_places=2,
- max_digits=4, verbose_name='TVA',
- help_text='en %')
- invoice = models.ForeignKey(Invoice, verbose_name='Facture',
- related_name='details')
- offersubscription = models.ForeignKey(OfferSubscription, null=True,
- blank=True, default=None,
- verbose_name='Abonnement')
- period_from = models.DateField(
- default=datetime.date(datetime.date.today().year,
- datetime.date.today().month, 1),
- null=True,
- blank=True,
- verbose_name=u'Début de période',
- help_text=u'Date de début de période sur laquelle est facturé cet item')
- period_to = models.DateField(
- default=(datetime.date(datetime.date.today().year,
- datetime.date.today().month + 1, 1) -
- datetime.timedelta(days=1)),
- null=True,
- blank=True,
- verbose_name=u'Fin de période',
- help_text=u'Date de fin de période sur laquelle est facturé cet item')
- def __unicode__(self):
- return self.label
- def total(self):
- "Calcul le total"
- return (self.amount * (self.tax / Decimal('100.0') +
- Decimal('1.0')) * self.quantity).quantize(Decimal('0.01'))
- class Meta:
- verbose_name = 'détail de facture'
- class Payment(models.Model):
- PAYMENT_MEAN_CHOICES = (
- ('cash', u'Espèces'),
- ('check', u'Chèque'),
- ('transfer', u'Virement'),
- ('other', u'Autre')
- )
- payment_mean = models.CharField(max_length=100, null=True,
- default='transfer',
- choices=PAYMENT_MEAN_CHOICES,
- verbose_name='Moyen de paiement')
- amount = models.DecimalField(max_digits=5, decimal_places=2, null=True,
- verbose_name='Montant')
- date = models.DateField(default=datetime.date.today)
- invoice = models.ForeignKey(Invoice, verbose_name='Facture',
- related_name='payments')
- def __unicode__(self):
- return u'Paiment de %0.2f€' % self.amount
- class Meta:
- verbose_name = 'paiement'
- @receiver(post_save, sender=Payment)
- def set_invoice_as_paid_if_needed(sender, instance, **kwargs):
- """
- Lorsqu'un paiement est enregistré, vérifie si la facture est alors
- complétement payée. Dans ce cas elle passe en réglée
- """
- if (instance.invoice.amount_paid >= instance.invoice.amount and
- instance.invoice.status == 'open'):
- instance.invoice.status = 'closed'
- instance.invoice.save()
|