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' );
}

4 Answers 4

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);
}
0
<?php 
// Register custom endpoint for split orders
function custom_endpoint_for_split_orders() {
    add_rewrite_endpoint( 'split-orders', EP_ROOT | EP_PAGES );
   // flush_rewrite_rules(); // Flush rewrite rules to ensure the new endpoint is recognized
`your text`}
add_action( 'init', 'custom_endpoint_for_split_orders' );


function custom_endpoint_for_split_orderss() {
    add_rewrite_endpoint( 'split-order', EP_ROOT | EP_PAGES );
}
add_action( 'init', 'custom_endpoint_for_split_orderss' );

function flush_rewrite_rules_on_activation() {
    custom_endpoint_for_split_orders(); // Register the endpoint
    custom_endpoint_for_split_orderss();
    flush_rewrite_rules(); // Flush rewrite rules
}
register_activation_hook( __FILE__, 'flush_rewrite_rules_on_activation' );

// Add menu item for split orders in "My Account" navigation
function add_split_orders_menu_item( $items ) {
    
    $items['split-orders'] = 'Orders 2';
    return $items;
}
add_filter( 'woocommerce_account_menu_items', 'add_split_orders_menu_item' );


function add_split_orders_table_menu_item( $items ) {
    $new_items`your text` = array();

    // Copy existing menu items
    foreach ( $items as $key => $item ) {
        $new_items[ $key ] = $item;

        // Insert the new menu item before the 'dashboard' item
        if ( $key === 'orders' ) {
            $new_items['split-orders'] = 'Orders 2';
        }
    }

    return $new_items;
}
add_filter( 'woocommerce_account_menu_items', 'add_split_orders_table_menu_item' );


/////// Mneu  code end  Here   
//adding splitting order functionality show  in  wp-admin Order table 
function action_woocommerce_checkout_order_processed( $order_id, $posted_data, $order ) {   
    // Array to store split orders
    $split_orders = array();

    // Get all items in the order
    $order_items = $order->get_items();

    // Flags to check if any product from category 494 and other categories is found
    $products_in_category_494 = false;
    $products_in_other_categories = false;

    // Create new split order only if there are products from both category 494 and other categories
    $split_order_needed = false;

    // Iterate through order items
    foreach ( $order_items as $item_id => $item ) {
        // Get the WC_Product Object
        $product = $item->get_product();
        
        // Get categories of the product
        $product_categories = $product->get_category_ids();

        // Check if the product belongs to the specified category (category ID: 494)
        if (in_array('494', $product_categories)) {
            // Set flag to true
            $products_in_category_494 = true;
        } else {
            // Set flag for other categories
            $products_in_other_categories = true;
        }
    }

    // Check if both types of products are found
    if ($products_in_category_494 && $products_in_other_categories) {
        $split_order_needed = true;
    }

    // If a split order is needed, proceed to split the order
    if ( $split_order_needed ) {
        // Create new split order
        $split_order = wc_create_order();

        // Iterate through order items
        foreach ( $order_items as $item_id => $item ) {
            // Get the WC_Product Object
            $product = $item->get_product();
            
            // Get categories of the product
            $product_categories = $product->get_category_ids();

            // Check if the product belongs to the specified category (category ID: 494)
            if (in_array('494', $product_categories)) {
                // Add product to the split order
                $split_order->add_product( $product, $item->get_quantity() );

                // Remove product from original order
                $order->remove_item( $item_id );
            }
        }

      
        $split_order->set_address( array(
            'first_name' => $order->get_billing_first_name(),
            'last_name'  => $order->get_billing_last_name(),
            'email'      => $order->get_billing_email(),
            'phone'      => $order->get_billing_phone(),
            'address_1'  => $order->get_billing_address_1(),
            'address_2'  => $order->get_billing_address_2(),
            'city'       => $order->get_billing_city(),
            'state'      => $order->get_billing_state(),
            'postcode'   => $order->get_billing_postcode(),
            'country'    => $order->get_billing_country()
        ), 'billing' );

        $split_order->set_address( array(
            'first_name' => $order->get_shipping_first_name(),
            'last_name'  => $order->get_shipping_last_name(),
            'address_1'  => $order->get_shipping_address_1(),
            'address_2'  => $order->get_shipping_address_2(),
            'city'       => $order->get_shipping_city(),
            'state'      => $order->get_shipping_state(),
            'postcode'   => $order->get_shipping_postcode(),
            'country'    => $order->get_shipping_country()
        ), 'shipping' );

        // Set the correct currency and payment gateway
        $split_order->set_currency( $order->get_currency() );
        $split_order->set_payment_method( $order->get_payment_method() );

        // Calculate totals
        $split_order->calculate_totals();

        // Set order note with original ID
        $split_order->add_order_note( 'Automated order. Created from the original order ID: ' . $order_id );

      
        $split_order->update_status( 'processing' );

        // Add the split order to the array of split orders
        $split_orders[] = $split_order;
    }

    // Re-calculate & save the original order
    $order->calculate_totals();
    $order->save();

    // Store split order data in order meta
    update_post_meta( $order_id, 'split_orders', $split_orders );
}
add_action( 'woocommerce_checkout_order_processed', 'action_woocommerce_checkout_order_processed', 10, 3 );

