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
This commit is contained in:
Christian 2026-03-02 06:22:33 +01:00
parent ea4905ef8a
commit 744b405142
3 changed files with 70 additions and 27 deletions

View File

@ -1 +1 @@
2.2.20 2.2.21

View File

@ -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, 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.status, f.uploaded_at, f.processed_at, f.detected_cvr,
f.detected_vendor_id, v.name as detected_vendor_name, 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 FROM incoming_files f
LEFT JOIN vendors v ON f.detected_vendor_id = v.id 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}) WHERE f.status IN ({placeholders})
ORDER BY f.uploaded_at DESC ORDER BY f.uploaded_at DESC
LIMIT %s 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, 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.status, f.uploaded_at, f.processed_at, f.detected_cvr,
f.detected_vendor_id, v.name as detected_vendor_name, 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 FROM incoming_files f
LEFT JOIN vendors v ON f.detected_vendor_id = v.id 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 ORDER BY f.uploaded_at DESC
LIMIT %s LIMIT %s
""" """
@ -3255,16 +3279,9 @@ async def retry_extraction(file_id: int):
logger.info(f"🔄 Retrying extraction for file {file_id}: {file_data['filename']}") logger.info(f"🔄 Retrying extraction for file {file_id}: {file_data['filename']}")
# Trigger re-analysis by calling the existing upload processing logic # Run full extraction cascade immediately
# For now, just mark as pending - the user can then run batch-analyze result = await reprocess_uploaded_file(file_id)
return result
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"
}
except HTTPException: except HTTPException:
raise raise

View File

@ -1812,9 +1812,10 @@ function renderUnhandledFiles(files) {
for (const file of files) { for (const file of files) {
const statusBadge = getFileStatusBadge(file.status); const statusBadge = getFileStatusBadge(file.status);
const vendorName = file.detected_vendor_name || '-'; const vendorName = file.best_vendor_name || file.vendor_name || file.detected_vendor_name || '-';
const confidence = file.vendor_match_confidence ? `${file.vendor_match_confidence}%` : '-'; const confRaw = file.vendor_match_confidence;
const amount = file.detected_amount ? formatCurrency(file.detected_amount) : '-'; 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') : '-'; const uploadDate = file.uploaded_at ? new Date(file.uploaded_at).toLocaleDateString('da-DK') : '-';
html += ` html += `
@ -1842,14 +1843,9 @@ function renderUnhandledFiles(files) {
<td>${statusBadge}</td> <td>${statusBadge}</td>
<td> <td>
<div class="btn-group btn-group-sm"> <div class="btn-group btn-group-sm">
${file.status === 'extraction_failed' ? <button class="btn btn-outline-warning" onclick="rerunSingleFile(${file.file_id})" title="Kør analyse igen">
`<button class="btn btn-outline-warning" onclick="retryExtraction(${file.file_id})" title="Prøv igen"> <i class="bi bi-arrow-repeat"></i>
<i class="bi bi-arrow-clockwise"></i> </button>
</button>` :
`<button class="btn btn-outline-primary" onclick="analyzeFile(${file.file_id})" title="Analyser">
<i class="bi bi-search"></i>
</button>`
}
<button class="btn btn-outline-secondary" onclick="viewFilePDF(${file.file_id})" title="Vis PDF"> <button class="btn btn-outline-secondary" onclick="viewFilePDF(${file.file_id})" title="Vis PDF">
<i class="bi bi-file-pdf"></i> <i class="bi bi-file-pdf"></i>
</button> </button>
@ -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 // NEW: Analyze single file
async function analyzeFile(fileId) { async function analyzeFile(fileId) {
try { try {