ui-nyla/src/types/mandate.ts
2026-04-21 00:50:42 +02:00

332 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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
* `az09` und `-`, Länge 232. 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;
}
}