332 lines
11 KiB
TypeScript
332 lines
11 KiB
TypeScript
/**
|
||
* 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<string, TablePermission>;
|
||
|
||
// Feld-Level (nur wo eingeschränkt)
|
||
fields?: Record<string, Record<string, FieldPermission>>;
|
||
|
||
// View-Level (Navigation)
|
||
// Keys are view codes like "trustee-positions", values are boolean visibility
|
||
// Special key "_all" means all views are visible
|
||
views: Record<string, boolean>;
|
||
|
||
// 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<string, FeatureConfig> = {
|
||
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;
|
||
}
|
||
}
|
||
|