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) // Sortiert nach position → Reihenfolge wird in der Graph-Ansicht beibehalten $fieldMeta = array(); // [fk_anlage_type][field_code] = {label, display_mode, badge_color, show_in_tree, show_in_hover} $sqlFields = "SELECT fk_anlage_type, field_code, field_label, field_type, tree_display_mode, badge_color, show_in_tree, show_in_hover"; $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, 'show_in_tree' => (int) $fObj->show_in_tree, 'show_in_hover' => (int) $fObj->show_in_hover, ); } $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, a.decommissioned, a.date_decommissioned,"; $sql .= " a.field_values, a.fk_contact, a.graph_x, a.graph_y, a.graph_width, a.graph_height,"; $sql .= " t.label as type_label, t.picto as type_picto, t.color as type_color,"; $sql .= " t.can_have_children as type_can_have_children,"; $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(); $nodeIsBuilding = array(); // Hierarchie: rowid → fk_parent (zum Traversieren nach oben) $nodeParentMap = array(); // Hierarchie-Kanten (Gerät→Gerät), werden durch echte Kabel ersetzt falls vorhanden $hierarchyEdges = array(); // Zwischenspeicher: alle DB-Zeilen für Zwei-Pass-Verarbeitung $rows = array(); $resql = $db->query($sql); if ($resql) { // 1. Pass: Alle Zeilen laden, Gebäude-Typen und Hierarchie merken while ($obj = $db->fetch_object($resql)) { $isBuilding = (!empty($obj->type_system_code) && $obj->type_system_code === 'GLOBAL'); $nodeIsBuilding[(int)$obj->rowid] = $isBuilding; $nodeParentMap[(int)$obj->rowid] = (int)$obj->fk_parent; $nodeIds[$obj->rowid] = true; $rows[] = $obj; } $db->free($resql); // 2. Pass: Nodes und Hierarchie-Edges aufbauen foreach ($rows as $obj) { $isBuilding = $nodeIsBuilding[(int)$obj->rowid]; $nodeId = 'n_'.$obj->rowid; $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, 'decommissioned' => isset($obj->decommissioned) ? (int) $obj->decommissioned : 0, 'date_decommissioned' => isset($obj->date_decommissioned) ? $obj->date_decommissioned : null, '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, 'graph_width' => $obj->graph_width !== null ? (float) $obj->graph_width : null, 'graph_height' => $obj->graph_height !== null ? (float) $obj->graph_height : null, ); // Feldwerte mit Metadaten (Label, Display-Modus, Badge-Farbe) // Iteration über $fieldMeta (nach position sortiert), nicht über $rawValues (JSON-Reihenfolge) // Aufteilen: fields = auf dem Node (show_in_tree=1), hover_fields = im Tooltip (show_in_hover=1) 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(); $treeFields = array(); $hoverFields = array(); foreach ($meta as $code => $fm) { $val = isset($rawValues[$code]) ? $rawValues[$code] : null; if ($val === '' || $val === null) continue; // Checkbox-Werte anpassen if ($fm['type'] === 'checkbox') { $val = $val ? '1' : '0'; } $fieldEntry = array( 'label' => $fm['label'], 'value' => $val, 'display' => $fm['display'], 'color' => $fm['color'], 'type' => $fm['type'], ); // Auf dem Node: nur Felder mit show_in_tree=1 if (!empty($fm['show_in_tree'])) { $treeFields[] = $fieldEntry; } // Im Tooltip: nur Felder mit show_in_hover=1 if (!empty($fm['show_in_hover'])) { $hoverFields[] = $fieldEntry; } } $nodeData['fields'] = $treeFields; $nodeData['hover_fields'] = $hoverFields; } } // Compound-Parent: Nächstes Gebäude in der Hierarchie nach oben finden // Alle Nodes innerhalb eines Gebäudes werden von diesem umschlossen // Gerät→Gerät Hierarchie wird als Kante dargestellt (nicht verschachtelt) $parentId = (int) $obj->fk_parent; if ($parentId > 0 && isset($nodeIds[$parentId])) { // Hierarchie-Kante zum direkten Parent (wird ggf. durch Kabel ersetzt) if (!$isBuilding) { $hierKey = min($parentId, (int)$obj->rowid).'_'.max($parentId, (int)$obj->rowid); $hierarchyEdges[$hierKey] = array( 'data' => array( 'id' => 'hier_'.$parentId.'_'.$obj->rowid, 'source' => 'n_'.$parentId, 'target' => 'n_'.$obj->rowid, 'is_hierarchy' => true, ), 'classes' => 'hierarchy-edge' ); } // Nach oben traversieren bis ein Gebäude gefunden wird → Compound-Parent $ancestorId = $parentId; $maxDepth = 20; // Endlosschleifen vermeiden while ($ancestorId > 0 && $maxDepth-- > 0) { if (!empty($nodeIsBuilding[$ancestorId])) { $nodeData['parent'] = 'n_'.$ancestorId; break; } $ancestorId = isset($nodeParentMap[$ancestorId]) ? $nodeParentMap[$ancestorId] : 0; } } $elements['nodes'][] = array( 'data' => $nodeData, 'classes' => $isBuilding ? 'building-node' : 'device-node' ); } } // Verbindungen laden $connObj = new AnlageConnection($db); $connections = $connObj->fetchBySociete($socId, 0); // Verwendete Kabeltypen für die Legende sammeln $usedCableTypes = array(); // Kabel-Paare merken (um Hierarchie-Kanten zu ersetzen) $cableConnectedPairs = 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', ); } } // Echte Kabelverbindung (nicht durchgeschleift) → Hierarchie-Kante überflüssig if (!$isPassthrough) { $src = (int) $conn->fk_source; $tgt = (int) $conn->fk_target; $pairKey = min($src, $tgt).'_'.max($src, $tgt); $cableConnectedPairs[$pairKey] = true; } $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' ); } // Hierarchie-Kanten hinzufügen, aber nur wenn kein echtes Kabel zwischen den Geräten existiert foreach ($hierarchyEdges as $hierKey => $hierEdge) { if (!isset($cableConnectedPairs[$hierKey])) { $elements['edges'][] = $hierEdge; } } // 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);