- Nodes standardmäßig gesperrt (autoungrabify), nur per "Anordnen"-Button verschiebbar, explizites Speichern/Abbrechen - Graph-Toolbar unter die System-Tab-Borderlinie verschoben - Anordnen/Speichern/Abbrechen rechts in Zeile 2 (Spacer) - Contact-Filter in graph_data.php: Kunden-Ebene zeigt nur Elemente ohne Kontaktzuweisung (konsistent mit Baumansicht) - CSS: display ohne !important damit style="display:none" greift - Changelog und Sprachdateien aktualisiert Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
247 lines
8.7 KiB
PHP
247 lines
8.7 KiB
PHP
<?php
|
|
/* Copyright (C) 2026 Alles Watt lauft
|
|
*
|
|
* AJAX-Endpunkt: Liefert Anlagen als Cytoscape.js Graph
|
|
* Gebäude-Typen (type_system_code=GLOBAL) = Räume (Compound-Container)
|
|
* Geräte-Typen = Geräte (Nodes innerhalb der Räume)
|
|
* Hierarchie über fk_parent (wie im Baum)
|
|
* Connections = Kabel-Edges zwischen Geräten
|
|
*/
|
|
|
|
if (!defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL', '1');
|
|
if (!defined('NOREQUIREMENU')) define('NOREQUIREMENU', '1');
|
|
if (!defined('NOREQUIREHTML')) define('NOREQUIREHTML', '1');
|
|
if (!defined('NOREQUIREAJAX')) define('NOREQUIREAJAX', '1');
|
|
|
|
$res = 0;
|
|
if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
|
|
if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
|
|
if (!$res) die("Include of main fails");
|
|
|
|
dol_include_once('/kundenkarte/class/anlageconnection.class.php');
|
|
|
|
header('Content-Type: application/json; charset=UTF-8');
|
|
|
|
$langs->loadLangs(array('kundenkarte@kundenkarte'));
|
|
|
|
$socId = GETPOSTINT('socid');
|
|
$contactId = GETPOSTINT('contactid');
|
|
|
|
$response = array('success' => false, 'error' => '');
|
|
|
|
// Berechtigungsprüfung
|
|
if (!$user->hasRight('kundenkarte', 'read')) {
|
|
$response['error'] = $langs->trans('ErrorPermissionDenied');
|
|
echo json_encode($response);
|
|
exit;
|
|
}
|
|
|
|
if ($socId <= 0) {
|
|
$response['error'] = 'Missing socid';
|
|
echo json_encode($response);
|
|
exit;
|
|
}
|
|
|
|
// Feld-Metadaten laden (field_code → Label, Display-Modus, Badge-Farbe pro Typ)
|
|
$fieldMeta = array(); // [fk_anlage_type][field_code] = {label, display_mode, badge_color}
|
|
$sqlFields = "SELECT fk_anlage_type, field_code, field_label, field_type, tree_display_mode, badge_color";
|
|
$sqlFields .= " FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_type_field WHERE active = 1 ORDER BY position";
|
|
$resFields = $db->query($sqlFields);
|
|
if ($resFields) {
|
|
while ($fObj = $db->fetch_object($resFields)) {
|
|
if ($fObj->field_type === 'header') continue;
|
|
$fieldMeta[(int)$fObj->fk_anlage_type][$fObj->field_code] = array(
|
|
'label' => $fObj->field_label,
|
|
'display' => $fObj->tree_display_mode ?: 'badge',
|
|
'color' => $fObj->badge_color ?: '',
|
|
'type' => $fObj->field_type,
|
|
);
|
|
}
|
|
$db->free($resFields);
|
|
}
|
|
|
|
// Elemente laden - OHNE GLOBAL-System (das ist nur die separate Gebäudestruktur)
|
|
// Gebäude/Räume werden über den Typ erkannt (type_system_code = GLOBAL)
|
|
// Hierarchie kommt aus fk_parent (wie im Baum)
|
|
$sql = "SELECT a.rowid, a.label, a.fk_parent, a.fk_system, a.fk_anlage_type,";
|
|
$sql .= " a.field_values, a.fk_contact, a.graph_x, a.graph_y,";
|
|
$sql .= " t.label as type_label, t.picto as type_picto, t.color as type_color,";
|
|
$sql .= " s.code as system_code, s.label as system_label,";
|
|
$sql .= " ts.code as type_system_code,";
|
|
$sql .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_files f WHERE f.fk_anlage = a.rowid AND f.file_type IN ('image/jpeg','image/png','image/gif','image/webp')) as image_count,";
|
|
$sql .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_files f WHERE f.fk_anlage = a.rowid AND f.file_type NOT IN ('image/jpeg','image/png','image/gif','image/webp')) as doc_count";
|
|
$sql .= " FROM ".MAIN_DB_PREFIX."kundenkarte_anlage as a";
|
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_anlage_type as t ON a.fk_anlage_type = t.rowid";
|
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system as s ON a.fk_system = s.rowid";
|
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system as ts ON t.fk_system = ts.rowid";
|
|
$sql .= " WHERE a.fk_soc = ".(int)$socId;
|
|
$sql .= " AND s.code != 'GLOBAL'";
|
|
if ($contactId > 0) {
|
|
$sql .= " AND a.fk_contact = ".(int)$contactId;
|
|
} else {
|
|
// Auf Kunden-Ebene nur Elemente ohne Kontaktzuweisung (wie im Baum)
|
|
$sql .= " AND (a.fk_contact IS NULL OR a.fk_contact = 0)";
|
|
}
|
|
$sql .= " AND a.status = 1";
|
|
$sql .= " ORDER BY a.fk_parent, a.rang, a.rowid";
|
|
|
|
$elements = array('nodes' => array(), 'edges' => array());
|
|
$nodeIds = array();
|
|
|
|
$resql = $db->query($sql);
|
|
if ($resql) {
|
|
while ($obj = $db->fetch_object($resql)) {
|
|
// Typ bestimmt ob Raum oder Gerät (GLOBAL-Typ = Gebäude/Raum)
|
|
$isBuilding = (!empty($obj->type_system_code) && $obj->type_system_code === 'GLOBAL');
|
|
|
|
$nodeId = 'n_'.$obj->rowid;
|
|
$nodeIds[$obj->rowid] = true;
|
|
|
|
$nodeData = array(
|
|
'id' => $nodeId,
|
|
'label' => $obj->label,
|
|
'type_label' => $obj->type_label ?: '',
|
|
'type_picto' => $obj->type_picto ?: '',
|
|
'type_color' => $obj->type_color ?: '',
|
|
'system_code' => $obj->system_code ?: '',
|
|
'system_label' => $obj->system_label ?: '',
|
|
'fk_parent' => (int) $obj->fk_parent,
|
|
'fk_anlage_type' => (int) $obj->fk_anlage_type,
|
|
'is_building' => $isBuilding,
|
|
'image_count' => (int) $obj->image_count,
|
|
'doc_count' => (int) $obj->doc_count,
|
|
'graph_x' => $obj->graph_x !== null ? (float) $obj->graph_x : null,
|
|
'graph_y' => $obj->graph_y !== null ? (float) $obj->graph_y : null,
|
|
);
|
|
|
|
// Feldwerte mit Metadaten (Label, Display-Modus, Badge-Farbe)
|
|
if (!empty($obj->field_values)) {
|
|
$rawValues = json_decode($obj->field_values, true);
|
|
if (is_array($rawValues) && !empty($rawValues)) {
|
|
$typeId = (int) $obj->fk_anlage_type;
|
|
$meta = isset($fieldMeta[$typeId]) ? $fieldMeta[$typeId] : array();
|
|
$fields = array();
|
|
foreach ($rawValues as $code => $val) {
|
|
if ($val === '' || $val === null) continue;
|
|
$fm = isset($meta[$code]) ? $meta[$code] : array();
|
|
$display = isset($fm['display']) ? $fm['display'] : 'badge';
|
|
// Versteckte Felder überspringen
|
|
if ($display === 'none') continue;
|
|
$label = isset($fm['label']) ? $fm['label'] : $code;
|
|
// Checkbox-Werte anpassen
|
|
if (isset($fm['type']) && $fm['type'] === 'checkbox') {
|
|
$val = $val ? '1' : '0';
|
|
}
|
|
$fields[] = array(
|
|
'label' => $label,
|
|
'value' => $val,
|
|
'display' => $display,
|
|
'color' => isset($fm['color']) ? $fm['color'] : '',
|
|
'type' => isset($fm['type']) ? $fm['type'] : 'text',
|
|
);
|
|
}
|
|
$nodeData['fields'] = $fields;
|
|
}
|
|
}
|
|
|
|
// Compound-Parent aus fk_parent (Eltern-Kind-Verschachtelung)
|
|
if ($obj->fk_parent > 0) {
|
|
$nodeData['parent'] = 'n_'.$obj->fk_parent;
|
|
}
|
|
|
|
$elements['nodes'][] = array(
|
|
'data' => $nodeData,
|
|
'classes' => $isBuilding ? 'building-node' : 'device-node'
|
|
);
|
|
}
|
|
$db->free($resql);
|
|
}
|
|
|
|
// Verbindungen laden
|
|
$connObj = new AnlageConnection($db);
|
|
$connections = $connObj->fetchBySociete($socId, 0);
|
|
|
|
// Verwendete Kabeltypen für die Legende sammeln
|
|
$usedCableTypes = array();
|
|
|
|
foreach ($connections as $conn) {
|
|
// Nur Edges für tatsächlich geladene Nodes
|
|
if (!isset($nodeIds[$conn->fk_source]) || !isset($nodeIds[$conn->fk_target])) {
|
|
continue;
|
|
}
|
|
|
|
$isPassthrough = empty($conn->label) && empty($conn->medium_type_label) && empty($conn->medium_type_text) && empty($conn->medium_spec);
|
|
|
|
// Edge-Label zusammenbauen
|
|
$edgeLabel = '';
|
|
$mediumType = !empty($conn->medium_type_label) ? $conn->medium_type_label : $conn->medium_type_text;
|
|
if (!empty($mediumType)) {
|
|
$edgeLabel = $mediumType;
|
|
if (!empty($conn->medium_spec)) {
|
|
$edgeLabel .= ' '.$conn->medium_spec;
|
|
}
|
|
if (!empty($conn->medium_length)) {
|
|
$edgeLabel .= ', '.$conn->medium_length;
|
|
}
|
|
}
|
|
|
|
// Farbe: aus Connection oder aus Medium-Type
|
|
$color = $conn->medium_color;
|
|
if (empty($color) && !empty($conn->fk_medium_type)) {
|
|
// Farbe aus Medium-Type-Tabelle holen
|
|
$sqlColor = "SELECT color, label_short FROM ".MAIN_DB_PREFIX."kundenkarte_medium_type WHERE rowid = ".(int)$conn->fk_medium_type;
|
|
$resColor = $db->query($sqlColor);
|
|
if ($resColor && $mtObj = $db->fetch_object($resColor)) {
|
|
$color = $mtObj->color;
|
|
}
|
|
}
|
|
|
|
// Kabeltyp für Legende merken
|
|
if (!$isPassthrough && !empty($mediumType)) {
|
|
$typeKey = $mediumType;
|
|
if (!isset($usedCableTypes[$typeKey])) {
|
|
$usedCableTypes[$typeKey] = array(
|
|
'label' => $mediumType,
|
|
'color' => $color ?: '#5a8a5a',
|
|
);
|
|
}
|
|
}
|
|
|
|
$elements['edges'][] = array(
|
|
'data' => array(
|
|
'id' => 'conn_'.$conn->id,
|
|
'source' => 'n_'.$conn->fk_source,
|
|
'target' => 'n_'.$conn->fk_target,
|
|
'label' => $edgeLabel,
|
|
'medium_type' => $mediumType,
|
|
'medium_spec' => $conn->medium_spec,
|
|
'medium_length' => $conn->medium_length,
|
|
'medium_color' => $color,
|
|
'connection_id' => (int) $conn->id,
|
|
'is_passthrough' => $isPassthrough,
|
|
),
|
|
'classes' => $isPassthrough ? 'passthrough-edge' : 'cable-edge'
|
|
);
|
|
}
|
|
|
|
// Prüfen ob gespeicherte Positionen vorhanden sind
|
|
$hasPositions = false;
|
|
foreach ($elements['nodes'] as $node) {
|
|
if ($node['data']['graph_x'] !== null) {
|
|
$hasPositions = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
$response['success'] = true;
|
|
$response['elements'] = $elements;
|
|
$response['cable_types'] = array_values($usedCableTypes);
|
|
$response['has_positions'] = $hasPositions;
|
|
$response['meta'] = array(
|
|
'socid' => $socId,
|
|
'contactid' => $contactId,
|
|
'total_nodes' => count($elements['nodes']),
|
|
'total_connections' => count($connections),
|
|
);
|
|
|
|
echo json_encode($response);
|