From ca9955fa9ab5816f3bbcd9e26e931819844c0293 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 25 Apr 2014 15:27:58 +0100 Subject: [PATCH] Update stock amounts with DB queries Closes #5367 @claudiosmweb and @coenjacobs please review --- includes/abstracts/abstract-wc-product.php | 80 +++++++----- includes/class-wc-ajax.php | 12 +- includes/class-wc-order.php | 14 +- includes/class-wc-product-variation.php | 145 ++++++++++++--------- 4 files changed, 140 insertions(+), 111 deletions(-) diff --git a/includes/abstracts/abstract-wc-product.php b/includes/abstracts/abstract-wc-product.php index 4902035c609..1adf3706722 100644 --- a/includes/abstracts/abstract-wc-product.php +++ b/includes/abstracts/abstract-wc-product.php @@ -149,63 +149,81 @@ class WC_Product { return $this->get_stock_quantity(); } + /** + * Check if the stock status needs changing + */ + private function check_stock_status() { + // Update stock status + if ( ! $this->backorders_allowed() && $this->get_total_stock() <= get_option( 'woocommerce_notify_no_stock_amount' ) ) { + $this->set_stock_status( 'outofstock' ); + + } elseif ( $this->backorders_allowed() || $this->get_total_stock() > get_option( 'woocommerce_notify_no_stock_amount' ) ) { + $this->set_stock_status( 'instock' ); + } + } + /** * Set stock level of the product. * - * @param mixed $amount (default: null) - * @return int Stock + * Uses queries rather than update_post_meta so we can do this in one query (to avoid stock issues). + * We cannot rely on the original loaded value in case another order was made since then. + * + * @param int $amount (default: null) + * @param string $mode can be set, add, or subtract + * @return int new stock level */ - public function set_stock( $amount = null ) { - if ( is_null( $amount ) ) { - return 0; - } + public function set_stock( $amount = null, $mode = 'set' ) { + global $wpdb; - if ( $this->managing_stock() ) { + if ( ! is_null( $amount ) && $this->managing_stock() ) { - // Update stock amount - $this->stock = apply_filters( 'woocommerce_stock_amount', $amount ); - - // Update meta - update_post_meta( $this->id, '_stock', $this->stock ); - - // Update stock status - if ( ! $this->backorders_allowed() && $this->get_total_stock() <= get_option( 'woocommerce_notify_no_stock_amount' ) ) { - $this->set_stock_status( 'outofstock' ); - - } elseif ( $this->backorders_allowed() || $this->get_total_stock() > get_option( 'woocommerce_notify_no_stock_amount' ) ) { - $this->set_stock_status( 'instock' ); + // Update stock in DB directly + switch ( $mode ) { + case 'add' : + $wpdb->query( "UPDATE {$wpdb->postmeta} SET meta_value = meta_value + {$amount} WHERE post_id = {$this->id} AND meta_key='_stock'" ); + break; + case 'subtract' : + $wpdb->query( "UPDATE {$wpdb->postmeta} SET meta_value = meta_value - {$amount} WHERE post_id = {$this->id} AND meta_key='_stock'" ); + break; + default : + $wpdb->query( "UPDATE {$wpdb->postmeta} SET meta_value = {$amount} WHERE post_id = {$this->id} AND meta_key='_stock'" ); + break; } + // Clear caches + wp_cache_delete( $this->id, 'post_meta' ); + // Clear total stock transient delete_transient( 'wc_product_total_stock_' . $this->id ); + // Stock status + $this->check_stock_status(); + // Trigger action do_action( 'woocommerce_product_set_stock', $this ); - - return $this->get_stock_quantity(); } - return 0; + return $this->get_stock_quantity(); } /** - * Reduce stock level of the product. + * Reduce stock level of the product. * - * @param int $by (default: 1) Amount to reduce by. - * @return int Stock + * @param int $amount (default: 1) Amount to reduce by. + * @return int new stock level */ - public function reduce_stock( $by = 1 ) { - return $this->set_stock( $this->stock - $by ); + public function reduce_stock( $amount = 1 ) { + return $this->set_stock( $amount, 'subtract' ); } /** * Increase stock level of the product. * - * @param int $by (default: 1) Amount to increase by - * @return int Stock + * @param int $amount (default: 1) Amount to increase by + * @return int new stock level */ - public function increase_stock( $by = 1 ) { - return $this->set_stock( $this->stock + $by ); + public function increase_stock( $amount = 1 ) { + return $this->set_stock( $amount, 'add' ); } /** diff --git a/includes/class-wc-ajax.php b/includes/class-wc-ajax.php index 55785ddbd83..3f57e505a4a 100644 --- a/includes/class-wc-ajax.php +++ b/includes/class-wc-ajax.php @@ -1005,14 +1005,12 @@ class WC_AJAX { $_product = $order->get_product_from_item( $order_item ); if ( $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) { + $stock_change = apply_filters( 'woocommerce_reduce_order_stock_quantity', $order_item_qty[ $item_id ], $item_id ); + $new_stock = $_product->reduce_stock( $stock_change ); - $old_stock = $_product->stock; - $stock_change = apply_filters( 'woocommerce_reduce_order_stock_quantity', $order_item_qty[ $item_id ], $item_id ); - $new_quantity = $_product->reduce_stock( $stock_change ); - - $return[] = sprintf( __( 'Item #%s stock reduced from %s to %s.', 'woocommerce' ), $order_item['product_id'], $old_stock, $new_quantity ); - $order->add_order_note( sprintf( __( 'Item #%s stock reduced from %s to %s.', 'woocommerce' ), $order_item['product_id'], $old_stock, $new_quantity) ); - $order->send_stock_notifications( $_product, $new_quantity, $order_item_qty[ $item_id ] ); + $return[] = sprintf( __( 'Item #%s stock reduced from %s to %s.', 'woocommerce' ), $order_item['product_id'], $new_stock + $stock_change, $new_stock ); + $order->add_order_note( sprintf( __( 'Item #%s stock reduced from %s to %s.', 'woocommerce' ), $order_item['product_id'], $new_stock + $stock_change, $new_stock ) ); + $order->send_stock_notifications( $_product, $new_stock, $order_item_qty[ $item_id ] ); } } diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php index ccf430fab96..e8e0a3c2e49 100644 --- a/includes/class-wc-order.php +++ b/includes/class-wc-order.php @@ -1568,17 +1568,11 @@ class WC_Order { $_product = $this->get_product_from_item( $item ); if ( $_product && $_product->exists() && $_product->managing_stock() ) { + $qty = apply_filters( 'woocommerce_order_item_quantity', $item['qty'], $this, $item ); + $new_stock = $_product->reduce_stock( $qty ); - $old_stock = $_product->stock; - - $qty = apply_filters( 'woocommerce_order_item_quantity', $item['qty'], $this, $item ); - - $new_quantity = $_product->reduce_stock( $qty ); - - $this->add_order_note( sprintf( __( 'Item #%s stock reduced from %s to %s.', 'woocommerce' ), $item['product_id'], $old_stock, $new_quantity) ); - - $this->send_stock_notifications( $_product, $new_quantity, $item['qty'] ); - + $this->add_order_note( sprintf( __( 'Item #%s stock reduced from %s to %s.', 'woocommerce' ), $item['product_id'], $new_stock + $qty, $new_stock) ); + $this->send_stock_notifications( $_product, $new_stock, $item['qty'] ); } } diff --git a/includes/class-wc-product-variation.php b/includes/class-wc-product-variation.php index a816f0978dd..a53d49e2caf 100644 --- a/includes/class-wc-product-variation.php +++ b/includes/class-wc-product-variation.php @@ -361,111 +361,130 @@ class WC_Product_Variation extends WC_Product { /** * Set stock level of the product variation. - * @param int $amount + * + * Uses queries rather than update_post_meta so we can do this in one query (to avoid stock issues). + * We cannot rely on the original loaded value in case another order was made since then. + * + * @param int $amount * @param bool $force_variation_stock If true, the variation's stock will be updated and not the parents. - * @return int - * @todo Need to return 0 if is_null? Or something. Should not be just return. + * @param string $mode can be set, add, or subtract + * @return int new stock level */ - function set_stock( $amount = null, $force_variation_stock = false ) { - if ( is_null( $amount ) ) - return; + public function set_stock( $amount = null, $force_variation_stock = false, $mode = 'set' ) { + global $wpdb; - if ( $amount === '' && $force_variation_stock ) { + if ( ! is_null( $amount ) ) { - // If amount is an empty string, stock management is being turned off at variation level - $this->variation_has_stock = false; - $this->stock = ''; - unset( $this->manage_stock ); + if ( '' === $amount && $force_variation_stock ) { - // Update meta - update_post_meta( $this->variation_id, '_stock', '' ); + // If amount is an empty string, stock management is being turned off at variation level + $this->variation_has_stock = false; + $this->stock = ''; + unset( $this->manage_stock ); - // Refresh parent prices - WC_Product_Variable::sync( $this->id ); + // Update meta + update_post_meta( $this->variation_id, '_stock', '' ); - } elseif ( $this->variation_has_stock || $force_variation_stock ) { + // Refresh parent prices + WC_Product_Variable::sync( $this->id ); - // Update stock amount - $this->stock = intval( $amount ); - $this->variation_has_stock = true; - $this->manage_stock = 'yes'; + } elseif ( $this->variation_has_stock || $force_variation_stock ) { - // Update meta - update_post_meta( $this->variation_id, '_stock', $this->stock ); + // Update stock values + $this->variation_has_stock = true; + $this->manage_stock = 'yes'; - // Clear total stock transient - delete_transient( 'wc_product_total_stock_' . $this->id ); + // Update stock in DB directly + switch ( $mode ) { + case 'add' : + $wpdb->query( "UPDATE {$wpdb->postmeta} SET meta_value = meta_value + {$amount} WHERE post_id = {$this->variation_id} AND meta_key='_stock'" ); + break; + case 'subtract' : + $wpdb->query( "UPDATE {$wpdb->postmeta} SET meta_value = meta_value - {$amount} WHERE post_id = {$this->variation_id} AND meta_key='_stock'" ); + break; + default : + $wpdb->query( "UPDATE {$wpdb->postmeta} SET meta_value = {$amount} WHERE post_id = {$this->variation_id} AND meta_key='_stock'" ); + break; + } - // Check parents out of stock attribute - if ( ! $this->is_in_stock() ) { + // Clear caches + wp_cache_delete( $this->variation_id, 'post_meta' ); - // Check parent - $parent_product = get_product( $this->id ); + // Update stock amount in class + $this->stock = get_post_meta( $this->variation_id, '_stock', true ); - // Only continue if the parent has backorders off and all children are stock managed and out of stock - if ( ! $parent_product->backorders_allowed() && $parent_product->get_total_stock() <= get_option( 'woocommerce_notify_no_stock_amount' ) ) { + // Clear total stock transient + delete_transient( 'wc_product_total_stock_' . $this->id ); - $all_managed = true; + // Check parents out of stock attribute + if ( ! $this->is_in_stock() ) { - if ( sizeof( $parent_product->get_children() ) > 0 ) { - foreach ( $parent_product->get_children() as $child_id ) { - $stock = get_post_meta( $child_id, '_stock', true ); - if ( $stock == '' ) { - $all_managed = false; - break; + // Check parent + $parent_product = get_product( $this->id ); + + // Only continue if the parent has backorders off and all children are stock managed and out of stock + if ( ! $parent_product->backorders_allowed() && $parent_product->get_total_stock() <= get_option( 'woocommerce_notify_no_stock_amount' ) ) { + + $all_managed = true; + + if ( sizeof( $parent_product->get_children() ) > 0 ) { + foreach ( $parent_product->get_children() as $child_id ) { + $stock = get_post_meta( $child_id, '_stock', true ); + if ( $stock == '' ) { + $all_managed = false; + break; + } } } + + if ( $all_managed ) { + $this->set_stock_status( 'outofstock' ); + } } - if ( $all_managed ) { - $this->set_stock_status( 'outofstock' ); - } + } elseif ( $this->is_in_stock() ) { + $this->set_stock_status( 'instock' ); } - } elseif ( $this->is_in_stock() ) { - $this->set_stock_status( 'instock' ); + // Refresh parent prices + WC_Product_Variable::sync( $this->id ); + + // Trigger action + do_action( 'woocommerce_product_set_stock', $this ); + + } else { + return parent::set_stock( $amount, $mode ); } - - // Refresh parent prices - WC_Product_Variable::sync( $this->id ); - - // Trigger action - do_action( 'woocommerce_product_set_stock', $this ); - - return $this->get_stock_quantity(); - - } else { - - return parent::set_stock( $amount ); - } + + return $this->get_stock_quantity(); } /** * Reduce stock level of the product. * - * @param int $by (default: 1) Amount to reduce by + * @param int $amount (default: 1) Amount to reduce by * @return int stock level */ - public function reduce_stock( $by = 1 ) { + public function reduce_stock( $amount = 1 ) { if ( $this->variation_has_stock ) { - return $this->set_stock( $this->stock - $by ); + return $this->set_stock( $amount, false, 'subtract' ); } else { - return parent::reduce_stock( $by ); + return parent::reduce_stock( $amount ); } } /** * Increase stock level of the product. * - * @param int $by (default: 1) Amount to increase by + * @param int $amount (default: 1) Amount to increase by * @return int stock level */ - public function increase_stock( $by = 1 ) { + public function increase_stock( $amount = 1 ) { if ( $this->variation_has_stock ) { - return $this->set_stock( $this->stock + $by ); + return $this->set_stock( $amount, false, 'add' ); } else { - return parent::increase_stock( $by ); + return parent::increase_stock( $amount ); } }