This is an illustrative project pattern, not an attributed client engagement Get a migration audit →
Illustrative · Wholesale & Distribution

Years of accounting history. One weekend. Zero rewritten customisations.

This is an illustrative project pattern — a composite scenario built from how legacy-version migrations actually go wrong, and how we structure them so they don’t. It is not a named client and no figures below are attributed to a real engagement. It exists to show the shape of the work and the standard we hold ourselves to.

Get a migration audit → See the migration service →
The situation

A wholesale distributor, four versions behind.

The pattern this represents: a distribution business running a heavily customised Odoo install several major versions back — old enough that the legacy surcharge has kicked in, new enough that nobody wants to touch it in case it breaks.

Stuck on an old version

Community edition, several major releases behind current. OCA module support for that version has already lapsed, so security patches have effectively stopped.

Custom pricing & discount logic

A bespoke tiered-discount engine on the sales order, built years ago by a since-departed freelancer. Nobody left in-house can read the code, let alone port it.

Years of ledger history

Full accounting history — invoices, credit notes, reconciled bank statements — sitting in that database. A botched migration doesn’t just break a feature, it breaks an audit trail.

Live integrations

A courier API for shipment tracking and a bank-feed connector, both wired against internal APIs that shift meaningfully between major Odoo versions.

No tolerance for downtime

Orders come in on weekdays, warehouse picks run daily. A migration that takes production offline mid-week isn’t survivable — it has to happen in a window nobody notices.

Burned once before

A previous in-place upgrade attempt (done without staging) had already corrupted a batch of stock moves. The business wanted proof of reversibility before agreeing to try again.

How we’d run it

Audit first. Then build in parallel.

The same five-step shape we use on every version jump — the only variable is how many version increments it takes to get there.

1. Module compatibility audit

Every custom module and every third-party dependency checked line-by-line against the target version’s API. The tiered-discount engine gets flagged early — that’s the module most likely to need a rewrite, not a port.

2. Parallel staging environment

Target version stood up on its own server, restored from a production snapshot, modules installed alongside it. Production keeps running untouched while staging gets built and tested in the background.

3. Full row-level data diff

Every table in staging reconciled against production — invoice counts, ledger balances, stock quantities, partner records. Anything that doesn’t match gets investigated and resolved before anyone signs off on a cutover date.

4. Single weekend cutover

Production frozen Friday evening, final data sync run, DNS/routing flipped to the new instance, smoke tests run against real workflows — sales order, invoice, stock move, bank reconciliation — before Monday users ever log in. A tested rollback path sits ready the whole time.

5. 30-day stabilisation

Real usage always surfaces something staging didn’t — an edge case in the discount engine, a courier webhook retry that behaves differently. Bug-watch included for a month so those get fixed fast, not queued.

6. Customisations preserved, not rebuilt

Where the old code still expresses the business logic correctly, we repackage it for the new API rather than rewriting the business rules from scratch — the discount tiers stay the discount tiers, they just run on current Odoo.

Two ways this could have gone

In-place gamble. Or a reversible plan.

In-place upgrade (the previous attempt)

  • Production taken down directly, upgrade run against the live database
  • Custom discount engine breaks silently — no compatibility check beforehand
  • No data diff, so corrupted stock moves surface days later, mid-audit
  • Rollback means restoring last night’s backup and re-keying a day’s orders
  • No fixed scope — every discovered break becomes a new billable surprise

Parallel migration (this pattern)

  • New version built and tested alongside live, production never at risk mid-build
  • Discount engine repackaged against the new API before cutover, not after
  • Row-level reconciliation signed off before a cutover date is even set
  • Rollback path rehearsed in advance — cutover weekend is reversible by design
  • Fixed-price quote set after the audit, so scope is known before work starts
100%row-level reconciliation required before cutover sign-off
1planned cutover window — no repeat outages
0business rules rewritten from scratch where the old logic still held
30 daysstabilisation and bug-watch included after go-live

Sitting on an old version with customisations you don’t want to lose?

The audit tells you exactly what migrates clean and what needs rework — before you commit to a date or a quote. Fixed scope, tested rollback, no in-place gamble.