+
-
@@ -1961,6 +2088,178 @@ async function retryExtraction(fileId) {
}
}
+// ─── Quick Vendor Split-View ─────────────────────────────────────────────
+async function openQuickVendorCreate(fileId, filename) {
+ // Reset
+ document.getElementById('qvFileId').value = fileId;
+ document.getElementById('qvExistingVendorId').value = '';
+ document.getElementById('qvSplitFilename').textContent = filename;
+ document.getElementById('qvName').value = '';
+ document.getElementById('qvCVR').value = '';
+ document.getElementById('qvEmail').value = '';
+ document.getElementById('qvPhone').value = '';
+ document.getElementById('qvAddress').value = '';
+ document.getElementById('qvPostal').value = '';
+ document.getElementById('qvCity').value = '';
+ document.getElementById('qvDomain').value = '';
+ document.getElementById('qvNotes').value = '';
+ document.getElementById('qvSearchInput').value = '';
+ document.getElementById('qvSearchResults').innerHTML = '
Søg for at finde eksisterende leverandør
';
+ document.getElementById('qvStatusAlert').className = 'alert d-none py-2 small';
+
+ // Load PDF in iframe
+ document.getElementById('qvPdfFrame').src = `/api/v1/supplier-invoices/files/${fileId}/download`;
+
+ // Open modal immediately
+ const modal = new bootstrap.Modal(document.getElementById('quickVendorSplitModal'), {backdrop: 'static'});
+ modal.show();
+
+ // Async: load extracted data and pre-fill form
+ try {
+ const resp = await fetch(`/api/v1/supplier-invoices/files/${fileId}/extracted-data`);
+ if (resp.ok) {
+ const data = await resp.json();
+ const ext = data.extraction || {};
+ let ai = {};
+ try { ai = JSON.parse(ext.llm_response_json || '{}'); } catch(e) {}
+
+ const name = ext.vendor_name || ai.vendor_name || '';
+ const cvr = (ext.vendor_cvr || ai.vendor_cvr || '').replace(/^DK/i, '').trim();
+ const addr = ai.vendor_address || '';
+
+ if (name) document.getElementById('qvName').value = name;
+ if (cvr) document.getElementById('qvCVR').value = cvr;
+ if (addr) {
+ // Try to split address into street / postal / city
+ const parts = addr.split(/,|\n/).map(s => s.trim()).filter(Boolean);
+ if (parts.length >= 1) document.getElementById('qvAddress').value = parts[0];
+ if (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;
+ }
+ }
+ }
+ }
+ } catch(e) {
+ console.warn('Could not pre-fill vendor form:', e);
+ }
+}
+
+async function qvSearchVendors(query) {
+ const results = document.getElementById('qvSearchResults');
+ if (!query || query.length < 2) {
+ results.innerHTML = '
Søg for at finde eksisterende leverandør
';
+ return;
+ }
+ try {
+ const resp = await fetch(`/api/v1/vendors?search=${encodeURIComponent(query)}&active_only=true`);
+ const vendors = await resp.json();
+ if (!vendors || vendors.length === 0) {
+ results.innerHTML = '
Ingen leverandører fundet
';
+ return;
+ }
+ results.innerHTML = vendors.slice(0, 10).map(v => `
+
+ `).join('');
+ } catch(e) {
+ results.innerHTML = '
Fejl ved søgning
';
+ }
+}
+
+function qvSelectVendor(vendorId, vendorName, vendorCVR) {
+ document.getElementById('qvExistingVendorId').value = vendorId;
+ document.getElementById('qvName').value = vendorName;
+ document.getElementById('qvCVR').value = vendorCVR;
+ const alert = document.getElementById('qvStatusAlert');
+ alert.className = 'alert alert-success py-2 small';
+ alert.textContent = `✅ Valgt: ${vendorName} — klik "Opret og link" for at linke`;
+}
+
+async function saveQuickVendor() {
+ const fileId = document.getElementById('qvFileId').value;
+ const existingId = document.getElementById('qvExistingVendorId').value;
+ const name = document.getElementById('qvName').value.trim();
+ const cvr = document.getElementById('qvCVR').value.trim();
+ const email = document.getElementById('qvEmail').value.trim();
+ const phone = document.getElementById('qvPhone').value.trim();
+ const address = document.getElementById('qvAddress').value.trim();
+ const postal = document.getElementById('qvPostal').value.trim();
+ const city = document.getElementById('qvCity').value.trim();
+ const domain = document.getElementById('qvDomain').value.trim();
+ const category = document.getElementById('qvCategory').value;
+ const notes = document.getElementById('qvNotes').value.trim();
+
+ const statusEl = document.getElementById('qvStatusAlert');
+
+ if (!name) {
+ statusEl.className = 'alert alert-danger py-2 small';
+ statusEl.textContent = 'Navn er påkrævet.';
+ return;
+ }
+
+ statusEl.className = 'alert alert-info py-2 small';
+ statusEl.textContent = 'Gemmer…';
+
+ try {
+ let vendorId = existingId ? parseInt(existingId) : null;
+
+ if (!vendorId) {
+ // Create new vendor
+ const payload = {
+ name, cvr_number: cvr || null,
+ email: email || null, phone: phone || null,
+ address: [address, postal && city ? `${postal} ${city}` : city].filter(Boolean).join('\n') || null,
+ postal_code: postal || null, city: city || null,
+ domain: domain || null, category,
+ notes: notes || null
+ };
+ const resp = await fetch('/api/v1/vendors', {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify(payload)
+ });
+ if (!resp.ok) {
+ const err = await resp.json().catch(() => ({}));
+ throw new Error(err.detail || 'Oprettelse fejlede');
+ }
+ const created = await resp.json();
+ vendorId = created.id;
+ }
+
+ // Link vendor to file
+ const linkResp = await fetch(`/api/v1/supplier-invoices/files/${fileId}/link-vendor`, {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({vendor_id: vendorId})
+ });
+ if (!linkResp.ok) {
+ const err = await linkResp.json().catch(() => ({}));
+ throw new Error(err.detail || 'Link fejlede');
+ }
+
+ statusEl.className = 'alert alert-success py-2 small';
+ statusEl.textContent = `✅ Leverandør ${existingId ? 'linket' : 'oprettet og linket'}!`;
+
+ setTimeout(() => {
+ bootstrap.Modal.getInstance(document.getElementById('quickVendorSplitModal')).hide();
+ loadUnhandledFiles();
+ }, 900);
+
+ } catch(e) {
+ statusEl.className = 'alert alert-danger py-2 small';
+ statusEl.textContent = '❌ ' + e.message;
+ }
+}
+
// Rerun full extraction for a file in the unhandled tab
async function rerunSingleFile(fileId) {
try {