Odoo18 November 2025

Migrating from EuroWin or Sage to Odoo: a migration guide for distribution

Real Cymit Química case: strategy, data mapping, ETL, cutover and go-live when migrating from EuroWin and Access to Odoo at a distribution company with more than two million references.

Migración ERPCasos de éxitoDistribución

Why migrate from EuroWin or Sage to Odoo in distribution

EuroWin and Sage are two of the most widely deployed ERPs in Spanish SMEs across the distribution, retail and light manufacturing sectors. They are tools that solved the problems of the nineties and the two-thousands very well: accounting, invoicing, purchase and sales orders, warehouse management. But in 2026 they have aged in a way that no patch can fix: their architecture prevents real integration with e-commerce platforms, catalogue automation, real-time business visibility and the scalability that modern distribution operations demand.

The migration I describe in this guide is not theoretical. I personally led the migration of Cymit Química from EuroWin + Microsoft Access + CRM Visual to Odoo when I was the company's CEO. The result was revenue growth from 2 M€ to 4 M€ and, ultimately, the acquisition of the company by Grupo PALEX. What follows is the real methodology I used, with the concrete problems that appeared and how we solved them.

Prior diagnosis: why many migrations fail

The most frequent failure in ERP migrations is not technical: it is about planning. The projects I have seen fail share common patterns:

  • Underestimating the quality of the source data: EuroWin and Sage store years of manually entered data, with inconsistencies, duplicates, poorly classified customers and products with broken references. Cleaning that data takes more time than the technical migration itself.
  • Trying to migrate everything at once without a rollback plan: the «big bang» without a safety net is the recipe for disaster in companies with daily operations that cannot stop.
  • Not involving the operational team from the start: end users know the particularities of the business that no external consultant is going to discover by reading the documentation. Their input in the data mapping is essential.
  • Migrating historical data that no one is going to use: migrating 10 years of warehouse movement history has a high cost and a questionable value. Defining, with business criteria, which history is necessary saves weeks of work.

Migration strategy: big bang vs phased

Big bang

The big bang migration consists of migrating all the data and doing the cutover to Odoo on a single date. The old system is frozen (no more operations are allowed), the full migration is executed and Odoo is activated as the only operating system.

Advantages: there is no dual-operation period; the complexity of keeping two systems synchronised disappears on the cutover day. It is simpler to manage operationally once executed.

Risks: if something fails on the cutover day (corrupted data, an integration that does not work, users who do not know how to operate the new system), the entire operation stops. It requires a clear and tested rollback plan.

When it is suitable: small or medium-sized companies (up to ~30 users) with relatively bounded product catalogues, where the operational risk of a short stoppage is acceptable.

Phased migration

The phased migration activates Odoo modules incrementally, keeping the old system for the areas not yet migrated until each phase is consolidated.

A typical order for a distribution company:

  1. Phase 0 (preparation): Odoo configuration, master data (customers, suppliers, products), accounting configuration. Running in parallel.
  2. Phase 1 (sales and purchases): sales and purchase orders in Odoo; invoicing stays in the old system temporarily or in parallel.
  3. Phase 2 (warehouse): stock and delivery note management in Odoo. The initial inventory is taken as a reference point.
  4. Phase 3 (accounting): accounting close in the old system and opening of balances in Odoo. Full invoicing in Odoo.
  5. Phase 4 (shutdown of the old system): history migrated or archived for consultation, old system deactivated.

When it is suitable: larger companies, complex operations with many integrations, or when the team needs learning time without the pressure of the business already depending on Odoo.

The decision at Cymit

At Cymit Química we opted for a hybrid approach: phases for the main operation (sales, purchases, warehouse) but with a defined and firm cutover date for accounting. The reason was that we had three disconnected systems (EuroWin, Access, CRM Visual) and a dual-operation period on three simultaneous fronts could not drag on indefinitely. Defining phases but with concrete deadlines was what allowed us to complete the migration without it turning into a never-ending project.

Data mapping: from EuroWin and Sage to Odoo

Customers and contacts

