Browse Source

Trying to clarify reconciliation algorithm

Alexandre Aubin 7 years ago
parent
commit
d5165ea3d5
1 changed files with 54 additions and 46 deletions
  1. 54 46
      coin/billing/models.py

+ 54 - 46
coin/billing/models.py

@@ -438,43 +438,44 @@ class Payment(models.Model):
         verbose_name = 'paiement'
 
 
+def get_active_payment_and_invoices(member):
+
+    # Fetch relevant and active payments / invoices
+    # and sort then by chronological order : olders first, newers last.
+
+    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)
+
+    active_payments = [p for p in this_member_payments if p.amount_not_allocated()    > 0]
+    active_invoices = [p for p in this_member_invoices if p.amount_remaining_to_pay() > 0]
+
+    return active_payments, active_invoices
+
+
 def update_accounting_for_member(member):
     """
     Met à jour le status des factures, des paiements et le solde du compte
     d'un utilisateur
     """
 
-    accounting_log.info("Member %s current balance is %f ..."
-                        % (str(member), float(member.balance)))
     accounting_log.info("Updating accounting for member %s ..."
                         % str(member))
+    accounting_log.info("Member %s current balance is %f ..."
+                        % (str(member), float(member.balance)))
 
-    # Fetch relevant and active payments / invoices
-    # and sort then by chronological order : olders first, newers last.
-
-    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)
+    reconcile_invoices_and_payments(member)
 
-    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])
-
-    if (number_of_active_payments == 0):
-        accounting_log.info("(No active payment for %s. No invoice/payment "
-                            "reconciliation needed for now.)."
-                            % str(member))
-    elif (number_of_active_invoices == 0):
-        accounting_log.info("(No active invoice for %s. No invoice/payment "
-                            "reconciliation needed for now.)."
-                            % str(member))
-    else:
-        accounting_log.info("Initiating reconciliation between "
-                            "invoice and payments for %s" % str(member))
-        reconcile_invoices_and_payments(this_member_invoices,
-                                        this_member_payments)
+    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")]
 
     member.balance = compute_balance(this_member_invoices,
                                      this_member_payments)
@@ -484,39 +485,46 @@ def update_accounting_for_member(member):
                         % (str(member),  float(member.balance)))
 
 
-def reconcile_invoices_and_payments(invoices, payments):
+def reconcile_invoices_and_payments(member):
     """
     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]
+    active_payments, active_invoices = get_active_payment_and_invoices(member)
 
-    if (len(active_payments) == 0):
-        accounting_log.info("No more active payment. Nothing to reconcile "
-                            "anymore.")
+    if active_payments == []:
+        accounting_log.info("(No active payment for %s. No invoice/payment "
+                            "reconciliation needed.)."
+                            % str(member))
         return
-    if (len(active_invoices) == 0):
-        accounting_log.info("No more active invoice. Nothing to reconcile "
-                            "anymore.")
+    elif active_invoices == []:
+        accounting_log.info("(No active invoice for %s. No invoice/payment "
+                            "reconciliation needed.)."
+                            % str(member))
         return
 
-    # Only consider the oldest active payment and the oldest active invoice
+    accounting_log.info("Initiating reconciliation between "
+                        "invoice and payments for %s" % str(member))
+
+    while active_payments != [] and active_invoices != []:
 
-    p = active_payments[0]
-    i = active_invoices[0]
+        # Only consider the oldest active payment and the oldest active invoice
+        p = active_payments[0]
+        i = active_invoices[0]
 
-    # TODO : should add an assert that the ammount not allocated / remaining to
-    # pay is lower before and after calling the allocate_to_invoice
+        # TODO : should add an assert that the ammount not allocated / remaining to
+        # pay is lower before and after calling the allocate_to_invoice
 
-    p.allocate_to_invoice(i)
+        p.allocate_to_invoice(i)
 
-    # Reconcicle next payment / invoice
+        active_payments, active_invoices = get_active_payment_and_invoices(member)
 
-    reconcile_invoices_and_payments(invoices, payments)
+    if active_payments == []:
+        accounting_log.info("No more active payment. Nothing to reconcile anymore.")
+    elif active_invoices == []:
+        accounting_log.info("No more active invoice. Nothing to reconcile anymore.")
+    return
 
 
 def compute_balance(invoices, payments):