- 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.
251 lines
8.3 KiB
Python
251 lines
8.3 KiB
Python
"""
|
|
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())
|