Files
motia/bitbylaw/IMPLEMENTATION_COMPLETE.md
bitbylaw b5abe6cf00 Implement EspoCRM-based sync strategy for Beteiligte entities
- Add SYNC_STRATEGY_ESPOCRM_BASED.md detailing the sync flows and status management.
- Create utilities for sync operations in services/beteiligte_sync_utils.py, including locking, timestamp comparison, conflict resolution, and notification handling.
- Implement entity mapping between EspoCRM and Advoware in services/espocrm_mapper.py.
- Develop a cron job for periodic sync checks in steps/vmh/beteiligte_sync_cron_step.py, emitting events for entities needing synchronization.
2026-02-07 15:21:16 +00:00

8.2 KiB

Beteiligte Sync Implementation - Fertig!

Stand: 7. Februar 2026
Status: Vollständig implementiert, ready for testing


📦 Implementierte Module

1. services/espocrm_mapper.py

Zweck: Entity-Transformation zwischen EspoCRM ↔ Advoware

Funktionen:

  • map_cbeteiligte_to_advoware(espo_entity) - EspoCRM → Advoware
  • map_advoware_to_cbeteiligte(advo_entity) - Advoware → EspoCRM
  • get_changed_fields(espo, advo) - Diff-Vergleich

Features:

  • Unterscheidet Person vs. Firma
  • Mapped Namen, Kontaktdaten, Handelsregister
  • Transformiert emailAddressData/phoneNumberData Arrays
  • Normalisiert Rechtsform und Anrede

2. services/beteiligte_sync_utils.py

Zweck: Sync-Utility-Funktionen

Funktionen:

  • acquire_sync_lock(entity_id) - Atomares Lock via syncStatus
  • release_sync_lock(entity_id, status, error, retry) - Lock freigeben + Update
  • parse_timestamp(ts) - Parse EspoCRM/Advoware Timestamps
  • compare_timestamps(espo, advo, last_sync) - Returns: espocrm_newer | advoware_newer | conflict | no_change
  • send_notification(entity_id, type, data) - EspoCRM In-App Notification
  • handle_advoware_deleted(entity_id, error) - Soft-Delete + Notification
  • resolve_conflict_espocrm_wins(entity_id, ...) - Konfliktauflösung

Features:

  • Race-Condition-Prevention durch syncStatus="syncing"
  • Automatische syncRetryCount Increment bei Fehlern
  • EspoCRM Notifications (🔔 Bell-Icon)
  • Timestamp-Normalisierung für beide Systeme

3. steps/vmh/beteiligte_sync_event_step.py

Zweck: Zentraler Sync-Handler (Webhooks + Cron)

Config:

subscribes: [
    'vmh.beteiligte.create',
    'vmh.beteiligte.update', 
    'vmh.beteiligte.delete',
    'vmh.beteiligte.sync_check'  # Von Cron
]

Ablauf:

  1. Acquire Lock (syncStatus → syncing)
  2. Fetch Entity von EspoCRM
  3. Bestimme Aktion:
    • Kein betnrhandle_create() - Neu in Advoware
    • Hat betnrhandle_update() - Sync mit Timestamp-Vergleich
  4. Release Lock mit finalem Status

handle_create():

  • Transform zu Advoware Format
  • POST /api/v1/advonet/Beteiligte
  • Update EspoCRM mit neuer betnr
  • Status → clean

handle_update():

  • Fetch von Advoware (betNr)
  • 404 → handle_advoware_deleted() (Soft-Delete)
  • Timestamp-Vergleich:
    • espocrm_newer → PUT zu Advoware
    • advoware_newer → PUT zu EspoCRM
    • conflictEspoCRM WINS → Überschreibe Advoware → Notification
    • no_change → Skip

Error Handling:

  • Try/Catch um alle Operationen
  • Bei Fehler: syncStatus=failed, syncErrorMessage, syncRetryCount++
  • Redis Queue Cleanup

4. steps/vmh/beteiligte_sync_cron_step.py

Zweck: Cron-Job der Events emittiert

Config:

schedule: '*/15 * * * *'  # Alle 15 Minuten
emits: ['vmh.beteiligte.sync_check']

Ablauf:

  1. Query 1: Entities mit Status pending_sync, dirty, failed (max 100)
  2. Query 2: clean Entities mit advowareLastSync < NOW() - 24h (max 50)
  3. Kombiniere + Dedupliziere
  4. Emittiere vmh.beteiligte.sync_check Event für JEDEN Beteiligten
  5. Log: Anzahl emittierter Events

Vorteile:

  • Kein Batch-Processing
  • Events werden einzeln vom normalen Handler verarbeitet
  • Code-Wiederverwendung (gleicher Handler wie Webhooks)

🔄 Sync-Flows

Flow A: EspoCRM Create/Update → Advoware (Webhook)

User ändert in EspoCRM
  ↓
EspoCRM Webhook → /vmh/webhook/beteiligte/update
  ↓
beteiligte_update_api_step.py → Emit 'vmh.beteiligte.update'
  ↓
beteiligte_sync_event_step.py → handler()
  ↓
Acquire Lock → Fetch EspoCRM → Timestamp-Check
  ↓
Update Advoware (oder Konflikt → EspoCRM wins)
  ↓
Release Lock → Status: clean

Timing: 2-5 Sekunden


