The WooCommerce plugin allows you to manage stock for each product, but you only have a single stock quantity field!
What if you have two warehouses and, as a store admin, need to manage the inventory for each location? Besides, what if an item is out of stock at location 1, but it’s in stock at location 2, and therefore the customer needs to be able to purchase it?
This amazing workaround will add a second input number in the product settings, redefine stock quantity and status on the frontend by summing up stock 1 + stock 2, and finally decrease stock 1 until it goes to 0, after which it will decrease stock 2.
This default behavior can be changed of course e.g. it’s possible to define from where the stock is reduced (by distance?) via additional code. Also, additional code can be written to make it compatible with variable products or custom product types, as well as make it work with refunds. Either way, enjoy!
PHP Snippet: Second Stock Location Management
/**
* @snippet Second Stock Location @ WooCommerce Edit Product
* @how-to businessbloomer.com/woocommerce-customization
* @author Rodolfo Melogli, Business Bloomer
* @compatible WooCommerce 8
* @community https://businessbloomer.com/club/
*/
add_action( 'woocommerce_product_options_stock', 'bbloomer_additional_stock_location' );
function bbloomer_additional_stock_location() {
global $product_object;
echo '<div class="show_if_simple show_if_variable">';
woocommerce_wp_text_input(
array(
'id' => '_stock2',
'value' => get_post_meta( $product_object->get_id(), '_stock2', true ),
'label' => '2nd Stock Location',
'data_type' => 'stock',
)
);
echo '</div>';
}
add_action( 'save_post_product', 'bbloomer_save_additional_stock' );
function bbloomer_save_additional_stock( $product_id ) {
global $typenow;
if ( 'product' === $typenow ) {
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
if ( isset( $_POST['_stock2'] ) ) {
update_post_meta( $product_id, '_stock2', $_POST['_stock2'] );
}
}
}
add_filter( 'woocommerce_product_get_stock_quantity' , 'bbloomer_get_overall_stock_quantity', 9999, 2 );
function bbloomer_get_overall_stock_quantity( $value, $product ) {
$value = (int) $value + (int) get_post_meta( $product->get_id(), '_stock2', true );
return $value;
}
add_filter( 'woocommerce_product_get_stock_status' , 'bbloomer_get_overall_stock_status', 9999, 2 );
function bbloomer_get_overall_stock_status( $status, $product ) {
if ( ! $product->managing_stock() ) return $status;
$stock = (int) $product->get_stock_quantity() + (int) get_post_meta( $product->get_id(), '_stock2', true );
$status = $stock && ( $stock > 0 ) ? 'instock' : 'outofstock';
return $status;
}
add_filter( 'woocommerce_payment_complete_reduce_order_stock', 'bbloomer_maybe_reduce_second_stock', 9999, 2 );
function bbloomer_maybe_reduce_second_stock( $reduce, $order_id ) {
$order = wc_get_order( $order_id );
$atleastastock2change = false;
foreach ( $order->get_items() as $item ) {
if ( ! $item->is_type( 'line_item' ) ) {
continue;
}
$product = $item->get_product();
$item_stock_reduced = $item->get_meta( '_reduced_stock', true );
if ( $item_stock_reduced || ! $product || ! $product->managing_stock() ) {
continue;
}
$qty = apply_filters( 'woocommerce_order_item_quantity', $item->get_quantity(), $order, $item );
$stock1 = (int) get_post_meta( $product->get_id(), '_stock', true );
if ( $qty <= $stock1 ) continue;
$atleastastock2change = true;
}
if ( ! $atleastastock2change ) return $reduce;
foreach ( $order->get_items() as $item ) {
if ( ! $item->is_type( 'line_item' ) ) {
continue;
}
$product = $item->get_product();
$item_stock_reduced = $item->get_meta( '_reduced_stock', true );
if ( $item_stock_reduced || ! $product || ! $product->managing_stock() ) {
continue;
}
$item_name = $product->get_formatted_name();
$qty = apply_filters( 'woocommerce_order_item_quantity', $item->get_quantity(), $order, $item );
$stock1 = (int) get_post_meta( $product->get_id(), '_stock', true );
$stock2 = (int) get_post_meta( $product->get_id(), '_stock2', true );
if ( $qty <= $stock1 ) {
wc_update_product_stock( $product, $qty, 'decrease' );
$order->add_order_note( sprintf( 'Reduced stock for item "%s"; Stock 1: "%s" to "%s".', $item_name, $stock1, $stock1 - $qty ) );
} else {
$newstock2 = $stock2 - ( $qty - $stock1 );
wc_update_product_stock( $product, $stock1, 'decrease' );
update_post_meta( $product->get_id(), '_stock2', $newstock2 );
$item->add_meta_data( '_reduced_stock', $qty, true );
$item->save();
$order->add_order_note( sprintf( 'Reduced stock for item "%s"; Stock 1: "%s" to "0" and Stock 2: "%s" to "%s".', $item_name, $stock1, $stock2, $newstock2 ) );
}
}
$order->get_data_store()->set_stock_reduced( $order_id, true );
return false;
}
In plain English:
- bbloomer_additional_stock_location shows the second stock quantity field
- bbloomer_save_additional_stock saves the custom stock amount
- bbloomer_get_overall_stock_quantity sets the product stock inventory to stock 1 + stock 2
- bbloomer_get_overall_stock_status sets the product stock status based on stock 1 + stock 2
- bbloomer_maybe_reduce_second_stock decreases the stock from stock 1 and then from stock 2 in case the ordered quantity is greater than stock 1, otherwise it lets WooCommerce do the default stock decrease
Advanced Plugin (Sync Stock Between Stores): WooMultistore
In case you need to sync products and stock levels across multiple WooCommerce stores, WooMultistore may be a good fit.
WooMultistore is a plugin specifically designed to manage multiple WooCommerce stores from a single interface. It allows for the synchronization of product data, stock levels, and other product-related details across multiple stores.
Great snippet , Thanks Rodolfo !
It inspired me to create a country based stock snippet ,
I have a question regarding the data type parameter in the code below
I could not find an online documentation regarding this parameter , even though it is used around 34 times in woocommerce files with multiple values (price, stock , percent, decimal) !
I wonder what is the benefit of this parameter ? and what is the difference between type and data_type parameters ?
I would really appreciate it if you could explain !
You’re welcome! That parameter just adds some conditional classes and formats the values according to the type – see function woocommerce_wp_text_input inside the plugin
How to make the snippet work for variable products
Thanks and regards,
Mike van Dijk
Hello Mike, the reason I haven’t done that is because I’m still validating if the system works and is compatible with any possible theme/plugin/whatever integration.
Once I’m happy with that, I will create a mini-plugin about this so that I can implement it for all product types.
Thank you for your patience
Itβs not clear how to assign a location for processing online orders and how to assign a second location, for example for a YITH POS plugin that already uses the base inventory location Woocommerce And how can he be taught to use the second supply location?
Hello Volodymyr, thanks so much for your comment! Yes, this is definitely possible, but I’m afraid it’s custom work. If you’d like to get a quote, feel free to contact me here. Thanks a lot for your understanding!
Hi I used this code but when I am in checkout page and click submit to register order this error is displayed:
‘Not enough units of pen code 123 are available in stock to fulfil this order’ and in product status column is written outstock . how can improve it ?
Thanks for your feedback Mina. Does that happen only for that product?
hi,
The same thing happens on my site, when clicking to make the payment, in all the products that have stock 0 in the main stock, having stock in stock2.
And orders are generated in the backend, but remain in “pending payment” status, but customers cannot follow their payment method.
I encountered this issue and managed to find a workaround, although it’s worth noting that altering the core functionality of WooCommerce appears to be the only viable solution. The key file that requires modification is located at:
/public_html/your_theme/wp-content/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php
To implement this change, we need to apply a filter hook to ‘get_query_for_stock’:
In this instance, I’ve named the hook ‘woocommerce_query_for_stock.’ Following this, we must add a filter function to the mentioned hook to modify the $query, like so:
And that’s it!
Haven’t tested it and can’t recreate the issue on my end, but thanks for sharing!
This error appear when the standard stock quantity is 0, at checkout woocommerce make a check to the stock status and return out of stock, how can we fix it?
I experienced the same issue as above. In case anyone else comes across this post I wanted to share that I was able to resolve it by leaving the Hold stock (minutes) option blank under WooCommerce > Settings > Products > Inventory.
Thank you!