diff --git a/ajax/equipment_connection.php b/ajax/equipment_connection.php index fbeb776..2be75c8 100755 --- a/ajax/equipment_connection.php +++ b/ajax/equipment_connection.php @@ -161,6 +161,10 @@ switch ($action) { $connection->position_y = GETPOSTINT('position_y'); $connection->path_data = GETPOST('path_data', 'nohtml'); $connection->bundled_terminals = GETPOST('bundled_terminals', 'alphanohtml'); + // Junction fields for branching from existing connections + $connection->junction_connection_id = GETPOSTINT('junction_connection_id') ?: null; + $connection->junction_x = GETPOST('junction_x', 'alpha') ? floatval(GETPOST('junction_x', 'alpha')) : null; + $connection->junction_y = GETPOST('junction_y', 'alpha') ? floatval(GETPOST('junction_y', 'alpha')) : null; $result = $connection->create($user); if ($result > 0) { diff --git a/class/equipmentconnection.class.php b/class/equipmentconnection.class.php index b22924a..3cf1e99 100755 --- a/class/equipmentconnection.class.php +++ b/class/equipmentconnection.class.php @@ -46,6 +46,12 @@ class EquipmentConnection extends CommonObject public $fk_carrier; public $position_y = 0; public $path_data; // SVG path for manually drawn connections + + // Junction fields - branching from existing connections + public $junction_connection_id; // ID of connection to branch from + public $junction_x; // X coordinate of junction point + public $junction_y; // Y coordinate of junction point + public $note_private; public $status = 1; @@ -91,6 +97,7 @@ class EquipmentConnection extends CommonObject $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 .= " junction_connection_id, junction_x, junction_y,"; $sql .= " note_private, status, date_creation, fk_user_creat"; $sql .= ") VALUES ("; $sql .= ((int) $conf->entity); @@ -115,6 +122,9 @@ class EquipmentConnection extends CommonObject $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->junction_connection_id > 0 ? ((int) $this->junction_connection_id) : "NULL"); + $sql .= ", ".($this->junction_x !== null ? floatval($this->junction_x) : "NULL"); + $sql .= ", ".($this->junction_y !== null ? floatval($this->junction_y) : "NULL"); $sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL"); $sql .= ", ".((int) $this->status); $sql .= ", '".$this->db->idate($now)."'"; @@ -184,6 +194,9 @@ class EquipmentConnection extends CommonObject $this->fk_carrier = $obj->fk_carrier; $this->position_y = $obj->position_y; $this->path_data = isset($obj->path_data) ? $obj->path_data : null; + $this->junction_connection_id = isset($obj->junction_connection_id) ? $obj->junction_connection_id : null; + $this->junction_x = isset($obj->junction_x) ? $obj->junction_x : null; + $this->junction_y = isset($obj->junction_y) ? $obj->junction_y : null; $this->note_private = $obj->note_private; $this->status = $obj->status; $this->date_creation = $this->db->jdate($obj->date_creation); @@ -337,6 +350,9 @@ class EquipmentConnection extends CommonObject $conn->fk_carrier = $obj->fk_carrier; $conn->position_y = $obj->position_y; $conn->path_data = isset($obj->path_data) ? $obj->path_data : null; + $conn->junction_connection_id = isset($obj->junction_connection_id) ? $obj->junction_connection_id : null; + $conn->junction_x = isset($obj->junction_x) ? $obj->junction_x : null; + $conn->junction_y = isset($obj->junction_y) ? $obj->junction_y : null; $conn->status = $obj->status; $conn->source_label = $obj->source_label; diff --git a/core/modules/modKundenKarte.class.php b/core/modules/modKundenKarte.class.php index 3dd5c7d..32accb8 100755 --- a/core/modules/modKundenKarte.class.php +++ b/core/modules/modKundenKarte.class.php @@ -76,7 +76,7 @@ class modKundenKarte extends DolibarrModules $this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@kundenkarte' // Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z' - $this->version = '9.1'; + $this->version = '9.2'; // Url to the file with your last numberversion of this module //$this->url_last_version = 'http://www.example.com/versionmodule.txt'; @@ -659,6 +659,9 @@ class modKundenKarte extends DolibarrModules // v8.6.0: has_product Flag für Typen $this->migrate_v860_has_product(); + + // v9.2.0: Junction-Verbindungen (Abzweigungen auf Leitungen) + $this->migrate_v920_junction_connections(); } /** @@ -1048,6 +1051,29 @@ class modKundenKarte extends DolibarrModules } } + /** + * Migration v9.2.0: Junction-Verbindungen + * Ermöglicht Abzweigungen von bestehenden Leitungen + */ + private function migrate_v920_junction_connections() + { + $table = MAIN_DB_PREFIX."kundenkarte_equipment_connection"; + + // junction_connection_id - die Verbindung von der abgezweigt wird + $resql = $this->db->query("SHOW COLUMNS FROM ".$table." LIKE 'junction_connection_id'"); + if (!$resql || $this->db->num_rows($resql) == 0) { + $this->db->query("ALTER TABLE ".$table." ADD COLUMN junction_connection_id int(11) DEFAULT NULL AFTER fk_target"); + $this->db->query("ALTER TABLE ".$table." ADD INDEX idx_junction_conn (junction_connection_id)"); + } + + // junction_x, junction_y - Koordinaten des Abzweigpunkts + $resql = $this->db->query("SHOW COLUMNS FROM ".$table." LIKE 'junction_x'"); + if (!$resql || $this->db->num_rows($resql) == 0) { + $this->db->query("ALTER TABLE ".$table." ADD COLUMN junction_x decimal(10,2) DEFAULT NULL AFTER junction_connection_id"); + $this->db->query("ALTER TABLE ".$table." ADD COLUMN junction_y decimal(10,2) DEFAULT NULL AFTER junction_x"); + } + } + /** * Function called when module is disabled. * Remove from database constants, boxes and permissions from Dolibarr database. diff --git a/js/kundenkarte.js b/js/kundenkarte.js index 7ff1720..989d6f5 100755 --- a/js/kundenkarte.js +++ b/js/kundenkarte.js @@ -7776,43 +7776,63 @@ e.stopPropagation(); // If we're in wire draw mode with a source terminal selected, - // create connection from that terminal to the clicked wire's target + // create TRUE junction connection to the clicked wire if (self.wireDrawMode && self.wireDrawSourceEq && connId) { var conn = self.connections.find(function(c) { return c.id == connId; }); - if (conn && conn.fk_target) { - // Create connection from selected terminal to wire's target equipment - var sourceEqId = self.wireDrawSourceEq; - var sourceTermId = self.wireDrawSourceTerm; - var targetEqId = conn.fk_target; - var targetTermId = conn.target_terminal_id || 'input'; + if (conn) { + // Calculate junction point on the wire (snapped to grid) + var svg = self.svgElement; + var pt = svg.createSVGPoint(); + pt.x = e.clientX; + pt.y = e.clientY; + var svgPt = pt.matrixTransform(svg.getScreenCTM().inverse()); - // Build path: source terminal → existing points → target terminal - var pathPoints = self.wireDrawPoints.slice(); // Copy existing points + // Snap to grid + var gridSize = self.GRID_SIZE; + var junctionX = Math.round(svgPt.x / gridSize) * gridSize; + var junctionY = Math.round(svgPt.y / gridSize) * gridSize; - // Add target terminal position - var targetEq = self.equipment.find(function(eq) { return eq.id == targetEqId; }); - if (targetEq) { - var terminals = self.getTerminals(targetEq); - var targetPos = self.getTerminalPosition(targetEq, targetTermId, terminals); - if (targetPos) { - pathPoints.push({x: targetPos.x, y: targetPos.y}); + // Build orthogonal path from source terminal to junction point + var pathPoints = self.wireDrawPoints.slice(); + if (pathPoints.length === 0) { + // Get source terminal position + var srcEq = self.equipment.find(function(eq) { return eq.id == self.wireDrawSourceEq; }); + if (srcEq) { + var srcTerminals = self.getTerminals(srcEq); + var srcPos = self.getTerminalPosition(srcEq, self.wireDrawSourceTerm, srcTerminals); + if (srcPos) { + pathPoints.push({x: srcPos.x, y: srcPos.y}); + } } } + // Add orthogonal bend point if needed (go vertical first, then horizontal) + if (pathPoints.length > 0) { + var lastPt = pathPoints[pathPoints.length - 1]; + // Add intermediate point for orthogonal routing + if (lastPt.x !== junctionX && lastPt.y !== junctionY) { + pathPoints.push({x: lastPt.x, y: junctionY}); + } + } + pathPoints.push({x: junctionX, y: junctionY}); + // Build path string var pathData = ''; for (var i = 0; i < pathPoints.length; i++) { pathData += (i === 0 ? 'M' : 'L') + ' ' + pathPoints[i].x + ' ' + pathPoints[i].y + ' '; } - // Store connection values to inherit + // Store junction info and show dialog self._inheritFromConnection = conn; - self._pendingPathData = pathData.trim() || null; + self._pendingPathData = pathData.trim(); + self._pendingJunction = { + connection_id: conn.id, + x: junctionX, + y: junctionY + }; - // Clear source terminal but keep draw mode active self.cleanupWireDrawState(true); - - self.showConnectionLabelDialog(sourceEqId, sourceTermId, targetEqId, targetTermId); + self.showJunctionDialog(self.wireDrawSourceEq, self.wireDrawSourceTerm, conn); return; } } @@ -7842,34 +7862,49 @@ e.preventDefault(); e.stopPropagation(); - // If in wire draw mode with source terminal, connect to output's source + // If in wire draw mode, create junction to this output if (self.wireDrawMode && self.wireDrawSourceEq && connId) { var conn = self.connections.find(function(c) { return c.id == connId; }); - if (conn && conn.fk_source) { - var sourceEqId = self.wireDrawSourceEq; - var sourceTermId = self.wireDrawSourceTerm; - var targetEqId = conn.fk_source; - var targetTermId = conn.source_terminal_id || 'output'; + if (conn) { + // Calculate junction point + var svg = self.svgElement; + var pt = svg.createSVGPoint(); + pt.x = e.clientX; + pt.y = e.clientY; + var svgPt = pt.matrixTransform(svg.getScreenCTM().inverse()); - // Build path with existing points + var gridSize = self.GRID_SIZE; + var junctionX = Math.round(svgPt.x / gridSize) * gridSize; + var junctionY = Math.round(svgPt.y / gridSize) * gridSize; + + // Build orthogonal path var pathPoints = self.wireDrawPoints.slice(); - var targetEq = self.equipment.find(function(eq) { return eq.id == targetEqId; }); - if (targetEq) { - var terminals = self.getTerminals(targetEq); - var targetPos = self.getTerminalPosition(targetEq, targetTermId, terminals); - if (targetPos) { - pathPoints.push({x: targetPos.x, y: targetPos.y}); + if (pathPoints.length === 0) { + var srcEq = self.equipment.find(function(eq) { return eq.id == self.wireDrawSourceEq; }); + if (srcEq) { + var srcTerminals = self.getTerminals(srcEq); + var srcPos = self.getTerminalPosition(srcEq, self.wireDrawSourceTerm, srcTerminals); + if (srcPos) pathPoints.push({x: srcPos.x, y: srcPos.y}); } } + if (pathPoints.length > 0) { + var lastPt = pathPoints[pathPoints.length - 1]; + if (lastPt.x !== junctionX && lastPt.y !== junctionY) { + pathPoints.push({x: lastPt.x, y: junctionY}); + } + } + pathPoints.push({x: junctionX, y: junctionY}); + var pathData = ''; for (var i = 0; i < pathPoints.length; i++) { pathData += (i === 0 ? 'M' : 'L') + ' ' + pathPoints[i].x + ' ' + pathPoints[i].y + ' '; } self._inheritFromConnection = conn; - self._pendingPathData = pathData.trim() || null; + self._pendingPathData = pathData.trim(); + self._pendingJunction = { connection_id: conn.id, x: junctionX, y: junctionY }; self.cleanupWireDrawState(true); - self.showConnectionLabelDialog(sourceEqId, sourceTermId, targetEqId, targetTermId); + self.showJunctionDialog(self.wireDrawSourceEq, self.wireDrawSourceTerm, conn); return; } } @@ -10746,6 +10781,160 @@ setTimeout(function() { $('#conn-label').focus(); }, 100); }, + // Dialog for junction connections (terminal to wire) + showJunctionDialog: function(sourceEqId, sourceTermId, targetConn) { + var self = this; + + var sourceEq = this.equipment.find(function(e) { return e.id == sourceEqId; }); + + // Inherit values from target connection + var defaultType = targetConn.connection_type || 'L1N'; + var defaultColor = targetConn.color || this.COLORS.connection; + var defaultMedium = targetConn.medium_type || ''; + var defaultLength = targetConn.medium_length || ''; + + var html = '