kundenkarte/class/equipmentconnection.class.php
data 619d14e8d5 feat(pwa): FI-Schutzgruppen, gebündelte Terminals, Terminal-Konfiguration
- Schutzgruppen-Zuordnung: Equipment kann FI/RCD zugeordnet werden
  - Farbliche Markierung der Schutzgruppen im Schaltplan
  - Dropdown zur Auswahl des Schutzgeräts im Equipment-Dialog
- Gebündelte Terminals: Multi-Phasen-Abgänge (E-Herd, Durchlauferhitzer)
  - "Alle bündeln" Option im Abgang-Dialog
  - Zentriertes Label über alle Terminals des Equipment
- Terminal-Anzahl aus terminals_config statt TE-Breite
  - Neozed 3F zeigt korrekt 3 statt 4 Terminals
  - Neue getTerminalCount() Hilfsfunktion
- Zuletzt bearbeitete Kunden (max. 5) auf Search-Screen
- Medium-Typen dynamisch aus DB mit Spezifikationen-Dropdown
- Terminal-Labels anklickbar zum direkten Bearbeiten
- Kontextmenü für leere Terminals (Input/Output Auswahl)
- Block-Label mit Einheiten (40A 30mA statt 40A30mA)
- Online-Status-Anzeige entfernt (funktionierte nicht zuverlässig)
- Service Worker v5.2: Versionierte Assets nicht cachen

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-02 14:34:54 +01:00

434 lines
15 KiB
PHP
Executable file

