from decimal import Decimal as D
class OfferApplications(object):
"""
A collection of offer applications and the discounts that they give.
Each offer application is stored as a dict which has fields for:
* The offer that led to the successful application
* The result instance
* The number of times the offer was successfully applied
"""
def __init__(self):
self.applications = {}
def __iter__(self):
return self.applications.values().__iter__()
def __len__(self):
return len(self.applications)
def add(self, offer, result):
if offer.id not in self.applications:
self.applications[offer.id] = {
'offer': offer,
'result': result,
'name': offer.name,
'description': result.description,
'voucher': offer.get_voucher(),
'freq': 0,
'discount': D('0.00')}
self.applications[offer.id]['discount'] += result.discount
self.applications[offer.id]['freq'] += 1
@property
def offer_discounts(self):
"""
Return basket discounts from offers (but not voucher offers)
"""
discounts = []
for application in self.applications.values():
if not application['voucher'] and application['discount'] > 0:
discounts.append(application)
return discounts
@property
def voucher_discounts(self):
"""
Return basket discounts from vouchers.
"""
discounts = []
for application in self.applications.values():
if application['voucher'] and application['discount'] > 0:
discounts.append(application)
return discounts
@property
def shipping_discounts(self):
"""
Return shipping discounts
"""
discounts = []
for application in self.applications.values():
if application['result'].affects_shipping:
discounts.append(application)
return discounts
@property
def grouped_voucher_discounts(self):
"""
Return voucher discounts aggregated up to the voucher level.
This is different to the voucher_discounts property as a voucher can
have multiple offers associated with it.
"""
voucher_discounts = {}
for application in self.voucher_discounts:
voucher = application['voucher']
discount = application['discount']
if voucher.code not in voucher_discounts:
voucher_discounts[voucher.code] = {
'voucher': voucher,
'discount': discount,
}
else:
voucher_discounts[voucher.code]['discount'] += discount
return voucher_discounts.values()
@property
def post_order_actions(self):
"""
Return successful offer applications which didn't lead to a discount
"""
applications = []
for application in self.applications.values():
if application['result'].affects_post_order:
applications.append(application)
return applications
@property
def offers(self):
"""
Return a dict of offers that were successfully applied
"""
return dict([(a['offer'].id, a['offer']) for a in
self.applications.values()])
class ApplicationResult(object):
is_final = is_successful = False
# Basket discount
discount = D('0.00')
description = None
# Offer applications can affect 3 distinct things
# (a) Give a discount off the BASKET total
# (b) Give a discount off the SHIPPING total
# (a) Trigger a post-order action
BASKET, SHIPPING, POST_ORDER = 0, 1, 2
affects = None
@property
def affects_basket(self):
return self.affects == self.BASKET
@property
def affects_shipping(self):
return self.affects == self.SHIPPING
@property
def affects_post_order(self):
return self.affects == self.POST_ORDER
[docs]class BasketDiscount(ApplicationResult):
"""
For when an offer application leads to a simple discount off the basket's
total
"""
affects = ApplicationResult.BASKET
def __init__(self, amount):
self.discount = amount
@property
def is_successful(self):
"""
Returns ``True`` if the discount is greater than zero
"""
return self.discount > 0
def __str__(self):
return '<Basket discount of %s>' % self.discount
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, self.discount)
# Helper global as returning zero discount is quite common
ZERO_DISCOUNT = BasketDiscount(D('0.00'))
[docs]class ShippingDiscount(ApplicationResult):
"""
For when an offer application leads to a discount from the shipping cost
"""
is_successful = is_final = True
affects = ApplicationResult.SHIPPING
SHIPPING_DISCOUNT = ShippingDiscount()
[docs]class PostOrderAction(ApplicationResult):
"""
For when an offer condition is met but the benefit is deferred until after
the order has been placed. E.g. buy 2 books and get 100 loyalty points.
"""
is_final = is_successful = True
affects = ApplicationResult.POST_ORDER
def __init__(self, description):
self.description = description