ProjoMania
Odoo 2026年10月22日 · Mohamed Magdy

Odoo 19 to 20: the breaks that install cleanly

We ported a 49-module, multi-company Odoo 19 codebase to Odoo 20. The loud failures were the easy part. Here are the silent ones — modules that install without an error and then produce the wrong numbers — and how we caught them.

Odoo 20 is one of the largest framework jumps in years. We have already ported a 49-module, multi-company Odoo 19 codebase to it — accounting extensions, access-restriction modules, dashboards, procurement, reporting. Every module installs. Every test that passed on 19 passes on 20.

That last sentence took most of the work. Before we got there, a long list of modules installed without a single error in the log and quietly returned wrong results. This post is about those.

The loud changes (budget for them, but they find themselves)

  • Security model. ir.model.access and ir.rule are merged into a single ir.access model. Old CSV access files and record rules have to be converted.
  • OWL 3. The web client moves to OWL 3 and the OWL 2 compatibility layer is removed. Custom widgets, client actions and anything still using legacy or jQuery code need porting.
  • Runtime. Odoo 20 requires Python 3.12–3.14, and pytz is no longer a core dependency — core uses zoneinfo.

Odoo ships codemods for much of this. Run them, but do not trust them blindly. In our port the OWL 3 codemod wrote imports that do not exist in 20 — and a single bad JavaScript import takes down the web client for every module, not just the one that contains it.

The silent changes (these are the ones that cost money)

Each of these installed cleanly and produced wrong output until we compared 19 and 20 side by side on identical data:

  • Financial reports bypass access. Odoo 20 builds the report query with access checks bypassed. A user restricted to a handful of journals saw a general ledger of 100,010 lines instead of 11. If you rely on record rules to separate companies or departments in accounting, re-prove them on 20 — do not assume they carried over.
  • The security codemod changes who can see what. It dropped a rule that gave sales team managers visibility of their team’s orders. And under ir.access, rows are combined with OR: a row without a domain next to a row with one grants everything.
  • Stock valuation is signed. Outgoing moves are now negative. A custom inventory-turnover KPI read −1,162 instead of 7.88, and margins built on the same field went above 100%.
  • Payment states moved. What used to be paid is now reconciled, and in_process is now paid. Every custom payment KPI read “unavailable” until we remapped them.
  • Line descriptions renamed. Invoice, sale and purchase lines carry the text in label, not name. Printed documents with custom layouts came out with blank product descriptions.
  • Core access widened. Stock users and invoicing users now see all sale orders. Banks no longer inherit the partner’s company. Neither is a bug in your code — but both change what your users see.
  • Constraints switched on. SQL constraints that 19 declared but ignored are enforced in 20. Duplicate codes that lived happily in your 19 data will block the data migration until they are cleaned up.

None of these show up as a red line in the install log. A green install is not a working upgrade.

How we caught them

Per module, the gate was not “it installs”. It was:

  1. A 19 baseline first. Install and test every module on a fresh 19 database before changing a line, so every later failure has something to be compared against.
  2. Parity, not just tests. For security modules, the access matrix of every role on 19 and 20 must be identical. For dashboards, the KPI numbers must match. For reports, the printed text must match.
  3. A browser sweep of every menu as an admin and as a restricted user, with zero console errors. Tests do not load OWL components; the sweep does.
  4. A whole-codebase install. Four defects only appeared with all 49 modules installed together — including a crash in Odoo 20’s chart-of-accounts loader that no single module could trigger.

The final run: all 49 modules on one database, 2,522 tests on 20 against 2,518 on 19, and no test that passes on 19 failing on 20.

Code is half of it

Porting the modules is the code half. Moving your data to 20 is the other half: Odoo’s upgrade service for the core, plus data scripts for every stored reference to a renamed field — report layouts that print name, icon values in configuration data, constraint duplicates. Plan both halves before you pick a cutover date.

Should you move now?

If you are on 19 with light customization, 20 is a planned project, not an emergency — 19 is supported until 2028. If you are heavily customized, start with an inventory: which modules touch access rules, payments, stock valuation or printed layouts. That list is your risk register.

The version compatibility matrix shows the complexity read for your exact path, and our Odoo upgrade service covers code and data together. If an upgrade has already gone wrong, talk to us — that is the work we do most.

与我们合作

报价、咨询或探索通话。