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}. `; } }