Step-by-step guide for ERP data migration. Covers data profiling, field mapping, transformation rules, validation testing, cutover planning, and common pitfalls.
ERP data migration is the process of moving structured business data—such as customer master records, inventory items, financial GL accounts, and sales orders—from legacy systems into a modern ERP platform. This playbook applies when you are mapping source fields to target fields and validating the migrated data for accuracy, completeness, and consistency. It is used during the data migration phase of an ERP implementation, especially when transitioning from multiple siloed systems (e.g., SAP, Oracle, FileMaker, or flat CSVs) into a unified ERP like Microsoft Dynamics 365 Finance & Operations, SAP S/4HANA, or Oracle Fusion.
Field mapping defines how data from the source system is transformed and placed into the corresponding fields in the ERP target. The goal is to ensure that every business attribute has a correct, reliable, and traceable path from source to target.
Each field mapping is defined by a source field, target field, and transformation logic. The source field may be a column in a CSV, a database column in a legacy system, or a field in a JSON API response. The target field is a database column in the ERP, often defined in a table like CustTable or InventTable.
Mapping rules are stored in a mapping configuration file that describes:
LegacySAP or CSV-2023-01)SalesOrderHeader, Vendor)sourceFieldtargetFieldtransformation (e.g., trim, uppercase, formatDate, lookup)required (boolean)defaultValueA minimal configuration in JSON:
{
"mappingId": "cust-migration-2024",
"sourceSystem": "CSV-2023-12",
"targetEntity": "CustTable",
"mappings": [
{
"sourceField": "CustomerID",
"targetField": "AccountNumber",
"transformation": "trim",
"required": true
},
{
"sourceField": "Name",
"targetField": "Name",
"transformation": "uppercase",
"required": true
},
{
"sourceField": "BillingCity",
"targetField": "City",
"transformation": "formatDate:YYYY-MM-DD",
"defaultValue": "Unknown"
}
]
}
Transformations are applied sequentially. Common ones include:
trim — removes leading/trailing whitespaceuppercase, lowercasepadLeft(length, char) — pads a value with a character to a fixed lengthformatDate(inputFormat, outputFormat) — converts 12/31/2023 to 2023-12-31lookup(table, keyField, valueField) — resolves a code to a description via a reference tableFor lookup, the source system must provide a lookup table. For example, to map CountryCode to CountryName, the system reads a Country table from the legacy system and uses it as a lookup.
The transformation engine executes on each row of input data. A transformation failure results in a mapping error, logged in a MappingErrorLog table with:
rowIdsourceRowNumbersourceFieldtargetFieldtransformationoriginalValueerrorMessageWhen a required field is missing or malformed:
MigrationLog with level: errorfailed in MigrationBatchValidationReport is generated, listing all failures by source file and row.Example error in logs:
[ERROR] Mapping failed for row 2034 in batch 'cust-migration-2024'
Source: CustomerID = "C00123 "
Target: AccountNumber = "C00123"
Transformation: trim → failed: expected "C00123", got "C00123 " (trailing space)
Reason: Source field 'CustomerID' is required but value is null or empty
Validation verifies that migrated data meets business rules, integrity constraints, and consistency criteria. It runs after the field mapping phase and before cutover.
Each validation rule is a SQL predicate or JSON-based condition that evaluates data in the target ERP. Rules are defined in a ValidationRule table:
| ruleId | description | query | enabled | severity |
|---|---|---|---|---|
| cust-101 | Customer must have at least one address | SELECT * FROM CustTable t JOIN CustTableAddress a ON t.RecId = a.CustTable WHERE a.RecId IS NOT NULL | true | warning |
| item-203 | Item cost must be positive | SELECT * FROM InventTable WHERE CostPrice <= 0 | true | error |
The validation engine executes all enabled rules and records results in ValidationResult:
{
"ruleId": "item-203",
"status": "failed",
"message": "Found 12 items with cost price <= 0",
"affectedRows": [
{ "itemId": "MAT-001", "costPrice": 0 },
{ "itemId": "LAB-005", "costPrice": -5.75 }
],
"executionTime": "2024-04-05T14:33:22Z"
}
Completeness is checked by counting records and verifying required fields.
A completeness check is defined by:
entity (e.g., CustTable)requiredFields: list of field namesthreshold: minimum number of records expectedThe validation engine runs:
SELECT
COUNT(*) AS totalRecords,
COUNT(CASE WHEN Name IS NOT NULL THEN 1 END) AS nameFilled,
COUNT(CASE WHEN AccountNumber IS NOT NULL THEN 1 END) AS accountNumberFilled
FROM CustTable
WHERE Status = 'Active';
If totalRecords < threshold or any requiredField has NULL values, the validation fails.
Consistency checks ensure referential integrity and business logic.
Example: Validate that every SalesOrderHeader has a CustAccount that exists in CustTable.
The engine runs:
SELECT
so.RecId,
so.CustAccount,
COUNT(*) AS missingCustomerCount
FROM SalesOrderHeader so
LEFT JOIN CustTable ct ON so.CustAccount = ct.AccountNumber
WHERE ct.RecId IS NULL
GROUP BY so.RecId, so.CustAccount;
If the result has any rows, the validation fails with a list of missing customer accounts.
A ValidationReport is generated after all rules are executed. It includes:
The report is published to a shared location (e.g., /reports/migration/2024-04-05/validation-report.json), and consumed by the ERP’s MigrationDashboard.
trim transformation is often applied, but if the target field is VARCHAR(20) and the source is TEXT, a trim step may be missed. The result: C00123 vs C00123 in the ERP, causing duplicate customers.12/31/2023 in source is interpreted as December 31 in target, but 2023-12-31 is interpreted as December 31. The transformation formatDate must specify input and output formats explicitly.CountryCode US is not in the lookup table, the result is NULL, but the transformation does not report this. The fix: add lookupFallback to the transformation, which sets a default value when lookup fails.OFFSET and LIMIT, and process in batches.item-203 must run after item-101 (which creates InventTable records). The engine must track rule dependencies and run them in order.ValidationResult. The result: reports show no data, even though queries ran.mapping-v1.2.json, but the configuration is updated without incrementing the version. The result: old batches use outdated rules.staging/cust-migration-2024.csv)INSERT INTO CustTable (...) VALUES (...))ValidationReport.json and publish to shared storageThe workflow is orchestrated using a script:
#!/bin/bash
MAPPING_FILE="config/mappings/cust-migration-2024.json"
SOURCE_DATA="staging/cust-migration-2024.csv"
TARGET_BATCH="cust-migration-2024"
# Step 1: Map and transform
./map-data.sh \
--input "$SOURCE_DATA" \
--mapping "$MAPPING_FILE" \
--output "transformed/cust-migration-2024.json"
# Step 2: Load into ERP
curl -X POST \
-H "Content-Type: application/json" \
-d @transformed/cust-migration-2024.json \
http://erp/api/v1/data/load/custtable
# Step 3: Validate
./validate-data.sh \
--batch "$TARGET_BATCH" \
--output "reports/validation-report-$(date +%Y-%m-%d).json"
# Step 4: Publish
aws s3 cp reports/validation-report-*.json s3://erp-migration/reports/
# Step 5: Mark batch as complete
psql -c "UPDATE MigrationBatch SET status = 'completed', reportPath = 'reports/validation-report-*.json' WHERE batchId = '$TARGET_BATCH'"
This workflow runs in CI/CD, ensuring consistency across multiple migration cycles.
required: true in mappinginputFormat and outputFormat in transformationlookupFallback for missing keysbatchId and associated reportPathThis playbook is the foundation of reliable, traceable, and maintainable ERP data migration.
This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy