-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Discount system #4296
Discount system #4296
Conversation
core/spec/lib/spree/core/testing_support/factories/line_item_discount_factory_spec.rb
Outdated
Show resolved
Hide resolved
1119896
to
11f654c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow. What a great piece of work. I have mostly questions :)
@@ -22,6 +22,7 @@ def eligible?(order, options = {}) | |||
|
|||
eligibility_errors.empty? | |||
end | |||
alias_method :eligible?, :order_discountable? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should deprecate it as well.
9ee9235
to
fcff0c8
Compare
e341f09
to
6b92efa
Compare
If we reduce the amount of promotions that can be activated, the performance when refreshing the cart improves dramatically. Eventually, this might not be necessary since Solidus#master branch is updated with solidusio#4304 and solidusio#4296. Until those land in master and we are able to update to Solidus 3.2, we need to maintain this fork.
b9ce1ec
to
282adc8
Compare
@@ -15,6 +15,19 @@ class CreateItemAdjustments < PromotionAction | |||
before_destroy :remove_adjustments_from_incomplete_orders | |||
before_discard :remove_adjustments_from_incomplete_orders | |||
|
|||
def can_discount?(klass) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should you not provide have it declared on the Spree::PromotionAction as well so that all actions inherit it with a default response ? Also what about Spree::PromotionAction::CreateAdjustment
, what if'll call can_discount?
on this class ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think we should declare it on the Spree::PromotionAction
base class and make it return false
by default. This will also be the correct response for the CreateAdjustment
class, which IMO should be deprecated and replaced by CreateItemAdjustments
with the DistributedAmount
calculator.
The promotion system is indeed quite complexe but powerful too and it needed some work, especialy on the performance side. Thanks for tackling it 👍 I've just a quick remark regarding "4. No more order-level adjustments.". Even if the legacy promotion system is not yet deleted, I think that level of discount should not be discarded that easily. Right now, in order to keep that level of discount, a user (and by a user I mean we @epicery as we have a case where this is necessary) has to choose between being locked on the legacy, or extends the new system with custom code to get the feature back. If not directly implemented by Solidus (which I can agree with), I cannot help but thought of how it could be extended and, IMO, it seems really hacky:
Would you be open to drop the discard of order level adjustment for this refactoring? |
@stem I am concerned about creating additional schisms in the community, so the compatibility decisions around any change like this are a big concern. Can you elaborate on why you say that order-level adjustments "can also be the right tool in some contexts"? I don't believe they are the right tool for anything, and I'd also like to know more about stores are using them. |
I would not like order level discounts to be a thing. Order level adjustments are still a part of the code base, and will probably need to be supported for a long time (there can also be manual adjustments - also a terrible idea - but most importantly I'm coming to the conclusion that the migration path for this should not touch completed orders, and for completed orders with promotions to still show up correctly in the admin panel, we must keep some support). That said: If you had a promotion that distributed a fixed amount over all line items of the order, would that fufill your use case? Can you give some more details on what kind of thing your promotion does? |
@jarednorman of course, I can try to explain why I think it's not a good idea to get rid of order level discount :) First, we're a marketplace, so accounting is quite different for us than for a regular store (no tax on products for us as we're not selling any products...) We're using order, shipment and line items' adjustments according to the intended discount:
And all discounts but the one at the marketplace level can be "paid" by the seller or the marketplace, so accounting is a bit more complex as we're pushing the discounts towards our account or the sellers'. For us, general discounts, both at the specific seller or the marketplace level, are generally X€ off and are cumulative with the line items discounts. Frontend complexity aside, spliting the amount would be difficult to correctly account for each possible situation.
To be crystal clear, I love the work from @mamoff. It goes in the right direction on many things, but IMO, Solidus should offer the building blocks for the order level, even if it does not ship with promotion actions that use them. And for some of our use cases of order level adjustments:
All that being said, I might be missing something both in the old and new system. NB: sorry for the long text, I tried to be as exhaustive as possible. |
From my experience, I would also prefer to keep a way to have order level discount for financial reason. Let's say you have a 15€ discount on your purchase and 7 articles in your cart. Our issues we had when trying to split this promotion was the following:
So splitting 15€ discount on 7 articles does not add up correctly. Maybe I am wrong and I don't use ruby's BigDecimal operations correctly. Maybe there is a way to do it correctly with the line item discount without losing 1 cent, but in our case, we had a hard time making invoicing and finance calculation corrects because of this. |
Thank you @stem and @loicginoux for the detailed response! This is great. Regarding fixed-amount whole-order discounts: @loicginoux The way the @stem In the current standard frontend, we display similar adjustments by summing them up if they have the same label, so that way it appears to the user like they're getting a whole-order adjustment while, behind the scenes, individual items are adjusted. I think about doing the same thing here; the customer doesn't really need to know how a discount is split up, they just need to know it's there and the right amount. What's more complicated with this proposed system is cumulative discounts, because with that, the order of discounts being applied starts to matter, and we currently have no way of giving a promotion precedence over another one. It might be worth exploring I think what you're using for cumulative discounts is something that has been reported as a bug elsewhere: #2741 What's also impossible with the proposed system is free item promotions, because I limited the scope to discounts. That's probably something we'll want to revisit. Regarding shipment adjustments: We use shipment adjustments for item taxes in our store (they depend on where things are shipped from in our jurisdiction), frequently surpassing the shipping amount, so that some of our shipments end up with a negative total. This would work, too. I also think that we should have more flexible shipping discounts; not just free shipping, but a large subset of the calculators we allow for line item discounts on shipments, too. |
@stem Those discounts can all be handled using line item adjustments. The issue with order-level discounts is that they are a perennial cause of accounting, tax calculation, and tax reporting issues. From an accounting perspective, they aren't a real thing. You cannot calculate tax correctly on an order if there are any order-level adjustments without making (probably wrong) assumptions about how they're being used. Different items and shipments may have different tax rates. You can't just assume that the adjustment should be distributed across the items in some arbitrary way because that will always be wrong for some cases. This incorrectness trickles into ERPs, and other systems that often don't even have the concept of arbitrary adjustments that aren't actually line items. Because we have the distributed amounts handler, using line item/shipment adjustments just forces you to say that either your $10 discount is off the items or the items and shipments and it forces you to be specific about how those amounts are distributed. This change does nothing to prevent you from having a flat discount, just makes you be more specific about how it is applied, which is a boon for both the correctness of your accounting and tax calculation. It's a significant footgun, and (not saying you don't, just in my experience) people don't seem to understand the downstream consequences and issues using them causes. |
While promotions and actions will stay the same, the way the new promotion system gets activated will be quite different. Adds a global switch so developers can choose one or the other.
OrderContents will always run the OrderUpdater, in which adjustments and discounts will be updated too. The design decision here is to skip anything to do with promotions in OrderContents and do it directly in the OrderUpdater instead.
This checks whether a promotion can at all be activated for the current order. It does not check whether anything is blocklisted, as that would prevent a line item to be discountable if any line items on the order were non-discountable, something I consider a bug with the legacy system.
This module handles discounts from the order updater. The system will check all line items and shipments for any applicable discounts and select the best for each line item / shipment in an understandable, performant way.
Now that we have this handy spot where all promotion counts come from, it's relatively easy to feature-flag the function.
People currently use order-level promotions in order to allow cumulative promotions. If we want to allow cumulative promotions, we need to somehow be clear about which promotion has priority after which promotion.
Similar to the scenario of multiple promotions, we need to account for multiple actions on the same promotion. Also here we need to sort by explicit priority when allowing cumulative promotions.
016178f
to
408315d
Compare
This integration spec comes from the experimental PR solidusio/solidus#4296. That PR will very probably not be merged, so I'm moving it here as a vantage point.
Closing in favor of https://github.com/friendlycart/solidus_friendly_promotions |
This integration spec comes from the experimental PR solidusio/solidus#4296. That PR will very probably not be merged, so I'm moving it here as a vantage point.
This integration spec comes from the experimental PR solidusio#4296. That PR will very probably not be merged, so I'm moving it here as a vantage point.
This integration spec comes from the experimental PR solidusio/solidus#4296. That PR will very probably not be merged, so I'm moving it here as a vantage point.
This integration spec comes from the experimental PR solidusio#4296. That PR will very probably not be merged, so I'm moving it here as a vantage point.
This integration spec comes from the experimental PR solidusio#4296. That PR will very probably not be merged, so I'm moving it here as a vantage point.
This integration spec comes from the experimental PR solidusio#4296. That PR will very probably not be merged, so I'm moving it here as a vantage point.
This integration spec comes from the experimental PR solidusio#4296. That PR will very probably not be merged, so I'm moving it here as a vantage point.
This integration spec comes from the experimental PR solidusio#4296. That PR will very probably not be merged, so I'm moving it here as a vantage point.
The current promotion system has a lot of problems, the most notable one being performance - however, the performance issues are really a symptom of architectural issues. We do need to support promotions though, as they are an integral part of what ecommerce does.
Here's a couple of the problems I see:
false
when the promotion does not, actually, apply. This was done - I believe - in order to boost performance.PromotionHandler::Cart
fromOrderContents
, and because that's brittle, we need to run the order updater before AND after we run the promotion handler. This leads to us having to do tax calculation TWICE every time we add something to the cart, leading to lots of unnecessary calls to - often external - services.Spree::Promotion#eligible_rules
.eligible?
(for orders, really) andactionable?
(for line items), but nothing for shipments. There's alsoapplicable?
which is only a type check. I propose renamingeligible?
andactionable?
intoorder_discountable?(order)
,line_item_discountable?(line_item)
andshipment_discountable?(shipment)
, so as to make the whole thing more understandable.Spree::Promotion#line_item_actionable?
) we also check order eligibility. By the time we do that, though, we've already checked order eligibility! That check should go, as it is quite expensive!This PR is a glimpse of what could be.
The general architecture is as follows:
adjustments
model, extracting discounts from there. Discounts and taxes should not be conflated in the same table.Checklist: