diff --git a/b-reference/frontend-nyla/formgenerator.md b/b-reference/frontend-nyla/formgenerator.md index 938030c..ec25173 100644 --- a/b-reference/frontend-nyla/formgenerator.md +++ b/b-reference/frontend-nyla/formgenerator.md @@ -230,9 +230,10 @@ Wenn eine Zeile geloescht wird (via `actionButtons` Delete), wird deren ID autom 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 +1. `column.filterOptions` (statische Enum, page-defined) -- wird direkt verwendet, kein Backend-Call +2. `column.options` (kommt aus `frontend_options` der Pydantic-Felder, automatisch via `resolveColumnTypes`) -- wird direkt verwendet +3. `hookData.fetchFilterValues(columnKey, crossFilters)` -- falls im Hook bereitgestellt +4. `GET {apiEndpoint}?mode=filterValues&column=xxx&pagination={currentFilters}` -- automatisch Bei mehr als 10 Werten erscheint ein **Suchfeld** (Client-seitige Filterung der geladenen Werte). @@ -240,6 +241,77 @@ Boolean-Spalten rendern als "Ja"/"Nein". Datum-Spalten rendern als Range-Picker --- +## Cell-Rendering: KEINE hardcoded Labels in Pages + +**Regel:** Pages duerfen Value->Label-Mappings NICHT im Frontend hardcoden (kein `formatter: (v) => v ? 'Ja' : 'Nein'`, kein `_STATUS_LABELS[v]`, kein `scopeLabels[v]`). Daten und Display-Labels kommen ausschliesslich aus dem Backend-Modell. + +### Pattern fuer Booleans + +Im Pydantic-Feld: + +```python +active: bool = Field( + default=True, + json_schema_extra={ + "frontend_type": "checkbox", + "label": "Aktiv", + "frontend_format_labels": ["Ja", "-", "Nein"], # [TRUE, NULL, FALSE] + }, +) +``` + +Im Frontend-Page reicht: + +```typescript +{ key: 'active', sortable: true, filterable: true } +``` + +Kein `formatter`, kein hardcoded `Ja`/`Nein`. `FormGeneratorTable.renderBooleanCell` greift automatisch und nutzt die i18n-aufgeloesten Labels. + +### Pattern fuer Enums (`select`-Felder) + +Im Pydantic-Feld: + +```python +status: str = Field( + default="running", + json_schema_extra={ + "frontend_type": "select", + "label": "Status", + "frontend_options": [ + {"value": "running", "label": "Läuft"}, + {"value": "completed", "label": "Abgeschlossen"}, + {"value": "failed", "label": "Fehlgeschlagen"}, + ], + }, +) +``` + +Im Frontend-Page reicht: + +```typescript +{ key: 'status', sortable: true, filterable: true } +``` + +`resolveColumnTypes` merged `options` automatisch in `ColumnConfig.options`. `FormGeneratorTable` resolved Cell-Value -> Label und Filter-Dropdown-Labels aus diesen Options. Der `i18nRegistry.resolveText` uebersetzt die Labels server-seitig basierend auf der Sprache des Requests. + +### Pattern fuer Header-Labels + +`column.label` ist optional. `resolveColumnTypes` zieht das Label aus dem Backend-Attribut (`label` aus `json_schema_extra`). Page setzt `label` nur, um den Backend-Default zu ueberschreiben. + +### Anti-Pattern (nicht erlaubt) + +```typescript +// FALSCH — hardcoded Labels gehoeren NICHT in die Page +formatter: (v: boolean) => v ? Ja : Nein +formatter: (v: string) => statusMap[v] ?? v +filterLabelResolver: (v: string) => myLabels[v] +``` + +Wenn das Backend keine `frontend_options`/`frontend_format_labels` setzt: **Pydantic-Modell erweitern, NICHT die Page hacken.** + +--- + ## Migrierte Endpunkte (Stand 2026-04-13) Alle folgenden Endpunkte unterstuetzen `mode=filterValues` und `mode=ids`: diff --git a/b-reference/platform/database-architecture.md b/b-reference/platform/database-architecture.md index 8062622..6cfbadb 100644 --- a/b-reference/platform/database-architecture.md +++ b/b-reference/platform/database-architecture.md @@ -1,6 +1,6 @@ - - + + # Datenbank-Architektur @@ -295,30 +295,34 @@ mandateId: str = Field( - `fk_target` ist die einzige FK-Annotation — sowohl fuer Orphan-Detection als auch Label-Resolution und UI-Attribute - Felder ohne DB-FK (Stripe-IDs, Graph-Node-IDs, polymorphe referenceId) haben kein `fk_target` - Startup-Validierung (`validateFkTargets`) prueft Vollstaendigkeit; fehlende Keys brechen den Start ab +- **Soft FK** (`"softFk": True`): Optionaler Marker fuer "weiche" Referenzen, die Sentinel-/Lineage-Werte halten, fuer die absichtlich keine DB-Zeile existiert (z. B. `AutoWorkflow.templateSourceId = "trustee-receipt-import"` aus `featureModule.getTemplateWorkflows()`). Label-Resolution funktioniert weiterhin; der Orphan-Scanner ueberspringt soft FKs vollstaendig (kein Display, kein Cleanup), damit korrekte Datensaetze nicht geloescht werden. ### Orphan-Scanner (`modules/system/databaseHealth.py`) - `_getTableStats(dbFilter)` — `pg_stat_user_tables` + `pg_total_relation_size` - `_scanOrphans(dbFilter)` — Same-DB: `NOT EXISTS`, Cross-DB: Parent-IDs laden + `NOT IN` - `_cleanOrphans(db, table, column)` — loescht Orphans, gibt Count zurueck -- `_cleanAllOrphans()` — alle Orphans bereinigen +- `_cleanAllOrphans(force, excludeUserFks)` — alle Orphans bereinigen; `excludeUserFks=True` ueberspringt UserInDB.id-Referenzen +- `_isUserIdFk(targetTable, targetColumn)` — Helper: matcht `UserInDB.id` (case-insensitive). Eine Stelle, die scan-route + clean-all + frontend gemeinsam nutzen - 5-Minuten-Cache fuer Orphan-Ergebnisse +- **Pre-Filter:** Source-/Target-Tabelle muss existieren, Source-/Target-Spalte muss als physische Spalte vorhanden sein, FK darf nicht `softFk: True` sein. Erst dann wird ein Orphan-Eintrag erzeugt — sonst werden produktive Datensaetze geloescht (z. B. Trustee-Workflows mit Sentinel-`templateSourceId`). +- **User-FK-Filter:** Orphans, die auf `UserInDB.id` zeigen (Audit-/Billing-/Membership-Reste geloeschter User), gehoeren in den separaten User-Purge-Workflow. Frontend-Checkbox `Ohne FK-Referenzen zu UserInDB.id` (default ON) blendet sie aus dem Scan-Resultat aus, und `clean-all` ueberspringt sie identisch — die UI zeigt also nie Orphans an, die der naechste Klick nicht auch loeschen wuerde. ### API (`modules/routes/routeAdminDatabaseHealth.py`) | Methode | Pfad | Beschreibung | |---------|------|-------------| | GET | `/api/admin/database-health/stats` | Tabellenstatistiken (optional `?db=...`) | -| GET | `/api/admin/database-health/orphans` | Orphan-Scan (optional `?db=...`) | +| GET | `/api/admin/database-health/orphans` | Orphan-Scan (optional `?db=...`, `?excludeUserFks=true`) | | POST | `/api/admin/database-health/orphans/clean` | Einzeln-Cleanup `{"db","table","column"}` | -| POST | `/api/admin/database-health/orphans/clean-all` | Batch-Cleanup aller Orphans | +| POST | `/api/admin/database-health/orphans/clean-all` | Batch-Cleanup `{"force","excludeUserFks"}` | Alle Endpunkte: SysAdmin-only via `requireSysAdminRole`. ### Frontend (`AdminDatabaseHealthPage.tsx`) - Tab "Statistiken": Sortierbare Tabelle mit DB-Filter und Summary-Leiste -- Tab "Orphan Cleanup": Tabelle mit Clean-Button pro Zeile + "Alle bereinigen" +- Tab "Orphan Cleanup": Tabelle mit Clean-Button pro Zeile + "Alle bereinigen", Checkboxen `Nur Probleme` und `Ohne FK-Referenzen zu UserInDB.id` (letzteres default ON) --- diff --git a/c-work/_CHANGELOG.md b/c-work/_CHANGELOG.md index 376b478..e5bafad 100644 --- a/c-work/_CHANGELOG.md +++ b/c-work/_CHANGELOG.md @@ -4,7 +4,7 @@ # Changelog (c-work) -Eine Zeile pro Change, neueste oben. Begruendungen gehoeren ins zugehoerige +Eine Zeile pro Change, NEUESTE EINTRAEGE ZUOBERST. Begruendungen gehoeren ins zugehoerige `c-work//.md` oder die PR-Beschreibung. Format: `- YYYY-MM-DD | | | [(c-work: )] [(PR: #123)]` @@ -14,11 +14,28 @@ Skip: reine Refactors, Formatting, Lint, Dep-Bumps, Test-only, Wiki-Tippfehler. ## 2026-04-26 +- 2026-04-26 | feat | gateway+frontend-nyla | Database-Health Orphan-Cleanup: neue Checkbox `Ohne FK-Referenzen zu UserInDB.id` (default ON). Deleted-User-Reste in Audit/Billing/Membership-Tabellen sammeln sich natuerlich an wenn ein User geloescht wird und gehoeren in den separaten User-Purge-Workflow, nicht in die generische FK-Bereinigung. Backend: `_isUserIdFk(targetTable, targetColumn)`-Helper (case-insensitive auf Tabellenname); `_cleanAllOrphans(force, excludeUserFks)` ueberspringt entsprechende Relationen; `/orphans?excludeUserFks=true` filtert Scan-Resultate; `OrphanCleanAllRequest.excludeUserFks` filtert clean-all. Frontend: Checkbox neben `Nur Probleme`, default checked, mit Tooltip; URL-Param + clean-all Body-Field synchron; `Alle bereinigen`-Counter zeigt jetzt nur Non-User-FK-Orphans +- 2026-04-26 | fix | gateway | aicorePluginOpenai: `max_tokens` durch `max_completion_tokens` ersetzt in `callAiBasic` und `callAiBasicStream`. Hintergrund: OpenAI lehnt `max_tokens` fuer gpt-5.x / o-series Modelle mit HTTP 400 `unsupported_parameter` ab (`Use 'max_completion_tokens' instead`). Im Log `log_app_20260426.log` (L741-764) sichtbar: `gpt-5.4-nano` failover scheiterte sofort, ModelSelector wechselte auf `claude-opus-4-6`. Per OpenAI API-Reference akzeptieren ALLE aktuellen Chat-Completions-Modelle (legacy gpt-4o/gpt-4.1, gpt-5.x, o1/o3/o4) `max_completion_tokens`, daher universeller Wechsel statt Modell-spezifischer Verzweigung +- 2026-04-26 | feat | gateway | PDF-Renderer Emoji-Support: Noto Emoji (monochrome, OFL) als Fallback-Font registriert. Bisher rendern WinAnsi-Core-Fonts (Helvetica/Courier) Emoji-Codepoints (U+2600+, U+1F300+) als fehlende Glyphen-Quadrate. Neu unter `gateway/assets/fonts/NotoEmoji-Regular.ttf` (~419 KB, 887 Codepoints) + `_pdfFontFallback.py` Helper: registriert die TTF einmalig bei reportlab, scannt deren cmap, und `wrapEmojiSpansInXml` umschliesst zusammenhaengende Emoji-Runs (codepoint >= U+2000 ∧ in cmap) mit `` — nestet sauber in ``/``/``. `rendererPdf._markdownInlineToReportlabXml` wendet das am Ende an, also greift es ueberall wo Paragraph-Markup gebaut wird (Headings, Paragraphs, Bullet-Lists, Table-Cells, extracted_text). `Preformatted` (Code-Blocks) ist Single-Font-only und bleibt unveraendert — Emojis in Code-Bloecken sind selten, Box-Drawing wird wie bisher zu ASCII normalisiert. Smoke-Test in `test_renderer_pdf_smoke.py` +- 2026-04-26 | fix | gateway | FK Orphan-Scanner loeschte korrekte Trustee-Workflows: `AutoWorkflow.templateSourceId` enthaelt teils Sentinel-IDs (z.B. `"trustee-receipt-import"` aus `featureModule.getTemplateWorkflows()`), die absichtlich keine DB-Zeile haben — wurden faelschlich als Orphans markiert und mit `force=true` (oder unter 50%-Schwelle) geloescht. Neuer `softFk: True` Flag in `fk_target`: `fkRegistry.FkRelationship` traegt das Flag, `databaseHealth._scanOrphans`/`_cleanOrphans`/`_listOrphans` ueberspringen soft FKs komplett (kein Display, kein Cleanup). Label-Resolution unveraendert. `templateSourceId` als `softFk: True` markiert. Wiki `b-reference/platform/database-architecture.md` aktualisiert +- 2026-04-26 | refactor | gateway+frontend-nyla | Letzte 4 hardcoded Cell-Label-Stellen entfernt: (1) `RoleView.scopeType` (select mit `frontend_options` System-Template/Template/Mandant) + `RoleView.userCount` -> `AdminMandateRolesPage` zieht Attribute jetzt von `RoleView`, kein lokaler `scopeType`-Formatter mehr; (2) `Invitation.expiredFlag` als Pydantic `@computed_field` (live aus `expiresAt`+`time.time()`) mit `frontend_format_labels=["Ja","-","Nein"]`; (3) `Invitation.emailSent` -> `emailSentFlag` umbenannt + neues `emailSentAt`-Feld (Persistenz im DB-Record), `routeInvitations.create_invitation` setzt beide nach erfolgreichem Mailversand; (4) `TrusteePositionView` mit `syncStatus` (select Ausstehend/Synchronisiert/Fehler/Abgebrochen) + `syncErrorMessage` -> `routeFeatureTrustee.get_positions` enriched Rows aus `TrusteeAccountingSync`, `useTrustee` lookt Attribute via neuem `attributesEntityName`-Override, `TrusteePositionsView` hat eigenen Sync-State + Custom-Renderer geloescht. `attributeUtils.getModelAttributeDefinitions` und `i18nRegistry.@i18nModel` verarbeiten jetzt auch `model_computed_fields` (Labels + `frontend_format_labels` werden registriert) +- 2026-04-26 | refactor | gateway+frontend-nyla | Hardcoded Cell-Labels aus FormGeneratorTable-Pages entfernt: Boolean-Formatter ("Ja"/"Nein", "OK"/"Fehler") und Enum-Maps (`_STATUS_LABELS`, `scopeLabels`) aus `GraphicalEditorWorkflowsPage`, `GraphicalEditorTemplatesPage`, `AutomationsDashboardPage`, `ComplianceAuditPage` ersatzlos geloescht. Stattdessen Pydantic-Modelle (`AutoWorkflow.active|sharedReadOnly|isTemplate|notifyOnFailure|templateScope`, `AutoRun.status`, `AutoStep.status`, `AutoTask.status`, `AutoVersion.status`, `Automation2WorkflowView.isRunning`, `AuditLogEntry.success`) mit `frontend_format_labels`/`frontend_options` ausgestattet. `resolveColumnTypes` merged jetzt auch `label` und `options` aus dem Backend; `FormGeneratorTable` rendert Cells UND Filter-Dropdowns ueber `column.options` automatisch — Pages duerfen Labels nicht mehr im Frontend hardcoden +- 2026-04-26 | feat | frontend-nyla | FormGeneratorTable + columnTypeResolver: `ColumnConfig.options` (aus `frontend_options` der Pydantic-Felder) ist jetzt erste Klasse; Cell-Renderer und Filter-Liste resolven Value -> Label automatisch; `column.label` ist optional und wird vom Backend-Attribut gefuellt +- 2026-04-26 | fix | gateway+frontend-nyla | GraphicalEditor Workflows-Tabelle: `createdAt`-Alias aus `routeFeatureGraphicalEditor.get_workflows` entfernt — Frontend nutzt nun das kanonische `sysCreatedAt`. `GraphicalEditorWorkflowsPage` + `GraphicalEditorTemplatesPage` holen Attribute jetzt von `Automation2WorkflowView` (mit `frontend_type=timestamp` fuer `sysCreatedAt`/`lastStartedAt` und `frontend_type=number` fuer `runCount`); Spalten haben explizit `sortable`/`filterable` gesetzt — fehlende Sort-Icons und Zahl-statt-PeriodPicker behoben. `Automation2Workflow`-TS-Interface auf `sysCreatedAt` umgestellt +- 2026-04-26 | refactor | frontend-nyla | RealEstate Parcels+Projects + GraphicalEditor Workflows+Templates: `apiEndpoint` auf den jeweiligen Listenroute gesetzt — Backend-Routen unterstuetzen `mode=filterValues&column=X` und `mode=ids` ueber `handleFilterValuesInMemory`/`handleIdsInMemory`; FormGeneratorTable holt Filter-Werte jetzt sauber vom Backend (kein Local-Mode mehr noetig) +- 2026-04-26 | feat | gateway | routeFeatureGraphicalEditor: `/workflows` und `/templates` Endpunkte unterstuetzen jetzt `mode=filterValues&column=X` und `mode=ids` (FormGeneratorTable Backend-Pattern) ueber `handleFilterValuesInMemory`/`handleIdsInMemory` aus routeHelpers +- 2026-04-26 | fix | frontend-nyla | AdminLanguagesPage: `hookData.fetchFilterValues` implementiert (offizielles Pattern fuer In-Memory-Tabellen ohne Backend-Endpunkt) — distinct Filter-Werte aus `displayRows` mit Cross-Filter-Support; ersetzt das zuvor versuchte FormGeneratorTable-Local-Mode +- 2026-04-26 | revert | frontend-nyla | FormGeneratorTable: Local-Mode-Fallback in `getUniqueValuesForColumn` und das Entfernen der console.warn rueckgaengig gemacht — silent Fallbacks verstossen gegen das Prinzip "klare Datenstrukturen + Modelle im Backend"; Tabellen muessen stattdessen `apiEndpoint` (Backend) oder `hookData.fetchFilterValues` (explizit) setzen +- 2026-04-26 | fix | frontend-nyla | AutomationsDashboardPage Workflows-Tab: Spalten `isRunning` und `runCount` als `sortable` + `filterable` markiert (Backend unterstuetzt JOIN-basierte Sortierung/Filterung dieser computed fields) +- 2026-04-26 | fix | gateway | Automation2 ExecutionEngine: `AutoRun.startedAt` wird jetzt in `createRun` gesetzt; `AutoRun.completedAt` wird in `updateRun` automatisch gesetzt sobald Status terminal wird (completed/failed/stopped/cancelled); routeWorkflowDashboard.stopRun setzt `completedAt` ebenfalls. Bisher wurden diese Felder nie befuellt — daher waren `started`/`completed` Spalten in der Runs-Tabelle leer - 2026-04-26 | fix | frontend-nyla | PeriodPicker in FormGeneratorTable: Preset-Kind (`thisMonth`, `thisQuarter` etc.) wird im Filter-Wert mitgespeichert, damit es beim Round-Trip erhalten bleibt und `isValueAllowed` nicht faelschlicherweise auf `ytd` zurueckfaellt - 2026-04-26 | fix | gateway | routeAudit: 500-Fehler bei Datumsfilter behoben — `PaginationParams(pageSize=999999)` verletzte `le=1000`-Constraint; nutzt jetzt `model_construct` + `SortField`-Konvertierung - 2026-04-26 | refactor | gateway | 5 Pattern-Inkonsistenzen aus FormGeneratorTable-Audit behoben: routeDataUsers stiller Fallback entfernt; routeFeatureRealEstate Projekte+Parzellen nutzen jetzt `applyFiltersAndSort` statt nur Sorting; routeAdminRbacRules custom filter/sort durch shared Helper ersetzt + `enrichRowsWithFkLabels` ergaenzt - 2026-04-26 | fix | frontend-nyla | ComplianceAuditPage: Fallback-Formatter fuer `instanceLabel` und `username` zeigen jetzt `NA(uuid)` statt abgeschnittener UUID ohne Kontext - 2026-04-26 | fix | gateway | aicoreModelRegistry: Race-Condition in `refreshModels` behoben — Lock verhindert konkurrierende Refreshes; harmlose Duplikate (gleicher Name+Connector) werden toleriert statt als Fehler geworfen +- 2026-04-26 | fix | gateway | routeDataFiles: `mode=filterValues` nutzt jetzt `enrichRowsWithFkLabels` + `handleFilterValuesInMemory` statt direktem `getDistinctColumnValues` — FK-Spalten (mandateId, featureInstanceId) zeigen wieder Labels statt UUIDs +- 2026-04-26 | fix | gateway | routeFeatureTrustee: 3x `mode=filterValues` (Documents, Positions, generisch) von `getDistinctColumnValuesWithRBAC` auf `enrichRowsWithFkLabels` + `handleFilterValuesInMemory` umgestellt — FK-Spalten (organisationId, roleId, userId, contractId etc.) zeigen Labels statt UUIDs; generischer Endpunkt nutzt zusaetzlich `_buildFeatureInternalResolvers` fuer Feature-interne FKs +- 2026-04-26 | fix | gateway | routeAudit: `_enrichUserAndInstanceLabels` setzt jetzt `NA(uuid)` als Fallback statt `None` fuer nicht aufloesbare FeatureInstance/User-IDs — Filter-Dropdown fuer Feature-Instanz war leer weil alle Labels `None` waren - 2026-04-26 | fix | gateway | Zwei Filter-Bugs: (1) `applyFiltersAndSort` in routeHelpers: `value is None` filtert jetzt auf leere Felder statt den Filter zu ueberspringen ("Leer"-Option funktioniert); (2) `routeAudit._applySortFilterSearch` durch Delegation an shared `applyFiltersAndSort` ersetzt — Datumsbereich-Filter (`between`-Operator) und Null-Filter funktionieren jetzt konsistent - 2026-04-26 | fix | gateway | Stille `except Exception`-Fallbacks in `mode=filterValues` entfernt: `routeFeatureTrustee` (_handleDocumentMode, _handlePositionMode, _paginatedReadEndpoint), `routeDataFiles`, `routeDataMandates` — Fehler bubblen jetzt hoch statt stillschweigend auf teuren In-Memory-Pfad auszuweichen - 2026-04-26 | fix | gateway | Filter-Dropdown-UUID-Bug: `enrichRowsWithFkLabels` fehlte im `mode=filterValues`-Pfad bei 8 Routen (routeDataConnections, routeInvitations, routeAdminFeatures, routeSubscription, routeFeatureRealEstate x2); Wiki `fk-label-resolution.md` mit Filter-Enrichment-Regel fuer AI-Agent ergaenzt @@ -68,4 +85,6 @@ Skip: reine Refactors, Formatting, Lint, Dep-Bumps, Test-only, Wiki-Tippfehler. - 2026-04-25 | fix | frontend-nyla | DataPicker-Modal auf CSS-Variablen umgestellt; Hover-Safety-Net `dataPickerLeaf:hover *` haelt Type-Hints auf blauem Hintergrund lesbar (c-work: c-work/4-done/2026-04-feature-instance-ref-adapter-migration.md) - 2026-04-25 | docs | wiki | Audit `2026-04-node-typization-audit.md` archiviert; Folge-Track-Doc `2026-04-feature-instance-ref-adapter-migration.md` direkt in `4-done/` als erledigt - 2026-04-25 | docs | wiki | Changelog-Konvention im `_CHANGELOG.md` eingefuehrt; in `README.md` + `doc-sync.mdc` referenziert +- 2026-04-26 | fix | gateway+frontend | Automation Workflow-Tab: `Automation2WorkflowView` erstellt damit sysCreatedAt (timestamp) und lastStartedAt korrekt als PeriodPicker-Spalten erkannt werden; lastStartedAt nutzt jetzt AutoRun.startedAt statt sysCreatedAt; computed-field Filter/Sort via applyFiltersAndSort in-memory; Runs-Tab auf startedAt/completedAt umgestellt statt System-Audit-Felder +- 2026-04-26 | perf | gateway | routeWorkflowDashboard get_system_workflows: N+1 AutoRun-Abfragen ersetzt durch LEFT JOIN + Subquery-Aggregation (eine Daten- + eine Count-Query); FK-Sort-Pfad nutzt eine gebündelte Run-Stats-Query; lastStartedAt/runCount/isRunning-Filter im Join-Pfad in SQL