Aller au contenu
Theux, Belgique

Sous-traitance 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 sous-traitance, 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.

Le tunnel de commande

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.

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>

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.

L'autre moitié de l'honnêteté

Trois erreurs qui sont les miennes, et ce qu'elles ont produit.

Vous ne cherchez pas quelqu'un qui ne tombe jamais, ça n'existe pas. Vous cherchez quelqu'un qui sait ce qui s'est passé, qui le dit avant qu'on le découvre, et qui en sort un garde-fou au lieu d'une promesse.

J'ai mis une instance à terre avec un décorateur

@api.depends('id') est interdit en Odoo 19 et l'exception tombe à l'import du module : le registre entier refuse de charger, tout répond 500, caisse comprise. C'était pendant la fermeture de caisse, et la gérante a vu « Connexion perdue » à l'écran. Sa fermeture a abouti quand même, 36 commandes, écart de 0,00 €.

Trois erreurs de méthode enchaînées, et aucune n'est celle d'Odoo : un modèle déployé sans mettre à jour le module, un arrêt de service qui rend la main avant l'arrêt réel donc deux mises à jour mortes sans que rien ne le dise, et un journal vide pris pour une réussite parce que le contrôle lisait un fichier que la configuration ne remplissait pas.

Ce qui en est resté Une liste de redémarrage exécutable où chaque case se vérifie elle-même, et qui refuse de démarrer tant qu'une session de caisse est ouverte. Plus une règle : une réparation ponctuelle laissée en place devient un piège. Celle posée après cet incident a écrasé un déploiement le lendemain. Retirée.

Mon convertisseur a rendu deux fiches injoignables

Odoo laisse la ligature œ intacte dans ses slugs : aucune normalisation Unicode ne la décompose, et \w la considère comme une lettre. Mon convertisseur, lui, n'acceptait que l'ASCII. La route ne matche jamais, la fiche répond 404, et rien ne le signale : ni erreur, ni alerte, ni ligne de journal.

Deux pièces en vente étaient dans ce cas, dont une depuis trois mois. Les mots en cause : « cœur » et « nœud », du vocabulaire courant en prêt-à-porter.

Ce qui en est resté Un motif de slug écrit une fois, relu par les convertisseurs et par le code qui écrit. Il refuse désormais d'enregistrer en silence une adresse qu'il ne saurait pas relire : l'erreur se voit à l'écriture, pas trois mois plus tard.

Ma reprise a coupé 63 titres en plein mot

website_meta_title est un Char(size=70) et Odoo tronque en silence. Mon script de reprise a suivi la même limite, au caractère et non au mot. Soixante-trois fiches se sont retrouvées dans Google avec un titre qui s'arrête au milieu du nom de l'enseigne. Taux de clic mesuré sur ces pages : 0,4 %.

Ce qui en est resté La coupe au mot, et un inventaire des 4 226 adresses avec les titres et les métas réellement servis, pas ceux qu'on croit avoir posés. Un chiffre qu'on ne voit pas est un chiffre auquel on finit par croire.

Quand ça tombe

Deux pannes, un garde-fou, et ce qu'il en reste.

19 minutes hors ligne, en pleine campagne

La boutique et la caisse ne répondaient plus. Le serveur, lui, allait très bien : charge à 0,01, 4,8 Go de mémoire libres, SSH instantané. Quand la machine est au repos et que le site est mort, c'est un interblocage, pas une surcharge.

Une commande payée pose un verrou, puis lance le rendu du bon de livraison. wkhtmltopdf redemande ses feuilles de style à Odoo, en HTTP, sur le serveur vivant. Avec deux workers, le premier attend le PDF, le second attend le verrou : plus personne pour servir la requête qui débloquerait tout. Et limit_time_real réglé à dix heures, donc rien ne serait venu dénouer ça avant le soir.

Le déblocage tient en un kill du rendu suspendu. Surtout pas un redémarrage : la caisse du magasin tourne sur cette instance, et un redémarrage coupe un encaissement en cours.

Un chien de garde, et la preuve qu'il mord

Chaque minute : un rendu PDF de plus de 120 secondes est tué, les verrous PostgreSQL tenus depuis plus de 60 secondes sont signalés, et la page de connexion est appelée en local avec une seconde tentative avant de crier. Un rendu légitime prend six secondes.

Il pousse ensuite son état vers un moniteur externe. S'il se tait, l'alerte tombe : un surveillant mort ne prévient de rien, et son silence ressemble beaucoup au calme.

Vérifié sur la production avec une vraie fausse panne : processus lancé à 13h33m59, tué tout seul à 13h36m04, après 121 secondes. Ce test a révélé un défaut invisible autrement, la fenêtre de silence était trop courte et aurait produit de fausses alertes. Son périmètre s'arrête là : il ne redémarre jamais rien.

La caisse n'imprime plus, et ce n'est pas Odoo

La box qui pilote l'imprimante ticket ne redémarrait plus. SSH répondait, mais les ports web étaient tous fermés, en connection refused et non en timeout : la machine est vivante et refuse activement, donc rien n'écoute.

Carte micro-SD sortie, lue dans un lecteur dont on a d'abord prouvé qu'il fonctionne. Elle annonce 127 139 840 octets, une taille qui ne correspond à aucune carte existante, sans aucune table de partition, et son premier secteur contient 55aa répété, un motif de test électronique. Une table valide porte cette signature sur ses deux derniers octets. Le contrôleur a perdu son adressage.

Le redémarrage n'a rien cassé, il a révélé une panne installée depuis des mois : un Linux qui tourne garde l'essentiel en mémoire et ne relit presque jamais son disque. Remède, image officielle vérifiée par empreinte, carte haute endurance, et un clone de secours dans le tiroir. Une caisse tombe aussi pour des raisons qui n'ont rien de logiciel.

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

Six 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é en sous-traitance.

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 sous-traitance 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.

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 Le reste du travail bgstudio.be