kundenkarte/ajax/export_schematic_pdf.php
data 71272fa425 fix(schematic): Terminal-Farbpropagierung, Auto-Naming, PWA-Abgänge
- buildTerminalPhaseMap: Schritt 1b - Leitungen mit expliziter Farbe als
  Startpunkte (nur Gerät→Gerät, keine Abgänge)
- buildTerminalPhaseMap: Block-Durchreichung (Top↔Bottom) entfernt
- buildTerminalPhaseMap: Junction-Verbindungen (Terminal→Leitung)
  bidirektional verarbeitet via _connectionById Index
- PWA: Abgangs-Rendering mit Index-Fallback wenn source_terminal_id fehlt
- PWA: Abgangs-Labels max-height 130px, min-height 30px
- Auto-Naming: EquipmentCarrier create/update → 'R' + count
- Auto-Naming: EquipmentPanel update → 'Feld ' + count
- pwa_api.php: Hardcoded Fallbacks 'Feld'/'Hutschiene' entfernt
- pwa.js: Hutschiene Auto-Naming dynamisch aus Panel-Carrier-Anzahl
- kundenkarte.js: Carrier-Dialog Placeholder 'z.B. R1 (automatisch)'
- SW Cache auf v12.5 hochgezählt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 09:57:58 +01:00

717 lines
23 KiB
PHP
Executable file

