Aller au contenu
Theux, Belgique

Renfort technique pour intégrateurs Odoo

Vos projets Odoo ne bloquent pas sur la compta.

Ils bloquent sur la boutique, sur la caisse, sur l'étiquette de colis qui ne sort pas de l'imprimante du dépôt. C'est la partie que personne n'aime chiffrer. C'est celle que je reprends.

808adresses indexées conservées, à l'identique
24modules Odoo livrés et en production
2 066produits migrés, 99 % de correspondance SKU
6semaines, d'une V14 fatiguée à une V19 stable

Le constat

Une migration se vend sur l'ERP. Elle se juge sur ce que le client touche tous les jours.

Le plan comptable est carré, les stocks sont repris, la facturation tourne. Et pourtant le client râle.

Parce que sa vendeuse scanne un article sur deux. Parce qu'une page de rayon met trois secondes à s'afficher. Parce que l'étiquette de transport ne sort pas, et que personne ne sait dire pourquoi.

Ces sujets tombent entre deux chaises. Ils demandent de connaître Odoo en profondeur et d'aimer le web, CUPS, les DNS, les feuilles de style et les files d'impression. Dans beaucoup d'agences, il n'y a personne pour les prendre.

Je fais ça, et seulement ça. En renfort de votre équipe, sous votre marque, sans jamais toucher à votre périmètre ERP.

Ce que je reprends

01 · Vitrine

La boutique et le site, dans Odoo

Thème, tunnel de commande, click and collect, pages de rayon, fiches produit. SEO technique, balisage schema.org, flux produits, redirections. Dans Odoo, pas dans un WordPress posé à côté qu'il faudra synchroniser toute sa vie.

02 · Terrain

Ce qui se passe dans le dépôt

Page de préparation des commandes, étiquettes de transport qui sortent toutes seules, impression depuis le serveur vers la box IoT, files CUPS et pilotes ZPL, caisse et codes-barres. Le genre de détail qui décide si l'outil est adopté ou contourné.

03 · Mesure

L'acquisition, et des chiffres relisables

Newsletters et campagnes dans Odoo, API Conversions de Meta écrite à la main, GA4, PostHog, Search Console, IndexNow. Des mesures qu'on peut recouper et contredire, pas un tableau de bord de démonstration.

04 · Exploitation

Mettre en production sans parier

Scripts de déploiement, sauvegarde avant chaque écriture, surveillance externe, chien de garde sur la prod, procédure de redémarrage qui se vérifie elle-même et sait revenir en arrière. Une boutique et une caisse sur la même instance ne pardonnent pas.

La douleur numéro un des migrations

Une migration Odoo perd les URL indexées. Celle-ci n'en a pas perdu une.

Odoo sert ses fiches produit sous /shop/... par une constante écrite en dur, SHOP_PATH, appelée à 27 endroits du code. L'ancien site les servait à la racine du domaine. Réécrire les adresses, c'était accepter de repartir de zéro sur Google, avec un client qui voit son trafic s'effondrer le lendemain de la bascule et une agence sans réponse à lui donner.

La parade n'est pas un plan de redirections : c'est un convertisseur d'URL werkzeug qui ne reconnaît que les slugs de produits publiés. Werkzeug essaie ses règles dans l'ordre, donc tout ce qui n'est pas un produit connu retombe sur le routage normal d'Odoo. Les pages de contenu, le back-office et les routes natives gardent la priorité.

Les 808 adresses indexées ont été conservées à l'identique. Il est resté 79 redirections de rubriques, toutes à zéro clic, au lieu des 887 prévues au départ.

Le périmètre a été tranché sur des données, pas au ressenti : six mois de Search Console exportés, 47 199 lignes relues. Verdict, 609 fiches produit cumulaient 281 clics quand la page d'accueil en faisait 3 857.

L'actif à protéger n'était donc pas le référencement des fiches, c'étaient les signaux locaux de la page d'accueil. Savoir ce qu'on ne doit pas protéger fait gagner autant de temps que le reste.

models/ir_http.pyle convertisseur, en entier
class EvProductSlugConverter(BaseConverter):
    """Convertisseur d'URL qui ne matche que les slugs de produits publiés."""
    regex = MOTIF_SLUG

    def to_python(self, value):
        # lang=None est indispensable : le convertisseur tourne PENDANT le routage,
        # avant qu'Odoo n'ait normalisé la langue du navigateur. Un visiteur qui
        # envoie « Accept-Language: fr-FR » laisse le contexte sur fr_FR, langue non
        # installée ici, et le moindre accès à l'ORM lève alors « Invalid language
        # code » : HTTP 422 sur les fiches produit ET sur toutes les pages de contenu.
        env = request.env(context={'lang': None})
        produit = env['product.template'].sudo().search(
            [('ev_url_slug', '=', value), ('is_published', '=', True)], limit=1
        )
        if not produit:
            raise ValidationError()   # werkzeug passe à la règle suivante
        return produit.with_env(request.env)

