gateway/modules/workflows/methods/methodTrustee/actions/syncToAccounting.py
2026-04-25 01:13:01 +02:00

100 lines
4.4 KiB
Python

# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
Sync trustee positions to accounting (Buha).
Input: featureInstanceId, documentList (DataRef on processDocuments[documents] —
list with one ActionDocument carrying JSON { positionIds, documentIds, ... }).
Reads positionIds from the first document and calls AccountingBridge.pushBatchToAccounting.
"""
import json
import logging
from typing import Dict, Any, List
from modules.datamodels.datamodelChat import ActionResult, ActionDocument
from modules.datamodels.datamodelDocref import DocumentReferenceList
logger = logging.getLogger(__name__)
def _resolveFirstDocument(documentListParam, services) -> Dict[str, Any] | None:
"""Resolve the first document from either Graph-Editor output (list of dicts) or Chat references.
Returns the parsed JSON dict or None.
"""
if isinstance(documentListParam, list) and documentListParam:
first = documentListParam[0]
if isinstance(first, dict) and ("documentData" in first or "documentName" in first):
rawData = first.get("documentData")
if rawData:
try:
return json.loads(rawData) if isinstance(rawData, str) else rawData
except (json.JSONDecodeError, TypeError):
pass
chatService = getattr(services, "chat", None)
if not chatService:
return None
try:
docList = DocumentReferenceList.from_string_list(
documentListParam if isinstance(documentListParam, list) else [documentListParam]
)
chatDocuments = chatService.getChatDocumentsFromDocumentList(docList)
if not chatDocuments:
return None
doc = chatDocuments[0]
rawBytes = chatService.getFileData(doc.fileId)
if not rawBytes:
return None
content = rawBytes.decode("utf-8") if isinstance(rawBytes, bytes) else rawBytes
return json.loads(content) if isinstance(content, str) else content
except Exception as e:
logger.debug("_resolveFirstDocument chat fallback failed: %s", e)
return None
async def syncToAccounting(self, parameters: Dict[str, Any]) -> ActionResult:
"""
Push trustee positions to the configured accounting system.
documentList must reference the message from processDocuments (one document with JSON { positionIds, documentIds }).
"""
featureInstanceId = parameters.get("featureInstanceId") or (self.services.featureInstanceId if hasattr(self.services, "featureInstanceId") else None)
documentListParam = parameters.get("documentList")
if not featureInstanceId:
return ActionResult.isFailure(error="featureInstanceId is required")
if not documentListParam:
return ActionResult.isFailure(error="documentList is required (reference to processDocuments result)")
try:
data = _resolveFirstDocument(documentListParam, self.services)
if data is None:
return ActionResult.isFailure(error="No document found for documentList; ensure processDocuments ran before this action")
positionIds = data.get("positionIds") or []
if not positionIds:
return ActionResult.isSuccess(documents=[
ActionDocument(documentName="sync_result", documentData=json.dumps({"pushed": 0, "message": "No positionIds in document"}), mimeType="application/json")
])
from modules.features.trustee.interfaceFeatureTrustee import getInterface as getTrusteeInterface
from modules.features.trustee.accounting.accountingBridge import AccountingBridge
trusteeInterface = getTrusteeInterface(
self.services.user,
mandateId=self.services.mandateId,
featureInstanceId=featureInstanceId
)
bridge = AccountingBridge(trusteeInterface)
results = await bridge.pushBatchToAccounting(featureInstanceId, positionIds)
successCount = sum(1 for r in results if r.success)
summary = {"pushed": successCount, "total": len(positionIds), "results": [{"positionId": pid, "success": r.success, "error": getattr(r, "errorMessage", None)} for pid, r in zip(positionIds, results)]}
return ActionResult.isSuccess(documents=[
ActionDocument(documentName="sync_result", documentData=json.dumps(summary), mimeType="application/json")
])
except Exception as e:
logger.exception("syncToAccounting failed")
return ActionResult.isFailure(error=str(e))