From 4a50d699365b9b7befec74261edfd1a5925f58e2 2016-09-15 23:35:12 From: Christopher Neugebauer Date: 2016-09-15 23:35:12 Subject: [PATCH] Moves total_payments() to Invoice model; adds balance_due() --- diff --git a/registrasion/controllers/invoice.py b/registrasion/controllers/invoice.py index d4be50b121af5c588cb4795b2fa5b73f615555e8..84af2a87cf3c4f32c60ef48af19ea5a86452bebf 100644 --- a/registrasion/controllers/invoice.py +++ b/registrasion/controllers/invoice.py @@ -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() diff --git a/registrasion/models/commerce.py b/registrasion/models/commerce.py index a9fc7341a061739701a95ff158a5a8af6bdc2512..0ab035d3ab6d09e04779cea63ba29e5b772770a7 100644 --- a/registrasion/models/commerce.py +++ b/registrasion/models/commerce.py @@ -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() diff --git a/registrasion/templatetags/registrasion_tags.py b/registrasion/templatetags/registrasion_tags.py index 89db7e252ed2364dc8fe4c3527f91ff80fe58bc8..fda5b2c0a57774cfba312fdf62d1f85f29c180ed 100644 --- a/registrasion/templatetags/registrasion_tags.py +++ b/registrasion/templatetags/registrasion_tags.py @@ -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 diff --git a/registrasion/tests/test_credit_note.py b/registrasion/tests/test_credit_note.py index c4be104161296c33af49be580bc6ecec886036f2..5cd43af2793265ace12fd32dd3b5632b47f09295 100644 --- a/registrasion/tests/test_credit_note.py +++ b/registrasion/tests/test_credit_note.py @@ -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): diff --git a/registrasion/tests/test_invoice.py b/registrasion/tests/test_invoice.py index 6ae662a327f09482f8e596202dea1ed2cad0e908..a1769fe0b18376e7ea3ae0cffce83fc6b8dd47e4 100644 --- a/registrasion/tests/test_invoice.py +++ b/registrasion/tests/test_invoice.py @@ -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",