Les cinq lignes de commentaire valent le code. Ce lang=None vient d'un incident réel : un convertisseur s'exécute pendant le routage, avant qu'Odoo n'ait normalisé la langue du navigateur. Un visiteur avec Accept-Language: fr-FR, langue non installée sur cette instance, faisait tomber les fiches produit et toutes les pages de contenu en HTTP 422. Ce genre de détail ne s'apprend pas dans la documentation.

Le site, en vrai

Odoo peut faire une boutique qui ne ressemble pas à Odoo.

Ce n'est pas un thème acheté. Thème sur mesure, tunnel en deux étapes, click and collect avec calendrier de retrait, tri par disponibilité réelle, pastille de rupture, données structurées complètes. Tout vit dans Odoo : un seul stock, un seul catalogue, une seule base clients.

Page d'accueil d'une boutique de prêt-à-porter développée dans Odoo 19, avec bandeau de collection et mosaïque de rayons
L'accueil. Bandeau de collection piloté depuis une étiquette produit, mosaïque de rayons, grille Instagram en bas de page.
Page de rayon Odoo avec tri par disponibilité, tailles disponibles au survol et pastilles de nouveauté
Un rayon. Tri « disponibles d'abord », tailles réellement en stock au survol, et une colonne stockée pour que le tri ne coûte rien.
Fiche produit Odoo avec galerie, moyens de paiement Mollie et description structurée
Une fiche. Galerie, paiement Mollie, description structurée, et le lien vers la collection à laquelle la pièce appartient.

Ce que le panier propose

Les suggestions ne se rattachent pas à la main.

Odoo sait afficher des accessoires et des alternatives, à condition que quelqu'un les associe produit par produit. Sur 926 fiches, personne ne le fait, et ce qui n'est pas fait reste vide. Ici, le bloc se calcule sur les tickets réellement encaissés, magasin compris.

Panier Odoo avec une ligne de commande, un accessoire suggéré, un bloc « Complétez votre tenue » de quatre produits, la barre de progression vers la livraison offerte et le récapitulatif des totaux
Un jean dans le panier, quatre pantalons proposés dessous. Aucune de ces associations n'a été saisie : elles sortent d'une table d'affinités entre catégories, recalculée sur l'historique.
  • 170 règles d'affinité entre catégories, soit 85 paires tenues dans les deux sens. Chacune porte son lift et le nombre de tickets qui la soutiennent, parce qu'une règle sans son effectif ne se relit pas.
  • Le nombre de tickets est le garde-fou. Une paire vue trois fois produit un lift spectaculaire et ne veut rien dire. Celle qui s'affiche sur la capture, « Jeans » vers « Pantalons », tient sur 765 tickets pour un lift de 3,03. C'est modeste et c'est solide.
  • Les associations traversent les rayons. La plus forte du dossier relie « Chaussures enfants » et « Basket filles », lift 154 sur 45 tickets. La suivante relie des boucles d'oreilles à une marque de bijoux : deux catégories qu'aucun arbre produit ne rapproche tout seul.
  • Le reste du panier travaille aussi. L'emballage cadeau est proposé en accessoire, et la barre annonce ce qui manque pour la livraison offerte plutôt que de laisser la cliente le calculer.

Le calcul lui-même, les cohortes et la segmentation des clientes vivent dans l'entrepôt de données et sont décrits sur la page pilotage. Ce qui est décrit ici est seulement ce que la boutique en fait : une table lue par le panier, servie depuis Odoo, sans intervention.

Le cas qui n'entre pas dans le moule

Une marque au catalogue, mais pas au panier.

Certaines maisons ne veulent pas être revendues en ligne, ou le stock est trop unique pour être promis à distance. Le réflexe est de les sortir du site. On perd alors les recherches qui portent le nom de la marque, et elles sont nombreuses.

Page de marque dans Odoo : présentation de la maison, adresse et horaires du magasin, explication du mode de commande sur demande
La marque reste visible, et la page dit comment commander. Où voir les sacs, quand, et par quel bouton les demander.
  • 138 références restent au catalogue, avec leurs fiches, leurs photos et leur disponibilité réelle. Elles sont indexables, et elles captent les recherches de marque.
  • L'ajout au panier est remplacé, pas retiré. Sur ces fiches, le bouton ouvre un formulaire de demande. La cliente dit quel modèle l'intéresse, la boutique vérifie et répond.
  • Le stock est unique et partagé avec le magasin. C'est la raison honnête du dispositif, et la page l'écrit : mieux vaut vérifier qu'un sac est là que l'annoncer à tort.
  • La page assume d'être locale. Adresse, horaires, distance depuis Liège, et un numéro à appeler avant de se déplacer. Ce n'est pas du contenu de remplissage, c'est ce que cherche quelqu'un qui tape le nom d'une marque avec celui d'une région.

