Quick Answer
80% of D2C brands break complex relationships during Odoo imports. The fix: Use dependency sequencing (parents before children), external IDs (map old → new IDs), and many-to-many formatting. Without proper relationship handling, expect $28,000-$80,000 in rework costs.
The Relationship Import Problem
Your Odoo import is humming along. You've imported 2,847 products. You've imported 3,200 customers. You're 80% done.
Then you get to the complex relationships. Products have multiple categories (parent, sub-category, sub-sub-category). Invoices have line items. Line items reference products. Products reference suppliers. Suppliers have multiple categories.
Suddenly your clean import breaks. Errors cascade. Data doesn't match up. You spend 60+ hours manually trying to fix relationships that should have been automated.
We've implemented 150+ Odoo systems. The ones that planned for complex relationships upfront (understanding one-to-many, many-to-many, foreign key constraints, load sequencing) imported flawlessly. The ones who "figured it out as they went"? We've seen them lose $28,000-$80,000 in rework time just trying to fix broken relationships post-import.
Why Relationships Break (3 Common Disasters)
Scenario 1: The Product-Category Disaster
You have 2,847 products. Each product belongs to a category. But you're selling on Shopify + Amazon + your own website. Each channel has different category hierarchies.
| Platform | Hierarchy | Levels |
|---|---|---|
| Shopify | Apparel → Men → Shirts | 3 levels |
| Amazon | Clothing → Shirts | 2 levels |
| Odoo | ONE primary category per product | Conflict |
⚠️ Result: Either way, you're broken. Pick Shopify's hierarchy and Amazon reporting breaks. Pick Amazon's and Shopify loses nuance. You manually maintain category overrides.
Scenario 2: The Invoice-Line-Item Nightmare
You're importing 50,000 historical invoices from QuickBooks. Each invoice has 2-5 line items. Total: 150,000 line items.
The relationship is: Invoice (parent) → Line Items (children)
If you import line items BEFORE invoices:
Line items reference non-existent invoices → Foreign key constraint violation → 4 hours of rework
If you import invoices WITHOUT line items:
Invoices have no detail → GL posting is wrong → Data integrity is broken
Scenario 3: The Many-to-Many Tags Puzzle
You have 3,200 customers. Each customer has multiple tags: "VIP," "Newsletter," "High-Spending," "Referral."
Odoo uses a many-to-many relationship:
• Customers table
• Tags table
• Customer-Tags junction table (links each customer to multiple tags)
If you import customers without tags:
Data is incomplete → Can't run reports like "VIP customer sales"
If you import tags as comma-separated text:
Odoo doesn't recognize it → You manually link 3,200 customers to tags → 150+ hours
TECHNIQUE 1: Dependency Sequencing (Load Order Matters)
Rule: Always import parent data before child data.
| Parent (Import First) | Child (Import After) |
|---|---|
| Product Categories | Products |
| Customers | Orders |
| Invoices | Invoice Line Items |
| Products | Stock Adjustments |
| Suppliers | Purchase Orders |
Example: Multi-Level Product Hierarchy
Week 1:
- Import Top-Level Categories: Apparel, Accessories, Electronics (10 records)
- Verify they created successfully
Week 2:
- Import Sub-Categories:
- Men (parent: Apparel)
- Women (parent: Apparel)
- Clothing (parent: Accessories)
- (60 records total)
- Each sub-category references a parent category that EXISTS
Week 3:
- Import Sub-Sub-Categories:
- Shirts (parent: Men)
- Dresses (parent: Women)
- Jewelry (parent: Accessories)
- (200 records)
Week 4:
- Import Products:
- TSH-001 (category: Shirts)
- DRS-001 (category: Dresses)
- JWL-001 (category: Jewelry)
- (2,847 products)
- Each product references a category that EXISTS
Import all 2,847 products with their categories
- Products reference categories that don't exist yet
- Foreign key constraints fail
- Import partially succeeds, partially fails
- Rework: 40+ hours manually linking products to categories
Cost of sequencing: 4 hours planning. Cost of not sequencing: 40+ hours rework.
TECHNIQUE 2: External IDs (Solving the ID Problem)
The Core Problem
When you export data from QuickBooks, customer #5892 becomes Odoo customer #847 (different ID). When you import orders that reference customer #5892, Odoo can't find them (it only knows about customer #847).
Solution: Use External IDs.
An External ID is a field that says: "This Odoo record was originally customer #5892 in QuickBooks."
How to Set Up External IDs
In Odoo, every import has two columns:
Model ID: Odoo's internal ID (auto-generated, changes when you reimport)
External ID: Your old system's ID (stable, never changes)
External ID | Name | Email
CUST-5892 | John Smith | john@example.com
CUST-5893 | Jane Doe | jane@example.com
CUST-5894 | Bob Johnson | bob@example.com
When you import, Odoo creates records AND stores the mapping:
• CUST-5892 → Odoo Customer ID 847
• CUST-5893 → Odoo Customer ID 848
• CUST-5894 → Odoo Customer ID 849
Now when you import orders:
External ID | Customer (External ID) | Amount
ORDER-9123 | CUST-5892 | $1,245
ORDER-9124 | CUST-5893 | $2,340
Odoo automatically resolves:
✓ CUST-5892 → Odoo Customer ID 847
✓ Links the order to the correct customer
✓ No confusion, no broken relationships
Cost of setting up External IDs: 2 hours. Cost of not having them: 80+ hours manual linking.
TECHNIQUE 3: Many-to-Many Field Formatting
The Challenge
You need to import customers with multiple tags. Odoo's Many2Many relationship requires special formatting.
How Many2Many Fields Work
In Odoo, a Many2Many field stores a LIST of related records. But CSV files can only have one value per cell.
Solution: Comma-separated values in a special format.
External ID | Name | Tags
CUST-001 | John Smith | "VIP,Newsletter,High-Value"
CUST-002 | Jane Doe | "VIP,Referral"
CUST-003 | Bob Johnson | "Newsletter"
Importing this into Odoo:
✓ Odoo recognizes the Tags field is Many2Many
✓ Sees the comma-separated values
✓ Looks up each tag in the Tags table
✓ Links the customer to ALL matching tags
⚠️ Wrong Format: VIP, Newsletter, High-Value (with spaces after commas) → Odoo can't parse it. Tags import as text, not relationships. Manual rework: 150+ hours.
Advanced Example: Product-Variant Many2Many
External ID | SKU | Name | Attributes
PROD-001 | TSH-001 | Basic T-Shirt | "Size:S,Size:M,Size:L,Color:Black,Color:White"
PROD-002 | DRS-001 | Summer Dress | "Size:XS,Size:S,Size:M,Color:Blue,Color:Green"
Odoo automatically:
✓ Splits the comma-separated attributes
✓ Finds matching attribute values
✓ Links product to all variants
✓ One CSV row creates multiple variant combinations
TECHNIQUE 4: Handling Circular Dependencies
The Problem
Sometimes data has circular relationships:
• Product A references Supplier B
• Supplier B references Product A (as their flagship product)
Solution: Two-Pass Import
External ID | SKU | Name
PROD-001 | TSH-001 | Basic T-Shirt
PROD-002 | DRS-001 | Summer Dress
External ID | Name | Flagship Product (External ID)
SUPP-001 | TextileCo | PROD-001
SUPP-002 | FashionMills | PROD-002
External ID | SKU | Supplier (External ID)
PROD-001 | TSH-001 | SUPP-001
PROD-002 | DRS-001 | SUPP-002
Three passes breaks the circular dependency.
TECHNIQUE 5: Referential Integrity Validation
Before final import, validate that all relationships will work.
Validation Checklist
❏ Count parent records in source system
❏ Count parent records being imported to Odoo
❏ Do they match? If not, you're missing parents.
import pandas as pd
from collections import Counter
# Load data
customers = pd.read_csv('customers.csv')
orders = pd.read_csv('orders.csv')
# Validation: Every order's customer_id must exist in customers
valid_customer_ids = set(customers['External ID'])
order_customer_ids = set(orders['Customer External ID'])
invalid_orders = order_customer_ids - valid_customer_ids
if invalid_orders:
print(f"ERROR: {len(invalid_orders)} orders reference non-existent customers")
print(f"Invalid customer IDs: {invalid_orders}")
else:
print("✓ All order-customer relationships are valid")
# Validation: Check for duplicates
duplicate_customers = customers[customers.duplicated(['External ID'], keep=False)]
if len(duplicate_customers) > 0:
print(f"ERROR: {len(duplicate_customers)} duplicate customer External IDs found")
else:
print("✓ No duplicate External IDs")
Cost of validation: 4-6 hours. Cost of bad relationships post-import: 80+ hours rework.
TECHNIQUE 6: Mapping Complex Hierarchies
The Challenge
You have a 3-level product category hierarchy:
• Apparel (Level 1)
• Men (Level 2)
• Shirts (Level 3)
Odoo can only assign ONE category per product (the leaf node: "Shirts"). But you need to track all levels for reporting.
Solution: Use Tags for Secondary Hierarchies
Primary Category (Odoo native field): Shirts
Tags (Secondary category tracking): Apparel, Men, Shirts
External ID | SKU | Name | Primary Category | Category Tags
PROD-001 | TSH-001 | Basic T-Shirt | Shirts | "Apparel,Men,Shirts"
PROD-002 | TSH-002 | Polo Shirt | Shirts | "Apparel,Men,Shirts"
PROD-003 | DRS-001 | Summer Dress | Dresses | "Apparel,Women,Dresses"
Now you can:
✓ Report by primary category (Shirts)
✓ Report by tag level (all products tagged "Men")
✓ Report by all three levels combined
Real-World Example: Complete Multi-Relationship Import
Scenario: $2.2M D2C brand importing from QuickBooks + Shopify + spreadsheets
Data Structure
Product Categories
150 (3-level hierarchy)
Products
2,847
Customers
3,200 (with tags)
Historical Orders
50,000 (150k line items)
Suppliers
1,500
Correct Import Sequence
| Week | Import Item | Records |
|---|---|---|
| 1 | Top-level categories, Tags | 10 + 50 |
| 2 | Sub-categories, Suppliers | 60 + 1,500 |
| 3 | Sub-sub-categories, Products (Pass 1) | 200 + 2,847 |
| 4 | Products (Pass 2: add supplier links) | 2,847 |
| 5 | Customers, Orders, Line Items | 3,200 + 50k + 150k |
Timeline: 5 weeks, careful planning, zero errors
Alternative (Bad Approach): Import everything at once, encounter 80+ errors, rework for 6 weeks
Action Items: Handle Complex Relationships
Before Your Next Import
❏ Map all relationships (one-to-many, many-to-many, circular)
❏ Determine load sequence (parents before children)
❏ Set up External IDs in your import file
❏ Create referential integrity validation script
❏ Test import on 100-200 sample records first
During Import
❏ Monitor for errors (especially foreign key violations)
❏ Stop and investigate any errors immediately
❏ Don't "skip errors and fix later"
Frequently Asked Questions
What's the most common relationship import mistake?
Importing children before parents. Example: Importing orders before customers exist. This causes foreign key violations and forces you to re-import everything.
How do I know if I need External IDs?
If you're importing data from another system (QuickBooks, Shopify, etc.) where records have IDs, you need External IDs. Without them, you can't link related records.
Can I import many-to-many relationships in one pass?
Yes, using comma-separated values in the CSV. Format: "Tag1,Tag2,Tag3" (no spaces after commas). Odoo will create all relationships automatically.
What's the cost of getting relationships wrong?
$28,000-$80,000 in rework time for a typical D2C brand. You'll spend 200+ hours manually fixing broken relationships, deleting bad data, and re-importing.
Free Data Relationship Audit
Stop guessing about how to structure your relationships. We'll map all relationships in your source data, identify circular dependencies, and design the correct import sequence. Most D2C brands discover their relationships are more complex than they thought. Getting this right upfront saves $40,000-$80,000 in post-import rework.
