bmc_hub/app/opportunities/backend/router.py
Christian 891180f3f0 Refactor opportunities and settings management
- Removed opportunity detail page route from views.py.
- Deleted opportunity_service.py as it is no longer needed.
- Updated router.py to seed new setting for case_type_module_defaults.
- Enhanced settings.html to include standard modules per case type with UI for selection.
- Implemented JavaScript functions to manage case type module defaults.
- Added RelationService for handling case relations with a tree structure.
- Created migration scripts (128 and 129) for new pipeline fields and descriptions.
- Added script to fix relation types in the database.
2026-02-15 11:12:58 +01:00

148 lines
4.5 KiB
Python

"""
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.first_name || ' ' || COALESCE(u.last_name, ''), '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.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 sag_tags st
JOIN tags t ON st.tag_id = t.id
WHERE st.sag_id = s.id AND t.name = '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():
"""
Legacy endpoint for stages.
Returns static stages mapped to Case statuses for compatibility.
"""
return [
{"id": "open", "name": "Åben"},
{"id": "won", "name": "Vundet"},
{"id": "lost", "name": "Tabt"}
]