feat: Enhance sync conflict detection and resolution logic in BeteiligteSync class

This commit is contained in:
2026-02-07 20:04:58 +00:00
parent 101f290293
commit 3d3014750f
2 changed files with 41 additions and 33 deletions

View File

@@ -241,28 +241,37 @@ class BeteiligteSync:
# PRIMÄR: rowId-basierte Änderungserkennung (zuverlässiger!) # PRIMÄR: rowId-basierte Änderungserkennung (zuverlässiger!)
espo_rowid = espo_entity.get('advowareRowId') espo_rowid = espo_entity.get('advowareRowId')
advo_rowid = advo_entity.get('rowId') advo_rowid = advo_entity.get('rowId')
last_sync = espo_entity.get('advowareLastSync')
espo_modified = espo_entity.get('modifiedAt')
if espo_rowid and advo_rowid: if espo_rowid and advo_rowid and last_sync:
if espo_rowid != advo_rowid: # Prüfe ob Advoware geändert wurde (rowId)
# rowId unterschiedlich → Advoware wurde geändert advo_changed = (espo_rowid != advo_rowid)
# Prüfe ob EspoCRM auch geändert wurde (seit letztem Sync)
espo_changed = False
if espo_modified:
try:
espo_ts = self.parse_timestamp(espo_modified)
sync_ts = self.parse_timestamp(last_sync)
if espo_ts and sync_ts:
espo_changed = (espo_ts > sync_ts)
except Exception as e:
self._log(f"Timestamp-Parse-Fehler: {e}", level='debug')
# Konfliktlogik: Beide geändert seit letztem Sync?
if advo_changed and espo_changed:
self._log(f"🚨 KONFLIKT: Beide Seiten geändert seit letztem Sync")
return 'conflict'
elif advo_changed:
self._log(f"Advoware rowId geändert: {espo_rowid[:20]}... → {advo_rowid[:20]}...") self._log(f"Advoware rowId geändert: {espo_rowid[:20]}... → {advo_rowid[:20]}...")
return 'advoware_newer' return 'advoware_newer'
elif espo_changed:
self._log(f"EspoCRM neuer (modifiedAt > lastSync)")
return 'espocrm_newer'
else: else:
# rowId gleich → keine Änderung in Advoware # Weder Advoware noch EspoCRM geändert
# Prüfe ob EspoCRM geändert wurde (via modifiedAt) return 'no_change'
espo_modified = espo_entity.get('modifiedAt')
last_sync = espo_entity.get('advowareLastSync')
if espo_modified and last_sync:
try:
espo_ts = self.parse_timestamp(espo_modified)
sync_ts = self.parse_timestamp(last_sync)
if espo_ts and sync_ts and espo_ts > sync_ts:
self._log(f"EspoCRM neuer (rowId gleich, aber modifiedAt > lastSync)")
return 'espocrm_newer'
except Exception as e:
self._log(f"Timestamp-Parse-Fehler: {e}", level='debug')
# Keine Änderungen # Keine Änderungen
self._log("Keine Änderungen (rowId identisch)") self._log("Keine Änderungen (rowId identisch)")
@@ -502,10 +511,6 @@ class BeteiligteSync:
update_data.update(extra_fields) update_data.update(extra_fields)
await self.espocrm.update_entity('CBeteiligte', entity_id, update_data) await self.espocrm.update_entity('CBeteiligte', entity_id, update_data)
'advowareLastSync': now,
'syncErrorMessage': f"Konflikt am {now}: {conflict_details}. EspoCRM hat gewonnen.",
'syncRetryCount': 0
})
self._log(f"Konflikt gelöst für {entity_id}: EspoCRM wins") self._log(f"Konflikt gelöst für {entity_id}: EspoCRM wins")

View File

@@ -30,8 +30,9 @@ class BeteiligteMapper:
""" """
logger.debug(f"Mapping EspoCRM → Advoware STAMMDATEN: {espo_entity.get('id')}") logger.debug(f"Mapping EspoCRM → Advoware STAMMDATEN: {espo_entity.get('id')}")
# Bestimme ob Person oder Firma # Bestimme ob Person oder Firma (über firmenname-Feld)
is_firma = bool(espo_entity.get('firmenname')) firmenname = espo_entity.get('firmenname')
is_firma = bool(firmenname and firmenname.strip())
rechtsform = espo_entity.get('rechtsform', '') rechtsform = espo_entity.get('rechtsform', '')
# Basis-Struktur (nur Stammdaten, keine Kontaktdaten!) # Basis-Struktur (nur Stammdaten, keine Kontaktdaten!)
@@ -41,11 +42,11 @@ class BeteiligteMapper:
# NAME: Person vs. Firma # NAME: Person vs. Firma
if is_firma: if is_firma:
# Firma: name = firmenname # Firma: Lese von firmenname-Feld
advo_data['name'] = espo_entity.get('firmenname', '') advo_data['name'] = firmenname
advo_data['vorname'] = None advo_data['vorname'] = None
else: else:
# Person: name = lastName, vorname = firstName # Natürliche Person: Lese von lastName/firstName
advo_data['name'] = espo_entity.get('lastName', '') advo_data['name'] = espo_entity.get('lastName', '')
advo_data['vorname'] = espo_entity.get('firstName', '') advo_data['vorname'] = espo_entity.get('firstName', '')
@@ -100,17 +101,19 @@ class BeteiligteMapper:
'advowareRowId': advo_entity.get('rowId'), # Änderungserkennung 'advowareRowId': advo_entity.get('rowId'), # Änderungserkennung
} }
# NAME: Person vs. Firma # NAME: Person vs. Firma (EspoCRM blendet lastName/firstName aus bei Firmen)
if is_person: if is_person:
# Person # Natürliche Person → lastName/firstName verwenden
espo_data['firstName'] = vorname espo_data['firstName'] = vorname
espo_data['lastName'] = advo_entity.get('name', '') espo_data['lastName'] = advo_entity.get('name', '')
espo_data['name'] = f"{vorname} {advo_entity.get('name', '')}".strip() espo_data['name'] = f"{vorname} {advo_entity.get('name', '')}".strip()
espo_data['firmenname'] = None espo_data['firmenname'] = None # Firma-Feld leer lassen
else: else:
# Firma # Firma → firmenname verwenden (EspoCRM zeigt dann nur dieses Feld)
espo_data['firmenname'] = advo_entity.get('name', '') firma_name = advo_entity.get('name', '')
espo_data['name'] = advo_entity.get('name', '') espo_data['firmenname'] = firma_name
espo_data['name'] = firma_name
# lastName/firstName nicht setzen (EspoCRM blendet sie aus bei Firmen)
espo_data['firstName'] = None espo_data['firstName'] = None
espo_data['lastName'] = None espo_data['lastName'] = None