Flow B: Advoware → EspoCRM Check (Cron)

Cron (alle 15 Min)
  ↓
beteiligte_sync_cron_step.py
  ↓
Query EspoCRM: Unclean + Stale Entities
  ↓
Emit 'vmh.beteiligte.sync_check' für jeden
  ↓
beteiligte_sync_event_step.py → handler()
  ↓
GLEICHE Logik wie Flow A!

Timing: Alle 15 Minuten


🎯 Status-Übergänge

pending_sync → syncing → clean         (Create erfolgreich)
pending_sync → syncing → failed        (Create fehlgeschlagen)

clean → dirty → syncing → clean        (Update erfolgreich)
clean → syncing → conflict → clean     (Konflikt → EspoCRM wins)

dirty → syncing → deleted_in_advoware  (404 von Advoware)

failed → syncing → clean               (Retry erfolgreich)
failed → syncing → failed              (Retry fehlgeschlagen, retryCount++)

deleted_in_advoware                    (Soft-Delete, bleibt bis manuelle Aktion)

📋 Testing Checklist

Unit Tests

  • Mapper Import
  • Sync Utils Import
  • Event Step Config Load
  • Cron Step Config Load
  • Mapper Transform Person
  • Mapper Transform Firma

Integration Tests (TODO)

  • Create: Neuer Beteiligter in EspoCRM → Advoware
  • Update: Änderung in EspoCRM → Advoware
  • Conflict: Beide geändert → EspoCRM wins
  • Advoware newer: Advoware → EspoCRM
  • 404 Handling: Soft-Delete + Notification
  • Cron: Query + Event Emission
  • Notification: In-App Notification sichtbar

🚀 Deployment

Voraussetzungen

EspoCRM Felder angelegt (syncStatus, betnr, advowareLastSync, advowareDeletedAt, syncErrorMessage, syncRetryCount)
Webhooks aktiviert (Create/Update/Delete)
Motia Workbench Restart (damit Steps geladen werden)

Schritte

  1. Motia Restart: systemctl restart motia (oder wie auch immer)
  2. Verify Steps:
    # Check ob Steps geladen wurden
    curl http://localhost:PORT/api/flows/vmh/steps
    
  3. Test Webhook: Ändere einen Beteiligten in EspoCRM
  4. Check Logs: Motia Workbench Logs → Event Handler Output
  5. Verify Advoware: Prüfe ob betNr gesetzt wurde
  6. Test Cron: Warte 15 Min oder trigger manuell

🔧 Configuration

Environment Variables (bereits gesetzt)

# EspoCRM
ESPOCRM_API_BASE_URL=https://crm.bitbylaw.com/api/v1
ESPOCRM_MARVIN_API_KEY=e53def10eea27b92a6cd00f40a3e09a4

# Advoware
ADVOWARE_API_BASE_URL=https://www2.advo-net.net:90
ADVOWARE_PRODUCT_ID=...
ADVOWARE_APP_ID=...
ADVOWARE_API_KEY=...

# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_DB_ADVOWARE_CACHE=1

📊 Monitoring

EspoCRM Queries

Entities die Sync benötigen:

GET /api/v1/CBeteiligte?where=[
  {type: 'in', attribute: 'syncStatus', 
   value: ['pending_sync', 'dirty', 'failed']}
]

Konflikte:

GET /api/v1/CBeteiligte?where=[
  {type: 'equals', attribute: 'syncStatus', value: 'conflict'}
]

Soft-Deletes:

GET /api/v1/CBeteiligte?where=[
  {type: 'equals', attribute: 'syncStatus', value: 'deleted_in_advoware'}
]

Sync-Fehler:

GET /api/v1/CBeteiligte?where=[
  {type: 'isNotNull', attribute: 'syncErrorMessage'}
]

Motia Logs

# Event Handler Logs
tail -f /path/to/motia/logs/events.log | grep "Beteiligte"

# Cron Logs
tail -f /path/to/motia/logs/cron.log | grep "Sync Cron"

🐛 Troubleshooting

Problem: Lock bleibt auf "syncing" hängen

Ursache: Handler-Crash während Sync
Lösung: Manuell Status auf "failed" setzen:

PUT /api/v1/CBeteiligte/{id}
{"syncStatus": "failed", "syncErrorMessage": "Manual reset"}

Problem: Notifications werden nicht angezeigt

Ursache: userId fehlt oder falsch
Check:

GET /api/v1/Notification?where=[{type: 'equals', attribute: 'relatedType', value: 'CBeteiligte'}]

Problem: Cron emittiert keine Events

Ursache: Query findet keine Entities
Debug: Führe Cron-Handler manuell aus und checke Logs


📈 Performance

Erwartete Last:

  • Webhooks: ~10-50 pro Tag (User-Änderungen)
  • Cron: Alle 15 Min → ~96 Runs/Tag
  • Events pro Cron: 0-100 (typisch 5-20)

Optimization:

  • Cron Max Entities: 150 total (100 unclean + 50 stale)
  • Event-Processing: Parallel (Motia-Standard)
  • Redis Caching: Token + Deduplication

Done!

Implementiert: 4 Module, ~800 Lines of Code
Status: Ready for Testing
Next Steps: Deploy + Integration Testing + Monitoring Setup

🎉 Viel Erfolg beim Testing!