From 1a23b3e11d209420d1a2423c7779c952f53474a1 Mon Sep 17 00:00:00 2001 From: FDS Date: Mon, 8 Jun 2026 21:20:33 +0200 Subject: [PATCH] =?UTF-8?q?Fix=20#55:=20Verbindungsfehler-Fallback=20?= =?UTF-8?q?=E2=80=93=20kein=20Retry-Loop,=20spezifische=20Fehlermeldung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Changes.md | 2 + src/Controllers/BelegController.php | 13 ++++++- src/Views/belege/detail.php | 11 ++++++ tests/43_KiConnectionFallbackTest.php | 54 +++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 tests/43_KiConnectionFallbackTest.php diff --git a/Changes.md b/Changes.md index 7de518d..9b34645 100644 --- a/Changes.md +++ b/Changes.md @@ -8,6 +8,8 @@ - **`LlmService`:** `probeOllama()` schreibt bei cURL-Fehlern jetzt einen Eintrag ins PHP-Fehlerlog (`docker compose logs web`) - **Einstellungsseite:** Neuer Button „Verbindung testen" im KI-Abschnitt; zeigt bei Erfolg verfügbare Modelle, bei Fehler cURL-Fehler, URL und Firewall-Hinweis an - **Route:** `POST /settings/llm-test` → `SettingsController::llmTest()` +- **`BelegController`:** Bei Verbindungsfehler wird jetzt `[]` statt `null` gespeichert (verhindert endlose Auto-Retry-Schleife); Fehlermeldung differenziert zwischen „Ollama nicht erreichbar" und „kein Vorschlag generiert" +- **Beleg-Detailseite:** Zeigt bei Verbindungsfehler spezifische Meldung „Ollama nicht erreichbar" mit Link zu den Einstellungen statt der generischen Warnung ## 2026-06-01 – Fix: EN16931-Validierungsfehler in ZUGFeRD-Rechnungen diff --git a/src/Controllers/BelegController.php b/src/Controllers/BelegController.php index b89cf76..4d145f9 100644 --- a/src/Controllers/BelegController.php +++ b/src/Controllers/BelegController.php @@ -378,8 +378,17 @@ class BelegController extends BaseController $svc = new KontierungsvorschlagService(); $vorschlag = $svc->generateForBeleg($beleg); - Beleg::saveKiVorschlag($id, $vorschlag); - echo json_encode($vorschlag ?? ['error' => 'Kein Vorschlag generiert']); + // [] = "verarbeitet, kein Ergebnis"; null würde endlose Auto-Retry-Schleife auslösen + Beleg::saveKiVorschlag($id, $vorschlag ?? []); + if ($vorschlag === null) { + $llm = new \Steuerkram\Services\LlmService(); + echo json_encode($llm->isReachable() + ? ['error' => 'Kein Vorschlag generiert'] + : ['connection_error' => true] + ); + return; + } + echo json_encode($vorschlag); } public function resetVorschlag(array $params): void diff --git a/src/Views/belege/detail.php b/src/Views/belege/detail.php index d05ea6c..ef5bb6e 100644 --- a/src/Views/belege/detail.php +++ b/src/Views/belege/detail.php @@ -1101,6 +1101,16 @@ function _showKiBadgeError() { ''; } +function _showKiBadgeConnectionError() { + const neuBtn = _kiAktiv + ? ' ' + : ''; + document.getElementById('ki-badge').innerHTML = + '
Ollama nicht erreichbar.' + neuBtn + + ' Einstellungen / Verbindung testen' + + '
'; +} + // Normalisiert _kiVorschlag zu einem Array (Objekt oder Array aus DB möglich). // Gibt null zurück wenn kein Ergebnis vorhanden. // Gibt leeres Array [] zurück wenn verarbeitet aber kein Ergebnis erzeugt. @@ -1119,6 +1129,7 @@ async function _startKiAnalyse() { try { const r = await fetch('/belege/' + _belegId + '/vorschlag/generate', {method: 'POST'}); const raw = await r.json(); + if (raw && raw.connection_error) { _showKiBadgeConnectionError(); return; } const arr = _normalizeKiArr(raw); if (arr && arr.length > 0) { _applyKiVorschlag(arr[0]); _showKiBadgeDone(arr); } else _showKiBadgeNoResult(); diff --git a/tests/43_KiConnectionFallbackTest.php b/tests/43_KiConnectionFallbackTest.php new file mode 100644 index 0000000..b240532 --- /dev/null +++ b/tests/43_KiConnectionFallbackTest.php @@ -0,0 +1,54 @@ +createTestBeleg(); + + // [] = "verarbeitet, kein Ergebnis" – verhindert endlose Auto-Retry-Schleife + Beleg::saveKiVorschlag($belegId, []); + $beleg = Beleg::find($belegId); + $this->assertNotEmpty($beleg['ki_vorschlag'], 'Leeres Array muss als JSON gespeichert werden, nicht als SQL NULL'); + + $decoded = json_decode($beleg['ki_vorschlag'], true); + $this->assert(is_array($decoded) && count($decoded) === 0, 'Gespeicherter Wert muss leeres Array sein'); + + // null = "noch nicht verarbeitet" – löst Auto-Retry auf der Beleg-Seite aus + Beleg::saveKiVorschlag($belegId, null); + $beleg = Beleg::find($belegId); + $this->assert($beleg['ki_vorschlag'] === null, 'saveKiVorschlag(null) muss SQL NULL ergeben'); + + // Nicht-leeres Array wird korrekt gespeichert und zurückgelesen + $vorschlag = [[ + 'buchungstyp' => 'Ausgabe', + 'kategorie_konto_id' => 5, + 'zahlungskonto_id' => 2, + 'ust_satz' => 19, + 'betrag_netto' => 99.00, + 'buchungstext' => 'Fallback-Test', + ]]; + Beleg::saveKiVorschlag($belegId, $vorschlag); + $beleg = Beleg::find($belegId); + $decoded = json_decode($beleg['ki_vorschlag'], true); + $this->assert(is_array($decoded) && count($decoded) === 1, 'Vorschlag-Array korrekt gespeichert'); + $this->assertEquals('Ausgabe', $decoded[0]['buchungstyp'], 'buchungstyp korrekt'); + } + + private function createTestBeleg(): int + { + Database::execute( + "INSERT INTO belege (beleg_nr, filename, storage_path, mime_type, file_hash, belegtyp) + VALUES ('TEST-KI-FALLBACK-001', 'test.pdf', 'static/uploads/test/test.pdf', 'application/pdf', + SHA2('fallback-test', 256), 'Eingangsrechnung')" + ); + return (int)Database::lastInsertId(); + } +}