BREAKING CHANGE

API and persisted records use PowerOnModel system fields:
- sysCreatedAt, sysCreatedBy, sysModifiedAt, sysModifiedBy
Removed legacy JSON/DB field names:
- _createdAt, _createdBy, _modifiedAt, _modifiedBy
Frontend (frontend_nyla) and gateway call sites were updated accordingly.
Database:
- Bootstrap runs idempotent backfill (_migrateSystemFieldColumns) from old
  underscore columns and selected business duplicates into sys* where sys* IS NULL.
- Re-run app bootstrap against each PostgreSQL database after deploy.
- Optional: DROP INDEX IF EXISTS "idx_invitation_createdby" if an old index remains;
  new index: idx_invitation_syscreatedby on Invitation(sysCreatedBy).
Tests:
- RBAC integration tests aligned with current GROUP mandate filter and UserMandate-based
  UserConnection GROUP clause; buildRbacWhereClause(..., mandateId=...) must be passed
  explicitly (same as production request context).
This commit is contained in:
ValueOn AG 2026-03-28 18:13:18 +01:00
parent f5f6cad542
commit 77e7eba711
34 changed files with 89 additions and 89 deletions

View file

@ -258,7 +258,7 @@ export interface Automation2Task {
result?: Record<string, unknown>; result?: Record<string, unknown>;
/** Workflow label (enriched by API) */ /** Workflow label (enriched by API) */
workflowLabel?: string; workflowLabel?: string;
/** Unix timestamp ms (from _createdAt) */ /** Unix timestamp ms (from sysCreatedAt) */
createdAt?: number; createdAt?: number;
/** Optional due date - configurable in future */ /** Optional due date - configurable in future */
dueAt?: number; dueAt?: number;

View file

@ -18,9 +18,9 @@ export interface Automation {
nextExecution?: number; nextExecution?: number;
executionLogs?: AutomationLog[]; executionLogs?: AutomationLog[];
allowedProviders?: string[]; allowedProviders?: string[];
_createdAt?: number; sysCreatedAt?: number;
_updatedAt?: number; _updatedAt?: number;
_createdByUserName?: string; sysCreatedByUserName?: string;
mandateName?: string; mandateName?: string;
featureInstanceName?: string; featureInstanceName?: string;
[key: string]: any; [key: string]: any;
@ -48,9 +48,9 @@ export interface AutomationTemplate {
label: TextMultilingual; label: TextMultilingual;
overview?: TextMultilingual; overview?: TextMultilingual;
template: string; // JSON string with {{KEY:...}} placeholders template: string; // JSON string with {{KEY:...}} placeholders
_createdAt?: number; sysCreatedAt?: number;
_createdBy?: string; sysCreatedBy?: string;
_createdByUserName?: string; sysCreatedByUserName?: string;
} }
// Workflow action definition from backend // Workflow action definition from backend
@ -301,7 +301,7 @@ export async function fetchAutomationTemplateById(
*/ */
export async function createAutomationTemplateApi( export async function createAutomationTemplateApi(
request: ApiRequestFunction, request: ApiRequestFunction,
templateData: Omit<AutomationTemplate, 'id' | '_createdAt' | '_createdBy'> templateData: Omit<AutomationTemplate, 'id' | 'sysCreatedAt' | 'sysCreatedBy'>
): Promise<AutomationTemplate> { ): Promise<AutomationTemplate> {
return await request({ return await request({
url: '/api/automation-templates', url: '/api/automation-templates',

View file

@ -9,7 +9,7 @@ export interface Prompt {
mandateId: string; mandateId: string;
content: string; content: string;
name: string; name: string;
_createdBy?: string; sysCreatedBy?: string;
_hideDelete?: boolean; _hideDelete?: boolean;
[key: string]: any; // Allow additional properties [key: string]: any; // Allow additional properties
} }

View file

@ -23,8 +23,8 @@ export interface RealEstateProject {
featureInstanceId?: string; featureInstanceId?: string;
perimeter?: any; perimeter?: any;
parzellen?: RealEstateParcel[]; parzellen?: RealEstateParcel[];
_createdAt?: number; sysCreatedAt?: number;
_modifiedAt?: number; sysModifiedAt?: number;
[key: string]: any; [key: string]: any;
} }
@ -38,8 +38,8 @@ export interface RealEstateParcel {
plz?: string; plz?: string;
perimeter?: any; perimeter?: any;
bauzone?: string; bauzone?: string;
_createdAt?: number; sysCreatedAt?: number;
_modifiedAt?: number; sysModifiedAt?: number;
[key: string]: any; [key: string]: any;
} }

View file

@ -18,10 +18,10 @@ export interface TrusteeOrganisation {
label: string; label: string;
enabled: boolean; enabled: boolean;
mandateId?: string; mandateId?: string;
_createdAt?: number; sysCreatedAt?: number;
_modifiedAt?: number; sysModifiedAt?: number;
_createdBy?: string; sysCreatedBy?: string;
_modifiedBy?: string; sysModifiedBy?: string;
[key: string]: any; [key: string]: any;
} }
@ -29,10 +29,10 @@ export interface TrusteeRole {
id: string; id: string;
desc: string; desc: string;
mandateId?: string; mandateId?: string;
_createdAt?: number; sysCreatedAt?: number;
_modifiedAt?: number; sysModifiedAt?: number;
_createdBy?: string; sysCreatedBy?: string;
_modifiedBy?: string; sysModifiedBy?: string;
[key: string]: any; [key: string]: any;
} }
@ -43,10 +43,10 @@ export interface TrusteeAccess {
userId: string; userId: string;
contractId?: string | null; contractId?: string | null;
mandateId?: string; mandateId?: string;
_createdAt?: number; sysCreatedAt?: number;
_modifiedAt?: number; sysModifiedAt?: number;
_createdBy?: string; sysCreatedBy?: string;
_modifiedBy?: string; sysModifiedBy?: string;
[key: string]: any; [key: string]: any;
} }
@ -56,10 +56,10 @@ export interface TrusteeContract {
label: string; label: string;
enabled: boolean; enabled: boolean;
mandateId?: string; mandateId?: string;
_createdAt?: number; sysCreatedAt?: number;
_modifiedAt?: number; sysModifiedAt?: number;
_createdBy?: string; sysCreatedBy?: string;
_modifiedBy?: string; sysModifiedBy?: string;
[key: string]: any; [key: string]: any;
} }
@ -71,10 +71,10 @@ export interface TrusteeDocument {
documentMimeType: string; documentMimeType: string;
documentData?: any; documentData?: any;
mandateId?: string; mandateId?: string;
_createdAt?: number; sysCreatedAt?: number;
_modifiedAt?: number; sysModifiedAt?: number;
_createdBy?: string; sysCreatedBy?: string;
_modifiedBy?: string; sysModifiedBy?: string;
[key: string]: any; [key: string]: any;
} }
@ -98,10 +98,10 @@ export interface TrusteePosition {
costCenter?: string; costCenter?: string;
bookingReference?: string; bookingReference?: string;
mandateId?: string; mandateId?: string;
_createdAt?: number; sysCreatedAt?: number;
_modifiedAt?: number; sysModifiedAt?: number;
_createdBy?: string; sysCreatedBy?: string;
_modifiedBy?: string; sysModifiedBy?: string;
[key: string]: any; [key: string]: any;
} }
@ -696,8 +696,8 @@ export interface TrusteePositionDocument {
documentId: string; documentId: string;
mandateId?: string; mandateId?: string;
featureInstanceId?: string; featureInstanceId?: string;
_createdAt?: number; sysCreatedAt?: number;
_modifiedAt?: number; sysModifiedAt?: number;
[key: string]: any; [key: string]: any;
} }

