from django.db import models from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.core.validators import MaxValueValidator from django.core.urlresolvers import reverse from django.core.exceptions import ValidationError from django.urls import reverse from django.core.exceptions import PermissionDenied from django.utils import timezone from djadhere.utils import is_overlapping, get_active_filter class Payment(models.Model): TRANSFERT = 0 WITHDRAWAL = 1 PAYMENT_CHOICES = ( (TRANSFERT, 'Virement'), (WITHDRAWAL, 'Prélèvement'), ) ADHESION = 0 SERVICE = 1 limit = models.Q(app_label='adhesions', model='adhesion') \ | models.Q(app_label='services', model='service') reason_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, limit_choices_to=limit) reason_id = models.PositiveIntegerField() reason = GenericForeignKey('reason_type', 'reason_id') amount = models.DecimalField(max_digits=9, decimal_places=2, verbose_name='Montant') period = models.PositiveIntegerField(validators=[MaxValueValidator(12)], verbose_name='Période (mois) (0 si non récurrent)') payment_method = models.IntegerField(choices=PAYMENT_CHOICES, verbose_name='Méthode de paiement') start = models.DateField(verbose_name='Date de paiement ou de début de paiement') end = models.DateField(null=True, blank=True, verbose_name='Date de fin de paiement (si récurrent)') validated = models.BooleanField(default=False, verbose_name='Paiement validé') class Meta: verbose_name = 'paiement' permissions = ( ('validate_payment', 'Peut valider les paiements'), ) @property def active(self): # Contrairement aux allocations d’IP, un paiement est considéré actif même s’il commence dans le futur # Seul les paiements récurrents peuvent être actifs if self.period == 0: return None today = timezone.localtime(timezone.now()).date() return self.end is None or (self.start <= today and self.end > today) @property def type(self): if self.reason_type.app_label == 'adhesions' \ and self.reason_type.model == 'adhesion': return Payment.ADHESION if self.reason_type.app_label == 'services' \ and self.reason_type.model == 'service': return Payment.SERVICE def type_verbose(self): if self.type == Payment.ADHESION: return 'Cotisation' if self.type == Payment.SERVICE: return 'Service' type_verbose.short_description = 'Type' def period_verbose(self): if self.period == 0: return 'non récurrent' elif self.period == 1: return 'mensuel' elif self.period == 3: return 'trimestriel' elif self.period == 6: return 'biannuel' elif self.period == 12: return 'annuel' else: return '%d mois' % self.period # FIXME @property def adherent(self): return self.get_adhesion().adherent def get_adhesion(self): if self.reason_type.app_label == 'adhesions' \ and self.reason_type.model == 'adhesion': return self.reason if self.reason_type.app_label == 'services' \ and self.reason_type.model == 'service': return self.reason.adhesion get_adhesion.short_description = 'Adhésion' # Penser à appele la méthode save ! def stop(self): if not self.active: raise PermissionDenied today = timezone.localtime(timezone.now()).date() self.end = max(self.start, today) def clean(self): super().clean() # S’il s’agit d’un paiement non récurrent, le champ end doit rester blanc if self.period == 0 and self.end: raise ValidationError({'end': "Un paiement non récurrent ne doit pas avoir de date de fin."}) # Vérification de la cohérence des champs start et end if self.end and self.start > self.end: raise ValidationError("La date de début de paiement doit être antérieur à la date de fin de paiement.") # Vérification de l’absence de chevauchement avec une période existante # FIXME ne fonctionne pas vraiment via les inlines… if self.reason: payments = Payment.objects.filter(reason_type=self.reason_type, reason_id=self.reason_id) if is_overlapping(self, payments): raise ValidationError("Les périodes de paiement ne doivent pas se chevaucher.") def get_absolute_url(self): return reverse('payment-detail', kwargs={'pk': self.pk}) def __str__(self): s = str(self.amount) + '€' if self.period: if self.period == 1: s += '/mois' elif self.period == 12: s += '/an' else: s += '/%d mois' % self.period if self.payment_method == self.TRANSFERT: s += ' (virement)' elif self.payment_method == self.WITHDRAWAL: s += ' (prélèvement)' return s