@@ -2374,7 +2384,8 @@ async function testTelefoniCall() {
function renderDriftConnectors() {
const container = document.getElementById('driftConnectorCards');
- if (!container) return;
+ const invoiceErrorFinderContainer = document.getElementById('invoiceErrorFinderSettingsCard');
+ if (!container && !invoiceErrorFinderContainer) return;
const connectors = [
{
@@ -2440,10 +2451,35 @@ function renderDriftConnectors() {
`
+ },
+ {
+ key: 'invoice-error-finder',
+ title: 'Faktura-fejl-finder',
+ description: 'Styr hvilke varetekster der skal ignoreres, sa gebyrer og fragt ikke opretter falske fejl.',
+ badge: 'Okonomi',
+ body: `
+
+
+
+
+
+
+
+
Matcher paa varetekst og beskrivelse i e-conomic samt varenavn i Simply-ordrer.
+
+
+
+
+
+
+
+ `
}
];
- container.innerHTML = connectors.map(connector => `
+ const renderConnectorCard = (connector) => `
@@ -2454,7 +2490,21 @@ function renderDriftConnectors() {
${connector.body}
- `).join('');
+ `;
+
+ if (container) {
+ container.innerHTML = connectors
+ .filter(connector => connector.key !== 'invoice-error-finder')
+ .map(renderConnectorCard)
+ .join('');
+ }
+
+ if (invoiceErrorFinderContainer) {
+ const invoiceErrorFinderConnector = connectors.find(connector => connector.key === 'invoice-error-finder');
+ invoiceErrorFinderContainer.innerHTML = invoiceErrorFinderConnector
+ ? renderConnectorCard(invoiceErrorFinderConnector)
+ : '';
+ }
}
async function loadSettings() {
@@ -2477,6 +2527,7 @@ async function loadSettings() {
renderDriftConnectors();
await loadUptimeKumaSettings();
await loadUISPSettings();
+ await loadInvoiceErrorFinderSettings();
await loadLabelPrinterSettings();
} catch (error) {
console.error('Error loading settings:', error);
@@ -2805,6 +2856,30 @@ async function loadUISPSettings() {
}
let driftBlacklistItems = [];
+const DEFAULT_INVOICE_ERROR_FINDER_IGNORE_TEXTS = [
+ 'faktureringsgebyr',
+ 'gebyr',
+ 'porto',
+ 'fragt',
+ 'fragtomkostning',
+ 'forsendelse',
+ 'shipping',
+ 'levering',
+ 'engangsydelse',
+ 'engangsarbejde',
+ 'oprettelse',
+ 'opstartsgebyr',
+ 'installation',
+ 'installationsgebyr',
+ 'timeforbrug',
+ 'arbejdstid',
+ 'konsulenttimer',
+ 'supporttid',
+ 'teknikertid',
+ 'montørtimer',
+ 'projektarbejde'
+];
+let invoiceErrorFinderIgnoreItems = [...DEFAULT_INVOICE_ERROR_FINDER_IGNORE_TEXTS];
function parseDriftBlacklistValue(rawValue) {
const raw = String(rawValue || '').trim();
@@ -2862,6 +2937,131 @@ function removeDriftBlacklistItem(item) {
renderDriftBlacklistList();
}
+function parseInvoiceErrorFinderIgnoreValue(rawValue) {
+ const raw = String(rawValue || '').trim();
+ if (!raw) return [...DEFAULT_INVOICE_ERROR_FINDER_IGNORE_TEXTS];
+
+ let values = [];
+ try {
+ const parsed = JSON.parse(raw);
+ if (Array.isArray(parsed)) {
+ values = parsed;
+ } else if (typeof parsed === 'string') {
+ values = [parsed];
+ }
+ } catch (e) {
+ values = raw.replaceAll(';', '\n').replaceAll(',', '\n').split('\n');
+ }
+
+ const seen = new Set();
+ const cleaned = [];
+ [...DEFAULT_INVOICE_ERROR_FINDER_IGNORE_TEXTS, ...values].forEach(item => {
+ const normalized = String(item || '').trim().toLowerCase();
+ if (!normalized || seen.has(normalized)) return;
+ seen.add(normalized);
+ cleaned.push(normalized);
+ });
+ return cleaned;
+}
+
+function renderInvoiceErrorFinderIgnoreList() {
+ const list = document.getElementById('invoiceErrorFinderIgnoreList');
+ if (!list) return;
+ if (!invoiceErrorFinderIgnoreItems.length) {
+ list.innerHTML = '
Ingen varetekster ignoreres endnu.';
+ return;
+ }
+ list.innerHTML = invoiceErrorFinderIgnoreItems.map(item => {
+ const safe = String(item).replace(//g, '>');
+ return `
${safe} `;
+ }).join('');
+}
+
+function addInvoiceErrorFinderIgnoreItem() {
+ const input = document.getElementById('invoiceErrorFinderIgnoreInput');
+ if (!input) return;
+ const value = String(input.value || '').trim().toLowerCase();
+ if (!value) return;
+ if (!invoiceErrorFinderIgnoreItems.includes(value)) {
+ invoiceErrorFinderIgnoreItems.push(value);
+ }
+ input.value = '';
+ renderInvoiceErrorFinderIgnoreList();
+}
+
+function removeInvoiceErrorFinderIgnoreItem(item) {
+ invoiceErrorFinderIgnoreItems = invoiceErrorFinderIgnoreItems.filter(v => v !== item);
+ renderInvoiceErrorFinderIgnoreList();
+}
+
+async function loadInvoiceErrorFinderSettings() {
+ try {
+ const response = await fetch('/api/v1/settings/invoice_error_finder_ignored_product_texts', { credentials: 'include' });
+ if (!response.ok) {
+ invoiceErrorFinderIgnoreItems = [...DEFAULT_INVOICE_ERROR_FINDER_IGNORE_TEXTS];
+ renderInvoiceErrorFinderIgnoreList();
+ return;
+ }
+
+ const setting = await response.json();
+ invoiceErrorFinderIgnoreItems = parseInvoiceErrorFinderIgnoreValue(setting?.value || '[]');
+ renderInvoiceErrorFinderIgnoreList();
+ } catch (e) {
+ console.warn('Invoice Error Finder settings load failed:', e);
+ invoiceErrorFinderIgnoreItems = [...DEFAULT_INVOICE_ERROR_FINDER_IGNORE_TEXTS];
+ renderInvoiceErrorFinderIgnoreList();
+ }
+}
+
+async function saveInvoiceErrorFinderSettings() {
+ const statusEl = document.getElementById('invoiceErrorFinderSaveStatus');
+ statusEl.textContent = 'Gemmer...';
+ statusEl.className = 'small text-muted';
+
+ const value = JSON.stringify(parseInvoiceErrorFinderIgnoreValue(invoiceErrorFinderIgnoreItems));
+ const payload = {
+ key: 'invoice_error_finder_ignored_product_texts',
+ value,
+ category: 'finance',
+ description: 'JSON array of invoice product texts/descriptions ignored by Invoice Error Finder',
+ value_type: 'string',
+ is_public: false
+ };
+
+ try {
+ let response = await fetch('/api/v1/settings/invoice_error_finder_ignored_product_texts', {
+ method: 'PUT',
+ credentials: 'include',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ value })
+ });
+
+ if (response.status === 404 || response.status === 405) {
+ response = await fetch('/api/v1/settings', {
+ method: 'POST',
+ credentials: 'include',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(payload)
+ });
+ }
+
+ if (!response.ok) {
+ throw new Error(await getErrorMessage(response, 'Kunne ikke gemme ignore-listen'));
+ }
+
+ invoiceErrorFinderIgnoreItems = parseInvoiceErrorFinderIgnoreValue(value);
+ renderInvoiceErrorFinderIgnoreList();
+ statusEl.textContent = '✅ Gemt';
+ statusEl.className = 'small text-success';
+ setTimeout(() => { statusEl.textContent = ''; }, 3000);
+ showNotification('Ignore-liste gemt', 'success');
+ } catch (error) {
+ statusEl.textContent = '❌ Kunne ikke gemme';
+ statusEl.className = 'small text-danger';
+ showNotification(error.message || 'Kunne ikke gemme ignore-listen', 'error');
+ }
+}
+
async function saveUISPSettings() {
const baseUrl = (document.getElementById('uispBaseUrl').value || '').trim();
const apiToken = (document.getElementById('uispApiToken').value || '').trim();
diff --git a/app/shared/frontend/base.html b/app/shared/frontend/base.html
index 2f8b39e..bf51814 100644
--- a/app/shared/frontend/base.html
+++ b/app/shared/frontend/base.html
@@ -1704,13 +1704,13 @@ if (bmcOriginalFetch) {
if (e.key === '+' && !e.ctrlKey && !e.metaKey && !e.shiftKey) {
if (['INPUT', 'TEXTAREA'].includes(e.target.tagName)) return;
e.preventDefault();
- openQuickCreateModal();
+ openNewCasePage();
}
// Cmd+Shift+C / Ctrl+Shift+C for QuickCreate
if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'c') {
e.preventDefault();
- openQuickCreateModal();
+ openNewCasePage();
}
// ESC to close
@@ -1719,22 +1719,14 @@ if (bmcOriginalFetch) {
}
});
- // QuickCreate modal opener function
- function openQuickCreateModal() {
- const quickCreateModal = new bootstrap.Modal(document.getElementById('quickCreateModal'));
- quickCreateModal.show();
- setTimeout(() => {
- const textInput = document.getElementById('quickCreateText');
- if (textInput) {
- textInput.focus();
- }
- }, 300);
+ function openNewCasePage() {
+ window.location.href = '/sag/new';
}
// QuickCreate button click handler
document.getElementById('quickCreateBtn')?.addEventListener('click', (e) => {
e.preventDefault();
- openQuickCreateModal();
+ openNewCasePage();
});
// Reset search when modal is closed
@@ -2257,9 +2249,6 @@ if (bmcOriginalFetch) {
});
-
-{% include ["quick_create_modal.html", "shared/frontend/quick_create_modal.html"] ignore missing %}
-
{% include ["manual_modal.html", "shared/frontend/manual_modal.html"] ignore missing %}
@@ -2305,6 +2294,11 @@ if (bmcOriginalFetch) {
+
+
+
+
Bruges som udgangspunkt på “Ny sag”.
+