- Add SYNC_STRATEGY_ESPOCRM_BASED.md detailing the sync flows and status management. - Create utilities for sync operations in services/beteiligte_sync_utils.py, including locking, timestamp comparison, conflict resolution, and notification handling. - Implement entity mapping between EspoCRM and Advoware in services/espocrm_mapper.py. - Develop a cron job for periodic sync checks in steps/vmh/beteiligte_sync_cron_step.py, emitting events for entities needing synchronization.
244 lines
8.5 KiB
Python
244 lines
8.5 KiB
Python
"""
|
|
EspoCRM ↔ Advoware Entity Mapper
|
|
|
|
Transformiert Beteiligte zwischen den beiden Systemen basierend auf ENTITY_MAPPING_CBeteiligte_Advoware.md
|
|
"""
|
|
|
|
from typing import Dict, Any, Optional, List
|
|
from datetime import datetime
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class BeteiligteMapper:
|
|
"""Mapper für CBeteiligte (EspoCRM) ↔ Beteiligte (Advoware)"""
|
|
|
|
@staticmethod
|
|
def map_cbeteiligte_to_advoware(espo_entity: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
Transformiert EspoCRM CBeteiligte → Advoware Beteiligte Format
|
|
|
|
Args:
|
|
espo_entity: CBeteiligte Entity von EspoCRM
|
|
|
|
Returns:
|
|
Dict für Advoware API (POST/PUT /api/v1/advonet/Beteiligte)
|
|
"""
|
|
logger.debug(f"Mapping EspoCRM → Advoware: {espo_entity.get('id')}")
|
|
|
|
# Bestimme ob Person oder Firma
|
|
is_firma = bool(espo_entity.get('firmenname'))
|
|
rechtsform = espo_entity.get('rechtsform', '')
|
|
|
|
# Basis-Struktur
|
|
advo_data = {
|
|
'rechtsform': rechtsform,
|
|
}
|
|
|
|
# NAME: Person vs. Firma
|
|
if is_firma:
|
|
# Firma: name = firmenname
|
|
advo_data['name'] = espo_entity.get('firmenname', '')
|
|
advo_data['vorname'] = None
|
|
else:
|
|
# Person: name = lastName, vorname = firstName
|
|
advo_data['name'] = espo_entity.get('lastName', '')
|
|
advo_data['vorname'] = espo_entity.get('firstName', '')
|
|
|
|
# ANREDE
|
|
salutation = espo_entity.get('salutationName', '')
|
|
if salutation:
|
|
advo_data['anrede'] = salutation
|
|
|
|
# GEBURTSDATUM
|
|
date_of_birth = espo_entity.get('dateOfBirth')
|
|
if date_of_birth:
|
|
advo_data['geburtsdatum'] = date_of_birth
|
|
|
|
# KONTAKTDATEN
|
|
# E-Mail (emailAddressData ist Array, wir nehmen Primary)
|
|
email_data = espo_entity.get('emailAddressData')
|
|
if email_data and isinstance(email_data, list):
|
|
primary_email = next((e for e in email_data if e.get('primary')), None)
|
|
if primary_email:
|
|
advo_data['emailGesch'] = primary_email.get('emailAddress')
|
|
elif espo_entity.get('emailAddress'):
|
|
advo_data['emailGesch'] = espo_entity.get('emailAddress')
|
|
|
|
# Telefon (phoneNumberData ist Array, wir nehmen Primary)
|
|
phone_data = espo_entity.get('phoneNumberData')
|
|
if phone_data and isinstance(phone_data, list):
|
|
primary_phone = next((p for p in phone_data if p.get('primary')), None)
|
|
if primary_phone:
|
|
phone_num = primary_phone.get('phoneNumber')
|
|
phone_type = primary_phone.get('type', '').lower()
|
|
|
|
if 'mobile' in phone_type or 'mobil' in phone_type:
|
|
advo_data['mobil'] = phone_num
|
|
else:
|
|
advo_data['telGesch'] = phone_num
|
|
elif espo_entity.get('phoneNumber'):
|
|
advo_data['telGesch'] = espo_entity.get('phoneNumber')
|
|
|
|
# HANDELSREGISTER (nur für Firmen)
|
|
if is_firma:
|
|
hr_nummer = espo_entity.get('handelsregisterNummer')
|
|
if hr_nummer:
|
|
advo_data['handelsRegisterNummer'] = hr_nummer
|
|
|
|
# DISGTYP (EspoCRM spezifisch - falls vorhanden)
|
|
disgtyp = espo_entity.get('disgTyp')
|
|
if disgtyp:
|
|
advo_data['disgTyp'] = disgtyp
|
|
|
|
logger.debug(f"Mapped to Advoware: name={advo_data.get('name')}, vorname={advo_data.get('vorname')}")
|
|
|
|
return advo_data
|
|
|
|
@staticmethod
|
|
def map_advoware_to_cbeteiligte(advo_entity: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
Transformiert Advoware Beteiligte → EspoCRM CBeteiligte Format
|
|
|
|
Args:
|
|
advo_entity: Beteiligter von Advoware API
|
|
|
|
Returns:
|
|
Dict für EspoCRM API (POST/PUT /api/v1/CBeteiligte)
|
|
"""
|
|
logger.debug(f"Mapping Advoware → EspoCRM: betNr={advo_entity.get('betNr')}")
|
|
|
|
# Bestimme ob Person oder Firma
|
|
vorname = advo_entity.get('vorname')
|
|
is_person = bool(vorname)
|
|
|
|
# Basis-Struktur
|
|
espo_data = {
|
|
'rechtsform': advo_entity.get('rechtsform', ''),
|
|
'betnr': advo_entity.get('betNr'), # Link zu Advoware
|
|
}
|
|
|
|
# NAME: Person vs. Firma
|
|
if is_person:
|
|
# Person
|
|
espo_data['firstName'] = vorname
|
|
espo_data['lastName'] = advo_entity.get('name', '')
|
|
espo_data['name'] = f"{vorname} {advo_entity.get('name', '')}".strip()
|
|
espo_data['firmenname'] = None
|
|
else:
|
|
# Firma
|
|
espo_data['firmenname'] = advo_entity.get('name', '')
|
|
espo_data['name'] = advo_entity.get('name', '')
|
|
espo_data['firstName'] = None
|
|
espo_data['lastName'] = None
|
|
|
|
# ANREDE
|
|
anrede = advo_entity.get('anrede')
|
|
if anrede:
|
|
espo_data['salutationName'] = anrede
|
|
|
|
# GEBURTSDATUM
|
|
geburtsdatum = advo_entity.get('geburtsdatum')
|
|
if geburtsdatum:
|
|
espo_data['dateOfBirth'] = geburtsdatum
|
|
|
|
# KONTAKTDATEN
|
|
# E-Mail (emailGesch ist primary)
|
|
email_gesch = advo_entity.get('emailGesch')
|
|
email = advo_entity.get('email')
|
|
|
|
primary_email = email_gesch or email
|
|
if primary_email:
|
|
espo_data['emailAddress'] = primary_email
|
|
espo_data['emailAddressData'] = [
|
|
{
|
|
'emailAddress': primary_email,
|
|
'primary': True,
|
|
'optOut': False,
|
|
'invalid': False
|
|
}
|
|
]
|
|
|
|
# Telefon (telGesch ist primary, mobil als secondary)
|
|
tel_gesch = advo_entity.get('telGesch')
|
|
tel_privat = advo_entity.get('telPrivat')
|
|
mobil = advo_entity.get('mobil')
|
|
|
|
phone_data = []
|
|
|
|
# Primary: telGesch oder telPrivat
|
|
primary_tel = tel_gesch or tel_privat
|
|
if primary_tel:
|
|
espo_data['phoneNumber'] = primary_tel
|
|
phone_data.append({
|
|
'phoneNumber': primary_tel,
|
|
'primary': True,
|
|
'type': 'Office' if tel_gesch else 'Home'
|
|
})
|
|
|
|
# Secondary: mobil
|
|
if mobil and mobil != primary_tel:
|
|
phone_data.append({
|
|
'phoneNumber': mobil,
|
|
'primary': False,
|
|
'type': 'Mobile'
|
|
})
|
|
|
|
if phone_data:
|
|
espo_data['phoneNumberData'] = phone_data
|
|
|
|
# HANDELSREGISTER (nur für Firmen)
|
|
if not is_person:
|
|
hr_nummer = advo_entity.get('handelsRegisterNummer')
|
|
if hr_nummer:
|
|
espo_data['handelsregisterNummer'] = hr_nummer
|
|
|
|
# DISGTYP
|
|
disgtyp = advo_entity.get('disgTyp')
|
|
if disgtyp:
|
|
espo_data['disgTyp'] = disgtyp
|
|
|
|
logger.debug(f"Mapped to EspoCRM: name={espo_data.get('name')}")
|
|
|
|
return espo_data
|
|
|
|
@staticmethod
|
|
def get_changed_fields(espo_entity: Dict[str, Any], advo_entity: Dict[str, Any]) -> List[str]:
|
|
"""
|
|
Vergleicht zwei Entities und gibt Liste der geänderten Felder zurück
|
|
|
|
Args:
|
|
espo_entity: EspoCRM CBeteiligte
|
|
advo_entity: Advoware Beteiligte
|
|
|
|
Returns:
|
|
Liste von Feldnamen die unterschiedlich sind
|
|
"""
|
|
# Mappe Advoware zu EspoCRM Format für Vergleich
|
|
mapped_advo = BeteiligteMapper.map_advoware_to_cbeteiligte(advo_entity)
|
|
|
|
changed = []
|
|
|
|
# Vergleiche wichtige Felder
|
|
compare_fields = [
|
|
'name', 'firstName', 'lastName', 'firmenname',
|
|
'emailAddress', 'phoneNumber',
|
|
'dateOfBirth', 'rechtsform',
|
|
'handelsregisterNummer'
|
|
]
|
|
|
|
for field in compare_fields:
|
|
espo_val = espo_entity.get(field)
|
|
advo_val = mapped_advo.get(field)
|
|
|
|
# Normalisiere None und leere Strings
|
|
espo_val = espo_val if espo_val else None
|
|
advo_val = advo_val if advo_val else None
|
|
|
|
if espo_val != advo_val:
|
|
changed.append(field)
|
|
logger.debug(f"Field '{field}' changed: EspoCRM='{espo_val}' vs Advoware='{advo_val}'")
|
|
|
|
return changed
|