<?php
/* Copyright (C) 2026 Alles Watt lauft
*
* PDF Export for Schematic Editor (Leitungslaufplan)
* Following DIN EN 61082 (Document structure) and DIN EN 81346 (Reference designation)
*/
$res = 0;
if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
if (!$res && file_exists("../../../../main.inc.php")) $res = @include "../../../../main.inc.php";
if (!$res) die("Include of main fails");
require_once DOL_DOCUMENT_ROOT.'/core/lib/pdf.lib.php';
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
dol_include_once('/kundenkarte/class/anlage.class.php');
dol_include_once('/kundenkarte/class/equipment.class.php');
dol_include_once('/kundenkarte/class/equipmentcarrier.class.php');
dol_include_once('/kundenkarte/class/equipmentconnection.class.php');
$langs->loadLangs(array('companies', 'kundenkarte@kundenkarte'));
// Get parameters
$anlageId = GETPOSTINT('anlage_id');
$svgContent = GETPOST('svg_content', 'restricthtml');
$format = GETPOST('format', 'alpha') ?: 'A4';
$orientation = GETPOST('orientation', 'alpha') ?: 'L'; // L=Landscape, P=Portrait
// Security check
if (!$user->hasRight('kundenkarte', 'read')) {
accessforbidden();
}
// Load Anlage data
$anlage = new Anlage($db);
if ($anlage->fetch($anlageId) <= 0) {
die('Anlage not found');
}
// Load company
$societe = new Societe($db);
$societe->fetch($anlage->fk_soc);
// Load carriers for this anlage
$carrier = new EquipmentCarrier($db);
$carriers = $carrier->fetchByAnlage($anlageId);
// Load equipment
$equipment = new Equipment($db);
$equipmentList = array();
foreach ($carriers as $c) {
$eqList = $equipment->fetchByCarrier($c->id);
$equipmentList = array_merge($equipmentList, $eqList);
}
// Load connections
$connection = new EquipmentConnection($db);
$connections = array();
foreach ($carriers as $c) {
$connList = $connection->fetchByCarrier($c->id);
$connections = array_merge($connections, $connList);
}
// Create PDF - Landscape A3 or A4 for schematic
$pdf = pdf_getInstance();
$pdf->SetCreator('Dolibarr - Kundenkarte Schaltplan');
$pdf->SetAuthor($user->getFullName($langs));
$pdf->SetTitle('Leitungslaufplan - '.$anlage->label);
// Page format
if ($format == 'A3') {
$pageWidth = 420;
$pageHeight = 297;
} else {
$pageWidth = 297;
$pageHeight = 210;
}
if ($orientation == 'P') {
$tmp = $pageWidth;
$pageWidth = $pageHeight;
$pageHeight = $tmp;
}
$pdf->SetMargins(10, 10, 10);
$pdf->SetAutoPageBreak(false);
$pdf->AddPage($orientation, array($pageWidth, $pageHeight));
// ============================================
// DIN EN 61082 / ISO 7200 Title Block (Schriftfeld)
// Position: Bottom right corner
// ============================================
$titleBlockWidth = 180;
$titleBlockHeight = 56;
$titleBlockX = $pageWidth - $titleBlockWidth - 10;
$titleBlockY = $pageHeight - $titleBlockHeight - 10;
// Draw title block frame
$pdf->SetDrawColor(0, 0, 0);
$pdf->SetLineWidth(0.5);
$pdf->Rect($titleBlockX, $titleBlockY, $titleBlockWidth, $titleBlockHeight);
// Title block grid - following DIN structure
// Row heights from bottom: 8, 8, 8, 8, 8, 8, 8 = 56mm total
$rowHeight = 8;
$rows = 7;
// Column widths: 30 | 50 | 50 | 50 = 180mm
$col1 = 30; // Labels
$col2 = 50; // Company info
$col3 = 50; // Document info
$col4 = 50; // Revision info
// Draw horizontal lines
for ($i = 1; $i < $rows; $i++) {
$y = $titleBlockY + ($i * $rowHeight);
$pdf->Line($titleBlockX, $y, $titleBlockX + $titleBlockWidth, $y);
}
// Draw vertical lines
$pdf->Line($titleBlockX + $col1, $titleBlockY, $titleBlockX + $col1, $titleBlockY + $titleBlockHeight);
$pdf->Line($titleBlockX + $col1 + $col2, $titleBlockY, $titleBlockX + $col1 + $col2, $titleBlockY + $titleBlockHeight);
$pdf->Line($titleBlockX + $col1 + $col2 + $col3, $titleBlockY, $titleBlockX + $col1 + $col2 + $col3, $titleBlockY + $titleBlockHeight);
// Fill in title block content
$pdf->SetFont('dejavusans', '', 6);
$pdf->SetTextColor(0, 0, 0);
// Row 1 (from top): Document title spanning full width
$pdf->SetFont('dejavusans', 'B', 12);
$pdf->SetXY($titleBlockX + 2, $titleBlockY + 1);
$pdf->Cell($titleBlockWidth - 4, $rowHeight - 2, 'LEITUNGSLAUFPLAN', 0, 0, 'C');
// Row 2: Installation name
$pdf->SetFont('dejavusans', 'B', 10);
$pdf->SetXY($titleBlockX + 2, $titleBlockY + $rowHeight + 1);
$pdf->Cell($titleBlockWidth - 4, $rowHeight - 2, $anlage->label, 0, 0, 'C');
// Row 3: Labels
$pdf->SetFont('dejavusans', '', 6);
$y = $titleBlockY + (2 * $rowHeight);
$pdf->SetXY($titleBlockX + 1, $y + 1);
$pdf->Cell($col1 - 2, 3, 'Erstellt', 0, 0, 'L');
$pdf->SetXY($titleBlockX + $col1 + 1, $y + 1);
$pdf->Cell($col2 - 2, 3, 'Kunde', 0, 0, 'L');
$pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 1);
$pdf->Cell($col3 - 2, 3, 'Projekt-Nr.', 0, 0, 'L');
$pdf->SetXY($titleBlockX + $col1 + $col2 + $col3 + 1, $y + 1);
$pdf->Cell($col4 - 2, 3, 'Blatt', 0, 0, 'L');
// Row 3: Values
$pdf->SetFont('dejavusans', '', 8);
$pdf->SetXY($titleBlockX + 1, $y + 4);
$pdf->Cell($col1 - 2, 4, dol_print_date(dol_now(), 'day'), 0, 0, 'L');
$pdf->SetXY($titleBlockX + $col1 + 1, $y + 4);
$pdf->Cell($col2 - 2, 4, dol_trunc($societe->name, 25), 0, 0, 'L');
$pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 4);
$pdf->Cell($col3 - 2, 4, $anlage->ref ?: '-', 0, 0, 'L');
$pdf->SetXY($titleBlockX + $col1 + $col2 + $col3 + 1, $y + 4);
$pdf->Cell($col4 - 2, 4, '1 / 1', 0, 0, 'L');
// Row 4: More labels
$y = $titleBlockY + (3 * $rowHeight);
$pdf->SetFont('dejavusans', '', 6);
$pdf->SetXY($titleBlockX + 1, $y + 1);
$pdf->Cell($col1 - 2, 3, 'Bearbeiter', 0, 0, 'L');
$pdf->SetXY($titleBlockX + $col1 + 1, $y + 1);
$pdf->Cell($col2 - 2, 3, 'Adresse', 0, 0, 'L');
$pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 1);
$pdf->Cell($col3 - 2, 3, 'Anlage', 0, 0, 'L');
$pdf->SetXY($titleBlockX + $col1 + $col2 + $col3 + 1, $y + 1);
$pdf->Cell($col4 - 2, 3, 'Revision', 0, 0, 'L');
// Row 4: Values
$pdf->SetFont('dejavusans', '', 8);
$pdf->SetXY($titleBlockX + 1, $y + 4);
$pdf->Cell($col1 - 2, 4, dol_trunc($user->getFullName($langs), 15), 0, 0, 'L');
$pdf->SetXY($titleBlockX + $col1 + 1, $y + 4);
$address = trim($societe->address.' '.$societe->zip.' '.$societe->town);
$pdf->Cell($col2 - 2, 4, dol_trunc($address, 25), 0, 0, 'L');
$pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 4);
$pdf->Cell($col3 - 2, 4, $anlage->type_label ?: '-', 0, 0, 'L');
$pdf->SetXY($titleBlockX + $col1 + $col2 + $col3 + 1, $y + 4);
$pdf->Cell($col4 - 2, 4, 'A', 0, 0, 'L');
// Row 5: Equipment count
$y = $titleBlockY + (4 * $rowHeight);
$pdf->SetFont('dejavusans', '', 6);
$pdf->SetXY($titleBlockX + 1, $y + 1);
$pdf->Cell($col1 - 2, 3, 'Komponenten', 0, 0, 'L');
$pdf->SetXY($titleBlockX + $col1 + 1, $y + 1);
$pdf->Cell($col2 - 2, 3, 'Verbindungen', 0, 0, 'L');
$pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 1);
$pdf->Cell($col3 - 2, 3, 'Hutschienen', 0, 0, 'L');
$pdf->SetXY($titleBlockX + $col1 + $col2 + $col3 + 1, $y + 1);
$pdf->Cell($col4 - 2, 3, 'Format', 0, 0, 'L');
$pdf->SetFont('dejavusans', '', 8);
$pdf->SetXY($titleBlockX + 1, $y + 4);
$pdf->Cell($col1 - 2, 4, count($equipmentList), 0, 0, 'L');
$pdf->SetXY($titleBlockX + $col1 + 1, $y + 4);
$pdf->Cell($col2 - 2, 4, count($connections), 0, 0, 'L');
$pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 4);
$pdf->Cell($col3 - 2, 4, count($carriers), 0, 0, 'L');
$pdf->SetXY($titleBlockX + $col1 + $col2 + $col3 + 1, $y + 4);
$pdf->Cell($col4 - 2, 4, $format.' '.$orientation, 0, 0, 'L');
// Row 6: Norm reference
$y = $titleBlockY + (5 * $rowHeight);
$pdf->SetFont('dejavusans', '', 6);
$pdf->SetXY($titleBlockX + 1, $y + 2);
$pdf->Cell($titleBlockWidth - 2, 4, 'Erstellt nach DIN EN 61082 / DIN EN 81346', 0, 0, 'C');
// Row 7: Company info
$y = $titleBlockY + (6 * $rowHeight);
$pdf->SetFont('dejavusans', 'B', 7);
$pdf->SetXY($titleBlockX + 1, $y + 2);
$pdf->Cell($titleBlockWidth - 2, 4, $mysoc->name, 0, 0, 'C');
// ============================================
// Draw the Schematic Content Area
// ============================================
$schematicX = 10;
$schematicY = 10;
$schematicWidth = $pageWidth - 20;
$schematicHeight = $titleBlockY - 15;
// Draw frame around schematic area
$pdf->SetDrawColor(0, 0, 0);
$pdf->SetLineWidth(0.3);
$pdf->Rect($schematicX, $schematicY, $schematicWidth, $schematicHeight);
// If SVG content provided, embed it
if (!empty($svgContent)) {
// Clean SVG for TCPDF
$svgContent = preg_replace('/<\?xml[^>]*\?>/', '', $svgContent);
$svgContent = preg_replace('/<!DOCTYPE[^>]*>/', '', $svgContent);
// Try to embed SVG
try {
// Scale SVG to fit in schematic area
$pdf->ImageSVG('@'.$svgContent, $schematicX + 2, $schematicY + 2, $schematicWidth - 4, $schematicHeight - 4, '', '', '', 0, false);
} catch (Exception $e) {
// SVG embedding failed - draw placeholder
$pdf->SetFont('dejavusans', 'I', 10);
$pdf->SetXY($schematicX + 10, $schematicY + 10);
$pdf->Cell(0, 10, 'SVG konnte nicht eingebettet werden: '.$e->getMessage(), 0, 1);
}
} else {
// Draw schematic manually if no SVG provided
drawSchematicContent($pdf, $carriers, $equipmentList, $connections, $schematicX, $schematicY, $schematicWidth, $schematicHeight);
}
// ============================================
// Add Wiring List on second page (Verdrahtungsliste)
// ============================================
if (count($connections) > 0) {
$pdf->AddPage($orientation, array($pageWidth, $pageHeight));
// Title
$pdf->SetFont('dejavusans', 'B', 14);
$pdf->SetXY(10, 10);
$pdf->Cell(0, 8, 'VERDRAHTUNGSLISTE / KLEMMENPLAN', 0, 1, 'L');
$pdf->SetFont('dejavusans', '', 9);
$pdf->SetXY(10, 18);
$pdf->Cell(0, 5, $anlage->label.' - '.$societe->name, 0, 1, 'L');
// Table header
$pdf->SetY(28);
$pdf->SetFont('dejavusans', 'B', 8);
$pdf->SetFillColor(220, 220, 220);
$colWidths = array(15, 35, 25, 35, 25, 25, 30, 30);
$headers = array('Nr.', 'Von (Quelle)', 'Klemme', 'Nach (Ziel)', 'Klemme', 'Typ', 'Leitung', 'Bemerkung');
$x = 10;
for ($i = 0; $i < count($headers); $i++) {
$pdf->SetXY($x, 28);
$pdf->Cell($colWidths[$i], 6, $headers[$i], 1, 0, 'C', true);
$x += $colWidths[$i];
}
// Table content
$pdf->SetFont('dejavusans', '', 7);
$y = 34;
$lineNum = 1;
// Build equipment lookup
$eqLookup = array();
foreach ($equipmentList as $eq) {
$eqLookup[$eq->id] = $eq;
}
foreach ($connections as $conn) {
// Skip rails/busbars in wiring list (they're separate)
if ($conn->is_rail) continue;
$sourceName = '-';
$sourceTerminal = $conn->source_terminal ?: '-';
$targetName = '-';
$targetTerminal = $conn->target_terminal ?: '-';
if ($conn->fk_source && isset($eqLookup[$conn->fk_source])) {
$sourceName = $eqLookup[$conn->fk_source]->label ?: $eqLookup[$conn->fk_source]->type_label_short;
}
if ($conn->fk_target && isset($eqLookup[$conn->fk_target])) {
$targetName = $eqLookup[$conn->fk_target]->label ?: $eqLookup[$conn->fk_target]->type_label_short;
}
// Connection type / medium
$connType = $conn->connection_type ?: '-';
$medium = trim($conn->medium_type.' '.$conn->medium_spec);
if (empty($medium)) $medium = '-';
$remark = $conn->output_label ?: '';
if ($y > $pageHeight - 25) {
// New page
$pdf->AddPage($orientation, array($pageWidth, $pageHeight));
$y = 10;
// Repeat header
$pdf->SetFont('dejavusans', 'B', 8);
$x = 10;
for ($i = 0; $i < count($headers); $i++) {
$pdf->SetXY($x, $y);
$pdf->Cell($colWidths[$i], 6, $headers[$i], 1, 0, 'C', true);
$x += $colWidths[$i];
}
$y += 6;
$pdf->SetFont('dejavusans', '', 7);
}
$x = 10;
$pdf->SetXY($x, $y);
$pdf->Cell($colWidths[0], 5, $lineNum, 1, 0, 'C');
$x += $colWidths[0];
$pdf->SetXY($x, $y);
$pdf->Cell($colWidths[1], 5, dol_trunc($sourceName, 18), 1, 0, 'L');
$x += $colWidths[1];
$pdf->SetXY($x, $y);
$pdf->Cell($colWidths[2], 5, $sourceTerminal, 1, 0, 'C');
$x += $colWidths[2];
$pdf->SetXY($x, $y);
$pdf->Cell($colWidths[3], 5, dol_trunc($targetName, 18), 1, 0, 'L');
$x += $colWidths[3];
$pdf->SetXY($x, $y);
$pdf->Cell($colWidths[4], 5, $targetTerminal, 1, 0, 'C');
$x += $colWidths[4];
$pdf->SetXY($x, $y);
$pdf->Cell($colWidths[5], 5, $connType, 1, 0, 'C');
$x += $colWidths[5];
$pdf->SetXY($x, $y);
$pdf->Cell($colWidths[6], 5, dol_trunc($medium, 15), 1, 0, 'L');
$x += $colWidths[6];
$pdf->SetXY($x, $y);
$pdf->Cell($colWidths[7], 5, dol_trunc($remark, 15), 1, 0, 'L');
$y += 5;
$lineNum++;
}
// Add busbars section if any
$busbars = array_filter($connections, function($c) { return $c->is_rail; });
if (count($busbars) > 0) {
$y += 10;
if ($y > $pageHeight - 40) {
$pdf->AddPage($orientation, array($pageWidth, $pageHeight));
$y = 10;
}
$pdf->SetFont('dejavusans', 'B', 10);
$pdf->SetXY(10, $y);
$pdf->Cell(0, 6, 'SAMMELSCHIENEN / PHASENSCHIENEN', 0, 1, 'L');
$y += 8;
$pdf->SetFont('dejavusans', 'B', 8);
$bbHeaders = array('Nr.', 'Bezeichnung', 'Typ', 'Von TE', 'Bis TE', 'Phasen', 'Ausnahmen');
$bbWidths = array(15, 50, 30, 20, 20, 30, 50);
$x = 10;
for ($i = 0; $i < count($bbHeaders); $i++) {
$pdf->SetXY($x, $y);
$pdf->Cell($bbWidths[$i], 6, $bbHeaders[$i], 1, 0, 'C', true);
$x += $bbWidths[$i];
}
$y += 6;
$pdf->SetFont('dejavusans', '', 7);
$bbNum = 1;
foreach ($busbars as $bb) {
$x = 10;
$pdf->SetXY($x, $y);
$pdf->Cell($bbWidths[0], 5, $bbNum, 1, 0, 'C');
$x += $bbWidths[0];
$pdf->SetXY($x, $y);
$pdf->Cell($bbWidths[1], 5, $bb->output_label ?: 'Sammelschiene '.$bbNum, 1, 0, 'L');
$x += $bbWidths[1];
$pdf->SetXY($x, $y);
$pdf->Cell($bbWidths[2], 5, $bb->connection_type ?: '-', 1, 0, 'C');
$x += $bbWidths[2];
$pdf->SetXY($x, $y);
$pdf->Cell($bbWidths[3], 5, $bb->rail_start_te ?: '-', 1, 0, 'C');
$x += $bbWidths[3];
$pdf->SetXY($x, $y);
$pdf->Cell($bbWidths[4], 5, $bb->rail_end_te ?: '-', 1, 0, 'C');
$x += $bbWidths[4];
$pdf->SetXY($x, $y);
$pdf->Cell($bbWidths[5], 5, $bb->rail_phases ?: '-', 1, 0, 'C');
$x += $bbWidths[5];
$pdf->SetXY($x, $y);
$pdf->Cell($bbWidths[6], 5, $bb->excluded_te ?: '-', 1, 0, 'L');
$y += 5;
$bbNum++;
}
}
}
// Output PDF
$filename = 'Leitungslaufplan_'.dol_sanitizeFileName($anlage->label).'_'.date('Y-m-d').'.pdf';
$pdf->Output($filename, 'D');
/**
* Draw schematic content directly in PDF
* Shows only the actual equipment and connections from the database (what was drawn in the editor)
*/
function drawSchematicContent(&$pdf, $carriers, $equipment, $connections, $startX, $startY, $width, $height) {
// Phase colors (DIN VDE compliant)
$phaseColors = array(
'L1' => array(139, 69, 19), // Brown
'L2' => array(0, 0, 0), // Black
'L3' => array(128, 128, 128), // Gray
'N' => array(0, 102, 204), // Blue
'PE' => array(0, 128, 0) // Green (simplified from green-yellow)
);
// Layout constants
$teWidth = 10; // mm per TE
$equipmentStartY = $startY + 20;
$blockWidth = 8;
$blockHeight = 20;
// Calculate total width needed
$maxTE = 0;
foreach ($carriers as $carrier) {
$maxTE = max($maxTE, $carrier->total_te ?: 12);
}
$contentWidth = min($width - 40, $maxTE * $teWidth + 40);
$contentStartX = $startX + 20;
// ========================================
// Draw equipment and connections
// ========================================
$carrierIndex = 0;
foreach ($carriers as $carrier) {
$carrierY = $equipmentStartY + $carrierIndex * 50;
$carrierX = $contentStartX;
$totalTE = $carrier->total_te ?: 12;
// Carrier label
$pdf->SetFont('dejavusans', '', 6);
$pdf->SetTextColor(100, 100, 100);
$pdf->SetXY($carrierX - 15, $carrierY + $blockHeight / 2 - 2);
$pdf->Cell(12, 4, $carrier->label ?: 'H'.($carrierIndex+1), 0, 0, 'R');
// Get equipment on this carrier
$carrierEquipment = array_filter($equipment, function($eq) use ($carrier) {
return $eq->fk_carrier == $carrier->id;
});
// Get busbars for this carrier
$carrierBusbars = array_filter($connections, function($c) use ($carrier) {
return $c->is_rail && $c->fk_carrier == $carrier->id;
});
// Sort equipment by position
usort($carrierEquipment, function($a, $b) {
return ($a->position_te ?: 1) - ($b->position_te ?: 1);
});
// Draw each equipment
foreach ($carrierEquipment as $eq) {
$eqPosTE = $eq->position_te ?: 1;
$eqWidthTE = $eq->width_te ?: 1;
$eqX = $carrierX + ($eqPosTE - 1) * $teWidth;
$eqWidth = $eqWidthTE * $teWidth - 2;
// Equipment block
$color = $eq->type_color ?: '#3498db';
list($r, $g, $b) = sscanf($color, "#%02x%02x%02x");
$pdf->SetFillColor($r ?: 52, $g ?: 152, $b ?: 219);
$pdf->Rect($eqX, $carrierY, $eqWidth, $blockHeight, 'F');
// Equipment label
$pdf->SetFont('dejavusans', 'B', 5);
$pdf->SetTextColor(255, 255, 255);
$label = $eq->type_label_short ?: $eq->label;
$pdf->SetXY($eqX, $carrierY + 3);
$pdf->Cell($eqWidth, 4, dol_trunc($label, 8), 0, 0, 'C');
// Second line label
if ($eq->label && $eq->type_label_short) {
$pdf->SetFont('dejavusans', '', 4);
$pdf->SetXY($eqX, $carrierY + 8);
$pdf->Cell($eqWidth, 3, dol_trunc($eq->label, 10), 0, 0, 'C');
}
$pdf->SetTextColor(0, 0, 0);
// Consumer label below equipment
$pdf->SetFont('dejavusans', '', 5);
$pdf->SetTextColor(80, 80, 80);
$pdf->SetXY($eqX - 2, $carrierY + $blockHeight + 1);
$consumerLabel = $eq->label ?: '';
$pdf->Cell($eqWidth + 4, 4, dol_trunc($consumerLabel, 12), 0, 0, 'C');
}
// Draw busbars (Phasenschienen) for this carrier
foreach ($carrierBusbars as $busbar) {
$busbarStartTE = $busbar->rail_start_te ?: 1;
$busbarEndTE = $busbar->rail_end_te ?: $busbarStartTE;
$busbarX = $carrierX + ($busbarStartTE - 1) * $teWidth;
$busbarWidth = ($busbarEndTE - $busbarStartTE + 1) * $teWidth;
$busbarY = $carrierY - 5;
// Busbar color
$phase = $busbar->rail_phases ?: $busbar->connection_type ?: 'L1';
$color = $phaseColors[$phase] ?? array(200, 100, 50);
$pdf->SetFillColor($color[0], $color[1], $color[2]);
$pdf->Rect($busbarX, $busbarY, $busbarWidth, 3, 'F');
// Draw vertical taps from busbar to equipment
$pdf->SetDrawColor($color[0], $color[1], $color[2]);
for ($te = $busbarStartTE; $te <= $busbarEndTE; $te++) {
// Check if there's equipment at this TE
$hasEquipment = false;
foreach ($carrierEquipment as $eq) {
$eqStart = $eq->position_te ?: 1;
$eqEnd = $eqStart + ($eq->width_te ?: 1) - 1;
if ($te >= $eqStart && $te <= $eqEnd) {
$hasEquipment = true;
break;
}
}
if ($hasEquipment) {
$tapX = $carrierX + ($te - 1) * $teWidth + $teWidth / 2;
$pdf->Line($tapX, $busbarY + 3, $tapX, $carrierY);
}
}
// Busbar label
$pdf->SetFont('dejavusans', 'B', 5);
$pdf->SetTextColor(255, 255, 255);
$pdf->SetXY($busbarX, $busbarY - 0.5);
$pdf->Cell($busbarWidth, 4, $phase, 0, 0, 'C');
}
// ========================================
// Draw Inputs (Anschlusspunkte) and Outputs (Abgänge)
// ========================================
// Get non-rail connections for this carrier's equipment
$carrierEqIds = array_map(function($eq) { return $eq->id; }, $carrierEquipment);
foreach ($connections as $conn) {
if ($conn->is_rail) continue;
// ANSCHLUSSPUNKT (Input) - fk_source is NULL, fk_target exists
if (empty($conn->fk_source) && !empty($conn->fk_target)) {
// Find target equipment
$targetEq = null;
foreach ($carrierEquipment as $eq) {
if ($eq->id == $conn->fk_target) {
$targetEq = $eq;
break;
}
}
if (!$targetEq) continue;
$eqPosTE = $targetEq->position_te ?: 1;
$eqWidthTE = $targetEq->width_te ?: 1;
$eqCenterX = $carrierX + ($eqPosTE - 1) * $teWidth + ($eqWidthTE * $teWidth) / 2;
$lineStartY = $carrierY - 18;
$lineEndY = $carrierY;
// Phase color
$phase = $conn->connection_type ?: 'L1';
$color = $phaseColors[$phase] ?? array(100, 100, 100);
$pdf->SetDrawColor($color[0], $color[1], $color[2]);
$pdf->SetFillColor($color[0], $color[1], $color[2]);
// Vertical line from top
$pdf->Line($eqCenterX, $lineStartY, $eqCenterX, $lineEndY);
// Circle at top (source indicator)
$pdf->Circle($eqCenterX, $lineStartY, 1.5, 0, 360, 'F');
// Phase label
$pdf->SetFont('dejavusans', 'B', 7);
$pdf->SetTextColor($color[0], $color[1], $color[2]);
$pdf->SetXY($eqCenterX - 5, $lineStartY - 5);
$pdf->Cell(10, 4, $phase, 0, 0, 'C');
// Optional label
if (!empty($conn->output_label)) {
$pdf->SetFont('dejavusans', '', 5);
$pdf->SetTextColor(100, 100, 100);
$pdf->SetXY($eqCenterX + 3, $lineStartY - 4);
$pdf->Cell(20, 3, dol_trunc($conn->output_label, 12), 0, 0, 'L');
}
}
// ABGANG (Output) - fk_source exists, fk_target is NULL
if (!empty($conn->fk_source) && empty($conn->fk_target)) {
// Find source equipment
$sourceEq = null;
foreach ($carrierEquipment as $eq) {
if ($eq->id == $conn->fk_source) {
$sourceEq = $eq;
break;
}
}
if (!$sourceEq) continue;
$eqPosTE = $sourceEq->position_te ?: 1;
$eqWidthTE = $sourceEq->width_te ?: 1;
$eqCenterX = $carrierX + ($eqPosTE - 1) * $teWidth + ($eqWidthTE * $teWidth) / 2;
$lineStartY = $carrierY + $blockHeight;
$lineLength = 18;
$lineEndY = $lineStartY + $lineLength;
// Phase color
$phase = $conn->connection_type ?: 'L1N';
$color = $phaseColors[$phase] ?? $phaseColors['L1'] ?? array(139, 69, 19);
$pdf->SetDrawColor($color[0], $color[1], $color[2]);
$pdf->SetFillColor($color[0], $color[1], $color[2]);
// Vertical line going down
$pdf->Line($eqCenterX, $lineStartY, $eqCenterX, $lineEndY);
// Arrow at end
$pdf->Polygon(array(
$eqCenterX - 1.5, $lineEndY - 2,
$eqCenterX, $lineEndY,
$eqCenterX + 1.5, $lineEndY - 2
), 'F');
// Left label: Bezeichnung (rotated text not easy in TCPDF, use horizontal)
if (!empty($conn->output_label)) {
$pdf->SetFont('dejavusans', 'B', 5);
$pdf->SetTextColor(0, 0, 0);
$pdf->SetXY($eqCenterX - 15, $lineEndY + 1);
$pdf->Cell(30, 3, dol_trunc($conn->output_label, 15), 0, 0, 'C');
}
// Right label: Kabeltyp + Größe
$cableInfo = trim(($conn->medium_type ?: '') . ' ' . ($conn->medium_spec ?: ''));
if (!empty($cableInfo)) {
$pdf->SetFont('dejavusans', '', 4);
$pdf->SetTextColor(100, 100, 100);
$pdf->SetXY($eqCenterX - 15, $lineEndY + 4);
$pdf->Cell(30, 3, dol_trunc($cableInfo, 18), 0, 0, 'C');
}
// Phase type
$pdf->SetFont('dejavusans', 'B', 5);
$pdf->SetTextColor($color[0], $color[1], $color[2]);
$pdf->SetXY($eqCenterX - 5, $lineEndY + 7);
$pdf->Cell(10, 3, $phase, 0, 0, 'C');
}
}
$carrierIndex++;
}
$pdf->SetTextColor(0, 0, 0);
// ========================================
// Legend - show phase colors used in busbars
// ========================================
$legendY = $startY + $height - 20;
$pdf->SetFont('dejavusans', '', 6);
$pdf->SetXY($startX + 5, $legendY);
$pdf->Cell(0, 4, 'Phasenfarben nach DIN VDE:', 0, 1, 'L');
$legendX = $startX + 5;
$phases = array('L1', 'L2', 'L3', 'N', 'PE');
foreach ($phases as $idx => $phase) {
$color = $phaseColors[$phase];
$pdf->SetFillColor($color[0], $color[1], $color[2]);
$pdf->Rect($legendX + $idx * 25, $legendY + 5, 8, 3, 'F');
$pdf->SetTextColor($color[0], $color[1], $color[2]);
$pdf->SetXY($legendX + $idx * 25 + 10, $legendY + 4);
$pdf->Cell(12, 4, $phase, 0, 0, 'L');
}
$pdf->SetTextColor(0, 0, 0);
}