EuroWin stores customers in its own tables with system-specific fields that have no direct correspondence in Odoo. The most frequent problems:

  • Duplicates: the same customer may have two records (one created by the sales team, another by the invoicing team) with slight variations in the name or in the CIF. Deduplication is manual or semi-automatic and requires human decision.
  • Customers with multiple addresses: in EuroWin the address is a field of the customer; in Odoo the model allows multiple contacts (parent/child) with different addresses. You have to decide how that structure is mapped.
  • Classifications and price rates: per-customer rates (discounts, special prices) must be migrated to Odoo's price lists, which have a more powerful but also more complex structure.

Indicative mapping of customer fields:

EuroWin / Sage fieldOdoo field (res.partner)Notes
Customer coderefKeep as internal reference
Legal namenameNormalise upper/lowercase
CIF/NIFvatAdd ES prefix if missing
Addressstreet, city, zip, state_id, country_idNormalise using the INE municipality catalogue
Phonephone / mobileCheck E.164 format
EmailemailValidate syntax; discard the invalid ones
Rate / discountproperty_product_pricelistCreate an equivalent price list in Odoo
Payment methodproperty_payment_term_idCreate equivalent payment terms
Accounting accountproperty_account_receivable_idMap to Odoo's chart of accounts

Products and catalogue

The product catalogue is usually the most complex asset to migrate in distribution companies. At Cymit, with more than two million references, it was the highest-volume work of the project. The critical points:

  • Internal reference vs supplier reference: EuroWin usually stores the supplier reference as the main reference. Odoo separates the internal reference (default_code) from the supplier references (product.supplierinfo). You have to decide what becomes what.
  • Categories and catalogue structure: EuroWin's category hierarchy rarely fits directly with the one you need in Odoo. It is an opportunity to redesign the catalogue taxonomy.
  • Units of measure: Odoo supports multiple UoM with conversion; EuroWin and Sage have variable support. Products sold in boxes but stored in units require specific configuration.
  • Prices and rates: the sales and cost prices in EuroWin must be mapped to Odoo's pricelists, which work differently (rules based on categories, quantities, dates).

Initial stock

The initial inventory is the starting point of the warehouse in Odoo. The options are:

  • Take the current stock from the old ERP as initial inventory: the stock quantity of each product is migrated to Odoo through an inventory adjustment. It is the fastest method but assumes that the old system's stock is correct.
  • Do a physical count before the cutover: more laborious but it eliminates the accumulated discrepancies. Recommended if there are reasons to doubt the reliability of the stock in EuroWin or Sage.

At Cymit we did a selective count: high-value or high-turnover products were physically recounted; the rest was taken from the system with an estimated correction factor. The differences were regularised during the first month of operation in Odoo.

Accounting and history

The accounting migration is the most delicate and the one that most requires business judgement. The key decisions are:

  • Opening date: the initial balance in Odoo must match the closing balance of the old system. It is usually migrated at the close of a fiscal year to simplify the reconciliation.
  • Invoice history: invoices issued before the cutover date are not migrated as Odoo documents (it is very costly and not very useful). They are kept in the old system in read-only mode. Only the outstanding receivable/payable balances (portfolio of effects) are migrated as opening entries.
  • Chart of accounts: the chart of accounts of EuroWin or Sage must be mapped to the Spanish chart of accounts in Odoo. Odoo includes the Spanish localisation (l10n_es) with the full PGC; the work consists of mapping the old system's accounts to the equivalent ones in the PGC.

ETL: extraction, transformation and load

Extraction from EuroWin

EuroWin does not have a modern API. The extraction routes are:

  1. Export to Excel/CSV from the application itself: the simplest way for small volumes. Limited in flexibility and without access to all the tables.
  2. Direct access to the EuroWin database: EuroWin stores its data in a SQL database (SQL Server or similar, depending on the version). With read-only access to that database, all the data can be extracted with SQL. This is the recommended route for full migrations.

Example of extracting customers from the EuroWin database (approximate structure, varies by version):

