How to update similar variation stock after purcha

2019-08-26 06:28发布

问题:

I struggle to find a way to update the stock of the similar variation of a product.

All my product have, say, a main variation, "Black" (30$) or "white" (250$) but sometimes they have another variation which is a "date start", so date_start:"12th june", date_start: "30th july", etc. I need to update the stock of the "date" variation when it's present (if there is no date, woocommerce update the main stock, no problem). If someone choose "Black"+"12th June" I need the stock of "12 June" to be decreased also for "white"...

Before someone ask, "black" and "white" have different price per product... And "date" change also per product, that's why I need to use variation (and not addon attribute with a plugin). Maybe someone have a better idea for organising this, but I try many other solution, always a caveat. This one seems the simpler, just have to find the good "hook" and "code"

Here is some pseudo code I made for this:

if(Product is sold):
VarSoldVariation = getSoldVariation(product->ID);
OtherVariationWithSameDate = getVariations (VarSoldVariation->pa_dates);
foreach(OtherVariationWithSameDate)updateStockVariation();
endif;

回答1:

OK, it seems a little weird for not using metadata/attributes for this case, instead of variations. However, I've done more unusual stuff with variations than this one, so without judging your decision:

At first, you have to find a suitable action hook which fires after an order takes place. Some of them are:

  • woocommerce_order_status_$STATUS_TRANSITION[from]_to_$STATUS_TRANSITION[to]
  • woocommerce_order_status_$STATUS_TRANSITION[to] (e.g. woocommerce_order_status_completed)
  • woocommerce_order_status_changed
  • woocommerce_payment_complete
  • woocommerce_thankyou

Update 2:

I rewrite the code with some improvements:

  1. In my initial answer, I used a WordPress get_posts function (which uses WP_Query) with a meta_query parameter, which you should definitely change to tax_query in this case, due to performance considerations. And we also know that it's better practice to use wc_get_products and WC_Product_Query where it's possible. However in this case it's not even needed to do a direct post query on db and it's possible to get the variations from get_available_variations method of WC_Product_Variable

  2. Now it checks for quantity on order item and uses it on other date variations stock update.

  3. Now it uses WC classes and functions wherever is possible, instead of direct updating of metadata, stock quantity, stock status, etc.

The Updated Code:

add_action('woocommerce_order_status_processing', 'sync_variations_stock');

/**
 * Update same date variations on customer order place
 * @param $order_id
 * @return void
 */
function sync_variations_stock($order_id)
{
    if (is_admin()) return; // make sure it's a user order and we aren't on admin dashboard
    $order = wc_get_order( $order_id );
    foreach( $order->get_items() as $item ) {
        if ($item->get_type() !== 'line_item') continue;    //if $item is not a product or variation
        $order_variation_count   = $item->get_quantity();
        $order_product_id        = $item->get_product_id();
        $order_variation_id      = $item->get_variation_id();

        if ( ! $order_variation_id ) continue;    // if the item isn't a variation

        $order_variation         = wc_get_product($order_variation_id);
        $order_variation_attribs = $order_variation->get_variation_attributes();
        if ( isset($order_variation_attribs['attribute_pa_date']) ) {
            $current_date_attrib = $order_variation_attribs['attribute_pa_date'];
        } else {
            continue; // stop if the variation in the order doesn't have 'pa_date' attrib
        }

        $product    = wc_get_product( $order_product_id );
        $variations = $product->get_available_variations();
        foreach ( $variations as $variation ) {
            if ( $variation['variation_id'] == $order_variation_id ) {
                continue;   //if this variation is the one we have in our order
            }
            if ( ! isset( $variation['attributes']['attribute_pa_color'] ) || !isset( $variation['attributes']['attribute_pa_date'] ) ) {
                continue;   //if this variation does not have the color or date attrib
            }

            if ( $variation['attributes']['attribute_pa_date'] == $current_date_attrib ) {

                /*
                 * wc_update_product_stock function sets the stock quantity if the variation stock management is enabled
                 * NOTE: This function may cause a negative stock even if the variation backorder is set to false
                 */
                wc_update_product_stock( $variation['variation_id'], $order_variation_count, 'decrease' );
                wc_delete_product_transients($variation['variation_id']); // Clear/refresh the variation cache (optionally if needed)
            }
        }
    }
}

Tested and It's Working!

My First Answer:

For this example, I will use the last one. However you should be careful about this hook, since it fires on every page load of the 'WC Thank You page'. It would be a good idea to use one of these hooks instead:
woocommerce_order_status_processing
woocommerce_order_status_completed
woocommerce_payment_complete

Final code would be something like this:

add_action('woocommerce_thankyou', 'sync_variations_stock');

