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 datetime
import discount
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ValidationError 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 django.utils import timezone
from registrasion import models as rego from registrasion import models as rego
@ -187,38 +188,47 @@ class CartController(object):
# Delete the existing entries. # Delete the existing entries.
rego.DiscountItem.objects.filter(cart=self.cart).delete() 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 # The highest-value discounts will apply to the highest-value
# products first. # products first.
product_items = self.cart.productitem_set.all() product_items = self.cart.productitem_set.all()
product_items = product_items.order_by('product__price') product_items = product_items.order_by('product__price')
product_items = reversed(product_items) product_items = reversed(product_items)
for item in 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): def _add_discount(self, product, quantity, discounts):
''' Calculates the best available discounts for this product. ''' Applies the best discounts on the given product, from the given
NB this will be super-inefficient in aggregate because discounts will discounts.'''
be re-tested for each product. We should work on that.'''
prod = ProductController(product) def matches(discount):
discounts = prod.available_discounts(self.cart.user) ''' Returns True if and only if the given discount apples to
discounts.sort(key=lambda discount: discount.value) 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: if quantity == 0:
break break
elif candidate.quantity == 0:
# Get the count of past uses of this discount condition # This discount clause has been exhausted by this cart
# 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:
continue continue
# Get a provisional instance for this DiscountItem # Get a provisional instance for this DiscountItem
@ -226,13 +236,13 @@ class CartController(object):
discount_item = rego.DiscountItem.objects.create( discount_item = rego.DiscountItem.objects.create(
product=product, product=product,
cart=self.cart, cart=self.cart,
discount=discount.discount, discount=candidate.discount,
quantity=quantity, quantity=quantity,
) )
# Truncate the quantity for this DiscountItem if we exceed quantity # Truncate the quantity for this DiscountItem if we exceed quantity
ours = discount_item.quantity ours = discount_item.quantity
allowed = discount.condition.quantity - past_uses allowed = candidate.quantity
if ours > allowed: if ours > allowed:
discount_item.quantity = allowed discount_item.quantity = allowed
# Update the remaining quantity. # Update the remaining quantity.
@ -240,4 +250,6 @@ class CartController(object):
else: else:
quantity = 0 quantity = 0
candidate.quantity -= discount_item.quantity
discount_item.save() discount_item.save()

View file

@ -1,18 +1,8 @@
import itertools
from collections import namedtuple
from django.db.models import Q from django.db.models import Q
from registrasion import models as rego from registrasion import models as rego
from conditions import ConditionController from conditions import ConditionController
DiscountEnabler = namedtuple(
"DiscountEnabler", (
"discount",
"condition",
"value"))
class ProductController(object): class ProductController(object):
@ -68,39 +58,3 @@ class ProductController(object):
return False return False
return True 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