2026-02-08 01:45:00 +01:00
"""
Fixed - Price Agreement Frontend Views
"""
from fastapi import APIRouter , Request
from fastapi . responses import HTMLResponse , JSONResponse
from fastapi . templating import Jinja2Templates
from app . core . database import execute_query
2026-10-08 18:00:47 +02:00
from app . core . config import settings
2026-02-08 01:45:00 +01:00
import logging
logger = logging . getLogger ( __name__ )
router = APIRouter ( )
templates = Jinja2Templates ( directory = " app " )
@router.get ( " /fixed-price-agreements " , response_class = HTMLResponse )
async def list_agreements ( request : Request ) :
""" List all fixed-price agreements """
# Load customers for the create modal
try :
customers_query = """
SELECT
id ,
name ,
cvr_number ,
email ,
phone ,
city ,
is_active
FROM customers
WHERE deleted_at IS NULL
AND is_active = true
ORDER BY name
LIMIT 1000
"""
customers = execute_query ( customers_query )
logger . info ( f " 📋 Loaded { len ( customers ) } customers for modal " )
except Exception as e :
logger . error ( f " ❌ Error loading customers: { e } " )
customers = [ ]
return templates . TemplateResponse ( " fixed_price/frontend/list.html " , {
" request " : request ,
" customers " : customers
} )
@router.get ( " /fixed-price-agreements/ {agreement_id} " , response_class = HTMLResponse )
async def agreement_detail ( request : Request , agreement_id : int ) :
""" Agreement detail page with periods and related sager """
from fastapi import HTTPException
try :
# Fetch agreement
agr_query = """
SELECT a . * ,
COALESCE ( p . used_hours , 0 ) as current_used_hours ,
COALESCE ( p . remaining_hours , a . monthly_hours ) as current_remaining_hours ,
p . status as current_period_status
FROM customer_fixed_price_agreements a
LEFT JOIN fixed_price_billing_periods p ON p . agreement_id = a . id
AND p . period_start < = CURRENT_DATE
AND p . period_end > = CURRENT_DATE
WHERE a . id = % s
"""
agreement = execute_query ( agr_query , ( agreement_id , ) )
if not agreement :
raise HTTPException ( status_code = 404 , detail = " Aftale ikke fundet " )
agreement = agreement [ 0 ]
# Fetch all billing periods
periods_query = """
SELECT * FROM fixed_price_billing_periods
WHERE agreement_id = % s
ORDER BY period_start DESC
"""
periods = execute_query ( periods_query , ( agreement_id , ) )
# Fetch related sager
sager_query = """
SELECT DISTINCT s . id , s . titel , s . status , s . created_at
FROM sag_sager s
INNER JOIN tmodule_times t ON t . sag_id = s . id
WHERE t . fixed_price_agreement_id = % s
AND s . deleted_at IS NULL
ORDER BY s . created_at DESC
LIMIT 50
"""
sager = execute_query ( sager_query , ( agreement_id , ) )
# Fetch time entries
time_query = """
SELECT t . * , s . titel as sag_titel
FROM tmodule_times t
LEFT JOIN sag_sager s ON s . id = t . sag_id
WHERE t . fixed_price_agreement_id = % s
ORDER BY t . created_at DESC
LIMIT 100
"""
time_entries = execute_query ( time_query , ( agreement_id , ) )
return templates . TemplateResponse ( " fixed_price/frontend/detail.html " , {
" request " : request ,
" agreement " : agreement ,
" periods " : periods ,
" sager " : sager ,
" time_entries " : time_entries
} )
except Exception as e :
logger . error ( f " ❌ Error loading agreement detail: { e } " )
raise HTTPException ( status_code = 500 , detail = str ( e ) )
@router.get ( " /fixed-price-agreements/reports/dashboard " , response_class = HTMLResponse )
async def reports_dashboard ( request : Request ) :
""" Reporting dashboard with profitability analysis """
try :
# Get summary stats
stats_query = """
SELECT
COUNT ( * ) FILTER ( WHERE status = ' active ' ) as active_agreements ,
COUNT ( * ) as total_agreements ,
SUM ( monthly_hours * hourly_rate ) as total_revenue ,
SUM ( monthly_hours * ( hourly_rate - 300 ) ) as estimated_profit
FROM customer_fixed_price_agreements
"""
2026-10-08 18:00:47 +02:00
if getattr ( settings , ' WORKFLOWS_ENABLED ' , False ) :
available = execute_query ( " SELECT 1 FROM information_schema.columns WHERE table_schema= ' public ' AND table_name= ' customer_fixed_price_agreements ' AND column_name= ' monthly_price ' " )
if available :
stats_query = """
SELECT
COUNT ( * ) FILTER ( WHERE status = ' active ' ) as active_agreements ,
COUNT ( * ) as total_agreements ,
SUM ( COALESCE ( monthly_price , monthly_hours * hourly_rate ) ) as total_revenue ,
SUM ( COALESCE ( monthly_price , monthly_hours * hourly_rate )
- monthly_hours * internal_cost_rate ) as estimated_profit
FROM customer_fixed_price_agreements
"""
2026-02-08 01:45:00 +01:00
stats = execute_query ( stats_query )
# Get performance data from view
performance_query = """
SELECT
* ,
CASE
WHEN total_revenue > 0 THEN ( total_profit / total_revenue * 100 )
ELSE 0
END as profit_margin
FROM fixed_price_agreement_performance
ORDER BY total_profit DESC
LIMIT 50
"""
performance = execute_query ( performance_query )
# Get monthly trends
trends_query = """
SELECT
* ,
month as period_month ,
CASE
WHEN monthly_total_revenue > 0 THEN ( monthly_profit / monthly_total_revenue * 100 )
ELSE 0
END as avg_profit_margin
FROM fixed_price_monthly_trends
ORDER BY month DESC
LIMIT 12
"""
trends = execute_query ( trends_query )
# Get customer breakdown
customer_query = """
SELECT
* ,
total_hours_used as total_used_hours ,
CASE
WHEN total_revenue > 0 THEN ( total_profit / total_revenue * 100 )
ELSE 0
END as avg_profit_margin
FROM fixed_price_customer_summary
ORDER BY total_used_hours DESC
LIMIT 20
"""
customers = execute_query ( customer_query )
return templates . TemplateResponse ( " fixed_price/frontend/reports.html " , {
" request " : request ,
" stats " : stats [ 0 ] if stats else { } ,
" performance " : performance ,
" trends " : trends ,
" customers " : customers
} )
except Exception as e :
logger . error ( f " ❌ Error loading reports: { e } " )
return templates . TemplateResponse ( " fixed_price/frontend/reports.html " , {
" request " : request ,
" stats " : { } ,
" performance " : [ ] ,
" trends " : [ ] ,
" customers " : [ ] ,
" error " : str ( e )
} )
@router.get ( " /api/fixed-price-agreements/customers " )
async def get_customers_for_agreements ( ) :
""" Get all active customers for fixed-price agreement creation """
try :
query = """
SELECT
id ,
name ,
cvr_number ,
email ,
phone ,
city ,
is_active
FROM customers
WHERE deleted_at IS NULL
AND is_active = true
ORDER BY name
LIMIT 1000
"""
customers = execute_query ( query )
logger . info ( f " 📋 Loaded { len ( customers ) } customers for fixed-price agreements " )
return customers
except Exception as e :
logger . error ( f " ❌ Error loading customers: { e } " )
return [ ]