diff --git a/.gitignore b/.gitignore
old mode 100644
new mode 100755
diff --git a/ChangeLog.md b/ChangeLog.md
index 033788d..c5ed219 100755
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,14 @@
# Changelog - Metallzuschlag Modul
+## 1.3 (2026-02-24)
+
+### Neu
+- Dashboard: Kabel-Kupferzuschlag-Diagramm mit zeitgenauem Verlauf
+- Kabel-Auswahl: Einzelne oder alle Kabel ein-/ausblenden
+- Modus-Auswahl: EUR/m (Stueckpreis) oder Gesamtbetrag (mit Mindestmenge)
+- API: getProductsWithKupfergehalt() - alle Kabel mit Kupfergehalt
+- API: getCableChartData() - Kupferzuschlag-Verlauf berechnen
+
## 1.2 (2026-02-24)
### Geaendert
diff --git a/class/metallzuschlagapi.class.php b/class/metallzuschlagapi.class.php
old mode 100644
new mode 100755
index c52cf94..fde52ab
--- a/class/metallzuschlagapi.class.php
+++ b/class/metallzuschlagapi.class.php
@@ -489,4 +489,138 @@ class MetallzuschlagApi
return $result;
}
+
+ /**
+ * Alle Produkte mit Kupfergehalt > 0 holen
+ *
+ * @return array Array von Objekten mit id, ref, label, kupfergehalt
+ */
+ public function getProductsWithKupfergehalt()
+ {
+ $results = array();
+
+ $sql = "SELECT p.rowid, p.ref, p.label, pe.kupfergehalt";
+ $sql .= " FROM ".$this->db->prefix()."product p";
+ $sql .= " INNER JOIN ".$this->db->prefix()."product_extrafields pe ON pe.fk_object = p.rowid";
+ $sql .= " WHERE pe.kupfergehalt IS NOT NULL AND pe.kupfergehalt > 0";
+ $sql .= " ORDER BY p.ref ASC";
+
+ $resql = $this->db->query($sql);
+ if ($resql) {
+ while ($obj = $this->db->fetch_object($resql)) {
+ $results[] = $obj;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Kupferzuschlag-Verlauf fuer Kabel berechnen
+ *
+ * @param int $days Zeitraum in Tagen
+ * @param array $productIds Produkt-IDs (leer = alle mit Kupfergehalt)
+ * @param string $mode 'per_meter' oder 'total' (mit Mindestmenge)
+ * @param string $source Quelle fuer CU-Notierungen
+ * @return array ['labels' => [...], 'products' => [id => ['label' => name, 'data' => [...]]]]
+ */
+ public function getCableChartData($days = 90, $productIds = array(), $mode = 'per_meter', $source = 'sonepar')
+ {
+ $result = array(
+ 'labels' => array(),
+ 'products' => array(),
+ );
+
+ // CU-Notierungen im Zeitraum holen
+ $dateFrom = date('Y-m-d', strtotime('-'.$days.' days'));
+
+ $sql = "SELECT date_notiz, value";
+ $sql .= " FROM ".$this->db->prefix()."metallzuschlag_history";
+ $sql .= " WHERE metal = 'CU'";
+ $sql .= " AND source = '".$this->db->escape($source)."'";
+ $sql .= " AND date_notiz >= '".$this->db->escape($dateFrom)."'";
+ $sql .= " ORDER BY date_notiz ASC";
+
+ $resql = $this->db->query($sql);
+ if (!$resql) {
+ return $result;
+ }
+
+ $cuByDate = array();
+ while ($obj = $this->db->fetch_object($resql)) {
+ $cuByDate[$obj->date_notiz] = (float) $obj->value;
+ $result['labels'][] = $obj->date_notiz;
+ }
+
+ if (empty($result['labels'])) {
+ return $result;
+ }
+
+ // Produkte mit Kupfergehalt holen
+ $sqlProd = "SELECT p.rowid, p.ref, p.label, pe.kupfergehalt";
+ $sqlProd .= " FROM ".$this->db->prefix()."product p";
+ $sqlProd .= " INNER JOIN ".$this->db->prefix()."product_extrafields pe ON pe.fk_object = p.rowid";
+ $sqlProd .= " WHERE pe.kupfergehalt IS NOT NULL AND pe.kupfergehalt > 0";
+
+ if (!empty($productIds)) {
+ $sqlProd .= " AND p.rowid IN (".implode(',', array_map('intval', $productIds)).")";
+ }
+
+ $sqlProd .= " ORDER BY p.ref ASC";
+
+ $resProd = $this->db->query($sqlProd);
+ if (!$resProd) {
+ return $result;
+ }
+
+ while ($product = $this->db->fetch_object($resProd)) {
+ $kupfergehalt = (float) $product->kupfergehalt;
+ $productId = (int) $product->rowid;
+
+ // Mindestmenge ermitteln (kleinste quantity aus Einkaufspreisen)
+ $quantity = 1;
+ if ($mode === 'total') {
+ $sqlQty = "SELECT MIN(quantity) as min_qty";
+ $sqlQty .= " FROM ".$this->db->prefix()."product_fournisseur_price";
+ $sqlQty .= " WHERE fk_product = ".$productId;
+ $sqlQty .= " AND quantity > 0";
+
+ $resQty = $this->db->query($sqlQty);
+ if ($resQty && $this->db->num_rows($resQty) > 0) {
+ $objQty = $this->db->fetch_object($resQty);
+ if ($objQty->min_qty > 0) {
+ $quantity = (float) $objQty->min_qty;
+ }
+ }
+ }
+
+ // Kupferzuschlag fuer jeden Tag berechnen
+ $data = array();
+ foreach ($result['labels'] as $date) {
+ $cuNotiz = isset($cuByDate[$date]) ? $cuByDate[$date] : 0;
+ if ($cuNotiz > 0) {
+ // Kupferzuschlag = Kupfergehalt × CU / 100.000 × Menge
+ $data[] = round($kupfergehalt * $cuNotiz / 100000 * $quantity, 2);
+ } else {
+ $data[] = null;
+ }
+ }
+
+ $label = $product->ref;
+ if (!empty($product->label)) {
+ $label .= ' - '.$product->label;
+ }
+ if ($mode === 'total' && $quantity > 1) {
+ $label .= ' ('.$quantity.'m)';
+ }
+
+ $result['products'][$productId] = array(
+ 'label' => $label,
+ 'data' => $data,
+ 'kupfergehalt' => $kupfergehalt,
+ );
+ }
+
+ return $result;
+ }
}
diff --git a/core/modules/modMetallzuschlag.class.php b/core/modules/modMetallzuschlag.class.php
index b074cfa..98279f4 100755
--- a/core/modules/modMetallzuschlag.class.php
+++ b/core/modules/modMetallzuschlag.class.php
@@ -49,7 +49,7 @@ class modMetallzuschlag extends DolibarrModules
$this->descriptionlong = "MetallzuschlagDescriptionLong";
$this->editor_name = 'Alles Watt laeuft';
$this->editor_url = '';
- $this->version = '1.2';
+ $this->version = '1.3';
$this->const_name = 'MAIN_MODULE_'.strtoupper($this->name);
$this->picto = 'fa-coins';
diff --git a/core/triggers/interface_99_modMetallzuschlag_MetallzuschlagTriggers.class.php b/core/triggers/interface_99_modMetallzuschlag_MetallzuschlagTriggers.class.php
old mode 100644
new mode 100755
diff --git a/langs/de_DE/metallzuschlag.lang b/langs/de_DE/metallzuschlag.lang
old mode 100644
new mode 100755
index a69b384..13be64a
--- a/langs/de_DE/metallzuschlag.lang
+++ b/langs/de_DE/metallzuschlag.lang
@@ -80,3 +80,12 @@ MetallzuschlagChartAL = Aluminium (AL) €/100kg
MetallzuschlagLast30Days = Letzte 30 Tage
MetallzuschlagLast90Days = Letzte 90 Tage
MetallzuschlagLast365Days = Letzte 365 Tage
+
+# Kabel-Diagramm
+MetallzuschlagCableChart = Kupferzuschlag Kabel
+MetallzuschlagSelectCables = Kabel auswählen
+MetallzuschlagAllCables = Alle Kabel
+MetallzuschlagPerMeter = EUR/m
+MetallzuschlagTotalAmount = Gesamtbetrag
+MetallzuschlagPerMeterEUR = Kupferzuschlag (EUR/m)
+MetallzuschlagTotalAmountEUR = Kupferzuschlag (EUR)
diff --git a/langs/en_US/metallzuschlag.lang b/langs/en_US/metallzuschlag.lang
index 01cbc73..b3e7b16 100755
--- a/langs/en_US/metallzuschlag.lang
+++ b/langs/en_US/metallzuschlag.lang
@@ -79,3 +79,12 @@ MetallzuschlagChartAL = Aluminum (AL) €/100kg
MetallzuschlagLast30Days = Last 30 days
MetallzuschlagLast90Days = Last 90 days
MetallzuschlagLast365Days = Last 365 days
+
+# Cable chart
+MetallzuschlagCableChart = Cable copper surcharge
+MetallzuschlagSelectCables = Select cables
+MetallzuschlagAllCables = All cables
+MetallzuschlagPerMeter = EUR/m
+MetallzuschlagTotalAmount = Total amount
+MetallzuschlagPerMeterEUR = Copper surcharge (EUR/m)
+MetallzuschlagTotalAmountEUR = Copper surcharge (EUR)
diff --git a/metallzuschlagindex.php b/metallzuschlagindex.php
index 8fb0f49..a715625 100755
--- a/metallzuschlagindex.php
+++ b/metallzuschlagindex.php
@@ -285,6 +285,157 @@ if (!empty($chartData['labels'])) {
print '';
}
+// Kabel-Diagramm: Kupferzuschlag pro Kabel
+$cablesWithKupfer = $api->getProductsWithKupfergehalt();
+
+if (!empty($cablesWithKupfer) && !empty($chartData['labels'])) {
+ print '
';
+
+ // Parameter auslesen
+ $cableIds = GETPOST('cable_ids', 'array');
+ $cableMode = GETPOST('cable_mode', 'alpha') ?: 'per_meter';
+
+ // URL-Basis fuer Links
+ $baseUrl = $_SERVER["PHP_SELF"].'?chart_days='.$chartDays;
+
+ // Kabel-Auswahl und Modus-Buttons
+ $cableButtons = '';
+
+ // Modus-Buttons
+ $modePerMeter = ($cableMode == 'per_meter') ? ' butActionActive' : '';
+ $modeTotal = ($cableMode == 'total') ? ' butActionActive' : '';
+ $cableButtons .= ''.$langs->trans("MetallzuschlagPerMeter").' ';
+ $cableButtons .= ''.$langs->trans("MetallzuschlagTotalAmount").'';
+
+ print load_fiche_titre($langs->trans("MetallzuschlagCableChart"), $cableButtons, '');
+
+ // Kabel-Auswahl (Checkboxen)
+ print '