Files
motia/bitbylaw/docs/archive/SYNC_STATUS_ANALYSIS.md
bitbylaw 89fc657d47 feat(sync): Implement comprehensive sync fixes and optimizations as of February 8, 2026
- Fixed initial sync logic to respect actual timestamps, preventing unwanted overwrites.
- Introduced exponential backoff for retry logic, with auto-reset for permanently failed entities.
- Added validation checks to ensure data consistency during sync processes.
- Corrected hash calculation to only include sync-relevant communications.
- Resolved issues with empty slots ignoring user inputs and improved conflict handling.
- Enhanced handling of Var4 and Var6 entries during sync conflicts.
- Documented changes and added new fields required in EspoCRM for improved sync management.

Also added a detailed analysis of syncStatus values in EspoCRM CBeteiligte, outlining responsibilities and ensuring robust sync mechanisms.
2026-02-08 22:59:47 +00:00

13 KiB
Raw Permalink Blame History

Analyse: syncStatus Werte in EspoCRM CBeteiligte

Datum: 8. Februar 2026 (Updated)

Design-Philosophie: Defense in Depth (Webhook + Cron Fallback)

Das System verwendet zwei parallele Sync-Trigger:

  1. Primary Path (Webhook): Echtzeit-Sync bei Änderungen in EspoCRM
  2. Fallback Path (Cron): 15-Minuten-Check falls Webhook fehlschlägt

Dies garantiert robuste Synchronisation auch bei temporären Webhook-Ausfällen.


Übersicht: Definierte syncStatus-Werte

Basierend auf Code-Analyse wurden folgende Status identifiziert:

Status Bedeutung Gesetzt von Zweck
pending_sync Wartet auf ersten Sync EspoCRM (bei CREATE) Cron-Fallback wenn Webhook fehlschlägt
dirty Daten geändert, Sync nötig EspoCRM (bei UPDATE) Cron-Fallback wenn Webhook fehlschlägt
syncing Sync läuft gerade Python (acquire_lock) Lock während Sync
clean Erfolgreich synchronisiert Python (release_lock) Sync erfolgreich
failed Sync fehlgeschlagen (< 5 Retries) Python (bei Fehler) Retry mit Backoff
permanently_failed Sync fehlgeschlagen (≥ 5 Retries) Python (max retries) Auto-Reset nach 24h
conflict Konflikt erkannt (optional) Python (bei Konflikt) UI-Visibility für Konflikte
deleted_in_advoware In Advoware gelöscht (404) Python (bei 404) Soft-Delete Strategie

Status-Verantwortlichkeiten

EspoCRM Verantwortung (Frontend/Hooks):

  • pending_sync - Bei CREATE neuer CBeteiligte Entity
  • dirty - Bei UPDATE existierender CBeteiligte Entity

Python Verantwortung (Sync-Handler):

  • syncing - Lock während Sync-Prozess
  • clean - Nach erfolgreichem Sync
  • failed - Bei Sync-Fehlern mit Retry
  • permanently_failed - Nach zu vielen Retries
  • conflict - Bei erkannten Konflikten (optional)
  • deleted_in_advoware - Bei 404 von Advoware API

Detaillierte Analyse

Python-Managed Status (funktionieren perfekt)

1. syncing

Wann gesetzt: Bei acquire_sync_lock() (Line 90)

await self.espocrm.update_entity('CBeteiligte', entity_id, {
    'syncStatus': 'syncing'
})

Sinnvoll: Ja - verhindert parallele Syncs, UI-Feedback


2. clean

Wann gesetzt: Bei release_sync_lock() nach erfolgreichem Sync

await sync_utils.release_sync_lock(entity_id, 'clean')

Verwendungen:

  • Nach CREATE: Line 223 (beteiligte_sync_event_step.py)
  • Nach espocrm_newer Sync: Line 336
  • Nach advoware_newer Sync: Line 369
  • Nach Konflikt-Auflösung: Line 423 + 643 (beteiligte_sync_utils.py)

