Compare commits

...

2 Commits

25 changed files with 244 additions and 92 deletions

View File

@@ -17,7 +17,7 @@ import pytz
from services.exceptions import LockAcquisitionError, SyncError, ValidationError from services.exceptions import LockAcquisitionError, SyncError, ValidationError
from services.redis_client import get_redis_client from services.redis_client import get_redis_client
from services.config import SYNC_CONFIG, get_lock_key, get_retry_delay_seconds from services.config import SYNC_CONFIG, get_lock_key, get_retry_delay_seconds
from services.logging_utils import get_logger from services.logging_utils import get_service_logger
import redis import redis
@@ -31,7 +31,7 @@ class BeteiligteSync:
def __init__(self, espocrm_api, redis_client: Optional[redis.Redis] = None, context=None): def __init__(self, espocrm_api, redis_client: Optional[redis.Redis] = None, context=None):
self.espocrm = espocrm_api self.espocrm = espocrm_api
self.context = context self.context = context
self.logger = get_logger('beteiligte_sync', context) self.logger = get_service_logger('beteiligte_sync', context)
# Use provided Redis client or get from factory # Use provided Redis client or get from factory
self.redis = redis_client or get_redis_client(strict=False) self.redis = redis_client or get_redis_client(strict=False)
@@ -46,6 +46,11 @@ class BeteiligteSync:
from services.notification_utils import NotificationManager from services.notification_utils import NotificationManager
self.notification_manager = NotificationManager(espocrm_api=self.espocrm, context=context) self.notification_manager = NotificationManager(espocrm_api=self.espocrm, context=context)
def _log(self, message: str, level: str = 'info') -> None:
"""Delegate logging to the logger with optional level"""
log_func = getattr(self.logger, level, self.logger.info)
log_func(message)
async def acquire_sync_lock(self, entity_id: str) -> bool: async def acquire_sync_lock(self, entity_id: str) -> bool:
""" """
Atomic distributed lock via Redis + syncStatus update Atomic distributed lock via Redis + syncStatus update

View File

@@ -10,12 +10,9 @@ Hilfsfunktionen für Document-Synchronisation mit xAI:
from typing import Dict, Any, Optional, List, Tuple from typing import Dict, Any, Optional, List, Tuple
from datetime import datetime, timedelta from datetime import datetime, timedelta
import logging
from services.sync_utils_base import BaseSyncUtils from services.sync_utils_base import BaseSyncUtils
logger = logging.getLogger(__name__)
# Max retry before permanent failure # Max retry before permanent failure
MAX_SYNC_RETRIES = 5 MAX_SYNC_RETRIES = 5

View File

@@ -5,6 +5,59 @@ Vereinheitlicht Logging über:
- Standard Python Logger - Standard Python Logger
- Motia FlowContext Logger - Motia FlowContext Logger
- Structured Logging - Structured Logging
Usage Guidelines:
=================
FOR SERVICES: Use get_service_logger('service_name', context)
-----------------------------------------------------------------
Example:
from services.logging_utils import get_service_logger
class XAIService:
def __init__(self, ctx=None):
self.logger = get_service_logger('xai', ctx)
def upload(self):
self.logger.info("Uploading file...")
FOR STEPS: Use ctx.logger directly (preferred)
-----------------------------------------------------------------
Steps already have ctx.logger available - use it directly:
async def handler(event_data, ctx: FlowContext):
ctx.logger.info("Processing event")
Alternative: Use get_step_logger() for additional loggers:
step_logger = get_step_logger('beteiligte_sync', ctx)
FOR SYNC UTILS: Inherit from BaseSyncUtils (provides self.logger)
-----------------------------------------------------------------
from services.sync_utils_base import BaseSyncUtils
class MySync(BaseSyncUtils):
def __init__(self, espocrm, redis, context):
super().__init__(espocrm, redis, context)
# self.logger is now available
def sync(self):
self._log("Syncing...", level='info')
FOR STANDALONE UTILITIES: Use get_logger()
-----------------------------------------------------------------
from services.logging_utils import get_logger
logger = get_logger('my_module', context)
logger.info("Processing...")
CONSISTENCY RULES:
==================
✅ Services: get_service_logger('service_name', ctx)
✅ Steps: ctx.logger (direct) or get_step_logger('step_name', ctx)
✅ Sync Utils: Inherit from BaseSyncUtils → use self._log() or self.logger
✅ Standalone: get_logger('module_name', ctx)
❌ DO NOT: Use module-level logging.getLogger(__name__)
❌ DO NOT: Mix get_logger() and get_service_logger() in same module
""" """
import logging import logging

View File

