feat: Enhance Advoware API integration with backward compatibility for data payloads and improve logging for sync events
This commit is contained in:
387
bitbylaw/scripts/test_beteiligte_sync.py
Executable file
387
bitbylaw/scripts/test_beteiligte_sync.py
Executable file
@@ -0,0 +1,387 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Beteiligte Sync Test Script
|
||||
|
||||
Testet die vollständige Sync-Funktionalität:
|
||||
1. Mapper-Transformationen
|
||||
2. Lock-Mechanismus
|
||||
3. Timestamp-Vergleich
|
||||
4. CREATE in Advoware (optional)
|
||||
5. UPDATE Sync (optional)
|
||||
6. Konflikt-Resolution
|
||||
|
||||
Usage:
|
||||
python test_beteiligte_sync.py --test-transforms # Nur Mapper testen
|
||||
python test_beteiligte_sync.py --test-live # Live-Test mit echten APIs
|
||||
python test_beteiligte_sync.py --entity-id=XXX # Spezifische Entity testen
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
sys.path.insert(0, '/opt/motia-app/bitbylaw')
|
||||
|
||||
from services.espocrm import EspoCRMAPI
|
||||
from services.advoware import AdvowareAPI
|
||||
from services.espocrm_mapper import BeteiligteMapper
|
||||
from services.beteiligte_sync_utils import BeteiligteSync
|
||||
|
||||
|
||||
class MockContext:
|
||||
"""Mock Context für Testing ohne Motia Workbench"""
|
||||
class Logger:
|
||||
def info(self, msg): print(f"ℹ️ {msg}")
|
||||
def debug(self, msg): print(f"🔍 {msg}")
|
||||
def warning(self, msg): print(f"⚠️ {msg}")
|
||||
def error(self, msg): print(f"❌ {msg}")
|
||||
|
||||
def __init__(self):
|
||||
self.logger = self.Logger()
|
||||
|
||||
|
||||
async def test_transforms():
|
||||
"""Test 1: Mapper-Transformationen"""
|
||||
print("\n" + "="*80)
|
||||
print("TEST 1: Mapper-Transformationen")
|
||||
print("="*80)
|
||||
|
||||
mapper = BeteiligteMapper()
|
||||
|
||||
# Test 1a: Person EspoCRM → Advoware
|
||||
print("\n📤 Test 1a: Person EspoCRM → Advoware")
|
||||
espo_person = {
|
||||
'id': 'test123',
|
||||
'firstName': 'Angela',
|
||||
'lastName': 'Mustermann',
|
||||
'rechtsform': 'Frau',
|
||||
'emailAddress': 'angela@example.com',
|
||||
'emailAddressData': [
|
||||
{'emailAddress': 'angela@example.com', 'primary': True}
|
||||
],
|
||||
'phoneNumber': '+49123456789',
|
||||
'dateOfBirth': '1980-05-15'
|
||||
}
|
||||
|
||||
advo_result = mapper.map_cbeteiligte_to_advoware(espo_person)
|
||||
print(f"✅ Mapped:")
|
||||
print(json.dumps(advo_result, indent=2, ensure_ascii=False))
|
||||
|
||||
# Test 1b: Firma EspoCRM → Advoware
|
||||
print("\n📤 Test 1b: Firma EspoCRM → Advoware")
|
||||
espo_firma = {
|
||||
'id': 'test456',
|
||||
'firmenname': 'Mustermann GmbH',
|
||||
'rechtsform': 'GmbH',
|
||||
'emailAddress': 'info@mustermann.de',
|
||||
'handelsregisterNummer': 'HRB 12345'
|
||||
}
|
||||
|
||||
advo_firma = mapper.map_cbeteiligte_to_advoware(espo_firma)
|
||||
print(f"✅ Mapped:")
|
||||
print(json.dumps(advo_firma, indent=2, ensure_ascii=False))
|
||||
|
||||
# Test 1c: Advoware → EspoCRM (Person)
|
||||
print("\n📥 Test 1c: Advoware → EspoCRM (Person)")
|
||||
advo_person = {
|
||||
'betNr': 104860,
|
||||
'vorname': 'Max',
|
||||
'name': 'Mustermann',
|
||||
'rechtsform': 'Herr',
|
||||
'emailGesch': 'max@example.com',
|
||||
'telGesch': '+49987654321',
|
||||
'geburtsdatum': '1975-03-20'
|
||||
}
|
||||
|
||||
espo_result = mapper.map_advoware_to_cbeteiligte(advo_person)
|
||||
print(f"✅ Mapped:")
|
||||
print(json.dumps(espo_result, indent=2, ensure_ascii=False))
|
||||
|
||||
print("\n✅ MAPPER TESTS PASSED!")
|
||||
|
||||
|
||||
async def test_lock_mechanism():
|
||||
"""Test 2: Lock-Mechanismus"""
|
||||
print("\n" + "="*80)
|
||||
print("TEST 2: Lock-Mechanismus")
|
||||
print("="*80)
|
||||
|
||||
espocrm = EspoCRMAPI()
|
||||
context = MockContext()
|
||||
sync_utils = BeteiligteSync(espocrm, context)
|
||||
|
||||
# Hole erste Entity
|
||||
result = await espocrm.list_entities('CBeteiligte', max_size=1)
|
||||
if not result.get('list'):
|
||||
print("❌ Keine Entities gefunden zum Testen")
|
||||
return
|
||||
|
||||
entity = result['list'][0]
|
||||
entity_id = entity['id']
|
||||
original_status = entity.get('syncStatus', 'clean')
|
||||
|
||||
print(f"\n🔒 Test Lock für Entity: {entity.get('name')} (ID: {entity_id})")
|
||||
print(f" Original Status: {original_status}")
|
||||
|
||||
# Test Lock Acquire
|
||||
print("\n1. Acquire Lock...")
|
||||
lock1 = await sync_utils.acquire_sync_lock(entity_id)
|
||||
print(f" Lock 1: {lock1} (erwartet: True)")
|
||||
|
||||
# Verify Status
|
||||
entity_check = await espocrm.get_entity('CBeteiligte', entity_id)
|
||||
print(f" Status nach Lock: {entity_check.get('syncStatus')} (erwartet: syncing)")
|
||||
|
||||
# Test Lock Already Held
|
||||
print("\n2. Versuche Lock erneut zu holen (sollte fehlschlagen)...")
|
||||
lock2 = await sync_utils.acquire_sync_lock(entity_id)
|
||||
print(f" Lock 2: {lock2} (erwartet: False)")
|
||||
|
||||
# Release Lock
|
||||
print("\n3. Release Lock...")
|
||||
await sync_utils.release_sync_lock(entity_id, 'clean')
|
||||
|
||||
entity_final = await espocrm.get_entity('CBeteiligte', entity_id)
|
||||
print(f" Status nach Release: {entity_final.get('syncStatus')} (erwartet: clean)")
|
||||
|
||||
# Restore Original
|
||||
if original_status != 'clean':
|
||||
await espocrm.update_entity('CBeteiligte', entity_id, {'syncStatus': original_status})
|
||||
print(f" Status restored to: {original_status}")
|
||||
|
||||
print("\n✅ LOCK TESTS PASSED!")
|
||||
|
||||
|
||||
async def test_timestamp_comparison():
|
||||
"""Test 3: Timestamp-Vergleich"""
|
||||
print("\n" + "="*80)
|
||||
print("TEST 3: Timestamp-Vergleich")
|
||||
print("="*80)
|
||||
|
||||
espocrm = EspoCRMAPI()
|
||||
context = MockContext()
|
||||
sync_utils = BeteiligteSync(espocrm, context)
|
||||
|
||||
# Test-Timestamps
|
||||
now = datetime.now()
|
||||
old = datetime(2026, 2, 1, 10, 0, 0)
|
||||
newer = datetime(2026, 2, 7, 14, 0, 0)
|
||||
|
||||
print("\n📅 Test-Timestamps:")
|
||||
print(f" old: {old}")
|
||||
print(f" newer: {newer}")
|
||||
print(f" now: {now}")
|
||||
|
||||
# Scenario 1: EspoCRM neuer
|
||||
print("\n1. EspoCRM neuer als Advoware:")
|
||||
result = sync_utils.compare_timestamps(newer, old, old)
|
||||
print(f" Result: {result} (erwartet: espocrm_newer)")
|
||||
|
||||
# Scenario 2: Advoware neuer
|
||||
print("\n2. Advoware neuer als EspoCRM:")
|
||||
result = sync_utils.compare_timestamps(old, newer, old)
|
||||
print(f" Result: {result} (erwartet: advoware_newer)")
|
||||
|
||||
# Scenario 3: Konflikt (beide geändert)
|
||||
print("\n3. Beide nach last_sync geändert (Konflikt):")
|
||||
result = sync_utils.compare_timestamps(newer, newer, old)
|
||||
print(f" Result: {result} (erwartet: conflict)")
|
||||
|
||||
# Scenario 4: Keine Änderungen
|
||||
print("\n4. Keine Änderungen seit last_sync:")
|
||||
result = sync_utils.compare_timestamps(old, old, newer)
|
||||
print(f" Result: {result} (erwartet: no_change)")
|
||||
|
||||
print("\n✅ TIMESTAMP TESTS PASSED!")
|
||||
|
||||
|
||||
async def test_live_entity(entity_id=None, dry_run=True):
|
||||
"""Test 4: Live Entity Sync (optional mit echtem API-Call)"""
|
||||
print("\n" + "="*80)
|
||||
print("TEST 4: Live Entity Sync")
|
||||
print("="*80)
|
||||
|
||||
espocrm = EspoCRMAPI()
|
||||
advoware = AdvowareAPI(MockContext())
|
||||
mapper = BeteiligteMapper()
|
||||
context = MockContext()
|
||||
sync_utils = BeteiligteSync(espocrm, context)
|
||||
|
||||
# Hole Entity
|
||||
if not entity_id:
|
||||
result = await espocrm.list_entities('CBeteiligte', max_size=5)
|
||||
entities = result.get('list', [])
|
||||
|
||||
# Suche eine mit betnr
|
||||
entity = next((e for e in entities if e.get('betnr')), entities[0] if entities else None)
|
||||
|
||||
if not entity:
|
||||
print("❌ Keine Entity gefunden")
|
||||
return
|
||||
|
||||
entity_id = entity['id']
|
||||
else:
|
||||
entity = await espocrm.get_entity('CBeteiligte', entity_id)
|
||||
|
||||
print(f"\n📋 Test Entity:")
|
||||
print(f" ID: {entity_id}")
|
||||
print(f" Name: {entity.get('name')}")
|
||||
print(f" betNr: {entity.get('betnr')}")
|
||||
print(f" syncStatus: {entity.get('syncStatus')}")
|
||||
print(f" modifiedAt: {entity.get('modifiedAt')}")
|
||||
print(f" advowareLastSync: {entity.get('advowareLastSync')}")
|
||||
|
||||
betnr = entity.get('betnr')
|
||||
|
||||
# Test Transformation
|
||||
print("\n🔄 Transformation EspoCRM → Advoware:")
|
||||
advo_data = mapper.map_cbeteiligte_to_advoware(entity)
|
||||
print(json.dumps(advo_data, indent=2, ensure_ascii=False))
|
||||
|
||||
# Wenn betnr vorhanden, teste Fetch von Advoware
|
||||
if betnr:
|
||||
print(f"\n📥 Fetch von Advoware (betNr={betnr}):")
|
||||
try:
|
||||
advo_result = await advoware.api_call(
|
||||
f'api/v1/advonet/Beteiligte/{betnr}',
|
||||
method='GET'
|
||||
)
|
||||
|
||||
if isinstance(advo_result, list):
|
||||
advo_entity = advo_result[0] if advo_result else None
|
||||
else:
|
||||
advo_entity = advo_result
|
||||
|
||||
if advo_entity:
|
||||
print(f"✅ Von Advoware geladen:")
|
||||
print(f" name: {advo_entity.get('name')}")
|
||||
print(f" vorname: {advo_entity.get('vorname')}")
|
||||
print(f" geaendertAm: {advo_entity.get('geaendertAm')}")
|
||||
|
||||
# Timestamp-Vergleich
|
||||
print(f"\n⏱️ Timestamp-Vergleich:")
|
||||
comparison = sync_utils.compare_timestamps(
|
||||
entity.get('modifiedAt'),
|
||||
advo_entity.get('geaendertAm'),
|
||||
entity.get('advowareLastSync')
|
||||
)
|
||||
print(f" Result: {comparison}")
|
||||
|
||||
# Zeige was geändert wäre
|
||||
changed = mapper.get_changed_fields(entity, advo_entity)
|
||||
if changed:
|
||||
print(f"\n📝 Geänderte Felder: {', '.join(changed)}")
|
||||
else:
|
||||
print(f"\n✅ Keine Feld-Unterschiede")
|
||||
else:
|
||||
print("❌ Keine Daten von Advoware")
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Fehler beim Fetch von Advoware: {e}")
|
||||
else:
|
||||
print("\n⚠️ Keine betnr vorhanden (Entity wurde noch nicht gesynct)")
|
||||
|
||||
if not dry_run:
|
||||
print("\n🆕 Würde CREATE in Advoware ausführen:")
|
||||
print(f" POST /api/v1/advonet/Beteiligte")
|
||||
print(f" Data: {json.dumps(advo_data, indent=2, ensure_ascii=False)}")
|
||||
print("\n⚠️ DRY RUN - Nicht ausgeführt!")
|
||||
|
||||
print("\n✅ LIVE ENTITY TEST COMPLETE!")
|
||||
|
||||
|
||||
async def test_full_sync_handler(entity_id):
|
||||
"""Test 5: Vollständiger Sync-Handler (simuliert Event)"""
|
||||
print("\n" + "="*80)
|
||||
print("TEST 5: Vollständiger Sync-Handler")
|
||||
print("="*80)
|
||||
|
||||
# Import Handler
|
||||
sys.path.insert(0, '/opt/motia-app/bitbylaw/steps/vmh')
|
||||
import beteiligte_sync_event_step
|
||||
|
||||
# Mock Event Data
|
||||
event_data = {
|
||||
'entity_id': entity_id,
|
||||
'action': 'sync_check',
|
||||
'source': 'test_script',
|
||||
'timestamp': datetime.now().isoformat()
|
||||
}
|
||||
|
||||
context = MockContext()
|
||||
|
||||
print(f"\n🎬 Simuliere Event für Entity: {entity_id}")
|
||||
print(f" Event: {json.dumps(event_data, indent=2)}")
|
||||
|
||||
print("\n⚠️ ACHTUNG: Dies führt ECHTE API-Calls aus!")
|
||||
response = input("Fortfahren? (y/N): ")
|
||||
|
||||
if response.lower() != 'y':
|
||||
print("❌ Abgebrochen")
|
||||
return
|
||||
|
||||
print("\n🚀 Handler wird ausgeführt...\n")
|
||||
|
||||
try:
|
||||
await beteiligte_sync_event_step.handler(event_data, context)
|
||||
print("\n✅ HANDLER ERFOLGREICH!")
|
||||
except Exception as e:
|
||||
print(f"\n❌ HANDLER FEHLER: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
async def main():
|
||||
parser = argparse.ArgumentParser(description='Test Beteiligte Sync')
|
||||
parser.add_argument('--test-transforms', action='store_true', help='Nur Mapper testen')
|
||||
parser.add_argument('--test-lock', action='store_true', help='Lock-Mechanismus testen')
|
||||
parser.add_argument('--test-timestamps', action='store_true', help='Timestamp-Vergleich testen')
|
||||
parser.add_argument('--test-live', action='store_true', help='Live-Test mit APIs')
|
||||
parser.add_argument('--test-full-handler', action='store_true', help='Vollständiger Handler (ECHTE CALLS!)')
|
||||
parser.add_argument('--entity-id', type=str, help='Spezifische Entity-ID testen')
|
||||
parser.add_argument('--all', action='store_true', help='Alle Tests ausführen')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Default: Alle Tests
|
||||
if not any([args.test_transforms, args.test_lock, args.test_timestamps,
|
||||
args.test_live, args.test_full_handler, args.all]):
|
||||
args.all = True
|
||||
|
||||
print("🧪 Beteiligte Sync Test Suite")
|
||||
print("="*80)
|
||||
|
||||
try:
|
||||
if args.all or args.test_transforms:
|
||||
await test_transforms()
|
||||
|
||||
if args.all or args.test_lock:
|
||||
await test_lock_mechanism()
|
||||
|
||||
if args.all or args.test_timestamps:
|
||||
await test_timestamp_comparison()
|
||||
|
||||
if args.all or args.test_live:
|
||||
await test_live_entity(args.entity_id, dry_run=True)
|
||||
|
||||
if args.test_full_handler:
|
||||
if not args.entity_id:
|
||||
print("\n❌ --entity-id erforderlich für --test-full-handler")
|
||||
else:
|
||||
await test_full_sync_handler(args.entity_id)
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("✅ ALLE TESTS ABGESCHLOSSEN!")
|
||||
print("="*80)
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ FEHLER: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
@@ -130,6 +130,9 @@ class AdvowareAPI:
|
||||
effective_headers = headers.copy() if headers else {}
|
||||
effective_headers['Authorization'] = f'Bearer {token}'
|
||||
effective_headers.setdefault('Content-Type', 'application/json')
|
||||
|
||||
# Prefer 'data' parameter over 'json_data' if provided (for backward compatibility)
|
||||
json_payload = data if data is not None else json_data
|
||||
|
||||
async with aiohttp.ClientSession(timeout=effective_timeout) as session:
|
||||
try:
|
||||
@@ -137,13 +140,13 @@ class AdvowareAPI:
|
||||
self.context.logger.debug(f"Making API call: {method} {url}")
|
||||
else:
|
||||
logger.debug(f"Making API call: {method} {url}")
|
||||
async with session.request(method, url, headers=effective_headers, params=params, json=json_data) as response:
|
||||
async with session.request(method, url, headers=effective_headers, params=params, json=json_payload) as response:
|
||||
response.raise_for_status()
|
||||
if response.status == 401:
|
||||
self._log("401 Unauthorized, refreshing token")
|
||||
token = self.get_access_token(force_refresh=True)
|
||||
effective_headers['Authorization'] = f'Bearer {token}'
|
||||
async with session.request(method, url, headers=effective_headers, params=params, json=json_data) as response:
|
||||
async with session.request(method, url, headers=effective_headers, params=params, json=json_payload) as response:
|
||||
response.raise_for_status()
|
||||
return await response.json() if response.content_type == 'application/json' else None
|
||||
response.raise_for_status()
|
||||
|
||||
@@ -17,21 +17,24 @@ class BeteiligteMapper:
|
||||
@staticmethod
|
||||
def map_cbeteiligte_to_advoware(espo_entity: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Transformiert EspoCRM CBeteiligte → Advoware Beteiligte Format
|
||||
Transformiert EspoCRM CBeteiligte → Advoware Beteiligte Format (STAMMDATEN)
|
||||
|
||||
WICHTIG: Kontaktdaten (Telefon, Email, Fax, Bankverbindungen) werden über
|
||||
separate Advoware-Endpoints gesynct und sind NICHT Teil dieser Mapping-Funktion.
|
||||
|
||||
Args:
|
||||
espo_entity: CBeteiligte Entity von EspoCRM
|
||||
|
||||
Returns:
|
||||
Dict für Advoware API (POST/PUT /api/v1/advonet/Beteiligte)
|
||||
Dict mit Stammdaten für Advoware API (POST/PUT /api/v1/advonet/Beteiligte)
|
||||
"""
|
||||
logger.debug(f"Mapping EspoCRM → Advoware: {espo_entity.get('id')}")
|
||||
logger.debug(f"Mapping EspoCRM → Advoware STAMMDATEN: {espo_entity.get('id')}")
|
||||
|
||||
# Bestimme ob Person oder Firma
|
||||
is_firma = bool(espo_entity.get('firmenname'))
|
||||
rechtsform = espo_entity.get('rechtsform', '')
|
||||
|
||||
# Basis-Struktur
|
||||
# Basis-Struktur (nur Stammdaten, keine Kontaktdaten!)
|
||||
advo_data = {
|
||||
'rechtsform': rechtsform,
|
||||
}
|
||||
@@ -56,43 +59,15 @@ class BeteiligteMapper:
|
||||
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
|
||||
# TODO: Weitere Stammdaten-Felder hier ergänzen (Steuernummer, etc.)
|
||||
|
||||
logger.debug(f"Mapped to Advoware: name={advo_data.get('name')}, vorname={advo_data.get('vorname')}")
|
||||
logger.debug(f"Mapped to Advoware STAMMDATEN: name={advo_data.get('name')}, vorname={advo_data.get('vorname')}, rechtsform={rechtsform}")
|
||||
|
||||
return advo_data
|
||||
|
||||
@@ -143,63 +118,16 @@ class BeteiligteMapper:
|
||||
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
|
||||
# TODO: Weitere Stammdaten-Felder hier ergänzen
|
||||
# HINWEIS: Kontaktdaten (Telefon, Email, Fax) werden über separate Endpoints gesynct
|
||||
|
||||
logger.debug(f"Mapped to EspoCRM: name={espo_data.get('name')}")
|
||||
logger.debug(f"Mapped to EspoCRM STAMMDATEN: name={espo_data.get('name')}")
|
||||
|
||||
return espo_data
|
||||
|
||||
|
||||
@@ -195,6 +195,30 @@ async def handle_update(entity_id, betnr, espo_entity, espocrm, advoware, sync_u
|
||||
|
||||
context.logger.info(f"⏱️ Timestamp-Vergleich: {comparison}")
|
||||
|
||||
# SPECIAL: Wenn LastSync null → immer von EspoCRM syncen (initial sync)
|
||||
if not espo_entity.get('advowareLastSync'):
|
||||
context.logger.info(f"📤 Initial Sync → EspoCRM STAMMDATEN zu Advoware")
|
||||
|
||||
# WICHTIG: Advoware benötigt vollständiges Objekt für PUT
|
||||
# Mapper liefert nur STAMMDATEN (keine Kontaktdaten - die kommen später über separate Endpoints)
|
||||
advo_updates = mapper.map_cbeteiligte_to_advoware(espo_entity)
|
||||
|
||||
# Merge mit aktuellen Advoware-Daten
|
||||
merged_data = {**advo_entity, **advo_updates}
|
||||
|
||||
context.logger.info(f"📝 Merge: {len(advo_updates)} Stammdaten-Felder → {len(merged_data)} Gesamt-Felder")
|
||||
context.logger.debug(f" Gesynct: {', '.join(advo_updates.keys())}")
|
||||
|
||||
await advoware.api_call(
|
||||
f'api/v1/advonet/Beteiligte/{betnr}',
|
||||
method='PUT',
|
||||
data=merged_data
|
||||
)
|
||||
|
||||
await sync_utils.release_sync_lock(entity_id, 'clean')
|
||||
context.logger.info(f"✅ Advoware aktualisiert (initial sync)")
|
||||
return
|
||||
|
||||
# KEIN SYNC NÖTIG
|
||||
if comparison == 'no_change':
|
||||
context.logger.info(f"✅ Keine Änderungen, Sync übersprungen")
|
||||
@@ -203,14 +227,22 @@ async def handle_update(entity_id, betnr, espo_entity, espocrm, advoware, sync_u
|
||||
|
||||
# ESPOCRM NEUER → Update Advoware
|
||||
if comparison == 'espocrm_newer':
|
||||
context.logger.info(f"📤 EspoCRM ist neuer → Update Advoware")
|
||||
context.logger.info(f"📤 EspoCRM ist neuer → Update Advoware STAMMDATEN")
|
||||
|
||||
advo_data = mapper.map_cbeteiligte_to_advoware(espo_entity)
|
||||
# WICHTIG: Advoware benötigt vollständiges Objekt für PUT
|
||||
# Mapper liefert nur STAMMDATEN (keine Kontaktdaten - die kommen über separate Endpoints)
|
||||
advo_updates = mapper.map_cbeteiligte_to_advoware(espo_entity)
|
||||
|
||||
# Merge mit aktuellen Advoware-Daten
|
||||
merged_data = {**advo_entity, **advo_updates}
|
||||
|
||||
context.logger.info(f"📝 Merge: {len(advo_updates)} Stammdaten-Felder → {len(merged_data)} Gesamt-Felder")
|
||||
context.logger.debug(f" Gesynct: {', '.join(advo_updates.keys())}")
|
||||
|
||||
await advoware.api_call(
|
||||
f'api/v1/advonet/Beteiligte/{betnr}',
|
||||
method='PUT',
|
||||
data=advo_data
|
||||
data=merged_data
|
||||
)
|
||||
|
||||
await sync_utils.release_sync_lock(entity_id, 'clean')
|
||||
@@ -218,7 +250,7 @@ async def handle_update(entity_id, betnr, espo_entity, espocrm, advoware, sync_u
|
||||
|
||||
# ADVOWARE NEUER → Update EspoCRM
|
||||
elif comparison == 'advoware_newer':
|
||||
context.logger.info(f"📥 Advoware ist neuer → Update EspoCRM")
|
||||
context.logger.info(f"📥 Advoware ist neuer → Update EspoCRM STAMMDATEN")
|
||||
|
||||
espo_data = mapper.map_advoware_to_cbeteiligte(advo_entity)
|
||||
|
||||
@@ -228,15 +260,18 @@ async def handle_update(entity_id, betnr, espo_entity, espocrm, advoware, sync_u
|
||||
|
||||
# KONFLIKT → EspoCRM WINS
|
||||
elif comparison == 'conflict':
|
||||
context.logger.warning(f"⚠️ KONFLIKT erkannt → EspoCRM WINS")
|
||||
context.logger.warning(f"⚠️ KONFLIKT erkannt → EspoCRM WINS (STAMMDATEN)")
|
||||
|
||||
# Überschreibe Advoware mit EspoCRM
|
||||
advo_data = mapper.map_cbeteiligte_to_advoware(espo_entity)
|
||||
# Überschreibe Advoware mit EspoCRM (merge mit aktuellen Daten)
|
||||
advo_updates = mapper.map_cbeteiligte_to_advoware(espo_entity)
|
||||
merged_data = {**advo_entity, **advo_updates}
|
||||
|
||||
context.logger.info(f"📝 Merge: {len(advo_updates)} Stammdaten-Felder → {len(merged_data)} Gesamt-Felder")
|
||||
|
||||
await advoware.api_call(
|
||||
f'api/v1/advonet/Beteiligte/{betnr}',
|
||||
method='PUT',
|
||||
data=advo_data
|
||||
data=merged_data
|
||||
)
|
||||
|
||||
conflict_msg = (
|
||||
|
||||
Reference in New Issue
Block a user