From 6a14b0c1ca0f14170a6efdf634efc7c50de2cf16 Mon Sep 17 00:00:00 2001 From: flfeders Date: Wed, 15 Jul 2026 13:11:57 +0200 Subject: [PATCH 1/4] Extend document title levels --- backend/src/utils/pdf.ts | 68 +++++++++++------- frontend/pages/createDocument/edit/[[id]].vue | 70 ++++++++++++++----- 2 files changed, 92 insertions(+), 46 deletions(-) diff --git a/backend/src/utils/pdf.ts b/backend/src/utils/pdf.ts index 3ba9248..76ef318 100644 --- a/backend/src/utils/pdf.ts +++ b/backend/src/utils/pdf.ts @@ -399,6 +399,12 @@ export const createInvoicePDF = async (server:FastifyInstance, returnMode, invoi let pageIndex = 0 + const getTitleKey = (row) => `${row.pos} - ${row.text}` + const getTransferSumText = (row) => { + const transferSum = invoiceData.total.titleSumsTransfer[getTitleKey(row)] + + return transferSum ? `Übertrag: ${transferSum}` : null + } invoiceData.rows.forEach((row, index) => { @@ -579,18 +585,20 @@ export const createInvoicePDF = async (server:FastifyInstance, returnMode, invoi console.log(invoiceData.rows[index + 1]) - if (invoiceData.rows[index + 1].mode === 'title') { - let transferSumText = `Übertrag: ${invoiceData.total.titleSumsTransfer[Object.keys(invoiceData.total.titleSums)[invoiceData.rows[index + 1].pos - 2]]}` - pages[pageCounter - 1].drawText(transferSumText, { - y: getCoordinatesForPDFLib(21, rowHeight - 2, page1).y, - x: getCoordinatesForPDFLib(21, rowHeight - 2, page1).x + 500 - fontBold.widthOfTextAtSize(transferSumText, 10), - size: 10, - color: rgb(0, 0, 0), - lineHeight: 10, - opacity: 1, - maxWidth: 240, - font: fontBold - }) + if (invoiceData.rows[index + 1]?.mode === 'title') { + const transferSumText = getTransferSumText(invoiceData.rows[index + 1]) + if (transferSumText) { + pages[pageCounter - 1].drawText(transferSumText, { + y: getCoordinatesForPDFLib(21, rowHeight - 2, page1).y, + x: getCoordinatesForPDFLib(21, rowHeight - 2, page1).x + 500 - fontBold.widthOfTextAtSize(transferSumText, 10), + size: 10, + color: rgb(0, 0, 0), + lineHeight: 10, + opacity: 1, + maxWidth: 240, + font: fontBold + }) + } } @@ -713,17 +721,19 @@ export const createInvoicePDF = async (server:FastifyInstance, returnMode, invoi if (index === 0 || pageIndex === 0) { rowHeight += 3 } else { - let transferSumText = `Übertrag: ${invoiceData.total.titleSumsTransfer[Object.keys(invoiceData.total.titleSums)[row.pos - 2]]}` - pages[pageCounter - 1].drawText(transferSumText, { - y: getCoordinatesForPDFLib(21, rowHeight - 2, page1).y, - x: getCoordinatesForPDFLib(21, rowHeight - 2, page1).x + 500 - fontBold.widthOfTextAtSize(transferSumText, 10), - size: 10, - color: rgb(0, 0, 0), - lineHeight: 10, - opacity: 1, - maxWidth: 240, - font: fontBold - }) + const transferSumText = getTransferSumText(row) + if (transferSumText) { + pages[pageCounter - 1].drawText(transferSumText, { + y: getCoordinatesForPDFLib(21, rowHeight - 2, page1).y, + x: getCoordinatesForPDFLib(21, rowHeight - 2, page1).x + 500 - fontBold.widthOfTextAtSize(transferSumText, 10), + size: 10, + color: rgb(0, 0, 0), + lineHeight: 10, + opacity: 1, + maxWidth: 240, + font: fontBold + }) + } pages[pageCounter - 1].drawLine({ start: getCoordinatesForPDFLib(20, rowHeight, page1), @@ -735,6 +745,10 @@ export const createInvoicePDF = async (server:FastifyInstance, returnMode, invoi rowHeight += 5 } + const titleLevel = Math.min(Math.max(Number(row.titleLevel) || 1, 1), 3) + const titleTextSize = titleLevel === 1 ? 12 : titleLevel === 2 ? 11 : 10 + const titleTextX = 35 + ((titleLevel - 1) * 7) + pages[pageCounter - 1].drawText(String(row.pos), { ...getCoordinatesForPDFLib(21, rowHeight, page1), size: 10, @@ -746,16 +760,16 @@ export const createInvoicePDF = async (server:FastifyInstance, returnMode, invoi }) pages[pageCounter - 1].drawText(splitStringBySpace(row.text, 60).join("\n"), { - ...getCoordinatesForPDFLib(35, rowHeight, page1), - size: 12, + ...getCoordinatesForPDFLib(titleTextX, rowHeight, page1), + size: titleTextSize, color: rgb(0, 0, 0), - lineHeight: 12, + lineHeight: titleTextSize, opacity: 1, maxWidth: 500, font: fontBold }) - rowHeight += splitStringBySpace(row.text, 60).length * 4.5 + rowHeight += splitStringBySpace(row.text, 60).length * (titleLevel === 1 ? 4.5 : 4) } else if (row.mode === 'text') { if (index === 0 || pageIndex === 0) { rowHeight += 3 diff --git a/frontend/pages/createDocument/edit/[[id]].vue b/frontend/pages/createDocument/edit/[[id]].vue index 767ab24..e24aa43 100644 --- a/frontend/pages/createDocument/edit/[[id]].vue +++ b/frontend/pages/createDocument/edit/[[id]].vue @@ -108,6 +108,11 @@ const taxTypeItems = [ { key: '19 UStG', label: '19 UStG Kleinunternehmer' }, { key: '12.3 UStG', label: '12.3 UStG' } ] +const titleLevelItems = [ + { label: 'Titel Ebene 1', value: 1 }, + { label: 'Untertitel Ebene 2', value: 2 }, + { label: 'Untertitel Ebene 3', value: 3 } +] const deliveryDateTypeItems = ['Lieferdatum', 'Lieferzeitraum', 'Leistungsdatum', 'Leistungszeitraum', 'Kein Lieferdatum anzeigen'] const paymentTypeItems = [ { key: 'transfer', label: 'Überweisung' }, @@ -832,6 +837,7 @@ const positionAddOptions = [ { mode: 'free', label: 'Freie Position', icon: 'i-heroicons-pencil-square' }, { mode: 'pagebreak', label: 'Seitenumbruch', icon: 'i-heroicons-document-minus' }, { mode: 'title', label: 'Titel', icon: 'i-heroicons-bars-3-bottom-left' }, + { mode: 'subtitle', label: 'Untertitel', icon: 'i-heroicons-list-bullet' }, { mode: 'text', label: 'Text', icon: 'i-heroicons-document-text' } ] @@ -895,10 +901,11 @@ const createPositionRow = (mode, taxPercentage) => { } } - if (mode === "title") { + if (mode === "title" || mode === "subtitle") { return { id: uuidv4(), mode: "title", + titleLevel: mode === "subtitle" ? 2 : 1, linkedEntitys: [] } } @@ -1152,12 +1159,10 @@ const documentTotal = computed(() => { if (row.mode === 'title') { let title = `${row.pos} - ${row.text}` titleSums[title] = 0 + if (lastTitle) { + titleSumsTransfer[title] = transferCounter + } lastTitle = title - - //Übertrag berechnen - titleSumsTransfer[Object.keys(titleSums)[row.pos - 2]] = transferCounter - - } else if (!['pagebreak', 'text'].includes(row.mode) && lastTitle !== "" && !row.optional && !row.alternative) { titleSums[lastTitle] = Number(titleSums[lastTitle]) + Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) / 100)) transferCounter += Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) / 100)) @@ -1610,16 +1615,33 @@ const onChangeTab = (index) => { } const setPosNumbers = () => { - let mainIndex = 1 - let subIndex = 1 + let titleCounters = [0, 0, 0] + let positionIndex = 1 + let currentTitlePath = [] + const hasTitles = itemInfo.value.rows.some(row => row.mode === "title") + let rows = itemInfo.value.rows.map(row => { if (row.mode === 'title') { - row.pos = mainIndex - mainIndex += 1 - subIndex = 1 + const titleLevel = Math.min(Math.max(Number(row.titleLevel) || 1, 1), titleCounters.length) + row.titleLevel = titleLevel + + for (let index = 0; index < titleLevel - 1; index += 1) { + if (titleCounters[index] === 0) { + titleCounters[index] = 1 + } + } + + titleCounters[titleLevel - 1] += 1 + for (let index = titleLevel; index < titleCounters.length; index += 1) { + titleCounters[index] = 0 + } + + currentTitlePath = titleCounters.slice(0, titleLevel) + row.pos = currentTitlePath.join(".") + positionIndex = 1 } else if (!['pagebreak', 'title', 'text'].includes(row.mode)) { - row.pos = itemInfo.value.rows.filter(i => i.mode === "title").length === 0 ? `${subIndex}` : `${mainIndex - 1}.${subIndex}` - subIndex += 1 + row.pos = hasTitles && currentTitlePath.length > 0 ? `${currentTitlePath.join(".")}.${positionIndex}` : `${positionIndex}` + positionIndex += 1 } if (!row.id) { @@ -3471,12 +3493,22 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = { v-if="row.mode === 'title'" :colspan="getTitlePositionColspan()" > - + + + + Date: Wed, 15 Jul 2026 13:21:31 +0200 Subject: [PATCH 2/4] Aggregate nested document title sums --- frontend/pages/createDocument/edit/[[id]].vue | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/frontend/pages/createDocument/edit/[[id]].vue b/frontend/pages/createDocument/edit/[[id]].vue index e24aa43..769dd69 100644 --- a/frontend/pages/createDocument/edit/[[id]].vue +++ b/frontend/pages/createDocument/edit/[[id]].vue @@ -1152,20 +1152,27 @@ const documentTotal = computed(() => { let titleSums = {} let titleSumsTransfer = {} - let lastTitle = "" + let activeTitleKeys = [] let transferCounter = 0 itemInfo.value.rows.forEach(row => { if (row.mode === 'title') { let title = `${row.pos} - ${row.text}` + const titleLevel = Math.min(Math.max(Number(row.titleLevel) || 1, 1), 3) titleSums[title] = 0 - if (lastTitle) { + if (activeTitleKeys.some(Boolean)) { titleSumsTransfer[title] = transferCounter } - lastTitle = title - } else if (!['pagebreak', 'text'].includes(row.mode) && lastTitle !== "" && !row.optional && !row.alternative) { - titleSums[lastTitle] = Number(titleSums[lastTitle]) + Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) / 100)) - transferCounter += Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) / 100)) + activeTitleKeys = activeTitleKeys.slice(0, titleLevel - 1) + activeTitleKeys[titleLevel - 1] = title + activeTitleKeys = activeTitleKeys.slice(0, titleLevel) + } else if (!['pagebreak', 'text'].includes(row.mode) && activeTitleKeys.some(Boolean) && !row.optional && !row.alternative) { + const rowSum = Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) / 100)) + + activeTitleKeys.forEach(titleKey => { + titleSums[titleKey] = Number(titleSums[titleKey]) + rowSum + }) + transferCounter += rowSum console.log(transferCounter) } }) From 720c70845e78787d541a7ef4786c653d1a78d9b6 Mon Sep 17 00:00:00 2001 From: flfeders Date: Mon, 20 Jul 2026 10:17:00 +0200 Subject: [PATCH 3/4] =?UTF-8?q?Rechnungsbetr=C3=A4ge=20deutsch=20formatier?= =?UTF-8?q?en?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/modules/serialexecution.service.ts | 5 ++++- backend/src/utils/pdf.ts | 19 ++++++++++++++----- backend/src/utils/stringRendering.ts | 9 +++++++-- frontend/pages/createDocument/edit/[[id]].vue | 4 ++-- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/backend/src/modules/serialexecution.service.ts b/backend/src/modules/serialexecution.service.ts index 14e5287..e05e51e 100644 --- a/backend/src/modules/serialexecution.service.ts +++ b/backend/src/modules/serialexecution.service.ts @@ -447,7 +447,10 @@ async function getCloseData(server:FastifyInstance,item: any, tenant: any, units // Formatiert Zahlen zu deutscher Währung function renderCurrency(value: any, currency = "€") { if (value === undefined || value === null) return "0,00 " + currency; - return Number(value).toFixed(2).replace(".", ",") + " " + currency; + return Number(value).toLocaleString("de-DE", { + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }) + " " + currency; } // Berechnet den Zeilenpreis (Menge * Preis * Rabatt) diff --git a/backend/src/utils/pdf.ts b/backend/src/utils/pdf.ts index 76ef318..067bf27 100644 --- a/backend/src/utils/pdf.ts +++ b/backend/src/utils/pdf.ts @@ -25,6 +25,10 @@ const getCoordinatesForPDFLib = (x:number ,y:number, page:any) => { } } +const getRightAlignedX = (rightEdgeMm: number, text: string, size: number, font: any, page: any) => { + return getCoordinatesForPDFLib(rightEdgeMm, 0, page).x - font.widthOfTextAtSize(text, size) +} + const getBackgroundSourceBuffer = async (server:FastifyInstance, path:string) => { @@ -372,8 +376,10 @@ export const createInvoicePDF = async (server:FastifyInstance, returnMode, invoi font: fontBold }) - pages[pageCounter - 1].drawText("Einheitspreis", { - ...getCoordinatesForPDFLib(150, 137, page1), + const unitPriceHeader = "Einheitspreis" + pages[pageCounter - 1].drawText(unitPriceHeader, { + y: getCoordinatesForPDFLib(150, 137, page1).y, + x: getRightAlignedX(177, unitPriceHeader, 12, fontBold, page1), size: 12, color: rgb(0, 0, 0), lineHeight: 12, @@ -508,7 +514,8 @@ export const createInvoicePDF = async (server:FastifyInstance, returnMode, invoi maxWidth: 240 }) pages[pageCounter - 1].drawText(row.price, { - ...getCoordinatesForPDFLib(150, rowHeight, page1), + y: getCoordinatesForPDFLib(150, rowHeight, page1).y, + x: getRightAlignedX(177, row.price, 10, font, page1), size: 10, color: rgb(0, 0, 0), lineHeight: 10, @@ -689,8 +696,10 @@ export const createInvoicePDF = async (server:FastifyInstance, returnMode, invoi font: fontBold }) - page.drawText("Einheitspreis", { - ...getCoordinatesForPDFLib(150, 22, page1), + const unitPriceHeader = "Einheitspreis" + page.drawText(unitPriceHeader, { + y: getCoordinatesForPDFLib(150, 22, page1).y, + x: getRightAlignedX(177, unitPriceHeader, 12, fontBold, page1), size: 12, color: rgb(0, 0, 0), lineHeight: 12, diff --git a/backend/src/utils/stringRendering.ts b/backend/src/utils/stringRendering.ts index 72dbad1..68100c4 100644 --- a/backend/src/utils/stringRendering.ts +++ b/backend/src/utils/stringRendering.ts @@ -1,6 +1,11 @@ export const renderAsCurrency = (value: string | number,currencyString = "€") => { - return `${Number(value).toFixed(2).replace(".",",")} ${currencyString}` + const formattedValue = Number(value).toLocaleString("de-DE", { + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }) + + return `${formattedValue} ${currencyString}` } export const splitStringBySpace = (input:string,maxSplitLength:number,removeLinebreaks = false) => { @@ -48,4 +53,4 @@ export const splitStringBySpace = (input:string,maxSplitLength:number,removeLine }) return returnSplitStrings -} \ No newline at end of file +} diff --git a/frontend/pages/createDocument/edit/[[id]].vue b/frontend/pages/createDocument/edit/[[id]].vue index 769dd69..54aface 100644 --- a/frontend/pages/createDocument/edit/[[id]].vue +++ b/frontend/pages/createDocument/edit/[[id]].vue @@ -1409,11 +1409,11 @@ const getDocumentData = async () => { return { ...row, - rowAmount: `${getRowAmount(row)} €`, + rowAmount: renderCurrency(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) / 100)), quantity: String(row.quantity).replace(".", ","), unit: unit.short, pos: String(row.pos), - price: `${String(row.price.toFixed(2)).replace(".", ",")} €`, + price: renderCurrency(row.price), discountText: `(Rabatt: ${row.discountPercent} %)` } } else { From 37d08ee984006b1c1d60d0437ac5877a1459ee5c Mon Sep 17 00:00:00 2001 From: flfeders Date: Mon, 20 Jul 2026 19:06:01 +0200 Subject: [PATCH 4/4] Prevent duplicate tenant memberships on import --- backend/scripts/dedupe-auth-tenant-users.ts | 67 +++++++++++++++++++++ backend/src/routes/admin.ts | 12 +++- backend/src/routes/auth/me.ts | 4 +- backend/src/utils/tenantFullExport.ts | 40 ++++++++++++ 4 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 backend/scripts/dedupe-auth-tenant-users.ts diff --git a/backend/scripts/dedupe-auth-tenant-users.ts b/backend/scripts/dedupe-auth-tenant-users.ts new file mode 100644 index 0000000..9bcd1fc --- /dev/null +++ b/backend/scripts/dedupe-auth-tenant-users.ts @@ -0,0 +1,67 @@ +import "dotenv/config" +import { drizzle } from "drizzle-orm/node-postgres" +import { sql } from "drizzle-orm" +import { Pool } from "pg" + +import { loadSecrets, secrets } from "../src/utils/secrets" + +async function run() { + let connectionString = process.env.DATABASE_URL + + if (!connectionString) { + await loadSecrets() + connectionString = secrets.DATABASE_URL + } + + if (!connectionString) { + throw new Error("DATABASE_URL not configured") + } + + const pool = new Pool({ + connectionString, + max: 1, + connectionTimeoutMillis: 10000, + }) + + try { + const db = drizzle(pool) + + const duplicates = await db.execute(sql` + SELECT tenant_id, user_id, count(*)::int AS count + FROM auth_tenant_users + GROUP BY tenant_id, user_id + HAVING count(*) > 1 + ORDER BY count(*) DESC; + `) + + console.log(`Gefundene doppelte Tenant-Zuordnungen: ${duplicates.rows.length}`) + for (const row of duplicates.rows as Record[]) { + console.log(JSON.stringify(row)) + } + + const deleted = await db.execute(sql` + DELETE FROM auth_tenant_users duplicate + USING auth_tenant_users keep + WHERE duplicate.tenant_id = keep.tenant_id + AND duplicate.user_id = keep.user_id + AND duplicate.ctid > keep.ctid; + `) + + console.log(`Entfernte doppelte Zeilen: ${deleted.rowCount || 0}`) + + await db.execute(sql` + CREATE UNIQUE INDEX IF NOT EXISTS auth_tenant_users_tenant_user_unique + ON auth_tenant_users (tenant_id, user_id); + `) + + console.log("Unique Index auth_tenant_users_tenant_user_unique ist vorhanden") + } finally { + await pool.end() + } +} + +run().catch((err) => { + console.error("Dedupe auth_tenant_users failed") + console.error(err) + process.exit(1) +}) diff --git a/backend/src/routes/admin.ts b/backend/src/routes/admin.ts index 8bf99fd..0dd7698 100644 --- a/backend/src/routes/admin.ts +++ b/backend/src/routes/admin.ts @@ -660,10 +660,16 @@ export default async function adminRoutes(server: FastifyInstance) { }) .from(authUserRoles), ]); + const uniqueMembershipRows = Array.from( + new Map(membershipRows.map((membership) => [ + `${membership.tenant_id}:${membership.user_id}`, + membership, + ])).values() + ); const users = userRows.map((user) => { const profiles = profileRows.filter((profile) => profile.user_id === user.id); - const memberships = membershipRows.filter((membership) => membership.user_id === user.id); + const memberships = uniqueMembershipRows.filter((membership) => membership.user_id === user.id); const roleAssignments = roleAssignmentRows.filter((assignment) => assignment.user_id === user.id); const preferredProfile = profiles.find((profile) => profile.active) || profiles[0]; const fallbackName = deriveNameFromEmail(user.email); @@ -683,7 +689,7 @@ export default async function adminRoutes(server: FastifyInstance) { const tenantsWithCounts = tenantRows.map((tenant) => ({ ...tenant, - user_count: membershipRows.filter((membership) => membership.tenant_id === tenant.id).length, + user_count: uniqueMembershipRows.filter((membership) => membership.tenant_id === tenant.id).length, })); return { @@ -691,7 +697,7 @@ export default async function adminRoutes(server: FastifyInstance) { tenants: tenantsWithCounts, roles: roleRows, unassignedProfiles: profileRows.filter((profile) => !profile.user_id), - memberships: membershipRows, + memberships: uniqueMembershipRows, roleAssignments: roleAssignmentRows, }; } catch (err) { diff --git a/backend/src/routes/auth/me.ts b/backend/src/routes/auth/me.ts index 3982474..ad013c7 100644 --- a/backend/src/routes/auth/me.ts +++ b/backend/src/routes/auth/me.ts @@ -69,7 +69,9 @@ export default async function meRoutes(server: FastifyInstance) { .innerJoin(tenants, eq(authTenantUsers.tenant_id, tenants.id)) .where(eq(authTenantUsers.user_id, userId)) - const tenantList = tenantRows ?? [] + const tenantList = Array.from( + new Map((tenantRows ?? []).map((tenant) => [tenant.id, tenant])).values() + ) // ---------------------------------------------------- // 3) ACTIVE TENANT diff --git a/backend/src/utils/tenantFullExport.ts b/backend/src/utils/tenantFullExport.ts index f2e4579..61ea64b 100644 --- a/backend/src/utils/tenantFullExport.ts +++ b/backend/src/utils/tenantFullExport.ts @@ -772,6 +772,37 @@ const insertRows = async (client: any, table: string, rows: Record[ return inserted } +const insertAuthTenantUserRows = async (client: any, rows: Record[], metadata: TableMetadata) => { + if (!rows.length) return 0 + + let inserted = 0 + const availableColumns = new Set(metadata.columns.filter((column) => !metadata.generatedColumns.has(column))) + + for (const row of rows) { + if (!row.tenant_id || !row.user_id) continue + + const existing = await client.query( + `select 1 from ${quoteIdent("auth_tenant_users")} where ${quoteIdent("tenant_id")} = $1 and ${quoteIdent("user_id")} = $2 limit 1`, + [row.tenant_id, row.user_id] + ) + if (existing.rows.length) continue + + const rowColumns = Object.keys(row).filter((column) => availableColumns.has(column)) + if (!rowColumns.length) continue + + const placeholders = rowColumns.map((_, index) => `$${index + 1}`).join(", ") + const values = rowColumns.map((column) => prepareColumnValue(row[column], metadata.jsonColumns.has(column))) + + await client.query( + `insert into ${quoteIdent("auth_tenant_users")} (${rowColumns.map(quoteIdent).join(", ")}) values (${placeholders})`, + values + ) + inserted += 1 + } + + return inserted +} + const insertIdentityRowsWithRemap = async ( client: any, table: string, @@ -882,6 +913,7 @@ export const importTenantFullExport = async ( ...Object.keys(exportData.tables).filter((table) => !importOrder.includes(table)).sort(), ].filter((table, index, all) => all.indexOf(table) === index) const specialTables = [ + columnsByTable.has("auth_tenant_users") ? "auth_tenant_users" : null, columnsByTable.has("bankaccounts") ? "bankaccounts" : null, columnsByTable.has("bankstatements") ? "bankstatements" : null, columnsByTable.has("letterheads") ? "letterheads" : null, @@ -905,6 +937,14 @@ export const importTenantFullExport = async ( await reportProgress("Dateien vorbereitet") const importedTables: { table: string; rows: number }[] = [] + const authTenantUsersMetadata = columnsByTable.get("auth_tenant_users") + if (authTenantUsersMetadata) { + const count = await insertAuthTenantUserRows(client, exportData.tables.auth_tenant_users || [], authTenantUsersMetadata) + importedTables.push({ table: "auth_tenant_users", rows: count }) + progressDone += 1 + await reportProgress("Tenant-Zuordnungen importiert") + } + const bankaccountMetadata = columnsByTable.get("bankaccounts") if (bankaccountMetadata) { const result = await insertIdentityRowsWithRemap(client, "bankaccounts", exportData.tables.bankaccounts || [], bankaccountMetadata)