Data mapping during ERP migration: Seamlessly synchronizing PlentyONE and Odoo
Data transport is the solved problem in an ERP migration. The unresolved one is semantic mapping: A field that uses PlentyONE as a simple property may require its own object relationship in Odoo's relational data model. If this translation is set up incorrectly, it doesn't result in error messages, but rather incorrect links – often only becoming apparent weeks after go-live.
This article treats mapping for what it is: an architectural task with clear rules. Business keys instead of surrogate keys, documented responsibilities for each entity, and validation before import. It also includes reference implementations for customers, prices, inventory, and orders, which allow these rules to be implemented productively.
Why data mapping is the real migration risk
PlentyONE, Shopware, and Odoo each use their own historically evolved data models for customers, products, variants, and prices. A field that is a simple property in one system may require its own relational structure in the target system. Those who underestimate this mapping and transfer data "mechanically" 1:1 will, at best, produce import errors—at worst, unnoticed incorrect mappings that only become apparent during operation.
This page deals with the mapping itself – that is, the semantic assignment between the systems. It clarifies which data should be included in the migration scope. PlentyONE Odoo Data Migration; how such a mapping is technically flawless when transferred to the productive go-live is shown ERP migration without downtime.
Proprietary structures vs. relational Odoo models
Odoo 19 uses a consistently relational model: objects such as customers, products, or orders consist of linked data records, not flat, self-contained tables. Therefore, PlentyONE and Shopware structures must not only be copied but also translated into this relational model – with correct foreign key relationships for customers, products, variants, and pricing logic.
Business Keys vs. Surrogate Keys
The central technical principle for any reputable ERP mapping: the strict separation between surrogate keys and business keys.
- Surrogate Keys These are purely technical, system-generated internal IDs (such as the numeric database ID from PlentyONE). They must never be used as primary keys between systems, as Odoo generates its own sequences and collisions would otherwise be unavoidable.
- Business Keys These are unique, technical identifiers that remain stable regardless of the system – such as the article number (SKU), the EAN, the VAT ID or the UUID from the Shopware frontend.
Mapping is therefore done exclusively via business keys. These are stored in dedicated custom fields in Odoo (e.g., x_shopware_uuid), to ensure referential integrity permanently. This decision is not an ad-hoc practice, but is explicitly documented in the underlying architecture as an Architecture Decision Record: If surrogate keys are mapped instead, immediate mapping errors are likely when creating new objects, because immutable business keys like the UUID prevent this confusion from the outset.
A practical example illustrates the risk: If a customer is accidentally deleted in Odoo and later recreated, they automatically receive a new internal database ID. If this internal ID were the reference between the systems, the connector would no longer be able to link the "new" customer to their previous order history. The stable business key reference – such as the original PlentyONE customer number or the Shopware UUID – remains valid in this case.
Product and variant mapping
Odoo structurally separates the parent product (product.template) and the specific variant (product.productThe mapping must accurately reflect this separation:
| Source system | Goal in Odoo 19 |
|---|---|
| Shopware parent | product.template |
| Shopware variant | product.product |
The SKU serves as a central, immutable business key throughout the entire process. If an SKU is missing, the import of that specific item is deliberately blocked by the ETL process, rather than generating an unclear data record.
Categories are always imported from top to bottom to ensure the category tree is built correctly. Since the Shopware frontend uses UUIDs internally instead of sequential IDs, these UUIDs are permanently stored in the Odoo database to maintain the stability of the relationship even after changes.
Reference implementation: Customer import
The customer module imports companies, contacts, delivery and billing addresses, as well as price lists, tax data, and fiscal positions in a clean parent-child structure. The order is deliberately fixed: first the parent company, then the associated contacts. This structure prevents a contact from being created in the system without a valid company assignment – an error that would be immediately apparent in downstream processes such as invoicing.
Reference implementation: Price import
To synchronize purchase prices, sales prices, tiered pricing, and B2B price lists, imports are carried out exclusively via the resource-efficient system. write()– or load()-Methods of the JSON-2 API. Price lists in Odoo are independent objects linked to customers or customer groups – individual B2B conditions are therefore not maintained as an additional field for the customer, but are created and referenced as a separate price list. This makes it possible to reuse the same pricing logic later for new customers with comparable conditions without having to rebuild the mapping.
Reference implementation: Inventory
Inventory synchronization transfers the inventory from the source system to the Odoo model. stock.quantThis is done exclusively via an audit-proof inventory (referred to as "Inventory Adjustment" in Odoo terminology), followed by strict validation of the target stock levels. A common technical error at this point is attempting to directly adjust stock levels via create() to create with arbitrary field values. Since Odoo 15, this direct write access is blocked by the system – stock levels may only be updated via the permitted inventory functions of stock.quant They can even be manipulated. This restriction is not an obstacle, but rather protects against precisely the kind of uncontrolled inventory changes that lead to negative or inexplicable stock levels in historically grown systems.
Reference implementation: Orders
When transferring historical orders, order items, and delivery and invoice statuses, one important rule applies: Delivered, invoiced, and open quantities must be reconstructed based on the actual historical documents and delivery/invoice statuses. Directly equating the delivered and invoiced quantity with the originally ordered quantity is only permissible if it corresponds to the historical situation from a technical perspective—for example, in the case of fully completed orders without partial deliveries. Otherwise, the ERP system would incorrectly classify the order as fully completed, even though a partial delivery or invoice was actually still outstanding. Therefore, the specific migration logic for this scenario must always be reviewed on a version- and implementation-specific basis and not automated across the board.
Customers, categories, manufacturers and taxes
Odoo manages companies and contacts in a shared parent-child model: The company is the parent, and the associated contacts are the child records. Based on the country of origin and VAT ID, the system automatically assigns the correct fiscal position (e.g., domestic, EU, third country) during import – a prerequisite for legally compliant invoice generation. The existing customer number from PlentyONE is retained as a unique reference.
A potentially serious error path in practice: If the fiscal position is incorrectly assigned – for example, because a country of origin was incorrectly entered – the system calculates incorrect tax logic on all subsequent invoices for that customer. This error often goes unnoticed until an external audit or a customer inquiry uncovers it. Therefore, validating the fiscal position assignment should be part of every dry run, not just the post-go-live check.
Validate mapping
Before any production import, systematic validation at the database level is essential. Typical, directly verifiable sources of error include:
Duplicate SKUs (critical error in variant assignment):
SELECT default_code, COUNT(*) FROM product_product
GROUP BY default_code HAVING COUNT(*) > 1;Products without a sales price:
SELECT id, name->>'en_US' AS name FROM product_template
WHERE list_price IS NULL OR list_price = 0;Negative inventory levels (Read-only check – correction is made exclusively via the system's permitted inventory functions, not via direct write access):
SELECT product_id, location_id, quantity FROM stock_quant
WHERE quantity < 0;These and other checks should be an integral part of every dry run – not a retrospective troubleshooting task after go-live. Two additional checks also belong in this recipe book: searching for variants without a valid parent product and checking for duplicate customer numbers or email addresses, which indicate accidental multiple entries in the source system. Experience shows that both of these errors are more common than a single faulty SKU, but can be detected just as reliably using simple SQL queries on the target database.
A complete validation round before production import should cover at least the following categories:
| Test category | Typical mistake | Impact if undetected |
|---|---|---|
| SKU uniqueness | duplicate article numbers | incorrect variant assignment |
| Parent product reference | Variant without product.template | blocked or faulty import |
| Price completeness | missing selling price | Item not for sale |
| Inventory plausibility | negative balances | faulty availability display |
| Fiscal position | Incorrect country of origin/VAT ID | incorrect tax calculation |
| Customer duplicates | Multiple email addresses created | inconsistent order history |
Each of these categories can be checked directly on the target database using relatively simple, repeatable queries – provided that these checks are a fixed, automated part of the dry run and not an occasional sample.
We explain how a complete test migration and cutover process is structured in [section/document]. ERP migration without downtimeWe describe the most common specific mapping pitfalls – from UUID conflicts to missing relations – in detail in [section/document]. PlentyONE Odoo migration error.
Mapping is one of several project phases. Our overview describes the complete process from system analysis to go-live. Migration from PlentyONE to Odoo.
Frequently asked questions about data mapping
Why can’t we simply use the PlentyONE ID as a reference in Odoo?
Because it’s a surrogate key – a purely technical, system-generated ID that has no business meaning and can lead to conflicts or invalid entries when the data record is recreated. Odoo assigns its own internal IDs via its own sequences; only a stable business key like the SKU or the Shopware UUID guarantees correct recognition across both systems.
What happens if an SKU is not unique in the source system?
The import of this specific item is intentionally blocked rather than creating an unclear or potentially incorrectly assigned data record. Duplicates must therefore be identified and cleaned up before the production import.
Can we test the mapping step by step without resetting the target system each time?
Yes. The `load()` method works idempotently with external IDs: an existing dataset is updated upon re-import instead of being duplicated. This allows for any number of repeated test runs against the same staging environment.
How long does a complete mapping project for a medium-sized catalog take?
This depends heavily on the complexity of the variant structure, the data quality, and the number of existing custom fields. A reliable time estimate is only possible after a technical analysis of your specific data structures.
We don’t know how accurately our article numbers and customer data are maintained.
This can be measured in advance, not estimated: Duplicate article numbers, variants without a parent product and missing mandatory fields are visible via simple queries at the database level – before the first productive import runs.
Sources
The concept of External IDs (XML IDs) and their role in data import is based on the official Odoo documentation: Odoo 19 – Export and import dataThe restriction of direct write access to stock.quant (since Odoo 15, over) _get_inventory_fields_write()) is described in both the technical migration guide and the Odoo documentation. Inventory adjustments proven.
Have the technical data mapping checked
A technical analysis of your systems will reveal whether your existing data model can be cleanly mapped to Odoo using business keys, or where specific conflicts exist regarding variants, customers, or pricing logic. This is precisely what we provide as part of our service. free, non-binding e-commerce audits.
Have your data mapping technically checked – free of charge and without obligation.
✔ Free of charge ✔ No obligation ✔ Concrete mapping logic instead of theory ✔ Response within 24 hours