# Advoware Calendar Sync - Event-Driven Design Dieser Abschnitt implementiert die bidirektionale Synchronisation zwischen Advoware-Terminen und Google Calendar. Das System wurde zu einem einfachen, event-driven Ansatz refaktoriert, 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 im Motia Workbench via context.logger. ### Sync-Phasen 1. **Cron-Step**: Tägliche Auslösung des Syncs. 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 täglich und emittiert "calendar_sync_all". #### 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_employee" pro Employee. #### 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. ### 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_employee". ## 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 ### calendar_sync_cron_step.py - **Type:** cron - **Flows:** advoware-calendar-sync ### calendar_sync_all_step.py - **Type:** event - **Subscribes:** calendar_sync_all - **Flows:** advoware-calendar-sync ### calendar_sync_event_step.py - **Type:** event - **Subscribes:** calendar_sync_employee - **Flows:** advoware-calendar-sync ### calendar_sync_api_step.py - **Type:** api - **Flows:** advoware-calendar-sync ## 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 curl -X POST "http://localhost:3000/advoware/calendar/sync" \ -H "Content-Type: application/json" \ -d '{"kuerzel": "PB"}' ``` ### Automatischer Sync Cron-Step läuft täglich. ## Fehlerbehandlung und Logging - **Locking**: Redis NX/EX verhindert parallele Syncs. - **Logging**: context.logger für Workbench-Sichtbarkeit. - **API-Fehler**: Retry mit Backoff. - **Parsing-Fehler**: Robuste Fallbacks. ## Sicherheit - Service Account für Google. - HMAC für Advoware. - Redis für Locking. ## Bekannte Probleme - Recurring-Events: Begrenzte Unterstützung. - Performance: Bei vielen Terminen Paginierung prüfen. ## Letzte Änderungen - Refaktorierung zu event-driven Design ohne PostgreSQL Hub. - Fixes für mehrtägige Termine: Korrekte Verwendung von `datumBis`. - Entfernung 24h-Limit; Google Calendar unterstützt lange Events. - Per-Employee Locking mit Redis. - Logging via context.logger für Workbench. - Neue Schritte: calendar_sync_all_step.py, calendar_sync_cron_step.py. - Workbench-Gruppierung: "advoware-calendar-sync".