silver metal railings

Anatomie d’un plugin WooCommerce : Création d’un tableau de bord statistique

Récemment, un client m’a demandé de développer un plugin WooCommerce pour afficher l’historique des ventes et le stock actuel dans un seul tableau sur 13 mois (mois en cours + 12 mois précédents).

Son objectif ? Pouvoir ajuster ses commandes fournisseurs en se basant sur des données réelles de vente plutôt que sur du feeling.

WooCommerce propose des stats de base, mais rien d’aussi précis et pratique. J’ai donc créé un plugin WooCommerce spécialisé dans l’historique des ventes et la gestion de stock qui répond exactement à ce besoin.

Dans cet article, je vais vous expliquer comment j’ai construit cette solution, étape par étape. On verra les choix techniques, les pièges à éviter et je partagerai le code complet à la fin.

🔧 Les choix techniques pour un plugin WooCommerce performant

Pour développer ce plugin, plusieurs décisions techniques ont été prises en pensant performance et maintenabilité.

1. Une table dédiée pour les statistiques

/**
 * Création de la table personnalisée lors de l'activation du plugin
 */
function create_monthly_sales_table() {
	global $wpdb;
	$table_name = $wpdb->prefix . 'wc_monthly_sales_history';

	$charset_collate = $wpdb->get_charset_collate();

	$sql = "CREATE TABLE IF NOT EXISTS $table_name (
        id bigint(20) NOT NULL AUTO_INCREMENT,
        product_id bigint(20) NOT NULL,
        month_key varchar(7) NOT NULL,  -- Format: YYYY-MM
        quantity int(11) NOT NULL DEFAULT 0,
        created_at datetime DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY  (id),
        UNIQUE KEY product_month (product_id, month_key),
        KEY month_key (month_key)
    ) $charset_collate;";

	require_once ABSPATH . 'wp-admin/includes/upgrade.php';
	dbDelta( $sql );
}
register_activation_hook( __FILE__, 'create_monthly_sales_table' );

Pourquoi ?

  • Requêtes plus rapides grâce aux index
  • Données mieux structurées
  • Nettoyage facilité
  • Pas de surcharge de la table _postmeta

2. Gestion des sauvegardes

Pour éviter de surcharger le serveur, j’utilise un hook WordPress quotidien qui vérifie et sauvegarde les données mensuelles :

  • Sauvegarde uniquement en début de mois
  • Calculs effectués une seule fois
wp_schedule_event( time(), 'daily', 'wc_save_monthly_stats' );

3. Intégration dans WooCommerce

L’interface s’intègre naturellement dans le menu WooCommerce :

  • Cohérence avec l’existant
  • Familiarité pour l’utilisateur
  • Style adapté à l’admin WordPress (avec un petit peu de folie)

4. Export CSV

Ajout d’une fonction d’export :

  • Format universel compatible tableur
  • Toutes les données en un clic
  • Le petit plus (pouvoir filtrer par catégorie et exporter les produits de la catégorie)

⚠️ Pièges à éviter lors du développement d’un plugin de gestion de stock

Quand on développe ce genre de plugin, certains points critiques peuvent vite devenir problématiques. Voici les principaux pièges à éviter :

Performance sur le mois en cours

Le calcul des ventes du mois en cours peut être très gourmand en ressources, surtout avec beaucoup de commandes.

// Cache des ventes du mois en cours
$current_month_key   = date( 'Y-m' );
$transient_key       = 'wc_current_month_stats_' . $current_month_key;
$current_month_stats = get_transient( $transient_key );

if ( false === $current_month_stats ) {
  $current_month_stats = $this->calculate_month_stats(
    new DateTime( 'first day of this month' ),
    new DateTime()
  );
  // Cache pour 1 heure
  set_transient( $transient_key, $current_month_stats, HOUR_IN_SECONDS );
}

Historique et données rétroactives

Les données historiques doivent pouvoir être recalculées en cas de besoin. J’ai donc ajouté :

  • Un bouton de réinitialisation dans l’interface
  • Une fonction de calcul rétroactif
  • Un message de confirmation pour éviter les clics accidentels

Gestion des différents types de produits

WooCommerce propose plusieurs façons de gérer les stocks. Pour ce plugin, je me suis concentré sur les deux plus courants :

  • Les produits simples : un stock unique
  • Les produits variables : un stock par variation

Autres points de vigilance

  • Statuts des commandes : quels statuts prendre en compte pour les stats ?
  • Performance sur gros catalogues : pagination ? lazy loading ? traitement par lots ?
  • Nettoyage des données : que faire des stats de produits supprimés ?

La clé est d’anticiper ces situations et de prévoir des solutions adaptées dès le départ.

Voici comment j’ai implémenté la sauvegarde automatique des statistiques de vente. L’idée est simple : chaque mois, on sauvegarde automatiquement les données du mois précédent.

