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()
|
||||
|
||||
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):
|
||||
''' Updates the status of this invoice based upon the total
|
||||
payments.'''
|
||||
|
||||
old_status = self.invoice.status
|
||||
total_paid = self.total_payments()
|
||||
total_paid = self.invoice.total_payments()
|
||||
num_payments = commerce.PaymentBase.objects.filter(
|
||||
invoice=self.invoice,
|
||||
).count()
|
||||
|
@ -366,7 +359,7 @@ class InvoiceController(ForId, object):
|
|||
def update_validity(self):
|
||||
''' Voids this invoice if the cart it is attached to has updated. '''
|
||||
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
|
||||
self.refund()
|
||||
else:
|
||||
|
@ -374,7 +367,7 @@ class InvoiceController(ForId, object):
|
|||
|
||||
def void(self):
|
||||
''' 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.")
|
||||
elif self.invoice.is_refunded:
|
||||
raise ValidationError("Refunded invoices may not be voided.")
|
||||
|
@ -394,7 +387,7 @@ class InvoiceController(ForId, object):
|
|||
raise ValidationError("Void invoices cannot be refunded")
|
||||
|
||||
# Raises a credit note fot the value of the invoice.
|
||||
amount = self.total_payments()
|
||||
amount = self.invoice.total_payments()
|
||||
|
||||
if amount == 0:
|
||||
self.void()
|
||||
|
|
|
@ -4,7 +4,7 @@ from . import inventory
|
|||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
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.encoding import python_2_unicode_compatible
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
@ -175,6 +175,17 @@ class Invoice(models.Model):
|
|||
def is_refunded(self):
|
||||
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
|
||||
user = models.ForeignKey(User)
|
||||
cart = models.ForeignKey(Cart, null=True)
|
||||
|
@ -224,6 +235,11 @@ class LineItem(models.Model):
|
|||
return "Line: %s * %d @ %s" % (
|
||||
self.description, self.quantity, self.price)
|
||||
|
||||
@property
|
||||
def total_price(self):
|
||||
''' price * quantity '''
|
||||
return self.price * self.quantity
|
||||
|
||||
invoice = models.ForeignKey(Invoice)
|
||||
description = models.CharField(max_length=255)
|
||||
quantity = models.PositiveIntegerField()
|
||||
|
|
|
@ -74,24 +74,3 @@ def items_purchased(context, category=None):
|
|||
return ItemController(context.request.user).items_purchased(
|
||||
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)
|
||||
|
||||
# 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)
|
||||
|
||||
# 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)
|
||||
|
||||
# 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)
|
||||
|
||||
# There should be no credit notes
|
||||
|
@ -64,7 +68,7 @@ class CreditNoteTestCase(TestHelperMixin, RegistrationCartTestCase):
|
|||
invoice.refund()
|
||||
|
||||
# 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)
|
||||
|
||||
# There should be a credit note generated out of the invoice.
|
||||
|
@ -84,7 +88,7 @@ class CreditNoteTestCase(TestHelperMixin, RegistrationCartTestCase):
|
|||
invoice.refund()
|
||||
|
||||
# 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)
|
||||
|
||||
# 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()
|
||||
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)
|
||||
|
||||
user_unclaimed = commerce.CreditNote.unclaimed()
|
||||
|
@ -384,7 +388,7 @@ class CreditNoteTestCase(TestHelperMixin, RegistrationCartTestCase):
|
|||
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)
|
||||
|
||||
user_unclaimed = commerce.CreditNote.unclaimed().filter(
|
||||
|
@ -426,7 +430,7 @@ class CreditNoteTestCase(TestHelperMixin, RegistrationCartTestCase):
|
|||
|
||||
# Because there's already an invoice open for this user
|
||||
# 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)
|
||||
|
||||
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)
|
||||
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):
|
||||
voucher = inventory.Voucher.objects.create(
|
||||
recipient="Voucher recipient",
|
||||
|
|
Loading…
Reference in a new issue