Le tunnel livré

Deux étapes, et rien qui ne serve la commande.

Le tunnel d'Odoo demande tout, tout de suite, et le compte avant la commande. Celui-ci demande l'e-mail, puis les coordonnées, puis fait payer. Voici les quatre écrans réels, dans l'ordre où la cliente les voit.

Première étape du tunnel de commande Odoo : saisie de l'adresse e-mail, récapitulatif de commande, code promo et éléments de réassurance
L'e-mail d'abord. Il suffit à reconnaître une cliente connue, à retrouver son panier, et à lui écrire s'il reste en plan. Le récapitulatif ne bouge plus ensuite : sous-total, TVA, code promo et carte cadeau au même endroit, du début à la fin.
Deuxième écran du tunnel : formulaire de coordonnées avec téléphone belge, société et TVA facultatives, rue et numéro séparés
Les coordonnées. Le téléphone porte sa raison d'être sous le champ. Société et TVA restent facultatives. Rue et numéro sont deux champs distincts : c'est exactement ce que bpost attend, et ça évite de deviner où finit la rue.
Le même écran rempli, avec la création de compte cochée : mot de passe, indicateur de validité et conditions générales acceptées
Le compte, facultatif et sur place. Pas d'inscription à passer avant d'acheter : une case, un mot de passe, et le compte s'ouvre à la confirmation de la commande. Les trois lignes disent ce qu'il apporte plutôt que de le réclamer.
Deuxième étape du tunnel : choix entre Click and Collect gratuit et livraison, calendrier de date de retrait, points de fidélité et moyens de paiement
Le retrait et le paiement. Click and Collect gratuit en tête, livraison à côté, calendrier des dates réellement ouvrables, points de fidélité annoncés avant de payer, et les moyens de paiement dans la colonne qui porte déjà le total.

Le Click and Collect

Un calendrier qui ne se maintient nulle part.

  • Les dates viennent des heures d'ouverture du magasin, déjà dans Odoo. Les jours proposés sont ceux où le magasin travaille, et les congés posés dans ce même calendrier bloquent les dates tout seuls. Rien ne se saisit deux fois, donc rien ne peut diverger.
  • Jamais le jour même. La première date proposée est demain : une commande passée à 23 h ne peut pas être promise pour le matin.
  • La date choisie s'écrit sur la commande, et le bon de livraison est régénéré pour la porter. La préparatrice la voit sur son écran sans aller la chercher, et le colis attend la bonne cliente le bon jour.
  • Le retrait est en tête, et gratuit. C'est le seul mode de livraison qui fait revenir quelqu'un dans le magasin, et une cliente qui vient chercher un jeans en repart rarement avec le jeans seul.
  • Les points de fidélité s'affichent avant le paiement : ce que la cliente a, ce qui lui manque pour cinq euros, et ce que la commande va lui rapporter. Le mécanisme existait déjà et personne ne le voyait : 2 170 clientes avaient des points et aucune page du tunnel ne le leur disait.
  • Quatre moyens de paiement par un seul contrat, carte, Bancontact, Belfius et PayPal, avec la note Google affichée juste à côté. Celle qui s'affiche ici est celle que les demandes d'avis ont fait bouger.

Dans le même tunnel

L'adresse se vérifie chez bpost pendant qu'elle se tape.

Une adresse mal écrite se paie deux fois : l'étiquette part, le colis revient, et la cliente écrit au magasin. Le tunnel interroge donc bpost à la frappe et pose ses suggestions dans les champs d'Odoo, sans en ajouter un seul à l'écran.

Étape 2 sur 3 Adresse de livraison
Continuer vers la livraison

Essayez Tapez « chaus » dans Rue, choisissez, puis tapez 8 dans Numéro. Les flèches parcourent la liste, Entrée choisit, Échap ferme. Le code postal et la ville interrogent le même niveau et se remplissent ensemble.

