bmc_hub/app/modules/sag/backend/router.py

368 lines
14 KiB
Python
Raw Normal View History

import logging
from typing import List, Optional
from fastapi import APIRouter, HTTPException, Query
from app.core.database import execute_query
from datetime import datetime
logger = logging.getLogger(__name__)
router = APIRouter()
# ============================================================================
# SAGER - CRUD Operations
# ============================================================================
@router.get("/sag")
async def list_sager(
status: Optional[str] = Query(None),
tag: Optional[str] = Query(None),
customer_id: Optional[int] = Query(None),
ansvarlig_bruger_id: Optional[int] = Query(None),
):
"""List all cases with optional filtering."""
try:
query = "SELECT * FROM sag_sager WHERE deleted_at IS NULL"
params = []
if status:
query += " AND status = %s"
params.append(status)
if customer_id:
query += " AND customer_id = %s"
params.append(customer_id)
if ansvarlig_bruger_id:
query += " AND ansvarlig_bruger_id = %s"
params.append(ansvarlig_bruger_id)
query += " ORDER BY created_at DESC"
cases = execute_query(query, tuple(params))
# If tag filter, filter in Python after fetch
if tag:
case_ids = [case['id'] for case in cases]
if case_ids:
tag_query = "SELECT sag_id FROM sag_tags WHERE tag_navn = %s AND deleted_at IS NULL"
tagged_cases = execute_query(tag_query, (tag,))
tagged_ids = set(t['sag_id'] for t in tagged_cases)
cases = [c for c in cases if c['id'] in tagged_ids]
return cases
except Exception as e:
logger.error("❌ Error listing cases: %s", e)
raise HTTPException(status_code=500, detail="Failed to list cases")
@router.post("/sag")
async def create_sag(data: dict):
"""Create a new case."""
try:
if not data.get('titel'):
raise HTTPException(status_code=400, detail="titel is required")
if not data.get('customer_id'):
raise HTTPException(status_code=400, detail="customer_id is required")
query = """
INSERT INTO sag_sager
(titel, beskrivelse, type, status, customer_id, ansvarlig_bruger_id, deadline)
VALUES (%s, %s, %s, %s, %s, %s, %s)
RETURNING *
"""
params = (
data.get('titel'),
data.get('beskrivelse', ''),
data.get('type', 'ticket'),
data.get('status', 'åben'),
data.get('customer_id'),
data.get('ansvarlig_bruger_id'),
data.get('deadline'),
)
result = execute_query(query, params)
if result:
logger.info("✅ Case created: %s", result[0]['id'])
return result[0]
raise HTTPException(status_code=500, detail="Failed to create case")
except Exception as e:
logger.error("❌ Error creating case: %s", e)
raise HTTPException(status_code=500, detail="Failed to create case")
@router.get("/sag/{sag_id}")
async def get_sag(sag_id: int):
"""Get a specific case."""
try:
query = "SELECT * FROM sag_sager WHERE id = %s AND deleted_at IS NULL"
result = execute_query(query, (sag_id,))
if not result:
raise HTTPException(status_code=404, detail="Case not found")
return result[0]
except HTTPException:
raise
except Exception as e:
logger.error("❌ Error getting case: %s", e)
raise HTTPException(status_code=500, detail="Failed to get case")
@router.patch("/sag/{sag_id}")
async def update_sag(sag_id: int, updates: dict):
"""Update a case."""
try:
# Check if case exists
check = execute_query("SELECT id FROM sag_sager WHERE id = %s AND deleted_at IS NULL", (sag_id,))
if not check:
raise HTTPException(status_code=404, detail="Case not found")
# Build dynamic update query
allowed_fields = ['titel', 'beskrivelse', 'type', 'status', 'ansvarlig_bruger_id', 'deadline']
set_clauses = []
params = []
for field in allowed_fields:
if field in updates:
set_clauses.append(f"{field} = %s")
params.append(updates[field])
if not set_clauses:
raise HTTPException(status_code=400, detail="No valid fields to update")
params.append(sag_id)
query = f"UPDATE sag_sager SET {', '.join(set_clauses)} WHERE id = %s RETURNING *"
result = execute_query(query, tuple(params))
if result:
logger.info("✅ Case updated: %s", sag_id)
return result[0]
raise HTTPException(status_code=500, detail="Failed to update case")
except HTTPException:
raise
except Exception as e:
logger.error("❌ Error updating case: %s", e)
raise HTTPException(status_code=500, detail="Failed to update case")
@router.delete("/sag/{sag_id}")
async def delete_sag(sag_id: int):
"""Soft-delete a case."""
try:
check = execute_query("SELECT id FROM sag_sager WHERE id = %s AND deleted_at IS NULL", (sag_id,))
if not check:
raise HTTPException(status_code=404, detail="Case not found")
query = "UPDATE sag_sager SET deleted_at = NOW() WHERE id = %s RETURNING id"
result = execute_query(query, (sag_id,))
if result:
logger.info("✅ Case soft-deleted: %s", sag_id)
return {"status": "deleted", "id": sag_id}
raise HTTPException(status_code=500, detail="Failed to delete case")
except HTTPException:
raise
except Exception as e:
logger.error("❌ Error deleting case: %s", e)
raise HTTPException(status_code=500, detail="Failed to delete case")
# ============================================================================
# RELATIONER - Case Relations
# ============================================================================
@router.get("/sag/{sag_id}/relationer")
async def get_relationer(sag_id: int):
"""Get all relations for a case."""
try:
# Check if case exists
check = execute_query("SELECT id FROM sag_sager WHERE id = %s AND deleted_at IS NULL", (sag_id,))
if not check:
raise HTTPException(status_code=404, detail="Case not found")
query = """
SELECT sr.*,
ss_kilde.titel as kilde_titel,
ss_mål.titel as mål_titel
FROM sag_relationer sr
JOIN sag_sager ss_kilde ON sr.kilde_sag_id = ss_kilde.id
JOIN sag_sager ss_mål ON sr.målsag_id = ss_mål.id
WHERE (sr.kilde_sag_id = %s OR sr.målsag_id = %s)
AND sr.deleted_at IS NULL
ORDER BY sr.created_at DESC
"""
result = execute_query(query, (sag_id, sag_id))
return result
except HTTPException:
raise
except Exception as e:
logger.error("❌ Error getting relations: %s", e)
raise HTTPException(status_code=500, detail="Failed to get relations")
@router.post("/sag/{sag_id}/relationer")
async def create_relation(sag_id: int, data: dict):
"""Add a relation to another case."""
try:
if not data.get('målsag_id') or not data.get('relationstype'):
raise HTTPException(status_code=400, detail="målsag_id and relationstype required")
målsag_id = data.get('målsag_id')
relationstype = data.get('relationstype')
# Validate both cases exist
check1 = execute_query("SELECT id FROM sag_sager WHERE id = %s AND deleted_at IS NULL", (sag_id,))
check2 = execute_query("SELECT id FROM sag_sager WHERE id = %s AND deleted_at IS NULL", (målsag_id,))
if not check1 or not check2:
raise HTTPException(status_code=404, detail="One or both cases not found")
query = """
INSERT INTO sag_relationer (kilde_sag_id, målsag_id, relationstype)
VALUES (%s, %s, %s)
RETURNING *
"""
result = execute_query(query, (sag_id, målsag_id, relationstype))
if result:
logger.info("✅ Relation created: %s -> %s (%s)", sag_id, målsag_id, relationstype)
return result[0]
raise HTTPException(status_code=500, detail="Failed to create relation")
except HTTPException:
raise
except Exception as e:
logger.error("❌ Error creating relation: %s", e)
raise HTTPException(status_code=500, detail="Failed to create relation")
@router.delete("/sag/{sag_id}/relationer/{relation_id}")
async def delete_relation(sag_id: int, relation_id: int):
"""Soft-delete a relation."""
try:
check = execute_query(
"SELECT id FROM sag_relationer WHERE id = %s AND deleted_at IS NULL AND (kilde_sag_id = %s OR målsag_id = %s)",
(relation_id, sag_id, sag_id)
)
if not check:
raise HTTPException(status_code=404, detail="Relation not found")
query = "UPDATE sag_relationer SET deleted_at = NOW() WHERE id = %s RETURNING id"
result = execute_query(query, (relation_id,))
if result:
logger.info("✅ Relation soft-deleted: %s", relation_id)
return {"status": "deleted", "id": relation_id}
raise HTTPException(status_code=500, detail="Failed to delete relation")
except HTTPException:
raise
except Exception as e:
logger.error("❌ Error deleting relation: %s", e)
raise HTTPException(status_code=500, detail="Failed to delete relation")
# ============================================================================
# TAGS - Case Tags
# ============================================================================
@router.get("/sag/{sag_id}/tags")
async def get_tags(sag_id: int):
"""Get all tags for a case."""
try:
check = execute_query("SELECT id FROM sag_sager WHERE id = %s AND deleted_at IS NULL", (sag_id,))
if not check:
raise HTTPException(status_code=404, detail="Case not found")
query = "SELECT * FROM sag_tags WHERE sag_id = %s AND deleted_at IS NULL ORDER BY created_at DESC"
result = execute_query(query, (sag_id,))
return result
except HTTPException:
raise
except Exception as e:
logger.error("❌ Error getting tags: %s", e)
raise HTTPException(status_code=500, detail="Failed to get tags")
@router.post("/sag/{sag_id}/tags")
async def add_tag(sag_id: int, data: dict):
"""Add a tag to a case."""
try:
if not data.get('tag_navn'):
raise HTTPException(status_code=400, detail="tag_navn is required")
check = execute_query("SELECT id FROM sag_sager WHERE id = %s AND deleted_at IS NULL", (sag_id,))
if not check:
raise HTTPException(status_code=404, detail="Case not found")
query = """
INSERT INTO sag_tags (sag_id, tag_navn)
VALUES (%s, %s)
RETURNING *
"""
result = execute_query(query, (sag_id, data.get('tag_navn')))
if result:
logger.info("✅ Tag added: %s -> %s", sag_id, data.get('tag_navn'))
return result[0]
raise HTTPException(status_code=500, detail="Failed to add tag")
except HTTPException:
raise
except Exception as e:
logger.error("❌ Error adding tag: %s", e)
raise HTTPException(status_code=500, detail="Failed to add tag")
@router.delete("/sag/{sag_id}/tags/{tag_id}")
async def delete_tag(sag_id: int, tag_id: int):
"""Soft-delete a tag."""
try:
check = execute_query(
"SELECT id FROM sag_tags WHERE id = %s AND sag_id = %s AND deleted_at IS NULL",
(tag_id, sag_id)
)
if not check:
raise HTTPException(status_code=404, detail="Tag not found")
query = "UPDATE sag_tags SET deleted_at = NOW() WHERE id = %s RETURNING id"
result = execute_query(query, (tag_id,))
if result:
logger.info("✅ Tag soft-deleted: %s", tag_id)
return {"status": "deleted", "id": tag_id}
raise HTTPException(status_code=500, detail="Failed to delete tag")
except HTTPException:
raise
except Exception as e:
logger.error("❌ Error deleting tag: %s", e)
raise HTTPException(status_code=500, detail="Failed to delete tag")
# ============================================================================
# HARDWARE - Placeholder endpoints for frontend compatibility
# ============================================================================
@router.get("/sag/{sag_id}/hardware")
async def list_case_hardware(sag_id: int):
"""List hardware associated with a case. Placeholder endpoint."""
# TODO: Implement when hardware-case relation is defined
return []
@router.post("/sag/{sag_id}/hardware")
async def add_case_hardware(sag_id: int):
"""Add hardware to case. Placeholder endpoint."""
# TODO: Implement when hardware-case relation is defined
return {"message": "Hardware endpoint not yet implemented"}
@router.delete("/sag/{sag_id}/hardware/{hardware_id}")
async def remove_case_hardware(sag_id: int, hardware_id: int):
"""Remove hardware from case. Placeholder endpoint."""
# TODO: Implement when hardware-case relation is defined
return {"message": "Hardware endpoint not yet implemented"}
# ============================================================================
# LOCATIONS - Placeholder endpoints for frontend compatibility
# ============================================================================
@router.get("/sag/{sag_id}/locations")
async def list_case_locations(sag_id: int):
"""List locations associated with a case. Placeholder endpoint."""
# TODO: Implement when location-case relation is defined
return []
@router.post("/sag/{sag_id}/locations")
async def add_case_location(sag_id: int):
"""Add location to case. Placeholder endpoint."""
# TODO: Implement when location-case relation is defined
return {"message": "Location endpoint not yet implemented"}
@router.delete("/sag/{sag_id}/locations/{location_id}")
async def remove_case_location(sag_id: int, location_id: int):
"""Remove location from case. Placeholder endpoint."""
# TODO: Implement when location-case relation is defined
return {"message": "Location endpoint not yet implemented"}