question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Products with options can be oversold

See original GitHub issue

When a product is configured with class or product options (not attributes) and tracks stock it possible to oversell the product. This behaviour is visible in the sandbox and in Oscar 1.5.

To reproduce:

  1. Create a product option, for example, a message to print on a t-shirt.
  2. Create a product type that uses this option in the dashboard.
  3. Buy the product multiple times with different messages.
  4. Edit the basket, changing the quantities to more than are available.
  5. Stock validation will allow product to be oversold.

basket_screenshot

If I am not mistaken this is because the availability validation happens at the basket line level and does not take the overall basket state into account. The is_purchase_permitted() check passes for each line item but should not pass as a whole because there are only 10 t-shirts available for purchase but 13 in the basket.

The options force the same product to be a separate line item (as it should be) but this means that line item validation will not work because there can be multiple lines with the same product. I am not sure the best way to fix this as it requires basket level validation and I had a quick look at the code but did not see a hook for adding this. Any suggestions?

Issue Analytics

  • State:open
  • Created 6 years ago
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

2reactions
ad-65commented, Mar 9, 2018

Not sure if this is the proper way but I am putting my solution here in case anybody else runs into this problem. What I ended up doing was taking the suggestion of using Basket.is_quantity_allowed() to check the basket totals by passing a line argument:

class Basket(AbstractBasket):
    
    def is_quantity_allowed(self, qty, line=None):
        """Override to check basket level validation.
        
        Add some validation to make sure the same products on different lines
        are not oversold.
        """
        
        #Call the original and stop if there are any problems found
        is_allowed, reason = super(Basket, self).is_quantity_allowed(qty)
        if not is_allowed:
            return is_allowed, reason
            
        #Check the basket totals
        if line:
            is_permitted, reason = line.purchase_info.availability.is_purchase_permitted(self.basket_quantity(line) + qty)
            if not is_permitted:
                reason = _(
                    "{reason} (there may be others in your basket)"
                ).format(
                    reason=reason,
                )
                return False, reason
                
        return True, None
        
    def basket_quantity(self, line):
        """Return the quantity of similar lines in the basket.
        
        The basket can contain multiple lines with the same product and
        stockrecord, but different options. Those quantities are summed up.
        """
        matching_lines = self.lines.filter(stockrecord=line.stockrecord)
        quantity = matching_lines.aggregate(Sum('quantity'))['quantity__sum']
        return quantity or 0

This has to be called somewhere during the BasketLineForm cleaning with the line argument passed:

class BasketLineForm(CoreBasketLineForm):

    def check_max_allowed_quantity(self, qty):
        """Override to pass the line.
        """
        qty_delta = qty - self.instance.quantity
        is_allowed, reason = self.instance.basket.is_quantity_allowed(qty_delta, line=self.instance)
        if not is_allowed:
            raise forms.ValidationError(reason)

I also added a similar check to CheckoutSessionMixin.check_basket_is_valid():

def check_basket_is_valid(self, request):
        """Checks to maker sure the basket is still valid. Called at each
        checkout step. 
        
        Add some validation to make sure the same products on different lines
        are not oversold.
        """
        super(CheckoutSessionMixin, self).check_basket_is_valid(request)
        messages = []
        strategy = request.strategy
        
        for line in request.basket.all_lines():
            result = strategy.fetch_for_line(line)
            basket_quantity = request.basket.basket_quantity(line)
            is_permitted, reason = result.availability.is_purchase_permitted(basket_quantity)
            if not is_permitted:
                msg = _(
                    "'{title}' is no longer available to buy (there are {basket_quantity} in your basket - {reason}). "
                    "Please adjust your basket to continue"
                ).format(
                    title=line.product.get_title(),
                    basket_quantity=basket_quantity,
                    reason=reason,
                )
                messages.append(msg)
                
        if messages:
            raise exceptions.FailedPreCondition(url=reverse("basket:summary"), messages=messages)

This will make sure the basket is valid at checkout.

Once again, not sure if this is the best way but it should work until this gets addressed in subsequent versions.

0reactions
pantlavanyacommented, Feb 26, 2021

Is this fixed ?

Metadata-Version: 2.1 Name: django-oscar Version: 3.0

Read more comments on GitHub >

github_iconTop Results From Across the Web

What Oversold Means for Stocks, With Examples - Investopedia
The term oversold refers to a condition where an asset has traded lower in price and has the potential for a price bounce....
Read more >
Five Oversold Stocks With Option Plays - TheStreet
Oil products like gas and diesel are much more expensive than coal, and natural gas can displace them in time. It's a mistake...
Read more >
Option Selling Ideas To Consider And Some To Avoid
In an overbought market, selling covered calls can make sense. There are opportunities to build positions as well by selling cash-secured ...
Read more >
Overbought vs Oversold: A Trader's Guide | IG International
These levels are most commonly associated with shares, but can be used to trade options, forex and commodities. For example, when a stock...
Read more >
What is Overselling (+ How to Prevent It) - Shopify
A lot of small businesses start off using the pen-and-paper method of inventory management, which can quickly lead to oversold items and ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found