from django.db import models from django.core.validators import MinValueValidator, MaxValueValidator from django.core.exceptions import ValidationError from django.urls import reverse class CurrentPaymentManager(models.Manager): def get_queryset(self): qs = super().get_queryset() qs = qs.annotate( payment_method=models.Subquery( PaymentUpdate.objects.filter(payment=models.OuterRef('pk'), validated=True) .values('payment_method')[:1] ) ) qs = qs.annotate( active=models.Case( models.When(payment_method__isnull=True, then=None), models.When(payment_method=PaymentUpdate.STOP, then=False), default=True, output_field=models.NullBooleanField() ) ) return qs class RecurringPayment(models.Model): objects = CurrentPaymentManager() @property def debtor(self): if hasattr(self, 'adhesion'): return self.adhesion if hasattr(self, 'service'): return self.service.adhesion def payment_type(self): if hasattr(self, 'adhesion'): return 'Adhésion' if hasattr(self, 'service'): return 'Service' payment_type.short_description = 'Type' def payment_object(self): return self.adhesion if hasattr(self, 'adhesion') else self.service def get_absolute_url(self): return reverse('admin:%s_%s_change' % (self._meta.app_label, self._meta.model_name), args=(self.pk,)) def get_current_payment(self): return self.updates.filter(validated=True).first() def get_current_payment_display(self): current = self.get_current_payment() if current: return str(current) else: return 'non renseignée' class Meta: verbose_name = 'paiement récurrent' verbose_name_plural = 'paiements récurrents' def __str__(self): if hasattr(self, 'adhesion'): return "Cotisation %s" % self.adhesion else: return "Contribution %s" % self.service class PaymentUpdate(models.Model): STOP = 0 FREE = 1 DEBIT = 2 TRANSFER = 3 CASH = 4 INVOICE = 5 PAYMENT_CHOICES = ( (DEBIT, 'Prélèvement'), (TRANSFER, 'Virement'), (CASH, 'Liquide'), (INVOICE, 'Facture'), (FREE, 'Gratuit'), (STOP, 'Arrêt'), ) created = models.DateTimeField(auto_now_add=True) payment = models.ForeignKey(RecurringPayment, related_name='updates', on_delete=models.CASCADE) amount = models.DecimalField(max_digits=9, decimal_places=2, verbose_name='Montant') period = models.PositiveIntegerField(validators=[MinValueValidator(1), MaxValueValidator(12)], verbose_name='Période (mois)') payment_method = models.IntegerField(choices=PAYMENT_CHOICES, default=DEBIT, verbose_name='Méthode de paiement') start = models.DateTimeField(verbose_name='Date') month_day = models.PositiveIntegerField(validators=[MinValueValidator(1), MaxValueValidator(25)], verbose_name='Jour souhaité de prélèvement', null=True, blank=True) validated = models.BooleanField(default=False, verbose_name='Saisie bancaire effectuée') class Meta: verbose_name = 'paiement' ordering = ('-start',) def clean(self): super().clean() errors = {} if self.payment_method == PaymentUpdate.STOP and self.amount: errors.update({'amount': "Pour l’arrêt d’un paiement, le montant doit être nul."}) if errors: raise ValidationError(errors) 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 def __str__(self): if self.payment_method == self.STOP: return 'paiement arrêté' if self.payment_method == self.FREE: return 'libre' 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.DEBIT: s += ' (prélèvement)' elif self.payment_method == self.TRANSFER: s += ' (virement)' elif self.payment_method == self.CASH: s += ' (liquide)' elif self.payment_method == self.INVOICE: s += ' (sur facture)' return s