Applying multiple offers per basket line

317 views
Skip to first unread message

Markus Bertheau

unread,
Jul 7, 2014, 12:54:02 PM7/7/14
to django...@googlegroups.com
We need multiple offers per basket line for our implementation. This is my plan for the obvious changes necessary:

1. add "allow other discounts to apply to the same product" to ConditionalOffer
2. When applying offers, order them by `allow_other_offers` (`True` first) and then priority
3. When applying an offer with `allow_other_offers = True` don't consume the line
4. Don't try to apply the same offer again

Now to the not-so-obvious changes in semantics.

1. When you have a percentage discount benefit, does it apply to the original price or to the discounted price?
2. Can someone imagine a use-case where implicitly allowing only one application of an offer to one line is a wrong thing to do?

That's all the thoughts I have so far. How does that sound?

I intend to implement that in the short term.

Thanks!

Markus :)

Markus Bertheau

unread,
Jul 8, 2014, 5:37:24 AM7/8/14
to django...@googlegroups.com
On Monday, July 7, 2014 6:54:02 PM UTC+2, Markus Bertheau wrote:
We need multiple offers per basket line for our implementation. This is my plan for the obvious changes necessary:

1. add "allow other discounts to apply to the same product" to ConditionalOffer
2. When applying offers, order them by `allow_other_offers` (`True` first) and then priority

I thought about this over night and now think another way is better: When applying an offer with allow_other_offers == True, don't ignore lines that already have offers applied. That way when ordering by priority is implemented the user retains full control over the order in which offers are applied.

Markus Bertheau

unread,
Jul 9, 2014, 10:30:07 AM7/9/14
to django...@googlegroups.com
After some more code reading I have a more concrete plan:

## Requirements

We need multiple benefits per basket line for our implementation. Two offers we want to combine are:

1. a usually time-based offer like "If Germany wins, next day 15% off all products"
2. a custom offer where every user gets a 50% discount up to 150 Euros per year paid by his employer

On our site it is so common that people buy only quantity 1 of one product that we will skip the UI
part of the basket functionality. Both offers should apply to the same product. For example if a
product costs 100 Euros and Germany won yesterday (which it did!) the first offer is active and the
price is 85 Euros. Then the other offer applies and the user gets a discount of 50% on the remaining
price. The end price is then 42,50 Euros.

## Current situation in Oscar

To implement offers oscar has the concept of conditions and benefits. An example condition is "buy
two products from category A". An example benefit is "get 50% off a product from category B".

Oscar takes care to consume the products mentioned in the condition and in the benefit so that

1. the same two products from category A don't allow a discount for a second product from category B
2. the two products from category A can't be discounted
3. the same product from category B that was discounted cannot be discounted again
4. the product from category B cannot be used as a condition for another benefit

It does so by using `Line._affected_quantity` to track what quantity of the line was used in
conditions (`Line.consume()`) or benefits (`Line.discount()`). Both methods use the same counter.

When testing a condition oscar looks in the basket whether there are lines that weren't used in
another condition or discounted by a benefit by comparing `Line.quantity` and
`Line._affected_quantity`.

### Shortcomings

* Mixing the tracking of condition-consumed and benefit-consumed items leads to problems such as
* `CountCondition` and `CoverageCondition` consume lines inconsistently. `CountCondition` discards
lines with a zero price, `CoverageCondition` does not.

## The plan

In order to fix the shortcomings of the current implementation and also realize the new requirements
I intend to

1. (Refactoring) Separately track which line items were used in which condition and which were used
   in which benefit. Details on semantics below.
  1. Use an extra data structure instead of basket lines to track that (anticipating
  2. Don't use lines that were used in a condition or in a benefit to satisfy another condition.
  3. Don't use lines that were used in a condition or in a benefit for another benefit.
  4. `CountCondition`: mark random N items from the range
  5. `CoverageCondition`: mark a quantity of one for random N items from the range
  6. `ValueCondition`: mark random items from the range in increments of quantity one until the
     condition value has been reached or surpassed
  7. Benefits mark all items that have received a discount
  8. Remove tracking infrastructure from `Line`.
2. (Feature) Support multiple benefits per line
  1. Add a field `conflicts_with_other_benefits` (better naming ideas very welcome!) to Benefit with
     default True.
  2. When looking for lines a benefit can be applied to make sure that for every line there is at
     most one benefit with `conflicts_with_other_benefits == True`.
3. (Feature) Apply offers in order of desecending `ConditionalOffer.priority`.

Thoughts? :)


Markus

Markus Bertheau

unread,
Jul 9, 2014, 10:44:51 AM7/9/14
to django...@googlegroups.com
How are benefits combined?


