Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
@@ -399,6 +405,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) => {
|
||||
|
||||
@@ -502,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,
|
||||
@@ -579,18 +592,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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -681,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,
|
||||
@@ -713,17 +730,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 +754,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 +769,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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -772,6 +772,37 @@ const insertRows = async (client: any, table: string, rows: Record<string, any>[
|
||||
return inserted
|
||||
}
|
||||
|
||||
const insertAuthTenantUserRows = async (client: any, rows: Record<string, any>[], 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)
|
||||
|
||||
Reference in New Issue
Block a user