recalculate_discounts now uses the available_discounts function from controllers.discount.
This commit is contained in:
parent
fb3878ce2e
commit
c41a9cadff
2 changed files with 36 additions and 70 deletions
|
@ -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()
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
Loading…
Reference in a new issue