On Wednesday, July 9, 2014 4:30:07 PM UTC+2, Markus Bertheau wrote:

2. (Feature) Support multiple benefits per line
  1. Add a field `conflicts_with_other_benefits` (better naming ideas very welcome!) to Benefit with
     default True.
  2. When looking for lines a benefit can be applied to make sure that for every line there is at
     most one benefit with `conflicts_with_other_benefits == True`.

   3. Benefits to the same line are applied in order on descending priority of their
      `ConditionalOffer` according to the following rules:
      1. Percentage benefits are calculated from the resulting discounted price of
         any earlier benefits.
      2. A fixed price benefit is applied if it results in a lower price than the
         resulting discounted price of any earlier benefits.
      3. Multibuy benefits result in a discounted price of 0.

Markus

David Winterbottom

unread,
Jul 15, 2014, 10:16:38 AM7/15/14
to django-oscar

--
https://github.com/tangentlabs/django-oscar
http://django-oscar.readthedocs.org/en/latest/
https://twitter.com/django_oscar
---
You received this message because you are subscribed to the Google Groups "django-oscar" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-oscar...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-oscar.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-oscar/de2d6682-4df7-44da-8d16-bf994c55b4b1%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

While these requirements are quite unusual (eg applying more than one offer to the same product), your proposed changes sound sensible
​, especially around how conditions and benefits consume products.

​One other complication that's worth thinking about is capturing consumption by value rather than count. Eg, consider a "Spend £10, get some benefit" offer. Should a £25 product qualify you for 2 benefits? At the moment, no - as Oscar will mark the whole product as consumed after the first offer application but it could be argued that only £10 of the £25 has been consumed.

Instead of "
conflicts_with_other_benefits
​", how about "can_apply_with_other_benefits"?​

​Good luck. Offers can get quite messy, especially when you take into account the US where offers are calculated and applied before taxes.​





--
David Winterbottom
Technical Director

Tangent Snowball
84-86 Great Portland Street
London W1W 7NR
England, UK

Markus Bertheau

unread,
Jul 15, 2014, 11:53:26 AM7/15/14
to django...@googlegroups.com, david.win...@tangentlabs.co.uk
On Tuesday, July 15, 2014 4:16:38 PM UTC+2, David Winterbottom wrote:

While these requirements are quite unusual (eg applying more than one offer to the same product), your proposed changes sound sensible
​, especially around how conditions and benefits consume products.

​One other complication that's worth thinking about is capturing consumption by value rather than count. Eg, consider a "Spend £10, get some benefit" offer. Should a £25 product qualify you for 2 benefits? At the moment, no - as Oscar will mark the whole product as consumed after the first offer application but it could be argued that only £10 of the £25 has been consumed.

I agree. These semantics have to be agreed on and communicated clearly in the dashboard. 

Instead of "
conflicts_with_other_benefits
​", how about "can_apply_with_other_benefits"?​

That sounds better. However it still doesn't capture the meaning "you can combine these benefits as long as no more than one benefit has can_apply_with_other_benefits = False".
 

​Good luck. Offers can get quite messy, especially when you take into account the US where offers are calculated and applied before taxes.​

Actually that's a very trivial part as far as I could see until now. I just use the unit_effective_price from the stockrecord and calculate the discounts based on that. I'm not through yet though, so maybe something more tricky tax-related will come up.

Thanks :)

Markus

epsy...@gmail.com

unread,
Nov 17, 2014, 8:37:46 AM11/17/14
to django...@googlegroups.com, david.win...@tangentlabs.co.uk
Hello,

there are a few cases that Oscar's offers don't handle well.
Here is a real life example:
1 offer: spend x amount and get a free shipping
2 offer: buy 2 products and get x% discount
3 offer: buy 3 products and get 2*x% discount

First of all 2 and 3 offer would be applied randomly by default and this is very counterintuitive.
I.e. the 2 offer could be active even if a user adds 3+ products.

Secondly, shipping is a different kind of benefit than, for example, % discount.
So it is very common to give free shipping even if the user already has some discounted products in the basket as long as he/she spends a specified amount.
As it is currently set up, when a user adds a product to the basket he would get upsell messages to add 1 product to get x% discount and to spend some more to get free shipping. And when the user does that he would only get one of those benefits randomly.

Shouldn't different kinds of benefits apply independently?

Also is there a way to do this (have both shipping and discount benefits) in the current version of Oscar?



вторник, 15 июля 2014 г., 18:16:38 UTC+4 пользователь David Winterbottom написал:
Reply all
Reply to author
Forward
0 new messages