L'étape d'adresse, rejouée ici avec les composants et les règles du module livré : 220 ms de repos, deux caractères minimum, six suggestions au plus, la liste posée dans le conteneur du champ. Les suggestions viennent d'un échantillon de rues belges embarqué dans cette page, pas d'un appel à bpost. Les valeurs sont pour l'exemple.
  • Le widget officiel de bpost a été posé, puis retiré le même jour. Il ajoutait ses quatre champs au-dessus de ceux d'Odoo, avec ses libellés : la cliente voyait « code postal », « nom de rue », « numéro de maison », puis quinze centimètres plus bas « Code postal », « Rue », « Numéro ». Les mêmes informations, demandées deux fois.
  • Il pesait 528 Ko d'Angular sur la page où la latence compte le plus de tout le site, et il lisait la clé d'API dans le HTML, donc elle était publique. On appelle maintenant le même service depuis le serveur : 16 Ko de JavaScript au total, et la clé ne quitte jamais la machine.
  • Le contrat a été trouvé en lisant le widget, rien n'étant documenté : quatre niveaux qui s'enchaînent, localité, rue, numéro, boîte, chacun exigeant le contexte du précédent. Le paramètre s'appelle locality, la réponse rend localityName, et l'en-tête est x-api-key : tout le reste répond 403 ou 400.
  • 220 ms de repos entre deux frappes, deux caractères minimum, six suggestions, quatre secondes de patience. En dessous de deux caractères tout le pays répond ; au-delà de quatre secondes, une suggestion arrive après que la cliente a fini de taper, donc elle ne sert plus à rien.
  • Le tunnel ne dépend jamais du service. Aucun champ n'est verrouillé, aucune valeur refusée, et si bpost ne répond pas il ne se passe simplement rien. Une adresse étrangère, un lieu-dit, une rue trop neuve : tout se tape à la main. Une API qui tombe ne doit pas empêcher une vente.
  • Et un interrupteur. Vider la clé dans les paramètres éteint les suggestions : le marqueur disparaît de la page, le script ne trouve rien et s'arrête. Le formulaire retrouve sa forme d'origine sans redémarrage ni mise à jour de module.
Ce que l'API ne dit jamais

Le service de validation ne répond jamais « faux ». Il rend ce qu'il a su reconnaître, et corrige en silence : « Peron » devient « PERRON ». Le seul signal exploitable est le champ qui revient vide quand il n'a pas su placer la rue ou le numéro.

Le numéro devant

« 82 Chaussée de Mons » ne rend ni rue ni numéro. La même adresse écrite « Chaussée de Mons 82 » rend les deux. Une bonne part des adresses signalées ne sont donc pas fausses, seulement mal écrites, et se réparent sans rien demander à la cliente.

L'audit du carnet

Le même service passe en revue les adresses déjà en base, celles reprises de l'ancien site comme celles saisies en magasin. On répare avant que le colis parte, pas après qu'il est revenu.

Le terrain, en vrai

Une page de préparation, écrite pour un téléphone tenu d'une main.

L'ancien site avait un outil de préparation maison, 5 292 lignes accumulées au fil des ans, qui mourait avec lui. Reconstruit en module Odoo : une route, une page autonome sans le gabarit de la boutique, aucun framework et aucune police distante. Elle s'ouvre vite sur le wifi du magasin, y compris quand il est mauvais.

Préparation 2 commandes · 3 articles
7 jours Aujourd'hui 1 Hier 1

Claire D.W26/0418
bpost

Sophie M.W26/0419
retrait

Essayez Cochez une ligne : elle se barre. Cochez tout un bon et le bouton de validation apparaît, puis demande confirmation en nommant la cliente. La flèche en haut à droite remet les deux bons dans la file.

L'écran de préparation, rejoué avec ses composants, ses dimensions et ses états. Le vrai est derrière une authentification : les noms et les références sont inventés, les vignettes masquées, et rien ici ne part vers une imprimante.
  • 44 px minimum par cible tactile. En dessous, on rate la case et on coche la ligne d'à côté. Ici, ça veut dire préparer le mauvais article.
  • La vignette fait 96 × 128, pas 72 × 96. C'est la photo qui fait reconnaître la pièce dans un rayon, le libellé ne sert qu'à confirmer. Trop petite, elle oblige à lire, et lire est lent.
  • Le bouton photo est hors du label. Dedans, un clic sur l'image ouvrirait la galerie et cocherait la case du même geste.
  • La dernière case ne valide pas. Cocher dit « j'ai la pièce en main » : fréquent, réversible. Valider expédie, crée une étiquette réelle et facture un envoi. Le bouton n'apparaît que quand tout est coché, et la confirmation nomme la cliente.
  • Rechargement toutes les 30 secondes, par injection des seules cartes nouvelles. Jamais par rechargement de la page : la préparatrice a le pouce sur l'écran.
  • Fenêtre de sept jours, « tout » par défaut. La base portait cinq bons prêts dont le plus vieux datait de cinq mois. Un bon oublié doit sauter aux yeux, pas se cacher derrière le bon onglet.