1. Vérification quotidienne

public function save_previous_month_stats() {
	$current_date = new DateTime();

	// Si on est le premier jour du mois, on sauvegarde le mois précédent
	if ( $current_date->format( 'd' ) === '01' ) {
		$previous_month     = new DateTime( 'first day of last month' );
		$previous_month_key = $previous_month->format( 'Y-m' );

		$this->save_month_stats( $previous_month_key );
	}
}

Cette méthode est appelée quotidiennement via un cron WordPress, mais n’agit que le premier jour du mois.

2. Sauvegarde des stats mensuelles

private function save_month_stats( $month_key ) {
  global $wpdb;

  $start_date = new DateTime( $month_key . '-01' );
  $end_date   = clone $start_date;
  $end_date->modify( 'last day of this month' );

  $stats = $this->calculate_month_stats( $start_date, $end_date );

  foreach ( $stats as $product_id => $quantity ) {
    $wpdb->replace(
      $this->table_name,
      array(
        'product_id' => $product_id,
        'month_key'  => $month_key,
        'quantity'   => $quantity,
      ),
      array( '%d', '%s', '%d' )
    );
  }
}

Pourquoi REPLACE ?

J’utilise REPLACE plutôt que INSERT car il permet de gérer automatiquement la mise à jour des données. Grâce à la clé unique sur product_id et month_key, si des stats existent déjà pour un produit sur un mois donné, elles seront simplement mises à jour.

C’est particulièrement utile quand on veut recalculer l’historique : pas besoin de se soucier si les données existent déjà ou non !

3. Calcul des statistiques

public function calculate_month_stats($start_date, $end_date, $batch_size = 50) {
  $stats = array();
  $offset = 0;

  do {
    $orders = wc_get_orders([
      'limit' => $batch_size,
      'offset' => $offset,
      'type' => 'shop_order',
      'status' => ['wc-completed', 'wc-lpc_delivered', 'wc-lpc_ready_to_ship'],
      'date_created' => $start_date->format('Y-m-d') . '...' . $end_date->format('Y-m-d'),
    ]);

    foreach ($orders as $order) {
      foreach ($order->get_items() as $item) {
        $product_id = $item->get_product_id();
        if (!isset($stats[$product_id])) {
          $stats[$product_id] = 0;
        }
        $stats[$product_id] += $item->get_quantity();
      }
    }

    $offset += $batch_size;
  } while (count($orders) === $batch_size);

  return $stats;
}

Je ne prends en compte que les commandes avec les statuts qui m’intéresse pour avoir des stats pertinentes.

Ce système permet d’avoir :

  • Une sauvegarde automatique mensuelle
  • Des données historiques fiables
  • La possibilité de recalculer si besoin
  • Un traitement par lot

La prochaine étape ? L’affichage de ces données dans l’interface d’administration… 😨😨😨

📊 Affichage des données : interface admin WooCommerce optimisée

Pour l’interface utilisateur, j’ai opté pour une approche simple mais fonctionnelle. La base est de récupérer tous les produits actifs :

$args = [
  'limit' => $batch_size,
  'offset' => $offset,
  'status' => 'publish',
  'orderby' => 'title',
  'order' => 'ASC'
];

if ($category_id) {
  $args['product_category_id'] = [$category_id];
}

$products = wc_get_products($args);

🤔 Ok, j’avoue, ce n’est pas la première version du plugin. La première version était une solution « barbare » avec limit => -1. Ça fonctionnait avec mes tests pour une boutique de plus de 1000 produits. Mais quand il y a encore plus de produits, de commandes et de transactions de stock, cela démultiplie très rapidement les traitements…

Crois-moi, c’est bien plus agréable de :

  • Prévoir et anticiper les grands volumes dès le début
  • Dormir tranquille en sachant que ton code tiendra la charge
  • Éviter les urgences et les refactos dans 6 mois

Que de :

  • Devoir tout reprendre quand ça explose
  • S’arracher les cheveux sur du code legacy
  • Expliquer à ton client pourquoi « ça rame »

Améliorations possibles (car on peut toujours en trouver)

  1. Pagination des résultats
  2. Filtres sur les colonnes
  3. Tri dynamique
  4. Export par lots de produits
  5. etc.

Une fois qu’on a récupéré tous les produits, il faut compiler toutes les données.

1. Gestion des stocks pour chaque produit

$stock_status = '';
if ($product->get_manage_stock()) {
  $stock_quantity = $product->get_stock_quantity();
  $stock_status = $stock_quantity !== null ? $stock_quantity : '';
} elseif ($product->is_in_stock()) {
  $stock_status = __('En stock', 'custom-stats-woocommerce');
} else {
  $stock_status = __('Rupture', 'custom-stats-woocommerce');
}

