feat: Firmen-Werkzeuge, Zubehör-System und Produkt-Zuordnung
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
66abaa088d
commit
a14b33b7c7
9 changed files with 1520 additions and 5 deletions
8
admin/anlage_types.php
Executable file → Normal file
8
admin/anlage_types.php
Executable file → Normal file
|
|
@ -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 '<td><input type="checkbox" name="can_have_equipment" value="1"'.($anlageType->can_have_equipment ? ' checked' : '').'>';
|
||||
print ' <span class="opacitymedium">('.$langs->trans('CanHaveEquipmentHelp').')</span></td></tr>';
|
||||
|
||||
// Hat Zubehör (Zubehör/Ersatzteile zuordnen)
|
||||
print '<tr><td>'.$langs->trans('HasAccessories').'</td>';
|
||||
print '<td><input type="checkbox" name="has_accessories" value="1"'.($anlageType->has_accessories ? ' checked' : '').'>';
|
||||
print ' <span class="opacitymedium">('.$langs->trans('HasAccessoriesHelp').')</span></td></tr>';
|
||||
|
||||
// Allowed parent types - with multi-select UI
|
||||
print '<tr><td>'.$langs->trans('AllowedParentTypes').'</td>';
|
||||
print '<td>';
|
||||
|
|
|
|||
154
ajax/anlage_accessory.php
Normal file
154
ajax/anlage_accessory.php
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Alles Watt lauft
|
||||
*
|
||||
* AJAX-Endpunkt für Anlagen-Zubehör Operationen
|
||||
*/
|
||||
|
||||
if (!defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL', '1');
|
||||
if (!defined('NOREQUIREMENU')) define('NOREQUIREMENU', '1');
|
||||
if (!defined('NOREQUIREHTML')) define('NOREQUIREHTML', '1');
|
||||
if (!defined('NOREQUIREAJAX')) define('NOREQUIREAJAX', '1');
|
||||
|
||||
$res = 0;
|
||||
if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
|
||||
if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
|
||||
if (!$res) die("Include of main fails");
|
||||
|
||||
dol_include_once('/kundenkarte/class/anlageaccessory.class.php');
|
||||
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
|
||||
$langs->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);
|
||||
|
|
@ -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";
|
||||
|
|
|
|||
391
class/anlageaccessory.class.php
Normal file
391
class/anlageaccessory.class.php
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Alles Watt lauft
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class AnlageAccessory
|
||||
* Verwaltet Zubehör/Ersatzteile für Anlagen-Elemente
|
||||
*/
|
||||
class AnlageAccessory extends CommonObject
|
||||
{
|
||||
public $element = 'anlageaccessory';
|
||||
public $table_element = 'kundenkarte_anlage_accessory';
|
||||
|
||||
public $fk_anlage;
|
||||
public $fk_product;
|
||||
public $qty;
|
||||
public $rang;
|
||||
public $note;
|
||||
|
||||
public $date_creation;
|
||||
public $fk_user_creat;
|
||||
|
||||
// Geladene Objekte (aus JOIN)
|
||||
public $product_ref;
|
||||
public $product_label;
|
||||
public $product_price;
|
||||
public $product_fk_unit;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param DoliDB $db Database handler
|
||||
*/
|
||||
public function __construct($db)
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
7
class/anlagetype.class.php
Executable file → Normal file
7
class/anlagetype.class.php
Executable file → Normal file
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
837
werkzeuge.php
Normal file
837
werkzeuge.php
Normal file
|
|
@ -0,0 +1,837 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Alles Watt lauft
|
||||
*
|
||||
* Firmen-Werkzeuge: Baumansicht für eigene Maschinen und Werkzeuge
|
||||
* System-Filter fix auf WERKZEUG
|
||||
*/
|
||||
|
||||
// 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/lib/company.lib.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.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');
|
||||
$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 '<div class="fichecenter">';
|
||||
|
||||
// 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 '<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
|
||||
if ($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="background:#f0f0f0;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'].'?action=edit&anlage_id='.$anlageId.'">'.$langs->trans('Modify').'</a>';
|
||||
}
|
||||
if ($permissiontodelete) {
|
||||
print '<a class="butActionDelete" href="'.$_SERVER['PHP_SELF'].'?action=delete&anlage_id='.$anlageId.'">'.$langs->trans('Delete').'</a>';
|
||||
}
|
||||
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'">'.$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'].'">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="hidden" name="action" value="'.$formAction.'">';
|
||||
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.'"'.$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
|
||||
$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><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'].'">'.$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'].'?action=create">';
|
||||
print '<i class="fa fa-plus"></i> '.$langs->trans('AddElement');
|
||||
print '</a>';
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
// Steuerungs-Buttons
|
||||
print '<div class="kundenkarte-tree-controls" style="margin-bottom:10px;">';
|
||||
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>';
|
||||
print '<button type="button" class="button small" id="btn-toggle-decommissioned" title="'.$langs->trans('ShowDecommissioned').'">';
|
||||
print '<i class="fa fa-eye-slash"></i> <span>'.$langs->trans('Decommissioned').'</span>';
|
||||
print '</button>';
|
||||
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" 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'].'?action=create"><i class="fa fa-plus"></i> '.$langs->trans('AddFirstTool').'</a>';
|
||||
}
|
||||
print '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
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-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'].'?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'].'?action=create&parent_id='.$node->id.'" title="'.$langs->trans('AddChild').'"><i class="fa fa-plus"></i></a>';
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?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'].'?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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue