diff --git a/app/billing/frontend/supplier_invoices.html b/app/billing/frontend/supplier_invoices.html
index bc61eac..b05d892 100644
--- a/app/billing/frontend/supplier_invoices.html
+++ b/app/billing/frontend/supplier_invoices.html
@@ -2118,113 +2118,63 @@ async function openQuickVendorCreate(fileId, filename) {
await qvLoadAndPrefill(fileId);
}
-async function qvLoadAndPrefill(fileId) {
+async function qvLoadAndPrefill(fileId, isRetry) {
const statusEl = document.getElementById('qvStatusAlert');
+ statusEl.className = 'alert alert-info py-2 small';
+ statusEl.innerHTML = 'Henter fakturadata…';
+
try {
const resp = await fetch(`/api/v1/supplier-invoices/files/${fileId}/extracted-data`);
if (!resp.ok) { throw new Error(`HTTP ${resp.status}`); }
const data = await resp.json();
- // Brug det server-normaliserede llm_data objekt som primær kilde
+ console.log('[QV] extracted-data response:', JSON.stringify({
+ file_id: data.file_id,
+ status: data.status,
+ has_extraction: !!data.extraction,
+ has_llm_data: !!data.llm_data,
+ llm_data: data.llm_data,
+ extraction_vendor_name: data.extraction?.vendor_name,
+ extraction_vendor_cvr: data.extraction?.vendor_cvr,
+ }));
+
+ // Normaliseret data fra server (backend bygger llm_data med rigtige feltnavne)
const ld = data.llm_data || {};
const ext = data.extraction || {};
- // llm_response_json kan være objekt (JSONB) eller string – parse sikkert som fallback
+ // llm_response_json: kan være JSONB-objekt eller string
let rawAi = {};
const rawLlm = ext.llm_response_json;
if (rawLlm) {
- if (typeof rawLlm === 'string') {
- try { rawAi = JSON.parse(rawLlm); } catch(e) {}
- } else if (typeof rawLlm === 'object') {
- rawAi = rawLlm;
- }
+ rawAi = (typeof rawLlm === 'string') ? (() => { try { return JSON.parse(rawLlm); } catch(e) { return {}; } })() : rawLlm;
}
+ console.log('[QV] rawAi keys:', Object.keys(rawAi).join(', ') || '(tom)');
- // Prioriter llm_data (server-normaliseret) → extraction kolonne → raw AI JSON
- const name = ld.vendor_name || ext.vendor_name || rawAi.vendor_name || rawAi.issuer || '';
- const cvr = (ld.vendor_cvr || ext.vendor_cvr || rawAi.vendor_cvr || rawAi.vendor_vat || '').toString().replace(/^DK/i, '').trim();
- const email = ld.vendor_email || rawAi.vendor_email || rawAi.supplier_email || '';
- const addr = ld.vendor_address || rawAi.vendor_address || rawAi.supplier_address || rawAi.vendor_street || '';
+ // Hent vendor-felter fra alle 3 kilder i prioriteret rækkefølge
+ const name = ld.vendor_name || ext.vendor_name || rawAi.vendor_name || rawAi.issuer || '';
+ const cvr = (ld.vendor_cvr || ext.vendor_cvr || rawAi.vendor_cvr || rawAi.vendor_vat || '').toString().replace(/^DK/i, '').trim();
+ const email = ld.vendor_email || rawAi.vendor_email || rawAi.supplier_email || '';
+ const addr = ld.vendor_address || rawAi.vendor_address || rawAi.supplier_address || rawAi.vendor_street || '';
const postal = ld.vendor_postal_code || rawAi.vendor_postal_code || rawAi.postal_code || '';
- const city = ld.vendor_city || rawAi.vendor_city || rawAi.city || '';
+ const city = ld.vendor_city || rawAi.vendor_city || rawAi.city || '';
- // Ingen data endnu – start automatisk reprocess
- if (!name && !cvr && !ext) {
- await qvAutoReprocess(fileId);
- return;
- }
- // Har extraction-række men ingen vendordata – prøv reprocess én gang
- if (!name && !cvr && !data._reprocessed) {
+ console.log('[QV] Parsed fields:', { name, cvr, email, addr, postal, city });
+
+ // Ingen extraction i DB overhovedet (fil aldrig kørt) → auto-reprocess
+ if (!data.extraction && !isRetry) {
+ console.log('[QV] Ingen extraction – starter auto-reprocess');
await qvAutoReprocess(fileId);
return;
}
- if (name) document.getElementById('qvName').value = name;
- if (cvr) document.getElementById('qvCVR').value = cvr;
- if (email) document.getElementById('qvEmail').value = email;
-
- if (addr) {
- const parts = addr.split(/,|\n/).map(s => s.trim()).filter(Boolean);
- if (parts.length >= 1) document.getElementById('qvAddress').value = parts[0];
- if (!postal && !city && parts.length >= 2) {
- const postalCity = parts[parts.length - 1];
- const m = postalCity.match(/^(\d{4})\s+(.+)$/);
- if (m) {
- document.getElementById('qvPostal').value = m[1];
- document.getElementById('qvCity').value = m[2];
- } else {
- document.getElementById('qvCity').value = postalCity;
- }
- }
+ // Extraction findes men ingen vendor data → tilbyd reprocess
+ if (!name && !cvr && !isRetry) {
+ console.log('[QV] Extraction uden vendor data – starter auto-reprocess');
+ await qvAutoReprocess(fileId);
+ return;
}
- if (postal) document.getElementById('qvPostal').value = postal;
- if (city) document.getElementById('qvCity').value = city;
-
- if (name || cvr) {
- statusEl.className = 'alert alert-success py-2 small';
- statusEl.textContent = `✅ Data hentet fra faktura${name ? ': ' + name : ''}`;
- setTimeout(() => { statusEl.className = 'alert d-none py-2 small'; }, 3000);
- } else {
- statusEl.className = 'alert alert-warning py-2 small';
- statusEl.innerHTML = 'AI fandt ingen leverandørdata (fx mangler CVR på fakturaen). Udfyld manuelt eller søg herover.';
- }
- } catch(e) {
- console.warn('Could not pre-fill vendor form:', e);
- statusEl.className = 'alert alert-danger py-2 small';
- statusEl.textContent = 'Fejl ved hentning af fakturadata.';
- }
-}
-
-async function qvAutoReprocess(fileId) {
- const statusEl = document.getElementById('qvStatusAlert');
- statusEl.className = 'alert alert-info py-2 small';
- statusEl.innerHTML = 'Analyserer faktura med AI – vent venligst…';
-
- try {
- const r = await fetch(`/api/v1/supplier-invoices/reprocess/${fileId}`, { method: 'POST' });
- if (!r.ok) { throw new Error(`Reprocess HTTP ${r.status}`); }
-
- // Reload data efter reprocess og marker at vi allerede har forsøgt
- const resp2 = await fetch(`/api/v1/supplier-invoices/files/${fileId}/extracted-data`);
- if (!resp2.ok) { throw new Error(`Extracted-data HTTP ${resp2.status}`); }
- const data2 = await resp2.json();
- data2._reprocessed = true; // forhindrer uendelig løkke
-
- const ld = data2.llm_data || {};
- const ext = data2.extraction || {};
- let rawAi = {};
- const rawLlm = ext.llm_response_json;
- if (rawLlm) {
- if (typeof rawLlm === 'string') { try { rawAi = JSON.parse(rawLlm); } catch(e) {} }
- else if (typeof rawLlm === 'object') { rawAi = rawLlm; }
- }
- const name = ld.vendor_name || ext.vendor_name || rawAi.vendor_name || rawAi.issuer || '';
- const cvr = (ld.vendor_cvr || ext.vendor_cvr || rawAi.vendor_cvr || rawAi.vendor_vat || '').toString().replace(/^DK/i, '').trim();
- const email = ld.vendor_email || rawAi.vendor_email || '';
- const addr = ld.vendor_address || rawAi.vendor_address || rawAi.vendor_street || '';
- const postal = ld.vendor_postal_code || rawAi.postal_code || '';
- const city = ld.vendor_city || rawAi.city || '';
+ // Udfyld form
if (name) document.getElementById('qvName').value = name;
if (cvr) document.getElementById('qvCVR').value = cvr;
if (email) document.getElementById('qvEmail').value = email;
@@ -2243,17 +2193,45 @@ async function qvAutoReprocess(fileId) {
if (name || cvr) {
statusEl.className = 'alert alert-success py-2 small';
- statusEl.textContent = `✅ AI-analyse færdig: ${name || cvr}`;
- setTimeout(() => { statusEl.className = 'alert d-none py-2 small'; }, 3000);
+ statusEl.textContent = `✅ Data hentet${name ? ': ' + name : ''}${cvr ? ' (' + cvr + ')' : ''}`;
+ setTimeout(() => { statusEl.className = 'alert d-none py-2 small'; }, 4000);
} else {
+ // AI fandt ingen vendor – men vis hvad der er (fakturanr, beløb)
+ const inv = ld.invoice_number || rawAi.invoice_number || '';
+ const amt = ld.total_amount || rawAi.total_amount || '';
statusEl.className = 'alert alert-warning py-2 small';
- statusEl.innerHTML = 'AI fandt ingen leverandørdata (fx mangler CVR på fakturaen). Udfyld manuelt eller søg herover.';
+ statusEl.innerHTML = `AI fandt ingen leverandørdata${inv ? ' (Faktura ' + inv + (amt ? ', ' + amt + ' DKK' : '') + ')' : ''}. Udfyld navn manuelt eller søg herover.`;
}
- loadUnhandledFiles(); // opdater tabel
} catch(e) {
- console.warn('Auto-reprocess failed:', e);
+ console.error('[QV] Fejl:', e);
+ statusEl.className = 'alert alert-danger py-2 small';
+ statusEl.textContent = 'Fejl ved hentning: ' + e.message;
+ }
+}
+
+async function qvAutoReprocess(fileId) {
+ const statusEl = document.getElementById('qvStatusAlert');
+ statusEl.className = 'alert alert-info py-2 small';
+ statusEl.innerHTML = 'Analyserer faktura med AI – vent venligst…';
+
+ try {
+ console.log('[QV] Starter reprocess for file:', fileId);
+ const r = await fetch(`/api/v1/supplier-invoices/reprocess/${fileId}`, { method: 'POST' });
+ if (!r.ok) {
+ const errBody = await r.text();
+ console.error('[QV] Reprocess fejlede:', r.status, errBody);
+ throw new Error(`Reprocess HTTP ${r.status}: ${errBody}`);
+ }
+ const reprocessResult = await r.json();
+ console.log('[QV] Reprocess result:', JSON.stringify(reprocessResult));
+
+ // Hent opdateret data med isRetry=true for at undgå uendelig løkke
+ await qvLoadAndPrefill(fileId, true);
+ loadUnhandledFiles();
+ } catch(e) {
+ console.error('[QV] Auto-reprocess fejl:', e);
statusEl.className = 'alert alert-warning py-2 small';
- statusEl.innerHTML = 'Kunne ikke køre AI-analyse automatisk. Klik ↺ i tabellen og prøv igen.';
+ statusEl.innerHTML = `Kunne ikke køre AI-analyse: ${e.message}. `;
}
}