From a14b33b7c72c7fd7df8312e1fb9ad842e180e8dc Mon Sep 17 00:00:00 2001 From: data Date: Tue, 3 Mar 2026 20:23:29 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20Firmen-Werkzeuge,=20Zubeh=C3=B6r-System?= =?UTF-8?q?=20und=20Produkt-Zuordnung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Neue Seite werkzeuge.php mit Baumansicht für Firmen-Maschinen/Werkzeuge - Menüpunkt "Firmen-Werkzeuge" unter Start-Menü - Neue Klasse AnlageAccessory für Zubehör/Ersatzteile pro Anlage - AJAX-Endpunkt ajax/anlage_accessory.php (CRUD + Lieferantenbestellung) - DB: fk_product auf Anlage, has_accessories auf AnlageType, Zubehör-Tabelle - Neues System WERKZEUG in Systemkategorien - Admin: Checkbox "Hat Zubehör" im Typ-Editor - Produkt-Autocomplete, Zubehör-Liste mit Bestellfunktion (CommandeFournisseur) - Produkt-JOIN in fetchChildren für product_ref im Baum - Übersetzungen de_DE + en_US Co-Authored-By: Claude Opus 4.6 --- admin/anlage_types.php | 8 + ajax/anlage_accessory.php | 154 +++++ class/anlage.class.php | 22 +- class/anlageaccessory.class.php | 391 ++++++++++++ class/anlagetype.class.php | 7 +- core/modules/modKundenKarte.class.php | 74 ++- langs/de_DE/kundenkarte.lang | 16 + langs/en_US/kundenkarte.lang | 16 + werkzeuge.php | 837 ++++++++++++++++++++++++++ 9 files changed, 1520 insertions(+), 5 deletions(-) mode change 100755 => 100644 admin/anlage_types.php create mode 100644 ajax/anlage_accessory.php create mode 100644 class/anlageaccessory.class.php mode change 100755 => 100644 class/anlagetype.class.php create mode 100644 werkzeuge.php diff --git a/admin/anlage_types.php b/admin/anlage_types.php old mode 100755 new mode 100644 index 10e727f..42bfccc --- a/admin/anlage_types.php +++ b/admin/anlage_types.php @@ -63,6 +63,7 @@ if ($action == 'add') { $anlageType->can_be_nested = GETPOSTINT('can_be_nested'); $anlageType->allowed_parent_types = preg_replace('/[^A-Z0-9_,]/i', '', GETPOST('allowed_parent_types', 'nohtml')); $anlageType->can_have_equipment = GETPOSTINT('can_have_equipment'); + $anlageType->has_accessories = GETPOSTINT('has_accessories'); $anlageType->picto = GETPOST('picto', 'alphanohtml'); $anlageType->color = GETPOST('color', 'alphanohtml'); $anlageType->position = GETPOSTINT('position'); @@ -113,6 +114,7 @@ if ($action == 'update') { $anlageType->can_be_nested = GETPOSTINT('can_be_nested'); $anlageType->allowed_parent_types = preg_replace('/[^A-Z0-9_,]/i', '', GETPOST('allowed_parent_types', 'nohtml')); $anlageType->can_have_equipment = GETPOSTINT('can_have_equipment'); + $anlageType->has_accessories = GETPOSTINT('has_accessories'); $anlageType->picto = GETPOST('picto', 'alphanohtml'); $anlageType->color = GETPOST('color', 'alphanohtml'); $anlageType->position = GETPOSTINT('position'); @@ -166,6 +168,7 @@ if ($action == 'copy' && $typeId > 0) { $newType->can_be_nested = $sourceType->can_be_nested; $newType->allowed_parent_types = $sourceType->allowed_parent_types; $newType->can_have_equipment = $sourceType->can_have_equipment; + $newType->has_accessories = $sourceType->has_accessories; $newType->picto = $sourceType->picto; $newType->color = $sourceType->color; $newType->position = $sourceType->position + 1; @@ -402,6 +405,11 @@ if (in_array($action, array('create', 'edit'))) { print 'can_have_equipment ? ' checked' : '').'>'; print ' ('.$langs->trans('CanHaveEquipmentHelp').')'; + // Hat Zubehör (Zubehör/Ersatzteile zuordnen) + print ''.$langs->trans('HasAccessories').''; + print 'has_accessories ? ' checked' : '').'>'; + print ' ('.$langs->trans('HasAccessoriesHelp').')'; + // Allowed parent types - with multi-select UI print ''.$langs->trans('AllowedParentTypes').''; print ''; diff --git a/ajax/anlage_accessory.php b/ajax/anlage_accessory.php new file mode 100644 index 0000000..a30459d --- /dev/null +++ b/ajax/anlage_accessory.php @@ -0,0 +1,154 @@ +loadLangs(array('kundenkarte@kundenkarte')); + +$action = GETPOST('action', 'aZ09'); + +$response = array('success' => false, 'error' => ''); + +// Berechtigungsprüfung +if (!$user->hasRight('kundenkarte', 'read')) { + $response['error'] = $langs->trans('ErrorPermissionDenied'); + echo json_encode($response); + exit; +} + +$accessory = new AnlageAccessory($db); + +switch ($action) { + case 'list': + // Alle Zubehörteile einer Anlage laden + $anlageId = GETPOSTINT('fk_anlage'); + if ($anlageId > 0) { + $accessories = $accessory->fetchAllByAnlage($anlageId); + $result = array(); + foreach ($accessories as $acc) { + $result[] = array( + 'id' => $acc->id, + 'fk_product' => $acc->fk_product, + 'product_ref' => $acc->product_ref, + 'product_label' => $acc->product_label, + 'product_price' => $acc->product_price, + 'qty' => $acc->qty, + 'note' => $acc->note, + ); + } + $response['success'] = true; + $response['accessories'] = $result; + } else { + $response['error'] = 'Missing fk_anlage'; + } + break; + + case 'add': + // Zubehör hinzufügen + if (!$user->hasRight('kundenkarte', 'write')) { + $response['error'] = $langs->trans('ErrorPermissionDenied'); + break; + } + + $accessory->fk_anlage = GETPOSTINT('fk_anlage'); + $accessory->fk_product = GETPOSTINT('fk_product'); + $accessory->qty = GETPOSTINT('qty') > 0 ? GETPOSTINT('qty') : 1; + $accessory->note = GETPOST('note', 'alphanohtml'); + + $result = $accessory->create($user); + if ($result > 0) { + $response['success'] = true; + $response['id'] = $result; + } else { + $response['error'] = $accessory->error ?: 'Fehler beim Speichern'; + } + break; + + case 'update': + // Zubehör aktualisieren (Menge, Notiz) + if (!$user->hasRight('kundenkarte', 'write')) { + $response['error'] = $langs->trans('ErrorPermissionDenied'); + break; + } + + $id = GETPOSTINT('id'); + if ($id > 0 && $accessory->fetch($id) > 0) { + $accessory->qty = GETPOSTINT('qty') > 0 ? GETPOSTINT('qty') : $accessory->qty; + $accessory->note = GETPOST('note', 'alphanohtml'); + + $result = $accessory->update($user); + if ($result > 0) { + $response['success'] = true; + } else { + $response['error'] = 'Fehler beim Speichern'; + } + } else { + $response['error'] = $langs->trans('ErrorRecordNotFound'); + } + break; + + case 'delete': + // Zubehör löschen + if (!$user->hasRight('kundenkarte', 'delete')) { + $response['error'] = $langs->trans('ErrorPermissionDenied'); + break; + } + + $id = GETPOSTINT('id'); + if ($id > 0 && $accessory->fetch($id) > 0) { + $result = $accessory->delete($user); + if ($result > 0) { + $response['success'] = true; + } else { + $response['error'] = 'Fehler beim Löschen'; + } + } else { + $response['error'] = $langs->trans('ErrorRecordNotFound'); + } + break; + + case 'order': + // Lieferantenbestellung aus Zubehör erstellen + if (!$user->hasRight('kundenkarte', 'write')) { + $response['error'] = $langs->trans('ErrorPermissionDenied'); + break; + } + + $anlageId = GETPOSTINT('fk_anlage'); + $supplierId = GETPOSTINT('supplier_id'); + $idsRaw = GETPOST('ids', 'array'); + + if ($anlageId > 0 && $supplierId > 0 && !empty($idsRaw)) { + $ids = array_map('intval', $idsRaw); + $result = $accessory->generateSupplierOrder($user, $supplierId, $anlageId, $ids); + if ($result > 0) { + $response['success'] = true; + $response['order_id'] = $result; + } else { + $response['error'] = $accessory->error ?: 'Fehler beim Erstellen der Bestellung'; + } + } else { + $response['error'] = 'Fehlende Parameter (Anlage, Lieferant, IDs)'; + } + break; + + default: + $response['error'] = 'Unknown action'; +} + +echo json_encode($response); diff --git a/class/anlage.class.php b/class/anlage.class.php index cac7377..f3c409e 100755 --- a/class/anlage.class.php +++ b/class/anlage.class.php @@ -24,6 +24,7 @@ class Anlage extends CommonObject public $fk_parent; public $fk_system; public $fk_building_node; + public $fk_product; public $manufacturer; public $model; @@ -96,7 +97,7 @@ class Anlage extends CommonObject $this->db->begin(); $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." ("; - $sql .= "entity, ref, label, fk_soc, fk_contact, fk_anlage_type, fk_parent, fk_system,"; + $sql .= "entity, ref, label, fk_soc, fk_contact, fk_anlage_type, fk_parent, fk_system, fk_product,"; $sql .= " manufacturer, model, serial_number, power_rating, field_values,"; $sql .= " location, installation_date, warranty_until,"; $sql .= " rang, level, note_private, note_public, status,"; @@ -110,6 +111,7 @@ class Anlage extends CommonObject $sql .= ", ".((int) $this->fk_anlage_type); $sql .= ", ".((int) ($this->fk_parent > 0 ? $this->fk_parent : 0)); $sql .= ", ".((int) $this->fk_system); + $sql .= ", ".($this->fk_product > 0 ? (int) $this->fk_product : "NULL"); $sql .= ", ".($this->manufacturer ? "'".$this->db->escape($this->manufacturer)."'" : "NULL"); $sql .= ", ".($this->model ? "'".$this->db->escape($this->model)."'" : "NULL"); $sql .= ", ".($this->serial_number ? "'".$this->db->escape($this->serial_number)."'" : "NULL"); @@ -203,6 +205,7 @@ class Anlage extends CommonObject $this->fk_parent = $obj->fk_parent; $this->fk_system = $obj->fk_system; $this->fk_building_node = isset($obj->fk_building_node) ? (int) $obj->fk_building_node : 0; + $this->fk_product = isset($obj->fk_product) ? (int) $obj->fk_product : null; $this->manufacturer = $obj->manufacturer; $this->model = $obj->model; @@ -231,8 +234,14 @@ class Anlage extends CommonObject $this->type_label = $obj->type_label; $this->type_short = $obj->type_short; $this->type_picto = $obj->type_picto; + $this->type_color = isset($obj->type_color) ? $obj->type_color : ''; $this->type_can_have_children = isset($obj->type_can_have_children) ? (int) $obj->type_can_have_children : 0; $this->type_can_have_equipment = isset($obj->type_can_have_equipment) ? (int) $obj->type_can_have_equipment : 0; + $this->type_has_accessories = isset($obj->type_has_accessories) ? (int) $obj->type_has_accessories : 0; + + // Produkt-Info (aus JOIN) + $this->product_ref = isset($obj->product_ref) ? $obj->product_ref : ''; + $this->product_label = isset($obj->product_label) ? $obj->product_label : ''; // System info $this->system_label = $obj->system_label; @@ -291,6 +300,7 @@ class Anlage extends CommonObject $sql .= ", note_private = ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL"); $sql .= ", note_public = ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "NULL"); $sql .= ", status = ".((int) $this->status); + $sql .= ", fk_product = ".($this->fk_product > 0 ? (int) $this->fk_product : "NULL"); $sql .= ", decommissioned = ".((int) $this->decommissioned); $sql .= ", date_decommissioned = ".($this->date_decommissioned ? "'".$this->db->escape($this->date_decommissioned)."'" : "NULL"); $sql .= ", fk_user_modif = ".((int) $user->id); @@ -407,9 +417,11 @@ class Anlage extends CommonObject $results = array(); - $sql = "SELECT a.*, t.label as type_label, t.label_short as type_short, t.picto as type_picto,"; + $sql = "SELECT a.*, t.label as type_label, t.label_short as type_short, t.picto as type_picto, t.color as type_color,"; $sql .= " t.can_have_children as type_can_have_children, t.can_have_equipment as type_can_have_equipment,"; + $sql .= " t.has_accessories as type_has_accessories,"; $sql .= " s.label as system_label, s.code as system_code,"; + $sql .= " p.ref as product_ref, p.label as product_label,"; // Count images $sql .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_files f WHERE f.fk_anlage = a.rowid AND f.file_type = 'image') as image_count,"; // Count documents (pdf + document) @@ -417,6 +429,7 @@ class Anlage extends CommonObject $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." 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."product as p ON a.fk_product = p.rowid"; $sql .= " WHERE a.fk_parent = ".((int) $parentId); $sql .= " AND a.entity = ".((int) $conf->entity); $sql .= " AND a.status = 1"; @@ -717,9 +730,11 @@ class Anlage extends CommonObject $results = array(); - $sql = "SELECT a.*, t.label as type_label, t.label_short as type_short, t.picto as type_picto,"; + $sql = "SELECT a.*, t.label as type_label, t.label_short as type_short, t.picto as type_picto, t.color as type_color,"; $sql .= " t.can_have_children as type_can_have_children, t.can_have_equipment as type_can_have_equipment,"; + $sql .= " t.has_accessories as type_has_accessories,"; $sql .= " s.label as system_label, s.code as system_code,"; + $sql .= " p.ref as product_ref, p.label as product_label,"; // Count images $sql .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_files f WHERE f.fk_anlage = a.rowid AND f.file_type = 'image') as image_count,"; // Count documents (pdf + document) @@ -727,6 +742,7 @@ class Anlage extends CommonObject $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." 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."product as p ON a.fk_product = p.rowid"; $sql .= " WHERE a.fk_parent = ".((int) $parentId); $sql .= " AND a.entity = ".((int) $conf->entity); $sql .= " AND a.status = 1"; diff --git a/class/anlageaccessory.class.php b/class/anlageaccessory.class.php new file mode 100644 index 0000000..9b2f0da --- /dev/null +++ b/class/anlageaccessory.class.php @@ -0,0 +1,391 @@ +db = $db; + } + + /** + * Zubehör erstellen + * + * @param User $user Benutzer + * @return int <0 bei Fehler, ID bei Erfolg + */ + public function create($user) + { + $error = 0; + $now = dol_now(); + + if (empty($this->fk_anlage) || empty($this->fk_product)) { + $this->error = 'ErrorMissingParameters'; + return -1; + } + + // Prüfen ob bereits vorhanden + if ($this->alreadyExists($this->fk_anlage, $this->fk_product)) { + $this->error = 'ErrorAccessoryAlreadyExists'; + return -2; + } + + $this->db->begin(); + + $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." ("; + $sql .= "fk_anlage, fk_product, qty, rang, note,"; + $sql .= " date_creation, fk_user_creat"; + $sql .= ") VALUES ("; + $sql .= ((int) $this->fk_anlage); + $sql .= ", ".((int) $this->fk_product); + $sql .= ", ".((float) ($this->qty > 0 ? $this->qty : 1)); + $sql .= ", ".((int) $this->rang); + $sql .= ", ".($this->note ? "'".$this->db->escape($this->note)."'" : "NULL"); + $sql .= ", '".$this->db->idate($now)."'"; + $sql .= ", ".((int) $user->id); + $sql .= ")"; + + $resql = $this->db->query($sql); + if (!$resql) { + $error++; + $this->errors[] = "Error ".$this->db->lasterror(); + } + + if (!$error) { + $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element); + } + + if ($error) { + $this->db->rollback(); + return -1 * $error; + } else { + $this->db->commit(); + return $this->id; + } + } + + /** + * Zubehör laden + * + * @param int $id ID + * @return int <0 bei Fehler, 0 nicht gefunden, >0 OK + */ + public function fetch($id) + { + $sql = "SELECT a.*, p.ref as product_ref, p.label as product_label, p.price as product_price, p.fk_unit as product_fk_unit"; + $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as a"; + $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON a.fk_product = p.rowid"; + $sql .= " WHERE a.rowid = ".((int) $id); + + $resql = $this->db->query($sql); + if ($resql) { + if ($this->db->num_rows($resql)) { + $obj = $this->db->fetch_object($resql); + $this->id = $obj->rowid; + $this->fk_anlage = $obj->fk_anlage; + $this->fk_product = $obj->fk_product; + $this->qty = $obj->qty; + $this->rang = $obj->rang; + $this->note = $obj->note; + $this->date_creation = $this->db->jdate($obj->date_creation); + $this->fk_user_creat = $obj->fk_user_creat; + $this->product_ref = $obj->product_ref; + $this->product_label = $obj->product_label; + $this->product_price = $obj->product_price; + $this->product_fk_unit = $obj->product_fk_unit; + $this->db->free($resql); + return 1; + } else { + $this->db->free($resql); + return 0; + } + } else { + $this->error = $this->db->lasterror(); + return -1; + } + } + + /** + * Zubehör aktualisieren + * + * @param User $user Benutzer + * @return int <0 bei Fehler, >0 OK + */ + public function update($user) + { + $error = 0; + + $this->db->begin(); + + $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET"; + $sql .= " qty = ".((float) $this->qty); + $sql .= ", rang = ".((int) $this->rang); + $sql .= ", note = ".($this->note ? "'".$this->db->escape($this->note)."'" : "NULL"); + $sql .= " WHERE rowid = ".((int) $this->id); + + $resql = $this->db->query($sql); + if (!$resql) { + $error++; + $this->errors[] = "Error ".$this->db->lasterror(); + } + + if ($error) { + $this->db->rollback(); + return -1 * $error; + } else { + $this->db->commit(); + return 1; + } + } + + /** + * Zubehör löschen + * + * @param User $user Benutzer + * @return int <0 bei Fehler, >0 OK + */ + public function delete($user) + { + $this->db->begin(); + + $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element; + $sql .= " WHERE rowid = ".((int) $this->id); + + $resql = $this->db->query($sql); + if (!$resql) { + $this->db->rollback(); + $this->error = $this->db->lasterror(); + return -1; + } + + $this->db->commit(); + return 1; + } + + /** + * Alle Zubehörteile einer Anlage laden + * + * @param int $anlageId Anlage-ID + * @return array Array von AnlageAccessory-Objekten + */ + public function fetchAllByAnlage($anlageId) + { + $results = array(); + + $sql = "SELECT a.*, p.ref as product_ref, p.label as product_label, p.price as product_price, p.fk_unit as product_fk_unit"; + $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as a"; + $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON a.fk_product = p.rowid"; + $sql .= " WHERE a.fk_anlage = ".((int) $anlageId); + $sql .= " ORDER BY a.rang ASC, a.rowid ASC"; + + $resql = $this->db->query($sql); + if ($resql) { + while ($obj = $this->db->fetch_object($resql)) { + $acc = new AnlageAccessory($this->db); + $acc->id = $obj->rowid; + $acc->fk_anlage = $obj->fk_anlage; + $acc->fk_product = $obj->fk_product; + $acc->qty = $obj->qty; + $acc->rang = $obj->rang; + $acc->note = $obj->note; + $acc->date_creation = $this->db->jdate($obj->date_creation); + $acc->fk_user_creat = $obj->fk_user_creat; + $acc->product_ref = $obj->product_ref; + $acc->product_label = $obj->product_label; + $acc->product_price = $obj->product_price; + $acc->product_fk_unit = $obj->product_fk_unit; + $results[] = $acc; + } + $this->db->free($resql); + } + + return $results; + } + + /** + * Prüfen ob Produkt bereits als Zubehör zugeordnet ist + * + * @param int $anlageId Anlage-ID + * @param int $productId Produkt-ID + * @return bool true wenn bereits vorhanden + */ + public function alreadyExists($anlageId, $productId) + { + $sql = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX.$this->table_element; + $sql .= " WHERE fk_anlage = ".((int) $anlageId); + $sql .= " AND fk_product = ".((int) $productId); + + $resql = $this->db->query($sql); + if ($resql) { + $obj = $this->db->fetch_object($resql); + return ($obj->cnt > 0); + } + return false; + } + + /** + * Lieferantenbestellung aus ausgewählten Zubehörteilen erstellen + * + * @param User $user Benutzer + * @param int $supplierId Lieferanten-ID (fournisseur) + * @param int $anlageId Anlage-ID + * @param array $selectedIds Array von Accessory-IDs + * @param array $quantities Optional: ID => Menge + * @return int Bestell-ID bei Erfolg, <0 bei Fehler + */ + public function generateSupplierOrder($user, $supplierId, $anlageId, $selectedIds, $quantities = array()) + { + global $conf, $langs, $mysoc; + + require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php'; + require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; + require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; + + if (empty($selectedIds)) { + $this->error = 'NoProductsSelected'; + return -1; + } + + // Lieferant laden + $supplier = new Societe($this->db); + if ($supplier->fetch($supplierId) <= 0) { + $this->error = 'ErrorLoadingSupplier'; + return -1; + } + + // Zubehör der Anlage laden + $accessories = $this->fetchAllByAnlage($anlageId); + if (!is_array($accessories) || empty($accessories)) { + $this->error = 'NoAccessoriesFound'; + return -2; + } + + // Ausgewählte filtern + $toAdd = array(); + foreach ($accessories as $acc) { + if (in_array($acc->id, $selectedIds)) { + $qty = isset($quantities[$acc->id]) ? (float) $quantities[$acc->id] : $acc->qty; + if ($qty > 0) { + $toAdd[] = array( + 'product_id' => $acc->fk_product, + 'qty' => $qty + ); + } + } + } + + if (empty($toAdd)) { + $this->error = 'NoValidProductsToAdd'; + return -2; + } + + // Lieferantenbestellung erstellen + $order = new CommandeFournisseur($this->db); + $order->socid = $supplierId; + $order->date = dol_now(); + $order->note_private = $langs->trans('OrderGeneratedFromAccessories'); + + $this->db->begin(); + + $result = $order->create($user); + if ($result <= 0) { + $this->error = $order->error; + $this->errors = $order->errors; + $this->db->rollback(); + return -3; + } + + // Produkte hinzufügen + foreach ($toAdd as $item) { + $product = new Product($this->db); + $product->fetch($item['product_id']); + + // MwSt-Satz ermitteln (Lieferant = Verkäufer, eigene Firma = Käufer) + $tva_tx = get_default_tva($supplier, $mysoc, $product->id); + $localtax1_tx = get_default_localtax($supplier, $mysoc, 1, $product->id); + $localtax2_tx = get_default_localtax($supplier, $mysoc, 2, $product->id); + + // Lieferantenpreis ermitteln + $fournPrice = $product->price; + $fournPriceId = 0; + $fournRef = ''; + $sqlFourn = "SELECT rowid, price as fourn_price, ref_fourn"; + $sqlFourn .= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price"; + $sqlFourn .= " WHERE fk_product = ".((int) $product->id); + $sqlFourn .= " AND fk_soc = ".((int) $supplierId); + $sqlFourn .= " ORDER BY price ASC LIMIT 1"; + $resFourn = $this->db->query($sqlFourn); + if ($resFourn && $this->db->num_rows($resFourn) > 0) { + $objFourn = $this->db->fetch_object($resFourn); + $fournPrice = $objFourn->fourn_price; + $fournPriceId = $objFourn->rowid; + $fournRef = $objFourn->ref_fourn; + } + + $lineResult = $order->addline( + $product->label, // Beschreibung + $fournPrice, // Preis HT + $item['qty'], // Menge + $tva_tx, // MwSt + $localtax1_tx, // Lokale Steuer 1 + $localtax2_tx, // Lokale Steuer 2 + $product->id, // Produkt-ID + $fournPriceId, // Lieferantenpreis-ID + $fournRef, // Lieferanten-Referenz + 0, // Rabatt + 'HT', // Preis-Basis + 0, // Preis TTC + 0, // Typ (0=Produkt) + 0, // Info bits + false, // notrigger + null, // Startdatum + null, // Enddatum + array(), // Optionen + $product->fk_unit // Einheit + ); + + if ($lineResult < 0) { + $this->error = $order->error; + $this->errors = $order->errors; + $this->db->rollback(); + return -4; + } + } + + $this->db->commit(); + return $order->id; + } +} diff --git a/class/anlagetype.class.php b/class/anlagetype.class.php old mode 100755 new mode 100644 index 07f743e..fd167a2 --- a/class/anlagetype.class.php +++ b/class/anlagetype.class.php @@ -26,6 +26,7 @@ class AnlageType extends CommonObject public $can_be_nested; public $allowed_parent_types; public $can_have_equipment; + public $has_accessories; public $picto; public $color; @@ -73,7 +74,7 @@ class AnlageType extends CommonObject $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." ("; $sql .= "entity, ref, label, label_short, description, fk_system,"; - $sql .= " can_have_children, can_be_nested, allowed_parent_types, can_have_equipment,"; + $sql .= " can_have_children, can_be_nested, allowed_parent_types, can_have_equipment, has_accessories,"; $sql .= " picto, color, is_system, position, active,"; $sql .= " date_creation, fk_user_creat"; $sql .= ") VALUES ("; @@ -87,6 +88,7 @@ class AnlageType extends CommonObject $sql .= ", ".((int) $this->can_be_nested); $sql .= ", ".($this->allowed_parent_types ? "'".$this->db->escape($this->allowed_parent_types)."'" : "NULL"); $sql .= ", ".((int) $this->can_have_equipment); + $sql .= ", ".((int) $this->has_accessories); $sql .= ", ".($this->picto ? "'".$this->db->escape($this->picto)."'" : "NULL"); $sql .= ", ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL"); $sql .= ", 0"; // is_system = 0 for user-created @@ -144,6 +146,7 @@ class AnlageType extends CommonObject $this->can_be_nested = $obj->can_be_nested; $this->allowed_parent_types = $obj->allowed_parent_types; $this->can_have_equipment = $obj->can_have_equipment ?? 0; + $this->has_accessories = $obj->has_accessories ?? 0; $this->picto = $obj->picto; $this->color = $obj->color; $this->is_system = $obj->is_system; @@ -190,6 +193,7 @@ class AnlageType extends CommonObject $sql .= ", can_be_nested = ".((int) $this->can_be_nested); $sql .= ", allowed_parent_types = ".($this->allowed_parent_types ? "'".$this->db->escape($this->allowed_parent_types)."'" : "NULL"); $sql .= ", can_have_equipment = ".((int) $this->can_have_equipment); + $sql .= ", has_accessories = ".((int) $this->has_accessories); $sql .= ", picto = ".($this->picto ? "'".$this->db->escape($this->picto)."'" : "NULL"); $sql .= ", color = ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL"); $sql .= ", position = ".((int) $this->position); @@ -312,6 +316,7 @@ class AnlageType extends CommonObject $type->can_be_nested = $obj->can_be_nested; $type->allowed_parent_types = $obj->allowed_parent_types; $type->can_have_equipment = $obj->can_have_equipment ?? 0; + $type->has_accessories = $obj->has_accessories ?? 0; $type->picto = $obj->picto; $type->is_system = $obj->is_system; $type->position = $obj->position; diff --git a/core/modules/modKundenKarte.class.php b/core/modules/modKundenKarte.class.php index afce6f9..1810463 100755 --- a/core/modules/modKundenKarte.class.php +++ b/core/modules/modKundenKarte.class.php @@ -76,7 +76,7 @@ class modKundenKarte extends DolibarrModules $this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@kundenkarte' // Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z' - $this->version = '8.4'; + $this->version = '8.5'; // Url to the file with your last numberversion of this module //$this->url_last_version = 'http://www.example.com/versionmodule.txt'; @@ -412,6 +412,23 @@ class modKundenKarte extends DolibarrModules 'target' => '', 'user' => 0, ); + // Werkzeuge-Seite unter Start-Menü + $this->menu[$r++] = array( + 'fk_menu' => 'fk_mainmenu=home', + 'type' => 'left', + 'titre' => 'CompanyTools', + 'prefix' => img_picto('', 'fa-wrench', 'class="pictofixedwidth valignmiddle paddingright"'), + 'mainmenu' => 'home', + 'leftmenu' => 'kundenkarte_werkzeuge', + 'url' => '/kundenkarte/werkzeuge.php', + 'langs' => 'kundenkarte@kundenkarte', + 'position' => 100, + 'enabled' => 'isModEnabled("kundenkarte")', + 'perms' => '$user->hasRight("kundenkarte", "read")', + 'target' => '', + 'user' => 0, + ); + /* END MODULEBUILDER LEFTMENU */ @@ -636,6 +653,9 @@ class modKundenKarte extends DolibarrModules // v8.0.0: Ausgebaut-Status für Anlagen $this->migrate_v800_decommissioned(); + + // v8.1.0: Werkzeuge & Zubehör + $this->migrate_v810_werkzeuge(); } /** @@ -950,6 +970,58 @@ class modKundenKarte extends DolibarrModules } } + /** + * Migration v8.1.0: Werkzeuge & Zubehör + * - fk_product auf Anlage (Produkt-Zuordnung) + * - has_accessories auf Anlage-Typ + * - Zubehör-Tabelle + * - WERKZEUG System-Kategorie + */ + private function migrate_v810_werkzeuge() + { + // 1. fk_product auf Anlage + $table = MAIN_DB_PREFIX."kundenkarte_anlage"; + $resql = $this->db->query("SHOW COLUMNS FROM ".$table." LIKE 'fk_product'"); + if (!$resql || $this->db->num_rows($resql) == 0) { + $this->db->query("ALTER TABLE ".$table." ADD COLUMN fk_product integer NULL AFTER fk_building_node"); + $this->db->query("ALTER TABLE ".$table." ADD INDEX idx_anlage_fk_product (fk_product)"); + } + + // 2. has_accessories auf Anlage-Typ + $table = MAIN_DB_PREFIX."kundenkarte_anlage_type"; + $resql = $this->db->query("SHOW COLUMNS FROM ".$table." LIKE 'has_accessories'"); + if (!$resql || $this->db->num_rows($resql) == 0) { + $this->db->query("ALTER TABLE ".$table." ADD COLUMN has_accessories tinyint DEFAULT 0 NOT NULL AFTER can_have_equipment"); + } + + // 3. Zubehör-Tabelle + $table = MAIN_DB_PREFIX."kundenkarte_anlage_accessory"; + $resql = $this->db->query("SHOW TABLES LIKE '".$this->db->escape($table)."'"); + if (!$resql || $this->db->num_rows($resql) == 0) { + $sql = "CREATE TABLE ".$table." ("; + $sql .= " rowid integer AUTO_INCREMENT PRIMARY KEY,"; + $sql .= " fk_anlage integer NOT NULL,"; + $sql .= " fk_product integer NOT NULL,"; + $sql .= " qty double DEFAULT 1,"; + $sql .= " rang integer DEFAULT 0,"; + $sql .= " note varchar(255),"; + $sql .= " date_creation datetime,"; + $sql .= " fk_user_creat integer,"; + $sql .= " UNIQUE KEY uk_anlage_accessory (fk_anlage, fk_product),"; + $sql .= " INDEX idx_accessory_anlage (fk_anlage),"; + $sql .= " CONSTRAINT fk_accessory_anlage FOREIGN KEY (fk_anlage) REFERENCES ".MAIN_DB_PREFIX."kundenkarte_anlage(rowid) ON DELETE CASCADE"; + $sql .= ") ENGINE=InnoDB"; + $this->db->query($sql); + } + + // 4. WERKZEUG System-Kategorie (falls nicht vorhanden) + $sysTable = MAIN_DB_PREFIX."c_kundenkarte_anlage_system"; + $resql = $this->db->query("SELECT rowid FROM ".$sysTable." WHERE code = 'WERKZEUG'"); + if (!$resql || $this->db->num_rows($resql) == 0) { + $this->db->query("INSERT INTO ".$sysTable." (code, label, active, position) VALUES ('WERKZEUG', 'Werkzeuge & Maschinen', 1, 90)"); + } + } + /** * Function called when module is disabled. * Remove from database constants, boxes and permissions from Dolibarr database. diff --git a/langs/de_DE/kundenkarte.lang b/langs/de_DE/kundenkarte.lang index 1505a42..1e2d564 100755 --- a/langs/de_DE/kundenkarte.lang +++ b/langs/de_DE/kundenkarte.lang @@ -561,3 +561,19 @@ Decommissioned = Ausgebaut Decommission = Ausbauen Recommission = Wieder einbauen ShowDecommissioned = Ausgebaute Elemente anzeigen + +# Werkzeuge & Zubehör +CompanyTools = Firmen-Werkzeuge +HasAccessories = Hat Zubehör +HasAccessoriesHelp = Ermöglicht die Zuordnung von Zubehör und Ersatzteilen +Accessories = Zubehör / Ersatzteile +NoAccessories = Kein Zubehör zugeordnet +SearchProduct = Produkt suchen +SelectSupplier = Lieferant auswählen +CreateSupplierOrder = Lieferantenbestellung erstellen +OrderAccessories = Zubehör bestellen +OrderGeneratedFromAccessories = Bestellung aus Anlagen-Zubehör generiert +ConfirmDeleteAccessory = Dieses Zubehör wirklich entfernen? +NoToolsYet = Noch keine Werkzeuge erfasst +AddFirstTool = Erstes Werkzeug hinzufügen +GoToTypeAdmin = Zur Typverwaltung diff --git a/langs/en_US/kundenkarte.lang b/langs/en_US/kundenkarte.lang index eaa1329..cb7baa3 100755 --- a/langs/en_US/kundenkarte.lang +++ b/langs/en_US/kundenkarte.lang @@ -309,3 +309,19 @@ Decommissioned = Decommissioned Decommission = Decommission Recommission = Recommission ShowDecommissioned = Show decommissioned elements + +# Tools & Accessories +CompanyTools = Company Tools +HasAccessories = Has Accessories +HasAccessoriesHelp = Allows assigning accessories and spare parts +Accessories = Accessories / Spare Parts +NoAccessories = No accessories assigned +SearchProduct = Search product +SelectSupplier = Select supplier +CreateSupplierOrder = Create supplier order +OrderAccessories = Order accessories +OrderGeneratedFromAccessories = Order generated from installation accessories +ConfirmDeleteAccessory = Really remove this accessory? +NoToolsYet = No tools registered yet +AddFirstTool = Add first tool +GoToTypeAdmin = Go to type administration diff --git a/werkzeuge.php b/werkzeuge.php new file mode 100644 index 0000000..08779e0 --- /dev/null +++ b/werkzeuge.php @@ -0,0 +1,837 @@ +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'); +$anlageId = GETPOSTINT('anlage_id'); +$parentId = GETPOSTINT('parent_id'); + +// Eigene Firma als Kontext +$socId = $mysoc->id; +if ($socId <= 0) { + setEventMessages('Eigene Firma nicht konfiguriert. Bitte unter Einrichtung → Firma konfigurieren.', null, 'errors'); + llxHeader('', 'Firmen-Werkzeuge'); + llxFooter(); + exit; +} + +// WERKZEUG-System ermitteln +$werkzeugSystemId = 0; +$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system WHERE code = 'WERKZEUG' AND active = 1"; +$resql = $db->query($sql); +if ($resql && $db->num_rows($resql) > 0) { + $obj = $db->fetch_object($resql); + $werkzeugSystemId = $obj->rowid; +} + +if ($werkzeugSystemId <= 0) { + setEventMessages('System WERKZEUG nicht gefunden. Bitte Modul deaktivieren und wieder aktivieren.', null, 'errors'); + llxHeader('', 'Firmen-Werkzeuge'); + llxFooter(); + exit; +} + +$systemId = $werkzeugSystemId; + +// Sicherstellen dass WERKZEUG für eigene Firma aktiviert ist +$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."kundenkarte_societe_system"; +$sql .= " WHERE fk_soc = ".((int) $socId)." AND fk_system = ".((int) $systemId); +$sql .= " AND (fk_contact IS NULL OR fk_contact = 0) AND active = 1"; +$resql = $db->query($sql); +if (!$resql || $db->num_rows($resql) == 0) { + // Automatisch aktivieren + $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) $systemId).", NOW(), ".((int) $user->id).", 1)"; + $db->query($sql); +} + +// Objekte initialisieren +$form = new Form($db); +$anlage = new Anlage($db); +$anlageType = new AnlageType($db); + +/* + * Actions + */ + +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']); + 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']); + 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 '
'; + +// Bestätigungsdialog +if ($action == 'delete') { + print $form->formconfirm( + $_SERVER['PHP_SELF'].'?anlage_id='.$anlageId, + $langs->trans('DeleteElement'), + $langs->trans('ConfirmDeleteElement'), + 'confirm_delete', + '', + 'yes', + 1 + ); +} + +// Typen für WERKZEUG-System laden +$types = $anlageType->fetchAllBySystem($systemId, 1, 1); // excludeGlobal=1, nur WERKZEUG-Typen + +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 '
'; + + if ($action == 'view') { + // Detail-Ansicht + print '

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

'; + + print ''; + + print ''; + print ''; + + // Zugeordnetes Produkt + if ($anlage->fk_product > 0) { + $product = new Product($db); + if ($product->fetch($anlage->fk_product) > 0) { + print ''; + print ''; + } + } + + // Dynamische Felder + $fieldValues = $anlage->getFieldValues(); + $typeFieldsList = $type->fetchFields(); + foreach ($typeFieldsList as $field) { + if ($field->field_type === 'header') { + print ''; + } else { + $value = isset($fieldValues[$field->field_code]) ? $fieldValues[$field->field_code] : ''; + if ($value !== '') { + print ''; + if ($field->field_type === 'date' && $value) { + print ''; + } elseif ($field->field_type === 'checkbox') { + print ''; + } else { + print ''; + } + } + } + } + + if ($anlage->note_private) { + 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('Decommissioned').' '.$langs->trans('Decommissioned').''; + if (!empty($anlage->date_decommissioned)) { + print ' '.dol_print_date(strtotime($anlage->date_decommissioned), 'day'); + } + print '
'; + + // Zubehör-Bereich (nur wenn Typ has_accessories hat) + if ($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').'

'; + } + + // Zubehör hinzufügen + if ($permissiontoadd) { + print '
'; + print '
'; + print ''; + print ''; + print ''; + print ''; + print '
'; + print '
'; + } + + // Bestellfunktion + if ($permissiontoadd && !empty($accessories)) { + print '
'; + print '
'.$langs->trans('OrderAccessories').'
'; + print '
'; + print ''; + print ''; + print '
'; + print '
'; + } + } + + // Aktions-Buttons + print '
'; + if ($permissiontoadd) { + print ''.$langs->trans('Modify').''; + } + if ($permissiontodelete) { + print ''.$langs->trans('Delete').''; + } + print ''.$langs->trans('Back').''; + print '
'; + + } else { + // Erstellen/Bearbeiten-Formular + $isEdit = ($action == 'edit'); + $formAction = $isEdit ? 'update' : 'add'; + + print '
'; + print ''; + print ''; + if ($isEdit) { + print ''; + } + + print ''; + + // Label + $labelValue = $isEdit ? $anlage->label : GETPOST('label'); + print ''; + print ''; + + // Typ + print ''; + print ''; + + // Übergeordnetes Element + $tree = $anlage->fetchTree($socId, $systemId); + $selectedParent = $isEdit ? $anlage->fk_parent : $parentId; + $excludeId = $isEdit ? $anlageId : 0; + print ''; + print ''; + + // Produkt-Zuordnung + $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 ''; + print ''; + + // Dynamische Felder werden per JS geladen + print ''; + + // Notizen + print ''; + $noteValue = $isEdit ? $anlage->note_private : (isset($_POST['note_private']) ? $_POST['note_private'] : ''); + print ''; + + print '
'.$langs->trans('Label').'
'.$langs->trans('Type').''; + if (empty($types)) { + print '
'.$langs->trans('NoTypesDefinedForSystem').' '.$langs->trans('GoToTypeAdmin').''; + } + print '
'.$langs->trans('SelectParent').'
'.$langs->trans('Product').''; + print ''; + print ''; + print '
'.$langs->trans('FieldNotes').'
'; + + print '
'; + print ''; + print ' '.$langs->trans('Cancel').''; + print '
'; + + print '
'; + } + + print '
'; + +} else { + // Baumansicht + + if ($permissiontoadd) { + print '
'; + print ''; + print ' '.$langs->trans('AddElement'); + print ''; + print '
'; + } + + // Steuerungs-Buttons + print '
'; + print ''; + print ''; + print ''; + print ''; + print '
'; + + // 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 '
'; + werkzeuge_printTree($tree, $socId, $systemId, $permissiontoadd, $permissiontodelete, $langs, 0, $typeFieldsMap); + print '
'; + } else { + print '
'; + print '
'; + print $langs->trans('NoToolsYet').'

'; + if ($permissiontoadd) { + print ' '.$langs->trans('AddFirstTool').''; + } + print '
'; + } +} + +print '
'; // fichecenter + +// Tooltip Container +print '
'; + +// JavaScript: Produkt-Autocomplete + Zubehör-AJAX +print ''; + +// CSS für Autocomplete +print ''; + +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 '
'; + print '
'; + + // Toggle + if ($hasChildren) { + print ''; + } else { + print ''; + } + + // Icon + $picto = $node->type_picto ? $node->type_picto : 'fa-wrench'; + print ''.kundenkarte_render_icon($picto).''; + + // Label + $viewUrl = $_SERVER['PHP_SELF'].'?action=view&anlage_id='.$node->id; + 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 + 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.''; + } + + // Produkt-Badge + if (!empty($node->fk_product) && !empty($node->product_ref)) { + print ' '.dol_escape_htmltag($node->product_ref).''; + } + + // Spacer + print ''; + + // Badges + $defaultBadgeColor = getDolGlobalString('KUNDENKARTE_TREE_BADGE_COLOR', '#2a4a5e'); + if (!empty($treeInfoBadges)) { + 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 ''; + } + + // Typ-Badge + if ($node->type_short || $node->type_label) { + $typeDisplay = $node->type_short ? $node->type_short : $node->type_label; + print ''.dol_escape_htmltag($typeDisplay).''; + } + + // Aktionen + print ''; + print ''; + if ($canEdit) { + 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 '
'; + + // Kinder + if ($hasChildren) { + print '
'; + werkzeuge_printTree($node->children, $socid, $systemId, $canEdit, $canDelete, $langs, $level + 1, $typeFieldsMap); + print '
'; + } + + print '
'; + } +} + +/** + * 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 ''; + if (!empty($node->children)) { + werkzeuge_printTreeOptions($node->children, $selected, $excludeId, $prefix.'── ', $level + 1); + } + } +}