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.
This commit is contained in:
2026-02-07 15:21:16 +00:00
parent e6ab22d5f4
commit b5abe6cf00
7 changed files with 2430 additions and 33 deletions

View File

@@ -0,0 +1,326 @@
# 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**:
```python
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 betnr** → `handle_create()` - Neu in Advoware
- **Hat betnr** → `handle_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
- `conflict`**EspoCRM 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**:
```python
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
- [x] Mapper Import
- [x] Sync Utils Import
- [x] Event Step Config Load
- [x] Cron Step Config Load
- [x] Mapper Transform Person
- [x] 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**:
```bash
# 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)
```bash
# 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**:
```javascript
GET /api/v1/CBeteiligte?where=[
{type: 'in', attribute: 'syncStatus',
value: ['pending_sync', 'dirty', 'failed']}
]
```
**Konflikte**:
```javascript
GET /api/v1/CBeteiligte?where=[
{type: 'equals', attribute: 'syncStatus', value: 'conflict'}
]
```
**Soft-Deletes**:
```javascript
GET /api/v1/CBeteiligte?where=[
{type: 'equals', attribute: 'syncStatus', value: 'deleted_in_advoware'}
]
```
**Sync-Fehler**:
```javascript
GET /api/v1/CBeteiligte?where=[
{type: 'isNotNull', attribute: 'syncErrorMessage'}
]
```
### Motia Logs
```bash
# 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:
```python
PUT /api/v1/CBeteiligte/{id}
{"syncStatus": "failed", "syncErrorMessage": "Manual reset"}
```
### Problem: Notifications werden nicht angezeigt
**Ursache**: userId fehlt oder falsch
**Check**:
```python
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!**