From c66d04f0b58de22084f5bce2e3e40e27cba7a9ba Mon Sep 17 00:00:00 2001 From: flfeders Date: Thu, 6 Aug 2026 14:52:38 +0200 Subject: [PATCH] Handle missing tables in tenant merge --- backend/src/utils/tenantMergeService.ts | 48 +++++++++++++++++-- .../tenant-imports/[importId].vue | 2 + 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/backend/src/utils/tenantMergeService.ts b/backend/src/utils/tenantMergeService.ts index 695fc91..a0b091c 100644 --- a/backend/src/utils/tenantMergeService.ts +++ b/backend/src/utils/tenantMergeService.ts @@ -80,6 +80,42 @@ const remapSourceTenant = (exportData: TenantFullExport, targetTenantId: number) return tables } +const splitSourceTablesByAvailability = ( + sourceTables: Record[]>, + metadata: Record +) => { + const available: Record[]> = {} + const unavailable = Object.keys(sourceTables).filter((table) => { + if (metadata[table]) { + available[table] = sourceTables[table] + return false + } + return sourceTables[table].length > 0 + }) + + return { available, unavailable } +} + +const appendUnavailableTableConflicts = (plan: TenantMergePlan, unavailableTables: string[]) => { + for (const table of unavailableTables.sort()) { + plan.items.push({ + id: `missing-target-table:${table}`, + table, + kind: "conflict", + sourceKey: "Zieltabelle fehlt", + targetKey: null, + label: `Tabelle ${table} ist im Zielsystem nicht vorhanden`, + defaultDecision: "target", + allowedDecisions: ["target"], + differences: ["Die erforderliche Tabelle fehlt im Zielschema und muss zuerst per Migration angelegt werden."], + sourceRow: {}, + targetRow: null, + }) + plan.summary.conflict += 1 + } + return plan +} + const loadTargetRows = async ( client: any, table: string, @@ -124,7 +160,8 @@ export const createTenantMergeDryRunWithClient = async ( targetTenantId: number ): Promise => { const metadata = await loadMergeMetadata(client) - const sourceTables = remapSourceTenant(exportData, targetTenantId) + const remappedSourceTables = remapSourceTenant(exportData, targetTenantId) + const { available: sourceTables, unavailable } = splitSourceTablesByAvailability(remappedSourceTables, metadata) const targetTables: Record[]> = {} for (const [table, sourceRows] of Object.entries(sourceTables)) { @@ -133,7 +170,7 @@ export const createTenantMergeDryRunWithClient = async ( targetTables[table] = await loadTargetRows(client, table, sourceRows, tableMetadata, targetTenantId) } - return buildTenantMergePlan(sourceTables, targetTables, metadata) + return appendUnavailableTableConflicts(buildTenantMergePlan(sourceTables, targetTables, metadata), unavailable) } export const createTenantMergeDryRun = async ( @@ -214,7 +251,9 @@ const topologicalTableOrder = (tables: string[], metadata: Record - metadata[table].foreignKeys.every((foreignKey) => !remaining.has(foreignKey.referencedTable) || foreignKey.referencedTable === table) + (metadata[table]?.foreignKeys || []).every((foreignKey) => + !remaining.has(foreignKey.referencedTable) || foreignKey.referencedTable === table + ) ) const next = ready.length ? ready.sort() : [Array.from(remaining).sort()[0]] for (const table of next) { @@ -240,7 +279,8 @@ export const executeTenantMergeWithClient = async ( decisions: Record ): Promise => { const metadata = await loadMergeMetadata(client) - const sourceTables = remapSourceTenant(rawExportData, targetTenantId) + const remappedSourceTables = remapSourceTenant(rawExportData, targetTenantId) + const { available: sourceTables } = splitSourceTablesByAvailability(remappedSourceTables, metadata) const preparedExport: TenantFullExport = prepareTenantFullExportRowsForImport({ ...rawExportData, tenantId: targetTenantId, diff --git a/frontend/pages/administration/tenant-imports/[importId].vue b/frontend/pages/administration/tenant-imports/[importId].vue index 4ecc953..5137bc9 100644 --- a/frontend/pages/administration/tenant-imports/[importId].vue +++ b/frontend/pages/administration/tenant-imports/[importId].vue @@ -217,12 +217,14 @@ onMounted(async () => {

Ziel behalten