Blog11 mars 2026 · Mis à jour le 13 mars 2026

Changement de logique :
La fin de uom.category dans Odoo 19

INTRODUCTION

Votre module inventaire vient de perdre son filet de sécurité

Si vous planifiez une migration Odoo 18 → 19 et que votre entreprise utilise des Unités de Mesure personnalisées, lisez cet article avant de lancer la mise à jour.

Odoo 19 a complètement supprimé le modèle uom.category. Pas déprécié, pas masqué derrière un paramètre — supprimé du code source. À sa place, un nouveau champ relative_uom_id sur uom.uom définit les relations de conversion directement entre unités.

Pourquoi est-ce important ? Parce que chaque module personnalisé qui référence uom.category — qu'il s'agisse d'un rapport d'inventaire, d'un calculateur de nomenclature (BOM), ou d'un outil de comparaison de prix d'achat — va planter lors de la mise à jour. Et si la migration des données est mal gérée, vos valorisations de stock historiques, vos calculs de coût moyen et vos couches FIFO peuvent se corrompre silencieusement.

Nous l'avons découvert lors d'une migration pilote pour un distributeur alimentaire multi-entrepôts. Leur logique UdM personnalisée touchait 14 modules. Cet article est le guide que nous avons construit pour résoudre le problème.

01

