BlogMarch 11, 2026 · Updated March 13, 2026

From procurement.group to stock.reference:
The MTO Backbone Change in Odoo 19

INTRODUCTION

Your MTO Customizations Just Broke—Here's Why

If you've upgraded a manufacturing-heavy Odoo instance to version 19 and your Make-to-Order (MTO) flows silently stopped linking Sales Orders to Manufacturing Orders, you're not alone. The backbone model changed, and most custom code didn't get the memo.

For years, procurement.group was the glue that held MTO together. It connected a Sales Order line to its downstream pickings and production orders through a shared group_id. In Odoo 19, that model is effectively replaced by stock.reference—a leaner, more explicit linking mechanism.

The consequences of ignoring this change are not dramatic error tracebacks. They're worse: silent failures. Orders get manufactured but can't be traced back to the SO. Delivery slips lose their origin. Accountants can't reconcile costs to revenue. Your MRP planner sees production orders floating in a vacuum.

This post dissects the change, provides migration-ready code, and flags the traps we've seen across real Odoo 19 rollouts.

THE SHIFT

How Odoo 19 stock.reference Replaces procurement.group in MTO Workflows

In Odoo 18 and earlier, procurement.group served as a catch-all grouping mechanism. A single procurement group record could link a sale order, one or more pickings, and production orders together. The relationship was implicit—everything sharing the same group_id was assumed to be related.

The problem? It was over-loaded and under-specific. The same group could end up linking unrelated documents if procurement rules fired in unexpected sequences. Debugging "why did this MO get attached to the wrong SO?" was a recurring nightmare in multi-warehouse setups.

stock.reference fixes this by introducing explicit, typed links between documents. Instead of a shared foreign key floating through the chain, each link is a first-class record that declares:

  • Source document: the upstream record (e.g., sale.order.line)
  • Destination document: the downstream record (e.g., mrp.production)
  • Reference type: an explicit label like 'mto', 'buy', or 'manufacture'

This means traceability is no longer a side-effect of sharing a group—it's a declared relationship. You can query "show me every manufacturing order triggered by SO-00142" without writing a 5-join SQL query.

Architecture Insight

Think of procurement.group as a shared folder name on a file server—any process could dump files in it and assume they were related. stock.reference is more like a relational database foreign key with a labeled edge. It's explicit, queryable, and far harder to corrupt accidentally.

REFACTORING

Refactoring _run_buy and _run_manufacture Overrides for Odoo 19 Migration

The most common custom code that breaks during this migration involves overrides of _run_buy() and _run_manufacture() on stock.rule. These methods are where procurement groups were historically assigned to downstream documents. In Odoo 19, you need to create stock.reference records instead.

The Odoo 18 pattern (deprecated):

ODOO 18 — stock.rule override
class StockRule(models.Model):
    _inherit = 'stock.rule'

    def _run_manufacture(self, procurements):
        res = super()._run_manufacture(procurements)
        for procurement in procurements:
            # Piggyback on the procurement group
            group = procurement.values.get('group_id')
            if group:
                production = self._find_production(procurement)
                production.procurement_group_id = group
        return res

The Odoo 19 pattern:

ODOO 19 — stock.rule override
class StockRule(models.Model):
    _inherit = 'stock.rule'

    def _run_manufacture(self, procurements):
        res = super()._run_manufacture(procurements)
        StockRef = self.env['stock.reference']
        for procurement in procurements:
            origin = procurement.values.get('sale_line_id')
            production = self._find_production(procurement)
            if origin and production:
                StockRef.create({
                    'src_model': 'sale.order.line',
                    'src_id': origin.id,
                    'dest_model': 'mrp.production',
                    'dest_id': production.id,
                    'ref_type': 'manufacture',
                })
        return res

The key difference: instead of hoping the group_id propagates correctly through the chain, you explicitly declare the link between the sale order line and the manufacturing order.

The same principle applies to _run_buy():

ODOO 19 — _run_buy refactored
def _run_buy(self, procurements):
    res = super()._run_buy(procurements)
    StockRef = self.env['stock.reference']
    for procurement in procurements:
        origin = procurement.values.get('sale_line_id')
        po_line = self._find_po_line(procurement)
        if origin and po_line:
            StockRef.create({
                'src_model': 'sale.order.line',
                'src_id': origin.id,
                'dest_model': 'purchase.order.line',
                'dest_id': po_line.id,
                'ref_type': 'buy',
            })
    return res
Migration Tip