Sinnvoll: Ja - zeigt erfolgreichen Sync an


3. failed

Wann gesetzt: Bei release_sync_lock() mit increment_retry=True

await sync_utils.release_sync_lock(entity_id, 'failed', str(e), increment_retry=True)

Verwendungen:

  • CREATE fehlgeschlagen: Line 235
  • UPDATE fehlgeschlagen: Line 431
  • Validation fehlgeschlagen: Lines 318, 358, 409
  • Exception im Handler: Line 139

Sinnvoll: Ja - ermöglicht Retry-Logik


4. permanently_failed

Wann gesetzt: Nach ≥ 5 Retries (Line 162, beteiligte_sync_utils.py)

if new_retry >= MAX_SYNC_RETRIES:
    update_data['syncStatus'] = 'permanently_failed'

Auto-Reset: Nach 24h durch Cron (Lines 64-85, beteiligte_sync_cron_step.py)

{'type': 'equals', 'attribute': 'syncStatus', 'value': 'permanently_failed'}
# → Reset zu 'failed' nach 24h

Sinnvoll: Ja - verhindert endlose Retries, aber ermöglicht Recovery


5. deleted_in_advoware

Wann gesetzt: Bei 404 von Advoware API (Line 530, beteiligte_sync_utils.py)

await self.espocrm.update_entity('CBeteiligte', entity_id, {
    'syncStatus': 'deleted_in_advoware',
    'advowareDeletedAt': now,
    'syncErrorMessage': f"Beteiligter existiert nicht mehr in Advoware. {error_details}"
})

Sinnvoll: Ja - Soft-Delete Strategie, ermöglicht manuelle Überprüfung


<EFBFBD> EspoCRM-Managed Status (Webhook-Trigger + Cron-Fallback)

Diese Status werden von EspoCRM gesetzt (nicht vom Python-Code):

6. pending_sync

Wann gesetzt: Von EspoCRM bei CREATE neuer CBeteiligte Entity

Zweck:

  • Primary: Webhook vmh.beteiligte.create triggert sofort
  • Fallback: Falls Webhook fehlschlägt, findet Cron diese Entities

Cron-Query (Line 45, beteiligte_sync_cron_step.py):

{'type': 'equals', 'attribute': 'syncStatus', 'value': 'pending_sync'}

Workflow:

1. User erstellt CBeteiligte in EspoCRM
2. EspoCRM setzt syncStatus = 'pending_sync'
3. PRIMARY: EspoCRM Webhook → vmh.beteiligte.create → Sofortiger Sync
   FALLBACK: Webhook failed → Cron (alle 15min) findet Entity via Status
4. Python Sync-Handler: pending_sync → syncing → clean/failed

Sinnvoll: Ja - Defense in Depth Design, garantiert Sync auch bei Webhook-Ausfall


7. dirty

Wann gesetzt: Von EspoCRM bei UPDATE existierender CBeteiligte Entity

Zweck:

  • Primary: Webhook vmh.beteiligte.update triggert sofort
  • Fallback: Falls Webhook fehlschlägt, findet Cron diese Entities

Cron-Query (Line 46, beteiligte_sync_cron_step.py):

{'type': 'equals', 'attribute': 'syncStatus', 'value': 'dirty'}

Workflow:

1. User ändert CBeteiligte in EspoCRM
2. EspoCRM setzt syncStatus = 'dirty' (nur wenn vorher 'clean')
3. PRIMARY: EspoCRM Webhook → vmh.beteiligte.update → Sofortiger Sync
   FALLBACK: Webhook failed → Cron (alle 15min) findet Entity via Status
4. Python Sync-Handler: dirty → syncing → clean/failed

Sinnvoll: Ja - Defense in Depth Design, garantiert Sync auch bei Webhook-Ausfall

Implementation in EspoCRM:

