# Advoware Calendar Sync - Event-Driven Design Dieser Abschnitt implementiert die bidirektionale Synchronisation zwischen Advoware-Terminen und Google Calendar. Das System nutzt einen event-driven Ansatz mit **Motia III v1.0-RC**, der auf direkten API-Calls basiert, mit Redis für Locking und Deduplikation. Es stellt sicher, dass Termine konsistent gehalten werden, mit Fokus auf Robustheit, Fehlerbehandlung und korrekte Handhabung von mehrtägigen Terminen. ## Übersicht Das System synchronisiert Termine zwischen: - **Advoware**: Zentrale Terminverwaltung mit detaillierten Informationen. - **Google Calendar**: Benutzerfreundliche Kalenderansicht für jeden Mitarbeiter. ## Architektur ### Event-Driven Design - **Direkte API-Synchronisation**: Kein zentraler Hub; Sync läuft direkt zwischen APIs. - **Redis Locking**: Per-Employee Locking verhindert Race-Conditions. - **Event Emission**: Cron → All-Step → Employee-Step für skalierbare Verarbeitung. - **Fehlerresistenz**: Einzelne Fehler stoppen nicht den gesamten Sync. - **Logging**: Alle Logs erscheinen in der iii Console via `ctx.logger`. ### Sync-Phasen 1. **Cron-Step**: Automatische Auslösung alle 15 Minuten. 2. **All-Step**: Fetcht alle Mitarbeiter und emittiert Events pro Employee. 3. **Employee-Step**: Synchronisiert Termine für einen einzelnen Mitarbeiter. ### Datenmapping und Standardisierung Beide Systeme werden auf gemeinsames Format normalisiert (Berlin TZ): ```python { 'start': datetime, # Berlin TZ 'end': datetime, 'text': str, 'notiz': str, 'ort': str, 'dauertermin': int, # 0/1 'turnus': int, # 0/1 'turnusArt': int, 'recurrence': str # RRULE oder None } ``` #### Advoware → Standard - Start: `datum` + `uhrzeitVon` (Fallback 09:00), oder `datum` als datetime. - End: `datumBis` + `uhrzeitBis` (Fallback 10:00), oder `datum` + 1h. - All-Day: `dauertermin=1` oder Dauer >1 Tag. - Recurring: `turnus`/`turnusArt` (vereinfacht, keine RRULE). #### Google → Standard - Start/End: `dateTime` oder `date` (All-Day). - All-Day: `dauertermin=1` wenn All-Day oder Dauer >1 Tag. - Recurring: RRULE aus `recurrence`. #### Standard → Advoware - POST/PUT: `datum`/`uhrzeitBis`/`datumBis` aus start/end. - Defaults: `vorbereitungsDauer='00:00:00'`, `sb`/`anwalt`=employee_kuerzel. #### Standard → Google - All-Day: `date` statt `dateTime`, end +1 Tag. - Recurring: RRULE aus `recurrence`. ## Funktionalität ### Automatische Kalender-Erstellung - Für jeden Advoware-Mitarbeiter wird ein Google Calendar mit dem Namen `AW-{Kuerzel}` erstellt. - Beispiel: Mitarbeiter mit Kürzel "SB" → Calendar "AW-SB". - Kalender wird mit dem Haupt-Google-Account (`lehmannundpartner@gmail.com`) als Owner geteilt. ### Sync-Details #### Cron-Step (calendar_sync_cron_step.py) - Läuft alle 15 Minuten und emittiert "calendar_sync_all". - **Trigger**: `cron("0 */15 * * * *")` (6-field: Sekunde Minute Stunde Tag Monat Wochentag) #### All-Step (calendar_sync_all_step.py) - Fetcht alle Mitarbeiter aus Advoware. - Filtert Debug-Liste (falls konfiguriert). - Setzt Redis-Lock pro Employee. - Emittiert "calendar_sync" Event pro Employee. - **Trigger**: `queue('calendar_sync_all')` #### Employee-Step (calendar_sync_event_step.py) - Fetcht Advoware-Termine für den Employee. - Fetcht Google-Events für den Employee. - Synchronisiert: Neue erstellen, Updates anwenden, Deletes handhaben. - Verwendet Locking, um parallele Syncs zu verhindern. - **Trigger**: `queue('calendar_sync')` #### API-Step (calendar_sync_api_step.py) - Manueller Trigger für einzelnen Employee oder "ALL". - Bei "ALL": Emittiert "calendar_sync_all". - Bei Employee: Setzt Lock und emittiert "calendar_sync". - **Trigger**: `http('POST', '/advoware/calendar/sync')` ## API-Schwächen und Fixes ### Advoware API - **Mehrtägige Termine**: `datumBis` wird korrekt für Enddatum verwendet; '00:00:00' als '23:59:59' interpretiert. - **Zeitformate**: Robuste Parsing mit Fallbacks. - **Keine 24h-Limit**: Termine können länger als 24h sein; Google Calendar unterstützt das. ### Google Calendar API - **Zeitbereiche**: Akzeptiert Events >24h ohne Probleme. - **Rate Limits**: Backoff-Retry implementiert. ## Step-Konfiguration (Motia III) ### calendar_sync_cron_step.py ```python config = { 'name': 'Calendar Sync Cron Job', 'flows': ['advoware'], 'triggers': [ cron("0 */15 * * * *") # Alle 15 Minuten (6-field format) ], 'enqueues': ['calendar_sync_all'] } ``` ### calendar_sync_all_step.py ```python config = { 'name': 'Calendar Sync All Step', 'flows': ['advoware'], 'triggers': [ queue('calendar_sync_all') ], 'enqueues': ['calendar_sync'] } ``` ### calendar_sync_event_step.py ```python config = { 'name': 'Calendar Sync Event Step', 'flows': ['advoware'], 'triggers': [ queue('calendar_sync') ], 'enqueues': [] } ``` ### calendar_sync_api_step.py ```python config = { 'name': 'Calendar Sync API Trigger', 'flows': ['advoware'], 'triggers': [ http('POST', '/advoware/calendar/sync') ], 'enqueues': ['calendar_sync', 'calendar_sync_all'] } ``` ## Setup ### Umgebungsvariablen ```env # Google Calendar GOOGLE_CALENDAR_SERVICE_ACCOUNT_PATH=service-account.json # Advoware API ADVOWARE_API_BASE_URL=https://www2.advo-net.net:90/ ADVOWARE_PRODUCT_ID=64 ADVOWARE_APP_ID=your_app_id ADVOWARE_API_KEY=your_api_key ADVOWARE_KANZLEI=your_kanzlei ADVOWARE_DATABASE=your_database ADVOWARE_USER=your_user ADVOWARE_ROLE=2 ADVOWARE_PASSWORD=your_password ADVOWARE_TOKEN_LIFETIME_MINUTES=55 ADVOWARE_API_TIMEOUT_SECONDS=30 # Redis REDIS_HOST=localhost REDIS_PORT=6379 REDIS_DB_CALENDAR_SYNC=1 REDIS_TIMEOUT_SECONDS=5 # Debug CALENDAR_SYNC_DEBUG_EMPLOYEES=PB,AI # Optional, filter employees ``` ## Verwendung ### Manueller Sync ```bash # Sync für einen bestimmten Mitarbeiter curl -X POST "http://localhost:3111/advoware/calendar/sync" \ -H "Content-Type: application/json" \ -d '{"kuerzel": "PB"}' # Sync für alle Mitarbeiter curl -X POST "http://localhost:3111/advoware/calendar/sync" \ -H "Content-Type: application/json" \ -d '{"kuerzel": "ALL"}' ``` ### Automatischer Sync Cron-Step läuft automatisch alle 15 Minuten. ## Fehlerbehandlung und Logging - **Locking**: Redis NX/EX verhindert parallele Syncs. - **Logging**: `ctx.logger` für iii Console-Sichtbarkeit. - **API-Fehler**: Retry mit Backoff. - **Parsing-Fehler**: Robuste Fallbacks. ## Sicherheit - Service Account für Google Calendar API. - HMAC-512 Authentifizierung für Advoware API. - Redis für Concurrency-Control. ## Bekannte Probleme - **Recurring-Events**: Begrenzte Unterstützung für komplexe Wiederholungen. - **Performance**: Bei vielen Terminen Paginierung beachten. - **Timezone-Handling**: Alle Operationen in Europe/Berlin TZ. ## Datenfluss ``` Cron (alle 15min) → calendar_sync_cron_step → ctx.enqueue(topic: "calendar_sync_all") → calendar_sync_all_step → Fetch Employees from Advoware → For each Employee: → Set Redis Lock (key: calendar_sync:employee:{kuerzel}) → ctx.enqueue(topic: "calendar_sync", data: {kuerzel, ...}) → calendar_sync_event_step → Fetch Advoware Termine (frNr, datum, text, etc.) → Fetch Google Calendar Events → 4-Phase Sync: 1. New from Advoware → Google 2. New from Google → Advoware 3. Process Deletes 4. Process Updates → Clear Redis Lock ``` ## Weitere Dokumentation - **Individual Step Docs**: Siehe `docs/` Ordner in diesem Verzeichnis - **Architecture Overview**: [../../docs/ARCHITECTURE.md](../../docs/ARCHITECTURE.md) - **Google Setup Guide**: [../../docs/GOOGLE_SETUP.md](../../docs/GOOGLE_SETUP.md) - **Troubleshooting**: [../../docs/TROUBLESHOOTING.md](../../docs/TROUBLESHOOTING.md) ## Migration Notes Dieses System wurde von **Motia v0.17** nach **Motia III v1.0-RC** migriert: ### Wichtige Änderungen: - ✅ `type: 'event'` → `triggers: [queue('topic')]` - ✅ `type: 'cron'` → `triggers: [cron('expression')]` (6-field format) - ✅ `type: 'api'` → `triggers: [http('METHOD', 'path')]` - ✅ `context.emit()` → `ctx.enqueue()` - ✅ `emits: [...]` → `enqueues: [...]` - ✅ Relative Imports → Absolute Imports mit `sys.path.insert()` - ✅ Motia Workbench → iii Console ### Kompatibilität: - ✅ Alle 4 Steps vollständig migriert - ✅ Google Calendar API Integration unverändert - ✅ Advoware API Integration unverändert - ✅ Redis Locking-Mechanismus unverändert - ✅ Datenbank-Schema kompatibel