gateway/modules/features/trustee/datamodelFeatureTrustee.py
patrick-motsch f8137e857a Add document type, payment data and detailed descriptions to trustee positions
Extend TrusteePosition with documentType, payeeIban, payeeName, payeeBic, paymentReference and dueDate fields. Overhaul AI extraction prompts to capture full document details in desc (line items, addresses, conditions), extract QR/ESR payment references and IBAN from invoices, and tag each record with its documentType. Sanitise and normalise all new fields on write.

Made-with: Cursor
2026-03-01 10:14:46 +01:00

811 lines
32 KiB
Python

# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""Trustee models: TrusteeOrganisation, TrusteeRole, TrusteeAccess, TrusteeContract, TrusteeDocument, TrusteePosition."""
from enum import Enum
from typing import Optional
from pydantic import BaseModel, Field
from modules.shared.attributeUtils import registerModelLabels
import uuid
class TrusteeOrganisation(BaseModel):
"""Represents trustee organisations (companies) within the Trustee feature."""
id: str = Field( # Unique string label (PK), not UUID
description="Unique organisation identifier (label)",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": False, # Editable at creation, then readonly
"frontend_required": True
}
)
label: str = Field(
description="Company name",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": False,
"frontend_required": True
}
)
enabled: bool = Field(
default=True,
description="Whether the organisation is enabled",
json_schema_extra={
"frontend_type": "checkbox",
"frontend_readonly": False,
"frontend_required": False
}
)
mandateId: Optional[str] = Field(
default=None,
description="Mandate ID (system-level organisation)",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": True,
"frontend_required": False
}
)
featureInstanceId: Optional[str] = Field(
default=None,
description="Feature Instance ID for instance-level isolation",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": True,
"frontend_required": False
}
)
# System attributes are automatically set by DatabaseConnector:
# _createdAt, _modifiedAt, _createdBy, _modifiedBy
registerModelLabels(
"TrusteeOrganisation",
{"en": "Organisation", "fr": "Organisation", "de": "Organisation"},
{
"id": {"en": "ID", "fr": "ID", "de": "ID"},
"label": {"en": "Label", "fr": "Libellé", "de": "Bezeichnung"},
"enabled": {"en": "Enabled", "fr": "Activé", "de": "Aktiviert"},
"mandateId": {"en": "Mandate", "fr": "Mandat", "de": "Mandat"},
"featureInstanceId": {"en": "Feature Instance", "fr": "Instance de fonctionnalité", "de": "Feature-Instanz"},
},
)
class TrusteeRole(BaseModel):
"""Defines roles within the Trustee feature."""
id: str = Field( # Unique string label (PK), not UUID
description="Unique role identifier (label)",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": False,
"frontend_required": True
}
)
desc: str = Field(
description="Role description",
json_schema_extra={
"frontend_type": "textarea",
"frontend_readonly": False,
"frontend_required": True
}
)
mandateId: Optional[str] = Field(
default=None,
description="Mandate ID",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": True,
"frontend_required": False
}
)
featureInstanceId: Optional[str] = Field(
default=None,
description="Feature Instance ID for instance-level isolation",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": True,
"frontend_required": False
}
)
# System attributes are automatically set by DatabaseConnector
registerModelLabels(
"TrusteeRole",
{"en": "Role", "fr": "Rôle", "de": "Rolle"},
{
"id": {"en": "ID", "fr": "ID", "de": "ID"},
"desc": {"en": "Description", "fr": "Description", "de": "Beschreibung"},
"mandateId": {"en": "Mandate", "fr": "Mandat", "de": "Mandat"},
"featureInstanceId": {"en": "Feature Instance", "fr": "Instance de fonctionnalité", "de": "Feature-Instanz"},
},
)
class TrusteeAccess(BaseModel):
"""Defines user access to organisations with specific roles."""
id: str = Field(
default_factory=lambda: str(uuid.uuid4()),
description="Unique access ID",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": True,
"frontend_required": False
}
)
organisationId: str = Field(
description="Reference to TrusteeOrganisation.id",
json_schema_extra={
"frontend_type": "select",
"frontend_readonly": False,
"frontend_required": True,
"frontend_options": "/api/trustee/{instanceId}/organisations/options"
}
)
roleId: str = Field(
description="Reference to TrusteeRole.id",
json_schema_extra={
"frontend_type": "select",
"frontend_readonly": False,
"frontend_required": True,
"frontend_options": "/api/trustee/{instanceId}/roles/options"
}
)
userId: str = Field(
description="User ID assigned to this role",
json_schema_extra={
"frontend_type": "select",
"frontend_readonly": False,
"frontend_required": True,
"frontend_options": "/api/users/options"
}
)
contractId: Optional[str] = Field(
default=None,
description="Optional reference to TrusteeContract.id. If None, access is for full organisation. If set, access is limited to this specific contract.",
json_schema_extra={
"frontend_type": "select",
"frontend_readonly": False,
"frontend_required": False,
"frontend_options": "/api/trustee/{instanceId}/contracts/options",
"frontend_depends_on": "organisationId"
}
)
mandateId: Optional[str] = Field(
default=None,
description="Mandate ID",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": True,
"frontend_required": False
}
)
featureInstanceId: Optional[str] = Field(
default=None,
description="Feature Instance ID for instance-level isolation",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": True,
"frontend_required": False
}
)
# System attributes are automatically set by DatabaseConnector
registerModelLabels(
"TrusteeAccess",
{"en": "Access", "fr": "Accès", "de": "Zugriff"},
{
"id": {"en": "ID", "fr": "ID", "de": "ID"},
"organisationId": {"en": "Organisation", "fr": "Organisation", "de": "Organisation"},
"roleId": {"en": "Role", "fr": "Rôle", "de": "Rolle"},
"userId": {"en": "User", "fr": "Utilisateur", "de": "Benutzer"},
"contractId": {"en": "Contract (optional)", "fr": "Contrat (optionnel)", "de": "Vertrag (optional)"},
"mandateId": {"en": "Mandate", "fr": "Mandat", "de": "Mandat"},
"featureInstanceId": {"en": "Feature Instance", "fr": "Instance de fonctionnalité", "de": "Feature-Instanz"},
},
)
class TrusteeContract(BaseModel):
"""Defines customer contracts within organisations."""
id: str = Field(
default_factory=lambda: str(uuid.uuid4()),
description="Unique contract ID",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": True,
"frontend_required": False
}
)
organisationId: str = Field(
description="Reference to TrusteeOrganisation.id (immutable after creation)",
json_schema_extra={
"frontend_type": "select",
"frontend_readonly": False, # Editable at creation, then readonly
"frontend_required": True,
"frontend_options": "/api/trustee/{instanceId}/organisations/options"
}
)
label: str = Field(
description="Label for the customer contract (e.g., 'Muster AG 2026')",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": False,
"frontend_required": True
}
)
enabled: bool = Field(
default=True,
description="Whether the contract is enabled",
json_schema_extra={
"frontend_type": "checkbox",
"frontend_readonly": False,
"frontend_required": False
}
)
mandateId: Optional[str] = Field(
default=None,
description="Mandate ID",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": True,
"frontend_required": False
}
)
featureInstanceId: Optional[str] = Field(
default=None,
description="Feature Instance ID for instance-level isolation",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": True,
"frontend_required": False
}
)
# System attributes are automatically set by DatabaseConnector
registerModelLabels(
"TrusteeContract",
{"en": "Contract", "fr": "Contrat", "de": "Vertrag"},
{
"id": {"en": "ID", "fr": "ID", "de": "ID"},
"organisationId": {"en": "Organisation", "fr": "Organisation", "de": "Organisation"},
"label": {"en": "Label", "fr": "Libellé", "de": "Bezeichnung"},
"enabled": {"en": "Enabled", "fr": "Activé", "de": "Aktiviert"},
"mandateId": {"en": "Mandate", "fr": "Mandat", "de": "Mandat"},
"featureInstanceId": {"en": "Feature Instance", "fr": "Instance de fonctionnalité", "de": "Feature-Instanz"},
},
)
class TrusteeDocumentTypeEnum(str, Enum):
"""Document type for trustee documents (expense extraction, ingest, sync)."""
INVOICE = "invoice"
EXPENSE_RECEIPT = "expense_receipt"
BANK_DOCUMENT = "bank_document"
CONTRACT = "contract"
UNKNOWN = "unknown"
AUTO = "auto"
class TrusteeDocument(BaseModel):
"""Contains document references for bookings.
Documents reference files in the central Files table via fileId.
This allows file content to be stored once and referenced by multiple features.
Note: organisationId and contractId removed as per architecture decision:
- The feature instance IS the organisation
- Contracts are eliminated from the model
"""
id: str = Field(
default_factory=lambda: str(uuid.uuid4()),
description="Unique document ID",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": True,
"frontend_required": False
}
)
fileId: Optional[str] = Field(
default=None,
description="Reference to central Files table (Files.id)",
json_schema_extra={
"frontend_type": "file_reference",
"frontend_readonly": False,
"frontend_required": False
}
)
documentName: str = Field(
description="File name (e.g., 'Beleg.pdf')",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": False,
"frontend_required": True
}
)
documentMimeType: str = Field(
default="application/octet-stream",
description="MIME type of the document",
json_schema_extra={
"frontend_type": "select",
"frontend_readonly": False,
"frontend_required": True,
"frontend_options": "/api/trustee/mime-types/options"
}
)
sourceType: Optional[str] = Field(
default=None,
description="Source type (e.g., 'sharepoint', 'upload', 'email')",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": True,
"frontend_required": False
}
)
sourceLocation: Optional[str] = Field(
default=None,
description="Original source location (e.g., SharePoint path)",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": True,
"frontend_required": False
}
)
mandateId: Optional[str] = Field(
default=None,
description="Mandate ID (auto-set from context)",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": True,
"frontend_required": False,
"frontend_hidden": True
}
)
featureInstanceId: Optional[str] = Field(
default=None,
description="Feature Instance ID for instance-level isolation (auto-set from context)",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": True,
"frontend_required": False,
"frontend_hidden": True
}
)
documentType: Optional[str] = Field(
default=None,
description="Document type (e.g. invoice, expense_receipt, bank_document, contract); use TrusteeDocumentTypeEnum values",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": False,
"frontend_required": False
}
)
externalBelegId: Optional[str] = Field(
default=None,
description="External Beleg-ID in accounting system (e.g. RMA); set on first successful upload, reused on re-sync",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": True,
"frontend_required": False,
"frontend_hidden": True
}
)
# System attributes are automatically set by DatabaseConnector
registerModelLabels(
"TrusteeDocument",
{"en": "Document", "fr": "Document", "de": "Dokument"},
{
"id": {"en": "ID", "fr": "ID", "de": "ID"},
"fileId": {"en": "File Reference", "fr": "Référence du fichier", "de": "Datei-Referenz"},
"documentName": {"en": "Document Name", "fr": "Nom du document", "de": "Dokumentname"},
"documentMimeType": {"en": "MIME Type", "fr": "Type MIME", "de": "MIME-Typ"},
"sourceType": {"en": "Source Type", "fr": "Type de source", "de": "Quelltyp"},
"sourceLocation": {"en": "Source Location", "fr": "Emplacement source", "de": "Quellort"},
"mandateId": {"en": "Mandate", "fr": "Mandat", "de": "Mandat"},
"featureInstanceId": {"en": "Feature Instance", "fr": "Instance de fonctionnalité", "de": "Feature-Instanz"},
"documentType": {"en": "Document Type", "fr": "Type de document", "de": "Dokumenttyp"},
"externalBelegId": {"en": "Beleg ID (Accounting)", "fr": "ID Beleg (Comptabilité)", "de": "Beleg-ID (Buchhaltung)"},
},
)
class TrusteePosition(BaseModel):
"""Contains booking positions (expense entries).
A position can have up to two document references: documentId (Beleg) and bankDocumentId (Bank-Referenz).
One document (e.g. bank statement) can generate many positions.
"""
id: str = Field(
default_factory=lambda: str(uuid.uuid4()),
description="Unique position ID",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": True,
"frontend_required": False
}
)
documentId: Optional[str] = Field(
default=None,
description="Reference to TrusteeDocument.id (Beleg / primary document)",
json_schema_extra={
"frontend_type": "select",
"frontend_readonly": False,
"frontend_required": False,
"frontend_options": "/api/trustee/{instanceId}/documents/options"
}
)
bankDocumentId: Optional[str] = Field(
default=None,
description="Reference to TrusteeDocument.id (Bank-Referenz / second document)",
json_schema_extra={
"frontend_type": "select",
"frontend_readonly": False,
"frontend_required": False,
"frontend_options": "/api/trustee/{instanceId}/documents/options"
}
)
valuta: Optional[str] = Field(
default=None,
description="Value date (ISO format: YYYY-MM-DD)",
json_schema_extra={
"frontend_type": "date",
"frontend_readonly": False,
"frontend_required": True
}
)
transactionDateTime: Optional[float] = Field(
default=None,
description="Transaction timestamp (UTC timestamp in seconds)",
json_schema_extra={
"frontend_type": "timestamp",
"frontend_readonly": False,
"frontend_required": True
}
)
company: str = Field(
default="",
description="Company name",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": False,
"frontend_required": False
}
)
desc: str = Field(
default="",
description="Description",
json_schema_extra={
"frontend_type": "textarea",
"frontend_readonly": False,
"frontend_required": False
}
)
tags: str = Field(
default="",
description="Tags (comma-separated)",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": False,
"frontend_required": False
}
)
bookingCurrency: str = Field(
default="CHF",
description="Booking currency code",
json_schema_extra={
"frontend_type": "select",
"frontend_readonly": False,
"frontend_required": True,
"frontend_options": [
{"value": "CHF", "label": {"en": "CHF", "fr": "CHF", "de": "CHF"}},
{"value": "EUR", "label": {"en": "EUR", "fr": "EUR", "de": "EUR"}},
{"value": "USD", "label": {"en": "USD", "fr": "USD", "de": "USD"}},
{"value": "GBP", "label": {"en": "GBP", "fr": "GBP", "de": "GBP"}},
]
}
)
bookingAmount: float = Field(
default=0.0,
description="Booking amount",
json_schema_extra={
"frontend_type": "number",
"frontend_readonly": False,
"frontend_required": True
}
)
originalCurrency: str = Field(
default="CHF",
description="Original currency code",
json_schema_extra={
"frontend_type": "select",
"frontend_readonly": False,
"frontend_required": True,
"frontend_options": [
{"value": "CHF", "label": {"en": "CHF", "fr": "CHF", "de": "CHF"}},
{"value": "EUR", "label": {"en": "EUR", "fr": "EUR", "de": "EUR"}},
{"value": "USD", "label": {"en": "USD", "fr": "USD", "de": "USD"}},
{"value": "GBP", "label": {"en": "GBP", "fr": "GBP", "de": "GBP"}},
]
}
)
originalAmount: float = Field(
default=0.0,
description="Original amount (manual input, no automatic currency conversion)",
json_schema_extra={
"frontend_type": "number",
"frontend_readonly": False,
"frontend_required": True
}
)
vatPercentage: float = Field(
default=0.0,
description="VAT percentage",
json_schema_extra={
"frontend_type": "number",
"frontend_readonly": False,
"frontend_required": False
}
)
vatAmount: float = Field(
default=0.0,
description="VAT amount (calculated: bookingAmount * vatPercentage / 100, can be manually overridden)",
json_schema_extra={
"frontend_type": "number",
"frontend_readonly": False,
"frontend_required": False
}
)
debitAccountNumber: Optional[str] = Field(
default=None,
description="Debit account number (e.g. '4200' for expenses)",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": False,
"frontend_required": False
}
)
creditAccountNumber: Optional[str] = Field(
default=None,
description="Credit account number (e.g. '1020' for bank)",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": False,
"frontend_required": False
}
)
taxCode: Optional[str] = Field(
default=None,
description="Tax code for the accounting system",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": False,
"frontend_required": False
}
)
costCenter: Optional[str] = Field(
default=None,
description="Cost center identifier",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": False,
"frontend_required": False
}
)
bookingReference: Optional[str] = Field(
default=None,
description="Booking reference (e.g. voucher number)",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": False,
"frontend_required": False
}
)
documentType: Optional[str] = Field(
default=None,
description="Document type that generated this position (invoice, expense_receipt, bank_document, contract, unknown)",
json_schema_extra={
"frontend_type": "select",
"frontend_readonly": True,
"frontend_required": False,
"frontend_options": [
{"value": "invoice", "label": {"en": "Invoice", "fr": "Facture", "de": "Rechnung"}},
{"value": "expense_receipt", "label": {"en": "Expense Receipt", "fr": "Reçu", "de": "Beleg"}},
{"value": "bank_document", "label": {"en": "Bank Statement", "fr": "Relevé bancaire", "de": "Bankauszug"}},
{"value": "contract", "label": {"en": "Contract", "fr": "Contrat", "de": "Vertrag"}},
{"value": "unknown", "label": {"en": "Other", "fr": "Autre", "de": "Sonstige"}},
]
}
)
payeeIban: Optional[str] = Field(
default=None,
description="IBAN of the payment recipient (from invoice / QR code)",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": False,
"frontend_required": False
}
)
payeeName: Optional[str] = Field(
default=None,
description="Bank or account holder name of the payment recipient",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": False,
"frontend_required": False
}
)
payeeBic: Optional[str] = Field(
default=None,
description="BIC / SWIFT code of the recipient bank",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": False,
"frontend_required": False
}
)
paymentReference: Optional[str] = Field(
default=None,
description="Structured payment reference (QR-Referenz, ESR, SCOR, Mitteilung)",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": False,
"frontend_required": False
}
)
dueDate: Optional[str] = Field(
default=None,
description="Payment due date (ISO format: YYYY-MM-DD)",
json_schema_extra={
"frontend_type": "date",
"frontend_readonly": False,
"frontend_required": False
}
)
mandateId: Optional[str] = Field(
default=None,
description="Mandate ID (auto-set from context)",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": True,
"frontend_required": False,
"frontend_hidden": True
}
)
featureInstanceId: Optional[str] = Field(
default=None,
description="Feature Instance ID for instance-level isolation (auto-set from context)",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": True,
"frontend_required": False,
"frontend_hidden": True
}
)
accountingSyncId: Optional[str] = Field(
default=None,
description="External ID (UUID) of the synced record in the accounting system; set by sync, used for duplicate check",
json_schema_extra={
"frontend_type": "text",
"frontend_readonly": True,
"frontend_required": False,
"frontend_hidden": True
}
)
# Allow extra fields like _createdAt from database
model_config = {"extra": "allow"}
registerModelLabels(
"TrusteePosition",
{"en": "Position", "fr": "Position", "de": "Position"},
{
"id": {"en": "ID", "fr": "ID", "de": "ID"},
"documentId": {"en": "Document", "fr": "Document", "de": "Dokument"},
"bankDocumentId": {"en": "Bank Reference", "fr": "Référence bancaire", "de": "Bank-Referenz"},
"valuta": {"en": "Value Date", "fr": "Date de valeur", "de": "Valutadatum"},
"transactionDateTime": {"en": "Transaction Date/Time", "fr": "Date/Heure de transaction", "de": "Transaktionszeitpunkt"},
"company": {"en": "Company", "fr": "Entreprise", "de": "Firma"},
"desc": {"en": "Description", "fr": "Description", "de": "Beschreibung"},
"tags": {"en": "Tags", "fr": "Tags", "de": "Tags"},
"bookingCurrency": {"en": "Booking Currency", "fr": "Devise de comptabilisation", "de": "Buchungswährung"},
"bookingAmount": {"en": "Booking Amount", "fr": "Montant de comptabilisation", "de": "Buchungsbetrag"},
"originalCurrency": {"en": "Original Currency", "fr": "Devise d'origine", "de": "Originalwährung"},
"originalAmount": {"en": "Original Amount", "fr": "Montant d'origine", "de": "Originalbetrag"},
"vatPercentage": {"en": "VAT Percentage", "fr": "Pourcentage TVA", "de": "MwSt-Prozentsatz"},
"vatAmount": {"en": "VAT Amount", "fr": "Montant TVA", "de": "MwSt-Betrag"},
"debitAccountNumber": {"en": "Debit Account", "fr": "Compte débit", "de": "Soll-Konto"},
"creditAccountNumber": {"en": "Credit Account", "fr": "Compte crédit", "de": "Haben-Konto"},
"taxCode": {"en": "Tax Code", "fr": "Code TVA", "de": "Steuercode"},
"costCenter": {"en": "Cost Center", "fr": "Centre de coûts", "de": "Kostenstelle"},
"bookingReference": {"en": "Booking Reference", "fr": "Référence de réservation", "de": "Buchungsreferenz"},
"documentType": {"en": "Document Type", "fr": "Type de document", "de": "Dokumenttyp"},
"payeeIban": {"en": "Payee IBAN", "fr": "IBAN bénéficiaire", "de": "Empfänger-IBAN"},
"payeeName": {"en": "Payee Name", "fr": "Nom du bénéficiaire", "de": "Empfänger-Name"},
"payeeBic": {"en": "Payee BIC/SWIFT", "fr": "BIC/SWIFT bénéficiaire", "de": "Empfänger-BIC"},
"paymentReference": {"en": "Payment Reference", "fr": "Référence de paiement", "de": "Zahlungsreferenz"},
"dueDate": {"en": "Due Date", "fr": "Date d'échéance", "de": "Fälligkeitsdatum"},
"mandateId": {"en": "Mandate", "fr": "Mandat", "de": "Mandat"},
"featureInstanceId": {"en": "Feature Instance", "fr": "Instance de fonctionnalité", "de": "Feature-Instanz"},
"accountingSyncId": {"en": "Accounting Sync ID", "fr": "ID sync comptabilité", "de": "Buha-Sync-ID"},
},
)
class TrusteeAccountingConfig(BaseModel):
"""Per-instance accounting system configuration with encrypted credentials.
Each feature instance can connect to exactly one accounting system.
Credentials are stored encrypted (decrypted at runtime by the AccountingBridge).
"""
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
featureInstanceId: str = Field(description="FK -> FeatureInstance.id (1:1)")
connectorType: str = Field(description="Connector type key, e.g. 'rma', 'bexio', 'abacus'")
displayLabel: str = Field(default="", description="User-visible label for this integration")
encryptedConfig: str = Field(default="", description="Encrypted JSON blob with connector credentials")
isActive: bool = Field(default=True)
lastSyncAt: Optional[float] = Field(default=None, description="Timestamp of last sync attempt")
lastSyncStatus: Optional[str] = Field(default=None, description="Last sync result: success, error, partial")
lastSyncErrorMessage: Optional[str] = Field(default=None, description="Error message when lastSyncStatus is error")
cachedChartOfAccounts: Optional[str] = Field(default=None, description="JSON-serialised chart of accounts cache (list of {accountNumber, label, accountType})")
chartCachedAt: Optional[float] = Field(default=None, description="Timestamp when cachedChartOfAccounts was last refreshed")
mandateId: Optional[str] = Field(default=None)
registerModelLabels(
"TrusteeAccountingConfig",
{"en": "Accounting Configuration", "de": "Buchhaltungs-Konfiguration", "fr": "Configuration comptable"},
{
"id": {"en": "ID", "fr": "ID", "de": "ID"},
"featureInstanceId": {"en": "Feature Instance", "fr": "Instance", "de": "Feature-Instanz"},
"connectorType": {"en": "System", "fr": "Système", "de": "System"},
"displayLabel": {"en": "Label", "fr": "Libellé", "de": "Bezeichnung"},
"isActive": {"en": "Active", "fr": "Actif", "de": "Aktiv"},
"lastSyncAt": {"en": "Last Sync", "fr": "Dernière sync.", "de": "Letzte Synchronisation"},
"lastSyncStatus": {"en": "Status", "fr": "Statut", "de": "Status"},
"lastSyncErrorMessage": {"en": "Error", "fr": "Erreur", "de": "Fehlermeldung"},
"cachedChartOfAccounts": {"en": "Cached Chart", "de": "Cached Kontoplan", "fr": "Plan comptable en cache"},
"chartCachedAt": {"en": "Chart Cached At", "de": "Kontoplan-Cache-Zeitpunkt", "fr": "Horodatage cache plan comptable"},
"mandateId": {"en": "Mandate", "fr": "Mandat", "de": "Mandat"},
},
)
class TrusteeAccountingSync(BaseModel):
"""Tracks which position was synced to which external system and when.
Used for duplicate prevention, audit trail, and retry logic.
"""
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
positionId: str = Field(description="FK -> TrusteePosition.id")
featureInstanceId: str = Field(description="FK -> FeatureInstance.id")
connectorType: str = Field(description="Connector type at time of sync")
externalId: Optional[str] = Field(default=None, description="ID assigned by the external system")
externalReference: Optional[str] = Field(default=None, description="Reference in the external system")
syncStatus: str = Field(default="pending", description="pending | synced | error | cancelled")
syncDirection: str = Field(default="push", description="push (local->ext) or pull (ext->local)")
syncedAt: Optional[float] = Field(default=None, description="Timestamp of successful sync")
errorMessage: Optional[str] = Field(default=None)
bookingPayload: Optional[dict] = Field(default=None, description="Payload sent to the external system (audit)")
mandateId: Optional[str] = Field(default=None)
registerModelLabels(
"TrusteeAccountingSync",
{"en": "Accounting Sync", "de": "Buchhaltungs-Synchronisation", "fr": "Synchronisation comptable"},
{
"id": {"en": "ID", "fr": "ID", "de": "ID"},
"positionId": {"en": "Position", "fr": "Position", "de": "Position"},
"connectorType": {"en": "System", "fr": "Système", "de": "System"},
"externalId": {"en": "External ID", "fr": "ID Externe", "de": "Externe ID"},
"syncStatus": {"en": "Status", "fr": "Statut", "de": "Status"},
"syncDirection": {"en": "Direction", "fr": "Direction", "de": "Richtung"},
"syncedAt": {"en": "Synced At", "fr": "Synchronisé à", "de": "Synchronisiert am"},
"errorMessage": {"en": "Error", "fr": "Erreur", "de": "Fehler"},
"mandateId": {"en": "Mandate", "fr": "Mandat", "de": "Mandat"},
},
)