# Copyright (c) 2025 Patrick Motsch # All rights reserved. """ Refresh accounting data from external system (e.g. Abacus) into local TrusteeData* tables. Checks lastSyncAt to avoid redundant imports unless forceRefresh is set. """ import json import logging import time from typing import Dict, Any from modules.datamodels.datamodelChat import ActionResult, ActionDocument logger = logging.getLogger(__name__) _SYNC_THRESHOLD_SECONDS = 3600 async def refreshAccountingData(self, parameters: Dict[str, Any]) -> ActionResult: """Import/refresh accounting data from the configured external system. If data was synced within the last hour and forceRefresh is not set, returns cached counts without triggering an external sync. """ featureInstanceId = parameters.get("featureInstanceId") or ( self.services.featureInstanceId if hasattr(self.services, "featureInstanceId") else None ) forceRefresh = parameters.get("forceRefresh", False) if isinstance(forceRefresh, str): forceRefresh = forceRefresh.lower() in ("true", "1", "yes") dateFrom = parameters.get("dateFrom") or None dateTo = parameters.get("dateTo") or None if not featureInstanceId: return ActionResult.isFailure(error="featureInstanceId is required") try: from modules.features.trustee.interfaceFeatureTrustee import getInterface as getTrusteeInterface from modules.features.trustee.datamodelFeatureTrustee import ( TrusteeAccountingConfig, TrusteeDataAccount, TrusteeDataJournalEntry, TrusteeDataJournalLine, TrusteeDataContact, TrusteeDataAccountBalance, ) trusteeInterface = getTrusteeInterface( self.services.user, mandateId=self.services.mandateId, featureInstanceId=featureInstanceId, ) cfgRecords = trusteeInterface.db.getRecordset( TrusteeAccountingConfig, recordFilter={"featureInstanceId": featureInstanceId, "isActive": True}, ) if not cfgRecords: return ActionResult.isFailure(error="No active accounting configuration found for this Trustee instance") cfgRecord = cfgRecords[0] lastSyncAt = cfgRecord.get("lastSyncAt") or 0 lastSyncStatus = cfgRecord.get("lastSyncStatus") or "" isFresh = ( lastSyncAt and (time.time() - lastSyncAt) < _SYNC_THRESHOLD_SECONDS and lastSyncStatus in ("success", "partial") ) if isFresh and not forceRefresh: counts = _getCachedCounts(trusteeInterface, featureInstanceId) counts["synced"] = False counts["lastSyncAt"] = lastSyncAt counts["lastSyncStatus"] = lastSyncStatus counts["message"] = f"Data is fresh (synced {int(time.time() - lastSyncAt)}s ago). Use forceRefresh=true to re-import." return ActionResult.isSuccess(documents=[ ActionDocument( documentName="refresh_result", documentData=json.dumps(counts, ensure_ascii=False), mimeType="application/json", ) ]) from modules.features.trustee.accounting.accountingDataSync import AccountingDataSync sync = AccountingDataSync(trusteeInterface) summary = await sync.importData( featureInstanceId=featureInstanceId, mandateId=self.services.mandateId, dateFrom=dateFrom, dateTo=dateTo, ) summary["synced"] = True summary.pop("startedAt", None) summary.pop("finishedAt", None) return ActionResult.isSuccess(documents=[ ActionDocument( documentName="refresh_result", documentData=json.dumps(summary, ensure_ascii=False), mimeType="application/json", ) ]) except Exception as e: logger.exception("refreshAccountingData failed") return ActionResult.isFailure(error=str(e)) def _getCachedCounts(trusteeInterface, featureInstanceId: str) -> Dict[str, Any]: """Count existing records per TrusteeData* table without triggering an external sync.""" from modules.features.trustee.datamodelFeatureTrustee import ( TrusteeDataAccount, TrusteeDataJournalEntry, TrusteeDataJournalLine, TrusteeDataContact, TrusteeDataAccountBalance, ) counts = {} for label, model in [ ("accounts", TrusteeDataAccount), ("journalEntries", TrusteeDataJournalEntry), ("journalLines", TrusteeDataJournalLine), ("contacts", TrusteeDataContact), ("accountBalances", TrusteeDataAccountBalance), ]: records = trusteeInterface.db.getRecordset( model, recordFilter={"featureInstanceId": featureInstanceId} ) counts[label] = len(records) if records else 0 return counts