-- Extraer clientes de EuroWin (SQL Server)
SELECT
    c.CodCliente        AS codigo_externo,
    c.RazonSocial       AS nombre,
    c.CIF               AS cif,
    c.Direccion         AS calle,
    c.CodPostal         AS cp,
    c.Poblacion         AS ciudad,
    c.Telefono          AS telefono,
    c.Email             AS email,
    c.CodFormaPago      AS forma_pago_codigo,
    c.CodTarifa         AS tarifa_codigo,
    c.Observaciones     AS notas_internas
FROM
    Clientes c
WHERE
    c.Activo = 1
ORDER BY
    c.CodCliente;

Cleaning and deduplication

The slowest and most critical step of the ETL process is data cleaning. A Python script for the most common tasks:

import pandas as pd
import re

# Cargar el CSV exportado de EuroWin
df = pd.read_csv('clientes_eurowin.csv', encoding='latin-1', sep=';')

# 1. Normalizar CIF: añadir prefijo ES si falta
def normalizar_cif(cif):
    if pd.isna(cif) or str(cif).strip() == '':
        return False
    cif = str(cif).strip().upper().replace(' ', '').replace('-', '')
    if not cif.startswith('ES'):
        cif = 'ES' + cif
    return cif

df['vat'] = df['cif'].apply(normalizar_cif)

# 2. Normalizar nombre: Title Case, quitar espacios dobles
df['name'] = df['nombre'].str.strip().str.title()
df['name'] = df['name'].str.replace(r'\s+', ' ', regex=True)

# 3. Validar emails
EMAIL_REGEX = re.compile(r'^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$')
df['email_valido'] = df['email'].apply(
    lambda x: str(x).strip().lower() if pd.notna(x) and EMAIL_REGEX.match(str(x).strip()) else False
)

# 4. Detectar duplicados por CIF (el identificador más fiable)
duplicados_cif = df[df.duplicated(subset=['vat'], keep=False) & (df['vat'] != False)]
print(f'Clientes con CIF duplicado: {len(duplicados_cif)}')
duplicados_cif.to_csv('revisar_duplicados_cif.csv', index=False)

# 5. Detectar duplicados por nombre (fuzzy: nombres muy similares)
# Requiere: pip install rapidfuzz
from rapidfuzz import fuzz
nombres = df['name'].dropna().unique().tolist()
posibles_duplicados = []
for i, n1 in enumerate(nombres):
    for n2 in nombres[i+1:]:
        if fuzz.ratio(n1, n2) > 85:
            posibles_duplicados.append({'nombre_1': n1, 'nombre_2': n2, 'similitud': fuzz.ratio(n1, n2)})

pd.DataFrame(posibles_duplicados).to_csv('revisar_duplicados_nombre.csv', index=False)
print(f'Pares de nombres con similitud >85%: {len(posibles_duplicados)}')

print('\nResumen:')
print(f'  Total clientes exportados: {len(df)}')
print(f'  Clientes con CIF válido: {(df['vat'] != False).sum()}')
print(f'  Clientes con email válido: {(df['email_valido'] != False).sum()}')

Load into Odoo via the XML-RPC API

Odoo has a complete XML-RPC API that allows records to be created and updated programmatically. It is the recommended route for loading migration data (instead of manual CSV imports, which are error-prone and not traceable):

import xmlrpc.client
import pandas as pd

# Conexión a Odoo
ODOO_URL = 'https://odoo.tudominio.com'
DB = 'odoo_produccion'
USER = 'admin@tudominio.com'
PASSWORD = 'la_password_del_admin'

common = xmlrpc.client.ServerProxy(f'{ODOO_URL}/xmlrpc/2/common')
uid = common.authenticate(DB, USER, PASSWORD, {})
models = xmlrpc.client.ServerProxy(f'{ODOO_URL}/xmlrpc/2/object')

