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(); if (empty($this->fk_anlage) || empty($this->label)) { $this->error = 'ErrorMissingParameters'; return -1; } // Get next position if (empty($this->position)) { $sql = "SELECT MAX(position) as maxpos FROM ".MAIN_DB_PREFIX.$this->table_element; $sql .= " WHERE fk_anlage = ".((int) $this->fk_anlage); $resql = $this->db->query($sql); if ($resql) { $obj = $this->db->fetch_object($resql); $this->position = ($obj->maxpos !== null) ? $obj->maxpos + 1 : 0; } } $this->db->begin(); $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." ("; $sql .= "entity, fk_anlage, fk_panel, label, total_te, position, note_private, status,"; $sql .= " date_creation, fk_user_creat"; $sql .= ") VALUES ("; $sql .= ((int) $conf->entity); $sql .= ", ".((int) $this->fk_anlage); $sql .= ", ".($this->fk_panel > 0 ? ((int) $this->fk_panel) : "NULL"); $sql .= ", '".$this->db->escape($this->label)."'"; $sql .= ", ".((int) ($this->total_te > 0 ? $this->total_te : 12)); $sql .= ", ".((int) $this->position); $sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL"); $sql .= ", ".((int) ($this->status !== null ? $this->status : 1)); $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.*, a.label as anlage_label, p.label as panel_label"; $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as c"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_anlage as a ON c.fk_anlage = a.rowid"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment_panel as p ON c.fk_panel = p.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_anlage = $obj->fk_anlage; $this->fk_panel = $obj->fk_panel; $this->label = $obj->label; $this->total_te = $obj->total_te; $this->position = $obj->position; $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->anlage_label = $obj->anlage_label; $this->panel_label = $obj->panel_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 .= " label = '".$this->db->escape($this->label)."'"; $sql .= ", fk_panel = ".($this->fk_panel > 0 ? ((int) $this->fk_panel) : "NULL"); $sql .= ", total_te = ".((int) ($this->total_te > 0 ? $this->total_te : 12)); $sql .= ", position = ".((int) $this->position); $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(); // Equipment is deleted via CASCADE $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 carriers for a Panel * * @param int $panelId Panel ID * @param int $activeOnly Only active carriers * @return array Array of EquipmentCarrier objects */ public function fetchByPanel($panelId, $activeOnly = 1) { $results = array(); $sql = "SELECT * FROM ".MAIN_DB_PREFIX.$this->table_element; $sql .= " WHERE fk_panel = ".((int) $panelId); if ($activeOnly) { $sql .= " AND status = 1"; } $sql .= " ORDER BY position ASC"; $resql = $this->db->query($sql); if ($resql) { while ($obj = $this->db->fetch_object($resql)) { $carrier = new EquipmentCarrier($this->db); $carrier->id = $obj->rowid; $carrier->entity = $obj->entity; $carrier->fk_anlage = $obj->fk_anlage; $carrier->fk_panel = $obj->fk_panel; $carrier->label = $obj->label; $carrier->total_te = $obj->total_te; $carrier->position = $obj->position; $carrier->note_private = $obj->note_private; $carrier->status = $obj->status; $results[] = $carrier; } $this->db->free($resql); } return $results; } /** * Fetch all carriers for an Anlage * * @param int $anlageId Anlage ID * @param int $activeOnly Only active carriers * @return array Array of EquipmentCarrier objects */ public function fetchByAnlage($anlageId, $activeOnly = 1) { $results = array(); $sql = "SELECT * FROM ".MAIN_DB_PREFIX.$this->table_element; $sql .= " WHERE fk_anlage = ".((int) $anlageId); if ($activeOnly) { $sql .= " AND status = 1"; } $sql .= " ORDER BY position ASC"; $resql = $this->db->query($sql); if ($resql) { while ($obj = $this->db->fetch_object($resql)) { $carrier = new EquipmentCarrier($this->db); $carrier->id = $obj->rowid; $carrier->entity = $obj->entity; $carrier->fk_anlage = $obj->fk_anlage; $carrier->fk_panel = $obj->fk_panel; $carrier->label = $obj->label; $carrier->total_te = $obj->total_te; $carrier->position = $obj->position; $carrier->note_private = $obj->note_private; $carrier->status = $obj->status; $results[] = $carrier; } $this->db->free($resql); } return $results; } /** * Fetch all equipment on this carrier * * @return array Array of Equipment objects */ public function fetchEquipment() { require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipment.class.php'; $equipment = new Equipment($this->db); $this->equipment = $equipment->fetchByCarrier($this->id); return $this->equipment; } /** * Belegte TE-Ranges zurückgeben (für Dezimal-Breiten) * * @return array Array von [start, end] Ranges */ public function getOccupiedRanges() { $ranges = array(); if (empty($this->equipment)) { $this->fetchEquipment(); } foreach ($this->equipment as $eq) { $ranges[] = array( 'start' => floatval($eq->position_te), 'end' => floatval($eq->position_te) + floatval($eq->width_te) ); } usort($ranges, function($a, $b) { return $a['start'] <=> $b['start']; }); return $ranges; } /** * Belegte TE-Summe * * @return float Belegte TE (kann Dezimal sein, z.B. 10.5) */ public function getUsedTE() { if (empty($this->equipment)) { $this->fetchEquipment(); } $used = 0; foreach ($this->equipment as $eq) { $used += floatval($eq->width_te); } return $used; } /** * Freie TE * * @return float Freie TE */ public function getFreeTE() { return $this->total_te - $this->getUsedTE(); } /** * Nächste freie Position finden (unterstützt Dezimal-Breiten) * * @param float $width Benötigte Breite in TE * @return float Position (1-basiert) oder -1 wenn kein Platz */ public function getNextFreePosition($width = 1) { $width = floatval($width); $ranges = $this->getOccupiedRanges(); $maxEnd = floatval($this->total_te) + 1; // Position 1 + total_te = Ende der Schiene $pos = 1.0; foreach ($ranges as $range) { // Passt in die Lücke vor diesem Element? if ($pos + $width <= $range['start'] + 0.001) { return $pos; } // Hinter dieses Element springen if ($range['end'] > $pos) { $pos = $range['end']; } } // Platz nach dem letzten Element? if ($pos + $width <= $maxEnd + 0.001) { return $pos; } return -1; } /** * Prüfen ob Position verfügbar ist (unterstützt Dezimal-Breiten) * * @param float $position Startposition (1-basiert) * @param float $width Breite in TE * @param int $excludeEquipmentId Equipment-ID zum Ausschließen (für Updates) * @return bool True wenn Position frei */ public function isPositionAvailable($position, $width, $excludeEquipmentId = 0) { $position = floatval($position); $width = floatval($width); // Grenzen prüfen (Position 1 = erstes TE) if ($position < 1 || $position + $width > floatval($this->total_te) + 1 + 0.001) { return false; } if (empty($this->equipment)) { $this->fetchEquipment(); } $newEnd = $position + $width; foreach ($this->equipment as $eq) { if ($excludeEquipmentId > 0 && $eq->id == $excludeEquipmentId) { continue; } // Overlap-Prüfung mit Half-Open-Ranges: [start, end) $eqStart = floatval($eq->position_te); $eqEnd = $eqStart + floatval($eq->width_te); if ($position < $eqEnd - 0.001 && $eqStart < $newEnd - 0.001) { return false; } } return true; } }