Synchroniser le stock entre plusieurs produits WooCommerce

Vous est-il déjà arrivé, sur votre boutique WooCommerce, de devoir créer deux fiches produits différentes, mais pour un produit identique ? 

En général le cas se présente lorsqu’on souhaite adapter la présentation d’un produit afin de le mettre en valeur pour des types de clients différents  :

  • si vous vendez à des particuliers et à des professionnels, mais que vous souhaitez leur servir des fiches produits adaptées à leurs profil ;
  • si vous souhaitez vendre un même produit, mais avec des conditions tarifaires différentes selon le client ciblé.

… et le problème qui se pose est :

Comment lier le stock de ses produits ?

Si un client achète un produit via l’une des fiches, comment répercuter le changement sur son produit jumeau ?

Comment synchroniser le stock

Je vais vous expliquer l’une des méthodes qui existe, celle qui a ma préférence. Il y en a d’autres qui utilisent des plugins notamment, mais celle que je vais vous montrer est très stable et peut être mise en place avec juste quelques fonctions.

Dans un premier temps nous allons ajouter une meta box pour lier les articles « similaires ». Ensuite on va greffer une petite fonction sur le hook de la mise à jour du stock produit : si cet article est lié à d’autres, alors on répercutera la quantité de stock sur ces derniers.

Simple non ? On y va !

Lier les produits entre eux

Nous avons besoin d’une boite d’info qui nous permettra, sur la fiche d’édition d’un produit woo, de lui associer des produits jumeaux. J’avais fait il y a longtemps un tutoriel sur les metabox bi-directionnelle, mais j’ai depuis mis ma technique à jour. Je vous la présente maintenant.

Cette partie est peut-être un petit peu complexe, donc si vous avez une licence d’Advanced Custom Fields, vous pouvez installer ACF Extended et utiliser ce code pour créer un champ relationnel tout prêt. Rendez-vous ensuite directement à la section suivante.

Sinon : nous voici dans le gras du code…

Le champ relationnel bi-directionnel

Dans le fichier functions.php de votre thème, ou bien dans un plugin custom, copier ces lignes PHP :

add_action( 'add_meta_boxes_product', 'register_sync_metabox' );
function register_sync_metabox() {
	add_meta_box( 'sync_metabox', 'Synchronisation du stock', 'sync_metabox', 'product', 'side', 'default');
}

add_action( 'admin_enqueue_scripts', 'my_admin_scripts_method' );
function my_admin_scripts_method() {
	wp_register_script( 'sync-script', plugins_url( 'script.js', __FILE__ ), 'selectwoo' );
}

function sync_metabox( $post ) {
	wp_nonce_field( 'update-synced_p_' . $post->ID, '_wpnonce_update_synced_p' );
	wp_enqueue_script( 'sync-script');
	?>
	<p>
		<label>Sélectionnez les produits avec lesquels vous souhaitez syncroniser le stock :</label><br>
		<input type="hidden" name="synchro_stock_with" value="" />
		<select name="synchro_stock_with[]" class="sync-stock-select" style="width:100%" multiple="multiple">
		<?php
	
		$products_synced = get_post_meta( $post->ID,'synchro_stock_with', true );
		if ( ! empty( $products_synced ) ) {
			foreach ( $products_synced as $id ) {
				$titre_produit = get_the_title( $id ) . " (id:{$id})";
				printf( '<option value="%1$s" selected>%2$s</option>', $id, $titre_produit );
			}
		}
		?>
		</select>
	</p>
	<?php
}
  • ligne 3 : on enregistre une nouvelle metabox qui va nous permettre d’ajouter un champ relationnel dans les réglages du produit.
  • lignes 8 et 15 : pour un champ relationnel, le plus simple est d’utiliser le script selectwoo pour aller chercher avec la WP API les produits que l’on souhaite associer à l’article courant. On va donc utiliser un fichier javascript pour gérer ce point (nous le verrons ensuite)
  • lignes 19 et 20 : on créer notre selectbox qui va enregistrer les articles associés dans une metadonnée synchro_stock_with.
  • ligne 24 : ce code sert à remettre dans la selectbox les produits liés que l’on a sauvegardé.

Si vous allez voir une page produit, pour verrez une metabox vide. C’est logique car il nous manque le javascript nous permettant d’aller sélectionner des produits woocommerce.

Le script selectWoo et l’API Rest

