Un export que l'interface ne propose pas, un contrôle de stock, une liste de relance : quand Dolibarr ne donne pas directement la réponse, une requête dans phpMyAdmin (ou tout autre client MySQL) la donne souvent. Voici cinq requêtes en lecture seule (SELECT), mises à jour pour le schéma actuel et vérifiées avec le mode ONLY_FULL_GROUP_BY, actif par défaut sur MySQL 8. Les anciennes versions qui circulent échouent pour la plupart : colonne renommée, GROUP BY incomplet, mauvaise table.
Les valeurs entre apostrophes (référence d'entrepôt, dates, libellé de catégorie) viennent de la base de démonstration de Dolibarr : remplacez-les par les vôtres. Aucune requête ne dépend d'un identifiant écrit en dur.
Nous avions d'abord placé ces valeurs dans des variables (SET @entrepot = '...'). Sur MariaDB, la comparaison avec une colonne a échoué : ERROR 1267 (HY000): Illegal mix of collations (utf8mb3_unicode_ci,IMPLICIT) and (utf8mb3_general_ci,IMPLICIT). Une variable prend la collation de la connexion, alors qu'une valeur écrite directement s'adapte à celle de la colonne. Nous gardons donc des valeurs directes.
Comment exporter les expéditions avec leur commande d'origine ?
Une expédition n'a pas de colonne "commande" : le lien est dans llx_element_element (source commande, cible shipping), la jointure que Dolibarr fait lui-même dans la classe Expedition. L'entrepôt est porté par chaque ligne d'expédition. Pour trouver la référence d'un entrepôt :
SELECT rowid, ref, lieu FROM llx_entrepot ORDER BY ref;
Puis les expéditions validées d'un entrepôt sur une période, avec la commande, le client et la quantité totale :
SELECT e.ref AS expedition, DATE(e.date_valid) AS validee_le, c.ref AS commande, s.nom AS client, SUM(ed.qty) AS quantite FROM llx_expedition AS e JOIN llx_expeditiondet AS ed ON ed.fk_expedition = e.rowid JOIN llx_entrepot AS w ON w.rowid = ed.fk_entrepot JOIN llx_societe AS s ON s.rowid = e.fk_soc LEFT JOIN llx_element_element AS el ON el.fk_target = e.rowid AND el.targettype = 'shipping' AND el.sourcetype = 'commande' LEFT JOIN llx_commande AS c ON c.rowid = el.fk_source WHERE w.ref = 'WAREHOUSEHOUSTON' -- réf. de l'entrepôt AND e.fk_statut > 0 AND e.date_valid >= '2025-01-01' AND e.date_valid < '2025-12-31' + INTERVAL 1 DAY GROUP BY e.rowid, e.ref, e.date_valid, c.ref, s.nom ORDER BY e.ref;
date_valid contient aussi l'heure : BETWEEN '...' AND '2025-12-31' s'arrête à minuit et oublie les expéditions du dernier jour, d'où le < date + INTERVAL 1 DAY. L'ancienne version de cette requête, groupée sur la seule référence, est refusée par MySQL 8 : ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause.
Comment repérer une commande fournisseur réceptionnée deux fois ?
Une réception enregistrée deux fois gonfle le stock (pour repartir d'un stock propre, voir remettre à zéro un Dolibarr). Chaque ligne réceptionnée est stockée dans llx_receptiondet_batch (l'ancienne llx_commande_fournisseur_dispatch, renommée par la migration vers Dolibarr 20). Cette requête liste les lignes de commande reçues en plus grande quantité que commandée :
SELECT cf.ref AS commande_fournisseur, p.ref AS produit, cfd.qty AS commandee, SUM(rb.qty) AS recue, COUNT(*) AS nb_lignes_reception FROM llx_receptiondet_batch AS rb JOIN llx_commande_fournisseurdet AS cfd ON cfd.rowid = rb.fk_elementdet JOIN llx_commande_fournisseur AS cf ON cf.rowid = cfd.fk_commande LEFT JOIN llx_product AS p ON p.rowid = cfd.fk_product WHERE rb.element_type = 'supplier_order' GROUP BY cfd.rowid, cf.ref, p.ref, cfd.qty HAVING SUM(rb.qty) > cfd.qty ORDER BY cf.ref;
Côté stock, les mouvements portent leur origine : order_supplier quand la ventilation se fait depuis la commande, reception quand le module Réceptions est utilisé. Celle-ci cherche les mouvements identiques (même origine, produit, entrepôt et quantité) :
SELECT COALESCE(cf.ref, r.ref) AS origine, p.ref AS produit, w.ref AS entrepot, m.value AS quantite, COUNT(*) AS nb_mouvements, MIN(m.datem) AS premier, MAX(m.datem) AS dernier FROM llx_stock_mouvement AS m JOIN llx_product AS p ON p.rowid = m.fk_product JOIN llx_entrepot AS w ON w.rowid = m.fk_entrepot LEFT JOIN llx_commande_fournisseur AS cf ON m.origintype = 'order_supplier' AND cf.rowid = m.fk_origin LEFT JOIN llx_reception AS r ON m.origintype = 'reception' AND r.rowid = m.fk_origin WHERE m.origintype IN ('order_supplier', 'reception') GROUP BY m.origintype, m.fk_origin, cf.ref, r.ref, p.ref, w.ref, m.value HAVING COUNT(*) > 1 ORDER BY origine;
Dans notre test, une commande de 10 unités ventilée deux fois ressort dans les deux requêtes (10 commandées, 20 reçues, 2 mouvements). Deux réceptions partielles de même quantité sont pourtant normales : vérifiez avant de corriger, puis corrigez par Corriger le stock sur la fiche produit plutôt qu'en supprimant des lignes. L'ancienne requête comparait la table à elle-même sans exclure la ligne courante : chaque mouvement se trouvait lui-même, et elle renvoyait les 104 mouvements ayant une origine.
Comment lister les contacts d'une catégorie ?
Les catégories de contacts sont dans llx_categorie_contact, reliée à llx_socpeople (les contacts). L'ancienne requête lisait llx_societe par llx_categorie_societe, c'est-à-dire les catégories de tiers. Nous filtrons sur le libellé et sur le type 4 (contact), car un même libellé peut exister pour les produits ou les tiers :
SELECT ct.lastname AS nom, ct.firstname AS prenom, ct.email, ct.phone, ct.phone_mobile, s.nom AS societe, co.label AS pays FROM llx_socpeople AS ct JOIN llx_categorie_contact AS cc ON cc.fk_socpeople = ct.rowid JOIN llx_categorie AS cat ON cat.rowid = cc.fk_categorie AND cat.type = 4 LEFT JOIN llx_societe AS s ON s.rowid = ct.fk_soc LEFT JOIN llx_c_country AS co ON co.rowid = ct.fk_pays WHERE cat.label = 'Family' -- libellé de la catégorie ORDER BY ct.lastname, ct.firstname;
Le pays est celui du contact (fk_pays), pas celui de sa société ; un contact sans société apparaît aussi grâce au LEFT JOIN.
Comment lister les factures en retard par commercial ?
Dolibarr sait déjà le faire : dans la liste des factures, le filtre Tiers ayant pour commercial et la case Alerte (factures validées dont l'échéance est dépassée) donnent la liste à l'écran. La requête sert pour un export ou un tableau de bord :
SELECT CONCAT_WS(' ', u.firstname, u.lastname) AS commercial, s.nom AS client, f.ref AS facture, f.date_lim_reglement AS echeance, DATEDIFF(CURDATE(), f.date_lim_reglement) AS jours_de_retard, f.total_ttc, COALESCE(pf.deja_paye, 0) AS deja_paye, f.total_ttc - COALESCE(pf.deja_paye, 0) AS reste_a_payer FROM llx_facture AS f JOIN llx_societe AS s ON s.rowid = f.fk_soc LEFT JOIN llx_societe_commerciaux AS sc ON sc.fk_soc = s.rowid LEFT JOIN llx_user AS u ON u.rowid = sc.fk_user LEFT JOIN ( SELECT fk_facture, SUM(amount) AS deja_paye FROM llx_paiement_facture GROUP BY fk_facture ) AS pf ON pf.fk_facture = f.rowid WHERE f.fk_statut = 1 AND f.paye = 0 AND f.type <> 2 AND f.date_lim_reglement < CURDATE() ORDER BY commercial, jours_de_retard DESC;
facnumbern'existe plus : la colonne s'appellerefdepuis la migration vers Dolibarr 10.CURDATE()remplace la date écrite en dur, etf.type <> 2écarte les avoirs.- Une société suivie par deux commerciaux fait apparaître la facture deux fois, une par commercial ; une société sans commercial apparaît avec un commercial vide.
deja_payene compte que les paiements : les avoirs et acomptes imputés ne sont pas déduits. Pour le montant exact restant dû, la liste de Dolibarr fait foi.
Ces requêtes servent souvent de point de départ à un export automatique vers un autre outil : c'est le travail de nos connecteurs. Un besoin de reporting sur votre Dolibarr ? Parlons-en.
- Les liens entre documents (commande, expédition, facture) passent par
llx_element_element. - Les contacts sont dans
llx_socpeople, leurs catégories dansllx_categorie_contact(type 4). - Filtrer par référence ou libellé plutôt que par identifiant, et mettre toutes les colonnes non agrégées dans le
GROUP BY. - Pour une date avec heure, utiliser
< date + INTERVAL 1 DAYplutôt queBETWEEN.
Testé avec Dolibarr 24.0.2 et MariaDB 11.4.13 ; requêtes également exécutées sur MySQL 8.4.11 et MySQL 5.7.33 avec ONLY_FULL_GROUP_BY.
Questions fréquentes
Où exécuter ces requêtes ?
Dans phpMyAdmin, Adminer ou le client mysql, sur la base de Dolibarr dont les identifiants figurent dans conf/conf.php. Ce sont des SELECT : elles ne modifient rien.
Mes tables ne commencent pas par llx_, est-ce grave ?
Non. llx_ est le préfixe par défaut, choisi à l'installation et visible dans conf/conf.php (variable $dolibarr_main_db_prefix). Remplacez-le dans chaque requête par le vôtre.
Que signifie l'erreur 1055 "not in GROUP BY clause" ?
Le mode ONLY_FULL_GROUP_BY, actif par défaut sur MySQL 8, refuse une colonne affichée qui n'est ni agrégée ni dans le GROUP BY. Il faut ajouter ces colonnes au GROUP BY, comme dans les requêtes de cette note.