// EspoCRM Hook: afterSave() in CBeteiligte
entity.set('syncStatus', entity.isNew() ? 'pending_sync' : 'dirty');

8. conflict ⚠️ (Optional)

Wann gesetzt: Aktuell NIE - Konflikte werden sofort auto-resolved

Aktuelles Verhalten:

# Bei Konflikt-Erkennung:
if comparison == 'conflict':
    # ... löse Konflikt (EspoCRM wins)
    await sync_utils.resolve_conflict_espocrm_wins(...)
    # Status geht direkt zu 'clean'

Potential für Verbesserung:

# Option: Intermediate 'conflict' Status für Admin-Review
if comparison == 'conflict' and not AUTO_RESOLVE_CONFLICTS:
    await espocrm.update_entity('CBeteiligte', entity_id, {
        'syncStatus': 'conflict',
        'conflictDetails': conflict_details
    })
    # Warte auf Admin-Aktion
else:
    # Auto-Resolve wie aktuell

Status: ⚠️ Optional - Aktuelles Auto-Resolve funktioniert, aber conflict Status könnte UI-Visibility verbessern


Cron-Job Queries Analyse

Datei: steps/vmh/beteiligte_sync_cron_step.py

Query 1: Normale Sync-Kandidaten

{
    'type': 'or',
    'value': [
        {'type': 'equals', 'attribute': 'syncStatus', 'value': 'pending_sync'},  # ✅ Von EspoCRM gesetzt
        {'type': 'equals', 'attribute': 'syncStatus', 'value': 'dirty'},         # ✅ Von EspoCRM gesetzt
        {'type': 'equals', 'attribute': 'syncStatus', 'value': 'failed'},        # ✅ Von Python gesetzt
    ]
}

Status: Funktioniert perfekt als Fallback-Mechanismus

Design-Vorteil:

  • Webhook-Ausfall? Cron findet alle pending_sync und dirty Entities
  • Temporäre Fehler? Cron retried alle failed Entities mit Backoff
  • Robustes System mit Defense in Depth

Query 2: Auto-Reset für permanently_failed

{'type': 'equals', 'attribute': 'syncStatus', 'value': 'permanently_failed'}
# + syncAutoResetAt < now

Status: Funktioniert perfekt

Query 3: Periodic Check für clean Entities

{'type': 'equals', 'attribute': 'syncStatus', 'value': 'clean'}
# + advowareLastSync > 24 Stunden alt

Status: Funktioniert als zusätzliche Sicherheitsebene


EspoCRM Integration Requirements

Damit das System vollständig funktioniert, muss EspoCRM folgende Status setzen:

1. Bei Entity Creation (beforeSave/afterSave Hook)

// EspoCRM: CBeteiligte Entity Hook
entity.set('syncStatus', 'pending_sync');

2. Bei Entity Update (beforeSave Hook)

// EspoCRM: CBeteiligte Entity Hook
if (!entity.isNew() && entity.get('syncStatus') === 'clean') {
    // Prüfe ob sync-relevante Felder geändert wurden
    const syncRelevantFields = ['name', 'vorname', 'anrede', 'geburtsdatum', 
                                'rechtsform', 'strasse', 'plz', 'ort',
                                'emailAddressData', 'phoneNumberData'];
    
    const hasChanges = syncRelevantFields.some(field => entity.isAttributeChanged(field));
    
    if (hasChanges) {
        entity.set('syncStatus', 'dirty');
    }
}

3. Entity Definition (entityDefs/CBeteiligte.json)

{
    "fields": {
        "syncStatus": {
            "type": "enum",
            "options": [
                "pending_sync",
                "dirty", 
                "syncing",
                "clean",
                "failed",
                "permanently_failed",
                "conflict",
                "deleted_in_advoware"
            ],
            "default": "pending_sync",
            "required": true,
            "readOnly": true
        }
    }
}

System-Architektur: Vollständiger Flow

Szenario 1: CREATE (Happy Path mit Webhook)

