Introduction
Si vous avez déjà travaillé avec Odoo, vous avez sûrement vu des champs dont la valeur est déterminée automatiquement. Un champ calculé stocké va plus loin : il calcule le résultat puis l’enregistre physiquement dans la base de données, comme une colonne classique.
Cette nuance change beaucoup de choses en pratique. Un champ calculé à la volée ne peut pas être efficacement recherché, filtré, groupé ou exporté. Un champ calculé stocké, lui, se comporte comme n’importe quel autre champ en base — mais sa valeur continue d’être déterminée par la logique métier que vous définissez.
Ce guide explique l’essentiel à connaître sur les champs calculés stockés dans Odoo : leur place dans le modèle de données, comment les créer via Odoo Studio ou en Python, des exemples concrets métiers, et les erreurs courantes à éviter.
Qu’est-ce qu’un champ calculé stocké dans Odoo
Dans l’ORM d’Odoo, chaque champ d’un modèle conserve une donnée. La plupart sont remplies manuellement par l’utilisateur. Un champ calculé, en revanche, voit sa valeur déterminée par une fonction Python plutôt que par une saisie utilisateur.
Un champ calculé stocké, c’est simplement un champ calculé déclaré avec store=True. Quand l’un de ses champs dépendants change, Odoo exécute la méthode de calcul et écrit le résultat dans la colonne de la table. Après cela, la valeur est disponible comme n’importe quel autre champ.
En pratique, le schéma courant en Python se compose d’une déclaration de champ et d’une méthode de calcul liée par @api.depends pour lister les dépendances.
Par exemple, on définit un champ numérique, on indique compute et store=True, et la méthode de calcul parcourt les enregistrements pour affecter la valeur calculée (quantité × prix unitaire).
C’est le paramètre store=True qui transforme un champ calculé volatile en champ persistant. Sans lui, la valeur est recalculée à chaque lecture et jamais sauvegardée en base.
Côté interface, un champ calculé stocké est indiscernable d’un champ classique : il apparaît dans les formulaires, listes et rapports. Les utilisateurs peuvent filtrer, grouper et exporter dessus sans savoir qu’il s’agit d’un calcul.
Champ calculé stocké vs non stocké
Comprendre cette différence est indispensable pour tout travail d’extension ou de développement Odoo :
- Champ calculé non stocké : la valeur est évaluée à la lecture. On ne peut pas s’en servir pour les filtres, la recherche ou le groupement. C’est léger en stockage mais inutilisable par les requêtes SQL.
- Champ calculé stocké : la valeur est calculée lorsque ses dépendances changent et conservée en base. Elle est alors searchable, filterable et exportable, mais occupe de l’espace comme une colonne normale.
Le choix n’est pas une question de supériorité absolue : il dépend de l’usage. Pour un affichage unique, un champ non stocké suffit. Si vous devez trier, filtrer ou agréger, choisissez stocké.
Principe de fonctionnement du champ
Quand vous déclarez un champ calculé stocké, l’ORM d’Odoo configure automatiquement des déclencheurs de recomputation en se basant sur les champs listés dans @api.depends().
Dès qu’un champ dépendant est modifié, Odoo marque l’enregistrement pour recomputation, exécute la méthode compute et écrit le résultat dans la colonne correspondante.
Cycle de recomputation
Voici la séquence d’événements typique :
- Un utilisateur ou un processus modifie un champ mentionné dans @api.depends().
- Odoo détecte le changement et identifie les enregistrements impactés.
- La méthode de calcul est invoquée pour ces enregistrements.
- La valeur calculée est écrite en base de données.
- Le champ est alors disponible pour recherche, filtres et export avec sa valeur à jour.
Généralement, la recomputation s’effectue immédiatement dans la même transaction. Pour de gros traitements, Odoo peut toutefois différer certaines recomputations et les exécuter en arrière-plan.
Dépendances entre modèles liés
Le décorateur @api.depends accepte des chemins pointés pour référencer des champs sur des modèles liés, par exemple partner_id.country_id.name.
Dans ce cas, Odoo surveille partner_id, country_id et name à travers les relations. Si le nom du pays change, tous les enregistrements concernés seront recomputés automatiquement.
Cette capacité à suivre des dependances cross-modèles est l’un des points forts du framework Odoo pour maintenir la cohérence des données.
Impact sur la base de données
Un champ stocké correspond à une colonne réelle dans la table PostgreSQL. Ainsi, il participe directement aux requêtes SQL : les recherches et filtres sont rapides et efficaces, comme pour un champ classique.
Cas d’usage en entreprise
Exemples concrets d’utilisation
Voici cinq cas concrets tirés de process métiers réels.
1. Ventes : pourcentage de marge sur les lignes de commande
Une équipe commerciale souhaite voir la marge sur chaque ligne sans calcul manuel. Un champ stocké prend le prix de vente et le coût, calcule le pourcentage de marge et l’enregistre sur la ligne. Le manager peut alors filtrer et repérer immédiatement les lignes non rentables ou segmenter les ventes par tranches de marge.
2. CRM : jours sans activité sur une opportunité
Sur le modèle CRM, un champ calculé stocké peut indiquer le nombre de jours depuis la dernière activité planifiée. Couplé à une action planifiée qui relance la recomputation chaque matin, cela permet à l’équipe commerciale de filtrer les leads inactifs selon un seuil défini.
3. Stock : quantité disponible nette
Pour des produits aux règles de stock complexes, un champ stocké peut contenir une valeur pré-calculée (quantité disponible moins réservations). Les responsables produit peuvent trier et filtrer la liste sans lancer des calculs de stock complexes pour chaque ligne affichée.
4. Comptabilité : nombre de factures échues par client
Sur le contact client, un champ stocké peut compter le nombre de factures échues. L’équipe compta peut ainsi trier les clients en un clic selon le nombre d’impayés — possible uniquement parce que le total est persistant en base.
5. Production : durée totale estimée d’un nomenclature
Créer ou personnaliser le champ
Dans une nomenclature, un champ stocké peut sommer les durées estimées des opérations associées. Les planificateurs peuvent filtrer et trier les BOMs par durée totale pour optimiser la capacité et l’ordonnancement : la valeur se met à jour automatiquement quand une opération est ajoutée ou modifiée.
Méthodes de création
Deux approches principales existent : Odoo Studio pour des besoins simples, ou du code Python dans un module pour un contrôle complet.
Odoo Studio permet d’ajouter des champs calculés sans coder. Pour des champs Integer, Float ou Monetary, Studio propose souvent une option de formule acceptant une expression de type Python. Studio gère les dépendances pour vous en coulisses.
Studio est parfait pour des calculs arithmétiques simples sur le même enregistrement : rapide à déployer et sans environnement de dev. Mais dès que la logique touche des modèles liés, des conditions ou des agrégations, vous atteignez les limites et il faudra un module personnalisé.
C’est une distinction importante dans la planification d’une personnalisation Odoo : Studio gagne en rapidité, Python en souplesse.
Module Python personnalisé
Pour toute logique dépassant la simple formule, on définit le champ en Python dans un module. Par exemple, on peut ajouter un champ pourcentage de marge sur les lignes de commande en héritant du modèle sale.order.line, en déclarant le champ avec compute et store=True, et en codant la méthode @api.depends pour price_unit et purchase_price.
À l’installation du module, Odoo crée la colonne correspondante en base, exécute la recomputation pour les enregistrements existants et commence à suivre les champs déclarés comme dépendances.
La convention x_ en préfixe des champs personnalisés évite les collisions avec les champs natifs et est une pratique courante en développement Odoo.
Rendre un champ calculé modifiable
Par défaut, les champs calculés sont en lecture seule. Si vous souhaitez autoriser une saisie manuelle qui écrase la valeur calculée, définissez une méthode inverse (inverse) qui prend en charge l’écriture et met à jour les champs sources. Ce pattern est utile lorsque le calcul fournit une valeur par défaut mais que l’utilisateur doit parfois ajuster.
Odoo Studio et l’API XML-RPC
Si vous gérez la structure des champs via l’API XML-RPC, vous pouvez créer des champs standards via ir.model.fields. Mais pour un champ calculé stocké dont la logique repose sur du Python, la méthode de calcul doit vivre côté serveur dans un module. L’API convient pour provisionner des champs simples lors de déploiements automatisés, mais la logique métier nécessite un module installé sur le serveur.
Bonnes pratiques
Bonnes pratiques suivies par des consultants Odoo expérimentés :
Déclarez précisément toutes les dépendances
La décoration @api.depends doit lister chaque champ que votre méthode lit. Omettre une dépendance empêche la mise à jour automatique quand cette donnée change. Passez la méthode en revue et inscrivez chaque accès de champ dans le décorateur.
Gardez les méthodes de calcul rapides
La méthode compute s’exécute sur tous les enregistrements affectés par un changement. Sur une instance chargée, cela peut concerner des milliers d’enregistrements. Évitez les requêtes supplémentaires en base dans le compute ; préférez les champs déjà préchargés et minimisez les recherches.
N’utilisez store=True que si nécessaire
Les champs stockés occupent de l’espace et déclenchent des écritures à chaque recomputation. Si le champ n’est destiné qu’à un affichage et ne sera jamais filtré ou groupé, un champ non stocké est plus léger. Décidez en connaissance de cause.
Gérez les cas limites dans la méthode de calcul
Anticipez les valeurs manquantes : division par zéro, enregistrements liés absents ou valeurs nulles sont des causes fréquentes d’erreurs silencieuses. Ajoutez des contrôles et des valeurs par défaut sécurisées lorsque le calcul ne peut pas se faire normalement.
Préparez la recomputation initiale pour les grandes tables
À l’installation d’un champ stocké, Odoo recompute la valeur pour tous les enregistrements existants. Sur des tables de centaines de milliers de lignes, cela peut durer longtemps. Testez en staging et prévoyez une fenêtre ou un traitement en background lors du déploiement en production.
Évitez les dépendances circulaires
Si A dépend de B et B dépend de A, Odoo lèvera une erreur au chargement du module. Concepteur vos dépendances pour qu’elles restent unidirectionnelles.
Pièges fréquents
Oublier store=True
C’est l’erreur la plus fréquente : le champ s’affiche correctement en vue formulaire, mais ne fonctionne pas en filtre ou rapport. Réfléchissez dès le départ si le champ doit être searchable ; si oui, mettez store=True immédiatement.
Omettre une dépendance dans @api.depends
Si votre méthode lit partner_id.country_id mais que vous ne listez que partner_id dans le décorateur, la valeur ne se mettra pas à jour quand le pays changera. Écrivez chaque étape du chemin d’accès dans @api.depends.
Erreurs silencieuses dans la méthode de calcul
Si la méthode lève une exception pour un enregistrement, Odoo saute silencieusement la recomputation pour celui-ci et conserve l’ancienne valeur. L’erreur peut apparaître dans les logs serveur mais pas à l’utilisateur, ce qui engendre des valeurs obsolètes difficiles à diagnostiquer. Testez la méthode sur des jeux de données variés.
Dégradation des performances sur grands volumes
Un compute qui passe inaperçu en dev peut devenir critique en production si la table grossit. Analysez le nombre de requêtes générées par enregistrement : une requête supplémentaire par enregistrement multipliée par dix mille devient vite problématique.
Usage de sudo() dans les computes
Appeler sudo() pour contourner les droits dans un compute est risqué. Si la valeur exposée révèle des données que l’utilisateur courant ne doit pas voir, vous contournerez le modèle de permissions d’Odoo. N’utilisez sudo() que si vous avez évalué les impacts sécurité.
Attendre une recomputation immédiate dans tous les contextes
La recomputation est généralement synchrone en mode interactif, mais peut être différée lors d’imports massifs, jobs en arrière-plan ou opérations ORM particulières. Ne basez pas de logique métier sur l’hypothèse que la valeur stockée est instantanément à jour après chaque écriture ; vérifiez le comportement selon le contexte d’utilisation.
Conclusion
Les champs calculés stockés sont des outils puissants pour étendre Odoo : ils automatisent des calculs, garantissent la cohérence des données et rendent les résultats exploitables dans les recherches et exports sans intervention manuelle.
Points clés à retenir :
- Activez store=True quand vous avez besoin que le champ soit searchable, filtrable ou exportable.
- Listez toutes les dépendances dans @api.depends(), y compris les chemins cross-modèles.
- Optimisez vos méthodes de calcul pour la performance et gérez explicitement les cas d’erreur.
- Utilisez Odoo Studio pour des formules simples ; passez au Python pour toute logique plus complexe.
- Anticipez la recomputation initiale lors du déploiement sur des tables volumineuses.
Que vous développiez un module, étendiez un modèle existant ou découvriez les types de champs Odoo, comprendre les champs calculés stockés permet d’articuler l’ORM, la base de données et la logique métier efficacement.
Besoin d’aide pour votre projet Odoo ?
Dasolo accompagne les entreprises dans l’implémentation, la personnalisation et l’optimisation d’Odoo pour des besoins variés. Que ce soit pour ajouter des champs calculés, bâtir des rapports basés sur des valeurs calculées ou pousser votre développement Odoo plus loin, notre équipe a l’expérience nécessaire.
Contactez-nous pour discuter de votre cas d’usage et trouver la solution adaptée à votre organisation.