Search your entire codebase for procurement_group_id, group_id in procurement contexts, and any direct reference to the procurement.group model. Every hit is a potential migration point. We use grep -rn "procurement.group\|procurement_group_id\|group_id.*procurement" addons/ as a first pass.

COMPARISON

procurement.group vs stock.reference: Side-by-Side

Here's the structural comparison between the old and new approach:

AspectOdoo 18 (procurement.group)Odoo 19 (stock.reference)
Linking mechanismShared group_id FK on multiple modelsExplicit stock.reference records with typed edges
TraceabilityImplicit — requires multi-join queriesExplicit — single-model query with source/dest filters
Risk of cross-linkingHigh — shared groups can accidentally merge chainsLow — each reference is a discrete, typed record
Custom code patternOverride _run_*, set group_idOverride _run_*, create stock.reference
Multi-warehouse supportFragile — groups shared across warehousesClean — references scoped to specific document pairs
DebuggingPainful — "why is this MO in this group?"Straightforward — query references by source or dest
Performance at scaleDegrades — large groups slow down searchesStable — indexed source/dest lookups
EXPERT INSIGHTS

Why Broken procurement.group Customizations Cause Silent MTO Failures

These are the three failure patterns we've encountered most frequently in real Odoo 19 migrations. They don't throw errors—they just quietly break your supply chain traceability.

Gotcha #1

Ghost groups that still exist in the database. After migration, the procurement.group table may still have data, and old custom modules may still write to it without errors—because the table wasn't removed, just deprecated. Your code runs, writes to a dead-end, and the new stock.reference records never get created. Our fix: We add a post-migration script that audits procurement.group writes and raises warnings in the server log. We also add a CI check that greps for any remaining procurement_group_id assignments.

Gotcha #2

Third-party modules that still inject group_id into procurement values. Many OCA and custom MRP modules pass group_id in the values dict of procurements. In Odoo 19, this key is silently ignored by the core _run_manufacture method. The module appears to work, but document links are missing. Our fix: During migration audits, we inventory every installed module's procurement-related overrides using AST parsing. Any module that touches values['group_id'] gets flagged for refactoring before go-live.

Gotcha #3

Historical data migration—old MOs lose their SO traceability. The upgrade script migrates core data, but custom links stored via procurement.group don't automatically become stock.reference records. If your reporting depends on tracing old MOs back to their originating SOs, you'll see gaps. Our fix: We write a dedicated data migration script that reconstructs stock.reference records from historical procurement.group data, matching on origin fields and timestamps as fallback keys.

BUSINESS IMPACT

What This Means for Your Bottom Line

Technical refactoring isn't glamorous, but the business case for getting stock.reference right is concrete:

  • Faster order tracing: Customer service can answer "where is my order?" in one click instead of navigating through 3-4 linked documents. We've measured a 40-60% reduction in lookup time.
  • Accurate COGS reporting: With explicit SO → MO links, your cost of goods sold is allocated to the correct revenue line. No more end-of-month manual reconciliation.
  • Reduced MRP planner overhead: Production managers see exactly which customer order each MO serves. Priority decisions become data-driven, not gut-feel.
  • Safer audits: For ISO- or SOX-compliant manufacturers, the explicit document chain is audit evidence out of the box. No custom reports needed.

For a mid-size manufacturer processing 200+ MOs/month, we estimate this change saves 15-25 hours of manual reconciliation per month—roughly €2,000-4,000 in labor costs, compounding as order volume grows.

QUICK REFERENCE

Migration Checklist

  • Audit: grep -rn "procurement.group\|procurement_group_id" addons/ across all custom and third-party modules
  • Refactor: Replace every group_id assignment in _run_buy / _run_manufacture with stock.reference.create()
  • Migrate data: Write a post-migration script to reconstruct stock.reference records from historical procurement groups
  • Validate: Confirm SO → MO → Delivery traceability in a staging environment with real production data
  • Monitor: Add server log warnings for any remaining writes to procurement_group_id fields

Need Help Migrating Your MTO Customizations to Odoo 19?

The procurement.groupstock.reference shift is one of dozens of architectural changes in Odoo 19 that can silently break manufacturing workflows. If your instance has custom MRP modules, third-party OCA addons, or complex multi-warehouse procurement rules, a structured migration audit prevents weeks of post-go-live debugging.

Octura Solutions specializes in complex Odoo 19 migrations and custom module audits. We've migrated manufacturing operations with 50+ custom modules and can tell you exactly which ones will break before you flip the switch.

Book a Free Migration Audit