diff --git a/b-reference/frontend-nyla/architecture.md b/b-reference/frontend-nyla/architecture.md index 5430185..7bed8e8 100644 --- a/b-reference/frontend-nyla/architecture.md +++ b/b-reference/frontend-nyla/architecture.md @@ -1,5 +1,5 @@ - + # Frontend Nyla -- Architektur @@ -32,7 +32,7 @@ Ergänzend typische Root-Dateien und Bereiche im Repo: `main.tsx`, `App.tsx`, `a | Komponente | Zweck | |------------|-------| | `UnifiedDataBar (UDB)` | Multi-Tab-Panel: Chats, Files, Sources — wird in Workspace, CommCoach und Graphical Editor genutzt | -| `FormGenerator` | Dynamische Formulare/Tabellen aus Backend-Attribut-Definitionen | +| `FormGenerator` | Dynamische Formulare/Tabellen aus Backend-Attribut-Definitionen (siehe [formgenerator.md](formgenerator.md)) | | `FolderTree` | Rekursiver Ordnerbaum mit Drag&Drop, Multi-Selection, Inline-Editing | | `MandateNavigation` | Feature-Baum-Navigation mit Mandanten-Kontext | | `Automation2FlowEditor` | Konsolidierter n8n-style Flow-Builder (Graphical Editor) mit 3-Column-Layout: [UDB + Chat/Tracing] [Canvas + Header] [Nodes] | diff --git a/b-reference/frontend-nyla/formgenerator.md b/b-reference/frontend-nyla/formgenerator.md new file mode 100644 index 0000000..eb41926 --- /dev/null +++ b/b-reference/frontend-nyla/formgenerator.md @@ -0,0 +1,208 @@ + + + + +# FormGenerator -- Referenz + +## Uebersicht + +Der FormGenerator besteht aus mehreren Komponenten: + +| Komponente | Datei | Zweck | +|------------|-------|-------| +| `FormGeneratorTable` | `components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx` | Generische Datentabelle mit Pagination, Sortierung, Filterung, Suche, Selektion, Bulk-Actions | +| `FormGeneratorControls` | `components/FormGenerator/FormGeneratorControls/FormGeneratorControls.tsx` | Toolbar: Suche, Pagination, Batch-Action-Buttons, "Select All Filtered"-Banner | +| `FormGeneratorForm` | `components/FormGenerator/FormGeneratorForm/FormGeneratorForm.tsx` | Dynamische Formulare aus Backend-Attribut-Definitionen | +| `FormGeneratorList` | `components/FormGenerator/FormGeneratorList/FormGeneratorList.tsx` | Listen-Darstellung | + +Diese Referenz fokussiert auf **FormGeneratorTable** und das zugehoerige Backend-API-Pattern. + +--- + +## FormGeneratorTable -- Props + +### Datenanbindung + +| Prop | Typ | Beschreibung | +|------|-----|-------------| +| `data` | `T[]` | Anzuzeigende Datensaetze (aktuelle Seite) | +| `columns` | `ColumnConfig[]` | Spaltendefinitionen (key, label, type, sortable, filterable, searchable, filterOptions, width) | +| `apiEndpoint` | `string?` | Backend-Endpunkt fuer automatische Filter-/ID-Abfragen | +| `hookData` | `{ refetch, pagination, fetchFilterValues? }` | Daten-Hook mit Refetch-Callback und Pagination-Metadata | +| `supportsBackendPagination` | `boolean` | Aktiviert serverseitige Pagination, Sortierung und Filterung | + +### Interaktion + +| Prop | Typ | Beschreibung | +|------|-----|-------------| +| `selectable` | `boolean` | Aktiviert Zeilen-Selektion (Checkboxen) | +| `onSelectionChange` | `(selectedIds: Set) => void` | Callback bei Aenderung der Selektion | +| `idField` | `string` | Feld fuer eindeutige ID (Default: `"id"`) | +| `isRowSelectable` | `(row: T) => boolean` | Bestimmt, ob eine Zeile selektierbar ist | +| `batchActions` | `BatchAction[]` | Bulk-Aktionen fuer selektierte Zeilen | +| `actionButtons` | `ActionButton[]` | Zeilen-Aktionen (Edit, Delete etc.) | + +### BatchAction Interface + +```typescript +interface BatchAction { + label: string; + icon?: React.ReactNode; + loading?: boolean; + onClick: (selectedRows: T[]) => void | Promise; + isApplicable?: (row: T) => boolean; +} +``` + +`isApplicable` ermoeglicht pro Action zu bestimmen, welche der selektierten Zeilen fuer diese Aktion qualifizieren. Der Button zeigt dann `(applicableCount/totalSelected)` und ist `disabled` wenn `applicableCount === 0`. + +--- + +## Unified Filter API + +### Prinzip + +Alle Daten-Endpunkte unterstuetzen drei Modi ueber Query-Parameter auf dem **gleichen** Endpunkt: + +| Modus | Query-Parameter | Response | Zweck | +|-------|----------------|----------|-------| +| Normal (default) | `pagination={...}` | `{ items: T[], pagination: PaginationMetadata }` | Paginierte Datenliste | +| `filterValues` | `mode=filterValues&column=xxx&pagination={crossFilters}` | `string[]` | Distinct-Werte fuer Spaltenfilter-Dropdown | +| `ids` | `mode=ids&pagination={filters}` | `string[]` | Alle IDs der gefilterten Datensaetze | + +**Kein separater `/filter-values`-Endpunkt.** Alle alten `/filter-values`-Sub-Pfade wurden entfernt (Stand 2026-04-13). + +### Cross-Filtering + +Bei `mode=filterValues` werden alle aktiven Filter **ausser** dem angeforderten Column-Filter angewendet (Cross-Filtering). So zeigt z.B. der Status-Filter nur Werte an, die bei den aktuell gesetzten Mandant- und Typ-Filtern vorkommen. + +### Beispiele + +``` +# Normale Liste (Seite 1, 20 Eintraege) +GET /api/users/?pagination={"page":1,"pageSize":20,"sort":[{"field":"name","direction":"asc"}]} + +# Filter-Werte fuer Spalte "status" (mit Cross-Filter auf mandateLabel) +GET /api/users/?mode=filterValues&column=status&pagination={"filters":{"mandateLabel":"Demo AG"}} + +# Alle IDs der gefilterten Ansicht (fuer "Select All Filtered") +GET /api/users/?mode=ids&pagination={"filters":{"status":"active"},"search":"admin"} +``` + +### Backend-Implementierung + +Zentrale Hilfsfunktionen in `gateway/modules/routes/routeHelpers.py`: + +| Funktion | Zweck | +|----------|-------| +| `handleFilterValuesMode(db, modelClass, column, ...)` | SQL-basierte Distinct-Werte via DB-Connector | +| `handleIdsMode(db, modelClass, ...)` | SQL-basierte ID-Liste via DB-Connector | +| `handleFilterValuesInMemory(items, column, ...)` | In-Memory Distinct-Werte (fuer enriched/aggregierte Listen) | +| `handleIdsInMemory(items, ...)` | In-Memory ID-Liste | +| `parseCrossFilterPagination(column, paginationJson)` | Parsed Pagination-JSON und entfernt den angeforderten Column-Filter | +| `_applyFiltersAndSort(items, paginationParams)` | In-Memory Filterung und Sortierung | +| `_extractDistinctValues(items, column, ...)` | Distinct-Werte aus Item-Liste extrahieren | +| `paginateInMemory(items, paginationParams)` | In-Memory Paginierung | + +### Route-Integration Pattern + +```python +@router.get("/", response_model=PaginatedResponse[MyModel]) +def list_items( + request: Request, + pagination: Optional[str] = Query(None), + mode: Optional[str] = Query(None, description="'filterValues' or 'ids'"), + column: Optional[str] = Query(None, description="Column key"), + context: RequestContext = Depends(getRequestContext), +): + if mode in ("filterValues", "ids"): + from modules.routes.routeHelpers import handleFilterValuesInMemory, handleIdsInMemory + items = _buildFullList(context) + if mode == "filterValues": + if not column: + raise HTTPException(status_code=400, detail="column required") + return handleFilterValuesInMemory(items, column, pagination) + return handleIdsInMemory(items, pagination) + + # Normal paginated list... +``` + +--- + +## Selektion + +### ID-basierte Selektion + +Die Selektion arbeitet mit **IDs** (nicht Zeilen-Indizes). Das `idField`-Prop bestimmt, welches Feld als eindeutiger Schluessel verwendet wird (Default: `"id"`). + +| State | Typ | Beschreibung | +|-------|-----|-------------| +| `selectedIds` | `Set` | Menge der selektierten IDs | + +### Selektion-Reset + +Die Selektion wird automatisch zurueckgesetzt bei: +- Seitenwechsel (currentPage) +- Seitengroesse-Aenderung (pageSize) +- Filter-Aenderung +- Suchbegriff-Aenderung +- Sortier-Aenderung + +### Select All Filtered + +Wenn `apiEndpoint` und `supportsBackendPagination` gesetzt sind, zeigt die Toolbar einen "Alle X Eintraege auswaehlen"-Button. Dieser ruft `GET {apiEndpoint}?mode=ids&pagination={currentFilters}` auf und fuegt alle zurueckgegebenen IDs zur Selektion hinzu. + +Ein "Auswahl aufheben"-Button erscheint, wenn "Select All Filtered" aktiv ist. + +### Row-Level Delete + +Wenn eine Zeile geloescht wird (via `actionButtons` Delete), wird deren ID automatisch aus `selectedIds` entfernt. + +--- + +## FilterValuesList (Spaltenfilter-Dropdown) + +Wenn ein filterbarer Spalten-Header geklickt wird, laedt das Dropdown die Distinct-Werte: + +1. `column.filterOptions` (statische Enum) -- wird direkt verwendet, kein Backend-Call +2. `hookData.fetchFilterValues(columnKey, crossFilters)` -- falls im Hook bereitgestellt +3. `GET {apiEndpoint}?mode=filterValues&column=xxx&pagination={currentFilters}` -- automatisch + +Bei mehr als 10 Werten erscheint ein **Suchfeld** (Client-seitige Filterung der geladenen Werte). + +Boolean-Spalten rendern als "Ja"/"Nein". Datum-Spalten rendern als Range-Picker. + +--- + +## Migrierte Endpunkte (Stand 2026-04-13) + +Alle folgenden Endpunkte unterstuetzen `mode=filterValues` und `mode=ids`: + +### Core Routes + +| Route-Datei | Endpunkt | Methode | +|-------------|----------|---------| +| `routeDataUsers.py` | `GET /api/users/` | SQL + In-Memory (je nach Scope) | +| `routeDataConnections.py` | `GET /api/connections/` | In-Memory | +| `routeDataPrompts.py` | `GET /api/prompts/` | In-Memory | +| `routeInvitations.py` | `GET /api/invitations/` | In-Memory | +| `routeSubscription.py` | `GET /api/subscriptions/admin/all` | In-Memory | +| `routeAdminFeatures.py` | `GET /api/admin/features/instances` | In-Memory | +| `routeAdminFeatures.py` | `GET /api/admin/features/templates/roles` | In-Memory | +| `routeAdminFeatures.py` | `GET /api/admin/features/instances/{id}/users` | In-Memory | +| `routeAdminRbacRules.py` | `GET /api/admin/rbac/roles` | In-Memory | +| `routeDataMandates.py` | `GET /api/mandates/` | SQL + In-Memory (je nach Scope) | +| `routeDataMandates.py` | `GET /api/mandates/{id}/users` | In-Memory | +| `routeDataFiles.py` | `GET /api/files/list` | SQL + In-Memory Fallback | +| `routeWorkflowDashboard.py` | `GET /api/automation/dashboard/` (Runs) | Enriched + SQL | +| `routeWorkflowDashboard.py` | `GET /api/automation/dashboard/workflows` | Enriched + SQL | +| `routeBilling.py` | `GET /api/billing/view/users/transactions` | Billing-Interface | + +### Feature Routes + +| Route-Datei | Endpunkt | Methode | +|-------------|----------|---------| +| `routeFeatureTrustee.py` | `GET /api/trustee/{id}/documents` | RBAC SQL + In-Memory Fallback | +| `routeFeatureTrustee.py` | `GET /api/trustee/{id}/positions` | RBAC SQL + In-Memory Fallback | +| `routeFeatureRealEstate.py` | `GET /api/realestate/{id}/projects` | In-Memory | +| `routeFeatureRealEstate.py` | `GET /api/realestate/{id}/parcels` | In-Memory | diff --git a/c-work/1-plan/2026-04-database-health-and-data-cleanup.md b/c-work/1-plan/2026-04-database-health-and-data-cleanup.md new file mode 100644 index 0000000..3133822 --- /dev/null +++ b/c-work/1-plan/2026-04-database-health-and-data-cleanup.md @@ -0,0 +1,228 @@ + + + + +# Database Health and Data Cleanup — Admin Page + +## Beschreibung und Kontext + +SysAdmin-Seite unter Admin > System > "Datenbank-Gesundheit" mit zwei Views: + +1. **Tabellenstatistiken** — Row Count, Size (MB), Index Size, Last Vacuum/Analyze fuer alle Tabellen in allen Datenbanken +2. **Orphan Cleanup** — Generische Erkennung verwaister Datensaetze (FK-Referenzen auf nicht-existierende Parent-Records) mit Cleanup-Buttons + +**Business-Treiber:** Beim Entfernen von Demo-Sets (und generell beim Loeschen von Mandanten/Features) bleiben verwaiste Daten im System. Aktuell gibt es keine Sicht auf den Zustand der Datenbanken und keine Moeglichkeit, Orphans systematisch zu erkennen und zu bereinigen. + +**Abhaengigkeiten:** +- FormGeneratorTable mit Batch-Handling (wird parallel implementiert) +- Bestehende Interface-Architektur (interfaceDb*, interfaceFeature*) + +**Risiko bei Nicht-Umsetzung:** Verwaiste Daten akkumulieren sich ueber die Zeit, belegen Speicher, und koennen zu Inkonsistenzen fuehren (z.B. Workflows ohne Mandate, Feature-Configs ohne Feature-Instanzen). + +## Fokus und kritische Details + +### Dynamische DB-Registry (Selbst-Registrierung) + +Jede Datenbank registriert sich **selbst** in ihrem Interface (`interfaceDb*.py` oder `interfaceFeature*.py`). Es gibt **keine** statische Liste aller Datenbanken. Neue DBs werden automatisch erkannt, entfernte DBs verschwinden. + +**Pattern:** Jedes Interface ruft beim Import oder bei der Initialisierung `_registerDatabase("poweron_xyz")` auf. Die Registry sammelt alle registrierten DBs dynamisch. + +### Deklaratives FK-Registry auf Model-Ebene + +Jedes `*Id`-Feld das eine echte FK-Beziehung darstellt, bekommt ein `fk_target` in `json_schema_extra`: + +```python +mandateId: str = Field( + ..., + json_schema_extra={ + ..., + "fk_target": {"db": "poweron_app", "table": "Mandate"}, + }, +) +``` + +Felder die **keine** DB-FK sind (Stripe-IDs, logische IDs, Graph-Node-IDs, polymorphe `referenceId`) bekommen **kein** `fk_target`. + +### Cross-DB Orphan Detection + +Viele FK-Beziehungen gehen ueber Datenbank-Grenzen hinweg (z.B. `AutoWorkflow.featureInstanceId` in `poweron_graphicaleditor` referenziert `FeatureInstance` in `poweron_app`). Der Scanner muss Parent-IDs aus der Ziel-DB laden und dann im Child-DB pruefen. + +### Performance + +- **Stats:** `pg_stat_user_tables` + `pg_total_relation_size` sind PostgreSQL-Katalog-Queries (kein Table-Scan). Ueber ~14 DBs mit je 5-15 Tabellen dauert der Call wenige Sekunden. +- **Orphans:** Cross-DB-Checks koennen bei grossen Tabellen langsam sein. Ergebnisse werden gecacht (5 Min TTL). +- **`n_live_tup`** ist ein Schaetzwert (aktualisiert nach ANALYZE/VACUUM). Fuer ein Health-Dashboard ausreichend. + +## Ziel und Nicht-Ziele + +**Ziel:** +- Dynamische DB-Registry die sich aus den Interfaces selbst befuellt +- Auto-Registry fuer PowerOnModel-Subklassen (Tabellenname = Klassenname) +- `fk_target` Annotationen auf allen echten FK-Feldern +- Generischer Orphan-Scanner der FK-Graph aus Model-Metadaten aufbaut +- Tabellenstatistiken via PostgreSQL-Katalog +- SysAdmin-UI mit zwei Tabs (Stats + Orphans) und Clean-Buttons +- Navigation-Eintrag unter Admin > System + +**Explizit NICHT:** +- Keine automatische Bereinigung (nur manuell per Button) +- Keine Schema-Migration oder Aenderung an bestehenden DB-Constraints +- Kein Monitoring/Alerting (nur On-Demand-Scan) +- Keine Aenderung an bestehenden Interface-Initialisierungen (nur Registrierungs-Call hinzufuegen) + +## Betroffene Module + +- **Gateway:** `modules/shared/dbRegistry.py` (neu), `modules/shared/fkRegistry.py` (neu), `modules/system/databaseHealth.py` (neu), `modules/routes/routeAdminDatabaseHealth.py` (neu), `modules/datamodels/datamodelBase.py` (Model-Registry), ~20 Datamodel-Dateien (fk_target Annotationen), ~13 Interface-Dateien (DB-Registrierung) +- **Frontend:** `AdminDatabaseHealthPage.tsx` (neu), Routing-Eintrag +- **DB-Migration:** nein +- **Andere Komponenten:** keine + +## Entscheidungen + +| Datum | Entscheidung | Begruendung | +|-------|-------------|------------| +| 2026-04-13 | Dynamische DB-Registry statt statischer Liste | Interfaces registrieren sich selbst — bei Add/Remove von DBs muss die Registry nicht manuell gepflegt werden | +| 2026-04-13 | `fk_target` in `json_schema_extra` statt DB-Constraints als Quelle | Ermoeglicht Cross-DB Orphan Detection (z.B. Greenfield → App DB); DB-Constraints existieren nur in poweron_app | +| 2026-04-13 | `__init_subclass__` fuer Model-Registry | Automatisch, kein manuelles Registrieren noetig; jede PowerOnModel-Subklasse ist sofort im Registry | +| 2026-04-13 | `pg_stat_user_tables` fuer Row Counts statt `SELECT COUNT(*)` | Schnell (Katalog-Query), ausreichend genau fuer Health-Dashboard | +| 2026-04-13 | SysAdmin-only Zugriff | Datenbank-Gesundheit ist eine System-Funktion, nicht mandantenspezifisch | + +## Umsetzungs-Checkliste + +### Phase 1: Infrastruktur (Backend) + +**LLM: Opus 4.6** (Architektur-Entscheidungen, Cross-File-Refactoring) + +- [ ] **DB-Registry** (`modules/shared/dbRegistry.py`) + - `registerDatabase(dbName: str, configPrefix: str = "DB")` — oeffentliche API, registriert eine DB + - `_getRegisteredDatabases() -> Dict[str, str]` — intern, gibt alle registrierten DBs zurueck + - `_getConnectorForDb(dbName: str) -> DatabaseConnector` — intern, Factory fuer Connector + - Thread-safe (Lock oder atomare Operationen) + - `registerDatabase` ohne `_` Prefix weil explizit als oeffentliche API fuer andere Module gedacht + +- [ ] **Selbst-Registrierung in allen Interfaces** (~13 Dateien) + - Jedes `interfaceDb*.py` und `interfaceFeature*.py` ruft `registerDatabase()` auf Modul-Ebene auf + - Kein Umbau der bestehenden Connector-Logik — nur ein zusaetzlicher Registrierungs-Call + +- [ ] **Model-Registry** (`modules/datamodels/datamodelBase.py`) + - `_MODEL_REGISTRY: Dict[str, Type[PowerOnModel]]` — automatisch via `__init_subclass__` + - `_getModelByTableName(tableName: str) -> Optional[Type[PowerOnModel]]` — Lookup + +### Phase 2: FK-Annotationen (Backend) + +**LLM: Composer Fast** (repetitive Annotation-Arbeit, klares Pattern) + +- [ ] **fk_target auf allen FK-Feldern** (~80-90 Felder in ~20 Dateien) + - `modules/datamodels/datamodelMembership.py` — UserMandate, FeatureAccess, Junctions + - `modules/datamodels/datamodelRbac.py` — Role, AccessRule + - `modules/datamodels/datamodelFeatures.py` — FeatureInstance + - `modules/datamodels/datamodelChat.py` — ChatWorkflow, ChatMessage, ChatLog, ChatDocument + - `modules/datamodels/datamodelFiles.py` — FileItem, FileFolder + - `modules/datamodels/datamodelKnowledge.py` — FileContentIndex, ContentChunk, WorkflowMemory + - `modules/datamodels/datamodelBilling.py` — BillingAccount, BillingTransaction, BillingSettings + - `modules/datamodels/datamodelSubscription.py` — MandateSubscription + - `modules/datamodels/datamodelInvitation.py` — Invitation + - `modules/datamodels/datamodelDataSource.py` — DataSource + - `modules/datamodels/datamodelFeatureDataSource.py` — FeatureDataSource + - `modules/datamodels/datamodelUam.py` — Token, AuthEvent, UserConnection, UserVoicePreferences, UserNotification + - `modules/features/graphicalEditor/datamodelFeatureGraphicalEditor.py` — AutoWorkflow, AutoVersion, AutoRun, AutoStepLog, AutoTask + - `modules/features/trustee/datamodelFeatureTrustee.py` — alle Trustee-Models + - `modules/features/workspace/datamodelFeatureWorkspace.py` — WorkspaceUserSettings + - `modules/features/neutralization/datamodelFeatureNeutralizer.py` — DataNeutraliserConfig + - `modules/features/realEstate/datamodelFeatureRealEstate.py` — Kanton, Parzelle, Projekt + - `modules/datamodels/datamodelContent.py` — ContentChunk, ContentObject (falls vorhanden) + - `modules/interfaces/interfaceDbManagement.py` — MessagingSubscription, Prompt (falls Models dort) + +### Phase 3: FK-Discovery und Orphan-Scanner (Backend) + +**LLM: Opus 4.6** (komplexe Cross-DB-Logik, SQL-Generierung) + +- [ ] **FK-Discovery** (`modules/shared/fkRegistry.py`) + - `_discoverFkRelationships() -> List[FkRelationship]` — scannt Model-Registry, liest `fk_target` + - Cached nach erstem Scan (Models aendern sich nicht zur Laufzeit) + - `FkRelationship` Dataclass: `sourceDb, sourceTable, sourceColumn, targetDb, targetTable` + +- [ ] **Orphan-Scanner** (`modules/system/databaseHealth.py`) + - `_scanOrphans(dbFilter: Optional[str]) -> List[OrphanResult]` + - Same-DB: `SELECT COUNT(*) WHERE col NOT IN (SELECT id FROM parent)` + - Cross-DB: Parent-IDs laden, dann `WHERE col NOT IN (...)` + - `_cleanOrphans(db, table, column) -> int` — loescht Orphans, gibt Count zurueck + - `_getTableStats(dbFilter: Optional[str]) -> List[TableStats]` — pg_stat_user_tables Query + +### Phase 4: API-Endpunkte (Backend) + +**LLM: Composer Fast** (Standard-Route-Pattern, klar definiert) + +- [ ] **Route** (`modules/routes/routeAdminDatabaseHealth.py`) + - Prefix: `/api/admin/database-health` + - `GET /stats` — Tabellenstatistiken (optional `?db=...`) + - `GET /orphans` — Orphan-Scan (optional `?db=...`) + - `POST /orphans/clean` — Cleanup Body: `{"db": "...", "table": "...", "column": "..."}` + - `POST /orphans/clean-all` — Alle Orphans bereinigen + - Alle Endpunkte: SysAdmin-only via `requireSysAdminRole` + +- [ ] **Router in app.py registrieren** + +### Phase 5: Navigation (Backend) + +**LLM: Composer Fast** (einzelne Zeile in mainSystem.py) + +- [ ] **Navigation-Eintrag** in `modules/system/mainSystem.py` + - Unter `admin-system-group`, order 98, sysAdminOnly + - Icon: `FaDatabase`, Path: `/admin/database-health` + +### Phase 6: Frontend + +**LLM: Opus 4.6** (neue Seite mit Tabs, FormGeneratorTable-Integration) + +- [ ] **AdminDatabaseHealthPage.tsx** mit zwei Tabs + - Tab 1 "Statistiken": FormGeneratorTable mit DB, Tabelle, Rows, Total Size, Table Size, Index Size, Last Vacuum, Last Analyze. Sortierbar, Summary-Zeile oben. + - Tab 2 "Orphan Cleanup": FormGeneratorTable mit DB, Tabelle, FK-Spalte, Referenz, Orphans, Total Rows, Clean-Button. Filter "Nur Probleme". Batch-Clean. +- [ ] **Routing** in Frontend PAGE_REGISTRY +- [ ] **i18n** — alle Labels mit `t()` taggen + +### Querschnitt-Checks + +- [ ] API-Endpunkte: 4 neue (stats, orphans, clean, clean-all) +- [ ] DB-Schema / Migration: nein +- [ ] Frontend-Komponenten: 1 neue Seite (2 Tabs) +- [ ] RBAC / Permissions: SysAdmin-only +- [ ] Neutralisierung betroffen? nein +- [ ] Navigation / Routing: 1 neuer Eintrag +- [ ] Billing-Impact? nein + +## Akzeptanzkriterien + +| # | Kriterium (Given-When-Then) | Prio | +|---|---------------------------|------| +| 1 | Given SysAdmin auf /admin/database-health, When Tab "Statistiken" geladen, Then werden alle Tabellen aller registrierten DBs mit Row Count und Size angezeigt | must | +| 2 | Given SysAdmin auf Tab "Orphan Cleanup", When Scan ausgefuehrt, Then werden alle FK-Beziehungen mit Orphan-Count angezeigt | must | +| 3 | Given Orphans in AutoWorkflow.featureInstanceId (Cross-DB), When "Clean" geklickt, Then werden die verwaisten AutoWorkflow-Zeilen geloescht und Count aktualisiert | must | +| 4 | Given neue Feature-DB wird hinzugefuegt (z.B. poweron_newfeature), When Interface sich registriert und Scan laeuft, Then erscheint die neue DB automatisch in Stats und Orphan-Scan | must | +| 5 | Given kein SysAdmin-User, When /admin/database-health aufgerufen, Then 403 Forbidden | must | +| 6 | Given grosse Tabelle (>100k Rows), When Orphan-Scan laeuft, Then antwortet der Scan innerhalb von 30 Sekunden | should | +| 7 | Given "Alle bereinigen" geklickt, When mehrere Tabellen Orphans haben, Then werden alle Orphans in allen Tabellen bereinigt und Summary angezeigt | should | + +## Testplan + +| ID | AC | Art | Automatisiert | Methode | Status | +|----|----|-----|--------------|---------|--------| +| T1 | 1 | api | nein | Manuell: GET /stats, pruefen ob alle DBs und Tabellen erscheinen | pending | +| T2 | 2 | api | nein | Manuell: Demo laden, Demo entfernen (ohne Cascade), GET /orphans pruefen | pending | +| T3 | 3 | api | nein | Manuell: POST /orphans/clean fuer AutoWorkflow, dann GET /orphans erneut | pending | +| T4 | 4 | unit | nein | Manuell: _registerDatabase() aufrufen, pruefen ob in _getRegisteredDatabases() | pending | +| T5 | 5 | api | nein | Manuell: Non-SysAdmin Request, 403 pruefen | pending | +| T6 | 7 | e2e | nein | Manuell: Mehrere Orphan-Typen erzeugen, "Alle bereinigen", Stats pruefen | pending | + +## Links + +- Cursor-Plan: `.cursor/plans/data_cleanup_admin_page_0a610562.plan.md` +- Demo-Config Bug-Fix: `gateway/modules/demoConfigs/investorDemo2026.py` (remove() Cascade) +- deleteMandate Cascade-Fix: `gateway/modules/interfaces/interfaceDbApp.py` (_cascadeDeleteGraphicalEditorData) +- Coding-Conventions: `wiki/d-guides/coding-conventions.md` + +## Abschluss + +- [ ] b-reference/ aktualisiert (gateway/architecture.md — neuer Abschnitt "Database Health") +- [ ] TOPICS.md aktualisiert (neues Thema "Database Health / Data Cleanup") +- [ ] Dieses Dokument → z-archive/ verschoben diff --git a/z-archive/post-cleanup-report-2026-04-12.md b/z-archive/post-cleanup-report-2026-04-12.md index 32d58a1..f5326e4 100644 --- a/z-archive/post-cleanup-report-2026-04-12.md +++ b/z-archive/post-cleanup-report-2026-04-12.md @@ -57,19 +57,23 @@ Dieser Endpunkt (`GET /user-language-options` in `routeI18n.py`) wurde entfernt. --- -### 2.3 Billing filter-values — KEIN Handlungsbedarf +### 2.3 Billing filter-values — Erledigt (2026-04-13) -**Befund:** `BillingDataView.tsx` ruft `/api/billing/view/users/transactions/filter-values` auf. Dieser Endpunkt existiert weiterhin in `routeBilling.py` (Zeile 1805). Es wurde nur der **Admin**-Endpunkt `GET /admin/transactions/{targetMandateId}/filter-values` entfernt, der im Frontend nirgends referenziert war. +**Befund (alt):** `BillingDataView.tsx` rief `/api/billing/view/users/transactions/filter-values` auf. -**Empfehlung:** Kein Handlungsbedarf. +**Update:** Im Rahmen der Unified Filter API Migration (2026-04-13) wurde das gesamte `/filter-values`-Pattern eliminiert. `BillingDataView.tsx` nutzt jetzt `GET /api/billing/view/users/transactions?mode=filterValues&column=xxx`. Der separate Endpunkt existiert nicht mehr. --- -### 2.4 FormGeneratorTable — Generisches filter-values-Pattern +### 2.4 FormGeneratorTable — Unified Filter API (aktualisiert 2026-04-13) -**Befund:** `FormGeneratorTable.tsx` baut generisch `{apiEndpoint}/filter-values`-URLs zusammen. Das ist ein allgemeines Pattern — jede Route, die als `apiEndpoint` übergeben wird, muss einen `/filter-values`-Subpfad anbieten. +**Befund (alt):** `FormGeneratorTable.tsx` baute generisch `{apiEndpoint}/filter-values`-URLs zusammen. -**Empfehlung:** Kein direkter Handlungsbedarf durch die Cleanup-Aktion. Aber: Falls in Zukunft Endpunkte entfernt werden, die als `apiEndpoint` im FormGenerator genutzt werden, muss der zugehörige `filter-values`-Pfad mitberücksichtigt werden. +**Update:** Das `/filter-values`-Sub-Pfad-Pattern wurde komplett durch Query-Parameter auf dem Haupt-Endpunkt ersetzt: +- `GET {apiEndpoint}?mode=filterValues&column=xxx` — Distinct-Werte für Spaltenfilter +- `GET {apiEndpoint}?mode=ids&pagination={filters}` — Alle gefilterten IDs (für "Select All Filtered") + +Alle separaten `/filter-values`-Endpunkte in Core-Routes und Feature-Routes (Trustee, RealEstate) wurden entfernt. Siehe `wiki/b-reference/frontend-nyla/formgenerator.md` für die aktuelle Dokumentation. ---