From 744b4051420f6d0621d63c55fa21945f819e7830 Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 2 Mar 2026 06:22:33 +0100 Subject: [PATCH] fix: vendor info + rerun button on supplier invoices v2.2.21 - Fix get_files_by_status query: LATERAL join to get latest extraction per file, returning vendor_name, vendor_match_confidence, total_amount - Fix renderUnhandledFiles: use best_vendor_name, convert confidence 0-1 to %, use total_amount field - Add rerun button for all files (not just failed) via rerunSingleFile() - rerunSingleFile() calls /reprocess/{file_id} and reloads unhandled tab - Fix retry_extraction endpoint to actually run extraction immediately --- VERSION | 2 +- app/billing/backend/supplier_invoices.py | 47 +++++++++++++------- app/billing/frontend/supplier_invoices.html | 48 ++++++++++++++++----- 3 files changed, 70 insertions(+), 27 deletions(-) diff --git a/VERSION b/VERSION index 0c2c783..eae4a66 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.2.20 +2.2.21 diff --git a/app/billing/backend/supplier_invoices.py b/app/billing/backend/supplier_invoices.py index b717121..6cd5334 100644 --- a/app/billing/backend/supplier_invoices.py +++ b/app/billing/backend/supplier_invoices.py @@ -339,10 +339,22 @@ async def get_files_by_status(status: Optional[str] = None, limit: int = 100): SELECT f.file_id, f.filename, f.file_path, f.file_size, f.mime_type, f.status, f.uploaded_at, f.processed_at, f.detected_cvr, f.detected_vendor_id, v.name as detected_vendor_name, - e.total_amount as detected_amount + ext.vendor_name, + ext.vendor_cvr, + ext.vendor_matched_id, + COALESCE(v_ext.name, ext.vendor_name, v.name) as best_vendor_name, + ext.total_amount, + ext.confidence as vendor_match_confidence FROM incoming_files f LEFT JOIN vendors v ON f.detected_vendor_id = v.id - LEFT JOIN extractions e ON f.file_id = e.file_id + LEFT JOIN LATERAL ( + SELECT vendor_name, vendor_cvr, vendor_matched_id, total_amount, confidence + FROM extractions + WHERE file_id = f.file_id + ORDER BY created_at DESC + LIMIT 1 + ) ext ON true + LEFT JOIN vendors v_ext ON v_ext.id = ext.vendor_matched_id WHERE f.status IN ({placeholders}) ORDER BY f.uploaded_at DESC LIMIT %s @@ -353,10 +365,22 @@ async def get_files_by_status(status: Optional[str] = None, limit: int = 100): SELECT f.file_id, f.filename, f.file_path, f.file_size, f.mime_type, f.status, f.uploaded_at, f.processed_at, f.detected_cvr, f.detected_vendor_id, v.name as detected_vendor_name, - e.total_amount as detected_amount + ext.vendor_name, + ext.vendor_cvr, + ext.vendor_matched_id, + COALESCE(v_ext.name, ext.vendor_name, v.name) as best_vendor_name, + ext.total_amount, + ext.confidence as vendor_match_confidence FROM incoming_files f LEFT JOIN vendors v ON f.detected_vendor_id = v.id - LEFT JOIN extractions e ON f.file_id = e.file_id + LEFT JOIN LATERAL ( + SELECT vendor_name, vendor_cvr, vendor_matched_id, total_amount, confidence + FROM extractions + WHERE file_id = f.file_id + ORDER BY created_at DESC + LIMIT 1 + ) ext ON true + LEFT JOIN vendors v_ext ON v_ext.id = ext.vendor_matched_id ORDER BY f.uploaded_at DESC LIMIT %s """ @@ -3254,17 +3278,10 @@ async def retry_extraction(file_id: int): ) logger.info(f"🔄 Retrying extraction for file {file_id}: {file_data['filename']}") - - # Trigger re-analysis by calling the existing upload processing logic - # For now, just mark as pending - the user can then run batch-analyze - - return { - "file_id": file_id, - "filename": file_data['filename'], - "message": "File marked for re-analysis. Run batch-analyze to process.", - "previous_status": file_data['status'], - "new_status": "pending" - } + + # Run full extraction cascade immediately + result = await reprocess_uploaded_file(file_id) + return result except HTTPException: raise diff --git a/app/billing/frontend/supplier_invoices.html b/app/billing/frontend/supplier_invoices.html index 81ed1de..e8b8902 100644 --- a/app/billing/frontend/supplier_invoices.html +++ b/app/billing/frontend/supplier_invoices.html @@ -1812,9 +1812,10 @@ function renderUnhandledFiles(files) { for (const file of files) { const statusBadge = getFileStatusBadge(file.status); - const vendorName = file.detected_vendor_name || '-'; - const confidence = file.vendor_match_confidence ? `${file.vendor_match_confidence}%` : '-'; - const amount = file.detected_amount ? formatCurrency(file.detected_amount) : '-'; + const vendorName = file.best_vendor_name || file.vendor_name || file.detected_vendor_name || '-'; + const confRaw = file.vendor_match_confidence; + const confidence = confRaw !== null && confRaw !== undefined ? `${Math.round(confRaw * 100)}%` : '-'; + const amount = file.total_amount ? formatCurrency(file.total_amount) : '-'; const uploadDate = file.uploaded_at ? new Date(file.uploaded_at).toLocaleDateString('da-DK') : '-'; html += ` @@ -1842,14 +1843,9 @@ function renderUnhandledFiles(files) { ${statusBadge}
- ${file.status === 'extraction_failed' ? - `` : - `` - } + @@ -1965,6 +1961,36 @@ async function retryExtraction(fileId) { } } +// Rerun full extraction for a file in the unhandled tab +async function rerunSingleFile(fileId) { + try { + showLoadingOverlay('Kører analyse...'); + + const response = await fetch(`/api/v1/supplier-invoices/reprocess/${fileId}`, { + method: 'POST' + }); + + if (!response.ok) { + const err = await response.json().catch(() => ({})); + throw new Error(err.detail || 'Analyse fejlede'); + } + + const result = await response.json(); + hideLoadingOverlay(); + + const confPct = result.confidence ? Math.round(result.confidence * 100) + '%' : '?%'; + const vendorInfo = result.vendor_id ? `Leverandør matchet (ID ${result.vendor_id})` : 'Ingen leverandør matchet'; + alert(`✅ Analyse færdig\n${vendorInfo}\nConfidence: ${confPct}`); + + loadUnhandledFiles(); + + } catch (error) { + hideLoadingOverlay(); + console.error('Rerun error:', error); + alert('❌ Fejl ved analyse: ' + error.message); + } +} + // NEW: Analyze single file async function analyzeFile(fileId) { try {