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. *