Browse Source

Displaying payment and balance in member interface

Alexandre Aubin 8 years ago
parent
commit
fd9f283030

+ 46 - 25
coin/billing/models.py

@@ -265,7 +265,7 @@ class Payment(models.Model):
     )
     )
 
 
     member = models.ForeignKey(Member, null=True, blank=True, default=None,
     member = models.ForeignKey(Member, null=True, blank=True, default=None,
-                               related_name='payment',
+                               related_name='payments',
                                verbose_name='membre',
                                verbose_name='membre',
                                on_delete=models.SET_NULL)
                                on_delete=models.SET_NULL)
 
 
@@ -281,9 +281,18 @@ class Payment(models.Model):
 
 
     amount_already_allocated = models.DecimalField(max_digits=5,
     amount_already_allocated = models.DecimalField(max_digits=5,
                                                    decimal_places=2,
                                                    decimal_places=2,
-                                                   default=0.0,
+                                                   null=True, blank=True, default=0.0,
                                                    verbose_name='montant déjà alloué')
                                                    verbose_name='montant déjà alloué')
 
 
+    def save(self, *args, **kwargs):
+        super(Payment, self).save(*args, **kwargs)
+       
+        # If this payment is related to a member, update the accounting for
+        # this member
+        if self.member != None:
+            update_accounting_for_member(self.member)
+
+
     def amount_not_allocated(self):
     def amount_not_allocated(self):
         return self.amount - self.amount_already_allocated
         return self.amount - self.amount_already_allocated
 
 
@@ -303,6 +312,8 @@ class Payment(models.Model):
         if (invoice.amount_remaining_to_pay() <= 0) and (invoice.status == "open"):
         if (invoice.amount_remaining_to_pay() <= 0) and (invoice.status == "open"):
             invoice.status = "closed"
             invoice.status = "closed"
 
 
+        invoice.save()
+        self.save()
 
 
     def __unicode__(self):
     def __unicode__(self):
         return 'Paiment de %0.2f€ le %s' % (self.amount, str(self.date))
         return 'Paiment de %0.2f€ le %s' % (self.amount, str(self.date))
@@ -311,32 +322,23 @@ class Payment(models.Model):
         verbose_name = 'paiement'
         verbose_name = 'paiement'
 
 
 
 
-@receiver(post_save, sender=Payment)
-@disable_for_loaddata
-def update_accounting(sender, instance, **kwargs):
-
-    # Ignore if there's no member set for the invoice/payment
-    member = instance.member
-    if member == None :
-        return
-    else:
-        update_accounting_for_member(member)
-
-
 def update_accounting_for_member(member):
 def update_accounting_for_member(member):
     """
     """
     Met à jour le status des factures, des paiements et le solde du compte
     Met à jour le status des factures, des paiements et le solde du compte
     d'un utilisateur
     d'un utilisateur
     """
     """
 
 
+    print "Member "+str(member)+" currently has a balance of "+str(member.balance)
+
     # Fetch relevant and active payments / invoices
     # Fetch relevant and active payments / invoices
     # and sort then by chronological order : olders first, newers last.
     # and sort then by chronological order : olders first, newers last.
 
 
-    this_member_payments = [p for p in Payment.objects.filter(member=member)
-                                                      .order_by("date")]
-    this_member_invoices = [i for i in Invoice.objects.filter(member=member)
-                                                      .filter(validated=True)
+    this_member_invoices = [i for i in member.invoices.filter(validated=True)
                                                       .order_by("date")]
                                                       .order_by("date")]
+    this_member_payments = [p for p in member.payments.order_by("date")]
+
+    # TODO / FIXME ^^^ maybe also consider only 'opened' invoices (i.e. not
+    # conflict / trouble invoices)
 
 
     number_of_active_payments = len([p for p in this_member_payments if p.amount_not_allocated()    > 0])
     number_of_active_payments = len([p for p in this_member_payments if p.amount_not_allocated()    > 0])
     number_of_active_invoices = len([p for p in this_member_invoices if p.amount_remaining_to_pay() > 0])
     number_of_active_invoices = len([p for p in this_member_invoices if p.amount_remaining_to_pay() > 0])
@@ -346,11 +348,14 @@ def update_accounting_for_member(member):
     elif (number_of_active_invoices == 0):
     elif (number_of_active_invoices == 0):
         print "No active invoice for "+str(member)+". Nothing to do."
         print "No active invoice for "+str(member)+". Nothing to do."
     else:
     else:
-        print "Initiating reconciliation between invoice and payments for user"+str(member)+"."
+        print "Initiating reconciliation between invoice and payments for user "+str(member)+"."
         reconcile_invoices_and_payments(this_member_invoices, this_member_payments)
         reconcile_invoices_and_payments(this_member_invoices, this_member_payments)
+
+    member.balance = compute_balance(this_member_invoices, this_member_payments)
+    member.save()
+
     print " "
     print " "
-    print " "
-    print " "
+    print "Member "+str(member)+" new balance is "+str(member.balance)
     print " "
     print " "
 
 
 
 
@@ -358,14 +363,16 @@ def reconcile_invoices_and_payments(invoices, payments):
     """
     """
     Rapproche des factures et des paiements qui sont actifs (paiement non alloué
     Rapproche des factures et des paiements qui sont actifs (paiement non alloué
     ou factures non entièrement payées) automatiquement.
     ou factures non entièrement payées) automatiquement.
