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:
2026-02-08 19:53:40 +00:00
parent da9a962858
commit ebbbf419ee
23 changed files with 7626 additions and 13 deletions

View File

@@ -0,0 +1,350 @@
"""
Detaillierte Analyse: Welche Felder sind bei PUT änderbar?
Basierend auf ersten Tests:
- POST funktioniert (alle 4 Felder)
- PUT funktioniert TEILWEISE
- DELETE = 403 Forbidden (wie bei Adressen/Bankverbindungen)
Felder laut Swagger:
- tlf (string, nullable)
- bemerkung (string, nullable)
- kommKz (enum/int)
- online (boolean)
Response enthält zusätzlich:
- id (int) - Kommunikations-ID
- betNr (int) - Beteiligten-ID
- kommArt (int) - Scheint von kommKz generiert zu werden
- rowId (string) - Änderungserkennung
"""
import asyncio
import json
from services.advoware import AdvowareAPI
TEST_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): 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_field_mutability():
"""Teste welche Felder bei PUT änderbar sind"""
context = SimpleContext()
advo = AdvowareAPI(context)
# STEP 1: Erstelle Test-Kommunikation
print_section("STEP 1: Erstelle Test-Kommunikation")
create_data = {
'kommKz': 1, # TelGesch
'tlf': '+49 511 000000-00',
'bemerkung': 'TEST-READONLY: Initial',
'online': False
}
print(f"📤 POST Data: {json.dumps(create_data, indent=2)}")
result = await advo.api_call(
f'api/v1/advonet/Beteiligte/{TEST_BETNR}/Kommunikationen',
method='POST',
data=create_data
)
if isinstance(result, list) and len(result) > 0:
created = result[0]
else:
created = result
komm_id = created['id']
original_rowid = created['rowId']
print(f"\n✅ Erstellt:")
print(f" ID: {komm_id}")
print(f" rowId: {original_rowid}")
print(f" kommArt: {created['kommArt']}")
print(f"\n📋 Vollständige Response:")
print(json.dumps(created, indent=2, ensure_ascii=False))
# STEP 2: Teste jedes Feld einzeln
print_section("STEP 2: Teste Feld-Änderbarkeit")
test_results = {}
# Test 1: tlf
print("\n🔬 Test 1/4: tlf (Telefonnummer/Email)")
print(" Änderung: '+49 511 000000-00''+49 511 111111-11'")
test_data = {
'kommKz': created['kommKz'],
'tlf': '+49 511 111111-11', # GEÄNDERT
'bemerkung': created['bemerkung'],
'online': created['online']
}
try:
result = await advo.api_call(
f'api/v1/advonet/Beteiligte/{TEST_BETNR}/Kommunikationen/{komm_id}',
method='PUT',
data=test_data
)
new_rowid = result['rowId']
rowid_changed = (new_rowid != original_rowid)
value_changed = (result['tlf'] == '+49 511 111111-11')
print(f" ✅ PUT erfolgreich")
print(f" 📊 Wert geändert: {value_changed}")
print(f" 📊 rowId geändert: {rowid_changed}")
print(f" Alt: {original_rowid}")
print(f" Neu: {new_rowid}")
test_results['tlf'] = {
'writable': value_changed,
'rowid_changed': rowid_changed,
'status': 'WRITABLE' if value_changed else 'READ-ONLY'
}
original_rowid = new_rowid # Update für nächsten Test
except Exception as e:
print(f" ❌ FEHLER: {e}")
test_results['tlf'] = {'writable': False, 'status': 'ERROR', 'error': str(e)}
# Test 2: bemerkung
print("\n🔬 Test 2/4: bemerkung")
print(" Änderung: 'TEST-READONLY: Initial''TEST-READONLY: Modified'")
test_data = {
'kommKz': created['kommKz'],
'tlf': result['tlf'], # Aktueller Wert
'bemerkung': 'TEST-READONLY: Modified', # GEÄNDERT
'online': result['online']
}
try:
result = await advo.api_call(
f'api/v1/advonet/Beteiligte/{TEST_BETNR}/Kommunikationen/{komm_id}',
method='PUT',
data=test_data
)
new_rowid = result['rowId']
rowid_changed = (new_rowid != original_rowid)
value_changed = (result['bemerkung'] == 'TEST-READONLY: Modified')
print(f" ✅ PUT erfolgreich")
print(f" 📊 Wert geändert: {value_changed}")
print(f" 📊 rowId geändert: {rowid_changed}")
test_results['bemerkung'] = {
'writable': value_changed,
'rowid_changed': rowid_changed,
'status': 'WRITABLE' if value_changed else 'READ-ONLY'
}
original_rowid = new_rowid
except Exception as e:
print(f" ❌ FEHLER: {e}")
test_results['bemerkung'] = {'writable': False, 'status': 'ERROR', 'error': str(e)}
# Test 3: kommKz
print("\n🔬 Test 3/4: kommKz (Kommunikationstyp)")
original_kommkz = result['kommKz']
target_kommkz = 6
print(f" Änderung: {original_kommkz} (TelGesch) → {target_kommkz} (TelPrivat)")
test_data = {
'kommKz': target_kommkz, # GEÄNDERT
'tlf': result['tlf'],
'bemerkung': f"TEST-READONLY: Versuch kommKz {original_kommkz}{target_kommkz}",
'online': result['online']
}
try:
result = await advo.api_call(
f'api/v1/advonet/Beteiligte/{TEST_BETNR}/Kommunikationen/{komm_id}',
method='PUT',
data=test_data
)
new_rowid = result['rowId']
rowid_changed = (new_rowid != original_rowid)
value_changed = (result['kommKz'] == target_kommkz)
print(f" ✅ PUT erfolgreich")
print(f" 📊 PUT Response kommKz: {result['kommKz']}")
print(f" 📊 PUT Response kommArt: {result['kommArt']}")
print(f" 📊 rowId geändert: {rowid_changed}")
# WICHTIG: Nachfolgender GET zur Verifizierung
print(f"\n 🔍 Verifizierung via GET...")
beteiligte_get = await advo.api_call(
f'api/v1/advonet/Beteiligte/{TEST_BETNR}',
method='GET'
)
if isinstance(beteiligte_get, list):
beteiligte_get = beteiligte_get[0]
kommunikationen_get = beteiligte_get.get('kommunikation', [])
verify_komm = next((k for k in kommunikationen_get if k['id'] == komm_id), None)
if verify_komm:
print(f" 📋 GET Response kommKz: {verify_komm['kommKz']}")
print(f" 📋 GET Response kommArt: {verify_komm['kommArt']}")
print(f" 📋 GET Response bemerkung: {verify_komm['bemerkung']}")
# Finale Bewertung basierend auf GET
actual_value_changed = (verify_komm['kommKz'] == target_kommkz)
if actual_value_changed:
print(f" ✅ BESTÄTIGT: kommKz wurde geändert auf {target_kommkz}")
else:
print(f" ❌ BESTÄTIGT: kommKz blieb bei {verify_komm['kommKz']} (nicht geändert!)")
test_results['kommKz'] = {
'writable': actual_value_changed,
'rowid_changed': rowid_changed,
'status': 'WRITABLE' if actual_value_changed else 'READ-ONLY',
'requested_value': target_kommkz,
'put_response_value': result['kommKz'],
'get_response_value': verify_komm['kommKz'],
'note': f"PUT sagte: {result['kommKz']}, GET sagte: {verify_komm['kommKz']}"
}
else:
print(f" ⚠️ Kommunikation nicht in GET gefunden")
test_results['kommKz'] = {
'writable': False,
'status': 'ERROR',
'error': 'Not found in GET'
}
original_rowid = new_rowid
except Exception as e:
print(f" ❌ FEHLER: {e}")
test_results['kommKz'] = {'writable': False, 'status': 'ERROR', 'error': str(e)}
# Test 4: online
print("\n🔬 Test 4/4: online (Boolean Flag)")
print(" Änderung: False → True")
test_data = {
'kommKz': result['kommKz'],
'tlf': result['tlf'],
'bemerkung': result['bemerkung'],
'online': True # GEÄNDERT
}
try:
result = await advo.api_call(
f'api/v1/advonet/Beteiligte/{TEST_BETNR}/Kommunikationen/{komm_id}',
method='PUT',
data=test_data
)
new_rowid = result['rowId']
rowid_changed = (new_rowid != original_rowid)
value_changed = (result['online'] == True)
print(f" ✅ PUT erfolgreich")
print(f" 📊 Wert geändert: {value_changed}")
print(f" 📊 rowId geändert: {rowid_changed}")
test_results['online'] = {
'writable': value_changed,
'rowid_changed': rowid_changed,
'status': 'WRITABLE' if value_changed else 'READ-ONLY'
}
except Exception as e:
print(f" ❌ FEHLER: {e}")
test_results['online'] = {'writable': False, 'status': 'ERROR', 'error': str(e)}
# ZUSAMMENFASSUNG
print_section("ZUSAMMENFASSUNG: Feld-Status")
print("\n📊 Ergebnisse:\n")
for field, result in test_results.items():
status = result['status']
icon = "" if status == "WRITABLE" else "" if status == "READ-ONLY" else "⚠️"
print(f" {icon} {field:15s}{status}")
if result.get('note'):
print(f" {result['note']}")
if result.get('error'):
print(f" ⚠️ {result['error']}")
# Count
writable = sum(1 for r in test_results.values() if r['status'] == 'WRITABLE')
readonly = sum(1 for r in test_results.values() if r['status'] == 'READ-ONLY')
print(f"\n📈 Statistik:")
print(f" WRITABLE: {writable}/{len(test_results)} Felder")
print(f" READ-ONLY: {readonly}/{len(test_results)} Felder")
print(f"\n⚠️ Test-Kommunikation {komm_id} manuell löschen!")
print(f" BetNr: {TEST_BETNR}")
return test_results
async def main():
print("\n" + "="*70)
print("KOMMUNIKATION API - FELDANALYSE")
print("="*70)
print("\nZiel: Herausfinden welche Felder bei PUT änderbar sind")
print("Methode: Einzelne Feldänderungen + rowId-Tracking\n")
try:
results = await test_field_mutability()
print_section("EMPFEHLUNG FÜR MAPPER")
writable_fields = [f for f, r in results.items() if r['status'] == 'WRITABLE']
readonly_fields = [f for f, r in results.items() if r['status'] == 'READ-ONLY']
if writable_fields:
print("\n✅ Für UPDATE (PUT) verwenden:")
for field in writable_fields:
print(f" - {field}")
if readonly_fields:
print("\n❌ NUR bei CREATE (POST) verwenden:")
for field in readonly_fields:
print(f" - {field}")
print("\n💡 Sync-Strategie:")
print(" - CREATE: Alle Felder")
print(" - UPDATE: Nur WRITABLE Felder")
print(" - DELETE: Notification (403 Forbidden)")
except Exception as e:
print(f"\n❌ Fehler: {e}")
import traceback
traceback.print_exc()
if __name__ == '__main__':
asyncio.run(main())