//display the split order table on thank you page user dashboard  
function display_split_orders( $order_id ) {
    // Get split orders from order meta
    $split_orders = get_post_meta( $order_id, 'split_orders', true );

    // Check if there are split orders
    if ( ! empty( $split_orders ) ) {
        echo '<section>';
        echo '<h2>Orders 2 Details</h2>';

        // Loop through split orders
        foreach ( $split_orders as $split_order ) {
            echo '<table class="woocommerce-table woocommerce-table--order-details shop_table order_details">';
            
            echo '<thead>';
            echo '<tr>';
            echo '<th class="woocommerce-table__product-name product-name" colspan="2">Split Order ID: ' . $split_order->get_id() . '</tr>';
            echo '<th class="woocommerce-table__product-name product-name">Product</th>';
            echo '<th class="woocommerce-table__product-table product-total">Total</th>';
            echo '</tr>';
            echo '</thead>';
           
            echo '<tbody>';

            // Loop through items in split order
            foreach ( $split_order->get_items() as $item_id => $item ) {
                $product = $item->get_product();
                echo '<tr class="woocommerce-table__line-item order_item">';
                echo '<td class="woocommerce-table__product-name product-name"><a href="' . $product->get_permalink() . '">' . $product->get_name() . '</a><strong class="product-quantity"> × ' . $item->get_quantity() . '</strong></td>';
                echo '<td class="woocommerce-table__product-total product-total"><span class="woocommerce-Price-amount amount">' . wc_price( $item->get_total() ) . '</span></td>';
                echo '</tr>';
            }

            echo '</tbody>';
            echo '<tfoot>';
            echo '<tr><th scope="row">Subtotal:</th><td><span class="woocommerce-Price-amount amount">' . wc_price( $split_order->get_subtotal() ) . '</span></td></tr>';
            echo '<tr><th scope="row">Total:</th><td><span class="woocommerce-Price-amount amount">' . wc_price( $split_order->get_total() ) . '</span></td></tr>';
            echo '</tfoot>';
            echo '</table>';
        }

        echo '</section>';
    } else {
        // echo '<p>No split orders found.</p>';
    }
}
add_action( 'woocommerce_thankyou', 'display_split_orders' );