Sécurité

auth="user" ne protège rien ici : toute cliente ayant un compte est un res.users. Ce qui tient, c'est le refus des comptes partagés plus l'exigence du groupe Stock.

ORM

stock.move.line.picked et stock.move.picked sont deux champs distincts, le premier n'alimente pas le second, et button_validate ne regarde que le second.

Première matinée

Deux commandes préparées, zéro étiquette sortie. La recherche d'imprimante ramenait celle des étiquettes d'article, et le transporteur du bon valait in_store. Corrigé, et écrit.

L'après-vente, et la loi

La rétractation n'est pas un retour. Depuis juin, elle a son propre bouton.

Deux régimes coexistent sur une boutique belge. Ils ne se ressemblent pas, les confondre coûte cher, et un intégrateur les découvre rarement dans le cahier des charges : ils tombent à la fin, juste avant la mise en ligne.

 Retour commercialRétractation
Motifselon les conditions de la maisonaucun exigé
Frais de livraison allernon remboursésremboursés si la commande est rendue en entier
Forme du remboursementavoir ou bon d'échangele moyen de paiement d'origine
Délaipolitique de la maison14 jours après la déclaration

Cette colonne de droite n'est pas négociable, et deux textes du site la contredisaient. Les conditions générales déduisaient les frais de port dès que le total repassait sous le seuil de livraison offerte, y compris sur un retour total : sur une rétractation entière, tous les paiements reçus doivent être rendus, livraison standard comprise. Et les conditions promettaient un échange que la politique de retour refusait deux pages plus loin. Les deux ont été réalignées sur ce qui se pratique vraiment.

Formulaire de rétractation en ligne : nom, adresse e-mail, référence de commande, choix entre commande entière ou partielle et commentaire facultatif
La rétractation. Le délai est annoncé, l'absence de motif aussi, et la déclaration est enregistrée avant l'accusé de réception, pas après. Le choix « toute la commande » précise au passage que les frais aller sont rendus.
Page de retour commercial : conditions de reprise, adresse d'expédition, puis recherche de la commande par numéro et adresse e-mail
Le retour commercial. Régime distinct, page distincte. Numéro de commande et e-mail suffisent à retrouver les articles : la cliente coche ce qu'elle renvoie au lieu de le décrire dans un message.

Ce qu'on a trouvé en ouvrant l'existant

Une page qui confirmait des demandes qu'elle n'enregistrait pas.

La page de rétractation reprise de l'ancien site affichait « veuillez remplir les informations ci-dessous », puis un volet dépliant qui annonçait « votre demande a été soumise avec succès ». Sans un seul champ de formulaire. Le formulaire n'avait pas migré, seul son habillage était passé. C'est pire qu'une page absente : la cliente lisait une confirmation pour une demande qui n'existait nulle part, et repartait rassurée.

  • La date de déclaration est posée à la création et n'est jamais recalculée. C'est elle qui fait foi, c'est elle qui fait courir les quatorze jours, et aucun traitement ne doit pouvoir la déplacer.
  • L'enregistrement et l'accusé horodaté sont automatiques. C'est le cœur de l'obligation légale : ça ne se confie pas à une tâche planifiée qui peut ne pas tourner.
  • Le remboursement, lui, reste manuel. La loi autorise à attendre le colis ou la preuve de son envoi, et rembourser au clic ouvrirait la porte à l'abus. C'est une décision du commerçant, pas un automatisme.
  • Un garde-fou quotidien pose un rappel à dix jours, pour que les quatorze ne soient jamais dépassés. Rien dans Odoo ne signale ce dépassement, et c'est pourtant lui qui coûte.
  • La commande n'est trouvée que si la référence et l'e-mail concordent, et le message d'erreur est volontairement le même dans les deux cas d'échec. Sinon le formulaire devient un moyen de tester l'existence des commandes, dont les numéros se suivent.
  • L'accusé ne s'efface pas après envoi. Odoo supprime par défaut certains messages une fois partis : un accusé de réception qui disparaît ne prouve plus rien, le jour où il faut prouver.

Détail qui a son importance : le lien du pied de page ne s'appelle plus « droit de rétractation » mais « renoncer au contrat ». Le texte européen veut une formule qui désigne la fonction, sans ambiguïté, pas l'intitulé d'une page d'information.

L'autre régime

Le retour commercial passe par un ticket, pas par une boîte mail.

Côté cliente

Un formulaire public : sa commande retrouvée par référence et e-mail, les articles à rendre cochés un par un, un motif par article, et le choix entre remboursement et bon d'échange.

Côté magasin

