models.py 5.8 KB

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