recalculate_discounts now uses the available_discounts function from controllers.discount.

This commit is contained in:
Christopher Neugebauer 2016-03-25 18:59:19 +11:00
parent fb3878ce2e
commit c41a9cadff
2 changed files with 36 additions and 70 deletions

View file

@ -1,8 +1,9 @@
import datetime
import discount
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ValidationError
from django.db.models import Max, Sum
from django.db.models import Max
from django.utils import timezone
from registrasion import models as rego
@ -187,38 +188,47 @@ class CartController(object):
# Delete the existing entries.
rego.DiscountItem.objects.filter(cart=self.cart).delete()
product_items = self.cart.productitem_set.all()
products = [i.product for i in product_items]
discounts = discount.available_discounts(self.cart.user, [], products)
# The highest-value discounts will apply to the highest-value
# products first.
product_items = self.cart.productitem_set.all()
product_items = product_items.order_by('product__price')
product_items = reversed(product_items)
for item in product_items:
self._add_discount(item.product, item.quantity)
self._add_discount(item.product, item.quantity, discounts)
def _add_discount(self, product, quantity):
''' Calculates the best available discounts for this product.
NB this will be super-inefficient in aggregate because discounts will
be re-tested for each product. We should work on that.'''
def _add_discount(self, product, quantity, discounts):
''' Applies the best discounts on the given product, from the given
discounts.'''
prod = ProductController(product)
discounts = prod.available_discounts(self.cart.user)
discounts.sort(key=lambda discount: discount.value)
def matches(discount):
''' Returns True if and only if the given discount apples to
our product. '''
if isinstance(discount.clause, rego.DiscountForCategory):
return discount.clause.category == product.category
else:
return discount.clause.product == product
for discount in reversed(discounts):
def value(discount):
''' Returns the value of this discount clause
as applied to this product '''
if discount.clause.percentage is not None:
return discount.clause.percentage * product.price
else:
return discount.clause.price
discounts = [i for i in discounts if matches(i)]
discounts.sort(key=value)
for candidate in reversed(discounts):
if quantity == 0:
break
# Get the count of past uses of this discount condition
# as this affects the total amount we're allowed to use now.
past_uses = rego.DiscountItem.objects.filter(
cart__user=self.cart.user,
discount=discount.discount,
)
agg = past_uses.aggregate(Sum("quantity"))
past_uses = agg["quantity__sum"]
if past_uses is None:
past_uses = 0
if past_uses == discount.condition.quantity:
elif candidate.quantity == 0:
# This discount clause has been exhausted by this cart
continue
# Get a provisional instance for this DiscountItem
@ -226,13 +236,13 @@ class CartController(object):
discount_item = rego.DiscountItem.objects.create(
product=product,
cart=self.cart,
discount=discount.discount,
discount=candidate.discount,
quantity=quantity,
)
# Truncate the quantity for this DiscountItem if we exceed quantity
ours = discount_item.quantity
allowed = discount.condition.quantity - past_uses
allowed = candidate.quantity
if ours > allowed:
discount_item.quantity = allowed
# Update the remaining quantity.
@ -240,4 +250,6 @@ class CartController(object):
else:
quantity = 0
candidate.quantity -= discount_item.quantity
discount_item.save()

View file

@ -1,18 +1,8 @@
import itertools
from collections import namedtuple
from django.db.models import Q
from registrasion import models as rego
from conditions import ConditionController
DiscountEnabler = namedtuple(
"DiscountEnabler", (
"discount",
"condition",
"value"))
class ProductController(object):
@ -68,39 +58,3 @@ class ProductController(object):
return False
return True
def get_enabler(self, condition):
if condition.percentage is not None:
value = condition.percentage * self.product.price
else:
value = condition.price
return DiscountEnabler(
discount=condition.discount,
condition=condition,
value=value
)
def available_discounts(self, user):
''' Returns the set of available discounts for this user, for this
product. '''
product_discounts = rego.DiscountForProduct.objects.filter(
product=self.product)
category_discounts = rego.DiscountForCategory.objects.filter(
category=self.product.category
)
potential_discounts = set(itertools.chain(
(self.get_enabler(i) for i in product_discounts),
(self.get_enabler(i) for i in category_discounts),
))
discounts = []
for discount in potential_discounts:
real_discount = rego.DiscountBase.objects.get_subclass(
pk=discount.discount.pk)
cond = ConditionController.for_condition(real_discount)
if cond.is_met(user, 0):
discounts.append(discount)
return discounts