function display_split_orders_content_in_table() {
    global $wp;

    // Check if the current URL contains the split orders endpoint
    if ( isset( $wp->query_vars['split-orders'] ) ) {
        // Get the current user ID
        $user_id = get_current_user_id();

        // Set pagination parameters
        $orders_per_page = -1;
        $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
        $offset = ( $paged - 1 ) * $orders_per_page;

        // Get orders for the current user with pagination
        $customer_orders = wc_get_orders( array(
            'customer'    => $user_id,
            'post_status' => array( 'wc-processing', 'wc-completed' ),
            'limit'       => $orders_per_page,
            'offset'      => $offset,
        ) );

        if ( $customer_orders ) {

            // Display split orders in a table
            echo '<div class="table-responsive splitorder">';
            echo '<table class="table cart-table order-table">';
            echo '<thead><tr><th>Order</th><th>Date</th><th>Status</th><th>Total</th><th>Actions</th></tr></thead>';
            echo '<tbody>';

            foreach ( $customer_orders as $order ) {
                // Get split orders data from order meta
                $split_orders = get_post_meta( $order->get_id(), 'split_orders', true );

                // Check if there are split orders
                if ( ! empty( $split_orders ) ) {
                    foreach ( $split_orders as $split_order ) {
                        // Get split order ID
                        $split_order_id = $split_order->get_id();

                        // Get split order data
                        $split_order_data = $split_order->get_data();

                        echo '<tr>';
                        echo '<td class="woocommerce-orders-table__cell woocommerce-orders-table__cell-order-number" ><a href="' . esc_url( home_url( '/my-account/split-order/' . $split_order_id ) ) . '">#'. $split_order_id .'</a></td>';
                        echo '<td>' . $order->get_date_created()->date_i18n( get_option( 'date_format' ) ) . '</td>';
                        echo '<td>' . wc_get_order_status_name( $split_order->get_status() ) . '</td>';
                        echo '<td>' . wc_price( $split_order_data['total'] ) .' for '. $split_order->get_item_count() .' items</td>';
                        echo '<td class="woocommerce-orders-table__cell woocommerce-orders-table__cell-order-actions"><a class="woocommerce-button button view" href="' . esc_url( home_url( '/my-account/split-order/' . $split_order_id ) ) . '">View</a></td>';
                        echo '</tr>';
                    }
                }
            }

            echo '</tbody>';
            echo '</table>';
            echo '</div>';

            // Add pagination
            $total_orders = count( $customer_orders );
            $total_pages = ceil( $total_orders / $orders_per_page );
            echo paginate_links( array(
                'total'   => $total_pages,
                'current' => $paged,
            ) );
        } else {
            // echo '<p>No orders found for the current user.</p>';
        }
    }
}
add_action( 'woocommerce_account_split-orders_endpoint', 'display_split_orders_content_in_table' );
// Handle requests to the split-order endpoint order view user 
function display_split_order_content() {
    global $wp;

   
    $current_url = home_url( $wp->request );
    $url_parts = parse_url( $current_url );

    if ( isset( $url_parts['path'] ) && strpos( $url_parts['path'], 'split-order/' ) !== false ) {
        // Extract the order ID from the URL
        $path_parts = explode( '/', $url_parts['path'] );
        $order_id = end( $path_parts );
        $split_order_id = absint( $order_id );

        // Get the split order object
        $split_order = wc_get_order( $split_order_id );

        if ( $split_order && ! is_wp_error( $split_order ) ) {
            // Display split order details
            echo '<p>
          Order #<mark class="order-number">'. $split_order->get_id() .'</mark> was placed on <mark class="order-date">'.$split_order->get_date_created()->date_i18n( get_option( 'date_format' ) ).'</mark> and is currently <mark class="order-status">'.wc_get_order_status_name( $split_order->get_status() ) .'</mark>.</p>';
            echo '<section>';
            echo '<h2>Order 2 Details:</h2>';
            echo '<table class="woocommerce-table woocommerce-table--order-details shop_table order_details">';
            echo '<thead>';
            echo '<tr>';
            echo '<th class="woocommerce-table__product-name product-name" colspan="2">Split Order ID: ' . $split_order->get_id() . '</tr>';
            echo '<th class="woocommerce-table__product-name product-name">Product</th>';
            echo '<th class="woocommerce-table__product-table product-total">Total</th>';
            '</tr>';
            '</thead>';
            echo '<tbody>';

            foreach ( $split_order->get_items() as $item_id => $item ) {
                echo '<tr class="woocommerce-table__line-item order_item">';
                echo '<td class="woocommerce-table__product-name product-name">';
                echo '<a href="' . $item->get_product()->get_permalink() . '">' . $item->get_name() . '</a>';
                echo '<strong class="product-quantity"> × ' . $item->get_quantity() . '</strong>';
                echo '</td>';
                echo '<td class="woocommerce-table__product-total product-total">';
                echo '<span class="woocommerce-Price-amount amount">' . wc_price( $item->get_total() ) . '</span>';
                echo '</td>';
                echo '</tr>';
            }

            echo '</tbody>';
            echo '<tfoot>';
            echo '<tr>';
            echo '<th scope="row">Subtotal:</th>';
            echo '<td><span class="woocommerce-Price-amount amount">' . wc_price( $split_order->get_subtotal() ) . '</span></td>';
            '</tr>';
            // Add billing address details
            echo '<tr>';
            echo '<th scope="row">Billing Address:</th>';
            echo '<td>';
            echo $split_order->get_formatted_billing_address(); // Display formatted billing address
            echo '</td>';
            echo '</tr>';

           

            echo '<tr>';
            echo '<th scope="row">Total:</th>';
echo '<td><span class="woocommerce-Price-amount amount">' . wc_price( $split_order->get_total() ) . '</span></td>
echo '</tr>';
            echo '</tfoot>';
            echo '</table>';
            echo '</section>';

        } else {
            // echo '<p>Split order not found.</p>';
        }
    }
}
add_action( 'woocommerce_account_split-order_endpoint', 'display_split_order_content' );

Your Answer

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

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