def crear_cliente_odoo(row, pricelist_map, payment_term_map):
    '''Crea un cliente en Odoo desde una fila del DataFrame limpio.'''
    vals = {
        'name': row['name'],
        'customer_rank': 1,
        'ref': str(row['codigo_externo']),
        'street': row.get('calle', ''),
        'zip': str(row.get('cp', '')),
        'city': row.get('ciudad', ''),
        'country_id': 69,    # España — ID verificar en tu instancia
        'lang': 'es_ES',
    }
    if row['vat']:
        vals['vat'] = row['vat']
    if row['email_valido']:
        vals['email'] = row['email_valido']
    if row.get('telefono'):
        vals['phone'] = str(row['telefono'])
    # Mapear tarifa y forma de pago usando los diccionarios de mapeo
    tarifa_codigo = row.get('tarifa_codigo')
    if tarifa_codigo and tarifa_codigo in pricelist_map:
        vals['property_product_pricelist'] = pricelist_map[tarifa_codigo]
    pago_codigo = row.get('forma_pago_codigo')
    if pago_codigo and pago_codigo in payment_term_map:
        vals['property_payment_term_id'] = payment_term_map[pago_codigo]

    partner_id = models.execute_kw(
        DB, uid, PASSWORD,
        'res.partner', 'create', [vals]
    )
    return partner_id

# Ejecutar la carga con manejo de errores
df_limpio = pd.read_csv('clientes_listos_para_odoo.csv')
errores = []
ok = 0

for idx, row in df_limpio.iterrows():
    try:
        pid = crear_cliente_odoo(row, pricelist_map={}, payment_term_map={})
        ok += 1
        if ok % 100 == 0:
            print(f'Cargados {ok} clientes...')
    except Exception as e:
        errores.append({'fila': idx, 'codigo': row['codigo_externo'], 'error': str(e)})

print(f'\nCarga completada: {ok} clientes OK, {len(errores)} errores.')
pd.DataFrame(errores).to_csv('errores_carga_clientes.csv', index=False)

Validation and reconciliation

Loading the data is not the end. Validation is the step that determines whether the migration is really correct or whether there are problems that will only appear weeks later in production.

The minimum checks after the load:

  • Record count: the number of customers, products and suppliers in Odoo must match that of the source system, minus the records that were removed during the cleaning. Document the difference and justify it.
  • Reconciliation of accounting balances: the total receivables balance in Odoo must match that of the old system at the cutover date. Any difference must have an explanation (manual entry, rounding difference, etc.).
  • Inventory verification: the total value of the inventory in Odoo must match that of the old system. Sampling of high-value products to verify individually.
  • Full-cycle test: create a test order in Odoo from start to finish (order → delivery note → invoice → payment) with real data to verify that the flow works correctly before go-live.
  • Validation with key users: have the sales, warehouse and accounting managers review their data in Odoo before the cutover. They will detect problems that no automated script will find.

Cutover plan

The cutover is the moment of maximum risk and maximum planning. A good cutover plan has, minute by minute, what is going to happen, who executes it and what check confirms that the step is done correctly.

The week before the cutover

  • Freeze changes in the old system (no new customers or products are created in EuroWin/Sage).
  • Run the full migration in a pre-production environment to verify that the scripts work without errors.
  • Final training of key users (managers of each area).
  • Prepare the rollback plan: what to do if on Monday morning Odoo is not working correctly.

The cutover weekend

# Plan de corte — ejemplo Cymit Química (simplificado)

Viernes 18:00 — Cierre operativo en EuroWin
  - Verificar que no hay pedidos pendientes de procesar
  - Hacer dump final de EuroWin (backup completo)
  - Tomar inventario de stock final

Viernes 20:00 — Extracción de datos finales
  - Extraer clientes, productos, stock, saldos contables
  - Ejecutar scripts de limpieza y transformación
  - Generar ficheros de carga para Odoo

Sábado 08:00 — Carga en Odoo producción
  - Cargar maestros (clientes, proveedores, productos)
  - Ajuste de inventario inicial
  - Apertura de saldos contables
  - Verificar conteos y reconciliaciones

Sábado 14:00 — Validación con usuarios clave
  - Responsable de ventas verifica sus clientes y tarifas
  - Responsable de almacén verifica stock
  - Contable verifica saldos de apertura

