From 291f278d99553adc5b0ce975fe26e132fccfbc17 Mon Sep 17 00:00:00 2001 From: flfeders Date: Thu, 6 Aug 2026 12:35:31 +0200 Subject: [PATCH] Add tenant merge review page --- frontend/composables/useAdmin.ts | 45 ++++ .../tenant-imports/[importId].vue | 248 ++++++++++++++++++ .../pages/administration/tenants/[id].vue | 7 +- 3 files changed, 299 insertions(+), 1 deletion(-) create mode 100644 frontend/pages/administration/tenant-imports/[importId].vue diff --git a/frontend/composables/useAdmin.ts b/frontend/composables/useAdmin.ts index ca7adbc..cdda5a1 100644 --- a/frontend/composables/useAdmin.ts +++ b/frontend/composables/useAdmin.ts @@ -61,6 +61,35 @@ export type TenantImportResult = { filesDone?: number filesTotal?: number error?: string | null + reviewUrl?: string +} + +export type TenantMergeKind = "import" | "existing" | "conflict" | "id_collision" +export type TenantMergeDecision = "source" | "target" +export type TenantMergeItem = { + id: string + table: string + kind: TenantMergeKind + label: string + sourceKey: string + targetKey: string | null + defaultDecision: TenantMergeDecision + allowedDecisions: TenantMergeDecision[] + differences: string[] + sourceRow: Record + targetRow: Record | null +} +export type TenantMergeReview = { + importId: string + tenantId: number + status: string + filename: string + summary: Record + tables: string[] + total: number + offset: number + limit: number + items: TenantMergeItem[] } export type TenantExportJob = { @@ -222,6 +251,20 @@ export const useAdmin = () => { }) } + const getTenantImportReview = async ( + importId: string, + query: { kind?: string; table?: string; offset?: number; limit?: number } = {}, + ): Promise => { + return await $api(`/api/admin/tenant-imports/${importId}/review`, { query }) + } + + const executeTenantImportMerge = async (importId: string, decisions: Record) => { + return await $api(`/api/admin/tenant-imports/${importId}/execute`, { + method: "POST", + body: { decisions }, + }) + } + const getSystemStatus = async (): Promise => { return await $api("/api/admin/system-status") } @@ -262,5 +305,7 @@ export const useAdmin = () => { getTenantExport, downloadTenantExport, importTenant, + getTenantImportReview, + executeTenantImportMerge, } } diff --git a/frontend/pages/administration/tenant-imports/[importId].vue b/frontend/pages/administration/tenant-imports/[importId].vue new file mode 100644 index 0000000..6781b58 --- /dev/null +++ b/frontend/pages/administration/tenant-imports/[importId].vue @@ -0,0 +1,248 @@ + + + diff --git a/frontend/pages/administration/tenants/[id].vue b/frontend/pages/administration/tenants/[id].vue index 8c5c637..6d2938d 100644 --- a/frontend/pages/administration/tenants/[id].vue +++ b/frontend/pages/administration/tenants/[id].vue @@ -206,7 +206,7 @@ const importTenantExport = async (event: Event) => { filename: job.filename || file.name, } - while (!["ready", "failed"].includes(job.status)) { + while (!["ready", "review", "failed"].includes(job.status)) { await new Promise((resolve) => setTimeout(resolve, 1500)) job = await admin.getTenantExport(importJobId) tenantImportProgress.value = { @@ -221,6 +221,11 @@ const importTenantExport = async (event: Event) => { throw new Error(job.error || "Import konnte nicht abgeschlossen werden.") } + if (job.status === "review") { + await router.push(`/administration/tenant-imports/${importJobId}`) + return + } + await fetchTenant() await auth.fetchMe() await auth.switchTenant(String(job.tenantId || targetTenantId))