0

I'm looking to split line items with a quantity > 1, into individual line items AFTER the order is received. I'm read a lot about this and see a bunch of great scripts that do this before the checkout process occurs such as https://businessbloomer.com/woocommerce-display-separate-cart-items-product-quantity-1/ and https://stackoverflow.com/questions/32485152/woocommerce-treat-cart-items-separate-if-quantity-is-more-than-1?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa.

Does anyone have any thoughts about how to do this AFTER the order has been completed? Or right on order creation in WooCommerce? I want anything with quantity > 1 to be broken out into individual line items.

So I believe I'll need to access all line items and then find those with quantity > 1 and then add new line items and decrement the quantity until it all balances out. Where I could use some help is how to create a line item? I know I can inspect them as shown below:

function inspect_line_items() 
{
  $order = wc_get_order( 390 );
    foreach ($order->get_items() as $item_id => $item_data) {

    // Get an instance of corresponding the WC_Product object
    $product = $item_data->get_product();
    $product_name = $product->get_name(); // Get the product name

    $item_quantity = $item_data->get_quantity(); // Get the item quantity

    $item_total = $item_data->get_total(); // Get the item line total

    // Displaying this data (to check)
    if ($item_quantity >1 ){
        echo 'HALP!';

      }
    }
}

Ok I'm continuing to try and I've been able to add line items (this isn't prod just testing it out, obviously this has some gaps :) ). That being said I can add a new item after checkout, and then with another hook change the order status back to processing. The issue is now the prices that are displayed. Even if I decrement the quantity for a particular item, it will still show the full/original cost. I tried updating the sub_total and total fields and then I get this funky Discount: line item. Any thoughts? Am I one the right track here?

add_action( 'woocommerce_checkout_create_order', 'change_total_on_checking', 20, 1 );
function change_total_on_checking( $order ) {
    // Get order total
    $total = $order->get_total();
    $items = $order->get_items();
    foreach ($order->get_items() as $item_id => $item_data) {

    // Get an instance of corresponding the WC_Product object
        $product = $item_data->get_product();
        $product_id = $item_data->get_id();
        $product_name = $product->get_name();
        $price = $product->get_price();


        $item_quantity = $item_data->get_quantity();
        $item_data['quantity'] = $item_data['quantity'] - 1 ; // Get the item quantity
        //$item_data['total'] = $item_data['total'] - $price;
        //$item_data['sub_total'] = $item_data['sub_total'] - $price;

        $item_total = $item_data->get_total(); // Get the item line total


        //do_action('woocommerce_add_to_order', $item_data['id'], $item_data['product_id'], $item_quantity, $item_data['variation_id']);
        WC()->cart->add_to_cart( $product_id, $item_quantity );
        $order->add_product( $product, 1);
        $order->calculate_totals(); // updating totals

        $order->save(); // Save the order data


    }

}

add_action( 'woocommerce_thankyou', 'woocommerce_thankyou_change_order_status', 10, 1 );
function woocommerce_thankyou_change_order_status( $order_id ){
    if( ! $order_id ) return;

    $order = wc_get_order( $order_id );

    $order->update_status( 'processing' );
}

3 Answers 3

1

So I ended up combining a few answer out there and split out orders when adding to the cart, and also when updating the cart. The below works, though it's purely a front end customization. Hope this helps someone else!

function bbloomer_split_product_individual_cart_items( $cart_item_data, $product_id ){
  $unique_cart_item_key = uniqid();
  $cart_item_data['unique_key'] = $unique_cart_item_key;
  return $cart_item_data;
}

add_filter( 'woocommerce_add_cart_item_data', 'bbloomer_split_product_individual_cart_items', 10, 2 );  

add_action( 'woocommerce_after_cart_item_quantity_update', 'limit_cart_item_quantity', 20, 4 );
function limit_cart_item_quantity( $cart_item_key, $quantity, $old_quantity, $cart ){

    // Here the quantity limit
    $limit = 1;
    $orders_added = $quantity - $limit;

    if( $quantity > $limit ){
        //Set existing line item quantity to the limit of 1
        $cart->cart_contents[ $cart_item_key ]['quantity'] = $limit;
        //get product id of item that was updated
        $product_id = $cart->cart_contents[ $cart_item_key ][ 'product_id' ];

        for( $i = 0; $i< $orders_added; $i++ ){
            //iterate over the number of orders you must as with quantity one
            $unique_cart_item_key = uniqid();
            //create unique cart item ID, this is what breaks it out as a separate line item
            $cart_item_data = array();
            //initialize cart_item_data array where the unique_cart_item_key will be stored
            $cart_item_data['unique_key'] = $unique_cart_item_key;
            //set the cart_item_data at unique_key = to the newly created unique_key

            //add that shit! this does not take into account variable products

            $cart->add_to_cart( $product_id, 1, 0, 0, $cart_item_data );

        }

        // Add a custom notice
        wc_add_notice( __('We Split out quantities of more than one into invididual line items for tracking purposes, please update quantities as needed'), 'notice' );
    }
}
0

Here is the solution I found - split done at order save, not at cart level:

add_action( 'woocommerce_after_order_object_save', array( $this, 'split_order_items' ), 10, 2 );
function split_order_items( $order, $data_store ) {
    $order_items = $order->get_items();

    foreach ( $order_items as $item ) {

        if ( $item instanceof WC_Order_Item_Product ) {
            $quantity = $item->get_quantity( 'edit' );
            $product  = $item->get_product();

            if ( $product && 1 < $quantity ) {
                $product = $item->get_product();
                $taxes   = $item->get_taxes();

                WP_Weixin::log( $order->get_item( $item->get_id() ) );

                $order->remove_item( $item->get_id() );

                for ( $i = $quantity; $i > 0; $i-- ) {
                    $new_item  = new WC_Order_Item_Product();
                    $new_taxes = array();

                    if ( ! empty( $taxes ) && is_array( $taxes ) ) {
                        foreach ( $taxes as $taxes_key => $tax_values ) {
                            $new_tax_values = array();

                            if ( ! empty( $values ) && is_array( $values ) ) {

                                foreach ( $values as $tax_key => $tax_value ) {
                                    $new_tax_values[ $tax_key ] = $tax_value / $quantity;
                                }
                            }

                            $new_taxes[ $taxes_key ] = $new_tax_values;
                        }
                    }

                    $new_item->set_product( $product );
                    $new_item->set_backorder_meta();
                    $new_item->set_props(
                        array(
                            'quantity'     => 1,
                            'subtotal'     => $item->get_subtotal( 'edit' ) / $quantity,
                            'total'        => $item->get_total( 'edit' ) / $quantity,
                            'subtotal_tax' => $item->get_subtotal_tax( 'edit' ) / $quantity,
                            'total_tax'    => $item->get_total_tax( 'edit' ) / $quantity,
                            'taxes'        => $new_taxes,
                        )
                    );
                    $order->add_item( $new_item );
                }
            }
        }
    }

    remove_action( 'woocommerce_after_order_object_save', array( $this, 'split_order_items' ), 10 );
    $order->save();
    add_action( 'woocommerce_after_order_object_save', array( $this, 'split_order_items' ), 10, 2 );
}

The minor downside is that the order is saved twice (the order items are protected, and they have to be saved in database before they can be accessed), which can be acceptable depending on the cases. This is the only way as far as I could tell after spending a few hours looking at the source code.

0

I read a lot the previous code, and i fixed this, totally backend costumization, I was in need of something like this, we add serial numbers to each product but we want customers to buy with quantity, hope helps some people,

add in functions.php of your theme/child-theme or any file included in functions.php

in last edition, I added support for variation products

add_action( 'woocommerce_after_order_object_save', 'split_order_items', 10, 2 );
function split_order_items($order) {
    $order_items = $order->get_items();
    foreach ($order_items as $item) {
        $quantity = $item->get_quantity('edit');
        $product = $item->get_product();
        $included_cats = [24, 25];
        $variable = false;
        if ($product->is_type('variation')) {
            $variation_id = $product->get_id();
            $variable_prod = $product;
            $product = wc_get_product($product->get_parent_id());
            $variable = true;
        }
        $category_id = get_the_terms($product->get_id(), 'product_cat')[0]->term_id;
        if ($product && $quantity > 1 && in_array($category_id, $included_cats)) {
            if ($variable) {
                $init_qty = $quantity;
                $item->set_quantity(1);
                if ($quantity > 1) $item->set_props([
                    'subtotal' => $item->get_subtotal('edit') / ($init_qty ?: $quantity),
                    'total' => $item->get_total('edit') / ($init_qty ?: $quantity),
                    'subtotal_tax' => $item->get_subtotal_tax('edit') / ($init_qty ?: $quantity),
                    'total_tax' => $item->get_total_tax('edit') / ($init_qty ?: $quantity),
                ]);
                if ($quantity == 1) continue;
                $quantity--;
            } else {
                $order->remove_item($item->get_id());
            }
            $taxes = $item->get_taxes();
            for ($i = $quantity; $i > 0; $i--) {
                $new_item = new WC_Order_Item_Product();
                $new_taxes = [];
                if ($variable) {
                    $new_item->set_product_id($product->get_id());
                    $new_item->set_variation_id($variation_id);
                    $new_item->set_variation(is_callable([$variable_prod, 'get_variation_attributes']) ? $variable_prod->get_variation_attributes() : []);
                    $new_item->set_name($variable_prod->get_name());
                    $new_item->set_tax_class($variable_prod->get_tax_class());
                } else {
                    $new_item->set_product($product);
                }
                if (!empty($taxes) && is_array($taxes)) {
                    foreach ($taxes as $taxes_key => $tax_values) {
                        $new_tax_values = [];
                        if (!empty($tax_values) && is_array($tax_values)) {
                            foreach ($tax_values as $tax_key => $tax_value) {
                                $new_tax_values[$tax_key] = $tax_value / $quantity;
                            }
                        }
                        $new_taxes[$taxes_key] = $new_tax_values;
                    }
                }
                $new_item->set_backorder_meta();
                $new_item->set_props([
                    'quantity' => 1,
                    'subtotal' => $item->get_subtotal('edit') / ($variable ? 1 : $quantity),
                    'total' => $item->get_total('edit') / ($variable ? 1 : $quantity),
                    'subtotal_tax' => $item->get_subtotal_tax('edit') / ($variable ? 1 : $quantity),
                    'total_tax' => $item->get_total_tax('edit') / ($variable ? 1 : $quantity),
                    'taxes' => $new_taxes,
                ]);
                $order->add_item($new_item);
            }
        }
    }
    remove_action('woocommerce_after_order_object_save', 'split_order_items', 10);
    $order->save();
    add_action('woocommerce_after_order_object_save', 'split_order_items', 10, 1);
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.