Steuerkram/public/static/js/beleg-ki.js

189 lines
7.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
const _kiBadgeEl = document.getElementById('ki-badge');
const _belegId = parseInt(_kiBadgeEl.dataset.belegId, 10);
const _kiAktiv = JSON.parse(_kiBadgeEl.dataset.kiAktiv);
const _kiVorschlag = JSON.parse(_kiBadgeEl.dataset.kiVorschlag);
function _selectById(selectEl, id) {
if (!selectEl) return;
const str = String(id);
for (const opt of selectEl.options) {
opt.selected = (opt.value === str);
}
}
// Option per Wert suchen – ohne den (KI-gelieferten) Wert in einen CSS-Selektor einzusetzen
function _findOption(selectEl, id) {
if (!selectEl) return null;
const str = String(id);
for (const opt of selectEl.options) {
if (opt.value === str) return opt;
}
return null;
}
function _applyKiVorschlag(v) {
const btRadio = Array.from(document.querySelectorAll('input[name="buchungstyp"]'))
.find(r => r.value === String(v.buchungstyp));
if (btRadio) { btRadio.checked = true; filterKategorieKonten(v.buchungstyp); }
const ustEl = document.getElementById('ust_satz_select');
if (ustEl) { ustEl.value = String(v.ust_satz); updateSteuerartVisibility(); }
const katEl2 = document.getElementById('kategorie_konto_id');
_selectById(katEl2, v.kategorie_konto_id);
if (katEl2) transferKontoNr(katEl2);
updateEigenanteilState();
const zhlEl2 = document.getElementById('zahlungskonto_id');
_selectById(zhlEl2, v.zahlungskonto_id);
if (zhlEl2) transferKontoNr(zhlEl2);
if (v.betrag_netto) setBetragNettoWert(v.betrag_netto);
const txtEl = document.querySelector('input[name="buchungstext"]');
if (txtEl && v.buchungstext) txtEl.value = v.buchungstext;
// Lieferant aus KI-Vorschlag übernehmen
if (v.supplier_name) {
const bar = document.querySelector('[data-beleg-id]');
const persistentInput = document.getElementById('supplier-persistent-input');
const trigger = document.getElementById('supplier-inline-trigger');
if (persistentInput) {
persistentInput.value = v.supplier_name;
if (bar) bar.dataset.supplierName = v.supplier_name;
if (v.supplier_found === false) persistentInput.focus();
} else if (trigger) {
trigger.dataset.raw = v.supplier_name;
if (bar) bar.dataset.supplierName = v.supplier_name;
trigger.textContent = v.supplier_name;
if (v.supplier_found === false && !trigger.querySelector('input')) {
trigger.click();
}
}
}
}
// Kleiner Helfer: Element mit Klasse und Textinhalt (niemals HTML) erzeugen
function _kiEl(tag, className, text) {
const el = document.createElement(tag);
if (className) el.className = className;
if (text !== undefined) el.textContent = String(text);
return el;
}
function _showKiBadgeDone(arr) {
// arr ist immer ein Array (bereits normalisiert).
// Alle Werte stammen aus DB/KI bzw. aus Kontobezeichnungen und werden
// ausschließlich per textContent eingefügt (Fix #213: Stored XSS).
const v = arr[0];
const katOpt = _findOption(document.getElementById('kategorie_konto_id'), v.kategorie_konto_id);
const zhlOpt = _findOption(document.getElementById('zahlungskonto_id'), v.zahlungskonto_id);
const katTxt = katOpt ? katOpt.text : v.kategorie_konto_id;
const zhlTxt = zhlOpt ? zhlOpt.text : v.zahlungskonto_id;
// Erste Zeile (bereits ins Formular übertragen)
const details = _kiEl('span', 'ki-badge-details',
(v.buchungstyp ? v.buchungstyp + ' · ' : '') +
'Kategorie: ' + katTxt + ' · ' +
'Zahlung: ' + zhlTxt);
if (v.supplier_name) {
details.append(' · Lieferant: ' + v.supplier_name);
if (v.supplier_found === false) {
details.append(' ', _kiEl('span', 'ki-badge-warn', '(neu – bitte prüfen)'));
}
}
const inhalt = document.createElement('div');
inhalt.append(_kiEl('strong', '', 'KI-Vorschlag:'), ' ', details);
// Alle Buchungszeilen mit Beträgen anzeigen
const fmt = n => parseFloat(n).toLocaleString('de-DE', {minimumFractionDigits: 2, maximumFractionDigits: 2});
arr.forEach((z, i) => {
const satz = parseFloat(z.ust_satz) || 0;
const netto = parseFloat(z.betrag_netto) || 0;
const ust = Math.round(netto * satz / 100 * 100) / 100;
const brutto = Math.round((netto + ust) * 100) / 100;
const zeile = _kiEl('span', 'ki-badge-zeile',
'Zeile ' + (i + 1) + ': '
+ 'Netto ' + fmt(netto) + ' € | USt ' + satz + '% ' + fmt(ust) + ' € | Brutto ' + fmt(brutto) + ' €');
if (i > 0) zeile.append(' ', _kiEl('span', 'ki-badge-hinweis', '(bitte als weitere Buchung erfassen)'));
inhalt.append(document.createElement('br'), zeile);
});
const box = _kiEl('div', 'flash flash-info ki-badge-done');
box.append(inhalt);
if (_kiAktiv) {
const neuBtn = _kiEl('button', 'btn btn-secondary btn-sm ki-badge-btn', 'Neu generieren');
neuBtn.type = 'button';
neuBtn.addEventListener('click', _startKiAnalyse);
box.append(neuBtn);
}
document.getElementById('ki-badge').replaceChildren(box);
}
function _showKiBadgeNoResult() {
const neuBtn = _kiAktiv
? ' <button class="btn btn-secondary btn-sm" onclick="_startKiAnalyse()">Erneut versuchen</button>'
: '';
document.getElementById('ki-badge').innerHTML =
'<div class="flash flash-warning">Kein Kontierungsvorschlag generiert.' + neuBtn + '</div>';
}
function _showKiBadgeLoading() {
document.getElementById('ki-badge').innerHTML =
'<div class="flash flash-info" style="opacity:.65">Analysiere Beleg … bitte warten.</div>';
}
function _showKiBadgeError() {
document.getElementById('ki-badge').innerHTML =
'<div class="flash flash-warning">Fehler bei der KI-Analyse. ' +
'<button class="btn btn-secondary btn-sm" onclick="_startKiAnalyse()">Erneut versuchen</button></div>';
}
function _showKiBadgeConnectionError() {
const neuBtn = _kiAktiv
? ' <button class="btn btn-secondary btn-sm" onclick="_startKiAnalyse()">Erneut versuchen</button>'
: '';
document.getElementById('ki-badge').innerHTML =
'<div class="flash flash-warning">Ollama nicht erreichbar.' + neuBtn +
' <a href="/settings" style="color:inherit;text-decoration:underline">Einstellungen / Verbindung testen</a>' +
'</div>';
}
// 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.
function _normalizeKiArr(raw) {
if (raw === null || raw === undefined) return null;
const arr = Array.isArray(raw) ? raw : [raw];
// Leeres Array = "verarbeitet, kein Ergebnis"
if (arr.length === 0) return [];
// Mindestens erstes Element muss gültig sein
return (arr[0] && arr[0].kategorie_konto_id) ? arr : [];
}
async function _startKiAnalyse() {
await fetch('/belege/' + _belegId + '/vorschlag/reset', {method: 'POST'});
_showKiBadgeLoading();
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();
} catch (e) {
_showKiBadgeError();
}
}
document.addEventListener('DOMContentLoaded', function () {
const arr = _normalizeKiArr(_kiVorschlag);
if (arr === null) {
if (_kiAktiv) { _startKiAnalyse(); }
} else if (arr.length === 0) {
// Verarbeitet, aber kein Ergebnis
_showKiBadgeNoResult();
} else {
// Vorschlag vorhanden → ins Formular übernehmen und anzeigen
_applyKiVorschlag(arr[0]);
_showKiBadgeDone(arr);
}
});