The riskiest moment in an ERP migration is not the day you load the data. It is three weeks later, when the accountant closes the first month in the new system and the trial balance does not match the old one. By then the project team has moved on, the legacy system is read-only, and every unexplained discrepancy chips away at trust in numbers people need to rely on daily.
We have learned to treat migration as a reconciliation exercise first and a technical exercise second. Moving records between databases is the easy part — Odoo's import framework plus some module-grade tooling, versioned in Git like everything else we build, handles that. The hard part is being able to prove, on cutover day, that what came out equals what went in.
So before any Odoo migration, we run the same checklist. And we do not load production data until every item on it has been agreed in writing. Here is what it looks like in practice.
Field mapping and cleanup rules — approved before anything moves
Every migration starts with a field mapping document: each source field, its destination in Odoo, and the transformation between the two. This sounds bureaucratic until you hit the fields that do not map cleanly — the free-text column that is actually three different things, the customer category codes nobody remembers the meaning of, the legacy tax configuration that has no equivalent in the new chart of accounts. The mapping surfaces those decisions early, while they are still cheap to make.
Cleanup is where projects quietly go wrong. Legacy data is never clean: duplicated partners, contacts without countries, products with inconsistent units. The temptation is to fix these silently during the load. We refuse. Every cleanup rule — merge duplicates on these criteria, default this missing value, exclude records in this state — is written down and approved by the client before the load runs. The client owns their data; we own the mechanics. When a rule changes data, the client should be the one who decided it, not the one who discovers it afterwards.
How much history to bring
Bringing everything over feels safe and is usually wrong. Deep history bloats the database, slows every dry-run, and drags years of legacy inconsistency into a system that will reject it on import. Bringing nothing is worse. Our default posture is simple: open items come over in full — every open invoice, unreconciled payment, open order, active contract, and current stock position — because these are the records people will act on from day one. Closed history comes over as opening balances per account and per partner, so the books tie out without replaying every posting since the company was founded. The deep history itself gets archived: the legacy system stays available read-only for as long as the client needs it, or we export the detail to a proper archive. Master data — active partners, products, employees — always migrates in full. The line between live and archived is a business decision, so it goes on the approval list next to the cleanup rules.
Dry-runs, the reconciliation report, and a rehearsed cutover
We never load production data only once. Migration scripts run against a staging database first — usually several times — and each dry-run produces a reconciliation report. The report is pass or fail, not a vibe. What has to match:
- Record counts per model — partners, products, open invoices, orders — source versus destination, with every exclusion accounted for by an approved rule.
- Financial control totals — the trial balance per account, and receivables and payables per partner, matching the source to the cent.
- Open-item detail — every open invoice and unreconciled payment traceable back to its source record.
- Stock quantities and valuation, wherever inventory is in scope.
- Spot checks the client picks — a handful of records they know intimately, verified by hand.
A dry-run that fails reconciliation is not a setback; it is the checklist doing its job. We fix the rule or the mapping, rerun, and compare again. Only when a full dry-run reconciles cleanly do we schedule cutover — and the cutover itself is rehearsed. We time the run, script the sequence, and agree a go/no-go checkpoint with the client. Behind it sits a rollback plan: the legacy system stays untouched, backups are restore-tested rather than assumed, and if reconciliation fails on the night, we revert and regroup. The business opens the next morning on the old system, not in limbo between two.
None of this is glamorous, and it front-loads work that some projects prefer to defer. But it is also why we can quote a fixed price after scoping instead of an open-ended hourly meter — the checklist is what makes the scope knowable, and the reconciliation report is what makes done mean done. If you are planning a move to Odoo and want to see where the dry-runs, the reconciliation report, and the cutover rehearsal sit inside a full project plan, our migration service page walks through the whole sequence.