Case study · Distribution and wholesale

Stabilize before you migrate: how a wholesale distributor recovered the truth of its inventory in Odoo

A real wholesale case, anonymized. A 230 GB ERP with 61 custom fields was ready to jump from Odoo 16 to Odoo 19. Before migrating, we cleaned up the truth of its inventory. Along the way we discovered that the costing method was hiding, on at least one product, the fact that the company was selling with no margin at all.

Migrating a large ERP is not copying a system from one version to another. It also carries over everything that system had wrong. When the foundation is dirty, migration does not clean it: it inherits it, makes it more expensive to fix, and on top of that slaps on the reassuring label of "new system." This piece describes how a wholesale distributor of lighting and project furniture in Mexico got ready to upgrade from Odoo 16 to Odoo 19, and why the first deliverable was not the migration, but the stabilization of its inventory.

The result of that preliminary phase was uncomfortable and valuable at the same time: the configured costing method was distorting the financial reading of the business. What follows are the real numbers from the diagnostic, anonymized, and the transferable principle they left behind.

Dashboard with four diagnostic indicators: a 230 GB database, 61 custom fields to update, 3 validated costing methods, and -36.6 percent deflation detected in the valuation of a FIFO scenario.
Four figures from the diagnostic, read straight from the project's stabilization documentation. No rounding.

The mirage of the clean migration

The most common advice when an Odoo version nears its end of support is blunt: upgrade. Odoo 16 would stop receiving support, the calendar pressure was real, and the obvious route seemed to be moving the database to version 19 and carrying on operating.

The problem is that "just upgrade" assumes that what gets carried over is healthy. In this case it was not. The starting point was an ERP with history:

Migrating all of that without reviewing it first is not a migration; it is moving house without opening the boxes to see what is inside. And inside there was a problem no version upgrade was going to solve on its own.

The diagnostic: an ERP that no longer told the truth

Before touching the version, we did what almost no one does: instead of trusting the numbers the system displayed, we put them to the test. The question was not "how do we migrate," but "can we trust what this inventory is telling us today."

The answer came through costing. A wholesale distributor lives on a margin that is often thin; if the system miscalculates the cost of what it sells, the company makes pricing decisions based on a reality that does not exist. That is why the core of the stabilization was to validate, with real products, the three costing methods Odoo can apply.

The test almost no one runs: validate costing before you trust it

Odoo lets you value inventory using three methods. In two lines each, for anyone who does not live in cost accounting:

It is not about which is "the best" in the abstract. It is about which one is correctly applied for each product family, and above all whether the numbers it produces are believable. Three scenarios were documented, one per method, with real products from the catalog. And that is where the finding appeared.

The climax: when costing lies

In the FIFO scenario, on a real product from the acoustic treatment category, the numbers told a story the operation had not noticed:

Chart contrasting the margin the operation assumed against the real recorded margin of 0 percent, alongside the -36.6 percent deflation in the valuation and the 990 units with no documented origin.
On one product under FIFO: recorded margin of 0%, deflation of -36.6%, and 990 units with no origin. The system showed stability; the test showed the opposite.

Each of those figures, on its own, is a yellow warning light. Together, on a single product, they are a diagnostic: the inventory was not a source of truth, it was a source of misplaced confidence. Had that foundation been migrated as-is to Odoo 19, the company would have rolled out a more modern system to keep making decisions on distorted data. The new version does not fix badly grounded costing; it inherits it with a better appearance.

The cure was not to re-implement, it was to stabilize

It is tempting to answer a finding like this with a full redesign. It was not necessary. The cure was surgical: give the inventory back its traceability, not reinvent it.

A central piece of that work was the bulk stock adjustment, done the right way. In a wholesale catalog with thousands of references, two products can share a name and still be different: same commercial name, different brand or line. If you try to adjust inventory by name or by internal reference, Odoo does not know which of the two to apply the change to and fails, or worse, applies it to the wrong one.

