Commit bad20d71e0d for woocommerce
commit bad20d71e0db858f6bcd4fe4f9ca5f513bbe5fff
Author: Tung Du <dinhtungdu@gmail.com>
Date: Thu Sep 10 10:25:00 2026 +0700
Add variation bulk action to set sale prices from regular prices (#68520)
* Add variation sale price bulk action
* Add changelog entry for variation sale prices
* Validate variation sale price reductions
* Simplify variation sale price calculation
diff --git a/plugins/woocommerce/changelog/wc-36802-assigned-issue b/plugins/woocommerce/changelog/wc-36802-assigned-issue
new file mode 100644
index 00000000000..d55ec79e8f7
--- /dev/null
+++ b/plugins/woocommerce/changelog/wc-36802-assigned-issue
@@ -0,0 +1,4 @@
+Significance: minor
+Type: enhancement
+
+Add a variation bulk action to set sale prices from regular prices.
diff --git a/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product-variation.js b/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product-variation.js
index d5afa834b81..c738b4625b0 100644
--- a/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product-variation.js
+++ b/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product-variation.js
@@ -1277,6 +1277,7 @@ jQuery( function ( $ ) {
break;
case 'variable_regular_price_increase':
case 'variable_regular_price_decrease':
+ case 'variable_sale_price_from_regular_price':
case 'variable_sale_price_increase':
case 'variable_sale_price_decrease':
let promptMessage =
diff --git a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-variations.php b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-variations.php
index 3051dc95ae3..cf199fe1381 100644
--- a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-variations.php
+++ b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-variations.php
@@ -90,6 +90,7 @@ $arrow_img_url = WC_ADMIN_IMAGES_FOLDER_URL . '/product_data/no-variati
<option value="variable_regular_price_increase"><?php esc_html_e( 'Increase regular prices (fixed amount or percentage)', 'woocommerce' ); ?></option>
<option value="variable_regular_price_decrease"><?php esc_html_e( 'Decrease regular prices (fixed amount or percentage)', 'woocommerce' ); ?></option>
<option value="variable_sale_price"><?php esc_html_e( 'Set sale prices', 'woocommerce' ); ?></option>
+ <option value="variable_sale_price_from_regular_price"><?php esc_html_e( 'Set sale prices to regular prices decreased by (fixed amount or percentage)', 'woocommerce' ); ?></option>
<option value="variable_sale_price_increase"><?php esc_html_e( 'Increase sale prices (fixed amount or percentage)', 'woocommerce' ); ?></option>
<option value="variable_sale_price_decrease"><?php esc_html_e( 'Decrease sale prices (fixed amount or percentage)', 'woocommerce' ); ?></option>
<option value="variable_sale_schedule"><?php esc_html_e( 'Set scheduled sale dates', 'woocommerce' ); ?></option>
diff --git a/plugins/woocommerce/includes/class-wc-ajax.php b/plugins/woocommerce/includes/class-wc-ajax.php
index eaddf7e50a7..b8dd43a7091 100644
--- a/plugins/woocommerce/includes/class-wc-ajax.php
+++ b/plugins/woocommerce/includes/class-wc-ajax.php
@@ -3220,6 +3220,49 @@ class WC_AJAX {
self::variation_bulk_set( $variations, 'sale_price', $data['value'] );
}
+ /**
+ * Bulk action - Set Sale Prices from Regular Prices.
+ *
+ * @param array $variations List of variations.
+ * @param array $data Data to set.
+ *
+ * @used-by bulk_edit_variations
+ *
+ * @return void
+ */
+ private static function variation_bulk_action_variable_sale_price_from_regular_price( $variations, $data ) {
+ $value = $data['value'] ?? null;
+ if ( ! is_scalar( $value ) ) {
+ return;
+ }
+
+ $value = (string) $value;
+ $is_percentage = '%' === substr( $value, -1 );
+ $adjustment = $is_percentage ? substr( $value, 0, -1 ) : $value;
+ if ( ! is_numeric( $adjustment ) || 0 > (float) $adjustment ) {
+ return;
+ }
+ $adjustment = (float) $adjustment;
+
+ foreach ( $variations as $variation_id ) {
+ $variation = wc_get_product( $variation_id );
+ if ( ! $variation instanceof WC_Product_Variation ) {
+ continue;
+ }
+
+ $regular_price = $variation->get_regular_price( 'edit' );
+
+ if ( '' === $regular_price || null === $regular_price ) {
+ continue;
+ }
+
+ $reduction = $is_percentage ? ( (float) $regular_price / 100 ) * $adjustment : $adjustment;
+
+ $variation->set_sale_price( (string) NumberUtil::round( max( 0, (float) $regular_price - $reduction ), wc_get_price_decimals() ) );
+ $variation->save();
+ }
+ }
+
/**
* Bulk action - Set Stock Status as In Stock.
*
@@ -3609,6 +3652,7 @@ class WC_AJAX {
*
* @uses WC_AJAX::variation_bulk_set()
* @uses WC_AJAX::variation_bulk_adjust_price()
+ * @uses WC_AJAX::variation_bulk_action_variable_sale_price_from_regular_price()
* @uses WC_AJAX::variation_bulk_action_variable_sale_price_decrease()
* @uses WC_AJAX::variation_bulk_action_variable_sale_price_increase()
* @uses WC_AJAX::variation_bulk_action_variable_regular_price_decrease()
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php b/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php
index 24cc3dbafb9..8e01bf97b0d 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php
@@ -1728,6 +1728,38 @@ class WC_AJAX_Test extends \WP_Ajax_UnitTestCase {
$variation->delete( true );
}
+ /**
+ * @testdox Should set variation sale prices from their regular prices.
+ * @testWith ["100", "10", "", "90"]
+ * ["100", "10%", "", "90"]
+ * ["100.55", "10.25", "", "90.3"]
+ * ["100.55", "10.5%", "", "89.99"]
+ * ["45", "23.5%", "", "34.43"]
+ * ["5", "10", "", "0"]
+ * ["", "10%", "", ""]
+ * ["100", "invalid", "25", "25"]
+ * ["100", "-10", "25", "25"]
+ *
+ * @param string $regular_price Regular price.
+ * @param string $adjustment Price adjustment.
+ * @param string $sale_price Existing sale price.
+ * @param string $expected_sale_price Expected sale price.
+ */
+ public function test_bulk_sale_price_from_regular_price( string $regular_price, string $adjustment, string $sale_price, string $expected_sale_price ): void {
+ $variation = new WC_Product_Variation();
+ $variation->set_regular_price( $regular_price );
+ $variation->set_sale_price( $sale_price );
+ $variation->save();
+
+ $method = new ReflectionMethod( WC_AJAX::class, 'variation_bulk_action_variable_sale_price_from_regular_price' );
+ $method->setAccessible( true );
+ $method->invokeArgs( null, array( array( $variation->get_id() ), array( 'value' => $adjustment ) ) );
+
+ $variation = wc_get_product( $variation->get_id() );
+
+ $this->assertSame( $expected_sale_price, $variation->get_sale_price( 'edit' ), 'The sale price should be calculated from the regular price.' );
+ }
+
/**
* @testdox Adding a custom field renders a Delete button with a valid delete nonce.
*/