|
@@ -148,11 +148,6 @@ class Invoice(models.Model):
|
|
|
null=True, blank=True,
|
|
|
verbose_name='PDF')
|
|
|
|
|
|
- amount_paid = models.DecimalField(max_digits=5,
|
|
|
- decimal_places=2,
|
|
|
- default=0,
|
|
|
- verbose_name='montant payé')
|
|
|
-
|
|
|
date_last_reminder_email = models.DateTimeField(null=True, blank=True,
|
|
|
verbose_name="Date du dernier email de relance envoyé")
|
|
|
|
|
@@ -184,20 +179,16 @@ class Invoice(models.Model):
|
|
|
|
|
|
def amount_paid(self):
|
|
|
"""
|
|
|
- Calcul le montant payé de la facture en fonction des éléments
|
|
|
- de paiements
|
|
|
+ Calcul le montant déjà payé à partir des allocations de paiements
|
|
|
"""
|
|
|
- total = Decimal('0.0')
|
|
|
- for payment in self.payments.all():
|
|
|
- total += payment.amount
|
|
|
- return total.quantize(Decimal('0.01'))
|
|
|
+ return sum([a.amount for a in self.allocations.all()])
|
|
|
amount_paid.short_description = 'Montant payé'
|
|
|
|
|
|
def amount_remaining_to_pay(self):
|
|
|
"""
|
|
|
Calcul le montant restant à payer
|
|
|
"""
|
|
|
- return self.amount() - self.amount_paid
|
|
|
+ return self.amount() - self.amount_paid()
|
|
|
amount_remaining_to_pay.short_description = 'Reste à payer'
|
|
|
|
|
|
def has_owner(self, username):
|
|
@@ -380,11 +371,6 @@ class Payment(models.Model):
|
|
|
invoice = models.ForeignKey(Invoice, verbose_name='facture associée', null=True,
|
|
|
blank=True, related_name='payments')
|
|
|
|
|
|
- amount_already_allocated = models.DecimalField(max_digits=5,
|
|
|
- decimal_places=2,
|
|
|
- null=True, blank=True, default=0.0,
|
|
|
- verbose_name='montant déjà alloué')
|
|
|
-
|
|
|
label = models.CharField(max_length=500,
|
|
|
null=True, blank=True, default="",
|
|
|
verbose_name='libellé')
|
|
@@ -392,7 +378,7 @@ class Payment(models.Model):
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
|
|
# Only if no amount already allocated...
|
|
|
- if self.amount_already_allocated == 0:
|
|
|
+ if self.amount_already_allocated() == 0:
|
|
|
|
|
|
# If there's a linked invoice and no member defined
|
|
|
if self.invoice and not self.member:
|
|
@@ -404,20 +390,26 @@ class Payment(models.Model):
|
|
|
|
|
|
def clean(self):
|
|
|
|
|
|
- # Only if no amount already allocated...
|
|
|
- if self.amount_already_allocated == 0:
|
|
|
+ # Only if no amount already alloca ted...
|
|
|
+ if self.amount_already_allocated() == 0:
|
|
|
|
|
|
# If there's a linked invoice and this payment would pay more than
|
|
|
# the remaining amount needed to pay the invoice...
|
|
|
if self.invoice and self.amount > self.invoice.amount_remaining_to_pay():
|
|
|
raise ValidationError("This payment would pay more than the invoice's remaining to pay")
|
|
|
|
|
|
+ def amount_already_allocated(self):
|
|
|
+ return sum([ a.amount for a in self.allocations.all() ])
|
|
|
|
|
|
def amount_not_allocated(self):
|
|
|
- return self.amount - self.amount_already_allocated
|
|
|
+ return self.amount - self.amount_already_allocated()
|
|
|
|
|
|
@transaction.atomic
|
|
|
def allocate_to_invoice(self, invoice):
|
|
|
+
|
|
|
+ # FIXME - Add asserts about remaining amount > 0, unpaid amount > 0,
|
|
|
+ # ...
|
|
|
+
|
|
|
amount_can_pay = self.amount_not_allocated()
|
|
|
amount_to_pay = invoice.amount_remaining_to_pay()
|
|
|
amount_to_allocate = min(amount_can_pay, amount_to_pay)
|
|
@@ -426,8 +418,9 @@ class Payment(models.Model):
|
|
|
% (float(amount_to_allocate), str(self.date),
|
|
|
invoice.number))
|
|
|
|
|
|
- self.amount_already_allocated += amount_to_allocate
|
|
|
- invoice.amount_paid += amount_to_allocate
|
|
|
+ PaymentAllocation.objects.create(invoice=invoice,
|
|
|
+ payment=self,
|
|
|
+ amount=amount_to_allocate)
|
|
|
|
|
|
# Close invoice if relevant
|
|
|
if (invoice.amount_remaining_to_pay() <= 0) and (invoice.status == "open"):
|
|
@@ -450,6 +443,22 @@ class Payment(models.Model):
|
|
|
verbose_name = 'paiement'
|
|
|
|
|
|
|
|
|
+# This corresponds to a (possibly partial) allocation of a given payment to
|
|
|
+# a given invoice.
|
|
|
+# E.g. consider an invoice I with total 15€ and a payment P with 10€.
|
|
|
+# There can be for example an allocation of 3.14€ from P to I.
|
|
|
+class PaymentAllocation(models.Model):
|
|
|
+
|
|
|
+ invoice = models.ForeignKey(Invoice, verbose_name='facture associée',
|
|
|
+ null=False, blank=False,
|
|
|
+ related_name='allocations')
|
|
|
+ payment = models.ForeignKey(Payment, verbose_name='facture associée',
|
|
|
+ null=False, blank=False,
|
|
|
+ related_name='allocations')
|
|
|
+ amount = models.DecimalField(max_digits=5, decimal_places=2, null=True,
|
|
|
+ verbose_name='montant')
|
|
|
+
|
|
|
+
|
|
|
def get_active_payment_and_invoices(member):
|
|
|
|
|
|
# Fetch relevant and active payments / invoices
|
|
@@ -566,7 +575,7 @@ def payment_changed(sender, instance, created, **kwargs):
|
|
|
accounting_log.info("Updating payment %s (Date: %s, Member: %s, Amount: %s, Label: %s, Allocated: %s)."
|
|
|
% (instance.pk, instance.date, instance.member,
|
|
|
instance.amount, instance.label.encode('utf-8'),
|
|
|
- instance.amount_already_allocated))
|
|
|
+ instance.amount_already_allocated()))
|
|
|
|
|
|
# If this payment is related to a member, update the accounting for
|
|
|
# this member
|
|
@@ -588,7 +597,7 @@ def invoice_changed(sender, instance, created, **kwargs):
|
|
|
% (instance.number, instance.member))
|
|
|
else:
|
|
|
accounting_log.info("Updating invoice %s (Member: %s, Total amount: %s, Amount paid: %s)."
|
|
|
- % (instance.number, instance.member, instance.amount(), instance.amount_paid ))
|
|
|
+ % (instance.number, instance.member, instance.amount(), instance.amount_paid() ))
|
|
|
|
|
|
|
|
|
def test_accounting_update():
|