Files
motia/bitbylaw/services/KOMMUNIKATION_SYNC_README.md
bitbylaw ebbbf419ee 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.
2026-02-08 19:53:40 +00:00

10 KiB

Kommunikation Sync Implementation

Overview

Bidirektionale Synchronisation von Email- und Telefon-Daten zwischen Advoware und EspoCRM.

Architektur-Übersicht

┌─────────────────────────────────────────────────────────────────┐
│                  Advoware ↔ EspoCRM Sync                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  ADVOWARE                          ESPOCRM                       │
│  ─────────────────                 ──────────────────            │
│  Beteiligte                        CBeteiligte                   │
│  └─ kommunikation[]                ├─ emailAddressData[]         │
│     ├─ id (unique int)             │  └─ emailAddress            │
│     ├─ rowId (string)              │     lower, primary          │
│     ├─ tlf (value)                 │                             │
│     ├─ bemerkung (marker!)         └─ phoneNumberData[]          │
│     ├─ kommKz (1-12)                  └─ phoneNumber             │
│     └─ online (bool)                     type, primary            │
│                                                                   │
│  MATCHING: Hash in bemerkung-Marker                             │
│  [ESPOCRM:hash:kommKz] User text                                │
└─────────────────────────────────────────────────────────────────┘

Core Features

1. Base64-basiertes Matching IMPLEMENTIERT

  • Problem: EspoCRM Arrays haben keine IDs
  • Lösung: Base64-kodierter Wert in Advoware bemerkung
  • Format: [ESPOCRM:bWF4QGV4YW1wbGUuY29t:4] Geschäftlich
  • Vorteil: Bidirektional! Marker enthält den tatsächlichen Wert (dekodierbar)

Warum Base64 statt Hash?

# Hash-Problem (alt): Nicht rückrechenbar
old_hash = hash("old@example.com")  # abc12345
# Bei Wert-Änderung in Advoware: Kein Match möglich! ❌

# Base64-Lösung (neu): Bidirektional
encoded = base64("old@example.com")  # b2xkQGV4YW1wbGUuY29t
decoded = decode(encoded)  # "old@example.com" ✅
# Kann dekodieren → Match in EspoCRM finden!

2. 4-Stufen Typ-Erkennung

1. Aus Marker:      [ESPOCRM:hash:3]        kommKz=3 (Mobil)
2. Aus Top-Level:   beteiligte.mobil        kommKz=3
3. Aus Pattern:     '@' in value            kommKz=4 (Email)
4. Default:         Fallback                kommKz=1 oder 4

3. Empty Slot System

  • Problem: DELETE ist 403 Forbidden in Advoware
  • Lösung: Leere Slots mit [ESPOCRM-SLOT:kommKz]
  • Wiederverwendung: Neue Einträge reuse leere Slots

4. Asymmetrischer Sync

Problem: Hash-basiertes Matching funktioniert NICHT bidirektional

  • Wenn Wert in Advoware ändert: Hash ändert sich → Kein Match in EspoCRM möglich

Lösung: Verschiedene Strategien je Richtung

Richtung Methode Grund
Advoware → EspoCRM FULL SYNC (kompletter Overwrite) Kein stabiles Matching möglich
EspoCRM → Advoware INCREMENTAL SYNC (Hash-basiert) EspoCRM-Wert bekannt → Hash berechenbar

Ablauf Advoware → EspoCRM (FULL SYNC):

1. Sammle ALLE Kommunikationen (ohne Empty Slots)
2. Setze/Update Marker für Rück-Sync
3. Ersetze KOMPLETTE emailAddressData[] und phoneNumberData[]

Ablauf EspoCRM → Advoware (INCREMENTAL):

1. Baue Hash-Maps von beiden Seiten
2. Vergleiche: Deleted, Changed, New
3. Apply Changes (Empty Slots, Updates, Creates)

Module Structure

services/
├── kommunikation_mapper.py          # Datentyp-Mapping & Marker-Logik
├── advoware_service.py              # Advoware API-Wrapper
└── kommunikation_sync_utils.py      # Sync-Manager (bidirectional)

Usage Example

from services.advoware_service import AdvowareService
from services.espocrm import EspoCrmService
from services.kommunikation_sync_utils import KommunikationSyncManager

# Initialize
advo = AdvowareService()
espo = EspoCrmService()
sync_manager = KommunikationSyncManager(advo, espo)

# Bidirectional Sync
result = sync_manager.sync_bidirectional(
    beteiligte_id='espocrm-bet-id',
    betnr=12345,
    direction='both'  # 'both', 'to_espocrm', 'to_advoware'
)

print(result)
# {
#     'advoware_to_espocrm': {
#         'emails_synced': 3,
#         'phones_synced': 2,
#         'errors': []
#     },
#     'espocrm_to_advoware': {
#         'created': 1,
#         'updated': 2,
#         'deleted': 0,
#         'errors': []
#     }
# }

Field Mapping

kommKz Enum (Advoware)

