From 90cbee6e81f06e8e04342b4c02620a097bda6aec Mon Sep 17 00:00:00 2001 From: Eduard Wisch Date: Sun, 16 Aug 2026 22:22:02 +0200 Subject: [PATCH] Geraete-Monitor: eigener PDF-Zweig statt einer Sammelzelle Die Dauerueberwachung lief im generischen else-Zweig und wurde von netdiagPdfFlattenResult() zu EINER |-getrennten Zeile zusammengeschoben. Bei zehn ueberwachten Geraeten standen damit die zehn Geraete-Zeilen und saemtliche Einzelausfaelle in einer einzigen Tabellenzelle - vollstaendig, aber beim Kunden nicht lesbar. Dauertest und WLAN-Kanal hatten laengst je einen Tabellen-Renderer, der Monitor nicht. netdiagPdfMonitor() gibt jetzt aus: Kennzahlenzeile, Tabelle "Ueberwachte Geraete", Tabelle "Einzelne Ausfaelle" mit Uhrzeiten, Hinweis zum Messverfahren am Schluss. Tabellenkoepfe wiederholen sich nach einem Seitenumbruch. Die Falle dabei, gegen die im Code ein Kommentar steht: beim Dauertest fuehrt ein gesetztes 'hinweis' zum sofortigen Abbruch der Ausgabe. Der Monitor legt aber IMMER einen Hinweis an (das Messverfahren gehoert zur Aussage dazu) - derselbe Aufbau haette hier bei jedem Lauf alle Zahlen verschluckt. Nur 'fehler' bricht ab. Obergrenze 200 Ausfallzeilen gegen ein im Sekundentakt flappendes Geraet. Gekuerzt wird sichtbar ("... und N weitere Ausfaelle") - stilles Kuerzen waere bei einem Abnahmebeleg eine Falschaussage. Ausserdem: 'ausfallzeitSek' heisst jetzt "Ausfallzeit (schlechtestes Geraet)". Der Wert ist der des schlechtesten Geraets, nicht die Summe - bei zehn Geraeten las sich "Ausfallzeit: 8 min" wie eine Gesamtaussage. Geprueft gegen die lokale Testinstanz mit vier konstruierten Faellen (10 Geraete mit Aussetzern, ein durchgehend totes Geraet, 250 Ausfaelle ueber sechs Seiten, abgerissener Lauf) und gegen die echte Monitor- Messung aus ND2026-0016. PDF gerendert und angesehen. Co-Authored-By: Claude Opus 5 (1M context) --- ChangeLog.md | 34 ++++++++ core/modules/modNetDiag.class.php | 2 +- lib/netdiag.lib.php | 5 +- lib/netdiag_pdf.lib.php | 133 ++++++++++++++++++++++++++++++ 4 files changed, 172 insertions(+), 2 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 7610276..80f2897 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,39 @@ # Changelog NetDiag +## 1.1.2 — 2026-08-16 + +### Geräte-Monitor bekommt einen eigenen PDF-Zweig + +Die Dauerüberwachung lief bisher in den generischen `else`-Zweig von +`netdiag_pdf.lib.php` und wurde von `netdiagPdfFlattenResult()` zu **einer** +`|`-getrennten Zeile zusammengeschoben. Bei zehn überwachten Geräten standen +damit die zehn Geräte-Zeilen UND sämtliche Einzelausfälle in einer einzigen +Tabellenzelle — vollständig, aber beim Kunden nicht lesbar. Dauertest und +WLAN-Kanal hatten längst je einen Tabellen-Renderer, der Monitor nicht. + +- **`netdiagPdfMonitor()`**: Kennzahlenzeile, Tabelle „Überwachte Geräte", + Tabelle „Einzelne Ausfälle" mit Uhrzeiten, Hinweis zum Messverfahren am Ende. +- **Die Falle dabei**, gegen die es einen Kommentar im Code gibt: beim Dauertest + führt ein gesetztes `hinweis` zum sofortigen Abbruch der Ausgabe. Der Monitor + legt aber IMMER einen Hinweis an (das Messverfahren gehört zur Aussage dazu) — + derselbe Aufbau hätte hier bei **jedem** Lauf alle Zahlen verschluckt. Nur + `fehler` bricht ab, der Hinweis steht am Schluss. +- Tabellenköpfe wiederholen sich nach einem Seitenumbruch („— Fortsetzung"), + wie in der Geräteliste. +- Obergrenze 200 Ausfallzeilen gegen den pathologischen Fall (ein im + Sekundentakt flappendes Gerät erzeugt über einen Arbeitstag vierstellig viele + Segmente). Gekürzt wird **sichtbar**: „… und N weitere Ausfälle" steht im + Dokument, sonst läse sich das Protokoll als vollständig. +- **`ausfallzeitSek` heißt jetzt „Ausfallzeit (schlechtestes Gerät)"** — der + Wert ist der des schlechtesten Geräts, nicht die Summe über alle. Bei zehn + überwachten Geräten las sich „Ausfallzeit: 8 min" wie eine Gesamtaussage. + Gleiche Korrektur in `app/src/lib/messfelder.ts`, damit App und PDF dasselbe + Feld gleich benennen. + +Geprüft gegen die lokale Testinstanz mit vier konstruierten Fällen (10 Geräte +mit Aussetzern, ein durchgehend totes Gerät, 250 Einzelausfälle über sechs +Seiten, abgerissener Lauf) und gegen die echte Monitor-Messung aus ND2026-0016. + ## 1.1.1 — 2026-08-16 ### Vier stille Fehler im Kundendokument und im Sync diff --git a/core/modules/modNetDiag.class.php b/core/modules/modNetDiag.class.php index 3efc43a..82bfdb5 100644 --- a/core/modules/modNetDiag.class.php +++ b/core/modules/modNetDiag.class.php @@ -67,7 +67,7 @@ class modNetDiag extends DolibarrModules $this->editor_url = ''; $this->editor_squarred_logo = ''; - $this->version = '1.1.1'; + $this->version = '1.1.2'; // Konstanten-Name in llx_const für Modul-Status $this->const_name = 'MAIN_MODULE_'.strtoupper($this->name); diff --git a/lib/netdiag.lib.php b/lib/netdiag.lib.php index 72a9f93..afdf782 100644 --- a/lib/netdiag.lib.php +++ b/lib/netdiag.lib.php @@ -338,7 +338,10 @@ function netdiagKundenfelder() // vollständig ins Kundendokument, nicht nur die Ampel. 'geraeteAnzahl' => array('Überwachte Geräte', ''), 'verfuegbarkeitProzent' => array('Verfügbarkeit (schlechtestes Gerät)', '%'), - 'ausfallzeitSek' => array('Ausfallzeit', 's'), + // „Ausfallzeit" allein war irreführend: der Wert ist der des schlechtesten + // Geräts, nicht die Summe über alle. Bei zehn überwachten Geräten liest + // sich „Ausfallzeit: 8 min" sonst wie eine Gesamtaussage. + 'ausfallzeitSek' => array('Ausfallzeit (schlechtestes Gerät)', 's'), 'aussetzer' => array('Aussetzer', ''), 'geraete' => array('Je Gerät', ''), 'ausfaelle' => array('Einzelne Ausfälle', ''), diff --git a/lib/netdiag_pdf.lib.php b/lib/netdiag_pdf.lib.php index 674a6f2..3de9165 100644 --- a/lib/netdiag_pdf.lib.php +++ b/lib/netdiag_pdf.lib.php @@ -198,6 +198,8 @@ function netdiagGeneratePdf($db, $protocol, $outputlangs) netdiagPdfStressTest($pdf, $m->result); } elseif ($m->tool === 'wifikanal') { netdiagPdfWifiKanal($pdf, $m->result); + } elseif ($m->tool === 'monitor') { + netdiagPdfMonitor($pdf, $m->result); } else { $pdf->MultiCell(180, 5, netdiagPdfFlattenResult($m->result), 1, 'L'); } @@ -371,6 +373,137 @@ function netdiagPdfStressTest($pdf, $json) } } +/** + * Geräte-Dauerüberwachung strukturiert ins PDF schreiben. + * + * Dritter eigener Zweig neben Dauertest und WLAN-Kanal, aus demselben Grund: + * `netdiagPdfFlattenResult()` schiebt alles zu EINER `|`-getrennten Zeile + * zusammen. Bei zehn überwachten Geräten standen damit die zehn Zeilen aus + * `geraete` UND sämtliche Einzelausfälle in einer einzigen Tabellenzelle — + * vollständig, aber beim Kunden nicht lesbar. + * + * ACHTUNG, Unterschied zu netdiagPdfStressTest(): dort führt ein gesetztes + * `hinweis` zum sofortigen Abbruch. Hier darf es das NICHT — der Monitor legt + * IMMER einen Hinweis an (das Messverfahren gehört zur Aussage dazu, siehe + * `buildMonitorMeasurement()` in app/src/lib/monitor.ts). Ein Frühabbruch + * würde also bei jedem Monitor-Lauf sämtliche Zahlen verschlucken. Der Hinweis + * steht deshalb am Ende, nur `fehler` bricht ab. + * + * @param TCPDF $pdf PDF-Objekt (Cursor steht direkt hinter der Titelzeile) + * @param string $json result-JSON der Messung + * @return void + */ +function netdiagPdfMonitor($pdf, $json) +{ + $data = json_decode((string) $json, true); + if (!is_array($data)) { + $pdf->MultiCell(180, 5, netdiagPdfFlattenResult($json), 1, 'L'); + return; + } + if (!empty($data['fehler']) || !empty($data['error'])) { + $pdf->MultiCell(180, 5, netdiagKundentext((string) ($data['fehler'] ?? $data['error'])), 1, 'L'); + return; + } + + // Kennzahlenzeile. Bezeichnungen bewusst wie in netdiagKundenfelder(), + // damit dasselbe Feld im PDF und in der Dolibarr-Ansicht gleich heißt. + $sum = array(); + if (isset($data['dauerSekunden'])) { + $sum[] = 'Messdauer: '.netdiagDauerLesbar($data['dauerSekunden']); + } + if (isset($data['intervallSek'])) { + $sum[] = 'Messabstand: '.$data['intervallSek'].' s'; + } + if (isset($data['geraeteAnzahl'])) { + $sum[] = 'Überwachte Geräte: '.$data['geraeteAnzahl']; + } + if (isset($data['verfuegbarkeitProzent']) && $data['verfuegbarkeitProzent'] !== null) { + $sum[] = 'Verfügbarkeit (schlechtestes Gerät): '.number_format((float) $data['verfuegbarkeitProzent'], 1, ',', '').' %'; + } + if (isset($data['aussetzer'])) { + $sum[] = 'Aussetzer: '.$data['aussetzer']; + } + if (!empty($data['ausfallzeitSek'])) { + $sum[] = 'Ausfallzeit (schlechtestes Gerät): '.netdiagDauerLesbar($data['ausfallzeitSek']); + } + if (!empty($data['laengsterAusfallSek'])) { + $sum[] = 'Längster Ausfall: '.netdiagDauerLesbar($data['laengsterAusfallSek']); + } + if (!empty($sum)) { + $pdf->MultiCell(180, 5, implode(' | ', $sum), 1, 'L'); + } + + // Je Gerät eine eigene Zeile — das ist der eigentliche Beleg. Die Zeilen + // kommen vorformatiert aus der App (dieselben, die dort in der Messungs- + // liste stehen), wie beim Dauertest: eine Quelle der Wahrheit. + $geraete = (isset($data['geraete']) && is_array($data['geraete'])) ? $data['geraete'] : array(); + if (!empty($geraete)) { + netdiagPdfEnsureSpace($pdf, 10); + $pdf->SetFont('', 'B', 8); + $pdf->Cell(180, 5, 'Überwachte Geräte ('.count($geraete).')', 1, 1, 'L'); + $pdf->SetFont('', '', 8); + foreach ($geraete as $line) { + netdiagPdfEnsureSpace($pdf, 5, function ($p) use ($geraete) { + $p->SetFont('', 'B', 8); + $p->Cell(180, 5, 'Überwachte Geräte ('.count($geraete).') — Fortsetzung', 1, 1, 'L'); + $p->SetFont('', '', 8); + }); + $pdf->Cell(180, 5, dol_trunc((string) $line, 120), 1, 1, 'L'); + } + } + + // Einzelne Ausfälle mit Uhrzeit. Genau das ist der Nachweis bei der + // häufigsten Reklamation („die Kamera fällt ständig aus"). + $ausfaelle = (isset($data['ausfaelle']) && is_array($data['ausfaelle'])) ? $data['ausfaelle'] : array(); + netdiagPdfEnsureSpace($pdf, 10); + if (empty($ausfaelle)) { + $pdf->SetFont('', 'I', 8); + $pdf->Cell(180, 5, 'Kein Ausfall während der Überwachung.', 1, 1, 'L'); + $pdf->SetFont('', '', 8); + } else { + $pdf->SetFont('', 'B', 8); + $pdf->Cell(180, 5, 'Einzelne Ausfälle ('.count($ausfaelle).')', 1, 1, 'L'); + $pdf->SetFont('', '', 8); + /* + * Obergrenze gegen den pathologischen Fall: ein im Sekundentakt + * flappendes Gerät erzeugt über einen Arbeitstag vierstellig viele + * Segmente — das wären zweistellig viele Seiten reine Ausfallliste. + * 200 Zeilen sind rund vier Seiten und belegen jede reale Störung + * ausreichend. Gekürzt wird NICHT stillschweigend: die Restzahl steht + * sichtbar im Dokument, sonst läse sich das Protokoll als vollständig. + */ + $max = 200; + $i = 0; + foreach ($ausfaelle as $line) { + if ($i >= $max) { + break; + } + netdiagPdfEnsureSpace($pdf, 5, function ($p) use ($ausfaelle) { + $p->SetFont('', 'B', 8); + $p->Cell(180, 5, 'Einzelne Ausfälle ('.count($ausfaelle).') — Fortsetzung', 1, 1, 'L'); + $p->SetFont('', '', 8); + }); + $pdf->Cell(180, 5, dol_trunc((string) $line, 120), 1, 1, 'L'); + $i++; + } + $rest = count($ausfaelle) - $max; + if ($rest > 0) { + netdiagPdfEnsureSpace($pdf, 5); + $pdf->SetFont('', 'I', 8); + $pdf->Cell(180, 5, '… und '.$rest.' weitere Ausfälle, hier aus Platzgründen nicht einzeln aufgeführt.', 1, 1, 'L'); + $pdf->SetFont('', '', 8); + } + } + + // Hinweis zum Schluss: Messverfahren und ggf. abgerissener Lauf. + if (!empty($data['hinweis'])) { + netdiagPdfEnsureSpace($pdf, 10); + $pdf->SetFont('', 'I', 7); + $pdf->MultiCell(180, 4, netdiagKundentext((string) $data['hinweis']), 1, 'L'); + $pdf->SetFont('', '', 8); + } +} + /** * WLAN-Kanal-Momentaufnahme im PDF als Tabelle ausgeben. *