Quantity / Tiered / Volume Pricing code!

47 views
Skip to first unread message

Nick Albright

unread,
Jun 29, 2017, 1:43:40 PM6/29/17
to django-oscar
I saw a few questions in here on volume pricing, but didn't see any solutions.  We just came up with one, so I thought I'd share.  Pretty much you need to mod 3 places.  The StockRecord model (add a quantity field), the Strategy for selecting a stock record, and lastly the logic for adding to the cart.  The latter is the big change, as we are just changing 1 line in the middle of it, so end up copy/pasting the whole function.


The stock record, to add quantity

# store/partner/models.py

from django.db import models

from oscar.apps.partner.abstract_models import AbstractStockRecord

class StockRecord( AbstractStockRecord ):
        quantity = models.IntegerField( db_index = True,
                                    default = 1,
                                    help_text = 'Min quantity to get the price')


    class Meta( AbstractStockRecord.Meta ):
        unique_together = ( 'product', 'partner', 'quantity' )
        index_together = ['product', 'quantity']
 


The strategy, to get passed in quantity, and select based on qty

# store/partner/strategy.py

from oscar.apps.partner.strategy import DeferredTax, StockRequired, Structured, PurchaseInfo

from store.partner.models import StockRecord
from decimal import Decimal as D


class UseQuantityStockRecord( object ):
    def fetch_for_line( self, line, stockrecord = None ):
        """
        Overrideen oscar's app/partner/strategy.py Base's method, to use qty
        """

        # Added quantity arg for quantity pricing
        return( self.fetch_for_product(line.product, quantity = line.quantity))


    def fetch_for_product(self, product, stockrecord=None, quantity=1):
        """
        Overrideen oscar's app/partner/strategy.py Structures's method, to use qty
        """

        if stockrecord is None:
            stockrecord = self.select_stockrecord(product, quantity=quantity)
        return PurchaseInfo(
            price=self.pricing_policy(product, stockrecord),
            availability=self.availability_policy(product, stockrecord),
            stockrecord=stockrecord)


    def select_stockrecord( self, product, quantity=1 ):

        try:
            return( product.stockrecords.filter( quantity__lte = quantity
                                             ).order_by('-quantity')[0])
        except (StockRecord.DoesNotExist, IndexError):
            return( None )



And the ugly cut and paste to just change 1 line essentially/the add to basket logic. :)

Enter code here...# store/basket/models.py

from oscar.apps.basket.abstract_models import AbstractBasket


class Basket( AbstractBasket ):
    """
    Needed to allow us to do quantity pricing for adding to the basket.

    We only change like 4 lines in the middle of add_product
    """

    def add_product(self, product, quantity=1, options=None):
        """
        Add a product to the basket

        'stock_info' is the price and availability data returned from
        a partner strategy class.

        The 'options' list should contains dicts with keys 'option' and 'value'
        which link the relevant product.Option model and string value
        respectively.

        Returns (line, created).
          line: the matching basket line
          created: whether the line was created or updated

        """
        if options is None:
            options = []
        if not self.id:
            self.save()

        # Ensure that all lines are the same currency
        price_currency = self.currency

        # BEGIN - Edits for quantity pricing
        try:
            stock_info = self.strategy.fetch_for_product(product, quantity=quantity)
        except TypeError: # Backwards compatibility!
            stock_info = self.strategy.fetch_for_product(product)
        # END - Edits for quantity pricing

        if price_currency and stock_info.price.currency != price_currency:
            raise ValueError((
                "Basket lines must all have the same currency. Proposed "
                "line has currency %s, while basket has currency %s")
                % (stock_info.price.currency, price_currency))

        if stock_info.stockrecord is None:
            raise ValueError((
                "Basket lines must all have stock records. Strategy hasn't "
                "found any stock record for product %s") % product)

        # Line reference is used to distinguish between variations of the same
        # product (eg T-shirts with different personalisations)
        line_ref = self._create_line_reference(
            product, stock_info.stockrecord, options)

        # Determine price to store (if one exists).  It is only stored for
        # audit and sometimes caching.
        defaults = {
            'quantity': quantity,
            'price_excl_tax': stock_info.price.excl_tax,
            'price_currency': stock_info.price.currency,
        }
        if stock_info.price.is_tax_known:
            defaults['price_incl_tax'] = stock_info.price.incl_tax

        # raise Exception( str( defaults ))
        line, created = self.lines.get_or_create(
            line_reference=line_ref,
            product=product,
            stockrecord=stock_info.stockrecord,
            defaults=defaults)
        if created:
            for option_dict in options:
                line.attributes.create(option=option_dict['option'],
                                       value=option_dict['value'])
        else:
            line.quantity += quantity
            line.save()
        self.reset_offer_applications()

        # Returning the line is useful when overriding this method.
        return line, created
    add_product.alters_data = True
    add = add_product


from oscar.apps.basket.models import *  # noqa



I hope this helps others looking to do the same!

Reply all
Reply to author
Forward
0 new messages