Skip to guide
Manamatha SarnakerOdoo ERP Consultant · Montreal

Migration & data quality

Odoo migration reconciliation checklist

A successful import is a technical event. Acceptance is a business decision. Use this checklist to give the people responsible for migrated records a clear way to check completeness, investigate exceptions and decide whether a load is ready.

By Manamatha Sarnaker · Odoo, business systems and reporting

1. Agree what acceptance means before loading

Write down which records are in scope, the source snapshot time, target company, currencies and responsible reviewers. A count without this boundary cannot establish completeness: two exports may legitimately cover different periods or companies.

Select checks for the record type. A contact migration needs identity and relationship checks; an opening-balance migration needs agreed financial controls. Totals that mix currencies or units can look plausible while having no useful business meaning.

  • Name the source snapshot, target environment and included record types.
  • Agree field mapping, identifier rules and related-record dependencies.
  • Define counts, totals and business examples to reconcile, with tolerances where justified.
  • Assign an owner to exceptions and a reviewer who can accept or reject the result.

2. Preserve a stable identity for every record

Odoo 19's import documentation explains how External IDs support updates and relationships. Use stable, unique source identifiers and a documented mapping, rather than treating a display name as proof that two records are the same. Check for collisions before a trial import.

The import tool's Test step checks import validity; it does not replace reconciliation against the agreed source. Work in a suitable test environment first. Loading related records requires the correct dependency order and the actual model's field rules.

3. Equal counts can conceal different records

In this synthetic example, both files contain four rows. The source has four unique accounts and a total of CAD 1,000.00. The target repeats ACC-002, loses ACC-004 and changes ACC-003's amount. Its total is CAD 790.00. Counting rows alone would miss all three problems.

Amounts are integer cents in one currency. They are illustrative control values, not Odoo accounting fields. Keep identifiers as text so formatting or leading zeros are not lost.

Synthetic source and target controls; CAD, integer cents converted for display
AccountSourceTargetFinding
ACC-001$100.00$100.00Matches
ACC-002$200.00$200.00 + $200.00Duplicate target key
ACC-003$300.00$290.00$10.00 difference
ACC-004$400.00MissingMissing target key
Total$1,000.00$790.00Target is $210.00 lower

4. Check both directions and keep exceptions visible

Compare source keys with target keys, then target keys with source keys. The first finds missing records; the second finds unexpected additions. Also test uniqueness on both sides. Do not hide duplicates by converting a file directly into a dictionary: that can silently keep only the last row.

The downloadable standard-library Python script groups rows by key, reports duplicate/missing/unexpected keys and compares values only when a key has one row on each side. It returns a hold decision for this example. An accepted sum can still hide offsetting errors, so inspect individual differences and representative business relationships too.

Run the downloaded example with Python 3; keep its CSV files beside it
python seo-worked-examples.py migration
# migration: hold
# source rows: 4; target rows: 4
# source cents: 100000; target cents: 79000
# duplicate target: ACC-002
# missing target: ACC-004
# amount mismatch: ACC-003

5. Make the release decision reviewable

Keep the snapshot reference, mapping revision, load identifier, check outputs and unresolved exceptions with the review. A business reviewer needs to know exactly which load was checked; approval of a trial load does not automatically accept a later changed load.

For this example, the decision is Hold: investigate the duplicate, restore or explain the missing record and resolve the changed value. Re-run the checks after correction. A useful acceptance record states who reviewed which controls, when they reviewed them and whether any remaining exceptions were explicitly accepted. Cutover, rollback and ongoing reconciliation should be agreed for the real engagement.

  • Source snapshot and load/version are identifiable.
  • Counts, keys, agreed totals and representative relationships have been checked.
  • Exceptions have owners and an explicit disposition.
  • The responsible business reviewer records acceptance or a hold decision.

Try the worked example.

Download the files below into one folder. Run python seo-worked-examples.py migration with Python 3.9 or newer. The script reads these local CSVs and prints its checks; it makes no network requests or system changes.

These small teaching datasets cannot validate a live ERP. Adapt the scope, controls and acceptance criteria to the actual records before relying on a report or migration decision.

How this connects to delivered work

The published 80K migration documents a Python ETL pipeline, SQL validation and finance-approved reconciliation for 80,000+ records. The exact client mappings, source platform and sign-off document are not public. The worked example below illustrates the checking principle without claiming to reproduce that engagement.

Technical references

Working through a similar question?

Describe your systems, the result you expect and what currently disagrees. Use anonymized examples and keep credentials and confidential exports out of the contact form.