1. What to migrate
| Data | Migrate? | How |
|---|---|---|
| Chart of accounts | Map to the localisation CoA, add missing accounts | Import / manual |
| Customers, vendors, contacts | Yes (active only, deduplicated) | CSV/XLSX import |
| Products, categories, UoM, variants, barcodes | Yes | Import (variants: import attribute values; v19+ "import product variants") |
| Vendor price lists, customer pricelists | Yes | Import |
| BoMs, work centers, routings | Yes (manufacturers) | Import BoM + lines |
| Opening stock (qty, location, lot, cost) | Yes | Inventory adjustment import, with unit cost |
| Opening trial balance | Yes | One opening journal entry (or Accounting > Opening balances) |
| Open AR/AP (unpaid invoices/bills) | Yes, one line per open document | Import as invoices/bills against an opening clearing account |
| Open SOs / POs | Yes (not fully delivered/received) | Import or re-key |
| Employees, contracts, leave balances | Yes | Import + allocation |
| Historical closed transactions | Usually no: keep the old system read-only or import summaries | — |
| Fixed assets | Yes, with accumulated depreciation | Import assets with "already depreciated" amount |
2. Steps
- Extract from the old system (Tally: masters + ledgers + stock summary; QuickBooks: lists + aged reports).
- Clean in a spreadsheet: remove duplicates, fix GSTIN/VAT format, unify units, give every product an Internal Reference.
- Map old codes → Odoo fields. Download Odoo's import template (Favorites > Import records > template), or export one record to see the columns.
- Use External IDs (
idcolumn, e.g.,legacy_cust_0012). Re-imports then update instead of duplicating, and relations (Many2one) can reference them (parent_id/id). - Trial import into a duplicate/test database → check counts and totals.
- Reconcile: stock value in Odoo = old stock report; AR total = old aged receivables; trial balance matches.
- Repeat the trial migration at least twice; time it, because the real cut-over has a time window.
- Final migration on go-live day.
3. Import order (dependencies)
1 Currencies / companies / users
2 Chart of accounts adjustments, taxes, fiscal positions, journals, payment terms
3 Product categories (+ accounts & costing) → UoM → attributes
4 Contacts (companies first, then child contacts with parent_id/id)
5 Products / variants / vendor pricelists / customer pricelists
6 Warehouses & locations → lots/serials
7 BoMs, work centers
8 Opening stock (inventory adjustment with cost)
9 Opening balances JE; open invoices & bills; open SO/PO
10 Employees, allocations
4. Opening stock and accounting: avoid double-counting
Odoo 18 (perpetual / automated valuation): the opening inventory adjustment posts Dr Stock Valuation / Cr Inventory adjustment (stock variation). Your opening trial balance must then not debit the stock account again. Either:
- import the TB without the stock line and let the adjustment create it, then clear the counterpart to equity/opening-balance account; or
- import the full TB, and set the adjustment counterpart account = opening balance / suspense account so it nets to zero.
Odoo 19 / 20: valuation layers are gone. Stock moves don't post entries; the inventory closing entry (Accounting > Review > Inventory Valuation) brings the stock account in line. Import the TB with the stock balance and run the closing for the opening date. Check that the Stock Variation balance is explained.
5. Open receivables and payables
- Import each unpaid invoice as a customer invoice dated with the original date, using a product/account "Opening AR" that books to an opening clearing account (not revenue).
- The total of the clearing account must equal the AR line in the opening trial balance. After posting both, the clearing account is zero.
- Do the same for vendor bills (AP).
6. Validation checklist (sign-off by the customer's accountant)
- [ ] Counts: customers, vendors, products, BoMs match the source
- [ ] Stock quantity per product/location/lot = physical count
- [ ] Stock value = old valuation report (± rounding)
- [ ] Trial balance in Odoo at opening date = old system's closing TB
- [ ] Aged receivable and aged payable totals match
- [ ] 10 random products checked (price, cost, taxes, routes, accounts)
- [ ] 10 random customers checked (pricelist, payment term, fiscal position, GSTIN)