From c7ba7a9cc5271afcc391aeea0d784460f7111089 Mon Sep 17 00:00:00 2001 From: florianfederspiel Date: Mon, 18 May 2026 21:49:12 +0200 Subject: [PATCH] KI-AGENT: Importiere Export in bestehenden Zielmandanten --- backend/src/routes/admin.ts | 11 +++- backend/src/utils/tenantFullExport.ts | 61 ++++++++++++++++++- .../pages/administration/tenants/[id].vue | 5 +- 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/backend/src/routes/admin.ts b/backend/src/routes/admin.ts index a4f2845..3c7f52b 100644 --- a/backend/src/routes/admin.ts +++ b/backend/src/routes/admin.ts @@ -970,8 +970,15 @@ export default async function adminRoutes(server: FastifyInstance) { const currentUser = await requireAdmin(req, reply); if (!currentUser) return; - const exportData = req.body as TenantFullExport; - const result = await importTenantFullExport(server, exportData); + const body = req.body as TenantFullExport | { exportData?: TenantFullExport; targetTenantId?: number }; + const exportData = "format" in body ? body : body.exportData; + const targetTenantId = "format" in body ? null : Number(body.targetTenantId || 0) || null; + + if (!exportData) { + return reply.code(400).send({ error: "exportData required" }); + } + + const result = await importTenantFullExport(server, exportData, { targetTenantId }); const fallbackName = deriveNameFromEmail(currentUser.email); await server.db diff --git a/backend/src/utils/tenantFullExport.ts b/backend/src/utils/tenantFullExport.ts index cbfb1b9..dc9584e 100644 --- a/backend/src/utils/tenantFullExport.ts +++ b/backend/src/utils/tenantFullExport.ts @@ -34,6 +34,10 @@ type ImportResult = { files: { restored: number; skipped: number } } +type ImportOptions = { + targetTenantId?: number | null +} + const quoteIdent = (value: string) => `"${value.replace(/"/g, '""')}"` const tableColumns = async (client: any) => { @@ -214,6 +218,54 @@ const restoreFiles = async (exportData: TenantFullExport) => { return { restored, skipped } } +const remapTenantScopedExport = ( + exportData: TenantFullExport, + targetTenantId?: number | null +): TenantFullExport => { + if (!targetTenantId || targetTenantId === exportData.tenantId) return exportData + + const sourceTenantId = exportData.tenantId + const sourcePathPrefix = `${sourceTenantId}/` + const targetPathPrefix = `${targetTenantId}/` + const tables: TableRows = {} + + for (const [table, rows] of Object.entries(exportData.tables || {})) { + tables[table] = rows.map((row) => { + const nextRow = { ...row } + + if (table === "tenants" && nextRow.id === sourceTenantId) { + nextRow.id = targetTenantId + } + + if (nextRow.tenant === sourceTenantId) { + nextRow.tenant = targetTenantId + } + + if (nextRow.tenant_id === sourceTenantId) { + nextRow.tenant_id = targetTenantId + } + + if (table === "files" && typeof nextRow.path === "string" && nextRow.path.startsWith(sourcePathPrefix)) { + nextRow.path = `${targetPathPrefix}${nextRow.path.slice(sourcePathPrefix.length)}` + } + + return nextRow + }) + } + + return { + ...exportData, + tenantId: targetTenantId, + tables, + files: (exportData.files || []).map((file) => ({ + ...file, + path: file.path?.startsWith(sourcePathPrefix) + ? `${targetPathPrefix}${file.path.slice(sourcePathPrefix.length)}` + : file.path, + })), + } +} + const prepareColumnValue = (value: any, isJsonColumn: boolean) => { if (!isJsonColumn || value === null || typeof value === "undefined") return value if (typeof value === "string") return value @@ -263,11 +315,16 @@ const refreshSequences = async (client: any, columnsByTable: Map => { - if (exportData?.format !== "fedeo.tenant-full-export" || exportData.version !== 1) { +export const importTenantFullExport = async ( + server: FastifyInstance, + rawExportData: TenantFullExport, + options: ImportOptions = {} +): Promise => { + if (rawExportData?.format !== "fedeo.tenant-full-export" || rawExportData.version !== 1) { throw new Error("Ungültiges FEDEO Mandantenexport-Format") } + const exportData = remapTenantScopedExport(rawExportData, options.targetTenantId) const client = await pool.connect() const importOrder = [ "tenants", diff --git a/frontend/pages/administration/tenants/[id].vue b/frontend/pages/administration/tenants/[id].vue index 488dddb..d2b6bb5 100644 --- a/frontend/pages/administration/tenants/[id].vue +++ b/frontend/pages/administration/tenants/[id].vue @@ -140,7 +140,10 @@ const importTenantExport = async (event: Event) => { try { const rawContent = await file.text() const exportData = JSON.parse(rawContent) - const result = await admin.importTenant(exportData) + const result = await admin.importTenant({ + exportData, + targetTenantId: tenantForm.value?.id || tenantId, + }) const rowCount = (result.tables || []).reduce((sum, table) => sum + table.rows, 0) lastImportResult.value = {