Odoo · Datos · Importación
Traer los datos de un ERP antiguo a Odoo nunca sale bien a la primera. Siempre aparece un cliente con el NIF mal escrito, un producto con una categoría que no existe o un pedido que apunta a un cliente borrado. La pregunta no es si habrá que repetir la carga, sino si repetirla duplicará todo. Con external IDs no lo hace.
La clave
La columna id hace la carga idempotente
Si el CSV que importas tiene una columna id, Odoo la trata como un external ID (el mismo mecanismo que los xml_id de los módulos). La primera vez crea el registro y guarda el identificador; las siguientes, encuentra el registro por ese identificador y lo actualiza. Usa el código del sistema antiguo con un prefijo propio y podrás relanzar la importación tantas veces como necesites.
id,name,vat,country_id/id,category_id/id
legacy.partner_1001,Ferretería Sol S.L.,ESB12345678,base.es,legacy.cat_retail
legacy.partner_1002,Talleres Martínez,ESB87654321,base.es,legacy.cat_workshop
Las relaciones usan el sufijo /id para referenciar otros registros por su external ID: base.es es España en cualquier base de datos, y legacy.cat_retail es la categoría que importaste antes. Así no dependes de nombres (que pueden repetirse) ni de IDs numéricos (que cambian entre entornos).
id sin punto, como partner_1001, se guarda como __import__.partner_1001. Funciona, pero mezcla tus datos con cualquier otra importación manual. Un prefijo propio (legacy., erp2019.) permite saber de dónde viene cada registro y borrarlos todos si hace falta empezar de cero.
Orden
De los catálogos a los documentos
Un registro solo puede referenciar a otro que ya exista. El orden de carga sigue las dependencias: primero lo que no apunta a nada, al final los documentos con líneas.
- Catálogos: categorías de cliente, unidades de medida, impuestos que falten.
- Contactos: primero las empresas, después las personas que dependen de ellas (
parent_id/id). - Productos y sus variantes.
- Documentos: pedidos, y después sus líneas (
order_id/id).
Por código
load(): el importador sin la interfaz
El asistente de importación de Odoo usa por debajo el método load(), que puedes llamar desde un script de odoo-bin shell o por RPC. Acepta la lista de columnas y las filas, aplica las mismas conversiones que la interfaz y no lanza excepciones: devuelve los mensajes de error. Si hay uno solo, revierte el lote entero y ids vale False.
import csv
with open("/data/partners.csv", newline="", encoding="utf-8") as fh:
reader = csv.reader(fh)
fields = next(reader)
rows = list(reader)
BATCH = 500
for start in range(0, len(rows), BATCH):
result = env["res.partner"].load(fields, rows[start:start + BATCH])
errors = [m for m in result["messages"] if m["type"] == "error"]
if errors:
for m in errors:
print(start + m["rows"]["from"], m["message"])
raise SystemExit("Lote con errores: corrige el CSV y relanza")
env.cr.commit() # cada lote correcto queda guardado
Como la carga es idempotente gracias a la columna id, si el lote 7 falla puedes corregir el CSV y relanzar el script completo: los seis primeros lotes se actualizarán sin duplicarse.
Historial
Lo que no se puede importar tal cual
Algunas cosas del sistema antiguo no tienen hueco directo. create_date y write_date los pone siempre el ORM, así que la fecha original de un pedido va en su campo de negocio (date_order), no en la fecha de creación. Y los documentos contables con efectos fiscales (facturas ya declaradas) normalmente no se reimportan como facturas nuevas: se cargan como saldos de apertura y el detalle histórico se conserva como adjunto o en el sistema antiguo.
| Herramienta | Úsala para | No es para |
|---|---|---|
| Asistente de importación | Cargas puntuales con revisión visual y botón de prueba | Decenas de miles de filas o cargas repetidas |
load() en un script | Migraciones grandes, por lotes, repetibles | Datos que necesitan lógica compleja por fila |
| XML/CSV en un módulo | Datos de configuración que forman parte del producto | Datos de negocio del cliente |
base_import_module | Instalar módulos de solo datos (sin Python) desde un zip | Migrar datos de negocio |
Resumen
Da a cada fila un external ID con prefijo propio y la carga se puede repetir sin duplicar. Referencia las relaciones con /id, carga en orden de dependencias y, para volúmenes grandes, usa load() por lotes con commit tras cada lote correcto. Las fechas originales van en campos de negocio, no en create_date. Más sobre external IDs en secuencias, referencias XML y env.ref().