Table des matières :
- Définir le référentiel : qui “possède” le stock (et pourquoi ça évite 80% des bugs)
- Modèle de données : mapper Odoo (quants/moves) vers PrestaShop (stock_available) sans perdre d’information
- Architecture bi-directionnelle : API Odoo, événements PrestaShop, files d’attente et idempotence
- Synchronisation des stocks : temps réel, batch incrémental, et gestion des conflits (le vrai sujet)
- Performance et résilience : le stock, c’est des écritures fréquentes (donc MySQL et workers doivent tenir)
- Sécurité, gouvernance et mise en prod : limiter les dégâts quand Odoo ou PrestaShop dérive
Définir le référentiel : qui “possède” le stock (et pourquoi ça évite 80% des bugs)
Dans une intégration e-commerce Odoo, la question structurante n’est pas “comment appeler l’API”, mais où se trouve la vérité. Si PrestaShop reste éditable sur les quantités (back-office, imports CSV, modules marketplace) et qu’Odoo pilote aussi les approvisionnements et réservations, vous créez deux systèmes maîtres. Résultat classique : oscillation des quantités, ventes en rupture, et tickets “stock négatif” impossibles à reproduire.
Pour une architecture bi-directionnelle viable, on sépare généralement : Odoo = système de record pour l’inventaire (achats, réceptions, emplacements, réservations, retours), PrestaShop = canal de vente qui expose une quantité “vendable” (sellable). Ça ne veut pas dire “one-way” : les événements e-commerce (commandes, annulations, retours, modifications d’adresse, statuts de paiement) doivent remonter vers Odoo, mais la reconstruction de la disponibilité se fait côté ERP.
PrestaShop, même en 9.x, garde un modèle stock orienté “quantité dispo par produit/combinaison” (table stock_available) et n’implémente pas nativement une logique d’emplacements, de réservations multi-étapes, ou de règles d’allocation fines. Si vous visez un stock multi-entrepôts ou un routage par canal, l’ERP est l’endroit rationnel. À ce sujet, le pattern “inventaire unifié + routage par canal” est détaillé ici : Inventaire unifié : synchronisation temps réel et routage par canal.
Pour éviter les “stocks fantômes”, formalisez ce référentiel dans un document d’intégration (1 page suffit) avec des règles testables :
- Qui a le droit d’écrire la quantité vendable ? (idéalement : uniquement Odoo via la synchronisation)
- Qu’est-ce qui est vendable ? (ex. seulement l’entrepôt “FR-LYON”, ou “FR-LYON + FR-PARIS”, mais pas les emplacements de retours)
- Qu’est-ce qui déclenche une mise à jour ? (réception, annulation, avoir, inventaire, correction manuelle)
- Que fait-on des modifications “hors process” dans PrestaShop ? (rejet, écrasement à la prochaine synchro, ou alerte)
Mini-scénario (très courant en France/UE) : vous opérez un entrepôt à Lyon + une boutique à Paris avec Click & Collect. Si PrestaShop “décrémente” le stock boutique dès la validation panier, alors qu’Odoo réserve à l’entrepôt selon une règle d’acheminement, vous pouvez vendre un article “disponible à Paris” qui est en fait physiquement à Lyon. La solution n’est pas un patch API : c’est de décider où se fait l’allocation (ERP), puis d’exposer en e-commerce une quantité vendable par canal (livraison vs retrait) — exactement le type de routage que couvre l’approche d’inventaire unifié.
Modèle de données : mapper Odoo (quants/moves) vers PrestaShop (stock_available) sans perdre d’information
Odoo Inventory repose sur deux concepts clés : stock.move (mouvements, états, réservation, traçabilité) et stock.quant (quantités par produit et emplacement). La disponibilité “à vendre” dérive d’un ensemble de règles (emplacements internes, réservés, sorties, délais, etc.). PrestaShop, lui, attend typiquement une quantité numérique par (id_product, id_product_attribute, id_shop). L’intégration doit donc projeter un état ERP riche vers un état e-commerce plus pauvre, en assumant la perte d’information.
Le mapping produit n’est pas trivial dès que vous avez des variantes. Côté Odoo, la séparation product.template / product.product (variants) doit être réconciliée avec le duo PrestaShop produit / déclinaisons. Dans la pratique, vous avez besoin d’un identifiant stable inter-systèmes : SKU (recommandé) + éventuellement un champ technique Odoo (ex. x_prestashop_id) si vous contrôlez strictement la création. Évitez de mapper uniquement par nom : c’est fragile (i18n, corrections marketing, normalisation).
Troisième point : unités de mesure, arrondis et packs. Odoo supporte des UoM (unit of measure) et des conversions ; PrestaShop vend quasi toujours à l’unité. Si vous vendez au poids/volume ou en lots, vous devez figer une convention : quantité PrestaShop = unité de vente et conversion côté Odoo à l’entrée/sortie. Pour la traçabilité (lot/série), PrestaShop n’a pas de modèle natif robuste : soit vous refusez ces produits en B2C, soit vous gérez la sélection des lots uniquement dans Odoo lors du picking.
Pour rendre le mapping robuste (et auditable), une bonne pratique consiste à conserver une table de correspondance côté intégration (ou dans Odoo) plutôt que de “recalculer” en permanence à partir de libellés :
| Besoin d’intégration | Odoo (source) | PrestaShop (cible) | Recommandation pratique |
|---|---|---|---|
| Identité produit | product.product.default_code (SKU) |
reference (produit/déclinaison) |
SKU unique et immuable ; bloquer les doublons |
| Identité variante | product.product |
id_product_attribute |
Stocker l’équivalence (Odoo variant ↔ déclinaison) |
| Portée boutique | warehouse_id / règles de routes |
id_shop |
Définir une table “canal ↔ entrepôt(s)” |
| Quantité vendable | stock.quant (+ règles) |
stock_available.quantity |
Synchroniser un snapshot (pas une logique) |
| Date de calcul | write_date ou timestamp d’intégration |
champ technique (table d’intégration) | Rejeter les mises à jour “plus anciennes” |
Deux pièges fréquents quand on passe de l’ERP au e-commerce :
- Multi-entrepôts vs multi-boutiques : PrestaShop sait gérer du multi-shop, mais
stock_availablereste un stockage “par boutique”, pas un moteur d’allocation. Donc c’est l’intégration qui doit trancher : quelle boutique expose quel entrepôt, et comment on agrège si plusieurs entrepôts alimentent un canal. - Packs / bundles : si PrestaShop vend un pack (ex. “kit”), mais qu’Odoo suit les composants, définissez une règle claire : soit le pack est un produit stocké (SKU propre), soit sa quantité vendable est le min des composants (et vous synchronisez une quantité calculée). Mélanger les deux sans règle produit des écarts impossibles à expliquer au support.
Checklist de mapping (simple, mais qui évite des semaines de correction) :
- [ ] SKU unique par variante, identique dans les deux systèmes
- [ ] Une stratégie explicite pour les produits sans SKU (interdits ? génération ?)
- [ ] Un mapping stable des taxes/écotaxes si vous vendez en UE (éviter de faire dériver “le prix” pour compenser un souci de TVA)
- [ ] Une décision “lots/séries en B2C : oui/non” + comportement en cas d’obligation de traçabilité
Architecture bi-directionnelle : API Odoo, événements PrestaShop, files d’attente et idempotence
Odoo expose historiquement des APIs XML-RPC et JSON-RPC, et de nombreux projets utilisent aussi les endpoints web (/web/dataset/call_kw) pour appeler des méthodes de modèles. La doc “External API” d’Odoo est le point de départ (adaptez la version à votre instance) : Documentation Odoo (section développeur). Ce choix a un impact direct sur votre architecture : l’API RPC est puissante (appel de méthodes serveur), mais nécessite une discipline stricte (droits, validations, compatibilité lors des upgrades).
Côté PrestaShop (8+ et a fortiori les versions basées Symfony), la bonne approche est d’éviter les traitements “synchro” dans le thread HTTP. Une commande validée doit publier un événement (hook actionValidateOrder, changements de statut, création d’avoir) puis déléguer l’intégration à un worker (Symfony Messenger, cron, ou service externe). Les problèmes de relecture / retry / DLQ (dead letter queue) sont des sujets d’exploitation, pas des “détails dev” : si vous n’avez pas de mécanisme de re-tentative, vous allez perdre des commandes.
L’idempotence n’est pas optionnelle en bi-directionnel. Le RFC HTTP définit explicitement :
“A request method is considered ‘idempotent’ if the intended effect on the server of multiple identical requests … is the same as the effect for a single such request.” (RFC 9110, RFC 9110)
Concrètement : chaque événement PrestaShop→Odoo doit avoir une clé idempotente (ex. order:{id_order}:{version}), stockée côté Odoo (champ externe) ou dans une table d’intégration. Sans ça, un retry peut créer 2 commandes ERP, 2 factures, ou 2 réservations.
Un moyen simple (et très efficace) de rendre l’architecture lisible consiste à lister vos messages métiers au lieu de parler “API” :
| Message | Émetteur | Consommateur | Clé idempotente (exemple) | Remarques |
|---|---|---|---|---|
order.created |
PrestaShop | Odoo | ps:order:100045:v1 |
Créer ou mettre à jour une commande en “draft/quotation” selon votre process |
order.paid |
PSP/PrestaShop | Odoo | ps:order:100045:paid |
Selon le paiement, déclencher confirmation + réservation |
order.cancelled |
PrestaShop | Odoo | ps:order:100045:cancel:v2 |
Important : gérer les annulations partielles / remboursements |
stock.snapshot |
Odoo | PrestaShop | odoo:stock:SKU123:WH1:ts169... |
Push d’un état, pas d’un delta “aveugle” |
return.received |
PrestaShop (RMA) | Odoo | ps:rma:5541:received |
Si retours gérés côté e-commerce, sinon générer le flux via ERP |
Ce tableau devient votre contrat d’intégration : il guide les tests, l’observabilité et le support.
Exemple minimal d’appel JSON-RPC vers Odoo (login + appel de méthode), à encapsuler dans un service et jamais directement dans un contrôleur :
// PHP 8.2+, via GuzzleHTTP (principe)
{
"jsonrpc": "2.0",
"method": "call",
"params": {
"service": "common",
"method": "authenticate",
"args": ["mydb", "api_user", "api_password", {}]
},
"id": 1
}
// puis appel modèle
{
"jsonrpc": "2.0",
"method": "call",
"params": {
"service": "object",
"method": "execute_kw",
"args": ["mydb", 42, "api_password",
"sale.order", "create",
[{"partner_id": 123, "origin": "PS#100045"}]
]
},
"id": 2
}
Deux compléments qui “font la différence” en production :
- Gestion de version d’événements : un statut commande peut changer 5 fois (payée, préparée, expédiée, partiellement remboursée, etc.). Versionner les messages (ou inclure un
updated_atmonotone) rend les rejouements beaucoup plus sûrs. - Validation métier côté Odoo : créez des règles explicites (ex. “si produit non trouvé → message en erreur + alerte”) plutôt que de “créer à la volée” des produits/clients ; sinon vous déplacez le chaos dans l’ERP.
Synchronisation des stocks : temps réel, batch incrémental, et gestion des conflits (le vrai sujet)
La synchronisation stock Odoo↔e-commerce se rate rarement “par manque d’API”, mais par mauvaise définition de la quantité vendable. Votre projection doit décider : prend-on en compte les réservations, les picking en cours, les délais fournisseur, les stocks en transit, les règles de réassort ? Pour un B2C standard, une règle robuste est : sellable = quants internes disponibles − quantités réservées sur les emplacements vendables, par entrepôt, puis agrégation par canal.
Pour éviter les ambiguïtés, écrivez la règle sous forme calculable (même si l’implémentation finale est différente) :
stock_physique_interne= somme des quants sur emplacements “internes vendables”stock_réservé= réservations (commandes confirmées/pickings en cours)stock_vendable=max(0, stock_physique_interne - stock_réservé - stock_sécurité)
Exemple chiffré : vous avez 12 unités en interne, 5 unités réservées sur des commandes Odoo, et un stock de sécurité de 2 (pour limiter les ventes pendant un inventaire tournant). La quantité exposée à PrestaShop devrait être 12 - 5 - 2 = 5. Ce type de calcul réduit les “surventes” sans exiger du temps réel parfait.
Deux stratégies cohabitent en production :
- Push événementiel : Odoo publie un événement quand un mouvement/quant change (webhook, bus, module custom), l’intégration push vers PrestaShop. Latence faible, mais complexité (fiabilité des webhooks, ordering des événements).
- Pull incrémental : PrestaShop (ou un service) tire périodiquement les deltas (depuis
write_date, séquence de mouvements, ou watermark). Latence un peu plus haute mais opérationnellement plus simple.
Le mode “temps réel” pur est souvent un piège si vous n’avez pas un broker et une observabilité propre. Les Fallacies of Distributed Computing (popularisées chez Sun Microsystems) rappellent notamment que supposer “un réseau fiable” est une erreur récurrente ; une synthèse est disponible ici : Fallacies of Distributed Computing (Wikipedia). Traduction terrain : vous aurez des timeouts, des doublons, des événements dans le désordre. Donc vous devez gérer : ordering (numéro de séquence / watermark), déduplication (idempotence), et résolution de conflit (quel système gagne).
Cas de conflit typique : une commande PrestaShop est payée, mais l’événement de décrément stock arrive après une mise à jour stock Odoo→PrestaShop (réception fournisseur). Si vous faites des “setQuantity” absolus sans versionnement, vous écrasez un état plus récent. La mitigation simple est de pousser des snapshots versionnés (ex. available_qty + computed_at + warehouse_id) et de refuser toute mise à jour plus ancienne côté PrestaShop. L’autre mitigation est de ne jamais calculer côté PrestaShop : vous mettez à jour uniquement une quantité “cache” et vous assumez l’eventual consistency.
Une règle pragmatique (souvent adoptée) : PrestaShop ne calcule rien et ne fait que stocker une projection. Si un module/marketplace modifie stock_available “à la volée”, votre prochaine synchro doit écraser la valeur et journaliser l’écart. C’est un garde-fou opérationnel, au même titre qu’un inventaire tournant.
Si vous avez un besoin multi-canal (site, marketplaces, retail), lisez aussi l’approche “routage par canal + inventaire unifié” : guide sur la synchro temps réel et l’allocation par canal. Cela clarifie où se fait l’arbitrage quand deux canaux “veulent” le même stock.
Performance et résilience : le stock, c’est des écritures fréquentes (donc MySQL et workers doivent tenir)
À volume, ce qui casse en premier est rarement Odoo, mais le couple MySQL + code d’update stock côté PrestaShop. Mettre à jour stock_available sur des milliers de déclinaisons, avec recalculs, triggers de modules, et requêtes non indexées, devient un goulet. Avant d’optimiser “au feeling”, mesurez : EXPLAIN, cardinalité, index composites sur les colonnes réellement filtrées. Référence directe (utile aussi pour les tables d’intégration) : Index MySQL : optimiser WHERE, JOIN et ORDER BY avec EXPLAIN.
Sur l’implémentation, évitez le N+1 “par déclinaison” : faites des upserts batch et regroupez par shop/warehouse. Si vous êtes en PrestaShop 9, exploitez un service Symfony + repository optimisé plutôt que de multiplier les appels StockAvailable::setQuantity() en boucle (qui déclenche de la logique et des hooks). En parallèle, surveillez la consommation mémoire des workers : l’hydratation Doctrine ou l’accumulation de messages peut provoquer un memory bloat. Pour les stacks Symfony, ce sujet est documenté dans : détecter les N+1 Doctrine et le memory bloat sur Symfony.
Ajouts très concrets côté exploitation (souvent oubliés) :
- Taille des lots : mieux vaut 10 lots de 1 000 mises à jour qu’un lot de 10 000 qui dépasse un timeout PHP/FPM ou monopolise un worker.
- Verrouillage : si plusieurs workers peuvent mettre à jour le stock, imposez une sérialisation par
id_shop(ou par entrepôt) pour réduire les écritures concurrentes sur les mêmes lignes. - Coût des hooks/modules : une mise à jour stock peut déclencher des recalculs (prix, caches, exports). Mesurez l’impact en environnement de préprod et documentez ce qui doit être désactivé lors d’un “rebuild stock” massif.
Enfin, n’exécutez pas la synchro stock dans la même transaction logique que les commandes. Découplez avec une file (RabbitMQ, Redis Streams, SQS…) + pattern outbox si vous devez garantir “commande enregistrée ⇒ événement publié”. Une description canonique du Transactional Outbox est disponible ici : Transactional Outbox (microservices.io). Ce n’est pas “overengineering” : c’est la différence entre “on a eu 3 stocks faux hier” et “on a un pipeline rejouable”.
Pour aller plus loin sur le diagnostic SQL (indispensable quand stock_available devient un hotspot), le guide MySQL ci-dessus est un bon complément : optimiser les index MySQL avec EXPLAIN.
Sécurité, gouvernance et mise en prod : limiter les dégâts quand Odoo ou PrestaShop dérive
Exposer Odoo à un canal e-commerce impose une politique stricte : compte technique dédié, droits minimaux, rotation des secrets, TLS, et idéalement un reverse-proxy qui impose du rate limiting. Si l’intégration vit dans un module PrestaShop, stocker un mot de passe Odoo en clair dans ps_configuration est une erreur fréquente ; utilisez au minimum des variables d’environnement côté serveur, et documentez la procédure d’accès (qui peut lire les secrets, où sont les logs). Pour le hardening global PrestaShop (sessions, back-office, surface d’attaque), gardez une base : Sécurité PrestaShop 2026 : risques majeurs et protections professionnelles.
Pour une touche “terrain” (et très applicable en contexte France/UE), ajoutez deux garde-fous qui réduisent le risque sans alourdir le projet :
- Minimisation des données : pour synchroniser une commande vers Odoo, vous n’avez souvent pas besoin de remonter tout le compte client (historique, opt-ins marketing). Ne transmettez que ce qui est nécessaire à la facturation/logistique. C’est aussi plus simple pour la conformité (RGPD).
- Journal d’intégration consultable : quand un client appelle (“mon colis n’est pas parti”), l’équipe support doit pouvoir voir où la commande est bloquée (file, Odoo, mapping produit). C’est un sujet de gouvernance autant que de technique.
La gouvernance “bi-directionnelle” doit être écrite noir sur blanc : quelles entités sont modifiables où (produits, prix, stocks, clients) et quelles modifications sont rejetées ou réconciliées. Exemple concret : si un opérateur modifie un stock dans PrestaShop “pour dépanner”, que fait le système ? Si vous n’imposez pas un verrou (droits BO, désactivation d’écrans, ou correction automatique par la prochaine synchro), vous fabriquez de l’incohérence silencieuse. À ce niveau, l’intégration est autant un sujet d’architecture que de process.
Pour industrialiser, une checklist minimale de mise en production (à adapter) :
- Traçabilité : correlation-id propagé PrestaShop→Odoo→logs, et stockage d’un journal d’intégration (table
integration_event, statuts, payload hash). - Rejeu : possibilité de reconsommer un lot d’événements (par plage de date / watermark) sans doublons.
- Observabilité : métriques (latence de file, taux d’erreur, retries), alerting, et runbook.
- Plan B : mode dégradé si Odoo est indisponible (accepter les commandes et synchroniser plus tard, ou bloquer le checkout selon votre SLA).
Pour cadrer la sécurité côté infrastructure (pratique si vous hébergez en France/UE, en on-prem ou cloud), une référence utile et pérenne est le guide d’hygiène informatique de l’ANSSI : Guide d’hygiène informatique (ANSSI). Sans entrer dans une démarche lourde, il donne des mesures actionnables (durcissement, gestion des accès, supervision) qui s’appliquent très bien à un couple PrestaShop/Odoo.
Si vous externalisez l’orchestration (ETL léger), n8n peut servir d’outil de diagnostic rapide, mais il faut le traiter comme un composant de prod (versions, secrets, monitoring). Pour les erreurs de workflows et les rechargements qui masquent des échecs, cf. : Workflows n8n : diagnostiquer les erreurs de chargement et rechargement.




