The call usually comes about eleven months in. Go-live happened, sort of. Finance is still closing the month in a spreadsheet, the warehouse team keeps a parallel notebook, and someone has quietly stopped using the CRM entirely. The system technically works. Nobody trusts it.
Odoo rarely fails because Odoo is bad software. It fails because the implementation encoded somebody's misunderstanding of the business into 40 custom modules, and now every fix breaks two things. Panorama Consulting's 2026 ERP Report still puts cost overruns and schedule slippage among the most persistent problems across ERP projects of every size — and in our experience the overrun is almost never in licensing. It's in the rework.
Here's the triage sequence we actually run when we inherit a broken deployment, in the order we run it.
Step 1: Read the database before you read the code
Skip the codebase for a day. Open PostgreSQL and ask the system what people are really doing.
A few queries tell you more than a week of stakeholder interviews:
-
Row counts per model over time. If
crm.leadstopped growing in March, sales abandoned the module in March. Nobody will volunteer this in a meeting. -
ir.model.fieldswherestate = 'manual'. Every field created through Studio or a quick patch. A healthy mid-size deployment has a few dozen. We've opened databases with 900. That number is a direct measure of how much undocumented business logic is floating outside version control. -
stock.quantversus what the warehouse counts. Any gap here means inventory valuation is wrong, which means the P&L is wrong, which is usually the real reason finance went back to Excel. -
ir_cronlast-run timestamps and failure counts. Silently dead crons are behind a surprising share of "the system doesn't update" complaints. -
Direct SQL writes. Grep the custom modules for
self.env.cr.executewithUPDATEorINSERT. Every one of those bypasses the ORM, so computed fields never recomputed and stored values are now lying to you.
This step tells you which subsystems are actually broken versus which ones are merely unloved. They need very different fixes and people conflate them constantly.
Step 2: Separate the four failure types
Almost every rescue we've run sorts into four buckets, and the diagnosis determines everything after it.
| Failure type | Symptom | Typical fix | Effort |
|---|---|---|---|
| Process mismatch | Users maintain shadow spreadsheets; the Odoo flow needs 9 clicks for something they do 200 times a day | Re-map the process, then reconfigure — usually less code, not more | Medium |
| Bad customization | Core modules edited in place, monkeypatched methods, upgrade-blocking overrides | Refactor to proper inheritance; delete what duplicates standard behaviour | High |
| Dirty data | Duplicate res.partner records, opening balances that don't tie, negative stock |
Cleanse, reconcile, re-open a controlled migration window | Medium–High |
| No ownership | It works, but nobody was trained and no one owns the master data | Internal key-user model, documentation, defined data stewardship | Low cost, high resistance |
Type 4 is the one clients least want to hear and the one that most often decides whether the rescue holds a year later. You can fix code in a sprint. You cannot fix "no one owns the product master" with a sprint.
Step 3: Audit the customizations against the upgrade path
This is where most inherited deployments are genuinely dangerous. Odoo only provides support and bug fixes for its three most recent major versions (supported versions policy), so a deployment stuck two versions back on unupgradeable custom code has a hard deadline whether or not anyone has written it down.
What we look for, roughly in descending order of how much it will hurt:
-
Edits to core addons in place. Someone changed
addons/sale/models/sale_order.pydirectly. Every future update overwrites it. This is the single most common cause of "we can't upgrade." -
Overrides that duplicate standard functionality. A custom pricing engine that reimplements pricelists. A custom approval flow where
base.group_rules and activities would have done it. Deleting code is the highest-ROI work in a rescue. -
Missing or wrong
dependsdeclarations on computed fields — the reason stored values drift from reality and nobody can reproduce it on demand. -
Data model abuse.
charfields holding what should bemany2onerelations, so reporting is impossible without string parsing.
Practical note: run the audit against a restored production copy on the target version with OCA's OpenUpgrade scripts, not against a clean demo database. Demo data hides exactly the constraint violations you need to find.
By the end of this step you should be able to sort every custom module into keep, refactor, or delete — with a rough hour estimate on each. That list, not a strategy deck, is the rescue plan. When we scope this as an Odoo consulting partner brought in mid-project, the delete column is usually the largest of the three, and clients are consistently surprised by that.
Step 4: Stabilise before you improve
There's a strong temptation to fix everything in one heroic release. Don't. A failed implementation has already spent the organisation's patience, and the next visible failure is often fatal to the project politically.
Sequence it:
- Weeks 1–2: stop the bleeding. Fix the crons, correct inventory valuation, get one clean month-end close. Nothing else.
- Weeks 3–6: retire the shadow systems, one team at a time. Pick the team with the most pain and the least political weight. Get a real win you can point to.
- Weeks 7+: the refactor and upgrade path. Only now touch the custom module debt, with tests.
The stabilisation phase produces almost no new features and it is the phase that saves the project. A single clean close does more for user trust than any amount of roadmap.
When rescue is the wrong answer
Sometimes the honest recommendation is to stop patching. We've walked into two situations where a rebuild on a fresh database was cheaper than a rescue:
The first is when the chart of accounts itself was configured wrong at go-live and a year of transactions sits on top of it. Restructuring live accounting data is slow, risky, and auditors hate it. Starting a new database with correct configuration and migrating open balances only is frequently the lower-risk path.
The second is when custom code exceeds roughly half the functional footprint. At that point you aren't running Odoo, you're running a bespoke application that happens to import from Odoo, and every upgrade will cost what the original build cost. Rebuilding against standard functionality — accepting that a few processes will change to match the software instead of the reverse — is usually cheaper over three years.
Both conversations are uncomfortable, and both are better had in month one of the rescue than month eight. The trade-off is real, though: a rebuild resets user trust to zero a second time, and some organisations genuinely cannot survive that. If adoption is already fragile, a slower rescue on flawed foundations can be the right call even when it's the technically worse one.
Where to start tomorrow
If you're currently living inside a struggling implementation, do one thing this week: restore a production copy to a staging server and run the queries in Step 1. Count the manual fields. Check the cron failures. Diff stock.quant against a physical count on one warehouse.
You'll have a factual picture of what's broken within a day, and it will almost certainly differ from the story being told in status meetings. Every rescue we've run well started with that gap.
The team at Master Software Solutions is an ERP consulting and AI engineering firm that helps mid-size businesses automate operations with Odoo and custom AI agents. They write about ERP adoption, implementation recovery, and business process automation.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.