Moves total_payments() to Invoice model; adds balance_due()
This commit is contained in:
parent
fc81f107ed
commit
4a50d69936
5 changed files with 43 additions and 40 deletions
|
@ -269,19 +269,12 @@ class InvoiceController(ForId, object):
|
||||||
|
|
||||||
CartController(self.invoice.cart).validate_cart()
|
CartController(self.invoice.cart).validate_cart()
|
||||||
|
|
||||||
def total_payments(self):
|
|
||||||
''' Returns the total amount paid towards this invoice. '''
|
|
||||||
|
|
||||||
payments = commerce.PaymentBase.objects.filter(invoice=self.invoice)
|
|
||||||
total_paid = payments.aggregate(Sum("amount"))["amount__sum"] or 0
|
|
||||||
return total_paid
|
|
||||||
|
|
||||||
def update_status(self):
|
def update_status(self):
|
||||||
''' Updates the status of this invoice based upon the total
|
''' Updates the status of this invoice based upon the total
|
||||||
payments.'''
|
payments.'''
|
||||||
|
|
||||||
old_status = self.invoice.status
|
old_status = self.invoice.status
|
||||||
total_paid = self.total_payments()
|
total_paid = self.invoice.total_payments()
|
||||||
num_payments = commerce.PaymentBase.objects.filter(
|
num_payments = commerce.PaymentBase.objects.filter(
|
||||||
invoice=self.invoice,
|
invoice=self.invoice,
|
||||||
).count()
|
).count()
|
||||||
|
@ -366,7 +359,7 @@ class InvoiceController(ForId, object):
|
||||||
def update_validity(self):
|
def update_validity(self):
|
||||||
''' Voids this invoice if the cart it is attached to has updated. '''
|
''' Voids this invoice if the cart it is attached to has updated. '''
|
||||||
if not self._invoice_matches_cart():
|
if not self._invoice_matches_cart():
|
||||||
if self.total_payments() > 0:
|
if self.invoice.total_payments() > 0:
|
||||||
# Free up the payments made to this invoice
|
# Free up the payments made to this invoice
|
||||||
self.refund()
|
self.refund()
|
||||||
else:
|
else:
|
||||||
|
@ -374,7 +367,7 @@ class InvoiceController(ForId, object):
|
||||||
|
|
||||||
def void(self):
|
def void(self):
|
||||||
''' Voids the invoice if it is valid to do so. '''
|
''' Voids the invoice if it is valid to do so. '''
|
||||||
if self.total_payments() > 0:
|
if self.invoice.total_payments() > 0:
|
||||||
raise ValidationError("Invoices with payments must be refunded.")
|
raise ValidationError("Invoices with payments must be refunded.")
|
||||||
elif self.invoice.is_refunded:
|
elif self.invoice.is_refunded:
|
||||||
raise ValidationError("Refunded invoices may not be voided.")
|
raise ValidationError("Refunded invoices may not be voided.")
|
||||||
|
@ -394,7 +387,7 @@ class InvoiceController(ForId, object):
|
||||||
raise ValidationError("Void invoices cannot be refunded")
|
raise ValidationError("Void invoices cannot be refunded")
|
||||||
|
|
||||||
# Raises a credit note fot the value of the invoice.
|
# Raises a credit note fot the value of the invoice.
|
||||||
amount = self.total_payments()
|
amount = self.invoice.total_payments()
|
||||||
|
|
||||||
if amount == 0:
|
if amount == 0:
|
||||||
self.void()
|
self.void()
|
||||||
|
|
|
@ -4,7 +4,7 @@ from . import inventory
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import F, Q
|
from django.db.models import F, Q, Sum
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
@ -175,6 +175,17 @@ class Invoice(models.Model):
|
||||||
def is_refunded(self):
|
def is_refunded(self):
|
||||||
return self.status == self.STATUS_REFUNDED
|
return self.status == self.STATUS_REFUNDED
|
||||||
|
|
||||||
|
def total_payments(self):
|
||||||
|
''' Returns the total amount paid towards this invoice. '''
|
||||||
|
|
||||||
|
payments = PaymentBase.objects.filter(invoice=self)
|
||||||
|
total_paid = payments.aggregate(Sum("amount"))["amount__sum"] or 0
|
||||||
|
return total_paid
|
||||||
|
|
||||||
|
def balance_due(self):
|
||||||
|
''' Returns the total balance remaining towards this invoice. '''
|
||||||
|
return self.value - self.total_payments()
|
||||||
|
|
||||||
# Invoice Number
|
# Invoice Number
|
||||||
user = models.ForeignKey(User)
|
user = models.ForeignKey(User)
|
||||||
cart = models.ForeignKey(Cart, null=True)
|
cart = models.ForeignKey(Cart, null=True)
|
||||||
|
@ -224,6 +235,11 @@ class LineItem(models.Model):
|
||||||
return "Line: %s * %d @ %s" % (
|
return "Line: %s * %d @ %s" % (
|
||||||
self.description, self.quantity, self.price)
|
self.description, self.quantity, self.price)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total_price(self):
|
||||||
|
''' price * quantity '''
|
||||||
|
return self.price * self.quantity
|
||||||
|
|
||||||
invoice = models.ForeignKey(Invoice)
|
invoice = models.ForeignKey(Invoice)
|
||||||
description = models.CharField(max_length=255)
|
description = models.CharField(max_length=255)
|
||||||
quantity = models.PositiveIntegerField()
|
quantity = models.PositiveIntegerField()
|
||||||
|
|
|
@ -74,24 +74,3 @@ def items_purchased(context, category=None):
|
||||||
return ItemController(context.request.user).items_purchased(
|
return ItemController(context.request.user).items_purchased(
|
||||||
category=category
|
category=category
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
|
||||||
def multiply(value, arg):
|
|
||||||
''' Multiplies value by arg.
|
|
||||||
|
|
||||||
This is useful when displaying invoices, as it lets you multiply the
|
|
||||||
quantity by the unit value.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
|
|
||||||
value (number)
|
|
||||||
|
|
||||||
arg (number)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
number: value * arg
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
return value * arg
|
|
||||||
|
|
|
@ -29,7 +29,9 @@ class CreditNoteTestCase(TestHelperMixin, RegistrationCartTestCase):
|
||||||
invoice.pay("Reference", to_pay)
|
invoice.pay("Reference", to_pay)
|
||||||
|
|
||||||
# The total paid should be equal to the value of the invoice only
|
# The total paid should be equal to the value of the invoice only
|
||||||
self.assertEqual(invoice.invoice.value, invoice.total_payments())
|
self.assertEqual(
|
||||||
|
invoice.invoice.value, invoice.invoice.total_payments()
|
||||||
|
)
|
||||||
self.assertTrue(invoice.invoice.is_paid)
|
self.assertTrue(invoice.invoice.is_paid)
|
||||||
|
|
||||||
# There should be a credit note generated out of the invoice.
|
# There should be a credit note generated out of the invoice.
|
||||||
|
@ -46,7 +48,9 @@ class CreditNoteTestCase(TestHelperMixin, RegistrationCartTestCase):
|
||||||
invoice.pay("Reference", invoice.invoice.value)
|
invoice.pay("Reference", invoice.invoice.value)
|
||||||
|
|
||||||
# The total paid should be equal to the value of the invoice only
|
# The total paid should be equal to the value of the invoice only
|
||||||
self.assertEqual(invoice.invoice.value, invoice.total_payments())
|
self.assertEqual(
|
||||||
|
invoice.invoice.value, invoice.invoice.total_payments()
|
||||||
|
)
|
||||||
self.assertTrue(invoice.invoice.is_paid)
|
self.assertTrue(invoice.invoice.is_paid)
|
||||||
|
|
||||||
# There should be no credit notes
|
# There should be no credit notes
|
||||||
|
@ -64,7 +68,7 @@ class CreditNoteTestCase(TestHelperMixin, RegistrationCartTestCase):
|
||||||
invoice.refund()
|
invoice.refund()
|
||||||
|
|
||||||
# The total paid should be zero
|
# The total paid should be zero
|
||||||
self.assertEqual(0, invoice.total_payments())
|
self.assertEqual(0, invoice.invoice.total_payments())
|
||||||
self.assertTrue(invoice.invoice.is_void)
|
self.assertTrue(invoice.invoice.is_void)
|
||||||
|
|
||||||
# There should be a credit note generated out of the invoice.
|
# There should be a credit note generated out of the invoice.
|
||||||
|
@ -84,7 +88,7 @@ class CreditNoteTestCase(TestHelperMixin, RegistrationCartTestCase):
|
||||||
invoice.refund()
|
invoice.refund()
|
||||||
|
|
||||||
# The total paid should be zero
|
# The total paid should be zero
|
||||||
self.assertEqual(0, invoice.total_payments())
|
self.assertEqual(0, invoice.invoice.total_payments())
|
||||||
self.assertTrue(invoice.invoice.is_refunded)
|
self.assertTrue(invoice.invoice.is_refunded)
|
||||||
|
|
||||||
# There should be a credit note generated out of the invoice.
|
# There should be a credit note generated out of the invoice.
|
||||||
|
@ -367,7 +371,7 @@ class CreditNoteTestCase(TestHelperMixin, RegistrationCartTestCase):
|
||||||
notes_value = self._generate_multiple_credit_notes()
|
notes_value = self._generate_multiple_credit_notes()
|
||||||
invoice = self._manual_invoice(notes_value + 1)
|
invoice = self._manual_invoice(notes_value + 1)
|
||||||
|
|
||||||
self.assertEqual(notes_value, invoice.total_payments())
|
self.assertEqual(notes_value, invoice.invoice.total_payments())
|
||||||
self.assertTrue(invoice.invoice.is_unpaid)
|
self.assertTrue(invoice.invoice.is_unpaid)
|
||||||
|
|
||||||
user_unclaimed = commerce.CreditNote.unclaimed()
|
user_unclaimed = commerce.CreditNote.unclaimed()
|
||||||
|
@ -384,7 +388,7 @@ class CreditNoteTestCase(TestHelperMixin, RegistrationCartTestCase):
|
||||||
invoice = self._manual_invoice(notes_value - 1)
|
invoice = self._manual_invoice(notes_value - 1)
|
||||||
|
|
||||||
|
|
||||||
self.assertEqual(notes_value - 1, invoice.total_payments())
|
self.assertEqual(notes_value - 1, invoice.invoice.total_payments())
|
||||||
self.assertTrue(invoice.invoice.is_paid)
|
self.assertTrue(invoice.invoice.is_paid)
|
||||||
|
|
||||||
user_unclaimed = commerce.CreditNote.unclaimed().filter(
|
user_unclaimed = commerce.CreditNote.unclaimed().filter(
|
||||||
|
@ -426,7 +430,7 @@ class CreditNoteTestCase(TestHelperMixin, RegistrationCartTestCase):
|
||||||
|
|
||||||
# Because there's already an invoice open for this user
|
# Because there's already an invoice open for this user
|
||||||
# The credit notes are not automatically applied.
|
# The credit notes are not automatically applied.
|
||||||
self.assertEqual(0, invoice.total_payments())
|
self.assertEqual(0, invoice.invoice.total_payments())
|
||||||
self.assertTrue(invoice.invoice.is_unpaid)
|
self.assertTrue(invoice.invoice.is_unpaid)
|
||||||
|
|
||||||
def test_credit_notes_are_applied_even_if_some_notes_are_claimed(self):
|
def test_credit_notes_are_applied_even_if_some_notes_are_claimed(self):
|
||||||
|
|
|
@ -97,6 +97,17 @@ class InvoiceTestCase(TestHelperMixin, RegistrationCartTestCase):
|
||||||
new_cart = TestingCartController.for_user(self.USER_1)
|
new_cart = TestingCartController.for_user(self.USER_1)
|
||||||
self.assertNotEqual(invoice.invoice.cart, new_cart.cart)
|
self.assertNotEqual(invoice.invoice.cart, new_cart.cart)
|
||||||
|
|
||||||
|
def test_total_payments_balance_due(self):
|
||||||
|
invoice = self._invoice_containing_prod_1(2)
|
||||||
|
for i in xrange(0, invoice.invoice.value):
|
||||||
|
self.assertTrue(
|
||||||
|
i + 1, invoice.invoice.total_payments()
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
invoice.invoice.value - i, invoice.invoice.balance_due()
|
||||||
|
)
|
||||||
|
invoice.pay("Pay 1", 1)
|
||||||
|
|
||||||
def test_invoice_includes_discounts(self):
|
def test_invoice_includes_discounts(self):
|
||||||
voucher = inventory.Voucher.objects.create(
|
voucher = inventory.Voucher.objects.create(
|
||||||
recipient="Voucher recipient",
|
recipient="Voucher recipient",
|
||||||
|
|
Loading…
Reference in a new issue