Comment Odoo 19 a restructuré les Unités de Mesure (et pourquoi c'est important)

Dans Odoo 18 et versions antérieures, les conversions d'UdM étaient organisées via une hiérarchie basée sur les catégories :

  • uom.category regroupait les unités liées (ex. : « Poids » contenant kg, g, lb, oz)
  • Chaque enregistrement uom.uom appartenait à exactement une catégorie via category_id
  • Au sein d'une catégorie, une unité était marquée comme « référence » (uom_type = 'reference')
  • Les conversions entre unités nécessitaient le même category_id

Le modèle Odoo 19 élimine complètement la table intermédiaire. À la place :

  • Chaque enregistrement uom.uom possède désormais un champ relative_uom_id pointant directement vers l'unité depuis laquelle il se convertit
  • L'unité de référence pointe vers elle-même (relative_uom_id = self)
  • Les champs factor et rounding subsistent, mais expriment le ratio par rapport à relative_uom_id
  • La conversion inter-unités est résolue en remontant la chaîne relative_uom_id jusqu'à un ancêtre commun

C'est architecturalement plus propre. Cela supprime une table SQL entière, simplifie le chemin de requête ORM pour les conversions et — point crucial — permet à Odoo de prendre en charge nativement les conversions chaînées multi-étapes que le modèle plat par catégorie ne pouvait pas exprimer sans bricolage.

Insight architectural

L'ancien modèle uom.category imposait une contrainte stricte : impossible de convertir entre catégories. Logique pour empêcher les conversions « kg vers heures », mais cela bloquait aussi des cas légitimes comme la conversion d'unités de conditionnement (ex. : « palette de 48 caisses » → « caisse de 12 unités » → « unité »). La nouvelle chaîne relative_uom_id gère cela nativement.

02

Logique de migration des données : transférer le code UdM d'Odoo 18 vers 19

Les scripts de mise à jour officiels gèrent les enregistrements uom.uom standard. Mais si vous avez des modules personnalisés qui référencent uom.category — clés étrangères, filtres de domaine, champs calculés ou logique Python — vous avez besoin d'une stratégie de migration manuelle.

Voici le modèle de migration que nous utilisons chez Octura Solutions :

Pythonmigration/19.0.1.0/pre-migrate.py
from odoo import api, SUPERUSER_ID
import logging

_logger = logging.getLogger(__name__)


def migrate(cr, version):
    """
    Pré-migration : Remapper les références uom.category
    vers relative_uom_id avant la suppression de la table.
    """
    env = api.Environment(cr, SUPERUSER_ID, {})

    # Étape 1 : Construire un mapping catégorie → UdM de référence
    cr.execute("""
        SELECT id, category_id
        FROM uom_uom
        WHERE uom_type = 'reference'
    """)
    cat_to_ref = {row[1]: row[0] for row in cr.fetchall()}

    # Étape 2 : Pour chaque UdM non-référence, définir relative_uom_id
    # vers l'unité de référence de son ancienne catégorie
    cr.execute("""
        SELECT id, category_id
        FROM uom_uom
        WHERE uom_type != 'reference'
    """)
    for uom_id, cat_id in cr.fetchall():
        ref_id = cat_to_ref.get(cat_id)
        if ref_id:
            cr.execute("""
                UPDATE uom_uom
                SET relative_uom_id = %s
                WHERE id = %s
            """, (ref_id, uom_id))
        else:
            _logger.warning(
                "UdM %s : aucune unité de référence trouvée "
                "pour la catégorie %s — correction manuelle requise",
                uom_id, cat_id,
            )

    # Étape 3 : Les unités de référence pointent vers elles-mêmes
    cr.execute("""
        UPDATE uom_uom
        SET relative_uom_id = id
        WHERE uom_type = 'reference'
    """)

    # Étape 4 : Remapper les clés étrangères personnalisées
    # Exemple : custom_inventory_report.uom_category_id
    cr.execute("""
        UPDATE custom_inventory_report r
        SET reference_uom_id = u.id
        FROM uom_uom u
        WHERE u.category_id = r.uom_category_id
          AND u.uom_type = 'reference'
    """)

    _logger.info(
        "Pré-migration terminée : %d catégories remappées",
        len(cat_to_ref),
    )

Principes clés derrière ce script :

Pré-migration

Exécutez ce script avant que les scripts officiels d'Odoo ne suppriment la table uom_category. Si vous l'exécutez après, les données de catégorie sont déjà perdues.

SQL brut

Nous utilisons cr.execute() au lieu de l'ORM car pendant la migration, les définitions de modèle peuvent ne pas correspondre au schéma de la base. Le SQL brut est la seule approche sûre.

Idempotent

Le script peut être exécuté plusieurs fois sans corrompre les données. Si relative_uom_id est déjà défini, l'UPDATE écrase simplement avec la même valeur.

Piste d'audit

Chaque UdM orpheline (pas d'unité de référence dans sa catégorie) est journalisée en warning. Cela détecte les problèmes de qualité de données avant qu'ils ne corrompent les valorisations.

03

Ancienne méthode vs. Odoo 19 : comparaison côte à côte

Ce tableau résume chaque changement majeur impactant le code des modules personnalisés :

AspectOdoo 18
(modèle uom.category)
Odoo 19
(relative_uom_id)
Modèle de donnéesuom.category + uom.uom (deux tables)uom.uom uniquement (une seule table)
Unité de référenceuom_type = 'reference' par catégorierelative_uom_id = self
Garde de conversionContrainte stricte : même category_id requisRésolu en remontant la chaîne relative_uom_id
Filtres de domaine[('category_id', '=', ref.category_id.id)][('relative_uom_id', '=', ref.relative_uom_id.id)]
Données XML<record model="uom.category"> requisPas d'enregistrement catégorie ; relative_uom_id directement
Droits d'accèsRègles ACL sur uom.category et uom.uomRègles ACL sur uom.uom uniquement
Conversion multi-étapesNon supportée nativement (code custom nécessaire)Intégrée via la traversée de la chaîne d'ancêtres
API Pythonuom._compute_quantity() vérifie category_iduom._compute_quantity() remonte relative_uom_id

En résumé : Tout code qui importe, interroge ou filtre sur uom.category doit être refactorisé. Cela inclut les vues XML avec des filtres de domaine category_id, les règles de sécurité référençant le modèle, et tout appel Python self.env['uom.category'].

RETOURS D'EXPÉRIENCE

3 pièges qui cassent les migrations UdM Odoo 19

Nous avons migré plusieurs bases de données à travers ce changement. Voici les pièges qui attrapent même les développeurs expérimentés :

Piège n°1

Catégories orphelines sans unité de référence. Dans Odoo 18, il était possible de créer un enregistrement uom.category et d'ajouter des unités sans en marquer une comme référence (surtout via des imports SQL directs ou des connecteurs tiers). Lors de la migration, ces unités n'ont pas de cible pour relative_uom_id — et l'UPDATE les ignore silencieusement. Après migration, tout mouvement de stock référençant ces unités lèvera une ValueError. Notre solution : nous exécutons une requête d'audit pré-vol qui signale chaque catégorie sans unité de référence, et nous en assignons une avant la migration.

Piège n°2

Champs calculés qui cachent category_id. De nombreux modules personnalisés stockent category_id dans un champ related ou compute pour la performance. Après migration, ces champs référencent une colonne qui n'existe plus. L'ORM ne lèvera pas d'erreur lors de la mise à jour du module — il retourne silencieusement False. Vos rapports affichent des blancs, et personne ne s'en aperçoit jusqu'à la clôture mensuelle. Notre solution : nous parcourons tout le code à la recherche de références à category_id avant la migration et construisons une checklist de refactorisation.

Piège n°3

Recalcul des valorisations de stock historiques. Si vos couches FIFO ou coût moyen ont été calculées avec une méthode _compute_quantity() qui référençait l'ancienne conversion par catégorie, les montants stockés sont corrects — mais les métadonnées du chemin de conversion peuvent être obsolètes. Si quelqu'un déclenche un recalcul post-migration (ex. : ajustement d'inventaire), la nouvelle logique relative_uom_id peut produire des résultats d'arrondi légèrement différents. Notre solution : nous gelons les couches de valorisation avant migration avec une table snapshot, puis exécutons un script de réconciliation post-migration pour vérifier qu'aucune couche n'a dérivé au-delà de la précision d'arrondi configurée.

Protocole Octura

Pour chaque migration UdM, nous livrons un Rapport d'Impact de Migration avant de toucher à la production : un document listant chaque module affecté, chaque champ remappé, et une matrice de tests couvrant les mouvements de stock, les explosions de nomenclature, les calculs de prix d'achat et les écritures comptables. Aucune surprise le jour du go-live.

04

Pourquoi ce changement technique fait économiser de l'argent à votre entreprise

Pour le lecteur non-technique : voici ce que la suppression de uom.category signifie concrètement pour votre résultat net.

Requêtes plus rapides

Chaque mouvement de stock, chaque ligne de commande, chaque explosion de nomenclature effectue une conversion d'UdM. Éliminer le JOIN vers uom_category réduit le temps de requête. Sur une base avec 500K+ mouvements de stock, nous avons mesuré une amélioration de 12 à 18% dans la génération des rapports de valorisation d'inventaire.

Maintenance simplifiée

Un modèle de moins signifie moins de règles ACL à maintenir, moins d'enregistrements à exporter/importer, et moins d'endroits où les erreurs de configuration peuvent se cacher. Pour les entreprises gérant 50+ UdM personnalisées sur plusieurs entrepôts, cette simplification réduit la surface d'attaque pour les bugs.

Pérennité

L'architecture relative_uom_id est clairement la direction qu'Odoo emprunte. Migrer maintenant signifie que votre prochaine mise à jour (v19 → v20) sera plus fluide et moins chère. Reporter signifie accumuler de la dette technique sur deux sauts de version.

Exemple concret : Pour un fabricant de taille moyenne avec 200 produits et 8 UdM personnalisées, une migration propre coûte environ 3 000 à 5 000 € en temps de conseil. Une migration bâclée qui corrompt les valorisations de stock ? Nous avons vu des factures de remédiation dépasser 25 000 € — sans compter la perturbation métier d'un entrepôt gelé pendant 2 semaines le temps de réparer les données.

Ne laissez pas un changement de schéma geler votre entrepôt

La suppression de uom.category est l'un des changements structurels les plus impactants d'Odoo 19 pour les entreprises à forte composante inventaire. Ce n'est pas optionnel, ce n'est pas reportable, et ce n'est pas quelque chose que votre équipe devrait découvrir en pleine mise à jour.

Si votre instance Odoo a une logique UdM personnalisée, des rapports d'inventaire custom, ou tout module qui touche à uom.category — faites un audit de migration avant de lancer le processus de mise à jour.

Octura Solutions est spécialisé dans les migrations Odoo complexes et les audits de modules personnalisés. Nous cartographierons chaque module affecté, écrirons les scripts de migration, et validerons vos valorisations de stock de bout en bout. Pas de suppositions. Pas de données corrompues. Pas d'entrepôts gelés.

Réserver un audit de migration gratuit →