Feature: Labelprint für Kistenetiketten hinzugefügt
This commit is contained in:
877
sources/getProductLabel.php
Normal file
877
sources/getProductLabel.php
Normal file
@@ -0,0 +1,877 @@
|
||||
<?php
|
||||
error_reporting(E_ALL & ~E_DEPRECATED);
|
||||
|
||||
require('../config.php');
|
||||
require('../EpiApi.php');
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
require_once __DIR__ . '/../vendor/tecnickcom/tcpdf/tcpdf.php';
|
||||
date_default_timezone_set('Europe/Berlin');
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
|
||||
$deviceNumber = (!empty($_GET['nr'])) ? '--' . trim($_GET['nr']) : '';
|
||||
|
||||
$Epi = new Epirent();
|
||||
$product =json_decode($Epi->requestEpiApi('/v1/product/'.$id.'?cl=' . Epirent_Mandant))->payload[0];
|
||||
$productImage =json_decode($Epi->requestEpiApi('/v1/product/image/'.$id.'?cl=' . Epirent_Mandant))->payload[0]->image_data;
|
||||
|
||||
|
||||
function s(?string $v): string { return trim((string)$v); }
|
||||
function fnum(?float $v, int $dec = 2): string {
|
||||
if ($v === null) return '';
|
||||
$n = number_format((float)$v, $dec, ',', '.');
|
||||
return rtrim(rtrim($n, '0'), ',');
|
||||
}
|
||||
|
||||
//Set Variables
|
||||
$productName = $product->name;
|
||||
$productNumber = $product->product_no;
|
||||
|
||||
$bruttoWeightSum = $product->tech_data->weight_gro;
|
||||
|
||||
$bundle = buildBundleForProduct($Epi, $product);
|
||||
|
||||
/** Test-Daten
|
||||
*/
|
||||
$dimsBHT =$product->tech_data->width_gro." x ".$product->tech_data->depth_gro." x ".$product->tech_data->height_gro;
|
||||
$volume = ((float)$product->tech_data->height_gro * (float)$product->tech_data->width_gro * (float)$product->tech_data->depth_gro)* 0.000001;
|
||||
$storageLoc = $product->product_data->storage_location_nearby;
|
||||
|
||||
/** Layout */
|
||||
$PAGE_SIZE = 'A4';
|
||||
$MARGIN_LEFT = 8;
|
||||
$MARGIN_TOP = 8;
|
||||
$MARGIN_RIGHT = 8;
|
||||
$MARGIN_BOTTOM = 10;
|
||||
|
||||
$CELL_PAD = 1.6;
|
||||
$LINE_GRAY = '#efefef';
|
||||
|
||||
$LEVEL_MIN = 1; $LEVEL_MAX = 5;
|
||||
$INDENT_PX_PER_LEVEL = 10;
|
||||
$BASE_FONT_PX = 10;
|
||||
$FONT_STEP_PX = 1.5;
|
||||
|
||||
|
||||
|
||||
/** TCPDF */
|
||||
$pdf = new TCPDF('P', 'mm', $PAGE_SIZE, true, 'UTF-8', false);
|
||||
|
||||
// 1) Pfade sauber
|
||||
$fontDir = realpath(__DIR__ . '/../src/assets/font') . '/';
|
||||
$tcpdfFontsDir = defined('K_PATH_FONTS') ? K_PATH_FONTS : (dirname((new \ReflectionClass('TCPDF'))->getFileName()) . '/fonts/');
|
||||
|
||||
// 2) Rechte (einmalig sicherstellen)
|
||||
// -> hast du schon gefixt
|
||||
|
||||
// 3) TTFs registrieren und Rückgabewerte merken
|
||||
$fontReg = file_exists($fontDir.'Hurme3/HurmeGeometricSans3-Regular.ttf')
|
||||
? TCPDF_FONTS::addTTFfont($fontDir.'Hurme3/HurmeGeometricSans3-Regular.ttf', 'TrueTypeUnicode', '', 96)
|
||||
: false;
|
||||
$fontBold = file_exists($fontDir.'Hurme1/HurmeGeometricSans1-Bold.ttf')
|
||||
? TCPDF_FONTS::addTTFfont($fontDir.'Hurme1/HurmeGeometricSans1-Bold.ttf', 'TrueTypeUnicode', '', 96)
|
||||
: false;
|
||||
$fontItal = file_exists($fontDir.'Hurme3/HurmeGeometricSans3-Italic.ttf')
|
||||
? TCPDF_FONTS::addTTFfont($fontDir.'Hurme3/HurmeGeometricSans3-Italic.ttf', 'TrueTypeUnicode', '', 96)
|
||||
: false;
|
||||
$fontBI = file_exists($fontDir.'Hurme1/HurmeGeometricSans1-BoldItalic.ttf')
|
||||
? TCPDF_FONTS::addTTFfont($fontDir.'Hurme1/HurmeGeometricSans1-BoldItalic.ttf', 'TrueTypeUnicode', '', 96)
|
||||
: false;
|
||||
|
||||
// 4) Fallback setzen
|
||||
$baseFont = $fontReg ?: 'dejavusans';
|
||||
|
||||
// 5) Header/Footer und Defaults
|
||||
$pdf->setHeaderFont([$baseFont, '', 10]);
|
||||
$pdf->setFooterFont([$baseFont, '', 9]);
|
||||
$pdf->SetDefaultMonospacedFont('courier');
|
||||
|
||||
// 6) *** AB JETZT KEIN dejavusans MEHR SETZEN! ***
|
||||
$pdf->SetFont($baseFont, '', 10.5);
|
||||
|
||||
|
||||
|
||||
$pdf->SetCreator('VT-Media EpiWebview');
|
||||
$pdf->SetAuthor('VT-Media');
|
||||
$pdf->SetTitle('Kistenetikett '.$productName);
|
||||
$pdf->SetSubject('Kistenetikett');
|
||||
|
||||
$pdf->setPrintHeader(false);
|
||||
$pdf->setPrintFooter(false);
|
||||
$pdf->SetMargins($MARGIN_LEFT, $MARGIN_TOP, $MARGIN_RIGHT);
|
||||
$pdf->SetAutoPageBreak(true, $MARGIN_BOTTOM);
|
||||
$pdf->setImageScale(1.0);
|
||||
$pdf->setFontSubsetting(true);
|
||||
$pdf->setCellPadding($CELL_PAD);
|
||||
|
||||
$pdf->AddPage();
|
||||
|
||||
|
||||
|
||||
|
||||
// --- LOGO in fester Box mit Rahmen, zentriert & verzerrungsfrei ---
|
||||
$logoPath = __DIR__ . Labelprint_Logopath;
|
||||
|
||||
$startX = $pdf->GetX();
|
||||
$startY = $pdf->GetY();
|
||||
|
||||
// feste Rahmen-Größe (in mm)
|
||||
$frameW = 38.0; // Gesamtbreite des Rahmens
|
||||
$frameH = 20; // Gesamthöhe des Rahmens
|
||||
$frameOffsetX = 1.4; // Abstand vom linken Margin (wie vorher genutzt)
|
||||
$frameOffsetY = 1.5; // Abstand von oben (wie vorher genutzt)
|
||||
|
||||
// Rahmen-Position
|
||||
$frameX = $startX + $frameOffsetX;
|
||||
$frameY = $startY + $frameOffsetY;
|
||||
|
||||
// Rahmen zeichnen
|
||||
$pdf->SetLineWidth(0.3);
|
||||
$pdf->Rect($frameX, $frameY, $frameW, $frameH, 'D');
|
||||
|
||||
// Logo innerhalb des Rahmens mit Innenabstand
|
||||
$innerPad = 1.2; // mm Luft im Rahmen
|
||||
$maxW = $frameW - 2 * $innerPad;
|
||||
$maxH = $frameH - 2 * $innerPad;
|
||||
|
||||
if (is_file($logoPath)) {
|
||||
// Seitenverhältnis ermitteln
|
||||
$imgWpx = $imgHpx = 0;
|
||||
if (@list($imgWpx, $imgHpx) = @getimagesize($logoPath)) {
|
||||
$ratio = ($imgHpx > 0) ? ($imgWpx / $imgHpx) : 1.0;
|
||||
|
||||
// proportional in den Rahmen einpassen
|
||||
if ($maxW / $maxH > $ratio) {
|
||||
$drawH = $maxH;
|
||||
$drawW = $maxH * $ratio;
|
||||
} else {
|
||||
$drawW = $maxW;
|
||||
$drawH = $maxW / $ratio;
|
||||
}
|
||||
|
||||
// zentrieren
|
||||
$imgX = $frameX + ($frameW - $drawW) / 2;
|
||||
$imgY = $frameY + ($frameH - $drawH) / 2;
|
||||
|
||||
// ausgeben
|
||||
$pdf->Image($logoPath, $imgX, $imgY, $drawW, $drawH, '', '', '', false, 300);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
$afterLogoX = $frameX + $frameW - 1.4; // gleicher Abstand wie zuvor, jetzt basierend auf Rahmenbreite
|
||||
$afterLogoBottomY = $frameY + $frameH; // falls du unten bündig etwas brauchst
|
||||
$pdf->SetXY($afterLogoX, $startY); // Cursor rechts neben dem Logo positionieren
|
||||
|
||||
|
||||
|
||||
$addrHtml = '
|
||||
<div style="line-height:1.25; font-size:6px; margin:0; padding:0;">'.Labelprint_Addresstext.'</div>';
|
||||
// Feste Box rechts neben dem Logo (direkt bündig)
|
||||
$addrBoxW = 30.0; // feste Breite (mm)
|
||||
$addrBoxH = 20.0; // feste Höhe (mm)
|
||||
$addrPad = 0.5; // Innenabstand Inhalt → Rahmen (mm)
|
||||
|
||||
// bündig rechts neben dem Logo-Rahmen starten
|
||||
$addrX = $frameX + $frameW; // kein zusätzlicher Abstand
|
||||
$addrY = $startY+1.5; // gleiche Oberkante wie Logo
|
||||
|
||||
// 1) festen Rahmen zeichnen
|
||||
$pdf->SetLineWidth(0.3);
|
||||
$pdf->Rect($addrX, $addrY, $addrBoxW, $addrBoxH, 'D');
|
||||
|
||||
// 2) HTML-Inhalt in die Box schreiben, mit Clipping (Überhang wird abgeschnitten)
|
||||
$pdf->StartTransform();
|
||||
$pdf->Rect($addrX, $addrY, $addrBoxW, $addrBoxH, 'CNZ'); // Clip auf Box setzen
|
||||
|
||||
$pdf->writeHTMLCell(
|
||||
$addrBoxW - 2*$addrPad, // feste Breite des Inhalts
|
||||
$addrBoxH - 2*$addrPad, // feste Höhe des Inhalts
|
||||
$addrX + $addrPad, // x mit Innenabstand
|
||||
$addrY + $addrPad, // y mit Innenabstand
|
||||
$addrHtml, // dein HTML
|
||||
0, // kein Border (Rahmen haben wir schon)
|
||||
0, // ln
|
||||
false, // fill
|
||||
true, // reseth
|
||||
'L', // align
|
||||
true // autopadding
|
||||
);
|
||||
|
||||
$pdf->StopTransform();
|
||||
|
||||
// Cursor steht bereits auf: $pdf->SetXY($addrX + $addrBoxW, $addrY);
|
||||
|
||||
// 1) HTML OHNE border (sonst Doppelrahmen)
|
||||
$productionHtml = '
|
||||
<div style="line-height:1.3; font-size:8px; margin:0; padding:0;">
|
||||
<b>Produktion</b><br/><br><br><br>
|
||||
</div>';
|
||||
|
||||
// 2) Feste Box rechts neben der Adress-Box
|
||||
$prodX = $addrX + $addrBoxW; // bündig rechts neben Adresse
|
||||
$prodY = $addrY; // gleiche Oberkante
|
||||
// Breite: restliche Seitenbreite bis zum rechten Rand
|
||||
$prodW = $pdf->getPageWidth() - $MARGIN_RIGHT - $prodX+2;
|
||||
// Höhe: fix (anpassen nach Wunsch)
|
||||
$prodH = 20.0; // mm
|
||||
$prodPad = 0; // Innenabstand
|
||||
|
||||
// 3) EIN sichtbarer Rahmen zeichnen
|
||||
$pdf->SetLineWidth(0.3);
|
||||
$pdf->Rect($prodX, $prodY, $prodW, $prodH, 'D');
|
||||
|
||||
// 4) Inhalt in die Box schreiben und clippen (Überhang abgeschnitten)
|
||||
$pdf->StartTransform();
|
||||
$pdf->Rect($prodX, $prodY, $prodW, $prodH, 'CNZ'); // Clip auf Box setzen
|
||||
|
||||
$pdf->writeHTMLCell(
|
||||
$prodW - 2*$prodPad, // feste Breite des Inhalts
|
||||
$prodH - 2*$prodPad, // feste Höhe des Inhalts
|
||||
$prodX + $prodPad, // x mit Innenabstand
|
||||
$prodY + $prodPad, // y mit Innenabstand
|
||||
$productionHtml, // Inhalt (ohne border!)
|
||||
0, // kein Border (Rahmen haben wir schon)
|
||||
0, // ln
|
||||
false, // fill
|
||||
true, // reseth
|
||||
'L', // align
|
||||
true // autopadding
|
||||
);
|
||||
|
||||
$pdf->StopTransform();
|
||||
|
||||
// 5) (Optional) Cursor direkt rechts neben der Box positionieren
|
||||
$pdf->SetXY($prodX + $prodW, $prodY);
|
||||
|
||||
// === Name-Box: feste Größe, Text bricht, kein Doppelrahmen ===
|
||||
|
||||
// Breite: gesamte Restbreite bis zum rechten Rand (unter Adresse + Produktion)
|
||||
$nameX = $frameX; // beginnt rechts neben dem Logo/Adressbereich
|
||||
$nameW = $pdf->getPageWidth() - $MARGIN_RIGHT - $nameX-65.2;
|
||||
|
||||
// Höhe & Padding der Box (anpassen nach Wunsch)
|
||||
$nameBoxH = 18.2; // mm - feste Höhe
|
||||
$namePad = 1.5; // mm - Innenabstand
|
||||
|
||||
// Y: direkt unter die höhere der beiden Boxen (Adresse vs. Produktion)
|
||||
$yAfterRow = max($addrY + $addrBoxH, $prodY + $prodH) + 0.0; // +2 mm Abstand
|
||||
$nameY = $yAfterRow;
|
||||
|
||||
// Inhalt OHNE border (sonst Doppelrahmen)
|
||||
$nameHtml = '
|
||||
<div style="font-size:15px; font-weight:700; line-height:1.3; margin:0; padding:0;">' . htmlspecialchars($productName) . '
|
||||
</div>';
|
||||
|
||||
// 1) Sichtbaren Rahmen zeichnen (feste Größe)
|
||||
$pdf->SetLineWidth(0.3);
|
||||
$pdf->Rect($nameX, $nameY, $nameW, $nameBoxH, 'D');
|
||||
|
||||
// 2) Inhalt in die Box schreiben und clippen
|
||||
$pdf->StartTransform();
|
||||
$pdf->Rect($nameX, $nameY, $nameW, $nameBoxH, 'CNZ'); // Clip auf Box setzen
|
||||
|
||||
$pdf->writeHTMLCell(
|
||||
$nameW - 2*$namePad, // feste Breite für den Text
|
||||
$nameBoxH - 2*$namePad, // feste Höhe für den Text
|
||||
$nameX + $namePad, // x mit Innenabstand
|
||||
$nameY + $namePad, // y mit Innenabstand
|
||||
$nameHtml,
|
||||
0, // kein HTML-Border
|
||||
0, // ln
|
||||
false, // fill
|
||||
true, // reseth
|
||||
'L', // align
|
||||
true // autopadding
|
||||
);
|
||||
|
||||
$pdf->StopTransform();
|
||||
|
||||
// Optional: Cursor unter die Box setzen (falls du danach im Fluss weitermachen willst)
|
||||
$pdf->SetXY($nameX, $nameY + $nameBoxH);
|
||||
|
||||
// 1) Box-Geometrie (immer gleich groß)
|
||||
$pnBoxW = 67.0; // mm – Gesamtbreite der Box
|
||||
$pnBoxH = 18.2; // mm – Gesamthöhe der Box
|
||||
$pnBoxPad = 2.0; // mm – Innenabstand innerhalb der Box
|
||||
|
||||
// Position der Box (aktuell am rechten Rand oben; passe frei an)
|
||||
$pnBoxX = $pdf->getPageWidth() - $MARGIN_RIGHT - $pnBoxW+1.8; // bündig am rechten Rand
|
||||
$pnBoxY = $startY+21.5; // gleiche Oberkante wie Kopf
|
||||
|
||||
// 2) Sichtbaren Rahmen zeichnen (einmalig)
|
||||
$pdf->SetLineWidth(0.3);
|
||||
$pdf->Rect($pnBoxX, $pnBoxY, $pnBoxW, $pnBoxH, 'D');
|
||||
|
||||
// 3) Clipping aktivieren, damit Inhalt in der Box bleibt
|
||||
$pdf->StartTransform();
|
||||
$pdf->Rect($pnBoxX, $pnBoxY, $pnBoxW, $pnBoxH, 'CNZ'); // Clip auf die Box
|
||||
|
||||
// 4) Produktnummer: frei verschiebbar innerhalb der Box
|
||||
$pnCombined = trim($productNumber . $deviceNumber);
|
||||
$pnText = htmlspecialchars($pnCombined !== '' ? $pnCombined : 'N/A');
|
||||
|
||||
// Offsets innerhalb der Box – hier stellst du die Position ein
|
||||
$pnTextOffsetX = 2.0; // mm von linker Innenkante
|
||||
$pnTextOffsetY = 1; // mm von oberer Innenkante
|
||||
$pnTextW = $pnBoxW - 2*$pnBoxPad; // nutzbare Breite
|
||||
$pnTextH = 8.0; // feste Höhe für den PN-Textbereich (mm)
|
||||
|
||||
$pnHtml = '
|
||||
<div style="font-size:20px; line-height:1.25; margin:0; padding:0;">
|
||||
'.$pnText.'
|
||||
</div>';
|
||||
|
||||
// PN-Text rendern (ohne HTML-Border!)
|
||||
$pdf->writeHTMLCell(
|
||||
$pnTextW, // Breite Textbereich
|
||||
$pnTextH, // Höhe Textbereich
|
||||
$pnBoxX + $pnBoxPad + $pnTextOffsetX, // X in der Box
|
||||
$pnBoxY + $pnBoxPad + $pnTextOffsetY, // Y in der Box
|
||||
$pnHtml,
|
||||
0, 0, false, true, 'L', true
|
||||
);
|
||||
|
||||
// 5) QR-Code: frei verschiebbar innerhalb der Box, verzerrungsfrei per size
|
||||
$qrData = trim((string)($productNumber . $deviceNumber));
|
||||
if ($qrData === '') {
|
||||
$qrData = 'N/A';
|
||||
}
|
||||
|
||||
$qrSize = 16.0; // mm – Kantenlänge des QR
|
||||
$qrOffsetX = $pnBoxW - $pnBoxPad - $qrSize - 2.0; // mm von linker Innenkante
|
||||
$qrOffsetY = $pnBoxH - $pnBoxPad - $qrSize +0.6; // mm von oberer Innenkante
|
||||
|
||||
$qrX = $pnBoxX + $qrOffsetX;
|
||||
$qrY = $pnBoxY + $qrOffsetY;
|
||||
|
||||
$qrStyle = [
|
||||
'border' => 0,
|
||||
'vpadding' => 0,
|
||||
'hpadding' => 0,
|
||||
'fgcolor' => [0,0,0],
|
||||
'bgcolor' => false,
|
||||
];
|
||||
|
||||
// QR rendern (Warnings/Deprecated für diesen Block stumm schalten)
|
||||
$_old_reporting = error_reporting();
|
||||
error_reporting($_old_reporting & ~(E_WARNING | E_DEPRECATED | E_NOTICE));
|
||||
$pdf->write2DBarcode($qrData, 'QRCODE,H', $qrX, $qrY, $qrSize, $qrSize, $qrStyle, 'N');
|
||||
error_reporting($_old_reporting);
|
||||
|
||||
// 6) Clipping beenden
|
||||
$pdf->StopTransform();
|
||||
|
||||
// 7) Optional: Cursor unter die PN-Box setzen, falls du im Fluss weiter willst
|
||||
$pdf->SetXY($pnBoxX, $pnBoxY + $pnBoxH);
|
||||
|
||||
/** Bundle */
|
||||
|
||||
$bundleHeader = '<div style=" background-color: black; color: white; font-size:10px; font-weight:700;line-height:1.4; margin:4px 0 3px 0;"> Bundleinhalt</div>';
|
||||
$pdf->writeHTMLCell(70, 50, 7.65, 46, $bundleHeader, 0, 0, 0, true, 'L', true);
|
||||
$bundleHeaderFillable = '<div style="border: 1px solid black; line-heigt:0.6"></div>';
|
||||
//$pdf->writeHTMLCell(130.8, 39.8, 74.5, 46, $bundleHeaderFillable, 0, 0, 0, false, 'L', true);
|
||||
drawFixedBox($pdf, 76.0, 47.7, 127.8, 4.65);
|
||||
|
||||
|
||||
$bundleRows = '';
|
||||
|
||||
$BASE_FONT_PX = 10.0; // Level 1
|
||||
$FONT_STEP_PX = 1.5; // je Ebene kleiner
|
||||
$MIN_FONT_PX = 6.0;
|
||||
$LINE_HEIGHT_BASE = 1.15; // Basis Zeilenhöhe
|
||||
$LINE_HEIGHT_STEP = 0.05; // je Ebene etwas kompakter
|
||||
$ROW_PAD_V_MM = 0.5; // vertikales Padding je Zeile
|
||||
$ROW_PAD_H_MM = 0.8; // horizontales Padding
|
||||
$WEIGHT_PAD_RIGHT_MM = 1.6; // mehr Abstand zum rechten Rand
|
||||
$INDENT_MM_PER_LVL = 2.8; // Einrückung je Ebene in mm (stabil!)
|
||||
|
||||
foreach ($bundle as $item) {
|
||||
$qty = (int)($item['qty'] ?? 1);
|
||||
$text = htmlspecialchars($item['text'] ?? '');
|
||||
$lvl = max($LEVEL_MIN, min($LEVEL_MAX, (int)($item['level'] ?? 1)));
|
||||
$wkg = isset($item['weight_kg']) ? (float)$item['weight_kg'] : null;
|
||||
|
||||
$textFontPx = max($MIN_FONT_PX, $BASE_FONT_PX - ($lvl - 1) * $FONT_STEP_PX);
|
||||
$weightFontPx = max($MIN_FONT_PX, $BASE_FONT_PX - ($lvl - 1) * $FONT_STEP_PX);
|
||||
$lineHeight = max(1.0, $LINE_HEIGHT_BASE - ($lvl - 1) * $LINE_HEIGHT_STEP);
|
||||
|
||||
// Einrückung robust über mm – aber nur Tabelle nutzen, wenn > 0 mm
|
||||
$indentMm = max(0, ($lvl - 1) * $INDENT_MM_PER_LVL);
|
||||
|
||||
// Name-Spalte: ohne Spacer-Tabelle bei Level 1 (indent=0), sonst mit
|
||||
if ($indentMm > 0) {
|
||||
$nameCellHtml = '
|
||||
<table cellspacing="0" cellpadding="0" width="100%">
|
||||
<tr>
|
||||
<td style="width:'.$indentMm.'mm; padding:0; margin:0;"></td>
|
||||
<td style="font-size:'.$textFontPx.'px; line-height:'.$lineHeight.'; padding:0; margin:0;">'.$text.'</td>
|
||||
</tr>
|
||||
</table>';
|
||||
} else {
|
||||
// Level 1: direkt rendern
|
||||
$nameCellHtml = '<span style="font-size:'.$textFontPx.'px; line-height:'.$lineHeight.';">'.$text.'</span>';
|
||||
}
|
||||
|
||||
$bundleRows .= '
|
||||
<tr>
|
||||
<!-- Anzahl -->
|
||||
<td style="
|
||||
width:12%;
|
||||
text-align:right;
|
||||
vertical-align:middle;
|
||||
font-size:'.$textFontPx.'px;
|
||||
line-height:'.$lineHeight.';
|
||||
padding:'.$ROW_PAD_V_MM.'mm '.$ROW_PAD_H_MM.'mm '.$ROW_PAD_V_MM.'mm '.$ROW_PAD_H_MM.'mm;">
|
||||
'.$qty.'
|
||||
</td>
|
||||
|
||||
<!-- Name mit Einrückung -->
|
||||
<td style="
|
||||
width:68%;
|
||||
vertical-align:middle;
|
||||
padding:'.$ROW_PAD_V_MM.'mm '.$ROW_PAD_H_MM.'mm;">
|
||||
'.$nameCellHtml.'
|
||||
</td>
|
||||
|
||||
<!-- Gewicht rechts mit extra Innenabstand -->
|
||||
<td style="
|
||||
width:20%;
|
||||
text-align:right;
|
||||
vertical-align:middle;
|
||||
font-size:'.$weightFontPx.'px;
|
||||
line-height:'.$lineHeight.';
|
||||
padding:'.$ROW_PAD_V_MM.'mm '.$WEIGHT_PAD_RIGHT_MM.'mm '.$ROW_PAD_V_MM.'mm '.$ROW_PAD_H_MM.'mm;">
|
||||
'.($wkg !== null ? fnum($wkg).' kg' : '').'
|
||||
</td>
|
||||
</tr>';
|
||||
}
|
||||
|
||||
$bundleHtml = '
|
||||
<table cellspacing="0" cellpadding="0" style="border:1px solid #000; border-collapse:collapse;" width="100%">
|
||||
<tbody>'.$bundleRows.'</tbody>
|
||||
</table>';
|
||||
|
||||
$pdf->writeHTMLCell(
|
||||
$pdf->getPageWidth() - 12.4,
|
||||
0,
|
||||
7.8,
|
||||
50.8,
|
||||
$bundleHtml,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
true,
|
||||
'L',
|
||||
true
|
||||
);
|
||||
|
||||
|
||||
|
||||
/** ---------- Meta-Block dynamisch unterhalb der Bundle-Tabelle ---------- */
|
||||
|
||||
// Höhe der zuletzt gerenderten Bundle-Tabelle holen
|
||||
$bundleH = $pdf->getLastH();
|
||||
|
||||
// Y-Position direkt nach der Bundle-Tabelle (mit 3 mm Abstand)
|
||||
$metaY = 47.6 + $bundleH; // 50 = dein bisheriges Y der Bundle-Tabelle
|
||||
$metaX = $MARGIN_LEFT;
|
||||
$metaW = $pdf->getPageWidth() - $MARGIN_LEFT - $MARGIN_RIGHT;
|
||||
|
||||
// Falls Meta-Block zu nah am Seitenende wäre → neue Seite
|
||||
$usableBottom = $pdf->getPageHeight() - $MARGIN_BOTTOM;
|
||||
if ($metaY > $usableBottom - 40) { // 40 mm Puffer für Meta
|
||||
$pdf->AddPage();
|
||||
$metaY = $MARGIN_TOP;
|
||||
}
|
||||
|
||||
// --- Inhalt wie gehabt ---
|
||||
$leftMeta = '
|
||||
<table cellspacing="0" cellpadding="'.$CELL_PAD.'" width="100%">
|
||||
<tbody>
|
||||
|
||||
<!-- Maße -->
|
||||
<tr>
|
||||
<!-- Zusammengefasste linke Zelle: hat Rahmen, innen KEIN Rahmen -->
|
||||
<td width="58%" border="1" style="padding-left:20px;">
|
||||
<table cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td width="64%">Maße <small>L x B x H</small></td>
|
||||
<td width="36%">in cm</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<!-- rechte Zelle mit Wert -->
|
||||
<td width="42%" border="1"><b> '.htmlspecialchars($dimsBHT).'</b></td>
|
||||
</tr>
|
||||
|
||||
<!-- Gewicht -->
|
||||
<tr>
|
||||
<td width="58%" border="1" style="padding-left:20px;">
|
||||
<table cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td width="64%">Gewicht</td>
|
||||
<td width="36%">in Kg</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td width="42%" border="1"><b> '.fnum($bruttoWeightSum).' Kg</b></td>
|
||||
</tr>
|
||||
|
||||
<!-- Volumen -->
|
||||
<tr>
|
||||
<td width="58%" border="1" style="padding-left:20px;">
|
||||
<table cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td width="64%">Volumen</td>
|
||||
<td width="36%">in m³</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td width="42%" border="1"><b> '.fnum($volume, 2).' m³</b></td>
|
||||
</tr>
|
||||
|
||||
<!-- Lagerplatz mit innerer Tabelle (optisch perfekt ausgerichtet) -->
|
||||
<tr>
|
||||
<td width="30%" border="1" style="padding-left:20px;">
|
||||
<table cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td width="100%">Lagerplatz</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td width="70%" border="1"><b> '.htmlspecialchars($storageLoc).'</b></td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>';
|
||||
|
||||
// ---- Koordinaten der Meta-Zeile bestimmen (nimm deine Werte) ----
|
||||
// Falls du sie schon berechnet hast, nutze deine $metaX, $metaY, $metaW.
|
||||
// Beispiel, wie du sie meist setzt:
|
||||
$metaX = $MARGIN_LEFT;
|
||||
$metaW = $pdf->getPageWidth() - $MARGIN_LEFT - $MARGIN_RIGHT;
|
||||
// $metaY hast du zuvor als Ziel-Y für den Meta-Block berechnet:
|
||||
/// $metaY = ... (z.B. 50 + $bundleH + 3);
|
||||
|
||||
// ---- Spaltenbreiten wie in deinem metaWrap: 62% | 2% | 36% ----
|
||||
$leftW = $metaW * 0.62;
|
||||
$gapW = $metaW * 0.02;
|
||||
$rightW = $metaW * 0.36;
|
||||
|
||||
// Obere linke Ecke der rechten Spalte:
|
||||
$rightX = $metaX + $leftW + $gapW;
|
||||
$rightY = $metaY;
|
||||
|
||||
// ---- Fester Rahmen in der rechten Spalte ----
|
||||
$frameOuterPad = 1.0; // mm: Abstand vom Spaltenrand
|
||||
$frameW = $rightW - 2*$frameOuterPad+8; // Rahmenbreite
|
||||
$frameH = 24.38; // mm: feste Rahmenhöhe, nach Wunsch
|
||||
$frameX = $rightX + $frameOuterPad-5.2;
|
||||
$frameY = $rightY + $frameOuterPad+0.5;
|
||||
|
||||
$pdf->SetLineWidth(0.3);
|
||||
$pdf->Rect($frameX, $frameY, $frameW, $frameH, 'D'); // der feste Rahmen
|
||||
|
||||
// ---- Base64-Bild zentriert & verzerrungsfrei in den Rahmen ----
|
||||
$tmpImg = tempnam(sys_get_temp_dir(), 'epi_') . '.png';
|
||||
file_put_contents($tmpImg, base64_decode($productImage));
|
||||
|
||||
list($imgWpx, $imgHpx) = getimagesize($tmpImg);
|
||||
$imgRatio = ($imgHpx > 0) ? ($imgWpx / $imgHpx) : 1.0;
|
||||
|
||||
$innerPad = 2.0; // mm: Innenabstand im Rahmen
|
||||
$maxW = $frameW - 2*$innerPad;
|
||||
$maxH = $frameH - 2*$innerPad;
|
||||
|
||||
// proportional einpassen
|
||||
if ($maxW / $maxH > $imgRatio) {
|
||||
$drawH = $maxH;
|
||||
$drawW = $maxH * $imgRatio;
|
||||
} else {
|
||||
$drawW = $maxW;
|
||||
$drawH = $maxW / $imgRatio;
|
||||
}
|
||||
|
||||
// zentrieren
|
||||
$imgX = $frameX + ($frameW - $drawW) / 2;
|
||||
$imgY = $frameY + ($frameH - $drawH) / 2;
|
||||
|
||||
$pdf->Image($tmpImg, $imgX, $imgY, $drawW, $drawH, '', '', '', false, 300);
|
||||
@unlink($tmpImg);
|
||||
|
||||
$metaWrap = '
|
||||
<table cellspacing="0" cellpadding="0" border="0" width="100%" style="margin-top:2px;">
|
||||
<tr>
|
||||
<td width="62%">'.$leftMeta.'</td>
|
||||
<td width="2%"></td>
|
||||
<td width="36%">'.$rightMeta.'</td>
|
||||
</tr>
|
||||
</table>';
|
||||
|
||||
// Meta-Block exakt an berechneter Position ausgeben
|
||||
$pdf->writeHTMLCell($metaW, 0, $metaX, $metaY, $metaWrap, 0, 0, 0, true, 'L', true);
|
||||
/** Output */
|
||||
// Falls trotz allem vorher etwas ausgegeben wurde (Warnings), Buffer leeren:
|
||||
if (ob_get_length()) { ob_end_clean(); }
|
||||
|
||||
|
||||
|
||||
// Äußerer Rahmen, Größenberechnung
|
||||
|
||||
// 1) Höhe der META-Zelle (zuletzt geschrieben) holen:
|
||||
$metaH = $pdf->getLastH();
|
||||
|
||||
// 2) Y der Bundle-Tabelle kennst du (50.8 in deinem Code) + deren Höhe:
|
||||
$bundleY = 50.8; // dein fixer Y für die Bundle-Tabelle
|
||||
// $bundleH hast du bereits oben: $bundleH = $pdf->getLastH();
|
||||
|
||||
// 3) Bottom-Kanten aller Boxen berechnen:
|
||||
$bottoms = [
|
||||
$frameY + $frameH, // Logo-Box
|
||||
$addrY + $addrBoxH, // Adress-Box
|
||||
$prodY + $prodH, // Produktion-Box
|
||||
$nameY + $nameBoxH, // Name-Box
|
||||
$pnBoxY + $pnBoxH, // PN/QR-Box
|
||||
$bundleY + $bundleH, // Bundle-Tabelle
|
||||
$metaY + $metaH, // Meta-Block (linke Tabelle + rechter Bildrahmen)
|
||||
];
|
||||
|
||||
// 4) Start-Y des Contentbereichs: oben an deinem Kopf beginnen
|
||||
$outerY = min($frameY, $addrY, $prodY, $nameY, $pnBoxY, 46.0 /*Bundle-Header-Y*/, $metaY);
|
||||
|
||||
// 5) Untere Kante ist die größte Bottom-Kante:
|
||||
$outerBottom = max($bottoms);
|
||||
|
||||
// 6) Außenrahmen-Geometrie:
|
||||
$outerX = $MARGIN_LEFT+1.2;
|
||||
$outerW = $pdf->getPageWidth() - $MARGIN_LEFT - $MARGIN_RIGHT+1;
|
||||
$outerH = max(2, $outerBottom - $outerY)-1.2; // Mindeshöhe absichern
|
||||
|
||||
// 7) Dicke Linie zeichnen (z.B. 1.2 mm)
|
||||
drawFixedBox($pdf, $outerX, $outerY, $outerW, $outerH, 1, '', 0, false);
|
||||
|
||||
|
||||
|
||||
$filename = 'Kistenetikett_'.preg_replace('/\s+/', '_', trim($productName)).'_'.$productNumber.$deviceNumber.'_'.date('Ymd_His').'.pdf';
|
||||
$pdf->Output($filename, 'I');
|
||||
|
||||
|
||||
/**
|
||||
* Zeichnet eine flexible, leere oder beschriftete Box mit TCPDF.
|
||||
*
|
||||
* @param TCPDF $pdf Referenz auf dein TCPDF-Objekt
|
||||
* @param float $x Linke obere Ecke (mm)
|
||||
* @param float $y Obere Position (mm)
|
||||
* @param float $w Breite (mm)
|
||||
* @param float $h Höhe (mm)
|
||||
* @param float $border Linienstärke in mm (z. B. 0.3)
|
||||
* @param string $text (Optional) Inhalt oder Beschriftung
|
||||
* @param float $pad Innenabstand in mm
|
||||
* @param bool $fill true = grau hinterlegt, false = leer
|
||||
* @param string|null $style (Optional) Linienstil – 'solid', 'dashed', 'dotted'
|
||||
*/
|
||||
function drawFixedBox(
|
||||
TCPDF $pdf,
|
||||
float $x,
|
||||
float $y,
|
||||
float $w,
|
||||
float $h,
|
||||
float $border = 0.3,
|
||||
string $text = '',
|
||||
float $pad = 1.5,
|
||||
bool $fill = false,
|
||||
?string $style = 'solid'
|
||||
): void {
|
||||
// Farbe für Füllung (hellgrau, wenn aktiviert)
|
||||
$fillColor = [240, 240, 240];
|
||||
|
||||
// Linienstil (falls angegeben)
|
||||
switch ($style) {
|
||||
case 'dashed':
|
||||
$pdf->SetLineStyle(['width' => $border, 'dash' => '3,2']);
|
||||
break;
|
||||
case 'dotted':
|
||||
$pdf->SetLineStyle(['width' => $border, 'dash' => '1,2']);
|
||||
break;
|
||||
default:
|
||||
$pdf->SetLineWidth($border);
|
||||
$pdf->SetLineStyle(['width' => $border]);
|
||||
break;
|
||||
}
|
||||
|
||||
// 1) Rahmen (und ggf. Füllung)
|
||||
if ($fill) {
|
||||
$pdf->SetFillColorArray($fillColor);
|
||||
$pdf->Rect($x, $y, $w, $h, 'DF'); // Draw + Fill
|
||||
} else {
|
||||
$pdf->Rect($x, $y, $w, $h, 'D'); // nur Rahmen
|
||||
}
|
||||
|
||||
// 2) Optionaler Textinhalt – sauber eingepasst
|
||||
if (trim($text) !== '') {
|
||||
$pdf->StartTransform();
|
||||
$pdf->Rect($x, $y, $w, $h, 'CNZ'); // Clip auf Box setzen
|
||||
|
||||
$pdf->writeHTMLCell(
|
||||
$w - 2*$pad, $h - 2*$pad,
|
||||
$x + $pad, $y + $pad,
|
||||
'<div style="font-size:9px; line-height:1.2;">'.htmlspecialchars($text).'</div>',
|
||||
0, 0, false, true, 'L', true
|
||||
);
|
||||
|
||||
$pdf->StopTransform();
|
||||
}
|
||||
|
||||
// Linienstil zurücksetzen
|
||||
$pdf->SetLineStyle(['width' => 0.3, 'dash' => 0]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Produkt-Details mit Cache holen (vermeidet doppelte API-Calls).
|
||||
*
|
||||
* @param Epirent $Epi
|
||||
* @param int|string $productPk
|
||||
* @param array $cache Referenz-Array für Produkt-Cache
|
||||
* @return object|null
|
||||
*/
|
||||
function fetchProductDetailCached($Epi, $productPk, array &$cache) {
|
||||
$key = (string)$productPk;
|
||||
if (isset($cache[$key])) {
|
||||
return $cache[$key];
|
||||
}
|
||||
try {
|
||||
$res = $Epi->requestEpiApi('/v1/product/' . $key . '?cl=' . Epirent_Mandant);
|
||||
$payload = json_decode($res, false);
|
||||
$prod = $payload->payload[0] ?? null;
|
||||
if ($prod) {
|
||||
$cache[$key] = $prod;
|
||||
return $prod;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// optional: loggen
|
||||
}
|
||||
$cache[$key] = null;
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Rekursive Hilfsfunktion: fügt Materialien (und deren Submaterialien) zu $bundle hinzu
|
||||
* und summiert alle Bruttogewichte in $bruttoWeightSum.
|
||||
*
|
||||
* @param Epirent $Epi
|
||||
* @param array $materials Materialliste (vom Produkt)
|
||||
* @param int $level aktuelle Ebene (1..MAX_BUNDLE_LEVEL)
|
||||
* @param array &$bundle Ergebnisliste (wird befüllt)
|
||||
* @param array &$cache Produkt-Cache
|
||||
* @param array &$seenStack Schutz gegen Zyklen (Stack von Produkt-Keys)
|
||||
* @param float $qtyMultiplier Multiplikator für Mengen aus übergeordneten Ebenen
|
||||
* @param float &$bruttoWeightSum Summiert alle weight_gro-Werte × Menge
|
||||
*/
|
||||
function addMaterialsRecursive($Epi, array $materials, int $level, array &$bundle, array &$cache, array &$seenStack, float $qtyMultiplier = 1.0): void
|
||||
{
|
||||
global $bruttoWeightSum;
|
||||
|
||||
$MAX_BUNDLE_LEVEL = 5;
|
||||
if ($level > $MAX_BUNDLE_LEVEL || empty($materials)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($materials as $mat) {
|
||||
$isFree = !empty($mat->is_free_material);
|
||||
$amount = isset($mat->amount) ? (float)$mat->amount : 1.0;
|
||||
$effQty = $amount * $qtyMultiplier;
|
||||
|
||||
// 🟢 1. FREIES MATERIAL → nur Name + Menge, kein weiterer Drilldown
|
||||
if ($isFree) {
|
||||
$name = isset($mat->name) ? (string)$mat->name : '(Unbenanntes freies Material)';
|
||||
$bundle[] = [
|
||||
'qty' => $effQty,
|
||||
'text' => $name,
|
||||
'level' => $level,
|
||||
'weight_kg' => null,
|
||||
];
|
||||
continue; // ⬅️ ganz wichtig: hier abbrechen
|
||||
}
|
||||
|
||||
// 🟡 2. "Normales" Produkt-Material
|
||||
$childPk = $mat->mat_product_pk ?? null;
|
||||
if (!$childPk) {
|
||||
$bundle[] = [
|
||||
'qty' => $effQty,
|
||||
'text' => '(Unbekanntes Material)',
|
||||
'level' => $level,
|
||||
'weight_kg' => null,
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Zyklen-Schutz
|
||||
$childKey = (string)$childPk;
|
||||
if (in_array($childKey, $seenStack, true)) {
|
||||
$bundle[] = [
|
||||
'qty' => $effQty,
|
||||
'text' => '[Zyklus erkannt bei Produkt ' . $childKey . ']',
|
||||
'level' => $level,
|
||||
'weight_kg' => null,
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Produktdetail abrufen (Cache-basiert)
|
||||
$child = fetchProductDetailCached($Epi, $childPk, $cache);
|
||||
|
||||
// Basisinfos
|
||||
$childName = $child->name ?? ('Produkt ' . $childKey);
|
||||
$childWeightNet = $child->tech_data->weight_net ?? null;
|
||||
$childWeightGross = $child->tech_data->weight_gro ?? null;
|
||||
$childNumber = $child->product_no ?? null;
|
||||
|
||||
// 🧮 Bruttogewicht zur globalen Summe addieren
|
||||
if (!empty($childWeightGross)) {
|
||||
$bruttoWeightSum += $effQty * (float)$childWeightGross;
|
||||
}
|
||||
|
||||
// 📦 Anzeige: Artikelnummer in Klammern voranstellen
|
||||
$displayName = $childNumber ? '(' . $childNumber . ') ' . $childName : $childName;
|
||||
|
||||
$bundle[] = [
|
||||
'qty' => $effQty,
|
||||
'text' => (string)$displayName,
|
||||
'level' => $level,
|
||||
'weight_kg' => $childWeightNet ? (float)$childWeightNet : null,
|
||||
];
|
||||
|
||||
// 🔁 Rekursion für Sub-Materialien
|
||||
if ($child && !empty($child->materials) && $level < $MAX_BUNDLE_LEVEL) {
|
||||
$seenStack[] = $childKey;
|
||||
addMaterialsRecursive($Epi, (array)$child->materials, $level + 1, $bundle, $cache, $seenStack, $effQty);
|
||||
array_pop($seenStack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Einstieg: Baut das Bundle eines Produkts bis Tiefe MAX_BUNDLE_LEVEL
|
||||
* und liefert zusätzlich die Bruttogewichtsumme zurück.
|
||||
*
|
||||
* @param Epirent $Epi
|
||||
* @param object $product
|
||||
* @param float &$bruttoWeightSum Rückgabe der aufsummierten weight_gro
|
||||
* @return array
|
||||
*/
|
||||
function buildBundleForProduct($Epi, $product, float &$bruttoWeightSum = 0.0): array
|
||||
{
|
||||
$bundle = [];
|
||||
$cache = [];
|
||||
$seen = [];
|
||||
$materials = isset($product->materials) ? (array)$product->materials : [];
|
||||
|
||||
addMaterialsRecursive($Epi, $materials, 1, $bundle, $cache, $seen, 1.0, $bruttoWeightSum);
|
||||
return $bundle;
|
||||
}
|
||||
Reference in New Issue
Block a user