Data Sovereignty and GDPR Compliance in Enterprise ERP Systems - AegeanFlows
How to architect SAP S4HANA and Odoo integrations that meet GDPR data residency requirements, minimise personal data exposure, and survive a regulatory audit.
The Compliance Challenge in ERP Integrations
When enterprises integrate ERP systems — SAP S4HANA sending data to Odoo, or Odoo pushing order data to a third-party logistics platform — they create data flows that may fall squarely under GDPR jurisdiction. Customer names, VAT numbers, delivery addresses, and purchase histories are all personal data under Article 4(1) of the GDPR.
The problem is not that integration teams are careless. It’s that GDPR compliance gets designed out during architecture review but never designed in during implementation. A well-intentioned developer creates an iFlow that passes a full customer record to an external API “for context,” not realising that the address fields constitute a GDPR-regulated personal data transfer.
This article provides a technical framework for building integrations that are compliant by design.
Personal Data in ERP Systems: What You’re Actually Handling
In SAP S4HANA, personal data is distributed across dozens of tables. The key ones:
| SAP Object | Table | Personal Data Fields |
|---|---|---|
| Business Partner | BUT000 | Name, gender, birth date |
| BP Address | ADRC | Street, city, postal code |
| BP Communication | ADR6 | Email address |
| Customer (FI) | KNA1 | Name 1, Name 2 |
| Employee | PA0002 | Name, national ID, birth date |
| User | USR02 | Username, email (in some configs) |
In Odoo, the equivalent is concentrated in res.partner:
name,email,phone,mobilestreet,city,zip,country_idvat(can be a natural person’s tax number in sole-trader scenarios)comment(free-text, often contains highly personal information added by sales reps)
The comment field is a GDPR risk. Sales teams write notes like “CEO, prefers calls on Tuesday mornings, going through a divorce.” This is unstructured personal data with no retention policy. Include it in your data inventory and apply a retention automation.
Data Residency: Keeping EU Data in the EU
GDPR Article 44 restricts transfers of personal data to third countries (outside the EEA) unless adequate safeguards exist. For enterprise integrations, this has three practical implications:
1. Cloud Infrastructure Location
If your SAP runs on AWS eu-central-1 (Frankfurt) and you’re integrating with a SaaS CRM whose nearest region is us-east-1 (Virginia), personal data is leaving the EEA. You need a Data Processing Agreement (DPA) and, depending on the destination country, a Standard Contractual Clause (SCC) or adequacy decision.
Architecture rule: For data flows involving personal data, map each destination and verify its GDPR legal basis before implementation, not after.
2. SAP BTP Subaccount Configuration
BTP Integration Suite allows you to pin subaccounts to specific regions. For EU-data-residency requirements:
BTP Global Account
└── Subaccount: PROD-EU (region: eu10 — Frankfurt)
└── Integration Suite
└── Subaccount: DEV (region: eu10 — Frankfurt)
└── Subaccount: US-NONPROD (region: us10 — Virginia)
→ Contains NO personal data (only synthetic test data)
Never process real customer data in non-EU subaccounts during testing. Use anonymised or synthetically generated test data.
3. Odoo Hosting
Odoo.sh data centres include EU options (Liège, Belgium). If you’re self-hosting, ensure the hosting provider has EU DPA and your backup destination is also in the EU.
Data Minimisation in Integration Design
GDPR Article 5(1)(c) requires that personal data be “adequate, relevant and limited to what is necessary.” In integration terms: don’t send fields you don’t need.
Before:
# BAD: Sending full partner record
partner_data = {
'name': bp['BusinessPartnerFullName'],
'email': bp['EmailAddress'],
'phone': bp['PhoneNumber'],
'mobile': bp['MobilePhoneNumber'],
'street': bp['StreetName'],
'city': bp['CityName'],
'zip': bp['PostalCode'],
'country_id': country_id,
'vat': bp['TaxNumber1'],
'birth_date': bp['BirthDate'], # ← Not needed for invoicing
'gender': bp['MaritalStatus'], # ← Definitely not needed
'comment': bp['InternalNotes'], # ← Potentially sensitive
}
After:
# GOOD: Only what's needed for invoicing and delivery
partner_data = {
'name': bp['BusinessPartnerFullName'],
'street': bp['StreetName'],
'city': bp['CityName'],
'zip': bp['PostalCode'],
'country_id': country_id,
'vat': bp['TaxNumber1'],
'x_sap_bp_id': bp['BusinessPartner'], # reference, not personal data
}
Create a field inventory for every integration. Document each field transferred, its personal data category (GDPR Art. 9 special category vs. ordinary personal data), and the legal basis for processing.
Pseudonymisation and Tokenisation
For integrations where you need referential integrity but not the underlying personal data, pseudonymisation is the answer.
Tokenisation example: A charter operator integrates SAP (passenger manifest data) with a third-party seat assignment system. The seat assignment system needs to know which passenger is in which seat, but doesn’t need their full name or passport number.
import hashlib
import hmac
SECRET_KEY = os.environ['PSEUDONYM_KEY'] # 32+ byte secret, stored in vault
def pseudonymise(personal_data: str) -> str:
"""Create a consistent, reversible-only-by-us pseudonym."""
return hmac.new(
SECRET_KEY.encode(),
personal_data.encode(),
hashlib.sha256
).hexdigest()[:16]
# Usage
passenger_token = pseudonymise(f"{passport_number}:{full_name}")
# Result: "a3f9b21c8e4d0f7a" — consistent for same input, meaningless to third party
The seat assignment system uses the token as the passenger identifier. If a GDPR erasure request comes in, you delete the real passenger record in SAP — the token in the seat assignment system becomes an orphaned reference with no personal data attached.
Retention Policies: Automated Enforcement in Odoo
Data minimisation requires not just collecting less, but deleting older data. Odoo doesn’t have a built-in data retention engine, but it’s straightforward to build one:
class DataRetentionPolicy(models.Model):
_name = 'data.retention.policy'
model_id = fields.Many2one('ir.model', required=True)
field_ids = fields.Many2many('ir.model.fields', string='Fields to Clear')
retention_days = fields.Integer(required=True, default=365)
domain = fields.Char(default='[]', help='Odoo domain to filter records')
last_run = fields.Datetime()
def apply_retention(self):
cutoff_date = fields.Datetime.now() - timedelta(days=self.retention_days)
Model = self.env[self.model_id.model]
domain = safe_eval(self.domain) + [('write_date', '<', cutoff_date)]
records = Model.search(domain)
if records:
clear_vals = {field.name: False for field in self.field_ids}
records.sudo().write(clear_vals)
self.env['data.retention.log'].create({
'policy_id': self.id,
'records_processed': len(records),
'run_date': fields.Datetime.now(),
})
self.last_run = fields.Datetime.now()
Configure cron-based execution and give your DPO (Data Protection Officer) access to the retention policy dashboard.
Audit Trails: Proving Compliance
GDPR requires you to demonstrate compliance, not just achieve it. For ERP integrations, this means:
1. Integration Audit Log
Every data transfer should produce an audit record:
class IntegrationAuditLog(models.Model):
_name = 'integration.audit.log'
timestamp = fields.Datetime(default=fields.Datetime.now, readonly=True)
source_system = fields.Char(readonly=True)
target_system = fields.Char(readonly=True)
data_category = fields.Selection([
('personal', 'Personal Data'),
('financial', 'Financial Data'),
('operational', 'Operational Data'),
])
record_count = fields.Integer(readonly=True)
fields_transferred = fields.Text(readonly=True)
legal_basis = fields.Selection([
('contract', 'Contract Performance'),
('legal_obligation', 'Legal Obligation'),
('legitimate_interest', 'Legitimate Interests'),
('consent', 'Consent'),
])
operator = fields.Char(readonly=True) # integration service account name
2. Subject Access Request Support
When a data subject requests access to their data (GDPR Article 15), you need to pull records from both SAP and Odoo. Build a cross-system search tool:
def get_person_data_across_systems(email: str) -> dict:
"""Collect all data held about a person across integrated systems."""
results = {}
# Search Odoo
odoo_partner = odoo_client.execute(
'res.partner', 'search_read',
[('email', '=', email)],
{'fields': ['name', 'street', 'city', 'phone', 'vat', 'write_date']}
)
results['odoo_partner'] = odoo_partner
# Search SAP (via BTP API)
sap_bp = call_sap_api(
f"/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner"
f"?$filter=EmailAddress eq '{email}'"
)
results['sap_business_partner'] = sap_bp
return results
Right to Erasure: The Integration Challenge
Erasure (GDPR Article 17) is the hardest GDPR requirement for integrated ERP systems. Deleting a customer from Odoo doesn’t delete them from SAP; deleting from SAP may break Odoo’s referential integrity.
Recommended approach: Erasure by pseudonymisation
Rather than deleting, overwrite personal data fields with the pseudonymised token:
def erase_partner(partner_id: int, reason: str):
"""GDPR erasure: replace personal data with non-reversible pseudonym."""
partner = env['res.partner'].browse(partner_id)
token = generate_erasure_token(partner.id)
partner.write({
'name': f'[ERASED-{token}]',
'email': f'erased-{token}@deleted.invalid',
'phone': False,
'mobile': False,
'street': False,
'city': False,
'zip': False,
'comment': False,
'active': False,
})
env['gdpr.erasure.log'].create({
'partner_id': partner_id,
'erasure_token': token,
'erased_by': env.user.id,
'reason': reason,
'timestamp': fields.Datetime.now(),
})
Keep the erasure log (with the token, not the personal data) for compliance purposes — you need to prove erasure occurred.
Compliance Checklist Before Go-Live
- Personal data fields inventoried for all integrated models
- Legal basis documented for each personal data flow
- Data residency confirmed (EU endpoints for EU personal data)
- Data minimisation applied (only necessary fields transferred)
- Retention policies configured and scheduled
- Audit logging enabled for all personal data transfers
- Right to access: cross-system search tool exists
- Right to erasure: pseudonymisation procedure defined
- DPAs signed with all third-party processors (BTP, cloud hosting)
- Privacy Impact Assessment (DPIA) completed if high-risk processing
Conclusion
GDPR compliance in ERP integrations is not a post-implementation checkbox. It requires deliberate architecture decisions: which fields cross system boundaries, where data is processed, how long it is retained, and how erasure requests are handled.
The technical patterns in this article — data minimisation in API calls, pseudonymisation tokens, automated retention, cross-system audit logs — give your integration team a concrete implementation path. The earlier these are built into the design, the cheaper they are to maintain.
Building a GDPR-compliant SAP or Odoo integration? Contact AegeanFlows for a compliance review.
Kostas Polakis — Enterprise Integration Architect & DPO advisory consultant, Athens, Greece.