@@ -14,7 +14,7 @@ import pytz
from services.exceptions import RedisConnectionError, LockAcquisitionError from services.exceptions import RedisConnectionError, LockAcquisitionError
from services.redis_client import get_redis_client from services.redis_client import get_redis_client
from services.config import SYNC_CONFIG, get_lock_key from services.config import SYNC_CONFIG, get_lock_key
from services.logging_utils import get_logger from services.logging_utils import get_service_logger
import redis import redis
@@ -31,7 +31,7 @@ class BaseSyncUtils:
""" """
self.espocrm = espocrm_api self.espocrm = espocrm_api
self.context = context self.context = context
self.logger = get_logger('sync_utils', context) self.logger = get_service_logger('sync_utils', context)
# Use provided Redis client or get from factory # Use provided Redis client or get from factory
self.redis = redis_client or get_redis_client(strict=False) self.redis = redis_client or get_redis_client(strict=False)

View File

@@ -1,10 +1,8 @@
"""xAI Files & Collections Service""" """xAI Files & Collections Service"""
import os import os
import aiohttp import aiohttp
import logging
from typing import Optional, List from typing import Optional, List
from services.logging_utils import get_service_logger
logger = logging.getLogger(__name__)
XAI_FILES_URL = "https://api.x.ai" XAI_FILES_URL = "https://api.x.ai"
XAI_MANAGEMENT_URL = "https://management-api.x.ai" XAI_MANAGEMENT_URL = "https://management-api.x.ai"
@@ -23,6 +21,7 @@ class XAIService:
self.api_key = os.getenv('XAI_API_KEY', '') self.api_key = os.getenv('XAI_API_KEY', '')
self.management_key = os.getenv('XAI_MANAGEMENT_KEY', '') self.management_key = os.getenv('XAI_MANAGEMENT_KEY', '')
self.ctx = ctx self.ctx = ctx
self.logger = get_service_logger('xai', ctx)
self._session: Optional[aiohttp.ClientSession] = None self._session: Optional[aiohttp.ClientSession] = None
if not self.api_key: if not self.api_key:
@@ -31,10 +30,9 @@ class XAIService:
raise ValueError("XAI_MANAGEMENT_KEY not configured in environment") raise ValueError("XAI_MANAGEMENT_KEY not configured in environment")
def _log(self, msg: str, level: str = 'info') -> None: def _log(self, msg: str, level: str = 'info') -> None:
if self.ctx: """Delegate logging to service logger"""
getattr(self.ctx.logger, level, self.ctx.logger.info)(msg) log_func = getattr(self.logger, level, self.logger.info)
else: log_func(msg)
getattr(logger, level, logger.info)(msg)
async def _get_session(self) -> aiohttp.ClientSession: async def _get_session(self) -> aiohttp.ClientSession:
if self._session is None or self._session.closed: if self._session is None or self._session.closed:

View File

@@ -17,7 +17,7 @@ from calendar_sync_utils import (
import math import math
import time import time
from datetime import datetime from datetime import datetime
from typing import Any from typing import Any, Dict
from motia import queue, FlowContext from motia import queue, FlowContext
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from services.advoware_service import AdvowareService from services.advoware_service import AdvowareService
@@ -33,7 +33,7 @@ config = {
} }
async def handler(input_data: dict, ctx: FlowContext): async def handler(input_data: Dict[str, Any], ctx: FlowContext) -> None:
""" """
Handler that fetches all employees, sorts by last sync time, Handler that fetches all employees, sorts by last sync time,
and emits calendar_sync_employee events for the oldest ones. and emits calendar_sync_employee events for the oldest ones.

View File

@@ -9,6 +9,7 @@ from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent)) sys.path.insert(0, str(Path(__file__).parent))
from calendar_sync_utils import log_operation from calendar_sync_utils import log_operation
from typing import Dict, Any
from motia import cron, FlowContext from motia import cron, FlowContext
@@ -23,7 +24,7 @@ config = {
} }
async def handler(input_data: dict, ctx: FlowContext): async def handler(input_data: Dict[str, Any], ctx: FlowContext) -> None:
"""Cron handler that triggers the calendar sync cascade.""" """Cron handler that triggers the calendar sync cascade."""
try: try:
log_operation('info', "Calendar Sync Cron: Starting to emit sync-all event", context=ctx) log_operation('info', "Calendar Sync Cron: Starting to emit sync-all event", context=ctx)
@@ -37,14 +38,6 @@ async def handler(input_data: dict, ctx: FlowContext):
}) })
log_operation('info', "Calendar Sync Cron: Emitted sync-all event", context=ctx) log_operation('info', "Calendar Sync Cron: Emitted sync-all event", context=ctx)
return {
'status': 'completed',
'triggered_by': 'cron'
}
except Exception as e: except Exception as e:
log_operation('error', f"Fehler beim Cron-Job: {e}", context=ctx) log_operation('error', f"Fehler beim Cron-Job: {e}", context=ctx)
return {
'status': 'error',
'error': str(e)
}

View File

@@ -14,6 +14,7 @@ import asyncio
import os import os
import datetime import datetime
from datetime import timedelta from datetime import timedelta
from typing import Dict, Any
import pytz import pytz
import backoff import backoff
import time import time
@@ -945,14 +946,14 @@ config = {
} }
async def handler(input_data: dict, ctx: FlowContext): async def handler(input_data: Dict[str, Any], ctx: FlowContext) -> None:
"""Main event handler for calendar sync.""" """Main event handler for calendar sync."""
start_time = time.time() start_time = time.time()
kuerzel = input_data.get('kuerzel') kuerzel = input_data.get('kuerzel')
if not kuerzel: if not kuerzel:
log_operation('error', "No kuerzel provided in event", context=ctx) log_operation('error', "No kuerzel provided in event", context=ctx)
return {'status': 400, 'body': {'error': 'No kuerzel provided'}} return
log_operation('info', f"Starting calendar sync for employee {kuerzel}", context=ctx) log_operation('info', f"Starting calendar sync for employee {kuerzel}", context=ctx)

View File

@@ -32,23 +32,33 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
body={'error': 'Endpoint required as query parameter'} body={'error': 'Endpoint required as query parameter'}
) )
ctx.logger.info("=" * 80)
ctx.logger.info("🔄 ADVOWARE PROXY: DELETE REQUEST")
ctx.logger.info("=" * 80)
ctx.logger.info(f"Endpoint: {endpoint}")
ctx.logger.info("=" * 80)
# Initialize Advoware client # Initialize Advoware client
advoware = AdvowareAPI(ctx) advoware = AdvowareAPI(ctx)
# Forward all query params except 'endpoint' # Forward all query params except 'endpoint'
params = {k: v for k, v in request.query_params.items() if k != 'endpoint'} params = {k: v for k, v in request.query_params.items() if k != 'endpoint'}
ctx.logger.info(f"Proxying DELETE request to Advoware: {endpoint}")
result = await advoware.api_call( result = await advoware.api_call(
endpoint, endpoint,
method='DELETE', method='DELETE',
params=params params=params
) )
ctx.logger.info("✅ Proxy DELETE erfolgreich")
return ApiResponse(status=200, body={'result': result}) return ApiResponse(status=200, body={'result': result})
except Exception as e: except Exception as e:
ctx.logger.error(f"Proxy error: {e}") ctx.logger.error("=" * 80)
ctx.logger.error("❌ ADVOWARE PROXY DELETE FEHLER")
ctx.logger.error(f"Endpoint: {request.query_params.get('endpoint', 'N/A')}")
ctx.logger.error(f"Error: {e}")
ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status=500, status=500,
body={'error': 'Internal server error', 'details': str(e)} body={'error': 'Internal server error', 'details': str(e)}

View File

@@ -32,23 +32,33 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
body={'error': 'Endpoint required as query parameter'} body={'error': 'Endpoint required as query parameter'}
) )
ctx.logger.info("=" * 80)
ctx.logger.info("🔄 ADVOWARE PROXY: GET REQUEST")
ctx.logger.info("=" * 80)
ctx.logger.info(f"Endpoint: {endpoint}")
ctx.logger.info("=" * 80)
# Initialize Advoware client # Initialize Advoware client
advoware = AdvowareAPI(ctx) advoware = AdvowareAPI(ctx)
# Forward all query params except 'endpoint' # Forward all query params except 'endpoint'
params = {k: v for k, v in request.query_params.items() if k != 'endpoint'} params = {k: v for k, v in request.query_params.items() if k != 'endpoint'}
ctx.logger.info(f"Proxying GET request to Advoware: {endpoint}")
result = await advoware.api_call( result = await advoware.api_call(
endpoint, endpoint,
method='GET', method='GET',
params=params params=params
) )
ctx.logger.info("✅ Proxy GET erfolgreich")
return ApiResponse(status=200, body={'result': result}) return ApiResponse(status=200, body={'result': result})
except Exception as e: except Exception as e:
ctx.logger.error(f"Proxy error: {e}") ctx.logger.error("=" * 80)
ctx.logger.error("❌ ADVOWARE PROXY GET FEHLER")
ctx.logger.error(f"Endpoint: {request.query_params.get('endpoint', 'N/A')}")
ctx.logger.error(f"Error: {e}")
ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status=500, status=500,
body={'error': 'Internal server error', 'details': str(e)} body={'error': 'Internal server error', 'details': str(e)}

View File

@@ -34,6 +34,12 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
body={'error': 'Endpoint required as query parameter'} body={'error': 'Endpoint required as query parameter'}
) )
ctx.logger.info("=" * 80)
ctx.logger.info("🔄 ADVOWARE PROXY: POST REQUEST")
ctx.logger.info("=" * 80)
ctx.logger.info(f"Endpoint: {endpoint}")
ctx.logger.info("=" * 80)
# Initialize Advoware client # Initialize Advoware client
advoware = AdvowareAPI(ctx) advoware = AdvowareAPI(ctx)
@@ -43,7 +49,6 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
# Get request body # Get request body
json_data = request.body json_data = request.body
ctx.logger.info(f"Proxying POST request to Advoware: {endpoint}")
result = await advoware.api_call( result = await advoware.api_call(
endpoint, endpoint,
method='POST', method='POST',
@@ -51,11 +56,17 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
json_data=json_data json_data=json_data
) )
ctx.logger.info("✅ Proxy POST erfolgreich")
return ApiResponse(status=200, body={'result': result}) return ApiResponse(status=200, body={'result': result})
except Exception as e: except Exception as e:
ctx.logger.error(f"Proxy error: {e}") ctx.logger.error("=" * 80)
ctx.logger.error("❌ ADVOWARE PROXY POST FEHLER")
ctx.logger.error(f"Endpoint: {request.query_params.get('endpoint', 'N/A')}")
ctx.logger.error(f"Error: {e}")
ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status=500, status=500,
body={'error': 'Internal server error', 'details': str(e)} body={'error': 'Internal server error', 'details': str(e)}
) )
)

View File

@@ -34,6 +34,12 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
body={'error': 'Endpoint required as query parameter'} body={'error': 'Endpoint required as query parameter'}
) )
ctx.logger.info("=" * 80)
ctx.logger.info("🔄 ADVOWARE PROXY: PUT REQUEST")
ctx.logger.info("=" * 80)
ctx.logger.info(f"Endpoint: {endpoint}")
ctx.logger.info("=" * 80)
# Initialize Advoware client # Initialize Advoware client
advoware = AdvowareAPI(ctx) advoware = AdvowareAPI(ctx)
@@ -43,7 +49,6 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
# Get request body # Get request body
json_data = request.body json_data = request.body
ctx.logger.info(f"Proxying PUT request to Advoware: {endpoint}")
result = await advoware.api_call( result = await advoware.api_call(
endpoint, endpoint,
method='PUT', method='PUT',
@@ -51,11 +56,17 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
json_data=json_data json_data=json_data
) )
ctx.logger.info("✅ Proxy PUT erfolgreich")
return ApiResponse(status=200, body={'result': result}) return ApiResponse(status=200, body={'result': result})
except Exception as e: except Exception as e:
ctx.logger.error(f"Proxy error: {e}") ctx.logger.error("=" * 80)
ctx.logger.error("❌ ADVOWARE PROXY PUT FEHLER")
ctx.logger.error(f"Endpoint: {request.query_params.get('endpoint', 'N/A')}")
ctx.logger.error(f"Error: {e}")
ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status=500, status=500,
body={'error': 'Internal server error', 'details': str(e)} body={'error': 'Internal server error', 'details': str(e)}
) )
)

View File

@@ -34,7 +34,7 @@ config = {
} }
async def handler(event_data: Dict[str, Any], ctx: FlowContext[Any]): async def handler(event_data: Dict[str, Any], ctx: FlowContext[Any]) -> None:
"""Zentraler Sync-Handler für Bankverbindungen""" """Zentraler Sync-Handler für Bankverbindungen"""
entity_id = event_data.get('entity_id') entity_id = event_data.get('entity_id')

View File

@@ -25,7 +25,7 @@ config = {
} }
async def handler(input_data: Dict[str, Any], ctx: FlowContext): async def handler(input_data: Dict[str, Any], ctx: FlowContext) -> None:
""" """
Cron-Handler: Findet alle Beteiligte die Sync benötigen und emittiert Events Cron-Handler: Findet alle Beteiligte die Sync benötigen und emittiert Events
""" """

View File

@@ -42,16 +42,13 @@ config = {
} }
async def handler(event_data: Dict[str, Any], ctx: FlowContext[Any]) -> Optional[Dict[str, Any]]: async def handler(event_data: Dict[str, Any], ctx: FlowContext[Any]) -> None:
""" """
Zentraler Sync-Handler für Beteiligte Zentraler Sync-Handler für Beteiligte
Args: Args:
event_data: Event data mit entity_id, action, source event_data: Event data mit entity_id, action, source
ctx: Motia FlowContext ctx: Motia FlowContext
Returns:
Optional result dict
""" """
entity_id = event_data.get('entity_id') entity_id = event_data.get('entity_id')
action = event_data.get('action') action = event_data.get('action')
@@ -61,11 +58,13 @@ async def handler(event_data: Dict[str, Any], ctx: FlowContext[Any]) -> Optional
if not entity_id: if not entity_id:
step_logger.error("Keine entity_id im Event gefunden") step_logger.error("Keine entity_id im Event gefunden")
return None return
step_logger.info( step_logger.info("=" * 80)
f"🔄 Sync-Handler gestartet: {action.upper()} | Entity: {entity_id} | Source: {source}" step_logger.info(f"🔄 BETEILIGTE SYNC HANDLER: {action.upper()}")
) step_logger.info("=" * 80)
step_logger.info(f"Entity: {entity_id} | Source: {source}")
step_logger.info("=" * 80)
# Get shared Redis client (centralized) # Get shared Redis client (centralized)
redis_client = get_redis_client(strict=False) redis_client = get_redis_client(strict=False)

View File

@@ -14,10 +14,9 @@ from motia import FlowContext
from services.espocrm import EspoCRMAPI from services.espocrm import EspoCRMAPI
from services.document_sync_utils import DocumentSync from services.document_sync_utils import DocumentSync
from services.xai_service import XAIService from services.xai_service import XAIService
from services.redis_client import get_redis_client
import hashlib import hashlib
import json import json
import redis
import os
config = { config = {
"name": "VMH Document Sync Handler", "name": "VMH Document Sync Handler",
@@ -32,7 +31,7 @@ config = {
} }
async def handler(event_data: Dict[str, Any], ctx: FlowContext[Any]): async def handler(event_data: Dict[str, Any], ctx: FlowContext[Any]) -> None:
"""Zentraler Sync-Handler für Documents""" """Zentraler Sync-Handler für Documents"""
entity_id = event_data.get('entity_id') entity_id = event_data.get('entity_id')
entity_type = event_data.get('entity_type', 'CDokumente') # Default: CDokumente entity_type = event_data.get('entity_type', 'CDokumente') # Default: CDokumente
@@ -52,20 +51,11 @@ async def handler(event_data: Dict[str, Any], ctx: FlowContext[Any]):
ctx.logger.info(f"Source: {source}") ctx.logger.info(f"Source: {source}")
ctx.logger.info("=" * 80) ctx.logger.info("=" * 80)
# Shared Redis client for distributed locking # Shared Redis client for distributed locking (centralized factory)
redis_host = os.getenv('REDIS_HOST', 'localhost') redis_client = get_redis_client(strict=False)
redis_port = int(os.getenv('REDIS_PORT', '6379'))
redis_db = int(os.getenv('REDIS_DB_ADVOWARE_CACHE', '1'))
redis_client = redis.Redis( # APIs initialisieren (mit Context für besseres Logging)
host=redis_host, espocrm = EspoCRMAPI(ctx)
port=redis_port,
db=redis_db,
decode_responses=True
)
# APIs initialisieren
espocrm = EspoCRMAPI()
sync_utils = DocumentSync(espocrm, redis_client, ctx) sync_utils = DocumentSync(espocrm, redis_client, ctx)
xai_service = XAIService(ctx) xai_service = XAIService(ctx)

View File

@@ -23,8 +23,11 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
try: try:
payload = request.body or [] payload = request.body or []
ctx.logger.info("VMH Webhook Bankverbindungen Create empfangen") ctx.logger.info("=" * 80)
ctx.logger.info("📥 VMH WEBHOOK: BANKVERBINDUNGEN CREATE")
ctx.logger.info("=" * 80)
ctx.logger.info(f"Payload: {json.dumps(payload, indent=2, ensure_ascii=False)}") ctx.logger.info(f"Payload: {json.dumps(payload, indent=2, ensure_ascii=False)}")
ctx.logger.info("=" * 80)
# Sammle alle IDs aus dem Batch # Sammle alle IDs aus dem Batch
entity_ids = set() entity_ids = set()
@@ -50,7 +53,8 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
} }
}) })
ctx.logger.info(f"VMH Create Webhook verarbeitet: {len(entity_ids)} Events emittiert") ctx.logger.info("VMH Create Webhook verarbeitet: "
f"{len(entity_ids)} Events emittiert")
return ApiResponse( return ApiResponse(
status=200, status=200,
@@ -62,7 +66,10 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
) )
except Exception as e: except Exception as e:
ctx.logger.error(f"Fehler beim Verarbeiten des VMH Create Webhooks: {e}") ctx.logger.error("=" * 80)
ctx.logger.error("❌ FEHLER: BANKVERBINDUNGEN CREATE WEBHOOK")
ctx.logger.error(f"Error: {e}")
ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status=500, status=500,
body={'error': 'Internal server error', 'details': str(e)} body={'error': 'Internal server error', 'details': str(e)}

View File

@@ -23,8 +23,11 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
try: try:
payload = request.body or [] payload = request.body or []
ctx.logger.info("VMH Webhook Bankverbindungen Delete empfangen") ctx.logger.info("=" * 80)
ctx.logger.info("📥 VMH WEBHOOK: BANKVERBINDUNGEN DELETE")
ctx.logger.info("=" * 80)
ctx.logger.info(f"Payload: {json.dumps(payload, indent=2, ensure_ascii=False)}") ctx.logger.info(f"Payload: {json.dumps(payload, indent=2, ensure_ascii=False)}")
ctx.logger.info("=" * 80)
# Sammle alle IDs # Sammle alle IDs
entity_ids = set() entity_ids = set()
@@ -50,7 +53,8 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
} }
}) })
ctx.logger.info(f"VMH Delete Webhook verarbeitet: {len(entity_ids)} Events emittiert") ctx.logger.info("VMH Delete Webhook verarbeitet: "
f"{len(entity_ids)} Events emittiert")
return ApiResponse( return ApiResponse(
status=200, status=200,
@@ -62,7 +66,10 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
) )
except Exception as e: except Exception as e:
ctx.logger.error(f"Fehler beim Verarbeiten des VMH Delete Webhooks: {e}") ctx.logger.error("=" * 80)
ctx.logger.error("❌ FEHLER: BANKVERBINDUNGEN DELETE WEBHOOK")
ctx.logger.error(f"Error: {e}")
ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status=500, status=500,
body={'error': 'Internal server error', 'details': str(e)} body={'error': 'Internal server error', 'details': str(e)}

