Un client vendant de l’outillage professionnel expédie ses commandes depuis trois entrepôts distincts, chacun disposant de son propre système de gestion physique de stock, sans lien entre eux au départ. WooCommerce, de son côté, ne connaît qu’un seul niveau de stock par produit. Le défi n’était donc pas de choisir une extension de gestion d’entrepôt du marché — le client avait déjà écarté cette option pour des raisons de coût de licence par utilisateur — mais de construire un connecteur maison capable de fusionner ces trois sources en un stock WooCommerce fiable.
Ce retour d’expérience détaille l’architecture retenue, les pièges rencontrés en cours de route, et les raisons de certains choix qui, sur le papier, semblaient plus complexes que nécessaire.
Le contexte : trois sources, une seule vérité
Chaque entrepôt expose son stock via un fichier CSV déposé toutes les dix minutes sur un espace FTP, généré par le logiciel de gestion propre à chaque site. Le connecteur WooCommerce ne pouvait donc pas se contenter d’écraser un stock par un autre : il devait additionner les trois quantités disponibles pour obtenir le stock global affiché aux clients, tout en gardant une trace de la répartition par entrepôt pour la logique d’expédition ultérieure.
Stocker la répartition par entrepôt en meta produit

Plutôt que de modifier directement le stock WooCommerce à chaque import, le connecteur stocke la quantité de chaque entrepôt dans une méta-donnée dédiée, puis recalcule le stock global à partir de ces trois valeurs :
function connecteur_mettre_a_jour_stock_entrepot( $sku, $entrepot, $quantite ) {
$product_id = wc_get_product_id_by_sku( $sku );
if ( ! $product_id ) {
return;
}
$product = wc_get_product( $product_id );
$product->update_meta_data( '_stock_entrepot_' . $entrepot, (int) $quantite );
$total = 0;
foreach ( array( 'nord', 'sud', 'ouest' ) as $code_entrepot ) {
$total += (int) $product->get_meta( '_stock_entrepot_' . $code_entrepot );
}
$product->set_stock_quantity( $total );
$product->set_stock_status( $total > 0 ? 'instock' : 'outofstock' );
$product->save();
}
Planifier l’import sans bloquer le site
Le traitement d’un fichier CSV de plusieurs milliers de lignes ne peut pas se faire dans une requête HTTP classique sans risquer un dépassement de temps d’exécution. Le connecteur s’appuie sur Action Scheduler, déjà présent via WooCommerce, pour planifier le traitement par lots de 200 lignes :
as_schedule_recurring_action(
strtotime( '+10 minutes' ),
10 * MINUTE_IN_SECONDS,
'connecteur_importer_stock_entrepots',
array(),
'connecteur-stock'
);
add_action( 'connecteur_importer_stock_entrepots', function() {
foreach ( array( 'nord', 'sud', 'ouest' ) as $entrepot ) {
connecteur_planifier_lots_csv( $entrepot );
}
} );
Chaque lot est ensuite traité par une action distincte, ce qui répartit la charge dans le temps plutôt que de tout traiter d’un bloc, et permet de reprendre le traitement à un lot précis en cas d’échec sans tout relancer depuis le début.
Le piège des écritures concurrentes
Le premier incident sérieux est survenu après un mois de production : deux lots de deux entrepôts différents mettaient à jour le même produit presque simultanément, et l’un des deux écrasait parfois le résultat de l’autre à cause d’une lecture du stock global avant que la première écriture ne soit committée en base. Le correctif a consisté à ajouter un verrou applicatif simple via un transient, le temps du recalcul :
function connecteur_avec_verrou_produit( $product_id, callable $callback ) {
$cle_verrou = 'connecteur_verrou_' . $product_id;
$tentative = 0;
while ( get_transient( $cle_verrou ) && $tentative < 20 ) {
usleep( 100000 );
$tentative++;
}
set_transient( $cle_verrou, 1, 10 );
$callback();
delete_transient( $cle_verrou );
}
Ce verrou n'a rien d'élégant, mais il a résolu l'incident en production sans nécessiter de file de messages dédiée, disproportionnée pour le volume réel du client.
Ce qui a bien fonctionné dès le départ
- Séparer strictement la donnée « stock par entrepôt » de la donnée « stock global WooCommerce », en ne considérant jamais la seconde comme une source de vérité éditable manuellement.
- Journaliser chaque import dans une table personnalisée dédiée, avec horodatage et nombre de lignes traitées, pour diagnostiquer rapidement un import silencieusement interrompu.
- Alerter par e-mail dès qu'un des trois entrepôts n'a pas fourni de fichier depuis plus de deux heures, plutôt que de laisser le stock se figer sans avertissement.
Sur ce projet, la vraie difficulté n'a jamais été le calcul du stock global lui-même, mais la robustesse face aux fichiers mal formés ou en retard : un connecteur de synchronisation se juge d'abord à sa capacité à échouer proprement, pas à sa capacité à fonctionner quand tout se passe bien.
Bilan après plusieurs mois d'exploitation
Depuis sa mise en place, le connecteur tourne toutes les dix minutes sans incident de sur-vente notable, hormis deux épisodes liés à des fichiers CSV corrompus côté entrepôt, détectés grâce au système d'alerte plutôt que remontés par un client mécontent. La logique de verrou, initialement perçue comme une complexité excessive par le client, s'est révélée être exactement ce qui a évité une régression au moment où le volume de commandes a doublé lors d'une opération commerciale.