# 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.datamodels.datamodelBase import PowerOnModel from modules.shared.attributeUtils import registerModelLabels import uuid class TrusteeOrganisation(PowerOnModel): """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: # sysCreatedAt, sysModifiedAt, sysCreatedBy, sysModifiedBy (PowerOnModel) 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(PowerOnModel): """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(PowerOnModel): """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(PowerOnModel): """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(PowerOnModel): """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(PowerOnModel): """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 } ) 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"}, }, ) # ── TrusteeData* tables (synced from external accounting apps for analysis) ── class TrusteeDataAccount(PowerOnModel): """Chart of accounts synced from external accounting system.""" id: str = Field(default_factory=lambda: str(uuid.uuid4())) accountNumber: str = Field(description="Account number (e.g. '1020')") label: str = Field(default="", description="Account name") accountType: Optional[str] = Field(default=None, description="asset / liability / equity / revenue / expense") accountGroup: Optional[str] = Field(default=None, description="Account group/category") currency: str = Field(default="CHF", description="Account currency") isActive: bool = Field(default=True) mandateId: Optional[str] = Field(default=None) featureInstanceId: Optional[str] = Field(default=None) registerModelLabels( "TrusteeDataAccount", {"en": "Account (Synced)", "de": "Konto (Sync)", "fr": "Compte (Sync)"}, { "id": {"en": "ID", "de": "ID", "fr": "ID"}, "accountNumber": {"en": "Account Number", "de": "Kontonummer", "fr": "Numéro de compte"}, "label": {"en": "Name", "de": "Bezeichnung", "fr": "Libellé"}, "accountType": {"en": "Type", "de": "Typ", "fr": "Type"}, "accountGroup": {"en": "Group", "de": "Gruppe", "fr": "Groupe"}, "currency": {"en": "Currency", "de": "Währung", "fr": "Devise"}, "isActive": {"en": "Active", "de": "Aktiv", "fr": "Actif"}, "mandateId": {"en": "Mandate", "de": "Mandat", "fr": "Mandat"}, "featureInstanceId": {"en": "Feature Instance", "de": "Feature-Instanz", "fr": "Instance"}, }, ) class TrusteeDataJournalEntry(PowerOnModel): """Journal entry header synced from external accounting system.""" id: str = Field(default_factory=lambda: str(uuid.uuid4())) externalId: Optional[str] = Field(default=None, description="ID in the source system") bookingDate: Optional[str] = Field(default=None, description="Booking date (YYYY-MM-DD)") reference: Optional[str] = Field(default=None, description="Booking reference / voucher number") description: str = Field(default="", description="Booking text") currency: str = Field(default="CHF") totalAmount: float = Field(default=0.0, description="Total amount of entry") mandateId: Optional[str] = Field(default=None) featureInstanceId: Optional[str] = Field(default=None) registerModelLabels( "TrusteeDataJournalEntry", {"en": "Journal Entry (Synced)", "de": "Buchung (Sync)", "fr": "Écriture (Sync)"}, { "id": {"en": "ID", "de": "ID", "fr": "ID"}, "externalId": {"en": "External ID", "de": "Externe ID", "fr": "ID externe"}, "bookingDate": {"en": "Date", "de": "Datum", "fr": "Date"}, "reference": {"en": "Reference", "de": "Referenz", "fr": "Référence"}, "description": {"en": "Description", "de": "Beschreibung", "fr": "Description"}, "currency": {"en": "Currency", "de": "Währung", "fr": "Devise"}, "totalAmount": {"en": "Amount", "de": "Betrag", "fr": "Montant"}, "mandateId": {"en": "Mandate", "de": "Mandat", "fr": "Mandat"}, "featureInstanceId": {"en": "Feature Instance", "de": "Feature-Instanz", "fr": "Instance"}, }, ) class TrusteeDataJournalLine(PowerOnModel): """Journal entry line (debit/credit) synced from external accounting system.""" id: str = Field(default_factory=lambda: str(uuid.uuid4())) journalEntryId: str = Field(description="FK → TrusteeDataJournalEntry.id") accountNumber: str = Field(description="Account number") debitAmount: float = Field(default=0.0) creditAmount: float = Field(default=0.0) currency: str = Field(default="CHF") taxCode: Optional[str] = Field(default=None) costCenter: Optional[str] = Field(default=None) description: str = Field(default="") mandateId: Optional[str] = Field(default=None) featureInstanceId: Optional[str] = Field(default=None) registerModelLabels( "TrusteeDataJournalLine", {"en": "Journal Line (Synced)", "de": "Buchungszeile (Sync)", "fr": "Ligne écriture (Sync)"}, { "id": {"en": "ID", "de": "ID", "fr": "ID"}, "journalEntryId": {"en": "Journal Entry", "de": "Buchung", "fr": "Écriture"}, "accountNumber": {"en": "Account", "de": "Konto", "fr": "Compte"}, "debitAmount": {"en": "Debit", "de": "Soll", "fr": "Débit"}, "creditAmount": {"en": "Credit", "de": "Haben", "fr": "Crédit"}, "currency": {"en": "Currency", "de": "Währung", "fr": "Devise"}, "taxCode": {"en": "Tax Code", "de": "Steuercode", "fr": "Code TVA"}, "costCenter": {"en": "Cost Center", "de": "Kostenstelle", "fr": "Centre de coûts"}, "description": {"en": "Description", "de": "Beschreibung", "fr": "Description"}, "mandateId": {"en": "Mandate", "de": "Mandat", "fr": "Mandat"}, "featureInstanceId": {"en": "Feature Instance", "de": "Feature-Instanz", "fr": "Instance"}, }, ) class TrusteeDataContact(PowerOnModel): """Customer or vendor synced from external accounting system.""" id: str = Field(default_factory=lambda: str(uuid.uuid4())) externalId: Optional[str] = Field(default=None, description="ID in the source system") contactType: str = Field(default="customer", description="customer / vendor / both") contactNumber: Optional[str] = Field(default=None, description="Customer/vendor number") name: str = Field(default="", description="Name / company") address: Optional[str] = Field(default=None) zip: Optional[str] = Field(default=None) city: Optional[str] = Field(default=None) country: Optional[str] = Field(default=None) email: Optional[str] = Field(default=None) phone: Optional[str] = Field(default=None) vatNumber: Optional[str] = Field(default=None) mandateId: Optional[str] = Field(default=None) featureInstanceId: Optional[str] = Field(default=None) registerModelLabels( "TrusteeDataContact", {"en": "Contact (Synced)", "de": "Kontakt (Sync)", "fr": "Contact (Sync)"}, { "id": {"en": "ID", "de": "ID", "fr": "ID"}, "externalId": {"en": "External ID", "de": "Externe ID", "fr": "ID externe"}, "contactType": {"en": "Type", "de": "Typ", "fr": "Type"}, "contactNumber": {"en": "Number", "de": "Nummer", "fr": "Numéro"}, "name": {"en": "Name", "de": "Name", "fr": "Nom"}, "address": {"en": "Address", "de": "Adresse", "fr": "Adresse"}, "zip": {"en": "ZIP", "de": "PLZ", "fr": "NPA"}, "city": {"en": "City", "de": "Ort", "fr": "Ville"}, "country": {"en": "Country", "de": "Land", "fr": "Pays"}, "email": {"en": "Email", "de": "E-Mail", "fr": "E-mail"}, "phone": {"en": "Phone", "de": "Telefon", "fr": "Téléphone"}, "vatNumber": {"en": "VAT Number", "de": "MWST-Nr.", "fr": "N° TVA"}, "mandateId": {"en": "Mandate", "de": "Mandat", "fr": "Mandat"}, "featureInstanceId": {"en": "Feature Instance", "de": "Feature-Instanz", "fr": "Instance"}, }, ) class TrusteeDataAccountBalance(PowerOnModel): """Account balance per period, derived from journal lines or directly from accounting system.""" id: str = Field(default_factory=lambda: str(uuid.uuid4())) accountNumber: str = Field(description="Account number") periodYear: int = Field(description="Fiscal year") periodMonth: int = Field(default=0, description="Month (1-12); 0 = annual total") openingBalance: float = Field(default=0.0) debitTotal: float = Field(default=0.0) creditTotal: float = Field(default=0.0) closingBalance: float = Field(default=0.0) currency: str = Field(default="CHF") mandateId: Optional[str] = Field(default=None) featureInstanceId: Optional[str] = Field(default=None) registerModelLabels( "TrusteeDataAccountBalance", {"en": "Account Balance (Synced)", "de": "Kontosaldo (Sync)", "fr": "Solde compte (Sync)"}, { "id": {"en": "ID", "de": "ID", "fr": "ID"}, "accountNumber": {"en": "Account", "de": "Konto", "fr": "Compte"}, "periodYear": {"en": "Year", "de": "Jahr", "fr": "Année"}, "periodMonth": {"en": "Month", "de": "Monat", "fr": "Mois"}, "openingBalance": {"en": "Opening Balance", "de": "Eröffnungssaldo", "fr": "Solde d'ouverture"}, "debitTotal": {"en": "Debit Total", "de": "Soll-Umsatz", "fr": "Total débit"}, "creditTotal": {"en": "Credit Total", "de": "Haben-Umsatz", "fr": "Total crédit"}, "closingBalance": {"en": "Closing Balance", "de": "Schlusssaldo", "fr": "Solde de clôture"}, "currency": {"en": "Currency", "de": "Währung", "fr": "Devise"}, "mandateId": {"en": "Mandate", "de": "Mandat", "fr": "Mandat"}, "featureInstanceId": {"en": "Feature Instance", "de": "Feature-Instanz", "fr": "Instance"}, }, ) class TrusteeAccountingConfig(PowerOnModel): """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(PowerOnModel): """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"}, }, )