From 709456301c4fbe3e664e0c6c68109fc3addd098d Mon Sep 17 00:00:00 2001 From: bitbylaw Date: Sat, 7 Feb 2026 22:38:53 +0000 Subject: [PATCH] feat: Optimize initial sync logic and remove redundant rowId updates in sync process --- bitbylaw/docs/BETEILIGTE_SYNC_ANALYSIS.md | 458 ++++++++++++++++++ bitbylaw/services/beteiligte_sync_utils.py | 7 +- .../steps/vmh/beteiligte_sync_event_step.py | 39 +- 3 files changed, 466 insertions(+), 38 deletions(-) create mode 100644 bitbylaw/docs/BETEILIGTE_SYNC_ANALYSIS.md diff --git a/bitbylaw/docs/BETEILIGTE_SYNC_ANALYSIS.md b/bitbylaw/docs/BETEILIGTE_SYNC_ANALYSIS.md new file mode 100644 index 00000000..6048b922 --- /dev/null +++ b/bitbylaw/docs/BETEILIGTE_SYNC_ANALYSIS.md @@ -0,0 +1,458 @@ +# Beteiligte Sync - Architektur-Analyse + +**Stand:** 7. Februar 2026 +**Analysiert:** Bidirektionale EspoCRM ↔ Advoware Beteiligte-Synchronisation + +--- + +## πŸ—οΈ ARCHITEKTUR-ÜBERSICHT + +### Komponenten + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ EspoCRM (Master) β”‚ +β”‚ Webhooks β†’ Motia β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ WEBHOOK HANDLER (3 Endpoints) β”‚ +β”‚ β€’ beteiligte_create_api_step.py β”‚ +β”‚ β€’ beteiligte_update_api_step.py ← Loop-Prevention entfernt β”‚ +β”‚ β€’ beteiligte_delete_api_step.py β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ emits events + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ CENTRAL SYNC HANDLER (Event-Based) β”‚ +β”‚ beteiligte_sync_event_step.py (~329 lines) β”‚ +β”‚ β”‚ +β”‚ Subscribes: vmh.beteiligte.{create,update,delete,sync_check} β”‚ +β”‚ β”‚ +β”‚ Flow: β”‚ +β”‚ 1. Distributed Lock (Redis + syncStatus) β”‚ +β”‚ 2. Fetch EspoCRM Entity β”‚ +β”‚ 3. Route: CREATE, UPDATE/CHECK, DELETE β”‚ +β”‚ 4. Release Lock + Update Status β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β–Ό β–Ό + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ handle_create β”‚ β”‚ handle_update β”‚ + β”‚ β”‚ β”‚ β”‚ + β”‚ β€’ Map to Advo β”‚ β”‚ β€’ Fetch Advo β”‚ + β”‚ β€’ POST β”‚ β”‚ β€’ Compare β”‚ + β”‚ β€’ GET rowId β”‚ β”‚ β€’ Sync/Skip β”‚ + β”‚ β€’ Write back β”‚ β”‚ β€’ Update rowId β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ SUPPORT SERVICES β”‚ +β”‚ β”‚ +β”‚ β€’ BeteiligteSync (sync_utils) (~524 lines) β”‚ +β”‚ - Locking, Compare, Merge, Conflict Resolution β”‚ +β”‚ β”‚ +β”‚ β€’ BeteiligteMapper (~200 lines) β”‚ +β”‚ - EspoCRM ↔ Advoware transformations β”‚ +β”‚ - None-value filtering β”‚ +β”‚ - Date format conversion β”‚ +β”‚ β”‚ +β”‚ β€’ AdvowareAPI / EspoCRMAPI β”‚ +β”‚ - HTTP clients mit Token-Caching β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## βœ… STΓ„RKEN (Was funktioniert gut) + +### 1. **Robustheit durch Distributed Locking** +```python +# 2-stufiges Locking verhindert Race Conditions: +# 1. Redis Lock (atomic, TTL 15min) +# 2. syncStatus Update (UI visibility) +lock_key = f"sync_lock:cbeteiligte:{entity_id}" +acquired = self.redis.set(lock_key, "locked", nx=True, ex=LOCK_TTL_SECONDS) +``` +βœ… **Gut:** Verhindert parallele Syncs derselben Entity +βœ… **Gut:** TTL verhindert Deadlocks bei Crashes +βœ… **Gut:** UI-Sichtbarkeit via syncStatus + +### 2. **PrimΓ€re Change Detection: rowId** +```python +# rowId Γ€ndert sich bei JEDEM Advoware PUT β†’ sehr zuverlΓ€ssig +if espo_rowid and advo_rowid: + advo_changed = (espo_rowid != advo_rowid) + espo_changed = (espo_modified > last_sync) +``` +βœ… **Sehr gut:** rowId ist Base64, Γ€ndert sich immer, keine NULLs +βœ… **Gut:** Timestamp als Fallback vorhanden +βœ… **Gut:** Konfliktlogik (beide geΓ€ndert) implementiert + +### 3. **API-Call-Optimierung (50% Reduktion)** +```python +# VORHER: PUT + GET (2 Calls) +# NACHHER: PUT Response enthΓ€lt neue rowId (1 Call) +put_result = await advoware.api_call(...) +new_rowid = put_result[0].get('rowId') # direkt aus Response! +``` +βœ… **Exzellent:** Keine extra GETs nach PUT nΓΆtig +βœ… **Gut:** Funktioniert fΓΌr CREATE, UPDATE, Conflict Resolution + +### 4. **Loop-Prevention auf EspoCRM-Seite** +```python +# ENTFERNT: should_skip_update() Filterung +# NEU: EspoCRM triggert keine Webhooks fΓΌr rowId-Updates +``` +βœ… **Gut:** Vereinfacht Code erheblich +βœ… **Gut:** Keine komplexe Filterlogik mehr nΓΆtig +βœ… **Gut:** Vertraut auf EspoCRM-Konfiguration + +### 5. **Mapper mit Validierung** +```python +# None-Filtering verhindert EspoCRM Validation Errors +espo_data = {k: v for k, v in espo_data.items() if v is not None} + +# Datumsformat-Konversion +dateOfBirth = geburtsdatum.split('T')[0] # '2001-01-05T00:00:00' β†’ '2001-01-05' +``` +βœ… **Gut:** Robuste Fehlerbehandlung +βœ… **Gut:** Dokumentiert welche Felder funktionieren (nur 8 von 14) + +### 6. **Error Handling mit Retry-Logik** +```python +MAX_SYNC_RETRIES = 5 +if new_retry >= MAX_SYNC_RETRIES: + update_data['syncStatus'] = 'permanently_failed' + await self.send_notification(...) +``` +βœ… **Gut:** Verhindert Endlos-Retries +βœ… **Gut:** Notification an User bei dauerhaftem Fehler + +--- + +## πŸ”΄ SCHWΓ„CHEN & VERBESSERUNGSPOTENZIALE + +### 1. **CREATE-Flow ineffizient (Extra GET nach POST)** + +**Problem:** +```python +# Nach POST: Extra GET nur fΓΌr rowId +result = await advoware.api_call(..., method='POST', data=advo_data) +new_betnr = result.get('betNr') + +# Extra GET! +created_entity = await advoware.api_call(f'.../{new_betnr}', method='GET') +new_rowid = created_entity.get('rowId') +``` + +**LΓΆsung:** +```python +# POST Response sollte rowId bereits enthalten (prΓΌfen!) +# Falls ja: Extrahiere direkt wie bei PUT +if isinstance(result, dict): + new_rowid = result.get('rowId') +elif isinstance(result, list): + new_rowid = result[0].get('rowId') +``` +⚠️ **TODO:** Teste ob Advoware POST auch rowId zurΓΌckgibt (wie PUT) + +--- + +### 2. **Doppeltes rowId-Update nach EspoCRMβ†’Advoware** + +**Problem:** +```python +# 1. Update via release_sync_lock extra_fields +await sync_utils.release_sync_lock(entity_id, 'clean', + extra_fields={'advowareRowId': new_rowid}) + +# 2. Aber VORHER bereits direktes Update! +if new_rowid: + await espocrm.update_entity('CBeteiligte', entity_id, { + 'advowareRowId': new_rowid + }) +``` + +**LΓΆsung:** Entweder/oder - nicht beides! +```python +# OPTION A: Nur release_lock (weniger Code, eleganter) +await sync_utils.release_sync_lock( + entity_id, 'clean', + extra_fields={'advowareRowId': new_rowid} +) + +# OPTION B: Direktes Update + release ohne extra_fields +await espocrm.update_entity('CBeteiligte', entity_id, { + 'advowareRowId': new_rowid, + 'syncStatus': 'clean', + 'advowareLastSync': now() +}) +await self.redis.delete(lock_key) +``` +⚠️ **Recommendation:** Option A ist eleganter (1 API Call statt 2) + +--- + +### 3. **Initial Sync Special Case nicht nΓΆtig?** + +**Problem:** +```python +# Separate Logik fΓΌr "kein lastSync" +if not espo_entity.get('advowareLastSync'): + context.logger.info(f"πŸ“€ Initial Sync β†’ ...") + # ... exakt derselbe Code wie bei espocrm_newer! +``` + +**LΓΆsung:** compare_entities sollte das automatisch erkennen +```python +# In compare_entities: +if not last_sync: + # Kein lastSync β†’ EspoCRM neuer (always sync on first run) + return 'espocrm_newer' +``` +βœ… **Eliminiert:** ~30 Zeilen Duplikat-Code in event_step.py + +--- + +### 4. **Conflict Resolution immer EspoCRM Wins** + +**Problem:** +```python +# Hardcoded: EspoCRM gewinnt immer +elif comparison == 'conflict': + context.logger.warn(f"⚠️ KONFLIKT erkannt β†’ EspoCRM WINS") + # ... force update zu Advoware +``` + +**Überlegungen:** +- FΓΌr **STAMMDATEN** ist das sinnvoll (EspoCRM ist Master) +- FΓΌr **Kontaktdaten** kΓΆnnte Advoware Master sein +- FΓΌr **Adresse** sollte vielleicht Merge stattfinden + +βœ… **Status:** OK fΓΌr aktuelle Scope (nur Stammdaten) +πŸ“ **SpΓ€ter:** Konfigurierbare Conflict Strategy pro Feld-Gruppe + +--- + +### 5. **Timestamp-Fallback verwendet geaendertAm (deprecated?)** + +**Code:** +```python +return self.compare_timestamps( + espo_entity.get('modifiedAt'), + advo_entity.get('geaendertAm'), # ← Swagger deprecated? + espo_entity.get('advowareLastSync') +) +``` + +⚠️ **TODO:** PrΓΌfe ob `geaendertAm` zuverlΓ€ssig ist oder ob Advoware ein anderes Feld hat + +--- + +### 6. **Keine Batch-Verarbeitung fΓΌr Webhooks** + +**Problem:** +```python +# Webhook-Handler: Emittiert Event pro Entity +for entity_id in entity_ids: + await context.emit({...}) # N Events +``` + +**Resultat:** Bei 100 Updates β†’ 100 separate Event-Handler-Invocations + +**LΓΆsung (Optional):** +```python +# Batch-Event mit allen IDs +await context.emit({ + 'topic': 'vmh.beteiligte.update_batch', + 'data': { + 'entity_ids': list(entity_ids), # Alle auf einmal + 'source': 'webhook' + } +}) + +# Handler verarbeitet in Parallel (mit Limit) +async def handler_batch(event_data, context): + entity_ids = event_data['entity_ids'] + + # Process max 10 parallel + semaphore = asyncio.Semaphore(10) + tasks = [sync_with_semaphore(id, semaphore) for id in entity_ids] + await asyncio.gather(*tasks) +``` +πŸ“ **Entscheidung:** Aktuell OK (Lock verhindert Probleme), aber bei >50 gleichzeitigen Updates kΓΆnnte Batch helfen + +--- + +### 7. **Fehlende Metriken/Monitoring** + +**Was fehlt:** +- Durchschnittliche Sync-Dauer pro Entity +- Anzahl Konflikte pro Tag +- API-Call-Count (EspoCRM vs Advoware) +- Failed Sync Ratio + +**LΓΆsung:** +```python +# In sync_utils oder neues monitoring_utils.py +class SyncMetrics: + async def record_sync(self, entity_id, duration, result, comparison): + await redis.hincrby('metrics:sync:daily', 'total', 1) + await redis.hincrby('metrics:sync:daily', f'result_{result}', 1) + await redis.lpush('metrics:sync:durations', duration) +``` + +--- + +## 🎯 VERBESSERUNGS-EMPFEHLUNGEN + +### PrioritΓ€t 1: SOFORT (Effizienz) + +1. **βœ… Eliminiere doppeltes rowId-Update** + ```python + # NUR in release_sync_lock, nicht vorher extra Update + ``` + Impact: -1 API Call pro EspoCRMβ†’Advoware Update (ca. 50% weniger EspoCRM calls) + +2. **βœ… Teste POST Response fΓΌr rowId** + ```python + # Falls POST auch rowId enthΓ€lt: Extra GET entfernen + ``` + Impact: -1 API Call pro CREATE (50% weniger bei Neuanlagen) + +### PrioritΓ€t 2: MITTELFRISTIG (Eleganz) + +3. **πŸ“ Merge Initial Sync in compare_entities** + ```python + # Eliminiert Special Case, -30 Zeilen + ``` + Impact: Cleaner Code, leichter wartbar + +4. **πŸ“ PrΓΌfe geaendertAm Timestamp** + ```python + # Stelle sicher dass Fallback funktioniert + ``` + Impact: Robustheit falls rowId mal fehlt + +### PrioritΓ€t 3: OPTIONAL (Features) + +5. **πŸ’‘ Batch-Processing fΓΌr Webhooks** + - Bei >50 gleichzeitigen Updates kΓΆnnte Performance leiden + - Aktuell nicht kritisch (Lock verhindert Probleme) + +6. **πŸ’‘ Metriken/Dashboard** + - Sync-Statistiken fΓΌr Monitoring + - Nicht kritisch aber nΓΌtzlich fΓΌr Ops + +--- + +## πŸ“Š PERFORMANCE-SCHΓ„TZUNG + +### Aktueller Stand (pro Entity) + +**CREATE:** +- 1Γ— EspoCRM GET (Entity laden) +- 1Γ— Advoware POST +- 1Γ— Advoware GET (rowId holen) ← **OPTIMIERBAR** +- 1Γ— EspoCRM PUT (betNr + rowId schreiben) ← **OPTIMIERBAR** +- 1Γ— EspoCRM PUT (syncStatus + lastSync) ← Teil von Lock-Release + +**Total: 5 API Calls** β†’ Mit Optimierung: **3 API Calls (-40%)** + +**UPDATE (EspoCRMβ†’Advoware):** +- 1Γ— EspoCRM GET (Entity laden) +- 1Γ— Advoware GET (Vergleich) +- 1Γ— Advoware PUT +- 1Γ— EspoCRM PUT (rowId update) ← **DOPPELT** +- 1Γ— EspoCRM PUT (Lock-Release mit rowId) ← **DOPPELT** + +**Total: 5 API Calls** β†’ Mit Optimierung: **4 API Calls (-20%)** + +**UPDATE (Advowareβ†’EspoCRM):** +- 1Γ— EspoCRM GET +- 1Γ— Advoware GET +- 1Γ— EspoCRM PUT (Daten) +- 1Γ— EspoCRM PUT (Lock-Release) + +**Total: 4 API Calls** β†’ Bereits optimal + +--- + +## 🎨 ARCHITEKTUR-BEWERTUNG + +### βœ… Was ist ROBUST? + +1. **Distributed Locking** - Verhindert Race Conditions +2. **rowId Change Detection** - Sehr zuverlΓ€ssig +3. **Retry Logic** - Graceful Degradation +4. **Error Handling** - Try-catch auf allen Ebenen +5. **TTL auf Locks** - Keine Deadlocks + +### βœ… Was ist EFFIZIENT? + +1. **PUT Response Parsing** - Spart GET nach Updates +2. **None-Filtering** - Verhindert unnΓΆtige Validierungsfehler +3. **Early Returns** - "no_change" skipped sofort +4. **Redis Token Caching** - Nicht bei jedem Call neu authentifizieren + +### βœ… Was ist ELEGANT? + +1. **Event-Driven Architecture** - Entkoppelt Webhook von Sync-Logik +2. **Mapper Pattern** - Transformationen zentral +3. **Utility Class** - Wiederverwendbare Funktionen +4. **Descriptive Logging** - Mit Emojis, sehr lesbar + +### ⚠️ Was kΓΆnnte ELEGANTER sein? + +1. **Doppelte rowId-Updates** - Redundant +2. **Initial Sync Special Case** - UnnΓΆtige Duplikation +3. **Keine Config fΓΌr Conflict Strategy** - Hardcoded +4. **Fehlende Metriken** - Monitoring schwierig + +--- + +## πŸ† GESAMTBEWERTUNG + +| Kategorie | Bewertung | Note | +|-----------|-----------|------| +| Robustheit | ⭐⭐⭐⭐⭐ | 9/10 - Sehr stabil durch Locking + Retry | +| Effizienz | β­β­β­β­β˜† | 7/10 - Gut, aber 2 klare Optimierungen mΓΆglich | +| Eleganz | β­β­β­β­β˜† | 8/10 - Sauber strukturiert, kleine Code-Duplikate | +| Wartbarkeit | ⭐⭐⭐⭐⭐ | 9/10 - Gut dokumentiert, klare Struktur | +| Erweiterbarkeit | β­β­β­β­β˜† | 8/10 - Event-Driven macht Extensions einfach | + +**Gesamt: 8.2/10 - SEHR GUT** + +--- + +## πŸš€ EMPFOHLENE NΓ„CHSTE SCHRITTE + +### Sofort (1-2h Aufwand): +1. βœ… Eliminiere doppeltes rowId-Update (5min) +2. βœ… Teste Advoware POST Response auf rowId (15min) +3. βœ… Falls ja: Entferne GET nach CREATE (5min) + +### Mittelfristig (2-4h): +4. πŸ“ Merge Initial Sync in compare_entities (30min) +5. πŸ“ Add Metrics Collection (1-2h) + +### Optional: +6. πŸ’‘ Batch-Processing (nur wenn Performance-Problem) +7. πŸ’‘ Configurable Conflict Strategy (bei neuen Requirements) + +--- + +## πŸ“ FAZIT + +**Das System ist produktionsreif und robust.** + +- **StΓ€rken:** Exzellentes Locking, zuverlΓ€ssige Change Detection, gutes Error Handling +- **Optimierungen:** 2-3 kleine Fixes kΓΆnnen 20-40% API Calls sparen +- **Architektur:** Sauber, wartbar, erweiterbar + +**Recommendation:** Ship it mit den 2 Quick-Fixes (doppeltes Update + POST rowId-Check). diff --git a/bitbylaw/services/beteiligte_sync_utils.py b/bitbylaw/services/beteiligte_sync_utils.py index 6e5f699c..b8b3578c 100644 --- a/bitbylaw/services/beteiligte_sync_utils.py +++ b/bitbylaw/services/beteiligte_sync_utils.py @@ -244,7 +244,12 @@ class BeteiligteSync: last_sync = espo_entity.get('advowareLastSync') espo_modified = espo_entity.get('modifiedAt') - if espo_rowid and advo_rowid and last_sync: + # SPECIAL CASE: Kein lastSync β†’ Initial Sync (EspoCRMβ†’Advoware) + if not last_sync: + self._log(f"Initial Sync (kein lastSync) β†’ EspoCRM neuer") + return 'espocrm_newer' + + if espo_rowid and advo_rowid: # PrΓΌfe ob Advoware geΓ€ndert wurde (rowId) advo_changed = (espo_rowid != advo_rowid) diff --git a/bitbylaw/steps/vmh/beteiligte_sync_event_step.py b/bitbylaw/steps/vmh/beteiligte_sync_event_step.py index c45ca4fc..6c857f47 100644 --- a/bitbylaw/steps/vmh/beteiligte_sync_event_step.py +++ b/bitbylaw/steps/vmh/beteiligte_sync_event_step.py @@ -204,35 +204,6 @@ async def handle_update(entity_id, betnr, espo_entity, espocrm, advoware, sync_u context.logger.info(f"⏱️ Vergleich: {comparison}") - # SPECIAL: Wenn LastSync null β†’ immer von EspoCRM syncen (initial sync) - if not espo_entity.get('advowareLastSync'): - context.logger.info(f"πŸ“€ Initial Sync β†’ EspoCRM STAMMDATEN zu Advoware") - - # OPTIMIERT: Use merge utility (reduces code duplication) - merged_data = sync_utils.merge_for_advoware_put(advo_entity, espo_entity, mapper) - - put_result = await advoware.api_call( - f'api/v1/advonet/Beteiligte/{betnr}', - method='PUT', - data=merged_data - ) - - # Extrahiere neue rowId aus PUT Response (spart extra GET!) - new_rowid = None - if isinstance(put_result, list) and len(put_result) > 0: - new_rowid = put_result[0].get('rowId') - elif isinstance(put_result, dict): - new_rowid = put_result.get('rowId') - - # Speichere neue rowId fΓΌr zukΓΌnftige Vergleiche - await sync_utils.release_sync_lock( - entity_id, - 'clean', - extra_fields={'advowareRowId': new_rowid} - ) - context.logger.info(f"βœ… Advoware aktualisiert (initial sync), neue rowId: {new_rowid[:20] if new_rowid else 'N/A'}...") - return - # KEIN SYNC NΓ–TIG if comparison == 'no_change': context.logger.info(f"βœ… Keine Γ„nderungen, Sync ΓΌbersprungen") @@ -259,19 +230,13 @@ async def handle_update(entity_id, betnr, espo_entity, espocrm, advoware, sync_u elif isinstance(put_result, dict): new_rowid = put_result.get('rowId') - # Schreibe neue rowId zurΓΌck nach EspoCRM - if new_rowid: - await espocrm.update_entity('CBeteiligte', entity_id, { - 'advowareRowId': new_rowid - }) - context.logger.info(f"πŸ“ rowId in EspoCRM aktualisiert: {new_rowid[:20]}...") - + # Release Lock + Update rowId in einem Call (effizienter!) await sync_utils.release_sync_lock( entity_id, 'clean', extra_fields={'advowareRowId': new_rowid} ) - context.logger.info(f"βœ… Advoware aktualisiert, neue rowId: {new_rowid[:20] if new_rowid else 'N/A'}...") + context.logger.info(f"βœ… Advoware aktualisiert, rowId in EspoCRM geschrieben: {new_rowid[:20] if new_rowid else 'N/A'}...") # ADVOWARE NEUER β†’ Update EspoCRM elif comparison == 'advoware_newer':