On créer donc un fichier javascript (tel qu’on l’a appelé ligne 10 du pavé de code précédent) et on y colle ce qui suit :

jQuery( document ).ready(function($){
	$sync = $('.sync-stock-select');
	$sync.select2({
		minimumInputLength:1,
		placeholder: 'Chercher un produit…',
		ajax: {
		  url: '/wp-json/wp/v2/product',
		  dataType: 'json',
		  method:'GET',
		  delay: 250,
		  data: function (params) {
			var query = {
			  search: params.term,
			}
			return query;
		  },
		  processResults: function (data) {
			var res = data.map(function(r) {
				return {
					id:r.id,
					text: `${r.title.rendered} (id:${r.id})`
				};
			});
			return {
			  results: res
			};
		  }
		}
	  });
});

SelectWoo est un fork de select2, davantage accessible, et embarqué directement dans WooCoomerce. Il s’utilise exactement comme Select2.

  • ligne 7 : selon le texte saisi dans notre champ de recherche, on va aller chercher les produits à associer dans la WP API, sur le endpoint product
  • ligne19 : dans la méthode processResults, on formate les données renvoyées par l’API afin de récupérer l’ID que à enregistrer

À ce stade vous pouvez tester que le champ se rempli correctement. Par contre notre boîte n’est pas encore fonctionnelle car nous ne sauvegardons par encore les liaisons produits.

La sauvegarde des meta qui lient les produits entre eux

La petite particularité du code sauvegarde ici, c’est que l’on n’enregistre pas la metadonnée synchro_stock_with uniquement sur ce produit, mais sur l’ensemble des produits associés. Sous le code PHP précédent, on continue avec celui-ci :

add_action('save_post','sauvegarde_metabox');
function sauvegarde_metabox( $post_id ){
	if ( ! wp_doing_ajax() && isset($_POST['synchro_stock_with'] ) ) {
    		check_admin_referer( 'update-synced_p_' . $post_id, '_wpnonce_update_synced_p' );

		$meta_key = 'synchro_stock_with';

		$previously_synced = (array) get_post_meta( $post_id, $meta_key, true );
		$freshly_synced = empty( $_POST['synchro_stock_with'] ) ? array() : array_map( 'intval', (array) $_POST['synchro_stock_with'] );

		update_post_meta( $post_id, $meta_key, $freshly_synced );

		foreach ( $freshly_synced as $id ) {
			$v = $freshly_synced;
			$v[] = $post_id;
			unset( $v[ array_search( $id, $v ) ] );
			update_post_meta( $id, $meta_key, $v );
		}

		$to_del = array_diff( $previously_synced, $freshly_synced );
		$erase_these = $to_del == $previously_synced;
		foreach ( $to_del as $id ) {
			if ( $erase_these ) {
				$s = (array) get_post_meta( $id, $meta_key, true );
				if ( false !== ( $key = array_search( intval( $post_id ), $s ) ) ) {
					unset( $s[ $key ] );
					update_post_meta( $id, $meta_key, $s );
				}
			} else {
				update_post_meta( $id, $meta_key, array() );
			}
		}
	}
}
  • lignes 43 et 44 : avant de sauvegarder quoi que ce soit, on va isoler les produits qui étaient associés à celui-ci, ainsi que ceux à ajouter (cette mécanique est importante car on va devoir dans certains cas dissocier le stock de certains produits)
  • ligne 46 : on sauvegarde la meta pour ce produit
  • ligne 48 : on boucle sur les nouveaux à associés pour y sauvegarder la meta synchro_stock_with, en prenant soin d’y ajouter l’ID du produit courant et de retirer celui de l’item concerné (il ne faudrait pas l’associer avec lui-même)
  • lignes 55 et 56 : dans le cas où l’on serait en train de vouloir briser une synchronisation de stock en place, il faut que l’on détermine :
    1. Est-ce qu’on est en train de retirer le produit d’une synchro stock entre plusieurs autres articles (qui resteront liés entre eux) ?
    2. Est-ce qu’on est en train d’exclure un autre produit d’une synchro stock que l’on souhaiterai toujours en place ?
  • ligne 60 : dans le cas n°1 on doit retirer notre ID d’ærticle de toutes les autres metadonnées synchro_stock_with concernées
  • ligne 65 : dans le cas n°2 on vide simplement la meta du produit à isoler.

Et voilà !! Notre champ relationnel bi-directionnel sur-mesure est opérationnel, et nous pouvons même l’utiliser dans de nombreuses autres situations. Gardez-le sous le coude pour vos projets…

