- Odoo
- Python
- ERP
Développer un module Odoo 19 qui survit aux mises à jour
Les règles que j'applique pour qu'un module métier sur-mesure ne casse pas à la montée de version : héritage propre, record rules et séparation des responsabilités.
Un module Odoo écrit vite fonctionne dès le premier jour. Le problème arrive six mois plus tard, à la mise à jour : des champs ont disparu du standard, une vue ne se charge plus, et le client découvre que son POS ne s'ouvre plus. Voici ce qui change tout.
Ne jamais écraser, toujours hériter
La tentation est de modifier directement un modèle standard. En pratique, on étend :
class PosSession(models.Model):
_inherit = "pos.session"
x_control_caisse_id = fields.Many2one(
"christo.control.caisse",
string="Contrôle de caisse",
copy=False,
index=True,
)Deux détails qui comptent : le préfixe sur les champs ajoutés, qui garantit qu'aucune version future d'Odoo n'introduira une collision de nom, et copy=False, pour qu'une duplication d'enregistrement ne traîne pas un lien de contrôle obsolète.
Les vues : xpath plutôt que remplacement
Remplacer une vue complète revient à geler la version d'Odoo au jour de l'écriture. L'héritage ciblé résiste bien mieux :
<record id="view_pos_session_form_inherit" model="ir.ui.view">
<field name="inherit_id" ref="point_of_sale.view_pos_session_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='session_info']" position="inside">
<field name="x_control_caisse_id" readonly="state != 'opened'"/>
</xpath>
</field>
</record>Si le groupe ciblé disparaît, l'erreur est explicite au chargement du module, pas trois semaines plus tard au détour d'un écran.
Les record rules sont la seule vraie frontière
Dans un contexte multi-boutiques, filtrer côté vue ne protège rien : l'utilisateur accède toujours aux données par l'API ou l'export. Le cloisonnement se fait au niveau de l'ORM.
<record id="rule_stock_picking_boutique" model="ir.rule">
<field name="model_id" ref="stock.model_stock_picking"/>
<field name="domain_force">
[('picking_type_id.warehouse_id', 'in', user.boutique_ids.ids)]
</field>
<field name="groups" eval="[(4, ref('group_responsable_boutique'))]"/>
</record>Un point souvent oublié : une règle sans groups s'applique à tout le monde, y compris aux utilisateurs techniques. Il faut toujours vérifier le comportement avec un compte de test non administrateur, jamais avec le superutilisateur.
Séparer le calcul métier des modèles
Les méthodes de calcul longues (réconciliation d'encaissements, valorisation de transferts inter-boutiques) n'ont pas leur place dans un compute. Un champ calculé est réévalué à chaque lecture du recordset ; une réconciliation sur plusieurs milliers de lignes transforme alors un simple affichage de liste en requête de plusieurs secondes.
Je préfère un champ stocké, mis à jour explicitement :
montant_reconcilie = fields.Monetary(compute="_compute_reconcilie", store=True)
@api.depends("line_ids.amount", "line_ids.state")
def _compute_reconcilie(self):
for record in self:
record.montant_reconcilie = sum(
line.amount for line in record.line_ids if line.state == "done"
)Le @api.depends est ce qui rend le champ stocké fiable : sans dépendance correcte, la valeur se désynchronise silencieusement.
Tester la montée de version avant le client
La vérification minimale avant toute livraison :
- Restaurer une copie de la base de production sur un environnement de staging.
- Lancer
odoo -u mon_module --stop-after-initet lire tous les avertissements, pas seulement les erreurs. - Ouvrir chaque vue modifiée avec un compte utilisateur réel.
Cette séquence prend vingt minutes. Une mise à jour ratée en production, sur un système de caisse, coûte une journée de chiffre d'affaires.
Articles liés
Intégrer les API de paiement des agrégateurs sans se faire piéger
Mobile Money, cartes bancaires, agrégateurs multiples : le modèle unifié, l'idempotence et la réconciliation qui évitent les doubles débits en production.
- Paiement
- FinTech
- Architecture
- API
Ma méthode pour un front-end Angular qui reste maintenable
Découpage par domaine, composants standalone, signaux contre RxJS, intercepteurs et détection de changement : les décisions que je prends au démarrage d'un projet Angular.
- Angular
- TypeScript
- Architecture
- Frontend