Merge branch 'refunds'
This commit is contained in:
		
						commit
						c790d5afd0
					
				
					 13 changed files with 287 additions and 19 deletions
				
			
		| 
						 | 
					@ -139,6 +139,7 @@ class CartController(object):
 | 
				
			||||||
        # It's not valid for users to re-enter a voucher they already have
 | 
					        # It's not valid for users to re-enter a voucher they already have
 | 
				
			||||||
        user_carts_with_voucher = rego.Cart.objects.filter(
 | 
					        user_carts_with_voucher = rego.Cart.objects.filter(
 | 
				
			||||||
            user=self.cart.user,
 | 
					            user=self.cart.user,
 | 
				
			||||||
 | 
					            released=False,
 | 
				
			||||||
            vouchers=voucher,
 | 
					            vouchers=voucher,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        if len(user_carts_with_voucher) > 0:
 | 
					        if len(user_carts_with_voucher) > 0:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,7 +44,7 @@ class CategoryConditionController(ConditionController):
 | 
				
			||||||
        ''' returns True if the user has a product from a category that invokes
 | 
					        ''' returns True if the user has a product from a category that invokes
 | 
				
			||||||
        this condition in one of their carts '''
 | 
					        this condition in one of their carts '''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        carts = rego.Cart.objects.filter(user=user)
 | 
					        carts = rego.Cart.objects.filter(user=user, released=False)
 | 
				
			||||||
        enabling_products = rego.Product.objects.filter(
 | 
					        enabling_products = rego.Product.objects.filter(
 | 
				
			||||||
            category=self.condition.enabling_category)
 | 
					            category=self.condition.enabling_category)
 | 
				
			||||||
        products = rego.ProductItem.objects.filter(
 | 
					        products = rego.ProductItem.objects.filter(
 | 
				
			||||||
| 
						 | 
					@ -64,7 +64,7 @@ class ProductConditionController(ConditionController):
 | 
				
			||||||
        ''' returns True if the user has a product that invokes this
 | 
					        ''' returns True if the user has a product that invokes this
 | 
				
			||||||
        condition in one of their carts '''
 | 
					        condition in one of their carts '''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        carts = rego.Cart.objects.filter(user=user)
 | 
					        carts = rego.Cart.objects.filter(user=user, released=False)
 | 
				
			||||||
        products = rego.ProductItem.objects.filter(
 | 
					        products = rego.ProductItem.objects.filter(
 | 
				
			||||||
            cart=carts,
 | 
					            cart=carts,
 | 
				
			||||||
            product=self.condition.enabling_products.all())
 | 
					            product=self.condition.enabling_products.all())
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,11 @@ class DiscountAndQuantity(object):
 | 
				
			||||||
        self.clause = clause
 | 
					        self.clause = clause
 | 
				
			||||||
        self.quantity = quantity
 | 
					        self.quantity = quantity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __repr__(self):
 | 
				
			||||||
 | 
					        print "(discount=%s, clause=%s, quantity=%d)" % (
 | 
				
			||||||
 | 
					            self.discount, self.clause, self.quantity,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def available_discounts(user, categories, products):
 | 
					def available_discounts(user, categories, products):
 | 
				
			||||||
    ''' Returns all discounts available to this user for the given categories
 | 
					    ''' Returns all discounts available to this user for the given categories
 | 
				
			||||||
| 
						 | 
					@ -57,6 +62,7 @@ def available_discounts(user, categories, products):
 | 
				
			||||||
        past_uses = rego.DiscountItem.objects.filter(
 | 
					        past_uses = rego.DiscountItem.objects.filter(
 | 
				
			||||||
            cart__user=user,
 | 
					            cart__user=user,
 | 
				
			||||||
            cart__active=False,  # Only past carts count
 | 
					            cart__active=False,  # Only past carts count
 | 
				
			||||||
 | 
					            cart__released=False,  # You can reuse refunded discounts
 | 
				
			||||||
            discount=discount.discount,
 | 
					            discount=discount.discount,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        agg = past_uses.aggregate(Sum("quantity"))
 | 
					        agg = past_uses.aggregate(Sum("quantity"))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,7 @@
 | 
				
			||||||
from decimal import Decimal
 | 
					from decimal import Decimal
 | 
				
			||||||
from django.core.exceptions import ObjectDoesNotExist
 | 
					from django.core.exceptions import ObjectDoesNotExist
 | 
				
			||||||
 | 
					from django.core.exceptions import ValidationError
 | 
				
			||||||
 | 
					from django.db import transaction
 | 
				
			||||||
from django.db.models import Sum
 | 
					from django.db.models import Sum
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from registrasion import models as rego
 | 
					from registrasion import models as rego
 | 
				
			||||||
| 
						 | 
					@ -11,6 +13,7 @@ class InvoiceController(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, invoice):
 | 
					    def __init__(self, invoice):
 | 
				
			||||||
        self.invoice = invoice
 | 
					        self.invoice = invoice
 | 
				
			||||||
 | 
					        self.update_validity()  # Make sure this invoice is up-to-date
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def for_cart(cls, cart):
 | 
					    def for_cart(cls, cart):
 | 
				
			||||||
| 
						 | 
					@ -20,10 +23,17 @@ class InvoiceController(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            invoice = rego.Invoice.objects.get(
 | 
					            invoice = rego.Invoice.objects.get(
 | 
				
			||||||
                cart=cart, cart_revision=cart.revision)
 | 
					                cart=cart,
 | 
				
			||||||
 | 
					                cart_revision=cart.revision,
 | 
				
			||||||
 | 
					                void=False,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        except ObjectDoesNotExist:
 | 
					        except ObjectDoesNotExist:
 | 
				
			||||||
            cart_controller = CartController(cart)
 | 
					            cart_controller = CartController(cart)
 | 
				
			||||||
            cart_controller.validate_cart()  # Raises ValidationError on fail.
 | 
					            cart_controller.validate_cart()  # Raises ValidationError on fail.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Void past invoices for this cart
 | 
				
			||||||
 | 
					            rego.Invoice.objects.filter(cart=cart).update(void=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            invoice = cls._generate(cart)
 | 
					            invoice = cls._generate(cart)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return InvoiceController(invoice)
 | 
					        return InvoiceController(invoice)
 | 
				
			||||||
| 
						 | 
					@ -91,29 +101,37 @@ class InvoiceController(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return invoice
 | 
					        return invoice
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def is_valid(self):
 | 
					    def update_validity(self):
 | 
				
			||||||
        ''' Returns true if the attached invoice is not void and it represents
 | 
					        ''' Updates the validity of this invoice if the cart it is attached to
 | 
				
			||||||
        a valid cart. '''
 | 
					        has updated. '''
 | 
				
			||||||
        if self.invoice.void:
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
        if self.invoice.cart is not None:
 | 
					        if self.invoice.cart is not None:
 | 
				
			||||||
            if self.invoice.cart.revision != self.invoice.cart_revision:
 | 
					            if self.invoice.cart.revision != self.invoice.cart_revision:
 | 
				
			||||||
                return False
 | 
					                self.void()
 | 
				
			||||||
        return True
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def void(self):
 | 
					    def void(self):
 | 
				
			||||||
        ''' Voids the invoice. '''
 | 
					        ''' Voids the invoice if it is valid to do so. '''
 | 
				
			||||||
 | 
					        if self.invoice.paid:
 | 
				
			||||||
 | 
					            raise ValidationError("Paid invoices cannot be voided, "
 | 
				
			||||||
 | 
					                                  "only refunded.")
 | 
				
			||||||
        self.invoice.void = True
 | 
					        self.invoice.void = True
 | 
				
			||||||
 | 
					        self.invoice.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @transaction.atomic
 | 
				
			||||||
    def pay(self, reference, amount):
 | 
					    def pay(self, reference, amount):
 | 
				
			||||||
        ''' Pays the invoice by the given amount. If the payment
 | 
					        ''' Pays the invoice by the given amount. If the payment
 | 
				
			||||||
        equals the total on the invoice, finalise the invoice.
 | 
					        equals the total on the invoice, finalise the invoice.
 | 
				
			||||||
        (NB should be transactional.)
 | 
					        (NB should be transactional.)
 | 
				
			||||||
        '''
 | 
					        '''
 | 
				
			||||||
        if self.invoice.cart is not None:
 | 
					        if self.invoice.cart:
 | 
				
			||||||
            cart = CartController(self.invoice.cart)
 | 
					            cart = CartController(self.invoice.cart)
 | 
				
			||||||
            cart.validate_cart()  # Raises ValidationError if invalid
 | 
					            cart.validate_cart()  # Raises ValidationError if invalid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.invoice.void:
 | 
				
			||||||
 | 
					            raise ValidationError("Void invoices cannot be paid")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.invoice.paid:
 | 
				
			||||||
 | 
					            raise ValidationError("Paid invoices cannot be paid again")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ''' Adds a payment '''
 | 
					        ''' Adds a payment '''
 | 
				
			||||||
        payment = rego.Payment.objects.create(
 | 
					        payment = rego.Payment.objects.create(
 | 
				
			||||||
            invoice=self.invoice,
 | 
					            invoice=self.invoice,
 | 
				
			||||||
| 
						 | 
					@ -129,8 +147,36 @@ class InvoiceController(object):
 | 
				
			||||||
        if total == self.invoice.value:
 | 
					        if total == self.invoice.value:
 | 
				
			||||||
            self.invoice.paid = True
 | 
					            self.invoice.paid = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if self.invoice.cart:
 | 
				
			||||||
                cart = self.invoice.cart
 | 
					                cart = self.invoice.cart
 | 
				
			||||||
                cart.active = False
 | 
					                cart.active = False
 | 
				
			||||||
                cart.save()
 | 
					                cart.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            self.invoice.save()
 | 
					            self.invoice.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @transaction.atomic
 | 
				
			||||||
 | 
					    def refund(self, reference, amount):
 | 
				
			||||||
 | 
					        ''' Refunds the invoice by the given amount. The invoice is
 | 
				
			||||||
 | 
					        marked as unpaid, and the underlying cart is marked as released.
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.invoice.void:
 | 
				
			||||||
 | 
					            raise ValidationError("Void invoices cannot be refunded")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ''' Adds a payment '''
 | 
				
			||||||
 | 
					        payment = rego.Payment.objects.create(
 | 
				
			||||||
 | 
					            invoice=self.invoice,
 | 
				
			||||||
 | 
					            reference=reference,
 | 
				
			||||||
 | 
					            amount=0 - amount,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        payment.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.invoice.paid = False
 | 
				
			||||||
 | 
					        self.invoice.void = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.invoice.cart:
 | 
				
			||||||
 | 
					            cart = self.invoice.cart
 | 
				
			||||||
 | 
					            cart.released = True
 | 
				
			||||||
 | 
					            cart.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.invoice.save()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										19
									
								
								registrasion/migrations/0008_cart_released.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								registrasion/migrations/0008_cart_released.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,19 @@
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('registrasion', '0007_auto_20160326_2105'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='cart',
 | 
				
			||||||
 | 
					            name='released',
 | 
				
			||||||
 | 
					            field=models.BooleanField(default=False),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
| 
						 | 
					@ -413,6 +413,7 @@ class Cart(models.Model):
 | 
				
			||||||
    reservation_duration = models.DurationField()
 | 
					    reservation_duration = models.DurationField()
 | 
				
			||||||
    revision = models.PositiveIntegerField(default=1)
 | 
					    revision = models.PositiveIntegerField(default=1)
 | 
				
			||||||
    active = models.BooleanField(default=True)
 | 
					    active = models.BooleanField(default=True)
 | 
				
			||||||
 | 
					    released = models.BooleanField(default=False)  # Refunds etc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def reserved_carts(cls):
 | 
					    def reserved_carts(cls):
 | 
				
			||||||
| 
						 | 
					@ -422,7 +423,7 @@ class Cart(models.Model):
 | 
				
			||||||
                Q(time_last_updated__gt=(
 | 
					                Q(time_last_updated__gt=(
 | 
				
			||||||
                    timezone.now()-F('reservation_duration')
 | 
					                    timezone.now()-F('reservation_duration')
 | 
				
			||||||
                                        ))) |
 | 
					                                        ))) |
 | 
				
			||||||
            Q(active=False)
 | 
					            (Q(active=False) & Q(released=False))
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -52,7 +52,7 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase):
 | 
				
			||||||
        cls.products = []
 | 
					        cls.products = []
 | 
				
			||||||
        for i in xrange(4):
 | 
					        for i in xrange(4):
 | 
				
			||||||
            prod = rego.Product.objects.create(
 | 
					            prod = rego.Product.objects.create(
 | 
				
			||||||
                name="Product 1",
 | 
					                name="Product " + str(i + 1),
 | 
				
			||||||
                description="This is a test product.",
 | 
					                description="This is a test product.",
 | 
				
			||||||
                category=cls.categories[i / 2],  # 2 products per category
 | 
					                category=cls.categories[i / 2],  # 2 products per category
 | 
				
			||||||
                price=Decimal("10.00"),
 | 
					                price=Decimal("10.00"),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -132,3 +132,21 @@ class CeilingsTestCases(RegistrationCartTestCase):
 | 
				
			||||||
        self.add_timedelta(self.RESERVATION + datetime.timedelta(seconds=1))
 | 
					        self.add_timedelta(self.RESERVATION + datetime.timedelta(seconds=1))
 | 
				
			||||||
        with self.assertRaises(ValidationError):
 | 
					        with self.assertRaises(ValidationError):
 | 
				
			||||||
            first_cart.validate_cart()
 | 
					            first_cart.validate_cart()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_items_released_from_ceiling_by_refund(self):
 | 
				
			||||||
 | 
					        self.make_ceiling("Limit ceiling", limit=1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        first_cart = CartController.for_user(self.USER_1)
 | 
				
			||||||
 | 
					        first_cart.add_to_cart(self.PROD_1, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        first_cart.cart.active = False
 | 
				
			||||||
 | 
					        first_cart.cart.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        second_cart = CartController.for_user(self.USER_2)
 | 
				
			||||||
 | 
					        with self.assertRaises(ValidationError):
 | 
				
			||||||
 | 
					            second_cart.add_to_cart(self.PROD_1, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        first_cart.cart.released = True
 | 
				
			||||||
 | 
					        first_cart.cart.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        second_cart.add_to_cart(self.PROD_1, 1)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -377,3 +377,27 @@ class DiscountTestCase(RegistrationCartTestCase):
 | 
				
			||||||
            [self.PROD_3, self.PROD_4],
 | 
					            [self.PROD_3, self.PROD_4],
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self.assertEqual(2, len(discounts))
 | 
					        self.assertEqual(2, len(discounts))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_discounts_are_released_by_refunds(self):
 | 
				
			||||||
 | 
					        self.add_discount_prod_1_includes_prod_2(quantity=2)
 | 
				
			||||||
 | 
					        cart = CartController.for_user(self.USER_1)
 | 
				
			||||||
 | 
					        cart.add_to_cart(self.PROD_1, 1)  # Enable the discount
 | 
				
			||||||
 | 
					        discounts = discount.available_discounts(self.USER_1, [], [self.PROD_2])
 | 
				
			||||||
 | 
					        self.assertEqual(1, len(discounts))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cart.cart.active = False  # Keep discount enabled
 | 
				
			||||||
 | 
					        cart.cart.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cart = CartController.for_user(self.USER_1)
 | 
				
			||||||
 | 
					        cart.add_to_cart(self.PROD_2, 2) # The discount will be exhausted
 | 
				
			||||||
 | 
					        cart.cart.active = False
 | 
				
			||||||
 | 
					        cart.cart.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        discounts = discount.available_discounts(self.USER_1, [], [self.PROD_2])
 | 
				
			||||||
 | 
					        self.assertEqual(0, len(discounts))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cart.cart.released = True
 | 
				
			||||||
 | 
					        cart.cart.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        discounts = discount.available_discounts(self.USER_1, [], [self.PROD_2])
 | 
				
			||||||
 | 
					        self.assertEqual(1, len(discounts))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -233,3 +233,41 @@ class EnablingConditionTestCases(RegistrationCartTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertTrue(self.PROD_1 in prods)
 | 
					        self.assertTrue(self.PROD_1 in prods)
 | 
				
			||||||
        self.assertTrue(self.PROD_2 in prods)
 | 
					        self.assertTrue(self.PROD_2 in prods)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_category_enabling_condition_fails_if_cart_refunded(self):
 | 
				
			||||||
 | 
					        self.add_category_enabling_condition(mandatory=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cart = CartController.for_user(self.USER_1)
 | 
				
			||||||
 | 
					        cart.add_to_cart(self.PROD_3, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cart.cart.active = False
 | 
				
			||||||
 | 
					        cart.cart.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cart_2 = CartController.for_user(self.USER_1)
 | 
				
			||||||
 | 
					        cart_2.add_to_cart(self.PROD_1, 1)
 | 
				
			||||||
 | 
					        cart_2.set_quantity(self.PROD_1, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cart.cart.released = True
 | 
				
			||||||
 | 
					        cart.cart.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with self.assertRaises(ValidationError):
 | 
				
			||||||
 | 
					            cart_2.set_quantity(self.PROD_1, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_product_enabling_condition_fails_if_cart_refunded(self):
 | 
				
			||||||
 | 
					        self.add_product_enabling_condition(mandatory=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cart = CartController.for_user(self.USER_1)
 | 
				
			||||||
 | 
					        cart.add_to_cart(self.PROD_2, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cart.cart.active = False
 | 
				
			||||||
 | 
					        cart.cart.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cart_2 = CartController.for_user(self.USER_1)
 | 
				
			||||||
 | 
					        cart_2.add_to_cart(self.PROD_1, 1)
 | 
				
			||||||
 | 
					        cart_2.set_quantity(self.PROD_1, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cart.cart.released = True
 | 
				
			||||||
 | 
					        cart.cart.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with self.assertRaises(ValidationError):
 | 
				
			||||||
 | 
					            cart_2.set_quantity(self.PROD_1, 1)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,11 +27,17 @@ class InvoiceTestCase(RegistrationCartTestCase):
 | 
				
			||||||
        # That invoice should have a value equal to cost of PROD_1
 | 
					        # That invoice should have a value equal to cost of PROD_1
 | 
				
			||||||
        self.assertEqual(self.PROD_1.price, invoice_1.invoice.value)
 | 
					        self.assertEqual(self.PROD_1.price, invoice_1.invoice.value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Adding item to cart should void all active invoices and produce
 | 
					        # Adding item to cart should produce a new invoice
 | 
				
			||||||
        # a new invoice
 | 
					 | 
				
			||||||
        current_cart.add_to_cart(self.PROD_2, 1)
 | 
					        current_cart.add_to_cart(self.PROD_2, 1)
 | 
				
			||||||
        invoice_2 = InvoiceController.for_cart(current_cart.cart)
 | 
					        invoice_2 = InvoiceController.for_cart(current_cart.cart)
 | 
				
			||||||
        self.assertNotEqual(invoice_1.invoice, invoice_2.invoice)
 | 
					        self.assertNotEqual(invoice_1.invoice, invoice_2.invoice)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # The old invoice should automatically be voided
 | 
				
			||||||
 | 
					        invoice_1_new = rego.Invoice.objects.get(pk=invoice_1.invoice.id)
 | 
				
			||||||
 | 
					        invoice_2_new = rego.Invoice.objects.get(pk=invoice_2.invoice.id)
 | 
				
			||||||
 | 
					        self.assertTrue(invoice_1_new.void)
 | 
				
			||||||
 | 
					        self.assertFalse(invoice_2_new.void)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Invoice should have two line items
 | 
					        # Invoice should have two line items
 | 
				
			||||||
        line_items = rego.LineItem.objects.filter(invoice=invoice_2.invoice)
 | 
					        line_items = rego.LineItem.objects.filter(invoice=invoice_2.invoice)
 | 
				
			||||||
        self.assertEqual(2, len(line_items))
 | 
					        self.assertEqual(2, len(line_items))
 | 
				
			||||||
| 
						 | 
					@ -104,3 +110,62 @@ class InvoiceTestCase(RegistrationCartTestCase):
 | 
				
			||||||
        self.assertEqual(
 | 
					        self.assertEqual(
 | 
				
			||||||
            self.PROD_1.price * Decimal("0.5"),
 | 
					            self.PROD_1.price * Decimal("0.5"),
 | 
				
			||||||
            invoice_1.invoice.value)
 | 
					            invoice_1.invoice.value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_invoice_voids_self_if_cart_is_invalid(self):
 | 
				
			||||||
 | 
					        current_cart = CartController.for_user(self.USER_1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Should be able to create an invoice after the product is added
 | 
				
			||||||
 | 
					        current_cart.add_to_cart(self.PROD_1, 1)
 | 
				
			||||||
 | 
					        invoice_1 = InvoiceController.for_cart(current_cart.cart)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertFalse(invoice_1.invoice.void)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Adding item to cart should produce a new invoice
 | 
				
			||||||
 | 
					        current_cart.add_to_cart(self.PROD_2, 1)
 | 
				
			||||||
 | 
					        invoice_2 = InvoiceController.for_cart(current_cart.cart)
 | 
				
			||||||
 | 
					        self.assertNotEqual(invoice_1.invoice, invoice_2.invoice)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Viewing invoice_1's invoice should show it as void
 | 
				
			||||||
 | 
					        invoice_1_new = InvoiceController(invoice_1.invoice)
 | 
				
			||||||
 | 
					        self.assertTrue(invoice_1_new.invoice.void)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Viewing invoice_2's invoice should *not* show it as void
 | 
				
			||||||
 | 
					        invoice_2_new = InvoiceController(invoice_2.invoice)
 | 
				
			||||||
 | 
					        self.assertFalse(invoice_2_new.invoice.void)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_voiding_invoice_creates_new_invoice(self):
 | 
				
			||||||
 | 
					        current_cart = CartController.for_user(self.USER_1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Should be able to create an invoice after the product is added
 | 
				
			||||||
 | 
					        current_cart.add_to_cart(self.PROD_1, 1)
 | 
				
			||||||
 | 
					        invoice_1 = InvoiceController.for_cart(current_cart.cart)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertFalse(invoice_1.invoice.void)
 | 
				
			||||||
 | 
					        invoice_1.void()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        invoice_2 = InvoiceController.for_cart(current_cart.cart)
 | 
				
			||||||
 | 
					        self.assertNotEqual(invoice_1.invoice, invoice_2.invoice)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_cannot_pay_void_invoice(self):
 | 
				
			||||||
 | 
					        current_cart = CartController.for_user(self.USER_1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Should be able to create an invoice after the product is added
 | 
				
			||||||
 | 
					        current_cart.add_to_cart(self.PROD_1, 1)
 | 
				
			||||||
 | 
					        invoice_1 = InvoiceController.for_cart(current_cart.cart)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        invoice_1.void()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with self.assertRaises(ValidationError):
 | 
				
			||||||
 | 
					            invoice_1.pay("Reference", invoice_1.invoice.value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_cannot_void_paid_invoice(self):
 | 
				
			||||||
 | 
					        current_cart = CartController.for_user(self.USER_1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Should be able to create an invoice after the product is added
 | 
				
			||||||
 | 
					        current_cart.add_to_cart(self.PROD_1, 1)
 | 
				
			||||||
 | 
					        invoice_1 = InvoiceController.for_cart(current_cart.cart)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        invoice_1.pay("Reference", invoice_1.invoice.value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with self.assertRaises(ValidationError):
 | 
				
			||||||
 | 
					            invoice_1.void()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										33
									
								
								registrasion/tests/test_refund.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								registrasion/tests/test_refund.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,33 @@
 | 
				
			||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					import pytz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from decimal import Decimal
 | 
				
			||||||
 | 
					from django.core.exceptions import ValidationError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from registrasion import models as rego
 | 
				
			||||||
 | 
					from registrasion.controllers.cart import CartController
 | 
				
			||||||
 | 
					from registrasion.controllers.invoice import InvoiceController
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from test_cart import RegistrationCartTestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					UTC = pytz.timezone('UTC')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RefundTestCase(RegistrationCartTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_refund_marks_void_and_unpaid_and_cart_released(self):
 | 
				
			||||||
 | 
					        current_cart = CartController.for_user(self.USER_1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Should be able to create an invoice after the product is added
 | 
				
			||||||
 | 
					        current_cart.add_to_cart(self.PROD_1, 1)
 | 
				
			||||||
 | 
					        invoice = InvoiceController.for_cart(current_cart.cart)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        invoice.pay("A Payment!", invoice.invoice.value)
 | 
				
			||||||
 | 
					        self.assertFalse(invoice.invoice.void)
 | 
				
			||||||
 | 
					        self.assertTrue(invoice.invoice.paid)
 | 
				
			||||||
 | 
					        self.assertFalse(invoice.invoice.cart.released)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        invoice.refund("A Refund!", invoice.invoice.value)
 | 
				
			||||||
 | 
					        self.assertTrue(invoice.invoice.void)
 | 
				
			||||||
 | 
					        self.assertFalse(invoice.invoice.paid)
 | 
				
			||||||
 | 
					        self.assertTrue(invoice.invoice.cart.released)
 | 
				
			||||||
| 
						 | 
					@ -126,3 +126,20 @@ class VoucherTestCases(RegistrationCartTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with self.assertRaises(ValidationError):
 | 
					        with self.assertRaises(ValidationError):
 | 
				
			||||||
            current_cart.apply_voucher(voucher.code)
 | 
					            current_cart.apply_voucher(voucher.code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return current_cart
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_refund_releases_used_vouchers(self):
 | 
				
			||||||
 | 
					        voucher = self.new_voucher(limit=2)
 | 
				
			||||||
 | 
					        current_cart = CartController.for_user(self.USER_1)
 | 
				
			||||||
 | 
					        current_cart.apply_voucher(voucher.code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        inv = InvoiceController.for_cart(current_cart.cart)
 | 
				
			||||||
 | 
					        inv.pay("Hello!", inv.invoice.value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        current_cart = CartController.for_user(self.USER_1)
 | 
				
			||||||
 | 
					        with self.assertRaises(ValidationError):
 | 
				
			||||||
 | 
					            current_cart.apply_voucher(voucher.code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        inv.refund("Hello!", inv.invoice.value)
 | 
				
			||||||
 | 
					        current_cart.apply_voucher(voucher.code)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue