Files
motia-iii/services/advoware_history_service.py
bsiggel 1ffc37b0b7 feat: Add Advoware History and Watcher services for document synchronization
- Implement AdvowareHistoryService for fetching and creating history entries.
- Implement AdvowareWatcherService for file operations including listing, downloading, and uploading with Blake3 hash verification.
- Introduce Blake3 utility functions for hash computation and verification.
- Create document sync cron step to poll Redis for pending Aktennummern and emit sync events.
- Develop document sync event handler to manage 3-way merge synchronization for Akten, including metadata updates and error handling.
2026-03-25 21:24:31 +00:00

154 lines
5.7 KiB
Python

"""
Advoware History API Client
API client for Advoware History (document timeline) operations.
Provides methods to:
- Get History entries for Akte
- Create new History entry
"""
from typing import Dict, Any, List, Optional
from datetime import datetime
from services.advoware import AdvowareAPI
from services.logging_utils import get_service_logger
from services.exceptions import AdvowareAPIError
class AdvowareHistoryService:
"""
Advoware History API client.
Provides methods to:
- Get History entries for Akte
- Create new History entry
"""
def __init__(self, ctx):
"""
Initialize service with context.
Args:
ctx: Motia context for logging
"""
self.ctx = ctx
self.logger = get_service_logger(__name__, ctx)
self.advoware = AdvowareAPI(ctx) # Reuse existing auth
self.logger.info("AdvowareHistoryService initialized")
def _log(self, message: str, level: str = 'info') -> None:
"""Helper for consistent logging"""
getattr(self.logger, level)(f"[AdvowareHistoryService] {message}")
async def get_akte_history(self, akte_nr: str) -> List[Dict[str, Any]]:
"""
Get all History entries for Akte.
Args:
akte_nr: Aktennummer (10-digit string, e.g., "2019001145")
Returns:
List of History entry dicts with fields:
- dat: str (timestamp)
- art: str (type, e.g., "Schreiben")
- text: str (description)
- datei: str (file path, e.g., "V:\\12345\\document.pdf")
- benutzer: str (user)
- versendeart: str
- hnr: int (History entry ID)
Raises:
AdvowareAPIError: If API call fails (non-retryable)
Note:
Uses correct endpoint: GET /api/v1/advonet/History?nr={aktennummer}
"""
self._log(f"Fetching History for Akte {akte_nr}")
try:
endpoint = "api/v1/advonet/History"
params = {'nr': akte_nr}
result = await self.advoware.api_call(endpoint, method='GET', params=params)
if not isinstance(result, list):
self._log(f"Unexpected History response format: {type(result)}", level='warning')
return []
self._log(f"Successfully fetched {len(result)} History entries for Akte {akte_nr}")
return result
except Exception as e:
error_msg = str(e)
# Advoware server bug: "Nullable object must have a value" in ConnectorFunctionsHistory.cs
# This is a server-side bug we cannot fix - return empty list and continue
if "Nullable object must have a value" in error_msg or "500" in error_msg:
self._log(
f"⚠️ Advoware server error for Akte {akte_nr} (likely null reference bug): {e}",
level='warning'
)
self._log(f"Continuing with empty History for Akte {akte_nr}", level='info')
return [] # Return empty list instead of failing
# For other errors, raise as before
self._log(f"Failed to fetch History for Akte {akte_nr}: {e}", level='error')
raise AdvowareAPIError(f"History fetch failed: {e}") from e
async def create_history_entry(
self,
akte_id: int,
entry_data: Dict[str, Any]
) -> Dict[str, Any]:
"""
Create new History entry.
Args:
akte_id: Advoware Akte ID
entry_data: History entry data with fields:
- dat: str (timestamp, ISO format)
- art: str (type, e.g., "Schreiben")
- text: str (description)
- datei: str (file path, e.g., "V:\\12345\\document.pdf")
- benutzer: str (user, default: "AI")
- versendeart: str (default: "Y")
- visibleOnline: bool (default: True)
- posteingang: int (default: 0)
Returns:
Created History entry
Raises:
AdvowareAPIError: If creation fails
"""
self._log(f"Creating History entry for Akte {akte_id}")
# Ensure required fields with defaults
now = datetime.now().isoformat()
payload = {
"betNr": entry_data.get('betNr'), # Can be null
"dat": entry_data.get('dat', now),
"art": entry_data.get('art', 'Schreiben'),
"text": entry_data.get('text', 'Document uploaded via Motia'),
"datei": entry_data.get('datei', ''),
"benutzer": entry_data.get('benutzer', 'AI'),
"gelesen": entry_data.get('gelesen'), # Can be null
"modified": entry_data.get('modified', now),
"vorgelegt": entry_data.get('vorgelegt', ''),
"posteingang": entry_data.get('posteingang', 0),
"visibleOnline": entry_data.get('visibleOnline', True),
"versendeart": entry_data.get('versendeart', 'Y')
}
try:
endpoint = f"api/v1/advonet/Akten/{akte_id}/History"
result = await self.advoware.api_call(endpoint, method='POST', json_data=payload)
if result:
self._log(f"Successfully created History entry for Akte {akte_id}")
return result
except Exception as e:
self._log(f"Failed to create History entry for Akte {akte_id}: {e}", level='error')
raise AdvowareAPIError(f"History entry creation failed: {e}") from e