ui-nyla/src/types/mandate.ts
2026-04-11 19:44:52 +02:00

323 lines
10 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; // 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<string, FeatureConfig> = {
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;
}
}