kommKz Name EspoCRM Target EspoCRM Type
1 TelGesch phoneNumberData Office
2 FaxGesch phoneNumberData Fax
3 Mobil phoneNumberData Mobile
4 MailGesch emailAddressData -
5 Internet (skipped) -
6 TelPrivat phoneNumberData Home
7 FaxPrivat phoneNumberData Fax
8 MailPrivat emailAddressData -
9 AutoTelefon phoneNumberData Mobile
10 Sonstige phoneNumberData Other
11 EPost emailAddressData -
12 Bea emailAddressData -

Note: Internet (kommKz=5) wird nicht synchronisiert (unklar ob Email/Phone).

Sync Scenarios

Scenario 1: Delete in EspoCRM

EspoCRM: max@example.com gelöscht
Advoware: [ESPOCRM:abc:4] max@example.com

→ UPDATE zu Empty Slot:
  tlf: ''
  bemerkung: [ESPOCRM-SLOT:4]
  online: False

Scenario 2: Change in EspoCRM

EspoCRM: max@old.com → max@new.com
Advoware: [ESPOCRM:oldhash:4] max@old.com

→ UPDATE with new hash:
  tlf: 'max@new.com'
  bemerkung: [ESPOCRM:newhash:4] Geschäftlich
  online: True

Scenario 3: New in EspoCRM

EspoCRM: Neue Email new@example.com

→ Suche Empty Slot (kommKz=4)
  IF found: REUSE (UPDATE)
  ELSE: CREATE new

Scenario 4: New in Advoware

Advoware: Neue Kommunikation (kein Marker)

→ Typ-Erkennung via Top-Level/Pattern
→ Sync zu EspoCRM
→ Marker in Advoware setzen

API Limitations

Advoware API v1

  • POST: /api/v1/advonet/Beteiligte/{betnr}/Kommunikationen

    • Required: tlf, kommKz
    • Optional: bemerkung, online
  • PUT: /api/v1/advonet/Beteiligte/{betnr}/Kommunikationen/{id}

    • Writable: tlf, bemerkung, online
    • READ-ONLY: kommKz (cannot change type!)
  • DELETE: 403 Forbidden

    • Use Empty Slots instead
  • ⚠️ BUG: kommKz always returns 0 in GET

    • Use Top-Level fields + Pattern detection

EspoCRM

  • emailAddressData: Array ohne IDs
  • phoneNumberData: Array ohne IDs
  • Kein CKommunikation Entity: Arrays nur in CBeteiligte

Testing

Run all tests:

cd /opt/motia-app/bitbylaw
python3 scripts/test_kommunikation_sync_implementation.py

Test Coverage:

  • Hash-Berechnung und Konsistenz
  • Marker-Parsing (Standard + Slot)
  • Marker-Erstellung
  • 4-Stufen Typ-Erkennung (alle Tiers)
  • Typ-Klassifizierung (Email vs Phone)
  • Integration Szenario
  • Top-Level Feld Priorität

Change Detection

Advoware Webhook

from services.kommunikation_sync_utils import detect_kommunikation_changes

if detect_kommunikation_changes(old_bet, new_bet):
    # rowId changed → Sync needed
    sync_manager.sync_bidirectional(bet_id, betnr, direction='to_espocrm')

EspoCRM Webhook

from services.kommunikation_sync_utils import detect_espocrm_kommunikation_changes

if detect_espocrm_kommunikation_changes(old_data, new_data):
    # Array changed → Sync needed
    sync_manager.sync_bidirectional(bet_id, betnr, direction='to_advoware')

Known Limitations

  1. FULL SYNC von Advoware → EspoCRM:

    • Arrays werden komplett überschrieben (kein Merge)
    • Grund: Hash-basiertes Matching funktioniert nicht bei Wert-Änderungen in Advoware
    • Risiko minimal: EspoCRM-Arrays haben keine Relationen
  2. Empty Slots Accumulation:

    • Gelöschte Einträge werden zu leeren Slots
    • Werden wiederverwendet, aber akkumulieren
    • TODO: Periodic cleanup job
  3. Partial Type Loss:

    • Advoware-Kommunikationen ohne Top-Level Match verlieren Feintyp
    • Fallback: @ → Email (4), sonst Phone (1)
  4. kommKz READ-ONLY:

    • Typ kann nach Erstellung nicht geändert werden
    • Workaround: DELETE + CREATE (manuell)
  5. Marker sichtbar:

    • [ESPOCRM:...] ist in Advoware UI sichtbar
    • User kann Text dahinter hinzufügen

Documentation

Implementation Status

COMPLETE

  • Marker-System (Hash + kommKz)
  • 4-Stufen Typ-Erkennung
  • Empty Slot System
  • Bidirektionale Sync-Logik
  • Advoware Service Wrapper
  • Change Detection
  • Test Suite
  • Documentation

Next Steps

  1. Integration in Webhook System

    • Add kommunikation change detection to beteiligte webhooks
    • Wire up sync calls
  2. Monitoring

    • Add metrics for sync operations
    • Track empty slot accumulation
  3. Maintenance

    • Implement periodic cleanup job for old empty slots
    • Add notification for type-change scenarios
  4. Testing

    • End-to-end tests with real Advoware/EspoCRM data
    • Load testing for large kommunikation arrays

Last Updated: 2024-01-26
Status: Implementation Complete - Ready for Integration