I'm trying to add a custom stock setting field to my WooCommerce products. The product page would display a number with the sum of the stock from both "warehouses".
I have one working code that adds such a field to simple product, everything works perfectly.
Unfortunately, I have a problem with products with product variations. I have code that adds a field with additional stock to product variations in the product settings, but the product page only displays stock from the standard stock.
Working code for simple products:
// Adding a second stock level
add_action( 'woocommerce_product_options_stock', 'bbloomer_additional_stock_location_for_simple_products' );
function bbloomer_additional_stock_location_for_simple_products() {
global $product_object;
if ($product_object->get_type() === 'simple') {
echo '<div class="show_if_simple">';
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_for_simple_products' );
function bbloomer_save_additional_stock_for_simple_products( $product_id ) {
$product = wc_get_product( $product_id );
if ( $product && $product->get_type() === 'simple' ) {
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
if ( isset( $_POST['_stock2'] ) ) {
update_post_meta( $product_id, '_stock2', sanitize_text_field( $_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;
}
The code for product variations (not working):
// Dodanie drugiego stanu magazynowego - produkty wariantowe
add_action( 'woocommerce_variation_options_pricing', 'bbloomer_additional_stock_location_for_variable_variations', 10, 3 );
function bbloomer_additional_stock_location_for_variable_variations( $loop, $variation_data, $variation ) {
woocommerce_wp_text_input(
array(
'id' => '_stock2_variable[' . $loop . ']',
'value' => get_post_meta( $variation->ID, '_stock2_variable', true ),
'label' => '2nd Stock Location',
'data_type' => 'stock',
)
);
}
// Save additional stock for variable product variations
add_action( 'woocommerce_save_product_variation', 'bbloomer_save_additional_stock_for_variable_variations', 10, 2 );
function bbloomer_save_additional_stock_for_variable_variations( $variation_id, $i ) {
if ( isset( $_POST['_stock2_variable'][$i] ) ) {
update_post_meta( $variation_id, '_stock2_variable', sanitize_text_field( $_POST['_stock2_variable'][$i] ) );
}
}
// Get overall stock quantity for variable products
add_filter( 'woocommerce_product_get_stock_quantity' , 'bbloomer_get_overall_stock_quantity_variable', 9999, 2 );
function bbloomer_get_overall_stock_quantity_variable( $value, $product ) {
if ($product->get_type() === 'variable') {
$variations = $product->get_available_variations();
$stock2_total = 0;
foreach ($variations as $variation) {
$stock2_total += (int) get_post_meta( $variation['variation_id'], '_stock2_variable', true );
}
$value += $stock2_total;
}
return $value;
}
// Get overall stock status for variable products
add_filter( 'woocommerce_product_get_stock_status' , 'bbloomer_get_overall_stock_status_variable', 9999, 2 );
function bbloomer_get_overall_stock_status_variable( $status, $product ) {
if ($product->get_type() === 'variable') {
$variations = $product->get_available_variations();
$stock2_total = 0;
foreach ($variations as $variation) {
$stock2_total += (int) get_post_meta( $variation['variation_id'], '_stock2_variable', true );
}
$stock = (int) $product->get_stock_quantity() + $stock2_total;
$status = $stock && ( $stock > 0 ) ? 'instock' : 'outofstock';
}
return $status;
}
// Reduce second stock upon order completion for variable product variations
add_filter( 'woocommerce_payment_complete_reduce_order_stock', 'bbloomer_maybe_reduce_second_stock_variable', 9999, 2 );
function bbloomer_maybe_reduce_second_stock_variable( $reduce, $order_id ) {
$order = wc_get_order( $order_id );
foreach ( $order->get_items() as $item ) {
if ( ! $item->is_type( 'line_item' ) ) {
continue;
}
$product = $item->get_product();
if ( $product && $product->get_type() === 'variation' ) {
$item_stock_reduced = $item->get_meta( '_reduced_stock_variable', true );
if ( $item_stock_reduced || ! $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;
$stock2 = (int) get_post_meta( $product->get_id(), '_stock2_variable', true );
$new_stock2 = $stock2 - ( $qty - $stock1 );
if ($new_stock2 < 0) {
$new_stock2 = 0;
}
update_post_meta( $product->get_id(), '_stock2_variable', $new_stock2 );
$item->add_meta_data( '_reduced_stock_variable', $qty, true );
$item->save();
$order->add_order_note( sprintf( 'Reduced stock for variation "%s"; Stock 1: "%s" to "0" and Stock 2 (Variable): "%s" to "%s".', $item->get_name(), $stock1, $stock2, $new_stock2 ) );
}
}
$order->get_data_store()->set_stock_reduced( $order_id, true );
return false;
}
Try the following code replacement that should work for all products:
add_action( 'woocommerce_product_options_stock', 'azt_add_product_2nd_stock' );
function azt_add_product_2nd_stock() {
global $product_object;
echo '<div class="hide_if_variable">';
woocommerce_wp_text_input( array(
'id' => '_stock2',
'value' => wc_stock_amount( $product_object->get_meta( '_stock2' ) ?? 0 ),
'label' => __( 'Quantity (2nd Stock)', 'woocommerce' ),
'desc_tip' => true,
'description' => __( 'Set the stock quantity from the 2nd stock location', 'woocommerce' ),
'type' => 'number',
'custom_attributes' => array( 'step' => 'any' ),
'data_type' => 'stock',
) );
echo '</div>';
}
add_action( 'woocommerce_variation_options_pricing', 'azt_add_product_variation_2nd_stock', 10, 3 );
function azt_add_product_variation_2nd_stock( $loop, $variation_data, $variation ) {
woocommerce_wp_text_input( array(
'id' => '_stock2['.$loop.']',
'value' => wc_stock_amount( get_post_meta($variation->ID, '_stock2', true) ),
'label' => __( 'Quantity (2nd Stock)', 'woocommerce' ),
'desc_tip' => true,
'description' => __( 'Set the stock quantity from the 2nd stock location', 'woocommerce' ),
'type' => 'number',
'custom_attributes' => array( 'step' => 'any' ),
'data_type' => 'stock',
) );
}
add_action( 'woocommerce_admin_process_product_object', 'azt_save_product_2nd_stock' );
function azt_save_product_2nd_stock( $product ) {
if ( ! $product->is_type('variable') && isset($_POST['_stock2']) ) {
$product->update_meta_data( '_stock2', wc_stock_amount( wp_unslash($_POST['_stock2']) ) );
}
}
add_action( 'woocommerce_admin_process_variation_object', 'azt_save_product_variation_2nd_stock', 10, 2 );
function azt_save_product_variation_2nd_stock( $variation, $i ) {
if ( isset($_POST['_stock2'][$i]) ) {
$variation->update_meta_data( '_stock2', wc_stock_amount( wp_unslash($_POST['_stock2'][$i]) ) );
}
}
add_filter( 'woocommerce_product_get_stock_quantity' , 'azt_get_overall_stock_quantity', 9999, 2 );
add_filter( 'woocommerce_product_variation_get_stock_quantity' , 'azt_get_overall_stock_quantity', 9999, 2 );
function azt_get_overall_stock_quantity( $value, $product ) {
return $value + (int) $product->get_meta('_stock2');
}
add_filter( 'woocommerce_product_get_stock_status' , 'azt_get_overall_stock_status', 9999, 2 );
add_filter( 'woocommerce_product_variation_get_stock_status' , 'azt_get_overall_stock_status', 9999, 2 );
function azt_get_overall_stock_status( $status, $product ) {
if ( $product->managing_stock() ) {
$stock = (int) $product->get_stock_quantity() + (int) $product->get_meta('_stock2');
$status = $stock && ( $stock > 0 ) ? 'instock' : 'outofstock';
}
return $status;
}
add_filter( 'woocommerce_payment_complete_reduce_order_stock', 'azt_maybe_reduce_second_stock', 9999, 2 );
function azt_maybe_reduce_second_stock( $reduce, $order_id ) {
$order = wc_get_order( $order_id );
$flag = false;
foreach ( $order->get_items() as $item ) {
if ( $item->is_type( 'line_item' ) ) {
$product = $item->get_product();
$reduced_stock = $item->get_meta('_reduced_stock', true );
if ( $reduced_stock || ! $product->managing_stock() ) {
continue;
}
$qty = apply_filters( 'woocommerce_order_item_quantity', $item->get_quantity(), $order, $item );
$stock = (int) $product->get_stock_quantity();
if ( $qty <= $stock ) continue;
$flag = true;
}
}
if ( ! $flag ) {
return $reduce;
}
foreach ( $order->get_items() as $item ) {
if ( $item->is_type( 'line_item' ) ) {
$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 );
$stock = (int) $product->get_stock_quantity();
$stock2 = (int) $product->get_meta('_stock2');
if ( $qty <= $stock ) {
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 {
$new_stock2 = $stock2 - ( $qty - $stock );
wc_update_product_stock( $product, $stock, 'decrease' );
$product->update_meta_data('_stock2', $new_stock2);
$product->save();
$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;
}
It should work.