Neues separates Feature: Stromlaufplan in aufgelöster Darstellung als PDF.
- WiringDiagramAnalyzer: PHP-Port der JS Phase-Map-Logik, Strompfad-Tracing
- WiringDiagramRenderer: TCPDF-Zeichnung mit VDE-Symbolen (LS, FI/RCD)
- PDF enthält: Schaltplan, Abgangsverzeichnis pro Hutschiene, Legende
- Abgangsnummern im Format R{Reihe}.{Position}
- Grüner Button in Schaltplan-Editor (Kunden + Kontakte)
- CLAUDE.md: Dateistruktur komplett überarbeitet, neue Features dokumentiert
- Version 9.7
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1239 lines
40 KiB
PHP
1239 lines
40 KiB
PHP
<?php
|
|
/* Copyright (C) 2026 Alles Watt lauft
|
|
*
|
|
* Leitungslaufplan (Stromlaufplan in aufgelöster Darstellung)
|
|
* PDF-Export nach DIN EN 61082
|
|
*
|
|
* Dieses Feature ist vollständig separiert und kann ohne Auswirkungen
|
|
* auf den restlichen Code entfernt werden.
|
|
*/
|
|
|
|
dol_include_once('/kundenkarte/class/anlage.class.php');
|
|
dol_include_once('/kundenkarte/class/equipment.class.php');
|
|
dol_include_once('/kundenkarte/class/equipmentpanel.class.php');
|
|
dol_include_once('/kundenkarte/class/equipmentcarrier.class.php');
|
|
dol_include_once('/kundenkarte/class/equipmentconnection.class.php');
|
|
|
|
|
|
/**
|
|
* Phasenfarben nach DIN VDE (RGB-Arrays für TCPDF)
|
|
*/
|
|
function getPhaseColorRGB($phase)
|
|
{
|
|
$colors = array(
|
|
'L1' => array(139, 69, 19), // Braun
|
|
'L2' => array(26, 26, 26), // Schwarz
|
|
'L3' => array(102, 102, 102), // Grau
|
|
'N' => array(0, 102, 204), // Blau
|
|
'PE' => array(39, 174, 96), // Grün
|
|
'LN' => array(139, 69, 19), // Braun
|
|
'3P' => array(155, 89, 182), // Lila
|
|
'3P+N' => array(52, 73, 94), // Dunkelblau
|
|
);
|
|
$p = strtoupper($phase);
|
|
return isset($colors[$p]) ? $colors[$p] : array(136, 136, 136);
|
|
}
|
|
|
|
/**
|
|
* Phase-Labels parsen (PHP-Port von JS parsePhaseLabels)
|
|
*/
|
|
function parsePhaseLabels($phases)
|
|
{
|
|
if (empty($phases)) return array();
|
|
$p = strtoupper(trim($phases));
|
|
|
|
$map = array(
|
|
'3P' => array('L1', 'L2', 'L3'),
|
|
'L1L2L3' => array('L1', 'L2', 'L3'),
|
|
'3P+N' => array('L1', 'L2', 'L3', 'N'),
|
|
'3PN' => array('L1', 'L2', 'L3', 'N'),
|
|
'3P+N+PE' => array('L1', 'L2', 'L3', 'N', 'PE'),
|
|
'3PNPE' => array('L1', 'L2', 'L3', 'N', 'PE'),
|
|
'L1N' => array('L1', 'N'),
|
|
'L1+N' => array('L1', 'N'),
|
|
'L1' => array('L1'),
|
|
'L2' => array('L2'),
|
|
'L3' => array('L3'),
|
|
'N' => array('N'),
|
|
'PE' => array('PE'),
|
|
);
|
|
|
|
if (isset($map[$p])) return $map[$p];
|
|
if (strpos($p, '+') !== false) return explode('+', $p);
|
|
if (strpos($p, ',') !== false) return array_map('trim', explode(',', $p));
|
|
return array($phases);
|
|
}
|
|
|
|
/**
|
|
* Terminals eines Equipment ermitteln (PHP-Port von JS getTerminals)
|
|
*/
|
|
function getEquipmentTerminals($eq)
|
|
{
|
|
// terminals_config aus dem Equipment-Typ
|
|
$terminalsConfig = isset($eq->terminals_config) ? $eq->terminals_config : '';
|
|
if (!empty($terminalsConfig)) {
|
|
// Literale \r\n bereinigen
|
|
$configStr = str_replace(array("\\r\\n", "\\r", "\\n", "\\t"), array(' ', ' ', ' ', ''), $terminalsConfig);
|
|
$config = @json_decode($configStr, true);
|
|
if (is_array($config) && isset($config['terminals'])) {
|
|
return $config['terminals'];
|
|
}
|
|
if (is_array($config)) {
|
|
$terminals = array();
|
|
if (isset($config['inputs'])) {
|
|
foreach ($config['inputs'] as $t) {
|
|
$terminals[] = array('id' => $t['id'], 'label' => $t['label'] ?? '●', 'pos' => 'top');
|
|
}
|
|
}
|
|
if (isset($config['outputs'])) {
|
|
foreach ($config['outputs'] as $t) {
|
|
$terminals[] = array('id' => $t['id'], 'label' => $t['label'] ?? '●', 'pos' => 'bottom');
|
|
}
|
|
}
|
|
if (!empty($terminals)) return $terminals;
|
|
}
|
|
}
|
|
|
|
// Default-Terminals nach type_ref
|
|
$typeRef = strtoupper(isset($eq->type_ref) ? $eq->type_ref : '');
|
|
$defaults = array(
|
|
'LS' => array(array('id'=>'t1','pos'=>'top'), array('id'=>'t2','pos'=>'bottom')),
|
|
'FI' => array(
|
|
array('id'=>'t1','pos'=>'top'), array('id'=>'t2','pos'=>'top'),
|
|
array('id'=>'t3','pos'=>'top'), array('id'=>'t4','pos'=>'top'),
|
|
array('id'=>'t5','pos'=>'bottom'), array('id'=>'t6','pos'=>'bottom'),
|
|
array('id'=>'t7','pos'=>'bottom'), array('id'=>'t8','pos'=>'bottom'),
|
|
),
|
|
'FI4P' => array(
|
|
array('id'=>'t1','pos'=>'top'), array('id'=>'t2','pos'=>'top'),
|
|
array('id'=>'t3','pos'=>'top'), array('id'=>'t4','pos'=>'top'),
|
|
array('id'=>'t5','pos'=>'bottom'), array('id'=>'t6','pos'=>'bottom'),
|
|
array('id'=>'t7','pos'=>'bottom'), array('id'=>'t8','pos'=>'bottom'),
|
|
),
|
|
'LS3P' => array(
|
|
array('id'=>'t1','pos'=>'top'), array('id'=>'t2','pos'=>'top'), array('id'=>'t3','pos'=>'top'),
|
|
array('id'=>'t4','pos'=>'bottom'), array('id'=>'t5','pos'=>'bottom'), array('id'=>'t6','pos'=>'bottom'),
|
|
),
|
|
'KLEMME' => array(array('id'=>'t1','pos'=>'top'), array('id'=>'t2','pos'=>'bottom')),
|
|
);
|
|
|
|
if (isset($defaults[$typeRef])) return $defaults[$typeRef];
|
|
if (strpos($typeRef, 'FI') !== false || strpos($typeRef, 'RCD') !== false) {
|
|
return (strpos($typeRef, '4P') !== false) ? $defaults['FI4P'] : $defaults['FI'];
|
|
}
|
|
return $defaults['LS'];
|
|
}
|
|
|
|
|
|
/**
|
|
* WiringDiagramAnalyzer - Analysiert die Schaltplan-Daten und baut Strompfade
|
|
*/
|
|
class WiringDiagramAnalyzer
|
|
{
|
|
private $db;
|
|
private $anlageId;
|
|
|
|
// Rohdaten
|
|
public $panels = array();
|
|
public $carriers = array();
|
|
public $allEquipment = array();
|
|
public $allConnections = array();
|
|
|
|
// Lookup-Maps
|
|
private $equipmentById = array();
|
|
private $carrierById = array();
|
|
private $carrierByEquipmentId = array();
|
|
|
|
// Phase-Propagierung
|
|
private $terminalPhaseMap = array();
|
|
private $terminalColorMap = array();
|
|
|
|
// Ergebnis
|
|
public $circuitPaths = array();
|
|
|
|
public function __construct($db, $anlageId)
|
|
{
|
|
$this->db = $db;
|
|
$this->anlageId = (int) $anlageId;
|
|
}
|
|
|
|
/**
|
|
* Alle Daten laden
|
|
*/
|
|
public function loadData()
|
|
{
|
|
// Panels
|
|
$panelObj = new EquipmentPanel($this->db);
|
|
$this->panels = $panelObj->fetchByAnlage($this->anlageId);
|
|
|
|
// Carriers
|
|
$carrierObj = new EquipmentCarrier($this->db);
|
|
$this->carriers = $carrierObj->fetchByAnlage($this->anlageId);
|
|
|
|
// Equipment + Connections pro Carrier
|
|
$eqObj = new Equipment($this->db);
|
|
$connObj = new EquipmentConnection($this->db);
|
|
|
|
foreach ($this->carriers as $c) {
|
|
$eqList = $eqObj->fetchByCarrier($c->id);
|
|
$connList = $connObj->fetchByCarrier($c->id);
|
|
$this->allEquipment = array_merge($this->allEquipment, $eqList);
|
|
$this->allConnections = array_merge($this->allConnections, $connList);
|
|
}
|
|
|
|
// Lookup-Maps bauen
|
|
foreach ($this->allEquipment as $eq) {
|
|
$this->equipmentById[$eq->id] = $eq;
|
|
}
|
|
foreach ($this->carriers as $c) {
|
|
$this->carrierById[$c->id] = $c;
|
|
}
|
|
foreach ($this->allEquipment as $eq) {
|
|
$this->carrierByEquipmentId[$eq->id] = isset($this->carrierById[$eq->fk_carrier])
|
|
? $this->carrierById[$eq->fk_carrier] : null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Analyse durchführen: Phase-Map bauen + Strompfade finden
|
|
*/
|
|
public function analyze()
|
|
{
|
|
$this->buildPhaseMap();
|
|
$this->buildCircuitPaths();
|
|
}
|
|
|
|
/**
|
|
* PHP-Port von JS buildTerminalPhaseMap()
|
|
* Propagiert Phasen von Eingängen durch Blöcke, Leitungen und Busbars
|
|
*/
|
|
private function buildPhaseMap()
|
|
{
|
|
$this->terminalPhaseMap = array();
|
|
$this->terminalColorMap = array();
|
|
|
|
$validPhases = array('L1', 'L2', 'L3', 'N', 'PE');
|
|
|
|
// Hilfsfunktionen als Closures
|
|
$setPhase = function($eqId, $termId, $phase, $color = null) use ($validPhases) {
|
|
if (!isset($this->terminalPhaseMap[$eqId])) $this->terminalPhaseMap[$eqId] = array();
|
|
if (!isset($this->terminalColorMap[$eqId])) $this->terminalColorMap[$eqId] = array();
|
|
if (isset($this->terminalPhaseMap[$eqId][$termId])) return false;
|
|
$this->terminalPhaseMap[$eqId][$termId] = $phase;
|
|
$this->terminalColorMap[$eqId][$termId] = $color ?: '#888';
|
|
return true;
|
|
};
|
|
|
|
$forcePhase = function($eqId, $termId, $phase, $color = null) {
|
|
if (!isset($this->terminalPhaseMap[$eqId])) $this->terminalPhaseMap[$eqId] = array();
|
|
if (!isset($this->terminalColorMap[$eqId])) $this->terminalColorMap[$eqId] = array();
|
|
if (isset($this->terminalPhaseMap[$eqId][$termId]) && $this->terminalPhaseMap[$eqId][$termId] === $phase) return false;
|
|
$this->terminalPhaseMap[$eqId][$termId] = $phase;
|
|
$this->terminalColorMap[$eqId][$termId] = $color ?: '#888';
|
|
return true;
|
|
};
|
|
|
|
$getColor = function($eqId, $termId) {
|
|
return isset($this->terminalColorMap[$eqId][$termId]) ? $this->terminalColorMap[$eqId][$termId] : null;
|
|
};
|
|
|
|
// Schritt 1: Anschlusspunkte (Inputs) als Startpunkte
|
|
foreach ($this->allConnections as $conn) {
|
|
if ($conn->is_rail) continue;
|
|
if (!empty($conn->fk_source)) continue;
|
|
if (empty($conn->fk_target) || empty($conn->target_terminal_id)) continue;
|
|
|
|
$phase = strtoupper($conn->connection_type ?: '');
|
|
if (!in_array($phase, $validPhases)) continue;
|
|
|
|
$rgb = getPhaseColorRGB($phase);
|
|
$inputColor = !empty($conn->color) ? $conn->color : sprintf('#%02x%02x%02x', $rgb[0], $rgb[1], $rgb[2]);
|
|
$setPhase($conn->fk_target, $conn->target_terminal_id, $phase, $inputColor);
|
|
}
|
|
|
|
// Schritt 2: Iterativ propagieren
|
|
$changed = true;
|
|
$iterations = 0;
|
|
while ($changed && $iterations++ < 20) {
|
|
$changed = false;
|
|
|
|
// Block-Durchreichung (top ↔ bottom)
|
|
foreach ($this->allEquipment as $eq) {
|
|
$terminals = getEquipmentTerminals($eq);
|
|
$topTerminals = array_values(array_filter($terminals, function($t) { return ($t['pos'] ?? '') === 'top'; }));
|
|
$bottomTerminals = array_values(array_filter($terminals, function($t) { return ($t['pos'] ?? '') === 'bottom'; }));
|
|
|
|
$pairCount = min(count($topTerminals), count($bottomTerminals));
|
|
for ($i = 0; $i < $pairCount; $i++) {
|
|
$topId = $topTerminals[$i]['id'] ?? 't'.($i+1);
|
|
$botId = $bottomTerminals[$i]['id'] ?? 't'.($i + count($topTerminals) + 1);
|
|
$topPhase = $this->terminalPhaseMap[$eq->id][$topId] ?? null;
|
|
$botPhase = $this->terminalPhaseMap[$eq->id][$botId] ?? null;
|
|
|
|
if ($topPhase && !$botPhase) {
|
|
if ($setPhase($eq->id, $botId, $topPhase, $getColor($eq->id, $topId))) $changed = true;
|
|
} elseif ($botPhase && !$topPhase) {
|
|
if ($setPhase($eq->id, $topId, $botPhase, $getColor($eq->id, $botId))) $changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Leitungen propagieren
|
|
foreach ($this->allConnections as $conn) {
|
|
if ($conn->is_rail) continue;
|
|
if (empty($conn->fk_source) || empty($conn->fk_target)) continue;
|
|
if (empty($conn->source_terminal_id) || empty($conn->target_terminal_id)) continue;
|
|
|
|
$srcPhase = $this->terminalPhaseMap[$conn->fk_source][$conn->source_terminal_id] ?? null;
|
|
$tgtPhase = $this->terminalPhaseMap[$conn->fk_target][$conn->target_terminal_id] ?? null;
|
|
|
|
if ($srcPhase && !$tgtPhase) {
|
|
if ($setPhase($conn->fk_target, $conn->target_terminal_id, $srcPhase, $getColor($conn->fk_source, $conn->source_terminal_id))) $changed = true;
|
|
} elseif ($tgtPhase && !$srcPhase) {
|
|
if ($setPhase($conn->fk_source, $conn->source_terminal_id, $tgtPhase, $getColor($conn->fk_target, $conn->target_terminal_id))) $changed = true;
|
|
}
|
|
}
|
|
|
|
// Busbar-Verteilung
|
|
foreach ($this->allConnections as $busbar) {
|
|
if (!$busbar->is_rail) continue;
|
|
|
|
$railStart = (int) ($busbar->rail_start_te ?: 1);
|
|
$railEnd = (int) ($busbar->rail_end_te ?: $railStart);
|
|
$posY = (int) ($busbar->position_y ?: 0);
|
|
$targetPos = ($posY === 0) ? 'top' : 'bottom';
|
|
|
|
// Phase-Labels
|
|
$phaseLabels = array();
|
|
if (!empty($busbar->phases_config)) {
|
|
$pc = @json_decode($busbar->phases_config, true);
|
|
if (is_array($pc) && !empty($pc)) $phaseLabels = $pc;
|
|
}
|
|
if (empty($phaseLabels)) {
|
|
$phaseLabels = parsePhaseLabels($busbar->rail_phases ?: $busbar->connection_type ?: '');
|
|
}
|
|
if (empty($phaseLabels)) continue;
|
|
|
|
// Eingespeiste Phasen sammeln
|
|
$fedPhases = array();
|
|
$fedColors = array();
|
|
foreach ($this->allEquipment as $eq) {
|
|
if ($eq->fk_carrier != $busbar->fk_carrier) continue;
|
|
$eqPosTE = floatval($eq->position_te ?: 1);
|
|
$eqWidthTE = floatval($eq->width_te ?: 1);
|
|
if (!($eqPosTE < $railEnd + 1 && $railStart < $eqPosTE + $eqWidthTE)) continue;
|
|
|
|
$terminals = getEquipmentTerminals($eq);
|
|
foreach ($terminals as $term) {
|
|
if (($term['pos'] ?? '') !== $targetPos) continue;
|
|
$termId = $term['id'] ?? '';
|
|
$phase = $this->terminalPhaseMap[$eq->id][$termId] ?? null;
|
|
if ($phase) {
|
|
$fedPhases[$phase] = true;
|
|
if (!isset($fedColors[$phase])) {
|
|
$fedColors[$phase] = $getColor($eq->id, $termId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (empty($fedPhases)) continue;
|
|
|
|
// Excluded TEs
|
|
$excludedTEs = array();
|
|
if (!empty($busbar->excluded_te)) {
|
|
$excludedTEs = array_map('intval', array_filter(array_map('trim', explode(',', $busbar->excluded_te))));
|
|
}
|
|
|
|
// Verteilen
|
|
foreach ($this->allEquipment as $eq) {
|
|
if ($eq->fk_carrier != $busbar->fk_carrier) continue;
|
|
$eqPosTE = floatval($eq->position_te ?: 1);
|
|
$eqWidthTE = floatval($eq->width_te ?: 1);
|
|
if (!($eqPosTE < $railEnd + 1 && $railStart < $eqPosTE + $eqWidthTE)) continue;
|
|
|
|
$terminals = getEquipmentTerminals($eq);
|
|
$posTerminals = array_values(array_filter($terminals, function($t) use ($targetPos) { return ($t['pos'] ?? '') === $targetPos; }));
|
|
|
|
foreach ($posTerminals as $idx => $term) {
|
|
$col = isset($term['col']) ? $term['col'] : ($idx % max(1, $eqWidthTE));
|
|
$absoluteTE = round($eqPosTE + $col);
|
|
|
|
if (in_array($absoluteTE, $excludedTEs)) continue;
|
|
if ($absoluteTE < $railStart || $absoluteTE > $railEnd) continue;
|
|
|
|
$teOffset = $absoluteTE - $railStart;
|
|
$phase = $phaseLabels[$teOffset % count($phaseLabels)];
|
|
|
|
if (!isset($fedPhases[$phase])) continue;
|
|
|
|
$phaseColor = $fedColors[$phase] ?? '#888';
|
|
$termId = $term['id'] ?? 't'.($idx+1);
|
|
if ($forcePhase($eq->id, $termId, $phase, $phaseColor)) $changed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Strompfade bauen: Für jeden Abgang eine Spalte
|
|
*/
|
|
private function buildCircuitPaths()
|
|
{
|
|
$this->circuitPaths = array();
|
|
|
|
// Alle Abgänge (Outputs) finden
|
|
foreach ($this->allConnections as $conn) {
|
|
if ($conn->is_rail) continue;
|
|
// Abgang = hat Source, kein Target
|
|
if (empty($conn->fk_source) || !empty($conn->fk_target)) continue;
|
|
// path_data = Junction-Verbindung, kein echter Abgang
|
|
if (!empty($conn->path_data)) continue;
|
|
|
|
$sourceEq = $this->equipmentById[$conn->fk_source] ?? null;
|
|
if (!$sourceEq) continue;
|
|
|
|
$carrier = $this->carrierByEquipmentId[$conn->fk_source] ?? null;
|
|
if (!$carrier) continue;
|
|
|
|
// Panel ermitteln
|
|
$panel = null;
|
|
if (!empty($carrier->fk_panel)) {
|
|
foreach ($this->panels as $p) {
|
|
if ($p->id == $carrier->fk_panel) { $panel = $p; break; }
|
|
}
|
|
}
|
|
|
|
// Phase bestimmen
|
|
$phase = '';
|
|
if (!empty($conn->connection_type) && in_array(strtoupper($conn->connection_type), array('L1','L2','L3','N','PE','LN','3P','3P+N'))) {
|
|
$phase = strtoupper($conn->connection_type);
|
|
}
|
|
// Fallback: Aus Phase-Map
|
|
if (empty($phase) && !empty($conn->source_terminal_id)) {
|
|
$phase = $this->terminalPhaseMap[$conn->fk_source][$conn->source_terminal_id] ?? '';
|
|
}
|
|
// Fallback: Erstbeste Phase vom Equipment
|
|
if (empty($phase) && isset($this->terminalPhaseMap[$conn->fk_source])) {
|
|
$phases = array_values($this->terminalPhaseMap[$conn->fk_source]);
|
|
if (!empty($phases)) $phase = $phases[0];
|
|
}
|
|
|
|
// Schutzgerät (FI/RCD)
|
|
$protectionDevice = null;
|
|
if (!empty($sourceEq->fk_protection)) {
|
|
$protectionDevice = $this->equipmentById[$sourceEq->fk_protection] ?? null;
|
|
}
|
|
|
|
// Block-Label (z.B. "B16", "C32")
|
|
$blockLabel = $sourceEq->getBlockLabel();
|
|
|
|
// Abgangsnummer: R{Reihe}.{Position}
|
|
$reihe = ($carrier->position ?? 0) + 1;
|
|
$pos = round(floatval($sourceEq->position_te));
|
|
$abgangNr = 'R'.$reihe.'.'.$pos;
|
|
|
|
// Kette aufbauen (von oben nach unten)
|
|
$chain = array();
|
|
|
|
// Phase-Rail
|
|
$chain[] = array('type' => 'phase_rail', 'label' => $phase ?: '?');
|
|
|
|
// Schutzgerät
|
|
if ($protectionDevice) {
|
|
$protLabel = $protectionDevice->label ?: ($protectionDevice->type_label_short ?: 'FI');
|
|
$protBlock = $protectionDevice->getBlockLabel();
|
|
$chain[] = array(
|
|
'type' => 'protection',
|
|
'equipment' => $protectionDevice,
|
|
'label' => $protLabel,
|
|
'block_label' => $protBlock,
|
|
);
|
|
}
|
|
|
|
// LS-Schalter (Breaker)
|
|
$chain[] = array(
|
|
'type' => 'breaker',
|
|
'equipment' => $sourceEq,
|
|
'label' => $sourceEq->label ?: ($sourceEq->type_label_short ?: 'LS'),
|
|
'block_label' => $blockLabel,
|
|
);
|
|
|
|
// Verbraucher (Abgang)
|
|
$chain[] = array('type' => 'consumer', 'label' => $conn->output_label ?: '-');
|
|
|
|
$this->circuitPaths[] = array(
|
|
'abgang_nr' => $abgangNr,
|
|
'output_label' => $conn->output_label ?: '-',
|
|
'phase' => $phase ?: '?',
|
|
'phase_color_rgb' => getPhaseColorRGB($phase),
|
|
'medium_type' => $conn->medium_type ?: '',
|
|
'medium_spec' => $conn->medium_spec ?: '',
|
|
'medium_length' => $conn->medium_length ?: '',
|
|
'chain' => $chain,
|
|
'protection_device' => $protectionDevice,
|
|
'breaker' => $sourceEq,
|
|
'carrier' => $carrier,
|
|
'panel' => $panel,
|
|
'connection' => $conn,
|
|
);
|
|
}
|
|
|
|
// Sortierung: FI-Gruppe → Carrier → Position
|
|
usort($this->circuitPaths, function($a, $b) {
|
|
$protA = $a['protection_device'] ? $a['protection_device']->id : PHP_INT_MAX;
|
|
$protB = $b['protection_device'] ? $b['protection_device']->id : PHP_INT_MAX;
|
|
if ($protA !== $protB) return $protA - $protB;
|
|
|
|
$carrA = $a['carrier']->position ?? 0;
|
|
$carrB = $b['carrier']->position ?? 0;
|
|
if ($carrA !== $carrB) return $carrA - $carrB;
|
|
|
|
$posA = $a['breaker']->position_te ?? 0;
|
|
$posB = $b['breaker']->position_te ?? 0;
|
|
return $posA <=> $posB;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Ergebnis: Strompfade
|
|
*/
|
|
public function getCircuitPaths()
|
|
{
|
|
return $this->circuitPaths;
|
|
}
|
|
|
|
/**
|
|
* Abgangs-Tabelle pro Carrier/Panel
|
|
*/
|
|
public function getAbgangTabelle()
|
|
{
|
|
$tabellen = array();
|
|
|
|
foreach ($this->carriers as $carrier) {
|
|
$panel = null;
|
|
if (!empty($carrier->fk_panel)) {
|
|
foreach ($this->panels as $p) {
|
|
if ($p->id == $carrier->fk_panel) { $panel = $p; break; }
|
|
}
|
|
}
|
|
|
|
$header = '';
|
|
if ($panel) $header .= $panel->label . ', ';
|
|
$header .= $carrier->label ?: ('Reihe '.($carrier->position + 1));
|
|
|
|
$rows = array();
|
|
foreach ($this->circuitPaths as $path) {
|
|
if ($path['carrier']->id != $carrier->id) continue;
|
|
|
|
$protLabel = '';
|
|
if ($path['protection_device']) {
|
|
$pd = $path['protection_device'];
|
|
$protLabel = ($pd->label ?: $pd->type_label_short ?: 'FI');
|
|
$protBlock = $pd->getBlockLabel();
|
|
if ($protBlock) $protLabel .= ' '.$protBlock;
|
|
}
|
|
|
|
$kabel = $path['medium_type'];
|
|
if ($path['medium_spec']) $kabel .= ' '.$path['medium_spec'];
|
|
if ($path['medium_length']) $kabel .= ' ('.$path['medium_length'].')';
|
|
|
|
$rows[] = array(
|
|
'abgang_nr' => $path['abgang_nr'],
|
|
'bezeichnung' => $path['output_label'],
|
|
'phase' => $path['phase'],
|
|
'absicherung' => $path['chain'][count($path['chain'])-2]['block_label'] ?? '',
|
|
'kabel' => trim($kabel),
|
|
'schutzgeraet' => $protLabel,
|
|
'bemerkung' => '',
|
|
);
|
|
}
|
|
|
|
if (!empty($rows)) {
|
|
$tabellen[] = array(
|
|
'header' => $header,
|
|
'carrier' => $carrier,
|
|
'panel' => $panel,
|
|
'rows' => $rows,
|
|
);
|
|
}
|
|
}
|
|
|
|
return $tabellen;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* WiringDiagramRenderer - Zeichnet den Leitungslaufplan als PDF
|
|
*/
|
|
class WiringDiagramRenderer
|
|
{
|
|
private $pdf;
|
|
private $circuitPaths;
|
|
private $analyzer;
|
|
private $anlage;
|
|
private $societe;
|
|
private $user;
|
|
|
|
// Seitengröße
|
|
private $pageWidth;
|
|
private $pageHeight;
|
|
private $orientation;
|
|
private $format;
|
|
|
|
// Layout-Konstanten
|
|
const MARGIN_LEFT = 15;
|
|
const MARGIN_RIGHT = 15;
|
|
const MARGIN_TOP = 12;
|
|
const COLUMN_WIDTH = 25;
|
|
const COLUMN_GAP = 3;
|
|
const PHASE_GAP = 5;
|
|
|
|
// Vertikale Positionen (werden in calculateLayout berechnet)
|
|
private $yPhaseL1;
|
|
private $yPhaseL2;
|
|
private $yPhaseL3;
|
|
private $yFiTop;
|
|
private $yFiBottom;
|
|
private $yLsTop;
|
|
private $yLsBottom;
|
|
private $yConsumer;
|
|
private $yCableLabel;
|
|
private $yAbgangLabel;
|
|
private $yAbgangNr;
|
|
private $yNRail;
|
|
private $yPeRail;
|
|
private $maxColumnsPerPage;
|
|
|
|
private $currentPage = 0;
|
|
private $totalPages = 1;
|
|
|
|
public function __construct($pdf, $analyzer, $anlage, $societe, $user, $format = 'A3', $orientation = 'L')
|
|
{
|
|
$this->pdf = $pdf;
|
|
$this->analyzer = $analyzer;
|
|
$this->circuitPaths = $analyzer->getCircuitPaths();
|
|
$this->anlage = $anlage;
|
|
$this->societe = $societe;
|
|
$this->user = $user;
|
|
$this->format = $format;
|
|
$this->orientation = $orientation;
|
|
|
|
// Seitengröße
|
|
if ($format == 'A3') {
|
|
$this->pageWidth = 420;
|
|
$this->pageHeight = 297;
|
|
} else {
|
|
$this->pageWidth = 297;
|
|
$this->pageHeight = 210;
|
|
}
|
|
if ($orientation == 'P') {
|
|
$tmp = $this->pageWidth;
|
|
$this->pageWidth = $this->pageHeight;
|
|
$this->pageHeight = $tmp;
|
|
}
|
|
|
|
$this->calculateLayout();
|
|
}
|
|
|
|
/**
|
|
* Layout berechnen
|
|
*/
|
|
private function calculateLayout()
|
|
{
|
|
$this->yPhaseL1 = self::MARGIN_TOP + 15;
|
|
$this->yPhaseL2 = $this->yPhaseL1 + self::PHASE_GAP;
|
|
$this->yPhaseL3 = $this->yPhaseL2 + self::PHASE_GAP;
|
|
|
|
$this->yFiTop = $this->yPhaseL3 + 18;
|
|
$this->yFiBottom = $this->yFiTop + 22;
|
|
|
|
$this->yLsTop = $this->yFiBottom + 12;
|
|
$this->yLsBottom = $this->yLsTop + 18;
|
|
|
|
$this->yConsumer = $this->yLsBottom + 12;
|
|
$this->yCableLabel = $this->yConsumer + 10;
|
|
$this->yAbgangLabel = $this->yCableLabel + 10;
|
|
$this->yAbgangNr = $this->yAbgangLabel + 8;
|
|
|
|
// N und PE unten (vor Titelfeld)
|
|
$this->yNRail = $this->pageHeight - 85;
|
|
$this->yPeRail = $this->yNRail + self::PHASE_GAP;
|
|
|
|
// Max Spalten pro Seite
|
|
$usableWidth = $this->pageWidth - self::MARGIN_LEFT - self::MARGIN_RIGHT - 30; // 30mm Phase-Labels links
|
|
$this->maxColumnsPerPage = max(1, floor($usableWidth / (self::COLUMN_WIDTH + self::COLUMN_GAP)));
|
|
|
|
// Gesamtseiten berechnen
|
|
$totalPaths = count($this->circuitPaths);
|
|
$this->totalPages = max(1, ceil($totalPaths / $this->maxColumnsPerPage));
|
|
}
|
|
|
|
/**
|
|
* Leitungslaufplan zeichnen (alle Seiten)
|
|
*/
|
|
public function render()
|
|
{
|
|
$totalPaths = count($this->circuitPaths);
|
|
|
|
if ($totalPaths === 0) {
|
|
$this->pdf->AddPage($this->orientation, array($this->pageWidth, $this->pageHeight));
|
|
$this->pdf->SetFont('dejavusans', 'B', 14);
|
|
$this->pdf->SetTextColor(100, 100, 100);
|
|
$this->pdf->Text(self::MARGIN_LEFT, $this->pageHeight / 2, 'Keine Abgänge konfiguriert');
|
|
$this->drawTitleBlock(1, 1);
|
|
return;
|
|
}
|
|
|
|
$pathIndex = 0;
|
|
$pageNum = 0;
|
|
|
|
while ($pathIndex < $totalPaths) {
|
|
$pageNum++;
|
|
$this->pdf->AddPage($this->orientation, array($this->pageWidth, $this->pageHeight));
|
|
|
|
// Spalten für diese Seite
|
|
$pagePaths = array_slice($this->circuitPaths, $pathIndex, $this->maxColumnsPerPage);
|
|
$numCols = count($pagePaths);
|
|
|
|
$startX = self::MARGIN_LEFT + 30; // 30mm für Phase-Labels links
|
|
$endX = $startX + ($numCols * (self::COLUMN_WIDTH + self::COLUMN_GAP));
|
|
|
|
// Phase-Labels links
|
|
$this->drawPhaseLabels();
|
|
|
|
// Phasenleiter oben (L1, L2, L3)
|
|
$this->drawPhaseRails($startX - 5, $endX + 5);
|
|
|
|
// N und PE unten
|
|
$this->drawNPeRails($startX - 5, $endX + 5);
|
|
|
|
// FI-Gruppen identifizieren für Trennlinien
|
|
$lastProtId = null;
|
|
|
|
// Strompfad-Spalten zeichnen
|
|
for ($col = 0; $col < $numCols; $col++) {
|
|
$path = $pagePaths[$col];
|
|
$x = $startX + $col * (self::COLUMN_WIDTH + self::COLUMN_GAP) + self::COLUMN_WIDTH / 2;
|
|
|
|
// FI-Gruppen-Trenner
|
|
$currentProtId = $path['protection_device'] ? $path['protection_device']->id : 0;
|
|
if ($lastProtId !== null && $lastProtId !== $currentProtId && $col > 0) {
|
|
$sepX = $x - (self::COLUMN_WIDTH + self::COLUMN_GAP) / 2;
|
|
$this->pdf->SetDrawColor(180, 180, 180);
|
|
$this->pdf->SetLineWidth(0.2);
|
|
$this->pdf->SetLineDashPattern(array(2, 2));
|
|
$this->pdf->Line($sepX, $this->yPhaseL1 - 5, $sepX, $this->yPeRail + 5);
|
|
$this->pdf->SetLineDashPattern(array());
|
|
}
|
|
$lastProtId = $currentProtId;
|
|
|
|
$this->drawCircuitColumn($path, $x);
|
|
}
|
|
|
|
// Titelfeld
|
|
$this->drawTitleBlock($pageNum, $this->totalPages + 2); // +2 für Tabelle + Legende
|
|
|
|
$pathIndex += $numCols;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Phase-Labels links zeichnen
|
|
*/
|
|
private function drawPhaseLabels()
|
|
{
|
|
$x = self::MARGIN_LEFT;
|
|
$this->pdf->SetFont('dejavusans', 'B', 9);
|
|
|
|
// L1
|
|
$rgb = getPhaseColorRGB('L1');
|
|
$this->pdf->SetTextColor($rgb[0], $rgb[1], $rgb[2]);
|
|
$this->pdf->Text($x, $this->yPhaseL1 - 1, 'L1');
|
|
|
|
// L2
|
|
$rgb = getPhaseColorRGB('L2');
|
|
$this->pdf->SetTextColor($rgb[0], $rgb[1], $rgb[2]);
|
|
$this->pdf->Text($x, $this->yPhaseL2 - 1, 'L2');
|
|
|
|
// L3
|
|
$rgb = getPhaseColorRGB('L3');
|
|
$this->pdf->SetTextColor($rgb[0], $rgb[1], $rgb[2]);
|
|
$this->pdf->Text($x, $this->yPhaseL3 - 1, 'L3');
|
|
|
|
// N
|
|
$rgb = getPhaseColorRGB('N');
|
|
$this->pdf->SetTextColor($rgb[0], $rgb[1], $rgb[2]);
|
|
$this->pdf->Text($x, $this->yNRail - 1, 'N');
|
|
|
|
// PE
|
|
$rgb = getPhaseColorRGB('PE');
|
|
$this->pdf->SetTextColor($rgb[0], $rgb[1], $rgb[2]);
|
|
$this->pdf->Text($x, $this->yPeRail - 1, 'PE');
|
|
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
}
|
|
|
|
/**
|
|
* Horizontale Phasenleiter oben (L1, L2, L3)
|
|
*/
|
|
private function drawPhaseRails($startX, $endX)
|
|
{
|
|
$this->pdf->SetLineWidth(0.5);
|
|
|
|
$phases = array('L1' => $this->yPhaseL1, 'L2' => $this->yPhaseL2, 'L3' => $this->yPhaseL3);
|
|
foreach ($phases as $phase => $y) {
|
|
$rgb = getPhaseColorRGB($phase);
|
|
$this->pdf->SetDrawColor($rgb[0], $rgb[1], $rgb[2]);
|
|
$this->pdf->Line($startX, $y, $endX, $y);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* N und PE Leiter unten
|
|
*/
|
|
private function drawNPeRails($startX, $endX)
|
|
{
|
|
$this->pdf->SetLineWidth(0.5);
|
|
|
|
$rgb = getPhaseColorRGB('N');
|
|
$this->pdf->SetDrawColor($rgb[0], $rgb[1], $rgb[2]);
|
|
$this->pdf->Line($startX, $this->yNRail, $endX, $this->yNRail);
|
|
|
|
$rgb = getPhaseColorRGB('PE');
|
|
$this->pdf->SetDrawColor($rgb[0], $rgb[1], $rgb[2]);
|
|
$this->pdf->Line($startX, $this->yPeRail, $endX, $this->yPeRail);
|
|
}
|
|
|
|
/**
|
|
* Eine Strompfad-Spalte zeichnen
|
|
*/
|
|
private function drawCircuitColumn($path, $x)
|
|
{
|
|
$phase = $path['phase'];
|
|
$phaseRGB = $path['phase_color_rgb'];
|
|
|
|
// Y-Position der Phase bestimmen (L1, L2 oder L3)
|
|
$phaseY = $this->yPhaseL1;
|
|
if ($phase === 'L2') $phaseY = $this->yPhaseL2;
|
|
elseif ($phase === 'L3') $phaseY = $this->yPhaseL3;
|
|
|
|
$this->pdf->SetDrawColor($phaseRGB[0], $phaseRGB[1], $phaseRGB[2]);
|
|
$this->pdf->SetLineWidth(0.4);
|
|
|
|
// Vertikale Linie von Phase runter zum FI oder LS
|
|
$hasFI = !empty($path['protection_device']);
|
|
$topTarget = $hasFI ? $this->yFiTop : $this->yLsTop;
|
|
|
|
// Anschluss an Phase-Rail (kleiner Punkt)
|
|
$this->pdf->Circle($x, $phaseY, 1, 0, 360, 'F', array(), array($phaseRGB[0], $phaseRGB[1], $phaseRGB[2]));
|
|
|
|
// Vertikale Linie Phase → FI/LS
|
|
$this->pdf->Line($x, $phaseY, $x, $topTarget);
|
|
|
|
// FI/RCD zeichnen
|
|
if ($hasFI) {
|
|
$this->drawRCDSymbol($x, $this->yFiTop, $path['protection_device']);
|
|
|
|
// Linie FI → LS
|
|
$this->pdf->SetDrawColor($phaseRGB[0], $phaseRGB[1], $phaseRGB[2]);
|
|
$this->pdf->SetLineWidth(0.4);
|
|
$this->pdf->Line($x, $this->yFiBottom, $x, $this->yLsTop);
|
|
}
|
|
|
|
// LS-Schalter zeichnen
|
|
$this->drawBreakerSymbol($x, $this->yLsTop, $path['breaker'], $path['chain']);
|
|
|
|
// Linie LS → Verbraucher
|
|
$this->pdf->SetDrawColor($phaseRGB[0], $phaseRGB[1], $phaseRGB[2]);
|
|
$this->pdf->SetLineWidth(0.4);
|
|
$this->pdf->Line($x, $this->yLsBottom, $x, $this->yConsumer);
|
|
|
|
// Abgang-Pfeil
|
|
$this->pdf->SetFillColor($phaseRGB[0], $phaseRGB[1], $phaseRGB[2]);
|
|
$arrowSize = 3;
|
|
$this->pdf->Polygon(array(
|
|
$x - $arrowSize, $this->yConsumer - $arrowSize,
|
|
$x + $arrowSize, $this->yConsumer - $arrowSize,
|
|
$x, $this->yConsumer + 1,
|
|
), 'F');
|
|
|
|
// Vertikale Linie zum N-Leiter
|
|
$this->pdf->SetDrawColor(180, 180, 180);
|
|
$this->pdf->SetLineWidth(0.15);
|
|
$this->pdf->SetLineDashPattern(array(1, 2));
|
|
$this->pdf->Line($x, $this->yConsumer + 2, $x, $this->yNRail);
|
|
$this->pdf->SetLineDashPattern(array());
|
|
|
|
// Anschluss an N-Rail
|
|
$nRGB = getPhaseColorRGB('N');
|
|
$this->pdf->Circle($x, $this->yNRail, 0.8, 0, 360, 'F', array(), array($nRGB[0], $nRGB[1], $nRGB[2]));
|
|
|
|
// Kabelbezeichnung
|
|
$this->pdf->SetFont('dejavusans', '', 6);
|
|
$this->pdf->SetTextColor(100, 100, 100);
|
|
$cableText = $path['medium_type'];
|
|
if ($path['medium_spec']) $cableText .= "\n".$path['medium_spec'];
|
|
if (!empty($cableText)) {
|
|
$this->pdf->SetXY($x - 12, $this->yCableLabel);
|
|
$this->pdf->MultiCell(24, 3, $cableText, 0, 'C');
|
|
}
|
|
|
|
// Abgang-Label (Verbraucher-Name)
|
|
$this->pdf->SetFont('dejavusans', 'B', 7);
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
$this->pdf->SetXY($x - 13, $this->yAbgangLabel);
|
|
$this->pdf->MultiCell(26, 3, $path['output_label'], 0, 'C');
|
|
|
|
// Abgangsnummer
|
|
$this->pdf->SetFont('dejavusans', '', 6);
|
|
$this->pdf->SetTextColor(120, 120, 120);
|
|
$this->pdf->Text($x - 6, $this->yAbgangNr + 8, $path['abgang_nr']);
|
|
}
|
|
|
|
/**
|
|
* LS-Schalter Symbol (vereinfacht)
|
|
*/
|
|
private function drawBreakerSymbol($x, $y, $eq, $chain)
|
|
{
|
|
$this->pdf->SetDrawColor(0, 0, 0);
|
|
$this->pdf->SetLineWidth(0.3);
|
|
|
|
// Vertikale Linie oben
|
|
$this->pdf->Line($x, $y, $x, $y + 4);
|
|
|
|
// Schaltkontakt (schräge Linie)
|
|
$this->pdf->Line($x, $y + 4, $x + 4, $y + 8);
|
|
|
|
// Auslöser (kleines Rechteck)
|
|
$this->pdf->SetFillColor(255, 255, 255);
|
|
$this->pdf->Rect($x - 2, $y + 8, 4, 3, 'DF');
|
|
|
|
// Vertikale Linie unten
|
|
$this->pdf->Line($x, $y + 11, $x, $y + 18);
|
|
|
|
// Block-Label rechts (z.B. "B16")
|
|
$blockLabel = '';
|
|
foreach ($chain as $c) {
|
|
if ($c['type'] === 'breaker') {
|
|
$blockLabel = $c['block_label'] ?? '';
|
|
break;
|
|
}
|
|
}
|
|
$this->pdf->SetFont('dejavusans', 'B', 7);
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
if ($blockLabel) {
|
|
$this->pdf->Text($x + 5, $y + 7, $blockLabel);
|
|
}
|
|
|
|
// Equipment-Label links (z.B. "F1")
|
|
$label = $eq->label ?: '';
|
|
if ($label) {
|
|
$this->pdf->SetFont('dejavusans', '', 6);
|
|
$this->pdf->SetTextColor(80, 80, 80);
|
|
$this->pdf->Text($x - 12, $y + 7, $label);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* FI/RCD Symbol (vereinfacht)
|
|
*/
|
|
private function drawRCDSymbol($x, $y, $eq)
|
|
{
|
|
$this->pdf->SetDrawColor(0, 0, 0);
|
|
$this->pdf->SetLineWidth(0.3);
|
|
|
|
$w = 16;
|
|
$h = 20;
|
|
|
|
// Umrandung
|
|
$this->pdf->SetFillColor(255, 255, 255);
|
|
$this->pdf->Rect($x - $w/2, $y, $w, $h, 'DF');
|
|
|
|
// Differenzstrom-Symbol (Kreis)
|
|
$this->pdf->Circle($x, $y + $h/2, 4, 0, 360, 'D');
|
|
|
|
// Vertikale Linie durch Kreis (Auslöser)
|
|
$this->pdf->Line($x, $y + $h/2 - 4, $x, $y + $h/2 + 4);
|
|
|
|
// Block-Label (z.B. "40A 30mA")
|
|
$blockLabel = $eq->getBlockLabel();
|
|
$this->pdf->SetFont('dejavusans', '', 5);
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
if ($blockLabel) {
|
|
$this->pdf->SetXY($x - $w/2, $y + $h - 5);
|
|
$this->pdf->Cell($w, 4, $blockLabel, 0, 0, 'C');
|
|
}
|
|
|
|
// Equipment-Label links (z.B. "Q1")
|
|
$label = $eq->label ?: '';
|
|
if ($label) {
|
|
$this->pdf->SetFont('dejavusans', '', 6);
|
|
$this->pdf->SetTextColor(80, 80, 80);
|
|
$this->pdf->Text($x - $w/2 - 10, $y + $h/2, $label);
|
|
}
|
|
|
|
// Ein/Ausgangs-Linien
|
|
$this->pdf->SetDrawColor(0, 0, 0);
|
|
$this->pdf->Line($x, $y - 2, $x, $y);
|
|
$this->pdf->Line($x, $y + $h, $x, $y + $h + 2);
|
|
}
|
|
|
|
/**
|
|
* Abgangs-Tabellen zeichnen
|
|
*/
|
|
public function renderAbgangTabelle()
|
|
{
|
|
$tabellen = $this->analyzer->getAbgangTabelle();
|
|
if (empty($tabellen)) return;
|
|
|
|
// Spaltenbreiten
|
|
$colWidths = array(18, 50, 15, 28, 45, 40, 30);
|
|
$colHeaders = array('Abg.Nr.', 'Bezeichnung', 'Phase', 'Absicherung', 'Kabel', 'Schutzgerät', 'Bemerkung');
|
|
$totalWidth = array_sum($colWidths);
|
|
|
|
foreach ($tabellen as $tabelle) {
|
|
$this->pdf->AddPage($this->orientation, array($this->pageWidth, $this->pageHeight));
|
|
|
|
$y = self::MARGIN_TOP;
|
|
|
|
// Tabellen-Header
|
|
$this->pdf->SetFont('dejavusans', 'B', 12);
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
$this->pdf->Text(self::MARGIN_LEFT, $y + 5, 'Abgangsverzeichnis - '.$tabelle['header']);
|
|
$y += 15;
|
|
|
|
// Spalten-Header
|
|
$this->pdf->SetFont('dejavusans', 'B', 7);
|
|
$this->pdf->SetFillColor(230, 230, 230);
|
|
$x = self::MARGIN_LEFT;
|
|
for ($i = 0; $i < count($colHeaders); $i++) {
|
|
$this->pdf->SetXY($x, $y);
|
|
$this->pdf->Cell($colWidths[$i], 7, $colHeaders[$i], 1, 0, 'C', true);
|
|
$x += $colWidths[$i];
|
|
}
|
|
$y += 7;
|
|
|
|
// Zeilen
|
|
$this->pdf->SetFont('dejavusans', '', 7);
|
|
$this->pdf->SetFillColor(255, 255, 255);
|
|
|
|
foreach ($tabelle['rows'] as $rowIdx => $row) {
|
|
$bgFill = ($rowIdx % 2 === 0);
|
|
if ($bgFill) $this->pdf->SetFillColor(248, 248, 248);
|
|
else $this->pdf->SetFillColor(255, 255, 255);
|
|
|
|
$x = self::MARGIN_LEFT;
|
|
$cells = array(
|
|
$row['abgang_nr'],
|
|
$row['bezeichnung'],
|
|
$row['phase'],
|
|
$row['absicherung'],
|
|
$row['kabel'],
|
|
$row['schutzgeraet'],
|
|
$row['bemerkung'],
|
|
);
|
|
|
|
for ($i = 0; $i < count($cells); $i++) {
|
|
$this->pdf->SetXY($x, $y);
|
|
$align = ($i === 2) ? 'C' : 'L'; // Phase zentriert
|
|
$this->pdf->Cell($colWidths[$i], 6, $cells[$i], 1, 0, $align, true);
|
|
$x += $colWidths[$i];
|
|
}
|
|
$y += 6;
|
|
|
|
// Seitenumbruch
|
|
if ($y > $this->pageHeight - 30) {
|
|
$this->pdf->AddPage($this->orientation, array($this->pageWidth, $this->pageHeight));
|
|
$y = self::MARGIN_TOP + 10;
|
|
}
|
|
}
|
|
|
|
// Titelfeld
|
|
$this->drawTitleBlock(0, 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Legende zeichnen
|
|
*/
|
|
public function renderLegende()
|
|
{
|
|
$this->pdf->AddPage($this->orientation, array($this->pageWidth, $this->pageHeight));
|
|
|
|
$y = self::MARGIN_TOP;
|
|
$x = self::MARGIN_LEFT;
|
|
|
|
$this->pdf->SetFont('dejavusans', 'B', 14);
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
$this->pdf->Text($x, $y + 5, 'Legende');
|
|
$y += 15;
|
|
|
|
// Phasenfarben
|
|
$this->pdf->SetFont('dejavusans', 'B', 10);
|
|
$this->pdf->Text($x, $y, 'Phasenfarben nach DIN VDE');
|
|
$y += 8;
|
|
|
|
$phases = array(
|
|
'L1' => 'Außenleiter 1 (Braun)',
|
|
'L2' => 'Außenleiter 2 (Schwarz)',
|
|
'L3' => 'Außenleiter 3 (Grau)',
|
|
'N' => 'Neutralleiter (Blau)',
|
|
'PE' => 'Schutzleiter (Grün-Gelb)',
|
|
);
|
|
|
|
$this->pdf->SetFont('dejavusans', '', 8);
|
|
foreach ($phases as $phase => $label) {
|
|
$rgb = getPhaseColorRGB($phase);
|
|
$this->pdf->SetFillColor($rgb[0], $rgb[1], $rgb[2]);
|
|
$this->pdf->Rect($x, $y, 20, 5, 'F');
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
$this->pdf->Text($x + 25, $y + 3, $phase.' - '.$label);
|
|
$y += 8;
|
|
}
|
|
|
|
$y += 5;
|
|
|
|
// Symbole
|
|
$this->pdf->SetFont('dejavusans', 'B', 10);
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
$this->pdf->Text($x, $y, 'Symbole');
|
|
$y += 10;
|
|
|
|
// LS-Symbol + Beschreibung
|
|
$this->pdf->SetFont('dejavusans', '', 8);
|
|
$this->drawBreakerSymbol($x + 8, $y, (object)array('label'=>'F1'), array(array('type'=>'breaker','block_label'=>'B16')));
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
$this->pdf->Text($x + 30, $y + 8, 'Leitungsschutzschalter (LS-Schalter)');
|
|
$y += 25;
|
|
|
|
// FI-Symbol + Beschreibung
|
|
$protDummy = new stdClass();
|
|
$protDummy->label = 'Q1';
|
|
$protDummy->type_label_short = 'FI';
|
|
// getBlockLabel brauchen wir nicht, Text wird direkt gesetzt
|
|
$this->pdf->SetDrawColor(0, 0, 0);
|
|
$this->pdf->SetLineWidth(0.3);
|
|
$this->pdf->SetFillColor(255, 255, 255);
|
|
$this->pdf->Rect($x, $y, 16, 20, 'DF');
|
|
$this->pdf->Circle($x + 8, $y + 10, 4, 0, 360, 'D');
|
|
$this->pdf->Line($x + 8, $y + 6, $x + 8, $y + 14);
|
|
$this->pdf->SetFont('dejavusans', '', 5);
|
|
$this->pdf->Text($x + 2, $y + 17, 'FI/RCD');
|
|
$this->pdf->SetFont('dejavusans', '', 8);
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
$this->pdf->Text($x + 30, $y + 10, 'Fehlerstrom-Schutzschalter (FI/RCD)');
|
|
$y += 28;
|
|
|
|
// Abgang-Pfeil
|
|
$this->pdf->SetFillColor(0, 0, 0);
|
|
$this->pdf->Polygon(array($x + 5, $y, $x + 11, $y, $x + 8, $y + 5), 'F');
|
|
$this->pdf->SetFont('dejavusans', '', 8);
|
|
$this->pdf->Text($x + 30, $y + 3, 'Abgang zum Verbraucher');
|
|
$y += 15;
|
|
|
|
// Norm-Hinweis
|
|
$y += 10;
|
|
$this->pdf->SetFont('dejavusans', 'I', 7);
|
|
$this->pdf->SetTextColor(120, 120, 120);
|
|
$this->pdf->Text($x, $y, 'Erstellt nach DIN EN 61082 / DIN EN 81346');
|
|
$this->pdf->Text($x, $y + 5, 'Bezugsbezeichnungen nach DIN EN 81346-2');
|
|
$this->pdf->Text($x, $y + 10, 'Schaltzeichen nach DIN EN 60617');
|
|
|
|
$this->drawTitleBlock(0, 0);
|
|
}
|
|
|
|
/**
|
|
* Titelfeld nach DIN EN 61082 / ISO 7200
|
|
*/
|
|
private function drawTitleBlock($pageNum = 0, $totalPages = 0)
|
|
{
|
|
$titleBlockWidth = 180;
|
|
$titleBlockHeight = 56;
|
|
$titleBlockX = $this->pageWidth - $titleBlockWidth - 10;
|
|
$titleBlockY = $this->pageHeight - $titleBlockHeight - 10;
|
|
|
|
// Rahmen
|
|
$this->pdf->SetDrawColor(0, 0, 0);
|
|
$this->pdf->SetLineWidth(0.5);
|
|
$this->pdf->Rect($titleBlockX, $titleBlockY, $titleBlockWidth, $titleBlockHeight);
|
|
|
|
$rowHeight = 8;
|
|
$col1 = 30; $col2 = 50; $col3 = 50; $col4 = 50;
|
|
|
|
// Horizontale Linien
|
|
for ($i = 1; $i < 7; $i++) {
|
|
$y = $titleBlockY + ($i * $rowHeight);
|
|
$this->pdf->Line($titleBlockX, $y, $titleBlockX + $titleBlockWidth, $y);
|
|
}
|
|
|
|
// Vertikale Linien
|
|
$this->pdf->Line($titleBlockX + $col1, $titleBlockY, $titleBlockX + $col1, $titleBlockY + $titleBlockHeight);
|
|
$this->pdf->Line($titleBlockX + $col1 + $col2, $titleBlockY, $titleBlockX + $col1 + $col2, $titleBlockY + $titleBlockHeight);
|
|
$this->pdf->Line($titleBlockX + $col1 + $col2 + $col3, $titleBlockY, $titleBlockX + $col1 + $col2 + $col3, $titleBlockY + $titleBlockHeight);
|
|
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
|
|
// Zeile 1: Titel
|
|
$this->pdf->SetFont('dejavusans', 'B', 12);
|
|
$this->pdf->SetXY($titleBlockX + 2, $titleBlockY + 1);
|
|
$this->pdf->Cell($titleBlockWidth - 4, $rowHeight - 2, 'LEITUNGSLAUFPLAN', 0, 0, 'C');
|
|
|
|
// Zeile 2: Anlage
|
|
$this->pdf->SetFont('dejavusans', 'B', 10);
|
|
$this->pdf->SetXY($titleBlockX + 2, $titleBlockY + $rowHeight + 1);
|
|
$this->pdf->Cell($titleBlockWidth - 4, $rowHeight - 2, $this->anlage->label, 0, 0, 'C');
|
|
|
|
// Zeile 3: Erstellt | Kunde | Projekt | Blatt
|
|
$y = $titleBlockY + (2 * $rowHeight);
|
|
$this->pdf->SetFont('dejavusans', '', 6);
|
|
$this->pdf->SetXY($titleBlockX + 1, $y + 1); $this->pdf->Cell($col1 - 2, 3, 'Erstellt', 0, 0);
|
|
$this->pdf->SetXY($titleBlockX + $col1 + 1, $y + 1); $this->pdf->Cell($col2 - 2, 3, 'Kunde', 0, 0);
|
|
$this->pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 1); $this->pdf->Cell($col3 - 2, 3, 'Projekt-Nr.', 0, 0);
|
|
$this->pdf->SetXY($titleBlockX + $col1 + $col2 + $col3 + 1, $y + 1); $this->pdf->Cell($col4 - 2, 3, 'Blatt', 0, 0);
|
|
|
|
$this->pdf->SetFont('dejavusans', '', 8);
|
|
$this->pdf->SetXY($titleBlockX + 1, $y + 4); $this->pdf->Cell($col1 - 2, 4, dol_print_date(dol_now(), 'day'), 0, 0);
|
|
$this->pdf->SetXY($titleBlockX + $col1 + 1, $y + 4); $this->pdf->Cell($col2 - 2, 4, dol_trunc($this->societe->name, 25), 0, 0);
|
|
$this->pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 4); $this->pdf->Cell($col3 - 2, 4, $this->anlage->ref ?: '-', 0, 0);
|
|
$blatt = ($pageNum > 0 && $totalPages > 0) ? $pageNum.' / '.$totalPages : '';
|
|
$this->pdf->SetXY($titleBlockX + $col1 + $col2 + $col3 + 1, $y + 4); $this->pdf->Cell($col4 - 2, 4, $blatt, 0, 0);
|
|
|
|
// Zeile 4: Bearbeiter | Adresse | Anlage | Revision
|
|
$y = $titleBlockY + (3 * $rowHeight);
|
|
$this->pdf->SetFont('dejavusans', '', 6);
|
|
$this->pdf->SetXY($titleBlockX + 1, $y + 1); $this->pdf->Cell($col1 - 2, 3, 'Bearbeiter', 0, 0);
|
|
$this->pdf->SetXY($titleBlockX + $col1 + 1, $y + 1); $this->pdf->Cell($col2 - 2, 3, 'Adresse', 0, 0);
|
|
$this->pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 1); $this->pdf->Cell($col3 - 2, 3, 'Anlage', 0, 0);
|
|
$this->pdf->SetXY($titleBlockX + $col1 + $col2 + $col3 + 1, $y + 1); $this->pdf->Cell($col4 - 2, 3, 'Revision', 0, 0);
|
|
|
|
$this->pdf->SetFont('dejavusans', '', 8);
|
|
global $langs;
|
|
$this->pdf->SetXY($titleBlockX + 1, $y + 4); $this->pdf->Cell($col1 - 2, 4, dol_trunc($this->user->getFullName($langs), 15), 0, 0);
|
|
$address = trim(($this->societe->address ?? '').' '.($this->societe->zip ?? '').' '.($this->societe->town ?? ''));
|
|
$this->pdf->SetXY($titleBlockX + $col1 + 1, $y + 4); $this->pdf->Cell($col2 - 2, 4, dol_trunc($address, 25), 0, 0);
|
|
$this->pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 4); $this->pdf->Cell($col3 - 2, 4, $this->anlage->type_label ?? '-', 0, 0);
|
|
$this->pdf->SetXY($titleBlockX + $col1 + $col2 + $col3 + 1, $y + 4); $this->pdf->Cell($col4 - 2, 4, 'A', 0, 0);
|
|
|
|
// Zeile 5: Abgänge | Format | Norm
|
|
$y = $titleBlockY + (4 * $rowHeight);
|
|
$this->pdf->SetFont('dejavusans', '', 6);
|
|
$this->pdf->SetXY($titleBlockX + 1, $y + 1); $this->pdf->Cell($col1 - 2, 3, 'Abgänge', 0, 0);
|
|
$this->pdf->SetXY($titleBlockX + $col1 + 1, $y + 1); $this->pdf->Cell($col2 - 2, 3, 'Format', 0, 0);
|
|
$this->pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 1); $this->pdf->Cell($col3 + $col4 - 2, 3, 'Norm', 0, 0);
|
|
|
|
$this->pdf->SetFont('dejavusans', '', 8);
|
|
$this->pdf->SetXY($titleBlockX + 1, $y + 4); $this->pdf->Cell($col1 - 2, 4, count($this->circuitPaths), 0, 0);
|
|
$this->pdf->SetXY($titleBlockX + $col1 + 1, $y + 4); $this->pdf->Cell($col2 - 2, 4, $this->format.' '.$this->orientation, 0, 0);
|
|
$this->pdf->SetFont('dejavusans', '', 6);
|
|
$this->pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 4); $this->pdf->Cell($col3 + $col4 - 2, 4, 'DIN EN 61082 / DIN EN 81346', 0, 0);
|
|
|
|
// Zeile 6-7: Firmenname
|
|
$y = $titleBlockY + (5 * $rowHeight);
|
|
$this->pdf->SetFont('dejavusans', 'B', 9);
|
|
$this->pdf->SetXY($titleBlockX + 2, $y + 3);
|
|
$this->pdf->Cell($titleBlockWidth - 4, $rowHeight * 2 - 6, $GLOBALS['mysoc']->name ?? 'ALLES WATT LÄUFT', 0, 0, 'C');
|
|
}
|
|
}
|