Commit fc331cd415d for woocommerce

commit fc331cd415dde2afe7494a95279073ef03f644b4
Author: Luigi Teschio <gigitux@gmail.com>
Date:   Mon May 11 15:05:03 2026 +0200

    Fix bulk quick edit behavior (#64734)

    Fix bulk quick edit selection

diff --git a/packages/js/experimental-products-app/changelog/fix-quick-edit-bulk-selection b/packages/js/experimental-products-app/changelog/fix-quick-edit-bulk-selection
new file mode 100644
index 00000000000..27ec239d527
--- /dev/null
+++ b/packages/js/experimental-products-app/changelog/fix-quick-edit-bulk-selection
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix quick edit bulk selection and hide unsupported SKU field in bulk edits.
diff --git a/packages/js/experimental-products-app/src/dataviews-actions/actions.test.tsx b/packages/js/experimental-products-app/src/dataviews-actions/actions.test.tsx
index aa69254fc65..d76183c72f0 100644
--- a/packages/js/experimental-products-app/src/dataviews-actions/actions.test.tsx
+++ b/packages/js/experimental-products-app/src/dataviews-actions/actions.test.tsx
@@ -87,6 +87,11 @@ describe( 'product list actions', () => {
 		status: 'draft',
 		name: 'Beanie',
 	} as ProductEntityRecord;
+	const hoodie = {
+		id: 34,
+		status: 'draft',
+		name: 'Hoodie',
+	} as ProductEntityRecord;

 	const deleteEntityRecord = jest.fn();
 	const invalidateResolution = jest.fn();
@@ -136,7 +141,11 @@ describe( 'product list actions', () => {

 		expect( quickEditProductAction ).toBeDefined();

-		getCallbackAction( quickEditProductAction! ).callback( [ product ], {
+		if ( ! quickEditProductAction ) {
+			throw new Error( 'Quick edit action not found.' );
+		}
+
+		getCallbackAction( quickEditProductAction ).callback( [ product ], {
 			onActionPerformed,
 		} );

@@ -155,6 +164,31 @@ describe( 'product list actions', () => {
 		expect( quickEditProductAction?.supportsBulk ).toBe( true );
 	} );

+	it( 'opens quick edit panel with all selected products when triggered as a bulk action', () => {
+		const { result } = renderHook( () => useProductActions() );
+		const quickEditProductAction = result.current.find(
+			( action ) => action.id === 'quick-edit-product'
+		);
+
+		expect( quickEditProductAction ).toBeDefined();
+
+		if ( ! quickEditProductAction ) {
+			throw new Error( 'Quick edit action not found.' );
+		}
+
+		getCallbackAction( quickEditProductAction ).callback(
+			[ product, hoodie ],
+			{
+				onActionPerformed,
+			}
+		);
+
+		expect( navigate ).toHaveBeenCalledWith(
+			'/products?activeView=draft&postId=12%2C34&quickEdit=true'
+		);
+		expect( onActionPerformed ).toHaveBeenCalledWith( [ product, hoodie ] );
+	} );
+
 	it( 'opens product editor when the Edit action is triggered', () => {
 		const { result } = renderHook( () => useProductActions() );
 		const editProductAction = result.current.find(
@@ -163,13 +197,17 @@ describe( 'product list actions', () => {

 		expect( editProductAction ).toBeDefined();

+		if ( ! editProductAction ) {
+			throw new Error( 'Edit action not found.' );
+		}
+
 		const originalLocation = window.location;
 		Object.defineProperty( window, 'location', {
 			writable: true,
 			value: { href: '' },
 		} );

-		getCallbackAction( editProductAction! ).callback( [ product ], {
+		getCallbackAction( editProductAction ).callback( [ product ], {
 			onActionPerformed,
 		} );

diff --git a/packages/js/experimental-products-app/src/dataviews-actions/actions.tsx b/packages/js/experimental-products-app/src/dataviews-actions/actions.tsx
index f8b965e8203..c59975513a6 100644
--- a/packages/js/experimental-products-app/src/dataviews-actions/actions.tsx
+++ b/packages/js/experimental-products-app/src/dataviews-actions/actions.tsx
@@ -31,7 +31,7 @@ type EditActionOptions = {
 function getQuickEditPath(
 	path: string,
 	query: Record< string, string | undefined >,
-	productId: number
+	productIds: number[]
 ) {
 	const nextQuery = Object.entries( query ).reduce(
 		( acc, [ key, value ] ) => {
@@ -46,7 +46,7 @@ function getQuickEditPath(

 	return getProductListNavigationPath( path, {
 		...nextQuery,
-		postId: String( productId ),
+		postId: productIds.join( ',' ),
 		quickEdit: 'true',
 	} );
 }
@@ -132,10 +132,10 @@ export const quickEditAction = ( {
 		return product.status !== 'trash';
 	},
 	callback( items, { onActionPerformed } ) {
-		const product = items[ 0 ];
+		const productIds = items.map( ( product ) => product.id );

-		if ( product ) {
-			navigate( getQuickEditPath( path, query, product.id ) );
+		if ( productIds.length > 0 ) {
+			navigate( getQuickEditPath( path, query, productIds ) );
 		}

 		if ( onActionPerformed ) {
diff --git a/packages/js/experimental-products-app/src/product-edit/utils.test.ts b/packages/js/experimental-products-app/src/product-edit/utils.test.ts
index 18ca85b30d9..aeeef708fbb 100644
--- a/packages/js/experimental-products-app/src/product-edit/utils.test.ts
+++ b/packages/js/experimental-products-app/src/product-edit/utils.test.ts
@@ -270,6 +270,9 @@ describe( 'product edit utils', () => {
 			'shipping_class',
 			'tax_status',
 		];
+		const bulkUniversalFieldIds = universalFieldIds.filter(
+			( fieldId ) => fieldId !== 'sku'
+		);

 		it( 'shows pricing, shipping, and linked product fields for simple physical products', () => {
 			const fieldIds = getVisibleFieldIds( [
@@ -432,6 +435,7 @@ describe( 'product edit utils', () => {
 			] );

 			expectFieldsHidden( fieldIds, priceFieldIds );
+			expectFieldsHidden( fieldIds, [ 'sku' ] );
 			expect( fieldIds ).toEqual(
 				expect.arrayContaining( [
 					'name',
@@ -442,11 +446,33 @@ describe( 'product edit utils', () => {
 					'featured',
 					'upsell_ids',
 					'cross_sell_ids',
-					...universalFieldIds,
+					...bulkUniversalFieldIds,
 				] )
 			);
 		} );

+		it( 'shows price but not SKU when bulk editing simple products', () => {
+			const fieldIds = getVisibleFieldIds( [
+				buildProduct( {
+					id: 1,
+					type: 'simple',
+					regular_price: '12',
+					price: '12',
+				} ),
+				buildProduct( {
+					id: 2,
+					type: 'simple',
+					regular_price: '15',
+					price: '15',
+				} ),
+			] );
+
+			expect( fieldIds ).toEqual(
+				expect.arrayContaining( [ 'price', 'regular_price' ] )
+			);
+			expectFieldsHidden( fieldIds, [ 'sku' ] );
+		} );
+
 		it( 'shows sellable instance fields for variations', () => {
 			const fieldIds = getVisibleFieldIds( [
 				buildProduct( {
@@ -496,12 +522,13 @@ describe( 'product edit utils', () => {
 			expect( fieldIds ).toEqual(
 				expect.arrayContaining( [
 					...priceFieldIds,
-					...universalFieldIds,
+					...bulkUniversalFieldIds,
 				] )
 			);
 			expectFieldsHidden( fieldIds, [
 				...parentOwnedFieldIds,
 				'downloadable',
+				'sku',
 			] );
 		} );

@@ -524,12 +551,13 @@ describe( 'product edit utils', () => {
 			] );

 			expect( fieldIds ).toEqual(
-				expect.arrayContaining( universalFieldIds )
+				expect.arrayContaining( bulkUniversalFieldIds )
 			);
 			expectFieldsHidden( fieldIds, [
 				...parentOwnedFieldIds,
 				...priceFieldIds,
 				'downloadable',
+				'sku',
 			] );
 		} );

@@ -560,12 +588,13 @@ describe( 'product edit utils', () => {
 			] );

 			expect( fieldIds ).toEqual(
-				expect.arrayContaining( universalFieldIds )
+				expect.arrayContaining( bulkUniversalFieldIds )
 			);
 			expectFieldsHidden( fieldIds, [
 				...parentOwnedFieldIds,
 				...priceFieldIds,
 				'downloadable',
+				'sku',
 			] );
 		} );

diff --git a/packages/js/experimental-products-app/src/product-edit/utils.ts b/packages/js/experimental-products-app/src/product-edit/utils.ts
index 0a9c0656c27..badf7158313 100644
--- a/packages/js/experimental-products-app/src/product-edit/utils.ts
+++ b/packages/js/experimental-products-app/src/product-edit/utils.ts
@@ -195,6 +195,9 @@ const SELLABLE_PRODUCT_EDIT_FIELD_ID_SET = new Set< ProductEditFieldId >( [
 	'date_on_sale_to',
 ] );

+const BULK_UNSUPPORTED_PRODUCT_EDIT_FIELD_ID_SET =
+	new Set< ProductEditFieldId >( [ 'sku' ] );
+
 function normalizeValue( value: unknown ) {
 	if ( value === undefined ) {
 		return '__undefined__';
@@ -435,12 +438,22 @@ export function getVisibleProductEditFields(
 ) {
 	const compatibleFieldIds =
 		getCommonProductTypeCompatibleFieldIds( products );
+	const isBulkEdit = products.length > 1;

 	return fields.reduce< ProductField[] >( ( visibleFields, field ) => {
 		if ( ! compatibleFieldIds.has( field.id ) ) {
 			return visibleFields;
 		}

+		if (
+			isBulkEdit &&
+			BULK_UNSUPPORTED_PRODUCT_EDIT_FIELD_ID_SET.has(
+				field.id as ProductEditFieldId
+			)
+		) {
+			return visibleFields;
+		}
+
 		if ( ! isFieldVisibleForProductRelationships( field.id, products ) ) {
 			return visibleFields;
 		}