From 44f2d458294b39cdf5c48380045c252d276d5035 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Sun, 26 Apr 2026 18:12:03 +0200
Subject: [PATCH] datamodel sctirc fk logic in one place
---
b-reference/frontend-nyla/formgenerator.md | 4 +-
b-reference/gateway/architecture.md | 2 +-
b-reference/gateway/fk-label-resolution.md | 241 ++++++++++++++++++
b-reference/platform/database-architecture.md | 8 +-
c-work/_CHANGELOG.md | 27 ++
5 files changed, 276 insertions(+), 6 deletions(-)
create mode 100644 b-reference/gateway/fk-label-resolution.md
diff --git a/b-reference/frontend-nyla/formgenerator.md b/b-reference/frontend-nyla/formgenerator.md
index b7304fc..938030c 100644
--- a/b-reference/frontend-nyla/formgenerator.md
+++ b/b-reference/frontend-nyla/formgenerator.md
@@ -157,7 +157,7 @@ GET /api/users/?mode=ids&pagination={"filters":{"status":"active"},"search":"adm
### Backend-Implementierung
-Zentrale Hilfsfunktionen in `gateway/modules/routes/routeHelpers.py`:
+Zentrale Hilfsfunktionen in `gateway/modules/routes/routeHelpers.py` (FK-Label-Aufloesung: siehe [FK Label Resolution](../gateway/fk-label-resolution.md)):
| Funktion | Zweck |
|----------|-------|
@@ -236,7 +236,7 @@ Wenn ein filterbarer Spalten-Header geklickt wird, laedt das Dropdown die Distin
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.
+Boolean-Spalten rendern als "Ja"/"Nein". Datum-Spalten rendern als Range-Picker (Von/Bis, `type` date/timestamp/time). Zahlenspalten (`integer`/`int`/`number`/`float`) rendern als Operatorauswahl (=, >, >=, <, <=, Zwischen) plus Wertfeld(er) und `Anwenden`; Filter-Payload: `{ operator, value }` bzw. `{ operator: "between", value: { from, to } }` (kompatibel mit `routeHelpers._matchesFilter` / SQL-Pagination).
---
diff --git a/b-reference/gateway/architecture.md b/b-reference/gateway/architecture.md
index 5875a67..5bda8b3 100644
--- a/b-reference/gateway/architecture.md
+++ b/b-reference/gateway/architecture.md
@@ -170,7 +170,7 @@ Alle 13 Trustee-Tabellen sind ueber paginierte, RBAC-gefilterte GET-Endpunkte ab
| `GET /api/trustee/{instanceId}/accounting/configs` | `TrusteeAccountingConfig` | Connector-Konfiguration (read-only; Secrets maskiert) |
| `GET /api/trustee/{instanceId}/accounting/syncs` | `TrusteeAccountingSync` | Audit-Eintraege der Sync-Laeufe (read-only) |
-Alle sieben Endpunkte teilen sich den Helper `_paginatedReadEndpoint` (`routeFeatureTrustee.py`), der das Pattern aus `get_documents` / `get_positions` reproduziert: Unified Filter API (`mode=filterValues|ids`), `_validateInstanceAccess` fuer Instanz-Gating und `getRecordsetPaginatedWithRBAC` mit `featureCode` + `data.feature.trustee.` fuer datenbasierten RBAC.
+Alle sieben Endpunkte teilen sich den Helper `_paginatedReadEndpoint` (`routeFeatureTrustee.py`), der das Pattern aus `get_documents` / `get_positions` reproduziert: Unified Filter API (`mode=filterValues|ids`), `_validateInstanceAccess` fuer Instanz-Gating und `getRecordsetPaginatedWithRBAC` mit `featureCode` + `data.feature.trustee.` fuer datenbasierten RBAC. FK-Felder werden automatisch via `enrichRowsWithFkLabels` aufgeloest (siehe [FK Label Resolution](fk-label-resolution.md)).
UI-seitig sind diese Endpunkte unter `/mandates/{m}/trustee/{i}/data-tables[?tab=]` (View `TrusteeDataTablesView`) zugaenglich; UI-Sichtbarkeit der Seite haengt am Permission-Eintrag `ui.feature.trustee.data-tables` (Template-Rollen `trustee-viewer`, `trustee-user`, `trustee-accountant`, Admin via Wildcard).
diff --git a/b-reference/gateway/fk-label-resolution.md b/b-reference/gateway/fk-label-resolution.md
new file mode 100644
index 0000000..550b99e
--- /dev/null
+++ b/b-reference/gateway/fk-label-resolution.md
@@ -0,0 +1,241 @@
+
+
+
+
+# FK Label Resolution
+
+## Ueberblick
+
+Wenn das Backend eine paginierte Liste ausliefert, enthalten FK-Felder (z.B. `mandateId`, `featureInstanceId`, `userId`) nur die rohe UUID. Das UI zeigt stattdessen ein menschenlesbares Label. Damit das funktioniert, fuegt `enrichRowsWithFkLabels()` automatisch eine `{field}Label`-Spalte pro FK-Feld hinzu, bevor die Response an das Frontend geht.
+
+```
+Beispiel: mandateId = "a1b2c3..." → mandateIdLabel = "Demo AG"
+```
+
+Das Frontend rendert dann `mandateIdLabel` anstelle der ID. Felder, die keinen Resolver haben, zeigen im UI `NA(...)`.
+
+## Architektur
+
+```mermaid
+flowchart TD
+ Model["Pydantic Model\n(json_schema_extra.fk_target)"] -->|"fk_target.table"| AutoBuild["_buildLabelResolversFromModel()"]
+ AutoBuild -->|"resolvers dict"| Enrich["enrichRowsWithFkLabels()"]
+ ExtraRes["extraResolvers\n(feature-intern)"] -->|"merge"| Enrich
+ Enrich -->|"rows + {field}Label"| Response["API Response"]
+ BuiltIn["_BUILTIN_FK_RESOLVERS\n(Mandate, FeatureInstance,\nUserInDB, Role)"] -->|"lookup"| AutoBuild
+```
+
+### Ablauf
+
+1. **Modell-Scan**: `_buildLabelResolversFromModel(modelClass)` iteriert ueber alle Felder des Pydantic-Modells und liest `json_schema_extra.fk_target.table`.
+2. **labelField-Gate**: Felder mit `fk_target.labelField = None` werden uebersprungen (Junction-IDs etc. brauchen kein Label).
+3. **Builtin-Lookup**: Wenn der Tabellenname in `_BUILTIN_FK_RESOLVERS` existiert, wird der zugehoerige Resolver dem Feld zugeordnet.
+4. **Extra-Resolvers**: Zusaetzliche Resolver (z.B. fuer feature-interne FKs) werden via `extraResolvers` gemerged.
+5. **Batch-Resolve**: Pro Resolver werden alle einzigartigen IDs gesammelt und in einem Batch aufgeloest.
+6. **Label-Injection**: Jede Row erhaelt eine neue Spalte `{field}Label` mit dem aufgeloesten Label (oder `None` wenn nicht aufloesbar).
+
+## Builtin-Resolvers
+
+Definiert in `gateway/modules/routes/routeHelpers.py`:
+
+| `fk_target.table` | Resolver-Funktion | Datenquelle | Label-Feld |
+|---|---|---|---|
+| `Mandate` | `resolveMandateLabels()` | `interfaceDbApp` → `getMandatesByIds()` | `label` oder `name` |
+| `FeatureInstance` | `resolveInstanceLabels()` | `interfaceFeatures` → `getFeatureInstance()` | `label` |
+| `UserInDB` | `resolveUserLabels()` | `interfaceDbApp` → `getRecordset(UserInDB)` | `displayName` / `username` / `email` |
+| `Role` | `resolveRoleLabels()` | `interfaceDbApp` → `getRecordset(Role)` | `roleLabel` |
+
+Diese vier Resolver decken alle plattformweiten FK-Beziehungen ab. FK-Felder, die auf andere Tabellen zeigen, werden **nicht** automatisch aufgeloest.
+
+## Pydantic-Modell-Annotation
+
+### Canonical Format
+
+Jede FK-Annotation verwendet ausschliesslich `fk_target` mit drei Pflichtschluesseln: `db`, `table`, `labelField`.
+
+```python
+mandateId: Optional[str] = Field(
+ default=None,
+ json_schema_extra={
+ "label": "Mandat",
+ "fk_target": {"db": "poweron_app", "table": "Mandate", "labelField": "label"},
+ }
+)
+```
+
+- `labelField` gibt an, welche Spalte als menschenlesbares Label dient.
+- `labelField: None` = kein Label noetig (Junction-IDs, reine Referenzen). Kein `displayField` wird ans Frontend geliefert, kein Resolver wird automatisch zugeordnet.
+- `"table": "UserInDB"` (physischer DB-Tabellenname), **nicht** `"User"`.
+
+### Startup-Validierung
+
+`validateFkTargets()` in `fkRegistry.py` prueft beim Gateway-Start, dass jeder `fk_target`-Dict exakt `db`, `table` und `labelField` enthaelt. Fehlende Keys brechen den Start ab.
+
+### Builtin-Ziel (automatisch aufgeloest)
+
+Wenn `fk_target.table` einem Builtin-Resolver entspricht und `labelField` gesetzt ist, ist keine weitere Konfiguration noetig:
+
+```python
+userId: str = Field(
+ json_schema_extra={
+ "label": "Benutzer",
+ "fk_target": {"db": "poweron_app", "table": "UserInDB", "labelField": "username"},
+ }
+)
+```
+
+`_buildLabelResolversFromModel` erkennt `table: "UserInDB"` und ordnet `resolveUserLabels` zu. Das Ergebnis: jede Row erhaelt `userIdLabel`.
+
+### Feature-internes FK-Ziel (extra Resolver noetig)
+
+Wenn `fk_target.table` **nicht** in den Builtins existiert (z.B. `TrusteeDataJournalEntry`), wird das Feld **nicht** automatisch aufgeloest. Hier muss der Route-Handler einen `extraResolver` bereitstellen:
+
+```python
+journalEntryId: str = Field(
+ json_schema_extra={
+ "label": "Buchung",
+ "fk_target": {"db": "poweron_trustee", "table": "TrusteeDataJournalEntry", "labelField": "reference"},
+ }
+)
+```
+
+Ohne `extraResolver` zeigt das UI `NA(...)` fuer dieses Feld.
+
+## Feature-interne FK-Resolver
+
+### Problem
+
+Die Builtin-Resolver decken nur plattformweite Entitaeten ab (Mandate, UserInDB, etc.). Feature-Module haben eigene Modelle (z.B. `TrusteeDataJournalEntry`, `TrusteeOrganisation`), die als FK-Ziele dienen. Diese muessen explizit aufgeloest werden.
+
+### Loesung: `extraResolvers` in `enrichRowsWithFkLabels`
+
+```python
+enrichRowsWithFkLabels(
+ rows,
+ modelClass,
+ extraResolvers={"journalEntryId": myResolverFunction},
+)
+```
+
+### Referenz-Implementierung: `_buildFeatureInternalResolvers`
+
+In `routeFeatureTrustee.py` existiert ein generischer Builder, der als Vorlage fuer andere Features dient:
+
+```python
+def _buildFeatureInternalResolvers(modelClass, db) -> Dict[str, Any]:
+ resolvers = {}
+ for name, fieldInfo in modelClass.model_fields.items():
+ extra = fieldInfo.json_schema_extra
+ if not extra or not isinstance(extra, dict):
+ continue
+ tgt = extra.get("fk_target")
+ if not isinstance(tgt, dict):
+ continue
+ tableName = tgt.get("table", "")
+ if tableName not in _FEATURE_ENTITY_MODELS:
+ continue
+ targetModel = _FEATURE_ENTITY_MODELS[tableName]
+
+ def _makeResolver(model, field=name):
+ def _resolve(ids):
+ result = {i: None for i in ids}
+ recs = db.getRecordset(model, recordFilter={"id": list(set(ids))}) or []
+ for r in recs:
+ row = r if isinstance(r, dict) else r.model_dump()
+ rid = row.get("id", "")
+ parts = []
+ for col in ("externalId", "reference", "bookingDate", "label", "name", "accountNumber"):
+ val = row.get(col)
+ if val:
+ parts.append(str(val))
+ if len(parts) >= 2:
+ break
+ result[rid] = " | ".join(parts) if parts else rid[:8]
+ return result
+ return _resolve
+
+ resolvers[name] = _makeResolver(targetModel)
+ return resolvers
+```
+
+Dieses Pattern:
+1. Scannt das Modell nach `fk_target`-Annotationen
+2. Prueft, ob das Ziel ein feature-internes Modell ist (via `_FEATURE_ENTITY_MODELS`-Dict)
+3. Erstellt einen Resolver, der die Ziel-Tabelle abfragt und ein Label aus den ersten 2 verfuegbaren beschreibenden Feldern baut
+4. Wird im Route-Handler via `_paginatedReadEndpoint` automatisch aufgerufen
+
+## WICHTIG: Filter-Dropdown-Enrichment (FormGeneratorTable)
+
+> **Regel fuer jeden Route-Handler der eine paginierte Datentabelle (FormGeneratorTable) bedient:**
+>
+> Der `mode=filterValues`-Pfad MUSS `enrichRowsWithFkLabels(items, ModelClass)` aufrufen
+> **bevor** `handleFilterValuesInMemory(items, column, ...)` aufgerufen wird.
+> Ohne diesen Schritt zeigen Filter-Dropdowns rohe UUIDs statt menschenlesbarer Labels.
+>
+> `_extractDistinctValues` erkennt FK-Labels nur, wenn `{field}Label`-Spalten in den Items vorhanden sind.
+> Diese werden ausschliesslich durch `enrichRowsWithFkLabels` hinzugefuegt.
+
+**Korrektes Pattern (In-Memory-Route):**
+
+```python
+if mode == "filterValues":
+ if not column:
+ raise HTTPException(status_code=400, detail="column parameter required")
+ items = _buildItems()
+ enrichRowsWithFkLabels(items, MyModel)
+ return handleFilterValuesInMemory(items, column, pagination)
+```
+
+**Korrektes Pattern (DB-Paginated mit Fallback):**
+
+```python
+if mode == "filterValues":
+ try:
+ values = db.getDistinctColumnValues(MyModel, column, crossPagination, recordFilter)
+ return JSONResponse(content=sorted(values, ...))
+ except Exception:
+ items = [r.model_dump() for r in db.getRecordset(MyModel, ...)]
+ enrichRowsWithFkLabels(items, MyModel)
+ return handleFilterValuesInMemory(items, column, pagination)
+```
+
+## Checkliste: Neues FK-Feld hinzufuegen
+
+### Fall 1: FK auf Mandate / FeatureInstance / UserInDB / Role
+
+1. `json_schema_extra` mit `fk_target` annotieren (`db`, `table`, `labelField` — alle drei Pflicht)
+2. Fertig — der Builtin-Resolver wird automatisch erkannt
+
+### Fall 2: FK auf ein feature-internes Modell
+
+1. `json_schema_extra` mit `fk_target` annotieren (`table` = Ziel-Modellname)
+2. Sicherstellen, dass das Ziel-Modell im Feature-Entity-Dict registriert ist (z.B. `_TRUSTEE_ENTITY_MODELS`)
+3. Im Route-Handler `_buildFeatureInternalResolvers` aufrufen und das Ergebnis als `extraResolvers` an `enrichRowsWithFkLabels` uebergeben
+4. Testen: API-Response pruefen, ob `{field}Label` korrekt aufgeloest wird (nicht `None` oder `NA(...)`)
+
+### Fall 3: FK auf ein Modell in einem anderen Feature oder einer externen Tabelle
+
+1. Einen dedizierten Resolver schreiben (Signatur: `(ids: List[str]) -> Dict[str, Optional[str]]`)
+2. Im Route-Handler als `extraResolvers` uebergeben
+3. Fuer haeufig verwendete Ziele: Resolver in `_BUILTIN_FK_RESOLVERS` in `routeHelpers.py` aufnehmen
+
+### Checkliste fuer neue Datentabellen-Route (FormGeneratorTable)
+
+1. Tabellen-Pfad: `enrichRowsWithFkLabels(rows, ModelClass)` vor Response
+2. **Filter-Pfad (`mode=filterValues`)**: `enrichRowsWithFkLabels(items, ModelClass)` vor `handleFilterValuesInMemory`
+3. IDs-Pfad (`mode=ids`): kein Enrichment noetig
+4. `fk_target` auf dem Modell mit `db`, `table`, `labelField` (Pflicht, validiert beim Start)
+
+## Kern-Dateien
+
+| Datei | Zweck |
+|---|---|
+| `gateway/modules/routes/routeHelpers.py` | `_BUILTIN_FK_RESOLVERS`, `_buildLabelResolversFromModel`, `enrichRowsWithFkLabels` |
+| `gateway/modules/shared/fkRegistry.py` | `validateFkTargets` (Startup-Validierung), FK-Discovery |
+| `gateway/modules/features/trustee/routeFeatureTrustee.py` | `_buildFeatureInternalResolvers` (Referenz-Implementierung) |
+| `gateway/modules/features/trustee/datamodelFeatureTrustee.py` | Beispiel-Annotationen (`fk_target` auf allen Modellen) |
+
+## Siehe auch
+
+- [FormGenerator Referenz](../frontend-nyla/formgenerator.md) — Frontend-Darstellung der aufgeloesten Labels
+- [Gateway Architektur](architecture.md) — Modulstruktur und routeHelpers
diff --git a/b-reference/platform/database-architecture.md b/b-reference/platform/database-architecture.md
index 1a02fdf..8062622 100644
--- a/b-reference/platform/database-architecture.md
+++ b/b-reference/platform/database-architecture.md
@@ -278,21 +278,23 @@ SysAdmin-Seite unter **Admin > System > Datenbank-Gesundheit** mit zwei Funktion
### FK-Annotationen (`fk_target`)
-Jedes `*Id`-Feld das eine echte FK-Beziehung darstellt, hat `fk_target` in `json_schema_extra`:
+Jedes `*Id`-Feld das eine echte FK-Beziehung darstellt, hat `fk_target` in `json_schema_extra` mit drei Pflichtschluesseln (`db`, `table`, `labelField`):
```python
mandateId: str = Field(
...,
json_schema_extra={
...,
- "fk_target": {"db": "poweron_app", "table": "Mandate"},
+ "fk_target": {"db": "poweron_app", "table": "Mandate", "labelField": "label"},
},
)
```
- Standard `targetColumn` ist `"id"`, Sonderfall `Feature.code` nutzt `"column": "code"`
+- `labelField` gibt das Feld fuer das menschenlesbare Label an; `None` = kein Label (Junction-IDs)
+- `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`
-- `fk_target` ist rein fuer Backend-Orphan-Detection, `fk_model`/`frontend_fk_*` bleiben fuer das UI
+- Startup-Validierung (`validateFkTargets`) prueft Vollstaendigkeit; fehlende Keys brechen den Start ab
### Orphan-Scanner (`modules/system/databaseHealth.py`)
diff --git a/c-work/_CHANGELOG.md b/c-work/_CHANGELOG.md
index 106adc0..7b5beec 100644
--- a/c-work/_CHANGELOG.md
+++ b/c-work/_CHANGELOG.md
@@ -12,6 +12,30 @@ type: `feat` `fix` `refactor` `docs` `test` `chore` `build` · scope: `gateway
Skip: reine Refactors, Formatting, Lint, Dep-Bumps, Test-only, Wiki-Tippfehler.
+## 2026-04-26
+
+- 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
+- 2026-04-26 | refactor | gateway | FK-Metadaten konsolidiert: alle Datamodels nutzen `fk_target` mit Pflicht-Keys `db`/`table`/`labelField`; `fk_model`/`fk_label_field` entfernt; `_BUILTIN_FK_RESOLVERS["UserInDB"]`; `_buildLabelResolversFromModel` skip ohne `labelField`; `attributeUtils` setzt `displayField` nur bei gesetztem `labelField`; `validateFkTargets()` Startup-Validierung in `fkRegistry.py` + `app.py lifespan`; Wiki `fk-label-resolution.md` + `database-architecture.md` aktualisiert
+- 2026-04-26 | fix | gateway | `connectorDbPostgre._ensureTableExists`: TEXT->DOUBLE PRECISION Spalten-Migration schlug fehl fuer ISO-Datetime-Strings — Regex `\\d{{4}}` korrigiert zu `\\d{4}` (doppelte Klammern waren kein f-string-Escaping sondern literal), `::timestamp` auf `::timestamptz` (Timezone-Offset korrekt parsen), SAVEPOINT pro ALTER (eine fehlgeschlagene Migration killt nicht mehr die gesamte Transaktion) — betraf MandateSubscription (6 Spalten) und BackgroundJob (3 Spalten)
+- 2026-04-26 | refactor | frontend-nyla | FlowEditor Form-Field-Type-Zentralisierung: `FORM_FIELD_TYPES` + `FORM_FIELD_TYPE_LABELS` in `attributeTypeMapper.ts`; `FormStartNodeConfig`, `FormNodeConfig`, `FieldBuilderEditor` beziehen Feldtypen aus zentraler Library statt hardcoded Listen; ClickUp-spezifische Typen (`clickup_status`, `clickup_tasks`) und zugehoerige UI (Connection-Picker, Status-Hinweis) entfernt; shared `FormField`-Typ auf `AttributeType` + generisches `options` umgestellt; `TriggerFormFieldRow` eliminiert; `clickupFormSync.ts` geloescht (dead code, nirgends importiert)
+- 2026-04-26 | refactor | gateway+frontend-nyla | Column-Type-Refactoring Schritte 1-10 abgeschlossen: 6 Pydantic View-Modelle (`UserMandateView`, `FeatureAccessView`, `BillingTransactionView`, `MandateSubscriptionView`, `UiLanguageSetView`, `DataNeutralizerAttributesView`) in `datamodelViews.py`; `createdAt`/`createdBy` Aliase in Invitation- und Billing-DTOs auf `sysCreatedAt`/`sysCreatedBy` standardisiert; `_COL_MAP`-Remapping in `interfaceDbBilling` entfernt; 7 Admin/Billing/Compliance-Seiten beziehen Spaltentypen via `resolveColumnTypes` + `fetchAttributes('')` statt hardcoded `type:` — tsc + Grep-Completeness-Check bestanden
+- 2026-04-26 | refactor | frontend-nyla | Schritt 8: Vollstaendigkeits-Grep — verbleibende hardcoded `type:` in ComplianceAuditPage entfernt (username/instanceLabel/ipAddress im Modell); alle anderen type:-Werte berechtigt dokumentiert (enriched View-Spalten, synthetische Zaehler, Alias-Keys)
+- 2026-04-26 | refactor | frontend-nyla | Schritt 7: weitere FormGenerator-Tabellen (AdminUsers, Connections, Files/Prompts, Mandate-Hook, RealEstate*, Trustee*) bauen Spaltentypen nur noch via `resolveColumnTypes` statt `type: attr.type` im Column-Map
+- 2026-04-26 | feat | gateway | `attributeUtils.getModelClasses`: Feature-`datamodel*.py` unter `modules/features/**` rekursiv importieren (Trustee, Teamsbot, GraphicalEditor, …) fuer `/api/attributes/{entityType}`
+- 2026-04-26 | refactor | frontend-nyla | Workflow-Seiten (GraphicalEditorWorkflowsPage, AutomationsDashboardPage) beziehen Spaltentypen via `resolveColumnTypes` + `fetchAttributes` vom Backend statt hardcoded `type:` im Frontend; neuer Shared-Utility `columnTypeResolver.ts`
+- 2026-04-26 | feat | frontend-nyla | `attributesApi.AttributeDefinition.type` nutzt `AttributeType` aus `attributeTypeMapper`; Mapper um Backend-Typ `object` (JSON/Dict) ergaenzt
+- 2026-04-26 | feat | gateway | Pydantic CHECK-Cleanup: fehlendes `frontend_type: "timestamp"` bei float-Zeitfeldern (UAM resetTokenExpires, DataSource, Security Token, Chat ChatLog/publishedAt/ActionItem/TaskItem/TaskHandover, Knowledge extractedAt); Redmine `*OnTs` von `number` auf `timestamp`; Redmine DTOs (`RedmineSyncResultDto`, `RedmineSyncStatusDto`, `RedmineConfigDto`); `UsageStatistics.periodStart` mit `frontend_type: "date"`; alle `frontend_type: "datetime"` auf `"timestamp"` (Audit, AuthEvent, GraphicalEditor Auto*, Messaging sentAt) — konsistent mit `attributeTypeMapper.isDateTimeType`
+- 2026-04-26 | refactor | gateway | Boot-Optimierung: Chatbot-Duplikat-Prewarm entfernt (`routeFeatureChatbot`), Stripe-Bootstrap parallelisiert via ThreadPoolExecutor (`stripeBootstrap`) — erwartete Bootzeit-Reduktion ~8s
+- 2026-04-26 | fix | gateway | `interfaceRbac`: Pagination-Dict-Filter (`getRecordsetPaginatedWithRBAC` / `getDistinctColumnValuesWithRBAC`) nutzen `_rbacAppendPaginationDictFilter` — numerische `gt`/`gte`/`lt`/`lte`/`between` mit `::double precision`, ISO-Datum + numerische Spalte als Unix-Bounds wie Connector
+- 2026-04-26 | feat | frontend-nyla | `FormGeneratorTable`: Datum-Filter nutzt PeriodPicker (Presets + Kalender) statt primitiver ``; PeriodPicker rendert ausserhalb `filterDropdownOptions` (Popover nicht durch `overflow-y: auto` geclippt)
+- 2026-04-26 | fix | gateway | `TrusteeDataJournalEntry.bookingDate`: `Optional[str]` -> `Optional[float]` (unix timestamp); Konvertierung in `_persistJournal` via `_isoDateToTimestamp` (ValueError bei ungueltigem Datum, kein Fallback); `_aggregateLocalMovements` liest float; FK-Label-Resolver formatiert float als ISO; Demo-Daten konvertiert; DB-Migration TEXT->DOUBLE PRECISION in `_ensureTableExists`
+- 2026-04-26 | fix | gateway | `datamodelFeatureTrustee`: `lastSyncAt`/`chartCachedAt`/`syncedAt` bekommt `frontend_type: "timestamp"`, `lastSyncDateFrom`/`lastSyncDateTo` `frontend_type: "date"` — damit Frontend Date-Filter statt Text anzeigt
+- 2026-04-26 | fix | frontend-nyla | `FormGeneratorTable`: Filter-Dropdown per `useLayoutEffect` als `position: fixed` in den Viewport geklemmt; Audit-Timestamp-Spalten (`sysCreatedAt` etc.) bei numerischem `type` als Datums-UI + kein distinct-Fetch
+- 2026-04-26 | feat | frontend-nyla | `FormGeneratorTable`: typbezogene Spaltenfilter — Zahlen (`integer`/`int`/`number`/`float`) mit Operator (=, >, >=, <, <=, Zwischen) und `Anwenden`; Datum-Filter mit CSS-Panel; kein `filterValues`-Fetch mehr fuer bool/date/number-Spalten
+- 2026-04-26 | fix | gateway | `routeHelpers._matchesBetween`: numerische `from`/`to` nach fehlgeschlagenem Datums-Parse (korrekte BETWEEN-Logik fuer Zahlenspalten); `connectorDbPostgre`: `gt`/`gte`/`lt`/`lte` und `between` auf INTEGER/DOUBLE PRECISION mit `::double precision` statt lexikographischem TEXT-Vergleich
+
## 2026-04-25
- 2026-04-25 | feat | * | Phase 4 FK: `frontend_fk_*` und FormGenerator-`fkSource`/Client-Cache entfernt; `fk_label_field` + `displayField` only; `_resolveRoleLabels`; `getRecordsetPaginated` + `getRecordsetPaginatedWithRBAC` + FK-Sort-Pfad mit `_enrichRowsWithFkLabels`; `attributeUtils` + betroffene Datamodels + Pages auf reines Backend-Enrichment
@@ -39,3 +63,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 | 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