View File

@@ -23,8 +23,11 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
try: try:
payload = request.body or [] payload = request.body or []
ctx.logger.info("VMH Webhook Bankverbindungen Update empfangen") ctx.logger.info("=" * 80)
ctx.logger.info("📥 VMH WEBHOOK: BANKVERBINDUNGEN UPDATE")
ctx.logger.info("=" * 80)
ctx.logger.info(f"Payload: {json.dumps(payload, indent=2, ensure_ascii=False)}") ctx.logger.info(f"Payload: {json.dumps(payload, indent=2, ensure_ascii=False)}")
ctx.logger.info("=" * 80)
# Sammle alle IDs # Sammle alle IDs
entity_ids = set() entity_ids = set()
@@ -50,7 +53,8 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
} }
}) })
ctx.logger.info(f"VMH Update Webhook verarbeitet: {len(entity_ids)} Events emittiert") ctx.logger.info("VMH Update Webhook verarbeitet: "
f"{len(entity_ids)} Events emittiert")
return ApiResponse( return ApiResponse(
status=200, status=200,
@@ -62,7 +66,10 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
) )
except Exception as e: except Exception as e:
ctx.logger.error(f"Fehler beim Verarbeiten des VMH Update Webhooks: {e}") ctx.logger.error("=" * 80)
ctx.logger.error("❌ FEHLER: BANKVERBINDUNGEN UPDATE WEBHOOK")
ctx.logger.error(f"Error: {e}")
ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status=500, status=500,
body={'error': 'Internal server error', 'details': str(e)} body={'error': 'Internal server error', 'details': str(e)}

View File

@@ -26,8 +26,11 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
try: try:
payload = request.body or [] payload = request.body or []
ctx.logger.info("VMH Webhook Beteiligte Create empfangen") ctx.logger.info("=" * 80)
ctx.logger.info("📥 VMH WEBHOOK: BETEILIGTE CREATE")
ctx.logger.info("=" * 80)
ctx.logger.info(f"Payload: {json.dumps(payload, indent=2, ensure_ascii=False)}") ctx.logger.info(f"Payload: {json.dumps(payload, indent=2, ensure_ascii=False)}")
ctx.logger.info("=" * 80)
# Sammle alle IDs aus dem Batch # Sammle alle IDs aus dem Batch
entity_ids = set() entity_ids = set()
@@ -53,7 +56,8 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
} }
}) })
ctx.logger.info(f"VMH Create Webhook verarbeitet: {len(entity_ids)} Events emittiert") ctx.logger.info("VMH Create Webhook verarbeitet: "
f"{len(entity_ids)} Events emittiert")
return ApiResponse( return ApiResponse(
status=200, status=200,
@@ -65,7 +69,10 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
) )
except Exception as e: except Exception as e:
ctx.logger.error(f"Fehler beim Verarbeiten des VMH Create Webhooks: {e}") ctx.logger.error("=" * 80)
ctx.logger.error("❌ FEHLER: VMH CREATE WEBHOOK")
ctx.logger.error(f"Error: {e}")
ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status=500, status=500,
body={ body={

View File

@@ -23,8 +23,11 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
try: try:
payload = request.body or [] payload = request.body or []
ctx.logger.info("VMH Webhook Beteiligte Delete empfangen") ctx.logger.info("=" * 80)
ctx.logger.info("📥 VMH WEBHOOK: BETEILIGTE DELETE")
ctx.logger.info("=" * 80)
ctx.logger.info(f"Payload: {json.dumps(payload, indent=2, ensure_ascii=False)}") ctx.logger.info(f"Payload: {json.dumps(payload, indent=2, ensure_ascii=False)}")
ctx.logger.info("=" * 80)
# Sammle alle IDs aus dem Batch # Sammle alle IDs aus dem Batch
entity_ids = set() entity_ids = set()
@@ -50,7 +53,8 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
} }
}) })
ctx.logger.info(f"VMH Delete Webhook verarbeitet: {len(entity_ids)} Events emittiert") ctx.logger.info("VMH Delete Webhook verarbeitet: "
f"{len(entity_ids)} Events emittiert")
return ApiResponse( return ApiResponse(
status=200, status=200,
@@ -62,7 +66,10 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
) )
except Exception as e: except Exception as e:
ctx.logger.error(f"Fehler beim Delete-Webhook: {e}") ctx.logger.error("=" * 80)
ctx.logger.error("❌ FEHLER: BETEILIGTE DELETE WEBHOOK")
ctx.logger.error(f"Error: {e}")
ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status=500, status=500,
body={'error': 'Internal server error', 'details': str(e)} body={'error': 'Internal server error', 'details': str(e)}

