Parcourir la source

amélioration adhésion / paiement actifs

Élie Bouttier il y a 7 ans
Parent
commit
c9dba25724

+ 5 - 4
adhesions/admin.py

@@ -12,6 +12,7 @@ from .forms import UserCreationForm
 from .models import User, Corporation, Adhesion
 from .models import User, Corporation, Adhesion
 from accounts.models import Profile
 from accounts.models import Profile
 from services.models import Service
 from services.models import Service
+from djadhere.utils import ActiveFilter
 
 
 
 
 ### Inlines
 ### Inlines
@@ -28,7 +29,7 @@ class ProfileInline(admin.StackedInline):
 
 
 class AdhesionInline(admin.StackedInline):
 class AdhesionInline(admin.StackedInline):
     model = Adhesion
     model = Adhesion
-    fields = ('id', 'active', 'notes',)
+    fields = ('id', 'notes',)
 
 
     def has_add_permission(self, request):
     def has_add_permission(self, request):
         return False
         return False
@@ -205,8 +206,8 @@ class CorporationAdmin(AdtSearchMixin, admin.ModelAdmin):
 
 
 
 
 class AdhesionAdmin(AdtSearchMixin, admin.ModelAdmin):
 class AdhesionAdmin(AdtSearchMixin, admin.ModelAdmin):
-    list_display = ('get_id', 'type', 'get_adherent_link',)
-    list_filter = (AdherentTypeFilter, 'active',)
+    list_display = ('get_id', 'type', 'get_adherent_link', 'is_active')
+    list_filter = (AdherentTypeFilter, ActiveFilter)
     list_select_related = ('user', 'user__profile', 'corporation',)
     list_select_related = ('user', 'user__profile', 'corporation',)
     fields = ('id', 'type', 'get_adherent_link', 'get_membership_link', 'get_antennas_link',)
     fields = ('id', 'type', 'get_adherent_link', 'get_membership_link', 'get_antennas_link',)
     readonly_fields = ('id', 'type', 'get_adherent_link', 'get_membership_link', 'get_antennas_link',)
     readonly_fields = ('id', 'type', 'get_adherent_link', 'get_membership_link', 'get_antennas_link',)
@@ -221,7 +222,7 @@ class AdhesionAdmin(AdtSearchMixin, admin.ModelAdmin):
     get_id.admin_order_field = 'id'
     get_id.admin_order_field = 'id'
 
 
     def get_membership_link(self, obj):
     def get_membership_link(self, obj):
-        return format_html(u'<a href="{}">{}</a>', obj.membership.get_absolute_url(), obj.membership.get_last_validated_update() or 'inconnue')
+        return format_html(u'<a href="{}">{}</a>', obj.membership.get_absolute_url(), obj.membership)
     get_membership_link.short_description = 'Cotisation'
     get_membership_link.short_description = 'Cotisation'
 
 
     def get_antennas_link(self, obj):
     def get_antennas_link(self, obj):

+ 20 - 0
adhesions/migrations/0017_auto_20180131_2046.py

@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.9 on 2018-01-31 19:46
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('adhesions', '0016_adhesion_membership'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='adhesion',
+            old_name='active',
+            new_name='active_legacy',
+        ),
+    ]

+ 29 - 2
adhesions/models.py

@@ -7,7 +7,27 @@ from django.core.urlresolvers import reverse
 from django.utils.html import format_html
 from django.utils.html import format_html
 
 
 from djadhere.utils import get_active_filter
 from djadhere.utils import get_active_filter
