302 lines
9.8 KiB
TypeScript
302 lines
9.8 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.
|
|
*/
|
|
|
|
// =============================================================================
|
|
// I18N
|
|
// =============================================================================
|
|
|
|
export interface I18nLabel {
|
|
de: string;
|
|
en: string;
|
|
fr?: string;
|
|
}
|
|
|
|
// =============================================================================
|
|
// 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: I18nLabel; // { de: "Treuhand", en: "Trustee" }
|
|
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: I18nLabel;
|
|
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: I18nLabel;
|
|
icon: string;
|
|
views: FeatureView[];
|
|
}
|
|
|
|
// =============================================================================
|
|
// 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: { de: 'Treuhand', en: 'Trustee' },
|
|
icon: 'briefcase',
|
|
views: [
|
|
{ code: 'dashboard', label: { de: 'Übersicht', en: 'Dashboard' }, path: 'dashboard' },
|
|
{ code: 'positions', label: { de: 'Positionen', en: 'Positions' }, path: 'positions' },
|
|
{ code: 'documents', label: { de: 'Dokumente', en: 'Documents' }, path: 'documents' },
|
|
{ code: 'position-documents', label: { de: 'Zuordnungen', en: 'Assignments' }, path: 'position-documents' },
|
|
{ code: 'expense-import', label: { de: 'Spesen Import', en: 'Expense Import' }, path: 'expense-import' },
|
|
{ code: 'instance-roles', label: { de: 'Rollen & Rechte', en: 'Roles & Permissions' }, path: 'instance-roles', adminOnly: true },
|
|
]
|
|
},
|
|
chatworkflow: {
|
|
code: 'chatworkflow',
|
|
label: { de: 'Workflow', en: 'Workflow' },
|
|
icon: 'play_circle',
|
|
views: [
|
|
{ code: 'dashboard', label: { de: 'Übersicht', en: 'Dashboard' }, path: 'dashboard' },
|
|
{ code: 'runs', label: { de: 'Runs', en: 'Runs' }, path: 'runs' },
|
|
{ code: 'files', label: { de: 'Dateien', en: 'Files' }, path: 'files' },
|
|
]
|
|
},
|
|
chatbot: {
|
|
code: 'chatbot',
|
|
label: { de: 'Chatbot', en: 'Chatbot' },
|
|
icon: 'chat',
|
|
views: [
|
|
{ code: 'conversations', label: { de: 'Konversationen', en: 'Conversations' }, path: 'conversations' },
|
|
{ code: 'settings', label: { de: 'Einstellungen', en: 'Settings' }, path: 'settings' },
|
|
]
|
|
},
|
|
realestate: {
|
|
code: 'realestate',
|
|
label: { de: 'Immobilien', en: 'Real Estate' },
|
|
icon: 'home',
|
|
views: [
|
|
{ code: 'dashboard', label: { de: 'Übersicht', en: 'Dashboard' }, path: 'dashboard' },
|
|
{ code: 'projects', label: { de: 'Projekte', en: 'Projects' }, path: 'projects' },
|
|
{ code: 'parcels', label: { de: 'Parzellen', en: 'Parcels' }, path: 'parcels' },
|
|
{ code: 'instance-roles', label: { de: 'Rollen & Rechte', en: 'Roles & Permissions' }, path: 'instance-roles', adminOnly: true },
|
|
]
|
|
},
|
|
chatplayground: {
|
|
code: 'chatplayground',
|
|
label: { de: 'Chat Playground', en: 'Chat Playground' },
|
|
icon: 'message',
|
|
views: [
|
|
{ code: 'playground', label: { de: 'Playground', en: 'Playground' }, path: 'playground' },
|
|
{ code: 'workflows', label: { de: 'Workflows', en: 'Workflows' }, path: 'workflows' },
|
|
]
|
|
},
|
|
automation: {
|
|
code: 'automation',
|
|
label: { de: 'Automatisierung', en: 'Automation' },
|
|
icon: 'settings',
|
|
views: [
|
|
{ code: 'definitions', label: { de: 'Definitionen', en: 'Definitions' }, path: 'definitions' },
|
|
{ code: 'templates', label: { de: 'Vorlagen', en: 'Templates' }, path: 'templates' },
|
|
{ code: 'logs', label: { de: 'Protokolle', en: 'Logs' }, path: 'logs' },
|
|
]
|
|
},
|
|
};
|
|
|
|
// =============================================================================
|
|
// 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: { _createdBy?: string },
|
|
userId: string
|
|
): boolean {
|
|
switch (level) {
|
|
case 'n':
|
|
return false;
|
|
case 'm':
|
|
return record._createdBy === userId;
|
|
case 'g':
|
|
case 'a':
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Holt das Label für die aktuelle Sprache
|
|
*/
|
|
export function getLabel(label: I18nLabel, lang: 'de' | 'en' | 'fr' = 'de'): string {
|
|
return label[lang] || label.de || label.en || '';
|
|
}
|