/** * 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. */ // ============================================================================= // 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: string; // German plaintext i18n key 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: string; // German plaintext i18n key 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: string; // German plaintext i18n key icon: string; views: FeatureView[]; deprecated?: boolean; } // ============================================================================= // 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: 'Treuhand', icon: 'briefcase', views: [ { code: 'dashboard', label: 'Übersicht', path: 'dashboard' }, { code: 'positions', label: 'Positionen', path: 'positions' }, { code: 'documents', label: 'Dokumente', path: 'documents' }, { code: 'position-documents', label: 'Zuordnungen', path: 'position-documents' }, { code: 'expense-import', label: 'Spesen Import', path: 'expense-import' }, { code: 'scan-upload', label: 'Scannen / Hochladen', path: 'scan-upload' }, { code: 'instance-roles', label: 'Rollen & Rechte', path: 'instance-roles', adminOnly: true }, { code: 'settings', label: 'Buchhaltungseinstellungen', path: 'settings' }, ] }, chatworkflow: { code: 'chatworkflow', label: 'Workflow', icon: 'play_circle', views: [ { code: 'dashboard', label: 'Übersicht', path: 'dashboard' }, { code: 'runs', label: 'Runs', path: 'runs' }, { code: 'files', label: 'Dateien', path: 'files' }, ] }, chatbot: { code: 'chatbot', label: 'Chatbot', icon: 'chat', views: [ { code: 'conversations', label: 'Konversationen', path: 'conversations' }, { code: 'settings', label: 'Einstellungen', path: 'settings' }, ] }, realestate: { code: 'realestate', label: 'Immobilien', icon: 'home', views: [ { code: 'dashboard', label: 'Karte', path: 'dashboard' }, { code: 'instance-roles', label: 'Rollen & Rechte', path: 'instance-roles', adminOnly: true }, ] }, teamsbot: { code: 'teamsbot', label: 'Teams Bot', icon: 'headset_mic', views: [ { code: 'dashboard', label: 'Übersicht', path: 'dashboard' }, { code: 'sessions', label: 'Sitzungen', path: 'sessions' }, { code: 'settings', label: 'Einstellungen', path: 'settings' }, ] }, graphicalEditor: { code: 'graphicalEditor', label: 'Grafischer Editor', icon: 'sitemap', views: [ { code: 'editor', label: 'Editor', path: 'editor' }, { code: 'workflows', label: 'Workflows', path: 'workflows' }, { code: 'templates', label: 'Vorlagen', path: 'templates' }, { code: 'workflows-tasks', label: 'Tasks', path: 'workflows-tasks' }, { code: 'dashboard', label: 'Dashboard', path: 'dashboard' }, ] }, neutralization: { code: 'neutralization', label: 'Neutralisierung', icon: 'shield_check', views: [ { code: 'dashboard', label: 'Neutralisierung testen', path: 'playground' }, { code: 'playground', label: 'Neutralisierung testen', path: 'playground' }, { code: 'config', label: 'Einstellungen', path: 'config' }, { code: 'attributes', label: 'Attribute', path: 'attributes' }, ] }, commcoach: { code: 'commcoach', label: 'Kommunikations-Coach', icon: 'account_voice', views: [ { code: 'dashboard', label: 'Dashboard', path: 'dashboard' }, { code: 'coaching', label: 'Coaching', path: 'coaching' }, { code: 'dossier', label: 'Dossier', path: 'dossier' }, { code: 'settings', label: 'Einstellungen', path: 'settings' }, ] }, workspace: { code: 'workspace', label: 'AI Workspace', icon: 'psychology', views: [ { code: 'dashboard', label: 'Dashboard', path: 'dashboard' }, { code: 'editor', label: 'Editor', path: 'editor' }, { code: 'rag-insights', label: 'Wissens-Insights', path: 'rag-insights' }, { code: 'settings', label: 'Einstellungen', 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: { sysCreatedBy?: string }, userId: string ): boolean { switch (level) { case 'n': return false; case 'm': return record.sysCreatedBy === userId; case 'g': case 'a': return true; default: return false; } }