From b90f13da34facb74dbfe4e6dbd8ad7857c3f2f90 Mon Sep 17 00:00:00 2001 From: data Date: Tue, 24 Feb 2026 10:46:11 +0100 Subject: [PATCH] feat: Kabel-Kupferzuschlag-Diagramm auf Dashboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Neues Diagramm zeigt Kupferzuschlag-Verlauf pro Kabel - Kabel-Auswahl mit Checkboxen (alle oder einzelne) - Modus-Auswahl: EUR/m oder Gesamtbetrag (mit Mindestmenge) - API: getProductsWithKupfergehalt(), getCableChartData() - Version auf 1.3 erhöht Co-Authored-By: Claude Opus 4.5 --- .gitignore | 0 ChangeLog.md | 9 ++ class/metallzuschlagapi.class.php | 134 ++++++++++++++++ core/modules/modMetallzuschlag.class.php | 2 +- ...lzuschlag_MetallzuschlagTriggers.class.php | 0 langs/de_DE/metallzuschlag.lang | 9 ++ langs/en_US/metallzuschlag.lang | 9 ++ metallzuschlagindex.php | 151 ++++++++++++++++++ sql/llx_metallzuschlag_history.key.sql | 0 sql/llx_metallzuschlag_history.sql | 0 10 files changed, 313 insertions(+), 1 deletion(-) mode change 100644 => 100755 .gitignore mode change 100644 => 100755 class/metallzuschlagapi.class.php mode change 100644 => 100755 core/triggers/interface_99_modMetallzuschlag_MetallzuschlagTriggers.class.php mode change 100644 => 100755 langs/de_DE/metallzuschlag.lang mode change 100644 => 100755 sql/llx_metallzuschlag_history.key.sql mode change 100644 => 100755 sql/llx_metallzuschlag_history.sql 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 '
'; + print '
'; + print ''; + print ''; + + print ''; + print ''; + print ''; + print ''; + print ''; + + $colCount = 0; + foreach ($cablesWithKupfer as $cable) { + $checked = (empty($cableIds) || in_array($cable->rowid, $cableIds)) ? ' checked' : ''; + print ''; + + $colCount++; + if ($colCount % 4 == 0) { + print ''; + } + } + + // Leere Zellen auffuellen + while ($colCount % 4 != 0) { + print ''; + $colCount++; + } + + print ''; + print ''; + print ''; + print ''; + print '
'.$langs->trans("MetallzuschlagSelectCables").'
'; + print ''; + print '
'; + print ' '; + print ''.$langs->trans("MetallzuschlagAllCables").''; + print '
'; + print '
'; + print '
'; + + // Kabel-Chart-Daten holen + $cableChartData = $api->getCableChartData($chartDays, $cableIds, $cableMode); + + if (!empty($cableChartData['products'])) { + print '
'; + print ''; + print '
'; + + // Farben fuer Kabel (verschiedene Farben) + $colors = array( + '#e6194B', '#3cb44b', '#ffe119', '#4363d8', '#f58231', + '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4', + '#469990', '#dcbeff', '#9A6324', '#fffac8', '#800000', + ); + + // Datasets fuer Chart.js vorbereiten + $jsLabels = json_encode($cableChartData['labels']); + $datasets = array(); + $colorIndex = 0; + + foreach ($cableChartData['products'] as $productId => $productData) { + $color = $colors[$colorIndex % count($colors)]; + $datasets[] = array( + 'label' => $productData['label'], + 'data' => $productData['data'], + 'borderColor' => $color, + 'backgroundColor' => 'transparent', + 'borderWidth' => 2, + 'tension' => 0.3, + 'spanGaps' => true, + ); + $colorIndex++; + } + + $jsDatasets = json_encode($datasets); + $yAxisLabel = ($cableMode == 'total') ? $langs->trans("MetallzuschlagTotalAmountEUR") : $langs->trans("MetallzuschlagPerMeterEUR"); + + print ''; + } else { + print '
'.$langs->trans("MetallzuschlagNoData").'
'; + } +} + // Verlaufstabelle print '
'; print load_fiche_titre($langs->trans("MetallzuschlagHistory"), '', ''); diff --git a/sql/llx_metallzuschlag_history.key.sql b/sql/llx_metallzuschlag_history.key.sql old mode 100644 new mode 100755 diff --git a/sql/llx_metallzuschlag_history.sql b/sql/llx_metallzuschlag_history.sql old mode 100644 new mode 100755