View File

@@ -26,8 +26,11 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
try: try:
payload = request.body or [] payload = request.body or []
ctx.logger.info("VMH Webhook Beteiligte Update empfangen") ctx.logger.info("=" * 80)
ctx.logger.info("📥 VMH WEBHOOK: BETEILIGTE UPDATE")
ctx.logger.info("=" * 80)
ctx.logger.info(f"Payload: {json.dumps(payload, indent=2, ensure_ascii=False)}") ctx.logger.info(f"Payload: {json.dumps(payload, indent=2, ensure_ascii=False)}")
ctx.logger.info("=" * 80)
# Sammle alle IDs aus dem Batch # Sammle alle IDs aus dem Batch
entity_ids = set() entity_ids = set()
@@ -53,7 +56,8 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
} }
}) })
ctx.logger.info(f"VMH Update Webhook verarbeitet: {len(entity_ids)} Events emittiert") ctx.logger.info("VMH Update Webhook verarbeitet: "
f"{len(entity_ids)} Events emittiert")
return ApiResponse( return ApiResponse(
status=200, status=200,
@@ -65,7 +69,10 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
) )
except Exception as e: except Exception as e:
ctx.logger.error(f"Fehler beim Verarbeiten des VMH Update Webhooks: {e}") ctx.logger.error("=" * 80)
ctx.logger.error("❌ FEHLER: VMH UPDATE WEBHOOK")
ctx.logger.error(f"Error: {e}")
ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status=500, status=500,
body={ body={

View File

@@ -25,17 +25,21 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
try: try:
payload = request.body or [] payload = request.body or []
ctx.logger.info("VMH Webhook Document Create empfangen") ctx.logger.info("=" * 80)
ctx.logger.info("📥 VMH WEBHOOK: DOCUMENT CREATE")
ctx.logger.info("=" * 80)
ctx.logger.debug(f"Payload: {json.dumps(payload, indent=2, ensure_ascii=False)}") ctx.logger.debug(f"Payload: {json.dumps(payload, indent=2, ensure_ascii=False)}")
# Sammle alle IDs aus dem Batch # Sammle alle IDs aus dem Batch
entity_ids = set() entity_ids = set()
entity_type = 'CDokumente' # Default
if isinstance(payload, list): if isinstance(payload, list):
for entity in payload: for entity in payload:
if isinstance(entity, dict) and 'id' in entity: if isinstance(entity, dict) and 'id' in entity:
entity_ids.add(entity['id']) entity_ids.add(entity['id'])
# Extrahiere entityType falls vorhanden # Take entityType from first entity if present
if entity_type == 'CDokumente':
entity_type = entity.get('entityType', 'CDokumente') entity_type = entity.get('entityType', 'CDokumente')
elif isinstance(payload, dict) and 'id' in payload: elif isinstance(payload, dict) and 'id' in payload:
entity_ids.add(payload['id']) entity_ids.add(payload['id'])
@@ -49,12 +53,15 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
'topic': 'vmh.document.create', 'topic': 'vmh.document.create',
'data': { 'data': {
'entity_id': entity_id, 'entity_id': entity_id,
'entity_type': entity_type if 'entity_type' in locals() else 'CDokumente', 'entity_type': entity_type,
'action': 'create', 'action': 'create',
'timestamp': payload[0].get('modifiedAt') if isinstance(payload, list) and payload else None 'timestamp': payload[0].get('modifiedAt') if isinstance(payload, list) and payload else None
} }
}) })
ctx.logger.info("✅ Document Create Webhook verarbeitet: "
f"{len(entity_ids)} Events emittiert")
return ApiResponse( return ApiResponse(
status=200, status=200,
body={ body={
@@ -65,8 +72,11 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
) )
except Exception as e: except Exception as e:
ctx.logger.error(f"Fehler im Document Create Webhook: {e}") ctx.logger.error("=" * 80)
ctx.logger.error("❌ FEHLER: DOCUMENT CREATE WEBHOOK")
ctx.logger.error(f"Error: {e}")
ctx.logger.error(f"Payload: {request.body}") ctx.logger.error(f"Payload: {request.body}")
ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status=500, status=500,

View File

@@ -25,16 +25,21 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
try: try:
payload = request.body or [] payload = request.body or []
ctx.logger.info("VMH Webhook Document Delete empfangen") ctx.logger.info("=" * 80)
ctx.logger.info("📥 VMH WEBHOOK: DOCUMENT DELETE")
ctx.logger.info("=" * 80)
ctx.logger.debug(f"Payload: {json.dumps(payload, indent=2, ensure_ascii=False)}") ctx.logger.debug(f"Payload: {json.dumps(payload, indent=2, ensure_ascii=False)}")
# Sammle alle IDs aus dem Batch # Sammle alle IDs aus dem Batch
entity_ids = set() entity_ids = set()
entity_type = 'CDokumente' # Default
if isinstance(payload, list): if isinstance(payload, list):
for entity in payload: for entity in payload:
if isinstance(entity, dict) and 'id' in entity: if isinstance(entity, dict) and 'id' in entity:
entity_ids.add(entity['id']) entity_ids.add(entity['id'])
# Take entityType from first entity if present
if entity_type == 'CDokumente':
entity_type = entity.get('entityType', 'CDokumente') entity_type = entity.get('entityType', 'CDokumente')
elif isinstance(payload, dict) and 'id' in payload: elif isinstance(payload, dict) and 'id' in payload:
entity_ids.add(payload['id']) entity_ids.add(payload['id'])
@@ -48,12 +53,15 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
'topic': 'vmh.document.delete', 'topic': 'vmh.document.delete',
'data': { 'data': {
'entity_id': entity_id, 'entity_id': entity_id,
'entity_type': entity_type if 'entity_type' in locals() else 'CDokumente', 'entity_type': entity_type,
'action': 'delete', 'action': 'delete',
'timestamp': payload[0].get('deletedAt') if isinstance(payload, list) and payload else None 'timestamp': payload[0].get('deletedAt') if isinstance(payload, list) and payload else None
} }
}) })
ctx.logger.info("✅ Document Delete Webhook verarbeitet: "
f"{len(entity_ids)} Events emittiert")
return ApiResponse( return ApiResponse(
status=200, status=200,
body={ body={
@@ -64,8 +72,11 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
) )
except Exception as e: except Exception as e:
ctx.logger.error(f"Fehler im Document Delete Webhook: {e}") ctx.logger.error("=" * 80)
ctx.logger.error("❌ FEHLER: DOCUMENT DELETE WEBHOOK")
ctx.logger.error(f"Error: {e}")
ctx.logger.error(f"Payload: {request.body}") ctx.logger.error(f"Payload: {request.body}")
ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status=500, status=500,

View File

@@ -25,16 +25,21 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
try: try:
payload = request.body or [] payload = request.body or []
ctx.logger.info("VMH Webhook Document Update empfangen") ctx.logger.info("=" * 80)
ctx.logger.info("📥 VMH WEBHOOK: DOCUMENT UPDATE")
ctx.logger.info("=" * 80)
ctx.logger.debug(f"Payload: {json.dumps(payload, indent=2, ensure_ascii=False)}") ctx.logger.debug(f"Payload: {json.dumps(payload, indent=2, ensure_ascii=False)}")
# Sammle alle IDs aus dem Batch # Sammle alle IDs aus dem Batch
entity_ids = set() entity_ids = set()
entity_type = 'CDokumente' # Default
if isinstance(payload, list): if isinstance(payload, list):
for entity in payload: for entity in payload:
if isinstance(entity, dict) and 'id' in entity: if isinstance(entity, dict) and 'id' in entity:
entity_ids.add(entity['id']) entity_ids.add(entity['id'])
# Take entityType from first entity if present
if entity_type == 'CDokumente':
entity_type = entity.get('entityType', 'CDokumente') entity_type = entity.get('entityType', 'CDokumente')
elif isinstance(payload, dict) and 'id' in payload: elif isinstance(payload, dict) and 'id' in payload:
entity_ids.add(payload['id']) entity_ids.add(payload['id'])
@@ -48,12 +53,15 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
'topic': 'vmh.document.update', 'topic': 'vmh.document.update',
'data': { 'data': {
'entity_id': entity_id, 'entity_id': entity_id,
'entity_type': entity_type if 'entity_type' in locals() else 'CDokumente', 'entity_type': entity_type,
'action': 'update', 'action': 'update',
'timestamp': payload[0].get('modifiedAt') if isinstance(payload, list) and payload else None 'timestamp': payload[0].get('modifiedAt') if isinstance(payload, list) and payload else None
} }
}) })
ctx.logger.info("✅ Document Update Webhook verarbeitet: "
f"{len(entity_ids)} Events emittiert")
return ApiResponse( return ApiResponse(
status=200, status=200,
body={ body={
@@ -64,8 +72,11 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
) )
except Exception as e: except Exception as e:
ctx.logger.error(f"Fehler im Document Update Webhook: {e}") ctx.logger.error("=" * 80)
ctx.logger.error("❌ FEHLER: DOCUMENT UPDATE WEBHOOK")
ctx.logger.error(f"Error: {e}")
ctx.logger.error(f"Payload: {request.body}") ctx.logger.error(f"Payload: {request.body}")
ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status=500, status=500,