$data[$product->get_id()] = [
  'id' => $product->get_id(),
  'name' => $product->get_name(),
  'stock' => $stock_status,
  'stock_status' => $product->get_stock_status(),
  'sales' => []
];

On récupére la quantité ou simplement si c’est en stock (pas de quantité au niveau du produit) ou si c’est en rupture.

2. Préparation des mois d’historique

for ( $i = 1; $i <= $months_back; $i++ ) {
  $date      = new DateTime( "-{$i} months" );
  $month_key = $date->format( 'Y-m' );

  $this->update_progress('initialization', $i, $months_back);

  // Vérifie si le mois existe déjà
  $existing_records = $wpdb->get_var(
    $wpdb->prepare(
      "SELECT COUNT(*) FROM {$this->table_name} WHERE month_key = %s",
      $month_key
    )
  );

  // Si le mois existe déjà, on le note dans les logs
  if ( $existing_records > 0 ) {
    $results['updated_records'] += $existing_records;
  }

  // Calcule et sauvegarde les stats du mois
  $start_date = new DateTime( $month_key . '-01' );
  $end_date   = clone $start_date;
  $end_date->modify( 'last day of this month' );

  $stats = $this->calculate_month_stats( $start_date, $end_date );

  foreach ( $stats as $product_id => $quantity ) {
    $wpdb->replace(
      $this->table_name,
      array(
        'product_id' => $product_id,
        'month_key'  => $month_key,
        'quantity'   => $quantity,
      ),
      array( '%d', '%s', '%d' )
    );

    // Si c'était un nouveau record
    if ( ! isset( $existing_records ) || $existing_records == 0 ) {
      ++$results['new_records'];
    }

    ++$results['total_records'];
  }

  ++$results['processed_months'];
}

On pense toujours volumétrie pour tous les traitements : on traite mois par mois et pour chaque mois :

$stats = $this->calculate_month_stats( $start_date, $end_date );

Qui lui va traiter les commandes par lots.

3. Récupération des stats en une seule requête

// Requête unique pour toutes les ventes
$historic_sales = $wpdb->get_results(
  $wpdb->prepare(
    "SELECT product_id, month_key, quantity 
                FROM {$this->table_name} 
                WHERE month_key IN (" . implode(',', array_fill(0, count($months), '%s')) . ")
                ORDER BY month_key DESC",
    $months
  )
);

Et voilà ! On a :

  • Les stocks à jour
  • L’historique des ventes
  • Le tout optimisé avec une seule requête SQL

Il ne reste plus qu’à ajouter les ventes du mois en cours (on pense au transient qu’on a vu au début de l’article) et on peut afficher notre tableau ! 👊👊👊

Le rendu (boosté par Claude 3.7 🤖🤖🤖) c’est pas ici :

Tableau historique des ventes et stocks WooCommerce plugin interface admin

Le zip du plugin est disponible ici : [lien]

⚠️ Attention : Ce plugin est fourni comme exemple pour illustrer la démarche de création et les choix techniques. Il n’a pas fait l’objet de tests approfondis (juste par moi en rapide 😁).

🤖 Pour les curieux : j’ai collaboré avec Claude 3.7 pour ce plugin. L’objectif n’était pas de laisser l’IA tout faire (clairement, si j’avais fait ça, le plugin ne serait pas fonctionnel 😅). Je l’ai plutôt utilisée comme :

  • Support à la réflexion
  • Aide à la recherche (notamment sur le fonctionnement de WooCommerce)
  • Assistant pour l’implémentation

L’IA m’a fait gagner plusieurs jours de travail. Résultat ? J’ai pu :

  • Éviter de sacrifier mes dimanches
  • Mieux gérer mon temps
  • Me reposer correctement
  • Jouer à MarioKart 🏎️ 🍄 🏁

L’objectif de cet article était de montrer :

  • Comment structurer un plugin à partir d’un besoin client
  • Les points techniques importants à considérer
  • La logique derrière chaque choix

Libre à vous de vous en inspirer et de l’améliorer selon vos besoins ! 😚😚😚

Samy Kantari - Expert WordPress + IA

Kantari Samy

Expert WordPress + IA

👨‍💻 10 ans dans le game WordPress, chez Whodunit, à bricoler du code, à dompter des bugs et à faire tourner des projets de toutes tailles.
Puis l’IA est arrivée… et là, révélation 💡 !
J’ai switché de mindset, réinventé ma façon de coder et avec le vibe coding : une nouvelle ère où je ne suis plus limité par le temps ni par les outils.

Aujourd’hui ? Je code toujours… Mais avec mon copilote IA.
On forme une team de choc. Lui, c’est la puissance. Moi, c’est la vision. Ensemble, on déverrouille ce qui semblait impossible hier. 🚀

10+ Années d'expérience
+++ Projets réalisés
80% code par IA
S’abonner
Notification pour
guest
0 Commentaires
Commentaires en ligne
Afficher tous les commentaires