# Copyright (c) 2025 Patrick Motsch # All rights reserved. """Trustee models: TrusteeOrganisation, TrusteeRole, TrusteeAccess, TrusteeContract, TrusteeDocument, TrusteePosition, TrusteePositionDocument.""" 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 TrusteeDocument(BaseModel): """Contains document references and receipts for bookings. 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 } ) documentData: Optional[bytes] = Field( default=None, description="The file content (binary)", json_schema_extra={ "frontend_type": "file", "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" } ) 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 } ) # System attributes are automatically set by DatabaseConnector registerModelLabels( "TrusteeDocument", {"en": "Document", "fr": "Document", "de": "Dokument"}, { "id": {"en": "ID", "fr": "ID", "de": "ID"}, "documentData": {"en": "Document Data", "fr": "Données du document", "de": "Dokumentdaten"}, "documentName": {"en": "Document Name", "fr": "Nom du document", "de": "Dokumentname"}, "documentMimeType": {"en": "MIME Type", "fr": "Type MIME", "de": "MIME-Typ"}, "mandateId": {"en": "Mandate", "fr": "Mandat", "de": "Mandat"}, "featureInstanceId": {"en": "Feature Instance", "fr": "Instance de fonctionnalité", "de": "Feature-Instanz"}, }, ) class TrusteePosition(BaseModel): """Contains booking positions (expense entries). 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 position ID", json_schema_extra={ "frontend_type": "text", "frontend_readonly": True, "frontend_required": False } ) 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 } ) 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 } ) # System attributes are automatically set by DatabaseConnector registerModelLabels( "TrusteePosition", {"en": "Position", "fr": "Position", "de": "Position"}, { "id": {"en": "ID", "fr": "ID", "de": "ID"}, "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"}, "mandateId": {"en": "Mandate", "fr": "Mandat", "de": "Mandat"}, "featureInstanceId": {"en": "Feature Instance", "fr": "Instance de fonctionnalité", "de": "Feature-Instanz"}, }, ) class TrusteePositionDocument(BaseModel): """Cross-reference table linking positions to documents (many-to-many). 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 link ID", json_schema_extra={ "frontend_type": "text", "frontend_readonly": True, "frontend_required": False } ) documentId: str = Field( description="Reference to TrusteeDocument.id", json_schema_extra={ "frontend_type": "select", "frontend_readonly": False, "frontend_required": True, "frontend_options": "/api/trustee/{instanceId}/documents/options" } ) positionId: str = Field( description="Reference to TrusteePosition.id", json_schema_extra={ "frontend_type": "select", "frontend_readonly": False, "frontend_required": True, "frontend_options": "/api/trustee/{instanceId}/positions/options" } ) 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 } ) # System attributes are automatically set by DatabaseConnector registerModelLabels( "TrusteePositionDocument", {"en": "Position-Document Link", "fr": "Lien Position-Document", "de": "Position-Dokument-Verknüpfung"}, { "id": {"en": "ID", "fr": "ID", "de": "ID"}, "documentId": {"en": "Document", "fr": "Document", "de": "Dokument"}, "positionId": {"en": "Position", "fr": "Position", "de": "Position"}, "mandateId": {"en": "Mandate", "fr": "Mandat", "de": "Mandat"}, "featureInstanceId": {"en": "Feature Instance", "fr": "Instance de fonctionnalité", "de": "Feature-Instanz"}, }, )