Problem/Motivation
In order for Commerce product bundle to work with commerce stock we will need to be able to check and update the stock level of the product associated with product bundle instead of the product bundle itself that should have no stock level of its own.
Proposed resolution
While a solution is currently in debate read comments, the following are things we know:
The following are the three Stock validation rules and their conditions should be updated:
Stock: disable add to cart
commerce-product:commerce-stock is NOT greater then 0
Stock: validate add to cart
stock-requested-total is greater then commerce-product:commerce-stock
Stock: validate checkout
stock-already-ordered is greater then commerce-product:commerce-stock
and we also need to handle the stock control rule
Decrease when completing the order process
Other impotent notes:
- commerce_stock_check_cart_product_level() is used to calculate the stock level already in the basket and any calculation rules will need to take this into account. Note that a product can belong to multiple bundles (or so I believe). see #8 & #15 for more details.
Original report by [Critical Mash]
I needed an immediate solution to managing stock levels for products in a bundle, so I did this quick hack to get it working. I thought someone else might have a similar need before bundle support can be added to commerce_stock (see roadmap #1269168: Commerce_stock for D7 & D8 Roadmap and these issues: #1220360: Product bundles and stock management of the individual items and #1220906: Integration with Commerce Product Bundle and similar approaches). What I did was add a custom action that could be used by the checkout rule "Decrease stock when completing the order process".
/**
* Rules to handle stock adjustments on session bundles
*/
function MY_MODULE_rules_action_info() {
$actions = array();
$actions['MY_MODULE_decrease_by_line_item'] = array(
'label' => t('Decrease the stock level for all referenced products that apply, given a line item'),
'group' => t('Commerce Stock'),
'parameter' => array(
'commerce_line_item' => array(
'type' => 'commerce_line_item',
'label' => t('Line item')
),
),
);
return $actions;
}
/**
* Altered version of commerce_stock_decrease_by_line_item() to find referenced products
*
* @param $line_item
* A line item object.
*/
function MY_MODULEr_decrease_by_line_item($line_item) {
// load commerce_stock.rules.inc to get commerce_stock_adjust()
module_load_include('inc', 'commerce_stock', 'commerce_stock.rules');
if ($line_item->type == 'product') {
// The product SKU that will have its stock level adjusted.
$sku = $line_item->line_item_label;
$product = commerce_product_load_by_sku($sku);
// check product for our reference fields then check each product
// field_bundle_sessions is the name of my product Reference field, you'll need to edit this to match your Product Bundle type
if(isset ($product->field_bundle_sessions)) {
foreach ($product->field_bundle_sessions['und'] as $productArray) {
// load the reference product
$refProduct = commerce_product_load($productArray['product_id']);
// after this, it works just the same as commerce_stock_decrease_by_line_item()
if (commerce_stock_product_type_enabled($refProduct->type)) {
if (!(commerce_stock_product_type_override_enabled($refProduct->type) && $refProduct->commerce_stock_override['und'][0]['value'] == 1)) {
$qty = (int) $line_item->quantity;
// Subtract the sold amount from the available stock level.
commerce_stock_adjust($refProduct, -$qty);
//drupal_set_message("Decrementing stock value for product: ".$refProduct->title);
}
}
} // end foreach
}
}
}
After this, I just edited the rule Decrease stock when completing the order process and added my new action to the action loop after Decrease the product stock level, given a line item.
So it's a little bit of a hack. A more permanent solution would likely involve checking the product type for any references to other products using a function similar to commerce_stock_product_type_enabled(). But I think it's a good proof of concept.
| Comment | File | Size | Author |
|---|---|---|---|
| #20 | package-rules.txt | 6.45 KB | geek-merlin |
| #11 | commerce_stock-stock_and_product_bundles-1288958-11.patch | 19.3 KB | Kazanir |
| #7 | stock_management_for_product_bundles.patch | 2.22 KB | stefank |
Comments
Comment #1
rfayThanks, @Critical Mash!
It would be even more useful if you'd create a sandbox with this (and the rules as a feature)! That is more likely to provide experience with this approach.
In my latest dev effort I created a main module with code and a feature submodule with rules and views in it.
Comment #2
johnodonnell@mac.com commentedYou're welcome. I'd like to work on this more in the future. I'll probably won't have time till October to sit down and do this properly. But I just wanted to get it out right now for anyone facing the same issue as me.
Comment #3
guy_schneerson commented@Critical Mash, thanks this is really useful, i didn't get a chance to look at bundles, but will do so in the next couple of weeks and check out your rule.
Comment #4
essbee commentedThis works really well - the only major thing missing is checking of stock availability when displaying the product bundle.
Currently a bundle where the sub products are out of stock will still display.
As I have an immediate need for this I'll have a bit of a dig through the Commerce Stock module implementation of stock level checking and see if it can be replicated for sub-products.
Comment #4.0
essbee commentedAdded some issue links
Comment #5
L5 commentedIt does subtracts stock from the sub-products. But the bundle will still be visible with an "add to cart" option. So visitors are still able to buy the sub-products through a bundle. The stock amount will be a negative amount (like -1).
Any new solutions on this issue?
Comment #6
guy_schneerson commentedSounds like we need to create custom rules for checking stock for bundles where the logic should be as follows.
Check if product is a product bundle
Cycle all the related products and for each check the stock level
If any are out of stock bundle is out of stock
Comment #7
stefank commentedHere is a patch with custom rule action to decrease the stock level of product bundle. Applied to the latest dev version based on the original post.
Comment #8
Kazanir commentedI'm looking at this problem intently (and in kind of a hurry since this is one of the very last things on my list) and not seeing a good solution to the stock-checking issue for bundles. I'm using a product bundle defined by a field collection which has a combination of quantities and product references. This means that my one product bundle might be composed of:
SKU1 x2
SKU2 x3
SKU3 x1
Each of these has their own stock levels and might be out of stock, or not. The problem arises when I want to check these values for addition to a cart -- or when included in a cart -- which might also be composed of these products independently, or of other bundles which might ALSO be composed of one or more of these products independently.
(Forgive me for engaging in a little programming-by-Rubber-Ducky in the issue queue here...)
The problem with this is that commerce_stock doesn't contemplate this possibility in any of the functions it appears to use BEFORE it shoots the calculation of "in stock or not?" over to Rules.
commerce_stock_check_cart_product_leveldoesn't provide any hooks to alter the checking process -- the calculation of the "cart product level" is hard-coded by product ID and thus not amenable to this need. The following functions seem to more or less lock this process in:The last function is also sinful in that it appears to assume that the $order in question is always the current shopping cart -- it looks for the product IDs on that order object but then checks those product IDs using
commerce_stock_check_cart_product_levelwhich only takes a product ID and checks against the current user's cart rather than retaining the context of the $order that the function requires. (Maybe I'm missing something and misunderstanding what's going on in these functions but this seems not very robust.)To properly handle bundles it seems like we need two different rules processes:
#1: New Rules that take a line item context and alter a reference to an array of products-and-quantities. I'm not sure how to compose that sort of custom data structure in Rules but the basic idea would be be:
For example. (If Rules can't do this easily I'd fall back to provide an API alter hook instead.)
This would have the purpose of transforming a line item into a set of "stock demanded" quantities and at this point is when product kits/bundles could be handled. One would use this process to calculate both the "quantity requested" on an add-to-cart action as well as calculating the current quantity of stock-demanded quantities of products in a current order (or any other context that you can turn into a list of line items, such as determining the stock effect of sending a package containing part of an order.)
Once you have calculated the stock-demanded levels for a given line item or set of items based on which function you're in, then you proceed to invoke:
#2: The processes that are already implemented to check if an item is in the stock for various conditions -- can Z order be checked out, can the add to cart form for Y product be displayed, can X number of Y product be added to Z order.
Then you process the "stock demanded" numbers for each "real" product ID using the already-implemented rules but any major changes delivered by bundles would be already taken care of by the process in #1.
I'd love some feedback. Apologies in advance if I've misunderstood something fundamental here and there is already a better way to do this.
Comment #9
guy_schneerson commentedHi @Kazanir and thanks for the detailed analysis, I have only head a quick read and when I have time will go over in more detail and see what I can learn from your notes.
The only comment I can think of for now is that the intention of the commerce_stock_check_cart_product_level() to count the products you are adding (its not its fault if those are bundles containing other products), as far as I remember this is only used in rules conditions and the way I always envisaged the product bundle integration is by creating alternative rule conditions (and corresponding functions) see #6, so the result will potentially be doubling up of the rules where one set is controlled by a rule condition of "Is product is a product bundle" = FALSE and one other rule set where it is TRUE.
This is only superficial answer and I will do my best to look into this in more detail but my philosophical approach is that because stock has so many different use cases those use cases should provide new rule conditions & actions if needed alongside rule sets, this will hopefully provide the most permutations of use cases with the list amount of code.
Comment #10
Kazanir commentedYeah, what you mention with additional Rules was my initial attempt at it -- I was going to provide a matching set of Rules just for my bundles. The problem came in implementing those Rules without being able to know how many of that product bundle had truly been ordered because of the overlap with other individual products.
I need this badly enough that I'm coding up some alterations to commerce_stock with an eye to making them still useful for others and not breaking the current functionality. I'll post patches and we'll see how things go.
Comment #11
Kazanir commentedHere's an initial very rough go at this problem.
Basically what I did is reworked the preliminary stages (before passing the stock check off to rules) and checked each line item through a series of new functions that assemble a "stock request" array -- this lets other modules alter how many products are actually being requested based on the attributes of a line item, and adds those up. The obvious point here is bundles or any other situation where a line item might place stock demands on some other SKU.
I implemented this, for now, through a series of alter hooks that let a module modify the stock request for any line item, or for a whole order, or for just the current cart. That's probably more places than is really necessary for bundles but I figured I might as well do it while I was at it.
I haven't really figured out how to deal with the messages component right now, since that is tied to Rules so tightly. Comments and "oh hell no"s are welcome. :)
Comment #12
geek-merlinI have a strong "oh hell let's do it like this" in this issue:
Rather than forwarding stock requests to the bundle components we should go the other way round, to calculate an effective bundle stock from the component stocks, which burns down to calculating the minumum of all component stocks, which is easy in rules.
I think we have just one simple step to go to make everything nice and clean: Let's abstract stock calculation to a rules component!
A battle plan for this:
* Module commerce_stock_rules_calculation
* ...provides a dropdown "stock calculation component" (components with signature product => decimal), and if set, anounces a product bundle property that is calculated by that component
Simple stock with its commerce_stock field can cleanly integrate into this.
Do we need anything more complex than this?
Comment #13
guy_schneerson commentedHi axel.rutz I also think we can find a simple solution for this. And sounds to me that getting the stock level of the smallest stock level in the bundle (effective stock level of bundle) is the way to go.
Not sure how you plan the "stock calculation component" to work can you provide more details.
Just to clarify the following are the three rules and their conditions that should be updated for stock checking:
Stock: disable add to cart
commerce-product:commerce-stock is NOT greater then 0
Stock: validate add to cart
stock-requested-total is greater then commerce-product:commerce-stock
Stock: validate checkout
stock-already-ordered is greater then commerce-product:commerce-stock
and we also need to create a version of the “Decrease when completing the order process”
(will update the issue summary with those)
I can think of a couple of ways of screening this cat:
option A – using rules:
1) new Module/sub module commerce_stock_product_bundle – provides a rule condition that can be used to calculate the effective stock level of a bundle.
2) Update the rules to use the new condition instead of of the data comparison.
To avoid having to update three rules (or more if site has custom rules) we can create a new rule/component for calculating the stock level and update the above three rules to use it. This will require only one rule update after enabling the commerce_stock_product_bundle module
Option B – Use a hook for calculating stock level
1) commerce_stock to provide a hook that allows modules to register stock level checking system.
2) commerce_stock to provide a configuration screen for selecting the price calculation to use (active calculation system)
3) commerce_stock provides a stock check rule that uses the active calculation system
4) commerce_stock_ss & commerce_stock_pb to provide two calculation systems
Notes:
Option A
+ should be easy to implement
- May be harder to configure for end user
Option B
- Harder to implement
+ easier to configure
+ More elegant will make it easier for other use cases like external API stock checking
In both cases the product_bundle should revert to using simple stock if not a bundle.
I can think of at list one issue so will have another read of #8 by Kazanir as looks like he may have raised some valid points
Comment #14
guy_schneerson commentedComment #15
guy_schneerson commentedJust had another look at #8 and Kazanir is correct in pointing out that the commerce_stock_check_cart_product_level() cant be overridden however if we are talking about the use of an "effective stock level" then this can be handled by the new sub module so where the code checks each product level and subtracts quantities that are in the cart (may need to take into account the stock-requested-total & stock_already_ordered so those are not calculated twice), This is impotent as a product can belong to multiple bundles (or so I belive)
Comment #16
guy_schneerson commentedComment #17
guy_schneerson commentedComment #18
guy_schneerson commentedAlso Kazanir the issue you found with commerce_stock_check_cart_product_level() not using the order is an issue and should be fixed regardless of this issue, Can you please create another issue for this and hopefully we can sort it independently.
Thanks again Kazanir, axel.rutz and everyone else that helped on this issue :)
Comment #19
geek-merlinRE #13
> Not sure how you plan the "stock calculation component" to work can you provide more details.
The "right way to do this" (TM) is imho like you outlined as (B):
* provide a rules event "get stock level for product" (using rules_invoke_all() this is also invokes a hook)
* provide a default rule "get value of stock field if exists"
* provide a action "get stock level of product"
this way we can
* have some rules like we know it for disablie-addtocart-button, ...
* have other rules that get or calculate stock level
this way we can maintain them separately, and drupal is about reusable components!
i don't think i find time for this but i will be happy to mentor it - feel free to PM me.
Comment #20
geek-merlinAnd here's my package rules. Have fun!
Comment #21
zerbax commentedI'm trying to apply this patches, to solve the problem too, but patches and package-rules contains errors for me when i apply them.
I've installed commerce stock version 7.x-2.2+4-dev and also 7.x-2.3.
Can someone help me?
Thanks
Comment #22
Steven Monetti commentedI had a similar challenge, but I also needed to decrease by the quantity of items in the kit. For example, the kit might have 2 wine glasses, which means I need to decrease wine glasses by 2 when the kit is purchased.
I did this by using a field collection on the product type. The field collection had two fields: Product Reference, and Quantity. Then, I used the following code for the rule action:
Hope this helps!
Comment #23
roxart commentedis there any update on this, I would really need something like this as I have things like: displays I will sell, those contain packs which are also sold single... so basically the amount of displays I have depends on the amount of packs...
any insight on how to solve this
Comment #24
guy_schneerson commented@roxart I have no plans for product bundles D7 support.
Hopefully the comments in this thread can help