Trois brouillons créés d'un coup : un ticket dans l'équipe Retours, le retour de marchandise côté stock, et la note de crédit. Tout se traite depuis le ticket, remise en stock ou mise au rebut article par article, puis remboursement.

Garde-fous

Aucune commande n'est exposée sans correspondance exacte de l'e-mail, le nombre d'essais par adresse IP est limité, et tout ce que la cliente écrit est échappé avant d'atterrir dans le ticket.

Un exemple de tuyauterie

Le flux produits, enrichi article par article.

1 557 articles servis en continu par un contrôleur Odoo. Odoo ne sait pas produire ça seul : la catégorie Google, le genre, la tranche d'âge et les images secondaires ont été déduits du catalogue et ajoutés. Voici un article, tel qu'il sort aujourd'hui.

/catalogue/produits.xml23 champs par article
<item>
  <g:id>01-729053-604-blanc-L</g:id>
  <g:item_group_id>T19092</g:item_group_id>
  <title>Débardeur avec finition en maille filet - YAYA - blanc (L)</title>
  <link>https://[domaine du client]/debardeur-avec-finition-en-maille-filet-yaya-blanc</link>
  <g:image_link>.../product.product/37438/image_1920</g:image_link>
  <g:additional_image_link>.../product.image/1116/image_1920</g:additional_image_link>
  <g:additional_image_link>.../product.image/1117/image_1920</g:additional_image_link>
  ... cinq autres images secondaires
  <g:availability>in_stock</g:availability>
  <g:price>34.95 EUR</g:price>
  <g:brand>YAYA</g:brand>
  <g:size>L</g:size>
  <g:google_product_category>212</g:google_product_category>
  <g:gender>female</g:gender>
  <g:age_group>adult</g:age_group>
</item>

L'autre bout du même tuyau

La mesure part du serveur, pas du navigateur.

Un flux produits sert à faire de la publicité. Une publicité ne s'optimise que si les conversions remontent. Or le marqueur posé dans la page est bloqué, tronqué ou expiré chez une bonne partie des visiteurs. Ici, les événements partent d'Odoo, qui sait ce qui s'est réellement passé.

7 615pages produit vues, envoyées en ViewContent
470ajouts au panier
67entrées dans le tunnel
50achats, confirmés par la commande elle-même
  • 8 202 événements au 22 septembre 2026, tous partis, aucun en échec. Ce n'est pas un appel direct dans le code du site : chaque événement est écrit dans une table Odoo, envoyé par un traitement de fond, et garde sa réponse de Meta, son compteur de tentatives et son état. Quand l'API répond mal, ça se rejoue au lieu de disparaître.
  • Chaque événement porte un identifiant partagé avec le marqueur du navigateur. C'est ce qui permet à Meta de reconnaître qu'un achat vu deux fois est le même achat. Sans lui, poser la mesure des deux côtés gonfle les chiffres au lieu de les fiabiliser.
  • L'achat est déclenché par la commande, pas par l'affichage d'une page de remerciement. Une cliente qui ferme l'onglet après avoir payé est comptée comme les autres.

Les cinquante achats remontés se comparent aux cinquante-sept commandes réellement encaissées sur le même intervalle. L'écart est celui du consentement, et il se lit. C'est le but : une mesure dont on connaît la marge vaut mieux qu'une mesure dont on ignore le trou.

Les preuves

Huit choses trouvées sur un seul dossier.

Une boutique de prêt-à-porter, site public et caisse sur la même instance, migrée de la V14 à la V19. Tout ce qui suit a été mesuré avant et après, pas estimé. Aucun de ces points n'était dans le cahier des charges, et c'est exactement le problème.

57 % des articles étaient inscannables en caisse

13 469 références sur 23 691. La règle Lot barcodes interceptait les codes internes avant la règle Produit. Le défaut était antérieur à la migration et personne ne l'avait jamais nommé : comme une partie des articles passait, tout le monde croyait à un scanner capricieux.

28 groupes d'administrateur sur le compte public

Les visiteurs anonymes du site naviguaient avec les droits d'un administrateur système. Le symptôme remonté était du SEO : un sitemap de 5 759 URL pour 756 produits publiés. La cause était de la sécurité. Odoo ne filtre les produits dépubliés que pour les utilisateurs externes.

2,5 s → 1,2 s sur les pages de rayon, cache chaud

27 000 règles de prix mortes réévaluées à chaque affichage, et un champ calculé qui finissait par s'appeler lui-même. Deux causes indépendantes qui se masquaient l'une l'autre dans le profil.

1 PDF suffisait à coucher la boutique et la caisse