<?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 EquipmentConnection
* Manages connections between equipment (generic for all system types)
*/
class EquipmentConnection extends CommonObject
{
public $element = 'equipmentconnection';
public $table_element = 'kundenkarte_equipment_connection';
public $fk_source;
public $source_terminal = 'output';
public $source_terminal_id;
public $bundled_terminals; // 'all' = alle Terminals belegt, '0,1,2' = spezifische Indizes, NULL = einzeln
public $fk_target;
public $target_terminal = 'input';
public $target_terminal_id;
// Connection properties
public $connection_type;
public $color;
// Output/endpoint info
public $output_label;
// Medium info (cable, wire, etc.)
public $medium_type;
public $medium_spec;
public $medium_length;
// Rail info
public $is_rail = 0;
public $rail_start_te;
public $rail_end_te;
public $rail_phases; // '3P', '3P+N', 'L1', 'L1N', etc.
public $excluded_te; // Comma-separated TE positions to exclude (gaps for FI)
public $fk_carrier;
public $position_y = 0;
public $path_data; // SVG path for manually drawn connections
public $note_private;
public $status = 1;
public $date_creation;
public $fk_user_creat;
public $fk_user_modif;
// Loaded objects
public $source_label;
public $target_label;
public $carrier_label;
public $source_pos;
public $source_width;
public $target_pos;
/**
* Constructor
*
* @param DoliDB $db Database handler
*/
public function __construct($db)
{
$this->db = $db;
}
/**
* Create object in database
*
* @param User $user User that creates
* @return int Return integer <0 if KO, Id of created object if OK
*/
public function create($user)
{
global $conf;
$error = 0;
$now = dol_now();
$this->db->begin();
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
$sql .= "entity, fk_source, source_terminal, source_terminal_id, bundled_terminals, fk_target, target_terminal, target_terminal_id,";
$sql .= " connection_type, color, output_label,";
$sql .= " medium_type, medium_spec, medium_length,";
$sql .= " is_rail, rail_start_te, rail_end_te, rail_phases, excluded_te, fk_carrier, position_y, path_data,";
$sql .= " note_private, status, date_creation, fk_user_creat";
$sql .= ") VALUES (";
$sql .= ((int) $conf->entity);
$sql .= ", ".($this->fk_source > 0 ? ((int) $this->fk_source) : "NULL");
$sql .= ", '".$this->db->escape($this->source_terminal ?: 'output')."'";
$sql .= ", ".($this->source_terminal_id ? "'".$this->db->escape($this->source_terminal_id)."'" : "NULL");
$sql .= ", ".($this->bundled_terminals ? "'".$this->db->escape($this->bundled_terminals)."'" : "NULL");
$sql .= ", ".($this->fk_target > 0 ? ((int) $this->fk_target) : "NULL");
$sql .= ", '".$this->db->escape($this->target_terminal ?: 'input')."'";
$sql .= ", ".($this->target_terminal_id ? "'".$this->db->escape($this->target_terminal_id)."'" : "NULL");
$sql .= ", ".($this->connection_type ? "'".$this->db->escape($this->connection_type)."'" : "NULL");
$sql .= ", ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
$sql .= ", ".($this->output_label ? "'".$this->db->escape($this->output_label)."'" : "NULL");
$sql .= ", ".($this->medium_type ? "'".$this->db->escape($this->medium_type)."'" : "NULL");
$sql .= ", ".($this->medium_spec ? "'".$this->db->escape($this->medium_spec)."'" : "NULL");
$sql .= ", ".($this->medium_length ? "'".$this->db->escape($this->medium_length)."'" : "NULL");
$sql .= ", ".((int) $this->is_rail);
$sql .= ", ".($this->rail_start_te > 0 ? ((int) $this->rail_start_te) : "NULL");
$sql .= ", ".($this->rail_end_te > 0 ? ((int) $this->rail_end_te) : "NULL");
$sql .= ", ".($this->rail_phases ? "'".$this->db->escape($this->rail_phases)."'" : "NULL");
$sql .= ", ".($this->excluded_te ? "'".$this->db->escape($this->excluded_te)."'" : "NULL");
$sql .= ", ".($this->fk_carrier > 0 ? ((int) $this->fk_carrier) : "NULL");
$sql .= ", ".((int) $this->position_y);
$sql .= ", ".($this->path_data ? "'".$this->db->escape($this->path_data)."'" : "NULL");
$sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL");
$sql .= ", ".((int) $this->status);
$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;
}
}
/**
* Load object from database
*
* @param int $id ID of record
* @return int Return integer <0 if KO, 0 if not found, >0 if OK
*/
public function fetch($id)
{
$sql = "SELECT c.*, ";
$sql .= " src.label as source_label, tgt.label as target_label, car.label as carrier_label";
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as c";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment as src ON c.fk_source = src.rowid";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment as tgt ON c.fk_target = tgt.rowid";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment_carrier as car ON c.fk_carrier = car.rowid";
$sql .= " WHERE c.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->entity = $obj->entity;
$this->fk_source = $obj->fk_source;
$this->source_terminal = $obj->source_terminal;
$this->source_terminal_id = $obj->source_terminal_id;
$this->bundled_terminals = isset($obj->bundled_terminals) ? $obj->bundled_terminals : null;
$this->fk_target = $obj->fk_target;
$this->target_terminal = $obj->target_terminal;
$this->target_terminal_id = $obj->target_terminal_id;
$this->connection_type = $obj->connection_type;
$this->color = $obj->color;
$this->output_label = $obj->output_label;
$this->medium_type = $obj->medium_type;
$this->medium_spec = $obj->medium_spec;
$this->medium_length = $obj->medium_length;
$this->is_rail = $obj->is_rail;
$this->rail_start_te = $obj->rail_start_te;
$this->rail_end_te = $obj->rail_end_te;
$this->rail_phases = $obj->rail_phases;
$this->excluded_te = $obj->excluded_te;
$this->fk_carrier = $obj->fk_carrier;
$this->position_y = $obj->position_y;
$this->path_data = isset($obj->path_data) ? $obj->path_data : null;
$this->note_private = $obj->note_private;
$this->status = $obj->status;
$this->date_creation = $this->db->jdate($obj->date_creation);
$this->fk_user_creat = $obj->fk_user_creat;
$this->fk_user_modif = $obj->fk_user_modif;
$this->source_label = $obj->source_label;
$this->target_label = $obj->target_label;
$this->carrier_label = $obj->carrier_label;
$this->db->free($resql);
return 1;
} else {
$this->db->free($resql);
return 0;
}
} else {
$this->error = $this->db->lasterror();
return -1;
}
}
/**
* Update object in database
*
* @param User $user User that modifies
* @return int Return integer <0 if KO, >0 if OK
*/
public function update($user)
{
$error = 0;
$this->db->begin();
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
$sql .= " fk_source = ".($this->fk_source > 0 ? ((int) $this->fk_source) : "NULL");
$sql .= ", source_terminal = '".$this->db->escape($this->source_terminal ?: 'output')."'";
$sql .= ", source_terminal_id = ".($this->source_terminal_id ? "'".$this->db->escape($this->source_terminal_id)."'" : "NULL");
$sql .= ", bundled_terminals = ".($this->bundled_terminals ? "'".$this->db->escape($this->bundled_terminals)."'" : "NULL");
$sql .= ", fk_target = ".($this->fk_target > 0 ? ((int) $this->fk_target) : "NULL");
$sql .= ", target_terminal = '".$this->db->escape($this->target_terminal ?: 'input')."'";
$sql .= ", target_terminal_id = ".($this->target_terminal_id ? "'".$this->db->escape($this->target_terminal_id)."'" : "NULL");
$sql .= ", connection_type = ".($this->connection_type ? "'".$this->db->escape($this->connection_type)."'" : "NULL");
$sql .= ", color = ".($this->color ? "'".$this->db->escape($this->color)."'" : "NULL");
$sql .= ", output_label = ".($this->output_label ? "'".$this->db->escape($this->output_label)."'" : "NULL");
$sql .= ", medium_type = ".($this->medium_type ? "'".$this->db->escape($this->medium_type)."'" : "NULL");
$sql .= ", medium_spec = ".($this->medium_spec ? "'".$this->db->escape($this->medium_spec)."'" : "NULL");
$sql .= ", medium_length = ".($this->medium_length ? "'".$this->db->escape($this->medium_length)."'" : "NULL");
$sql .= ", is_rail = ".((int) $this->is_rail);
$sql .= ", rail_start_te = ".($this->rail_start_te > 0 ? ((int) $this->rail_start_te) : "NULL");
$sql .= ", rail_end_te = ".($this->rail_end_te > 0 ? ((int) $this->rail_end_te) : "NULL");
$sql .= ", rail_phases = ".($this->rail_phases ? "'".$this->db->escape($this->rail_phases)."'" : "NULL");
$sql .= ", excluded_te = ".($this->excluded_te ? "'".$this->db->escape($this->excluded_te)."'" : "NULL");
$sql .= ", fk_carrier = ".($this->fk_carrier > 0 ? ((int) $this->fk_carrier) : "NULL");
$sql .= ", position_y = ".((int) $this->position_y);
$sql .= ", path_data = ".($this->path_data ? "'".$this->db->escape($this->path_data)."'" : "NULL");
$sql .= ", note_private = ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL");
$sql .= ", status = ".((int) $this->status);
$sql .= ", fk_user_modif = ".((int) $user->id);
$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;
}
}
/**
* Delete object in database
*
* @param User $user User that deletes
* @return int Return integer <0 if KO, >0 if OK
*/
public function delete($user)
{
$error = 0;
$this->db->begin();
$sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." 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;
}
}
/**
* Fetch all connections for a carrier
*
* @param int $carrierId Carrier ID
* @param int $activeOnly Only active connections
* @return array Array of EquipmentConnection objects
*/
public function fetchByCarrier($carrierId, $activeOnly = 1)
{
$results = array();
$sql = "SELECT c.*, ";
$sql .= " src.label as source_label, src.position_te as source_pos, src.width_te as source_width,";
$sql .= " tgt.label as target_label, tgt.position_te as target_pos";
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as c";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment as src ON c.fk_source = src.rowid";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment as tgt ON c.fk_target = tgt.rowid";
$sql .= " WHERE c.fk_carrier = ".((int) $carrierId);
if ($activeOnly) {
$sql .= " AND c.status = 1";
}
$sql .= " ORDER BY c.position_y ASC, c.rowid ASC";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$conn = new EquipmentConnection($this->db);
$conn->id = $obj->rowid;
$conn->entity = $obj->entity;
$conn->fk_source = $obj->fk_source;
$conn->source_terminal = $obj->source_terminal;
$conn->source_terminal_id = $obj->source_terminal_id;
$conn->bundled_terminals = isset($obj->bundled_terminals) ? $obj->bundled_terminals : null;
$conn->fk_target = $obj->fk_target;
$conn->target_terminal = $obj->target_terminal;
$conn->target_terminal_id = $obj->target_terminal_id;
$conn->connection_type = $obj->connection_type;
$conn->color = $obj->color;
$conn->output_label = $obj->output_label;
$conn->medium_type = $obj->medium_type;
$conn->medium_spec = $obj->medium_spec;
$conn->medium_length = $obj->medium_length;
$conn->is_rail = $obj->is_rail;
$conn->rail_start_te = $obj->rail_start_te;
$conn->rail_end_te = $obj->rail_end_te;
$conn->rail_phases = $obj->rail_phases;
$conn->excluded_te = $obj->excluded_te;
$conn->fk_carrier = $obj->fk_carrier;
$conn->position_y = $obj->position_y;
$conn->path_data = isset($obj->path_data) ? $obj->path_data : null;
$conn->status = $obj->status;
$conn->source_label = $obj->source_label;
$conn->source_pos = $obj->source_pos;
$conn->source_width = $obj->source_width;
$conn->target_label = $obj->target_label;
$conn->target_pos = $obj->target_pos;
$results[] = $conn;
}
$this->db->free($resql);
}
return $results;
}
/**
* Fetch all outputs for an equipment
*
* @param int $equipmentId Equipment ID
* @return array Array of EquipmentConnection objects
*/
public function fetchOutputs($equipmentId)
{
$results = array();
$sql = "SELECT * FROM ".MAIN_DB_PREFIX.$this->table_element;
$sql .= " WHERE fk_source = ".((int) $equipmentId);
$sql .= " AND fk_target IS NULL";
$sql .= " AND status = 1";
$sql .= " ORDER BY position_y ASC";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$conn = new EquipmentConnection($this->db);
$conn->id = $obj->rowid;
$conn->fk_source = $obj->fk_source;
$conn->connection_type = $obj->connection_type;
$conn->color = $obj->color;
$conn->output_label = $obj->output_label;
$conn->medium_type = $obj->medium_type;
$conn->medium_spec = $obj->medium_spec;
$conn->medium_length = $obj->medium_length;
$conn->fk_carrier = $obj->fk_carrier;
$conn->position_y = $obj->position_y;
$conn->status = $obj->status;
$results[] = $conn;
}
$this->db->free($resql);
}
return $results;
}
/**
* Get display color
*
* @return string Color hex code
*/
public function getColor()
{
if (!empty($this->color)) {
return $this->color;
}
return '#888888'; // Default grey
}
/**
* Get display label for output
*
* @return string Display label
*/
public function getDisplayLabel()
{
$parts = array();
if ($this->output_label) {
$parts[] = $this->output_label;
}
if ($this->medium_type) {
$mediumInfo = $this->medium_type;
if ($this->medium_spec) {
$mediumInfo .= ' '.$this->medium_spec;
}
if ($this->medium_length) {
$mediumInfo .= ' ('.$this->medium_length.')';
}
$parts[] = $mediumInfo;
}
return implode(' - ', $parts);
}
}