- Add detailed documentation in README.md for all 10 audit commands - Fix Advoware API parameter from 'frNr' to 'frnr' for proper filtering - Fix subject field mapping from 'betreff' to 'text' in API responses - Add verify-sync command for bidirectional sync verification - Add query-frnr and query-event commands for individual record lookup - Add management commands: find-duplicates, delete-duplicates, find-orphaned, cleanup-orphaned - Improve error handling and output formatting - Remove temporary test script
431 lines
12 KiB
Markdown
431 lines
12 KiB
Markdown
# 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 <command> [options]
|
|
```
|
|
|
|
### Befehle
|
|
|
|
#### `audit <employee_kuerzel> <google|advoware> [--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 <employee_kuerzel>`
|
|
|
|
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 <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 <event_id>`
|
|
|
|
Zeigt Sync-Informationen für eine spezifische Google Event ID.
|
|
|
|
**Beispiel:**
|
|
```bash
|
|
python audit_calendar_sync.py query-event jao7r00j26lt1i0chk454bi9as
|
|
```
|
|
|
|
#### `verify-sync <frnr>`
|
|
|
|
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.
|
|
|