/** * Multi-Tenant Mandate Types * * Hierarchie: Mandate → Feature → Instanz → Views/Permissions * * Ein User gehört KEINEM Mandanten direkt an. * Er hat Zugriff auf Feature-Instanzen, die zu Mandanten gehören. */ // ============================================================================= // I18N // ============================================================================= export interface I18nLabel { de: string; en: string; fr?: string; } // ============================================================================= // ACCESS LEVELS // ============================================================================= /** * Access Level für CRUD-Operationen * - 'n': None - Kein Zugriff * - 'm': My - Nur eigene Datensätze * - 'g': Group - Alle Datensätze der Instanz * - 'a': All - Alle Datensätze (mandantenübergreifend) */ export type AccessLevel = 'n' | 'm' | 'g' | 'a'; // ============================================================================= // PERMISSIONS // ============================================================================= /** * Tabellen-Berechtigungen */ export interface TablePermission { view: boolean; read: AccessLevel; create: AccessLevel; update: AccessLevel; delete: AccessLevel; } /** * Feld-Berechtigungen (optional, nur wo eingeschränkt) */ export interface FieldPermission { read: boolean; write: boolean; } /** * Summarische Berechtigungen pro Feature-Instanz * Werden einmalig beim Login/Refresh geladen */ export interface InstancePermissions { // Tabellen-Level (CRUD pro Tabelle) tables: Record; // Feld-Level (nur wo eingeschränkt) fields?: Record>; // View-Level (Navigation) // Keys are view codes like "trustee-positions", values are boolean visibility // Special key "_all" means all views are visible views: Record; // Admin flag (has admin role in this instance) isAdmin?: boolean; } // ============================================================================= // FEATURE INSTANCE // ============================================================================= /** * Eine Feature-Instanz ist die Arbeitseinheit für einen User * z.B. "Trustee für PamoCreate AG bei Soha Treuhand" */ export interface FeatureInstance { id: string; // UUID der Instanz featureCode: string; // "trustee", "chatbot", "chatworkflow", etc. mandateId: string; // Zugehöriger Mandant mandateName: string; // Für Anzeige instanceLabel: string; // z.B. "PamoCreate AG" userRoles: string[]; // Rollen des Users in dieser Instanz (kann mehrere haben) permissions: InstancePermissions; } // ============================================================================= // MANDATE FEATURE // ============================================================================= /** * Ein Feature innerhalb eines Mandanten * Gruppiert alle Instanzen eines Feature-Typs */ export interface MandateFeature { code: string; // "trustee", "chatbot", "chatworkflow", etc. label: I18nLabel; // { de: "Treuhand", en: "Trustee" } icon: string; // Material/React Icon Name instances: FeatureInstance[]; } // ============================================================================= // MANDATE // ============================================================================= /** * Ein Mandant (oberste Ebene) * Enthält mehrere Features mit deren Instanzen */ export interface Mandate { id: string; // mandateId name: string; // Technischer Identifier label?: string; // Anzeige-Label (fuer FK-Referenzen und UI) code?: string; // Optionaler Code features: MandateFeature[]; } // ============================================================================= // API RESPONSE // ============================================================================= /** * Response von GET /features/my * Enthält alle für den User sichtbaren Mandate + Features + Instanzen + Permissions */ export interface FeaturesMyResponse { mandates: Mandate[]; } // ============================================================================= // USER (Ohne Mandant-Zugehörigkeit) // ============================================================================= /** * User-Daten nach Login * KEIN mandateId mehr - User arbeitet mit Feature-Instanzen */ export interface User { id: string; username: string; email: string; fullName: string; language: string; enabled: boolean; authenticationAuthority: string; isSysAdmin: boolean; roleLabels?: string[]; // System-weite Rollen (z.B. ["sysadmin"]) } // ============================================================================= // NAVIGATION // ============================================================================= /** * View-Definition für Feature-Navigation */ export interface FeatureView { code: string; // z.B. "dashboard", "contracts", "documents" label: I18nLabel; icon?: string; path: string; // Relativer Pfad innerhalb der Instanz adminOnly?: boolean; // Nur für Admin-Rollen sichtbar } /** * Feature-Konfiguration für Navigation * Definiert welche Views ein Feature hat */ export interface FeatureConfig { code: string; label: I18nLabel; icon: string; views: FeatureView[]; } // ============================================================================= // FEATURE REGISTRY (DEPRECATED) // ============================================================================= /** * @deprecated Since Navigation-API-Konzept implementation. * * Navigation is now provided by the backend via GET /api/navigation. * The backend is the Single Source of Truth for navigation structure. * * Icon mapping is now handled by src/config/pageRegistry.ts using uiComponent codes. * * This registry is kept for backward compatibility with existing code that may * still reference it. It will be removed in a future version. * * TODO: Remove after all references are migrated to use backend navigation. */ export const FEATURE_REGISTRY: Record = { trustee: { code: 'trustee', label: { de: 'Treuhand', en: 'Trustee' }, icon: 'briefcase', views: [ { code: 'dashboard', label: { de: 'Übersicht', en: 'Dashboard' }, path: 'dashboard' }, { code: 'positions', label: { de: 'Positionen', en: 'Positions' }, path: 'positions' }, { code: 'documents', label: { de: 'Dokumente', en: 'Documents' }, path: 'documents' }, { code: 'position-documents', label: { de: 'Zuordnungen', en: 'Assignments' }, path: 'position-documents' }, { code: 'expense-import', label: { de: 'Spesen Import', en: 'Expense Import' }, path: 'expense-import' }, { code: 'scan-upload', label: { de: 'Scannen / Hochladen', en: 'Scan / Upload' }, path: 'scan-upload' }, { code: 'instance-roles', label: { de: 'Rollen & Rechte', en: 'Roles & Permissions' }, path: 'instance-roles', adminOnly: true }, { code: 'settings', label: { de: 'Buchhaltungseinstellungen', en: 'Accounting Settings' }, path: 'settings' }, ] }, chatworkflow: { code: 'chatworkflow', label: { de: 'Workflow', en: 'Workflow' }, icon: 'play_circle', views: [ { code: 'dashboard', label: { de: 'Übersicht', en: 'Dashboard' }, path: 'dashboard' }, { code: 'runs', label: { de: 'Runs', en: 'Runs' }, path: 'runs' }, { code: 'files', label: { de: 'Dateien', en: 'Files' }, path: 'files' }, ] }, chatbot: { code: 'chatbot', label: { de: 'Chatbot', en: 'Chatbot' }, icon: 'chat', views: [ { code: 'conversations', label: { de: 'Konversationen', en: 'Conversations' }, path: 'conversations' }, { code: 'settings', label: { de: 'Einstellungen', en: 'Settings' }, path: 'settings' }, ] }, realestate: { code: 'realestate', label: { de: 'Immobilien', en: 'Real Estate' }, icon: 'home', views: [ { code: 'dashboard', label: { de: 'Karte', en: 'Map' }, path: 'dashboard' }, { code: 'instance-roles', label: { de: 'Rollen & Rechte', en: 'Roles & Permissions' }, path: 'instance-roles', adminOnly: true }, ] }, chatplayground: { code: 'chatplayground', label: { de: 'Chat Playground', en: 'Chat Playground' }, icon: 'message', views: [ { code: 'playground', label: { de: 'Playground', en: 'Playground' }, path: 'playground' }, { code: 'workflows', label: { de: 'Workflows', en: 'Workflows' }, path: 'workflows' }, ] }, codeeditor: { code: 'codeeditor', label: { de: 'Code Editor', en: 'Code Editor' }, icon: 'description', views: [ { code: 'editor', label: { de: 'Editor', en: 'Editor' }, path: 'editor' }, { code: 'workflows', label: { de: 'Workflows', en: 'Workflows' }, path: 'workflows' }, ] }, teamsbot: { code: 'teamsbot', label: { de: 'Teams Bot', en: 'Teams Bot' }, icon: 'headset_mic', views: [ { code: 'dashboard', label: { de: 'Übersicht', en: 'Dashboard' }, path: 'dashboard' }, { code: 'sessions', label: { de: 'Sitzungen', en: 'Sessions' }, path: 'sessions' }, { code: 'settings', label: { de: 'Einstellungen', en: 'Settings' }, path: 'settings' }, ] }, automation: { code: 'automation', label: { de: 'Automatisierung', en: 'Automation' }, icon: 'settings', views: [ { code: 'definitions', label: { de: 'Definitionen', en: 'Definitions' }, path: 'definitions' }, { code: 'templates', label: { de: 'Vorlagen', en: 'Templates' }, path: 'templates' }, { code: 'logs', label: { de: 'Protokolle', en: 'Logs' }, path: 'logs' }, ] }, neutralization: { code: 'neutralization', label: { de: 'Neutralisierung', en: 'Neutralization', fr: 'Neutralisation' }, icon: 'shield_check', views: [ { code: 'dashboard', label: { de: 'Neutralisierung testen', en: 'Test Neutralization', fr: 'Tester neutralisation' }, path: 'playground' }, { code: 'playground', label: { de: 'Neutralisierung testen', en: 'Test Neutralization', fr: 'Tester neutralisation' }, path: 'playground' }, { code: 'config', label: { de: 'Einstellungen', en: 'Settings', fr: 'Paramètres' }, path: 'config' }, { code: 'attributes', label: { de: 'Attribute', en: 'Attributes', fr: 'Attributs' }, path: 'attributes' }, ] }, commcoach: { code: 'commcoach', label: { de: 'Kommunikations-Coach', en: 'Communication Coach', fr: 'Coach Communication' }, icon: 'account_voice', views: [ { code: 'dashboard', label: { de: 'Dashboard', en: 'Dashboard', fr: 'Tableau de bord' }, path: 'dashboard' }, { code: 'coaching', label: { de: 'Coaching', en: 'Coaching', fr: 'Coaching' }, path: 'coaching' }, { code: 'dossier', label: { de: 'Dossier', en: 'Dossier', fr: 'Dossier' }, path: 'dossier' }, { code: 'settings', label: { de: 'Einstellungen', en: 'Settings', fr: 'Paramètres' }, path: 'settings' }, ] }, workspace: { code: 'workspace', label: { de: 'AI Workspace', en: 'AI Workspace', fr: 'AI Workspace' }, icon: 'psychology', views: [ { code: 'dashboard', label: { de: 'Dashboard', en: 'Dashboard', fr: 'Tableau de bord' }, path: 'dashboard' }, { code: 'settings', label: { de: 'Einstellungen', en: 'Settings', fr: 'Parametres' }, path: 'settings' }, ] }, }; // ============================================================================= // HELPERS // ============================================================================= /** * Prüft ob ein AccessLevel Zugriff gewährt (nicht 'n') */ export function hasAccess(level: AccessLevel): boolean { return level !== 'n'; } /** * Prüft ob ein User einen Datensatz bearbeiten darf basierend auf AccessLevel */ export function canAccessRecord( level: AccessLevel, record: { _createdBy?: string }, userId: string ): boolean { switch (level) { case 'n': return false; case 'm': return record._createdBy === userId; case 'g': case 'a': return true; default: return false; } } /** * Holt das Label für die aktuelle Sprache */ export function getLabel(label: I18nLabel, lang: 'de' | 'en' | 'fr' = 'de'): string { return label[lang] || label.de || label.en || ''; }