loadLangs(array('companies', 'kundenkarte@kundenkarte')); // Get parameters // Support both 'id' and 'contactid' for compatibility with Dolibarr's contact navigation arrows $id = GETPOSTINT('id'); if ($id <= 0) { $id = GETPOSTINT('contactid'); } $action = GETPOST('action', 'aZ09'); $confirm = GETPOST('confirm', 'alpha'); $systemId = GETPOSTINT('system'); $anlageId = GETPOSTINT('anlage_id'); $parentId = GETPOSTINT('parent_id'); // Security check if (!$user->hasRight('kundenkarte', 'read')) { accessforbidden(); } // Initialize objects $object = new Contact($db); $form = new Form($db); $anlage = new Anlage($db); $anlageType = new AnlageType($db); // Load contact - id is required if ($id <= 0) { // If no id, try to get it from anlage_id if ($anlageId > 0) { $tmpAnlage = new Anlage($db); if ($tmpAnlage->fetch($anlageId) > 0) { $id = $tmpAnlage->fk_contact; if ($id > 0) { // Redirect to include id in URL for proper navigation $redirectUrl = $_SERVER['PHP_SELF'].'?id='.$id; if ($systemId > 0) $redirectUrl .= '&system='.$systemId; if ($action) $redirectUrl .= '&action='.urlencode($action); if ($anlageId > 0) $redirectUrl .= '&anlage_id='.$anlageId; header('Location: '.$redirectUrl); exit; } } } // Still no id - show error accessforbidden($langs->trans('ErrorRecordNotFound')); } $result = $object->fetch($id); if ($result <= 0) { dol_print_error($db, $object->error); exit; } $permissiontoread = $user->hasRight('kundenkarte', 'read'); $permissiontoadd = $user->hasRight('kundenkarte', 'write'); $permissiontodelete = $user->hasRight('kundenkarte', 'delete'); // Load ALL available systems (from dictionary) $allSystems = array(); $sql = "SELECT rowid, code, label, picto, color FROM ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system WHERE active = 1 ORDER BY position ASC"; $resql = $db->query($sql); if ($resql) { while ($obj = $db->fetch_object($resql)) { $allSystems[$obj->rowid] = $obj; } } // Load systems ENABLED for this contact $customerSystems = array(); $sql = "SELECT ss.rowid, ss.fk_system, s.code, s.label, s.picto, s.color FROM ".MAIN_DB_PREFIX."kundenkarte_societe_system ss JOIN ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system s ON s.rowid = ss.fk_system WHERE ss.fk_soc = ".((int) $object->socid)." AND ss.fk_contact = ".((int) $id)." AND ss.active = 1 AND s.active = 1 AND s.code != 'GLOBAL' ORDER BY s.position ASC"; $resql = $db->query($sql); if ($resql) { while ($obj = $db->fetch_object($resql)) { $customerSystems[$obj->fk_system] = $obj; } } // Default to first enabled system if not specified if (empty($systemId) && !empty($customerSystems)) { $systemId = array_key_first($customerSystems); } /* * Actions */ // Add system to contact if ($action == 'add_system' && $permissiontoadd) { $newSystemId = GETPOSTINT('new_system_id'); if ($newSystemId > 0 && !isset($customerSystems[$newSystemId])) { $sql = "INSERT INTO ".MAIN_DB_PREFIX."kundenkarte_societe_system"; $sql .= " (entity, fk_soc, fk_contact, fk_system, date_creation, fk_user_creat, active)"; $sql .= " VALUES (".$conf->entity.", ".((int) $object->socid).", ".((int) $id).", ".((int) $newSystemId).", NOW(), ".((int) $user->id).", 1)"; $result = $db->query($sql); if ($result) { setEventMessages($langs->trans('SystemAdded'), null, 'mesgs'); header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$newSystemId); exit; } else { setEventMessages($db->lasterror(), null, 'errors'); } } $action = ''; } // Remove system from contact if ($action == 'confirm_remove_system' && $confirm == 'yes' && $permissiontodelete) { $removeSystemId = GETPOSTINT('remove_system_id'); if ($removeSystemId > 0) { // Check if system has any elements for this contact $sql = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX."kundenkarte_anlage WHERE fk_soc = ".((int) $object->socid)." AND fk_contact = ".((int) $id)." AND fk_system = ".((int) $removeSystemId); $resql = $db->query($sql); $obj = $db->fetch_object($resql); if ($obj->cnt > 0) { setEventMessages($langs->trans('ErrorSystemHasElements'), null, 'errors'); } else { $sql = "DELETE FROM ".MAIN_DB_PREFIX."kundenkarte_societe_system WHERE fk_soc = ".((int) $object->socid)." AND fk_contact = ".((int) $id)." AND fk_system = ".((int) $removeSystemId); $db->query($sql); setEventMessages($langs->trans('SystemRemoved'), null, 'mesgs'); // Switch to another system or none unset($customerSystems[$removeSystemId]); if (!empty($customerSystems)) { $systemId = array_key_first($customerSystems); } else { $systemId = 0; } } } header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.($systemId ? '&system='.$systemId : '')); exit; } if ($action == 'add' && $permissiontoadd) { $anlage->label = GETPOST('label', 'alphanohtml'); $anlage->fk_soc = $object->socid; $anlage->fk_contact = $id; $anlage->fk_anlage_type = GETPOSTINT('fk_anlage_type'); $anlage->fk_parent = GETPOSTINT('fk_parent'); $anlage->fk_system = $systemId; $anlage->fk_product = GETPOSTINT('fk_product') > 0 ? GETPOSTINT('fk_product') : null; $anlage->note_private = isset($_POST['note_private']) ? $_POST['note_private'] : ''; $anlage->status = 1; // Get type - but keep current system for GLOBAL types (buildings) $type = new AnlageType($db); if ($type->fetch($anlage->fk_anlage_type) > 0) { // Only change system if type is NOT a global type // Global types (buildings) should stay in the current system if ($type->system_code !== 'GLOBAL') { $anlage->fk_system = $type->fk_system; } } // All fields come from dynamic fields now $fieldValues = array(); $fields = $type->fetchFields(); foreach ($fields as $field) { if ($field->field_type === 'header') continue; // Skip headers $value = GETPOST('field_'.$field->field_code, 'alphanohtml'); if ($value !== '') { $fieldValues[$field->field_code] = $value; } } $anlage->setFieldValues($fieldValues); $result = $anlage->create($user); if ($result > 0) { setEventMessages($langs->trans('RecordSaved'), null, 'mesgs'); header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId); exit; } else { setEventMessages($anlage->error, $anlage->errors, 'errors'); $action = 'create'; } } if ($action == 'update' && $permissiontoadd) { $anlage->fetch($anlageId); $systemIdBefore = $anlage->fk_system; // Remember original system $anlage->label = GETPOST('label', 'alphanohtml'); $anlage->fk_anlage_type = GETPOSTINT('fk_anlage_type'); $anlage->fk_parent = GETPOSTINT('fk_parent'); $anlage->fk_product = GETPOSTINT('fk_product') > 0 ? GETPOSTINT('fk_product') : null; $anlage->note_private = isset($_POST['note_private']) ? $_POST['note_private'] : ''; // Get type - but keep current system for GLOBAL types (buildings) $type = new AnlageType($db); if ($type->fetch($anlage->fk_anlage_type) > 0) { // Only change system if type is NOT a global type // Global types (buildings) should stay in their current system if ($type->system_code !== 'GLOBAL') { $anlage->fk_system = $type->fk_system; } } // All fields come from dynamic fields now $fieldValues = array(); $fields = $type->fetchFields(); foreach ($fields as $field) { if ($field->field_type === 'header') continue; // Skip headers $value = GETPOST('field_'.$field->field_code, 'alphanohtml'); if ($value !== '') { $fieldValues[$field->field_code] = $value; } } $anlage->setFieldValues($fieldValues); $result = $anlage->update($user); if ($result > 0) { setEventMessages($langs->trans('RecordSaved'), null, 'mesgs'); header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId); exit; } else { setEventMessages($anlage->error, $anlage->errors, 'errors'); $action = 'edit'; } } if ($action == 'confirm_delete' && $confirm == 'yes' && $permissiontodelete) { $anlage->fetch($anlageId); $systemIdBefore = $anlage->fk_system; $result = $anlage->delete($user); if ($result > 0) { setEventMessages($langs->trans('RecordDeleted'), null, 'mesgs'); } else { setEventMessages($anlage->error, $anlage->errors, 'errors'); } header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemIdBefore); exit; } // File upload (multi-file support) if ($action == 'uploadfile' && $permissiontoadd) { $anlage->fetch($anlageId); $upload_dir = $anlage->getFileDirectory(); // Create directory if not exists if (!is_dir($upload_dir)) { dol_mkdir($upload_dir); } $uploadCount = 0; if (!empty($_FILES['userfiles']['name'][0])) { // Multi-file upload $fileCount = count($_FILES['userfiles']['name']); for ($i = 0; $i < $fileCount; $i++) { if ($_FILES['userfiles']['error'][$i] === UPLOAD_ERR_OK && !empty($_FILES['userfiles']['name'][$i])) { $tmpName = $_FILES['userfiles']['tmp_name'][$i]; $fileName = dol_sanitizeFileName($_FILES['userfiles']['name'][$i]); $destPath = $upload_dir.'/'.$fileName; // Handle duplicate filenames $counter = 1; $baseName = pathinfo($fileName, PATHINFO_FILENAME); $extension = pathinfo($fileName, PATHINFO_EXTENSION); while (file_exists($destPath)) { $fileName = $baseName.'_'.$counter.'.'.$extension; $destPath = $upload_dir.'/'.$fileName; $counter++; } if (move_uploaded_file($tmpName, $destPath)) { // Add to database $anlagefile = new AnlageFile($db); $anlagefile->fk_anlage = $anlageId; $anlagefile->filename = $fileName; // IMPORTANT: Store ONLY relative path (anlagen/socid/anlageid/filename) - never full path! $anlagefile->filepath = 'anlagen/'.$anlage->fk_soc.'/'.$anlage->id.'/'.$fileName; $anlagefile->filesize = $_FILES['userfiles']['size'][$i]; $anlagefile->mimetype = dol_mimetype($fileName); $anlagefile->create($user); // Generate thumbnail $anlagefile->generateThumbnail(); $uploadCount++; } } } if ($uploadCount > 0) { $msg = $uploadCount > 1 ? $uploadCount.' Dateien hochgeladen' : 'Datei hochgeladen'; setEventMessages($msg, null, 'mesgs'); } } header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=view&anlage_id='.$anlageId); exit; } // File delete (after confirmation) if ($action == 'confirm_deletefile' && $confirm == 'yes' && $permissiontodelete) { $fileId = GETPOSTINT('fileid'); if ($fileId > 0) { $anlagefile = new AnlageFile($db); if ($anlagefile->fetch($fileId) > 0) { // Delete method handles physical file and database if ($anlagefile->delete($user) > 0) { setEventMessages($langs->trans('FileDeleted'), null, 'mesgs'); } else { setEventMessages($langs->trans('Error'), null, 'errors'); } } } header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=view&anlage_id='.$anlageId); exit; } // Toggle file pinned status if ($action == 'togglepin' && $permissiontoadd) { $fileId = GETPOSTINT('fileid'); if ($fileId > 0) { $anlagefile = new AnlageFile($db); if ($anlagefile->fetch($fileId) > 0) { if ($anlagefile->togglePin($user) > 0) { $msg = $anlagefile->is_pinned ? $langs->trans('FilePinned') : $langs->trans('FileUnpinned'); setEventMessages($msg, null, 'mesgs'); } } } header('Location: '.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=view&anlage_id='.$anlageId.'#files'); exit; } /* * View */ $title = $langs->trans('TechnicalInstallations').' - '.$object->getFullName($langs); // Ansichtsmodus: pro System konfigurierbar (tree/graph/both) dol_include_once('/kundenkarte/lib/graph_view.lib.php'); $allowedViews = 'both'; if ($systemId > 0 && isset($customerSystems[$systemId]) && !empty($customerSystems[$systemId]->view_modes)) { $allowedViews = $customerSystems[$systemId]->view_modes; } $defaultView = getDolGlobalString('KUNDENKARTE_DEFAULT_VIEW', 'tree'); $viewMode = GETPOST('view', 'aZ09'); if (!in_array($viewMode, array('tree', 'graph'))) { $viewMode = $defaultView; } // Erzwinge erlaubten Modus wenn nur einer verfügbar if ($allowedViews === 'tree') { $viewMode = 'tree'; } elseif ($allowedViews === 'graph') { $viewMode = 'graph'; } $jsFiles = array('/kundenkarte/js/kundenkarte.js?v='.time()); $cssFiles = array('/kundenkarte/css/kundenkarte.css?v='.time()); kundenkarte_graph_add_assets($jsFiles, $cssFiles, $viewMode); llxHeader('', $title, '', '', 0, 0, $jsFiles, $cssFiles); // Prepare tabs $head = contact_prepare_head($object); print dol_get_fiche_head($head, 'anlagen', $langs->trans("ContactAddress"), -1, 'contact'); // Contact card $linkback = ''.$langs->trans("BackToList").''; dol_banner_tab($object, 'id', $linkback, 1, 'rowid', 'nom'); // Admin-Schnellzugriff (nur für Modul-Admins) if ($user->admin || $user->hasRight('kundenkarte', 'admin')) { $adminUrl = dol_buildpath('/kundenkarte/admin/setup.php', 1); print '
'; print ''; print '
'; } print '
'; // Confirmation dialogs if ($action == 'delete') { print $form->formconfirm( $_SERVER['PHP_SELF'].'?id='.$id.'&anlage_id='.$anlageId.'&system='.$systemId, $langs->trans('DeleteElement'), $langs->trans('ConfirmDeleteElement'), 'confirm_delete', '', 'yes', 1 ); } if ($action == 'remove_system') { $removeSystemId = GETPOSTINT('remove_system_id'); $sysLabel = isset($customerSystems[$removeSystemId]) ? $customerSystems[$removeSystemId]->label : ''; print $form->formconfirm( $_SERVER['PHP_SELF'].'?id='.$id.'&remove_system_id='.$removeSystemId, $langs->trans('RemoveSystem'), $langs->trans('ConfirmRemoveSystem', $sysLabel), 'confirm_remove_system', '', 'yes', 1 ); } if ($action == 'askdeletefile') { $fileId = GETPOSTINT('fileid'); print $form->formconfirm( $_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&anlage_id='.$anlageId.'&fileid='.$fileId, $langs->trans('Delete'), $langs->trans('ConfirmDeleteFile'), 'confirm_deletefile', '', 'yes', 1 ); } // System tabs (only show enabled systems for this contact) print '
'; print '
'; foreach ($customerSystems as $sysId => $sys) { $activeClass = ($sysId == $systemId) ? ' active' : ''; print '
'; print ''; if ($sys->picto) { print ''.kundenkarte_render_icon($sys->picto).''; } print ''.dol_escape_htmltag($sys->label).''; print ''; // Remove button (only if no elements) if ($permissiontodelete && $sysId == $systemId) { print ' '; } print '
'; } // Add system button (always on the right) if ($permissiontoadd) { // Get systems not yet enabled for this contact $availableSystems = array_diff_key($allSystems, $customerSystems); if (!empty($availableSystems)) { print ''; } } print '
'; // Steuerungs-Buttons (nur wenn kein Formular aktiv) $isTreeView = !in_array($action, array('create', 'edit', 'view', 'copy')); if ($isTreeView) { $toggleView = ($viewMode === 'graph') ? 'tree' : 'graph'; $toggleUrl = $_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&view='.$toggleView; $toggleIcon = ($viewMode === 'graph') ? 'fa-list' : 'fa-sitemap'; $toggleLabel = ($viewMode === 'graph') ? $langs->trans('TreeView') : $langs->trans('GraphView'); if ($viewMode !== 'graph') { // Baumansicht: Controls auf gleicher Zeile wie System-Tabs (im Wrapper) print '
'; if ($allowedViews === 'both') { print ''; print ' '.$toggleLabel; print ''; } print ''; print ''; print ''; $showDecomm = getDolGlobalInt('KUNDENKARTE_SHOW_DECOMMISSIONED', 0); print ''; if ($systemId > 0) { $exportUrl = dol_buildpath('/kundenkarte/ajax/export_tree_pdf.php', 1).'?socid='.$object->socid.'&contactid='.$id.'&system='.$systemId; print ''; print ' PDF Export'; print ''; } print '
'; } } print '
'; // End kundenkarte-system-tabs-wrapper // Graph-Toolbar: UNTER der System-Tab-Borderlinie if ($isTreeView && $viewMode === 'graph') { kundenkarte_graph_print_toolbar(array( 'socid' => $object->socid, 'contactid' => $id, 'systemid' => $systemId, 'viewMode' => $viewMode, 'allowedViews' => $allowedViews, 'permissiontoadd' => $permissiontoadd, 'pageUrl' => $_SERVER['PHP_SELF'], )); } // Add system form (hidden by default) if ($permissiontoadd && !empty($availableSystems)) { print ''; } // Check if contact has any systems if (empty($customerSystems)) { print '
'; print '
'; print $langs->trans('NoSystemsConfigured').'

'; if ($permissiontoadd && !empty($allSystems)) { print $langs->trans('ClickAddSystemToStart'); } else { print $langs->trans('ContactAdminToAddSystems'); } print '
'; } elseif ($systemId > 0) { // Show form or tree for selected system if (in_array($action, array('create', 'edit', 'view', 'copy'))) { // Load element for edit/view/copy if ($action != 'create' && $anlageId > 0) { $anlage->fetch($anlageId); $type = new AnlageType($db); $type->fetch($anlage->fk_anlage_type); $type->fetchFields(); } // Load types for select $types = $anlageType->fetchAllBySystem($systemId); print '
'; if ($action == 'view') { // View mode print '

'.dol_escape_htmltag($anlage->label).'

'; print ''; print ''; print ''; // Zugeordnetes Produkt (nur wenn Typ es erlaubt) if ($type->has_product && $anlage->fk_product > 0) { require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; $product = new Product($db); if ($product->fetch($anlage->fk_product) > 0) { print ''; print ''; } } // Dynamic fields - all fields come from type definition $fieldValues = $anlage->getFieldValues(); $typeFieldsList = $type->fetchFields(); foreach ($typeFieldsList as $field) { if ($field->field_type === 'header') { // Section header print ''; } else { $value = isset($fieldValues[$field->field_code]) ? $fieldValues[$field->field_code] : ''; if ($value !== '') { print ''; // Format date fields if ($field->field_type === 'date' && $value) { print ''; } elseif ($field->field_type === 'checkbox') { print ''; } else { print ''; } } } } if ($anlage->note_private) { print ''; print ''; } // Creation date if ($anlage->date_creation) { print ''; print ''; } // Last modification date if ($anlage->tms && $anlage->tms != $anlage->date_creation) { print ''; print ''; } // Ausgebaut-Status if (!empty($anlage->decommissioned)) { print ''; print ''; } print '
'.$langs->trans('Type').''.dol_escape_htmltag($anlage->type_label).'
'.$langs->trans('Product').''.dol_escape_htmltag($product->ref).''; print ' - '.dol_escape_htmltag($product->label); if ($product->price > 0) { print ' ('.price($product->price).' €)'; } print '
'.dol_escape_htmltag($field->field_label).'
'.dol_escape_htmltag($field->field_label).''.dol_print_date(strtotime($value), 'day').'
'.($value ? $langs->trans('Yes') : $langs->trans('No')).'
'.dol_escape_htmltag($value).'
'.$langs->trans('FieldNotes').''.dol_htmlentitiesbr($anlage->note_private).'
'.$langs->trans('DateCreation').''.dol_print_date($anlage->date_creation, 'dayhour').'
'.$langs->trans('DateLastModification').''.dol_print_date($anlage->tms, 'dayhour').'
'.$langs->trans('Decommissioned').' '.$langs->trans('Decommissioned').''; if (!empty($anlage->date_decommissioned)) { print ' '.dol_print_date(strtotime($anlage->date_decommissioned), 'day'); } print '
'; // Files section $anlagefile = new AnlageFile($db); $files = $anlagefile->fetchAllByAnlage($anlageId); print '

'.$langs->trans('AttachedFiles').'

'; if ($permissiontoadd) { print '
'; print ''; print '
'; print ''; print '
'; print ''; print '

Dateien hierher ziehen oder durchsuchen

'; print '

Bilder, PDF, Office-Dokumente, ZIP-Archive

'; print '
'; print ''; print '
'; print ''; print '

'; } if (!empty($files)) { print '
'; foreach ($files as $file) { $pinnedClass = $file->is_pinned ? ' kundenkarte-file-pinned' : ''; print '
'; if ($file->is_pinned) { print '
'; } print '
'; if ($file->file_type == 'image') { $thumbUrl = $file->getThumbUrl(); if ($thumbUrl) { print ''; } else { print ''; } } elseif ($file->file_type == 'pdf') { // PDF preview using iframe print '
'; print ''; print '
'; } else { // Show icon based on file type $fileIcon = 'fa-file-o'; $iconColor = '#999'; if ($file->file_type == 'archive') { $fileIcon = 'fa-file-archive-o'; $iconColor = '#f39c12'; } elseif ($file->file_type == 'document') { $ext = strtolower(pathinfo($file->filename, PATHINFO_EXTENSION)); if (in_array($ext, array('doc', 'docx'))) { $fileIcon = 'fa-file-word-o'; $iconColor = '#2b579a'; } elseif (in_array($ext, array('xls', 'xlsx'))) { $fileIcon = 'fa-file-excel-o'; $iconColor = '#217346'; } elseif ($ext == 'txt') { $fileIcon = 'fa-file-text-o'; $iconColor = '#666'; } else { $fileIcon = 'fa-file-text-o'; $iconColor = '#3498db'; } } print ''; } print '
'; print '
'; print '
'.dol_escape_htmltag(dol_trunc($file->filename, 35)).'
'; print '
'.dol_print_size($file->filesize).'
'; print '
'; print ''; if ($permissiontoadd) { $pinClass = $file->is_pinned ? ' kundenkarte-file-btn-pinned' : ''; $pinTitle = $file->is_pinned ? $langs->trans('Unpin') : $langs->trans('Pin'); print ''; } if ($permissiontodelete) { print ''; } print '
'; print '
'; print '
'; } print '
'; } else { print '

'.$langs->trans('NoFiles').'

'; } // Equipment section (only if type can have equipment) if ($type->can_have_equipment) { print '

'.$langs->trans('Equipment').' - Schaltplan

'; // Equipment container print '
'; // Schematic Editor print '
'; print '
'; print '
'; print ''.$langs->trans('SchematicEditor').' (Klick auf Block = Bearbeiten | Drag = Verschieben | + = Hinzufügen)'; print '
'; print '
'; // Zoom controls print '
'; print ''; print '100%'; print ''; print ''; print ''; print '
'; // Manual wire draw toggle print ''; print ''; print ''; // BOM (Stückliste) button print ''; // Audit Log button print ''; // PDF Export button $pdfExportUrl = dol_buildpath('/kundenkarte/ajax/export_schematic_pdf.php', 1).'?anlage_id='.$anlageId.'&format=A4&orientation=L'; print ''; print ' PDF Export'; print ''; print '
'; print '
'; print '
Bereit
'; print '
'; print '
'; print '
'; print '
'; // .kundenkarte-equipment-container // Initialize SchematicEditor JavaScript print ''; } // Zubehör-Bereich (nur wenn Typ has_accessories hat) if (!empty($type->has_accessories)) { $accessoryObj = new AnlageAccessory($db); $accessories = $accessoryObj->fetchAllByAnlage($anlageId); print '

'.$langs->trans('Accessories').'

'; if (!empty($accessories)) { print '
'; print ''; print ''; print ''; print ''; print ''; print ''; if ($permissiontodelete) { print ''; } print ''; foreach ($accessories as $acc) { print ''; print ''; print ''; print ''; print ''; if ($permissiontodelete) { print ''; } print ''; } print '
'.$langs->trans('ProductRef').''.$langs->trans('Label').''.$langs->trans('Qty').''.$langs->trans('Note').''.$langs->trans('Action').'
'.dol_escape_htmltag($acc->product_ref).''.dol_escape_htmltag($acc->product_label).''.$acc->qty.''.dol_escape_htmltag($acc->note).'
'; print '
'; } else { print '

'.$langs->trans('NoAccessories').'

'; } if ($permissiontoadd) { print '
'; print '
'; print ''; print ''; print ''; print ''; print '
'; print '
'; } if ($permissiontoadd && !empty($accessories)) { print '
'; print '
'.$langs->trans('OrderAccessories').'
'; print '
'; print ''; print ''; print '
'; print '
'; } } // Action buttons print '
'; if ($permissiontoadd) { print ''.$langs->trans('Modify').''; print ''.$langs->trans('Copy').''; } if ($permissiontodelete) { print ''.$langs->trans('Delete').''; } print ''.$langs->trans('Back').''; print '
'; } else { // Create/Edit/Copy form $isEdit = ($action == 'edit'); $isCopy = ($action == 'copy'); $formAction = $isEdit ? 'update' : 'add'; print '
'; print ''; print ''; if ($isEdit) { print ''; } if ($isCopy) { print ''; } print ''; // Label $labelValue = ''; if ($isEdit) { $labelValue = $anlage->label; } elseif ($isCopy) { $labelValue = $anlage->label.' (Kopie)'; } else { $labelValue = GETPOST('label'); } print ''; print ''; // Kategorie (Gebäude/Standort vs Element/Gerät) $currentCategory = ''; if (($isEdit || $isCopy) && !empty($anlage->fk_anlage_type)) { // Kategorie des aktuellen Typs ermitteln foreach ($types as $t) { if ($t->id == $anlage->fk_anlage_type) { $currentCategory = ($t->system_code === 'GLOBAL') ? 'building' : 'element'; break; } } } $postedCategory = GETPOST('element_category', 'alpha'); if ($postedCategory) $currentCategory = $postedCategory; print ''; print ''; // Type (gefiltert nach Kategorie) print ''; print ''; // Parent (uses contact-specific tree) $tree = $anlage->fetchTreeByContact($object->socid, $id, $systemId); $selectedParent = ($isEdit || $isCopy) ? $anlage->fk_parent : $parentId; $excludeId = $isEdit ? $anlageId : 0; print ''; print ''; // Produkt-Zuordnung (wird per JS ein-/ausgeblendet je nach Typ) $productValue = ''; $productId = 0; if (($isEdit || $isCopy) && $anlage->fk_product > 0) { require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; $product = new Product($db); if ($product->fetch($anlage->fk_product) > 0) { $productValue = $product->ref.' - '.$product->label; $productId = $product->id; } } print ''; print ''; // Dynamic fields will be inserted here via JavaScript print ''; // Notes (always at the end) print ''; $noteValue = ($isEdit || $isCopy) ? $anlage->note_private : (isset($_POST['note_private']) ? $_POST['note_private'] : ''); print ''; print '
'.$langs->trans('Label').'
'.$langs->trans('Category').'
'.$langs->trans('Type').''; if (empty($types)) { print '
'.$langs->trans('NoTypesDefinedForSystem').''; } print '
'.$langs->trans('SelectParent').'
'.$langs->trans('FieldNotes').'
'; print '
'; print ''; print ' '.$langs->trans('Cancel').''; print '
'; print '
'; // JavaScript: Kategorie-Filter + Select2 mit Icons print ''; } print '
'; } else { // Listenansicht (Baum oder Graph) if ($permissiontoadd && $viewMode !== 'graph') { print '
'; print ''; print ' '.$langs->trans('AddElement'); print ''; print '
'; } if ($viewMode === 'graph' && $isTreeView) { // Graph-Ansicht: Container rendern, Daten werden per AJAX geladen kundenkarte_graph_print_container(array( 'socid' => $object->socid, 'contactid' => $id, 'systemid' => $systemId, 'permissiontoadd' => $permissiontoadd, 'permissiontodelete' => $permissiontodelete, 'pageUrl' => $_SERVER['PHP_SELF'], )); } else { // Baumansicht (klassisch) // Load tree for this contact $tree = $anlage->fetchTreeByContact($object->socid, $id, $systemId); // Pre-load all type fields for tooltip and tree display $typeFieldsMap = array(); $sql = "SELECT f.*, f.fk_anlage_type FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_type_field f WHERE f.active = 1 ORDER BY f.position ASC"; $resql = $db->query($sql); if ($resql) { while ($obj = $db->fetch_object($resql)) { if (!isset($typeFieldsMap[$obj->fk_anlage_type])) { $typeFieldsMap[$obj->fk_anlage_type] = array(); } $typeFieldsMap[$obj->fk_anlage_type][] = $obj; } $db->free($resql); } // Verbindungen werden nur im Graph angezeigt, nicht im Baum $connectionsByTarget = array(); if (!empty($tree)) { print '
'; printTree($tree, $id, $systemId, $permissiontoadd, $permissiontodelete, $langs, 0, $typeFieldsMap, $connectionsByTarget); print '
'; } else { print '
'.$langs->trans('NoInstallations').'
'; } } } } print '
'; print dol_get_fiche_end(); // Tooltip container print '
'; // Produkt-Autocomplete + Zubehör-AJAX (nur wenn Formular oder Detailansicht aktiv) if (in_array($action, array('create', 'edit', 'copy', 'view'))) { print ''; print ''; } llxFooter(); $db->close(); /** * Print tree recursively (root level - handles cable line assignment) */ function printTree($nodes, $contactid, $systemId, $canEdit, $canDelete, $langs, $level = 0, $typeFieldsMap = array(), $connectionsByTarget = array()) { foreach ($nodes as $node) { $hasChildren = !empty($node->children); $fieldValues = $node->getFieldValues(); // Build tooltip data - only label, type and dynamic fields $tooltipData = array( 'label' => $node->label, 'type' => $node->type_label, 'note_html' => $node->note_private ? nl2br(htmlspecialchars($node->note_private, ENT_QUOTES, 'UTF-8')) : '', 'fields' => array() ); // Collect fields for tooltip (show_in_hover) and tree label (show_in_tree) $treeInfoBadges = array(); // Fields to display as badges (right side) $treeInfoParentheses = array(); // Fields to display in parentheses (after label) if (!empty($typeFieldsMap[$node->fk_anlage_type])) { foreach ($typeFieldsMap[$node->fk_anlage_type] as $fieldDef) { // Handle header fields if ($fieldDef->field_type === 'header') { if ($fieldDef->show_in_hover) { $tooltipData['fields'][$fieldDef->field_code] = array( 'label' => $fieldDef->field_label, 'value' => '', 'type' => 'header' ); } continue; } $value = isset($fieldValues[$fieldDef->field_code]) ? $fieldValues[$fieldDef->field_code] : ''; // Add to tooltip if show_in_hover if ($fieldDef->show_in_hover && $value !== '') { // Format date values $displayValue = $value; if ($fieldDef->field_type === 'date' && $value) { $displayValue = dol_print_date(strtotime($value), 'day'); } $tooltipData['fields'][$fieldDef->field_code] = array( 'label' => $fieldDef->field_label, 'value' => $displayValue, 'type' => $fieldDef->field_type ); } // Add to tree label info if show_in_tree if ($fieldDef->show_in_tree && $value !== '') { // Format date for tree info too $displayVal = $value; if ($fieldDef->field_type === 'date' && $value) { $displayVal = dol_print_date(strtotime($value), 'day'); } // Store as array with field info $fieldInfo = array( 'label' => $fieldDef->field_label, 'value' => $displayVal, 'code' => $fieldDef->field_code, 'type' => $fieldDef->field_type, 'color' => $fieldDef->badge_color ?? '' ); // Sort into badge or parentheses based on field setting $displayMode = $fieldDef->tree_display_mode ?? 'badge'; if ($displayMode === 'parentheses') { $treeInfoParentheses[] = $fieldInfo; } else { $treeInfoBadges[] = $fieldInfo; } } } } // Show cable connections TO this node (BEFORE the element - cable appears above verbraucher) $hasConnection = !empty($connectionsByTarget[$node->id]); if ($hasConnection) { foreach ($connectionsByTarget[$node->id] as $conn) { // Build cable info (type + spec + length) $cableInfo = ''; if ($conn->medium_type_label || $conn->medium_type_text) { $cableInfo = $conn->medium_type_label ?: $conn->medium_type_text; } if ($conn->medium_spec) { $cableInfo .= ($cableInfo ? ' ' : '').$conn->medium_spec; } if ($conn->medium_length) { $cableInfo .= ' ('.$conn->medium_length.')'; } // If label exists, show cable info as badge. Otherwise show cable info as main text $mainText = $conn->label ? $conn->label : $cableInfo; $badgeText = $conn->label ? $cableInfo : ''; $connEditUrl = dol_buildpath('/kundenkarte/anlage_connection.php', 1).'?id='.$conn->id.'&socid='.$node->fk_soc.'&contactid='.$id.'&system_id='.$systemId; print ''; print ''; if ($mainText) { print ''.dol_escape_htmltag($mainText).''; } if ($badgeText) { print ' '.dol_escape_htmltag($badgeText).''; } print ''; } } // CSS class basierend auf Kabel-Verbindung und Typ-Kategorie $nodeClass = 'kundenkarte-tree-node'; if (!$hasConnection && $level > 0) { $nodeClass .= ' no-cable'; // durchgeschleift - kein eigenes Kabel } if (!empty($node->decommissioned)) { $nodeClass .= ' decommissioned'; } if ($node->type_can_have_equipment) { $nodeClass .= ' node-equipment'; // Geräte-Container (Schaltschrank, Verteiler) } elseif ($node->type_can_have_children) { $nodeClass .= ' node-structure'; // Gebäudeteil (Gebäude, Etage, Raum) } else { $nodeClass .= ' node-leaf'; // Endgerät } print '
'; print '
'; // Toggle if ($hasChildren) { print ''; } else { print ''; } // Icon with tooltip data $picto = $node->type_picto ? $node->type_picto : 'fa-cube'; print ''.kundenkarte_render_icon($picto).''; // Label with parentheses info (directly after label, only values) $viewUrl = $_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=view&anlage_id='.$node->id; print ''.dol_escape_htmltag($node->label); // Parentheses info directly after label (only values, no field names) if (!empty($treeInfoParentheses)) { $infoValues = array(); foreach ($treeInfoParentheses as $info) { $infoValues[] = dol_escape_htmltag($info['value']); } print ' ('.implode(', ', $infoValues).')'; } print ''; // Ausgebaut-Badge mit Datum if (!empty($node->decommissioned)) { $decommDate = !empty($node->date_decommissioned) ? dol_print_date(strtotime($node->date_decommissioned), 'day') : ''; $decommText = $langs->trans('Decommissioned'); if ($decommDate) $decommText .= ' '.$decommDate; print ' '.$decommText.''; } // Spacer to push badges to the right print ''; // Badges (far right, before actions) $defaultBadgeColor = getDolGlobalString('KUNDENKARTE_TREE_BADGE_COLOR', '#2a4a5e'); if (!empty($treeInfoBadges)) { print ''; foreach ($treeInfoBadges as $info) { $badgeIcon = kundenkarte_get_field_icon($info['code'], $info['type']); // Use field-specific color if set, otherwise global default $fieldBadgeColor = !empty($info['color']) ? $info['color'] : $defaultBadgeColor; print ''; print ' '.dol_escape_htmltag($info['value']); print ''; } print ''; } // File indicators if ($node->image_count > 0 || $node->doc_count > 0) { $totalFiles = $node->image_count + $node->doc_count; print ''; print ''; print ' '.$totalFiles; print ''; print ''; } // Type badge if ($node->type_short || $node->type_label) { $typeDisplay = $node->type_short ? $node->type_short : $node->type_label; print ''.dol_escape_htmltag($typeDisplay).''; } // Actions print ''; print ''; if ($canEdit) { print ''; print ''; print ''; $decommLabel = $node->decommissioned ? $langs->trans('Recommission') : $langs->trans('Decommission'); $decommIcon = $node->decommissioned ? 'fa-plug' : 'fa-power-off'; print ''; } if ($canDelete) { print ''; } print ''; print '
'; // Children - vertical tree layout with multiple parallel cable lines if ($hasChildren) { // First pass: assign cable index to each child with cable $cableCount = 0; $childCableIndex = array(); // child_id => cable_index foreach ($node->children as $child) { if (!empty($connectionsByTarget[$child->id])) { $cableCount++; $childCableIndex[$child->id] = $cableCount; } } print '
'; // Render children - each row has cable line columns on the left $prevHadCable = false; $isFirst = true; foreach ($node->children as $child) { $myCableIdx = isset($childCableIndex[$child->id]) ? $childCableIndex[$child->id] : 0; $hasOwnCable = !empty($connectionsByTarget[$child->id]); // Add spacer row before elements with their own cable (except first) if ($hasOwnCable && !$isFirst) { // Spacer row with active cable lines passing through $spacerActiveLines = array(); $foundCurrent = false; foreach ($node->children as $c) { if ($c->id == $child->id) { $foundCurrent = true; } if ($foundCurrent && isset($childCableIndex[$c->id])) { $spacerActiveLines[] = $childCableIndex[$c->id]; } } print '
'; for ($i = $cableCount; $i >= 1; $i--) { $isActive = in_array($i, $spacerActiveLines); $lineClass = 'cable-line'; if ($isActive) { $lineClass .= ' active'; } print ''; } print '
'; print '
'; } // Find which cable lines are still active (run past this row to children below) $childActiveLinesAfter = array(); $foundCurrent = false; foreach ($node->children as $c) { if ($c->id == $child->id) { $foundCurrent = true; continue; } if ($foundCurrent && isset($childCableIndex[$c->id])) { $childActiveLinesAfter[] = $childCableIndex[$c->id]; } } printTreeWithCableLines(array($child), $contactid, $systemId, $canEdit, $canDelete, $langs, $level + 1, $typeFieldsMap, $connectionsByTarget, $myCableIdx, $cableCount, $childActiveLinesAfter); $prevHadCable = $hasOwnCable; $isFirst = false; } print '
'; } print '
'; } } /** * Print tree with multiple parallel cable lines * Each child with its own cable gets a vertical line that runs from top past all siblings */ function printTreeWithCableLines($nodes, $contactid, $systemId, $canEdit, $canDelete, $langs, $level = 0, $typeFieldsMap = array(), $connectionsByTarget = array(), $myCableIndex = 0, $totalCables = 0, $activeLinesAfter = array()) { foreach ($nodes as $node) { $hasChildren = !empty($node->children); $hasConnection = !empty($connectionsByTarget[$node->id]); $fieldValues = $node->getFieldValues(); // Build tooltip data $tooltipData = array( 'label' => $node->label, 'type' => $node->type_label, 'note_html' => $node->note_private ? nl2br(htmlspecialchars($node->note_private, ENT_QUOTES, 'UTF-8')) : '', 'fields' => array() ); $treeInfoBadges = array(); $treeInfoParentheses = array(); if (!empty($typeFieldsMap[$node->fk_anlage_type])) { foreach ($typeFieldsMap[$node->fk_anlage_type] as $fieldDef) { if ($fieldDef->field_type === 'header') continue; $value = isset($fieldValues[$fieldDef->field_code]) ? $fieldValues[$fieldDef->field_code] : ''; if ($fieldDef->show_in_hover && $value !== '') { $displayValue = $value; if ($fieldDef->field_type === 'date' && $value) { $displayValue = dol_print_date(strtotime($value), 'day'); } $tooltipData['fields'][$fieldDef->field_code] = array( 'label' => $fieldDef->field_label, 'value' => $displayValue, 'type' => $fieldDef->field_type ); } if ($fieldDef->show_in_tree && $value !== '') { $displayVal = ($fieldDef->field_type === 'date' && $value) ? dol_print_date(strtotime($value), 'day') : $value; $fieldInfo = array( 'label' => $fieldDef->field_label, 'value' => $displayVal, 'code' => $fieldDef->field_code, 'type' => $fieldDef->field_type, 'color' => $fieldDef->badge_color ?? '' ); $displayMode = $fieldDef->tree_display_mode ?? 'badge'; if ($displayMode === 'parentheses') { $treeInfoParentheses[] = $fieldInfo; } else { $treeInfoBadges[] = $fieldInfo; } } } } // All lines that need to be drawn for this row (active lines passing through + my line if I have cable) $allLines = $activeLinesAfter; if ($hasConnection && $myCableIndex > 0) { $allLines[] = $myCableIndex; } sort($allLines); // Cable connection row (BEFORE the element) if ($hasConnection) { foreach ($connectionsByTarget[$node->id] as $conn) { $cableInfo = ''; if ($conn->medium_type_label || $conn->medium_type_text) { $cableInfo = $conn->medium_type_label ?: $conn->medium_type_text; } if ($conn->medium_spec) { $cableInfo .= ($cableInfo ? ' ' : '').$conn->medium_spec; } if ($conn->medium_length) { $cableInfo .= ' ('.$conn->medium_length.')'; } $mainText = $conn->label ? $conn->label : $cableInfo; $badgeText = $conn->label ? $cableInfo : ''; $connEditUrl = dol_buildpath('/kundenkarte/anlage_connection.php', 1).'?id='.$conn->id.'&socid='.$node->fk_soc.'&contactid='.$id.'&system_id='.$systemId; print '
'; // Draw vertical line columns (for cables passing through) // Reverse order: index 1 (first element) = rightmost, highest index = leftmost for ($i = $totalCables; $i >= 1; $i--) { $isActive = in_array($i, $activeLinesAfter); $isMyLine = ($i == $myCableIndex); $lineClass = 'cable-line'; $inlineStyle = ''; if ($isActive) { $lineClass .= ' active'; // Line continues down } if ($isMyLine) { $lineClass .= ' my-line conn-line'; // This is my cable line - ends here with horizontal // Calculate width: columns to the right * 15px + 8px to reach element border $columnsToRight = $i - 1; $horizontalWidth = ($columnsToRight * 15) + 8; $inlineStyle = ' style="--h-width: '.$horizontalWidth.'px;"'; } print ''; } print ''; print ''; if ($mainText) { print ''.dol_escape_htmltag($mainText).''; } if ($badgeText) { print ' '.dol_escape_htmltag($badgeText).''; } print ''; print '
'; } } // Node row $nodeClass = 'kundenkarte-tree-row node-row'; if (!$hasConnection && $level > 0) { $nodeClass .= ' no-cable'; } if (!empty($node->decommissioned)) { $nodeClass .= ' decommissioned'; } print '
'; // Draw vertical line columns (for cables passing through) // Reverse order: index 1 (first element) = rightmost, highest index = leftmost for ($i = $totalCables; $i >= 1; $i--) { $isActive = in_array($i, $activeLinesAfter); $isMyLine = ($i == $myCableIndex && $hasConnection); $lineClass = 'cable-line'; $inlineStyle = ''; if ($isActive) { $lineClass .= ' active'; } if ($isMyLine) { $lineClass .= ' my-line node-line'; // Horizontal connector to this node // Calculate width: columns to the right * 15px + 8px to reach element border $columnsToRight = $i - 1; $horizontalWidth = ($columnsToRight * 15) + 8; $inlineStyle = ' style="--h-width: '.$horizontalWidth.'px;"'; } print ''; } // Typ-Kategorie als CSS-Klasse $nodeContentClass = 'kundenkarte-tree-node-content'; if ($node->type_can_have_equipment) { $nodeContentClass .= ' node-equipment'; } elseif ($node->type_can_have_children) { $nodeContentClass .= ' node-structure'; } else { $nodeContentClass .= ' node-leaf'; } print '
'; print '
'; if ($hasChildren) { print ''; } else { print ''; } $picto = $node->type_picto ? $node->type_picto : 'fa-cube'; print ''.kundenkarte_render_icon($picto).''; $viewUrl = $_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=view&anlage_id='.$node->id; // Label with parentheses info (only values, no field names) print ''.dol_escape_htmltag($node->label); if (!empty($treeInfoParentheses)) { $infoValues = array(); foreach ($treeInfoParentheses as $info) { $infoValues[] = dol_escape_htmltag($info['value']); } print ' ('.implode(', ', $infoValues).')'; } print ''; // Ausgebaut-Badge mit Datum if (!empty($node->decommissioned)) { $decommDate = !empty($node->date_decommissioned) ? dol_print_date(strtotime($node->date_decommissioned), 'day') : ''; $decommText = $langs->trans('Decommissioned'); if ($decommDate) $decommText .= ' '.$decommDate; print ' '.$decommText.''; } // Spacer to push badges to the right print ''; // Badges (far right) if (!empty($treeInfoBadges)) { $defaultBadgeColor = getDolGlobalString('KUNDENKARTE_TREE_BADGE_COLOR', '#2a4a5e'); print ''; foreach ($treeInfoBadges as $info) { $badgeIcon = kundenkarte_get_field_icon($info['code'], $info['type']); $fieldBadgeColor = !empty($info['color']) ? $info['color'] : $defaultBadgeColor; print ''; print ' '.dol_escape_htmltag($info['value']); print ''; } print ''; } // File indicators if ($node->image_count > 0 || $node->doc_count > 0) { $totalFiles = $node->image_count + $node->doc_count; print ''; print ''; print ' '.$totalFiles; print ''; print ''; } if ($node->type_short || $node->type_label) { $typeDisplay = $node->type_short ? $node->type_short : $node->type_label; print ''.dol_escape_htmltag($typeDisplay).''; } print ''; print ''; if ($canEdit) { print ''; print ''; print ''; $decommLabel = $node->decommissioned ? $langs->trans('Recommission') : $langs->trans('Decommission'); $decommIcon = $node->decommissioned ? 'fa-plug' : 'fa-power-off'; print ''; } if ($canDelete) { print ''; } print ''; print '
'; print '
'; // node-content print '
'; // tree-row // Children if ($hasChildren) { // First pass: assign cable index to each child with cable $childCableCount = 0; $childCableIndex = array(); // child_id => cable_index foreach ($node->children as $child) { if (!empty($connectionsByTarget[$child->id])) { $childCableCount++; $childCableIndex[$child->id] = $childCableCount; } } print '
'; $prevHadCable = false; $isFirst = true; foreach ($node->children as $child) { $myCableIdx = isset($childCableIndex[$child->id]) ? $childCableIndex[$child->id] : 0; $hasOwnCable = !empty($connectionsByTarget[$child->id]); // Add spacer row before elements with their own cable (except first) if ($hasOwnCable && !$isFirst) { // Spacer row with active cable lines passing through $spacerActiveLines = array(); $foundCurrent = false; foreach ($node->children as $c) { if ($c->id == $child->id) { $foundCurrent = true; } if ($foundCurrent && isset($childCableIndex[$c->id])) { $spacerActiveLines[] = $childCableIndex[$c->id]; } } print '
'; for ($i = $childCableCount; $i >= 1; $i--) { $isActive = in_array($i, $spacerActiveLines); $lineClass = 'cable-line'; if ($isActive) { $lineClass .= ' active'; } print ''; } print '
'; print '
'; } // Find which cable lines are still active (run past this row to children below) $childActiveLinesAfter = array(); $foundCurrent = false; foreach ($node->children as $c) { if ($c->id == $child->id) { $foundCurrent = true; continue; } if ($foundCurrent && isset($childCableIndex[$c->id])) { $childActiveLinesAfter[] = $childCableIndex[$c->id]; } } printTreeWithCableLines(array($child), $contactid, $systemId, $canEdit, $canDelete, $langs, $level + 1, $typeFieldsMap, $connectionsByTarget, $myCableIdx, $childCableCount, $childActiveLinesAfter); $prevHadCable = $hasOwnCable; $isFirst = false; } print '
'; } } } /** * Print tree options for select */ function printTreeOptions($nodes, $selected = 0, $excludeId = 0, $prefix = '', $level = 0) { foreach ($nodes as $node) { if ($node->id == $excludeId) continue; $sel = ($node->id == $selected) ? ' selected' : ''; $icon = !empty($node->type_picto) ? $node->type_picto : 'fa-cube'; $color = !empty($node->type_color) ? $node->type_color : '#888'; print ''; if (!empty($node->children)) { printTreeOptions($node->children, $selected, $excludeId, $prefix.'── ', $level + 1); } } }