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.
This commit is contained in:
153
services/advoware_history_service.py
Normal file
153
services/advoware_history_service.py
Normal file
@@ -0,0 +1,153 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user