From a6b45d08bfacff1aacb0bd7c2313fff134f10d4b Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Wed, 10 Mar 2021 11:47:22 +0100 Subject: [PATCH 01/17] Added low stock threshold input to the Admin UI. --- .../meta-boxes/views/html-variation-admin.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/includes/admin/meta-boxes/views/html-variation-admin.php b/includes/admin/meta-boxes/views/html-variation-admin.php index 01a0a20e7eb..0122b732cca 100644 --- a/includes/admin/meta-boxes/views/html-variation-admin.php +++ b/includes/admin/meta-boxes/views/html-variation-admin.php @@ -210,6 +210,23 @@ defined( 'ABSPATH' ) || exit; ) ); + woocommerce_wp_text_input( + array( + 'id' => "variable_low_stock_threshold{$loop}", + 'name' => "variable_low_stock_threshold[{$loop}]", + 'value' => $variation_object->get_low_stock_amount( 'edit' ), + 'placeholder' => get_option( 'woocommerce_notify_low_stock_amount' ), + 'label' => __( 'Low stock threshold', 'woocommerce' ), + 'desc_tip' => true, + 'description' => __( 'When product stock reaches this amount you will be notified by email', 'woocommerce' ), + 'type' => 'number', + 'custom_attributes' => array( + 'step' => 'any', + ), + 'wrapper_class' => 'form-row', + ) + ); + /** * Variation options inventory action. * From b92d1d13910d1341c5b142f1acf65c26d21c637b Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Wed, 10 Mar 2021 15:38:49 +0100 Subject: [PATCH 02/17] Harmonize the id and name with simple product. --- includes/admin/meta-boxes/views/html-variation-admin.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/admin/meta-boxes/views/html-variation-admin.php b/includes/admin/meta-boxes/views/html-variation-admin.php index 0122b732cca..e4ec22287b2 100644 --- a/includes/admin/meta-boxes/views/html-variation-admin.php +++ b/includes/admin/meta-boxes/views/html-variation-admin.php @@ -212,8 +212,8 @@ defined( 'ABSPATH' ) || exit; woocommerce_wp_text_input( array( - 'id' => "variable_low_stock_threshold{$loop}", - 'name' => "variable_low_stock_threshold[{$loop}]", + 'id' => "variable_low_stock_amount{$loop}", + 'name' => "variable_low_stock_amount[{$loop}]", 'value' => $variation_object->get_low_stock_amount( 'edit' ), 'placeholder' => get_option( 'woocommerce_notify_low_stock_amount' ), 'label' => __( 'Low stock threshold', 'woocommerce' ), From 685be2a791e66bd693e6ace816e7c41bb59436ba Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Wed, 10 Mar 2021 17:24:52 +0100 Subject: [PATCH 03/17] Connect the UI to the db to allow updates of the values. --- includes/admin/meta-boxes/class-wc-meta-box-product-data.php | 1 + .../data-stores/class-wc-product-variation-data-store-cpt.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/includes/admin/meta-boxes/class-wc-meta-box-product-data.php b/includes/admin/meta-boxes/class-wc-meta-box-product-data.php index 8438a8ad3ea..13d70c47f40 100644 --- a/includes/admin/meta-boxes/class-wc-meta-box-product-data.php +++ b/includes/admin/meta-boxes/class-wc-meta-box-product-data.php @@ -509,6 +509,7 @@ class WC_Meta_Box_Product_Data { ), 'manage_stock' => isset( $_POST['variable_manage_stock'][ $i ] ), 'stock_quantity' => $stock, + 'low_stock_amount' => isset( $_POST['variable_low_stock_amount'][ $i ] ) && '' !== $_POST['variable_low_stock_amount'][ $i ] ? wc_stock_amount( wp_unslash( $_POST['variable_low_stock_amount'][ $i ] ) ) : '', 'backorders' => isset( $_POST['variable_backorders'], $_POST['variable_backorders'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_backorders'][ $i ] ) ) : null, 'stock_status' => isset( $_POST['variable_stock_status'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_stock_status'][ $i ] ) ) : null, 'image_id' => isset( $_POST['upload_image_id'][ $i ] ) ? wc_clean( wp_unslash( $_POST['upload_image_id'][ $i ] ) ) : null, diff --git a/includes/data-stores/class-wc-product-variation-data-store-cpt.php b/includes/data-stores/class-wc-product-variation-data-store-cpt.php index 534ba382ef8..cf481afc41a 100644 --- a/includes/data-stores/class-wc-product-variation-data-store-cpt.php +++ b/includes/data-stores/class-wc-product-variation-data-store-cpt.php @@ -357,6 +357,7 @@ class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT impl 'date_on_sale_to' => get_post_meta( $id, '_sale_price_dates_to', true ), 'manage_stock' => get_post_meta( $id, '_manage_stock', true ), 'stock_status' => get_post_meta( $id, '_stock_status', true ), + 'low_stock_amount' => get_post_meta( $id, '_low_stock_amount', true ), 'shipping_class_id' => current( $this->get_term_ids( $id, 'product_shipping_class' ) ), 'virtual' => get_post_meta( $id, '_virtual', true ), 'downloadable' => get_post_meta( $id, '_downloadable', true ), @@ -404,7 +405,6 @@ class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT impl 'sku' => get_post_meta( $product->get_parent_id(), '_sku', true ), 'manage_stock' => get_post_meta( $product->get_parent_id(), '_manage_stock', true ), 'backorders' => get_post_meta( $product->get_parent_id(), '_backorders', true ), - 'low_stock_amount' => get_post_meta( $product->get_parent_id(), '_low_stock_amount', true ), 'stock_quantity' => wc_stock_amount( get_post_meta( $product->get_parent_id(), '_stock', true ) ), 'weight' => get_post_meta( $product->get_parent_id(), '_weight', true ), 'length' => get_post_meta( $product->get_parent_id(), '_length', true ), From e2e589b049acb4990217c0ebb2c83b58e9c32aa5 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Wed, 10 Mar 2021 17:57:56 +0100 Subject: [PATCH 04/17] Don't take the low stock amount info from the parent of the variation, but the variation itself. Potentially bw incompatible. --- includes/wc-stock-functions.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/includes/wc-stock-functions.php b/includes/wc-stock-functions.php index 4cbf1ed90cc..e619da0785f 100644 --- a/includes/wc-stock-functions.php +++ b/includes/wc-stock-functions.php @@ -395,9 +395,6 @@ add_action( 'woocommerce_order_status_on-hold', 'wc_release_stock_for_order', 11 * @return int */ function wc_get_low_stock_amount( WC_Product $product ) { - if ( $product->is_type( 'variation' ) ) { - $product = wc_get_product( $product->get_parent_id() ); - } $low_stock_amount = $product->get_low_stock_amount(); if ( '' === $low_stock_amount ) { $low_stock_amount = get_option( 'woocommerce_notify_low_stock_amount', 2 ); From f7755f123ea74e45eb2ca835fff18e1bf089fd26 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Wed, 10 Mar 2021 18:15:31 +0100 Subject: [PATCH 05/17] Enable bulk updates of variations with low stock amount. --- .../js/admin/meta-boxes-product-variation.js | 1 + .../views/html-product-data-variations.php | 1 + includes/class-wc-ajax.php | 27 +++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/assets/js/admin/meta-boxes-product-variation.js b/assets/js/admin/meta-boxes-product-variation.js index 55ac9327559..551ad33a114 100644 --- a/assets/js/admin/meta-boxes-product-variation.js +++ b/assets/js/admin/meta-boxes-product-variation.js @@ -763,6 +763,7 @@ jQuery( function( $ ) { case 'variable_regular_price' : case 'variable_sale_price' : case 'variable_stock' : + case 'variable_low_stock_amount' : case 'variable_weight' : case 'variable_length' : case 'variable_width' : diff --git a/includes/admin/meta-boxes/views/html-product-data-variations.php b/includes/admin/meta-boxes/views/html-product-data-variations.php index 15af978f892..bc35103b2e6 100644 --- a/includes/admin/meta-boxes/views/html-product-data-variations.php +++ b/includes/admin/meta-boxes/views/html-product-data-variations.php @@ -75,6 +75,7 @@ if ( ! defined( 'ABSPATH' ) ) { + diff --git a/includes/class-wc-ajax.php b/includes/class-wc-ajax.php index 964c0e2a0bb..5dbd366f4a3 100644 --- a/includes/class-wc-ajax.php +++ b/includes/class-wc-ajax.php @@ -2301,6 +2301,32 @@ class WC_AJAX { } } + /** + * Bulk action - Set Low Stock Amount. + * + * @param array $variations List of variations. + * @param array $data Data to set. + * + * @used-by bulk_edit_variations + */ + private static function variation_bulk_action_variable_low_stock_amount( $variations, $data ) { + if ( ! isset( $data['value'] ) ) { + return; + } + + $low_stock_amount = wc_stock_amount( wc_clean( $data['value'] ) ); + + foreach ( $variations as $variation_id ) { + $variation = wc_get_product( $variation_id ); + if ( $variation->managing_stock() ) { + $variation->set_low_stock_amount( $low_stock_amount ); + } else { + $variation->set_low_stock_amount( '' ); + } + $variation->save(); + } + } + /** * Bulk action - Set Weight. * @@ -2547,6 +2573,7 @@ class WC_AJAX { * @uses WC_AJAX::variation_bulk_action_toggle_virtual() * @uses WC_AJAX::variation_bulk_action_toggle_downloadable() * @uses WC_AJAX::variation_bulk_action_toggle_enabled + * @uses WC_AJAX::variation_bulk_action_variable_low_stock_amount() */ public static function bulk_edit_variations() { ob_start(); From b48a3892648e328301504af025cdb47f34fcdff1 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Thu, 11 Mar 2021 16:21:43 +0100 Subject: [PATCH 06/17] Add support for low stock amount to REST API v3. Ref #27371. --- .../Version2/class-wc-rest-products-v2-controller.php | 3 +++ .../class-wc-rest-product-variations-controller.php | 10 ++++++++++ .../Version3/class-wc-rest-products-controller.php | 11 +++++++++++ 3 files changed, 24 insertions(+) diff --git a/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php b/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php index 7d06b2573d2..cc0c71c10e4 100644 --- a/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php +++ b/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php @@ -766,6 +766,9 @@ class WC_REST_Products_V2_Controller extends WC_REST_CRUD_Controller { case 'backordered': $base_data['backordered'] = $product->is_on_backorder(); break; + case 'low_stock_amount': + $base_data['low_stock_amount'] = $product->get_low_stock_amount(); + break; case 'sold_individually': $base_data['sold_individually'] = $product->is_sold_individually(); break; diff --git a/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php b/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php index 417804ff383..321e2a69729 100644 --- a/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php +++ b/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php @@ -65,6 +65,7 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V 'backorders' => $object->get_backorders(), 'backorders_allowed' => $object->backorders_allowed(), 'backordered' => $object->is_on_backorder(), + 'low_stock_amount' => $object->get_low_stock_amount(), 'weight' => $object->get_weight(), 'dimensions' => array( 'length' => $object->get_length(), @@ -185,9 +186,13 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V $stock_quantity += wc_stock_amount( $request['inventory_delta'] ); $variation->set_stock_quantity( $stock_quantity ); } + if ( isset( $request['low_stock_amount'] ) ) { + $variation->set_low_stock_amount( wc_stock_amount( $request['low_stock_amount'] ) ); + } } else { $variation->set_backorders( 'no' ); $variation->set_stock_quantity( '' ); + $variation->set_low_stock_amount( '' ); } // Regular Price. @@ -597,6 +602,11 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V 'context' => array( 'view', 'edit' ), 'readonly' => true, ), + 'low_stock_amount' => array( + 'description' => __( 'Low Stock amount for the variation.', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + ), 'weight' => array( /* translators: %s: weight unit */ 'description' => sprintf( __( 'Variation weight (%s).', 'woocommerce' ), $weight_unit ), diff --git a/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php b/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php index bad0b9bca14..34264b88a4f 100644 --- a/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php +++ b/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php @@ -551,11 +551,17 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller { $stock_quantity += wc_stock_amount( $request['inventory_delta'] ); $product->set_stock_quantity( wc_stock_amount( $stock_quantity ) ); } + + // Low stock amount. + if ( isset( $request['low_stock_amount'] ) ) { + $product->set_low_stock_amount( wc_stock_amount( $request['low_stock_amount'] ) ); + } } else { // Don't manage stock. $product->set_manage_stock( 'no' ); $product->set_stock_quantity( '' ); $product->set_stock_status( $stock_status ); + $product->set_low_stock_amount( '' ); } } elseif ( ! $product->is_type( 'variable' ) ) { $product->set_stock_status( $stock_status ); @@ -985,6 +991,11 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller { 'context' => array( 'view', 'edit' ), 'readonly' => true, ), + 'low_stock_amount' => array( + 'description' => __( 'Low Stock amount for the product.', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + ), 'sold_individually' => array( 'description' => __( 'Allow one item to be bought in a single order.', 'woocommerce' ), 'type' => 'boolean', From 1f8f8580d13c32d1903b8089051d461052cb6fb3 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Thu, 11 Mar 2021 16:26:41 +0100 Subject: [PATCH 07/17] Better wording of the tooltip--being more specific about what the amount relates to. --- includes/admin/meta-boxes/views/html-variation-admin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/admin/meta-boxes/views/html-variation-admin.php b/includes/admin/meta-boxes/views/html-variation-admin.php index e4ec22287b2..994cf065567 100644 --- a/includes/admin/meta-boxes/views/html-variation-admin.php +++ b/includes/admin/meta-boxes/views/html-variation-admin.php @@ -218,7 +218,7 @@ defined( 'ABSPATH' ) || exit; 'placeholder' => get_option( 'woocommerce_notify_low_stock_amount' ), 'label' => __( 'Low stock threshold', 'woocommerce' ), 'desc_tip' => true, - 'description' => __( 'When product stock reaches this amount you will be notified by email', 'woocommerce' ), + 'description' => __( 'When variation stock reaches this amount you will be notified by email', 'woocommerce' ), 'type' => 'number', 'custom_attributes' => array( 'step' => 'any', From aac4ecc9430f47574c067392e28581b210785936 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Thu, 11 Mar 2021 16:58:51 +0100 Subject: [PATCH 08/17] Added comment documenting the history of the function. --- includes/wc-stock-functions.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/includes/wc-stock-functions.php b/includes/wc-stock-functions.php index e619da0785f..f0c031fbbe2 100644 --- a/includes/wc-stock-functions.php +++ b/includes/wc-stock-functions.php @@ -390,6 +390,10 @@ add_action( 'woocommerce_order_status_on-hold', 'wc_release_stock_for_order', 11 /** * Return low stock amount to determine if notification needs to be sent * + * Since 5.3.0, this function no longer redirects from variation to its parent product. + * Low stock amount can now be attached to the variation itself and if it isn't, + * the default from store-wide setting gets used. + * * @param WC_Product $product Product to get data from. * @since 3.5.0 * @return int From 1b53b724b292e4613007b645f117f2934b75ac88 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Thu, 11 Mar 2021 17:02:22 +0100 Subject: [PATCH 09/17] Fix existing tests. --- .../rest-api/Tests/Version3/product-variations.php | 6 +++++- .../legacy/unit-tests/rest-api/Tests/Version3/products.php | 2 +- .../Version3/class-wc-rest-products-controller-tests.php | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/legacy/unit-tests/rest-api/Tests/Version3/product-variations.php b/tests/legacy/unit-tests/rest-api/Tests/Version3/product-variations.php index 98bfda23c6b..b0a3e805094 100644 --- a/tests/legacy/unit-tests/rest-api/Tests/Version3/product-variations.php +++ b/tests/legacy/unit-tests/rest-api/Tests/Version3/product-variations.php @@ -6,6 +6,9 @@ * @since 3.5.0 */ +/** + * Product_Variations_API class. + */ class Product_Variations_API extends WC_REST_Unit_Test_Case { /** @@ -397,7 +400,7 @@ class Product_Variations_API extends WC_REST_Unit_Test_Case { $data = $response->get_data(); $properties = $data['schema']['properties']; - $this->assertEquals( 37, count( $properties ) ); + $this->assertEquals( 38, count( $properties ) ); $this->assertArrayHasKey( 'id', $properties ); $this->assertArrayHasKey( 'date_created', $properties ); $this->assertArrayHasKey( 'date_modified', $properties ); @@ -424,6 +427,7 @@ class Product_Variations_API extends WC_REST_Unit_Test_Case { $this->assertArrayHasKey( 'backorders', $properties ); $this->assertArrayHasKey( 'backorders_allowed', $properties ); $this->assertArrayHasKey( 'backordered', $properties ); + $this->assertArrayHasKey( 'low_stock_amount', $properties ); $this->assertArrayHasKey( 'weight', $properties ); $this->assertArrayHasKey( 'dimensions', $properties ); $this->assertArrayHasKey( 'shipping_class', $properties ); diff --git a/tests/legacy/unit-tests/rest-api/Tests/Version3/products.php b/tests/legacy/unit-tests/rest-api/Tests/Version3/products.php index fa00aa68af9..72fa8055563 100644 --- a/tests/legacy/unit-tests/rest-api/Tests/Version3/products.php +++ b/tests/legacy/unit-tests/rest-api/Tests/Version3/products.php @@ -642,7 +642,7 @@ class WC_Tests_API_Product extends WC_REST_Unit_Test_Case { $response = $this->server->dispatch( $request ); $data = $response->get_data(); $properties = $data['schema']['properties']; - $this->assertEquals( 65, count( $properties ) ); + $this->assertEquals( 66, count( $properties ) ); } /** diff --git a/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller-tests.php b/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller-tests.php index ebc6d0fdfdc..be242cded44 100644 --- a/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller-tests.php +++ b/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller-tests.php @@ -65,6 +65,7 @@ class WC_REST_Products_Controller_Tests extends WC_REST_Unit_Test_Case { 'backorders', 'backorders_allowed', 'backordered', + 'low_stock_amount', 'sold_individually', 'weight', 'dimensions', From 8bfa97d633b73510defe573301b408d96949df36 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Mon, 15 Mar 2021 12:37:39 +0100 Subject: [PATCH 10/17] Use null rather than empty string to signify unset value. --- .../class-wc-rest-products-v2-controller.php | 2 +- .../class-wc-rest-product-variations-controller.php | 13 +++++++++---- .../Version3/class-wc-rest-products-controller.php | 11 ++++++++--- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php b/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php index cc0c71c10e4..2e80879f194 100644 --- a/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php +++ b/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php @@ -767,7 +767,7 @@ class WC_REST_Products_V2_Controller extends WC_REST_CRUD_Controller { $base_data['backordered'] = $product->is_on_backorder(); break; case 'low_stock_amount': - $base_data['low_stock_amount'] = $product->get_low_stock_amount(); + $base_data['low_stock_amount'] = '' === $product->get_low_stock_amount() ? null : $product->get_low_stock_amount(); break; case 'sold_individually': $base_data['sold_individually'] = $product->is_sold_individually(); diff --git a/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php b/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php index 321e2a69729..9d2000b23eb 100644 --- a/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php +++ b/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php @@ -65,7 +65,7 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V 'backorders' => $object->get_backorders(), 'backorders_allowed' => $object->backorders_allowed(), 'backordered' => $object->is_on_backorder(), - 'low_stock_amount' => $object->get_low_stock_amount(), + 'low_stock_amount' => '' === $object->get_low_stock_amount() ? null : $object->get_low_stock_amount(), 'weight' => $object->get_weight(), 'dimensions' => array( 'length' => $object->get_length(), @@ -186,8 +186,13 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V $stock_quantity += wc_stock_amount( $request['inventory_delta'] ); $variation->set_stock_quantity( $stock_quantity ); } - if ( isset( $request['low_stock_amount'] ) ) { - $variation->set_low_stock_amount( wc_stock_amount( $request['low_stock_amount'] ) ); + // isset() returns false for value null, thus we need to check whether the value has been sent by the request. + if ( array_key_exists( 'low_stock_amount', $request->get_params() ) ) { + if ( null === $request['low_stock_amount'] ) { + $variation->set_low_stock_amount( '' ); + } else { + $variation->set_low_stock_amount( wc_stock_amount( $request['low_stock_amount'] ) ); + } } } else { $variation->set_backorders( 'no' ); @@ -604,7 +609,7 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V ), 'low_stock_amount' => array( 'description' => __( 'Low Stock amount for the variation.', 'woocommerce' ), - 'type' => 'integer', + 'type' => array( 'integer', 'null' ), 'context' => array( 'view', 'edit' ), ), 'weight' => array( diff --git a/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php b/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php index 34264b88a4f..00966cedc30 100644 --- a/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php +++ b/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php @@ -553,8 +553,13 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller { } // Low stock amount. - if ( isset( $request['low_stock_amount'] ) ) { - $product->set_low_stock_amount( wc_stock_amount( $request['low_stock_amount'] ) ); + // isset() returns false for value null, thus we need to check whether the value has been sent by the request. + if ( array_key_exists( 'low_stock_amount', $request->get_params() ) ) { + if ( null === $request['low_stock_amount'] ) { + $product->set_low_stock_amount( '' ); + } else { + $product->set_low_stock_amount( wc_stock_amount( $request['low_stock_amount'] ) ); + } } } else { // Don't manage stock. @@ -993,7 +998,7 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller { ), 'low_stock_amount' => array( 'description' => __( 'Low Stock amount for the product.', 'woocommerce' ), - 'type' => 'integer', + 'type' => array( 'integer', 'null' ), 'context' => array( 'view', 'edit' ), ), 'sold_individually' => array( From 2ae5ec59baf79f4bd8512598f80cd1a81c390faf Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Mon, 15 Mar 2021 12:42:10 +0100 Subject: [PATCH 11/17] Improve the description of the low stock threshold tooltip. --- includes/admin/meta-boxes/views/html-product-data-inventory.php | 2 +- includes/admin/meta-boxes/views/html-variation-admin.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/admin/meta-boxes/views/html-product-data-inventory.php b/includes/admin/meta-boxes/views/html-product-data-inventory.php index cb5d406b231..cf8e23a6113 100644 --- a/includes/admin/meta-boxes/views/html-product-data-inventory.php +++ b/includes/admin/meta-boxes/views/html-product-data-inventory.php @@ -78,7 +78,7 @@ if ( ! defined( 'ABSPATH' ) ) { 'placeholder' => get_option( 'woocommerce_notify_low_stock_amount' ), 'label' => __( 'Low stock threshold', 'woocommerce' ), 'desc_tip' => true, - 'description' => __( 'When product stock reaches this amount you will be notified by email', 'woocommerce' ), + 'description' => __( 'When product stock reaches this amount you will be notified by email. The default value can be set in Settings > Products > Inventory.', 'woocommerce' ), 'type' => 'number', 'custom_attributes' => array( 'step' => 'any', diff --git a/includes/admin/meta-boxes/views/html-variation-admin.php b/includes/admin/meta-boxes/views/html-variation-admin.php index 994cf065567..cca50558def 100644 --- a/includes/admin/meta-boxes/views/html-variation-admin.php +++ b/includes/admin/meta-boxes/views/html-variation-admin.php @@ -218,7 +218,7 @@ defined( 'ABSPATH' ) || exit; 'placeholder' => get_option( 'woocommerce_notify_low_stock_amount' ), 'label' => __( 'Low stock threshold', 'woocommerce' ), 'desc_tip' => true, - 'description' => __( 'When variation stock reaches this amount you will be notified by email', 'woocommerce' ), + 'description' => __( 'When variation stock reaches this amount you will be notified by email. The default value can be set in Settings > Products > Inventory.', 'woocommerce' ), 'type' => 'number', 'custom_attributes' => array( 'step' => 'any', From e6cdd74a7970552041d249ff3a46a5bdadf57626 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Mon, 15 Mar 2021 18:31:31 +0100 Subject: [PATCH 12/17] Add parent's default for low stock if notthing is set on individual variation. I.e. use variation low stock amount value, if not available, use parent product's value, if not available, use the store-wide default. --- includes/wc-stock-functions.php | 11 +- .../php/includes/wc-stock-functions-tests.php | 152 ++++++++++++++++++ 2 files changed, 161 insertions(+), 2 deletions(-) diff --git a/includes/wc-stock-functions.php b/includes/wc-stock-functions.php index f0c031fbbe2..390f2358bc1 100644 --- a/includes/wc-stock-functions.php +++ b/includes/wc-stock-functions.php @@ -391,8 +391,9 @@ add_action( 'woocommerce_order_status_on-hold', 'wc_release_stock_for_order', 11 * Return low stock amount to determine if notification needs to be sent * * Since 5.3.0, this function no longer redirects from variation to its parent product. - * Low stock amount can now be attached to the variation itself and if it isn't, - * the default from store-wide setting gets used. + * Low stock amount can now be attached to the variation itself and if it isn't, only + * then we check the parent product, and if it's not there, then we take the default + * from the store-wide setting. * * @param WC_Product $product Product to get data from. * @since 3.5.0 @@ -400,6 +401,12 @@ add_action( 'woocommerce_order_status_on-hold', 'wc_release_stock_for_order', 11 */ function wc_get_low_stock_amount( WC_Product $product ) { $low_stock_amount = $product->get_low_stock_amount(); + + if ( '' === $low_stock_amount && $product->is_type( 'variation' ) ) { + $product = wc_get_product( $product->get_parent_id() ); + $low_stock_amount = $product->get_low_stock_amount(); + } + if ( '' === $low_stock_amount ) { $low_stock_amount = get_option( 'woocommerce_notify_low_stock_amount', 2 ); } diff --git a/tests/php/includes/wc-stock-functions-tests.php b/tests/php/includes/wc-stock-functions-tests.php index d815eeee5d8..1bc00319ef5 100644 --- a/tests/php/includes/wc-stock-functions-tests.php +++ b/tests/php/includes/wc-stock-functions-tests.php @@ -191,4 +191,156 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case { } } } + + /** + * Test wc_get_low_stock_amount with a simple product which has low stock amount set. + */ + public function test_wc_get_low_stock_amount_simple_set() { + $product_low_stock_amount = 5; + $site_wide_low_stock_amount = 3; + + // Set the store-wide default. + update_option( 'woocommerce_notify_low_stock_amount', $site_wide_low_stock_amount ); + + // Simple product, set low stock amount. + $product = WC_Helper_Product::create_simple_product( + true, + array( + 'manage_stock' => true, + 'stock_quantity' => 10, + 'low_stock_amount' => $product_low_stock_amount, + ) + ); + + $this->assertEquals( $product_low_stock_amount, wc_get_low_stock_amount( $product ) ); + } + + /** + * Test wc_get_low_stock_amount with a simple product which doesn't have low stock amount set. + */ + public function test_wc_get_low_stock_amount_simple_unset() { + $site_wide_low_stock_amount = 3; + + // Set the store-wide default. + update_option( 'woocommerce_notify_low_stock_amount', $site_wide_low_stock_amount ); + + // Simple product, don't set low stock amount. + $product = WC_Helper_Product::create_simple_product( + true, + array( + 'manage_stock' => true, + 'stock_quantity' => 10, + ) + ); + + $this->assertEquals( $site_wide_low_stock_amount, wc_get_low_stock_amount( $product ) ); + } + + /** + * Test wc_get_low_stock_amount with a variable product which has low stock amount set on the variation level, + * but not on the parent level. Should use the value from the variation. + */ + public function test_wc_get_low_stock_amount_variation_set_parent_unset() { + $site_wide_low_stock_amount = 3; + $variation_low_stock_amount = 7; + + // Set the store-wide default. + update_option( 'woocommerce_notify_low_stock_amount', $site_wide_low_stock_amount ); + + // Parent low stock amount NOT set. + $variable_product = WC_Helper_Product::create_variation_product(); + $variable_product->set_manage_stock( false ); + + // Set the variation low stock amount. + $variations = $variable_product->get_available_variations( 'objects' ); + $var1 = $variations[0]; + $var1->set_manage_stock( true ); + $var1->set_low_stock_amount( $variation_low_stock_amount ); + $var1->save(); + + $this->assertEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) ); + + // Even after turning on manage stock on the parent, but with no value. + $variable_product->set_manage_stock( true ); + $this->assertEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) ); + + // Ans also after turning the manage stock off again on the parent. + $variable_product->set_manage_stock( true ); + $this->assertEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) ); + } + + /** + * Test wc_get_low_stock_amount with a variable product which has low stock amount set on the variation level, + * and also on the parent level. Should use the value from the variation. + */ + public function test_wc_get_low_stock_amount_variation_set_parent_set() { + $site_wide_low_stock_amount = 3; + $parent_low_stock_amount = 5; + $variation_low_stock_amount = 7; + + // Set the store-wide default. + update_option( 'woocommerce_notify_low_stock_amount', $site_wide_low_stock_amount ); + + // Set the parent low stock amount. + $variable_product = WC_Helper_Product::create_variation_product(); + $variable_product->set_manage_stock( true ); + $variable_product->set_low_stock_amount( $parent_low_stock_amount ); + $variable_product->save(); + + // Set the variation low stock amount. + $variations = $variable_product->get_available_variations( 'objects' ); + $var1 = $variations[0]; + $var1->set_manage_stock( true ); + $var1->set_low_stock_amount( $variation_low_stock_amount ); + $var1->save(); + + $this->assertEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) ); + } + + /** + * Test wc_get_low_stock_amount with a variable product which has low stock amount set on the parent level, + * but NOT on the variation level. Should use the value from the parent. + */ + public function test_wc_get_low_stock_amount_variation_unset_parent_set() { + $site_wide_low_stock_amount = 3; + $parent_low_stock_amount = 5; + + // Set the store-wide default. + update_option( 'woocommerce_notify_low_stock_amount', $site_wide_low_stock_amount ); + + // Set the parent low stock amount. + $variable_product = WC_Helper_Product::create_variation_product(); + $variable_product->set_manage_stock( true ); + $variable_product->set_low_stock_amount( $parent_low_stock_amount ); + $variable_product->save(); + + // Set the variation low stock amount. + $variations = $variable_product->get_available_variations( 'objects' ); + $var1 = $variations[0]; + + $this->assertEquals( $parent_low_stock_amount, wc_get_low_stock_amount( $var1 ) ); + } + + /** + * Test wc_get_low_stock_amount with a variable product which *doesn't have* low stock amount set either on the parent level, + * or on the variation level. Should use the value from the site-wide setting. + */ + public function test_wc_get_low_stock_amount_variation_unset_parent_unset() { + $site_wide_low_stock_amount = 3; + + // Set the store-wide default. + update_option( 'woocommerce_notify_low_stock_amount', $site_wide_low_stock_amount ); + + // Set the parent low stock amount. + $variable_product = WC_Helper_Product::create_variation_product(); + $variable_product->set_manage_stock( false ); + + // Set the variation low stock amount. + $variations = $variable_product->get_available_variations( 'objects' ); + $var1 = $variations[0]; + $var1->set_manage_stock( false ); + + $this->assertEquals( $site_wide_low_stock_amount, wc_get_low_stock_amount( $var1 ) ); + } + } From bb5c56c14890ebd71709e218362fcfbdc26fde6d Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Mon, 15 Mar 2021 18:41:55 +0100 Subject: [PATCH 13/17] Set correct placeholder value for variation--first the parent, then the store-wide default. --- includes/admin/meta-boxes/views/html-variation-admin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/admin/meta-boxes/views/html-variation-admin.php b/includes/admin/meta-boxes/views/html-variation-admin.php index cca50558def..88f058695b9 100644 --- a/includes/admin/meta-boxes/views/html-variation-admin.php +++ b/includes/admin/meta-boxes/views/html-variation-admin.php @@ -215,7 +215,7 @@ defined( 'ABSPATH' ) || exit; 'id' => "variable_low_stock_amount{$loop}", 'name' => "variable_low_stock_amount[{$loop}]", 'value' => $variation_object->get_low_stock_amount( 'edit' ), - 'placeholder' => get_option( 'woocommerce_notify_low_stock_amount' ), + 'placeholder' => $product_object->get_manage_stock() && '' !== $product_object->get_low_stock_amount() ? $product_object->get_low_stock_amount() : get_option( 'woocommerce_notify_low_stock_amount' ), 'label' => __( 'Low stock threshold', 'woocommerce' ), 'desc_tip' => true, 'description' => __( 'When variation stock reaches this amount you will be notified by email. The default value can be set in Settings > Products > Inventory.', 'woocommerce' ), From f33c80186f3b3d16bae0cfc4f5751a32891dfa15 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Tue, 16 Mar 2021 08:51:29 +0100 Subject: [PATCH 14/17] Updated the wording of the tooltip. --- includes/admin/meta-boxes/views/html-variation-admin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/admin/meta-boxes/views/html-variation-admin.php b/includes/admin/meta-boxes/views/html-variation-admin.php index 88f058695b9..08dff38127a 100644 --- a/includes/admin/meta-boxes/views/html-variation-admin.php +++ b/includes/admin/meta-boxes/views/html-variation-admin.php @@ -218,7 +218,7 @@ defined( 'ABSPATH' ) || exit; 'placeholder' => $product_object->get_manage_stock() && '' !== $product_object->get_low_stock_amount() ? $product_object->get_low_stock_amount() : get_option( 'woocommerce_notify_low_stock_amount' ), 'label' => __( 'Low stock threshold', 'woocommerce' ), 'desc_tip' => true, - 'description' => __( 'When variation stock reaches this amount you will be notified by email. The default value can be set in Settings > Products > Inventory.', 'woocommerce' ), + 'description' => __( 'When variation stock reaches this amount you will be notified by email. The fallback is parent product\'s value and the default value can be set in Settings > Products > Inventory.', 'woocommerce' ), 'type' => 'number', 'custom_attributes' => array( 'step' => 'any', From a2e9c055d508905af5acaa62feaf0a3ccaf5b725 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Tue, 16 Mar 2021 12:39:25 +0100 Subject: [PATCH 15/17] Make the inherited values explicit by stating where it comes from. --- .../views/html-product-data-inventory.php | 8 ++++++-- .../meta-boxes/views/html-variation-admin.php | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/includes/admin/meta-boxes/views/html-product-data-inventory.php b/includes/admin/meta-boxes/views/html-product-data-inventory.php index cf8e23a6113..c1086b96c2a 100644 --- a/includes/admin/meta-boxes/views/html-product-data-inventory.php +++ b/includes/admin/meta-boxes/views/html-product-data-inventory.php @@ -75,10 +75,14 @@ if ( ! defined( 'ABSPATH' ) ) { array( 'id' => '_low_stock_amount', 'value' => $product_object->get_low_stock_amount( 'edit' ), - 'placeholder' => get_option( 'woocommerce_notify_low_stock_amount' ), + 'placeholder' => printf( + /* translators: %d: Amount of stock left */ + esc_attr__( 'Store-wide threshold (%d)', 'woocommerce' ), + esc_attr( get_option( 'woocommerce_notify_low_stock_amount' ) ) + ), 'label' => __( 'Low stock threshold', 'woocommerce' ), 'desc_tip' => true, - 'description' => __( 'When product stock reaches this amount you will be notified by email. The default value can be set in Settings > Products > Inventory.', 'woocommerce' ), + 'description' => __( 'When product stock reaches this amount you will be notified by email. It is possible to define different values for each variation individually. The shop default value can be set in Settings > Products > Inventory.', 'woocommerce' ), 'type' => 'number', 'custom_attributes' => array( 'step' => 'any', diff --git a/includes/admin/meta-boxes/views/html-variation-admin.php b/includes/admin/meta-boxes/views/html-variation-admin.php index 08dff38127a..9691f011945 100644 --- a/includes/admin/meta-boxes/views/html-variation-admin.php +++ b/includes/admin/meta-boxes/views/html-variation-admin.php @@ -215,10 +215,20 @@ defined( 'ABSPATH' ) || exit; 'id' => "variable_low_stock_amount{$loop}", 'name' => "variable_low_stock_amount[{$loop}]", 'value' => $variation_object->get_low_stock_amount( 'edit' ), - 'placeholder' => $product_object->get_manage_stock() && '' !== $product_object->get_low_stock_amount() ? $product_object->get_low_stock_amount() : get_option( 'woocommerce_notify_low_stock_amount' ), + 'placeholder' => $product_object->get_manage_stock() && '' !== $product_object->get_low_stock_amount() + ? printf( + /* translators: %d: Amount of stock left */ + esc_attr__( 'Parent product\'s threshold (%d)', 'woocommerce' ), + esc_attr( $product_object->get_low_stock_amount() ) + ) + : printf( + /* translators: %d: Amount of stock left */ + esc_attr__( 'Store-wide threshold (%d)', 'woocommerce' ), + esc_attr( get_option( 'woocommerce_notify_low_stock_amount' ) ) + ), 'label' => __( 'Low stock threshold', 'woocommerce' ), 'desc_tip' => true, - 'description' => __( 'When variation stock reaches this amount you will be notified by email. The fallback is parent product\'s value and the default value can be set in Settings > Products > Inventory.', 'woocommerce' ), + 'description' => __( 'When variation stock reaches this amount you will be notified by email. The default value for all variations can be set in the product Inventory tab. The shop default value can be set in Settings > Products > Inventory.', 'woocommerce' ), 'type' => 'number', 'custom_attributes' => array( 'step' => 'any', From cb6e0d820162dd97ad939fc187c64c9a604cf413 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Tue, 16 Mar 2021 13:01:15 +0100 Subject: [PATCH 16/17] Don't echo the strings, return them for later use. --- .../views/html-product-data-inventory.php | 2 +- .../meta-boxes/views/html-variation-admin.php | 24 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/includes/admin/meta-boxes/views/html-product-data-inventory.php b/includes/admin/meta-boxes/views/html-product-data-inventory.php index c1086b96c2a..4bab3e0bd0e 100644 --- a/includes/admin/meta-boxes/views/html-product-data-inventory.php +++ b/includes/admin/meta-boxes/views/html-product-data-inventory.php @@ -75,7 +75,7 @@ if ( ! defined( 'ABSPATH' ) ) { array( 'id' => '_low_stock_amount', 'value' => $product_object->get_low_stock_amount( 'edit' ), - 'placeholder' => printf( + 'placeholder' => sprintf( /* translators: %d: Amount of stock left */ esc_attr__( 'Store-wide threshold (%d)', 'woocommerce' ), esc_attr( get_option( 'woocommerce_notify_low_stock_amount' ) ) diff --git a/includes/admin/meta-boxes/views/html-variation-admin.php b/includes/admin/meta-boxes/views/html-variation-admin.php index 9691f011945..8570dc9ad3d 100644 --- a/includes/admin/meta-boxes/views/html-variation-admin.php +++ b/includes/admin/meta-boxes/views/html-variation-admin.php @@ -210,22 +210,24 @@ defined( 'ABSPATH' ) || exit; ) ); + $low_stock_placeholder = ( $product_object->get_manage_stock() && '' !== $product_object->get_low_stock_amount() ) + ? sprintf( + /* translators: %d: Amount of stock left */ + esc_attr__( 'Parent product\'s threshold (%d)', 'woocommerce' ), + esc_attr( $product_object->get_low_stock_amount() ) + ) + : sprintf( + /* translators: %d: Amount of stock left */ + esc_attr__( 'Store-wide threshold (%d)', 'woocommerce' ), + esc_attr( get_option( 'woocommerce_notify_low_stock_amount' ) ) + ); + woocommerce_wp_text_input( array( 'id' => "variable_low_stock_amount{$loop}", 'name' => "variable_low_stock_amount[{$loop}]", 'value' => $variation_object->get_low_stock_amount( 'edit' ), - 'placeholder' => $product_object->get_manage_stock() && '' !== $product_object->get_low_stock_amount() - ? printf( - /* translators: %d: Amount of stock left */ - esc_attr__( 'Parent product\'s threshold (%d)', 'woocommerce' ), - esc_attr( $product_object->get_low_stock_amount() ) - ) - : printf( - /* translators: %d: Amount of stock left */ - esc_attr__( 'Store-wide threshold (%d)', 'woocommerce' ), - esc_attr( get_option( 'woocommerce_notify_low_stock_amount' ) ) - ), + 'placeholder' => $low_stock_placeholder, 'label' => __( 'Low stock threshold', 'woocommerce' ), 'desc_tip' => true, 'description' => __( 'When variation stock reaches this amount you will be notified by email. The default value for all variations can be set in the product Inventory tab. The shop default value can be set in Settings > Products > Inventory.', 'woocommerce' ), From 351c07d655896048568c669a063bc9af3061e872 Mon Sep 17 00:00:00 2001 From: Peter Fabian Date: Tue, 16 Mar 2021 13:08:29 +0100 Subject: [PATCH 17/17] Improved tests, added save points, fixed comments. --- tests/php/includes/wc-stock-functions-tests.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/php/includes/wc-stock-functions-tests.php b/tests/php/includes/wc-stock-functions-tests.php index 1bc00319ef5..368eab017cf 100644 --- a/tests/php/includes/wc-stock-functions-tests.php +++ b/tests/php/includes/wc-stock-functions-tests.php @@ -250,6 +250,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case { // Parent low stock amount NOT set. $variable_product = WC_Helper_Product::create_variation_product(); $variable_product->set_manage_stock( false ); + $variable_product->save(); // Set the variation low stock amount. $variations = $variable_product->get_available_variations( 'objects' ); @@ -262,10 +263,12 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case { // Even after turning on manage stock on the parent, but with no value. $variable_product->set_manage_stock( true ); + $variable_product->save(); $this->assertEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) ); // Ans also after turning the manage stock off again on the parent. - $variable_product->set_manage_stock( true ); + $variable_product->set_manage_stock( false ); + $variable_product->save(); $this->assertEquals( $variation_low_stock_amount, wc_get_low_stock_amount( $var1 ) ); } @@ -314,7 +317,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case { $variable_product->set_low_stock_amount( $parent_low_stock_amount ); $variable_product->save(); - // Set the variation low stock amount. + // Don't set the variation low stock amount. $variations = $variable_product->get_available_variations( 'objects' ); $var1 = $variations[0]; @@ -335,7 +338,7 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case { $variable_product = WC_Helper_Product::create_variation_product(); $variable_product->set_manage_stock( false ); - // Set the variation low stock amount. + // Don't set the variation low stock amount. $variations = $variable_product->get_available_variations( 'objects' ); $var1 = $variations[0]; $var1->set_manage_stock( false );