View file

@ -165,7 +165,7 @@ export function useMandates() {
return false; // Don't show readonly fields in edit form return false; // Don't show readonly fields in edit form
} }
// Also filter out common non-editable fields // Also filter out common non-editable fields
const nonEditableFields = ['id', 'mandateId', '_createdBy', '_hideDelete']; const nonEditableFields = ['id', 'mandateId', 'sysCreatedBy', '_hideDelete'];
return !nonEditableFields.includes(attr.name); return !nonEditableFields.includes(attr.name);
}) })
.map(attr => { .map(attr => {
@ -305,7 +305,7 @@ export function useMandates() {
return false; return false;
} }
// Filter out ID fields and other auto-generated fields // Filter out ID fields and other auto-generated fields
const nonEditableFields = ['id', 'mandateId', '_createdBy', '_hideDelete']; const nonEditableFields = ['id', 'mandateId', 'sysCreatedBy', '_hideDelete'];
return !nonEditableFields.includes(attr.name); return !nonEditableFields.includes(attr.name);
}) })
.map(attr => { .map(attr => {

View file

@ -206,7 +206,7 @@ export function useRbacRoles() {
return false; // Don't show readonly fields in edit form return false; // Don't show readonly fields in edit form
} }
// Also filter out common non-editable fields // Also filter out common non-editable fields
const nonEditableFields = ['id', 'roleId', '_createdBy', '_hideDelete']; const nonEditableFields = ['id', 'roleId', 'sysCreatedBy', '_hideDelete'];
return !nonEditableFields.includes(attr.name); return !nonEditableFields.includes(attr.name);
}) })
.map(attr => { .map(attr => {
@ -346,7 +346,7 @@ export function useRbacRoles() {
return false; return false;
} }
// Filter out ID fields and other auto-generated fields // Filter out ID fields and other auto-generated fields
const nonEditableFields = ['id', 'roleId', '_createdBy', '_hideDelete']; const nonEditableFields = ['id', 'roleId', 'sysCreatedBy', '_hideDelete'];
return !nonEditableFields.includes(attr.name); return !nonEditableFields.includes(attr.name);
}) })
.map(attr => { .map(attr => {

View file

@ -182,7 +182,7 @@ export function useRbacRules() {
return false; // Don't show readonly fields in edit form return false; // Don't show readonly fields in edit form
} }
// Also filter out common non-editable fields // Also filter out common non-editable fields
const nonEditableFields = ['id', 'ruleId', '_createdBy', '_hideDelete']; const nonEditableFields = ['id', 'ruleId', 'sysCreatedBy', '_hideDelete'];
return !nonEditableFields.includes(attr.name); return !nonEditableFields.includes(attr.name);
}) })
.map(attr => { .map(attr => {
@ -322,7 +322,7 @@ export function useRbacRules() {
return false; return false;
} }
// Filter out ID fields and other auto-generated fields // Filter out ID fields and other auto-generated fields
const nonEditableFields = ['id', 'ruleId', '_createdBy', '_hideDelete']; const nonEditableFields = ['id', 'ruleId', 'sysCreatedBy', '_hideDelete'];
return !nonEditableFields.includes(attr.name); return !nonEditableFields.includes(attr.name);
}) })
.map(attr => { .map(attr => {

View file

@ -536,7 +536,7 @@ export function useAutomationTemplates() {
return await fetchAutomationTemplateById(request, templateId); return await fetchAutomationTemplateById(request, templateId);
}, [request]); }, [request]);
const createTemplate = useCallback(async (data: Omit<AutomationTemplate, 'id' | '_createdAt' | '_createdBy'>) => { const createTemplate = useCallback(async (data: Omit<AutomationTemplate, 'id' | 'sysCreatedAt' | 'sysCreatedBy'>) => {
return await createAutomationTemplateApi(request, data); return await createAutomationTemplateApi(request, data);
}, [request]); }, [request]);

View file

@ -83,11 +83,11 @@ export function useTablePermission(tableName: string) {
canDelete: hasAccess(permission.delete), canDelete: hasAccess(permission.delete),
// Record-basierte Prüfungen // Record-basierte Prüfungen
canReadRecord: (record: { _createdBy?: string }) => canReadRecord: (record: { sysCreatedBy?: string }) =>
canAccessRecord(permission.read, record, userId), canAccessRecord(permission.read, record, userId),
canUpdateRecord: (record: { _createdBy?: string }) => canUpdateRecord: (record: { sysCreatedBy?: string }) =>
canAccessRecord(permission.update, record, userId), canAccessRecord(permission.update, record, userId),
canDeleteRecord: (record: { _createdBy?: string }) => canDeleteRecord: (record: { sysCreatedBy?: string }) =>
canAccessRecord(permission.delete, record, userId), canAccessRecord(permission.delete, record, userId),
}; };
} }
@ -296,7 +296,7 @@ export function useInstancePermissions(): InstancePermissions | undefined {
*/ */
export function useCanEditRecord( export function useCanEditRecord(
tableName: string, tableName: string,
record: { _createdBy?: string } | undefined, record: { sysCreatedBy?: string } | undefined,
userId: string userId: string
): boolean { ): boolean {
const { update } = useTablePermission(tableName); const { update } = useTablePermission(tableName);
@ -311,7 +311,7 @@ export function useCanEditRecord(
*/ */
export function useCanDeleteRecord( export function useCanDeleteRecord(
tableName: string, tableName: string,
record: { _createdBy?: string } | undefined, record: { sysCreatedBy?: string } | undefined,
userId: string userId: string
): boolean { ): boolean {
const { delete: deleteLevel } = useTablePermission(tableName); const { delete: deleteLevel } = useTablePermission(tableName);
@ -329,7 +329,7 @@ interface PermissionGateProps {
table?: string; table?: string;
view?: string; view?: string;
action?: 'view' | 'read' | 'create' | 'update' | 'delete'; action?: 'view' | 'read' | 'create' | 'update' | 'delete';
record?: { _createdBy?: string }; record?: { sysCreatedBy?: string };
children: React.ReactNode; children: React.ReactNode;
fallback?: React.ReactNode; fallback?: React.ReactNode;
} }

View file

@ -157,7 +157,7 @@ export function usePrompts() {
return false; // Don't show readonly fields in edit form return false; // Don't show readonly fields in edit form
} }
// Also filter out common non-editable fields // Also filter out common non-editable fields
const nonEditableFields = ['id', 'mandateId', '_createdBy', '_hideDelete']; const nonEditableFields = ['id', 'mandateId', 'sysCreatedBy', '_hideDelete'];
return !nonEditableFields.includes(attr.name); return !nonEditableFields.includes(attr.name);
}) })
.map(attr => { .map(attr => {
@ -367,7 +367,7 @@ export function usePrompts() {
return false; return false;
} }
// Filter out ID fields and other auto-generated fields // Filter out ID fields and other auto-generated fields
const nonEditableFields = ['id', 'mandateId', '_createdBy', '_hideDelete']; const nonEditableFields = ['id', 'mandateId', 'sysCreatedBy', '_hideDelete'];
return !nonEditableFields.includes(attr.name); return !nonEditableFields.includes(attr.name);
}) })
.map(attr => { .map(attr => {
@ -530,7 +530,7 @@ export function usePromptOperations() {
try { try {
// Pass all provided fields (supports partial inline updates like isSystem toggle) // Pass all provided fields (supports partial inline updates like isSystem toggle)
const { id, mandateId, _createdBy, _createdAt, _modifiedAt, _permissions, ...requestBody } = updateData; const { id, mandateId, sysCreatedBy, sysCreatedAt, sysModifiedAt, _permissions, ...requestBody } = updateData;
const updatedPrompt = await updatePromptApi(request, promptId, requestBody as UpdatePromptData); const updatedPrompt = await updatePromptApi(request, promptId, requestBody as UpdatePromptData);

View file

@ -165,7 +165,7 @@ function _createRealEstateEntityHook<T extends { id: string }>(config: RealEstat
.filter(attr => { .filter(attr => {
if (attr.readonly === true || attr.editable === false) return false; if (attr.readonly === true || attr.editable === false) return false;
if (attr.name === 'id') return false; if (attr.name === 'id') return false;
const nonEditable = ['_createdBy', '_createdAt', '_modifiedBy', '_modifiedAt']; const nonEditable = ['sysCreatedBy', 'sysCreatedAt', 'sysModifiedBy', 'sysModifiedAt'];
return !nonEditable.includes(attr.name); return !nonEditable.includes(attr.name);
}) })
.map(attr => { .map(attr => {
@ -210,7 +210,7 @@ function _createRealEstateEntityHook<T extends { id: string }>(config: RealEstat
const generateCreateFieldsFromAttributes = useCallback(() => { const generateCreateFieldsFromAttributes = useCallback(() => {
if (!attributes || attributes.length === 0) return []; if (!attributes || attributes.length === 0) return [];
return attributes return attributes
.filter(attr => !['_createdBy', '_createdAt', '_modifiedBy', '_modifiedAt', 'mandateId', 'featureInstanceId'].includes(attr.name)) .filter(attr => !['sysCreatedBy', 'sysCreatedAt', 'sysModifiedBy', 'sysModifiedAt', 'mandateId', 'featureInstanceId'].includes(attr.name))
.map(attr => { .map(attr => {
let fieldType: 'string' | 'boolean' | 'email' | 'textarea' | 'date' | 'enum' | 'multiselect' | 'readonly' | 'number' = 'string'; let fieldType: 'string' | 'boolean' | 'email' | 'textarea' | 'date' | 'enum' | 'multiselect' | 'readonly' | 'number' = 'string';
let options: Array<{ value: string | number; label: string }> | undefined; let options: Array<{ value: string | number; label: string }> | undefined;

View file

@ -218,7 +218,7 @@ function _createTrusteeEntityHook<T extends { id: string }>(config: TrusteeEntit
if (attr.name === 'id') { if (attr.name === 'id') {
return false; return false;
} }
const nonEditableFields = ['_createdBy', '_createdAt', '_modifiedBy', '_modifiedAt']; const nonEditableFields = ['sysCreatedBy', 'sysCreatedAt', 'sysModifiedBy', 'sysModifiedAt'];
return !nonEditableFields.includes(attr.name); return !nonEditableFields.includes(attr.name);
}) })
.map(attr => { .map(attr => {
@ -284,7 +284,7 @@ function _createTrusteeEntityHook<T extends { id: string }>(config: TrusteeEntit
return attributes return attributes
.filter(attr => { .filter(attr => {
const systemFields = ['_createdBy', '_createdAt', '_modifiedBy', '_modifiedAt', 'mandateId']; const systemFields = ['sysCreatedBy', 'sysCreatedAt', 'sysModifiedBy', 'sysModifiedAt', 'mandateId'];
return !systemFields.includes(attr.name); return !systemFields.includes(attr.name);
}) })
.map(attr => { .map(attr => {

View file

@ -175,7 +175,7 @@ export function useTrusteeAccess() {
if (attr.readonly === true || attr.editable === false) { if (attr.readonly === true || attr.editable === false) {
return false; return false;
} }
const nonEditableFields = ['id', 'mandate', '_createdBy', '_modifiedBy', '_createdAt', '_modifiedAt']; const nonEditableFields = ['id', 'mandate', 'sysCreatedBy', 'sysModifiedBy', 'sysCreatedAt', 'sysModifiedAt'];
return !nonEditableFields.includes(attr.name); return !nonEditableFields.includes(attr.name);
}) })
.map(attr => { .map(attr => {

View file

@ -175,7 +175,7 @@ export function useTrusteeContracts() {
if (attr.readonly === true || attr.editable === false) { if (attr.readonly === true || attr.editable === false) {
return false; return false;
} }
const nonEditableFields = ['id', 'mandate', '_createdBy', '_modifiedBy', '_createdAt', '_modifiedAt']; const nonEditableFields = ['id', 'mandate', 'sysCreatedBy', 'sysModifiedBy', 'sysCreatedAt', 'sysModifiedAt'];
return !nonEditableFields.includes(attr.name); return !nonEditableFields.includes(attr.name);
}) })
.map(attr => { .map(attr => {

View file

@ -176,7 +176,7 @@ export function useTrusteeDocuments() {
return false; return false;
} }
// documentData is handled separately (binary upload) // documentData is handled separately (binary upload)
const nonEditableFields = ['id', 'documentData', 'mandate', '_createdBy', '_modifiedBy', '_createdAt', '_modifiedAt']; const nonEditableFields = ['id', 'documentData', 'mandate', 'sysCreatedBy', 'sysModifiedBy', 'sysCreatedAt', 'sysModifiedAt'];
return !nonEditableFields.includes(attr.name); return !nonEditableFields.includes(attr.name);
}) })
.map(attr => { .map(attr => {

View file

@ -174,7 +174,7 @@ export function useTrusteeOrganisations() {
if (attr.readonly === true || attr.editable === false) { if (attr.readonly === true || attr.editable === false) {
return false; return false;
} }
const nonEditableFields = ['mandate', '_createdBy', '_modifiedBy', '_createdAt', '_modifiedAt']; const nonEditableFields = ['mandate', 'sysCreatedBy', 'sysModifiedBy', 'sysCreatedAt', 'sysModifiedAt'];
return !nonEditableFields.includes(attr.name); return !nonEditableFields.includes(attr.name);
}) })
.map(attr => { .map(attr => {

View file

@ -163,7 +163,7 @@ export function useTrusteePositionDocuments() {
if (attr.readonly === true || attr.editable === false) { if (attr.readonly === true || attr.editable === false) {
return false; return false;
} }
const nonEditableFields = ['id', 'mandate', '_createdBy', '_modifiedBy', '_createdAt', '_modifiedAt']; const nonEditableFields = ['id', 'mandate', 'sysCreatedBy', 'sysModifiedBy', 'sysCreatedAt', 'sysModifiedAt'];
return !nonEditableFields.includes(attr.name); return !nonEditableFields.includes(attr.name);
}) })
.map(attr => { .map(attr => {

View file

@ -178,7 +178,7 @@ export function useTrusteePositions() {
if (attr.readonly === true || attr.editable === false) { if (attr.readonly === true || attr.editable === false) {
return false; return false;
} }
const nonEditableFields = ['id', 'mandate', '_createdBy', '_modifiedBy', '_createdAt', '_modifiedAt']; const nonEditableFields = ['id', 'mandate', 'sysCreatedBy', 'sysModifiedBy', 'sysCreatedAt', 'sysModifiedAt'];
return !nonEditableFields.includes(attr.name); return !nonEditableFields.includes(attr.name);
}) })
.map(attr => { .map(attr => {

View file

@ -176,7 +176,7 @@ export function useTrusteeRoles() {
if (attr.readonly === true || attr.editable === false) { if (attr.readonly === true || attr.editable === false) {
return false; return false;
} }
const nonEditableFields = ['mandate', '_createdBy', '_modifiedBy', '_createdAt', '_modifiedAt']; const nonEditableFields = ['mandate', 'sysCreatedBy', 'sysModifiedBy', 'sysCreatedAt', 'sysModifiedAt'];
return !nonEditableFields.includes(attr.name); return !nonEditableFields.includes(attr.name);
}) })
.map(attr => { .map(attr => {

View file

@ -412,7 +412,7 @@ export function useOrgUsers() {
return false; // Don't show readonly fields in edit form return false; // Don't show readonly fields in edit form
} }
// Also filter out common non-editable fields // Also filter out common non-editable fields
const nonEditableFields = ['id', 'mandateId', '_createdBy', '_hideDelete']; const nonEditableFields = ['id', 'mandateId', 'sysCreatedBy', '_hideDelete'];
return !nonEditableFields.includes(attr.name); return !nonEditableFields.includes(attr.name);
}) })
.map(attr => { .map(attr => {
@ -560,7 +560,7 @@ export function useOrgUsers() {
return false; return false;
} }
// Filter out ID fields and other auto-generated fields // Filter out ID fields and other auto-generated fields
const nonEditableFields = ['id', 'mandateId', '_createdBy', '_hideDelete', 'authenticationAuthority']; const nonEditableFields = ['id', 'mandateId', 'sysCreatedBy', '_hideDelete', 'authenticationAuthority'];
return !nonEditableFields.includes(attr.name); return !nonEditableFields.includes(attr.name);
}) })
.map(attr => { .map(attr => {

View file

@ -224,7 +224,7 @@ export function useUserWorkflows(options?: { instanceId?: string; featureCode?:
return false; // Don't show readonly fields in edit form return false; // Don't show readonly fields in edit form
} }
// Also filter out common non-editable fields // Also filter out common non-editable fields
const nonEditableFields = ['id', 'mandateId', '_createdBy', '_hideDelete']; const nonEditableFields = ['id', 'mandateId', 'sysCreatedBy', '_hideDelete'];
return !nonEditableFields.includes(attr.name); return !nonEditableFields.includes(attr.name);
}) })
.map(attr => { .map(attr => {

View file

@ -204,7 +204,7 @@ export const ConnectionsPage: React.FC = () => {
// Form attributes for edit modal // Form attributes for edit modal
const formAttributes = useMemo(() => { const formAttributes = useMemo(() => {
const excludedFields = ['id', 'mandateId', 'userId', '_createdBy', '_createdAt', '_modifiedAt', 'connectedAt', 'lastChecked']; const excludedFields = ['id', 'mandateId', 'userId', 'sysCreatedBy', 'sysCreatedAt', 'sysModifiedAt', 'connectedAt', 'lastChecked'];
return (attributes || []) return (attributes || [])
.filter(attr => !excludedFields.includes(attr.name)); .filter(attr => !excludedFields.includes(attr.name));
}, [attributes]); }, [attributes]);

View file

@ -142,7 +142,7 @@ export const FilesPage: React.FC = () => {
})); }));
cols.push({ cols.push({
key: '_createdBy', key: 'sysCreatedBy',
label: 'Created By', label: 'Created By',
type: 'text' as any, type: 'text' as any,
sortable: true, sortable: true,
@ -289,7 +289,7 @@ export const FilesPage: React.FC = () => {
}, [selectedFolderId, _tableRefetch]); }, [selectedFolderId, _tableRefetch]);
const formAttributes = useMemo(() => { const formAttributes = useMemo(() => {
const excludedFields = ['id', 'mandateId', 'fileHash', '_createdBy', '_createdAt', '_modifiedAt', 'creationDate', 'source']; const excludedFields = ['id', 'mandateId', 'fileHash', 'sysCreatedBy', 'sysCreatedAt', 'sysModifiedAt', 'creationDate', 'source'];
return (attributes || []).filter(attr => !excludedFields.includes(attr.name)); return (attributes || []).filter(attr => !excludedFields.includes(attr.name));
}, [attributes]); }, [attributes]);

View file

@ -53,7 +53,7 @@ export const PromptsPage: React.FC = () => {
// Generate columns from attributes - exclude ID fields from display // Generate columns from attributes - exclude ID fields from display
const columns = useMemo(() => { const columns = useMemo(() => {
// Fields to hide in table view // Fields to hide in table view
const hiddenColumns = ['id', 'mandateId', '_createdAt', '_modifiedAt', '_hideDelete', '_permissions']; const hiddenColumns = ['id', 'mandateId', 'sysCreatedAt', 'sysModifiedAt', '_hideDelete', '_permissions'];
const cols = (attributes || []) const cols = (attributes || [])
.filter(attr => !hiddenColumns.includes(attr.name)) .filter(attr => !hiddenColumns.includes(attr.name))
@ -71,9 +71,9 @@ export const PromptsPage: React.FC = () => {
fkDisplayField: (attr as any).fkDisplayField, fkDisplayField: (attr as any).fkDisplayField,
})); }));
// Add _createdBy column with FK resolution to show username // Add sysCreatedBy column with FK resolution to show username
cols.push({ cols.push({
key: '_createdBy', key: 'sysCreatedBy',
label: 'Created By', label: 'Created By',
type: 'text' as any, type: 'text' as any,
sortable: true, sortable: true,
@ -148,7 +148,7 @@ export const PromptsPage: React.FC = () => {
// Form attributes for create/edit modal // Form attributes for create/edit modal
const formAttributes = useMemo(() => { const formAttributes = useMemo(() => {
const excludedFields = ['id', 'mandateId', 'isSystem', '_createdBy', '_createdAt', '_modifiedAt', '_hideDelete', '_permissions']; const excludedFields = ['id', 'mandateId', 'isSystem', 'sysCreatedBy', 'sysCreatedAt', 'sysModifiedAt', '_hideDelete', '_permissions'];
return (attributes || []) return (attributes || [])
.filter(attr => !excludedFields.includes(attr.name)); .filter(attr => !excludedFields.includes(attr.name));
}, [attributes]); }, [attributes]);

View file

@ -110,9 +110,9 @@ export const AutomationDefinitionsView: React.FC = () => {
const columns = useMemo(() => { const columns = useMemo(() => {
const hiddenColumns = [ const hiddenColumns = [
'id', 'mandateId', 'featureInstanceId', '_createdBy', '_createdAt', '_modifiedAt', 'id', 'mandateId', 'featureInstanceId', 'sysCreatedBy', 'sysCreatedAt', 'sysModifiedAt',
'template', 'executionLogs', 'placeholders', 'template', 'executionLogs', 'placeholders',
'_createdByUserName', 'mandateName', 'featureInstanceName', 'sysCreatedByUserName', 'mandateName', 'featureInstanceName',
]; ];
const attrColumns = (attributes || []) const attrColumns = (attributes || [])
.filter(attr => !hiddenColumns.includes(attr.name)) .filter(attr => !hiddenColumns.includes(attr.name))
@ -130,7 +130,7 @@ export const AutomationDefinitionsView: React.FC = () => {
const enrichedColumns = [ const enrichedColumns = [
{ key: 'mandateName', label: 'Mandant', type: 'text' as any, sortable: true, filterable: true, searchable: true, width: 150, minWidth: 100, maxWidth: 250 }, { key: 'mandateName', label: 'Mandant', type: 'text' as any, sortable: true, filterable: true, searchable: true, width: 150, minWidth: 100, maxWidth: 250 },
{ key: 'featureInstanceName', label: 'Feature-Instanz', type: 'text' as any, sortable: true, filterable: true, searchable: true, width: 160, minWidth: 100, maxWidth: 250 }, { key: 'featureInstanceName', label: 'Feature-Instanz', type: 'text' as any, sortable: true, filterable: true, searchable: true, width: 160, minWidth: 100, maxWidth: 250 },
{ key: '_createdByUserName', label: 'Erstellt von', type: 'text' as any, sortable: true, filterable: true, searchable: true, width: 150, minWidth: 100, maxWidth: 250 }, { key: 'sysCreatedByUserName', label: 'Erstellt von', type: 'text' as any, sortable: true, filterable: true, searchable: true, width: 150, minWidth: 100, maxWidth: 250 },
]; ];
return [...attrColumns, ...enrichedColumns]; return [...attrColumns, ...enrichedColumns];
}, [attributes]); }, [attributes]);

View file

@ -52,7 +52,7 @@ export const AutomationTemplatesView: React.FC = () => {
value ? <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: '0.75rem', padding: '0.125rem 0.5rem', borderRadius: 10, background: 'var(--info-color, #3182ce)', color: '#fff' }}><FaLock style={{ fontSize: '0.625rem' }} /> System</span> value ? <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: '0.75rem', padding: '0.125rem 0.5rem', borderRadius: 10, background: 'var(--info-color, #3182ce)', color: '#fff' }}><FaLock style={{ fontSize: '0.625rem' }} /> System</span>
: <span style={{ fontSize: '0.75rem', padding: '0.125rem 0.5rem', borderRadius: 10, background: 'var(--success-color, #38a169)', color: '#fff' }}>Instanz</span> : <span style={{ fontSize: '0.75rem', padding: '0.125rem 0.5rem', borderRadius: 10, background: 'var(--success-color, #38a169)', color: '#fff' }}>Instanz</span>
}, },
{ key: '_createdByUserName', label: 'Erstellt von', type: 'string' as const, width: 150 }, { key: 'sysCreatedByUserName', label: 'Erstellt von', type: 'string' as const, width: 150 },
], []); ], []);
const handleEditClick = async (template: AutomationTemplate) => { const handleEditClick = async (template: AutomationTemplate) => {

View file

@ -110,7 +110,7 @@ export const RealEstateParcelsView: React.FC = () => {
}; };
const formAttributes = useMemo(() => { const formAttributes = useMemo(() => {
const excluded = ['id', 'mandateId', 'instanceId', '_createdBy', '_createdAt', '_modifiedAt', '_modifiedBy']; const excluded = ['id', 'mandateId', 'instanceId', 'sysCreatedBy', 'sysCreatedAt', 'sysModifiedAt', 'sysModifiedBy'];
return (attributes || []).filter(attr => !excluded.includes(attr.name)); return (attributes || []).filter(attr => !excluded.includes(attr.name));
}, [attributes]); }, [attributes]);

View file

@ -106,7 +106,7 @@ export const RealEstateProjectsView: React.FC = () => {
}; };
const formAttributes = useMemo(() => { const formAttributes = useMemo(() => {
const excluded = ['id', 'mandateId', 'instanceId', '_createdBy', '_createdAt', '_modifiedAt', '_modifiedBy']; const excluded = ['id', 'mandateId', 'instanceId', 'sysCreatedBy', 'sysCreatedAt', 'sysModifiedAt', 'sysModifiedBy'];
return (attributes || []).filter(attr => !excluded.includes(attr.name)); return (attributes || []).filter(attr => !excluded.includes(attr.name));
}, [attributes]); }, [attributes]);

View file

@ -150,7 +150,7 @@ export const TrusteeDocumentsView: React.FC = () => {
// Form attributes (exclude system fields) // Form attributes (exclude system fields)
const formAttributes = useMemo(() => { const formAttributes = useMemo(() => {
const excludedFields = ['id', 'mandateId', 'instanceId', '_createdBy', '_createdAt', '_modifiedAt', '_modifiedBy']; const excludedFields = ['id', 'mandateId', 'instanceId', 'sysCreatedBy', 'sysCreatedAt', 'sysModifiedAt', 'sysModifiedBy'];
return (attributes || []).filter(attr => !excludedFields.includes(attr.name)); return (attributes || []).filter(attr => !excludedFields.includes(attr.name));
}, [attributes]); }, [attributes]);

View file

@ -52,7 +52,7 @@ export const TrusteePositionDocumentsView: React.FC = () => {
if (!attributes || attributes.length === 0) return []; if (!attributes || attributes.length === 0) return [];
// Exclude system fields from table columns // Exclude system fields from table columns
const excludedFields = ['id', 'mandateId', 'featureInstanceId', '_createdBy', '_createdAt', '_modifiedAt', '_modifiedBy']; const excludedFields = ['id', 'mandateId', 'featureInstanceId', 'sysCreatedBy', 'sysCreatedAt', 'sysModifiedAt', 'sysModifiedBy'];
return attributes return attributes
.filter((attr: any) => !excludedFields.includes(attr.name)) .filter((attr: any) => !excludedFields.includes(attr.name))
@ -127,7 +127,7 @@ export const TrusteePositionDocumentsView: React.FC = () => {
// Form attributes (exclude system fields) // Form attributes (exclude system fields)
const formAttributes = useMemo(() => { const formAttributes = useMemo(() => {
const excludedFields = ['id', 'mandateId', 'featureInstanceId', '_createdBy', '_createdAt', '_modifiedAt', '_modifiedBy']; const excludedFields = ['id', 'mandateId', 'featureInstanceId', 'sysCreatedBy', 'sysCreatedAt', 'sysModifiedAt', 'sysModifiedBy'];
return (attributes || []).filter((attr: any) => !excludedFields.includes(attr.name)); return (attributes || []).filter((attr: any) => !excludedFields.includes(attr.name));
}, [attributes]); }, [attributes]);

View file

@ -257,7 +257,7 @@ export const TrusteePositionsView: React.FC = () => {
const positionColumnOrder = [ const positionColumnOrder = [
'_documentRefs', // Belege (download icons) '_documentRefs', // Belege (download icons)
'_syncStatus', // Sync-Status '_syncStatus', // Sync-Status
'_createdAt', // Erstellt am 'sysCreatedAt', // Erstellt am
'valuta', // Valuta date 'valuta', // Valuta date
'tags', 'tags',
'company', 'company',
@ -372,7 +372,7 @@ export const TrusteePositionsView: React.FC = () => {
// Form attributes (exclude system fields) // Form attributes (exclude system fields)
const formAttributes = useMemo(() => { const formAttributes = useMemo(() => {
const excludedFields = ['id', 'mandateId', 'instanceId', '_createdBy', '_createdAt', '_modifiedAt', '_modifiedBy']; const excludedFields = ['id', 'mandateId', 'instanceId', 'sysCreatedBy', 'sysCreatedAt', 'sysModifiedAt', 'sysModifiedBy'];
return (attributes || []).filter(attr => !excludedFields.includes(attr.name)); return (attributes || []).filter(attr => !excludedFields.includes(attr.name));
}, [attributes]); }, [attributes]);

View file

@ -60,7 +60,7 @@ const NeutralizationPanel: React.FC<NeutralizationPanelProps> = ({ instanceId })
patternType: m.patternType || 'unknown', patternType: m.patternType || 'unknown',
fileId: m.fileId, fileId: m.fileId,
fileName: m.fileName, fileName: m.fileName,
createdAt: m.createdAt || m._createdAt, createdAt: m.createdAt || m.sysCreatedAt,
}))); })));
} catch (err) { } catch (err) {
console.error('Failed to load mappings:', err); console.error('Failed to load mappings:', err);

View file

@ -322,14 +322,14 @@ export function hasAccess(level: AccessLevel): boolean {
*/ */
export function canAccessRecord( export function canAccessRecord(
level: AccessLevel, level: AccessLevel,
record: { _createdBy?: string }, record: { sysCreatedBy?: string },
userId: string userId: string
): boolean { ): boolean {
switch (level) { switch (level) {
case 'n': case 'n':
return false; return false;
case 'm': case 'm':
return record._createdBy === userId; return record.sysCreatedBy === userId;
case 'g': case 'g':
case 'a': case 'a':
return true; return true;