models.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. # -*- coding: utf-8 -*-
  2. import datetime
  3. import random
  4. from decimal import Decimal
  5. from django.db import models
  6. from django.db.models.signals import post_save
  7. from django.dispatch import receiver
  8. from coin.offers.models import OfferSubscription
  9. from coin.members.models import Member
  10. def next_invoice_number():
  11. "Détermine un numéro de facture aléatoire"
  12. return u'%s%02i-%i-%i' % (datetime.date.today().year,
  13. datetime.date.today().month,
  14. random.randrange(100, 999),
  15. random.randrange(100, 999))
  16. class Invoice(models.Model):
  17. INVOICES_STATUS_CHOICES = (
  18. ('draft', u'Brouillon'),
  19. ('open', u'A payer'),
  20. ('closed', u'Reglée'),
  21. ('trouble', u'Litige')
  22. )
  23. number = models.CharField(max_length=25,
  24. default=next_invoice_number,
  25. unique=True,
  26. verbose_name='Numéro')
  27. status = models.CharField(max_length=50, choices=INVOICES_STATUS_CHOICES,
  28. default='draft',
  29. verbose_name='Statut')
  30. date = models.DateField(default=datetime.date.today, null=True)
  31. date_due = models.DateField(
  32. default=(datetime.date(datetime.date.today().year,
  33. datetime.date.today().month + 1, 1) -
  34. datetime.timedelta(days=1)),
  35. null=True,
  36. verbose_name=u'Date d\'échéance de paiement')
  37. member = models.ForeignKey(Member, null=True, blank=True, default=None,
  38. related_name='invoices',
  39. verbose_name='Membre',
  40. on_delete=models.SET_NULL)
  41. def amount(self):
  42. "Calcul le montant de la facture en fonction des éléments de détails"
  43. total = Decimal('0.0')
  44. for detail in self.details.all():
  45. total += detail.total()
  46. return total.quantize(Decimal('0.01'))
  47. amount.short_description = 'Montant'
  48. def amount_paid(self):
  49. """
  50. Calcul le montant payé de la facture en fonction des éléments
  51. de paiements
  52. """
  53. total = Decimal('0.0')
  54. for payment in self.payments.all():
  55. total += payment.amount
  56. return total.quantize(Decimal('0.01'))
  57. amount_paid.short_description = 'Montant payé'
  58. def amount_remaining_to_pay(self):
  59. """
  60. Calcul le montant restant à payer
  61. """
  62. return self.amount() - self.amount_paid()
  63. amount_remaining_to_pay.short_description = 'Reste à payer'
  64. def has_owner(self, username):
  65. "Check if passed username (ex gmajax) is owner of the invoice"
  66. return (self.username == username)
  67. def __unicode__(self):
  68. return u'#%s %0.2f€ %s' % (self.number, self.amount(), self.date_due)
  69. class Meta:
  70. verbose_name = 'facture'
  71. class InvoiceDetail(models.Model):
  72. label = models.CharField(max_length=100)
  73. amount = models.DecimalField(max_digits=5, decimal_places=2,
  74. verbose_name='Montant')
  75. quantity = models.DecimalField(null=True, verbose_name=u'Quantité',
  76. default=1.0, decimal_places=2, max_digits=4)
  77. tax = models.DecimalField(null=True, default=0.0, decimal_places=2,
  78. max_digits=4, verbose_name='TVA',
  79. help_text='en %')
  80. invoice = models.ForeignKey(Invoice, verbose_name='Facture',
  81. related_name='details')
  82. offersubscription = models.ForeignKey(OfferSubscription, null=True,
  83. blank=True, default=None,
  84. verbose_name='Abonnement')
  85. period_from = models.DateField(
  86. default=datetime.date(datetime.date.today().year,
  87. datetime.date.today().month, 1),
  88. null=True,
  89. blank=True,
  90. verbose_name=u'Début de période',
  91. help_text=u'Date de début de période sur laquelle est facturé cet item')
  92. period_to = models.DateField(
  93. default=(datetime.date(datetime.date.today().year,
  94. datetime.date.today().month + 1, 1) -
  95. datetime.timedelta(days=1)),
  96. null=True,
  97. blank=True,
  98. verbose_name=u'Fin de période',
  99. help_text=u'Date de fin de période sur laquelle est facturé cet item')
  100. def __unicode__(self):
  101. return self.label
  102. def total(self):
  103. "Calcul le total"
  104. return (self.amount * (self.tax / Decimal('100.0') +
  105. Decimal('1.0')) * self.quantity).quantize(Decimal('0.01'))
  106. class Meta:
  107. verbose_name = 'détail de facture'
  108. class Payment(models.Model):
  109. PAYMENT_MEAN_CHOICES = (
  110. ('cash', u'Espèces'),
  111. ('check', u'Chèque'),
  112. ('transfer', u'Virement'),
  113. ('other', u'Autre')
  114. )
  115. payment_mean = models.CharField(max_length=100, null=True,
  116. default='transfer',
  117. choices=PAYMENT_MEAN_CHOICES,
  118. verbose_name='Moyen de paiement')
  119. amount = models.DecimalField(max_digits=5, decimal_places=2, null=True,
  120. verbose_name='Montant')
  121. date = models.DateField(default=datetime.date.today)
  122. invoice = models.ForeignKey(Invoice, verbose_name='Facture',
  123. related_name='payments')
  124. def __unicode__(self):
  125. return u'Paiment de %0.2f€' % self.amount
  126. class Meta:
  127. verbose_name = 'paiement'
  128. @receiver(post_save, sender=Payment)
  129. def set_invoice_as_paid_if_needed(sender, instance, **kwargs):
  130. """
  131. Lorsqu'un paiement est enregistré, vérifie si la facture est alors
  132. complétement payée. Dans ce cas elle passe en réglée
  133. """
  134. if (instance.invoice.amount_paid >= instance.invoice.amount and
  135. instance.invoice.status == 'open'):
  136. instance.invoice.status = 'closed'
  137. instance.invoice.save()