2025-12-08 09:15:52 +01:00
{% extends "shared/frontend/base.html" %}
2025-12-07 03:29:54 +01:00
2025-12-08 09:15:52 +01:00
{% block title %}Kassekladde - BMC Hub{% endblock %}
2025-12-07 03:29:54 +01:00
2025-12-08 09:15:52 +01:00
{% block extra_css %}
< style >
.stat-card {
text-align: center;
padding: 1.5rem;
}
2025-12-07 03:29:54 +01:00
2025-12-08 09:15:52 +01:00
.stat-card h3 {
font-size: 2rem;
font-weight: 700;
margin-bottom: 0;
}
2025-12-07 03:29:54 +01:00
2025-12-08 09:15:52 +01:00
.stat-card p {
color: var(--text-secondary);
font-size: 0.9rem;
margin-bottom: 0;
}
2025-12-07 03:29:54 +01:00
2025-12-08 09:15:52 +01:00
.stat-card.overdue h3 { color: var(--danger); }
.stat-card.due-soon h3 { color: var(--warning); }
.stat-card.pending h3 { color: var(--accent); }
.stat-card.total h3 { color: var(--text-secondary); }
2025-12-07 03:29:54 +01:00
2025-12-08 09:15:52 +01:00
.badge {
padding: 0.4rem 0.8rem;
border-radius: 6px;
font-weight: 500;
}
2025-12-07 03:29:54 +01:00
2025-12-08 09:15:52 +01:00
.status-pending { background-color: #ffc107; color: #000; }
.status-approved { background-color: #17a2b8; color: #fff; }
.status-sent { background-color: var(--success); color: #fff; }
.status-paid { background-color: #6c757d; color: #fff; }
.status-overdue { background-color: var(--danger); color: #fff; }
.status-unpaid { background-color: #ffc107; color: #000; }
.line-item {
background: var(--accent-light);
padding: 1rem;
border-radius: 8px;
margin-bottom: 0.5rem;
}
2025-12-07 03:29:54 +01:00
2025-12-08 09:15:52 +01:00
.filter-pills {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
margin-bottom: 1rem;
}
2025-12-07 03:29:54 +01:00
2025-12-08 09:15:52 +01:00
.filter-pill {
padding: 0.5rem 1rem;
border-radius: 20px;
border: 1px solid #dee2e6;
background: var(--bg-card);
cursor: pointer;
transition: all 0.2s;
font-size: 0.9rem;
}
2025-12-07 03:29:54 +01:00
2025-12-08 09:15:52 +01:00
.filter-pill:hover, .filter-pill.active {
background: var(--accent);
color: white;
border-color: var(--accent);
}
2025-12-07 03:29:54 +01:00
2025-12-08 09:15:52 +01:00
/* Tab Navigation */
.nav-tabs {
border-bottom: 2px solid #dee2e6;
}
2025-12-07 03:29:54 +01:00
2025-12-08 09:15:52 +01:00
.nav-tabs .nav-link {
color: var(--text-secondary);
border: none;
border-bottom: 3px solid transparent;
padding: 0.75rem 1.5rem;
font-weight: 500;
transition: all 0.2s;
}
2025-12-07 03:29:54 +01:00
2025-12-08 09:15:52 +01:00
.nav-tabs .nav-link:hover {
border-color: var(--accent-light);
color: var(--accent);
}
2025-12-07 03:29:54 +01:00
2025-12-08 09:15:52 +01:00
.nav-tabs .nav-link.active {
color: var(--accent);
border-bottom-color: var(--accent);
background: none;
}
2025-12-07 03:29:54 +01:00
2025-12-08 09:15:52 +01:00
/* Pending Files Status Badges */
.status-ai_extracted { background-color: #17a2b8; color: #fff; }
.status-processing { background-color: #6c757d; color: #fff; }
.status-failed { background-color: var(--danger); color: #fff; }
.status-completed { background-color: var(--success); color: #fff; }
< / style >
{% endblock %}
2025-12-07 03:29:54 +01:00
2025-12-08 09:15:52 +01:00
{% block content %}
2025-12-07 03:29:54 +01:00
<!-- Page Header -->
< div class = "d-flex justify-content-between align-items-center mb-4" >
< div >
< h2 class = "mb-1" > 📋 Leverandørfakturaer< / h2 >
< p class = "text-muted mb-0" > Kassekladde - Integration med e-conomic< / p >
< / div >
< div >
< a href = "/billing/templates" class = "btn btn-outline-secondary me-2" >
< i class = "bi bi-grid-3x3 me-2" > < / i > Se Templates
< / a >
< a href = "/billing/template-builder" class = "btn btn-outline-primary me-2" >
< i class = "bi bi-puzzle me-2" > < / i > Template Builder
< / a >
< button class = "btn btn-success" onclick = "openUploadModal()" >
< i class = "bi bi-cloud-upload me-2" > < / i > Upload Faktura
< / button >
< / div >
< / div >
<!-- Statistics Cards -->
< div class = "row mb-4" id = "statsCards" >
< div class = "col-md-3" >
< div class = "card stat-card overdue" >
< h3 id = "statOverdueCount" > -< / h3 >
< p > Overskredet< / p >
< small class = "text-muted" id = "statOverdueAmount" > -< / small >
< / div >
< / div >
< div class = "col-md-3" >
< div class = "card stat-card due-soon" >
< h3 id = "statDueSoonCount" > -< / h3 >
< p > Forfald inden 7 dage< / p >
< small class = "text-muted" id = "statDueSoonAmount" > -< / small >
< / div >
< / div >
< div class = "col-md-3" >
< div class = "card stat-card pending" >
< h3 id = "statPendingCount" > -< / h3 >
< p > Afventer behandling< / p >
< small class = "text-muted" id = "statPendingAmount" > -< / small >
< / div >
< / div >
< div class = "col-md-3" >
< div class = "card stat-card total" >
< h3 id = "statTotalCount" > -< / h3 >
< p > Ubetalt i alt< / p >
< small class = "text-muted" id = "statUnpaidAmount" > -< / small >
< / div >
< / div >
< / div >
2025-12-08 09:15:52 +01:00
<!-- Tab Navigation -->
< ul class = "nav nav-tabs mb-4" id = "mainTabs" >
< li class = "nav-item" >
< a class = "nav-link active" id = "invoices-tab" data-bs-toggle = "tab" href = "#invoices-content" onclick = "switchToInvoicesTab()" >
< i class = "bi bi-receipt me-2" > < / i > Fakturaer
< / a >
< / li >
< li class = "nav-item" >
< a class = "nav-link" id = "pending-files-tab" data-bs-toggle = "tab" href = "#pending-files-content" onclick = "switchToPendingFilesTab()" >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< i class = "bi bi-hourglass-split me-2" > < / i > Mangler Behandling
2025-12-08 09:15:52 +01:00
< span class = "badge bg-warning text-dark ms-2" id = "pendingFilesCount" style = "display: none;" > 0< / span >
< / a >
< / li >
< / ul >
<!-- Tab Content -->
< div class = "tab-content" id = "mainTabContent" >
<!-- Invoices Tab -->
< div class = "tab-pane fade show active" id = "invoices-content" >
<!-- Filters -->
< div class = "card mb-4" >
< div class = "card-body" >
< div class = "filter-pills" >
< div class = "filter-pill active" data-filter = "all" onclick = "applyFilter('all', this)" >
< i class = "bi bi-list-ul me-1" > < / i > Alle
< / div >
< div class = "filter-pill" data-filter = "pending" onclick = "applyFilter('pending', this)" >
< i class = "bi bi-clock me-1" > < / i > Afventer
< / div >
< div class = "filter-pill" data-filter = "approved" onclick = "applyFilter('approved', this)" >
< i class = "bi bi-check-circle me-1" > < / i > Godkendt
< / div >
< div class = "filter-pill" data-filter = "sent_to_economic" onclick = "applyFilter('sent_to_economic', this)" >
< i class = "bi bi-send me-1" > < / i > Sendt til e-conomic
< / div >
< div class = "filter-pill" data-filter = "overdue" onclick = "applyFilter('overdue', this)" >
< i class = "bi bi-exclamation-triangle me-1" > < / i > Overskredet
< / div >
< / div >
2025-12-07 03:29:54 +01:00
< / div >
2025-12-08 09:15:52 +01:00
< / div >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
<!-- Bulk Actions Bar for Invoices -->
< div class = "alert alert-light border mb-3" id = "invoiceBulkActionsBar" style = "display: none;" >
< div class = "d-flex align-items-center justify-content-between" >
< div >
< strong > < span id = "selectedInvoicesCount" > 0< / span > fakturaer valgt< / strong >
< / div >
< div class = "btn-group" role = "group" >
< button type = "button" class = "btn btn-sm btn-outline-primary" onclick = "bulkSendToEconomic()" title = "Send til e-conomic kassekladde" >
< i class = "bi bi-send me-1" > < / i > Send til Kassekladde
< / button >
< button type = "button" class = "btn btn-sm btn-outline-warning" onclick = "bulkResetInvoices()" title = "Nulstil til afventer behandling" >
< i class = "bi bi-arrow-counterclockwise me-1" > < / i > Reset
< / button >
< button type = "button" class = "btn btn-sm btn-outline-success" onclick = "bulkMarkAsPaid()" title = "Marker som betalt" >
< i class = "bi bi-cash-coin me-1" > < / i > Marker Betalt
< / button >
< / div >
< / div >
< / div >
2025-12-08 09:15:52 +01:00
<!-- Invoices Table -->
< div class = "card" >
< div class = "card-body" >
< div class = "table-responsive" >
< table class = "table table-hover" >
< thead >
< tr >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< th style = "width: 40px;" >
< input type = "checkbox" class = "form-check-input" id = "selectAllInvoices" onchange = "toggleSelectAllInvoices()" >
< / th >
2025-12-08 09:15:52 +01:00
< th > Fakturanr.< / th >
< th > Leverandør< / th >
< th > Fakturadato< / th >
< th > Forfaldsdato< / th >
< th > Beløb< / th >
< th > Status< / th >
< th > e-conomic< / th >
< th > Handlinger< / th >
< / tr >
< / thead >
< tbody id = "invoicesTable" >
< tr >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< td colspan = "9" class = "text-center py-4" >
2025-12-08 09:15:52 +01:00
< div class = "spinner-border text-primary" role = "status" >
< span class = "visually-hidden" > Indlæser...< / span >
< / div >
< / td >
< / tr >
< / tbody >
< / table >
< / div >
2025-12-07 03:29:54 +01:00
< / div >
< / div >
2025-12-08 09:15:52 +01:00
2025-12-07 03:29:54 +01:00
< / div >
2025-12-08 09:15:52 +01:00
<!-- Pending Files Tab -->
< div class = "tab-pane fade" id = "pending-files-content" >
<!-- Pending Files Table -->
< div class = "card" >
< div class = "card-body" >
< div class = "d-flex justify-content-between align-items-center mb-3" >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< h5 class = "mb-0" > ⏳ Filer der mangler behandling< / h5 >
2025-12-08 09:15:52 +01:00
< button class = "btn btn-sm btn-outline-primary" onclick = "loadPendingFiles()" >
< i class = "bi bi-arrow-clockwise me-2" > < / i > Opdater
< / button >
< / div >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
<!-- Bulk Actions Bar -->
< div class = "alert alert-light border mb-3" id = "bulkActionsBar" style = "display: none;" >
< div class = "d-flex align-items-center justify-content-between" >
< div >
< strong > < span id = "selectedFilesCount" > 0< / span > filer valgt< / strong >
< / div >
< div class = "btn-group" role = "group" >
< button type = "button" class = "btn btn-sm btn-outline-success" onclick = "bulkCreateInvoices()" title = "Opret fakturaer" >
< i class = "bi bi-plus-circle me-1" > < / i > Opret Fakturaer
< / button >
< button type = "button" class = "btn btn-sm btn-outline-primary" onclick = "bulkReprocess()" title = "Genbehandle filer" >
< i class = "bi bi-arrow-clockwise me-1" > < / i > Genbehandle
< / button >
< button type = "button" class = "btn btn-sm btn-outline-danger" onclick = "bulkDelete()" title = "Slet filer" >
< i class = "bi bi-trash me-1" > < / i > Slet
< / button >
< / div >
< / div >
< / div >
2025-12-08 09:15:52 +01:00
< div class = "table-responsive" >
< table class = "table table-hover" >
< thead >
< tr >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< th style = "width: 40px;" >
< input type = "checkbox" class = "form-check-input" id = "selectAllFiles" onchange = "toggleSelectAll()" >
< / th >
2025-12-08 09:15:52 +01:00
< th > Filnavn< / th >
< th > Upload Dato< / th >
< th > Status< / th >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< th > Quick Analysis< / th >
2025-12-08 09:15:52 +01:00
< th > Leverandør< / th >
< th > Template< / th >
< th > Handlinger< / th >
< / tr >
< / thead >
< tbody id = "pendingFilesTable" >
< tr >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< td colspan = "8" class = "text-center py-4" >
2025-12-08 09:15:52 +01:00
< div class = "spinner-border text-primary" role = "status" >
< span class = "visually-hidden" > Indlæser...< / span >
< / div >
< / td >
< / tr >
< / tbody >
< / table >
< / div >
< / div >
2025-12-07 03:29:54 +01:00
< / div >
2025-12-08 09:15:52 +01:00
2025-12-07 03:29:54 +01:00
< / div >
2025-12-08 09:15:52 +01:00
2025-12-07 03:29:54 +01:00
< / div >
< / div >
<!-- Create/Edit Invoice Modal -->
< div class = "modal fade" id = "invoiceModal" tabindex = "-1" >
< div class = "modal-dialog modal-lg" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" > Ny Leverandørfaktura< / h5 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" > < / button >
< / div >
< div class = "modal-body" >
< form id = "invoiceForm" >
< input type = "hidden" id = "invoiceId" >
< div class = "row mb-3" >
< div class = "col-md-6" >
< label class = "form-label" > Fakturanummer *< / label >
< input type = "text" class = "form-control" id = "invoiceNumber" required >
< / div >
< div class = "col-md-6" >
< label class = "form-label" > Leverandør *< / label >
< select class = "form-select" id = "vendorId" required >
< option value = "" > Vælg leverandør...< / option >
< / select >
< / div >
< / div >
< div class = "row mb-3" >
< div class = "col-md-6" >
< label class = "form-label" > Fakturadato *< / label >
< input type = "date" class = "form-control" id = "invoiceDate" required >
< / div >
< div class = "col-md-6" >
< label class = "form-label" > Forfaldsdato< / label >
< input type = "date" class = "form-control" id = "dueDate" >
< / div >
< / div >
< div class = "row mb-3" >
< div class = "col-md-4" >
< label class = "form-label" > Total beløb (inkl. moms) *< / label >
< input type = "number" step = "0.01" class = "form-control" id = "totalAmount" required >
< / div >
< div class = "col-md-4" >
< label class = "form-label" > Moms beløb< / label >
< input type = "number" step = "0.01" class = "form-control" id = "vatAmount" >
< / div >
< div class = "col-md-4" >
< label class = "form-label" > Valuta< / label >
< select class = "form-select" id = "currency" >
< option value = "DKK" selected > DKK< / option >
< option value = "EUR" > EUR< / option >
< option value = "USD" > USD< / option >
< / select >
< / div >
< / div >
< div class = "mb-3" >
< label class = "form-label" > Beskrivelse< / label >
< textarea class = "form-control" id = "description" rows = "3" > < / textarea >
< / div >
< div class = "mb-3" >
< label class = "form-label" > Noter (interne)< / label >
< textarea class = "form-control" id = "notes" rows = "2" > < / textarea >
< / div >
<!-- Line Items -->
< div class = "mb-3" >
< div class = "d-flex justify-content-between align-items-center mb-2" >
< label class = "form-label mb-0" > Linjer< / label >
< button type = "button" class = "btn btn-sm btn-outline-primary" onclick = "addLine()" >
< i class = "bi bi-plus-circle me-1" > < / i > Tilføj linje
< / button >
< / div >
< div id = "lineItems" >
<!-- Lines will be added dynamically -->
< / div >
< / div >
< / form >
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn btn-secondary" data-bs-dismiss = "modal" > Annuller< / button >
< button type = "button" class = "btn btn-primary" onclick = "saveInvoice()" > Gem< / button >
< / div >
< / div >
< / div >
< / div >
<!-- View Invoice Details Modal -->
< div class = "modal fade" id = "viewInvoiceModal" tabindex = "-1" >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< div class = "modal-dialog modal-xl" >
2025-12-07 03:29:54 +01:00
< div class = "modal-content" >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< div class = "modal-header bg-light" >
< h5 class = "modal-title" > < i class = "bi bi-receipt me-2" > < / i > Fakturadetaljer< / h5 >
2025-12-07 03:29:54 +01:00
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" > < / button >
< / div >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< div class = "modal-body" id = "invoiceDetails" style = "max-height: 75vh; overflow-y: auto;" >
2025-12-07 03:29:54 +01:00
<!-- Details will be loaded dynamically -->
< / div >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< div class = "modal-footer bg-light" >
2025-12-07 03:29:54 +01:00
< button type = "button" class = "btn btn-secondary" data-bs-dismiss = "modal" > Luk< / button >
< button type = "button" class = "btn btn-success" id = "approveBtn" onclick = "approveInvoice()" >
< i class = "bi bi-check-circle me-1" > < / i > Godkend
< / button >
< button type = "button" class = "btn btn-primary" id = "sendToEconomicBtn" onclick = "sendToEconomic()" >
< i class = "bi bi-send me-1" > < / i > Send til e-conomic
< / button >
< / div >
< / div >
< / div >
< / div >
<!-- Upload Invoice Modal -->
< div class = "modal fade" id = "uploadModal" tabindex = "-1" >
< div class = "modal-dialog modal-lg" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" > < i class = "bi bi-cloud-upload me-2" > < / i > Upload Leverandørfaktura< / h5 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" > < / button >
< / div >
< div class = "modal-body" >
< div class = "alert alert-info" >
< i class = "bi bi-info-circle me-2" > < / i >
< strong > AI-Analyse:< / strong > Systemet vil automatisk udtrække fakturadata (CVR, beløb, linjer) fra PDF/billede via AI.
Leverandør matches automatisk via CVR nummer.
< / div >
< form id = "uploadForm" enctype = "multipart/form-data" >
< div class = "mb-3" >
2025-12-08 09:15:52 +01:00
< label class = "form-label" > Vælg filer (PDF, PNG, JPG) *< / label >
< input type = "file" class = "form-control" id = "fileInput" accept = ".pdf,.png,.jpg,.jpeg" multiple required >
< div class = "form-text" > Max 50 MB pr. fil. Vælg flere filer med Cmd/Ctrl. AI vil udtrække CVR, fakturanummer, beløb og linjer.< / div >
< div id = "fileCount" class = "mt-2 text-muted small" > < / div >
2025-12-07 03:29:54 +01:00
< / div >
<!-- Upload Progress -->
< div id = "uploadProgress" class = "d-none mb-3" >
< div class = "progress" >
< div class = "progress-bar progress-bar-striped progress-bar-animated"
role="progressbar" style="width: 0%" id="progressBar">< / div >
< / div >
< div class = "text-center mt-2" id = "progressText" > Uploader...< / div >
< / div >
<!-- Upload Result -->
< div id = "uploadResult" class = "d-none" >
<!-- Will show success/error/duplicate alerts -->
< / div >
< / form >
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn btn-secondary" data-bs-dismiss = "modal" > Annuller< / button >
< button type = "button" class = "btn btn-success" onclick = "uploadInvoice()" id = "uploadBtn" >
< i class = "bi bi-cloud-upload me-2" > < / i > Upload & Analyser
< / button >
< / div >
< / div >
< / div >
< / div >
2025-12-08 09:15:52 +01:00
<!-- Review Extracted Data Modal -->
< div class = "modal fade" id = "reviewExtractedDataModal" tabindex = "-1" >
< div class = "modal-dialog modal-xl" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" > < i class = "bi bi-search me-2" > < / i > Gennemse Udtrukne Data< / h5 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" > < / button >
< / div >
< div class = "modal-body" id = "reviewModalContent" >
< div class = "text-center py-4" >
< div class = "spinner-border text-primary" role = "status" >
< span class = "visually-hidden" > Indlæser...< / span >
< / div >
< / div >
< / div >
< div class = "modal-footer" >
< input type = "hidden" id = "reviewModalFileId" >
< button type = "button" class = "btn btn-warning" onclick = "reprocessFromModal()" >
< i class = "bi bi-arrow-clockwise me-2" > < / i > Genbehandle
< / button >
< button type = "button" class = "btn btn-secondary" data-bs-dismiss = "modal" > Luk< / button >
< button type = "button" class = "btn btn-primary" onclick = "openManualEntryMode()" >
< i class = "bi bi-pencil me-2" > < / i > Manuel Indtastning
< / button >
< button type = "button" class = "btn btn-success" onclick = "createInvoiceFromExtraction()" >
< i class = "bi bi-check-circle me-2" > < / i > Opret Faktura
< / button >
< / div >
< / div >
< / div >
< / div >
<!-- Manual Entry Modal -->
< div class = "modal fade" id = "manualEntryModal" tabindex = "-1" >
< div class = "modal-dialog modal-xl" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" > < i class = "bi bi-pencil-square me-2" > < / i > Manuel Indtastning af Faktura< / h5 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" > < / button >
< / div >
< div class = "modal-body" >
< input type = "hidden" id = "manualEntryFileId" >
< div class = "row" >
<!-- Left: PDF Viewer -->
< div class = "col-md-6" >
< h6 class = "mb-3" > PDF Dokument< / h6 >
2025-12-08 23:46:18 +01:00
< div style = "border: 1px solid #ddd; border-radius: 4px; height: 600px; overflow: hidden;" >
< iframe id = "manualEntryPdfViewer" type = "application/pdf" width = "100%" height = "100%" style = "border: none;" > < / iframe >
2025-12-08 09:15:52 +01:00
< / div >
< / div >
<!-- Right: Form -->
< div class = "col-md-6" >
< h6 class = "mb-3" > Faktura Detaljer< / h6 >
< form id = "manualEntryForm" >
<!-- Vendor Selection -->
< div class = "mb-3" >
< label class = "form-label" > Leverandør *< / label >
< select class = "form-select" id = "manualVendorId" required >
< option value = "" > Vælg leverandør...< / option >
< / select >
< button type = "button" class = "btn btn-sm btn-outline-primary mt-2" onclick = "openCreateVendorInline()" >
< i class = "bi bi-plus-circle me-1" > < / i > Opret ny leverandør
< / button >
< / div >
<!-- Invoice Details -->
< div class = "row mb-3" >
< div class = "col-md-6" >
< label class = "form-label" > Fakturanummer *< / label >
< input type = "text" class = "form-control" id = "manualInvoiceNumber" required >
< / div >
< div class = "col-md-6" >
< label class = "form-label" > Type< / label >
< select class = "form-select" id = "manualInvoiceType" >
< option value = "invoice" > Faktura< / option >
< option value = "credit_note" > Kreditnota< / option >
< / select >
< /div>
< / div >
< div class = "row mb-3" >
< div class = "col-md-6" >
< label class = "form-label" > Fakturadato *< / label >
< input type = "date" class = "form-control" id = "manualInvoiceDate" required >
< / div >
< div class = "col-md-6" >
< label class = "form-label" > Forfaldsdato< / label >
< input type = "date" class = "form-control" id = "manualDueDate" >
< / div >
< / div >
< div class = "row mb-3" >
< div class = "col-md-6" >
< label class = "form-label" > Total Beløb *< / label >
< input type = "number" step = "0.01" class = "form-control" id = "manualTotalAmount" required >
< / div >
< div class = "col-md-6" >
< label class = "form-label" > Valuta< / label >
< select class = "form-select" id = "manualCurrency" >
< option value = "DKK" > DKK< / option >
< option value = "EUR" > EUR< / option >
< option value = "USD" > USD< / option >
< / select >
< / div >
< / div >
<!-- Line Items -->
< div class = "mb-3" >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< label class = "form-label" > Produktlinier< / label >
< div class = "alert alert-light border py-2 px-3 mb-2" style = "font-size: 0.875rem;" >
💡 < strong > Momskoder:< / strong >
< span class = "badge bg-success ms-2" > I25< / span > 25% moms (standard) ·
< span class = "badge bg-warning text-dark" > I52< / span > Omvendt betalingspligt ·
< span class = "badge bg-secondary" > I0< / span > 0% moms (momsfri)
2025-12-08 23:46:18 +01:00
< / div >
2025-12-08 09:15:52 +01:00
< div id = "manualLineItems" >
<!-- Lines will be added here -->
< / div >
< button type = "button" class = "btn btn-sm btn-outline-primary mt-2" onclick = "addManualLine()" >
< i class = "bi bi-plus-circle me-1" > < / i > Tilføj linje
< / button >
< / div >
< div class = "mb-3" >
< label class = "form-label" > Noter< / label >
< textarea class = "form-control" id = "manualNotes" rows = "2" > < / textarea >
< / div >
< / form >
< / div >
< / div >
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn btn-secondary" data-bs-dismiss = "modal" > Annuller< / button >
< button type = "button" class = "btn btn-success" onclick = "saveManualInvoice()" >
< i class = "bi bi-save me-2" > < / i > Gem Faktura
< / button >
< / div >
< / div >
< / div >
< / div >
<!-- Link/Create Vendor Modal -->
< div class = "modal fade" id = "linkVendorModal" tabindex = "-1" >
< div class = "modal-dialog" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" > Link eller Opret Leverandør< / h5 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" > < / button >
< / div >
< div class = "modal-body" >
< input type = "hidden" id = "linkVendorFileId" >
< input type = "hidden" id = "linkVendorName" >
< input type = "hidden" id = "linkVendorCVR" >
< div class = "alert alert-info" >
< strong > Fundet leverandør:< / strong > < br >
Navn: < span id = "linkVendorDisplayName" > < / span > < br >
CVR: < span id = "linkVendorDisplayCVR" > < / span >
< / div >
< ul class = "nav nav-tabs mb-3" role = "tablist" >
< li class = "nav-item" >
< button class = "nav-link active" data-bs-toggle = "tab" data-bs-target = "#linkExisting" > Link Eksisterende< / button >
< / li >
< li class = "nav-item" >
< button class = "nav-link" data-bs-toggle = "tab" data-bs-target = "#createNew" > Opret Ny< / button >
< / li >
< / ul >
< div class = "tab-content" >
<!-- Link Existing -->
< div class = "tab-pane fade show active" id = "linkExisting" >
< div class = "mb-3" >
< label class = "form-label" > Søg efter leverandør< / label >
< input type = "text" class = "form-control" id = "vendorSearchInput" placeholder = "Søg navn eller CVR..." >
< / div >
< div id = "vendorSearchResults" class = "list-group" style = "max-height: 300px; overflow-y: auto;" >
< div class = "text-center text-muted py-3" > Søg for at finde leverandører< / div >
< / div >
< / div >
<!-- Create New -->
< div class = "tab-pane fade" id = "createNew" >
< form id = "createVendorForm" >
< div class = "mb-3" >
< label class = "form-label" > Navn *< / label >
< input type = "text" class = "form-control" id = "newVendorName" required >
< / div >
< div class = "mb-3" >
< label class = "form-label" > CVR Nummer< / label >
< input type = "text" class = "form-control" id = "newVendorCVR" maxlength = "8" >
< / div >
< div class = "mb-3" >
< label class = "form-label" > Email< / label >
< input type = "email" class = "form-control" id = "newVendorEmail" >
< / div >
< div class = "mb-3" >
< label class = "form-label" > Telefon< / label >
< input type = "tel" class = "form-control" id = "newVendorPhone" >
< / div >
< div class = "mb-3" >
< label class = "form-label" > Adresse< / label >
< textarea class = "form-control" id = "newVendorAddress" rows = "2" > < / textarea >
< / div >
< / form >
< / div >
< / div >
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn btn-secondary" data-bs-dismiss = "modal" > Annuller< / button >
< button type = "button" class = "btn btn-primary" onclick = "saveVendorLink()" id = "linkExistingBtn" >
< i class = "bi bi-link-45deg me-2" > < / i > Link Leverandør
< / button >
< button type = "button" class = "btn btn-success" onclick = "createAndLinkVendor()" id = "createNewBtn" style = "display: none;" >
< i class = "bi bi-plus-circle me-2" > < / i > Opret og Link
< / button >
< / div >
< / div >
< / div >
< / div >
{% endblock %}
{% block extra_js %}
2025-12-07 03:29:54 +01:00
< script >
// Global state
let currentInvoiceId = null;
let currentFilter = 'all';
let allInvoices = [];
// Load data on page load
document.addEventListener('DOMContentLoaded', () => {
loadStats();
loadInvoices();
loadVendors();
setDefaultDates();
2025-12-08 09:15:52 +01:00
loadPendingFilesCount(); // Load count for badge
2025-12-07 03:29:54 +01:00
});
// Set default dates
function setDefaultDates() {
const today = new Date().toISOString().split('T')[0];
const dueDateObj = new Date();
dueDateObj.setDate(dueDateObj.getDate() + 30);
const dueDate = dueDateObj.toISOString().split('T')[0];
document.getElementById('invoiceDate').value = today;
document.getElementById('dueDate').value = dueDate;
}
2025-12-08 09:15:52 +01:00
// Load statistics
async function loadStats() {
try {
const response = await fetch('/api/v1/supplier-invoices/stats/overview');
const stats = await response.json();
document.getElementById('statOverdueCount').textContent = stats.overdue_count || 0;
document.getElementById('statOverdueAmount').textContent = formatCurrency(stats.overdue_amount);
document.getElementById('statDueSoonCount').textContent = stats.due_soon_count || 0;
document.getElementById('statDueSoonAmount').textContent = formatCurrency(stats.unpaid_amount);
document.getElementById('statPendingCount').textContent = stats.pending_count || 0;
document.getElementById('statPendingAmount').textContent = formatCurrency(stats.unpaid_amount);
document.getElementById('statTotalCount').textContent = stats.total_invoices || 0;
document.getElementById('statUnpaidAmount').textContent = formatCurrency(stats.unpaid_amount);
} catch (error) {
console.error('Failed to load stats:', error);
}
}
// Load invoices
async function loadInvoices(status = null) {
try {
let url = '/api/v1/supplier-invoices';
const params = new URLSearchParams();
if (status & & status !== 'all') {
if (status === 'overdue') {
params.append('overdue_only', 'true');
} else {
params.append('status', status);
}
}
if (params.toString()) {
url += '?' + params.toString();
}
const response = await fetch(url);
allInvoices = await response.json();
renderInvoices(allInvoices);
} catch (error) {
console.error('Failed to load invoices:', error);
document.getElementById('invoicesTable').innerHTML = `
< tr >
< td colspan = "8" class = "text-center text-danger py-4" >
< i class = "bi bi-exclamation-triangle me-2" > < / i >
Fejl ved indlæsning af fakturaer
< / td >
< / tr >
`;
}
}
// Render invoices table
function renderInvoices(invoices) {
const tbody = document.getElementById('invoicesTable');
if (!invoices || invoices.length === 0) {
tbody.innerHTML = `
< tr >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< td colspan = "9" class = "text-center text-muted py-4" >
2025-12-08 09:15:52 +01:00
< i class = "bi bi-inbox me-2" > < / i >
Ingen fakturaer fundet
< / td >
< / tr >
`;
return;
}
tbody.innerHTML = invoices.map(inv => {
const isCreditNote = inv.invoice_type === 'credit_note';
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
const isLocked = inv.economic_voucher_number; // Locked if already sent to e-conomic
2025-12-08 09:15:52 +01:00
return `
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< tr class = "${isCreditNote ? 'table-warning' : ''}" >
< td onclick = "event.stopPropagation();" >
< input type = "checkbox" class = "form-check-input invoice-checkbox"
data-invoice-id="${inv.id}"
${isLocked ? 'disabled' : ''}
onchange="updateInvoiceBulkActionsBar()">
< / td >
< td onclick = "viewInvoice(${inv.id})" style = "cursor: pointer;" >
2025-12-08 09:15:52 +01:00
${isCreditNote ? '< i class = "bi bi-arrow-return-left text-warning me-1" title = "Kreditnota" > < / i > ' : ''}
< strong > ${inv.invoice_number}< / strong >
< / td >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< td onclick = "viewInvoice(${inv.id})" style = "cursor: pointer;" > ${inv.vendor_full_name || inv.vendor_name || '-'}< / td >
< td onclick = "viewInvoice(${inv.id})" style = "cursor: pointer;" > ${formatDate(inv.invoice_date)}< / td >
< td onclick = "viewInvoice(${inv.id})" style = "cursor: pointer;" > ${formatDate(inv.due_date)}< / td >
< td onclick = "viewInvoice(${inv.id})" style = "cursor: pointer;" > < strong class = "${isCreditNote ? 'text-danger' : ''}" > ${formatCurrency(inv.total_amount)}< / strong > < / td >
< td onclick = "viewInvoice(${inv.id})" style = "cursor: pointer;" > ${getStatusBadge(inv.computed_status || inv.status)}< / td >
< td onclick = "viewInvoice(${inv.id})" style = "cursor: pointer;" > ${inv.economic_voucher_number ? `< span class = "badge bg-success" > Bilag #${inv.economic_voucher_number}< / span > ` : '-'}< / td >
2025-12-08 09:15:52 +01:00
< td onclick = "event.stopPropagation();" >
< div class = "btn-group btn-group-sm" >
< button class = "btn btn-outline-primary" onclick = "viewInvoice(${inv.id})" title = "Vis detaljer" >
< i class = "bi bi-eye" > < / i >
< / button >
${inv.status === 'pending' & & !isCreditNote ? `
< button class = "btn btn-outline-success" onclick = "quickApprove(${inv.id})" title = "Godkend" >
< i class = "bi bi-check-circle" > < / i >
< / button >
` : ''}
${inv.status === 'approved' & & !inv.economic_voucher_number ? `
< button class = "btn btn-outline-primary" onclick = "quickSendToEconomic(${inv.id})" title = "Send til e-conomic" >
< i class = "bi bi-send" > < / i >
< / button >
` : ''}
${!inv.economic_voucher_number ? `
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< button class = "btn btn-outline-success" onclick = "markInvoiceAsPaid(${inv.id})" title = "Marker som betalt" >
< i class = "bi bi-cash-coin" > < / i >
< / button >
2025-12-08 09:15:52 +01:00
< button class = "btn btn-outline-danger" onclick = "deleteInvoice(${inv.id})" title = "Slet" >
< i class = "bi bi-trash" > < / i >
< / button >
` : ''}
< / div >
< / td >
< / tr >
`}).join('');
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
// Reset bulk selection
updateInvoiceBulkActionsBar();
2025-12-08 09:15:52 +01:00
}
// Load vendors for dropdown
async function loadVendors() {
try {
const response = await fetch('/api/v1/vendors');
const vendors = await response.json();
const select = document.getElementById('vendorId');
select.innerHTML = '< option value = "" > Vælg leverandør...< / option > ' +
vendors.map(v => `< option value = "${v.id}" > ${v.name}< / option > `).join('');
} catch (error) {
console.error('Failed to load vendors:', error);
}
}
// ========== PENDING FILES FUNCTIONS ==========
// Load pending files count for badge (on page load)
async function loadPendingFilesCount() {
try {
const response = await fetch('/api/v1/pending-supplier-invoice-files');
const data = await response.json();
const count = data.count || 0;
const badge = document.getElementById('pendingFilesCount');
if (count > 0) {
badge.textContent = count;
badge.style.display = 'inline-block';
} else {
badge.style.display = 'none';
}
} catch (error) {
console.error('Failed to load pending files count:', error);
}
}
// Switch to invoices tab
function switchToInvoicesTab() {
// Load invoices when switching to this tab
loadInvoices(currentFilter);
}
// Switch to pending files tab
function switchToPendingFilesTab() {
// Load pending files when switching to this tab
loadPendingFiles();
}
// Load pending uploaded files
async function loadPendingFiles() {
try {
const tbody = document.getElementById('pendingFilesTable');
tbody.innerHTML = `
< tr >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< td colspan = "8" class = "text-center py-4" >
2025-12-08 09:15:52 +01:00
< div class = "spinner-border text-primary" role = "status" >
< span class = "visually-hidden" > Indlæser...< / span >
< / div >
< / td >
< / tr >
`;
const response = await fetch('/api/v1/pending-supplier-invoice-files');
const data = await response.json();
// Update badge count
const count = data.count || 0;
const badge = document.getElementById('pendingFilesCount');
if (count > 0) {
badge.textContent = count;
badge.style.display = 'inline-block';
} else {
badge.style.display = 'none';
}
renderPendingFiles(data.files || []);
} catch (error) {
console.error('Failed to load pending files:', error);
document.getElementById('pendingFilesTable').innerHTML = `
< tr >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< td colspan = "8" class = "text-center text-danger py-4" >
2025-12-08 09:15:52 +01:00
< i class = "bi bi-exclamation-triangle me-2" > < / i >
Fejl ved indlæsning af filer
< / td >
< / tr >
`;
}
}
// Render pending files table
function renderPendingFiles(files) {
const tbody = document.getElementById('pendingFilesTable');
if (!files || files.length === 0) {
tbody.innerHTML = `
< tr >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< td colspan = "8" class = "text-center text-muted py-4" >
2025-12-08 09:15:52 +01:00
< i class = "bi bi-inbox me-2" > < / i >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
Ingen filer mangler behandling
2025-12-08 09:15:52 +01:00
< / td >
< / tr >
`;
return;
}
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
tbody.innerHTML = files.map(file => {
// Check if this is BMC's own outgoing invoice
const isOwnInvoice = file.is_own_invoice === true;
const rowClass = isOwnInvoice ? 'table-danger' : (file.status === 'duplicate' ? 'table-danger' : '');
// Format Quick Analysis data
let quickAnalysisHtml = '< span class = "text-muted" > -< / span > ';
if (file.detected_cvr || file.detected_document_type || file.detected_document_number) {
quickAnalysisHtml = `< div class = "small" > `;
// OUTGOING INVOICE WARNING
if (isOwnInvoice) {
quickAnalysisHtml += `< div > < span class = "badge bg-danger" > < i class = "bi bi-exclamation-triangle me-1" > < / i > UDGÅENDE FAKTURA< / span > < / div > `;
quickAnalysisHtml += `< div class = "text-danger small mt-1" > < strong > BMCs egen faktura - slet eller ignorer< / strong > < / div > `;
}
if (file.detected_document_type) {
const isCredit = file.detected_document_type === 'credit_note';
quickAnalysisHtml += `< div > < span class = "badge ${isCredit ? 'bg-warning text-dark' : 'bg-primary'}" > ${isCredit ? '💳 Kreditnota' : '📄 Faktura'}< / span > < / div > `;
}
if (file.detected_document_number) {
quickAnalysisHtml += `< div class = "text-muted mt-1" > < i class = "bi bi-hash" > < / i > ${file.detected_document_number}< / div > `;
}
if (file.detected_cvr) {
quickAnalysisHtml += `< div class = "text-muted mt-1" > < i class = "bi bi-building" > < / i > CVR: ${file.detected_cvr}< / div > `;
}
if (file.detected_vendor_name) {
quickAnalysisHtml += `< div class = "text-success mt-1" > < i class = "bi bi-check-circle" > < / i > ${file.detected_vendor_name}< / div > `;
}
quickAnalysisHtml += `< / div > `;
}
return `
< tr class = "${rowClass}" >
< td >
< input type = "checkbox" class = "form-check-input file-checkbox" value = "${file.file_id}" onchange = "updateBulkActions()" $ { file . status = == ' duplicate ' | | isOwnInvoice ? ' disabled ' : ' ' } >
< / td >
2025-12-08 09:15:52 +01:00
< td >
< i class = "bi bi-file-earmark-pdf text-danger me-2" > < / i >
< strong > ${file.filename}< / strong >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
${isOwnInvoice ? `
< div class = "mt-1" >
< span class = "badge bg-danger" > < i class = "bi bi-exclamation-triangle me-1" > < / i > UDGÅENDE FAKTURA (BMC)< / span >
< / div >
` : ''}
${file.status === 'duplicate' & & file.error_message ? `
< div class = "mt-1" >
< span class = "badge bg-danger" > < i class = "bi bi-exclamation-triangle me-1" > < / i > ${file.error_message}< / span >
< / div >
` : ''}
2025-12-08 09:15:52 +01:00
< / td >
< td > ${formatDate(file.uploaded_at)}< / td >
< td > ${getFileStatusBadge(file.status)}< / td >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< td > ${quickAnalysisHtml}< / td >
2025-12-08 09:15:52 +01:00
< td >
${file.vendor_matched_id ?
`< span class = "badge bg-success" title = "${file.matched_vendor_name}" > < i class = "bi bi-check-circle me-1" > < / i > ${file.matched_vendor_name}< / span > ` :
(file.vendor_name ?
`< div class = "d-flex align-items-center gap-2" >
< span class = "badge bg-warning text-dark" title = "CVR: ${file.vendor_cvr || 'Ukendt'}" >
< i class = "bi bi-question-circle me-1" > < / i > ${file.vendor_name}
< / span >
< button class = "btn btn-xs btn-outline-primary" onclick = "linkOrCreateVendor(${file.file_id}, '${escapeHtml(file.vendor_name)}', '${file.vendor_cvr || ''}')" title = "Link eller opret leverandør" >
< i class = "bi bi-link-45deg" > < / i >
< / button >
< / div > ` :
'< span class = "text-muted" > -< / span > ')
}
< / td >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< td >
${file.template_id ?
`< span class = "badge bg-info" > < i class = "bi bi-file-text me-1" > < / i > Template #${file.template_id}< / span > ` :
(file.has_invoice2data_template ?
`< span class = "badge bg-success" > < i class = "bi bi-file-earmark-code me-1" > < / i > Invoice2Data: ${file.invoice2data_template_name}< / span > ` :
(file.vendor_matched_id || file.detected_vendor_id ?
`< div class = "d-flex flex-column gap-1" >
< span class = "badge bg-warning text-dark" >
< i class = "bi bi-exclamation-triangle me-1" > < / i > Ingen template
< / span >
< a href = "#" onclick = "createTemplateForFile(${file.file_id}, ${file.vendor_matched_id || file.detected_vendor_id}, '${escapeHtml(file.matched_vendor_name || file.detected_vendor_name || 'leverandør')}'); return false;"
class="small text-primary" title="Opret template med data fra denne fil">
< i class = "bi bi-plus-circle me-1" > < / i > Opret template for ${file.matched_vendor_name || file.detected_vendor_name || 'leverandør'}
< / a >
< / div > ` :
'< span class = "text-muted" > -< / span > '))
}
< / td >
2025-12-08 09:15:52 +01:00
< td >
< div class = "btn-group btn-group-sm" >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
${isOwnInvoice ? `
< button class = "btn btn-outline-danger" disabled title = "Udgående faktura - kan ikke behandles" >
< i class = "bi bi-x-circle me-1" > < / i > Udgående Faktura
< / button >
` : ''}
${file.status === 'duplicate' & & !isOwnInvoice ? `
< button class = "btn btn-outline-secondary" disabled title = "Dublet - kan ikke behandles" >
< i class = "bi bi-lock me-1" > < / i > Låst (Dublet)
< / button >
` : ''}
${(file.status === 'ai_extracted' || file.status === 'processed') & & !isOwnInvoice ? `
2025-12-08 09:15:52 +01:00
< button class = "btn btn-outline-primary" onclick = "reviewExtractedData(${file.file_id})" title = "Gennemse udtrukne data" >
< i class = "bi bi-eye me-1" > < / i > Gennemse
< / button >
` : ''}
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
${(file.status === 'failed' || file.status === 'pending') & & !isOwnInvoice ? `
2025-12-08 09:15:52 +01:00
< button class = "btn btn-outline-warning" onclick = "retryExtraction(${file.file_id})" title = "Behandl fil med AI" >
< i class = "bi bi-arrow-clockwise me-1" > < / i > Behandl
< / button >
` : ''}
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
${file.status === 'processing' & & !isOwnInvoice ? `
2025-12-08 09:15:52 +01:00
< button class = "btn btn-outline-secondary" disabled >
< i class = "bi bi-hourglass-split me-1" > < / i > Behandler...
< / button >
` : ''}
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
${file.status !== 'duplicate' ? `
< a href = "/api/v1/supplier-invoices/files/${file.file_id}/download" class = "btn btn-outline-success" target = "_blank" title = "Se PDF" >
< i class = "bi bi-file-pdf me-1" > < / i > Se PDF
< / a >
` : ''}
2025-12-08 09:15:52 +01:00
< button class = "btn btn-outline-danger" onclick = "deletePendingFile(${file.file_id})" title = "Slet fil" >
< i class = "bi bi-trash" > < / i >
< / button >
< / div >
< / td >
< / tr >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
`;
}).join('');
2025-12-08 09:15:52 +01:00
}
// Get status badge for file status
function getFileStatusBadge(status) {
const badges = {
'pending': '< span class = "badge status-pending" > < i class = "bi bi-clock me-1" > < / i > Afventer< / span > ',
'processing': '< span class = "badge status-processing" > < i class = "bi bi-hourglass-split me-1" > < / i > Behandler< / span > ',
'ai_extracted': '< span class = "badge status-ai_extracted" > < i class = "bi bi-robot me-1" > < / i > AI Udtrukket< / span > ',
'processed': '< span class = "badge status-sent" > < i class = "bi bi-check-circle me-1" > < / i > Behandlet< / span > ',
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
'failed': '< span class = "badge status-failed" > < i class = "bi bi-exclamation-triangle me-1" > < / i > Fejlet< / span > ',
'duplicate': '< span class = "badge bg-danger" > < i class = "bi bi-x-circle me-1" > < / i > DUBLET< / span > '
2025-12-08 09:15:52 +01:00
};
return badges[status] || `< span class = "badge bg-secondary" > ${status}< / span > `;
}
// Review extracted data from file
async function reviewExtractedData(fileId) {
try {
const response = await fetch(`/api/v1/supplier-invoices/files/${fileId}/extracted-data`);
const data = await response.json();
if (!data.extraction) {
alert('Ingen udtrukne data fundet. Prøv at behandle filen igen.');
return;
}
const ext = data.extraction;
const lines = data.extraction_lines || [];
// Parse JSON if llm_response_json exists
let aiData = null;
if (ext.llm_response_json) {
try {
aiData = JSON.parse(ext.llm_response_json);
} catch (e) {
console.error('Failed to parse llm_response_json:', e);
}
}
// Normalize CVR - remove DK prefix for display
const normalizedCVR = (ext.vendor_cvr || aiData?.vendor_cvr || '-')
.replace(/^DK/i, '')
.replace(/^dk/i, '')
.trim();
// Build modal content
let modalContent = `
< div class = "alert alert-info" >
< strong > Fil:< / strong > ${data.filename}< br >
< strong > Status:< / strong > ${data.status}< br >
< strong > Confidence:< / strong > ${ext.confidence ? (ext.confidence * 100).toFixed(0) + '%' : 'N/A'}< br >
${ext.vendor_matched_id ?
`< strong class = "text-success" > ✓ Leverandør fundet i system (ID: ${ext.vendor_matched_id})< / strong > ` :
`< strong class = "text-warning" > ⚠ Leverandør ikke fundet - skal oprettes< / strong > `
}
< / div >
< h5 > Udtrukne Data:< / h5 >
< table class = "table table-sm" >
< tr > < th > Felt< / th > < th > Værdi< / th > < / tr >
< tr > < td > Fakturanummer< / td > < td > < strong > ${aiData?.invoice_number || ext.document_id || '-'}< / strong > < / td > < / tr >
< tr > < td > Fakturadato< / td > < td > ${ext.document_date || aiData?.invoice_date || '-'}< / td > < / tr >
< tr > < td > Forfaldsdato< / td > < td > ${ext.due_date || aiData?.due_date || '-'}< / td > < / tr >
< tr > < td > Total beløb< / td > < td > < strong > ${ext.total_amount || aiData?.total_amount || '-'} ${ext.currency || aiData?.currency || 'DKK'}< / strong > < / td > < / tr >
< tr > < td > Leverandør navn< / td > < td > ${ext.vendor_name || aiData?.vendor_name || '-'}< / td > < / tr >
< tr > < td > Leverandør CVR< / td > < td > < strong > ${normalizedCVR}< / strong > < / td > < / tr >
< tr > < td > Leverandør adresse< / td > < td > ${aiData?.vendor_address || '-'}< / td > < / tr >
< / table >
${lines.length > 0 || (aiData?.line_items & & aiData.line_items.length > 0) || (aiData?.lines & & aiData.lines.length > 0) ? `
< h6 > Fakturalinjer:< / h6 >
< table class = "table table-sm" >
< thead >
< tr > < th > Beskrivelse< / th > < th > Antal< / th > < th > Pris< / th > < th > Total< / th > < / tr >
< / thead >
< tbody >
${(lines.length > 0 ? lines : (aiData.line_items || aiData.lines || [])).map(line => `
< tr >
< td > ${line.description || '-'}< / td >
< td > ${line.quantity || '-'}< / td >
< td > ${line.unit_price || '-'}< / td >
< td > ${line.total_amount || line.total_price || line.line_total || '-'}< / td >
< / tr >
`).join('')}
< / tbody >
< / table >
` : '< p class = "text-muted" > Ingen linjer fundet< / p > '}
${data.pdf_text_preview ? `
< h6 class = "mt-3" > PDF Tekst Preview:< / h6 >
< div class = "border rounded p-3 bg-light" style = "max-height: 500px; overflow-y: auto;" >
< pre class = "mb-0" style = "font-size: 0.85rem; white-space: pre-wrap; word-wrap: break-word;" > ${data.pdf_text_preview}< / pre >
< / div >
` : ''}
`;
document.getElementById('reviewModalContent').innerHTML = modalContent;
document.getElementById('reviewModalFileId').value = fileId;
new bootstrap.Modal(document.getElementById('reviewExtractedDataModal')).show();
} catch (error) {
console.error('Failed to load extracted data:', error);
alert('Kunne ikke hente udtrukne data: ' + error.message);
}
}
// Retry extraction for failed file
async function retryExtraction(fileId) {
try {
if (!confirm('Vil du prøve at udtrække data fra denne fil igen?')) return;
const response = await fetch(`/api/v1/supplier-invoices/reprocess/${fileId}`, {
method: 'POST'
});
if (response.ok) {
const result = await response.json();
// Reload pending files list to show updated status
await loadPendingFiles();
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
// Show warning if no template was matched
let message = 'Fil behandlet med succes!';
if (result.warning) {
message = result.warning + '\n\n' + message + '\n\nOvervej at oprette en template for hurtigere behandling.';
}
2025-12-08 09:15:52 +01:00
// Show success message and offer to review
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
if (confirm(message + '\n\nVil du gennemse de udtrukne data?')) {
2025-12-08 09:15:52 +01:00
reviewExtractedData(fileId);
}
} else {
const error = await response.json();
alert('Kunne ikke behandle fil: ' + (error.detail || 'Ukendt fejl'));
}
} catch (error) {
console.error('Failed to retry extraction:', error);
alert('Kunne ikke prøve igen: ' + error.message);
}
}
// Reprocess from review modal
async function reprocessFromModal() {
const fileId = document.getElementById('reviewModalFileId').value;
if (!fileId) {
alert('Ingen fil valgt');
return;
}
try {
if (!confirm('Vil du genbehandle denne fil? Den nuværende udtrækning vil blive overskrevet.')) return;
// Show loading in modal
document.getElementById('reviewModalContent').innerHTML = `
< div class = "text-center py-5" >
< div class = "spinner-border text-primary mb-3" role = "status" > < / div >
< p > Genbehandler fil...< / p >
< / div >
`;
const response = await fetch(`/api/v1/supplier-invoices/reprocess/${fileId}`, {
method: 'POST'
});
if (response.ok) {
// Reload the extracted data in the modal
await reviewExtractedData(fileId);
// Also reload pending files list
await loadPendingFiles();
} else {
const error = await response.json();
alert('Kunne ikke genbehandle fil: ' + (error.detail || 'Ukendt fejl'));
}
} catch (error) {
console.error('Failed to reprocess:', error);
alert('Kunne ikke genbehandle: ' + error.message);
}
}
// Delete pending file
async function deletePendingFile(fileId) {
try {
if (!confirm('Er du sikker på at du vil slette denne fil? Dette kan ikke fortrydes.')) return;
const response = await fetch(`/api/v1/supplier-invoices/files/${fileId}`, {
method: 'DELETE'
});
if (response.ok) {
alert('Fil slettet!');
loadPendingFiles(); // Reload list
} else {
const error = await response.json();
alert('Kunne ikke slette fil: ' + (error.detail || 'Ukendt fejl'));
}
} catch (error) {
console.error('Failed to delete file:', error);
alert('Kunne ikke slette fil: ' + error.message);
}
}
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
// Create template for file
async function createTemplateForFile(fileId, vendorId, vendorName) {
try {
// Get file data including PDF text
const response = await fetch(`/api/v1/supplier-invoices/files/${fileId}/extracted-data`);
if (!response.ok) {
throw new Error('Kunne ikke hente fil data');
}
const data = await response.json();
const pdfText = data.pdf_text || '';
// Store data in sessionStorage for template creation page
sessionStorage.setItem('templateCreateData', JSON.stringify({
vendorId: vendorId,
vendorName: vendorName,
fileId: fileId,
pdfText: pdfText,
sampleInvoice: data.llm_data || {}
}));
// Navigate to template builder page
window.location.href = `/billing/template-builder?vendor=${vendorId}&file=${fileId}`;
} catch (error) {
console.error('Failed to prepare template creation:', error);
alert('Kunne ikke forberede template oprettelse: ' + error.message);
}
}
2025-12-08 09:15:52 +01:00
// Link or create vendor for extraction
let selectedVendorId = null;
function linkOrCreateVendor(fileId, vendorName, vendorCVR) {
selectedVendorId = null;
document.getElementById('linkVendorFileId').value = fileId;
document.getElementById('linkVendorName').value = vendorName;
document.getElementById('linkVendorCVR').value = vendorCVR;
document.getElementById('linkVendorDisplayName').textContent = vendorName;
document.getElementById('linkVendorDisplayCVR').textContent = vendorCVR || 'Ikke fundet';
// Pre-fill create form
document.getElementById('newVendorName').value = vendorName;
document.getElementById('newVendorCVR').value = vendorCVR;
// Reset search
document.getElementById('vendorSearchInput').value = '';
document.getElementById('vendorSearchResults').innerHTML = '< div class = "text-center text-muted py-3" > Søg for at finde leverandører< / div > ';
// Show modal
const modal = new bootstrap.Modal(document.getElementById('linkVendorModal'));
modal.show();
}
// Search vendors in link modal
let vendorSearchTimer;
document.addEventListener('DOMContentLoaded', () => {
const searchInput = document.getElementById('vendorSearchInput');
if (searchInput) {
searchInput.addEventListener('input', (e) => {
clearTimeout(vendorSearchTimer);
const query = e.target.value.trim();
if (query.length < 2 ) {
document.getElementById('vendorSearchResults').innerHTML = '< div class = "text-center text-muted py-3" > Indtast mindst 2 tegn< / div > ';
return;
}
vendorSearchTimer = setTimeout(() => searchVendorsForLink(query), 300);
});
}
// Tab switching
const tabs = document.querySelectorAll('[data-bs-toggle="tab"]');
tabs.forEach(tab => {
tab.addEventListener('shown.bs.tab', (e) => {
const target = e.target.getAttribute('data-bs-target');
if (target === '#createNew') {
document.getElementById('linkExistingBtn').style.display = 'none';
document.getElementById('createNewBtn').style.display = 'inline-block';
} else {
document.getElementById('linkExistingBtn').style.display = 'inline-block';
document.getElementById('createNewBtn').style.display = 'none';
}
});
});
});
async function searchVendorsForLink(query) {
try {
const response = await fetch(`/api/v1/vendors?search=${encodeURIComponent(query)}&limit=20`);
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
const vendors = await response.json(); // API returns array directly
2025-12-08 09:15:52 +01:00
const resultsDiv = document.getElementById('vendorSearchResults');
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
if (!vendors || vendors.length === 0) {
2025-12-08 09:15:52 +01:00
resultsDiv.innerHTML = '< div class = "text-center text-muted py-3" > Ingen leverandører fundet< / div > ';
return;
}
resultsDiv.innerHTML = vendors.map(v => `
< button type = "button" class = "list-group-item list-group-item-action ${selectedVendorId === v.id ? 'active' : ''}"
onclick="selectVendorForLink(${v.id}, '${escapeHtml(v.name)}')">
< div class = "d-flex justify-content-between align-items-center" >
< div >
< strong > ${v.name}< / strong >
${v.cvr_number ? `< br > < small class = "text-muted" > CVR: ${v.cvr_number}< / small > ` : ''}
< / div >
${selectedVendorId === v.id ? '< i class = "bi bi-check-circle text-success" > < / i > ' : ''}
< / div >
< / button >
`).join('');
} catch (error) {
console.error('Vendor search failed:', error);
document.getElementById('vendorSearchResults').innerHTML = '< div class = "text-center text-danger py-3" > Søgning fejlede< / div > ';
}
}
function selectVendorForLink(vendorId, vendorName) {
selectedVendorId = vendorId;
// Re-render to show selection
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
const searchInputElem = document.getElementById('vendorSearchInput');
if (searchInputElem & & searchInputElem.value) {
searchVendorsForLink(searchInputElem.value);
2025-12-08 09:15:52 +01:00
}
}
async function saveVendorLink() {
if (!selectedVendorId) {
alert('Vælg venligst en leverandør fra listen');
return;
}
try {
const fileId = document.getElementById('linkVendorFileId').value;
// Update extraction with vendor_matched_id
const response = await fetch(`/api/v1/supplier-invoices/files/${fileId}/link-vendor`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({vendor_id: selectedVendorId})
});
if (response.ok) {
alert('Leverandør linket!');
bootstrap.Modal.getInstance(document.getElementById('linkVendorModal')).hide();
loadPendingFiles();
} else {
const error = await response.json();
alert('Kunne ikke linke leverandør: ' + (error.detail || 'Ukendt fejl'));
}
} catch (error) {
console.error('Link vendor failed:', error);
alert('Kunne ikke linke leverandør: ' + error.message);
}
}
async function createAndLinkVendor() {
const form = document.getElementById('createVendorForm');
if (!form.checkValidity()) {
form.reportValidity();
return;
}
try {
const fileId = document.getElementById('linkVendorFileId').value;
// Create vendor
const vendorData = {
name: document.getElementById('newVendorName').value,
cvr_number: document.getElementById('newVendorCVR').value || null,
email: document.getElementById('newVendorEmail').value || null,
phone: document.getElementById('newVendorPhone').value || null,
address: document.getElementById('newVendorAddress').value || null
};
const createResp = await fetch('/api/v1/vendors', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(vendorData)
});
if (!createResp.ok) {
const error = await createResp.json();
alert('Kunne ikke oprette leverandør: ' + (error.detail || 'Ukendt fejl'));
return;
}
const newVendor = await createResp.json();
// Link vendor
const linkResp = await fetch(`/api/v1/supplier-invoices/files/${fileId}/link-vendor`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({vendor_id: newVendor.id})
});
if (linkResp.ok) {
alert('Leverandør oprettet og linket!');
bootstrap.Modal.getInstance(document.getElementById('linkVendorModal')).hide();
loadPendingFiles();
} else {
const error = await linkResp.json();
alert('Leverandør oprettet, men kunne ikke linkes: ' + (error.detail || 'Ukendt fejl'));
}
} catch (error) {
console.error('Create and link vendor failed:', error);
alert('Kunne ikke oprette leverandør: ' + error.message);
}
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Create invoice from extracted data
async function createInvoiceFromExtraction() {
try {
const fileId = document.getElementById('reviewModalFileId').value;
if (!confirm('Vil du oprette en leverandørfaktura fra disse udtrukne data?')) return;
// Show loading state
const modal = document.getElementById('reviewModal');
const originalContent = document.getElementById('reviewModalContent').innerHTML;
document.getElementById('reviewModalContent').innerHTML = `
< div class = "text-center py-5" >
< div class = "spinner-border text-primary mb-3" > < / div >
< p > Opretter faktura...< / p >
< / div >
`;
const response = await fetch(`/api/v1/supplier-invoices/from-extraction/${fileId}`, {
method: 'POST'
});
if (response.ok) {
const result = await response.json();
alert(`✅ Faktura oprettet!\n\nFakturanummer: ${result.invoice_number}\nLeverandør: ${result.vendor_name}\nBeløb: ${result.total_amount} ${result.currency}`);
// Close modal and refresh
const modalInstance = bootstrap.Modal.getInstance(modal);
if (modalInstance) {
modalInstance.hide();
}
loadPendingFiles();
loadInvoices();
loadStats();
} else {
const error = await response.json();
document.getElementById('reviewModalContent').innerHTML = originalContent;
alert('Kunne ikke oprette faktura: ' + (error.detail || 'Ukendt fejl'));
}
} catch (error) {
console.error('Failed to create invoice:', error);
alert('Kunne ikke oprette faktura: ' + error.message);
}
}
// ========== MANUAL ENTRY MODE ==========
let manualLineCounter = 0;
async function openManualEntryMode() {
2025-12-07 03:29:54 +01:00
try {
2025-12-08 09:15:52 +01:00
console.log('Opening manual entry mode...');
2025-12-07 03:29:54 +01:00
2025-12-08 09:15:52 +01:00
const fileId = document.getElementById('reviewModalFileId').value;
console.log('File ID:', fileId);
2025-12-07 03:29:54 +01:00
2025-12-08 09:15:52 +01:00
if (!fileId) {
alert('Ingen fil ID fundet');
return;
}
2025-12-07 03:29:54 +01:00
2025-12-08 23:46:18 +01:00
// Set file ID
document.getElementById('manualEntryFileId').value = fileId;
// Clear form
document.getElementById('manualEntryForm').reset();
document.getElementById('manualLineItems').innerHTML = '';
manualLineCounter = 0;
2025-12-08 09:15:52 +01:00
// Close review modal
2025-12-08 23:46:18 +01:00
const reviewModal = bootstrap.Modal.getInstance(document.getElementById('reviewExtractedDataModal'));
2025-12-08 09:15:52 +01:00
if (reviewModal) {
reviewModal.hide();
}
2025-12-08 23:46:18 +01:00
// Open manual entry modal first
console.log('Opening manual entry modal...');
const manualModal = new bootstrap.Modal(document.getElementById('manualEntryModal'));
manualModal.show();
2025-12-08 09:15:52 +01:00
2025-12-08 23:46:18 +01:00
// Wait a bit for modal to render
await new Promise(resolve => setTimeout(resolve, 300));
// Load PDF after modal is open
2025-12-08 09:15:52 +01:00
console.log('Loading PDF...');
document.getElementById('manualEntryPdfViewer').src = `/api/v1/supplier-invoices/files/${fileId}/pdf`;
// Load vendors into dropdown
console.log('Loading vendors...');
await loadVendorsForManual();
// Load extracted data and prefill form
console.log('Loading extracted data...');
try {
const response = await fetch(`/api/v1/supplier-invoices/files/${fileId}/extracted-data`);
if (response.ok) {
const data = await response.json();
console.log('Extracted data:', data);
// Prefill form fields
if (data.llm_data) {
const llm = data.llm_data;
2025-12-08 23:46:18 +01:00
console.log('LLM data invoice_number:', llm.invoice_number);
2025-12-08 09:15:52 +01:00
// Invoice number
if (llm.invoice_number) {
2025-12-08 23:46:18 +01:00
console.log('Setting invoice number:', llm.invoice_number);
2025-12-08 09:15:52 +01:00
document.getElementById('manualInvoiceNumber').value = llm.invoice_number;
2025-12-08 23:46:18 +01:00
} else {
console.warn('No invoice_number in llm_data');
2025-12-08 09:15:52 +01:00
}
// Invoice date
if (llm.invoice_date) {
document.getElementById('manualInvoiceDate').value = llm.invoice_date;
}
// Due date
if (llm.due_date) {
document.getElementById('manualDueDate').value = llm.due_date;
}
// Total amount
if (llm.total_amount) {
document.getElementById('manualTotalAmount').value = Math.abs(llm.total_amount);
}
// Currency
if (llm.currency) {
document.getElementById('manualCurrency').value = llm.currency;
}
// Invoice type (check if credit note)
if (llm.document_type === 'credit_note') {
document.getElementById('manualInvoiceType').value = 'credit_note';
}
// Vendor - select if matched
if (data.vendor_matched_id) {
2025-12-08 23:46:18 +01:00
document.getElementById('manualVendorId').value = data.vendor_matched_id;
2025-12-08 09:15:52 +01:00
}
// Add line items
if (llm.lines & & llm.lines.length > 0) {
llm.lines.forEach(line => {
addManualLine();
const lineNum = manualLineCounter;
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
// SKU
if (line.sku) {
document.getElementById(`manualLineSku${lineNum}`).value = line.sku;
}
// Description
2025-12-08 09:15:52 +01:00
if (line.description) {
2025-12-08 23:46:18 +01:00
let desc = line.description;
// Add VAT note to description if present
if (line.vat_note === 'reverse_charge') {
desc += ' ⚠️ OMVENDT BETALINGSPLIGT';
} else if (line.vat_note === 'copydan_included') {
desc += ' [Copydan incl.]';
}
document.getElementById(`manualLineDesc${lineNum}`).value = desc;
2025-12-08 09:15:52 +01:00
}
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
// Quantity
2025-12-08 09:15:52 +01:00
if (line.quantity) {
document.getElementById(`manualLineQty${lineNum}`).value = line.quantity;
}
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
// Unit price
2025-12-08 09:15:52 +01:00
if (line.unit_price) {
document.getElementById(`manualLinePrice${lineNum}`).value = Math.abs(line.unit_price);
}
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
// VAT code - auto-select based on vat_note
const vatCodeSelect = document.getElementById(`manualLineVatCode${lineNum}`);
if (line.vat_note === 'reverse_charge') {
vatCodeSelect.value = 'I52';
} else if (line.vat_rate === 0) {
vatCodeSelect.value = 'I0';
} else {
vatCodeSelect.value = 'I25';
}
// Contra account
if (line.contra_account) {
document.getElementById(`manualLineContra${lineNum}`).value = line.contra_account;
2025-12-08 09:15:52 +01:00
}
});
} else {
// Add one empty line if no lines extracted
addManualLine();
}
console.log('✅ Form prefilled with extracted data');
} else {
// No extracted data, add empty line
addManualLine();
}
} else {
console.warn('⚠️ Could not load extracted data, starting with empty form');
addManualLine();
}
} catch (error) {
console.error('Error loading extracted data:', error);
// Add empty line on error
addManualLine();
}
console.log('Manual entry modal opened successfully');
2025-12-07 03:29:54 +01:00
} catch (error) {
2025-12-08 09:15:52 +01:00
console.error('Error opening manual entry mode:', error);
alert('Fejl ved åbning af manuel indtastning: ' + error.message);
2025-12-07 03:29:54 +01:00
}
}
2025-12-08 09:15:52 +01:00
async function loadVendorsForManual() {
2025-12-07 03:29:54 +01:00
try {
2025-12-08 09:15:52 +01:00
console.log('Fetching vendors from API...');
const response = await fetch('/api/v1/vendors');
2025-12-07 03:29:54 +01:00
2025-12-08 09:15:52 +01:00
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
2025-12-07 03:29:54 +01:00
}
2025-12-08 09:15:52 +01:00
const vendors = await response.json();
console.log(`Loaded ${vendors.length} vendors`);
const select = document.getElementById('manualVendorId');
if (!select) {
throw new Error('manualVendorId element not found');
2025-12-07 03:29:54 +01:00
}
2025-12-08 09:15:52 +01:00
select.innerHTML = '< option value = "" > Vælg leverandør...< / option > ';
2025-12-07 03:29:54 +01:00
2025-12-08 09:15:52 +01:00
vendors.forEach(vendor => {
const option = document.createElement('option');
option.value = vendor.id;
option.textContent = `${vendor.name}${vendor.cvr_number ? ' (CVR: ' + vendor.cvr_number + ')' : ''}`;
select.appendChild(option);
});
console.log('Vendors loaded successfully');
2025-12-07 03:29:54 +01:00
} catch (error) {
2025-12-08 09:15:52 +01:00
console.error('Failed to load vendors:', error);
alert('Kunne ikke hente leverandører: ' + error.message);
2025-12-07 03:29:54 +01:00
}
}
2025-12-08 09:15:52 +01:00
function addManualLine() {
try {
manualLineCounter++;
console.log('Adding manual line', manualLineCounter);
const lineHtml = `
< div class = "card mb-2" id = "manualLine${manualLineCounter}" >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< div class = "card-body p-2" >
< div class = "row g-2 align-items-center" >
< div class = "col-md-1" >
< label class = "form-label mb-0 small text-muted" > Varenr< / label >
< input type = "text" class = "form-control form-control-sm" placeholder = "SKU"
id="manualLineSku${manualLineCounter}" name="line_sku[]">
< / div >
< div class = "col-md-3" >
< label class = "form-label mb-0 small text-muted" > Beskrivelse< / label >
2025-12-08 09:15:52 +01:00
< input type = "text" class = "form-control form-control-sm" placeholder = "Beskrivelse"
id="manualLineDesc${manualLineCounter}" name="line_description[]">
< / div >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< div class = "col-md-1" >
< label class = "form-label mb-0 small text-muted" > Antal< / label >
2025-12-08 09:15:52 +01:00
< input type = "number" class = "form-control form-control-sm" placeholder = "Antal"
id="manualLineQty${manualLineCounter}" name="line_quantity[]" value="1" step="1">
< / div >
< div class = "col-md-2" >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< label class = "form-label mb-0 small text-muted" > Enhedspris< / label >
2025-12-08 09:15:52 +01:00
< input type = "number" class = "form-control form-control-sm" placeholder = "Pris"
id="manualLinePrice${manualLineCounter}" name="line_price[]" step="0.01">
< / div >
< div class = "col-md-2" >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< label class = "form-label mb-0 small text-muted" > Momskode< / label >
< select class = "form-select form-select-sm" id = "manualLineVatCode${manualLineCounter}" name = "line_vat_code[]" >
< option value = "I25" selected > I25 - 25% moms< / option >
< option value = "I52" > I52 - Omvendt betalingspligt< / option >
< option value = "I0" > I0 - 0% moms (momsfri)< / option >
< / select >
< / div >
< div class = "col-md-2" >
< label class = "form-label mb-0 small text-muted" > Modkonto< / label >
< input type = "text" class = "form-control form-control-sm" placeholder = "5810"
id="manualLineContra${manualLineCounter}" name="line_contra[]" value="5810">
2025-12-08 09:15:52 +01:00
< / div >
< div class = "col-md-1" >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< label class = "form-label mb-0 small text-muted" > < / label >
2025-12-08 09:15:52 +01:00
< button type = "button" class = "btn btn-sm btn-outline-danger w-100" onclick = "removeManualLine(${manualLineCounter})" >
< i class = "bi bi-trash" > < / i >
< / button >
< / div >
< / div >
< / div >
< / div >
2025-12-07 03:29:54 +01:00
`;
2025-12-08 09:15:52 +01:00
const container = document.getElementById('manualLineItems');
if (!container) {
throw new Error('manualLineItems container not found');
}
container.insertAdjacentHTML('beforeend', lineHtml);
console.log('Manual line added successfully');
} catch (error) {
console.error('Error adding manual line:', error);
alert('Fejl ved tilføjelse af linje: ' + error.message);
2025-12-07 03:29:54 +01:00
}
}
2025-12-08 09:15:52 +01:00
function removeManualLine(lineId) {
document.getElementById(`manualLine${lineId}`).remove();
}
async function saveManualInvoice() {
2025-12-07 03:29:54 +01:00
try {
2025-12-08 09:15:52 +01:00
const fileId = document.getElementById('manualEntryFileId').value;
const vendorId = document.getElementById('manualVendorId').value;
const invoiceNumber = document.getElementById('manualInvoiceNumber').value;
const invoiceDate = document.getElementById('manualInvoiceDate').value;
const dueDate = document.getElementById('manualDueDate').value;
const totalAmount = parseFloat(document.getElementById('manualTotalAmount').value);
const currency = document.getElementById('manualCurrency').value;
const invoiceType = document.getElementById('manualInvoiceType').value;
const notes = document.getElementById('manualNotes').value;
// Validate required fields
if (!vendorId || !invoiceNumber || !invoiceDate || !totalAmount) {
alert('Udfyld venligst alle påkrævede felter (markeret med *)');
return;
}
// Collect line items
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
const skus = document.getElementsByName('line_sku[]');
2025-12-08 09:15:52 +01:00
const descriptions = document.getElementsByName('line_description[]');
const quantities = document.getElementsByName('line_quantity[]');
const prices = document.getElementsByName('line_price[]');
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
const vatCodes = document.getElementsByName('line_vat_code[]');
const contraAccounts = document.getElementsByName('line_contra[]');
2025-12-08 09:15:52 +01:00
const lines = [];
for (let i = 0; i < descriptions.length ; i + + ) {
if (descriptions[i].value.trim()) {
const qty = parseFloat(quantities[i].value) || 1;
const price = parseFloat(prices[i].value) || 0;
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
const vatCode = vatCodes[i].value;
2025-12-08 23:46:18 +01:00
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
// Determine VAT rate from code
let vatRate = 25.00;
if (vatCode === 'I52' || vatCode === 'I0') {
vatRate = 0.00;
2025-12-08 23:46:18 +01:00
}
2025-12-08 09:15:52 +01:00
lines.push({
line_number: i + 1,
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
sku: skus[i].value.trim() || null,
description: descriptions[i].value,
2025-12-08 09:15:52 +01:00
quantity: qty,
unit_price: price,
line_total: qty * price,
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
vat_code: vatCode,
2025-12-08 23:46:18 +01:00
vat_rate: vatRate,
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
contra_account: contraAccounts[i].value.trim() || '5810'
2025-12-08 09:15:52 +01:00
});
}
}
// Create invoice
const response = await fetch('/api/v1/supplier-invoices', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
vendor_id: parseInt(vendorId),
invoice_number: invoiceNumber,
invoice_date: invoiceDate,
due_date: dueDate || invoiceDate,
total_amount: totalAmount,
currency: currency,
invoice_type: invoiceType,
status: invoiceType === 'credit_note' ? 'credited' : 'unpaid',
notes: `Manuel indtastning fra fil ID ${fileId}. ${notes}`.trim(),
lines: lines
})
});
if (response.ok) {
const result = await response.json();
// Mark file as completed
await fetch(`/api/v1/supplier-invoices/files/${fileId}`, {
method: 'PATCH',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({status: 'completed'})
});
// Close modal and refresh
bootstrap.Modal.getInstance(document.getElementById('manualEntryModal')).hide();
alert(`✅ ${invoiceType === 'credit_note' ? 'Kreditnota' : 'Faktura'} oprettet!\n\nFakturanummer: ${invoiceNumber}\nBeløb: ${totalAmount} ${currency}`);
loadPendingFiles();
loadInvoices();
loadStats();
} else {
const error = await response.json();
alert('Kunne ikke oprette faktura: ' + (error.detail || 'Ukendt fejl'));
}
2025-12-07 03:29:54 +01:00
} catch (error) {
2025-12-08 09:15:52 +01:00
console.error('Failed to save manual invoice:', error);
alert('Kunne ikke gemme faktura: ' + error.message);
2025-12-07 03:29:54 +01:00
}
}
2025-12-08 09:15:52 +01:00
// ========== END MANUAL ENTRY MODE ==========
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
// ========== BULK ACTIONS FOR PENDING FILES ==========
// Toggle select all checkboxes
function toggleSelectAll() {
const selectAll = document.getElementById('selectAllFiles');
const checkboxes = document.querySelectorAll('.file-checkbox');
checkboxes.forEach(cb => cb.checked = selectAll.checked);
updateBulkActions();
}
// Update bulk actions bar visibility and count
function updateBulkActions() {
const checkboxes = document.querySelectorAll('.file-checkbox:checked');
const count = checkboxes.length;
const bulkBar = document.getElementById('bulkActionsBar');
const countSpan = document.getElementById('selectedFilesCount');
if (count > 0) {
bulkBar.style.display = 'block';
countSpan.textContent = count;
} else {
bulkBar.style.display = 'none';
document.getElementById('selectAllFiles').checked = false;
}
}
// Get selected file IDs
function getSelectedFileIds() {
const checkboxes = document.querySelectorAll('.file-checkbox:checked');
return Array.from(checkboxes).map(cb => parseInt(cb.value));
}
// Bulk create invoices from selected files
async function bulkCreateInvoices() {
const fileIds = getSelectedFileIds();
if (fileIds.length === 0) {
alert('Vælg venligst filer først');
return;
}
if (!confirm(`Opret fakturaer fra ${fileIds.length} valgte filer?`)) return;
try {
let successCount = 0;
let failCount = 0;
for (const fileId of fileIds) {
try {
// Get extracted data
const dataResp = await fetch(`/api/v1/supplier-invoices/files/${fileId}/extracted-data`);
const data = await dataResp.json();
if (!data.llm_data || !data.vendor_matched_id) {
failCount++;
continue;
}
const llm = data.llm_data;
// Create invoice
const invoiceResp = await fetch('/api/v1/supplier-invoices', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
vendor_id: data.vendor_matched_id,
invoice_number: llm.invoice_number,
invoice_date: llm.invoice_date,
due_date: llm.due_date,
total_amount: llm.total_amount,
currency: llm.currency || 'DKK',
invoice_type: llm.document_type === 'credit_note' ? 'credit_note' : 'invoice',
status: 'unpaid',
notes: `Oprettet fra fil ID ${fileId}`,
lines: (llm.lines || []).map((line, idx) => ({
line_number: idx + 1,
sku: line.sku || null,
description: line.description,
quantity: line.quantity || 1,
unit_price: line.unit_price || 0,
line_total: line.line_total || 0,
vat_code: line.vat_note === 'reverse_charge' ? 'I52' : 'I25',
vat_rate: line.vat_rate || 25,
contra_account: '5810'
}))
})
});
if (invoiceResp.ok) {
successCount++;
} else {
failCount++;
}
} catch (error) {
console.error(`Failed to create invoice from file ${fileId}:`, error);
failCount++;
}
}
alert(`✅ ${successCount} fakturaer oprettet\n${failCount > 0 ? `❌ ${failCount} fejlede` : ''}`);
// Reload data
loadPendingFiles();
loadInvoices();
loadStats();
} catch (error) {
console.error('Bulk create failed:', error);
alert('❌ Fejl ved bulk oprettelse: ' + error.message);
}
}
// Bulk reprocess selected files
async function bulkReprocess() {
const fileIds = getSelectedFileIds();
if (fileIds.length === 0) {
alert('Vælg venligst filer først');
return;
}
if (!confirm(`Genbehandle ${fileIds.length} valgte filer?`)) return;
try {
let successCount = 0;
let failCount = 0;
for (const fileId of fileIds) {
try {
const response = await fetch(`/api/v1/supplier-invoices/reprocess/${fileId}`, {
method: 'POST'
});
if (response.ok) {
successCount++;
} else {
failCount++;
}
} catch (error) {
console.error(`Failed to reprocess file ${fileId}:`, error);
failCount++;
}
}
alert(`✅ ${successCount} filer genbehandlet\n${failCount > 0 ? `❌ ${failCount} fejlede` : ''}`);
loadPendingFiles();
} catch (error) {
console.error('Bulk reprocess failed:', error);
alert('❌ Fejl ved bulk genbehandling: ' + error.message);
}
}
// Bulk delete selected files
async function bulkDelete() {
const fileIds = getSelectedFileIds();
if (fileIds.length === 0) {
alert('Vælg venligst filer først');
return;
}
if (!confirm(`⚠️ ADVARSEL: Slet ${fileIds.length} valgte filer permanent?\n\nDenne handling kan ikke fortrydes!`)) return;
try {
let successCount = 0;
let failCount = 0;
for (const fileId of fileIds) {
try {
const response = await fetch(`/api/v1/supplier-invoices/files/${fileId}`, {
method: 'DELETE'
});
if (response.ok) {
successCount++;
} else {
failCount++;
}
} catch (error) {
console.error(`Failed to delete file ${fileId}:`, error);
failCount++;
}
}
alert(`✅ ${successCount} filer slettet\n${failCount > 0 ? `❌ ${failCount} fejlede` : ''}`);
loadPendingFiles();
} catch (error) {
console.error('Bulk delete failed:', error);
alert('❌ Fejl ved bulk sletning: ' + error.message);
}
}
// ========== END BULK ACTIONS ==========
// ========== INVOICE BULK ACTIONS ==========
// Toggle select all invoices
function toggleSelectAllInvoices() {
const selectAll = document.getElementById('selectAllInvoices');
const checkboxes = document.querySelectorAll('.invoice-checkbox:not(:disabled)');
checkboxes.forEach(cb => cb.checked = selectAll.checked);
updateInvoiceBulkActionsBar();
}
// Update invoice bulk actions bar visibility and count
function updateInvoiceBulkActionsBar() {
const checkboxes = document.querySelectorAll('.invoice-checkbox:checked');
const count = checkboxes.length;
const bulkBar = document.getElementById('invoiceBulkActionsBar');
const countSpan = document.getElementById('selectedInvoicesCount');
if (count > 0) {
bulkBar.style.display = 'block';
countSpan.textContent = count;
} else {
bulkBar.style.display = 'none';
document.getElementById('selectAllInvoices').checked = false;
}
}
// Get selected invoice IDs
function getSelectedInvoiceIds() {
const checkboxes = document.querySelectorAll('.invoice-checkbox:checked');
return Array.from(checkboxes).map(cb => parseInt(cb.dataset.invoiceId));
}
// Bulk send to e-conomic
async function bulkSendToEconomic() {
const invoiceIds = getSelectedInvoiceIds();
if (invoiceIds.length === 0) {
alert('Vælg venligst fakturaer først');
return;
}
if (!confirm(`Send ${invoiceIds.length} fakturaer til e-conomic kassekladde?`)) return;
try {
let successCount = 0;
let failCount = 0;
let errors = [];
for (const invoiceId of invoiceIds) {
try {
const response = await fetch(`/api/v1/supplier-invoices/${invoiceId}/send-to-economic`, {
method: 'POST',
headers: {'Content-Type': 'application/json'}
});
if (response.ok) {
successCount++;
} else {
const errorData = await response.json();
errors.push(`Faktura #${invoiceId}: ${errorData.detail || response.statusText}`);
failCount++;
}
} catch (error) {
console.error(`Failed to send invoice ${invoiceId}:`, error);
errors.push(`Faktura #${invoiceId}: ${error.message}`);
failCount++;
}
}
let message = `✅ ${successCount} fakturaer sendt til e-conomic`;
if (failCount > 0) {
message += `\n\n❌ ${failCount} fejlede:\n${errors.join('\n')}`;
}
alert(message);
// Reload invoices
loadInvoices(currentFilter);
} catch (error) {
console.error('Bulk send to e-conomic failed:', error);
alert('❌ Fejl ved bulk sending: ' + error.message);
}
}
// Bulk reset invoices (move back to pending)
async function bulkResetInvoices() {
const invoiceIds = getSelectedInvoiceIds();
if (invoiceIds.length === 0) {
alert('Vælg venligst fakturaer først');
return;
}
if (!confirm(`Nulstil ${invoiceIds.length} fakturaer til afventer behandling?`)) return;
try {
let successCount = 0;
let failCount = 0;
for (const invoiceId of invoiceIds) {
try {
const response = await fetch(`/api/v1/supplier-invoices/${invoiceId}`, {
method: 'PATCH',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ status: 'pending' })
});
if (response.ok) {
successCount++;
} else {
failCount++;
}
} catch (error) {
console.error(`Failed to reset invoice ${invoiceId}:`, error);
failCount++;
}
}
alert(`✅ ${successCount} fakturaer nulstillet\n${failCount > 0 ? `❌ ${failCount} fejlede` : ''}`);
// Reload invoices
loadInvoices(currentFilter);
} catch (error) {
console.error('Bulk reset failed:', error);
alert('❌ Fejl ved bulk nulstilling: ' + error.message);
}
}
// Bulk mark as paid
async function bulkMarkAsPaid() {
const invoiceIds = getSelectedInvoiceIds();
if (invoiceIds.length === 0) {
alert('Vælg venligst fakturaer først');
return;
}
if (!confirm(`Marker ${invoiceIds.length} fakturaer som betalt?`)) return;
try {
let successCount = 0;
let failCount = 0;
for (const invoiceId of invoiceIds) {
try {
const response = await fetch(`/api/v1/supplier-invoices/${invoiceId}`, {
method: 'PATCH',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
status: 'paid',
payment_date: new Date().toISOString().split('T')[0]
})
});
if (response.ok) {
successCount++;
} else {
failCount++;
}
} catch (error) {
console.error(`Failed to mark invoice ${invoiceId} as paid:`, error);
failCount++;
}
}
alert(`✅ ${successCount} fakturaer markeret som betalt\n${failCount > 0 ? `❌ ${failCount} fejlede` : ''}`);
// Reload invoices
loadInvoices(currentFilter);
} catch (error) {
console.error('Bulk mark as paid failed:', error);
alert('❌ Fejl ved bulk betaling: ' + error.message);
}
}
// Individual mark as paid
async function markInvoiceAsPaid(invoiceId) {
if (!confirm('Marker denne faktura som betalt?')) return;
try {
const response = await fetch(`/api/v1/supplier-invoices/${invoiceId}`, {
method: 'PATCH',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
status: 'paid',
payment_date: new Date().toISOString().split('T')[0]
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Failed to mark as paid');
}
alert('✅ Faktura markeret som betalt');
loadInvoices(currentFilter);
} catch (error) {
console.error('Failed to mark invoice as paid:', error);
alert('❌ Fejl: ' + error.message);
}
}
// ========== END INVOICE BULK ACTIONS ==========
2025-12-08 09:15:52 +01:00
// ========== END PENDING FILES FUNCTIONS ==========
2025-12-07 03:29:54 +01:00
// Filter functions
function applyFilter(filter, element) {
currentFilter = filter;
// Update active pill
document.querySelectorAll('.filter-pill').forEach(pill => pill.classList.remove('active'));
element.classList.add('active');
// Reload invoices with filter
loadInvoices(filter);
}
// Open create modal
function openCreateModal() {
document.getElementById('invoiceForm').reset();
document.getElementById('invoiceId').value = '';
document.getElementById('lineItems').innerHTML = '';
setDefaultDates();
new bootstrap.Modal(document.getElementById('invoiceModal')).show();
}
// Add line item
let lineCounter = 0;
function addLine() {
lineCounter++;
const lineHtml = `
< div class = "line-item" id = "line-${lineCounter}" >
< div class = "row" >
< div class = "col-md-5" >
< input type = "text" class = "form-control form-control-sm mb-2"
placeholder="Beskrivelse" name="line_description[]">
< / div >
< div class = "col-md-2" >
< input type = "number" step = "1" class = "form-control form-control-sm mb-2"
placeholder="Antal" name="line_quantity[]" value="1">
< / div >
< div class = "col-md-2" >
< input type = "number" step = "0.01" class = "form-control form-control-sm mb-2"
placeholder="Pris" name="line_price[]">
< / div >
< div class = "col-md-2" >
< select class = "form-select form-select-sm mb-2" name = "line_vat_code[]" >
< option value = "I25" > Moms 25%< / option >
< option value = "I0" > Moms 0%< / option >
< option value = "IY25" > Omvendt 25%< / option >
< / select >
< / div >
< div class = "col-md-1" >
< button type = "button" class = "btn btn-sm btn-outline-danger" onclick = "removeLine(${lineCounter})" >
< i class = "bi bi-x" > < / i >
< / button >
< / div >
< / div >
< / div >
`;
document.getElementById('lineItems').insertAdjacentHTML('beforeend', lineHtml);
}
function removeLine(lineId) {
document.getElementById(`line-${lineId}`)?.remove();
}
// Save invoice
async function saveInvoice() {
try {
const form = document.getElementById('invoiceForm');
if (!form.checkValidity()) {
form.reportValidity();
return;
}
// Collect line items
const lines = [];
const descriptions = form.querySelectorAll('[name="line_description[]"]');
const quantities = form.querySelectorAll('[name="line_quantity[]"]');
const prices = form.querySelectorAll('[name="line_price[]"]');
const vatCodes = form.querySelectorAll('[name="line_vat_code[]"]');
for (let i = 0; i < descriptions.length ; i + + ) {
if (descriptions[i].value.trim()) {
const qty = parseFloat(quantities[i].value) || 1;
const price = parseFloat(prices[i].value) || 0;
const lineTotal = qty * price;
// Calculate VAT based on code
let vatRate = 25;
if (vatCodes[i].value === 'I0') vatRate = 0;
const vatAmount = lineTotal * (vatRate / 100);
lines.push({
description: descriptions[i].value,
quantity: qty,
unit_price: price,
line_total: lineTotal + vatAmount,
vat_code: vatCodes[i].value,
vat_rate: vatRate,
vat_amount: vatAmount,
contra_account: '5810'
});
}
}
const data = {
invoice_number: document.getElementById('invoiceNumber').value,
vendor_id: parseInt(document.getElementById('vendorId').value),
invoice_date: document.getElementById('invoiceDate').value,
due_date: document.getElementById('dueDate').value,
total_amount: parseFloat(document.getElementById('totalAmount').value),
vat_amount: parseFloat(document.getElementById('vatAmount').value) || 0,
currency: document.getElementById('currency').value,
description: document.getElementById('description').value,
notes: document.getElementById('notes').value,
lines: lines
};
const response = await fetch('/api/v1/supplier-invoices', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (response.ok) {
bootstrap.Modal.getInstance(document.getElementById('invoiceModal')).hide();
loadInvoices(currentFilter);
loadStats();
alert('✅ Faktura oprettet');
} else {
const error = await response.json();
alert('❌ Fejl: ' + (error.detail || 'Kunne ikke oprette faktura'));
}
} catch (error) {
console.error('Failed to save invoice:', error);
alert('❌ Fejl ved oprettelse af faktura');
}
}
// View invoice details
async function viewInvoice(invoiceId) {
try {
const response = await fetch(`/api/v1/supplier-invoices/${invoiceId}`);
const invoice = await response.json();
currentInvoiceId = invoiceId;
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
// Check if invoice can be edited (not yet sent to e-conomic)
const isEditable = !invoice.economic_voucher_number;
2025-12-07 03:29:54 +01:00
const detailsHtml = `
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
<!-- Header Section -->
< div class = "card mb-3" >
< div class = "card-body" >
< div class = "row" >
< div class = "col-md-6" >
< div class = "mb-3" >
< label class = "form-label text-muted small mb-1" > Fakturanummer< / label >
${isEditable ?
`< input type = "text" class = "form-control" id = "editInvoiceNumber" value = "${invoice.invoice_number}" > ` :
`< div class = "fw-bold" > ${invoice.invoice_number}< / div > `
}
< / div >
< div class = "mb-3" >
< label class = "form-label text-muted small mb-1" > Leverandør< / label >
< div class = "fw-bold" > ${invoice.vendor_full_name || invoice.vendor_name}< / div >
< / div >
< div class = "mb-3" >
< label class = "form-label text-muted small mb-1" > Status< / label >
< div > ${getStatusBadge(invoice.status)}< / div >
< / div >
< / div >
< div class = "col-md-6" >
< div class = "mb-3" >
< label class = "form-label text-muted small mb-1" > Fakturadato< / label >
${isEditable ?
`< input type = "date" class = "form-control" id = "editInvoiceDate" value = "${invoice.invoice_date}" > ` :
`< div class = "fw-bold" > ${formatDate(invoice.invoice_date)}< / div > `
}
< / div >
< div class = "mb-3" >
< label class = "form-label text-muted small mb-1" > Forfaldsdato< / label >
${isEditable ?
`< input type = "date" class = "form-control" id = "editDueDate" value = "${invoice.due_date}" > ` :
`< div class = "fw-bold" > ${formatDate(invoice.due_date)}< / div > `
}
< / div >
< div class = "mb-3" >
< label class = "form-label text-muted small mb-1" > Totalbeløb< / label >
${isEditable ?
`< div class = "input-group" >
< input type = "number" step = "0.01" class = "form-control" id = "editTotalAmount" value = "${invoice.total_amount}" >
< span class = "input-group-text" > kr.< / span >
< / div > ` :
`< div class = "fw-bold fs-5 text-success" > ${formatCurrency(invoice.total_amount)}< / div > `
}
< / div >
< / div >
< / div >
${invoice.notes ? `
< div class = "mt-3 pt-3 border-top" >
< label class = "form-label text-muted small mb-1" > Noter< / label >
< div class = "alert alert-light mb-0" > ${invoice.notes}< / div >
< / div >
` : ''}
${invoice.economic_voucher_number ? `
< div class = "mt-3 pt-3 border-top" >
< div class = "alert alert-success mb-0" >
< i class = "bi bi-check-circle me-2" > < / i >
< strong > Sendt til e-conomic< / strong > - Bilagsnummer: < strong > ${invoice.economic_voucher_number}< / strong >
< br > < small > Kassekladde ${invoice.economic_journal_number}, Regnskabsår ${invoice.economic_accounting_year}< / small >
< / div >
< / div >
` : ''}
2025-12-07 03:29:54 +01:00
< / div >
< / div >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
<!-- Line Items Section -->
< div class = "card" >
< div class = "card-header bg-light" >
< h6 class = "mb-0" > < i class = "bi bi-list-ul me-2" > < / i > Produktlinier (${(invoice.lines || []).length})< / h6 >
2025-12-07 03:29:54 +01:00
< / div >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< div class = "card-body p-0" >
${isEditable ? `
< div class = "alert alert-info mb-0 border-0 rounded-0" style = "font-size: 0.875rem;" >
💡 < strong > Momskoder:< / strong >
< span class = "badge bg-success ms-2" > I25< / span > 25% moms (standard) ·
< span class = "badge bg-warning text-dark" > I52< / span > Omvendt betalingspligt ·
< span class = "badge bg-secondary" > I0< / span > 0% (momsfri)
< / div >
` : ''}
< div class = "table-responsive" >
< table class = "table table-hover mb-0" >
< thead class = "table-light" >
< tr >
< th style = "width: 100px;" > Varenr< / th >
< th > Beskrivelse< / th >
< th style = "width: 80px;" class = "text-end" > Antal< / th >
< th style = "width: 120px;" class = "text-end" > Enhedspris< / th >
< th style = "width: 120px;" class = "text-end" > Total< / th >
< th style = "width: 140px;" > Momskode< / th >
< th style = "width: 100px;" > Modkonto< / th >
< / tr >
< / thead >
< tbody id = "invoiceLinesList" >
${(invoice.lines || []).map((line, idx) => `
< tr data-line-id = "${line.id || idx}" >
< td >
${isEditable ?
`< input type = "text" class = "form-control form-control-sm line-sku" value = "${line.sku || ''}" placeholder = "SKU" > ` :
`< code class = "small" > ${line.sku || '-'}< / code > `
}
< / td >
< td >
${isEditable ?
`< input type = "text" class = "form-control form-control-sm line-description" value = "${line.description || ''}" placeholder = "Beskrivelse" > ` :
(line.description || '')
}
< / td >
< td class = "text-end" >
${isEditable ?
`< input type = "number" step = "1" class = "form-control form-control-sm text-end line-quantity" value = "${line.quantity}" > ` :
line.quantity
}
< / td >
< td class = "text-end" >
${isEditable ?
`< input type = "number" step = "0.01" class = "form-control form-control-sm text-end line-price" value = "${line.unit_price}" > ` :
formatCurrency(line.unit_price)
}
< / td >
< td class = "text-end" > < strong > ${formatCurrency(line.line_total)}< / strong > < / td >
< td >
${isEditable ? `
< select class = "form-select form-select-sm line-vat-code" >
< option value = "I25" $ { line . vat_code = == ' I25 ' ? ' selected ' : ' ' } > I25 - 25%< / option >
< option value = "I52" $ { line . vat_code = == ' I52 ' ? ' selected ' : ' ' } > I52 - Omvendt< / option >
< option value = "I0" $ { line . vat_code = == ' I0 ' ? ' selected ' : ' ' } > I0 - Momsfri< / option >
< / select >
` : `< span class = "badge bg-${line.vat_code === 'I25' ? 'success' : line.vat_code === 'I52' ? 'warning text-dark' : 'secondary'}" > ${line.vat_code}< / span > < small class = "text-muted" > (${line.vat_rate}%)< / small > `}
< / td >
< td >
${isEditable ?
`< input type = "text" class = "form-control form-control-sm line-contra" value = "${line.contra_account || '5810'}" > ` :
`< code class = "small" > ${line.contra_account || '5810'}< / code > `
}
< / td >
< / tr >
`).join('')}
< / tbody >
< tfoot class = "table-light" >
< tr >
< td colspan = "4" class = "text-end" > < strong > I alt:< / strong > < / td >
< td class = "text-end" > < strong class = "fs-5 text-success" > ${formatCurrency(invoice.total_amount)}< / strong > < / td >
< td colspan = "2" > < / td >
< / tr >
< / tfoot >
< / table >
< / div >
< / div >
${isEditable ? `
< div class = "card-footer bg-light" >
< button type = "button" class = "btn btn-primary" onclick = "saveInvoiceChanges()" >
< i class = "bi bi-save me-1" > < / i > Gem alle ændringer
< / button >
< / div >
` : ''}
< / div >
2025-12-07 03:29:54 +01:00
`;
document.getElementById('invoiceDetails').innerHTML = detailsHtml;
// Show/hide action buttons based on status
document.getElementById('approveBtn').style.display = invoice.status === 'pending' ? 'inline-block' : 'none';
document.getElementById('sendToEconomicBtn').style.display =
(invoice.status === 'approved' & & !invoice.economic_voucher_number) ? 'inline-block' : 'none';
new bootstrap.Modal(document.getElementById('viewInvoiceModal')).show();
} catch (error) {
console.error('Failed to load invoice:', error);
alert('❌ Kunne ikke hente faktura');
}
}
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
// Save invoice changes
async function saveInvoiceChanges() {
if (!currentInvoiceId) return;
try {
// Collect header data
const invoiceNumber = document.getElementById('editInvoiceNumber')?.value;
const invoiceDate = document.getElementById('editInvoiceDate')?.value;
const dueDate = document.getElementById('editDueDate')?.value;
const totalAmount = parseFloat(document.getElementById('editTotalAmount')?.value);
// Collect line items
const lineRows = document.querySelectorAll('#invoiceLinesList tr');
const lines = [];
lineRows.forEach((row, idx) => {
const sku = row.querySelector('.line-sku')?.value || '';
const description = row.querySelector('.line-description')?.value || '';
const quantity = parseFloat(row.querySelector('.line-quantity')?.value) || 1;
const unitPrice = parseFloat(row.querySelector('.line-price')?.value) || 0;
const vatCode = row.querySelector('.line-vat-code')?.value || 'I25';
const contraAccount = row.querySelector('.line-contra')?.value || '5810';
// Determine VAT rate from code
let vatRate = 25.00;
if (vatCode === 'I52' || vatCode === 'I0') {
vatRate = 0.00;
}
lines.push({
line_number: idx + 1,
sku: sku.trim() || null,
description: description,
quantity: quantity,
unit_price: unitPrice,
line_total: quantity * unitPrice,
vat_code: vatCode,
vat_rate: vatRate,
contra_account: contraAccount
});
});
// Update invoice
const response = await fetch(`/api/v1/supplier-invoices/${currentInvoiceId}`, {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
invoice_number: invoiceNumber,
invoice_date: invoiceDate,
due_date: dueDate,
total_amount: totalAmount,
lines: lines
})
});
if (response.ok) {
alert('✅ Ændringer gemt');
// Reload invoice details
await viewInvoice(currentInvoiceId);
// Refresh list
loadInvoices(currentFilter);
} else {
const error = await response.json();
alert('❌ Kunne ikke gemme: ' + (error.detail || 'Ukendt fejl'));
}
} catch (error) {
console.error('Failed to save invoice changes:', error);
alert('❌ Fejl ved gem: ' + error.message);
}
}
2025-12-07 03:29:54 +01:00
// Approve invoice
async function approveInvoice() {
if (!currentInvoiceId) return;
if (!confirm('Godkend denne faktura?')) return;
try {
const response = await fetch(`/api/v1/supplier-invoices/${currentInvoiceId}/approve`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ approved_by: 'CurrentUser' }) // TODO: Get from auth
});
if (response.ok) {
bootstrap.Modal.getInstance(document.getElementById('viewInvoiceModal')).hide();
loadInvoices(currentFilter);
loadStats();
alert('✅ Faktura godkendt');
} else {
const error = await response.json();
alert('❌ Fejl: ' + (error.detail || 'Kunne ikke godkende'));
}
} catch (error) {
console.error('Failed to approve:', error);
alert('❌ Fejl ved godkendelse');
}
}
// Send to e-conomic
async function sendToEconomic() {
if (!currentInvoiceId) return;
if (!confirm('Send denne faktura til e-conomic kassekladde?')) return;
try {
const response = await fetch(`/api/v1/supplier-invoices/${currentInvoiceId}/send-to-economic`, {
method: 'POST'
});
if (response.ok) {
const result = await response.json();
bootstrap.Modal.getInstance(document.getElementById('viewInvoiceModal')).hide();
loadInvoices(currentFilter);
loadStats();
alert(`✅ Faktura sendt til e-conomic\nBilagsnummer: ${result.voucher_number}`);
} else {
const error = await response.json();
alert('❌ Fejl: ' + (error.detail || 'Kunne ikke sende til e-conomic'));
}
} catch (error) {
console.error('Failed to send to e-conomic:', error);
alert('❌ Fejl ved sending til e-conomic');
}
}
// Quick approve
async function quickApprove(invoiceId) {
if (!confirm('Godkend denne faktura?')) return;
try {
const response = await fetch(`/api/v1/supplier-invoices/${invoiceId}/approve`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ approved_by: 'CurrentUser' })
});
if (response.ok) {
loadInvoices(currentFilter);
loadStats();
} else {
alert('❌ Kunne ikke godkende');
}
} catch (error) {
console.error('Failed to approve:', error);
}
}
// Quick send to e-conomic
async function quickSendToEconomic(invoiceId) {
if (!confirm('Send til e-conomic?')) return;
try {
const response = await fetch(`/api/v1/supplier-invoices/${invoiceId}/send-to-economic`, {
method: 'POST'
});
if (response.ok) {
const result = await response.json();
loadInvoices(currentFilter);
loadStats();
alert(`✅ Sendt - Bilag #${result.voucher_number}`);
} else {
const error = await response.json();
alert('❌ Fejl: ' + (error.detail || 'Kunne ikke sende'));
}
} catch (error) {
console.error('Failed to send:', error);
}
}
// Delete invoice
async function deleteInvoice(invoiceId) {
if (!confirm('Slet denne faktura?')) return;
try {
const response = await fetch(`/api/v1/supplier-invoices/${invoiceId}`, {
method: 'DELETE'
});
if (response.ok) {
loadInvoices(currentFilter);
loadStats();
} else {
alert('❌ Kunne ikke slette');
}
} catch (error) {
console.error('Failed to delete:', error);
}
}
// Utility functions
function formatCurrency(amount) {
if (amount === null || amount === undefined) return '-';
return new Intl.NumberFormat('da-DK', {
style: 'currency',
currency: 'DKK'
}).format(amount);
}
function formatDate(dateStr) {
if (!dateStr) return '-';
const date = new Date(dateStr);
return date.toLocaleDateString('da-DK');
}
function getStatusBadge(status) {
const badges = {
'pending': '< span class = "badge status-pending" > Afventer< / span > ',
'approved': '< span class = "badge status-approved" > Godkendt< / span > ',
'sent_to_economic': '< span class = "badge status-sent" > Sendt< / span > ',
'paid': '< span class = "badge status-paid" > Betalt< / span > ',
'overdue': '< span class = "badge status-overdue" > Overskredet< / span > ',
'cancelled': '< span class = "badge bg-secondary" > Annulleret< / span > '
};
return badges[status] || `< span class = "badge bg-secondary" > ${status}< / span > `;
}
// ========== UPLOAD FUNCTIONS ==========
function openUploadModal() {
document.getElementById('uploadForm').reset();
document.getElementById('uploadProgress').classList.add('d-none');
document.getElementById('uploadResult').classList.add('d-none');
document.getElementById('uploadResult').innerHTML = '';
document.getElementById('uploadBtn').disabled = false;
2025-12-08 09:15:52 +01:00
document.getElementById('fileCount').textContent = '';
// Add event listener for file selection
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', function() {
const count = this.files.length;
const fileCountDiv = document.getElementById('fileCount');
if (count > 0) {
fileCountDiv.innerHTML = `< i class = "bi bi-files me-1" > < / i > < strong > ${count} fil(er) valgt< / strong > `;
} else {
fileCountDiv.textContent = '';
}
});
2025-12-07 03:29:54 +01:00
new bootstrap.Modal(document.getElementById('uploadModal')).show();
}
async function uploadInvoice() {
const fileInput = document.getElementById('fileInput');
2025-12-08 09:15:52 +01:00
const files = Array.from(fileInput.files);
2025-12-07 03:29:54 +01:00
2025-12-08 09:15:52 +01:00
if (files.length === 0) {
alert('Vælg venligst minimum én fil');
2025-12-07 03:29:54 +01:00
return;
}
2025-12-08 09:15:52 +01:00
// Validate all files
2025-12-07 03:29:54 +01:00
const allowedTypes = ['.pdf', '.png', '.jpg', '.jpeg'];
2025-12-08 09:15:52 +01:00
for (const file of files) {
const ext = '.' + file.name.split('.').pop().toLowerCase();
if (!allowedTypes.includes(ext)) {
alert(`Kun PDF, PNG eller JPG filer tilladt: ${file.name}`);
return;
}
// Validate file size (50 MB)
if (file.size > 50 * 1024 * 1024) {
alert(`Fil for stor (max 50 MB): ${file.name}`);
return;
}
2025-12-07 03:29:54 +01:00
}
const uploadBtn = document.getElementById('uploadBtn');
const progressDiv = document.getElementById('uploadProgress');
const progressBar = document.getElementById('progressBar');
const progressText = document.getElementById('progressText');
const resultDiv = document.getElementById('uploadResult');
2025-12-08 09:15:52 +01:00
const results = [];
const totalFiles = files.length;
2025-12-07 03:29:54 +01:00
try {
// Show progress
uploadBtn.disabled = true;
progressDiv.classList.remove('d-none');
resultDiv.classList.add('d-none');
2025-12-08 09:15:52 +01:00
// Upload each file
for (let i = 0; i < files.length ; i + + ) {
const file = files[i];
const fileNum = i + 1;
progressBar.style.width = `${(i / totalFiles) * 100}%`;
progressText.textContent = `Uploader fil ${fileNum}/${totalFiles}: ${file.name}...`;
// Create FormData
const formData = new FormData();
formData.append('file', file);
try {
// Upload file
const response = await fetch('/api/v1/supplier-invoices/upload', {
method: 'POST',
body: formData
});
const result = await response.json();
results.push({ file: file.name, success: response.ok, result });
} catch (error) {
results.push({ file: file.name, success: false, result: { message: error.message } });
}
}
2025-12-07 03:29:54 +01:00
progressBar.style.width = '100%';
2025-12-08 09:15:52 +01:00
progressText.textContent = `Færdig! ${totalFiles} fil(er) uploadet`;
2025-12-07 03:29:54 +01:00
// Hide progress after animation
setTimeout(() => {
progressDiv.classList.add('d-none');
2025-12-08 09:15:52 +01:00
showMultiUploadResult(results);
2025-12-07 03:29:54 +01:00
}, 500);
} catch (error) {
console.error('Upload failed:', error);
progressDiv.classList.add('d-none');
2025-12-08 09:15:52 +01:00
showMultiUploadResult([{ file: 'Unknown', success: false, result: { message: error.message } }]);
2025-12-07 03:29:54 +01:00
} finally {
uploadBtn.disabled = false;
}
}
2025-12-08 09:15:52 +01:00
// Show results for multiple file uploads
function showMultiUploadResult(results) {
const resultDiv = document.getElementById('uploadResult');
resultDiv.classList.remove('d-none');
const successCount = results.filter(r => r.success).length;
const failCount = results.length - successCount;
let html = '';
if (successCount > 0) {
html += `
< div class = "alert alert-success" >
< h6 > < i class = "bi bi-check-circle me-2" > < / i > ${successCount} Faktura(er) Uploadet!< / h6 >
< p class = "mb-2" > Filerne er uploadet og klar til gennemgang.< / p >
< ul class = "small mb-0" >
`;
results.filter(r => r.success).forEach(r => {
html += `< li > < strong > ${r.file}< / strong > - ${r.result.message || 'Uploadet'}< / li > `;
});
html += `< / ul > < / div > `;
}
if (failCount > 0) {
html += `
< div class = "alert alert-warning mt-2" >
< h6 > < i class = "bi bi-exclamation-triangle me-2" > < / i > ${failCount} Fil(er) Fejlede< / h6 >
< ul class = "small mb-0" >
`;
results.filter(r => !r.success).forEach(r => {
html += `< li > < strong > ${r.file}< / strong > - ${r.result.message || 'Ukendt fejl'}< / li > `;
});
html += `< / ul > < / div > `;
}
html += `
< div class = "mt-3" >
< button class = "btn btn-primary btn-sm" onclick = "closeUploadAndRefresh()" >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< i class = "bi bi-check-lg me-1" > < / i > OK - Gå til Mangler Behandling
2025-12-08 09:15:52 +01:00
< / button >
< / div >
`;
resultDiv.innerHTML = html;
}
2025-12-07 03:29:54 +01:00
function showUploadResult(result, success) {
const resultDiv = document.getElementById('uploadResult');
resultDiv.classList.remove('d-none');
if (result.status === 'duplicate') {
// Duplicate file or invoice
resultDiv.innerHTML = `
< div class = "alert alert-warning" >
< h6 > < i class = "bi bi-exclamation-triangle me-2" > < / i > Duplicate Detected< / h6 >
< p class = "mb-2" > ${result.message}< / p >
${result.invoice_id ? `
< button class = "btn btn-sm btn-outline-primary" onclick = "viewInvoice(${result.invoice_id})" >
< i class = "bi bi-eye me-1" > < / i > Vis eksisterende faktura
< / button >
` : ''}
< / div >
`;
2025-12-08 09:15:52 +01:00
} else if (success & & (result.status === 'success' || result.status === 'needs_review')) {
// Success - show that file is ready for review
2025-12-07 03:29:54 +01:00
resultDiv.innerHTML = `
< div class = "alert alert-success" >
2025-12-08 09:15:52 +01:00
< h6 > < i class = "bi bi-check-circle me-2" > < / i > Faktura Uploadet!< / h6 >
< p class = "mb-3" > ${result.message || 'Filen er uploadet og klar til gennemgang.'}< / p >
2025-12-07 03:29:54 +01:00
< div class = "mt-3" >
2025-12-08 09:15:52 +01:00
< button class = "btn btn-primary btn-sm" onclick = "closeUploadAndRefresh()" >
feat: Implement quick analysis on PDF upload for CVR, document type, and number extraction
- Added `check_invoice_number_exists` method in `EconomicService` to verify invoice numbers in e-conomic journals.
- Introduced `quick_analysis_on_upload` method in `OllamaService` for extracting critical fields from uploaded PDFs, including CVR, document type, and document number.
- Created migration script to add new fields for storing detected CVR, vendor ID, document type, and document number in the `incoming_files` table.
- Developed comprehensive tests for the quick analysis functionality, validating CVR detection, document type identification, and invoice number extraction.
2025-12-09 14:54:33 +01:00
< i class = "bi bi-check-lg me-1" > < / i > OK - Gå til Mangler Behandling
2025-12-08 09:15:52 +01:00
< / button >
2025-12-07 03:29:54 +01:00
< / div >
< / div >
`;
2025-12-08 09:15:52 +01:00
// Auto-refresh file list
2025-12-07 03:29:54 +01:00
setTimeout(() => {
2025-12-08 09:15:52 +01:00
loadPendingFiles();
2025-12-07 03:29:54 +01:00
loadStats();
2025-12-08 09:15:52 +01:00
}, 500);
2025-12-07 03:29:54 +01:00
} else if (result.status === 'error' & & result.needs_review) {
// AI parsing failed - needs manual review
resultDiv.innerHTML = `
< div class = "alert alert-warning" >
< h6 > < i class = "bi bi-exclamation-triangle me-2" > < / i > AI-Analyse Fejlede - Manuel Gennemgang Nødvendig< / h6 >
< p class = "mb-2" > ${result.message}< / p >
${result.error_details ? `
< details class = "mt-2" >
< summary class = "text-muted small" > Tekniske detaljer< / summary >
< pre class = "small mt-2 text-muted" > ${result.error_details}< / pre >
< / details >
` : ''}
< div class = "mt-3" >
< small class = "text-muted" >
< i class = "bi bi-info-circle me-1" > < / i >
Filen er gemt og kan behandles manuelt senere.
< / small >
< / div >
< button class = "btn btn-secondary btn-sm mt-2" onclick = "closeUploadAndRefresh()" >
< i class = "bi bi-check-lg me-1" > < / i > OK
< / button >
< / div >
`;
} else {
// Error
resultDiv.innerHTML = `
< div class = "alert alert-danger" >
< h6 > < i class = "bi bi-x-circle me-2" > < / i > Upload Fejlede< / h6 >
< p class = "mb-0" > ${result.message || result.detail || 'Ukendt fejl'}< / p >
< / div >
`;
}
}
function closeUploadAndRefresh() {
bootstrap.Modal.getInstance(document.getElementById('uploadModal')).hide();
loadInvoices(currentFilter);
loadStats();
}
// Create template from successfully uploaded invoice
async function createTemplateFromInvoice(invoiceId, vendorId) {
try {
const loadingDiv = document.getElementById('uploadResult');
loadingDiv.innerHTML = `
< div class = "alert alert-info" >
< div class = "d-flex align-items-center" >
< div class = "spinner-border spinner-border-sm me-3" role = "status" > < / div >
< div >
< strong > Opretter template...< / strong > < br >
< small > AI analyserer faktura og genererer patterns...< / small >
< / div >
< / div >
< / div >
`;
// Step 1: Get PDF text from invoice
const reprocessResp = await fetch(`/api/v1/supplier-invoices/reprocess/${invoiceId}`, {
method: 'POST'
});
const pdfData = await reprocessResp.json();
if (!pdfData.pdf_text) {
throw new Error('Kunne ikke hente PDF tekst');
}
// Step 2: AI analyze
const aiResp = await fetch('/api/v1/supplier-invoices/ai-analyze', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
pdf_text: pdfData.pdf_text,
vendor_id: vendorId
})
});
const aiData = await aiResp.json();
// Step 3: Convert AI data to template format
const fieldMappings = {};
const detectionPatterns = [];
// Build field mappings from AI response
if (aiData.vendor_cvr || aiData.cvr) {
const cvrValue = aiData.vendor_cvr?.value || aiData.vendor_cvr || aiData.cvr;
fieldMappings.vendor_cvr = {
pattern: aiData.vendor_cvr?.pattern || `DK\\s*(${cvrValue})`,
group: 1
};
}
if (aiData.invoice_number) {
const invNum = aiData.invoice_number?.value || aiData.invoice_number;
fieldMappings.invoice_number = {
pattern: aiData.invoice_number?.pattern || `(?:Nummer|Faktura|Invoice)\\s*(${invNum})`,
group: 1
};
}
if (aiData.invoice_date) {
fieldMappings.invoice_date = {
pattern: aiData.invoice_date?.pattern || `Dato\\s*(\\d{1,2}[\\/.\\-]\\d{1,2}[\\/.\\-]\\d{2,4})`,
group: 1,
format: "DD.MM.YYYY"
};
}
if (aiData.total_amount) {
fieldMappings.total_amount = {
pattern: aiData.total_amount?.pattern || `(?:Total|I alt|Beløb)\\s*([\\d.,]+)`,
group: 1
};
}
// Lines markers
if (aiData.lines_start) {
fieldMappings.lines_start = {
pattern: aiData.lines_start?.pattern || aiData.lines_start
};
}
if (aiData.lines_end) {
fieldMappings.lines_end = {
pattern: aiData.lines_end?.pattern || aiData.lines_end
};
}
// Detection patterns
if (aiData.detection_patterns & & Array.isArray(aiData.detection_patterns)) {
aiData.detection_patterns.forEach((p, i) => {
const pattern = typeof p === 'string' ? p : p.pattern;
const weight = typeof p === 'object' ? p.weight : (0.5 - i * 0.1);
detectionPatterns.push({
type: 'text',
pattern: pattern,
weight: Math.max(0.1, weight)
});
});
} else {
// Default detection patterns if none provided
detectionPatterns.push({type: 'text', pattern: 'Faktura', weight: 0.3});
}
// Step 4: Create template
const createResp = await fetch('/api/v1/supplier-invoices/templates', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
vendor_id: vendorId,
template_name: `Auto-generated ${new Date().toISOString().split('T')[0]}`,
detection_patterns: detectionPatterns,
field_mappings: fieldMappings
})
});
const template = await createResp.json();
if (!createResp.ok) {
throw new Error(template.detail || 'Kunne ikke oprette template');
}
// Success!
loadingDiv.innerHTML = `
< div class = "alert alert-success" >
< h6 > < i class = "bi bi-check-circle me-2" > < / i > Template Oprettet!< / h6 >
< p class = "mb-2" > Næste gang en faktura fra denne leverandør uploades, vil den blive behandlet automatisk på 0.1 sekunder i stedet for ${aiData._processing_time || '10'} sekunder!< / p >
< div class = "mt-3" >
< a href = "/billing/template-builder?template_id=${template.template_id}" class = "btn btn-primary btn-sm me-2" >
< i class = "bi bi-pencil me-1" > < / i > Rediger Template
< / a >
< a href = "/billing/templates" class = "btn btn-outline-primary btn-sm me-2" >
< i class = "bi bi-list me-1" > < / i > Se Alle Templates
< / a >
< button class = "btn btn-success btn-sm" onclick = "closeUploadAndRefresh()" >
< i class = "bi bi-check-lg me-1" > < / i > Luk
< / button >
< / div >
< / div >
`;
} catch (error) {
console.error('Template creation error:', error);
document.getElementById('uploadResult').innerHTML = `
< div class = "alert alert-danger" >
< h6 > < i class = "bi bi-x-circle me-2" > < / i > Kunne ikke oprette template< / h6 >
< p class = "mb-0" > ${error.message}< / p >
< button class = "btn btn-secondary btn-sm mt-2" onclick = "closeUploadAndRefresh()" > Luk< / button >
< / div >
`;
}
}
< / script >
2025-12-08 09:15:52 +01:00
{% endblock %}