diff --git a/app/dashboard/backend/mission_service.py b/app/dashboard/backend/mission_service.py index 59f1899..b85d3e6 100644 --- a/app/dashboard/backend/mission_service.py +++ b/app/dashboard/backend/mission_service.py @@ -107,6 +107,21 @@ class MissionService: FROM contacts c WHERE RIGHT(regexp_replace(COALESCE(c.phone, ''), '\\D', '', 'g'), 8) = RIGHT(regexp_replace(%s, '\\D', '', 'g'), 8) OR RIGHT(regexp_replace(COALESCE(c.mobile, ''), '\\D', '', 'g'), 8) = RIGHT(regexp_replace(%s, '\\D', '', 'g'), 8) + ORDER BY ( + SELECT COUNT(*) + FROM sag_kontakter sk + JOIN sag_sager s ON s.id = sk.sag_id + WHERE sk.contact_id = c.id + AND sk.deleted_at IS NULL + AND s.deleted_at IS NULL + AND LOWER(COALESCE(s.status, '')) <> 'lukket' + ) DESC, + ( + SELECT MAX(t.started_at) + FROM telefoni_opkald t + WHERE t.kontakt_id = c.id + ) DESC NULLS LAST, + c.id ASC LIMIT 1 """ row = execute_query_single(query, (caller_number, caller_number)) diff --git a/app/modules/telefoni/backend/service.py b/app/modules/telefoni/backend/service.py index 295f644..c96d3cf 100644 --- a/app/modules/telefoni/backend/service.py +++ b/app/modules/telefoni/backend/service.py @@ -29,6 +29,14 @@ class TelefoniService: c.id, c.first_name, c.last_name, + ( + SELECT cu.id + FROM contact_companies cc + JOIN customers cu ON cu.id = cc.customer_id + WHERE cc.contact_id = c.id + ORDER BY cc.is_primary DESC NULLS LAST, cc.id ASC + LIMIT 1 + ) AS company_id, ( SELECT cu.name FROM contact_companies cc @@ -36,11 +44,25 @@ class TelefoniService: WHERE cc.contact_id = c.id ORDER BY cc.is_primary DESC NULLS LAST, cc.id ASC LIMIT 1 - ) AS company + ) AS company, + ( + SELECT COUNT(*) + FROM sag_kontakter sk + JOIN sag_sager s ON s.id = sk.sag_id + WHERE sk.contact_id = c.id + AND sk.deleted_at IS NULL + AND s.deleted_at IS NULL + AND LOWER(COALESCE(s.status, '')) <> 'lukket' + ) AS open_case_count, + ( + SELECT MAX(t.started_at) + FROM telefoni_opkald t + WHERE t.kontakt_id = c.id + ) AS last_call_at FROM contacts c WHERE RIGHT(regexp_replace(COALESCE(c.phone, ''), '\\D', '', 'g'), 8) = %s OR RIGHT(regexp_replace(COALESCE(c.mobile, ''), '\\D', '', 'g'), 8) = %s - ORDER BY c.id ASC + ORDER BY open_case_count DESC, last_call_at DESC NULLS LAST, c.id ASC LIMIT 1 """ row = execute_query_single(query, (suffix8, suffix8)) @@ -49,6 +71,7 @@ class TelefoniService: return { "id": row["id"], "name": f"{(row.get('first_name') or '').strip()} {(row.get('last_name') or '').strip()}".strip(), + "company_id": row.get("company_id"), "company": row.get("company"), } diff --git a/app/settings/backend/router.py b/app/settings/backend/router.py index a47c026..7791dce 100644 --- a/app/settings/backend/router.py +++ b/app/settings/backend/router.py @@ -259,6 +259,14 @@ async def get_setting_categories(): return [row['category'] for row in result] if result else [] +@router.get("/settings/migrations/status", tags=["Settings"]) +async def get_migration_statuses_api(): + """Expose migration status via API router (served under /api/v1).""" + from app.settings.backend.views import migration_statuses + + return migration_statuses() + + @router.post("/settings/sync-from-env", tags=["Settings"]) async def sync_settings_from_env(): """Sync settings from .env file into database (only updates empty values)""" diff --git a/app/settings/backend/views.py b/app/settings/backend/views.py index cadcd42..1b52bf0 100644 --- a/app/settings/backend/views.py +++ b/app/settings/backend/views.py @@ -281,12 +281,6 @@ def migration_statuses(): release_db_connection(conn) -@router.get("/api/v1/settings/migrations/status", tags=["Frontend"]) -def migration_statuses_api_v1_alias(): - """API-prefixed alias for environments/routes expecting /api/v1 prefix.""" - return migration_statuses() - - @router.post("/settings/migrations/execute", tags=["Frontend"]) def execute_migration(payload: MigrationExecution): """Execute a migration SQL file""" diff --git a/migrations/151_fix_opportunity_comment_attachments_schema.sql b/migrations/151_fix_opportunity_comment_attachments_schema.sql new file mode 100644 index 0000000..8bbd801 --- /dev/null +++ b/migrations/151_fix_opportunity_comment_attachments_schema.sql @@ -0,0 +1,57 @@ +-- Migration 151: Reconcile opportunity comment attachments schema with migration 019 expectations +-- Some environments already had pipeline_opportunity_comment_attachments with legacy columns, +-- so migration 019 (CREATE TABLE IF NOT EXISTS) did not add these fields. + +ALTER TABLE IF EXISTS pipeline_opportunity_comment_attachments + ADD COLUMN IF NOT EXISTS content_type VARCHAR(100), + ADD COLUMN IF NOT EXISTS stored_name TEXT, + ADD COLUMN IF NOT EXISTS uploaded_by_user_id INTEGER; + +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'pipeline_opportunity_comment_attachments' + AND column_name = 'file_path' + ) THEN + UPDATE pipeline_opportunity_comment_attachments + SET stored_name = COALESCE(stored_name, file_path) + WHERE stored_name IS NULL; + END IF; + + IF EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'pipeline_opportunity_comment_attachments' + AND column_name = 'file_type' + ) THEN + UPDATE pipeline_opportunity_comment_attachments + SET content_type = COALESCE(content_type, file_type) + WHERE content_type IS NULL; + END IF; +END +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'pipeline_opportunity_comment_attachments_uploaded_by_fkey' + ) THEN + ALTER TABLE pipeline_opportunity_comment_attachments + ADD CONSTRAINT pipeline_opportunity_comment_attachments_uploaded_by_fkey + FOREIGN KEY (uploaded_by_user_id) + REFERENCES users(user_id) + ON DELETE SET NULL; + END IF; +END +$$; + +-- Keep existing rows valid while allowing future inserts to set explicit stored_name. +UPDATE pipeline_opportunity_comment_attachments +SET stored_name = COALESCE(stored_name, filename) +WHERE stored_name IS NULL; diff --git a/static/js/telefoni.js b/static/js/telefoni.js index 18038cc..c579093 100644 --- a/static/js/telefoni.js +++ b/static/js/telefoni.js @@ -163,6 +163,7 @@ if (action === 'create-case') { const qs = new URLSearchParams(); if (contact?.id) qs.set('contact_id', String(contact.id)); + if (contact?.company_id) qs.set('customer_id', String(contact.company_id)); qs.set('title', `Telefonsamtale – ${number}`); qs.set('telefoni_opkald_id', String(callId)); window.location.href = `/sag/new?${qs.toString()}`;