Diagram: two products with the same commercial name but a different brand collide in a name-based adjustment; Odoo's unique external identifier disambiguates and routes the adjustment to the correct record.
Odoo's external identifier removes the ambiguity: every adjustment reaches the exact record, not the one with the similar name.

The solution was to anchor each adjustment to the unique external identifier Odoo assigns to every variant, not to its name. With that anchor, the bulk load stops guessing: each counted quantity lands on the correct variant, with its date and its location. Physical stock and system stock line up again, which is the minimum condition for any subsequent costing to mean anything.

Stabilize before you migrate, the full flow

Order matters. This is the flow that separated stabilization from migration, and why each step comes before the next.

flowchart LR
    A[Legacy ERP<br/>230 GB, 61 fields] --> B{Are the numbers<br/>believable?}
    B -->|Validate costing<br/>3 methods, real products| C[Finding:<br/>0% margin, -36.6% deflation]
    C --> D[Stabilize:<br/>adjust by external ID]
    D --> E[Reconciled inventory<br/>physical = system]
    E --> F{Now yes:<br/>migrate to v19}
    F --> G[Odoo 19<br/>on a trustworthy base]

    classDef riesgo fill:#3d0a0a,stroke:#f87171,stroke-width:1px,color:#fee2e2;
    classDef prueba fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#e2e8f0;
    classDef cura fill:#052e16,stroke:#34d399,stroke-width:1px,color:#e2e8f0;

    class A,C riesgo
    class B,D prueba
    class E,F,G cura

Diagram: in red the inherited risk, in purple the tests that reveal it, in green the trustworthy base on which migrating is finally worth it.

The transferable principle

What this wholesaler learned applies to any distributor running Odoo and thinking about a version upgrade. It is not a product recipe, it is a sequence of judgment:

  1. Do not confuse upgrading with cleaning up. End of support forces you to migrate; it does not force you to migrate the mess. They are two projects, and the cleanup goes first.
  2. Put costing to the test with real products, not with theory. A well-chosen method that is badly applied produces numbers that look healthy and are not.
  3. Treat a 0% margin as an alarm, not as a data point. In wholesale, a margin the system reports at zero is almost never reality; it is a signal that costing lost the thread.
  4. Adjust inventory by unique identifier, never by name. In large catalogs, names repeat. The external identifier is the only reliable anchor.
  5. Reconcile physical against system before migrating. If those two numbers do not match today in the old version, they will not match tomorrow in the new one; they will just look more official.
  6. Migrate when the base tells the truth, not when the calendar squeezes. The urgency is real, but a migration on distorted data costs more to fix afterward than to stabilize beforehand.

Closing: migration is the second problem

The underlying lesson is simple and counterintuitive. When a company feels that "its inventory does not add up" or that "the margin the system reports does not feel real," the instinct is to migrate to something better. But the ERP version is almost never the problem. The problem is that the base lost its truth, and no new software gives it back on its own.

Stabilizing before you migrate is not just one more technical step. It is the difference between rolling out a trustworthy system and rolling out one that lies with a better interface. First the truth of the inventory. Then the version.

If your distribution business runs Odoo and is thinking about a version upgrade, the conversation starts at [email protected]. We bring the stabilization method and the judgment for when it does, or does not, make sense to migrate yet.


Note on accuracy and privacy

The figures in this article (a 230 GB database, 61 custom fields, close to 29,000 lines of Python and 12,000 of XML, and in a FIFO costing scenario: a 0% margin, -36.6% deflation, 11.4% rotation, and 990 units with no documented origin) come from the stabilization documentation of a real wholesale project executed between January and June 2026. No figure was estimated or rounded. The client is presented anonymously: the company, its brands, its suppliers, and the people involved are not named, and the article focuses exclusively on the technical learning. No personal data was read or cited; only operational aggregates.

← Back to Blog