189 lines
7.9 KiB
JavaScript
189 lines
7.9 KiB
JavaScript
'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);
|
||
}
|
||
});
|