- 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.
154 lines
5.7 KiB
Python
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
|