Version 3.4.0 - Kategorie-Auswahl, Icons, Sicherheitsfixes
- Kategorie-Select (Gebäude/Standort vs Element/Gerät) beim Erstellen - Select2 mit FontAwesome-Icons und Farbkodierung für Typ-Auswahl - GLOBAL-Gebäudetypen aus Admin Element-Typen ausgeblendet (eigener Tab) - Aktions-Buttons rechtsbündig in der Typ-Verwaltung - Sicherheits-Fixes: Berechtigungsprüfungen, Path-Traversal, Transaktionen - Version auf 3.4.0 aktualisiert Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
37ae45e8f2
commit
f2f3393b12
13 changed files with 285 additions and 24 deletions
|
|
@ -13,6 +13,9 @@ Das KundenKarte-Modul erweitert Dolibarr um zwei wichtige Funktionen fuer Kunden
|
||||||
### Technische Anlagen (Anlagen)
|
### Technische Anlagen (Anlagen)
|
||||||
- Hierarchische Baumstruktur fuer technische Installationen
|
- Hierarchische Baumstruktur fuer technische Installationen
|
||||||
- Flexible Systemkategorien (z.B. Strom, Internet, Kabel, Sat)
|
- Flexible Systemkategorien (z.B. Strom, Internet, Kabel, Sat)
|
||||||
|
- Kategorie-Auswahl beim Erstellen: Gebaeude/Standort oder Element/Geraet
|
||||||
|
- Typ-Select mit FontAwesome-Icons und Farbkodierung (Select2)
|
||||||
|
- Gebaeude-Typen gruppiert nach Ebene (Gebaeude, Etage, Fluegel, Raum, Bereich)
|
||||||
- Konfigurierbare Element-Typen mit individuellen Feldern
|
- Konfigurierbare Element-Typen mit individuellen Feldern
|
||||||
- Datei-Upload mit Bild-Vorschau und PDF-Anzeige
|
- Datei-Upload mit Bild-Vorschau und PDF-Anzeige
|
||||||
- Separate Verwaltung pro Kunde oder pro Kontakt/Adresse (z.B. verschiedene Gebaeude)
|
- Separate Verwaltung pro Kunde oder pro Kontakt/Adresse (z.B. verschiedene Gebaeude)
|
||||||
|
|
@ -63,6 +66,8 @@ Im Admin-Bereich (Home > Setup > Module > KundenKarte) koennen Sie:
|
||||||
- **Anlagen-Systeme**: System-Kategorien anlegen (z.B. Strom, Internet)
|
- **Anlagen-Systeme**: System-Kategorien anlegen (z.B. Strom, Internet)
|
||||||
- **Element-Typen**: Geraetetypen definieren (z.B. Zaehler, Router, Wallbox)
|
- **Element-Typen**: Geraetetypen definieren (z.B. Zaehler, Router, Wallbox)
|
||||||
- **Typ-Felder**: Individuelle Felder pro Geraetetyp konfigurieren
|
- **Typ-Felder**: Individuelle Felder pro Geraetetyp konfigurieren
|
||||||
|
- **Gebaeudetypen**: Strukturtypen (Haus, Etage, Raum etc.) fuer die Gebaeude-Hierarchie
|
||||||
|
- **Kabeltypen**: Verbindungsmedien (NYM, NYY, CAT etc.) mit Spezifikationen
|
||||||
- **Equipment-Typen**: Schaltplan-Komponenten (z.B. Sicherungsautomaten, FI-Schalter) mit Breite (TE), Farbe und Terminal-Konfiguration
|
- **Equipment-Typen**: Schaltplan-Komponenten (z.B. Sicherungsautomaten, FI-Schalter) mit Breite (TE), Farbe und Terminal-Konfiguration
|
||||||
- **Phasenschienen-Typen**: Sammelschienen/Phasenschienen-Vorlagen (L1, L2, L3, N, PE, 3P+N etc.) mit Farben und Linien-Konfiguration
|
- **Phasenschienen-Typen**: Sammelschienen/Phasenschienen-Vorlagen (L1, L2, L3, N, PE, 3P+N etc.) mit Farben und Linien-Konfiguration
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -345,6 +345,7 @@ if (in_array($action, array('create', 'edit'))) {
|
||||||
$selAll = (empty($anlageType->fk_system)) ? ' selected' : '';
|
$selAll = (empty($anlageType->fk_system)) ? ' selected' : '';
|
||||||
print '<option value="0"'.$selAll.'>'.$langs->trans('AllSystems').'</option>';
|
print '<option value="0"'.$selAll.'>'.$langs->trans('AllSystems').'</option>';
|
||||||
foreach ($systems as $sys) {
|
foreach ($systems as $sys) {
|
||||||
|
if ($sys->code === 'GLOBAL') continue; // Gebaeude-Typen haben eigenen Tab
|
||||||
$sel = ($anlageType->fk_system == $sys->rowid) ? ' selected' : '';
|
$sel = ($anlageType->fk_system == $sys->rowid) ? ' selected' : '';
|
||||||
print '<option value="'.$sys->rowid.'"'.$sel.'>'.dol_escape_htmltag($sys->label).'</option>';
|
print '<option value="'.$sys->rowid.'"'.$sel.'>'.dol_escape_htmltag($sys->label).'</option>';
|
||||||
}
|
}
|
||||||
|
|
@ -634,6 +635,7 @@ if (in_array($action, array('create', 'edit'))) {
|
||||||
print '<select name="system" class="flat" onchange="this.form.submit();">';
|
print '<select name="system" class="flat" onchange="this.form.submit();">';
|
||||||
print '<option value="0">'.$langs->trans('All').'</option>';
|
print '<option value="0">'.$langs->trans('All').'</option>';
|
||||||
foreach ($systems as $sys) {
|
foreach ($systems as $sys) {
|
||||||
|
if ($sys->code === 'GLOBAL') continue; // Gebaeude-Typen haben eigenen Tab
|
||||||
$sel = ($systemFilter == $sys->rowid) ? ' selected' : '';
|
$sel = ($systemFilter == $sys->rowid) ? ' selected' : '';
|
||||||
print '<option value="'.$sys->rowid.'"'.$sel.'>'.dol_escape_htmltag($sys->label).'</option>';
|
print '<option value="'.$sys->rowid.'"'.$sel.'>'.dol_escape_htmltag($sys->label).'</option>';
|
||||||
}
|
}
|
||||||
|
|
@ -648,7 +650,7 @@ if (in_array($action, array('create', 'edit'))) {
|
||||||
print '</div>';
|
print '</div>';
|
||||||
|
|
||||||
// List
|
// List
|
||||||
$types = $anlageType->fetchAllBySystem($systemFilter, 0);
|
$types = $anlageType->fetchAllBySystem($systemFilter, 0, 1);
|
||||||
|
|
||||||
print '<table class="noborder centpercent">';
|
print '<table class="noborder centpercent">';
|
||||||
print '<tr class="liste_titre">';
|
print '<tr class="liste_titre">';
|
||||||
|
|
@ -658,7 +660,7 @@ if (in_array($action, array('create', 'edit'))) {
|
||||||
print '<th class="center">'.$langs->trans('CanHaveChildren').'</th>';
|
print '<th class="center">'.$langs->trans('CanHaveChildren').'</th>';
|
||||||
print '<th class="center">'.$langs->trans('Position').'</th>';
|
print '<th class="center">'.$langs->trans('Position').'</th>';
|
||||||
print '<th class="center">'.$langs->trans('Status').'</th>';
|
print '<th class="center">'.$langs->trans('Status').'</th>';
|
||||||
print '<th class="center">'.$langs->trans('Actions').'</th>';
|
print '<th class="right">'.$langs->trans('Actions').'</th>';
|
||||||
print '</tr>';
|
print '</tr>';
|
||||||
|
|
||||||
foreach ($types as $type) {
|
foreach ($types as $type) {
|
||||||
|
|
@ -692,8 +694,8 @@ if (in_array($action, array('create', 'edit'))) {
|
||||||
}
|
}
|
||||||
print '</td>';
|
print '</td>';
|
||||||
|
|
||||||
print '<td class="center nowraponall">';
|
print '<td class="right nowraponall">';
|
||||||
print '<div style="display:inline-flex;gap:8px;">';
|
print '<div style="display:inline-flex;gap:8px;justify-content:flex-end;">';
|
||||||
print '<a class="button buttongen" style="width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center;" href="'.$_SERVER['PHP_SELF'].'?action=edit&typeid='.$type->id.'&system='.$systemFilter.'" title="'.$langs->trans('Edit').'"><i class="fas fa-pen"></i></a>';
|
print '<a class="button buttongen" style="width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center;" href="'.$_SERVER['PHP_SELF'].'?action=edit&typeid='.$type->id.'&system='.$systemFilter.'" title="'.$langs->trans('Edit').'"><i class="fas fa-pen"></i></a>';
|
||||||
print '<a class="button buttongen" style="width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center;" href="'.$_SERVER['PHP_SELF'].'?action=copy&typeid='.$type->id.'&system='.$systemFilter.'&token='.newToken().'" title="'.$langs->trans('Copy').'"><i class="fas fa-copy"></i></a>';
|
print '<a class="button buttongen" style="width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center;" href="'.$_SERVER['PHP_SELF'].'?action=copy&typeid='.$type->id.'&system='.$systemFilter.'&token='.newToken().'" title="'.$langs->trans('Copy').'"><i class="fas fa-copy"></i></a>';
|
||||||
if (!$type->is_system) {
|
if (!$type->is_system) {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,13 @@ dol_include_once('/kundenkarte/class/anlage.class.php');
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// Berechtigungsprüfung
|
||||||
|
if (!$user->hasRight('kundenkarte', 'read')) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['error' => 'Permission denied']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$anlageId = GETPOSTINT('anlage_id');
|
$anlageId = GETPOSTINT('anlage_id');
|
||||||
|
|
||||||
if ($anlageId <= 0) {
|
if ($anlageId <= 0) {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,13 @@ dol_include_once('/kundenkarte/class/anlage.class.php');
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// Berechtigungsprüfung
|
||||||
|
if (!$user->hasRight('kundenkarte', 'read')) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['error' => 'Permission denied']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$anlageId = GETPOSTINT('anlage_id');
|
$anlageId = GETPOSTINT('anlage_id');
|
||||||
|
|
||||||
if ($anlageId <= 0) {
|
if ($anlageId <= 0) {
|
||||||
|
|
|
||||||
3
ajax/equipment_type_block_image.php
Normal file → Executable file
3
ajax/equipment_type_block_image.php
Normal file → Executable file
|
|
@ -148,6 +148,9 @@ switch ($action) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Path-Traversal-Schutz: nur Dateiname ohne Verzeichnisanteile
|
||||||
|
$selectedImage = basename($selectedImage);
|
||||||
|
|
||||||
// Validate that the image exists
|
// Validate that the image exists
|
||||||
$imagePath = $uploadDir . $selectedImage;
|
$imagePath = $uploadDir . $selectedImage;
|
||||||
if (!file_exists($imagePath)) {
|
if (!file_exists($imagePath)) {
|
||||||
|
|
|
||||||
64
class/anlageconnection.class.php
Normal file → Executable file
64
class/anlageconnection.class.php
Normal file → Executable file
|
|
@ -58,6 +58,17 @@ class AnlageConnection extends CommonObject
|
||||||
{
|
{
|
||||||
global $conf;
|
global $conf;
|
||||||
|
|
||||||
|
$error = 0;
|
||||||
|
$now = dol_now();
|
||||||
|
|
||||||
|
$this->db->begin();
|
||||||
|
|
||||||
|
// installation_date als DATE-Feld (YYYY-MM-DD String) sicher escapen
|
||||||
|
$installDateSQL = "NULL";
|
||||||
|
if ($this->installation_date) {
|
||||||
|
$installDateSQL = "'".$this->db->escape($this->installation_date)."'";
|
||||||
|
}
|
||||||
|
|
||||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
||||||
$sql .= "entity, fk_source, fk_target, label,";
|
$sql .= "entity, fk_source, fk_target, label,";
|
||||||
$sql .= "fk_medium_type, medium_type_text, medium_spec, medium_length, medium_color,";
|
$sql .= "fk_medium_type, medium_type_text, medium_spec, medium_length, medium_color,";
|
||||||
|
|
@ -74,21 +85,28 @@ class AnlageConnection extends CommonObject
|
||||||
$sql .= ", ".($this->medium_length ? "'".$this->db->escape($this->medium_length)."'" : "NULL");
|
$sql .= ", ".($this->medium_length ? "'".$this->db->escape($this->medium_length)."'" : "NULL");
|
||||||
$sql .= ", ".($this->medium_color ? "'".$this->db->escape($this->medium_color)."'" : "NULL");
|
$sql .= ", ".($this->medium_color ? "'".$this->db->escape($this->medium_color)."'" : "NULL");
|
||||||
$sql .= ", ".($this->route_description ? "'".$this->db->escape($this->route_description)."'" : "NULL");
|
$sql .= ", ".($this->route_description ? "'".$this->db->escape($this->route_description)."'" : "NULL");
|
||||||
$sql .= ", ".($this->installation_date ? "'".$this->db->escape($this->installation_date)."'" : "NULL");
|
$sql .= ", ".$installDateSQL;
|
||||||
$sql .= ", ".(int)($this->status ?: 1);
|
$sql .= ", ".(int)($this->status ?: 1);
|
||||||
$sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL");
|
$sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL");
|
||||||
$sql .= ", ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "NULL");
|
$sql .= ", ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "NULL");
|
||||||
$sql .= ", NOW()";
|
$sql .= ", '".$this->db->idate($now)."'";
|
||||||
$sql .= ", ".(int)$user->id;
|
$sql .= ", ".(int)$user->id;
|
||||||
$sql .= ")";
|
$sql .= ")";
|
||||||
|
|
||||||
$resql = $this->db->query($sql);
|
$resql = $this->db->query($sql);
|
||||||
if ($resql) {
|
if ($resql) {
|
||||||
$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
|
$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
|
||||||
return $this->id;
|
|
||||||
} else {
|
} else {
|
||||||
|
$error++;
|
||||||
$this->error = $this->db->lasterror();
|
$this->error = $this->db->lasterror();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($error) {
|
||||||
|
$this->db->rollback();
|
||||||
return -1;
|
return -1;
|
||||||
|
} else {
|
||||||
|
$this->db->commit();
|
||||||
|
return $this->id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,6 +175,16 @@ class AnlageConnection extends CommonObject
|
||||||
*/
|
*/
|
||||||
public function update($user)
|
public function update($user)
|
||||||
{
|
{
|
||||||
|
$error = 0;
|
||||||
|
|
||||||
|
$this->db->begin();
|
||||||
|
|
||||||
|
// installation_date als DATE-Feld (YYYY-MM-DD String) sicher escapen
|
||||||
|
$installDateSQL = "NULL";
|
||||||
|
if ($this->installation_date) {
|
||||||
|
$installDateSQL = "'".$this->db->escape($this->installation_date)."'";
|
||||||
|
}
|
||||||
|
|
||||||
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
|
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
|
||||||
$sql .= " fk_source = ".(int)$this->fk_source;
|
$sql .= " fk_source = ".(int)$this->fk_source;
|
||||||
$sql .= ", fk_target = ".(int)$this->fk_target;
|
$sql .= ", fk_target = ".(int)$this->fk_target;
|
||||||
|
|
@ -167,7 +195,7 @@ class AnlageConnection extends CommonObject
|
||||||
$sql .= ", medium_length = ".($this->medium_length ? "'".$this->db->escape($this->medium_length)."'" : "NULL");
|
$sql .= ", medium_length = ".($this->medium_length ? "'".$this->db->escape($this->medium_length)."'" : "NULL");
|
||||||
$sql .= ", medium_color = ".($this->medium_color ? "'".$this->db->escape($this->medium_color)."'" : "NULL");
|
$sql .= ", medium_color = ".($this->medium_color ? "'".$this->db->escape($this->medium_color)."'" : "NULL");
|
||||||
$sql .= ", route_description = ".($this->route_description ? "'".$this->db->escape($this->route_description)."'" : "NULL");
|
$sql .= ", route_description = ".($this->route_description ? "'".$this->db->escape($this->route_description)."'" : "NULL");
|
||||||
$sql .= ", installation_date = ".($this->installation_date ? "'".$this->db->escape($this->installation_date)."'" : "NULL");
|
$sql .= ", installation_date = ".$installDateSQL;
|
||||||
$sql .= ", status = ".(int)$this->status;
|
$sql .= ", status = ".(int)$this->status;
|
||||||
$sql .= ", note_private = ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL");
|
$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 .= ", note_public = ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "NULL");
|
||||||
|
|
@ -175,11 +203,17 @@ class AnlageConnection extends CommonObject
|
||||||
$sql .= " WHERE rowid = ".(int)$this->id;
|
$sql .= " WHERE rowid = ".(int)$this->id;
|
||||||
|
|
||||||
$resql = $this->db->query($sql);
|
$resql = $this->db->query($sql);
|
||||||
if ($resql) {
|
if (!$resql) {
|
||||||
return 1;
|
$error++;
|
||||||
} else {
|
|
||||||
$this->error = $this->db->lasterror();
|
$this->error = $this->db->lasterror();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($error) {
|
||||||
|
$this->db->rollback();
|
||||||
return -1;
|
return -1;
|
||||||
|
} else {
|
||||||
|
$this->db->commit();
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,15 +225,25 @@ class AnlageConnection extends CommonObject
|
||||||
*/
|
*/
|
||||||
public function delete($user)
|
public function delete($user)
|
||||||
{
|
{
|
||||||
|
$error = 0;
|
||||||
|
|
||||||
|
$this->db->begin();
|
||||||
|
|
||||||
$sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element;
|
$sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element;
|
||||||
$sql .= " WHERE rowid = ".(int)$this->id;
|
$sql .= " WHERE rowid = ".(int)$this->id;
|
||||||
|
|
||||||
$resql = $this->db->query($sql);
|
$resql = $this->db->query($sql);
|
||||||
if ($resql) {
|
if (!$resql) {
|
||||||
return 1;
|
$error++;
|
||||||
} else {
|
|
||||||
$this->error = $this->db->lasterror();
|
$this->error = $this->db->lasterror();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($error) {
|
||||||
|
$this->db->rollback();
|
||||||
return -1;
|
return -1;
|
||||||
|
} else {
|
||||||
|
$this->db->commit();
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -269,9 +269,10 @@ class AnlageType extends CommonObject
|
||||||
*
|
*
|
||||||
* @param int $systemId System ID (0 = all)
|
* @param int $systemId System ID (0 = all)
|
||||||
* @param int $activeOnly Only active types
|
* @param int $activeOnly Only active types
|
||||||
|
* @param int $excludeGlobal 1 = GLOBAL-Typen ausschliessen (fuer Admin-Ansicht)
|
||||||
* @return array Array of AnlageType objects
|
* @return array Array of AnlageType objects
|
||||||
*/
|
*/
|
||||||
public function fetchAllBySystem($systemId = 0, $activeOnly = 1)
|
public function fetchAllBySystem($systemId = 0, $activeOnly = 1, $excludeGlobal = 0)
|
||||||
{
|
{
|
||||||
$results = array();
|
$results = array();
|
||||||
|
|
||||||
|
|
@ -279,9 +280,18 @@ class AnlageType extends CommonObject
|
||||||
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as t";
|
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as t";
|
||||||
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system as s ON t.fk_system = s.rowid";
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system as s ON t.fk_system = s.rowid";
|
||||||
$sql .= " WHERE 1 = 1";
|
$sql .= " WHERE 1 = 1";
|
||||||
|
if ($excludeGlobal) {
|
||||||
|
// GLOBAL-Typen (Gebaeude) ausschliessen (Admin-Ansicht)
|
||||||
|
$sql .= " AND (s.code IS NULL OR s.code != 'GLOBAL')";
|
||||||
|
}
|
||||||
if ($systemId > 0) {
|
if ($systemId > 0) {
|
||||||
// Show types for this system AND GLOBAL types (types from GLOBAL system are available everywhere)
|
if (!$excludeGlobal) {
|
||||||
|
// Typen dieses Systems UND GLOBAL-Typen (fuer Tabs-Ansicht)
|
||||||
$sql .= " AND (t.fk_system = ".((int) $systemId)." OR s.code = 'GLOBAL')";
|
$sql .= " AND (t.fk_system = ".((int) $systemId)." OR s.code = 'GLOBAL')";
|
||||||
|
} else {
|
||||||
|
// Nur Typen dieses Systems (fuer Admin-Ansicht)
|
||||||
|
$sql .= " AND t.fk_system = ".((int) $systemId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ($activeOnly) {
|
if ($activeOnly) {
|
||||||
$sql .= " AND t.active = 1";
|
$sql .= " AND t.active = 1";
|
||||||
|
|
|
||||||
3
class/buildingtype.class.php
Normal file → Executable file
3
class/buildingtype.class.php
Normal file → Executable file
|
|
@ -63,6 +63,7 @@ class BuildingType extends CommonObject
|
||||||
{
|
{
|
||||||
global $conf;
|
global $conf;
|
||||||
|
|
||||||
|
$now = dol_now();
|
||||||
$this->ref = trim($this->ref);
|
$this->ref = trim($this->ref);
|
||||||
$this->label = trim($this->label);
|
$this->label = trim($this->label);
|
||||||
|
|
||||||
|
|
@ -85,7 +86,7 @@ class BuildingType extends CommonObject
|
||||||
$sql .= ", ".(int)($this->can_have_children !== null ? $this->can_have_children : 1);
|
$sql .= ", ".(int)($this->can_have_children !== null ? $this->can_have_children : 1);
|
||||||
$sql .= ", ".(int)($this->position ?: 0);
|
$sql .= ", ".(int)($this->position ?: 0);
|
||||||
$sql .= ", ".(int)($this->active !== null ? $this->active : 1);
|
$sql .= ", ".(int)($this->active !== null ? $this->active : 1);
|
||||||
$sql .= ", NOW()";
|
$sql .= ", '".$this->db->idate($now)."'";
|
||||||
$sql .= ", ".(int)$user->id;
|
$sql .= ", ".(int)$user->id;
|
||||||
$sql .= ")";
|
$sql .= ")";
|
||||||
|
|
||||||
|
|
|
||||||
4
class/terminalbridge.class.php
Normal file → Executable file
4
class/terminalbridge.class.php
Normal file → Executable file
|
|
@ -48,6 +48,8 @@ class TerminalBridge extends CommonObject
|
||||||
*/
|
*/
|
||||||
public function create($user)
|
public function create($user)
|
||||||
{
|
{
|
||||||
|
global $conf;
|
||||||
|
|
||||||
$error = 0;
|
$error = 0;
|
||||||
$now = dol_now();
|
$now = dol_now();
|
||||||
|
|
||||||
|
|
@ -63,7 +65,7 @@ class TerminalBridge extends CommonObject
|
||||||
$sql .= " terminal_side, terminal_row, color, bridge_type, label,";
|
$sql .= " terminal_side, terminal_row, color, bridge_type, label,";
|
||||||
$sql .= " status, date_creation, fk_user_creat";
|
$sql .= " status, date_creation, fk_user_creat";
|
||||||
$sql .= ") VALUES (";
|
$sql .= ") VALUES (";
|
||||||
$sql .= ((int) $this->entity ?: 1);
|
$sql .= ((int) ($conf->entity));
|
||||||
$sql .= ", ".((int) $this->fk_anlage);
|
$sql .= ", ".((int) $this->fk_anlage);
|
||||||
$sql .= ", ".((int) $this->fk_carrier);
|
$sql .= ", ".((int) $this->fk_carrier);
|
||||||
$sql .= ", ".((int) $this->start_te);
|
$sql .= ", ".((int) $this->start_te);
|
||||||
|
|
|
||||||
|
|
@ -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'
|
$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'
|
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
|
||||||
$this->version = '3.3.2';
|
$this->version = '3.4.0';
|
||||||
// Url to the file with your last numberversion of this module
|
// Url to the file with your last numberversion of this module
|
||||||
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,11 @@ NoInstallations = Keine Installationen vorhanden
|
||||||
SelectSystem = System auswaehlen
|
SelectSystem = System auswaehlen
|
||||||
AllSystems = Alle Systeme
|
AllSystems = Alle Systeme
|
||||||
AllSystemsHint = Leer lassen fuer alle Systeme
|
AllSystemsHint = Leer lassen fuer alle Systeme
|
||||||
|
Category = Kategorie
|
||||||
|
SelectCategory = Kategorie auswaehlen
|
||||||
SelectType = Typ auswaehlen
|
SelectType = Typ auswaehlen
|
||||||
SelectParent = Uebergeordnetes Element
|
SelectParent = Uebergeordnetes Element
|
||||||
|
TechnicalElement = Element / Geraet
|
||||||
Root = Stamm
|
Root = Stamm
|
||||||
|
|
||||||
# Customer System Management
|
# Customer System Management
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,11 @@ EditElement = Edit element
|
||||||
DeleteElement = Delete element
|
DeleteElement = Delete element
|
||||||
NoInstallations = No installations
|
NoInstallations = No installations
|
||||||
SelectSystem = Select system
|
SelectSystem = Select system
|
||||||
|
Category = Category
|
||||||
|
SelectCategory = Select category
|
||||||
SelectType = Select type
|
SelectType = Select type
|
||||||
SelectParent = Parent element
|
SelectParent = Parent element
|
||||||
|
TechnicalElement = Element / Device
|
||||||
Root = Root
|
Root = Root
|
||||||
|
|
||||||
# Customer System Management
|
# Customer System Management
|
||||||
|
|
@ -208,3 +211,12 @@ Close = Close
|
||||||
Confirm = Confirm
|
Confirm = Confirm
|
||||||
Yes = Yes
|
Yes = Yes
|
||||||
No = No
|
No = No
|
||||||
|
|
||||||
|
# Building Types
|
||||||
|
BuildingStructure = Building / Location
|
||||||
|
BuildingLevelBuilding = Building
|
||||||
|
BuildingLevelFloor = Floor / Level
|
||||||
|
BuildingLevelWing = Wing
|
||||||
|
BuildingLevelCorridor = Corridor / Hallway
|
||||||
|
BuildingLevelRoom = Room
|
||||||
|
BuildingLevelArea = Area / Zone
|
||||||
|
|
|
||||||
173
tabs/anlagen.php
173
tabs/anlagen.php
|
|
@ -652,14 +652,80 @@ if (empty($customerSystems)) {
|
||||||
print '<tr><td class="titlefield fieldrequired">'.$langs->trans('Label').'</td>';
|
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>';
|
print '<td><input type="text" name="label" class="flat minwidth300" value="'.dol_escape_htmltag($labelValue).'" required></td></tr>';
|
||||||
|
|
||||||
// Type
|
// Kategorie (Gebäude/Standort vs Element/Gerät)
|
||||||
print '<tr><td class="fieldrequired">'.$langs->trans('Type').'</td>';
|
$currentCategory = '';
|
||||||
|
if (($isEdit || $isCopy) && !empty($anlage->fk_anlage_type)) {
|
||||||
|
// Kategorie des aktuellen Typs ermitteln
|
||||||
|
foreach ($types as $t) {
|
||||||
|
if ($t->id == $anlage->fk_anlage_type) {
|
||||||
|
$currentCategory = ($t->system_code === 'GLOBAL') ? 'building' : 'element';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$postedCategory = GETPOST('element_category', 'alpha');
|
||||||
|
if ($postedCategory) $currentCategory = $postedCategory;
|
||||||
|
|
||||||
|
print '<tr><td class="fieldrequired">'.$langs->trans('Category').'</td>';
|
||||||
|
print '<td><select name="element_category" class="flat minwidth200" id="select_category">';
|
||||||
|
print '<option value="">'.$langs->trans('SelectCategory').'</option>';
|
||||||
|
print '<option value="building"'.($currentCategory === 'building' ? ' selected' : '').'>'.$langs->trans('BuildingStructure').'</option>';
|
||||||
|
print '<option value="element"'.($currentCategory === 'element' ? ' selected' : '').'>'.$langs->trans('TechnicalElement').'</option>';
|
||||||
|
print '</select></td></tr>';
|
||||||
|
|
||||||
|
// Type (gefiltert nach Kategorie)
|
||||||
|
print '<tr id="row_type"><td class="fieldrequired">'.$langs->trans('Type').'</td>';
|
||||||
print '<td><select name="fk_anlage_type" class="flat minwidth200" id="select_type" required>';
|
print '<td><select name="fk_anlage_type" class="flat minwidth200" id="select_type" required>';
|
||||||
print '<option value="">'.$langs->trans('SelectType').'</option>';
|
print '<option value="">'.$langs->trans('SelectType').'</option>';
|
||||||
|
|
||||||
|
// Typen nach Kategorie gruppieren
|
||||||
|
$buildingTypes = array();
|
||||||
|
$elementTypes = array();
|
||||||
foreach ($types as $t) {
|
foreach ($types as $t) {
|
||||||
$selected = (($isEdit || $isCopy) && $anlage->fk_anlage_type == $t->id) ? ' selected' : '';
|
if ($t->system_code === 'GLOBAL') {
|
||||||
print '<option value="'.$t->id.'"'.$selected.'>'.dol_escape_htmltag($t->label).'</option>';
|
$buildingTypes[] = $t;
|
||||||
|
} else {
|
||||||
|
$elementTypes[] = $t;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gebäude-Typen nach level_type gruppieren (position-basiert)
|
||||||
|
if (!empty($buildingTypes)) {
|
||||||
|
$lastGroup = '';
|
||||||
|
foreach ($buildingTypes as $t) {
|
||||||
|
// Gruppierung nach Position: 10-99=Gebäude, 100-199=Etage, 200-299=Flügel, 300-399=Flur, 400-599=Raum, 600+=Außen
|
||||||
|
if ($t->position < 100) $group = $langs->trans('BuildingLevelBuilding');
|
||||||
|
elseif ($t->position < 200) $group = $langs->trans('BuildingLevelFloor');
|
||||||
|
elseif ($t->position < 300) $group = $langs->trans('BuildingLevelWing');
|
||||||
|
elseif ($t->position < 400) $group = $langs->trans('BuildingLevelCorridor');
|
||||||
|
elseif ($t->position < 600) $group = $langs->trans('BuildingLevelRoom');
|
||||||
|
else $group = $langs->trans('BuildingLevelArea');
|
||||||
|
|
||||||
|
if ($group !== $lastGroup) {
|
||||||
|
if ($lastGroup !== '') print '</optgroup>';
|
||||||
|
print '<optgroup label="'.dol_escape_htmltag($group).'" class="type-category-building">';
|
||||||
|
$lastGroup = $group;
|
||||||
|
}
|
||||||
|
$selected = (($isEdit || $isCopy) && $anlage->fk_anlage_type == $t->id) ? ' selected' : '';
|
||||||
|
$picto = !empty($t->picto) ? dol_escape_htmltag($t->picto) : '';
|
||||||
|
$color = !empty($t->color) ? dol_escape_htmltag($t->color) : '';
|
||||||
|
print '<option value="'.$t->id.'" data-category="building" data-icon="'.$picto.'" data-color="'.$color.'"'.$selected.'>'.dol_escape_htmltag($t->label).'</option>';
|
||||||
|
}
|
||||||
|
if ($lastGroup !== '') print '</optgroup>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Element-Typen
|
||||||
|
if (!empty($elementTypes)) {
|
||||||
|
print '<optgroup label="'.$langs->trans('TechnicalElement').'" class="type-category-element">';
|
||||||
|
foreach ($elementTypes as $t) {
|
||||||
|
$selected = (($isEdit || $isCopy) && $anlage->fk_anlage_type == $t->id) ? ' selected' : '';
|
||||||
|
$picto = !empty($t->picto) ? dol_escape_htmltag($t->picto) : '';
|
||||||
|
$color = !empty($t->color) ? dol_escape_htmltag($t->color) : '';
|
||||||
|
print '<option value="'.$t->id.'" data-category="element" data-icon="'.$picto.'" data-color="'.$color.'"'.$selected.'>'.dol_escape_htmltag($t->label).'</option>';
|
||||||
|
}
|
||||||
|
print '</optgroup>';
|
||||||
|
}
|
||||||
|
|
||||||
print '</select>';
|
print '</select>';
|
||||||
if (empty($types)) {
|
if (empty($types)) {
|
||||||
print '<br><span class="warning">'.$langs->trans('NoTypesDefinedForSystem').'</span>';
|
print '<br><span class="warning">'.$langs->trans('NoTypesDefinedForSystem').'</span>';
|
||||||
|
|
@ -692,6 +758,105 @@ if (empty($customerSystems)) {
|
||||||
print '</div>';
|
print '</div>';
|
||||||
|
|
||||||
print '</form>';
|
print '</form>';
|
||||||
|
|
||||||
|
// JavaScript: Kategorie-Filter + Select2 mit Icons
|
||||||
|
print '<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
var $catSelect = $("#select_category");
|
||||||
|
var $typeSelect = $("#select_type");
|
||||||
|
|
||||||
|
// Alle Options und Optgroups als HTML-String sichern
|
||||||
|
var allOptionsHtml = $typeSelect.html();
|
||||||
|
|
||||||
|
// Select2 Template-Funktion mit Icons
|
||||||
|
function formatTypeOption(option) {
|
||||||
|
if (!option.id) return option.text; // Placeholder
|
||||||
|
var $opt = $(option.element);
|
||||||
|
var icon = $opt.data("icon");
|
||||||
|
var color = $opt.data("color") || "#666";
|
||||||
|
if (icon) {
|
||||||
|
return $("<span><i class=\"fa " + icon + "\" style=\"color:" + color + ";width:20px;margin-right:8px;text-align:center;\"></i>" + option.text + "</span>");
|
||||||
|
}
|
||||||
|
return option.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select2 initialisieren
|
||||||
|
function initSelect2() {
|
||||||
|
// Falls bereits initialisiert, zerstören
|
||||||
|
if ($typeSelect.hasClass("select2-hidden-accessible")) {
|
||||||
|
$typeSelect.select2("destroy");
|
||||||
|
}
|
||||||
|
$typeSelect.select2({
|
||||||
|
templateResult: formatTypeOption,
|
||||||
|
templateSelection: formatTypeOption,
|
||||||
|
placeholder: "'.dol_escape_js($langs->trans('SelectType')).'",
|
||||||
|
allowClear: true,
|
||||||
|
width: "300px",
|
||||||
|
dropdownAutoWidth: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterTypes() {
|
||||||
|
var category = $catSelect.val();
|
||||||
|
var currentVal = $typeSelect.val();
|
||||||
|
|
||||||
|
// Select2 zerstören vor DOM-Änderungen
|
||||||
|
if ($typeSelect.hasClass("select2-hidden-accessible")) {
|
||||||
|
$typeSelect.select2("destroy");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alle Options zurücksetzen
|
||||||
|
$typeSelect.html(allOptionsHtml);
|
||||||
|
|
||||||
|
if (!category) {
|
||||||
|
$typeSelect.prop("disabled", true);
|
||||||
|
$("#row_type").hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nicht passende Options entfernen
|
||||||
|
$typeSelect.find("option[data-category]").each(function() {
|
||||||
|
if ($(this).data("category") !== category) {
|
||||||
|
$(this).remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Leere Optgroups entfernen
|
||||||
|
$typeSelect.find("optgroup").each(function() {
|
||||||
|
if ($(this).find("option").length === 0) {
|
||||||
|
$(this).remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$typeSelect.prop("disabled", false);
|
||||||
|
$("#row_type").show();
|
||||||
|
|
||||||
|
// Wert wiederherstellen falls noch vorhanden
|
||||||
|
if (currentVal && $typeSelect.find("option[value=\"" + currentVal + "\"]").length) {
|
||||||
|
$typeSelect.val(currentVal);
|
||||||
|
} else {
|
||||||
|
$typeSelect.val("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select2 neu initialisieren
|
||||||
|
initSelect2();
|
||||||
|
}
|
||||||
|
|
||||||
|
$catSelect.on("change", function() {
|
||||||
|
$typeSelect.val("");
|
||||||
|
filterTypes();
|
||||||
|
$typeSelect.trigger("change");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initial filtern
|
||||||
|
if ($catSelect.val()) {
|
||||||
|
filterTypes();
|
||||||
|
} else {
|
||||||
|
$typeSelect.prop("disabled", true);
|
||||||
|
$("#row_type").hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>';
|
||||||
}
|
}
|
||||||
|
|
||||||
print '</div>';
|
print '</div>';
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue