Compare commits

...

5 Commits

6 changed files with 86 additions and 18 deletions

View File

@ -9,7 +9,7 @@ from typing import List, Dict, Optional
from datetime import datetime, date, timedelta
from decimal import Decimal
from pathlib import Path
from app.core.database import execute_query, execute_insert, execute_update, execute_query_single
from app.core.database import execute_query, execute_insert, execute_update, execute_query_single, table_has_column
from app.core.config import settings
from app.services.economic_service import get_economic_service
from app.services.ollama_service import ollama_service
@ -710,8 +710,29 @@ async def list_supplier_invoices(
params.append(vendor_id)
if sag_id:
query += " AND si.sag_id = %s"
params.append(sag_id)
if table_has_column("supplier_invoices", "sag_id"):
query += " AND si.sag_id = %s"
params.append(sag_id)
elif (
table_has_column("supplier_invoice_relations", "supplier_invoice_id")
and table_has_column("supplier_invoice_relations", "relation_type")
and table_has_column("supplier_invoice_relations", "relation_id")
):
query += """
AND EXISTS (
SELECT 1
FROM supplier_invoice_relations sir
WHERE sir.supplier_invoice_id = si.id
AND sir.relation_type = 'sag'
AND sir.relation_id = %s
)
"""
params.append(sag_id)
else:
logger.warning(
"⚠️ supplier invoice sag filter requested, but no schema link available (sag_id column/relation table missing)"
)
query += " AND 1 = 0"
if overdue_only:
query += " AND si.due_date < CURRENT_DATE AND si.paid_date IS NULL"

View File

@ -3461,7 +3461,7 @@
<div class="d-flex justify-content-between align-items-center mb-1">
<label class="mb-0 text-secondary" style="font-size:0.8rem;">Status</label>
<select id="topbarStatusSelect" class="form-select form-select-sm bg-light" style="width: 62%;">
<select id="topbarStatusSelect" class="form-select form-select-sm bg-light" style="width: 62%;" onchange="saveCaseStatusFromTopbar()">
{% for st in status_options %}
<option value="{{ st }}" {% if (case.status or '')|lower == st|lower %}selected{% endif %}>{{ st|capitalize }}</option>
{% endfor %}
@ -3708,11 +3708,6 @@
});
};
bindChange('topbarStatusSelect', async (el) => {
await patchCase({ status: el.value || 'åben' });
location.reload();
});
bindChange('topbarTypeSelect', async (el) => {
await patchCase({ type: String(el.value || 'ticket').toLowerCase() });
location.reload();

View File

@ -1267,7 +1267,7 @@ window.addEventListener('unhandledrejection', function(event) {
<script src="/static/js/tag-picker.js?v=2.2"></script>
<script src="/static/js/task-template-selector.js?v=1.1"></script>
<script src="/static/js/notifications.js?v=1.0"></script>
<script src="/static/js/telefoni.js?v=2.2"></script>
<script src="/static/js/telefoni.js?v=2.3"></script>
<script src="/static/js/sms.js?v=1.0"></script>
<script src="/static/js/bug-report.js?v=1.0"></script>
<script src="/static/js/bottom-bar.js?v=2.23"></script>

View File

@ -536,12 +536,18 @@ if __name__ == "__main__":
log_level="info"
)
else:
api_workers_raw = os.getenv("API_WORKERS", "1").strip()
try:
api_workers = max(1, int(api_workers_raw))
except ValueError:
api_workers = 1
uvicorn.run(
"main:app",
host="0.0.0.0",
port=8000,
reload=False,
workers=2,
workers=api_workers,
timeout_keep_alive=65,
access_log=True,
log_level="info"

View File

@ -0,0 +1,48 @@
-- Migration 190: Align sag_sager status constraint with current case status model
-- Fixes PATCH /api/v1/sag/{id} failures when using statuses beyond 'åben'/'lukket'.
DO $$
DECLARE
constraint_row RECORD;
BEGIN
-- Drop legacy check constraints on sag_sager.status regardless of their generated name.
FOR constraint_row IN
SELECT c.conname
FROM pg_constraint c
JOIN pg_class t ON t.oid = c.conrelid
JOIN pg_namespace n ON n.oid = t.relnamespace
WHERE n.nspname = 'public'
AND t.relname = 'sag_sager'
AND c.contype = 'c'
AND pg_get_constraintdef(c.oid) ILIKE '%status%'
LOOP
EXECUTE format('ALTER TABLE public.sag_sager DROP CONSTRAINT IF EXISTS %I', constraint_row.conname);
END LOOP;
END $$;
UPDATE public.sag_sager
SET status = CASE
WHEN lower(trim(status)) IN ('aaben', 'open') THEN 'åben'
WHEN lower(trim(status)) IN ('i_gang', 'in_progress', 'under behandling') THEN 'under behandling'
WHEN lower(trim(status)) IN ('on_hold', 'waiting', 'afventer') THEN 'afventer'
WHEN lower(trim(status)) IN ('resolved', 'løst') THEN 'løst'
WHEN lower(trim(status)) IN ('closed', 'afsluttet', 'lukket') THEN 'lukket'
ELSE status
END;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM pg_constraint c
JOIN pg_class t ON t.oid = c.conrelid
JOIN pg_namespace n ON n.oid = t.relnamespace
WHERE n.nspname = 'public'
AND t.relname = 'sag_sager'
AND c.conname = 'sag_sager_status_check'
) THEN
ALTER TABLE public.sag_sager
ADD CONSTRAINT sag_sager_status_check
CHECK (status IN ('åben', 'under behandling', 'afventer', 'løst', 'lukket'));
END IF;
END $$;

View File

@ -226,18 +226,16 @@
}
function connect() {
if (ws && ws.readyState === WebSocket.OPEN) {
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
return;
}
const token = getToken();
if (!token) {
scheduleReconnect();
return;
}
const proto = window.location.protocol === 'https:' ? 'wss' : 'ws';
const url = `${proto}://${window.location.host}/api/v1/telefoni/ws?token=${encodeURIComponent(token)}`;
// Fallback to cookie-auth websocket when token is HttpOnly and cannot be read by JS.
const url = token
? `${proto}://${window.location.host}/api/v1/telefoni/ws?token=${encodeURIComponent(token)}`
: `${proto}://${window.location.host}/api/v1/telefoni/ws`;
ws = new WebSocket(url);
ws.onopen = () => console.log('📞 Telefoni WS connected');