# 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. ## Audit und Management Tool (`audit_calendar_sync.py`) Das `audit_calendar_sync.py` Tool bietet umfassende Audit-, Management- und Debugging-Funktionen für die Calendar-Synchronisation. Es ermöglicht die Überprüfung der Sync-Integrität, das Aufräumen von Duplikaten und verwaisten Einträgen sowie detaillierte Abfragen einzelner Termine. ### Verwendung ```bash cd /opt/motia-app/bitbylaw source python_modules/bin/activate python steps/advoware_cal_sync/audit_calendar_sync.py [options] ``` ### Befehle #### `audit [--delete-orphaned-google]` Auditiert Sync-Einträge für einen spezifischen Mitarbeiter und prüft deren Existenz in beiden Systemen. **Parameter:** - `employee_kuerzel`: Mitarbeiter-Kürzel (z.B. "SB", "UR") - `google|advoware`: System, das auditiert werden soll - `--delete-orphaned-google`: Optional, löscht Google-Events die in Google existieren aber nicht in der DB **Beispiel:** ```bash # Audit Google Calendar für Mitarbeiter SB python audit_calendar_sync.py audit SB google # Audit Advoware für Mitarbeiter UR mit Löschung verwaister Google-Events python audit_calendar_sync.py audit UR google --delete-orphaned-google ``` **Ausgabe:** - Anzahl der DB-Einträge - Anzahl der Events im Zielsystem - Anzahl existierender/verwaiste Einträge - Details zu verwaisten Einträgen #### `delete-calendar ` Löscht den Google Calendar für einen spezifischen Mitarbeiter (falls vorhanden). **Beispiel:** ```bash python audit_calendar_sync.py delete-calendar SB ``` #### `list-all` Listet alle Google Calendars auf, einschließlich Name, ID, Primary-Status und Access-Role. **Beispiel:** ```bash python audit_calendar_sync.py list-all ``` **Ausgabe:** ``` === All Google Calendars (27) === AW-SB (ID: abc123@group.calendar.google.com, Primary: False, Access: owner) AW-UR (ID: def456@group.calendar.google.com, Primary: False, Access: owner) ... ``` #### `find-duplicates` Findet duplizierte Google Calendars nach Namen. **Beispiel:** ```bash python audit_calendar_sync.py find-duplicates ``` **Ausgabe:** ``` === Duplicate Calendars Found (2 unique names with duplicates) === Total duplicate calendars: 3 Calendar Name: 'AW-SB' - 2 instances ID: abc123@group.calendar.google.com, Primary: False, Access Role: owner ID: xyz789@group.calendar.google.com, Primary: False, Access Role: owner ``` #### `delete-duplicates` Findet und löscht duplizierte Calendars (behält jeweils einen pro Namen). **Beispiel:** ```bash python audit_calendar_sync.py delete-duplicates ``` #### `find-orphaned` Findet AW-* Calendars ohne entsprechende Mitarbeiter in der Datenbank. **Beispiel:** ```bash python audit_calendar_sync.py find-orphaned ``` #### `cleanup-orphaned` Findet und löscht verwaiste AW-* Calendars. **Beispiel:** ```bash python audit_calendar_sync.py cleanup-orphaned ``` #### `query-frnr ` Zeigt alle Sync-Informationen für eine spezifische Advoware frNr. **Beispiel:** ```bash python audit_calendar_sync.py query-frnr 79291 ``` **Ausgabe:** ``` === Sync Information for frNr: 79291 === Found 1 sync entry Sync ID: 6ee9ba95-8aff-4868-9171-c10a8789427c Employee: UR Advoware frNr: 79291 Google Event ID: jao7r00j26lt1i0chk454bi9as Source System: advoware Sync Strategy: source_system_wins Sync Status: synced Last Sync: 2025-10-24 23:30:17.692668+00:00 Created: 2025-10-24 07:22:41.729295+00:00 Updated: 2025-10-24 07:22:41.729295+00:00 ``` #### `query-event ` Zeigt Sync-Informationen für eine spezifische Google Event ID. **Beispiel:** ```bash python audit_calendar_sync.py query-event jao7r00j26lt1i0chk454bi9as ``` #### `verify-sync ` Vollständige Sync-Verifikation: Prüft einen Termin in beiden Systemen (Advoware und Google Calendar). **Beispiel:** ```bash python audit_calendar_sync.py verify-sync 79291 ``` **Ausgabe:** ``` === Sync Verification for frNr: 79291 === Employee: UR Sync Status: synced Last Sync: 2025-10-24 23:30:17.692668+00:00 --- Checking Advoware --- ✅ Found in Advoware: Subject: Jour fixe iS Neomi - Teilnahme im Einzelfall Date: 2024-06-04T17:00:00 Time: N/A End Time: 19:00:00 End Date: 2026-02-03T00:00:00 Last Modified: 2025-09-29T11:55:43.624 frNr: 79291 --- Checking Google Calendar --- ✅ Found in Google Calendar: Summary: Advoware (frNr: 79291) Start: 2024-06-04T17:00:00+02:00 End: 2024-06-04T19:00:00+02:00 --- Sync Status Summary --- ✅ Synchronized: Exists in both systems ``` ### Technische Details #### Datenbank-Integration - Verwendet PostgreSQL-Verbindung aus `config.py` - Tabelle: `calendar_sync` - Felder: `sync_id`, `employee_kuerzel`, `advoware_frnr`, `google_event_id`, etc. #### API-Integration - **Google Calendar API**: `calendarList().list()` mit Paginierung (maxResults=250) - **Advoware API**: `GET /api/v1/advonet/Termine` mit `frnr` Filter - Automatische Token-Verwaltung und Fehlerbehandlung #### Sicherheit - Verwendet bestehende Service-Account und API-Credentials - Keine zusätzlichen Berechtigungen erforderlich ### Häufige Anwendungsfälle #### 1. Nach der Erstinstallation ```bash # Alle Calendars auflisten python audit_calendar_sync.py list-all # Duplikate finden und entfernen python audit_calendar_sync.py find-duplicates python audit_calendar_sync.py delete-duplicates # Verwaiste Calendars entfernen python audit_calendar_sync.py find-orphaned python audit_calendar_sync.py cleanup-orphaned ``` #### 2. Bei Sync-Problemen ```bash # Sync-Status für einen Mitarbeiter prüfen python audit_calendar_sync.py audit SB google # Einzelnen Termin verifizieren python audit_calendar_sync.py verify-sync 79291 # Sync-Informationen abfragen python audit_calendar_sync.py query-frnr 79291 ``` #### 3. Regelmäßige Wartung ```bash # Wöchentliche Überprüfung auf Duplikate python audit_calendar_sync.py find-duplicates # Monatliche Bereinigung verwaister Einträge python audit_calendar_sync.py cleanup-orphaned ``` ### Fehlerbehandlung - **API-Fehler**: Automatische Retry-Logik mit Backoff - **Berechtigungsfehler**: Klare Fehlermeldungen mit Lösungsvorschlägen - **Netzwerkprobleme**: Timeout-Handling und Wiederholungen - **Dateninkonsistenzen**: Detaillierte Logging für Debugging ### Performance - **Paginierung**: Automatische Handhabung großer Resultsets - **Batch-Verarbeitung**: Effiziente API-Calls mit minimalen Requests - **Caching**: Wiederverwendung von API-Verbindungen wo möglich ### Logging Alle Operationen werden über `context.logger` geloggt und sind in der Motia Workbench sichtbar. Zusätzliche Debug-Informationen werden auf der Konsole ausgegeben.