Add tests for Kommunikation Sync implementation and verification scripts
- Implemented comprehensive tests for the Kommunikation Sync functionality, covering base64 encoding, marker parsing, creation, type detection, and integration scenarios. - Added a verification script to check for unique IDs in Advoware communications, ensuring stability and integrity of the IDs. - Created utility scripts for code validation, notification testing, and PUT response detail analysis to enhance development and testing processes. - Updated README with details on new tools and their usage.
This commit is contained in:
45
bitbylaw/scripts/espocrm_tests/README.md
Normal file
45
bitbylaw/scripts/espocrm_tests/README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# EspoCRM API - Test Scripts
|
||||
|
||||
Test-Scripts für EspoCRM Custom Entity Tests.
|
||||
|
||||
## Scripts
|
||||
|
||||
### test_espocrm_kommunikation.py
|
||||
Test für CBeteiligte Kommunikation-Felder in EspoCRM.
|
||||
|
||||
**Testet:**
|
||||
- emailAddressData[] Struktur
|
||||
- phoneNumberData[] Struktur
|
||||
- Primary Flags
|
||||
- CRUD Operations
|
||||
|
||||
### test_espocrm_kommunikation_detail.py
|
||||
Detaillierter Test der Kommunikations-Entities.
|
||||
|
||||
### test_espocrm_phone_email_entities.py
|
||||
Test für Phone/Email Sub-Entities.
|
||||
|
||||
**Testet:**
|
||||
- Nested Entity Structure
|
||||
- Relationship Management
|
||||
- Data Consistency
|
||||
|
||||
### test_espocrm_hidden_ids.py
|
||||
Test für versteckte ID-Felder in EspoCRM.
|
||||
|
||||
### test_espocrm_id_collections.py
|
||||
Test für ID-Collection Handling.
|
||||
|
||||
### test_espocrm_id_injection.py
|
||||
Test für ID-Injection Vulnerabilities.
|
||||
|
||||
## Verwendung
|
||||
|
||||
```bash
|
||||
cd /opt/motia-app/bitbylaw
|
||||
python scripts/espocrm_tests/test_espocrm_kommunikation.py
|
||||
```
|
||||
|
||||
## Verwandte Dokumentation
|
||||
|
||||
- [../../services/ESPOCRM_SERVICE.md](../../services/ESPOCRM_SERVICE.md) - EspoCRM API Service
|
||||
261
bitbylaw/scripts/espocrm_tests/test_espocrm_hidden_ids.py
Normal file
261
bitbylaw/scripts/espocrm_tests/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())
|
||||
250
bitbylaw/scripts/espocrm_tests/test_espocrm_id_collections.py
Normal file
250
bitbylaw/scripts/espocrm_tests/test_espocrm_id_collections.py
Normal file
@@ -0,0 +1,250 @@
|
||||
"""
|
||||
Test: Gibt es ID-Collections für EmailAddress/PhoneNumber?
|
||||
|
||||
In EspoCRM gibt es bei Many-to-Many Beziehungen oft:
|
||||
- entityNameIds (Array von IDs)
|
||||
- entityNameNames (Dict ID → Name)
|
||||
|
||||
Zum Beispiel: teamsIds, teamsNames
|
||||
|
||||
Hypothese: Es könnte emailAddressesIds oder ähnlich geben
|
||||
"""
|
||||
|
||||
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 search_for_id_fields():
|
||||
"""Suche nach allen ID-ähnlichen Feldern"""
|
||||
print_section("SUCHE: ID-Collections")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
entity = await espo.get_entity('CBeteiligte', TEST_BETEILIGTE_ID)
|
||||
|
||||
print("\n🔍 Alle Felder die 'Ids' enthalten:")
|
||||
ids_fields = {k: v for k, v in entity.items() if 'Ids' in k}
|
||||
for key, value in sorted(ids_fields.items()):
|
||||
print(f" • {key:40s}: {value}")
|
||||
|
||||
print("\n🔍 Alle Felder die 'Names' enthalten:")
|
||||
names_fields = {k: v for k, v in entity.items() if 'Names' in k}
|
||||
for key, value in sorted(names_fields.items()):
|
||||
print(f" • {key:40s}: {value}")
|
||||
|
||||
print("\n🔍 Alle Felder mit 'email' oder 'phone' (case-insensitive):")
|
||||
comm_fields = {k: v for k, v in entity.items()
|
||||
if 'email' in k.lower() or 'phone' in k.lower()}
|
||||
for key, value in sorted(comm_fields.items()):
|
||||
value_str = str(value)[:80] if not isinstance(value, list) else f"[{len(value)} items]"
|
||||
print(f" • {key:40s}: {value_str}")
|
||||
|
||||
|
||||
async def test_specific_fields():
|
||||
"""Teste spezifische Feld-Namen die existieren könnten"""
|
||||
print_section("TEST: Spezifische Feld-Namen")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
potential_fields = [
|
||||
'emailAddressesIds',
|
||||
'emailAddressIds',
|
||||
'phoneNumbersIds',
|
||||
'phoneNumberIds',
|
||||
'emailIds',
|
||||
'phoneIds',
|
||||
'emailAddressesNames',
|
||||
'phoneNumbersNames',
|
||||
]
|
||||
|
||||
print("\n📋 Teste mit select Parameter:\n")
|
||||
|
||||
for field in potential_fields:
|
||||
try:
|
||||
result = await espo.api_call(
|
||||
f'CBeteiligte/{TEST_BETEILIGTE_ID}',
|
||||
params={'select': f'id,{field}'}
|
||||
)
|
||||
if field in result and result[field] is not None:
|
||||
print(f" ✅ {field:30s}: {result[field]}")
|
||||
else:
|
||||
print(f" ⚠️ {field:30s}: Im Response aber None/leer")
|
||||
except Exception as e:
|
||||
print(f" ❌ {field:30s}: {str(e)[:60]}")
|
||||
|
||||
|
||||
async def test_with_loadAdditionalFields():
|
||||
"""EspoCRM unterstützt manchmal loadAdditionalFields Parameter"""
|
||||
print_section("TEST: loadAdditionalFields Parameter")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
params_to_test = [
|
||||
{'loadAdditionalFields': 'true'},
|
||||
{'loadAdditionalFields': '1'},
|
||||
{'withLinks': 'true'},
|
||||
{'withRelated': 'emailAddresses,phoneNumbers'},
|
||||
]
|
||||
|
||||
for params in params_to_test:
|
||||
print(f"\n📋 Teste mit params: {params}")
|
||||
try:
|
||||
result = await espo.api_call(
|
||||
f'CBeteiligte/{TEST_BETEILIGTE_ID}',
|
||||
params=params
|
||||
)
|
||||
|
||||
# Suche nach neuen Feldern
|
||||
new_fields = {k: v for k, v in result.items()
|
||||
if ('email' in k.lower() or 'phone' in k.lower())
|
||||
and 'Data' not in k}
|
||||
|
||||
if new_fields:
|
||||
print(" ✅ Neue Felder gefunden:")
|
||||
for k, v in new_fields.items():
|
||||
print(f" • {k}: {v}")
|
||||
else:
|
||||
print(" ⚠️ Keine neuen Felder")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ Error: {e}")
|
||||
|
||||
|
||||
async def test_create_with_explicit_ids():
|
||||
"""
|
||||
Was wenn wir bei CREATE/UPDATE explizite IDs für Email/Phone mitgeben?
|
||||
Vielleicht gibt EspoCRM dann IDs zurück?
|
||||
"""
|
||||
print_section("IDEE: Explizite IDs bei UPDATE mitgeben")
|
||||
|
||||
print("\n💡 EspoCRM Standard-Verhalten:")
|
||||
print(" Bei Many-to-Many Beziehungen (z.B. Teams):")
|
||||
print(" - INPUT: teamsIds: ['id1', 'id2']")
|
||||
print(" - OUTPUT: teamsIds: ['id1', 'id2']")
|
||||
print(" ")
|
||||
print(" Könnte bei emailAddresses ähnlich funktionieren?")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
# Hole aktuelle Daten
|
||||
entity = await espo.get_entity('CBeteiligte', TEST_BETEILIGTE_ID)
|
||||
current_emails = entity.get('emailAddressData', [])
|
||||
|
||||
print("\n📋 Aktuelle emailAddressData:")
|
||||
for e in current_emails:
|
||||
print(f" • {e.get('emailAddress')}")
|
||||
|
||||
# Versuche ein Update mit hypothetischen emailAddressesIds
|
||||
print("\n🧪 Test: UPDATE mit emailAddressesIds Feld")
|
||||
print(" (DRY RUN - nicht wirklich ausgeführt)")
|
||||
|
||||
# Generiere Test-IDs (EspoCRM IDs sind meist 17 Zeichen)
|
||||
test_ids = [f"test{str(i).zfill(13)}" for i in range(len(current_emails))]
|
||||
|
||||
print(f"\n Würde senden:")
|
||||
print(f" emailAddressesIds: {test_ids}")
|
||||
print(f" emailAddressData: {[e['emailAddress'] for e in current_emails]}")
|
||||
|
||||
print("\n ⚠️ Zu riskant ohne zu wissen was passiert")
|
||||
|
||||
|
||||
async def check_standard_contact_entity():
|
||||
"""
|
||||
Prüfe wie es bei Standard Contact Entity funktioniert
|
||||
(als Referenz für Custom Entity)
|
||||
"""
|
||||
print_section("REFERENZ: Standard Contact Entity")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
print("\n📋 Hole ersten Contact als Referenz:")
|
||||
try:
|
||||
contacts = await espo.api_call('Contact', params={'maxSize': 1})
|
||||
|
||||
if contacts and contacts.get('list'):
|
||||
contact = contacts['list'][0]
|
||||
|
||||
print(f"\n Contact: {contact.get('name')}")
|
||||
print(f"\n 🔍 Email/Phone-relevante Felder:")
|
||||
|
||||
for key, value in sorted(contact.items()):
|
||||
if 'email' in key.lower() or 'phone' in key.lower():
|
||||
value_str = str(value)[:80] if not isinstance(value, (list, dict)) else type(value).__name__
|
||||
print(f" • {key:35s}: {value_str}")
|
||||
else:
|
||||
print(" ⚠️ Keine Contacts vorhanden")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ Error: {e}")
|
||||
|
||||
|
||||
async def main():
|
||||
print("\n" + "="*70)
|
||||
print("SUCHE: EMAIL/PHONE ID-COLLECTIONS")
|
||||
print("="*70)
|
||||
print("\nZiel: Finde ID-Arrays für EmailAddress/PhoneNumber Entities\n")
|
||||
|
||||
try:
|
||||
await search_for_id_fields()
|
||||
await test_specific_fields()
|
||||
await test_with_loadAdditionalFields()
|
||||
await test_create_with_explicit_ids()
|
||||
await check_standard_contact_entity()
|
||||
|
||||
print_section("FAZIT")
|
||||
|
||||
print("\n🎯 Wenn KEINE ID-Collections existieren:")
|
||||
print("\n Option 1: Separate CKommunikation Entity ✅ BESTE LÖSUNG")
|
||||
print(" Struktur:")
|
||||
print(" {")
|
||||
print(" 'id': 'espocrm-generated-id',")
|
||||
print(" 'beteiligteId': '68e4af00...',")
|
||||
print(" 'typ': 'Email/Phone',")
|
||||
print(" 'wert': 'max@example.com',")
|
||||
print(" 'advowareId': 149331,")
|
||||
print(" 'advowareRowId': 'ABC...'")
|
||||
print(" }")
|
||||
print("\n Vorteile:")
|
||||
print(" • Eigene Entity-ID für jede Kommunikation")
|
||||
print(" • advowareId/advowareRowId als eigene Felder")
|
||||
print(" • Sauberes Datenmodell")
|
||||
print(" • Stabiles bidirektionales Matching")
|
||||
|
||||
print("\n Option 2: One-Way Sync (Advoware → EspoCRM)")
|
||||
print(" • Matching via Wert (emailAddress/phoneNumber)")
|
||||
print(" • Nur Advoware-Änderungen werden synchronisiert")
|
||||
print(" • EspoCRM als Read-Only Viewer")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Fehler: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
225
bitbylaw/scripts/espocrm_tests/test_espocrm_id_injection.py
Normal file
225
bitbylaw/scripts/espocrm_tests/test_espocrm_id_injection.py
Normal file
@@ -0,0 +1,225 @@
|
||||
"""
|
||||
TEST: Können wir eigene IDs in emailAddressData setzen?
|
||||
|
||||
Wenn EspoCRM IDs beim UPDATE akzeptiert und speichert,
|
||||
dann können wir:
|
||||
- Advoware-ID als 'id' in emailAddressData speichern
|
||||
- Stabiles Matching haben
|
||||
- Bidirektionalen Sync machen
|
||||
|
||||
Vorsichtiger Test mit Backup!
|
||||
"""
|
||||
|
||||
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 test_id_persistence():
|
||||
"""
|
||||
Teste ob EspoCRM IDs in emailAddressData speichert
|
||||
|
||||
Ablauf:
|
||||
1. Hole aktuelle Daten (Backup)
|
||||
2. Füge 'id' Feld zu EINEM Email hinzu
|
||||
3. UPDATE
|
||||
4. GET wieder
|
||||
5. Prüfe ob 'id' noch da ist
|
||||
6. Restore original falls nötig
|
||||
"""
|
||||
print_section("TEST: ID Persistence in emailAddressData")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
# 1. Backup
|
||||
print("\n1️⃣ Backup: Hole aktuelle Daten")
|
||||
entity_backup = await espo.get_entity('CBeteiligte', TEST_BETEILIGTE_ID)
|
||||
emails_backup = entity_backup.get('emailAddressData', [])
|
||||
|
||||
print(f" Backup: {len(emails_backup)} Emails gesichert")
|
||||
for email in emails_backup:
|
||||
print(f" • {email['emailAddress']}")
|
||||
|
||||
# 2. Modifiziere NUR das erste Email (primary)
|
||||
print("\n2️⃣ Modifikation: Füge 'id' zu primary Email hinzu")
|
||||
|
||||
emails_modified = []
|
||||
for i, email in enumerate(emails_backup):
|
||||
email_copy = email.copy()
|
||||
if email_copy.get('primary'): # Nur primary modifizieren
|
||||
# Nutze einen recognizable Test-Wert
|
||||
test_id = f"advoware-{i+1}-test-123"
|
||||
email_copy['id'] = test_id
|
||||
print(f" ✏️ {email['emailAddress']:40s} → id={test_id}")
|
||||
else:
|
||||
print(f" ⏭️ {email['emailAddress']:40s} (unverändert)")
|
||||
emails_modified.append(email_copy)
|
||||
|
||||
# 3. UPDATE
|
||||
print("\n3️⃣ UPDATE: Sende modifizierte Daten")
|
||||
try:
|
||||
await espo.update_entity('CBeteiligte', TEST_BETEILIGTE_ID, {
|
||||
'emailAddressData': emails_modified
|
||||
})
|
||||
print(" ✅ UPDATE erfolgreich")
|
||||
except Exception as e:
|
||||
print(f" ❌ UPDATE fehlgeschlagen: {e}")
|
||||
return
|
||||
|
||||
# 4. GET wieder
|
||||
print("\n4️⃣ GET: Hole Daten wieder ab")
|
||||
await asyncio.sleep(0.5) # Kurze Pause
|
||||
entity_after = await espo.get_entity('CBeteiligte', TEST_BETEILIGTE_ID)
|
||||
emails_after = entity_after.get('emailAddressData', [])
|
||||
|
||||
print(f" Nach UPDATE: {len(emails_after)} Emails")
|
||||
|
||||
# 5. Vergleiche
|
||||
print("\n5️⃣ VERGLEICH: Ist 'id' noch da?")
|
||||
id_found = False
|
||||
for email in emails_after:
|
||||
email_addr = email['emailAddress']
|
||||
has_id = 'id' in email
|
||||
|
||||
if has_id:
|
||||
print(f" ✅ {email_addr:40s} → id={email['id']}")
|
||||
id_found = True
|
||||
else:
|
||||
print(f" ❌ {email_addr:40s} → KEIN id Feld")
|
||||
|
||||
# 6. Ergebnis
|
||||
print(f"\n6️⃣ ERGEBNIS:")
|
||||
if id_found:
|
||||
print(" 🎉 SUCCESS! EspoCRM speichert und liefert 'id' Feld zurück!")
|
||||
print(" → Wir können Advoware-IDs in emailAddressData speichern")
|
||||
print(" → Stabiles bidirektionales Matching möglich")
|
||||
else:
|
||||
print(" ❌ FAILED: EspoCRM ignoriert/entfernt 'id' Feld")
|
||||
print(" → Wert-basiertes Matching notwendig")
|
||||
print(" → Hybrid-Strategie (primary-Flag) ist beste Option")
|
||||
|
||||
# 7. Restore (optional - nur wenn User will)
|
||||
print(f"\n7️⃣ CLEANUP:")
|
||||
print(" Original-Daten (ohne id):")
|
||||
for email in emails_backup:
|
||||
print(f" • {email['emailAddress']}")
|
||||
|
||||
if id_found:
|
||||
restore = input("\n 🔄 Restore zu Original (ohne id)? [y/N]: ").strip().lower()
|
||||
if restore == 'y':
|
||||
await espo.update_entity('CBeteiligte', TEST_BETEILIGTE_ID, {
|
||||
'emailAddressData': emails_backup
|
||||
})
|
||||
print(" ✅ Restored")
|
||||
else:
|
||||
print(" ⏭️ Nicht restored (id bleibt)")
|
||||
|
||||
return id_found
|
||||
|
||||
|
||||
async def test_custom_field_approach():
|
||||
"""
|
||||
Alternative: Nutze ein custom field in CBeteiligte für ID-Mapping
|
||||
|
||||
Idee: Speichere JSON-Mapping in einem Textfeld
|
||||
"""
|
||||
print_section("ALTERNATIVE: Custom Field für ID-Mapping")
|
||||
|
||||
print("\n💡 Idee: Nutze custom field 'kommunikationMapping'")
|
||||
print(" Struktur:")
|
||||
print(" {")
|
||||
print(' "emails": [')
|
||||
print(' {"emailAddress": "max@example.com", "advowareId": 123, "advowareRowId": "ABC"}')
|
||||
print(' ],')
|
||||
print(' "phones": [')
|
||||
print(' {"phoneNumber": "+49...", "advowareId": 456, "advowareRowId": "DEF"}')
|
||||
print(' ]')
|
||||
print(" }")
|
||||
|
||||
print("\n✅ Vorteile:")
|
||||
print(" • Stabiles Matching via advowareId")
|
||||
print(" • Change Detection via advowareRowId")
|
||||
print(" • Bidirektionaler Sync möglich")
|
||||
|
||||
print("\n❌ Nachteile:")
|
||||
print(" • Erfordert custom field in EspoCRM")
|
||||
print(" • Daten-Duplikation (in Data + Mapping)")
|
||||
print(" • Fragil wenn emailAddress/phoneNumber ändert")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
# Prüfe ob custom field existiert
|
||||
print("\n🔍 Prüfe ob 'kommunikationMapping' Feld existiert:")
|
||||
try:
|
||||
entity = await espo.get_entity('CBeteiligte', TEST_BETEILIGTE_ID)
|
||||
if 'kommunikationMapping' in entity:
|
||||
print(f" ✅ Feld existiert: {entity['kommunikationMapping']}")
|
||||
else:
|
||||
print(f" ❌ Feld existiert nicht")
|
||||
print(f" → Müsste in EspoCRM angelegt werden")
|
||||
except Exception as e:
|
||||
print(f" ❌ Error: {e}")
|
||||
|
||||
|
||||
async def main():
|
||||
print("\n" + "="*70)
|
||||
print("TEST: KÖNNEN WIR EIGENE IDs IN emailAddressData SETZEN?")
|
||||
print("="*70)
|
||||
print("\nZiel: Herausfinden ob EspoCRM 'id' Felder akzeptiert und speichert\n")
|
||||
|
||||
try:
|
||||
# Haupttest
|
||||
id_works = await test_id_persistence()
|
||||
|
||||
# Alternative
|
||||
await test_custom_field_approach()
|
||||
|
||||
print_section("FINAL RECOMMENDATION")
|
||||
|
||||
if id_works:
|
||||
print("\n🎯 EMPFEHLUNG: Nutze 'id' Feld in emailAddressData")
|
||||
print("\n📋 Implementation:")
|
||||
print(" 1. Bei Advoware → EspoCRM: Füge 'id' mit Advoware-ID hinzu")
|
||||
print(" 2. Matching via 'id' Feld")
|
||||
print(" 3. Change Detection via Advoware rowId")
|
||||
print(" 4. Bidirektionaler Sync möglich")
|
||||
else:
|
||||
print("\n🎯 EMPFEHLUNG A: Hybrid-Strategie (primary-Flag)")
|
||||
print(" • Einfach zu implementieren")
|
||||
print(" • Nutzt Standard-EspoCRM")
|
||||
print(" • Eingeschränkt bidirektional")
|
||||
|
||||
print("\n🎯 EMPFEHLUNG B: Custom Field 'kommunikationMapping'")
|
||||
print(" • Vollständig bidirektional")
|
||||
print(" • Erfordert EspoCRM-Anpassung")
|
||||
print(" • Komplexere Implementation")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Fehler: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
277
bitbylaw/scripts/espocrm_tests/test_espocrm_kommunikation.py
Normal file
277
bitbylaw/scripts/espocrm_tests/test_espocrm_kommunikation.py
Normal file
@@ -0,0 +1,277 @@
|
||||
"""
|
||||
Test: EspoCRM Kommunikation - Wie werden Kontaktdaten gespeichert?
|
||||
|
||||
Prüfe:
|
||||
1. Gibt es ein separates CKommunikation Entity?
|
||||
2. Wie sind Telefon/Email/Fax in CBeteiligte gespeichert?
|
||||
3. Sind es Arrays oder einzelne Felder?
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from services.espocrm import EspoCRMAPI
|
||||
|
||||
# Test-Beteiligter mit Kommunikationsdaten
|
||||
TEST_BETEILIGTE_ID = '68e4af00172be7924' # Angela Mustermanns
|
||||
|
||||
|
||||
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): print(f"[DEBUG] {msg}")
|
||||
|
||||
def __init__(self):
|
||||
self.logger = self.Logger()
|
||||
|
||||
|
||||
def print_section(title):
|
||||
print("\n" + "="*70)
|
||||
print(title)
|
||||
print("="*70)
|
||||
|
||||
|
||||
async def test_cbeteiligte_structure():
|
||||
"""Analysiere CBeteiligte Kommunikationsfelder"""
|
||||
|
||||
print_section("TEST 1: CBeteiligte Entity Struktur")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
# Hole Beteiligten
|
||||
entity = await espo.get_entity('CBeteiligte', TEST_BETEILIGTE_ID)
|
||||
|
||||
print(f"\n✅ Beteiligter geladen: {entity.get('name')}")
|
||||
print(f" ID: {entity.get('id')}")
|
||||
print(f" betNr: {entity.get('betnr')}")
|
||||
|
||||
# Suche nach Kommunikationsfeldern
|
||||
print("\n📊 Kommunikations-relevante Felder:")
|
||||
|
||||
comm_fields = [
|
||||
'phoneNumber', 'phoneNumberData',
|
||||
'emailAddress', 'emailAddressData',
|
||||
'fax', 'faxData',
|
||||
'mobile', 'mobileData',
|
||||
'website',
|
||||
# Plural Varianten
|
||||
'phoneNumbers', 'emailAddresses', 'faxNumbers',
|
||||
# Link-Felder
|
||||
'kommunikationIds', 'kommunikationNames',
|
||||
'kommunikationenIds', 'kommunikationenNames',
|
||||
'ckommunikationIds', 'ckommunikationNames'
|
||||
]
|
||||
|
||||
found_fields = {}
|
||||
|
||||
for field in comm_fields:
|
||||
if field in entity:
|
||||
value = entity[field]
|
||||
found_fields[field] = value
|
||||
print(f"\n ✓ {field}:")
|
||||
print(f" Typ: {type(value).__name__}")
|
||||
|
||||
if isinstance(value, list):
|
||||
print(f" Anzahl: {len(value)}")
|
||||
if len(value) > 0:
|
||||
print(f" Beispiel: {json.dumps(value[0], indent=6, ensure_ascii=False)}")
|
||||
elif isinstance(value, dict):
|
||||
print(f" Keys: {list(value.keys())}")
|
||||
print(f" Content: {json.dumps(value, indent=6, ensure_ascii=False)}")
|
||||
else:
|
||||
print(f" Wert: {value}")
|
||||
|
||||
if not found_fields:
|
||||
print("\n ⚠️ Keine Standard-Kommunikationsfelder gefunden")
|
||||
|
||||
# Zeige alle Felder die "comm", "phone", "email", "fax", "tel" enthalten
|
||||
print("\n📋 Alle Felder mit Kommunikations-Keywords:")
|
||||
keywords = ['comm', 'phone', 'email', 'fax', 'tel', 'mobil', 'kontakt']
|
||||
|
||||
matching_fields = {}
|
||||
for key, value in entity.items():
|
||||
key_lower = key.lower()
|
||||
if any(kw in key_lower for kw in keywords):
|
||||
matching_fields[key] = value
|
||||
print(f" • {key}: {type(value).__name__}")
|
||||
if isinstance(value, (str, int, bool)) and value:
|
||||
print(f" = {value}")
|
||||
|
||||
return entity, found_fields
|
||||
|
||||
|
||||
async def test_ckommunikation_entity():
|
||||
"""Prüfe ob CKommunikation Entity existiert"""
|
||||
|
||||
print_section("TEST 2: CKommunikation Entity")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
# Versuche CKommunikation zu listen
|
||||
try:
|
||||
result = await espo.list_entities('CKommunikation', max_size=5)
|
||||
|
||||
print(f"✅ CKommunikation Entity existiert!")
|
||||
print(f" Anzahl gefunden: {len(result)}")
|
||||
|
||||
if result:
|
||||
print(f"\n📋 Beispiel-Kommunikation:")
|
||||
print(json.dumps(result[0], indent=2, ensure_ascii=False))
|
||||
|
||||
return True, result
|
||||
|
||||
except Exception as e:
|
||||
if '404' in str(e) or 'not found' in str(e).lower():
|
||||
print(f"❌ CKommunikation Entity existiert NICHT")
|
||||
print(f" Fehler: {e}")
|
||||
return False, None
|
||||
else:
|
||||
print(f"⚠️ Fehler beim Abrufen: {e}")
|
||||
return None, None
|
||||
|
||||
|
||||
async def test_entity_metadata():
|
||||
"""Hole Entity-Metadaten von CBeteiligte"""
|
||||
|
||||
print_section("TEST 3: CBeteiligte Metadaten")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
# Hole Metadaten (falls API das unterstützt)
|
||||
try:
|
||||
# Versuche Entity-Defs zu holen
|
||||
metadata = await espo.api_call('/Metadata', method='GET')
|
||||
|
||||
if 'entityDefs' in metadata and 'CBeteiligte' in metadata['entityDefs']:
|
||||
beteiligte_def = metadata['entityDefs']['CBeteiligte']
|
||||
|
||||
print("✅ Metadaten verfügbar")
|
||||
|
||||
if 'fields' in beteiligte_def:
|
||||
fields = beteiligte_def['fields']
|
||||
|
||||
print(f"\n📊 Kommunikations-Felder in Definition:")
|
||||
for field_name, field_def in fields.items():
|
||||
field_lower = field_name.lower()
|
||||
if any(kw in field_lower for kw in ['comm', 'phone', 'email', 'fax', 'tel']):
|
||||
print(f"\n • {field_name}:")
|
||||
print(f" type: {field_def.get('type')}")
|
||||
if 'entity' in field_def:
|
||||
print(f" entity: {field_def.get('entity')}")
|
||||
if 'link' in field_def:
|
||||
print(f" link: {field_def.get('link')}")
|
||||
|
||||
return metadata
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Metadaten nicht verfügbar: {e}")
|
||||
return None
|
||||
|
||||
|
||||
async def test_list_all_entities():
|
||||
"""Liste alle verfügbaren Entities"""
|
||||
|
||||
print_section("TEST 4: Alle verfügbaren Entities")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
# Häufige Entity-Namen die mit Kommunikation zu tun haben könnten
|
||||
test_entities = [
|
||||
'CKommunikation',
|
||||
'Kommunikation',
|
||||
'Communication',
|
||||
'PhoneNumber',
|
||||
'EmailAddress',
|
||||
'CPhoneNumber',
|
||||
'CEmailAddress',
|
||||
'CPhone',
|
||||
'CEmail',
|
||||
'CContact',
|
||||
'ContactData'
|
||||
]
|
||||
|
||||
print("\n🔍 Teste verschiedene Entity-Namen:\n")
|
||||
|
||||
existing = []
|
||||
|
||||
for entity_name in test_entities:
|
||||
try:
|
||||
result = await espo.list_entities(entity_name, max_size=1)
|
||||
print(f" ✅ {entity_name} - existiert ({len(result)} gefunden)")
|
||||
existing.append(entity_name)
|
||||
except Exception as e:
|
||||
if '404' in str(e) or 'not found' in str(e).lower():
|
||||
print(f" ❌ {entity_name} - existiert nicht")
|
||||
else:
|
||||
print(f" ⚠️ {entity_name} - Fehler: {str(e)[:50]}")
|
||||
|
||||
return existing
|
||||
|
||||
|
||||
async def main():
|
||||
print("\n" + "="*70)
|
||||
print("ESPOCRM KOMMUNIKATION ANALYSE")
|
||||
print("="*70)
|
||||
print("\nZiel: Verstehen wie Kommunikationsdaten in EspoCRM gespeichert sind")
|
||||
print("Frage: Gibt es separate Kommunikations-Entities oder nur Felder?\n")
|
||||
|
||||
try:
|
||||
# Test 1: CBeteiligte Struktur
|
||||
entity, comm_fields = await test_cbeteiligte_structure()
|
||||
|
||||
# Test 2: CKommunikation Entity
|
||||
ckommunikation_exists, ckommunikation_data = await test_ckommunikation_entity()
|
||||
|
||||
# Test 3: Metadaten
|
||||
# metadata = await test_entity_metadata()
|
||||
|
||||
# Test 4: Liste entities
|
||||
existing_entities = await test_list_all_entities()
|
||||
|
||||
# Zusammenfassung
|
||||
print_section("ZUSAMMENFASSUNG")
|
||||
|
||||
print("\n📊 Erkenntnisse:")
|
||||
|
||||
if comm_fields:
|
||||
print(f"\n✅ CBeteiligte hat Kommunikationsfelder:")
|
||||
for field, value in comm_fields.items():
|
||||
vtype = type(value).__name__
|
||||
print(f" • {field} ({vtype})")
|
||||
|
||||
if ckommunikation_exists:
|
||||
print(f"\n✅ CKommunikation Entity existiert")
|
||||
print(f" → Separate Kommunikations-Entities möglich")
|
||||
elif ckommunikation_exists == False:
|
||||
print(f"\n❌ CKommunikation Entity existiert NICHT")
|
||||
print(f" → Kommunikation nur als Felder in CBeteiligte")
|
||||
|
||||
if existing_entities:
|
||||
print(f"\n📋 Gefundene Kommunikations-Entities:")
|
||||
for ename in existing_entities:
|
||||
print(f" • {ename}")
|
||||
|
||||
print("\n💡 Empfehlung:")
|
||||
if not comm_fields and not ckommunikation_exists:
|
||||
print(" ⚠️ Keine Kommunikationsstruktur gefunden")
|
||||
print(" → Eventuell müssen Custom Fields erst angelegt werden")
|
||||
elif comm_fields and not ckommunikation_exists:
|
||||
print(" → Verwende vorhandene Felder in CBeteiligte (phoneNumber, emailAddress, etc.)")
|
||||
print(" → Sync als Teil des Beteiligte-Syncs (nicht separat)")
|
||||
elif ckommunikation_exists:
|
||||
print(" → Verwende CKommunikation Entity für separaten Kommunikations-Sync")
|
||||
print(" → Ermöglicht mehrere Kommunikationseinträge pro Beteiligten")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Fehler: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,202 @@
|
||||
"""
|
||||
Detail-Analyse: emailAddressData und phoneNumberData Struktur
|
||||
|
||||
Erkenntnisse:
|
||||
- CKommunikation Entity existiert NICHT in EspoCRM
|
||||
- CBeteiligte hat phoneNumberData und emailAddressData Arrays
|
||||
- PhoneNumber und EmailAddress Entities existieren (aber 403 Forbidden - nur intern)
|
||||
|
||||
Jetzt: Analysiere die Data-Arrays im Detail
|
||||
"""
|
||||
|
||||
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): print(f"[DEBUG] {msg}")
|
||||
|
||||
def __init__(self):
|
||||
self.logger = self.Logger()
|
||||
|
||||
|
||||
def print_section(title):
|
||||
print("\n" + "="*70)
|
||||
print(title)
|
||||
print("="*70)
|
||||
|
||||
|
||||
async def analyze_communication_data():
|
||||
"""Detaillierte Analyse der Communication-Data Felder"""
|
||||
|
||||
print_section("DETAIL-ANALYSE: emailAddressData und phoneNumberData")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
# Hole Beteiligten
|
||||
entity = await espo.get_entity('CBeteiligte', TEST_BETEILIGTE_ID)
|
||||
|
||||
print(f"\n✅ Beteiligter: {entity.get('name')}")
|
||||
print(f" ID: {entity.get('id')}")
|
||||
|
||||
# emailAddressData
|
||||
print("\n" + "="*50)
|
||||
print("emailAddressData")
|
||||
print("="*50)
|
||||
|
||||
email_data = entity.get('emailAddressData', [])
|
||||
|
||||
if email_data:
|
||||
print(f"\n📧 {len(email_data)} Email-Adresse(n):\n")
|
||||
|
||||
for i, email in enumerate(email_data):
|
||||
print(f"[{i+1}] {json.dumps(email, indent=2, ensure_ascii=False)}")
|
||||
|
||||
# Analysiere Struktur
|
||||
if i == 0:
|
||||
print(f"\n📊 Feld-Struktur:")
|
||||
for key, value in email.items():
|
||||
print(f" • {key:20s}: {type(value).__name__:10s} = {value}")
|
||||
else:
|
||||
print("\n❌ Keine Email-Adressen vorhanden")
|
||||
|
||||
# phoneNumberData
|
||||
print("\n" + "="*50)
|
||||
print("phoneNumberData")
|
||||
print("="*50)
|
||||
|
||||
phone_data = entity.get('phoneNumberData', [])
|
||||
|
||||
if phone_data:
|
||||
print(f"\n📞 {len(phone_data)} Telefonnummer(n):\n")
|
||||
|
||||
for i, phone in enumerate(phone_data):
|
||||
print(f"[{i+1}] {json.dumps(phone, indent=2, ensure_ascii=False)}")
|
||||
|
||||
# Analysiere Struktur
|
||||
if i == 0:
|
||||
print(f"\n📊 Feld-Struktur:")
|
||||
for key, value in phone.items():
|
||||
print(f" • {key:20s}: {type(value).__name__:10s} = {value}")
|
||||
else:
|
||||
print("\n❌ Keine Telefonnummern vorhanden")
|
||||
|
||||
# Prüfe andere Beteiligten mit mehr Kommunikationsdaten
|
||||
print_section("SUCHE: Beteiligter mit mehr Kommunikationsdaten")
|
||||
|
||||
print("\n🔍 Liste erste 20 Beteiligte und prüfe Kommunikationsdaten...\n")
|
||||
|
||||
beteiligte_list = await espo.list_entities('CBeteiligte', max_size=20)
|
||||
|
||||
best_example = None
|
||||
max_comm_count = 0
|
||||
|
||||
for bet in beteiligte_list:
|
||||
# list_entities kann Strings oder Dicts zurückgeben
|
||||
if isinstance(bet, str):
|
||||
continue
|
||||
|
||||
email_count = len(bet.get('emailAddressData', []))
|
||||
phone_count = len(bet.get('phoneNumberData', []))
|
||||
total = email_count + phone_count
|
||||
|
||||
if total > 0:
|
||||
print(f"• {bet.get('name', 'N/A')[:40]:40s} | "
|
||||
f"Email: {email_count} | Phone: {phone_count}")
|
||||
|
||||
if total > max_comm_count:
|
||||
max_comm_count = total
|
||||
best_example = bet
|
||||
|
||||
if best_example and max_comm_count > 0:
|
||||
print(f"\n✅ Bester Beispiel-Beteiligter: {best_example.get('name')}")
|
||||
print(f" Gesamt: {max_comm_count} Kommunikationseinträge")
|
||||
|
||||
print("\n📧 emailAddressData:")
|
||||
for i, email in enumerate(best_example.get('emailAddressData', [])):
|
||||
print(f"\n [{i+1}] {json.dumps(email, indent=6, ensure_ascii=False)}")
|
||||
|
||||
print("\n📞 phoneNumberData:")
|
||||
for i, phone in enumerate(best_example.get('phoneNumberData', [])):
|
||||
print(f"\n [{i+1}] {json.dumps(phone, indent=6, ensure_ascii=False)}")
|
||||
|
||||
return entity, email_data, phone_data, best_example
|
||||
|
||||
|
||||
async def main():
|
||||
print("\n" + "="*70)
|
||||
print("ESPOCRM KOMMUNIKATION - DETAIL-ANALYSE")
|
||||
print("="*70)
|
||||
print("\nZiel: Verstehe die Struktur von emailAddressData und phoneNumberData")
|
||||
print("Frage: Haben diese Arrays IDs für Matching mit Advoware?\n")
|
||||
|
||||
try:
|
||||
entity, emails, phones, best = await analyze_communication_data()
|
||||
|
||||
print_section("ZUSAMMENFASSUNG")
|
||||
|
||||
print("\n📊 Erkenntnisse:")
|
||||
|
||||
print("\n1️⃣ EspoCRM Standard-Struktur:")
|
||||
print(" • emailAddressData: Array von Email-Objekten")
|
||||
print(" • phoneNumberData: Array von Telefon-Objekten")
|
||||
print(" • Keine separate CKommunikation Entity")
|
||||
|
||||
if emails:
|
||||
print("\n2️⃣ emailAddressData Felder:")
|
||||
sample = emails[0]
|
||||
for key in sample.keys():
|
||||
print(f" • {key}")
|
||||
|
||||
if 'id' in sample:
|
||||
print("\n ✅ Hat 'id' Feld → Kann für Matching verwendet werden!")
|
||||
else:
|
||||
print("\n ❌ Kein 'id' Feld → Matching via Wert (emailAddress)")
|
||||
|
||||
if phones:
|
||||
print("\n3️⃣ phoneNumberData Felder:")
|
||||
sample = phones[0]
|
||||
for key in sample.keys():
|
||||
print(f" • {key}")
|
||||
|
||||
if 'id' in sample:
|
||||
print("\n ✅ Hat 'id' Feld → Kann für Matching verwendet werden!")
|
||||
else:
|
||||
print("\n ❌ Kein 'id' Feld → Matching via Wert (phoneNumber)")
|
||||
|
||||
print("\n💡 Sync-Strategie:")
|
||||
print("\n Option A: Kommunikation als Teil von Beteiligte-Sync")
|
||||
print(" ────────────────────────────────────────────────────")
|
||||
print(" • emailAddressData → Advoware Kommunikation (kommKz=4)")
|
||||
print(" • phoneNumberData → Advoware Kommunikation (kommKz=1)")
|
||||
print(" • Sync innerhalb von beteiligte_sync.py")
|
||||
print(" • Kein separates Entity in EspoCRM nötig")
|
||||
|
||||
print("\n Option B: Custom CKommunikation Entity erstellen")
|
||||
print(" ────────────────────────────────────────────────────")
|
||||
print(" • Neues Custom Entity in EspoCRM anlegen")
|
||||
print(" • Many-to-One Beziehung zu CBeteiligte")
|
||||
print(" • Separater kommunikation_sync.py")
|
||||
print(" • Ermöglicht mehr Flexibilität (Fax, BeA, etc.)")
|
||||
|
||||
print("\n ⚠️ WICHTIG:")
|
||||
print(" • Standard EspoCRM hat NUR Email und Phone")
|
||||
print(" • Advoware hat 12 verschiedene Kommunikationstypen")
|
||||
print(" • Für vollständigen Sync → Custom Entity empfohlen")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Fehler: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,297 @@
|
||||
"""
|
||||
Test: PhoneNumber und EmailAddress als System-Entities
|
||||
|
||||
Hypothese:
|
||||
- PhoneNumber und EmailAddress sind separate Entities mit IDs
|
||||
- CBeteiligte hat Links/Relations zu diesen Entities
|
||||
- Wir können über related entries an die IDs kommen
|
||||
|
||||
Ziele:
|
||||
1. Hole CBeteiligte mit expanded relationships
|
||||
2. Prüfe ob phoneNumbers/emailAddresses als Links verfügbar sind
|
||||
3. Extrahiere IDs der verknüpften PhoneNumber/EmailAddress Entities
|
||||
"""
|
||||
|
||||
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 test_related_entities():
|
||||
"""Test 1: Hole CBeteiligte mit allen verfügbaren Feldern"""
|
||||
print_section("TEST 1: CBeteiligte - Alle Felder")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
# Hole Entity
|
||||
entity = await espo.get_entity('CBeteiligte', TEST_BETEILIGTE_ID)
|
||||
|
||||
print(f"\n✅ Beteiligter: {entity.get('name')}")
|
||||
print(f"\n📋 Alle Top-Level Felder:")
|
||||
for key in sorted(entity.keys()):
|
||||
value = entity[key]
|
||||
value_type = type(value).__name__
|
||||
|
||||
# Zeige nur ersten Teil von langen Werten
|
||||
if isinstance(value, str) and len(value) > 60:
|
||||
display = f"{value[:60]}..."
|
||||
elif isinstance(value, list):
|
||||
display = f"[{len(value)} items]"
|
||||
elif isinstance(value, dict):
|
||||
display = f"{{dict with {len(value)} keys}}"
|
||||
else:
|
||||
display = value
|
||||
|
||||
print(f" • {key:30s}: {value_type:10s} = {display}")
|
||||
|
||||
# Suche nach ID-Feldern für Kommunikation
|
||||
print(f"\n🔍 Suche nach ID-Feldern für Email/Phone:")
|
||||
|
||||
potential_id_fields = [k for k in entity.keys() if 'email' in k.lower() or 'phone' in k.lower()]
|
||||
for field in potential_id_fields:
|
||||
print(f" • {field}: {entity.get(field)}")
|
||||
|
||||
return entity
|
||||
|
||||
|
||||
async def test_list_with_select():
|
||||
"""Test 2: Nutze select Parameter um spezifische Felder zu holen"""
|
||||
print_section("TEST 2: CBeteiligte mit select Parameter")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
# Versuche verschiedene Feld-Namen
|
||||
potential_fields = [
|
||||
'emailAddresses',
|
||||
'phoneNumbers',
|
||||
'emailAddressId',
|
||||
'phoneNumberId',
|
||||
'emailAddressIds',
|
||||
'phoneNumberIds',
|
||||
'emailAddressList',
|
||||
'phoneNumberList'
|
||||
]
|
||||
|
||||
print(f"\n📋 Teste verschiedene Feld-Namen:")
|
||||
|
||||
for field in potential_fields:
|
||||
try:
|
||||
result = await espo.api_call(
|
||||
f'CBeteiligte/{TEST_BETEILIGTE_ID}',
|
||||
params={'select': field}
|
||||
)
|
||||
if result and field in result:
|
||||
print(f" ✅ {field:30s}: {result[field]}")
|
||||
else:
|
||||
print(f" ❌ {field:30s}: Nicht im Response")
|
||||
except Exception as e:
|
||||
print(f" ❌ {field:30s}: Error - {e}")
|
||||
|
||||
|
||||
async def test_entity_relationships():
|
||||
"""Test 3: Hole Links/Relationships über dedizierte Endpoints"""
|
||||
print_section("TEST 3: Entity Relationships")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
# Test verschiedene Relationship-Endpoints
|
||||
relationship_names = [
|
||||
'emailAddresses',
|
||||
'phoneNumbers',
|
||||
'emails',
|
||||
'phones'
|
||||
]
|
||||
|
||||
for rel_name in relationship_names:
|
||||
print(f"\n🔗 Teste Relationship: {rel_name}")
|
||||
try:
|
||||
# EspoCRM API Format: /Entity/{id}/relationship-name
|
||||
result = await espo.api_call(f'CBeteiligte/{TEST_BETEILIGTE_ID}/{rel_name}')
|
||||
|
||||
if result:
|
||||
print(f" ✅ Success! Type: {type(result)}")
|
||||
|
||||
if isinstance(result, dict):
|
||||
print(f" 📋 Response Keys: {list(result.keys())}")
|
||||
|
||||
# Häufige EspoCRM Response-Strukturen
|
||||
if 'list' in result:
|
||||
items = result['list']
|
||||
print(f" 📊 {len(items)} Einträge in 'list'")
|
||||
if items:
|
||||
print(f"\n Erster Eintrag:")
|
||||
print(json.dumps(items[0], indent=6, ensure_ascii=False))
|
||||
|
||||
if 'total' in result:
|
||||
print(f" 📊 Total: {result['total']}")
|
||||
|
||||
elif isinstance(result, list):
|
||||
print(f" 📊 {len(result)} Einträge direkt als Liste")
|
||||
if result:
|
||||
print(f"\n Erster Eintrag:")
|
||||
print(json.dumps(result[0], indent=6, ensure_ascii=False))
|
||||
else:
|
||||
print(f" ⚠️ Empty response")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
if '404' in error_msg:
|
||||
print(f" ❌ 404 Not Found - Relationship existiert nicht")
|
||||
elif '403' in error_msg:
|
||||
print(f" ❌ 403 Forbidden - Kein Zugriff")
|
||||
else:
|
||||
print(f" ❌ Error: {error_msg}")
|
||||
|
||||
|
||||
async def test_direct_entity_access():
|
||||
"""Test 4: Direkter Zugriff auf PhoneNumber/EmailAddress Entities"""
|
||||
print_section("TEST 4: Direkte Entity-Abfrage")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
# Versuche die Entities direkt zu listen
|
||||
for entity_type in ['PhoneNumber', 'EmailAddress']:
|
||||
print(f"\n📋 Liste {entity_type} Entities:")
|
||||
try:
|
||||
# Mit Filter für unseren Beteiligten
|
||||
result = await espo.api_call(
|
||||
entity_type,
|
||||
params={
|
||||
'maxSize': 5,
|
||||
'where': json.dumps([{
|
||||
'type': 'equals',
|
||||
'attribute': 'parentId',
|
||||
'value': TEST_BETEILIGTE_ID
|
||||
}])
|
||||
}
|
||||
)
|
||||
|
||||
if result and 'list' in result:
|
||||
items = result['list']
|
||||
print(f" ✅ {len(items)} Einträge gefunden")
|
||||
for item in items:
|
||||
print(f"\n 📧/📞 {entity_type}:")
|
||||
print(json.dumps(item, indent=6, ensure_ascii=False))
|
||||
else:
|
||||
print(f" ⚠️ Keine Einträge oder unerwartetes Format")
|
||||
print(f" Response: {result}")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
if '403' in error_msg:
|
||||
print(f" ❌ 403 Forbidden")
|
||||
print(f" → Versuche ohne Filter...")
|
||||
|
||||
try:
|
||||
# Ohne Filter
|
||||
result = await espo.api_call(entity_type, params={'maxSize': 3})
|
||||
print(f" ✅ Ohne Filter: {result.get('total', 0)} total existieren")
|
||||
except Exception as e2:
|
||||
print(f" ❌ Auch ohne Filter: {e2}")
|
||||
else:
|
||||
print(f" ❌ Error: {error_msg}")
|
||||
|
||||
|
||||
async def test_espocrm_metadata():
|
||||
"""Test 5: Prüfe EspoCRM Metadata für CBeteiligte"""
|
||||
print_section("TEST 5: EspoCRM Metadata")
|
||||
|
||||
context = SimpleContext()
|
||||
espo = EspoCRMAPI(context)
|
||||
|
||||
print(f"\n📋 Hole Metadata für CBeteiligte:")
|
||||
try:
|
||||
# EspoCRM bietet manchmal Metadata-Endpoints
|
||||
result = await espo.api_call('Metadata')
|
||||
|
||||
if result and 'entityDefs' in result:
|
||||
if 'CBeteiligte' in result['entityDefs']:
|
||||
bet_meta = result['entityDefs']['CBeteiligte']
|
||||
|
||||
print(f"\n ✅ CBeteiligte Metadata gefunden")
|
||||
|
||||
if 'links' in bet_meta:
|
||||
print(f"\n 🔗 Links/Relationships:")
|
||||
for link_name, link_def in bet_meta['links'].items():
|
||||
if 'email' in link_name.lower() or 'phone' in link_name.lower():
|
||||
print(f" • {link_name}: {link_def}")
|
||||
|
||||
if 'fields' in bet_meta:
|
||||
print(f"\n 📋 Relevante Felder:")
|
||||
for field_name, field_def in bet_meta['fields'].items():
|
||||
if 'email' in field_name.lower() or 'phone' in field_name.lower():
|
||||
print(f" • {field_name}: {field_def.get('type', 'unknown')}")
|
||||
else:
|
||||
print(f" ⚠️ Unerwartetes Format")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ Error: {e}")
|
||||
|
||||
|
||||
async def main():
|
||||
print("\n" + "="*70)
|
||||
print("ESPOCRM PHONENUMBER/EMAILADDRESS - ENTITIES & IDS")
|
||||
print("="*70)
|
||||
print("\nZiel: Finde IDs für PhoneNumber/EmailAddress über Relationships\n")
|
||||
|
||||
try:
|
||||
# Test 1: Alle Felder inspizieren
|
||||
entity = await test_related_entities()
|
||||
|
||||
# Test 2: Select Parameter
|
||||
await test_list_with_select()
|
||||
|
||||
# Test 3: Relationships
|
||||
await test_entity_relationships()
|
||||
|
||||
# Test 4: Direkte Entity-Abfrage
|
||||
await test_direct_entity_access()
|
||||
|
||||
# Test 5: Metadata
|
||||
await test_espocrm_metadata()
|
||||
|
||||
print_section("ZUSAMMENFASSUNG")
|
||||
|
||||
print("\n🎯 Erkenntnisse:")
|
||||
print("\n Wenn PhoneNumber/EmailAddress System-Entities sind:")
|
||||
print(" 1. ✅ Sie haben eigene IDs")
|
||||
print(" 2. ✅ Stabiles Matching möglich")
|
||||
print(" 3. ✅ Bidirektionaler Sync machbar")
|
||||
print(" 4. ✅ Change Detection via ID")
|
||||
|
||||
print("\n Wenn wir IDs haben:")
|
||||
print(" • Können Advoware-ID zu EspoCRM-ID mappen")
|
||||
print(" • Können Änderungen tracken")
|
||||
print(" • Kein Problem bei Wert-Änderungen")
|
||||
|
||||
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