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.
 	 */