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 context.logger.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') context.logger.info(f"Advoware Mitarbeiter abgerufen: {len(result) if isinstance(result, list) else 'unbekannt'}") return result if isinstance(result, list) else [] except Exception as e: context.logger.error(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: context.logger.info(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'] context.logger.info(f"Google Calendar '{calendar_name}' erstellt mit ID: {calendar_id}") return calendar_id except Exception as e: context.logger.error(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 [] context.logger.info(f"Advoware Termine für {employee_kuerzel}: {len(appointments)} gefunden") return appointments except Exception as e: context.logger.error(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', []) context.logger.info(f"Google Calendar Events: {len(events)} gefunden") return events except Exception as e: context.logger.error(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() context.logger.info(f"Termin {appointment.get('frNr')} zu Google Calendar hinzugefügt") return created_event except Exception as e: context.logger.error(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() context.logger.info(f"Neuer Advoware Termin erstellt: {new_frnr}, frNr in Google aktualisiert") return new_frnr except Exception as e: context.logger.error(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 context.logger.info(f"Starte Advoware Calendar Sync, full_content: {full_content}") # Google Calendar Service initialisieren service = await get_google_service(context) if not service: context.logger.warning("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: context.logger.warning(f"Mitarbeiter ohne Kürzel übersprungen: {employee}") continue context.logger.info(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) context.logger.info(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: context.logger.error(f"Fehler beim Advoware Calendar Sync: {e}") return { 'status': 500, 'body': { 'error': 'Internal server error', 'details': str(e) } }