Il est maintenant possible d’éditer un produit, et de dire que son stock doit être synchronisé avec tel ou tel autre marchandise. Essayez et vous verrez que les metadonnées se mettent à jour simultanément sur l’ensemble des articles liés.

Il ne nous reste plus qu’à configurer les mises à jour de stock.

Garder un stock synchronisé

À chaque fois que la quantité en réserve d’un produit varie (en cas de commande ou réapprovisionnement), Woocommerce va déclencher une action nommée woocommerce_product_set_stock. Nous allons nous greffer dessus pour savoir si notre article est sujet à une synchronisation du stock. Et si tel est le cas on reportera programmatiquement la nouvelle quantité disponible sur les autres produits.

Ajoutons ce code à notre fichier PHP :

add_action( 'woocommerce_product_set_stock', 'w_sync_stock' );
function w_sync_stock( $product ) {
	remove_action( 'woocommerce_product_set_stock', 'w_sync_stock' );
	$synced_products = get_post_meta( $product->get_id(), 'synchro_stock_with', true );
	foreach ( $synced_products as $synced_product ) {
		$other = wc_get_product( $synced_product );
		$other->set_stock_quantity( $product->get_stock_quantity() );
		$other->save();
	}
	add_action( 'woocommerce_product_set_stock', 'w_sync_stock' );
}
  • ligne 3 : je me greffe sur l’action set_stock
  • lignes 6 et 7 : je récupère les produits concernés…
  • ligne 9 : … et pour chacun d’entre eux je met à jour la valeur du stock

Vous avez sans doute remarqué qu’à l’ouverture et la fermeture de la fonction, je supprime temporairement mon hook ? Cela est nécessaire, car notre fonction met à jour le stock d’autres produits, et réexécute l’action woocommerce_product_set_stock, notre script pourrait alors rentrer dans une boucle infinie de mise à jour.

Synchroniser le stock des produits variables

Il reste le cas un peu particulier des produits variables. En effet on a ajouté la meta synchro_stock_with sur le produit principal, mais pas sur les variations, il va donc falloir se greffer sur woocommerce_variation_set_stock, mais cette fois si on ira chercher la meta sur le produit « parent » de la variation.

add_action('woocommerce_variation_set_stock', 'w_sync_variation_stock' );
function w_sync_variation_stock( $variation ) {
	remove_action( 'woocommerce_variation_set_stock', 'w_sync_variation_stock' );
	$parent = $variation->get_parent_id();
	$synced_products = get_post_meta( $parent, 'synchro_stock_with', true );
	foreach ( $synced_products as $synced_product ) {
		$atts = $variation->get_variation_attributes();
		$parent = wc_get_product( $synced_product );
		$synced_var = $parent->get_available_variations();
		$same = wp_list_filter( $synced_var, array( 'attributes' => $atts ) );
		if ( ! empty( $same ) ) {
			$target = reset( $same );
			$target = $target['variation_id'];
			$other = wc_get_product( $target );
			$other->set_stock_quantity( $variation->get_stock_quantity() );
			$other->save();
		}
	}
	add_action( 'woocommerce_variation_set_stock', 'w_sync_variation_stock' );
}
  • ligne 15 : on se hook sur l’action set_stock des variations
  • ligne 19 : on récupère les produits parents synchronisés
  • ligne 21 à 24 : on retrouve les variations équivalentes chez les produits jumeaux. Il est impératif d’avoir les mêmes attributs de variations pour pouvoir synchroniser le stock !
  • ligne 29 : pour chaque variation jumelle trouvée on met à jour la quantité de produits en stock.

Pour conclure

Bravo ! Vous avez réalisé une synchronisation de stock entre produits identiques, et cela sans utiliser d’extensions usines à gaz 🙂

Me concernant j’ai utilisé cette méthode sur 3 boutiques e-commerce que j’ai mise en place pour mes clients.
Cela faisait longtemps que je n’avais pas rédigé de tutoriel, mais je me suis dit que vous seriez peut-être intéressés par des snippets de code WooCommerce ? J’ai développé beaucoup de sites de vente en ligne ces dernières années et j’ai encore plein d’astuces de ce genre en réserve…

Alors à bientôt pour de nouveaux tutos !

Soyez le premier à commenter !

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.

Publié le 01 février 2022
par Willy Bahuaud
Catégorie Développement WordPress