Project 02 · Finance · Data Accuracy

Moving a Company's Accounts from Wave to Odoo

A company wanted to move its full accounting records from an old tool (Wave) to a new one (Odoo). I built my own Python scripts to do this. The goal was not just "move the data" — it was make sure every number matches to the cent, and prove it.

Python Accounting Wave → Odoo Multi-Currency Automatic Checks Safe To Re-run
My role
Built the whole migration
From
Wave Accounting
To
Odoo ERP
Tool
Python scripts
Accuracy goal
Match to the cent
Re-runnable
Yes, safely

01 Why moving accounts is so hard

Most data moves can handle a small mistake — a duplicate contact, a missing tag. Accounting cannot. In a company's books, every amount has to add up. If the totals are off by even a few cents, the whole set of records is in doubt. So "almost right" is the same as "wrong."

Wave and Odoo also store information in different ways. Wave is simple and flat. Odoo uses a proper double-entry system, where every amount must appear twice — once as money in and once as money out — so the books always balance. Ready-made import tools often squash these details and quietly change numbers. You cannot allow that with financial records. So I built a custom tool in Python, with checking built in from the start, not added at the end.

The standard I set

The job was only "done" when an automatic check proved that the new totals in Odoo matched the old totals in Wave — for every account and every currency, down to the cent — and when running the whole process again made no new or duplicate records. Provable and repeatable.

02 The four steps: get, change, load, check

1

Get

Pull all the records out of Wave into a holding area, keeping the originals safe.

2

Change

Turn each record into Odoo's format and build balanced double-entry lines.

3

Load

Put the records into Odoo, each with a fixed ID so it updates instead of duplicating.

4

Check

Compare the totals, confirm the books balance, and flag anything that does not match.

Each step writes to a holding area first, instead of pushing data straight into the new system. This is what makes the move safe to repeat. If one step fails, I can fix it and run it again without starting over. And because every record gets a fixed ID, running it twice updates the same records instead of creating copies.

03 Mapping: a clear list of "this becomes that"

The heart of the work is a set of simple mapping lists. Instead of hiding rules inside the code, I wrote them out plainly: this Wave account becomes that Odoo account, this tax becomes that tax, this currency converts this way. An accountant can read and check these lists, and I can fix any special case in one place.

ItemIn WaveIn OdooThe rule
AccountsAccount name + typeAccount codeMatch by a clear list; stop if one is missing
People & companiesCustomer / VendorContactRemove duplicates by name + e-mail
TaxesSales / purchase tax %Tax ruleMatch by rate and type
CurrencyAmount + currencyAmount + currencyKeep the original; convert at the date's rate
TransactionsInvoice / bill / entryJournal entryRebuilt so each one balances
build_entry.py
# Turn a simple Wave transaction into a balanced Odoo entry.
# An entry is only created if money in equals money out.
def build_entry(txn, accounts, rates):
    lines = []
    for part in txn.parts:
        account = accounts.find(part.account)         # stop loudly if not found
        amount  = convert(part.amount, txn.date, rates)
        lines.append(Line(account, amount))

    money_in  = sum(l.debit  for l in lines)
    money_out = sum(l.credit for l in lines)
    if round(money_in - money_out, 2) != 0:
        raise NotBalanced(txn.id)          # never load an unbalanced entry

    return Entry(id=fixed_id(txn), date=txn.date, lines=lines)

04 Handling more than one currency

Money in different currencies is where simple tools lose small amounts. If you convert each line on its own with normal computer math, tiny rounding errors build up and the totals stop matching. I avoided this in three ways:

  • Exact money math. All amounts use Python's exact Decimal type, not normal decimals, so there are no rounding surprises.
  • Keep the original amount. Each line keeps its real amount in its own currency, plus the converted amount, so reports are correct in both.
  • Use the right day's rate. Each amount is converted using the exchange rate from the day it happened — not today's rate — so old reports stay correct.
convert.py
from decimal import Decimal, ROUND_HALF_UP

CENTS = Decimal("0.01")

def convert(amount, on_date, rates):
    rate = rates.on(on_date)                  # the rate for that exact day
    return (Decimal(str(amount)) * rate).quantize(CENTS, ROUND_HALF_UP)

05 Proving the books are correct

The checking step is what lets me say "done" with proof instead of hope. It runs three checks, and if any one fails, the move is not approved:

  • Balance check. Every entry, and the books as a whole, must have money in equal to money out.
  • Totals match. The new totals in Odoo are compared with the old totals in Wave, per account and per currency. The allowed difference is zero.
  • Fingerprint check. Each record gets a short "fingerprint" (a hash). Comparing fingerprints proves nothing was changed, dropped, or copied during the move.
check.py
import hashlib, json

def fingerprint(record):
    # A short, stable code for a record, used to spot any change.
    text = json.dumps(record, sort_keys=True, default=str)
    return hashlib.sha256(text.encode()).hexdigest()

def check_totals(wave, odoo):
    for account, expected in wave.items():
        got = odoo.get(account, Decimal("0"))
        if round(got - expected, 2) != 0:
            raise Mismatch(account, expected, got)   # to the cent
    return "BALANCED"
Safe to run again

Because every record has a fixed ID, the whole process can be run, fixed, and run again as many times as needed during the switch — always ending in one correct result, with no duplicates. This turned a stressful one-shot move into a calm, repeatable task.

06 What the client got

  • A complete, balanced set of books in Odoo, with full history and correct currencies.
  • An automatic report proving the totals matched the old system exactly — the proof an accountant or auditor needs.
  • A clear, repeatable process that lowered the risk of the switch and can be run again if needed.
  • Confidence that the books they now run the business on are correct from day one.

Have a move that has to be exactly right?

If you are switching a key finance or business system, I build the checking in from the start.

[email protected]