+    Retourne la balance restante (positive si 'trop payé', négative si factures
+    restantes à payer)  
     """
     """
 
 
     active_payments = [p for p in payments if p.amount_not_allocated()    > 0]
     active_payments = [p for p in payments if p.amount_not_allocated()    > 0]
     active_invoices = [i for i in invoices if i.amount_remaining_to_pay() > 0]
     active_invoices = [i for i in invoices if i.amount_remaining_to_pay() > 0]
 
 
-    print "Active payment, invoices"
-    print active_payments
-    print active_invoices
+    #print "Active payment, invoices"
+    #print active_payments
+    #print active_invoices
 
 
     if (len(active_payments) == 0):
     if (len(active_payments) == 0):
         print "No more active payment. Nothing to reconcile anymore."
         print "No more active payment. Nothing to reconcile anymore."
@@ -385,10 +392,20 @@ def reconcile_invoices_and_payments(invoices, payments):
     p.allocate_to_invoice(i)
     p.allocate_to_invoice(i)
 
 
     # Reconcicle next payment / invoice
     # Reconcicle next payment / invoice
-
+    
     reconcile_invoices_and_payments(invoices, payments)
     reconcile_invoices_and_payments(invoices, payments)
 
 
 
 
+def compute_balance(invoices, payments):
+
+    active_payments = [p for p in payments if p.amount_not_allocated()    > 0]
+    active_invoices = [i for i in invoices if i.amount_remaining_to_pay() > 0]
+    
+    s = 0
+    s -= sum([ i.amount_remaining_to_pay() for i in active_invoices ])
+    s += sum([ p.amount_not_allocated()    for p in active_payments ])
+
+    return s
 
 
 def test_accounting_update():
 def test_accounting_update():
 
 
@@ -423,3 +440,7 @@ def test_accounting_update():
     payment = Payment.objects.create(amount=20,
     payment = Payment.objects.create(amount=20,
                                      member=johndoe)
                                      member=johndoe)
 
 
+
+    Member.objects.all().delete()
+    Payment.objects.all().delete()
+    Invoice.objects.all().delete()

+ 4 - 0
coin/members/models.py

@@ -76,6 +76,10 @@ class Member(CoinLdapSyncMixin, AbstractUser):
                         blank=True,
                         blank=True,
                         verbose_name="Date du dernier email de relance de cotisation envoyé")
                         verbose_name="Date du dernier email de relance de cotisation envoyé")
 
 
+    balance = models.DecimalField(max_digits=5, decimal_places=2, default=0,
+                                 verbose_name='account balance')
+
+
     # Following fields are managed by the parent class AbstractUser :
     # Following fields are managed by the parent class AbstractUser :
     # username, first_name, last_name, email
     # username, first_name, last_name, email
     # However we hack the model to force theses fields to be required. (see
     # However we hack the model to force theses fields to be required. (see

+ 28 - 0
coin/members/templates/members/invoices.html

@@ -1,6 +1,9 @@
 {% extends "base.html" %}
 {% extends "base.html" %}
 
 
 {% block content %}
 {% block content %}
+
+<h2>Balance : {{ balance }} €</h2>
+
 <h2>Mes factures</h2>
 <h2>Mes factures</h2>
 
 
 <table id="member_invoices" class="full-width">
 <table id="member_invoices" class="full-width">
@@ -28,6 +31,31 @@
     </tbody>
     </tbody>
 </table>
 </table>
 
 
+
+<h2>Mes paiements</h2>
+
+<table id="member_payments" class="full-width">
+    <thead>
+        <tr>
+            <th>Date</th>
+            <th>Montant</th>
+            <th>Alloué</th>
+        </tr>
+    </thead>
+    <tbody>
+        {% for payment in payments %}
+        <tr>
+            <td>{{ payment.date }}</td>
+            <td>{{ payment.amount }}</td>
+            <td>{{ payment.amount_already_allocated }}</td>
+        </tr>
+        {% empty %}
+        <tr class="placeholder"><td colspan="6">Aucun paiement.</td></tr>
+        {% endfor %}
+    </tbody>
+</table>
+
+
 <h2>Coordonnées bancaires</h2>
 <h2>Coordonnées bancaires</h2>
 <div id="payment-howto" class="panel">
 <div id="payment-howto" class="panel">
     {% include "billing/payment_howto.html" %}
     {% include "billing/payment_howto.html" %}

+ 5 - 1
coin/members/views.py

@@ -37,10 +37,14 @@ def subscriptions(request):
 
 
 @login_required
 @login_required
 def invoices(request):
 def invoices(request):
+    balance  = request.user.balance
     invoices = request.user.invoices.filter(validated=True).order_by('-date')
     invoices = request.user.invoices.filter(validated=True).order_by('-date')
+    payments = request.user.payments.filter().order_by('-date')
 
 
     return render_to_response('members/invoices.html',
     return render_to_response('members/invoices.html',
-                              {'invoices': invoices},
+                              {'balance' : balance, 
+                               'invoices': invoices, 
+                               'payments': payments},
                               context_instance=RequestContext(request))
                               context_instance=RequestContext(request))