Pre-optimization commit: Calendar sync implementation with hub design
This commit is contained in:
@@ -1,308 +0,0 @@
|
||||
from services.advoware import AdvowareAPI
|
||||
from config import Config
|
||||
from googleapiclient.discovery import build
|
||||
from google.oauth2.credentials import Credentials
|
||||
from google.auth.transport.requests import Request
|
||||
from google_auth_oauthlib.flow import InstalledAppFlow
|
||||
import json
|
||||
import datetime
|
||||
import pickle
|
||||
import os.path
|
||||
import redis
|
||||
|
||||
config = {
|
||||
'type': 'api',
|
||||
'name': 'Advoware Calendar Sync',
|
||||
'description': 'Synchronisiert Advoware Termine mit Google Calendar für alle Mitarbeiter',
|
||||
'path': '/advoware/calendar/sync',
|
||||
'method': 'POST',
|
||||
'flows': ['advoware'],
|
||||
'emits': []
|
||||
}
|
||||
|
||||
SCOPES = ['https://www.googleapis.com/auth/calendar']
|
||||
|
||||
async def get_google_service(context):
|
||||
"""Initialisiert Google Calendar API Service"""
|
||||
creds = None
|
||||
|
||||
# Token aus Datei laden falls vorhanden
|
||||
if os.path.exists('token.pickle'):
|
||||
with open('token.pickle', 'rb') as token:
|
||||
creds = pickle.load(token)
|
||||
|
||||
# Wenn keine validen Credentials, neu authentifizieren
|
||||
if not creds or not creds.valid:
|
||||
if creds and creds.expired and creds.refresh_token:
|
||||
creds.refresh(Request())
|
||||
else:
|
||||
# Hier würde normalerweise der OAuth Flow laufen
|
||||
# Für Server-Umgebung brauchen wir Service Account oder gespeicherte Credentials
|
||||
print("WARNING: Google OAuth Credentials nicht gefunden. Bitte token.pickle bereitstellen oder Google Calendar Sync überspringen.")
|
||||
return None
|
||||
|
||||
# Token speichern
|
||||
with open('token.pickle', 'wb') as token:
|
||||
pickle.dump(creds, token)
|
||||
|
||||
return build('calendar', 'v3', credentials=creds)
|
||||
|
||||
async def get_advoware_employees(context):
|
||||
"""Ruft alle Mitarbeiter von Advoware ab"""
|
||||
advoware = AdvowareAPI(context)
|
||||
try:
|
||||
# Annahme: Mitarbeiter-Endpoint existiert ähnlich wie andere
|
||||
result = await advoware.api_call('Mitarbeiter')
|
||||
print(f"Advoware Mitarbeiter abgerufen: {len(result) if isinstance(result, list) else 'unbekannt'}")
|
||||
return result if isinstance(result, list) else []
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Abrufen der Mitarbeiter: {e}")
|
||||
return []
|
||||
|
||||
async def ensure_google_calendar(service, employee_kuerzel, context):
|
||||
"""Stellt sicher, dass ein Google Calendar für den Mitarbeiter existiert"""
|
||||
calendar_name = f"AW-{employee_kuerzel}"
|
||||
|
||||
try:
|
||||
# Bestehende Kalender prüfen
|
||||
calendar_list = service.calendarList().list().execute()
|
||||
for calendar in calendar_list.get('items', []):
|
||||
if calendar['summary'] == calendar_name:
|
||||
print(f"Google Calendar '{calendar_name}' existiert bereits")
|
||||
return calendar['id']
|
||||
|
||||
# Neuen Kalender erstellen
|
||||
calendar_body = {
|
||||
'summary': calendar_name,
|
||||
'description': f'Advoware Termine für Mitarbeiter {employee_kuerzel}',
|
||||
'timeZone': 'Europe/Berlin'
|
||||
}
|
||||
|
||||
created_calendar = service.calendars().insert(body=calendar_body).execute()
|
||||
calendar_id = created_calendar['id']
|
||||
print(f"Google Calendar '{calendar_name}' erstellt mit ID: {calendar_id}")
|
||||
|
||||
return calendar_id
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fehler bei Google Calendar für {employee_kuerzel}: {e}")
|
||||
return None
|
||||
|
||||
async def get_advoware_appointments(employee_kuerzel, context):
|
||||
"""Ruft Termine eines Mitarbeiters aus Advoware ab"""
|
||||
advoware = AdvowareAPI(context)
|
||||
|
||||
# Zeitraum: aktuelles Jahr + 2 Jahre
|
||||
from_date = datetime.datetime.now().strftime('%Y-01-01T00:00:00Z')
|
||||
to_date = (datetime.datetime.now() + datetime.timedelta(days=730)).strftime('%Y-12-31T23:59:59Z')
|
||||
|
||||
try:
|
||||
params = {
|
||||
'kuerzel': employee_kuerzel,
|
||||
'from': from_date,
|
||||
'to': to_date
|
||||
}
|
||||
result = await advoware.api_call('Termine', method='GET', params=params)
|
||||
appointments = result if isinstance(result, list) else []
|
||||
print(f"Advoware Termine für {employee_kuerzel}: {len(appointments)} gefunden")
|
||||
return appointments
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Abrufen der Termine für {employee_kuerzel}: {e}")
|
||||
return []
|
||||
|
||||
async def get_google_events(service, calendar_id, context):
|
||||
"""Ruft Events aus Google Calendar ab"""
|
||||
try:
|
||||
now = datetime.datetime.utcnow()
|
||||
from_date = now.strftime('%Y-01-01T00:00:00Z')
|
||||
to_date = (now + datetime.timedelta(days=730)).strftime('%Y-12-31T23:59:59Z')
|
||||
|
||||
events_result = service.events().list(
|
||||
calendarId=calendar_id,
|
||||
timeMin=from_date,
|
||||
timeMax=to_date,
|
||||
singleEvents=True,
|
||||
orderBy='startTime'
|
||||
).execute()
|
||||
|
||||
events = events_result.get('items', [])
|
||||
print(f"Google Calendar Events: {len(events)} gefunden")
|
||||
return events
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Abrufen der Google Events: {e}")
|
||||
return []
|
||||
|
||||
async def sync_appointment_to_google(service, calendar_id, appointment, full_content, context):
|
||||
"""Synchronisiert einen Advoware-Termin zu Google Calendar"""
|
||||
try:
|
||||
# Start- und Endzeit aus Advoware-Daten
|
||||
start_date = appointment.get('datum')
|
||||
end_date = appointment.get('datumBis') or start_date
|
||||
start_time = appointment.get('uhrzeitBis', '00:00:00') # Advoware hat uhrzeitBis als Endzeit?
|
||||
end_time = appointment.get('uhrzeitBis', '23:59:59')
|
||||
|
||||
# Vollständiges Event oder nur "blocked"
|
||||
if full_content:
|
||||
summary = appointment.get('text', 'Advoware Termin')
|
||||
description = f"Advoware Termin\nNotiz: {appointment.get('notiz', '')}\nOrt: {appointment.get('ort', '')}\nRaum: {appointment.get('raum', '')}"
|
||||
location = appointment.get('ort', '')
|
||||
else:
|
||||
summary = "Blocked (Advoware)"
|
||||
description = "Termin aus Advoware"
|
||||
location = ""
|
||||
|
||||
event_body = {
|
||||
'summary': summary,
|
||||
'description': description,
|
||||
'location': location,
|
||||
'start': {
|
||||
'dateTime': f"{start_date}T{start_time}",
|
||||
'timeZone': 'Europe/Berlin',
|
||||
},
|
||||
'end': {
|
||||
'dateTime': f"{end_date}T{end_time}",
|
||||
'timeZone': 'Europe/Berlin',
|
||||
},
|
||||
'extendedProperties': {
|
||||
'private': {
|
||||
'advoware_frnr': str(appointment.get('frNr'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Event erstellen
|
||||
created_event = service.events().insert(calendarId=calendar_id, body=event_body).execute()
|
||||
print(f"Termin {appointment.get('frNr')} zu Google Calendar hinzugefügt")
|
||||
return created_event
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Sync zu Google für Termin {appointment.get('frNr')}: {e}")
|
||||
return None
|
||||
|
||||
async def sync_event_to_advoware(service, calendar_id, event, employee_kuerzel, context):
|
||||
"""Synchronisiert ein Google Event zu Advoware (falls keine frNr vorhanden)"""
|
||||
try:
|
||||
# Prüfen ob bereits eine frNr vorhanden
|
||||
extended_props = event.get('extendedProperties', {}).get('private', {})
|
||||
frnr = extended_props.get('advoware_frnr')
|
||||
|
||||
if frnr:
|
||||
# Bereits synchronisiert
|
||||
return None
|
||||
|
||||
# Neuen Termin in Advoware erstellen
|
||||
advoware = AdvowareAPI(context)
|
||||
|
||||
# Start/End aus Google Event extrahieren
|
||||
start = event.get('start', {}).get('dateTime', '')
|
||||
end = event.get('end', {}).get('dateTime', '')
|
||||
|
||||
# Advoware-Termin erstellen
|
||||
appointment_data = {
|
||||
'text': event.get('summary', 'Google Calendar Termin'),
|
||||
'notiz': event.get('description', ''),
|
||||
'ort': event.get('location', ''),
|
||||
'datum': start[:10] if start else datetime.datetime.now().strftime('%Y-%m-%d'),
|
||||
'uhrzeitBis': start[11:19] if start else '09:00:00',
|
||||
'datumBis': end[:10] if end else start[:10] if start else datetime.datetime.now().strftime('%Y-%m-%d'),
|
||||
'sb': employee_kuerzel,
|
||||
'anwalt': employee_kuerzel
|
||||
}
|
||||
|
||||
result = await advoware.api_call('Termine', method='POST', json_data=appointment_data)
|
||||
|
||||
if result and isinstance(result, dict):
|
||||
new_frnr = result.get('frNr')
|
||||
if new_frnr:
|
||||
# frNr zurück in Google Event schreiben
|
||||
event['extendedProperties'] = event.get('extendedProperties', {})
|
||||
event['extendedProperties']['private'] = event['extendedProperties'].get('private', {})
|
||||
event['extendedProperties']['private']['advoware_frnr'] = str(new_frnr)
|
||||
|
||||
service.events().update(calendarId=calendar_id, eventId=event['id'], body=event).execute()
|
||||
print(f"Neuer Advoware Termin erstellt: {new_frnr}, frNr in Google aktualisiert")
|
||||
return new_frnr
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Sync zu Advoware für Google Event {event.get('id')}: {e}")
|
||||
return None
|
||||
|
||||
async def handler(req, context):
|
||||
try:
|
||||
# Konfiguration aus Request-Body
|
||||
body = req.get('body', {})
|
||||
full_content = body.get('full_content', True) # Default: volle Termindetails
|
||||
|
||||
print(f"Starte Advoware Calendar Sync, full_content: {full_content}")
|
||||
|
||||
# Google Calendar Service initialisieren
|
||||
service = await get_google_service(context)
|
||||
if not service:
|
||||
print("Google Calendar Service nicht verfügbar. Sync wird übersprungen.")
|
||||
return {
|
||||
'status': 200,
|
||||
'body': {
|
||||
'status': 'skipped',
|
||||
'reason': 'Google Calendar credentials not configured',
|
||||
'total_synced': 0
|
||||
}
|
||||
}
|
||||
|
||||
# Alle Mitarbeiter abrufen
|
||||
employees = await get_advoware_employees(context)
|
||||
|
||||
if not employees:
|
||||
return {'status': 500, 'body': {'error': 'Keine Mitarbeiter gefunden'}}
|
||||
|
||||
total_synced = 0
|
||||
|
||||
for employee in employees:
|
||||
kuerzel = employee.get('kuerzel') or employee.get('anwalt')
|
||||
if not kuerzel:
|
||||
print(f"Mitarbeiter ohne Kürzel übersprungen: {employee}")
|
||||
continue
|
||||
|
||||
print(f"Verarbeite Mitarbeiter: {kuerzel}")
|
||||
|
||||
# Google Calendar sicherstellen
|
||||
calendar_id = await ensure_google_calendar(service, kuerzel, context)
|
||||
if not calendar_id:
|
||||
continue
|
||||
|
||||
# Termine aus beiden Systemen abrufen
|
||||
advoware_appointments = await get_advoware_appointments(kuerzel, context)
|
||||
google_events = await get_google_events(service, calendar_id, context)
|
||||
|
||||
# Advoware → Google syncen
|
||||
google_frnrs = {event.get('extendedProperties', {}).get('private', {}).get('advoware_frnr') for event in google_events}
|
||||
|
||||
for appointment in advoware_appointments:
|
||||
frnr = str(appointment.get('frNr'))
|
||||
if frnr not in google_frnrs:
|
||||
await sync_appointment_to_google(service, calendar_id, appointment, full_content, context)
|
||||
total_synced += 1
|
||||
|
||||
# Google → Advoware syncen
|
||||
for event in google_events:
|
||||
await sync_event_to_advoware(service, calendar_id, event, kuerzel, context)
|
||||
|
||||
print(f"Advoware Calendar Sync abgeschlossen. {total_synced} Termine synchronisiert.")
|
||||
|
||||
return {
|
||||
'status': 200,
|
||||
'body': {
|
||||
'status': 'completed',
|
||||
'total_synced': total_synced,
|
||||
'employees_processed': len([e for e in employees if e.get('kuerzel') or e.get('anwalt')])
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Advoware Calendar Sync: {e}")
|
||||
return {
|
||||
'status': 500,
|
||||
'body': {
|
||||
'error': 'Internal server error',
|
||||
'details': str(e)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
import json
|
||||
import redis
|
||||
from config import Config
|
||||
|
||||
CALENDAR_SYNC_LOCK_KEY = 'calendar_sync_lock'
|
||||
|
||||
config = {
|
||||
'type': 'api',
|
||||
@@ -11,11 +15,34 @@ config = {
|
||||
|
||||
async def handler(req, context):
|
||||
try:
|
||||
# Prüfe ob bereits ein Sync läuft
|
||||
redis_client = redis.Redis(
|
||||
host=Config.REDIS_HOST,
|
||||
port=int(Config.REDIS_PORT),
|
||||
db=int(Config.REDIS_DB_CALENDAR_SYNC),
|
||||
socket_timeout=Config.REDIS_TIMEOUT_SECONDS
|
||||
)
|
||||
|
||||
if redis_client.get(CALENDAR_SYNC_LOCK_KEY):
|
||||
context.logger.info("Calendar Sync API: Sync bereits aktiv, überspringe")
|
||||
return {
|
||||
'status': 409,
|
||||
'body': {
|
||||
'status': 'conflict',
|
||||
'message': 'Calendar sync bereits aktiv',
|
||||
'triggered_by': 'api'
|
||||
}
|
||||
}
|
||||
|
||||
# Konfiguration aus Request-Body
|
||||
body = req.get('body', {})
|
||||
full_content = body.get('full_content', True)
|
||||
|
||||
print(f"Calendar Sync API aufgerufen, full_content: {full_content}")
|
||||
context.logger.info(f"Calendar Sync API aufgerufen, full_content: {full_content}")
|
||||
|
||||
# Setze Lock für 30 Minuten (Sync sollte max 30 Minuten dauern)
|
||||
redis_client.set(CALENDAR_SYNC_LOCK_KEY, 'api', ex=1800)
|
||||
context.logger.info("Calendar Sync API: Lock gesetzt")
|
||||
|
||||
# Emit Event für den Sync
|
||||
await context.emit({
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import json
|
||||
import redis
|
||||
from config import Config
|
||||
|
||||
CALENDAR_SYNC_LOCK_KEY = 'calendar_sync_lock'
|
||||
|
||||
config = {
|
||||
'type': 'cron',
|
||||
@@ -10,7 +14,25 @@ config = {
|
||||
|
||||
async def handler(event, context):
|
||||
try:
|
||||
context.logger.info("Calendar Sync Cron: Starte automatische Synchronisation alle 15 Minuten")
|
||||
# Prüfe ob bereits ein Sync läuft
|
||||
redis_client = redis.Redis(
|
||||
host=Config.REDIS_HOST,
|
||||
port=int(Config.REDIS_PORT),
|
||||
db=int(Config.REDIS_DB_CALENDAR_SYNC),
|
||||
socket_timeout=Config.REDIS_TIMEOUT_SECONDS
|
||||
)
|
||||
|
||||
if redis_client.get(CALENDAR_SYNC_LOCK_KEY):
|
||||
context.logger.info("Calendar Sync Cron: Sync bereits aktiv, überspringe")
|
||||
return {
|
||||
'status': 'skipped',
|
||||
'reason': 'sync_already_running',
|
||||
'triggered_by': 'cron'
|
||||
}
|
||||
|
||||
# Setze Lock für 30 Minuten (Sync sollte max 30 Minuten dauern)
|
||||
redis_client.set(CALENDAR_SYNC_LOCK_KEY, 'cron', ex=1800)
|
||||
context.logger.info("Calendar Sync Cron: Lock gesetzt, starte automatische Synchronisation alle 15 Minuten")
|
||||
|
||||
# Emit Event für den Sync
|
||||
await context.emit({
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ config = {
|
||||
'path': '/advoware/proxy',
|
||||
'method': 'DELETE',
|
||||
'emits': [],
|
||||
'flows': ['basic-tutorial', 'advoware']
|
||||
'flows': ['advoware']
|
||||
}
|
||||
|
||||
async def handler(req, context):
|
||||
@@ -23,10 +23,7 @@ async def handler(req, context):
|
||||
json_data = None
|
||||
|
||||
context.logger.info(f"Proxying request to Advoware: {method} {endpoint}")
|
||||
context.logger.info(f"Query params: {params}")
|
||||
result = await advoware.api_call(endpoint, method=method, params=params, json_data=json_data)
|
||||
context.logger.info(f"Advoware API response received, length: {len(str(result)) if result else 0}")
|
||||
context.logger.info(f"Response preview: {str(result)[:500] if result else 'None'}")
|
||||
|
||||
return {'status': 200, 'body': {'result': result}}
|
||||
except Exception as e:
|
||||
|
||||
@@ -7,7 +7,7 @@ config = {
|
||||
'path': '/advoware/proxy',
|
||||
'method': 'GET',
|
||||
'emits': [],
|
||||
'flows': ['basic-tutorial', 'advoware']
|
||||
'flows': ['advoware']
|
||||
}
|
||||
|
||||
async def handler(req, context):
|
||||
@@ -23,10 +23,7 @@ async def handler(req, context):
|
||||
json_data = None
|
||||
|
||||
context.logger.info(f"Proxying request to Advoware: {method} {endpoint}")
|
||||
context.logger.info(f"Query params: {params}")
|
||||
result = await advoware.api_call(endpoint, method=method, params=params, json_data=json_data)
|
||||
context.logger.info(f"Advoware API response received, length: {len(str(result)) if result else 0}")
|
||||
context.logger.info(f"Response preview: {str(result)[:500] if result else 'None'}")
|
||||
|
||||
return {'status': 200, 'body': {'result': result}}
|
||||
except Exception as e:
|
||||
|
||||
@@ -7,7 +7,7 @@ config = {
|
||||
'path': '/advoware/proxy',
|
||||
'method': 'PUT',
|
||||
'emits': [],
|
||||
'flows': ['basic-tutorial', 'advoware']
|
||||
'flows': ['advoware']
|
||||
}
|
||||
|
||||
async def handler(req, context):
|
||||
@@ -23,11 +23,7 @@ async def handler(req, context):
|
||||
json_data = req.get('body')
|
||||
|
||||
context.logger.info(f"Proxying request to Advoware: {method} {endpoint}")
|
||||
context.logger.info(f"Query params: {params}")
|
||||
context.logger.info(f"Request body: {json_data}")
|
||||
result = await advoware.api_call(endpoint, method=method, params=params, json_data=json_data)
|
||||
context.logger.info(f"Advoware API response received, length: {len(str(result)) if result else 0}")
|
||||
context.logger.info(f"Response preview: {str(result)[:500] if result else 'None'}")
|
||||
|
||||
return {'status': 200, 'body': {'result': result}}
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user