- LlmService: Auto-Discovery lokales Ollama + OpenAI-kompatibler Remote-API-Modus - KontierungsvorschlagService: PDF-Textextraktion (pdftotext) + Vision für Bilder - Async-Badge in Beleg-Detailansicht: "Analysiere..." → Formular vorausgefüllt - Einstellung in Settings (default: aus) mit Provider-Status-Anzeige - .env.example: Beispielkonfiguration für beide Provider-Typen (auskommentiert) - Dockerfile: poppler-utils für pdftotext - Migration 011: ki_vorschlag/ki_vorschlag_at in belege, ki_vorschlag_aktiv in settings - Test 11: saveKiVorschlag, JSON-Extraktion, isEnabled-Default
73 lines
3.1 KiB
PHP
73 lines
3.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use Steuerkram\Database;
|
|
use Steuerkram\Models\Beleg;
|
|
use Steuerkram\Services\KontierungsvorschlagService;
|
|
|
|
class KontierungsvorschlagTest extends TestCase
|
|
{
|
|
protected string $testName = 'Test 11: KI-Kontierungsvorschlag';
|
|
|
|
public function run(): void
|
|
{
|
|
// 1. Einstellung ist standardmäßig deaktiviert
|
|
$svc = new KontierungsvorschlagService();
|
|
$this->assert(!$svc->isEnabled(), 'ki_vorschlag_aktiv sollte per Default 0 sein');
|
|
|
|
// 2. saveKiVorschlag speichert und liest korrekt zurück
|
|
$belegId = $this->createTestBeleg();
|
|
|
|
$vorschlag = [
|
|
'buchungstyp' => 'Ausgabe',
|
|
'kategorie_konto_id' => 42,
|
|
'zahlungskonto_id' => 10,
|
|
'ust_satz' => 19,
|
|
'betrag_netto' => 33.64,
|
|
'buchungstext' => 'Testrechnung',
|
|
];
|
|
Beleg::saveKiVorschlag($belegId, $vorschlag);
|
|
|
|
$beleg = Beleg::find($belegId);
|
|
$this->assertNotEmpty($beleg['ki_vorschlag'], 'ki_vorschlag sollte gespeichert sein');
|
|
$this->assertNotEmpty($beleg['ki_vorschlag_at'], 'ki_vorschlag_at sollte gesetzt sein');
|
|
|
|
$decoded = json_decode($beleg['ki_vorschlag'], true);
|
|
$this->assertEquals('Ausgabe', $decoded['buchungstyp'], 'buchungstyp korrekt gespeichert');
|
|
$this->assertEquals(19, $decoded['ust_satz'], 'ust_satz korrekt gespeichert');
|
|
$this->assertEquals(33.64, $decoded['betrag_netto'], 'betrag_netto korrekt gespeichert');
|
|
|
|
// 3. saveKiVorschlag(null) löscht den Vorschlag
|
|
Beleg::saveKiVorschlag($belegId, null);
|
|
$beleg = Beleg::find($belegId);
|
|
$this->assert($beleg['ki_vorschlag'] === null, 'ki_vorschlag sollte nach Reset null sein');
|
|
|
|
// 4. JSON-Extraktion: Prosa vor JSON wird korrekt extrahiert
|
|
$this->testJsonExtraktion();
|
|
}
|
|
|
|
private function createTestBeleg(): int
|
|
{
|
|
Database::execute(
|
|
"INSERT INTO belege (beleg_nr, filename, storage_path, mime_type, file_hash, eingangs_datum, belegtyp)
|
|
VALUES ('TEST-KI-0001', 'test.pdf', 'static/uploads/test/test.pdf', 'application/pdf',
|
|
SHA2('test', 256), CURDATE(), 'Eingangsrechnung')"
|
|
);
|
|
return (int)Database::lastInsertId();
|
|
}
|
|
|
|
private function testJsonExtraktion(): void
|
|
{
|
|
// Simuliert die Regex-Extraktion aus einer LLM-Antwort mit vorangehendem Text
|
|
$antwort = "Hier ist mein Kontierungsvorschlag:\n{\"buchungstyp\":\"Ausgabe\",\"kategorie_konto_id\":5,\"zahlungskonto_id\":2,\"ust_satz\":19,\"betrag_netto\":100.00,\"buchungstext\":\"Test\"}";
|
|
preg_match('/\{[^{}]*\}/s', $antwort, $m);
|
|
$data = json_decode($m[0] ?? '{}', true);
|
|
$this->assertEquals('Ausgabe', $data['buchungstyp'] ?? '', 'JSON-Extraktion aus LLM-Antwort mit Prosa');
|
|
$this->assertEquals(19, $data['ust_satz'] ?? -1, 'ust_satz aus extrahiertem JSON');
|
|
|
|
// Antwort ohne JSON
|
|
preg_match('/\{[^{}]*\}/s', 'Keine Angabe möglich.', $m2);
|
|
$this->assert(empty($m2), 'Keine JSON-Extraktion ohne JSON-Block');
|
|
}
|
|
}
|