Odoo ships a major release every year. That is a good thing: it is how the accounting reports, the website builder and the mobile apps keep improving without anyone paying for a rewrite. But it also means a system that is not maintained falls behind quickly, and we meet businesses running versions that came out before some of their staff joined.
The version number is never the real problem. The real problem is what stops them moving.
What actually blocks an upgrade
In almost every stuck system we have audited, the blocker is the same: somebody changed Odoo's own source instead of writing a module beside it. It is quicker on the day. A field gets added directly to a core view, a method gets edited in place, a report template gets rewritten where it sits. Everything works. Everyone is happy.
Then the next version lands and those files have moved, been renamed, or been rewritten upstream. There is no clean way to carry the change forward, because the change was never separable in the first place. The upgrade stops being a technical task and becomes an archaeology project.
The second most common blocker is data. Fields that were filled in by hand with inconsistent values, records that were imported twice, an opening balance that never reconciled. None of that breaks day-to-day use, so nobody fixes it, and then it surfaces in the middle of a migration when the pressure is highest.
What a maintainable customisation looks like
A custom module should be a separate addon that can be switched off without taking the database with it. It should extend Odoo's models through inheritance rather than editing them. It should have tests around anything that touches money, stock or payroll, so that an upgrade tells you it broke before your accountant does. And it should have a paragraph of documentation explaining why it exists, written for whoever inherits it in three years.
None of that is exotic. It is the standard way Odoo is meant to be extended. It just takes slightly longer on the day than editing the file in front of you.
If you are already stuck
It is fixable, and it is fixable in a defined order. Audit what you have: version, module list, code quality, data integrity, server health. Separate the customisations that earn their place from the ones that were a workaround for a process nobody agreed. Port the first group properly and delete the second. Clean the data on a copy, not on production. Then upgrade in a rehearsed run before you do it for real.
What you should not do is treat the upgrade as a single event to be survived. The businesses that never get stuck are the ones that move one version a year as routine maintenance, which is boring, cheap, and the entire point.
If your Odoo is a few versions behind and nobody is sure what would break, a written audit is a good first step. It costs a fixed amount, it produces a plan with options, and you can take that plan to anyone.