2

I need to change the normal behavior of the place_order button at checkout: if there's already an order which is not completed (status = processing), WooCommerce should add items to that order instead of creating a new one. Otherwise, it should create a new order in the default way.

function custom_order() {
    $user_role = wp_get_current_user()->roles[0];
        
    $customer = new WC_Customer($user_id);
    $last_order = $customer->get_last_order();
        
    $last_order_id = $last_order->get_id();
    $last_order_data = $last_order->get_data();
    $last_order_status = $last_order->get_status();
                
    if ( $user_role === "administrator" ) {
        if ($last_order_status === "processing") {          
            foreach ( WC()->cart->get_cart() as $cart_item ) {
                $product = $cart_item['data'];
                $product_id = $product->get_id();
                $quantity = $product->get_quantity();
    
                $last_order->add_product($product, $quantity);
            }
        }

        else {
            // do the normal thing
        }
    }
}

I've tried the following hooks:

add_action('woocommerce_new_order', 'custom_order', 10, 3);
add_filter('woocommerce_create_order', 'custom_order', 10, 2);

Which is the right one and how to add this new condition to the default order function?

7uc1f3r
  • 28,449
  • 17
  • 32
  • 50
checkm
  • 89
  • 9

2 Answers2

1
add_filter('woocommerce_create_order', 'create_or_update_order', 10, 2);

function create_or_update_order() {
    $user_obj = wp_get_current_user();

    $user_role = $user_obj->roles[0];
    $user_id = $user_obj->ID;
    $customer = new WC_Customer($user_id);
    $last_order = $customer->get_last_order();
    $last_order_id = $last_order->get_id();
    $last_order_data = $last_order->get_data();
    $last_order_status = $last_order->get_status();

    if ('administrator' === $user_role) {
        if ('processing' === $last_order_status) {
            foreach (WC()->cart->get_cart() as $cart_item) {

                $product = $cart_item['data'];
                $product_id = $product->get_id();
                $quantity = $cart_item['quantity'];

                $last_order->add_product($product, $quantity);
            }
          return $last_order_id;
        } else {
            return null;
        }
    }
    return null;
}

Inside the class class-wc-checkout.php, the create_order function provides a hook just before creating the order. It will not create another order if the order ID already exist. We will return the order ID if the conditions met.

public function create_order( $data ) {
    // Give plugins the opportunity to create an order themselves.
    $order_id = apply_filters( 'woocommerce_create_order', null, $this );
    if ( $order_id ) {
        return $order_id;
    }......
mujuonly
  • 11,370
  • 5
  • 45
  • 75
  • Returning the $order_id works well, thank you. However, as suggested by @7uc1f3r, more steps are needed to update the order correctly. For instance, if a product is already present in the previous order, one should just increase its quantity instead of adding a new line with `$last_order->add_product($product, $quantity);`. More code is also needed for applying coupon codes, calculating totals, etc. – checkm Apr 20 '22 at 13:29
  • @checkm - You may need to add new conditions for that as per your requirements. – mujuonly Apr 20 '22 at 13:31
1

The woocommerce_create_order filter hook is indeed the right hook to use for your question.

However, when just using $last_order->add_product( $product, $quantity ); you will notice 2 issues

  • When a product already exists in the last order, the function adds a new line instead of just increasing the quantity of the existing product
  • Totals are not recalculated and are therefore displayed incorrectly

So what you need to do is, in addition to loop through the cart items, also loop through the existing order items and compare them.

If the item already exist, update the item, if not, add the item to the order

The second param $checkout enable you to compare and adjust billing information and such if desired

So you get:

function filter_woocommerce_create_order( $null, $checkout ) {
    // Get current user role
    $user = wp_get_current_user();
    $roles = ( array ) $user->roles;

    // Check user role
    if ( in_array( 'administrator', $roles ) ) {
        // Get last order
        $customer = new WC_Customer( get_current_user_id() );
        $last_order = $customer->get_last_order();

        // IS WC_Order
        if ( is_a( $last_order, 'WC_Order' ) ) {
            // Compare status
            if ( $last_order->get_status() == 'processing' ) {
                // Get cart items quantities
                $cart_item_quantities = WC()->cart->get_cart_item_quantities();

                // Loop through last order
                foreach ( $last_order->get_items() as $item ) {
                    // Get product id
                    $item_product_id = $item->get_variation_id() ? $item->get_variation_id() : $item->get_product_id();

                    // Product already exists in last order, update the product quantity
                    if ( array_key_exists( $item_product_id, $cart_item_quantities ) ) {
                        // Get order item quantity
                        $order_item_quantity = $item->get_quantity();

                        // Get order item price
                        $order_item_price = $last_order->get_item_subtotal( $item, true, false );

                        // Get cart item quantity
                        $cart_item_quantity = $cart_item_quantities[$item_product_id];

                        // Calculate new quantity
                        $new_quantity = $order_item_quantity + $cart_item_quantity;
                        
                        // The new line item price
                        $new_line_item_price = $order_item_price * $new_quantity;

                        // Update order item quantity
                        $item->set_quantity( $new_quantity );
                        
                        // Set the new price
                        $item->set_subtotal( $new_line_item_price ); 
                        $item->set_total( $new_line_item_price );

                        // Make new taxes calculations
                        $item->calculate_taxes();

                        // Save line item data
                        $item->save();

                        // Remove from array
                        unset( $cart_item_quantities[$item_product_id] );
                    }
                }

                // Then loop through remaining cart items
                foreach ( $cart_item_quantities as $key => $cart_item_quantity ) {
                    // Product does not exist in last order, add the product
                    $last_order->add_product( wc_get_product( $key ), $cart_item_quantity );
                }

                // Recalculate and save
                $last_order->calculate_totals();

                // Return last order ID
                return $last_order->get_id();
            }
        }
    }

    return $null;
}
add_filter( 'woocommerce_create_order', 'filter_woocommerce_create_order', 10, 2 );

Related: Split a WooCommerce order and create a new order if the original order has products in backorder

7uc1f3r
  • 28,449
  • 17
  • 32
  • 50
  • Didn't know about `WC()->cart->get_cart_item_quantities()`. I looped through `$last_order->get_items()` instead, creating an array like `$old_order_products_ids[$product_id] = $item` and comparing with `array_key_exists($new_product_id, $last_order_products_ids)` as you did. However, applying a coupon looks even less intuitive, as there's no specific function to process already created orders: one must add them as new items + calculate their effect on every single product. – checkm Apr 21 '22 at 11:49
  • 1
    @checkm Towards coupons I believe the easiest way is to remove them first via [`WC_Abstract_Order::remove_coupon()`](https://woocommerce.wp-a2z.org/oik_api/wc_abstract_orderremove_coupon/) and then just reapply it via `$last_order->apply_coupon($coupon_code);`. In this way 1 or more coupons are applied and the order discount is recalculated based on the adjusted order. You can go in many directions with this code, but my answer contains the basics of going through both the cart and the existing order. It all depends on what you want to take into account – 7uc1f3r Apr 21 '22 at 11:57
  • 1
    To remove and apply again worked perfectly. Saved hours. Thank you. – checkm Apr 21 '22 at 12:58