1. User erstellt CBeteiligte in EspoCRM
2. EspoCRM Hook setzt syncStatus = 'pending_sync'
3. EspoCRM Webhook triggert vmh.beteiligte.create Event
4. Python Event-Handler:
   - acquire_lock() → syncStatus = 'syncing'
   - handle_create() → POST zu Advoware
   - release_lock() → syncStatus = 'clean'
5. ✅ Erfolgreich synchronisiert

Szenario 2: CREATE (Webhook failed → Cron Fallback)

1. User erstellt CBeteiligte in EspoCRM
2. EspoCRM Hook setzt syncStatus = 'pending_sync'
3. ❌ Webhook Service down/failed
4. 15 Minuten später: Cron läuft
5. Cron Query findet Entity via syncStatus = 'pending_sync'
6. Cron emittiert vmh.beteiligte.sync_check Event
7. Python Event-Handler wie in Szenario 1
8. ✅ Erfolgreich synchronisiert (mit Verzögerung)

Szenario 3: UPDATE (Happy Path mit Webhook)

1. User ändert CBeteiligte in EspoCRM
2. EspoCRM Hook setzt syncStatus = 'dirty' (war vorher 'clean')
3. EspoCRM Webhook triggert vmh.beteiligte.update Event
4. Python Event-Handler:
   - acquire_lock() → syncStatus = 'syncing'
   - handle_update() → Sync-Logik
   - release_lock() → syncStatus = 'clean'
5. ✅ Erfolgreich synchronisiert

Szenario 4: Sync-Fehler mit Retry

1-3. Wie Szenario 1/3
4. Python Event-Handler:
   - acquire_lock() → syncStatus = 'syncing'
   - handle_xxx() → ❌ Exception
   - release_lock(increment_retry=True) → syncStatus = 'failed', syncNextRetry = now + backoff
5. Cron findet Entity via syncStatus = 'failed'
6. Prüft syncNextRetry → noch nicht erreicht → skip
7. Nach Backoff-Zeit: Retry
8. Erfolgreich → syncStatus = 'clean'
   ODER nach 5 Retries → syncStatus = 'permanently_failed'

Empfehlungen

Status-Design ist korrekt

Das aktuelle Design mit 8 Status ist optimal für:

  • Defense in Depth (Webhook + Cron Fallback)
  • Robustheit bei Webhook-Ausfall
  • Retry-Mechanismus mit Exponential Backoff
  • Soft-Delete Strategie
  • UI-Visibility

🔵 EspoCRM Implementation erforderlich

CRITICAL: EspoCRM muss folgende Status setzen:

  1. pending_sync bei CREATE
  2. dirty bei UPDATE (nur wenn vorher clean)
  3. Default-Wert in Entity Definition

Implementation: EspoCRM Hooks in CBeteiligte Entity

🟡 Optional: Conflict Status

Current: Auto-Resolve funktioniert Enhancement: Intermediate conflict Status für UI-Visibility und Admin-Review


Zusammenfassung

Status-Verteilung

EspoCRM Verantwortung (2 Status):

  • pending_sync - Bei CREATE
  • dirty - Bei UPDATE

Python Verantwortung (6 Status):

  • syncing - Lock während Sync
  • clean - Erfolgreich gesynct
  • failed - Retry nötig
  • permanently_failed - Max retries erreicht
  • deleted_in_advoware - 404 von Advoware
  • ⚠️ conflict - Optional für UI-Visibility

System-Qualität

Architektur: (5/5) - Defense in Depth Design Robustheit: (5/5) - Funktioniert auch bei Webhook-Ausfall Status-Design: (5/5) - Alle Status sinnvoll und notwendig

Einzige Requirement: EspoCRM muss pending_sync und dirty setzen


Review erstellt von: GitHub Copilot
Review-Datum: 8. Februar 2026 (Updated)
Status: Design validiert, EspoCRM Integration dokumentiert