wkhtmltopdf redemande ses feuilles de style à Odoo, en HTTP, sur le serveur vivant. Avec deux workers, un rendu un peu lourd les prend tous les deux et plus rien ne répond. Diagnostic, puis chien de garde qui tue les rendus qui s'éternisent.

281 envois par un automatisme que personne ne savait décrire

L'e-mail « donnez-nous votre avis » de l'ancien site n'était ni dans l'outil d'envoi ni dans une extension du marché : un greffon maison, chargé sans activation, invisible dans l'administration. 281 demandes entre mai et août, 26 avis récoltés, note passée de 4,6 à 4,7. Avant de déclarer un automatisme perdu, il faut aller voir sur l'ancien serveur, il répond encore.

0 étiquette sortie, et pas une seule erreur signalée

Un PNG envoyé dans une file ZPL. CUPS répondait fièrement completed, le pilote jetait le travail à la poubelle. Deux jours de colis étiquetés à la main avant que quelqu'un ose dire que ça ne marchait pas.

2 170 clientes avaient des points, et rien ne le leur disait

Le programme de fidélité repris de WooCommerce fonctionnait parfaitement. Aucune page du tunnel n'en soufflait mot. Le mécanisme existait déjà dans Odoo : il suffisait de l'afficher là où la cliente est identifiée et où une remise l'intéresse encore.

10 948 e-mails en 43 minutes, zéro échec, zéro rebond

Après avoir compris pourquoi Odoo mettait une heure là où l'ancien outil mettait deux minutes : un seul fil de cron, et deux tâches qui ne peuvent pas se croiser. Et après avoir mesuré que l'accélérateur maison rendait l'envoi plus lent, puis l'avoir retiré.

Ce ne sont pas des anecdotes. Chacun de ces points a été écrit, daté et rangé dans une base de connaissances relisable, avec la cause, la parade et le piège à ne pas refaire. C'est ce qui reste chez vous quand je pars.

Quatre pièges que la documentation ne mentionne pas

Chacun a coûté une demi-journée. Une fois.

Ceux-là sont dans le code de l'éditeur, pas dans le mien. Ils ont un point commun : ils ne lèvent aucune erreur, ou ils la lèvent très loin de leur cause. C'est pour ça qu'ils coûtent cher la première fois, et rien du tout la deuxième.

literal_eval

Les domaines des campagnes marketing sont lus avec literal_eval, pas safe_eval : ni datetime, ni context_today, ni relativedelta n'y passent. Tout calendrier doit vivre dans un champ stocké qu'un cron entretient.

marketing.campaign

Une campagne aspire tout l'historique le jour où on la démarre. Sur les commandes, ça faisait 11 849 lignes reprises de l'ancien site, prêtes à recevoir un e-mail. Ce qui protège : un filtre sur l'origine web plus un plancher de date. Ne jamais élargir un domaine de campagne sans compter d'abord ce qui entrerait.

ir.qweb

Odoo surveille les balises de script de ses pages de contenu et réécrit ce qu'il ne reconnaît pas. Un pixel publicitaire collé dans l'éditeur ne part jamais, et rien ne le signale. Le suivi des conversions a donc été écrit côté serveur, avec l'API dédiée.

cookie

Un cookie de consentement illisible fait tomber tout le site en 500, pas seulement la barre qui le lit. Un visiteur avec un cookie corrompu ne voit plus rien, et son navigateur le lui renvoie à chaque tentative.

Ce qui reste dans le dépôt

Vingt-quatre modules, nommés par leur rôle.

Le préfixe porte le nom du client, il est masqué ici. Chacun est un module Odoo autonome, installé en production, versionné, avec ses commentaires en français et sa raison d'être écrite en tête de fichier.

website_…routage, URL, données structurées, sitemap
theme_…thème du site, sur mesure
…_vitrineaccueil, collections, mise en avant
…_fiche_produitgalerie, variantes, disponibilité réelle
…_checkouttunnel en deux étapes, totaux, fidélité
website_sale_pickup_datecalendrier de retrait, click and collect
…_molliepaiements, remboursements, lettrage
…_carte_cadeauchèques cadeaux, web et magasin
…_carte_libremontant libre sur carte cadeau
…_espace_clientcommandes, retours, points de fidélité
…_retractationdroit de rétractation, états et délais
…_returnsretours, avoirs, remise en stock
…_preparationpréparation des commandes web
…_bpostétiquettes de transport, aide à la saisie
…_etiquette_zplétiquettes d'article, ZPL natif
…_impressionfiles d'impression, box IoT
…_ticketticket de caisse, tiroir, arrondis
…_catalogueflux produits, Google et Meta
…_analyticsmesure serveur, conversions, PostHog
…_emailsgabarits transactionnels, prénom, portail
…_notificationsalertes internes en temps réel
…_photothequemédiathèque, séries, photo de rayon
…_documentsdocuments clients, pièces jointes
…_maintenancepage d'attente, bascules, garde-fous
34 000lignes de Python : 13 200 dans les modules, le reste dans 122 scripts d'exploitation
9 400lignes de QWeb, plus 11 400 de JavaScript et de feuilles de style
2fronts sur la même instance, sans interruption : la boutique et la caisse

