|
@@ -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()
|