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
- Cron-Step: Automatische Auslösung alle 15 Minuten.
- All-Step: Fetcht alle Mitarbeiter und emittiert Events pro Employee.
- Employee-Step: Synchronisiert Termine für einen einzelnen Mitarbeiter.
Datenmapping und Standardisierung
Beide Systeme werden auf gemeinsames Format normalisiert (Berlin TZ):
{
'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), oderdatumals datetime. - End:
datumBis+uhrzeitBis(Fallback 10:00), oderdatum+ 1h. - All-Day:
dauertermin=1oder Dauer >1 Tag. - Recurring:
turnus/turnusArt(vereinfacht, keine RRULE).
Google → Standard
- Start/End:
dateTimeoderdate(All-Day). - All-Day:
dauertermin=1wenn All-Day oder Dauer >1 Tag. - Recurring: RRULE aus
recurrence.
Standard → Advoware
- POST/PUT:
datum/uhrzeitBis/datumBisaus start/end. - Defaults:
vorbereitungsDauer='00:00:00',sb/anwalt=employee_kuerzel.
Standard → Google
- All-Day:
datestattdateTime, 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:
datumBiswird 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
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
config = {
'name': 'Calendar Sync All Step',
'flows': ['advoware'],
'triggers': [
queue('calendar_sync_all')
],
'enqueues': ['calendar_sync']
}
calendar_sync_event_step.py
config = {
'name': 'Calendar Sync Event Step',
'flows': ['advoware'],
'triggers': [
queue('calendar_sync')
],
'enqueues': []
}
calendar_sync_api_step.py
config = {
'name': 'Calendar Sync API Trigger',
'flows': ['advoware'],
'triggers': [
http('POST', '/advoware/calendar/sync')
],
'enqueues': ['calendar_sync', 'calendar_sync_all']
}
Setup
Umgebungsvariablen
# 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
# 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.loggerfü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
- Google Setup Guide: ../../docs/GOOGLE_SETUP.md
- Troubleshooting: ../../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