/** * 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; // Kurzzeichen / Slug des Mandanten (audit-stable) mandateLabel?: string; // Voller Name des Mandanten (UI-Anzeige) — optional fuer Backwards-Compat 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. * * Felder: * - `id` — UUID des Mandanten. * - `name` — Kurzzeichen / Slug (UNIQUE, audit-stable). Nur lowercase * `a–z0–9` und `-`, Länge 2–32. Wird vom System aus `label` * generiert und kann nur durch einen PlatformAdmin geändert * werden (siehe `wiki/b-reference/platform/rbac.md`). * - `label` — Voller Name (Pflichtfeld), wird im UI gerendert. Frei * änderbar durch Mandate-Admin. */ export interface Mandate { id: string; // mandateId name: string; // Kurzzeichen (Slug, audit-stable) label: string; // Voller Name — Anzeige im UI (mandatory) 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; // Infrastructure/System Operator (RBAC bypass) isPlatformAdmin: boolean; // Cross-Mandate Governance (no RBAC bypass) roleLabels?: string[]; // Mandanten-scoped role labels } // ============================================================================= // 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: 'data-tables', label: 'Daten-Tabellen', path: 'data-tables' }, { code: 'position-documents', label: 'Zuordnungen', path: 'position-documents' }, { code: 'import-process', label: 'Import & Verarbeitung', path: 'import-process' }, { 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; } }