update
This commit is contained in:
@@ -363,48 +363,65 @@ export default function BestellungDetail() {
|
||||
|
||||
let curY = await addPdfHeader(doc, settings, 210);
|
||||
|
||||
// Document title below header
|
||||
// ── Document title ──
|
||||
doc.setFontSize(14);
|
||||
doc.setFont('helvetica', 'bold');
|
||||
doc.text(title, 10, curY);
|
||||
curY += 10;
|
||||
|
||||
// Metadata block
|
||||
doc.setFontSize(10);
|
||||
doc.setFont('helvetica', 'normal');
|
||||
const meta: [string, string][] = [
|
||||
['Bezeichnung', bestellung.bezeichnung],
|
||||
['Lieferant', bestellung.lieferant_name || '–'],
|
||||
['Besteller', bestellung.besteller_name || '–'],
|
||||
['Status', BESTELLUNG_STATUS_LABELS[bestellung.status]],
|
||||
['Bestelldatum', bestellung.bestellt_am ? formatDate(bestellung.bestellt_am) : '–'],
|
||||
['Erstellt am', formatDate(bestellung.erstellt_am)],
|
||||
];
|
||||
for (const [label, value] of meta) {
|
||||
// ── Helper: two-column label/value row ──
|
||||
const row = (label: string, value: string, col2X = 70) => {
|
||||
doc.setFontSize(9);
|
||||
doc.setFont('helvetica', 'bold');
|
||||
doc.text(`${label}:`, 10, curY);
|
||||
doc.text(label + ':', 10, curY);
|
||||
doc.setFont('helvetica', 'normal');
|
||||
doc.text(value, 45, curY);
|
||||
doc.text(value, col2X, curY);
|
||||
curY += 5;
|
||||
};
|
||||
|
||||
// ── Vendor block ──
|
||||
if (bestellung.lieferant_name) {
|
||||
doc.setFontSize(10);
|
||||
doc.setFont('helvetica', 'bold');
|
||||
doc.text('Lieferant', 10, curY);
|
||||
curY += 5;
|
||||
row('Name', bestellung.lieferant_name);
|
||||
if (bestellung.lieferant_kontakt_name) row('Ansprechpartner', bestellung.lieferant_kontakt_name);
|
||||
if (bestellung.lieferant_adresse) row('Adresse', bestellung.lieferant_adresse);
|
||||
if (bestellung.lieferant_email) row('E-Mail', bestellung.lieferant_email);
|
||||
if (bestellung.lieferant_telefon) row('Telefon', bestellung.lieferant_telefon);
|
||||
curY += 3;
|
||||
}
|
||||
curY += 4;
|
||||
|
||||
// Line items table
|
||||
// ── Besteller block ──
|
||||
if (bestellung.besteller_name) {
|
||||
doc.setFontSize(10);
|
||||
doc.setFont('helvetica', 'bold');
|
||||
doc.text('Besteller', 10, curY);
|
||||
curY += 5;
|
||||
const nameWithRank = bestellung.besteller_dienstgrad
|
||||
? `${bestellung.besteller_dienstgrad} ${bestellung.besteller_name}`
|
||||
: bestellung.besteller_name;
|
||||
row('Name', nameWithRank);
|
||||
if (bestellung.besteller_email) row('E-Mail', bestellung.besteller_email);
|
||||
curY += 3;
|
||||
}
|
||||
|
||||
// ── Order info block ──
|
||||
doc.setFontSize(10);
|
||||
doc.setFont('helvetica', 'bold');
|
||||
doc.text('Bestellinformationen', 10, curY);
|
||||
curY += 5;
|
||||
row('Bezeichnung', bestellung.bezeichnung);
|
||||
row('Status', BESTELLUNG_STATUS_LABELS[bestellung.status]);
|
||||
row('Erstellt am', formatDate(bestellung.erstellt_am));
|
||||
if (bestellung.bestellt_am) row('Bestelldatum', formatDate(bestellung.bestellt_am));
|
||||
curY += 5;
|
||||
|
||||
// ── Line items table ──
|
||||
const steuersatz = (parseFloat(String(bestellung.steuersatz)) || 20) / 100;
|
||||
const rows = positionen.map((p) => {
|
||||
const ep = p.einzelpreis != null ? parseFloat(String(p.einzelpreis)) : undefined;
|
||||
const menge = parseFloat(String(p.menge)) || 0;
|
||||
const gesamt = ep != null ? ep * menge : undefined;
|
||||
return [
|
||||
p.bezeichnung,
|
||||
p.artikelnummer || '',
|
||||
`${menge} ${p.einheit}`,
|
||||
ep != null ? formatCurrency(ep) : '–',
|
||||
gesamt != null ? formatCurrency(gesamt) : '–',
|
||||
];
|
||||
});
|
||||
const hasPrices = positionen.some((p) => p.einzelpreis != null);
|
||||
|
||||
// Total
|
||||
const totalNetto = positionen.reduce((sum, p) => {
|
||||
const ep = p.einzelpreis != null ? parseFloat(String(p.einzelpreis)) : 0;
|
||||
const m = parseFloat(String(p.menge)) || 0;
|
||||
@@ -412,28 +429,95 @@ export default function BestellungDetail() {
|
||||
}, 0);
|
||||
const totalBrutto = totalNetto * (1 + steuersatz);
|
||||
|
||||
autoTable(doc, {
|
||||
head: [['Bezeichnung', 'Art.-Nr.', 'Menge', 'Einzelpreis', 'Gesamt']],
|
||||
body: rows,
|
||||
startY: curY,
|
||||
headStyles: { fillColor: [66, 66, 66], textColor: 255, fontStyle: 'bold' },
|
||||
alternateRowStyles: { fillColor: [245, 245, 245] },
|
||||
margin: { left: 10, right: 10 },
|
||||
styles: { fontSize: 9, cellPadding: 2 },
|
||||
columnStyles: {
|
||||
0: { cellWidth: 60 },
|
||||
1: { cellWidth: 30 },
|
||||
2: { cellWidth: 25, halign: 'right' },
|
||||
3: { cellWidth: 30, halign: 'right' },
|
||||
4: { cellWidth: 30, halign: 'right' },
|
||||
},
|
||||
foot: [
|
||||
['', '', '', 'Netto:', formatCurrency(totalNetto)],
|
||||
['', '', '', 'Brutto:', formatCurrency(totalBrutto)],
|
||||
],
|
||||
footStyles: { fillColor: [255, 255, 255], textColor: [0, 0, 0], fontStyle: 'bold' },
|
||||
didDrawPage: addPdfFooter(doc, settings),
|
||||
});
|
||||
if (hasPrices) {
|
||||
const rows = positionen.map((p) => {
|
||||
const ep = p.einzelpreis != null ? parseFloat(String(p.einzelpreis)) : undefined;
|
||||
const menge = parseFloat(String(p.menge)) || 0;
|
||||
const gesamt = ep != null ? ep * menge : undefined;
|
||||
return [
|
||||
p.bezeichnung,
|
||||
p.artikelnummer || '',
|
||||
`${menge} ${p.einheit}`,
|
||||
ep != null ? formatCurrency(ep) : '–',
|
||||
gesamt != null ? formatCurrency(gesamt) : '–',
|
||||
];
|
||||
});
|
||||
|
||||
autoTable(doc, {
|
||||
head: [['Bezeichnung', 'Art.-Nr.', 'Menge', 'Einzelpreis', 'Gesamt']],
|
||||
body: rows,
|
||||
startY: curY,
|
||||
theme: 'grid',
|
||||
headStyles: { fillColor: [66, 66, 66], textColor: 255, fontStyle: 'bold' },
|
||||
margin: { left: 10, right: 10 },
|
||||
styles: { fontSize: 9, cellPadding: 2 },
|
||||
columnStyles: {
|
||||
0: { cellWidth: 60 },
|
||||
1: { cellWidth: 28 },
|
||||
2: { cellWidth: 25, halign: 'right' },
|
||||
3: { cellWidth: 30, halign: 'right' },
|
||||
4: { cellWidth: 30, halign: 'right' },
|
||||
},
|
||||
foot: [
|
||||
['', '', '', 'Netto:', formatCurrency(totalNetto)],
|
||||
['', '', '', `Brutto (${bestellung.steuersatz ?? 20}% USt.):`, formatCurrency(totalBrutto)],
|
||||
],
|
||||
footStyles: { fillColor: [255, 255, 255], textColor: [0, 0, 0], fontStyle: 'bold' },
|
||||
didDrawPage: addPdfFooter(doc, settings),
|
||||
});
|
||||
} else {
|
||||
const rows = positionen.map((p) => {
|
||||
const menge = parseFloat(String(p.menge)) || 0;
|
||||
return [p.bezeichnung, p.artikelnummer || '', `${menge} ${p.einheit}`];
|
||||
});
|
||||
|
||||
autoTable(doc, {
|
||||
head: [['Bezeichnung', 'Art.-Nr.', 'Menge']],
|
||||
body: rows,
|
||||
startY: curY,
|
||||
theme: 'grid',
|
||||
headStyles: { fillColor: [66, 66, 66], textColor: 255, fontStyle: 'bold' },
|
||||
margin: { left: 10, right: 10 },
|
||||
styles: { fontSize: 9, cellPadding: 2 },
|
||||
columnStyles: {
|
||||
0: { cellWidth: 100 },
|
||||
1: { cellWidth: 40 },
|
||||
2: { cellWidth: 33, halign: 'right' },
|
||||
},
|
||||
didDrawPage: addPdfFooter(doc, settings),
|
||||
});
|
||||
}
|
||||
|
||||
// ── Signature section ──
|
||||
const signY = (doc as any).lastAutoTable?.finalY ?? curY;
|
||||
const sigStartY = signY + 15;
|
||||
|
||||
// Ensure there's enough space; add a page if needed
|
||||
if (sigStartY + 25 > doc.internal.pageSize.height - 20) {
|
||||
doc.addPage();
|
||||
curY = 20;
|
||||
} else {
|
||||
curY = sigStartY;
|
||||
}
|
||||
|
||||
doc.setFontSize(9);
|
||||
doc.setFont('helvetica', 'normal');
|
||||
doc.setTextColor(0, 0, 0);
|
||||
|
||||
// Signature line
|
||||
doc.setDrawColor(0, 0, 0);
|
||||
doc.setLineWidth(0.4);
|
||||
doc.line(10, curY, 90, curY);
|
||||
|
||||
// Date line (right side)
|
||||
doc.line(120, curY, 200, curY);
|
||||
|
||||
curY += 4;
|
||||
const sigName = bestellung.besteller_dienstgrad
|
||||
? `${bestellung.besteller_dienstgrad} ${bestellung.besteller_name || ''}`
|
||||
: (bestellung.besteller_name || '');
|
||||
doc.text(sigName, 10, curY);
|
||||
doc.text('Ort, Datum', 120, curY);
|
||||
|
||||
doc.save(`bestellung_${kennung.replace('/', '-')}.pdf`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user