function sync_variations_stock($order_id)
{
    $order = wc_get_order( $order_id );
    foreach( $order->get_items() as $item ){
        $product_id = $item->get_product_id();
        $product_variation_id = $item->get_variation_id();

        if (!$product_variation_id) return; // if the item isn't a variation

        $date_variation = get_post_meta( $product_variation_id, 'attribute_pa_date', true);
        $color_variation = get_post_meta( $product_variation_id, 'attribute_pa_color', true);

        if ( ! $date_variation && ! $color_variation ) return;  //if the variation doesn't have date and color attributes

        $args = array(
            'post_parent' => $product_id,
            'post_type' => 'product_variation',
            'posts_per_page' => -1,
            'meta_query' => array(
                array(
                    'key' => 'attribute_pa_date',
                    'value' => $date_variation,
                    'compare' => '='
                ),
                array(
                    'key' => 'attribute_pa_color',
                    'value' => $color_variation,
                    'compare' => '!='
                )
            ),
        );
        $other_date_variations = get_posts($args);

        if( is_array($other_date_variations) && !empty($other_date_variations) ){
            foreach ($other_date_variations as $date_variation) {

                // do your stock updating proccess here. (updateStockVariation() as you write in your code)

                $variation_id = $date_variation->ID;
                $date_variation_stock = (int) get_post_meta( $variation_id, '_stock', true);
                if ($date_variation_stock > 0) {    //to prevent backorders
                    $date_variation_stock =  $date_variation_stock - 1;
                    update_post_meta($variation_id, '_stock', $date_variation_stock);

                    // if the variation is now out-of-stock, set it as so
                    if ($date_variation_stock === 0) {
                        update_post_meta($variation_id, '_stock_status', 'outofstock');
                        wp_set_post_terms( $variation_id, 'outofstock', 'product_visibility', true );
                    }
                }
            }
        }
    }
}

Note: You have to replace attribute_pa_date & attribute_pa_color to match your attribute slugs.

Update 1
There are other consideration in this topic. WC Variation stock quantities may be changed in other senarios and circumstances, such as order edit on dashboard, order refunds, direct product edit, etc. Before going live, you have to think about these too.
Whoever as I said, there may be other ways to do what you are trying to. But I couldn't understand your setup and the relation between your variations and the dates. I think it's better to ask a approach related question for this, on WB.SE



回答2:

I also just made a small change. In your code, if people refresh the page, the stock of the other variation are decreased... As woocommerce will always decrease the stock of the bought variation first, I go get this stock variation number and update other one with it. So I'm sure everything stays the same. :) Here is the code updated:

    function sync_variations_stock($order_id)
    {
        if (is_admin()) return; // make sure it's a user order and we aren't on admin dashboard
        $order = wc_get_order( $order_id );
        foreach( $order->get_items() as $item ) {
            if ($item->get_type() !== 'line_item') continue;    //if $item is not a product or variation
            $order_variation_count   = $item->get_quantity();
            $order_product_id        = $item->get_product_id();
            $order_variation_id      = $item->get_variation_id();

            if ( ! $order_variation_id ) continue;    // if the item isn't a variation

            $order_variation         = wc_get_product($order_variation_id);
            $order_variation_attribs = $order_variation->get_variation_attributes();
            if ( isset($order_variation_attribs['attribute_pa_dates']) ) {
                $current_date_attrib = $order_variation_attribs['attribute_pa_dates'];
//Get the stock of the current variation for updating others.
                $new_stock = $order_variation->get_stock_quantity();
            } else {
                continue; // stop if the variation in the order doesn't have 'pa_dates' attrib
            }

            $product    = wc_get_product( $order_product_id );
            $variations = $product->get_available_variations();
            foreach ( $variations as $variation ) {
                if ( $variation['variation_id'] == $order_variation_id ) {
                    continue;   //if this variation is the one we have in our order
                }
                if ( ! isset( $variation['attributes']['attribute_pa_admissible-emploi-quebec'] ) || !isset( $variation['attributes']['attribute_pa_dates'] ) ) {
                    continue;   //if this variation does not have the color or date attrib
                }

                if ( $variation['attributes']['attribute_pa_dates'] == $current_date_attrib ) {

                    /*
                     * wc_update_product_stock function sets the stock quantity if the variation stock management is enabled
                     * NOTE: This function may cause a negative stock even if the variation backorder is set to false
                     */
                    //wc_update_product_stock( $variation['variation_id'], $order_variation_count, 'decrease' );
//Update stock of other variation with the stock number of the one just bought
                    wc_update_product_stock( $variation['variation_id'], $new_stock, 'set' );
                    wc_delete_product_transients($variation['variation_id']); // Clear/refresh the variation cache (optionally if needed)
                }
            }
        }
    }