Browse Source

amélioration adhésion / paiement actifs

Élie Bouttier 7 years ago
parent
commit
c9dba25724

+ 5 - 4
adhesions/admin.py

@@ -12,6 +12,7 @@ from .forms import UserCreationForm
 from .models import User, Corporation, Adhesion
 from accounts.models import Profile
 from services.models import Service
+from djadhere.utils import ActiveFilter
 
 
 ### Inlines
@@ -28,7 +29,7 @@ class ProfileInline(admin.StackedInline):
 
 class AdhesionInline(admin.StackedInline):
     model = Adhesion
-    fields = ('id', 'active', 'notes',)
+    fields = ('id', 'notes',)
 
     def has_add_permission(self, request):
         return False
@@ -205,8 +206,8 @@ class CorporationAdmin(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',)
     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'
 
     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'
 
     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 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):
@@ -60,13 +80,15 @@ class Adhesion(models.Model):
     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)
     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)
     corporation = models.OneToOneField(Corporation, null=True)
 
     membership = models.OneToOneField(RecurringPayment, on_delete=models.CASCADE)
 
+    objects = ActiveAdhesionManager()
+
     def save(self, *args, **kwargs):
         if not hasattr(self, 'membership'):
             self.membership = RecurringPayment.objects.create()
@@ -107,6 +129,11 @@ class Adhesion(models.Model):
     def get_absolute_url(self):
         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):
         return 'ADT%d' % self.id
         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.admin import ServiceAdmin
 from adhesions.admin import AdhesionAdmin
+from djadhere.utils import ActiveFilter
 from .models import RecurringPayment, PaymentUpdate
 from .utils import notify_payment_update
 
@@ -35,30 +36,6 @@ class PaymentTypeFilter(admin.SimpleListFilter):
             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):
     title = 'opérations en attente'
     parameter_name = 'pending'
@@ -134,10 +111,10 @@ def prefix_search_field(prefix, field):
 ### 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',)
     inlines = (PendingPaymentUpdateInline, ValidatedPaymentUpdateInline,)
-    list_filter = (PaymentTypeFilter, PaymentStatusFilter, PendingPaymentFilter,)
+    list_filter = (PaymentTypeFilter, ActiveFilter, PendingPaymentFilter,)
     fields = ('payment_type', 'payment_object_link', 'debtor_link',)
     readonly_fields = ('payment_type', 'payment_object_link', 'debtor_link',)
     search_fields = \

+ 33 - 30
banking/models.py

@@ -4,19 +4,28 @@ from django.core.exceptions import ValidationError
 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
     def debtor(self):
@@ -25,6 +34,13 @@ class RecurringPayment(models.Model):
         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
 
@@ -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,))
 
     @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:
         verbose_name = 'paiement récurrent'
         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):
-        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):

+ 21 - 0
djadhere/utils.py

@@ -4,6 +4,27 @@ from django.utils import timezone
 from django.forms import widgets
 from django.utils.safestring import mark_safe
 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

+ 1 - 1
services/admin.py

@@ -245,7 +245,7 @@ class ServiceAdmin(admin.ModelAdmin):
     get_adherent_link.short_description = Adhesion.get_adherent_link.short_description
 
     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'
 
     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