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

239 lines
12 KiB
Python

# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
Trustee document workflow method: extract from files, process to positions, sync to accounting.
"""
import logging
from modules.workflows.methods.methodBase import MethodBase
from modules.datamodels.datamodelWorkflowActions import WorkflowActionDefinition, WorkflowActionParameter
from modules.shared.frontendTypes import FrontendType
from .actions.extractFromFiles import extractFromFiles
from .actions.processDocuments import processDocuments
from .actions.syncToAccounting import syncToAccounting
from .actions.refreshAccountingData import refreshAccountingData
from .actions.queryData import queryData
logger = logging.getLogger(__name__)
class MethodTrustee(MethodBase):
"""Trustee document and expense workflow: extract, process, sync to accounting."""
def __init__(self, services):
super().__init__(services)
self.name = "trustee"
self.description = "Trustee document extraction, processing and accounting sync"
self._actions = {
"extractFromFiles": WorkflowActionDefinition(
actionId="trustee.extractFromFiles",
description="Extract document type and data from PDF/JPG (fileIds or SharePoint folder)",
dynamicMode=False,
# Runtime returns ActionResult.isSuccess(documents=[...]); see
# actions/extractFromFiles.py. Keep this in sync with the
# graphical-editor adapter (nodeDefinitions/trustee.py).
outputType="ActionResult",
parameters={
"fileIds": WorkflowActionParameter(
name="fileIds",
type="List[str]",
frontendType=FrontendType.JSON,
required=False,
description="List of file IDs already in DB (alternative to connectionReference + sharepointFolder)",
),
"connectionReference": WorkflowActionParameter(
name="connectionReference",
type="ConnectionRef",
frontendType=FrontendType.USER_CONNECTION,
required=False,
description="Microsoft connection for SharePoint (use with sharepointFolder)",
),
"sharepointFolder": WorkflowActionParameter(
name="sharepointFolder",
type="str",
frontendType=FrontendType.TEXT,
required=False,
description="SharePoint folder path (e.g. /sites/MySite/Documents/Expenses)",
),
"featureInstanceId": WorkflowActionParameter(
name="featureInstanceId",
type="FeatureInstanceRef",
frontendType=FrontendType.TEXT,
required=True,
description="Trustee feature instance",
),
"prompt": WorkflowActionParameter(
name="prompt",
type="str",
uiHint="textarea",
frontendType=FrontendType.TEXTAREA,
required=False,
description="AI prompt for extraction (optional)",
),
},
execute=extractFromFiles.__get__(self, self.__class__),
),
"processDocuments": WorkflowActionDefinition(
actionId="trustee.processDocuments",
description="Create TrusteeDocument + TrusteePosition from extraction result (documentList from previous action)",
dynamicMode=False,
# Runtime returns ActionResult.isSuccess(documents=[...]).
outputType="ActionResult",
parameters={
"documentList": WorkflowActionParameter(
name="documentList",
# Concrete shape consumed by _resolveDocumentList (list
# of dicts with documentName/documentData/mimeType).
type="List[ActionDocument]",
frontendType=FrontendType.DOCUMENT_REFERENCE,
required=True,
description="DataRef to upstream documents (e.g. trustee.extractFromFiles → documents)",
),
"featureInstanceId": WorkflowActionParameter(
name="featureInstanceId",
type="FeatureInstanceRef",
frontendType=FrontendType.TEXT,
required=True,
description="Trustee feature instance",
),
},
execute=processDocuments.__get__(self, self.__class__),
),
"syncToAccounting": WorkflowActionDefinition(
actionId="trustee.syncToAccounting",
description="Push trustee positions to accounting (documentList = processDocuments result)",
dynamicMode=False,
# Runtime returns ActionResult.isSuccess(documents=[...]).
outputType="ActionResult",
parameters={
"documentList": WorkflowActionParameter(
name="documentList",
# Concrete shape consumed by syncToAccounting._resolveDocumentList:
# list of ActionDocument dicts produced by processDocuments.
type="List[ActionDocument]",
frontendType=FrontendType.DOCUMENT_REFERENCE,
required=True,
description="DataRef to upstream documents (e.g. trustee.processDocuments → documents)",
),
"featureInstanceId": WorkflowActionParameter(
name="featureInstanceId",
type="FeatureInstanceRef",
frontendType=FrontendType.TEXT,
required=True,
description="Trustee feature instance",
),
},
execute=syncToAccounting.__get__(self, self.__class__),
),
"refreshAccountingData": WorkflowActionDefinition(
actionId="trustee.refreshAccountingData",
description="Import/refresh accounting data from external system (e.g. Abacus) into local tables. Checks cache freshness; use forceRefresh to re-import.",
dynamicMode=True,
outputType="TrusteeRefreshResult",
parameters={
"featureInstanceId": WorkflowActionParameter(
name="featureInstanceId",
type="FeatureInstanceRef",
frontendType=FrontendType.TEXT,
required=True,
description="Trustee feature instance",
),
"forceRefresh": WorkflowActionParameter(
name="forceRefresh",
type="bool",
frontendType=FrontendType.CHECKBOX,
required=False,
description="Force re-import even if data is fresh (default: false)",
),
"dateFrom": WorkflowActionParameter(
name="dateFrom",
type="str",
frontendType=FrontendType.TEXT,
required=False,
description="Start date filter for journal entries (YYYY-MM-DD)",
),
"dateTo": WorkflowActionParameter(
name="dateTo",
type="str",
frontendType=FrontendType.TEXT,
required=False,
description="End date filter for journal entries (YYYY-MM-DD)",
),
},
execute=refreshAccountingData.__get__(self, self.__class__),
),
"queryData": WorkflowActionDefinition(
actionId="trustee.queryData",
description="Read data from the Trustee DB (lookup tenant+rent, raw recordset, or aggregate). Does NOT trigger an external sync.",
dynamicMode=False,
outputType="QueryResult",
parameters={
"featureInstanceId": WorkflowActionParameter(
name="featureInstanceId",
type="FeatureInstanceRef",
frontendType=FrontendType.TEXT,
required=True,
description="Trustee feature instance",
),
"mode": WorkflowActionParameter(
name="mode",
type="str",
frontendType=FrontendType.TEXT,
required=True,
description="Query mode: lookup | raw | aggregate",
),
"entity": WorkflowActionParameter(
name="entity",
type="str",
frontendType=FrontendType.TEXT,
required=True,
description="Entity to query: tenantWithRent | contact | journalLines | accounts | balances",
),
"tenantNameRef": WorkflowActionParameter(
name="tenantNameRef",
type="str",
frontendType=FrontendType.TEXT,
required=False,
description="Tenant name to match (or {{wire.field}} placeholder)",
),
"tenantAddressRef": WorkflowActionParameter(
name="tenantAddressRef",
type="str",
frontendType=FrontendType.TEXT,
required=False,
description="Tenant address to match (tolerant)",
),
"period": WorkflowActionParameter(
name="period",
type="str",
frontendType=FrontendType.TEXT,
required=False,
description="Period filter: YYYY or YYYY-MM-DD/YYYY-MM-DD",
),
"rentAccountPattern": WorkflowActionParameter(
name="rentAccountPattern",
type="str",
frontendType=FrontendType.TEXT,
required=False,
description="Account-number pattern for rent revenue (e.g. '6000-6099' or '6*')",
),
"filterJson": WorkflowActionParameter(
name="filterJson",
type="str",
frontendType=FrontendType.TEXTAREA,
required=False,
description="Optional JSON filter for mode=raw/aggregate",
),
},
execute=queryData.__get__(self, self.__class__),
),
}
self._validateActions()
self.extractFromFiles = extractFromFiles.__get__(self, self.__class__)
self.processDocuments = processDocuments.__get__(self, self.__class__)
self.syncToAccounting = syncToAccounting.__get__(self, self.__class__)
self.refreshAccountingData = refreshAccountingData.__get__(self, self.__class__)
self.queryData = queryData.__get__(self, self.__class__)