diff --git a/concepts/Kontext-20260328.md b/concepts/Kontext-20260328.md new file mode 100644 index 0000000..946a4bf --- /dev/null +++ b/concepts/Kontext-20260328.md @@ -0,0 +1,128 @@ +Kontext: Wir arbeiten an einer Multi-Tenant-Applikation mit Feature-Store-Modell. Ich möchte UI und Gateway austesten und offene Themen weiterführen. Hier ist der vollständige architektonische Kontext: + +--- + +## Architektur-Überblick + +- **Backend (Gateway):** `@poweron/gateway` — FastAPI (Python), PostgreSQL +- **Frontend:** `@poweron/frontend_nyla` — React/TypeScript (Vite) +- **Konzeptdokument:** `@poweron/wiki/concepts/Multi-Mandate-Onboarding-und-Store-Konzept.md` +- **Umsetzungsbericht (103 Punkte OK):** `@poweron/wiki/concepts/Multi-Mandate-Umsetzungsbericht.md` + +--- + +## RBAC-Rollenmodell (KRITISCH — nie verwechseln!) + +Es gibt ZWEI vollständig getrennte Rollensysteme. Siehe auch: `@poweron/.cursor/rules/rbac-role-separation.mdc` + +### 1. Mandantenrollen (Mandate Roles) +- Labels: `admin`, `user`, `viewer` (generisch) +- Gespeichert mit: `featureCode=NULL`, `featureInstanceId=NULL`, `mandateId=` +- Templates: `isSystemRole=True`, `mandateId=NULL`, `featureCode=NULL` +- Verknüpfung: `UserMandateRole` Junction Table +- Logik: Zuordnung User → Mandant erfolgt via `createUserMandate()` in `interfaceDbApp.py`, welche IMMER mindestens eine Mandate-Rolle verlangt (ValueError wenn leer) + +### 2. Feature-Instanz-Rollen (Feature Instance Roles) +- Labels: `workspace-admin`, `workspace-user`, `workspace-viewer`, `automation-admin`, etc. (Feature-Prefix!) +- Gespeichert mit: `featureCode=`, `featureInstanceId=`, `mandateId=` +- Templates: `isSystemRole=False`, `mandateId=NULL`, `featureInstanceId=NULL`, `featureCode=` (globale Vorlagen) +- Verknüpfung: `FeatureAccessRole` Junction Table +- Logik: Zuordnung User → Feature-Instanz erfolgt via `createFeatureAccess()` in `interfaceDbApp.py`, welche IMMER mindestens eine Instanz-Rolle verlangt (ValueError wenn leer) +- Jedes Feature-Modul definiert seine `TEMPLATE_ROLES` in `mainXxx.py` + +### Strikte Regeln: +1. NIE eine Mandantenrolle (`admin`) einer `FeatureAccessRole` zuweisen +2. NIE eine Feature-Rolle (`workspace-admin`) einer `UserMandateRole` zuweisen +3. Admin-Lookup für Feature-Instanzen: `roleLabel.endswith("-admin")`, NICHT `"admin" in roleLabel` +4. `_copyTemplateRoles` sucht: `featureCode=X, mandateId=NULL, featureInstanceId=NULL` — nie `isSystemRole=True` +5. Alle Pfade, die `FeatureAccess` erstellen, MÜSSEN mindestens eine instanz-scoped roleId mitgeben + +### Datenmodelle: +- `Role`: `@poweron/gateway/modules/datamodels/datamodelRbac.py` — Felder: id, roleLabel, description, mandateId, featureInstanceId, featureCode, isSystemRole +- `UserMandate`: `@poweron/gateway/modules/datamodels/datamodelMembership.py` — User-Mitgliedschaft in Mandant +- `FeatureAccess`: `@poweron/gateway/modules/datamodels/datamodelMembership.py` — User-Zugriff auf Feature-Instanz +- `FeatureInstance`: `@poweron/gateway/modules/datamodels/datamodelFeatures.py` — Instanz eines Features in einem Mandanten +- `Mandate`: `@poweron/gateway/modules/datamodels/datamodelUam.py` — Mandantenmodell mit mandateType (system/personal/company), isSystem, deletedAt + +--- + +## Feature-Store-Konzept ("Own Instance Pattern") + +- **UI:** `@poweron/frontend_nyla/src/pages/Store.tsx` — Pro Mandant ein "Aktivieren für [Mandant]" Button +- **Backend:** `@poweron/gateway/modules/routes/routeStore.py` — `POST /api/store/activate` +- **Flow:** User wählt Feature → wählt Mandant → Backend erstellt `FeatureInstance` mit `copyTemplateRoles=True` → findet Admin-Rolle (`endswith("-admin")`) → weist User via `createFeatureAccess(userId, instanceId, roleIds=[adminRoleId])` zu +- **Auto-Provisioning:** `GET /api/store/mandates` erstellt automatisch Personal-Mandate, wenn User keine Admin-Mandate hat +- **Template-Rollen-Kopie:** `@poweron/gateway/modules/interfaces/interfaceFeatures.py` — `_copyTemplateRoles()` und `syncRolesFromTemplate()` +- **Orphan Control:** Letzer FeatureAccess entfernt → FeatureInstance wird gelöscht + +--- + +## Schlüssel-Backend-Dateien + +| Datei | Zweck | +|-------|-------| +| `@poweron/gateway/modules/interfaces/interfaceDbApp.py` | Kern-DB-Interface: createUserMandate, createFeatureAccess, _provisionMandateForUser (44x `_`-Prefix-Filter für System-Felder) | +| `@poweron/gateway/modules/interfaces/interfaceFeatures.py` | Feature-Instanz-Verwaltung: _copyTemplateRoles, syncRolesFromTemplate | +| `@poweron/gateway/modules/routes/routeStore.py` | Store-API: activate, mandates-list | +| `@poweron/gateway/modules/routes/routeAdminFeatures.py` | Admin-API: add_user_to_feature_instance (Rollen-Validierung: nur Instanz-Rollen!), rename instance | +| `@poweron/gateway/modules/routes/routeSystem.py` | `/api/navigation` — liefert isAdmin pro FeatureInstance | +| `@poweron/gateway/modules/routes/routeSecurityLocal.py` | Login, Register, _provisionMandateForUser | +| `@poweron/gateway/modules/shared/attributeUtils.py` | Generiert Attribute aus Pydantic-Models für FormGenerator | +| `@poweron/gateway/modules/connectors/connectorDbPostgre.py` | DB-Connector: _saveRecord setzt automatisch _createdAt, _createdBy, _modifiedAt, _modifiedBy | +| `@poweron/gateway/modules/migration/migrateRootUsers.py` | Root→Personal-Mandate-Migration | + +--- + +## Schlüssel-Frontend-Dateien + +| Datei | Zweck | +|-------|-------| +| `@poweron/frontend_nyla/src/pages/Store.tsx` | Feature-Store mit Mandate-Kontext | +| `@poweron/frontend_nyla/src/components/UnifiedDataBar/UnifiedDataBar.tsx` | UDB: Chats, Files, Sources Tabs | +| `@poweron/frontend_nyla/src/components/UnifiedDataBar/ChatsTab.tsx` | Chats: Aktiv/Archiv, Rename/Delete/Archive Icons | +| `@poweron/frontend_nyla/src/components/UnifiedDataBar/FilesTab.tsx` | Files: Scope/Neutralize Icons | +| `@poweron/frontend_nyla/src/components/UnifiedDataBar/SourcesTab.tsx` | Sources: Scope/Neutralize auf Active Sources | +| `@poweron/frontend_nyla/src/components/Navigation/MandateNavigation.tsx` | Nav-Tree mit Rename-Icon für Instanz-Admins | +| `@poweron/frontend_nyla/src/components/Navigation/TreeNavigation/TreeNavigation.tsx` | Generischer Tree mit actions-Slot | +| `@poweron/frontend_nyla/src/components/OnboardingAssistant.tsx` | Globaler Onboarding-Assistent (Dashboard, dismissible, reaktivierbar via UserSection) | +| `@poweron/frontend_nyla/src/components/Navigation/UserSection.tsx` | User-Kontextmenü: Guthaben, Einstellungen, Onboarding | +| `@poweron/frontend_nyla/src/pages/Settings.tsx` | User-Settings: profile, appearance, voice, neutralization, privacy | +| `@poweron/frontend_nyla/src/components/FormGenerator/` | Dynamische Formulare aus Backend-Attribut-Definitionen | +| `@poweron/frontend_nyla/src/hooks/useNavigation.ts` | Navigation-Hook mit FeatureInstance.isAdmin | +| `@poweron/frontend_nyla/src/pages/views/workspace/WorkspacePage.tsx` | Workspace-Feature-Seite | + +--- + +## System-Felder (DB-Metadaten) — OFFENER PUNKT + +Die DB-Schicht (`connectorDbPostgre.py`) pflegt automatisch 4 System-Felder für jede Tabelle: +- `_createdAt` (float, UTC timestamp) +- `_createdBy` (str, userId) +- `_modifiedAt` (float, UTC timestamp) +- `_modifiedBy` (str, userId) + +Diese werden aktuell in `interfaceDbApp.py` an 44 Stellen via `{k: v for k, v in record.items() if not k.startswith("_")}` herausgefiltert, bevor Records an die API/Frontend gehen. Es gibt KEINE gemeinsame Base-Klasse — alle 30+ Pydantic-Models erben direkt von `BaseModel`. + +**Offene Architektur-Entscheidung:** Diese 4 System-Felder sollen im UI als read-only sichtbar sein (z.B. in Tabellen/Formularen). Optionen: +- (a) `PowerOnModel(BaseModel)` als Base-Klasse mit den 4 Feldern (mit `alias="_createdAt"` etc.) einführen, `_`-Filter entfernen +- (b) Einige Models (`FeatureDataSource`, `DataSource`, `FileFolder`, `Invitation`, `Messaging`) haben bereits eigene `createdAt`/`createdBy` als Business-Felder — Namenskonflikte zu klären! +- Der `FormGenerator` unterstützt bereits `readonly`-Attribute korrekt + +--- + +## Weitere offene Themen aus dem vorherigen Chat + +1. **CommCoach UDB-Integration:** UDB in CommCoach einbinden (ohne Chats-Tab), gleicher AI-Service wie Workspace, CommCoach behält eigene SSE-Chat-Komponente +2. **Zentrale AI-Neutralisierung:** RAG mandate-weit verfügbar, Neutralisierungs-Pipeline weiter konsolidieren +3. **Settings-Seite:** 5 Tabs vorhanden (profile, appearance, voice, neutralization, privacy) — Vollständigkeit prüfen + +--- + +## Coding-Konventionen + +- Alle internen Funktionen beginnen mit `_` Prefix (z.B. `_copyTemplateRoles`) +- camelCase für alle Variablen und Funktionsnamen (kein snake_case) +- Keine unnötigen Fallbacks — Fehler müssen propagiert werden +- Keine Deprecations — alte Logik löschen statt deprecaten +- Keine Backwards-Compatibility-Workarounds +- Pydantic-Models sind die einzige Quelle für UI-Feld-Definitionen (via `attributeUtils.py`) \ No newline at end of file diff --git a/concepts/Multi-Mandate-Umsetzungsbericht.md b/concepts/Multi-Mandate-Umsetzungsbericht.md new file mode 100644 index 0000000..1ece829 --- /dev/null +++ b/concepts/Multi-Mandate-Umsetzungsbericht.md @@ -0,0 +1,258 @@ +# Multi-Mandate Konzept — Umsetzungsbericht + +**Datum:** 2026-03-28 (aktualisiert) +**Basis:** [Multi-Mandate-Onboarding-und-Store-Konzept.md](./Multi-Mandate-Onboarding-und-Store-Konzept.md) + +--- + +## Legende + +| Status | Bedeutung | +|--------|-----------| +| OK | Vollstaendig umgesetzt gemaess Konzept | + +--- + +## 1. Datenmodell-Erweiterung + +| Punkt | Status | Details | +|-------|--------|---------| +| `MandateType` Enum (`system`, `personal`, `company`) | OK | Auf `Mandate`-Model in `datamodelUam.py`. | +| `isSystem` separat von `mandateType` | OK | Zwei unabhaengige Felder mit klarer Trennung. | +| `mandateType` mutabel/informativ | OK | Keine Geschaeftslogik an den Typ gebunden. | +| Default fuer bestehende Mandanten: `company` | OK | Migration setzt Default. | + +--- + +## 2. Onboarding-Flows + +| Punkt | Status | Details | +|-------|--------|---------| +| Zwei CTAs auf Login-Seite (Personal/Company) | OK | `Login.tsx`: "Kostenlos testen" und "Fuer Unternehmen". | +| Register-Formular mit `registrationType` + `companyName` | OK | `Register.tsx` liest `type` aus URL, zeigt Firmenname bei `company`. | +| Backend: `_provisionMandateForUser` mit korrektem Typ | OK | `routeSecurityLocal.py` erstellt Mandate mit `personal`/`company`. | +| Subscription startet als `PENDING` | OK | `MandateSubscription` mit `status=PENDING` bei Erstellung. | +| `_activatePendingSubscriptions` bei Login | OK | Wird sowohl bei Local-Login als auch bei Google-OAuth aufgerufen. | +| OAuth-Onboarding-Wizard (Post-Login) | OK | `OnboardingWizard.tsx` existiert, wird bei `isNewUser=true` gezeigt. | +| Invitation-Flow (bestehendes Modell) | OK | Unveraendert, funktioniert mit Multi-Mandate-Modell. | + +--- + +## 3. Feature Store: Own Instance Pattern + +| Punkt | Status | Details | +|-------|--------|---------| +| `mandateId` immer required (explizit, nie implizit) | OK | `StoreActivateRequest.mandateId: str = Field(...)` (required). | +| Mehrere Instanzen desselben Features erlaubt | OK | Jede Aktivierung erstellt neue `FeatureInstance`. | +| Store fuer alle User sichtbar (Upselling) | OK | Kein Gate auf Admin-Rolle fuer Store-Ansicht. | +| Auto-Provisioning Personal-Mandate bei 0 Admin-Mandaten | OK | Automatische Mandate-Erstellung wenn User keine Admin-Mandate hat. | +| Orphan Control bei Deaktivierung | OK | Letzer `FeatureAccess` entfernt -> `FeatureInstance` geloescht. | +| Store.tsx Header: "Own Instance Pattern" | OK | Kommentar aktualisiert. | +| Docstring in `routeStore.py` | OK | Korrigiert: "mandateId is None" entfernt, da mandateId jetzt required. | + +--- + +## 4. Root-Mandant: Nur technisch + +| Punkt | Status | Details | +|-------|--------|---------| +| `createUser` weist NICHT zum Root-Mandanten zu | OK | `_assignUserToRootMandate` wurde geloescht. | +| Keine Feature-Instanzen fuer Endkunden im Root | OK | Migration entfernt Root-Instanzen. | +| sysadmin-User behalten Root-Mitgliedschaft | OK | Step 2 der Migration entfernt nur Nicht-SysAdmin-User. | + +--- + +## 5. Migration: Root-Mandant bereinigen + +| Punkt | Status | Details | +|-------|--------|---------| +| Step 1: Feature-Daten migrieren (alle User) | OK | `migrateRootUsers.py`: Erstellt Ziel-Instanzen, migriert 5 Tabellen. | +| Step 2: Root bereinigen | OK | Entfernt Root-FeatureInstances, entfernt Nicht-SysAdmin-UserMandates. | +| Idempotent + DB-Flag | OK | `_isMigrationCompleted` / `_setMigrationCompleted` mit DB-Record. | +| Bootstrap-Integration | OK | `interfaceBootstrap.py` ruft Migration automatisch auf. | +| Voice/Documents Migration | OK | `migrateVoiceAndDocuments.py` migriert VoiceSettings + CoachingDocuments. | + +--- + +## 6. Unified Data Layer (UDL) + +| Punkt | Status | Details | +|-------|--------|---------| +| `FileItem`: `scope` + `neutralize` Felder | OK | `scope: str` (default "personal"), `neutralize: bool` (default False). | +| `DataSource`: `scope` + `neutralize` Felder | OK | Felder vorhanden in `datamodelDataSource.py`. | +| `FeatureDataSource`: `scope` + `neutralize` Felder | OK | Felder hinzugefuegt in `datamodelFeatureDataSource.py`. | +| `FileContentIndex`: `scope`, `isNeutralized`, `userId`, `mandateId` | OK | Alle 4 Felder vorhanden. | +| `scope=global` RBAC (nur SysAdmin) | OK | Auf PATCH und PUT Endpunkten enforced. | +| Re-Indexing bei Scope/Neutralize-Aenderung | OK | PATCH-Endpoints triggern `_autoIndexFile`. | +| `indexFile` liest `scope` aus FileItem | OK | Setzt `scope` und `isNeutralized` auf FileContentIndex. | +| Alte DB-Records mit `scope=None` | OK | `_convertFileItems` setzt Default `"personal"` vor Validierung. | +| PATCH `/api/datasources/{id}/scope` + `/neutralize` | OK | Neue Route `routeDataSources.py` mit Lookup fuer DataSource + FeatureDataSource. | + +--- + +## 7. Daten-Neutralisierung + +| Punkt | Status | Details | +|-------|--------|---------| +| Flag auf Datenquelle (`neutralize`) | OK | Auf FileItem, DataSource und FeatureDataSource. | +| Fail-Safe: Dokument NICHT weitergeben bei Fehler | OK | `_neutralizeRequest` returned excludedDocs, Call laeuft weiter ohne fehlgeschlagene Docs. | +| `_neutralizeRequest` neutralisiert `prompt` UND `messages` | OK | Beide werden verarbeitet. | +| Graceful Degradation statt RuntimeError | OK | Einzelne Dokumente werden ausgeschlossen, nicht der gesamte Call. | +| Neutralisierte Docs im Chat-UI sichtbar | OK | `ChatStream.tsx`: `
` Block mit "Gesendete Daten", neutralisiert/uebersprungen Badges, und "Nicht gesendet (Neutralisierung fehlgeschlagen)" Bereich fuer excludedDocs. | +| User kann Platzhalter-Mappings einsehen/loeschen | OK | Settings-Tab "Datenneutralisierung" mit Tabelle aller `DataNeutralizerAttributes` pro User. GET/DELETE API unter `/api/local/neutralization-mappings`. | +| `requireNeutralization` Flag pro AI-Call | OK | Feld auf `AiCallRequest` (Optional[bool]). `_shouldNeutralize` respektiert per-Request Override. UI-Toggle (Lock-Button) in `WorkspaceInput.tsx` neben Send-Button. | + +--- + +## 8. UI-Komponente: Unified Data Bar (UDB) + +| Punkt | Status | Details | +|-------|--------|---------| +| UDB als wiederverwendbare Plattform-Komponente | OK | `components/UnifiedDataBar/` mit UnifiedDataBar, ChatsTab, FilesTab, SourcesTab. | +| Keine Render-Props, direkte Tab-Komposition | OK | Tabs direkt gerendert, `hideTabs`-Prop verfuegbar. | +| `hideTabs` fuer Feature-spezifische Konfiguration | OK | CommCoach nutzt `hideTabs={['chats']}`. | +| Alte Komponenten geloescht (FileBrowser, DataSourcePanel, ConversationList) | OK | Alle drei Dateien geloescht. | + +### 8.1 Files-Tab + +| Punkt | Status | Details | +|-------|--------|---------| +| FolderTree mit Ordnerstruktur | OK | Nutzt `FolderTree`-Komponente. | +| Upload (Drag-Drop + Dateidialog) | OK | Implementiert. | +| Suche | OK | Volltextsuche ueber Dateinamen und Tags. | +| Scope-Icons inline (cycling) | OK | Klick wechselt personal -> featureInstance -> mandate. Icons auf 14px vergroessert fuer bessere Sichtbarkeit. | +| Neutralisierungs-Toggle inline | OK | Lock-Emoji mit opacity 0.4/1.0 Toggle. 14px fuer Sichtbarkeit. | +| Legende | OK | Am unteren Rand der Komponente. | + +### 8.2 Sources-Tab + +| Punkt | Status | Details | +|-------|--------|---------| +| Connection Browsing (Services, Ordner, Dateien) | OK | Volle Browsing-Logik transferiert. | +| Feature Data Browsing (Mandate -> Instanzen -> Tabellen) | OK | Volle Logik transferiert. | +| Active Sources mit Scope/Neutralize-Icons | OK | Scope-Cycling und Neutralisierungs-Toggle auf Active-Elementen. | +| Icons NUR auf Active Sources (nicht auf Browse/Feature Data) | OK | Konzeptkonform. | +| PATCH-Endpoints fuer DataSource/FeatureDataSource | OK | `routeDataSources.py` registriert unter `/api/datasources`. | + +### 8.3 Chats-Tab + +| Punkt | Status | Details | +|-------|--------|---------| +| Baumstruktur nach Feature-Instanzen | OK | Gruppiert nach `featureInstanceId` mit aufgeloestem `featureLabel`. | +| Feature-Label (nicht UUID) als Gruppenname | OK | Backend `listWorkspaceWorkflows` reichert `featureLabel` und `featureCode` aus `FeatureInstance`-Lookup an. | +| Suchfunktion | OK | Volltextsuche ueber Chat-Labels. | +| Flat Mode (chronologische Liste) | OK | Toggle im Tab-Header. | +| Drag-and-Drop in Prompt | OK | `draggable` + `onDragStart` mit `application/chat-id`. | +| Create New Button | OK | "+" Button wenn `onCreateNew` gesetzt. | +| Aktiv/Archiv Filter-Tabs | OK | Sub-Tabs "Aktiv (N)" und "Archiv (N)" unter der Toolbar. Nutzt `includeArchived=true` im API-Call. | +| Inline Action Icons (Umbenennen, Archivieren, Loeschen) | OK | Drei Icons pro Chat-Eintrag auf Hover: Stift, Box/Pfeil, Muelleimer. Archivierung/Wiederherstellung per PATCH workflow status. | +| Active Workflow Highlighting | OK | CSS-Klasse auf aktiven Chat. | +| API-Response korrekt gelesen (`workflows` key) | OK | `response.data?.workflows` (Bugfix angewendet). | + +### 8.4 FolderTree + +| Punkt | Status | Details | +|-------|--------|---------| +| `FileNode` mit `scope` + `neutralize` | OK | Optionale Felder auf dem Interface. | +| Scope/Neutralize Icons in Datei-Zeilen | OK | Zwischen Dateiname und Aktions-Buttons. 14px Emoji-Groesse. | +| Callbacks `onScopeChange`/`onNeutralizeToggle` | OK | Durch `SelectionCtx` durchgereicht. | + +--- + +## 9. RAG als zentrales Repository + +| Punkt | Status | Details | +|-------|--------|---------| +| Scope-basiertes Filtering bei RAG-Queries | OK | `semanticSearch` filtert nach Scope. | +| Union-Query (personal + featureInstance + mandate + global) | OK | Implementiert im Knowledge-Service. | +| `scope` aus FileItem in RAG-Index uebernommen | OK | `indexFile` liest und setzt Scope. | +| `isNeutralized` im RAG-Index | OK | Wird bei Neutralisierung gesetzt. | + +--- + +## 10. Onboarding-Assistant + +| Punkt | Status | Details | +|-------|--------|---------| +| Global (nicht Feature-gebunden) | OK | Keine `instanceId`/`mandateId` Props. | +| Auf Dashboard-Seite | OK | In `Dashboard.tsx` eingebunden. | +| 4 globale Schritte (Mandant, Feature, Datenquelle, Chat) | OK | API-basierte Pruefung. | +| "Nicht wieder anzeigen" Checkbox | OK | `localStorage`-basiert mit Checkbox. | +| Reaktivierbar ueber User-Kontextmenu | OK | "Onboarding-Assistent" in `UserSection.tsx`. | +| Kontextsensitive Callouts | OK | Pro Schritt ein Tipp-Text (Callout) mit farbiger Hervorhebung fuer den naechsten offenen Schritt. Texte fuer alle 4 Schritte definiert. | + +--- + +## 11. Mandanten-Loeschung: Kaskade + +| Punkt | Status | Details | +|-------|--------|---------| +| FeatureAccess loeschen | OK | Pro Instanz. | +| FeatureInstances loeschen | OK | Alle im Mandanten. | +| Instanz-Daten: ChatWorkflow, ChatMessage, ChatLog | OK | Geloescht pro Instanz. | +| Instanz-Daten: FileItem | OK | Geloescht pro Instanz. | +| Instanz-Daten: DataSource | OK | Geloescht pro Instanz. | +| Instanz-Daten: DataNeutralizerAttributes | OK | Geloescht pro Instanz. | +| Instanz-Daten: FileContentIndex (RAG) | OK | Geloescht pro Instanz. | +| UserMandate-Zuordnungen | OK | Entfernt. | +| MandateSubscription | OK | Entfernt. | +| Stripe-Subscription kuendigen | OK | `stripe.Subscription.cancel(stripeSubId)` wird pro aktiver Subscription vor dem Loeschen aufgerufen. | +| `isSystem=true` Schutz | OK | Systemmandate nicht loeschbar. | +| Soft-Delete mit 30-Tage Retention | OK | `deletedAt` Feld auf Mandate-Model. Soft-Delete setzt `enabled=False` + `deletedAt=now()`. `purgeExpiredMandates(retentionDays=30)` im Bootstrap. `restoreMandate()` fuer Wiederherstellung. | + +--- + +## 12. Subscription: Datenvolumen als Parameter + +| Punkt | Status | Details | +|-------|--------|---------| +| `maxDataVolumeMB` in SubscriptionPlan | OK | Feld existiert auf `SubscriptionPlan` (`Optional[int]`). | +| API fuer Datenvolumen-Nutzung | OK | `GET /api/subscription/data-volume/{mandateId}` berechnet Nutzung (FileItem.fileSize Summe), liefert `usedMB`, `maxDataVolumeMB`, `percentUsed`, `warning`. | + +--- + +## 13. Voice-Settings Zentralisierung + +| Punkt | Status | Details | +|-------|--------|---------| +| `UserVoicePreferences` Model (User-Level) | OK | In `datamodelUam.py`. | +| Settings-Seite mit Tabs inkl. "Stimme & Sprache" | OK | `Settings.tsx` mit 5 Tabs (inkl. Datenneutralisierung). | +| CommCoach nutzt zentrale Voice-Preferences | OK | Kein eigenes Voice-Silo mehr. | +| Workspace nutzt zentrale Voice-Preferences | OK | `WorkspaceInput.tsx` liest von `/api/local/voice-preferences`. | + +--- + +## 14. Code-Hygiene + +| Punkt | Status | Details | +|-------|--------|---------| +| `_assignUserToRootMandate` geloescht | OK | Methode entfernt. | +| Store.tsx: "Own Instance Pattern" im Kommentar | OK | Aktualisiert. | +| `interfaceFeatureWorkspace.py`: Kein "VoiceSettings" in Docstring | OK | Bereinigt. | +| `routeFeatureCommcoach.py`: Kein "voice endpoints" in Docstring | OK | Bereinigt. | +| CommCoach: Eigene Dokument-/Voice-Endpoints geloescht | OK | Entfernt. | +| Alte FileBrowser/DataSourcePanel/ConversationList geloescht | OK | Alle drei entfernt. | + +--- + +## Zusammenfassung + +| Kategorie | OK | +|-----------|-----| +| Datenmodell | 4 | +| Onboarding-Flows | 7 | +| Feature Store | 7 | +| Root-Mandant | 3 | +| Migration | 5 | +| Unified Data Layer | 9 | +| Neutralisierung | 7 | +| UDB (UI) | 27 | +| RAG | 4 | +| Onboarding-Assistant | 6 | +| Mandanten-Loeschung | 12 | +| Subscription/Datenvolumen | 2 | +| Voice-Zentralisierung | 4 | +| Code-Hygiene | 6 | +| **Gesamt** | **103** | + +Alle Punkte sind vollstaendig umgesetzt. Keine offenen oder teilweisen Punkte verbleibend.