Comment on travaille

Quatre règles, écrites avant de commencer.

1
En marque blanche

Votre client ne me voit pas, ou il me voit sous votre nom. Au choix, et c'est le vôtre. Je ne démarche jamais un compte rencontré chez vous.

2
Par lot, au forfait

Un périmètre écrit, un prix, une date. Ce qui sort du périmètre est chiffré à part, avant d'être fait. Pas de régie qui dérive et qu'on découvre à la facture.

3
Dans votre dépôt

Du code commenté en français, des modules propres hors de vos arbres git, les scripts de déploiement et la procédure de retour arrière. Rien qui ne tourne que sur ma machine.

4
Sans toucher à votre ERP

Je reste sur le web, le terrain et l'exploitation. La compta, les stocks, la production et la paie restent chez vous. Si un correctif doit passer par votre périmètre, je le documente et je vous le passe.

Les questions qui viennent toujours

Sept réponses, avant le premier appel.

Une migration vers Odoo fait-elle perdre le référencement ?

Pas si les adresses sont conservées. Odoo sert ses fiches produit sous /shop/... par une constante écrite en dur, appelée à 27 endroits du code. Un convertisseur d'URL werkzeug qui ne reconnaît que les slugs de produits publiés permet de garder les adresses de l'ancien site à l'identique, sans voler les URL des pages de contenu ni des routes natives. Sur le dossier de référence, les 808 adresses indexées ont été conservées et il n'est resté que 79 redirections de rubriques, toutes à zéro clic, au lieu des 887 prévues au départ.

Travaillez-vous en marque blanche pour une agence Odoo ?

Oui, c'est le mode par défaut. Le client final ne me voit pas, ou il me voit sous le nom de l'agence : c'est l'agence qui choisit. Je ne démarche jamais un compte rencontré chez vous.

Quel périmètre Odoo prenez-vous, et lequel ne prenez-vous pas ?

Je prends le site et la boutique dans Odoo, le tunnel de commande, le SEO technique et les flux produits, les outils de terrain (préparation de commandes, étiquettes de transport, impression vers box IoT, caisse et codes-barres) et l'exploitation (déploiement, surveillance, retour arrière). Je ne touche pas à la comptabilité, aux stocks, à la production ni à la paie : ce périmètre reste chez l'intégrateur.

Comment se facture une mission de renfort Odoo ?

Par lot, au forfait : un périmètre écrit, un prix et une date. Ce qui sort du périmètre est chiffré à part avant d'être réalisé. Pas de régie qui dérive et qu'on découvre à la facture.

Gérez-vous le droit de rétractation et les retours dans Odoo ?

Oui, et les deux régimes sont tenus séparés. La rétractation est une obligation légale : quatorze jours calendrier sans motif à compter de la réception du bien, articles VI.47 à VI.53 du Code de droit économique belge, et depuis le 19 juin 2026 une fonction de rétractation en ligne imposée par l'article 11 bis de la directive 2011/83/UE, inséré par la directive (UE) 2023/2673, avec accusé de réception horodaté sur support durable. Le retour commercial, lui, relève de la politique du commerçant et passe par un formulaire public qui crée le ticket, le retour de marchandise et la note de crédit en brouillon. Sur une rétractation totale, tous les paiements reçus sont remboursés, frais de livraison standard compris.

Que reste-t-il à l'agence à la fin de la mission ?

Des modules Odoo propres déposés dans un répertoire distinct de vos arbres git, du code commenté en français, les scripts de déploiement et la procédure de retour arrière, plus une base de connaissances qui documente chaque cause, chaque parade et chaque piège rencontré.

Sur quelle version d'Odoo intervenez-vous ?

Odoo 19, en production, boutique publique et point de vente sur la même instance. L'expérience de référence est une migration complète de V14 à V19, réalisée en six semaines.

Un dossier qui traîne depuis trois mois ?

Décrivez-le en cinq lignes. Je réponds sous 48 h : si c'est pour moi, ce que ça coûte et combien de temps ça prend. Si ce n'est pas pour moi, je le dis aussi, et je le dis vite.

Écrire geoffrey@bgstudio.be L'autre front Pilotage et données Le reste du travail bgstudio.be