From 1778815a1e7224d06c7861886c3322f6c3cd14e2 Mon Sep 17 00:00:00 2001 From: seb Date: Sat, 17 Oct 2026 22:03:24 +0200 Subject: [PATCH] u --- src/services/manifestPdf.js | 259 +++++++++++++++++++----------------- 1 file changed, 134 insertions(+), 125 deletions(-) diff --git a/src/services/manifestPdf.js b/src/services/manifestPdf.js index df174ea..b4efcf7 100644 --- a/src/services/manifestPdf.js +++ b/src/services/manifestPdf.js @@ -413,16 +413,16 @@ export function generateManifestPdf({ // Delivery columns (Wareneingang) const colDeliv = { - check: 53, checkW: 18, - pos: 71, posW: 18, - thumb: 89, thumbW: 30, - suppSku: 119, suppSkuW: 52, - ownSku: 171, ownSkuW: 92, - ean: 263, eanW: 78, - name: 341, nameW: 85, - deliv: 426, delivW: 38, - stock: 464, stockW: 38, - added: 502, addedW: 40, + pos: 45, posW: 20, + thumb: 67, thumbW: 30, + suppSku: 99, suppSkuW: 52, + ownSku: 153, ownSkuW: 92, + ean: 247, eanW: 78, + name: 327, nameW: 81, + check: 410, checkW: 18, + deliv: 428, delivW: 40, + added: 468, addedW: 42, + stock: 510, stockW: 38, }; const drawTableHeader = (atY) => { @@ -435,7 +435,6 @@ export function generateManifestPdf({ .fillColor('#ffffff'); if (isDelivery) { - doc.text('Pos', colDeliv.pos, atY + 6, { width: colDeliv.posW, align: 'center' }); doc.text('Bild', colDeliv.thumb, atY + 6, { width: colDeliv.thumbW, align: 'center' }); doc.text('Lief.-SKU', colDeliv.suppSku, atY + 6, { width: colDeliv.suppSkuW }); @@ -443,8 +442,8 @@ export function generateManifestPdf({ doc.text('EAN', colDeliv.ean, atY + 6, { width: colDeliv.eanW }); doc.text('Bezeichnung', colDeliv.name, atY + 6, { width: colDeliv.nameW }); doc.text('Geliefert', colDeliv.deliv, atY + 6, { width: colDeliv.delivW, align: 'right' }); - doc.text('Bestand', colDeliv.stock, atY + 6, { width: colDeliv.stockW, align: 'right' }); doc.text('Gebucht', colDeliv.added, atY + 6, { width: colDeliv.addedW, align: 'right' }); + doc.text('Bestand', colDeliv.stock, atY + 6, { width: colDeliv.stockW, align: 'right' }); } else if (showPrices) { doc.fontSize(8); doc.text('Pos', colXPrices.pos + 3, atY + 6, { width: colWidthPrices.pos }); @@ -503,7 +502,118 @@ export function generateManifestPdf({ const nameWidth = showPrices ? colWidthPrices.name : colWidthNoPrices.name; const puSuffix = line.pu && line.pu > 1 ? ` (VPE ${line.pu})` : ''; - const nameText = (line.name || 'Position') + puSuffix; + const ownName = (line.ownArticleName || line.articleName || '').trim(); + const baseName = (isDelivery && line.ownSku && ownName) + ? ownName + : (line.name || 'Position'); + const nameText = baseName + puSuffix; + + if (isDelivery) { + const rowHeight = 38; + if (y + rowHeight > 730) { + doc.addPage(); + y = drawTableHeader(45); + } + + if (index % 2 === 1) { + doc.rect(45, y - 2, 505, rowHeight) + .fillColor(lightBg) + .fill(); + } + + const centerY = y + 12; + + // 1. Pos + doc.font('Helvetica') + .fontSize(8.5) + .fillColor(textColor) + .text(String(index + 1), colDeliv.pos, centerY, { width: colDeliv.posW, align: 'center' }); + + // 2. Thumbpic + if (line.thumbPng) { + try { + doc.image(line.thumbPng, colDeliv.thumb + 2, y + 4, { + fit: [26, 26], + align: 'center', + valign: 'center', + }); + } catch (_) { + doc.roundedRect(colDeliv.thumb + 2, y + 4, 26, 26, 2) + .fillColor('#f1f5f9').fill() + .strokeColor('#cbd5e1').lineWidth(0.5).stroke(); + } + } else { + doc.roundedRect(colDeliv.thumb + 2, y + 4, 26, 26, 2) + .fillColor('#f1f5f9').fill() + .strokeColor('#cbd5e1').lineWidth(0.5).stroke(); + doc.font('Helvetica').fontSize(6).fillColor(mutedColor).text('Kein Bild', colDeliv.thumb + 2, y + 14, { width: 26, align: 'center' }); + } + + // 3. Supplier SKU + doc.font('Helvetica').fontSize(7.5).fillColor(textColor) + .text(line.supplierSku || '–', colDeliv.suppSku, y + 12, { width: colDeliv.suppSkuW, ellipsis: true }); + + // 4. Eigene SKU as text + Code128 Barcode + const ownSkuText = line.ownSku || '–'; + doc.font('Helvetica-Bold').fontSize(7).fillColor(primaryColor) + .text(ownSkuText, colDeliv.ownSku, y + 2, { width: colDeliv.ownSkuW, ellipsis: true }); + if (line.ownSkuBarcode) { + try { + doc.image(line.ownSkuBarcode, colDeliv.ownSku, y + 12, { width: 88, height: 20 }); + } catch (_) { } + } + + // 5. EAN as barcode + if (line.ean) { + doc.font('Helvetica').fontSize(6.5).fillColor(mutedColor) + .text(line.ean, colDeliv.ean, y + 2, { width: colDeliv.eanW, ellipsis: true }); + if (line.eanBarcode) { + try { + doc.image(line.eanBarcode, colDeliv.ean, y + 12, { width: 76, height: 20 }); + } catch (_) { } + } + } else { + doc.font('Helvetica').fontSize(8).fillColor(mutedColor) + .text('–', colDeliv.ean, y + 12, { width: colDeliv.eanW, align: 'center' }); + } + + // 6. Name (own article name if own SKU is connected) + doc.font('Helvetica').fontSize(7.5).fillColor(textColor) + .text(nameText, colDeliv.name, y + 6, { width: colDeliv.nameW }); + + // 7. Checkbox outline (to cross with pencil) - left of geliefert + doc.rect(colDeliv.check + 3, centerY - 1, 12, 12) + .strokeColor('#64748b') + .lineWidth(0.8) + .stroke(); + + // 8. Menge geliefert + doc.font('Helvetica-Bold').fontSize(8).fillColor(textColor) + .text(formatQuantity(amount) + ' Stk', colDeliv.deliv, y + 12, { width: colDeliv.delivW, align: 'right' }); + + // 9. Menge added / gebucht (swapped before bestand) + const hasBooked = line.trackStock && line.addedQuantity != null && line.addedQuantity > 0; + const bookedText = line.trackStock + ? (line.addedQuantity != null && line.addedQuantity > 0 ? `+${formatQuantity(line.addedQuantity)} Stk` : (line.addedQuantity === 0 ? '0 Stk' : '–')) + : '–'; + doc.font('Helvetica-Bold').fontSize(8).fillColor(hasBooked ? '#16a34a' : mutedColor) + .text(bookedText, colDeliv.added, y + 12, { width: colDeliv.addedW, align: 'right' }); + + // 10. Menge already in warehouse (bestand) + const stockText = line.trackStock ? (formatQuantity(line.currentStock ?? 0) + ' Stk') : '–'; + doc.font('Helvetica').fontSize(7.5).fillColor(line.trackStock ? textColor : mutedColor) + .text(stockText, colDeliv.stock, y + 12, { width: colDeliv.stockW, align: 'right' }); + + // Row separator + doc.moveTo(45, y + rowHeight - 2) + .lineTo(550, y + rowHeight - 2) + .strokeColor(lightBorder) + .lineWidth(0.5) + .stroke(); + + y += rowHeight; + return; + } // Measure actual rendered text heights to prevent overlaps doc.font('Helvetica').fontSize(8.5); @@ -542,113 +652,6 @@ export function generateManifestPdf({ const textY = y + 1; - if (isDelivery) { - const rowHeight = 38; - if (y + rowHeight > 730) { - doc.addPage(); - y = drawTableHeader(45); - } - - if (index % 2 === 1) { - doc.rect(45, y - 2, 505, rowHeight) - .fillColor(lightBg) - .fill(); - } - - const centerY = y + 12; - - // 1. Checkbox outline (to cross with pencil) - doc.rect(colDeliv.check + 3, centerY - 1, 12, 12) - .strokeColor('#64748b') - .lineWidth(0.8) - .stroke(); - - // 2. Pos - doc.font('Helvetica') - .fontSize(8.5) - .fillColor(textColor) - .text(String(index + 1), colDeliv.pos, centerY, { width: colDeliv.posW, align: 'center' }); - - // 3. Thumbpic - if (line.thumbPng) { - try { - doc.image(line.thumbPng, colDeliv.thumb + 2, y + 4, { - fit: [26, 26], - align: 'center', - valign: 'center', - }); - } catch (_) { - doc.roundedRect(colDeliv.thumb + 2, y + 4, 26, 26, 2) - .fillColor('#f1f5f9').fill() - .strokeColor('#cbd5e1').lineWidth(0.5).stroke(); - } - } else { - doc.roundedRect(colDeliv.thumb + 3, y + 4, 26, 26, 2) - .fillColor('#f1f5f9').fill() - .strokeColor('#cbd5e1').lineWidth(0.5).stroke(); - doc.font('Helvetica').fontSize(6).fillColor(mutedColor).text('Kein Bild', colDeliv.thumb + 2, y + 14, { width: 26, align: 'center' }); - } - - // 4. Supplier SKU - doc.font('Helvetica').fontSize(7.5).fillColor(textColor) - .text(line.supplierSku || '–', colDeliv.suppSku, y + 12, { width: colDeliv.suppSkuW, ellipsis: true }); - - // 5. Eigene SKU as text + Code128 Barcode - const ownSkuText = line.ownSku || line.sku || '–'; - doc.font('Helvetica-Bold').fontSize(7).fillColor(primaryColor) - .text(ownSkuText, colDeliv.ownSku, y + 2, { width: colDeliv.ownSkuW, ellipsis: true }); - if (line.ownSkuBarcode) { - try { - doc.image(line.ownSkuBarcode, colDeliv.ownSku, y + 12, { width: 88, height: 20 }); - } catch (_) { } - } - - // 6. EAN as barcode - if (line.ean) { - doc.font('Helvetica').fontSize(6.5).fillColor(mutedColor) - .text(line.ean, colDeliv.ean, y + 2, { width: colDeliv.eanW, ellipsis: true }); - if (line.eanBarcode) { - try { - doc.image(line.eanBarcode, colDeliv.ean, y + 12, { width: 76, height: 20 }); - } catch (_) { } - } - } else { - doc.font('Helvetica').fontSize(8).fillColor(mutedColor) - .text('–', colDeliv.ean, y + 12, { width: colDeliv.eanW, align: 'center' }); - } - - // 7. Name - doc.font('Helvetica').fontSize(7.5).fillColor(textColor) - .text(nameText, colDeliv.name, y + 6, { width: colDeliv.nameW }); - - // 8. Menge geliefert - doc.font('Helvetica-Bold').fontSize(8).fillColor(textColor) - .text(formatQuantity(amount) + ' Stk', colDeliv.deliv, y + 12, { width: colDeliv.delivW, align: 'right' }); - - // 9. Menge already in warehouse - const stockText = line.trackStock ? (formatQuantity(line.currentStock ?? 0) + ' Stk') : '–'; - doc.font('Helvetica').fontSize(7.5).fillColor(line.trackStock ? textColor : mutedColor) - .text(stockText, colDeliv.stock, y + 12, { width: colDeliv.stockW, align: 'right' }); - - // 10. Menge added / gebucht - const hasBooked = line.trackStock && line.addedQuantity != null && line.addedQuantity > 0; - const bookedText = line.trackStock - ? (line.addedQuantity != null && line.addedQuantity > 0 ? `+${formatQuantity(line.addedQuantity)} Stk` : (line.addedQuantity === 0 ? '0 Stk' : '–')) - : '–'; - doc.font('Helvetica-Bold').fontSize(8).fillColor(hasBooked ? '#16a34a' : mutedColor) - .text(bookedText, colDeliv.added, y + 12, { width: colDeliv.addedW, align: 'right' }); - - // Row separator - doc.moveTo(45, y + rowHeight - 2) - .lineTo(550, y + rowHeight - 2) - .strokeColor(lightBorder) - .lineWidth(0.5) - .stroke(); - - y += rowHeight; - return; - } - if (showPrices) { doc.font('Helvetica') .fontSize(8.5) @@ -981,7 +984,8 @@ export async function createAndAttachManifestPdf(googleAuthId, companyId, manife manifest.lines = await Promise.all( manifest.lines.map(async (line) => { const rawSku = line.sku ? String(line.sku).trim() : ''; - let ownSku = rawSku; + let ownSku = ''; + let ownArticleName = ''; let supplierSku = ''; let ean = ''; let trackStock = false; @@ -992,23 +996,23 @@ export async function createAndAttachManifestPdf(googleAuthId, companyId, manife if (rawSku) { const findSql = isSqlite - ? `SELECT a.id, a.sku, a.track_stock, at.name, + ? `SELECT a.id, a.sku, a.track_stock, + (SELECT at.name FROM articletext at WHERE at.article_id = a.id ORDER BY CASE WHEN at.language_id = (SELECT default_language_id FROM company WHERE id = @companyId) THEN 0 ELSE 1 END, at.id ASC LIMIT 1) AS article_name, (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = a.id AND (asup.entity_id = @partnerId OR @partnerId IS NULL) LIMIT 1) AS supplier_sku, (SELECT ae.ean FROM articleean ae WHERE ae.article_id = a.id ORDER BY ae.id ASC LIMIT 1) AS ean, (SELECT p.hash FROM articlepic ap INNER JOIN pic p ON p.id = ap.pic_id WHERE ap.article_id = a.id ORDER BY ap.sort ASC LIMIT 1) AS pic_hash, (SELECT p.smallthumbbinary FROM articlepic ap INNER JOIN pic p ON p.id = ap.pic_id WHERE ap.article_id = a.id ORDER BY ap.sort ASC LIMIT 1) AS smallthumbbinary FROM article a - LEFT JOIN articletext at ON at.article_id = a.id WHERE a.company_id = @companyId AND (a.sku = @sku OR EXISTS (SELECT 1 FROM articlesupplier asup WHERE asup.article_id = a.id AND asup.sku = @sku)) LIMIT 1` - : `SELECT TOP 1 a.id, a.sku, a.track_stock, at.name, + : `SELECT TOP 1 a.id, a.sku, a.track_stock, + (SELECT TOP 1 at.name FROM articletext at WHERE at.article_id = a.id ORDER BY CASE WHEN at.language_id = (SELECT default_language_id FROM company WHERE id = @companyId) THEN 0 ELSE 1 END, at.id ASC) AS article_name, (SELECT TOP 1 asup.sku FROM articlesupplier asup WHERE asup.article_id = a.id AND (asup.entity_id = @partnerId OR @partnerId IS NULL)) AS supplier_sku, (SELECT TOP 1 ae.ean FROM articleean ae WHERE ae.article_id = a.id ORDER BY ae.id ASC) AS ean, (SELECT TOP 1 p.hash FROM articlepic ap INNER JOIN pic p ON p.id = ap.pic_id WHERE ap.article_id = a.id ORDER BY ap.sort ASC) AS pic_hash, (SELECT TOP 1 p.smallthumbbinary FROM articlepic ap INNER JOIN pic p ON p.id = ap.pic_id WHERE ap.article_id = a.id ORDER BY ap.sort ASC) AS smallthumbbinary FROM article a - LEFT JOIN articletext at ON at.article_id = a.id WHERE a.company_id = @companyId AND (a.sku = @sku OR EXISTS (SELECT 1 FROM articlesupplier asup WHERE asup.article_id = a.id AND asup.sku = @sku))`; @@ -1017,6 +1021,7 @@ export async function createAndAttachManifestPdf(googleAuthId, companyId, manife if (art) { articleId = art.id; ownSku = art.sku || rawSku; + ownArticleName = (art.article_name || art.name || '').trim(); supplierSku = art.supplier_sku || (rawSku !== art.sku ? rawSku : ''); ean = art.ean || ''; trackStock = Number(art.track_stock ?? 0) === 1; @@ -1036,6 +1041,8 @@ export async function createAndAttachManifestPdf(googleAuthId, companyId, manife const stockRes = await store.provider.query(stockSql, { companyId, ownSku, articleId }).catch(() => ({ rows: [] })); currentStock = Number(stockRes.rows?.[0]?.stock ?? 0); } + } else { + supplierSku = rawSku; } } @@ -1058,6 +1065,8 @@ export async function createAndAttachManifestPdf(googleAuthId, companyId, manife return { ...line, ownSku, + ownArticleName, + articleName: ownArticleName, supplierSku, ean, trackStock,