Sábado 18:00 — Decisión GO / NO-GO
  - Si todo cuadra: GO (Odoo en producción el lunes)
  - Si hay problemas críticos: NO-GO (EuroWin sigue activo, analizar)

Domingo — Buffer para correcciones menores
  - Resolver incidencias detectadas el sábado
  - Preparar soporte reforzado para el lunes

Lunes 08:00 — Go-live
  - Odoo es el único sistema operativo
  - Soporte técnico disponible todo el día

Rollback plan

The rollback plan must be documented and executable without depending on the migration consultant. If on Monday morning there is a critical problem in Odoo that prevents operations, the steps must be clear:

  1. Communicate to the whole team that the rollback is being activated.
  2. Deactivate user access to Odoo (so that they do not generate data that is later inconsistent).
  3. Reactivate EuroWin/Sage with the data from Friday's backup.
  4. Document all the movements that were made in Odoo before the rollback in order to reproduce them in the old system.
  5. Root-cause analysis of the problem and a correction plan before the next cutover attempt.

Training and change management

At Cymit, the biggest obstacle was not technical: it was the resistance to change of a team that had spent years working with EuroWin and knew its shortcuts and its limitations. Training cannot be a PDF and a two-hour session.

What worked:

  • Training by roles, not by features: instead of teaching «this is Odoo», teach «this is how you process a sales order in your new system». Each role has a specific flow that must be trained until it is automatic.
  • Training environment with anonymised real data: users learn better when they see their own customers and products, even if anonymised. A generic training environment does not prepare them for reality.
  • Champion users per area: identify one person from each area (sales, warehouse, accounting) who is trained more in depth and becomes the first point of contact for their colleagues. This drastically reduces dependence on external support.
  • Reinforced support for the first 30 days: the period immediately after go-live is where 80% of the questions concentrate. Having support available and fast during that month makes the difference between a migration that «went well» and one that «was a disaster».

Main risks and how to mitigate them

RiskProbabilityImpactMitigation
Source data with more duplicates and errors than expectedHighHigh (delays the project)Data audit in the first weeks, not in the last one
Team rejection of the changeMediumHigh (incorrect use of the system)Involve the team from the design phase, not only in the training
Data loss at the cutoverLowCriticalVerified backups before the cutover, tested rollback plan
Integration with other tools that fails (online shop, POS, logistics)MediumHighInventory all the integrations in the diagnosis phase, not at the end
The project drags on indefinitely (scope creep)HighMedium (cost)Define a closed scope for the base migration; improvements in later phases
Paralysis due to not being able to access the historyMediumLow-MediumKeep EuroWin/Sage in read-only mode for 12 months after the cutover

Lessons learned at Cymit

Five years after the process, and having seen other migrations since then, the lessons I would take to any similar project are:

  1. The quality of the source data determines 70% of the project's success. Invest time in auditing it at the beginning, not at the end. Every data-quality problem you discover in production costs ten times more than having detected it earlier.
  2. Do not migrate what you do not use. The invoice history from 8 years ago that no one consults can stay in the old system in archive mode. Migrating for the sake of completeness lengthens the project without adding real value.
  3. Define the success criterion before you start. «The migration is fine» is ambiguous. «The receivables balance matches the previous balance sheet with a tolerance of 1€, the customer and product counts are within a justified 2% variation and a full sales-warehouse-invoicing cycle has been processed successfully» is a verifiable criterion.
  4. The go-live day is not the end: it is the beginning. The first 90 days are the period of greatest risk and greatest need for support. Plan resources for that period before starting the project.
  5. The platform you build must be acquirable. At Cymit, the clean architecture in Odoo was part of the valuation argument in the M&A process with PALEX. If a corporate transaction is a possibility on your horizon, the technology is part of the price.

Are you considering migrating from EuroWin, Sage or another legacy ERP to Odoo?

Have an Odoo project or an implementation that never quite works?

Tell me directly — I run a free audit for qualified projects.