Browse Source

Displaying payment and balance in member interface

Alexandre Aubin 8 years ago
parent
commit
be58fc0222

+ 46 - 25
coin/billing/models.py

@@ -291,7 +291,7 @@ class Payment(models.Model):
     )
 
     member = models.ForeignKey(Member, null=True, blank=True, default=None,
-                               related_name='payment',
+                               related_name='payments',
                                verbose_name='membre',
                                on_delete=models.SET_NULL)
 
@@ -307,9 +307,18 @@ class Payment(models.Model):
 
     amount_already_allocated = models.DecimalField(max_digits=5,
                                                    decimal_places=2,
-                                                   default=0.0,
+                                                   null=True, blank=True, default=0.0,
                                                    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):
         return self.amount - self.amount_already_allocated
 
@@ -329,6 +338,8 @@ class Payment(models.Model):
         if (invoice.amount_remaining_to_pay() <= 0) and (invoice.status == "open"):
             invoice.status = "closed"
 
+        invoice.save()
+        self.save()
 
     def __unicode__(self):
         return 'Paiment de %0.2f€ le %s' % (self.amount, str(self.date))
@@ -337,32 +348,23 @@ class Payment(models.Model):
         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):
     """
     Met à jour le status des factures, des paiements et le solde du compte
     d'un utilisateur
     """
 
+    print "Member "+str(member)+" currently has a balance of "+str(member.balance)
+
     # Fetch relevant and active payments / invoices
     # 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")]
+    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_invoices = len([p for p in this_member_invoices if p.amount_remaining_to_pay() > 0])
@@ -372,11 +374,14 @@ def update_accounting_for_member(member):
     elif (number_of_active_invoices == 0):
         print "No active invoice for "+str(member)+". Nothing to do."
     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)
+
+    member.balance = compute_balance(this_member_invoices, this_member_payments)
+    member.save()
+
     print " "
-    print " "
-    print " "
+    print "Member "+str(member)+" new balance is "+str(member.balance)
     print " "
 
 
@@ -384,14 +389,16 @@ def reconcile_invoices_and_payments(invoices, payments):
     """
     Rapproche des factures et des paiements qui sont actifs (paiement non alloué
     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_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):
         print "No more active payment. Nothing to reconcile anymore."
@@ -411,10 +418,20 @@ def reconcile_invoices_and_payments(invoices, payments):
     p.allocate_to_invoice(i)
 
     # Reconcicle next payment / invoice
-
+    
     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():
 
@@ -449,3 +466,7 @@ def test_accounting_update():
     payment = Payment.objects.create(amount=20,
                                      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,
                         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 :
     # username, first_name, last_name, email
     # 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" %}
 
 {% block content %}
+
+<h2>Balance : {{ balance }} €</h2>
+
 <h2>Mes factures</h2>
 
 <table id="member_invoices" class="full-width">
@@ -28,6 +31,31 @@
     </tbody>
 </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>
 <div id="payment-howto" class="panel">
     {% include "billing/payment_howto.html" %}

+ 5 - 1
coin/members/views.py

@@ -37,10 +37,14 @@ def subscriptions(request):
 
 @login_required
 def invoices(request):
+    balance  = request.user.balance
     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',
-                              {'invoices': invoices},
+                              {'balance' : balance, 
+                               'invoices': invoices, 
+                               'payments': payments},
                               context_instance=RequestContext(request))