-from banking.models import RecurringPayment
+from banking.models import RecurringPayment, PaymentUpdate
+
+
+class ActiveAdhesionManager(models.Manager):
+    def get_queryset(self):
+        qs = super().get_queryset()
+        qs = qs.annotate(
+                payment_method=models.Subquery(
+                                PaymentUpdate.objects.filter(payment=models.OuterRef('membership__pk'))
+                                                     .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 User(AuthUser):
 class User(AuthUser):
@@ -60,13 +80,15 @@ class Adhesion(models.Model):
     id = models.AutoField(verbose_name='Numéro d’adhérent', primary_key=True, editable=True)
     id = models.AutoField(verbose_name='Numéro d’adhérent', primary_key=True, editable=True)
     created = models.DateTimeField(null=True, blank=True, auto_now_add=True)
     created = models.DateTimeField(null=True, blank=True, auto_now_add=True)
     notes = models.TextField(blank=True, default='')
     notes = models.TextField(blank=True, default='')
-    active = models.NullBooleanField(default=None, verbose_name='Adhésion en cours')
+    active_legacy = models.NullBooleanField(default=None, verbose_name='Adhésion en cours')
 
 
     user = models.OneToOneField(User, null=True)
     user = models.OneToOneField(User, null=True)
     corporation = models.OneToOneField(Corporation, null=True)
     corporation = models.OneToOneField(Corporation, null=True)
 
 
     membership = models.OneToOneField(RecurringPayment, on_delete=models.CASCADE)
     membership = models.OneToOneField(RecurringPayment, on_delete=models.CASCADE)
 
 
+    objects = ActiveAdhesionManager()
+
     def save(self, *args, **kwargs):
     def save(self, *args, **kwargs):
         if not hasattr(self, 'membership'):
         if not hasattr(self, 'membership'):
             self.membership = RecurringPayment.objects.create()
             self.membership = RecurringPayment.objects.create()
@@ -107,6 +129,11 @@ class Adhesion(models.Model):
     def get_absolute_url(self):
     def get_absolute_url(self):
         return reverse('admin:%s_%s_change' % (self._meta.app_label, self._meta.model_name), args=(self.pk,))
         return reverse('admin:%s_%s_change' % (self._meta.app_label, self._meta.model_name), args=(self.pk,))
 
 
+    def is_active(self):
+        return self.active
+    is_active.boolean = True
+    is_active.short_description = 'Actif'
+
     def __str__(self):
     def __str__(self):
         return 'ADT%d' % self.id
         return 'ADT%d' % self.id
         if self.id is None:
         if self.id is None:

+ 3 - 26
banking/admin.py

@@ -12,6 +12,7 @@ from functools import update_wrapper
 from services.models import ServiceType
 from services.models import ServiceType
 from services.admin import ServiceAdmin
 from services.admin import ServiceAdmin
 from adhesions.admin import AdhesionAdmin
 from adhesions.admin import AdhesionAdmin
+from djadhere.utils import ActiveFilter
 from .models import RecurringPayment, PaymentUpdate
 from .models import RecurringPayment, PaymentUpdate
 from .utils import notify_payment_update
 from .utils import notify_payment_update
 
 
@@ -35,30 +36,6 @@ class PaymentTypeFilter(admin.SimpleListFilter):
             return queryset.filter(service__isnull=False)
             return queryset.filter(service__isnull=False)
 
 
 
 
-class PaymentStatusFilter(admin.SimpleListFilter):
-    title = 'actif'
-    parameter_name = 'active'
-
-    def lookups(self, request, model_admin):
-        return (
-            (0, 'Non renseigné'),
-            (1, 'Inactif'),
-            (2, 'Actif'),
-        )
-
-    def queryset(self, request, queryset):
-        payments = PaymentUpdate.objects.filter(validated=True).order_by('payment', '-start')\
-                        .distinct('payment').values('payment_method', 'payment__pk')
-        actives = filter(lambda p: p['payment_method'] != PaymentUpdate.STOP, payments)
-        actives = map(lambda p: p['payment__pk'], actives)
-        if self.value() == '0':
-            return queryset.exclude(pk__in=actives).filter(updates__isnull=True)
-        if self.value() == '1':
-            return queryset.exclude(pk__in=actives).exclude(updates__isnull=True)
-        if self.value() == '2':
-            return queryset.filter(pk__in=actives)
-
-
 class PendingPaymentFilter(admin.SimpleListFilter):
 class PendingPaymentFilter(admin.SimpleListFilter):
     title = 'opérations en attente'
     title = 'opérations en attente'
     parameter_name = 'pending'
     parameter_name = 'pending'
@@ -134,10 +111,10 @@ def prefix_search_field(prefix, field):
 ### ModelAdmin
 ### ModelAdmin
 
 
 class RecurringPaymentAdmin(admin.ModelAdmin):
 class RecurringPaymentAdmin(admin.ModelAdmin):
-    list_display = ('id', 'payment_type', 'payment_object_link', 'get_active', 'get_last_validated_update', 'get_pending',)
+    list_display = ('id', 'payment_type', 'payment_object_link', 'get_active', 'get_pending',)
     list_select_related = ('adhesion', 'service', 'service__service_type',)
     list_select_related = ('adhesion', 'service', 'service__service_type',)
     inlines = (PendingPaymentUpdateInline, ValidatedPaymentUpdateInline,)
     inlines = (PendingPaymentUpdateInline, ValidatedPaymentUpdateInline,)
-    list_filter = (PaymentTypeFilter, PaymentStatusFilter, PendingPaymentFilter,)
+    list_filter = (PaymentTypeFilter, ActiveFilter, PendingPaymentFilter,)
     fields = ('payment_type', 'payment_object_link', 'debtor_link',)
     fields = ('payment_type', 'payment_object_link', 'debtor_link',)
     readonly_fields = ('payment_type', 'payment_object_link', 'debtor_link',)
     readonly_fields = ('payment_type', 'payment_object_link', 'debtor_link',)
     search_fields = \
     search_fields = \

+ 33 - 30
banking/models.py

@@ -4,19 +4,28 @@ from django.core.exceptions import ValidationError
 from django.core.urlresolvers import reverse
 from django.core.urlresolvers import reverse
 
 
 
 
-class RecurringPayment(models.Model):
-    def get_last_validated_update(self):
-        for update in self.updates.all():
-            if update.validated:
-                return update
-    get_last_validated_update.short_description = 'Paiement'
+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'))
+                                                     .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
 
 
-    def payment_type(self):
-        if hasattr(self, 'adhesion'):
-            return 'Adhésion'
-        if hasattr(self, 'service'):
-            return 'Service'
-    payment_type.short_description = 'Type'
+
+class RecurringPayment(models.Model):
+    objects = CurrentPaymentManager()
 
 
     @property
     @property
     def debtor(self):
     def debtor(self):
@@ -25,6 +34,13 @@ class RecurringPayment(models.Model):
         if hasattr(self, 'service'):
         if hasattr(self, 'service'):
             return self.service.adhesion
             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):
     def payment_object(self):
         return self.adhesion if hasattr(self, 'adhesion') else self.service
         return self.adhesion if hasattr(self, 'adhesion') else self.service
 
 
@@ -32,31 +48,18 @@ class RecurringPayment(models.Model):
         return reverse('admin:%s_%s_change' % (self._meta.app_label, self._meta.model_name), args=(self.pk,))
         return reverse('admin:%s_%s_change' % (self._meta.app_label, self._meta.model_name), args=(self.pk,))
 
 
     @property
     @property
-    def active(self):
-        payment = self.get_last_validated_update()
-        return payment is not None and payment.payment_method != PaymentUpdate.STOP
+    def current(self):
+        return self.updates.last()
 
 
-    @property
-    def amount(self):
-        payment = self.get_last_validated_update()
-        if payment is not None and payment.payment_method != PaymentUpdate.STOP:
-            return payment.amount
-        else:
-            return None
+    def is_active(self):
+        return self.current and self.current.payment_method != PaymentUpdate.STOP
 
 
     class Meta:
     class Meta:
         verbose_name = 'paiement récurrent'
         verbose_name = 'paiement récurrent'
         verbose_name_plural = 'paiements récurrents'
         verbose_name_plural = 'paiements récurrents'
 
 
-    def display(self):
-        payment = self.get_last_validated_update()
-        return str(payment) if payment else "pas de paiement"
-
     def __str__(self):
     def __str__(self):
-        if hasattr(self, 'adhesion'):
-            return 'Cotisation adhérent %s' % self.adhesion
-        if hasattr(self, 'service'):
-            return 'Contribution au service %s' % self.service
+        return str(self.current or 'non renseignée')
 
 
 
 
 class PaymentUpdate(models.Model):
 class PaymentUpdate(models.Model):

+ 21 - 0
djadhere/utils.py

@@ -4,6 +4,27 @@ from django.utils import timezone
 from django.forms import widgets
 from django.forms import widgets
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
 from django.conf import settings
 from django.conf import settings
+from django.contrib.admin import SimpleListFilter
+
+
+class ActiveFilter(SimpleListFilter):
+    title = 'actif'
+    parameter_name = 'active'
+
+    def lookups(self, request, model_admin):
+        return (
+            ('?', 'Non renseigné'),
+            ('0', 'Inactif'),
+            ('1', 'Actif'),
+        )
+
+    def queryset(self, request, queryset):
+        if self.value() == '0':
+            return queryset.filter(active__isnull=True)
+        if self.value() == '1':
+            return queryset.filter(active=False)
+        if self.value() == '2':
+            return queryset.filter(active=True)
 
 
 
 
 # Ce widget permet d’afficher un champ de formulaire sous forme de texte
 # Ce widget permet d’afficher un champ de formulaire sous forme de texte

+ 1 - 1
services/admin.py

@@ -245,7 +245,7 @@ class ServiceAdmin(admin.ModelAdmin):
     get_adherent_link.short_description = Adhesion.get_adherent_link.short_description
     get_adherent_link.short_description = Adhesion.get_adherent_link.short_description
 
 
     def get_contribution_link(self, obj):
     def get_contribution_link(self, obj):
-        return format_html(u'<a href="{}">{}</a>', obj.contribution.get_absolute_url(), obj.contribution.get_last_validated_update() or 'inconnue')
+        return format_html(u'<a href="{}">{}</a>', obj.contribution.get_absolute_url(), obj.contribution)
     get_contribution_link.short_description = 'Contribution financière'
     get_contribution_link.short_description = 'Contribution financière'
 
 
     def get_actions(self, request):
     def get_actions(self, request):

+ 33 - 0
services/fields.py

@@ -0,0 +1,33 @@
+from django import forms
+
+
+from .widgets import LatLongWidget
+
+
+class LatLongField(forms.MultiValueField):
+    widget = LatLongWidget
+    srid = 4326
+
+    default_error_messages = {
+        'invalid_latitude' : _('Enter a valid latitude.'),
+        'invalid_longitude' : _('Enter a valid longitude.'),
+    }
+
+    def __init__(self, *args, **kwargs):
+        fields = (forms.FloatField(min_value=-90, max_value=90), 
+                  forms.FloatField(min_value=-180, max_value=180))
+        super().__init__(fields, *args, **kwargs)
+
+    def compress(self, data_list):
+        if data_list:
+            # Raise a validation error if latitude or longitude is empty
+            # (possible if LatLongField has required=False).
+            if data_list[0] in validators.EMPTY_VALUES:
+                raise forms.ValidationError(self.error_messages['invalid_latitude'])
+            if data_list[1] in validators.EMPTY_VALUES:
+                raise forms.ValidationError(self.error_messages['invalid_longitude'])
+            # SRID=4326;POINT(1.12345789 1.123456789)
+            srid_str = 'SRID=%d'%self.srid
+            point_str = 'POINT(%f %f)'%tuple(reversed(data_list))
+            return ';'.join([srid_str, point_str])
+        return None