diff --git a/concepts/Plan-SystemFields-Architecture.md b/concepts/Plan-SystemFields-Architecture.md new file mode 100644 index 0000000..508127b --- /dev/null +++ b/concepts/Plan-SystemFields-Architecture.md @@ -0,0 +1,573 @@ +# Plan: System-Felder Architektur (inkl. Review-Findings) + +## Entscheidung: Echte Pydantic-Felder (KEIN extra='allow') + +Pydantic v2 erlaubt keine `_`-prefixed Felder als regulaere model_fields. +Deshalb: **Felder OHNE Underscore-Prefix** in einer Base-Class, DB-Spalten werden umbenannt. + +Feldnamen: `sysCreatedAt`, `sysCreatedBy`, `sysModifiedAt`, `sysModifiedBy` + +Vorteile gegenueber extra='allow': +- Felder sind in model_fields (type-safe, sichtbar fuer getModelAttributeDefinitions) +- model_dump() inkludiert sie natuerlich +- Kein extra='allow' noetig - wird global entfernt +- Kein Spezial-Code in attributeUtils noetig + +--- + +## Deploy-Reihenfolge (Boot-Sequenz) + +1. Deploy neuer Code +2. Boot: `_ensureTableExists` fuegt neue `sysCreatedAt` etc. Spalten automatisch hinzu (da jetzt in model_fields) +3. Boot: Bootstrap `_migrateSystemFieldColumns` kopiert Daten von alten Spalten in neue +4. Applikation laeuft — referenziert nur noch `sysCreatedAt` etc. + +Alte `_createdAt`-Spalten bleiben in DB bestehen (kein DROP COLUMN), werden aber ignoriert. + +--- + +## Ziel-Architektur (Datenfluss) + +``` +PostgreSQL (sysCreatedAt, sysCreatedBy, sysModifiedAt, sysModifiedBy) + | +connectorDbPostgre.py (_saveRecord setzt die 4 Felder automatisch) + | +PowerOnModel (echte Felder: sysCreatedAt etc., readonly, visible=false) + | +Alle persistenten Models erben von PowerOnModel + | +interfaceDbApp.py (KEIN _-Filter mehr, Konstruktor akzeptiert alle Felder) + | +Route Endpoints (model_dump() inkludiert system fields) + | +Frontend (API liefert sysCreatedAt etc., FormGenerator zeigt sie nur wenn visible=true) +``` + +--- + +## Schritt 1: PowerOnModel Basisklasse erstellen + +Neue Datei: `gateway/modules/datamodels/datamodelBase.py` + +```python +from typing import Optional +from pydantic import BaseModel, Field +from modules.shared.attributeUtils import registerModelLabels + +class PowerOnModel(BaseModel): + sysCreatedAt: Optional[float] = Field( + default=None, + description="Record creation timestamp (UTC, set by system)", + json_schema_extra={ + "frontend_type": "timestamp", + "frontend_readonly": True, + "frontend_required": False, + "frontend_visible": False, + "system": True, + } + ) + sysCreatedBy: Optional[str] = Field( + default=None, + description="User ID who created this record (set by system)", + json_schema_extra={ + "frontend_type": "text", + "frontend_readonly": True, + "frontend_required": False, + "frontend_visible": False, + "system": True, + } + ) + sysModifiedAt: Optional[float] = Field( + default=None, + description="Record last modification timestamp (UTC, set by system)", + json_schema_extra={ + "frontend_type": "timestamp", + "frontend_readonly": True, + "frontend_required": False, + "frontend_visible": False, + "system": True, + } + ) + sysModifiedBy: Optional[str] = Field( + default=None, + description="User ID who last modified this record (set by system)", + json_schema_extra={ + "frontend_type": "text", + "frontend_readonly": True, + "frontend_required": False, + "frontend_visible": False, + "system": True, + } + ) + +registerModelLabels( + "PowerOnModel", + {"en": "Base Record", "de": "Basisdatensatz"}, + { + "sysCreatedAt": {"en": "Created At", "de": "Erstellt am", "fr": "Cree le"}, + "sysCreatedBy": {"en": "Created By", "de": "Erstellt von", "fr": "Cree par"}, + "sysModifiedAt": {"en": "Modified At", "de": "Geaendert am", "fr": "Modifie le"}, + "sysModifiedBy": {"en": "Modified By", "de": "Geaendert von", "fr": "Modifie par"}, + }, +) +``` + +### Label-Vererbung in attributeUtils.py (Review #6) + +`getModelAttributeDefinitions` liest Labels anhand des Model-Namens (z.B. "Prompt"). +Die Labels der Basis-Klasse "PowerOnModel" werden NICHT automatisch vererbt. + +**Fix:** `getModelAttributeDefinitions` anpassen: wenn ein Label fuer ein Feld +nicht im aktuellen Model gefunden wird, in der MRO (Method Resolution Order) +der Elternklassen suchen. Pseudocode: + +```python +labels = getModelLabels(model_name, userLanguage) +if not labels.get(fieldName): + for parent in modelClass.__mro__: + parentLabels = getModelLabels(parent.__name__, userLanguage) + if parentLabels.get(fieldName): + labels[fieldName] = parentLabels[fieldName] + break +``` + +--- + +## Schritt 2: connectorDbPostgre.py anpassen + +DB-Spaltennamen von `_createdAt` auf `sysCreatedAt` etc. aendern: + +### 2a: _create_table_from_model (Zeile ~598) +Vorher: `"_createdAt" DOUBLE PRECISION` etc. hardcoded append +Nachher: ENTFERNEN — Spalten kommen jetzt automatisch aus model_fields via `_get_model_fields()` + +### 2b: _save_record columns-Liste (Zeile ~629) +Vorher: `columns = [...] + ["_createdAt", "_createdBy", "_modifiedAt", "_modifiedBy"]` +Nachher: `columns = ["id"] + [field for field in fields.keys() if field != "id"]` +(system fields sind jetzt in model_fields, kein Append noetig) + +### 2c: KRITISCH — _save_record ON CONFLICT Klausel (Zeile ~689-694) (Review #2) +Vorher: +```python +updates = ", ".join( + [f'"{col}" = EXCLUDED."{col}"' + for col in columns[1:] + if col not in ["_createdAt", "_createdBy"]] +) +``` +Nachher: +```python +updates = ", ".join( + [f'"{col}" = EXCLUDED."{col}"' + for col in columns[1:] + if col not in ["sysCreatedAt", "sysCreatedBy"]] +) +``` +Zweck: bei UPSERT werden sysCreatedAt/sysCreatedBy NICHT ueberschrieben. + +### 2d: _save_record Timestamp-String-Parsing (Zeile ~651) (Review #3) +Vorher: `if col in ["_createdAt", "_modifiedAt"] and value is not None:` +Nachher: `if col in ["sysCreatedAt", "sysModifiedAt"] and value is not None:` + +### 2e: KRITISCH — _saveRecord Metadata-Logik (Zeile ~746) (Review #11) +Vorher: +```python +if "_createdAt" not in record: + record["_createdAt"] = currentTime +``` +Problem: da sysCreatedAt ein model_field mit default=None ist, ist es +bei model_dump() IMMER im Dict (als None). `"sysCreatedAt" not in record` +waere deshalb NIE True. + +Nachher: +```python +if not record.get("sysCreatedAt"): + record["sysCreatedAt"] = currentTime + if effective_user_id: + record["sysCreatedBy"] = effective_user_id +elif not record.get("sysCreatedBy"): + if effective_user_id: + record["sysCreatedBy"] = effective_user_id +record["sysModifiedAt"] = currentTime +if effective_user_id: + record["sysModifiedBy"] = effective_user_id +``` + +### 2f: _ensureTableExists (Zeile ~521) +Vorher: `desired_columns |= {"_createdAt", "_modifiedAt", "_createdBy", "_modifiedBy"}` +Nachher: ENTFERNEN (kommen aus model_fields) + +### 2g: _buildPaginationClauses (Zeile ~995) +Vorher: `fields["_createdAt"] = "DOUBLE PRECISION"` etc. +Nachher: ENTFERNEN (kommen aus model_fields) + +### 2h: getDistinctColumnValues (Zeile ~1193) +Vorher: `fields["_createdAt"] = "DOUBLE PRECISION"` etc. +Nachher: ENTFERNEN (kommen aus model_fields) + +### 2i: _system Tabelle (Review #7) +Die interne `_system`-Tabelle hat hardcoded `_createdAt`/`_modifiedAt` in +CREATE TABLE (Zeile ~326-333, ~447-454). `SystemTable` erbt von `BaseModel`. + +Empfehlung: `SystemTable` auf `PowerOnModel` umstellen, hardcoded +`_createdAt`/`_modifiedAt` auf `sysCreatedAt`/`sysModifiedAt` aendern. +Bootstrap-Migration muss auch `_system` beruecksichtigen. + +--- + +## Schritt 3: Alle persistenten Models auf PowerOnModel umstellen + +### Kern-Datamodels (gateway/modules/datamodels/): + +- datamodelUam.py: User, Mandate, UserConnection, UserVoicePreferences, UserInDB +- datamodelRbac.py: Role, AccessRule +- datamodelMembership.py: UserMandate, FeatureAccess, UserMandateRole, FeatureAccessRole +- datamodelFeatures.py: Feature, FeatureInstance +- datamodelUtils.py: Prompt (ENTFERNE extra='allow') +- datamodelFiles.py: FileItem (ENTFERNE extra='allow'), FileData +- datamodelFileFolder.py: FileFolder +- datamodelInvitation.py: Invitation +- datamodelFeatureDataSource.py: FeatureDataSource +- datamodelDataSource.py: DataSource +- datamodelMessaging.py: MessagingSubscription +- datamodelBilling.py: BillingAccount, BillingTransaction +- datamodelSubscription.py: MandateSubscription +- datamodelNotification.py: UserNotification +- datamodelSecurity.py: Token, AuthEvent +- datamodelKnowledge.py: persistente Knowledge-Models +- datamodelChat.py: persistente Chat-Models + +### Feature-Datamodels (gateway/modules/features/): + +- commcoach/datamodelCommcoach.py: CoachingContext, CoachingSession, CoachingMessage, CoachingTask, CoachingScore, CoachingUserProfile, CoachingPersona, CoachingBadge +- workspace/datamodelFeatureWorkspace.py: WorkspaceUserSettings +- neutralization/datamodelFeatureNeutralizer.py: DataNeutraliserConfig +- automation2/datamodelFeatureAutomation2.py: Automation2Workflow, Automation2WorkflowRun, Automation2HumanTask +- trustee/datamodelFeatureTrustee.py: TrusteeOrganisation, TrusteeRole, TrusteeAccess, TrusteeContract, TrusteeDocument, TrusteePosition (ENTFERNE extra='allow'), TrusteeDataAccount, TrusteeDataJournalEntry, TrusteeDataJournalLine, TrusteeDataContact, TrusteeDataAccountBalance, TrusteeAccountingConfig, TrusteeAccountingSync +- teamsbot/datamodelTeamsbot.py: TeamsbotSession, TeamsbotTranscript, TeamsbotBotResponse, TeamsbotSystemBot, TeamsbotUserAccount, TeamsbotUserSettings, TeamsbotConfig +- automation/datamodelFeatureAutomation.py: AutomationDefinition, AutomationTemplate +- realEstate/datamodelFeatureRealEstate.py: Land, Kanton, Gemeinde, Parzelle, Projekt, Dokument, Kontext + +### Connector-intern: +- connectorDbPostgre.py: SystemTable (Review #7) + +### NICHT umstellen (reine DTOs): +- PaginationParams, PaginatedResponse, AttributeDefinition, FilePreview, TextMultilingual +- CreateContextRequest, UpdateContextRequest, etc. (Request/Response DTOs) +- DataNeutralizerAttributes (DTO) +- GeoPunkt, GeoPolylinie (embedded value objects) +- Enums + +--- + +## Schritt 4: Business-Feld-Duplikate entfernen + +Felder die durch die neuen System-Felder ersetzt werden: + +### datamodelFileFolder.py: +- ENTFERNE: `createdAt: float` -> ersetzt durch sysCreatedAt + +### datamodelFiles.py: +- ENTFERNE: `creationDate: float` -> ersetzt durch sysCreatedAt + +### datamodelInvitation.py: +- ENTFERNE: `createdAt: float` -> ersetzt durch sysCreatedAt +- ENTFERNE: `createdBy: str` -> ersetzt durch sysCreatedBy + +### datamodelFeatureDataSource.py: +- ENTFERNE: `createdAt: float` -> ersetzt durch sysCreatedAt + +### datamodelDataSource.py (Review #5): +- ENTFERNE: `createdAt: float` -> ersetzt durch sysCreatedAt + +### datamodelNotification.py (Review #5): +- ENTFERNE: `createdAt: float` auf UserNotification -> ersetzt durch sysCreatedAt + +### datamodelSecurity.py (Review #5): +- ENTFERNE: `createdAt: Optional[float]` auf Token -> ersetzt durch sysCreatedAt + +### datamodelMessaging.py: +- ENTFERNE: `createdBy: str` -> ersetzt durch sysCreatedBy +- ENTFERNE: `modifiedBy: str` -> ersetzt durch sysModifiedBy + +### commcoach/datamodelCommcoach.py: +- ENTFERNE: `createdAt` auf 8 Models -> ersetzt durch sysCreatedAt +- ENTFERNE: `updatedAt` auf mehreren Models -> ersetzt durch sysModifiedAt + +### teamsbot/datamodelTeamsbot.py: +- ENTFERNE: `creationDate` auf 6 Models -> ersetzt durch sysCreatedAt +- ENTFERNE: `lastModified` auf 4 Models -> ersetzt durch sysModifiedAt + +--- + +## Schritt 5: _-Filter und Passthrough-Logik bereinigen + +### 5a: 44x _-Filter in interfaceDbApp.py entfernen + +Vorher (44 Stellen): +```python +cleanedRecord = {k: v for k, v in record.items() if not k.startswith("_")} +return UserMandate(**cleanedRecord) +``` + +Nachher: +```python +return UserMandate(**record) +``` + +Da alle Models jetzt von PowerOnModel erben und die system fields als echte +model_fields haben, akzeptiert der Konstruktor sie direkt. + +### 5b: Muster B — User.model_validate statt Denylist (Review #1) + +Die 2 Stellen mit Zusatzausschluss `hashedPassword`/`resetToken`/`resetTokenExpires` +(Zeile ~652 und ~2560-2567) ersetzen durch: + +```python +user = User.model_validate(userRecord) +if not hasattr(user, 'roleLabels') or user.roleLabels is None: + # roleLabels Defaulting wie bisher + pass +``` + +Pydantic v2 `extra='ignore'` verwirft unbekannte Keys automatisch. +Die Sicherheit steckt im Schema von `User` (Allowlist), nicht in +einer parallelen Ausschlussliste (Denylist). + +### 5c: startswith("_") Passthrough entfernen (Review #4) + +In `interfaceDbApp.py` Zeile ~192 und `interfaceDbManagement.py` Zeile ~180: + +```python +if fieldName.startswith("_"): + simpleFields[fieldName] = value +``` + +Diese Sonderbehandlung wird OBSOLET: `sysCreatedAt` ist ein regulaeres +model_field und landet automatisch in simpleFields. Die `startswith("_")`- +Branch kann ENTFERNT werden. + +### 5d: Gleiche Bereinigung in: +- gateway/modules/interfaces/interfaceFeatures.py (9 Stellen, nur Muster A) +- gateway/modules/interfaces/interfaceDbManagement.py + +### Inventar Muster A (Ziel-Model pro Block, interfaceDbApp.py): + +| Ziel-Model | Typische Funktionen / Bereich | Zeilen (ca.) | +|------------|------------------------------|--------------| +| `User` | getUsers, Listen, einzelner User | ~531, ~563, ~589, ~880, ~920, ~981, ~1044 | +| `Mandate` | Mandanten-Filter fuer API | ~1332, ~1381 | +| `UserMandate` | Membership | ~1797, ~1820, ~1872, ~2002, ~2596 | +| `UserMandateRole` | Junction | ~2026, ~2123, ~2131, ~2617 | +| `FeatureAccess` | Feature-Zugriff | ~2196, ~2219, ~2243, ~2292 | +| `Invitation` | Einladungen | ~2430, ~2450, ~2471, ~2492, ~2513, ~2534 | +| `FeatureInstance` | Instanz laden / nach Mandant | ~2637, ~2682 | +| `Feature` | getFeatureByCode | ~2657 | +| `UserNotification` | Benachrichtigungen | ~2706, ~2737 | +| `AccessRule` | Regeln laden | ~2765, ~2786, ~3366 | +| `Role` | Rollen nach Instanz/Code/Mandant/alle | ~2807, ~2832, ~3550, ~3571 | +| `Token` | Tokens nach Connection/User | ~3031, ~3052 | + +### Sonderfall Token: +Zeile ~3006: `Token(**tokens[0])` ohne vorherigen _-Filter — nach +Umbenennung konsistent, aber an andere Token-Pfade angleichen. + +### Sonderfall Invitation + recordFilter: +`getInvitationsByCreator` nutzt `recordFilter={"createdBy": creatorId}` (~2489). +Nach Entfernung des Business-Felds auf `recordFilter={"sysCreatedBy": creatorId}` umstellen. + +### Sonderfall UserNotification + Sortierung: +`getNotificationsByUser` sortiert mit `x.createdAt` (~2740). +Nach Entfernung auf `x.sysCreatedAt` aendern. + +--- + +## Schritt 6: extra='allow' global entfernen + +Entfernen aus: +- datamodelUtils.py: Prompt (`ConfigDict(extra='allow')`) +- datamodelFiles.py: FileItem (`ConfigDict(extra='allow')`) +- features/trustee/datamodelFeatureTrustee.py: TrusteePosition (`model_config={"extra":"allow"}`) + +Keine `model_config` mit `extra='allow'` mehr in der gesamten Applikation. + +--- + +## Schritt 7: Bootstrap-Migration (interfaceBootstrap.py) + +Neue Funktion `_migrateSystemFieldColumns(db)` in interfaceBootstrap.py, +aufgerufen frueh in `initBootstrap()` (NACH initRootMandate, VOR allem anderen): + +```python +def _migrateSystemFieldColumns(db: DatabaseConnector) -> None: + """Migrate old _createdAt/_createdBy/_modifiedAt/_modifiedBy columns + to new sysCreatedAt/sysCreatedBy/sysModifiedAt/sysModifiedBy columns. + Also migrates business field duplicates (createdAt, creationDate, etc.) + Idempotent: safe to run on every boot.""" + + COLUMN_RENAMES = { + "_createdAt": "sysCreatedAt", + "_createdBy": "sysCreatedBy", + "_modifiedAt": "sysModifiedAt", + "_modifiedBy": "sysModifiedBy", + } + + BUSINESS_FIELD_MIGRATIONS = { + "FileFolder": {"createdAt": "sysCreatedAt"}, + "FileItem": {"creationDate": "sysCreatedAt"}, + "Invitation": {"createdAt": "sysCreatedAt", "createdBy": "sysCreatedBy"}, + "FeatureDataSource": {"createdAt": "sysCreatedAt"}, + "DataSource": {"createdAt": "sysCreatedAt"}, + "UserNotification": {"createdAt": "sysCreatedAt"}, + "Token": {"createdAt": "sysCreatedAt"}, + "MessagingSubscription": {"createdBy": "sysCreatedBy", "modifiedBy": "sysModifiedBy"}, + "CoachingContext": {"createdAt": "sysCreatedAt"}, + "CoachingSession": {"createdAt": "sysCreatedAt", "updatedAt": "sysModifiedAt"}, + "CoachingMessage": {"createdAt": "sysCreatedAt"}, + "CoachingTask": {"createdAt": "sysCreatedAt", "updatedAt": "sysModifiedAt"}, + "CoachingScore": {"createdAt": "sysCreatedAt"}, + "CoachingUserProfile": {"createdAt": "sysCreatedAt", "updatedAt": "sysModifiedAt"}, + "CoachingPersona": {"createdAt": "sysCreatedAt", "updatedAt": "sysModifiedAt"}, + "CoachingBadge": {"createdAt": "sysCreatedAt"}, + "TeamsbotSession": {"creationDate": "sysCreatedAt", "lastModified": "sysModifiedAt"}, + "TeamsbotTranscript": {"creationDate": "sysCreatedAt"}, + "TeamsbotBotResponse": {"creationDate": "sysCreatedAt"}, + "TeamsbotSystemBot": {"creationDate": "sysCreatedAt", "lastModified": "sysModifiedAt"}, + "TeamsbotUserAccount": {"creationDate": "sysCreatedAt", "lastModified": "sysModifiedAt"}, + "TeamsbotUserSettings": {"creationDate": "sysCreatedAt", "lastModified": "sysModifiedAt"}, + "_system": {"_createdAt": "sysCreatedAt", "_modifiedAt": "sysModifiedAt"}, + } + + # Fuer jede Tabelle in der DB: + # 1. Alte _-Spalten -> neue sys-Spalten kopieren (COLUMN_RENAMES) + # 2. Business-Felder -> sys-Felder kopieren (BUSINESS_FIELD_MIGRATIONS) + # 3. Nur wo sysX IS NULL (idempotent) + # 4. Neue Spalten werden automatisch von _ensureTableExists erstellt +``` + +--- + +## Schritt 8: Gateway Calling References anpassen + +Alle Dateien, die `_createdAt`/`_createdBy`/`_modifiedAt`/`_modifiedBy` referenzieren: + +### Interfaces: +- gateway/modules/interfaces/interfaceDbApp.py (44x _-Filter + direkte Referenzen) +- gateway/modules/interfaces/interfaceDbManagement.py +- gateway/modules/interfaces/interfaceBootstrap.py +- gateway/modules/interfaces/interfaceRbac.py +- gateway/modules/interfaces/interfaceDbChat.py +- gateway/modules/interfaces/interfaceDbBilling.py + +### Routes: +- gateway/modules/routes/routeDataFiles.py +- gateway/modules/routes/routeBilling.py +- gateway/modules/routes/routeAdminAutomationLogs.py +- gateway/modules/routes/routeAdminRbacRules.py +- gateway/modules/routes/routeAdminAutomationEvents.py +- gateway/modules/routes/routeSecurityLocal.py +- gateway/modules/routes/routeSecurityGoogle.py + +### Features: +- gateway/modules/features/trustee/interfaceFeatureTrustee.py +- gateway/modules/features/chatbot/interfaceFeatureChatbot.py +- gateway/modules/features/automation2/routeFeatureAutomation2.py +- gateway/modules/features/automation/routeFeatureAutomation.py +- gateway/modules/features/automation/interfaceFeatureAutomation.py +- gateway/modules/features/workspace/mainWorkspace.py +- gateway/modules/features/teamsbot/routeFeatureTeamsbot.py +- gateway/modules/features/teamsbot/interfaceFeatureTeamsbot.py + +### Sonstige Gateway: +- gateway/modules/connectors/connectorDbPostgre.py +- gateway/modules/auth/tokenManager.py +- gateway/modules/serviceCenter/services/serviceChat/mainServiceChat.py +- gateway/modules/serviceCenter/services/serviceKnowledge/mainServiceKnowledge.py +- gateway/modules/serviceCenter/services/serviceAgent/mainServiceAgent.py +- gateway/modules/serviceCenter/services/serviceGeneration/mainServiceGeneration.py +- gateway/modules/workflows/automation/mainWorkflow.py +- gateway/modules/security/rbac.py +- gateway/modules/shared/gdprDeletion.py +- gateway/modules/migration/migrateRootUsers.py +- gateway/scripts/script_db_export_migration.py +- gateway/tests/integration/rbac/test_rbac_database.py + +### Business-Feld Referenzen (creationDate, .createdAt, .createdBy, etc.): +- gateway/modules/features/teamsbot/datamodelTeamsbot.py +- gateway/modules/features/teamsbot/interfaceFeatureTeamsbot.py +- gateway/modules/features/teamsbot/routeFeatureTeamsbot.py +- gateway/modules/features/commcoach/datamodelCommcoach.py +- gateway/modules/datamodels/datamodelFiles.py +- gateway/modules/datamodels/datamodelMessaging.py +- gateway/modules/datamodels/datamodelDataSource.py +- gateway/modules/datamodels/datamodelNotification.py +- gateway/modules/datamodels/datamodelSecurity.py +- gateway/modules/interfaces/interfaceDbManagement.py +- gateway/modules/routes/routeSecurityLocal.py +- gateway/modules/routes/routeSecurityGoogle.py +- gateway/modules/serviceCenter/services/serviceChat/mainServiceChat.py +- gateway/modules/serviceCenter/services/serviceGeneration/mainServiceGeneration.py +- gateway/modules/auth/tokenManager.py + +--- + +## Schritt 9: Frontend Calling References anpassen + +Alle Dateien die `_createdAt`/`_createdBy`/`_modifiedAt`/`_modifiedBy` oder +Business-Duplikate referenzieren: + +### API Types: +- frontend_nyla/src/api/automationApi.ts + `_createdAt`, `_createdBy`, `_createdByUserName` -> `sysCreatedAt`, `sysCreatedBy` +- frontend_nyla/src/api/automation2Api.ts + `createdAt` Kommentar -> `sysCreatedAt` + +### Type Definitions: +- frontend_nyla/src/types/mandate.ts + `record._createdBy` -> `record.sysCreatedBy` (RBAC owner check) + +### Hooks: +- frontend_nyla/src/hooks/useAutomations.ts + `Omit<..., '_createdAt' | '_createdBy'>` -> `Omit<..., 'sysCreatedAt' | 'sysCreatedBy'>` + +### Pages (excludedFields/hiddenColumns Listen): +- frontend_nyla/src/pages/basedata/PromptsPage.tsx +- frontend_nyla/src/pages/basedata/FilesPage.tsx +- frontend_nyla/src/pages/basedata/ConnectionsPage.tsx +- frontend_nyla/src/pages/views/trustee/TrusteePositionsView.tsx +- frontend_nyla/src/pages/views/trustee/TrusteePositionDocumentsView.tsx +- frontend_nyla/src/pages/views/trustee/TrusteeDocumentsView.tsx +- frontend_nyla/src/pages/views/realestate/RealEstateProjectsView.tsx +- frontend_nyla/src/pages/views/realestate/RealEstateParcelsView.tsx +- frontend_nyla/src/pages/views/automation/AutomationDefinitionsView.tsx +- frontend_nyla/src/pages/views/automation/AutomationTemplatesView.tsx +- frontend_nyla/src/pages/views/workspace/NeutralizationPanel.tsx + `m.createdAt || m._createdAt` -> `m.sysCreatedAt` + +### Hinweis zu den excludedFields-Listen: +Da die System-Felder jetzt mit `visible=false` in den Attributen kommen, +werden viele dieser manuellen Ausschluss-Listen OBSOLET. Der FormGenerator +filtert sie automatisch raus. Die Listen koennen vereinfacht werden. + +--- + +## Zusammenfassung + +| Bereich | Dateien | Aenderungsart | +|---------|---------|---------------| +| Neue Datei | datamodelBase.py | 1 neue Datei (PowerOnModel + Labels) | +| Connector | connectorDbPostgre.py | ~15 Stellen: Spaltennamen + Logik-Fix | +| attributeUtils | attributeUtils.py | Label-Vererbung via MRO | +| Kern-Datamodels | ~17 Dateien | BaseModel -> PowerOnModel | +| Feature-Datamodels | 8 Dateien | BaseModel -> PowerOnModel | +| Business-Felder | ~13 Models | Felder entfernen | +| extra='allow' | 3 Models | ConfigDict entfernen | +| _-Filter | interfaceDbApp.py + 2 weitere | ~50 Stellen vereinfachen | +| Muster B | interfaceDbApp.py (2 Stellen) | Denylist -> User.model_validate | +| Passthrough | interfaceDbApp.py + interfaceDbManagement.py | startswith("_") entfernen | +| Gateway Referenzen | ~30 Dateien | _createdAt -> sysCreatedAt | +| Frontend Referenzen | ~13 Dateien | _createdAt -> sysCreatedAt | +| Bootstrap | interfaceBootstrap.py | Migration-Funktion | +| registerModelLabels | ~10 Dateien | Labels fuer entfernte Felder bereinigen | +| _system Tabelle | connectorDbPostgre.py | Hardcoded Spalten umbenennen |