""" Matching-Strategie für Kommunikation ohne ID Problem: - emailAddressData und phoneNumberData haben KEINE id-Felder - Können keine rowId in EspoCRM speichern (keine Custom-Felder) - Wie matchen wir Advoware ↔ EspoCRM? Lösungsansätze: 1. Wert-basiertes Matching (emailAddress/phoneNumber als Schlüssel) 2. Advoware als Master (One-Way-Sync mit Neuanlage bei Änderung) 3. Hash-basiertes Matching in bemerkung-Feld 4. Position-basiertes Matching (primary-Flag) """ import asyncio import json from services.espocrm import EspoCRMAPI from services.advoware import AdvowareAPI TEST_BETEILIGTE_ID = '68e4af00172be7924' TEST_ADVOWARE_BETNR = 104860 class SimpleContext: class Logger: def info(self, msg): print(f"[INFO] {msg}") def error(self, msg): print(f"[ERROR] {msg}") def warning(self, msg): print(f"[WARN] {msg}") def debug(self, msg): pass # Suppress debug def __init__(self): self.logger = self.Logger() def print_section(title): print("\n" + "="*70) print(title) print("="*70) async def test_value_based_matching(): """ Strategie 1: Wert-basiertes Matching Idee: Verwende emailAddress/phoneNumber selbst als Schlüssel Vorteile: - Einfach zu implementieren - Funktioniert für Duplikats-Erkennung Nachteile: - Wenn Wert ändert, verlieren wir Verbindung - Keine Change-Detection möglich (kein Timestamp/rowId) """ print_section("STRATEGIE 1: Wert-basiertes Matching") context = SimpleContext() espo = EspoCRMAPI(context) advo = AdvowareAPI(context) # Hole Daten espo_entity = await espo.get_entity('CBeteiligte', TEST_BETEILIGTE_ID) advo_entity = await advo.api_call(f'api/v1/advonet/Beteiligte/{TEST_ADVOWARE_BETNR}') print("\n📧 EspoCRM Emails:") espo_emails = {e['emailAddress']: e for e in espo_entity.get('emailAddressData', [])} for email, data in espo_emails.items(): print(f" • {email:40s} primary={data.get('primary', False)}") print("\n📧 Advoware Kommunikation (Typ MailGesch=4, MailPrivat=8):") advo_komm = advo_entity.get('kommunikation', []) advo_emails = [k for k in advo_komm if k.get('kommKz') in [4, 8]] # Email-Typen for k in advo_emails: print(f" • {k.get('tlf', ''):40s} Typ={k.get('kommKz')} ID={k.get('id')} " f"rowId={k.get('rowId')}") print("\n🔍 Matching-Ergebnis:") matched = [] unmatched_espo = [] unmatched_advo = [] for advo_k in advo_emails: email_value = advo_k.get('tlf', '').strip() if email_value in espo_emails: matched.append((advo_k, espo_emails[email_value])) print(f" ✅ MATCH: {email_value}") else: unmatched_advo.append(advo_k) print(f" ❌ Nur in Advoware: {email_value}") for email_value in espo_emails: if not any(k.get('tlf', '').strip() == email_value for k in advo_emails): unmatched_espo.append(espo_emails[email_value]) print(f" ⚠️ Nur in EspoCRM: {email_value}") print(f"\n📊 Statistik:") print(f" • Matched: {len(matched)}") print(f" • Nur Advoware: {len(unmatched_advo)}") print(f" • Nur EspoCRM: {len(unmatched_espo)}") # Problem-Szenario: Was wenn Email-Adresse ändert? print("\n⚠️ PROBLEM-SZENARIO: Email-Adresse ändert") print(" 1. Advoware: max@old.de → max@new.de (UPDATE mit gleicher ID)") print(" 2. Wert-Matching findet max@old.de nicht mehr in EspoCRM") print(" 3. Sync würde max@new.de NEU anlegen statt UPDATE") print(" 4. Ergebnis: Duplikat (max@old.de + max@new.de)") return matched, unmatched_advo, unmatched_espo async def test_advoware_master_sync(): """ Strategie 2: Advoware als Master (One-Way-Sync) Idee: - Ignoriere EspoCRM-Änderungen - Bei jedem Sync: Überschreibe komplette Arrays in EspoCRM Vorteile: - Sehr einfach - Keine Change-Detection nötig - Keine Matching-Probleme Nachteile: - Verliert EspoCRM-Änderungen - Nicht bidirektional """ print_section("STRATEGIE 2: Advoware als Master (One-Way)") context = SimpleContext() advo = AdvowareAPI(context) advo_entity = await advo.api_call(f'api/v1/advonet/Beteiligte/{TEST_ADVOWARE_BETNR}') advo_komm = advo_entity.get('kommunikation', []) print("\n📋 Sync-Ablauf:") print(" 1. Hole alle Advoware Kommunikationen") print(" 2. Konvertiere zu EspoCRM Format:") # Konvertierung email_data = [] phone_data = [] for k in advo_komm: komm_kz = k.get('kommKz', 0) wert = k.get('tlf', '').strip() online = k.get('online', False) # Email-Typen: 4=MailGesch, 8=MailPrivat, 11=EPost if komm_kz in [4, 8, 11] and wert: email_data.append({ 'emailAddress': wert, 'lower': wert.lower(), 'primary': online, # online=true → primary 'optOut': False, 'invalid': False }) # Phone-Typen: 1=TelGesch, 2=FaxGesch, 3=Mobil, 6=TelPrivat, 7=FaxPrivat elif komm_kz in [1, 2, 3, 6, 7] and wert: # Mapping kommKz → EspoCRM type type_map = { 1: 'Office', # TelGesch 3: 'Mobile', # Mobil 6: 'Home', # TelPrivat 2: 'Fax', # FaxGesch 7: 'Fax', # FaxPrivat } phone_data.append({ 'phoneNumber': wert, 'type': type_map.get(komm_kz, 'Other'), 'primary': online, 'optOut': False, 'invalid': False }) print(f"\n 📧 {len(email_data)} Emails:") for e in email_data: print(f" • {e['emailAddress']:40s} primary={e['primary']}") print(f"\n 📞 {len(phone_data)} Phones:") for p in phone_data: print(f" • {p['phoneNumber']:40s} type={p['type']:10s} primary={p['primary']}") print("\n 3. UPDATE CBeteiligte (überschreibt komplette Arrays)") print(" → emailAddressData: [...]") print(" → phoneNumberData: [...]") print("\n✅ Vorteile:") print(" • Sehr einfach zu implementieren") print(" • Keine Matching-Logik erforderlich") print(" • Advoware ist immer Source of Truth") print("\n❌ Nachteile:") print(" • EspoCRM-Änderungen gehen verloren") print(" • Nicht bidirektional") print(" • User könnten verärgert sein") return email_data, phone_data async def test_hybrid_strategy(): """ Strategie 3: Hybrid - Advoware Master + EspoCRM Ergänzungen Idee: - Advoware-Kommunikationen sind primary=true (wichtig, geschützt) - EspoCRM kann zusätzliche Einträge mit primary=false hinzufügen - Nur Advoware-Einträge werden synchronisiert Vorteile: - Flexibilität für EspoCRM-User - Advoware behält Kontrolle über wichtige Daten Nachteile: - Komplexere Logik - Braucht Markierung (primary-Flag) """ print_section("STRATEGIE 3: Hybrid (Advoware Primary + EspoCRM Secondary)") context = SimpleContext() espo = EspoCRMAPI(context) advo = AdvowareAPI(context) # Hole Daten espo_entity = await espo.get_entity('CBeteiligte', TEST_BETEILIGTE_ID) advo_entity = await advo.api_call(f'api/v1/advonet/Beteiligte/{TEST_ADVOWARE_BETNR}') advo_komm = advo_entity.get('kommunikation', []) espo_emails = espo_entity.get('emailAddressData', []) print("\n📋 Regel:") print(" • primary=true → Kommt von Advoware (synchronisiert)") print(" • primary=false → Nur in EspoCRM (wird NICHT zu Advoware)") print("\n📧 Aktuelle EspoCRM Emails:") for e in espo_emails: source = "Advoware" if e.get('primary') else "EspoCRM" print(f" • {e['emailAddress']:40s} primary={e.get('primary')} → {source}") print("\n🔄 Sync-Logik:") print(" 1. Hole Advoware Kommunikationen") print(" 2. Konvertiere zu EspoCRM (mit primary=true)") print(" 3. Hole aktuelle EspoCRM Einträge mit primary=false") print(" 4. Merge: Advoware (primary) + EspoCRM (secondary)") print(" 5. UPDATE CBeteiligte mit gemergtem Array") # Simulation advo_emails = [k for k in advo_komm if k.get('kommKz') in [4, 8, 11]] merged_emails = [] # Von Advoware (primary=true) for k in advo_emails: merged_emails.append({ 'emailAddress': k.get('tlf', ''), 'lower': k.get('tlf', '').lower(), 'primary': True, # Immer primary für Advoware 'optOut': False, 'invalid': False }) # Von EspoCRM (nur non-primary behalten) for e in espo_emails: if not e.get('primary', False): merged_emails.append(e) print(f"\n📊 Merge-Ergebnis: {len(merged_emails)} Emails") for e in merged_emails: source = "Advoware" if e.get('primary') else "EspoCRM" print(f" • {e['emailAddress']:40s} [{source}]") print("\n✅ Vorteile:") print(" • Advoware behält Kontrolle") print(" • EspoCRM-User können ergänzen") print(" • Kein Datenverlust") print("\n⚠️ Einschränkungen:") print(" • EspoCRM kann Advoware-Daten NICHT ändern") print(" • primary-Flag muss geschützt werden") return merged_emails async def test_bemerkung_tracking(): """ Strategie 4: Tracking via bemerkung-Feld Idee: Speichere Advoware-ID in bemerkung Format: "Advoware-ID: 149331 | Tatsächliche Bemerkung" Vorteile: - Stabiles Matching möglich - Kann Änderungen tracken Nachteile: - bemerkung-Feld wird "verschmutzt" - User sichtbar - Fragil (User könnte löschen) """ print_section("STRATEGIE 4: Tracking via bemerkung-Feld") context = SimpleContext() advo = AdvowareAPI(context) advo_entity = await advo.api_call(f'api/v1/advonet/Beteiligte/{TEST_ADVOWARE_BETNR}') advo_komm = advo_entity.get('kommunikation', []) print("\n⚠️ PROBLEM: EspoCRM emailAddressData/phoneNumberData haben KEIN bemerkung-Feld!") print("\nStruktur emailAddressData:") print(" {") print(" 'emailAddress': 'max@example.com',") print(" 'lower': 'max@example.com',") print(" 'primary': true,") print(" 'optOut': false,") print(" 'invalid': false") print(" }") print("\n ❌ Kein 'bemerkung' oder 'notes' Feld verfügbar") print(" ❌ Kein Custom-Feld möglich in Standard-Arrays") print("\n📋 Alternative: Advoware bemerkung nutzen") print(" → Speichere EspoCRM-Wert in Advoware bemerkung") for k in advo_komm[:3]: # Erste 3 als Beispiel advo_id = k.get('id') wert = k.get('tlf', '') bemerkung = k.get('bemerkung', '') print(f"\n Advoware ID {advo_id}:") print(f" Wert: {wert}") print(f" Bemerkung: {bemerkung or '(leer)'}") print(f" → Neue Bemerkung: 'EspoCRM: {wert} | {bemerkung}'") print("\n✅ Matching-Strategie:") print(" 1. Parse bemerkung: Extrahiere 'EspoCRM: '") print(" 2. Matche Advoware ↔ EspoCRM via Wert in bemerkung") print(" 3. Wenn Wert ändert: Update bemerkung") print("\n❌ Nachteile:") print(" • bemerkung für User sichtbar und änderbar") print(" • Fragil wenn User bemerkung bearbeitet") print(" • Komplexe Parse-Logik") async def main(): print("\n" + "="*70) print("KOMMUNIKATION MATCHING-STRATEGIEN OHNE ID") print("="*70) try: # Test alle Strategien await test_value_based_matching() await test_advoware_master_sync() await test_hybrid_strategy() await test_bemerkung_tracking() print_section("EMPFEHLUNG") print("\n🎯 BESTE LÖSUNG: Strategie 3 (Hybrid)") print("\n✅ Begründung:") print(" 1. Advoware behält Kontrolle (primary=true)") print(" 2. EspoCRM kann ergänzen (primary=false)") print(" 3. Einfach zu implementieren") print(" 4. Kein Datenverlust") print(" 5. primary-Flag ist Standard in EspoCRM") print("\n📋 Implementation:") print(" • Advoware → EspoCRM: Setze primary=true") print(" • EspoCRM → Advoware: Ignoriere primary=false Einträge") print(" • Matching: Via Wert (emailAddress/phoneNumber)") print(" • Change Detection: rowId in Advoware (wie bei Adressen)") print("\n🔄 Sync-Ablauf:") print(" 1. Webhook von Advoware") print(" 2. Lade Advoware Kommunikationen") print(" 3. Filter: Nur Typen die EspoCRM unterstützt") print(" 4. Konvertiere zu emailAddressData/phoneNumberData") print(" 5. Setze primary=true für alle") print(" 6. Merge mit bestehenden primary=false Einträgen") print(" 7. UPDATE CBeteiligte") print("\n⚠️ Einschränkungen akzeptiert:") print(" • EspoCRM → Advoware: Nur primary=false Einträge") print(" • Keine bidirektionale Sync für Wert-Änderungen") print(" • Bei Wert-Änderung: Neuanlage statt Update") except Exception as e: print(f"\n❌ Fehler: {e}") import traceback traceback.print_exc() if __name__ == "__main__": asyncio.run(main())