u
This commit is contained in:
@@ -52,11 +52,11 @@ export function formatQuantity(val) {
|
||||
const TYPE_CONFIG = {
|
||||
quote: {
|
||||
docTitle: 'ANGEBOT',
|
||||
numberPrefix: 'Angebots-Nr.',
|
||||
numberPrefix: 'Angebot-Nr.',
|
||||
showPrices: true,
|
||||
filenamePrefix: 'angebot',
|
||||
introText: 'Wir freuen uns über Ihr Interesse und bieten Ihnen freibleibend wie folgt an:',
|
||||
closingText: 'Dieses Angebot ist freibleibend und 30 Tage ab Ausstellungsdatum gültig. Wir freuen uns auf Ihren Auftrag!',
|
||||
closingText: '',
|
||||
defaultSubject: 'Angebot',
|
||||
},
|
||||
order: {
|
||||
@@ -114,7 +114,12 @@ export function generateManifestPdf({
|
||||
try {
|
||||
const doc = new PDFDocument({
|
||||
size: 'A4',
|
||||
margin: 45,
|
||||
margins: {
|
||||
top: 45,
|
||||
bottom: 25,
|
||||
left: 45,
|
||||
right: 45,
|
||||
},
|
||||
bufferPages: true,
|
||||
info: {
|
||||
Title: `${config.docTitle} ${manifest.number || manifest.id}`,
|
||||
@@ -195,10 +200,16 @@ export function generateManifestPdf({
|
||||
}
|
||||
|
||||
// Document Number under Document Title
|
||||
doc.font('Helvetica-Bold')
|
||||
.fontSize(11)
|
||||
const headerNumStr = `${config.numberPrefix} ${manifest.number || manifest.id}`;
|
||||
let headerNumFontSize = 11;
|
||||
const headerNumWidth = 250;
|
||||
doc.font('Helvetica-Bold');
|
||||
while (headerNumFontSize > 8 && doc.fontSize(headerNumFontSize).widthOfString(headerNumStr) > headerNumWidth) {
|
||||
headerNumFontSize -= 0.5;
|
||||
}
|
||||
doc.fontSize(headerNumFontSize)
|
||||
.fillColor(primaryColor)
|
||||
.text(`${config.numberPrefix} ${manifest.number || manifest.id}`, 350, y, { align: 'right', width: 200 });
|
||||
.text(headerNumStr, 300, y, { align: 'right', width: headerNumWidth, lineBreak: false });
|
||||
|
||||
y += 18;
|
||||
|
||||
@@ -261,12 +272,6 @@ export function generateManifestPdf({
|
||||
doc.text(chosenAddress.county, 45, y);
|
||||
y += 13;
|
||||
}
|
||||
} else {
|
||||
doc.font('Helvetica')
|
||||
.fontSize(9)
|
||||
.fillColor(mutedColor)
|
||||
.text(`Keine ${partnerLabel} hinterlegt`, 45, y);
|
||||
y += 14;
|
||||
}
|
||||
|
||||
// Right side Meta Box
|
||||
@@ -287,16 +292,24 @@ export function generateManifestPdf({
|
||||
doc.font('Helvetica')
|
||||
.fontSize(8.5)
|
||||
.fillColor(mutedColor)
|
||||
.text(label, metaBoxX + 10, metaY, { width: 95 });
|
||||
doc.font('Helvetica-Bold')
|
||||
.fontSize(8.5)
|
||||
.text(label, metaBoxX + 10, metaY, { width: 72, lineBreak: false });
|
||||
|
||||
const valText = val || '–';
|
||||
doc.font('Helvetica-Bold');
|
||||
let valFontSize = 8.5;
|
||||
const maxValWidth = 128;
|
||||
while (valFontSize > 6.5 && doc.fontSize(valFontSize).widthOfString(valText) > maxValWidth) {
|
||||
valFontSize -= 0.5;
|
||||
}
|
||||
doc.fontSize(valFontSize)
|
||||
.fillColor(textColor)
|
||||
.text(val || '–', metaBoxX + 105, metaY, { width: 105, align: 'right' });
|
||||
.text(valText, metaBoxX + 82, metaY, { width: maxValWidth, align: 'right', lineBreak: false });
|
||||
metaY += 15;
|
||||
};
|
||||
|
||||
drawMetaRow(`${config.docTitle.slice(0, 1) + config.docTitle.slice(1).toLowerCase()}-Nr.:`, String(manifest.number || manifest.id));
|
||||
const dateLabel = type === 'shipment' ? 'Versanddatum:' : (type === 'delivery' ? 'Eingangsdatum:' : 'Belegdatum:');
|
||||
const numLabel = config.numberPrefix ? (config.numberPrefix.endsWith(':') ? config.numberPrefix : `${config.numberPrefix}:`) : 'Beleg-Nr.:';
|
||||
drawMetaRow(numLabel, String(manifest.number || manifest.id));
|
||||
const dateLabel = type === 'shipment' ? 'Versanddatum:' : (type === 'delivery' ? 'Eingangsdatum:' : 'Datum:');
|
||||
drawMetaRow(dateLabel, formatDate(manifest.date || manifest.createdAt));
|
||||
|
||||
if (manifest.externalNumber) {
|
||||
@@ -344,11 +357,11 @@ export function generateManifestPdf({
|
||||
const showPrices = config.showPrices;
|
||||
|
||||
// Column setups
|
||||
const colXPrices = { pos: 45, sku: 75, name: 165, amount: 345, net: 405, tax: 465, total: 495 };
|
||||
const colWidthPrices = { pos: 25, sku: 85, name: 175, amount: 55, net: 55, tax: 25, total: 55 };
|
||||
const colXPrices = { pos: 45, sku: 70, name: 150, amount: 335, gross: 385, tax: 450, total: 482 };
|
||||
const colWidthPrices = { pos: 22, sku: 75, name: 180, amount: 45, gross: 60, tax: 30, total: 65 };
|
||||
|
||||
const colXNoPrices = { pos: 45, sku: 80, name: 185, amount: 455 };
|
||||
const colWidthNoPrices = { pos: 30, sku: 100, name: 265, amount: 95 };
|
||||
const colWidthNoPrices = { pos: 30, sku: 100, name: 265, amount: 92 };
|
||||
|
||||
const drawTableHeader = (atY) => {
|
||||
doc.rect(45, atY, 505, 20)
|
||||
@@ -364,9 +377,9 @@ export function generateManifestPdf({
|
||||
doc.text('Art.-Nr.', colXPrices.sku, atY + 6, { width: colWidthPrices.sku });
|
||||
doc.text('Bezeichnung', colXPrices.name, atY + 6, { width: colWidthPrices.name });
|
||||
doc.text('Menge', colXPrices.amount, atY + 6, { width: colWidthPrices.amount, align: 'right' });
|
||||
doc.text('E-Preis Netto', colXPrices.net, atY + 6, { width: colWidthPrices.net, align: 'right' });
|
||||
doc.text('E-Preis Brutto', colXPrices.gross, atY + 6, { width: colWidthPrices.gross, align: 'right' });
|
||||
doc.text('MwSt', colXPrices.tax, atY + 6, { width: colWidthPrices.tax, align: 'right' });
|
||||
doc.text('Gesamt Netto', colXPrices.total, atY + 6, { width: colWidthPrices.total, align: 'right' });
|
||||
doc.text('Gesamt Brutto', colXPrices.total, atY + 6, { width: colWidthPrices.total, align: 'right' });
|
||||
} else {
|
||||
doc.text('Pos', colXNoPrices.pos + 3, atY + 6, { width: colWidthNoPrices.pos });
|
||||
doc.text('Art.-Nr.', colXNoPrices.sku, atY + 6, { width: colWidthNoPrices.sku });
|
||||
@@ -389,15 +402,21 @@ export function generateManifestPdf({
|
||||
const net = Number(line.net || 0);
|
||||
const rebate = Number(line.rebate || 0);
|
||||
const taxrate = Number(line.taxrate || 0);
|
||||
const taxFactor = 1 + taxrate / 100;
|
||||
|
||||
totalQty += amount;
|
||||
|
||||
let lineNet = 0;
|
||||
let lineGross = 0;
|
||||
let unitGross = net * taxFactor;
|
||||
let unitNetAfterRebate = net;
|
||||
let unitGrossAfterRebate = unitGross;
|
||||
if (showPrices) {
|
||||
unitNetAfterRebate = net * (1 - rebate / 100);
|
||||
unitGrossAfterRebate = unitNetAfterRebate * taxFactor;
|
||||
lineNet = amount * unitNetAfterRebate;
|
||||
const lineTax = lineNet * (taxrate / 100);
|
||||
lineGross = lineNet + lineTax;
|
||||
|
||||
totalNet += lineNet;
|
||||
|
||||
@@ -407,13 +426,31 @@ export function generateManifestPdf({
|
||||
taxBreakdown.set(taxrate, currTax);
|
||||
}
|
||||
|
||||
// Row height estimation
|
||||
const hasComment = Boolean(line.comment);
|
||||
const nameWidth = showPrices ? colWidthPrices.name : colWidthNoPrices.name;
|
||||
const puSuffix = line.pu && line.pu > 1 ? ` (VPE ${line.pu})` : '';
|
||||
const nameText = (line.name || 'Position') + puSuffix;
|
||||
|
||||
// Measure actual rendered text heights to prevent overlaps
|
||||
doc.font('Helvetica').fontSize(8.5);
|
||||
const nameHeight = doc.heightOfString(nameText, { width: nameWidth });
|
||||
|
||||
const hasRebate = showPrices && rebate > 0;
|
||||
let extraLines = 0;
|
||||
if (hasComment) extraLines += 1;
|
||||
if (hasRebate) extraLines += 1;
|
||||
const rowHeight = 18 + extraLines * 11;
|
||||
const rebateText = hasRebate ? `Rabatt: ${rebate}% (effektiver E-Preis: ${formatCurrency(unitGrossAfterRebate)})` : '';
|
||||
let rebateHeight = 0;
|
||||
if (hasRebate) {
|
||||
doc.font('Helvetica').fontSize(7.5);
|
||||
rebateHeight = doc.heightOfString(rebateText, { width: nameWidth }) + 2;
|
||||
}
|
||||
|
||||
const hasComment = Boolean(line.comment);
|
||||
let commentHeight = 0;
|
||||
if (hasComment) {
|
||||
doc.font('Helvetica-Oblique').fontSize(7.5);
|
||||
commentHeight = doc.heightOfString(line.comment, { width: nameWidth }) + 2;
|
||||
}
|
||||
|
||||
const contentHeight = nameHeight + rebateHeight + commentHeight;
|
||||
const rowHeight = Math.max(18, Math.ceil(contentHeight + 6));
|
||||
|
||||
// Check page overflow
|
||||
if (y + rowHeight > 730) {
|
||||
@@ -428,72 +465,70 @@ export function generateManifestPdf({
|
||||
.fill();
|
||||
}
|
||||
|
||||
const textY = y + 1;
|
||||
|
||||
if (showPrices) {
|
||||
doc.font('Helvetica')
|
||||
.fontSize(8.5)
|
||||
.fillColor(textColor);
|
||||
|
||||
const puSuffix = line.pu && line.pu > 1 ? ` (VPE ${line.pu})` : '';
|
||||
|
||||
if (showPrices) {
|
||||
// Pos
|
||||
doc.text(String(index + 1), colXPrices.pos + 3, y, { width: colWidthPrices.pos });
|
||||
doc.text(String(index + 1), colXPrices.pos + 3, textY, { width: colWidthPrices.pos });
|
||||
// SKU
|
||||
doc.text(line.sku || '–', colXPrices.sku, y, { width: colWidthPrices.sku, ellipsis: true });
|
||||
// Name
|
||||
doc.text((line.name || 'Position') + puSuffix, colXPrices.name, y, {
|
||||
width: colWidthPrices.name,
|
||||
ellipsis: true,
|
||||
});
|
||||
doc.text(line.sku || '–', colXPrices.sku, textY, { width: colWidthPrices.sku, ellipsis: true });
|
||||
// Name (wraps naturally without clipping)
|
||||
doc.text(nameText, colXPrices.name, textY, { width: nameWidth });
|
||||
// Amount
|
||||
doc.text(formatQuantity(amount), colXPrices.amount, y, {
|
||||
doc.text(formatQuantity(amount), colXPrices.amount, textY, {
|
||||
width: colWidthPrices.amount,
|
||||
align: 'right',
|
||||
});
|
||||
// Unit Price Net
|
||||
doc.text(formatCurrency(net), colXPrices.net, y, {
|
||||
width: colWidthPrices.net,
|
||||
// Unit Price Gross
|
||||
doc.text(formatCurrency(unitGross), colXPrices.gross, textY, {
|
||||
width: colWidthPrices.gross,
|
||||
align: 'right',
|
||||
});
|
||||
// Tax Rate
|
||||
doc.text(`${taxrate}%`, colXPrices.tax, y, {
|
||||
doc.text(`${taxrate}%`, colXPrices.tax, textY, {
|
||||
width: colWidthPrices.tax,
|
||||
align: 'right',
|
||||
});
|
||||
// Line Total Net
|
||||
// Line Total Gross
|
||||
doc.font('Helvetica-Bold')
|
||||
.text(formatCurrency(lineNet), colXPrices.total, y, {
|
||||
.text(formatCurrency(lineGross), colXPrices.total, textY, {
|
||||
width: colWidthPrices.total,
|
||||
align: 'right',
|
||||
});
|
||||
|
||||
let subY = y + 12;
|
||||
let subY = textY + nameHeight + 2;
|
||||
if (hasRebate) {
|
||||
doc.font('Helvetica')
|
||||
.fontSize(7.5)
|
||||
.fillColor(secondaryColor)
|
||||
.text(`Rabatt: ${rebate}% (effektiver E-Preis: ${formatCurrency(unitNetAfterRebate)})`, colXPrices.name, subY, {
|
||||
width: colWidthPrices.name,
|
||||
.text(rebateText, colXPrices.name, subY, {
|
||||
width: nameWidth,
|
||||
});
|
||||
subY += 10;
|
||||
subY += rebateHeight;
|
||||
}
|
||||
if (hasComment) {
|
||||
doc.font('Helvetica-Oblique')
|
||||
.fontSize(7.5)
|
||||
.fillColor(mutedColor)
|
||||
.text(line.comment, colXPrices.name, subY, {
|
||||
width: colWidthPrices.name,
|
||||
width: nameWidth,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
doc.font('Helvetica')
|
||||
.fontSize(8.5)
|
||||
.fillColor(textColor);
|
||||
|
||||
// Without Prices
|
||||
doc.text(String(index + 1), colXNoPrices.pos + 3, y, { width: colWidthNoPrices.pos });
|
||||
doc.text(line.sku || '–', colXNoPrices.sku, y, { width: colWidthNoPrices.sku, ellipsis: true });
|
||||
doc.text((line.name || 'Position') + puSuffix, colXNoPrices.name, y, {
|
||||
width: colWidthNoPrices.name,
|
||||
ellipsis: true,
|
||||
});
|
||||
doc.text(String(index + 1), colXNoPrices.pos + 3, textY, { width: colWidthNoPrices.pos });
|
||||
doc.text(line.sku || '–', colXNoPrices.sku, textY, { width: colWidthNoPrices.sku, ellipsis: true });
|
||||
doc.text(nameText, colXNoPrices.name, textY, { width: nameWidth });
|
||||
doc.font('Helvetica-Bold')
|
||||
.text(formatQuantity(amount) + ' Stk', colXNoPrices.amount, y, {
|
||||
.text(formatQuantity(amount) + ' Stk', colXNoPrices.amount, textY, {
|
||||
width: colWidthNoPrices.amount,
|
||||
align: 'right',
|
||||
});
|
||||
@@ -502,8 +537,8 @@ export function generateManifestPdf({
|
||||
doc.font('Helvetica-Oblique')
|
||||
.fontSize(7.5)
|
||||
.fillColor(mutedColor)
|
||||
.text(line.comment, colXNoPrices.name, y + 12, {
|
||||
width: colWidthNoPrices.name,
|
||||
.text(line.comment, colXNoPrices.name, textY + nameHeight + 2, {
|
||||
width: nameWidth,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -657,6 +692,7 @@ export function generateManifestPdf({
|
||||
const range = doc.bufferedPageRange();
|
||||
for (let i = 0; i < range.count; i++) {
|
||||
doc.switchToPage(i);
|
||||
doc.page.margins.bottom = 0;
|
||||
|
||||
// Footer rule
|
||||
doc.moveTo(45, 785)
|
||||
|
||||
Reference in New Issue
Block a user