kundenkarte/werkzeuge.php
data 16e51a799a feat(v8.6): Räumlichkeit, Verteilungs-Tabellen, Bundled-Terminals, PWA-Updates
- 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>
2026-03-09 01:33:05 +01:00

982 lines
36 KiB
PHP
Executable file

<?php
/* Copyright (C) 2026 Alles Watt lauft
*
* Mein Betrieb: Baumansicht für eigene Maschinen, Werkzeuge und Geräte
* Multi-System mit System-Tabs (wie Kunden-Anlagen)
*/
// Load Dolibarr environment
$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");
require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
dol_include_once('/kundenkarte/class/anlage.class.php');
dol_include_once('/kundenkarte/class/anlagetype.class.php');
dol_include_once('/kundenkarte/class/anlagefile.class.php');
dol_include_once('/kundenkarte/class/anlageaccessory.class.php');
dol_include_once('/kundenkarte/lib/kundenkarte.lib.php');
// Übersetzungen
$langs->loadLangs(array('companies', 'kundenkarte@kundenkarte'));
// Berechtigungen
if (!$user->hasRight('kundenkarte', 'read')) {
accessforbidden();
}
$permissiontoread = $user->hasRight('kundenkarte', 'read');
$permissiontoadd = $user->hasRight('kundenkarte', 'write');
$permissiontodelete = $user->hasRight('kundenkarte', 'delete');
$action = GETPOST('action', 'aZ09');
$confirm = GETPOST('confirm', 'alpha');
$systemId = GETPOSTINT('system');
$anlageId = GETPOSTINT('anlage_id');
$parentId = GETPOSTINT('parent_id');
// Virtuelle Firma-ID für "Mein Betrieb" - braucht keinen echten Societe-Eintrag
// Die Anlage-Tabelle verwendet diese ID nur als Gruppierung
$socId = 99999999;
// ALLE verfügbaren Systeme laden
$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;
}
}
// Für diesen virtuellen Betrieb aktivierte Systeme laden
$customerSystems = array();
$sql = "SELECT ss.rowid, ss.fk_system, s.code, s.label, s.picto, s.color";
$sql .= " FROM ".MAIN_DB_PREFIX."kundenkarte_societe_system ss";
$sql .= " JOIN ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system s ON s.rowid = ss.fk_system";
$sql .= " WHERE ss.fk_soc = ".((int) $socId)." AND (ss.fk_contact IS NULL OR ss.fk_contact = 0) AND ss.active = 1 AND s.active = 1";
$sql .= " AND s.code != 'GLOBAL'";
$sql .= " ORDER BY s.position ASC";
$resql = $db->query($sql);
if ($resql) {
while ($obj = $db->fetch_object($resql)) {
$customerSystems[$obj->fk_system] = $obj;
}
}
// Standard: Erstes aktiviertes System falls nicht angegeben
if (empty($systemId) && !empty($customerSystems)) {
$systemId = array_key_first($customerSystems);
}
// Objekte initialisieren
$form = new Form($db);
$anlage = new Anlage($db);
$anlageType = new AnlageType($db);
/*
* Actions
*/
// System hinzufügen
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) $socId).", 0, ".((int) $newSystemId).", NOW(), ".((int) $user->id).", 1)";
$result = $db->query($sql);
if ($result) {
setEventMessages($langs->trans('SystemAdded'), null, 'mesgs');
header('Location: '.$_SERVER['PHP_SELF'].'?system='.$newSystemId);
exit;
} else {
setEventMessages($db->lasterror(), null, 'errors');
}
}
$action = '';
}
// System entfernen
if ($action == 'confirm_remove_system' && $confirm == 'yes' && $permissiontodelete) {
$removeSystemId = GETPOSTINT('remove_system_id');
if ($removeSystemId > 0) {
// Prüfen ob System noch Elemente hat
$sql = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX."kundenkarte_anlage WHERE fk_soc = ".((int) $socId)." AND (fk_contact IS NULL OR fk_contact = 0) 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) $socId)." AND (fk_contact IS NULL OR fk_contact = 0) AND fk_system = ".((int) $removeSystemId);
$db->query($sql);
setEventMessages($langs->trans('SystemRemoved'), null, 'mesgs');
unset($customerSystems[$removeSystemId]);
if (!empty($customerSystems)) {
$systemId = array_key_first($customerSystems);
} else {
$systemId = 0;
}
}
}
header('Location: '.$_SERVER['PHP_SELF'].($systemId ? '?system='.$systemId : ''));
exit;
}
if ($action == 'add' && $permissiontoadd) {
$anlage->label = GETPOST('label', 'alphanohtml');
$anlage->fk_soc = $socId;
$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;
// Dynamische Felder
$type = new AnlageType($db);
if ($type->fetch($anlage->fk_anlage_type) > 0) {
$fieldValues = array();
$fields = $type->fetchFields();
foreach ($fields as $field) {
if ($field->field_type === 'header') continue;
$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'].'?system='.$systemId);
exit;
} else {
setEventMessages($anlage->error, $anlage->errors, 'errors');
$action = 'create';
}
}
if ($action == 'update' && $permissiontoadd) {
$anlage->fetch($anlageId);
$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'] : '';
// Dynamische Felder
$type = new AnlageType($db);
if ($type->fetch($anlage->fk_anlage_type) > 0) {
$fieldValues = array();
$fields = $type->fetchFields();
foreach ($fields as $field) {
if ($field->field_type === 'header') continue;
$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'].'?system='.$systemId);
exit;
} else {
setEventMessages($anlage->error, $anlage->errors, 'errors');
$action = 'edit';
}
}
if ($action == 'confirm_delete' && $confirm == 'yes' && $permissiontodelete) {
$anlage->fetch($anlageId);
$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']);
exit;
}
/*
* View
*/
$title = $langs->trans('CompanyTools');
$jsFiles = array('/kundenkarte/js/kundenkarte.js?v='.time());
$cssFiles = array('/kundenkarte/css/kundenkarte.css?v='.time());
llxHeader('', $title, '', '', 0, 0, $jsFiles, $cssFiles);
print load_fiche_titre($title, '', 'fa-wrench');
print '<div class="fichecenter">';
// Bestätigungsdialoge
if ($action == 'delete') {
print $form->formconfirm(
$_SERVER['PHP_SELF'].'?system='.$systemId.'&anlage_id='.$anlageId,
$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'].'?remove_system_id='.$removeSystemId,
$langs->trans('RemoveSystem'),
$langs->trans('ConfirmRemoveSystem', $sysLabel),
'confirm_remove_system',
'',
'yes',
1
);
}
// System-Tabs
print '<div class="kundenkarte-system-tabs-wrapper">';
print '<div class="kundenkarte-system-tabs">';
foreach ($customerSystems as $sysId => $sys) {
$activeClass = ($sysId == $systemId) ? ' active' : '';
print '<div class="kundenkarte-system-tab'.$activeClass.'" data-system="'.$sysId.'">';
print '<a href="'.$_SERVER['PHP_SELF'].'?system='.$sysId.'" style="text-decoration:none;color:inherit;display:flex;align-items:center;gap:8px;">';
if ($sys->picto) {
print '<span class="kundenkarte-system-tab-icon" style="color:'.$sys->color.';">'.kundenkarte_render_icon($sys->picto).'</span>';
}
print '<span>'.dol_escape_htmltag($sys->label).'</span>';
print '</a>';
// Entfernen-Button (nur beim aktiven Tab)
if ($permissiontodelete && $sysId == $systemId) {
print ' <a href="'.$_SERVER['PHP_SELF'].'?action=remove_system&remove_system_id='.$sysId.'" class="kundenkarte-system-remove" title="'.$langs->trans('RemoveSystem').'"><i class="fa fa-times"></i></a>';
}
print '</div>';
}
// System hinzufügen Button
if ($permissiontoadd) {
$availableSystems = array_diff_key($allSystems, $customerSystems);
// GLOBAL ausschließen
foreach ($availableSystems as $k => $v) {
if ($v->code === 'GLOBAL') unset($availableSystems[$k]);
}
if (!empty($availableSystems)) {
print '<button type="button" class="button small kundenkarte-add-system-btn" onclick="document.getElementById(\'add-system-form\').style.display=\'block\';">';
print '<i class="fa fa-plus"></i> '.$langs->trans('AddSystem');
print '</button>';
}
}
print '</div>';
// Steuerungs-Buttons (nur in Baumansicht)
$isTreeView = !in_array($action, array('create', 'edit', 'view'));
if ($isTreeView && $systemId > 0) {
print '<div class="kundenkarte-tree-controls">';
print '<button type="button" class="kundenkarte-view-toggle" id="btn-compact-mode" title="Kompakte Ansicht">';
print '<i class="fa fa-compress"></i> <span>Kompakt</span>';
print '</button>';
print '<button type="button" class="button small" id="btn-expand-all" title="'.$langs->trans('ExpandAll').'">';
print '<i class="fa fa-expand"></i> '.$langs->trans('ExpandAll');
print '</button>';
print '<button type="button" class="button small" id="btn-collapse-all" title="'.$langs->trans('CollapseAll').'">';
print '<i class="fa fa-compress"></i> '.$langs->trans('CollapseAll');
print '</button>';
$showDecomm = getDolGlobalInt('KUNDENKARTE_SHOW_DECOMMISSIONED', 0);
print '<button type="button" class="button small'.($showDecomm ? ' active' : '').'" id="btn-toggle-decommissioned" title="'.$langs->trans('ShowDecommissioned').'">';
print '<i class="fa '.($showDecomm ? 'fa-eye' : 'fa-eye-slash').'"></i> <span>'.$langs->trans('Decommissioned').'</span>';
print '</button>';
print '</div>';
}
print '</div>'; // End system-tabs-wrapper
// System-Hinzufügen-Formular (versteckt)
if ($permissiontoadd && !empty($availableSystems)) {
print '<div id="add-system-form" class="kundenkarte-add-system-form" style="display:none;margin-bottom:15px;">';
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="add_system">';
print '<strong>'.$langs->trans('SelectSystemToAdd').':</strong> ';
print '<select name="new_system_id" class="flat">';
print '<option value="">'.$langs->trans('Select').'</option>';
foreach ($availableSystems as $avSys) {
print '<option value="'.$avSys->rowid.'">'.dol_escape_htmltag($avSys->label).'</option>';
}
print '</select>';
print ' <button type="submit" class="button small">'.$langs->trans('Add').'</button>';
print ' <button type="button" class="button small" onclick="document.getElementById(\'add-system-form\').style.display=\'none\';">'.$langs->trans('Cancel').'</button>';
print '</form>';
print '</div>';
}
// Prüfen ob Systeme konfiguriert sind
if (empty($customerSystems)) {
print '<div class="opacitymedium" style="padding:20px;text-align:center;">';
print '<i class="fa fa-info-circle" style="font-size:24px;margin-bottom:10px;"></i><br>';
print $langs->trans('NoSystemsConfigured').'<br><br>';
if ($permissiontoadd && !empty($allSystems)) {
print $langs->trans('ClickAddSystemToStart');
} else {
print $langs->trans('ContactAdminToAddSystems');
}
print '</div>';
} elseif ($systemId > 0) {
// Typen für ausgewähltes System laden
$types = $anlageType->fetchAllBySystem($systemId, 1, 1);
if (in_array($action, array('create', 'edit', 'view'))) {
// Formular oder Detail-Ansicht
if ($action != 'create' && $anlageId > 0) {
$anlage->fetch($anlageId);
$type = new AnlageType($db);
$type->fetch($anlage->fk_anlage_type);
$type->fetchFields();
}
print '<div class="kundenkarte-element-form">';
if ($action == 'view') {
// Detail-Ansicht
print '<h3>'.dol_escape_htmltag($anlage->label).'</h3>';
print '<table class="border centpercent">';
print '<tr><td class="titlefield">'.$langs->trans('Type').'</td>';
print '<td>'.dol_escape_htmltag($anlage->type_label).'</td></tr>';
// Zugeordnetes Produkt (nur wenn Typ es erlaubt)
if ($type->has_product && $anlage->fk_product > 0) {
$product = new Product($db);
if ($product->fetch($anlage->fk_product) > 0) {
print '<tr><td>'.$langs->trans('Product').'</td>';
print '<td><a href="'.DOL_URL_ROOT.'/product/card.php?id='.$product->id.'">'.dol_escape_htmltag($product->ref).'</a>';
print ' - '.dol_escape_htmltag($product->label);
if ($product->price > 0) {
print ' <span class="opacitymedium">('.price($product->price).' €)</span>';
}
print '</td></tr>';
}
}
// Dynamische Felder
$fieldValues = $anlage->getFieldValues();
$typeFieldsList = $type->fetchFields();
foreach ($typeFieldsList as $field) {
if ($field->field_type === 'header') {
print '<tr class="liste_titre"><th colspan="2" style="padding:8px;">'.dol_escape_htmltag($field->field_label).'</th></tr>';
} else {
$value = isset($fieldValues[$field->field_code]) ? $fieldValues[$field->field_code] : '';
if ($value !== '') {
print '<tr><td>'.dol_escape_htmltag($field->field_label).'</td>';
if ($field->field_type === 'date' && $value) {
print '<td>'.dol_print_date(strtotime($value), 'day').'</td></tr>';
} elseif ($field->field_type === 'checkbox') {
print '<td>'.($value ? $langs->trans('Yes') : $langs->trans('No')).'</td></tr>';
} else {
print '<td>'.dol_escape_htmltag($value).'</td></tr>';
}
}
}
}
if ($anlage->note_private) {
print '<tr><td>'.$langs->trans('FieldNotes').'</td>';
print '<td>'.dol_htmlentitiesbr($anlage->note_private).'</td></tr>';
}
// Ausgebaut-Status
if (!empty($anlage->decommissioned)) {
print '<tr><td>'.$langs->trans('Decommissioned').'</td>';
print '<td><span class="badge-decommissioned"><i class="fa fa-power-off"></i> '.$langs->trans('Decommissioned').'</span>';
if (!empty($anlage->date_decommissioned)) {
print ' '.dol_print_date(strtotime($anlage->date_decommissioned), 'day');
}
print '</td></tr>';
}
print '</table>';
// Zubehör-Bereich (nur wenn Typ has_accessories hat)
if ($type->has_accessories) {
$accessoryObj = new AnlageAccessory($db);
$accessories = $accessoryObj->fetchAllByAnlage($anlageId);
print '<br><h4><i class="fa fa-puzzle-piece"></i> '.$langs->trans('Accessories').'</h4>';
if (!empty($accessories)) {
print '<div class="div-table-responsive">';
print '<table class="tagtable liste">';
print '<tr class="liste_titre">';
print '<th>'.$langs->trans('ProductRef').'</th>';
print '<th>'.$langs->trans('Label').'</th>';
print '<th class="right">'.$langs->trans('Qty').'</th>';
print '<th>'.$langs->trans('Note').'</th>';
if ($permissiontodelete) {
print '<th class="right">'.$langs->trans('Action').'</th>';
}
print '</tr>';
foreach ($accessories as $acc) {
print '<tr>';
print '<td><a href="'.DOL_URL_ROOT.'/product/card.php?id='.$acc->fk_product.'">'.dol_escape_htmltag($acc->product_ref).'</a></td>';
print '<td>'.dol_escape_htmltag($acc->product_label).'</td>';
print '<td class="right">'.$acc->qty.'</td>';
print '<td>'.dol_escape_htmltag($acc->note).'</td>';
if ($permissiontodelete) {
print '<td class="right"><a href="#" class="btn-delete-accessory" data-id="'.$acc->id.'" title="'.$langs->trans('Delete').'"><i class="fa fa-trash"></i></a></td>';
}
print '</tr>';
}
print '</table>';
print '</div>';
} else {
print '<p class="opacitymedium">'.$langs->trans('NoAccessories').'</p>';
}
// Zubehör hinzufügen
if ($permissiontoadd) {
print '<div id="add-accessory-form" style="margin-top:10px;">';
print '<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;">';
print '<input type="text" id="accessory_product_search" class="flat minwidth300" placeholder="'.$langs->trans('SearchProduct').'...">';
print '<input type="hidden" id="accessory_product_id" value="">';
print '<input type="number" id="accessory_qty" class="flat" value="1" min="1" style="width:80px;">';
print '<button type="button" id="btn-add-accessory" class="button small" disabled>'.$langs->trans('Add').'</button>';
print '</div>';
print '</div>';
}
// Bestellfunktion
if ($permissiontoadd && !empty($accessories)) {
print '<div style="margin-top:15px;padding:10px;border:1px solid #444;border-radius:4px;">';
print '<h5 style="margin:0 0 10px 0;"><i class="fa fa-shopping-cart"></i> '.$langs->trans('OrderAccessories').'</h5>';
print '<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;">';
print '<select id="supplier_select" class="flat minwidth200">';
print '<option value="">'.$langs->trans('SelectSupplier').'</option>';
// Lieferanten laden
$sqlSupp = "SELECT s.rowid, s.nom FROM ".MAIN_DB_PREFIX."societe s WHERE s.fournisseur = 1 AND s.status = 1 ORDER BY s.nom";
$resSupp = $db->query($sqlSupp);
if ($resSupp) {
while ($objSupp = $db->fetch_object($resSupp)) {
print '<option value="'.$objSupp->rowid.'">'.dol_escape_htmltag($objSupp->nom).'</option>';
}
}
print '</select>';
print '<button type="button" id="btn-order-accessories" class="button small">'.$langs->trans('CreateSupplierOrder').'</button>';
print '</div>';
print '</div>';
}
}
// Aktions-Buttons
print '<div class="tabsAction">';
if ($permissiontoadd) {
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?system='.$systemId.'&action=edit&anlage_id='.$anlageId.'">'.$langs->trans('Modify').'</a>';
}
if ($permissiontodelete) {
print '<a class="butActionDelete" href="'.$_SERVER['PHP_SELF'].'?system='.$systemId.'&action=delete&anlage_id='.$anlageId.'">'.$langs->trans('Delete').'</a>';
}
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?system='.$systemId.'">'.$langs->trans('Back').'</a>';
print '</div>';
} else {
// Erstellen/Bearbeiten-Formular
$isEdit = ($action == 'edit');
$formAction = $isEdit ? 'update' : 'add';
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'?system='.$systemId.'">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="'.$formAction.'">';
print '<input type="hidden" name="system" value="'.$systemId.'">';
if ($isEdit) {
print '<input type="hidden" name="anlage_id" value="'.$anlageId.'">';
}
print '<table class="border centpercent" id="element_form_table">';
// Label
$labelValue = $isEdit ? $anlage->label : GETPOST('label');
print '<tr><td class="titlefield fieldrequired">'.$langs->trans('Label').'</td>';
print '<td><input type="text" name="label" class="flat minwidth300" value="'.dol_escape_htmltag($labelValue).'" required></td></tr>';
// Typ
print '<tr><td class="fieldrequired">'.$langs->trans('Type').'</td>';
print '<td><select name="fk_anlage_type" class="flat minwidth200" id="select_type" required>';
print '<option value="">'.$langs->trans('SelectType').'</option>';
foreach ($types as $t) {
$selected = ($isEdit && $anlage->fk_anlage_type == $t->id) ? ' selected' : '';
print '<option value="'.$t->id.'" data-has-product="'.($t->has_product ? '1' : '0').'" data-has-accessories="'.($t->has_accessories ? '1' : '0').'"'.$selected.'>'.dol_escape_htmltag($t->label).'</option>';
}
print '</select>';
if (empty($types)) {
print '<br><span class="warning">'.$langs->trans('NoTypesDefinedForSystem').' <a href="'.dol_buildpath('/kundenkarte/admin/anlage_types.php', 1).'">'.$langs->trans('GoToTypeAdmin').'</a></span>';
}
print '</td></tr>';
// Übergeordnetes Element
$tree = $anlage->fetchTree($socId, $systemId);
$selectedParent = $isEdit ? $anlage->fk_parent : $parentId;
$excludeId = $isEdit ? $anlageId : 0;
print '<tr><td>'.$langs->trans('SelectParent').'</td>';
print '<td><select name="fk_parent" class="flat minwidth200">';
print '<option value="0">('.$langs->trans('Root').')</option>';
werkzeuge_printTreeOptions($tree, $selectedParent, $excludeId);
print '</select></td></tr>';
// Produkt-Zuordnung (wird per JS ein-/ausgeblendet je nach Typ)
$productValue = '';
$productId = 0;
if ($isEdit && $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 '<tr id="row_product" style="display:none;"><td>'.$langs->trans('Product').'</td>';
print '<td>';
print '<input type="text" id="product_search" class="flat minwidth300" placeholder="'.$langs->trans('SearchProduct').'..." value="'.dol_escape_htmltag($productValue).'">';
print '<input type="hidden" name="fk_product" id="fk_product" value="'.$productId.'">';
print '</td></tr>';
// Dynamische Felder werden per JS geladen
print '<tbody id="dynamic_fields"></tbody>';
// Notizen
print '<tr><td>'.$langs->trans('FieldNotes').'</td>';
$noteValue = $isEdit ? $anlage->note_private : (isset($_POST['note_private']) ? $_POST['note_private'] : '');
print '<td><textarea name="note_private" class="flat minwidth300" rows="3">'.htmlspecialchars($noteValue, ENT_QUOTES, 'UTF-8').'</textarea></td></tr>';
print '</table>';
print '<div class="center" style="margin-top:20px;">';
print '<button type="submit" class="button button-save">'.$langs->trans('Save').'</button>';
print ' <a class="button button-cancel" href="'.$_SERVER['PHP_SELF'].'?system='.$systemId.'">'.$langs->trans('Cancel').'</a>';
print '</div>';
print '</form>';
}
print '</div>';
} else {
// Baumansicht
if ($permissiontoadd) {
print '<div style="margin-bottom:15px;">';
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?system='.$systemId.'&action=create">';
print '<i class="fa fa-plus"></i> '.$langs->trans('AddElement');
print '</a>';
print '</div>';
}
// Baum laden
$tree = $anlage->fetchTree($socId, $systemId);
// Feld-Metadaten laden
$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);
}
if (!empty($tree)) {
print '<div class="kundenkarte-tree'.($showDecomm ? ' show-decommissioned' : '').'" data-system="'.$systemId.'" data-socid="'.$socId.'">';
werkzeuge_printTree($tree, $socId, $systemId, $permissiontoadd, $permissiontodelete, $langs, 0, $typeFieldsMap);
print '</div>';
} else {
print '<div class="opacitymedium" style="padding:20px;text-align:center;">';
print '<i class="fa fa-wrench" style="font-size:48px;margin-bottom:15px;color:#666;"></i><br>';
print $langs->trans('NoToolsYet').'<br><br>';
if ($permissiontoadd) {
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?system='.$systemId.'&action=create"><i class="fa fa-plus"></i> '.$langs->trans('AddFirstTool').'</a>';
}
print '</div>';
}
}
} // Ende elseif ($systemId > 0)
print '</div>'; // fichecenter
// Tooltip Container
print '<div id="kundenkarte-tooltip" class="kundenkarte-tooltip"></div>';
// JavaScript: Produkt-Autocomplete + Zubehör-AJAX
print '<script>
$(document).ready(function() {
var baseUrl = "'.dol_escape_js(dol_buildpath('/kundenkarte', 1)).'";
// Produkt-Zeile ein-/ausblenden je nach Typ-Flag has_product
var $typeSelect = $("#select_type");
function updateProductRow() {
var $selected = $typeSelect.find("option:selected");
var hasProduct = $selected.data("has-product");
if (hasProduct == 1) {
$("#row_product").show();
} else {
$("#row_product").hide();
$("#fk_product").val("");
$("#product_search").val("");
}
}
$typeSelect.on("change", updateProductRow);
updateProductRow(); // Initial
// Produkt-Autocomplete
function initProductAutocomplete(inputSelector, hiddenSelector) {
var $input = $(inputSelector);
var $hidden = $(hiddenSelector);
if (!$input.length) return;
var searchTimeout;
$input.on("input", function() {
clearTimeout(searchTimeout);
var term = $(this).val();
if (term.length < 2) {
$hidden.val("");
$(".product-autocomplete-dropdown").remove();
return;
}
searchTimeout = setTimeout(function() {
$.get(baseUrl + "/ajax/equipment.php", {
action: "get_products",
term: term,
token: $("input[name=token]").val() || ""
}, function(data) {
$(".product-autocomplete-dropdown").remove();
if (data.success && data.products && data.products.length > 0) {
var $dropdown = $("<div class=\"product-autocomplete-dropdown\"></div>");
$.each(data.products, function(i, p) {
var label = p.ref + " - " + p.label;
if (p.price > 0) label += " (" + p.price + " €)";
$dropdown.append(
$("<div class=\"product-autocomplete-item\"></div>")
.text(label)
.data("id", p.id)
.data("ref", p.ref)
.data("label", p.label)
);
});
$input.after($dropdown);
$dropdown.on("click", ".product-autocomplete-item", function() {
var id = $(this).data("id");
var ref = $(this).data("ref");
var label = $(this).data("label");
$input.val(ref + " - " + label);
$hidden.val(id);
$dropdown.remove();
// Zubehör: Button aktivieren
if (inputSelector === "#accessory_product_search") {
$("#btn-add-accessory").prop("disabled", false);
}
});
}
});
}, 300);
});
// Dropdown schließen bei Klick außerhalb
$(document).on("click", function(e) {
if (!$(e.target).closest(inputSelector + ", .product-autocomplete-dropdown").length) {
$(".product-autocomplete-dropdown").remove();
}
});
// Bei manuellem Leeren auch Hidden zurücksetzen
$input.on("change", function() {
if (!$(this).val()) {
$hidden.val("");
if (inputSelector === "#accessory_product_search") {
$("#btn-add-accessory").prop("disabled", true);
}
}
});
}
initProductAutocomplete("#product_search", "#fk_product");
initProductAutocomplete("#accessory_product_search", "#accessory_product_id");
// Zubehör hinzufügen
$("#btn-add-accessory").on("click", function() {
var productId = $("#accessory_product_id").val();
var qty = $("#accessory_qty").val() || 1;
var anlageId = '.((int) $anlageId).';
if (!productId || !anlageId) return;
$.post(baseUrl + "/ajax/anlage_accessory.php", {
action: "add",
fk_anlage: anlageId,
fk_product: productId,
qty: qty,
token: $("input[name=token]").val() || ""
}).done(function(data) {
if (data.success) {
location.reload();
} else {
alert(data.error || "Fehler");
}
}).fail(function() {
alert("Server-Fehler");
});
});
// Zubehör löschen
$(".btn-delete-accessory").on("click", function(e) {
e.preventDefault();
if (!confirm("'.$langs->trans('ConfirmDeleteAccessory').'")) return;
var accId = $(this).data("id");
$.post(baseUrl + "/ajax/anlage_accessory.php", {
action: "delete",
id: accId,
token: $("input[name=token]").val() || ""
}).done(function(data) {
if (data.success) {
location.reload();
} else {
alert(data.error || "Fehler");
}
});
});
// Lieferantenbestellung
$("#btn-order-accessories").on("click", function() {
var supplierId = $("#supplier_select").val();
var anlageId = '.((int) $anlageId).';
if (!supplierId) {
alert("Bitte Lieferant auswählen");
return;
}
// Alle Zubehör-IDs sammeln
var ids = [];
$(".btn-delete-accessory").each(function() {
ids.push($(this).data("id"));
});
if (ids.length === 0) return;
$.post(baseUrl + "/ajax/anlage_accessory.php", {
action: "order",
fk_anlage: anlageId,
supplier_id: supplierId,
"ids[]": ids,
token: $("input[name=token]").val() || ""
}).done(function(data) {
if (data.success && data.order_id) {
window.location.href = "'.DOL_URL_ROOT.'/fourn/commande/card.php?id=" + data.order_id;
} else {
alert(data.error || "Fehler beim Erstellen der Bestellung");
}
}).fail(function() {
alert("Server-Fehler");
});
});
});
</script>';
// CSS für Autocomplete
print '<style>
.product-autocomplete-dropdown {
position: absolute;
z-index: 1000;
background: var(--colorbackbody, #fff);
border: 1px solid #555;
border-radius: 4px;
max-height: 300px;
overflow-y: auto;
min-width: 300px;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}
.product-autocomplete-item {
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid #333;
}
.product-autocomplete-item:hover {
background: var(--colorbacklinepairhover, #333);
}
.product-autocomplete-item:last-child {
border-bottom: none;
}
</style>';
llxFooter();
$db->close();
/**
* Baum rekursiv ausgeben (vereinfachte Version für Werkzeuge-Seite)
*/
function werkzeuge_printTree($nodes, $socid, $systemId, $canEdit, $canDelete, $langs, $level = 0, $typeFieldsMap = array())
{
foreach ($nodes as $node) {
$hasChildren = !empty($node->children);
$fieldValues = $node->getFieldValues();
// Badges sammeln
$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_tree && $value !== '') {
$displayVal = $value;
if ($fieldDef->field_type === 'date' && $value) {
$displayVal = dol_print_date(strtotime($value), 'day');
}
$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;
}
}
}
}
$nodeClass = 'kundenkarte-tree-node';
if (!empty($node->decommissioned)) {
$nodeClass .= ' decommissioned';
}
if ($node->type_can_have_children) {
$nodeClass .= ' node-structure';
} else {
$nodeClass .= ' node-leaf';
}
print '<div class="'.$nodeClass.'">';
print '<div class="kundenkarte-tree-item" data-anlage-id="'.$node->id.'">';
// Toggle
if ($hasChildren) {
print '<span class="kundenkarte-tree-toggle"><i class="fa fa-chevron-down"></i></span>';
} else {
print '<span class="kundenkarte-tree-toggle" style="visibility:hidden;"><i class="fa fa-chevron-down"></i></span>';
}
// Icon
$picto = $node->type_picto ? $node->type_picto : 'fa-wrench';
print '<span class="kundenkarte-tree-icon">'.kundenkarte_render_icon($picto).'</span>';
// Label
$viewUrl = $_SERVER['PHP_SELF'].'?system='.$systemId.'&action=view&anlage_id='.$node->id;
print '<span class="kundenkarte-tree-label">'.dol_escape_htmltag($node->label);
if (!empty($treeInfoParentheses)) {
$infoValues = array();
foreach ($treeInfoParentheses as $info) {
$infoValues[] = dol_escape_htmltag($info['value']);
}
print ' <span class="kundenkarte-tree-label-info">('.implode(', ', $infoValues).')</span>';
}
print '</span>';
// Ausgebaut-Badge
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 ' <span class="badge-decommissioned"><i class="fa fa-power-off"></i> '.$decommText.'</span>';
}
// Produkt-Badge
if (!empty($node->fk_product) && !empty($node->product_ref)) {
print ' <span class="badge badge-secondary"><i class="fa fa-cube"></i> '.dol_escape_htmltag($node->product_ref).'</span>';
}
// Spacer
print '<span class="kundenkarte-tree-spacer"></span>';
// Badges
$defaultBadgeColor = getDolGlobalString('KUNDENKARTE_TREE_BADGE_COLOR', '#2a4a5e');
if (!empty($treeInfoBadges)) {
print '<span class="kundenkarte-tree-badges">';
foreach ($treeInfoBadges as $info) {
$badgeIcon = kundenkarte_get_field_icon($info['code'], $info['type']);
$fieldBadgeColor = !empty($info['color']) ? $info['color'] : $defaultBadgeColor;
print '<span class="kundenkarte-tree-badge" title="'.dol_escape_htmltag($info['label']).'" style="background:linear-gradient(135deg, '.$fieldBadgeColor.' 0%, '.kundenkarte_adjust_color($fieldBadgeColor, -20).' 100%);">';
print '<i class="fa '.$badgeIcon.'"></i> '.dol_escape_htmltag($info['value']);
print '</span>';
}
print '</span>';
}
// Typ-Badge
if ($node->type_short || $node->type_label) {
$typeDisplay = $node->type_short ? $node->type_short : $node->type_label;
print '<span class="kundenkarte-tree-type badge badge-secondary">'.dol_escape_htmltag($typeDisplay).'</span>';
}
// Aktionen
print '<span class="kundenkarte-tree-actions">';
print '<a href="'.$viewUrl.'" title="'.$langs->trans('View').'"><i class="fa fa-eye"></i></a>';
if ($canEdit) {
print '<a href="'.$_SERVER['PHP_SELF'].'?system='.$systemId.'&action=create&parent_id='.$node->id.'" title="'.$langs->trans('AddChild').'"><i class="fa fa-plus"></i></a>';
print '<a href="'.$_SERVER['PHP_SELF'].'?system='.$systemId.'&action=edit&anlage_id='.$node->id.'" title="'.$langs->trans('Edit').'"><i class="fa fa-edit"></i></a>';
$decommLabel = $node->decommissioned ? $langs->trans('Recommission') : $langs->trans('Decommission');
$decommIcon = $node->decommissioned ? 'fa-plug' : 'fa-power-off';
print '<a href="#" class="btn-toggle-decommissioned" data-anlage-id="'.$node->id.'" title="'.$decommLabel.'"><i class="fa '.$decommIcon.'"></i></a>';
}
if ($canDelete) {
print '<a href="'.$_SERVER['PHP_SELF'].'?system='.$systemId.'&action=delete&anlage_id='.$node->id.'" title="'.$langs->trans('Delete').'" class="deletelink"><i class="fa fa-trash"></i></a>';
}
print '</span>';
print '</div>';
// Kinder
if ($hasChildren) {
print '<div class="kundenkarte-tree-children">';
werkzeuge_printTree($node->children, $socid, $systemId, $canEdit, $canDelete, $langs, $level + 1, $typeFieldsMap);
print '</div>';
}
print '</div>';
}
}
/**
* Baum-Options für Select (vereinfacht)
*/
function werkzeuge_printTreeOptions($nodes, $selected = 0, $excludeId = 0, $prefix = '', $level = 0)
{
foreach ($nodes as $node) {
if ($node->id == $excludeId) continue;
$sel = ($node->id == $selected) ? ' selected' : '';
print '<option value="'.$node->id.'"'.$sel.'>'.$prefix.dol_escape_htmltag($node->label).'</option>';
if (!empty($node->children)) {
werkzeuge_printTreeOptions($node->children, $selected, $excludeId, $prefix.'── ', $level + 1);
}
}
}