skip to Main Content

I’m trying to find a solution to use a double inventory where a different stock is being used based based on the user role. I know there are many easy solutions to limit the amount of products they can order at once, but this doesn’t prevent them from placing another order. Ideally I would like to have a second inventory for each product which is used for a specific user role.

I’ve already been experimenting with a workaround where I add each product twice with a different inventory and show/hide the product based on user role. But this requires a lot of changes throughout the entire website to show/hide products from every page and element, not to mention all the links to product pages.

2

Answers


  1. Chosen as BEST ANSWER

    Thanks for getting me started, I already figured it would be a complex task :) With some minor adjustments in your code I got it working for simple products (just haven't tested the last step after purchase yet).

    Being motivated by your approach, I pushed forward and created a solution for variable products by adding a custom field for each variation:

    add_action( 'woocommerce_variation_options_inventory', 'sf_role_based_stock', 10, 3 );
    
    function sf_role_based_stock( $loop, $variation_data, $variation ) {
    
        woocommerce_wp_text_input(
            array(
                'id'            => '_stock_role_variation[' . $loop . ']',
                'label'         => 'Role based stock quantity',
                'wrapper_class' => 'form-row',
                'placeholder'   => 'Type here...',
                'desc_tip'      => true,
                'description'   => 'Enter a number to set custom role stock quantity on variation level.',
                'value'         => get_post_meta( $variation->ID, '_stock_role_variation', true )
            )
        );
    
    }
    

    And saving the data from these custom fields again:

    add_action( 'woocommerce_save_product_variation', 'sf_save_role_based_stock', 10, 2 );
    
    function sf_save_role_based_stock( $variation_id, $loop ) {
        $stockrolevariation = ! empty( $_POST[ '_stock_role_variation' ][ $loop ] ) ? $_POST[ '_stock_role_variation' ][ $loop ] : '';
        update_post_meta( $variation_id, '_stock_role_variation', sanitize_text_field( $stockrolevariation ) );
    }
    

    Everything above works fine :) but now I have been struggling for a few hours to get it working on the product page. I understand we need jQuery to get the variation id (unless I'm wrong?). Showing the variation id in HTML is easy, but how do we get the custom role stock value in php? Is it only possible with AJAX? Based on one of your examples I created this:

    add_filter( 'woocommerce_product_get_stock_quantity' , 'sf_get_stock_quantity_custom_role', 9999, 2 );
    function sf_get_stock_quantity_custom_role( $value, $product ) {
        if ( wc_current_user_has_role( 'Custom_Role' ) ) {
            global $custom_variation_stock;
            if ($product->is_type( 'variable' ))
            {
                // Get custom role stock for variable products
                
                echo '<div class="variation_info"></div>';
                wc_enqueue_js( "
                     $( 'input.variation_id' ).change( function(){
                        if( '' != $(this).val() ) {
                           var var_id = $(this).val();
                           $('.variation_info').html(var_id);
                        }
                     });
                  " );
                
                $variation_id = ''; // Need to get variation id here
                $value = get_post_meta( '$variation_id', '_stock_role_variation', true );
                
            } else {
                // Get custom role stock for simple products
                $value = get_post_meta( $product->get_id(), '_stock_role', true );
            }
        }
        return $value;
    }
    
    

    In another attempt I managed to get ALL role based stock quantities for every variation of the product without jQuery, but then I don't know how to determine which one is currently selected:

    add_filter( 'woocommerce_product_get_stock_quantity' , 'sf_get_stock_quantity_custom_role', 9999, 2 );
    function sf_get_stock_quantity_custom_role( $value, $product ) {
        if ( wc_current_user_has_role( 'Custom_Role' ) ) {
            global $custom_variation_stock;
            if ($product->is_type( 'variable' ))
            {
                foreach($product->get_available_variations() as $variation){
                    $variation_id = $variation['variation_id'];
                    $variation_obj = new WC_Product_variation($variation_id);
                    $value = get_post_meta( $variation_id, '_stock_role_variation', true );
                    echo "Custom role variation stock: " . $value . " ";
                }
            } else {
                // Get custom role stock for simple products
                $value = get_post_meta( $product->get_id(), '_stock_role', true );
            }
        }
        return $value;
    }
    

  2. This is a very complex task. First, you need to display an additional input field in the Edit Product page:

    add_action( 'woocommerce_product_options_stock', 'bbloomer_role_based_stock' );
    
    function bbloomer_role_based_stock() {
        global $product_object;
        echo '<div class="stock_fields show_if_simple show_if_variable">';
        woocommerce_wp_text_input(
            array(
                'id' => '_stock_role',
                'value' => get_post_meta( $product_object->get_id(), '_stock_role', true ),
                'label' => 'Stock by role',
                'data_type' => 'stock',
        ));
        echo '</div>';      
    }
    

    Then, you need to save it:

    add_action( 'save_post_product', 'bbloomer_save_additional_stocks' );
      
    function bbloomer_save_additional_stocks( $product_id ) {
        global $typenow;
        if ( 'product' === $typenow ) {
            if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
            if ( isset( $_POST['_stock_role'] ) ) {
                update_post_meta( $product_id, '_stock_role', $_POST['_stock_role'] );
            }
        }
    }
    

    Then, you need to filter stock quantity and status if the customer is logged in:

    add_filter( 'woocommerce_product_get_stock_quantity' , 'bbloomer_get_stock_quantity_curr_role', 9999, 2 );
    
    function bbloomer_get_stock_quantity_curr_role( $value, $product ) {
        if ( wc_current_user_has_role( 'custom_role' ) ) {
            $value = get_post_meta( $product_id, '_stock_role', true ) ? get_post_meta( $product_id, '_stock_role', true ) : 0;
        }
        return $value;
    }
    
    
    add_filter( 'woocommerce_product_get_stock_status' , 'bbloomer_get_stock_status_curr_role', 9999, 2 );
    
    function bbloomer_get_stock_status_curr_role( $status, $product ) {
        if ( wc_current_user_has_role( 'custom_role' ) ) {
            $value = get_post_meta( $product_id, '_stock_role', true );
            $status = $value && ( $value > 0 ) ? 'instock' : 'outofstock';
        }
        return $status;
    }
    

    Finally, you need to reduce the correct stock on payment complete:

    add_filter( 'woocommerce_payment_complete_reduce_order_stock', 'bbloomer_maybe_reduce_role_stock', 9999, 2 );
    
    function bbloomer_maybe_reduce_role_stock( $reduce, $order_id ) {
        $order = wc_get_order( $order_id );
        if ( wc_user_has_role( $order->get_customer_id(), 'custom_role' ) { 
            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 );
                $item_name = $product->get_formatted_name();
                $old_stock = get_post_meta( $product_id, '_stock_role', true ) ? (int) get_post_meta( $product_id, '_stock_role', true ) : 0;
                update_post_meta( $product_id, '_stock_role', $old_stock - $qty );
                $item->add_meta_data( '_reduced_stock', $qty, true );
                $item->save();
                $order->add_order_note( sprintf( 'Reduced role stock for item "%s" from %s to %s.', $item_name, $old_stock, $old_stock - $qty ) );
                $reduce = false;
            }   
        }   
        return $reduce;
    }
    

    Totally untested and works with simple products only.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search