- output_location (Räumlichkeit): Neues Textfeld am Abgang für Raum/Ort des Verbrauchers. DB-Migration, Backend (AJAX), Frontend (Website + PWA), Anzeige im Schaltplan (kursiv) und in PDF-Tabellen. - Verteilungs-Tabellen: Kundenansicht (A4, Nr/Verbraucher/Räumlichkeit) und Technikeransicht (A4, R.Klem/FI/Nr/Verbraucher/Räumlichkeit/Typ) im Leitungslaufplan-PDF. Gruppiert nach Feld/Reihe mit automatischem Seitenumbruch. - Bundled-Terminals Checkbox: Im Website-Abgang-Dialog (war vorher nur PWA). - PWA: Diverse Verbesserungen, Service Worker v12.4, Connection-Modal erweitert. - Typ-Flags: has_product auch für Gebäudetypen, Equipment-Typ Erweiterungen. - CLAUDE.md + Doku aktualisiert. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2131 lines
70 KiB
PHP
2131 lines
70 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'),
|
|
),
|
|
'HS3P' => 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 $hauptschalter = null;
|
|
|
|
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);
|
|
}
|
|
|
|
// Connections deduplizieren (Cross-Carrier können doppelt geladen werden)
|
|
$uniqueConns = array();
|
|
foreach ($this->allConnections as $c) {
|
|
$uniqueConns[$c->id] = $c;
|
|
}
|
|
$this->allConnections = array_values($uniqueConns);
|
|
|
|
// Connections mit fk_carrier=NULL aber fk_source/fk_target auf Equipment dieser Anlage
|
|
// (Abgänge/Eingänge werden oft ohne fk_carrier gespeichert)
|
|
$loadedConnIds = array();
|
|
foreach ($this->allConnections as $c) {
|
|
$loadedConnIds[$c->id] = true;
|
|
}
|
|
$equipmentIds = array();
|
|
foreach ($this->allEquipment as $eq) {
|
|
$equipmentIds[] = (int) $eq->id;
|
|
}
|
|
if (!empty($equipmentIds)) {
|
|
$idList = implode(',', $equipmentIds);
|
|
$sql = "SELECT c.*, src.label as source_label, src.position_te as source_pos, src.width_te as source_width,";
|
|
$sql .= " tgt.label as target_label, tgt.position_te as target_pos,";
|
|
$sql .= " bt.phases_config as busbar_phases_config";
|
|
$sql .= " FROM ".MAIN_DB_PREFIX."kundenkarte_equipment_connection as c";
|
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment as src ON c.fk_source = src.rowid";
|
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment as tgt ON c.fk_target = tgt.rowid";
|
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_busbar_type as bt ON c.fk_busbar_type = bt.rowid";
|
|
$sql .= " WHERE c.fk_carrier IS NULL AND c.status = 1";
|
|
$sql .= " AND (c.fk_source IN (".$idList.") OR c.fk_target IN (".$idList."))";
|
|
$resql = $this->db->query($sql);
|
|
if ($resql) {
|
|
while ($obj = $this->db->fetch_object($resql)) {
|
|
if (isset($loadedConnIds[$obj->rowid])) continue;
|
|
$conn = new EquipmentConnection($this->db);
|
|
$conn->id = $obj->rowid;
|
|
$conn->fk_carrier = $obj->fk_carrier;
|
|
$conn->fk_source = $obj->fk_source;
|
|
$conn->fk_target = $obj->fk_target;
|
|
$conn->source_terminal_id = $obj->source_terminal_id;
|
|
$conn->target_terminal_id = $obj->target_terminal_id;
|
|
$conn->connection_type = $obj->connection_type;
|
|
$conn->color = $obj->color;
|
|
$conn->output_label = $obj->output_label;
|
|
$conn->output_location = isset($obj->output_location) ? $obj->output_location : null;
|
|
$conn->medium_type = $obj->medium_type;
|
|
$conn->medium_spec = $obj->medium_spec;
|
|
$conn->medium_length = $obj->medium_length;
|
|
$conn->is_rail = $obj->is_rail;
|
|
$conn->path_data = $obj->path_data;
|
|
$conn->bundled_terminals = $obj->bundled_terminals;
|
|
$conn->status = $obj->status;
|
|
$this->allConnections[] = $conn;
|
|
}
|
|
$this->db->free($resql);
|
|
}
|
|
}
|
|
|
|
// 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->findHauptschalter();
|
|
$this->buildCircuitPaths();
|
|
}
|
|
|
|
/**
|
|
* Hauptschalter finden (Equipment-Typ mit Ref HS*)
|
|
*/
|
|
private function findHauptschalter()
|
|
{
|
|
foreach ($this->allEquipment as $eq) {
|
|
$typeRef = strtoupper($eq->type_ref ?? '');
|
|
if (strpos($typeRef, 'HS') === 0) {
|
|
$this->hauptschalter = $eq;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 ?: '-',
|
|
'output_location' => $conn->output_location ?: '',
|
|
'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;
|
|
}
|
|
|
|
/**
|
|
* Abgangsverzeichnis als flache Tabelle (alle Abgänge, nicht pro Hutschiene)
|
|
*
|
|
* @return array Flaches Array von Zeilen
|
|
*/
|
|
/**
|
|
* Verteilungsdaten gruppiert nach Feld (Panel) und Reihe (Carrier)
|
|
* Für Kundenansicht und Technikeransicht-Tabellen
|
|
*
|
|
* @return array Verschachtelt: [panelId => ['panel' => obj, 'carriers' => [carrierId => ['carrier' => obj, 'paths' => [...]]]]]
|
|
*/
|
|
public function getVerteilungData()
|
|
{
|
|
$grouped = array();
|
|
|
|
foreach ($this->circuitPaths as $path) {
|
|
$panel = $path['panel'];
|
|
$carrier = $path['carrier'];
|
|
$panelId = $panel ? $panel->id : 0;
|
|
$carrierId = $carrier ? $carrier->id : 0;
|
|
|
|
if (!isset($grouped[$panelId])) {
|
|
$grouped[$panelId] = array(
|
|
'panel' => $panel,
|
|
'carriers' => array(),
|
|
);
|
|
}
|
|
if (!isset($grouped[$panelId]['carriers'][$carrierId])) {
|
|
$grouped[$panelId]['carriers'][$carrierId] = array(
|
|
'carrier' => $carrier,
|
|
'paths' => array(),
|
|
);
|
|
}
|
|
$grouped[$panelId]['carriers'][$carrierId]['paths'][] = $path;
|
|
}
|
|
|
|
// Nach Panel-Position und Carrier-Position sortieren
|
|
uasort($grouped, function($a, $b) {
|
|
$posA = $a['panel'] ? ($a['panel']->position ?? 0) : 0;
|
|
$posB = $b['panel'] ? ($b['panel']->position ?? 0) : 0;
|
|
return $posA - $posB;
|
|
});
|
|
foreach ($grouped as &$pData) {
|
|
uasort($pData['carriers'], function($a, $b) {
|
|
$posA = $a['carrier'] ? ($a['carrier']->position ?? 0) : 0;
|
|
$posB = $b['carrier'] ? ($b['carrier']->position ?? 0) : 0;
|
|
return $posA - $posB;
|
|
});
|
|
}
|
|
|
|
return $grouped;
|
|
}
|
|
|
|
/**
|
|
* Abgangsverzeichnis als flache Tabelle (alle Abgänge, nicht pro Hutschiene)
|
|
*
|
|
* @return array Flaches Array von Zeilen
|
|
*/
|
|
public function getAbgangTabelle()
|
|
{
|
|
$rows = array();
|
|
|
|
foreach ($this->circuitPaths as $path) {
|
|
$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'],
|
|
'raeumlichkeit' => $path['output_location'],
|
|
'phase' => $path['phase'],
|
|
'absicherung' => $path['chain'][count($path['chain'])-2]['block_label'] ?? '',
|
|
'kabel' => trim($kabel),
|
|
'schutzgeraet' => $protLabel,
|
|
'bemerkung' => '',
|
|
);
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 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 MIN_COLUMN_WIDTH = 25;
|
|
const MAX_COLUMN_WIDTH = 50;
|
|
const COLUMN_GAP = 3;
|
|
const PHASE_GAP = 5;
|
|
|
|
// Dynamisch berechnet
|
|
private $columnWidth;
|
|
|
|
// 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;
|
|
|
|
// Einspeisung + Hauptschalter (nur Seite 1)
|
|
private $yEinspL1;
|
|
private $yEinspL2;
|
|
private $yEinspL3;
|
|
private $yHsContactTop;
|
|
|
|
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;
|
|
|
|
// TCPDF: Kein automatischer Seitenumbruch, keine Header/Footer, kein Credit-Link
|
|
$this->pdf->SetAutoPageBreak(false, 0);
|
|
$this->pdf->setPrintHeader(false);
|
|
$this->pdf->setPrintFooter(false);
|
|
// "Powered by TCPDF" Credit-Link auf letzter Seite deaktivieren
|
|
$ref = new ReflectionProperty(get_class($this->pdf), 'tcpdflink');
|
|
$ref->setAccessible(true);
|
|
$ref->setValue($this->pdf, false);
|
|
|
|
// Hack-Font registrieren (Monospace, technische Pläne)
|
|
if (class_exists('TCPDF_FONTS')) {
|
|
$fontDir = '/usr/share/fonts/TTF/';
|
|
foreach (array('Hack-Regular.ttf', 'Hack-Bold.ttf', 'Hack-Italic.ttf', 'Hack-BoldItalic.ttf') as $f) {
|
|
if (file_exists($fontDir.$f)) {
|
|
TCPDF_FONTS::addTTFfont($fontDir.$f, 'TrueTypeUnicode', '', 96);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
/**
|
|
* Seitenunabhängige Layout-Werte berechnen
|
|
*/
|
|
private function calculateLayout()
|
|
{
|
|
// Einspeisung (primäre Phasenleiter, nur Seite 1)
|
|
$this->yEinspL1 = self::MARGIN_TOP + 3;
|
|
$this->yEinspL2 = $this->yEinspL1 + 3;
|
|
$this->yEinspL3 = $this->yEinspL2 + 3;
|
|
|
|
// Hauptschalter-Kontakte (zwischen Einspeisung und Sammelschiene)
|
|
$this->yHsContactTop = $this->yEinspL3 + 5;
|
|
|
|
// N und PE werden in setPageLayout() relativ zum Content positioniert
|
|
|
|
// Max Spalten pro Seite (mit minimaler Breite berechnen)
|
|
$usableWidth = $this->pageWidth - self::MARGIN_LEFT - self::MARGIN_RIGHT - 30;
|
|
$this->maxColumnsPerPage = max(1, floor($usableWidth / (self::MIN_COLUMN_WIDTH + self::COLUMN_GAP)));
|
|
|
|
// Gesamtseiten berechnen
|
|
$totalPaths = count($this->circuitPaths);
|
|
$this->totalPages = max(1, ceil($totalPaths / $this->maxColumnsPerPage));
|
|
|
|
// Dynamische Spaltenbreite: Verfügbaren Platz gleichmäßig verteilen
|
|
$colsOnPage = min($totalPaths, $this->maxColumnsPerPage);
|
|
if ($colsOnPage > 0) {
|
|
$this->columnWidth = min(self::MAX_COLUMN_WIDTH, max(self::MIN_COLUMN_WIDTH,
|
|
($usableWidth - ($colsOnPage - 1) * self::COLUMN_GAP) / $colsOnPage));
|
|
} else {
|
|
$this->columnWidth = self::MIN_COLUMN_WIDTH;
|
|
}
|
|
|
|
// Initiales Layout (Seite 1)
|
|
$hasHS = ($this->analyzer->hauptschalter !== null);
|
|
$this->setPageLayout(true, $hasHS);
|
|
}
|
|
|
|
/**
|
|
* Seitenspezifische Y-Positionen berechnen
|
|
* Seite 1 mit HS: Sammelschiene tiefer (Platz für Einspeisung + HS)
|
|
* Seite 2+: Sammelschiene oben
|
|
*/
|
|
private function setPageLayout($isFirstPage, $hasHS)
|
|
{
|
|
if ($isFirstPage && $hasHS) {
|
|
// Sammelschiene unterhalb des Hauptschalters
|
|
$contactLen = 7;
|
|
$this->yPhaseL1 = $this->yHsContactTop + $contactLen + 5;
|
|
} else {
|
|
// Sammelschiene direkt oben
|
|
$this->yPhaseL1 = self::MARGIN_TOP + 15;
|
|
}
|
|
|
|
$this->yPhaseL2 = $this->yPhaseL1 + self::PHASE_GAP;
|
|
$this->yPhaseL3 = $this->yPhaseL2 + self::PHASE_GAP;
|
|
|
|
// Dynamische vertikale Verteilung: Verfügbaren Platz bis Titelfeld nutzen
|
|
$titleBlockTop = $this->pageHeight - 66;
|
|
$availableHeight = $titleBlockTop - $this->yPhaseL3 - 10; // 10mm Puffer
|
|
|
|
// Feste Element-Höhen
|
|
$fiHeight = 22; // FI/RCD-Symbol
|
|
$lsHeight = 18; // LS-Symbol
|
|
$labelBlock = 18; // Kabel + Abgang-Label + Nr (kompakt)
|
|
$npeGap = self::PHASE_GAP; // N-PE Abstand
|
|
|
|
$fixedContent = $fiHeight + $lsHeight + $labelBlock + $npeGap;
|
|
|
|
// Gewichtete Gaps: Verbindungslinien bekommen mehr Platz, N/PE weniger
|
|
// Gewichte: L3→FI(3), FI→LS(2), LS→Pfeil(3), Labels→N(1) = Summe 9
|
|
$totalWeight = 9;
|
|
$remainingSpace = max(0, $availableHeight - $fixedContent);
|
|
$unit = $remainingSpace / $totalWeight;
|
|
|
|
$gapPhaseToFi = max(10, $unit * 3); // Lange Leitung Phase → FI
|
|
$gapFiToLs = max(6, $unit * 2); // Kurze Verbindung FI → LS
|
|
$gapLsToConsumer = max(8, $unit * 3); // Leitung LS → Verbraucher
|
|
$gapLabelsToN = max(5, $unit * 1); // Kompakter Abstand Labels → N
|
|
|
|
$this->yFiTop = $this->yPhaseL3 + $gapPhaseToFi;
|
|
$this->yFiBottom = $this->yFiTop + $fiHeight;
|
|
|
|
$this->yLsTop = $this->yFiBottom + $gapFiToLs;
|
|
$this->yLsBottom = $this->yLsTop + $lsHeight;
|
|
|
|
$this->yConsumer = $this->yLsBottom + $gapLsToConsumer;
|
|
$this->yCableLabel = $this->yConsumer + 6;
|
|
$this->yAbgangLabel = $this->yCableLabel + 7;
|
|
$this->yAbgangNr = $this->yAbgangLabel + 5;
|
|
|
|
// N und PE unter den Abgängen
|
|
$this->yNRail = $this->yAbgangNr + $gapLabelsToN;
|
|
$this->yPeRail = $this->yNRail + $npeGap;
|
|
|
|
// Sicherheit: Nicht ins Titelfeld zeichnen
|
|
if ($this->yPeRail + 5 > $titleBlockTop) {
|
|
$this->yNRail = $titleBlockTop - 10;
|
|
$this->yPeRail = $this->yNRail + $npeGap;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Leitungslaufplan zeichnen (alle Seiten)
|
|
* Seite 1: Einspeisung → Hauptschalter → Sammelschiene → Strompfade
|
|
* Seite 2+: Sammelschiene → Strompfade
|
|
*/
|
|
public function render()
|
|
{
|
|
$totalPaths = count($this->circuitPaths);
|
|
$hasHS = ($this->analyzer->hauptschalter !== null);
|
|
|
|
if ($totalPaths === 0 && !$hasHS) {
|
|
$this->pdf->AddPage($this->orientation, array($this->pageWidth, $this->pageHeight));
|
|
$this->pdf->SetFont('hack', '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++;
|
|
$isFirstPage = ($pageNum === 1);
|
|
|
|
// Seitenspezifische Y-Positionen berechnen
|
|
$this->setPageLayout($isFirstPage, $hasHS);
|
|
|
|
$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);
|
|
|
|
// Dynamische Spaltenbreite für diese Seite
|
|
$colW = $this->columnWidth;
|
|
$totalContentWidth = $numCols * ($colW + self::COLUMN_GAP) - self::COLUMN_GAP;
|
|
$availWidth = $this->pageWidth - self::MARGIN_LEFT - self::MARGIN_RIGHT - 30;
|
|
|
|
// Zentriert positionieren
|
|
$startX = self::MARGIN_LEFT + 30 + ($availWidth - $totalContentWidth) / 2;
|
|
$endX = $startX + $totalContentWidth;
|
|
|
|
// Einspeisung + Hauptschalter (nur Seite 1, zentriert über Strompfaden)
|
|
if ($isFirstPage && $hasHS) {
|
|
$contentCenter = $startX + $totalContentWidth / 2;
|
|
$this->drawEinspeisungUndHauptschalter($contentCenter);
|
|
}
|
|
|
|
// Sammelschiene-Labels links
|
|
$this->drawPhaseLabels();
|
|
|
|
// Sammelschiene (L1, L2, L3) über volle Breite
|
|
$railStartX = self::MARGIN_LEFT + 25;
|
|
$railEndX = $this->pageWidth - self::MARGIN_RIGHT;
|
|
$this->drawPhaseRails($railStartX, $railEndX);
|
|
|
|
// N und PE unten über volle Breite
|
|
$this->drawNPeRails($railStartX, $railEndX);
|
|
|
|
// FI-Gruppen identifizieren für Trennlinien
|
|
$lastProtId = null;
|
|
|
|
// Strompfad-Spalten zeichnen
|
|
for ($col = 0; $col < $numCols; $col++) {
|
|
$path = $pagePaths[$col];
|
|
$x = $startX + $col * ($colW + self::COLUMN_GAP) + $colW / 2;
|
|
|
|
// FI-Gruppen-Trenner
|
|
$currentProtId = $path['protection_device'] ? $path['protection_device']->id : 0;
|
|
if ($lastProtId !== null && $lastProtId !== $currentProtId && $col > 0) {
|
|
$sepX = $x - ($colW + self::COLUMN_GAP) / 2;
|
|
$this->pdf->SetLineStyle(array('width' => 0.2, 'dash' => '2,2', 'color' => array(180, 180, 180)));
|
|
$this->pdf->Line($sepX, $this->yPhaseL1 - 5, $sepX, $this->yPeRail + 5);
|
|
$this->pdf->SetLineStyle(array('dash' => 0));
|
|
}
|
|
$lastProtId = $currentProtId;
|
|
|
|
$this->drawCircuitColumn($path, $x);
|
|
}
|
|
|
|
// Mini-Legende auf Seite 1 (unten links, neben dem Titelfeld)
|
|
if ($isFirstPage) {
|
|
$this->drawMiniLegende();
|
|
}
|
|
|
|
// Titelfeld
|
|
$this->drawTitleBlock($pageNum, $this->totalPages + 1); // +1 für Tabelle
|
|
|
|
$pathIndex += $numCols;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Phase-Labels links zeichnen
|
|
*/
|
|
private function drawPhaseLabels()
|
|
{
|
|
$x = self::MARGIN_LEFT;
|
|
$this->pdf->SetFont('hack', '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);
|
|
}
|
|
|
|
/**
|
|
* Kompakte Legende unten links auf Seite 1 (neben dem Titelfeld)
|
|
*/
|
|
private function drawMiniLegende()
|
|
{
|
|
$titleBlockWidth = 180;
|
|
$titleBlockX = $this->pageWidth - $titleBlockWidth - 10;
|
|
$titleBlockY = $this->pageHeight - 56 - 10;
|
|
|
|
// Legende links neben dem Titelfeld
|
|
$legendX = self::MARGIN_LEFT;
|
|
$legendY = $titleBlockY;
|
|
$legendWidth = $titleBlockX - $legendX - 5;
|
|
|
|
// Phasenfarben als horizontale Reihe
|
|
$this->pdf->SetFont('hack', '', 5);
|
|
$phaseList = array(
|
|
'L1' => 'Braun', 'L2' => 'Schwarz', 'L3' => 'Grau',
|
|
'N' => 'Blau', 'PE' => 'Grün-Gelb',
|
|
);
|
|
|
|
$px = $legendX;
|
|
$py = $legendY + 2;
|
|
foreach ($phaseList as $phase => $farbe) {
|
|
$rgb = getPhaseColorRGB($phase);
|
|
$this->pdf->SetFillColor($rgb[0], $rgb[1], $rgb[2]);
|
|
$this->pdf->Rect($px, $py, 8, 3, 'F');
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
$this->pdf->Text($px + 9, $py + 1.5, $phase);
|
|
$px += 18;
|
|
}
|
|
|
|
// Symbol-Erklärungen darunter (Zeile 2)
|
|
$py += 7;
|
|
$this->pdf->SetFont('hack', '', 4.5);
|
|
$this->pdf->SetTextColor(100, 100, 100);
|
|
|
|
// LS-Symbol mini
|
|
$this->pdf->SetDrawColor(0, 0, 0);
|
|
$this->pdf->SetLineWidth(0.2);
|
|
$sx = $legendX + 2;
|
|
$this->pdf->Line($sx, $py, $sx, $py + 2);
|
|
$this->pdf->Line($sx, $py + 2, $sx + 2, $py + 4);
|
|
$this->pdf->Rect($sx - 1, $py + 4, 2, 1.5, 'D');
|
|
$this->pdf->Line($sx, $py + 5.5, $sx, $py + 7);
|
|
$this->pdf->Text($sx + 4, $py + 3, 'LS-Schalter');
|
|
|
|
// FI-Symbol mini
|
|
$sx = $legendX + 32;
|
|
$this->pdf->Rect($sx - 2, $py, 4, 7, 'D');
|
|
$this->pdf->Circle($sx, $py + 3.5, 1.5, 0, 360, 'D');
|
|
$this->pdf->Text($sx + 4, $py + 3, 'FI/RCD');
|
|
|
|
// Abgang-Pfeil mini
|
|
$sx = $legendX + 58;
|
|
$this->pdf->SetFillColor(0, 0, 0);
|
|
$this->pdf->Polygon(array($sx - 1.5, $py + 1, $sx + 1.5, $py + 1, $sx, $py + 4), 'F');
|
|
$this->pdf->Text($sx + 4, $py + 2, 'Abgang');
|
|
|
|
// Norm-Hinweis (Zeile 3)
|
|
$py += 10;
|
|
$this->pdf->SetFont('hack', 'I', 4);
|
|
$this->pdf->SetTextColor(140, 140, 140);
|
|
$this->pdf->Text($legendX, $py, 'DIN EN 61082 / DIN EN 81346 / DIN EN 60617');
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* Einspeisung + Hauptschalter zeichnen (nur Seite 1)
|
|
* 3 kurze Einspeise-Phasenleiter oben, darunter 3-poliger HS mit
|
|
* mechanischer Kopplung, verbunden mit der Sammelschiene darunter.
|
|
*/
|
|
private function drawEinspeisungUndHauptschalter($centerX = null)
|
|
{
|
|
$hs = $this->analyzer->hauptschalter;
|
|
if (!$hs) return;
|
|
|
|
$spacing = 6; // Abstand zwischen den 3 HS-Polen
|
|
$hsCenter = $centerX ?: (self::MARGIN_LEFT + 30); // Zentriert über Strompfaden
|
|
$contactLen = 7;
|
|
|
|
$poleX = array(
|
|
'L1' => $hsCenter - $spacing,
|
|
'L2' => $hsCenter,
|
|
'L3' => $hsCenter + $spacing,
|
|
);
|
|
|
|
$einspY = array(
|
|
'L1' => $this->yEinspL1,
|
|
'L2' => $this->yEinspL2,
|
|
'L3' => $this->yEinspL3,
|
|
);
|
|
|
|
$sammelY = array(
|
|
'L1' => $this->yPhaseL1,
|
|
'L2' => $this->yPhaseL2,
|
|
'L3' => $this->yPhaseL3,
|
|
);
|
|
|
|
// "Einspeisung" Label (links neben der Schaltgruppe)
|
|
$this->pdf->SetFont('hack', '', 5);
|
|
$this->pdf->SetTextColor(120, 120, 120);
|
|
$this->pdf->Text($hsCenter - $spacing - 22, $this->yEinspL2 - 1, 'Einspeisung');
|
|
|
|
// Einspeisung-Rails (kurze Linien) + HS-Kontakte + Verbindung zur Sammelschiene
|
|
$einspStartX = $hsCenter - $spacing - 12;
|
|
$einspEndX = $hsCenter + $spacing + 12;
|
|
$couplingPoints = array();
|
|
|
|
$this->pdf->SetLineWidth(0.5);
|
|
foreach (array('L1', 'L2', 'L3') as $phase) {
|
|
$rgb = getPhaseColorRGB($phase);
|
|
$y = $einspY[$phase];
|
|
$px = $poleX[$phase];
|
|
|
|
// Einspeisung-Rail (kurze horizontale Linie)
|
|
$this->pdf->SetDrawColor($rgb[0], $rgb[1], $rgb[2]);
|
|
$this->pdf->Line($einspStartX, $y, $einspEndX, $y);
|
|
|
|
// Phase-Label auf der Einspeisung
|
|
$this->pdf->SetFont('hack', '', 5);
|
|
$this->pdf->SetTextColor($rgb[0], $rgb[1], $rgb[2]);
|
|
$this->pdf->Text($einspStartX - 7, $y - 1, $phase);
|
|
|
|
// Anschluss-Punkt auf Einspeisung
|
|
$this->pdf->Circle($px, $y, 1, 0, 360, 'F', array(), array($rgb[0], $rgb[1], $rgb[2]));
|
|
|
|
// Vertikale Linie Einspeisung → Schaltkontakt
|
|
$this->pdf->SetDrawColor($rgb[0], $rgb[1], $rgb[2]);
|
|
$this->pdf->SetLineWidth(0.4);
|
|
$this->pdf->Line($px, $y, $px, $this->yHsContactTop);
|
|
|
|
// Schaltkontakt-Symbol (DIN EN 60617)
|
|
$this->pdf->SetDrawColor(0, 0, 0);
|
|
$this->pdf->SetLineWidth(0.3);
|
|
$this->pdf->Circle($px, $this->yHsContactTop, 0.8, 0, 360, 'F', array(), array(0, 0, 0));
|
|
$this->pdf->Line($px, $this->yHsContactTop, $px + 3, $this->yHsContactTop + $contactLen);
|
|
$this->pdf->Circle($px, $this->yHsContactTop + $contactLen + 1, 0.8, 0, 360, 'F', array(), array(0, 0, 0));
|
|
|
|
// Vertikale Linie Schaltkontakt → Sammelschiene
|
|
$this->pdf->SetDrawColor($rgb[0], $rgb[1], $rgb[2]);
|
|
$this->pdf->SetLineWidth(0.4);
|
|
$this->pdf->Line($px, $this->yHsContactTop + $contactLen + 1, $px, $sammelY[$phase]);
|
|
|
|
// Anschluss-Punkt auf Sammelschiene
|
|
$this->pdf->Circle($px, $sammelY[$phase], 1, 0, 360, 'F', array(), array($rgb[0], $rgb[1], $rgb[2]));
|
|
|
|
// Kopplungspunkt merken (Mitte des Schaltkontakts)
|
|
$couplingPoints[] = array('x' => $px + 1.5, 'y' => $this->yHsContactTop + $contactLen * 0.4);
|
|
}
|
|
|
|
// Mechanische Kopplung (gestrichelte Linie durch die Schaltkontakte)
|
|
if (count($couplingPoints) >= 2) {
|
|
$this->pdf->SetLineStyle(array('width' => 0.3, 'dash' => '1.5,1', 'color' => array(0, 0, 0)));
|
|
for ($j = 0; $j < count($couplingPoints) - 1; $j++) {
|
|
$this->pdf->Line(
|
|
$couplingPoints[$j]['x'], $couplingPoints[$j]['y'],
|
|
$couplingPoints[$j+1]['x'], $couplingPoints[$j+1]['y']
|
|
);
|
|
}
|
|
$this->pdf->SetLineStyle(array('dash' => 0));
|
|
}
|
|
|
|
// HS-Bezeichnung links (z.B. "Q0")
|
|
$label = $hs->label ?: 'Q0';
|
|
$this->pdf->SetFont('hack', 'B', 7);
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
$midY = $this->yHsContactTop + $contactLen / 2;
|
|
$this->pdf->Text($poleX['L1'] - 12, $midY, $label);
|
|
|
|
// Block-Label rechts (z.B. "63A 3P")
|
|
$blockLabel = method_exists($hs, 'getBlockLabel') ? $hs->getBlockLabel() : '';
|
|
if ($blockLabel) {
|
|
$this->pdf->SetFont('hack', '', 6);
|
|
$this->pdf->Text($poleX['L3'] + 5, $midY, $blockLabel);
|
|
}
|
|
|
|
// Typ-Bezeichnung links unter der HS-Bezeichnung (nicht im Symbol-Bereich)
|
|
$typeLabel = $hs->type_label_short ?? ($hs->type_label ?? '');
|
|
if ($typeLabel) {
|
|
$this->pdf->SetFont('hack', '', 5);
|
|
$this->pdf->SetTextColor(100, 100, 100);
|
|
$this->pdf->Text($poleX['L1'] - 12, $midY + 5, $typeLabel);
|
|
}
|
|
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
}
|
|
|
|
/**
|
|
* Eine Strompfad-Spalte zeichnen
|
|
*/
|
|
private function drawCircuitColumn($path, $x)
|
|
{
|
|
$phase = $path['phase'];
|
|
$phaseRGB = $path['phase_color_rgb'];
|
|
$conn = $path['connection'];
|
|
|
|
// 3-Phasen erkennen: bundled_terminals = 'all' oder Phase = 3P/3P+N/3P+N+PE
|
|
$is3Phase = (!empty($conn->bundled_terminals) && $conn->bundled_terminals === 'all')
|
|
|| in_array($phase, array('3P', '3P+N', '3P+N+PE'));
|
|
|
|
$hasFI = !empty($path['protection_device']);
|
|
$topTarget = $hasFI ? $this->yFiTop : $this->yLsTop;
|
|
$colW = $this->columnWidth;
|
|
|
|
if ($is3Phase) {
|
|
// === Drehstrom: 3 Linien von L1, L2, L3 konvergieren zum LS ===
|
|
$phaseSpacing = 3; // Horizontaler Abstand der 3 Pole
|
|
$mergeY = $this->yPhaseL3 + ($topTarget - $this->yPhaseL3) * 0.3; // Konvergenz-Punkt
|
|
|
|
foreach (array('L1' => $this->yPhaseL1, 'L2' => $this->yPhaseL2, 'L3' => $this->yPhaseL3) as $ph => $pY) {
|
|
$rgb = getPhaseColorRGB($ph);
|
|
$this->pdf->SetDrawColor($rgb[0], $rgb[1], $rgb[2]);
|
|
$this->pdf->SetLineWidth(0.4);
|
|
|
|
// Punkt auf Phase-Rail
|
|
$this->pdf->Circle($x, $pY, 1, 0, 360, 'F', array(), array($rgb[0], $rgb[1], $rgb[2]));
|
|
|
|
// Diagonale Linie von Phase-Rail zum Konvergenz-Punkt
|
|
$offset = ($ph === 'L1') ? -$phaseSpacing : (($ph === 'L3') ? $phaseSpacing : 0);
|
|
$this->pdf->Line($x, $pY, $x + $offset, $mergeY);
|
|
|
|
// Vertikale Linie vom Konvergenz-Punkt zum FI/LS
|
|
$this->pdf->Line($x + $offset, $mergeY, $x + $offset, $topTarget);
|
|
}
|
|
|
|
// FI/RCD
|
|
if ($hasFI) {
|
|
$this->drawRCDSymbol($x, $this->yFiTop, $path['protection_device']);
|
|
// 3 Linien FI → LS
|
|
foreach (array('L1', 'L2', 'L3') as $i => $ph) {
|
|
$rgb = getPhaseColorRGB($ph);
|
|
$this->pdf->SetDrawColor($rgb[0], $rgb[1], $rgb[2]);
|
|
$this->pdf->SetLineWidth(0.4);
|
|
$offset = ($i - 1) * $phaseSpacing;
|
|
$this->pdf->Line($x + $offset, $this->yFiBottom, $x + $offset, $this->yLsTop);
|
|
}
|
|
}
|
|
|
|
// 3-poliger LS-Schalter
|
|
$this->draw3PhaseBreakerSymbol($x, $this->yLsTop, $path['breaker'], $path['chain'], $phaseSpacing);
|
|
|
|
// 3 Linien LS → konvergieren → 1 Linie zum Verbraucher
|
|
$mergeYBottom = $this->yLsBottom + ($this->yConsumer - $this->yLsBottom) * 0.4;
|
|
foreach (array('L1', 'L2', 'L3') as $i => $ph) {
|
|
$rgb = getPhaseColorRGB($ph);
|
|
$this->pdf->SetDrawColor($rgb[0], $rgb[1], $rgb[2]);
|
|
$this->pdf->SetLineWidth(0.4);
|
|
$offset = ($i - 1) * $phaseSpacing;
|
|
$this->pdf->Line($x + $offset, $this->yLsBottom, $x, $mergeYBottom);
|
|
}
|
|
|
|
// Einzelne Linie zum Verbraucher
|
|
$this->pdf->SetDrawColor($phaseRGB[0], $phaseRGB[1], $phaseRGB[2]);
|
|
$this->pdf->SetLineWidth(0.4);
|
|
$this->pdf->Line($x, $mergeYBottom, $x, $this->yConsumer);
|
|
|
|
} else {
|
|
// === Einphasig: Eine Linie von der Phase ===
|
|
$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);
|
|
|
|
// Punkt auf Phase-Rail
|
|
$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
|
|
if ($hasFI) {
|
|
$this->drawRCDSymbol($x, $this->yFiTop, $path['protection_device']);
|
|
$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 (einpolig)
|
|
$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 (für beide: einphasig und 3-phasig)
|
|
$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');
|
|
|
|
// Gestrichelte Linie Abgang → N-Leiter
|
|
$this->pdf->SetLineStyle(array('width' => 0.15, 'dash' => '1,2', 'color' => array(180, 180, 180)));
|
|
$this->pdf->Line($x, $this->yConsumer + 2, $x, $this->yNRail);
|
|
$this->pdf->SetLineStyle(array('dash' => 0));
|
|
|
|
// Kabelbezeichnung
|
|
$this->pdf->SetFont('hack', '', 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 - $colW/2, $this->yCableLabel);
|
|
$this->pdf->MultiCell($colW, 3, $cableText, 0, 'C');
|
|
}
|
|
|
|
// Abgang-Label (Verbraucher-Name)
|
|
$this->pdf->SetFont('hack', 'B', 7);
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
$this->pdf->SetXY($x - $colW/2, $this->yAbgangLabel);
|
|
$this->pdf->MultiCell($colW, 3, $path['output_label'], 0, 'C');
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
|
|
// Equipment-Label rechts oben (z.B. "R2.1") — deutlich sichtbar
|
|
$label = $eq->label ?: '';
|
|
if ($label) {
|
|
$this->pdf->SetFont('hack', 'B', 8);
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
$this->pdf->Text($x + 5, $y + 5, $label);
|
|
}
|
|
|
|
// Block-Label rechts darunter (z.B. "B16")
|
|
$blockLabel = '';
|
|
foreach ($chain as $c) {
|
|
if ($c['type'] === 'breaker') {
|
|
$blockLabel = $c['block_label'] ?? '';
|
|
break;
|
|
}
|
|
}
|
|
if ($blockLabel) {
|
|
$this->pdf->SetFont('hack', '', 6);
|
|
$this->pdf->SetTextColor(80, 80, 80);
|
|
$this->pdf->Text($x + 5, $y + 9, $blockLabel);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 3-poliger LS-Schalter (Drehstrom)
|
|
* 3 parallele Schaltkontakte mit mechanischer Kopplung
|
|
*/
|
|
private function draw3PhaseBreakerSymbol($x, $y, $eq, $chain, $phaseSpacing = 3)
|
|
{
|
|
$contactLen = 7;
|
|
$couplingPoints = array();
|
|
|
|
// 3 Pole zeichnen (L1=links, L2=mitte, L3=rechts)
|
|
foreach (array('L1', 'L2', 'L3') as $i => $ph) {
|
|
$rgb = getPhaseColorRGB($ph);
|
|
$offset = ($i - 1) * $phaseSpacing;
|
|
$px = $x + $offset;
|
|
|
|
$this->pdf->SetDrawColor(0, 0, 0);
|
|
$this->pdf->SetLineWidth(0.3);
|
|
|
|
// Eingangs-Punkt oben
|
|
$this->pdf->Line($px, $y, $px, $y + 4);
|
|
|
|
// Schaltkontakt (schräge Linie)
|
|
$this->pdf->Line($px, $y + 4, $px + 3, $y + 8);
|
|
|
|
// Auslöser (kleines Rechteck)
|
|
$this->pdf->SetFillColor(255, 255, 255);
|
|
$this->pdf->Rect($px - 1.5, $y + 8, 3, 2.5, 'DF');
|
|
|
|
// Ausgangs-Linie unten
|
|
$this->pdf->Line($px, $y + 10.5, $px, $y + 18);
|
|
|
|
// Kopplungspunkt merken
|
|
$couplingPoints[] = array('x' => $px + 1.5, 'y' => $y + 4 + $contactLen * 0.4);
|
|
}
|
|
|
|
// Mechanische Kopplung (gestrichelte Linie durch die Schaltkontakte)
|
|
if (count($couplingPoints) >= 2) {
|
|
$this->pdf->SetLineStyle(array('width' => 0.25, 'dash' => '1.5,1', 'color' => array(0, 0, 0)));
|
|
for ($j = 0; $j < count($couplingPoints) - 1; $j++) {
|
|
$this->pdf->Line(
|
|
$couplingPoints[$j]['x'], $couplingPoints[$j]['y'],
|
|
$couplingPoints[$j+1]['x'], $couplingPoints[$j+1]['y']
|
|
);
|
|
}
|
|
$this->pdf->SetLineStyle(array('dash' => 0));
|
|
}
|
|
|
|
// Equipment-Label rechts oben (z.B. "R3.1")
|
|
$label = $eq->label ?: '';
|
|
if ($label) {
|
|
$this->pdf->SetFont('hack', 'B', 8);
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
$this->pdf->Text($x + $phaseSpacing + 5, $y + 5, $label);
|
|
}
|
|
|
|
// Block-Label rechts darunter (z.B. "LS 3P")
|
|
$blockLabel = '';
|
|
foreach ($chain as $c) {
|
|
if ($c['type'] === 'breaker') {
|
|
$blockLabel = $c['block_label'] ?? '';
|
|
break;
|
|
}
|
|
}
|
|
if ($blockLabel) {
|
|
$this->pdf->SetFont('hack', '', 6);
|
|
$this->pdf->SetTextColor(80, 80, 80);
|
|
$this->pdf->Text($x + $phaseSpacing + 5, $y + 9, $blockLabel);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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('hack', '', 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('hack', '', 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);
|
|
}
|
|
|
|
/**
|
|
* Abgangsverzeichnis als einzelne Tabelle zeichnen
|
|
*/
|
|
public function renderAbgangTabelle()
|
|
{
|
|
$rows = $this->analyzer->getAbgangTabelle();
|
|
if (empty($rows)) return;
|
|
|
|
// Spaltenbreiten (volle Seitenbreite nutzen)
|
|
$totalWidth = $this->pageWidth - self::MARGIN_LEFT - self::MARGIN_RIGHT;
|
|
$colWidths = array(
|
|
round($totalWidth * 0.06), // Abg.Nr.
|
|
round($totalWidth * 0.22), // Bezeichnung
|
|
round($totalWidth * 0.06), // Phase
|
|
round($totalWidth * 0.10), // Absicherung
|
|
round($totalWidth * 0.18), // Kabel
|
|
round($totalWidth * 0.18), // Schutzgerät
|
|
);
|
|
$colWidths[] = $totalWidth - array_sum($colWidths); // Bemerkung = Rest
|
|
$colHeaders = array('Abg.Nr.', 'Bezeichnung', 'Phase', 'Absicherung', 'Kabel', 'Schutzgerät', 'Bemerkung');
|
|
|
|
$this->pdf->AddPage($this->orientation, array($this->pageWidth, $this->pageHeight));
|
|
|
|
$y = self::MARGIN_TOP;
|
|
|
|
// Titel mit Unterstrich
|
|
$this->pdf->SetFont('hack', 'B', 14);
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
$this->pdf->Text(self::MARGIN_LEFT, $y + 6, 'ABGANGSVERZEICHNIS');
|
|
$y += 10;
|
|
|
|
// Untertitel: Anlage + HS-Info
|
|
$subText = $this->anlage->label;
|
|
$hs = $this->analyzer->hauptschalter;
|
|
if ($hs) {
|
|
$hsLabel = $hs->label ?: 'Hauptschalter';
|
|
$hsBlock = method_exists($hs, 'getBlockLabel') ? $hs->getBlockLabel() : '';
|
|
if ($hsBlock) $hsLabel .= ' '.$hsBlock;
|
|
$subText .= ' | Vorsicherung: '.$hsLabel;
|
|
}
|
|
$this->pdf->SetFont('hack', '', 9);
|
|
$this->pdf->SetTextColor(80, 80, 80);
|
|
$this->pdf->Text(self::MARGIN_LEFT, $y + 4, $subText);
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
|
|
// Trennlinie
|
|
$this->pdf->SetDrawColor(0, 0, 0);
|
|
$this->pdf->SetLineWidth(0.5);
|
|
$y += 7;
|
|
$this->pdf->Line(self::MARGIN_LEFT, $y, self::MARGIN_LEFT + $totalWidth, $y);
|
|
$y += 5;
|
|
|
|
// Spalten-Header
|
|
$headerHeight = 9;
|
|
$rowHeight = 8;
|
|
$this->pdf->SetFont('hack', 'B', 8);
|
|
$this->pdf->SetFillColor(60, 60, 60);
|
|
$this->pdf->SetTextColor(255, 255, 255);
|
|
$x = self::MARGIN_LEFT;
|
|
for ($i = 0; $i < count($colHeaders); $i++) {
|
|
$this->pdf->SetXY($x, $y);
|
|
$this->pdf->Cell($colWidths[$i], $headerHeight, $colHeaders[$i], 1, 0, 'C', true);
|
|
$x += $colWidths[$i];
|
|
}
|
|
$y += $headerHeight;
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
|
|
// Datenzeilen
|
|
$this->pdf->SetFont('hack', '', 8);
|
|
|
|
foreach ($rows as $rowIdx => $row) {
|
|
if ($rowIdx % 2 === 0) $this->pdf->SetFillColor(245, 245, 245);
|
|
else $this->pdf->SetFillColor(255, 255, 255);
|
|
|
|
// Phasenfarbe für Phase-Zelle
|
|
$phaseRGB = getPhaseColorRGB($row['phase']);
|
|
|
|
$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-Zelle farbig hervorheben
|
|
if ($i === 2) {
|
|
$this->pdf->SetTextColor($phaseRGB[0], $phaseRGB[1], $phaseRGB[2]);
|
|
$this->pdf->SetFont('hack', 'B', 8);
|
|
}
|
|
$this->pdf->Cell($colWidths[$i], $rowHeight, $cells[$i], 1, 0, $align, true);
|
|
if ($i === 2) {
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
$this->pdf->SetFont('hack', '', 8);
|
|
}
|
|
$x += $colWidths[$i];
|
|
}
|
|
$y += $rowHeight;
|
|
|
|
// Seitenumbruch mit erneuter Kopfzeile
|
|
if ($y > $this->pageHeight - 30) {
|
|
$this->drawTitleBlock(0, 0);
|
|
$this->pdf->AddPage($this->orientation, array($this->pageWidth, $this->pageHeight));
|
|
$y = self::MARGIN_TOP;
|
|
|
|
// Kopfzeile wiederholen
|
|
$this->pdf->SetFont('hack', 'B', 8);
|
|
$this->pdf->SetFillColor(60, 60, 60);
|
|
$this->pdf->SetTextColor(255, 255, 255);
|
|
$x = self::MARGIN_LEFT;
|
|
for ($i = 0; $i < count($colHeaders); $i++) {
|
|
$this->pdf->SetXY($x, $y);
|
|
$this->pdf->Cell($colWidths[$i], $headerHeight, $colHeaders[$i], 1, 0, 'C', true);
|
|
$x += $colWidths[$i];
|
|
}
|
|
$y += $headerHeight;
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
$this->pdf->SetFont('hack', '', 8);
|
|
}
|
|
}
|
|
|
|
// 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('hack', 'B', 14);
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
$this->pdf->Text($x, $y + 5, 'Legende');
|
|
$y += 15;
|
|
|
|
// Phasenfarben
|
|
$this->pdf->SetFont('hack', '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('hack', '', 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('hack', 'B', 10);
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
$this->pdf->Text($x, $y, 'Symbole');
|
|
$y += 10;
|
|
|
|
// Hauptschalter-Symbol (3-polig mit mechanischer Kopplung)
|
|
$this->pdf->SetDrawColor(0, 0, 0);
|
|
$this->pdf->SetLineWidth(0.3);
|
|
$hsSymX = $x + 5;
|
|
$hsSymY = $y + 2;
|
|
$hsSpacing = 5;
|
|
for ($p = 0; $p < 3; $p++) {
|
|
$px = $hsSymX + $p * $hsSpacing;
|
|
$this->pdf->Circle($px, $hsSymY, 0.7, 0, 360, 'F', array(), array(0, 0, 0));
|
|
$this->pdf->Line($px, $hsSymY, $px + 2, $hsSymY + 5);
|
|
$this->pdf->Circle($px, $hsSymY + 6, 0.7, 0, 360, 'F', array(), array(0, 0, 0));
|
|
}
|
|
// Mechanische Kopplung
|
|
$this->pdf->SetLineStyle(array('width' => 0.2, 'dash' => '1,1', 'color' => array(0, 0, 0)));
|
|
$this->pdf->Line($hsSymX + 1, $hsSymY + 2, $hsSymX + 2 * $hsSpacing + 1, $hsSymY + 2);
|
|
$this->pdf->SetLineStyle(array('dash' => 0));
|
|
$this->pdf->SetFont('hack', '', 8);
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
$this->pdf->Text($x + 30, $y + 5, 'Hauptschalter 3-polig (Q0)');
|
|
$y += 14;
|
|
|
|
// LS-Symbol + Beschreibung
|
|
$this->pdf->SetFont('hack', '', 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('hack', '', 5);
|
|
$this->pdf->Text($x + 2, $y + 17, 'FI/RCD');
|
|
$this->pdf->SetFont('hack', '', 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('hack', '', 8);
|
|
$this->pdf->Text($x + 30, $y + 3, 'Abgang zum Verbraucher');
|
|
$y += 15;
|
|
|
|
// Norm-Hinweis
|
|
$y += 10;
|
|
$this->pdf->SetFont('hack', '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 (nur bis Zeile 5, Zeile 6-7 = Firmenname ohne Trennung)
|
|
for ($i = 1; $i <= 5; $i++) {
|
|
$y = $titleBlockY + ($i * $rowHeight);
|
|
$this->pdf->Line($titleBlockX, $y, $titleBlockX + $titleBlockWidth, $y);
|
|
}
|
|
|
|
// Vertikale Linien (nur bis Zeile 5, Firmenname spannt volle Breite)
|
|
$verticalBottom = $titleBlockY + (5 * $rowHeight);
|
|
$this->pdf->Line($titleBlockX + $col1, $titleBlockY, $titleBlockX + $col1, $verticalBottom);
|
|
$this->pdf->Line($titleBlockX + $col1 + $col2, $titleBlockY, $titleBlockX + $col1 + $col2, $verticalBottom);
|
|
$this->pdf->Line($titleBlockX + $col1 + $col2 + $col3, $titleBlockY, $titleBlockX + $col1 + $col2 + $col3, $verticalBottom);
|
|
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
|
|
// Zeile 1: Titel
|
|
$this->pdf->SetFont('hack', '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('hack', '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('hack', '', 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('hack', '', 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('hack', '', 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('hack', '', 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('hack', '', 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('hack', '', 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('hack', '', 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('hack', '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');
|
|
}
|
|
|
|
// ======================================================================
|
|
// Verteilungs-Tabellen (A4 Hochformat)
|
|
// ======================================================================
|
|
|
|
/**
|
|
* Gemeinsamer Seiten-Header für Verteilungs-Tabellen
|
|
* Titel, Kundendaten, Firmenlogo
|
|
*
|
|
* @param string $title Seitentitel (z.B. "Verteilung")
|
|
* @return float Y-Position nach dem Header
|
|
*/
|
|
private function drawVerteilungHeader($title)
|
|
{
|
|
$margin = 20;
|
|
|
|
// Titel
|
|
$this->pdf->SetFont('hack', 'B', 16);
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
$this->pdf->SetXY($margin, 15);
|
|
$this->pdf->Cell(170, 10, $title, 0, 0, 'C');
|
|
// Unterstrich
|
|
$this->pdf->SetLineWidth(0.5);
|
|
$this->pdf->SetDrawColor(0, 0, 0);
|
|
$titleWidth = $this->pdf->GetStringWidth($title);
|
|
$titleCenter = $margin + 85;
|
|
$this->pdf->Line($titleCenter - $titleWidth / 2, 26, $titleCenter + $titleWidth / 2, 26);
|
|
|
|
// Felder links
|
|
$this->pdf->SetFont('hack', 'B', 9);
|
|
$labelX = $margin + 5;
|
|
$valueX = $margin + 30;
|
|
$y = 32;
|
|
|
|
$fields = array(
|
|
'Name:' => $this->societe->name ?? '',
|
|
'Objekt:' => trim(($this->societe->address ?? '').' '.($this->societe->zip ?? '').' '.($this->societe->town ?? '')),
|
|
'Raum:' => '',
|
|
'Verteilung:' => $this->anlage->label ?? '',
|
|
);
|
|
|
|
foreach ($fields as $label => $value) {
|
|
$this->pdf->SetFont('hack', 'B', 9);
|
|
$this->pdf->Text($labelX, $y, $label);
|
|
$this->pdf->SetFont('hack', '', 9);
|
|
$this->pdf->Text($valueX, $y, dol_trunc($value, 50));
|
|
$y += 5;
|
|
}
|
|
|
|
// Firmenname rechts oben
|
|
$this->pdf->SetFont('hack', 'B', 10);
|
|
$this->pdf->SetXY(140, 32);
|
|
$this->pdf->Cell(50, 5, $GLOBALS['mysoc']->name ?? 'ALLES WATT LÄUFT', 0, 0, 'R');
|
|
$this->pdf->SetFont('hack', '', 7);
|
|
$this->pdf->SetTextColor(100, 100, 100);
|
|
$this->pdf->SetXY(140, 38);
|
|
$fullName = $this->user->getFullName($GLOBALS['langs']);
|
|
$this->pdf->Cell(50, 4, 'Inh. '.$fullName, 0, 0, 'R');
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
|
|
// Trennlinie
|
|
$y = 56;
|
|
$this->pdf->SetLineWidth(0.3);
|
|
$this->pdf->Line($margin, $y, 210 - $margin, $y);
|
|
|
|
return $y + 8;
|
|
}
|
|
|
|
/**
|
|
* Tabellen-Sektion zeichnen: "Feld X - Reihe Y" Header + Tabelle
|
|
*
|
|
* @param float $y Aktuelle Y-Position
|
|
* @param string $sectionTitle z.B. "Feld 2 - Reihe 3"
|
|
* @param array $colHeaders Spalten-Überschriften
|
|
* @param array $colWidths Spaltenbreiten
|
|
* @param array $rows Datenzeilen (je ein Array mit Werten in gleicher Reihenfolge wie colHeaders)
|
|
* @param string $pageTitle Für Header-Wiederholung bei Seitenumbruch
|
|
* @return float Neue Y-Position nach der Tabelle
|
|
*/
|
|
private function drawVerteilungTable(&$y, $sectionTitle, $colHeaders, $colWidths, $rows, $pageTitle)
|
|
{
|
|
$margin = 20;
|
|
$headerHeight = 7;
|
|
$rowHeight = 6.5;
|
|
$totalWidth = array_sum($colWidths);
|
|
$pageBottom = 277; // A4: 297 - 20mm Rand
|
|
|
|
// Prüfen ob genug Platz für Header + mindestens 2 Zeilen
|
|
if ($y + 20 + $headerHeight + $rowHeight * 2 > $pageBottom) {
|
|
$this->pdf->AddPage('P', array(210, 297));
|
|
$y = $this->drawVerteilungHeader($pageTitle);
|
|
}
|
|
|
|
// Sektions-Header (z.B. "Feld 2 - Reihe 3")
|
|
$this->pdf->SetFont('hack', 'B', 9);
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
$this->pdf->SetFillColor(230, 230, 230);
|
|
$this->pdf->SetXY($margin, $y);
|
|
$this->pdf->Cell($totalWidth, $headerHeight, ' '.$sectionTitle, 1, 0, 'L', true);
|
|
$y += $headerHeight;
|
|
|
|
// Spalten-Header
|
|
$this->pdf->SetFont('hack', 'B', 8);
|
|
$this->pdf->SetFillColor(240, 240, 240);
|
|
$this->pdf->SetTextColor(0, 0, 0);
|
|
$x = $margin;
|
|
for ($i = 0; $i < count($colHeaders); $i++) {
|
|
$this->pdf->SetXY($x, $y);
|
|
$this->pdf->Cell($colWidths[$i], $headerHeight, $colHeaders[$i], 1, 0, 'C', true);
|
|
$x += $colWidths[$i];
|
|
}
|
|
$y += $headerHeight;
|
|
|
|
// Datenzeilen
|
|
$this->pdf->SetFont('hack', '', 8);
|
|
foreach ($rows as $rowIdx => $cells) {
|
|
// Seitenumbruch?
|
|
if ($y + $rowHeight > $pageBottom) {
|
|
$this->drawVerteilungFooter();
|
|
$this->pdf->AddPage('P', array(210, 297));
|
|
$y = $this->drawVerteilungHeader($pageTitle);
|
|
|
|
// Sektions-Header wiederholen
|
|
$this->pdf->SetFont('hack', 'B', 9);
|
|
$this->pdf->SetFillColor(230, 230, 230);
|
|
$this->pdf->SetXY($margin, $y);
|
|
$this->pdf->Cell($totalWidth, $headerHeight, ' '.$sectionTitle.' (Forts.)', 1, 0, 'L', true);
|
|
$y += $headerHeight;
|
|
|
|
// Spalten-Header wiederholen
|
|
$this->pdf->SetFont('hack', 'B', 8);
|
|
$this->pdf->SetFillColor(240, 240, 240);
|
|
$x = $margin;
|
|
for ($i = 0; $i < count($colHeaders); $i++) {
|
|
$this->pdf->SetXY($x, $y);
|
|
$this->pdf->Cell($colWidths[$i], $headerHeight, $colHeaders[$i], 1, 0, 'C', true);
|
|
$x += $colWidths[$i];
|
|
}
|
|
$y += $headerHeight;
|
|
$this->pdf->SetFont('hack', '', 8);
|
|
}
|
|
|
|
// Zeile zeichnen
|
|
$x = $margin;
|
|
for ($i = 0; $i < count($cells); $i++) {
|
|
$this->pdf->SetXY($x, $y);
|
|
$this->pdf->Cell($colWidths[$i], $rowHeight, ' '.$cells[$i], 1, 0, 'L');
|
|
$x += $colWidths[$i];
|
|
}
|
|
$y += $rowHeight;
|
|
}
|
|
|
|
$y += 5; // Abstand zwischen Sektionen
|
|
return $y;
|
|
}
|
|
|
|
/**
|
|
* Footer für Verteilungs-Seiten (Seitenzahl)
|
|
*/
|
|
private function drawVerteilungFooter()
|
|
{
|
|
// Wird am Ende pro Seite gesetzt (aliasNbPages)
|
|
}
|
|
|
|
/**
|
|
* Kundenansicht rendern (A4 Hochformat)
|
|
* Einfache Tabelle: Nr. | Verbraucher | Räumlichkeit
|
|
* Gruppiert nach Feld (Panel) und Reihe (Carrier)
|
|
*/
|
|
public function renderKundenansicht()
|
|
{
|
|
$grouped = $this->analyzer->getVerteilungData();
|
|
if (empty($grouped)) return;
|
|
|
|
$pageTitle = 'Verteilung';
|
|
|
|
// Erste Seite
|
|
$this->pdf->AddPage('P', array(210, 297));
|
|
$y = $this->drawVerteilungHeader($pageTitle);
|
|
|
|
// Spalten-Definition
|
|
$totalWidth = 170; // 210 - 2*20mm Rand
|
|
$colHeaders = array('Nr.', 'Verbraucher', 'Räumlichkeit');
|
|
$colWidths = array(
|
|
round($totalWidth * 0.12), // Nr.
|
|
round($totalWidth * 0.50), // Verbraucher
|
|
);
|
|
$colWidths[] = $totalWidth - array_sum($colWidths); // Räumlichkeit = Rest
|
|
|
|
foreach ($grouped as $panelId => $pData) {
|
|
foreach ($pData['carriers'] as $carrierId => $cData) {
|
|
$panel = $pData['panel'];
|
|
$carrier = $cData['carrier'];
|
|
|
|
// Sektions-Titel: "Feld X - Reihe Y"
|
|
$panelLabel = $panel ? $panel->label : 'Feld ?';
|
|
$carrierLabel = $carrier ? $carrier->label : 'Reihe ?';
|
|
$sectionTitle = $panelLabel.' - '.$carrierLabel;
|
|
|
|
// Zeilen aufbauen
|
|
$rows = array();
|
|
foreach ($cData['paths'] as $path) {
|
|
$rows[] = array(
|
|
$path['abgang_nr'],
|
|
$path['output_label'],
|
|
$path['output_location'],
|
|
);
|
|
}
|
|
|
|
$y = $this->drawVerteilungTable($y, $sectionTitle, $colHeaders, $colWidths, $rows, $pageTitle);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Technikeransicht rendern (A4 Hochformat)
|
|
* Erweiterte Tabelle: R.Klem. | FI | Nr. | Verbraucher | Räumlichkeit | Typ
|
|
* Gruppiert nach Feld (Panel) und Reihe (Carrier)
|
|
*/
|
|
public function renderTechnikeransicht()
|
|
{
|
|
$grouped = $this->analyzer->getVerteilungData();
|
|
if (empty($grouped)) return;
|
|
|
|
$pageTitle = 'Verteilung';
|
|
|
|
// Erste Seite
|
|
$this->pdf->AddPage('P', array(210, 297));
|
|
$y = $this->drawVerteilungHeader($pageTitle);
|
|
|
|
// Spalten-Definition
|
|
$totalWidth = 170; // 210 - 2*20mm Rand
|
|
$colHeaders = array('R.Klem.', 'FI', 'Nr.', 'Verbraucher', 'Räumlichkeit', 'Typ');
|
|
$colWidths = array(
|
|
round($totalWidth * 0.10), // R.Klem.
|
|
round($totalWidth * 0.08), // FI
|
|
round($totalWidth * 0.10), // Nr.
|
|
round($totalWidth * 0.30), // Verbraucher
|
|
round($totalWidth * 0.25), // Räumlichkeit
|
|
);
|
|
$colWidths[] = $totalWidth - array_sum($colWidths); // Typ = Rest
|
|
|
|
foreach ($grouped as $panelId => $pData) {
|
|
foreach ($pData['carriers'] as $carrierId => $cData) {
|
|
$panel = $pData['panel'];
|
|
$carrier = $cData['carrier'];
|
|
|
|
// Sektions-Titel: "Feld X - Reihe Y"
|
|
$panelLabel = $panel ? $panel->label : 'Feld ?';
|
|
$carrierLabel = $carrier ? $carrier->label : 'Reihe ?';
|
|
$sectionTitle = $panelLabel.' - '.$carrierLabel;
|
|
|
|
// Zeilen aufbauen
|
|
$rows = array();
|
|
foreach ($cData['paths'] as $path) {
|
|
// FI/RCD Label
|
|
$fiLabel = '';
|
|
if ($path['protection_device']) {
|
|
$pd = $path['protection_device'];
|
|
$fiLabel = $pd->label ?: ($pd->type_label_short ?: 'FI');
|
|
}
|
|
|
|
// Kabel-Typ (Querschnitt)
|
|
$kabelTyp = '';
|
|
if ($path['medium_spec']) $kabelTyp = $path['medium_spec'];
|
|
elseif ($path['medium_type']) $kabelTyp = $path['medium_type'];
|
|
|
|
$rows[] = array(
|
|
'', // R.Klem. — leer (Reihenklemmen noch nicht implementiert)
|
|
$fiLabel,
|
|
$path['abgang_nr'],
|
|
$path['output_label'],
|
|
$path['output_location'],
|
|
$kabelTyp,
|
|
);
|
|
}
|
|
|
|
$y = $this->drawVerteilungTable($y, $sectionTitle, $colHeaders, $colWidths, $rows, $pageTitle);
|
|
}
|
|
}
|
|
}
|
|
}
|