feat: Implement bidirectional synchronization utilities for Advoware and EspoCRM communications
- Added KommunikationSyncManager class to handle synchronization logic. - Implemented methods for loading data, computing diffs, and applying changes between Advoware and EspoCRM. - Introduced 3-way diffing mechanism to intelligently resolve conflicts. - Added helper methods for creating empty slots and detecting changes in communications. - Enhanced logging for better traceability during synchronization processes.
This commit is contained in:
261
bitbylaw/scripts/test_espocrm_hidden_ids.py
Normal file
261
bitbylaw/scripts/test_espocrm_hidden_ids.py
Normal file
@@ -0,0 +1,261 @@
|
||||
"""
|
||||
Deep-Dive: Suche nach versteckten ID-Feldern
|
||||
|
||||
Die Relationships emailAddresses/phoneNumbers existieren (kein 404),
|
||||
aber wir bekommen 403 Forbidden.
|
||||
|
||||
Möglichkeiten:
|
||||
1. IDs sind in emailAddressData versteckt (vielleicht als 'id' Feld?)
|
||||
2. Es gibt ein separates ID-Array
|
||||
3. IDs sind in einem anderen Format gespeichert
|
||||
4. Admin-API-Key hat nicht genug Rechte
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from services.espocrm import EspoCRMAPI
|
||||
|
||||
TEST_BETEILIGTE_ID = '68e4af00172be7924'
|
||||
|
||||
|
||||
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
|
||||
|
||||
def __init__(self):
|
||||
self.logger = self.Logger()
|
||||
|
||||
|
||||
def print_section(title):
|
||||
print("\n" + "="*70)
|
||||
print(title)
|
||||
print("="*70)
|
||||
|
||||
|
||||
async def inspect_email_data_structure():
|
||||
"""Schaue sehr genau in emailAddressData/phoneNumberData"""
|
||||
print_section("DEEP INSPECTION: emailAddressData Structure")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
entity = await espo.get_entity('CBeteiligte', TEST_BETEILIGTE_ID)
|
||||
|
||||
email_data = entity.get('emailAddressData', [])
|
||||
|
||||
print(f"\n📧 emailAddressData hat {len(email_data)} Einträge\n")
|
||||
|
||||
for i, email in enumerate(email_data):
|
||||
print(f"[{i+1}] RAW Type: {type(email)}")
|
||||
print(f" Keys: {list(email.keys())}")
|
||||
print(f" JSON:\n")
|
||||
print(json.dumps(email, indent=4, ensure_ascii=False))
|
||||
|
||||
# Prüfe ob 'id' Feld vorhanden ist
|
||||
if 'id' in email:
|
||||
print(f"\n ✅ ID GEFUNDEN: {email['id']}")
|
||||
else:
|
||||
print(f"\n ❌ Kein 'id' Feld")
|
||||
|
||||
# Prüfe alle Felder auf ID-ähnliche Werte
|
||||
print(f"\n Alle Werte:")
|
||||
for key, value in email.items():
|
||||
print(f" {key:20s} = {value}")
|
||||
print()
|
||||
|
||||
|
||||
async def test_raw_api_call():
|
||||
"""Mache rohe API-Calls um zu sehen was wirklich zurückkommt"""
|
||||
print_section("RAW API CALL: Direkt ohne Wrapper")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
# Test 1: Normale Entity-Abfrage
|
||||
print(f"\n1️⃣ GET /CBeteiligte/{TEST_BETEILIGTE_ID}")
|
||||
result1 = await espo.api_call(f'CBeteiligte/{TEST_BETEILIGTE_ID}')
|
||||
|
||||
# Zeige nur Email-relevante Felder
|
||||
email_fields = {k: v for k, v in result1.items() if 'email' in k.lower()}
|
||||
print(json.dumps(email_fields, indent=2, ensure_ascii=False))
|
||||
|
||||
# Test 2: Mit maxDepth Parameter (falls EspoCRM das unterstützt)
|
||||
print(f"\n2️⃣ GET mit maxDepth=2")
|
||||
try:
|
||||
result2 = await espo.api_call(
|
||||
f'CBeteiligte/{TEST_BETEILIGTE_ID}',
|
||||
params={'maxDepth': '2'}
|
||||
)
|
||||
email_fields2 = {k: v for k, v in result2.items() if 'email' in k.lower()}
|
||||
print(json.dumps(email_fields2, indent=2, ensure_ascii=False))
|
||||
except Exception as e:
|
||||
print(f" ❌ Error: {e}")
|
||||
|
||||
# Test 3: Select nur emailAddressData
|
||||
print(f"\n3️⃣ GET mit select=emailAddressData")
|
||||
result3 = await espo.api_call(
|
||||
f'CBeteiligte/{TEST_BETEILIGTE_ID}',
|
||||
params={'select': 'emailAddressData'}
|
||||
)
|
||||
print(json.dumps(result3, indent=2, ensure_ascii=False))
|
||||
|
||||
|
||||
async def search_for_link_table():
|
||||
"""Suche nach EntityEmailAddress oder EntityPhoneNumber Link-Tables"""
|
||||
print_section("SUCHE: Link-Tables")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
# In EspoCRM gibt es manchmal Link-Tables wie "EntityEmailAddress"
|
||||
link_table_names = [
|
||||
'EntityEmailAddress',
|
||||
'EntityPhoneNumber',
|
||||
'ContactEmailAddress',
|
||||
'ContactPhoneNumber',
|
||||
'CBeteiligteEmailAddress',
|
||||
'CBeteiligtePhoneNumber'
|
||||
]
|
||||
|
||||
for table_name in link_table_names:
|
||||
print(f"\n🔍 Teste: {table_name}")
|
||||
try:
|
||||
result = await espo.api_call(table_name, params={'maxSize': 3})
|
||||
print(f" ✅ Existiert! Total: {result.get('total', 'unknown')}")
|
||||
if result.get('list'):
|
||||
print(f" Beispiel:")
|
||||
print(json.dumps(result['list'][0], indent=6, ensure_ascii=False))
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
if '404' in error_msg:
|
||||
print(f" ❌ 404 - Existiert nicht")
|
||||
elif '403' in error_msg:
|
||||
print(f" ⚠️ 403 - Existiert aber kein Zugriff")
|
||||
else:
|
||||
print(f" ❌ {error_msg}")
|
||||
|
||||
|
||||
async def test_update_with_ids():
|
||||
"""Test: Kann ich beim UPDATE IDs setzen?"""
|
||||
print_section("TEST: Update mit IDs")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
print(f"\n💡 Idee: Vielleicht kann man beim UPDATE IDs mitgeben")
|
||||
print(f" und EspoCRM erstellt dann die Verknüpfung?\n")
|
||||
|
||||
# Hole aktuelle Daten
|
||||
entity = await espo.get_entity('CBeteiligte', TEST_BETEILIGTE_ID)
|
||||
current_emails = entity.get('emailAddressData', [])
|
||||
|
||||
print(f"Aktuelle Emails:")
|
||||
for email in current_emails:
|
||||
print(f" • {email.get('emailAddress')}")
|
||||
|
||||
# Versuche ein Update mit expliziter ID
|
||||
print(f"\n🧪 Teste: Füge 'id' Feld zu emailAddressData hinzu")
|
||||
|
||||
test_emails = []
|
||||
for email in current_emails:
|
||||
email_copy = email.copy()
|
||||
# Generiere eine Test-ID (oder verwende eine echte wenn wir eine finden)
|
||||
email_copy['id'] = f"test-id-{hash(email['emailAddress']) % 100000}"
|
||||
test_emails.append(email_copy)
|
||||
print(f" • {email['emailAddress']:40s} → id={email_copy['id']}")
|
||||
|
||||
print(f"\n⚠️ ACHTUNG: Würde jetzt UPDATE machen mit:")
|
||||
print(json.dumps({'emailAddressData': test_emails}, indent=2, ensure_ascii=False))
|
||||
print(f"\n→ NICHT ausgeführt (zu riskant ohne Backup)")
|
||||
|
||||
|
||||
async def check_database_or_config():
|
||||
"""Prüfe ob es Config/Settings gibt die IDs aktivieren"""
|
||||
print_section("ESPOCRM CONFIG: ID-Unterstützung")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
print(f"\n📋 Hole App-Informationen:")
|
||||
try:
|
||||
# EspoCRM hat oft einen /App endpoint
|
||||
app_info = await espo.api_call('App/user')
|
||||
|
||||
# Zeige nur relevante Felder
|
||||
if app_info:
|
||||
relevant = ['acl', 'preferences', 'settings']
|
||||
for key in relevant:
|
||||
if key in app_info:
|
||||
print(f"\n{key}:")
|
||||
# Suche nach Email/Phone-relevanten Einstellungen
|
||||
data = app_info[key]
|
||||
if isinstance(data, dict):
|
||||
email_phone_settings = {k: v for k, v in data.items()
|
||||
if 'email' in k.lower() or 'phone' in k.lower()}
|
||||
if email_phone_settings:
|
||||
print(json.dumps(email_phone_settings, indent=2, ensure_ascii=False))
|
||||
else:
|
||||
print(" (keine Email/Phone-spezifischen Einstellungen)")
|
||||
except Exception as e:
|
||||
print(f" ❌ Error: {e}")
|
||||
|
||||
# Prüfe Settings
|
||||
print(f"\n📋 System Settings:")
|
||||
try:
|
||||
settings = await espo.api_call('Settings')
|
||||
if settings:
|
||||
email_phone_settings = {k: v for k, v in settings.items()
|
||||
if 'email' in k.lower() or 'phone' in k.lower()}
|
||||
if email_phone_settings:
|
||||
print(json.dumps(email_phone_settings, indent=2, ensure_ascii=False))
|
||||
except Exception as e:
|
||||
print(f" ❌ Error: {e}")
|
||||
|
||||
|
||||
async def main():
|
||||
print("\n" + "="*70)
|
||||
print("DEEP DIVE: SUCHE NACH PHONENUMBER/EMAILADDRESS IDs")
|
||||
print("="*70)
|
||||
|
||||
try:
|
||||
# Sehr detaillierte Inspektion
|
||||
await inspect_email_data_structure()
|
||||
|
||||
# Rohe API-Calls
|
||||
await test_raw_api_call()
|
||||
|
||||
# Link-Tables
|
||||
await search_for_link_table()
|
||||
|
||||
# Update-Test (ohne tatsächlich zu updaten)
|
||||
await test_update_with_ids()
|
||||
|
||||
# Config
|
||||
await check_database_or_config()
|
||||
|
||||
print_section("FAZIT")
|
||||
|
||||
print("\n🎯 Mögliche Szenarien:")
|
||||
print("\n1️⃣ IDs existieren NICHT in emailAddressData")
|
||||
print(" → Wert-basiertes Matching notwendig")
|
||||
print(" → Hybrid-Strategie (primary-Flag)")
|
||||
|
||||
print("\n2️⃣ IDs existieren aber sind versteckt/nicht zugänglich")
|
||||
print(" → API-Rechte müssen erweitert werden")
|
||||
print(" → Admin muss emailAddresses/phoneNumbers Relationship freigeben")
|
||||
|
||||
print("\n3️⃣ IDs können beim UPDATE gesetzt werden")
|
||||
print(" → Wir könnten eigene IDs generieren")
|
||||
print(" → Advoware-ID direkt als EspoCRM-ID nutzen")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Fehler: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user