""" Opportunities (Pipeline) Router Re-implemented to use the standard 'Sag' (Case) model. Each 'Opportunity' is now a Case with template_key='pipeline'. """ from fastapi import APIRouter, HTTPException, Request, Body, Query from app.core.database import execute_query import logging from typing import Optional, List, Dict, Any logger = logging.getLogger(__name__) router = APIRouter() @router.get("/opportunities", tags=["Opportunities"]) async def list_opportunities( q: Optional[str] = None, stage: Optional[str] = None, status: Optional[str] = None ): """ List all 'pipeline' cases. Criteria: - template_key = 'pipeline' - OR has tag 'pipeline' """ query = """ SELECT s.id, s.titel, s.status, s.pipeline_amount, s.pipeline_probability, s.pipeline_stage_id, ps.name AS pipeline_stage, s.created_at, s.deadline, s.beskrivelse, s.customer_id, COALESCE(c.name, 'Ukendt kunde') as customer_name, s.ansvarlig_bruger_id, COALESCE(u.full_name, u.username, 'Ingen') as ansvarlig_navn FROM sag_sager s LEFT JOIN customers c ON s.customer_id = c.id LEFT JOIN users u ON s.ansvarlig_bruger_id = u.user_id LEFT JOIN pipeline_stages ps ON ps.id = s.pipeline_stage_id WHERE s.deleted_at IS NULL AND ( s.template_key = 'pipeline' OR EXISTS ( SELECT 1 FROM entity_tags et JOIN tags t ON t.id = et.tag_id WHERE et.entity_type = 'case' AND et.entity_id = s.id AND LOWER(t.name) = 'pipeline' ) OR EXISTS ( SELECT 1 FROM sag_tags st WHERE st.sag_id = s.id AND st.deleted_at IS NULL AND LOWER(st.tag_navn) = 'pipeline' ) ) """ params = [] if q: query += " AND (s.titel ILIKE %s OR c.name ILIKE %s)" params.extend([f"%{q}%", f"%{q}%"]) if status and status != 'all': if status == 'open': query += " AND s.status = 'åben'" elif status in ('won', 'lost', 'lukket'): query += " AND s.status = 'lukket'" if stage and stage != 'all': query += " AND LOWER(COALESCE(ps.name, '')) = LOWER(%s)" params.append(stage) query += " ORDER BY s.created_at DESC" try: results = execute_query(query, tuple(params)) # Transform to match frontend expectations somewhat, or just return as cases # We'll return as cases but add 'amount' and 'probability' as nulls if frontend expects them # Actually better to update frontend to not expect them. return results except Exception as e: logger.error(f"Failed to list opportunities: {e}") # Return empty list on error instead of 500 to keep UI responsive return [] @router.post("/opportunities", tags=["Opportunities"]) async def create_opportunity( payload: Dict[str, Any] = Body(...) ): """ Create a new 'pipeline' case. """ try: title = payload.get("title") customer_id = payload.get("customer_id") description = payload.get("description", "") # Map frontend 'expected_close_date' to 'deadline' deadline = payload.get("expected_close_date") or None if deadline == "": deadline = None if not title: raise HTTPException(status_code=400, detail="Mangler titel") if not customer_id: raise HTTPException(status_code=400, detail="Mangler kunde") # Insert as a Case with template_key='pipeline' query = """ INSERT INTO sag_sager ( titel, beskrivelse, customer_id, status, template_key, deadline, created_by_user_id ) VALUES ( %s, %s, %s, 'åben', 'pipeline', %s, 1 ) RETURNING id, titel """ result = execute_query(query, (title, description, customer_id, deadline)) if result: new_case = result[0] return new_case else: raise HTTPException(status_code=500, detail="Kunne ikke oprette pipeline sag") except Exception as e: logger.error(f"Failed to create opportunity case: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/pipeline/stages", tags=["Opportunities"]) async def list_pipeline_stages(): """List available pipeline stages from DB with a safe static fallback.""" try: stages = execute_query( """ SELECT id, name, color, sort_order FROM pipeline_stages WHERE COALESCE(is_active, TRUE) = TRUE ORDER BY sort_order ASC, id ASC """ ) if stages: return stages except Exception as e: logger.warning("Could not load pipeline stages from DB: %s", e) return [ {"id": 1, "name": "Lead", "color": "#6c757d", "sort_order": 10}, {"id": 2, "name": "Kontakt", "color": "#17a2b8", "sort_order": 20}, {"id": 3, "name": "Tilbud", "color": "#ffc107", "sort_order": 30}, {"id": 4, "name": "Forhandling", "color": "#fd7e14", "sort_order": 40}, {"id": 5, "name": "Vundet", "color": "#28a745", "sort_order": 50}, {"id": 6, "name": "Tabt", "color": "#dc3545", "sort_order": 60}, ]