from fastapi import APIRouter, Depends, HTTPException, Query, Request from typing import Optional # import sys # Removed - no longer needed import os from shared.auth.dependencies import get_current_user from shared.auth.models import CurrentUser import logging logger = logging.getLogger(__name__) from ..models.dashboard import DashboardSummary, TrendsResponse, TrendData from ..services.dashboard_service import DashboardService router = APIRouter() @router.get("/summary") async def get_dashboard_summary( request: Request, company: str = Query(description="Codul firmei"), luna: Optional[int] = Query(None, ge=1, le=12, description="Luna contabilă (1-12)"), an: Optional[int] = Query(None, ge=2000, le=2100, description="Anul contabil"), current_user: CurrentUser = Depends(get_current_user) ): """ Obține toate datele pentru dashboard într-un singur apel - Necesită autentificare JWT - Returnează statistici clienți/furnizori și trezorerie - Include metadata cache pentru Telegram Bot (X-Include-Cache-Metadata header) - Suportă filtrare pe luna/an contabil (dacă nu sunt specificate, folosește ultima perioadă) """ try: # Verifică dacă utilizatorul are acces la firma specificată if company not in current_user.companies: raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}") result = await DashboardService.get_complete_summary(company, current_user.username, luna=luna, an=an, request=request) # Convert Pydantic model to dict for JSON serialization result_dict = result.dict() if hasattr(result, 'dict') else result # Add cache metadata if requested (for Telegram Bot) include_metadata = request.headers.get('X-Include-Cache-Metadata', '').lower() == 'true' if include_metadata: cache_hit = getattr(request.state, 'cache_hit', False) response_time = getattr(request.state, 'response_time_ms', 0) cache_source = getattr(request.state, 'cache_source', None) result_dict['cache_hit'] = cache_hit result_dict['response_time_ms'] = response_time # Always include cache_source, even if None result_dict['cache_source'] = cache_source return result_dict except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: raise HTTPException(status_code=500, detail=f"Eroare la obținerea datelor dashboard: {str(e)}") @router.get("/trends", response_model=TrendsResponse) async def get_dashboard_trends( request: Request, company: str = Query(description="Codul firmei"), period: str = Query(default="30d", description="Perioada pentru trends: 7d, 30d, ytd, 12m"), luna: Optional[int] = Query(None, ge=1, le=12, description="Luna contabilă (1-12)"), an: Optional[int] = Query(None, ge=2000, le=2100, description="Anul contabil"), compare_previous: bool = Query(default=True, description="Compară cu perioada anterioară"), current_user: CurrentUser = Depends(get_current_user) ): """ Obține trenduri pentru indicatorii principali (clienți/furnizori) - period: "7d" (7 zile), "30d" (30 zile), "ytd" (year to date), "12m" (12 luni) - luna/an: perioada contabilă de referință (dacă nu sunt specificate, folosește ultima perioadă) - compare_previous: dacă să compare cu perioada anterioară - Necesită autentificare JWT - Returnează date pentru grafice de trenduri """ try: # Verifică dacă utilizatorul are acces la firma specificată if company not in current_user.companies: raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}") # Validează perioada valid_periods = ["7d", "30d", "ytd", "12m"] if period not in valid_periods: raise HTTPException( status_code=400, detail=f"Perioadă nevalidă: {period}. Valori permise: {', '.join(valid_periods)}" ) # Obține datele de trenduri result = await DashboardService.get_trends(int(company), period, luna=luna, an=an, request=request) # Convert to dict if needed result_dict = result.dict() if hasattr(result, 'dict') else result # Add cache metadata if requested (for Telegram Bot) include_metadata = request.headers.get('X-Include-Cache-Metadata', '').lower() == 'true' if include_metadata: cache_hit = getattr(request.state, 'cache_hit', False) response_time = getattr(request.state, 'response_time_ms', 0) cache_source = getattr(request.state, 'cache_source', None) result_dict['cache_hit'] = cache_hit result_dict['response_time_ms'] = response_time # Always include cache_source, even if None result_dict['cache_source'] = cache_source # Return as TrendsResponse return TrendsResponse(**result_dict) except ValueError as e: logger.error(f"Value error in trends endpoint: {str(e)}") raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"Eroare la obținerea trendurilor: {str(e)}") raise HTTPException(status_code=500, detail=f"Eroare la obținerea trendurilor: {str(e)}") @router.get("/detailed-data") async def get_detailed_data( company: str = Query(description="Codul firmei"), data_type: str = Query(description="Tipul de date: clients, suppliers, treasury"), luna: Optional[int] = Query(None, ge=1, le=12, description="Luna contabilă (1-12)"), an: Optional[int] = Query(None, ge=2000, le=2100, description="Anul contabil"), page: int = Query(default=1, ge=1), page_size: int = Query(default=25, ge=1, le=100), search: str = Query(default="", description="Termen de căutare"), current_user: CurrentUser = Depends(get_current_user) ): """ Obține date detaliate pentru tabelele din dashboard """ logger.info(f"[ROUTER] detailed-data called: company={company}, data_type={data_type}") try: if company not in current_user.companies: raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}") logger.info(f"[ROUTER] Calling DashboardService.get_detailed_data") result = await DashboardService.get_detailed_data( company=company, data_type=data_type, luna=luna, an=an, page=page, page_size=page_size, search=search ) logger.info(f"[ROUTER] Service returned: {len(result.get('data', []))} rows") return result except Exception as e: logger.error(f"Eroare la obținerea datelor detaliate: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/performance") async def get_performance( company: int = Query(..., description="ID-ul firmei"), period: str = Query("7d", regex="^(7d|1m|3m|6m|ytd|12m)$", description="Perioada pentru analiză"), current_user: CurrentUser = Depends(get_current_user) ): """ Returnează date performanță pentru perioada selectată - Necesită autentificare JWT - Returnează grafice încasări vs plăți pentru perioada selectată - Calculează indicatori: rata încasării, cash conversion, working capital """ try: # Verifică dacă utilizatorul are acces la firma specificată if str(company) not in current_user.companies: raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}") result = await DashboardService.get_performance_data(company, period) # Convert to Chart.js compatible format return { "labels": result.get("labels", []), "datasets": [{ "data": result.get("data", []), "label": result.get("label", "Performance"), "borderColor": result.get("borderColor", "#3B82F6"), "backgroundColor": result.get("backgroundColor", "rgba(59, 130, 246, 0.1)"), "tension": 0.4 }] } except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"Eroare la obținerea datelor de performanță: {str(e)}") raise HTTPException(status_code=500, detail=f"Eroare la obținerea datelor de performanță: {str(e)}") @router.get("/cashflow") async def get_cashflow( company: int = Query(..., description="ID-ul firmei"), period: str = Query("7d", regex="^(7d|1m|3m|6m)$", description="Perioada pentru previziune"), current_user: CurrentUser = Depends(get_current_user) ): """ Returnează previziune cash flow pentru perioada selectată - Necesită autentificare JWT - Analizează scadențele viitoare pentru calculul cash flow-ului - Identifică zilele critice cu deficit de cash """ try: # Verifică dacă utilizatorul are acces la firma specificată if str(company) not in current_user.companies: raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}") result = await DashboardService.get_cashflow_forecast(company, period) # Convert to Chart.js compatible format return { "labels": result.get("labels", []), "datasets": [{ "data": result.get("data", []), "label": result.get("label", "Cash Flow"), "borderColor": result.get("borderColor", "#10B981"), "backgroundColor": result.get("backgroundColor", "rgba(16, 185, 129, 0.1)"), "tension": 0.4 }] } except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"Eroare la obținerea previziunii cash flow: {str(e)}") raise HTTPException(status_code=500, detail=f"Eroare la obținerea previziunii cash flow: {str(e)}") @router.get("/maturity") async def get_maturity_analysis( request: Request, company: int = Query(..., description="ID-ul firmei"), period: str = Query("7d", regex="^(7d|1m|3m|6m|12m|all)$", description="Orizont de planificare pentru analiza scadențelor"), luna: Optional[int] = Query(None, ge=1, le=12, description="Luna contabilă (1-12)"), an: Optional[int] = Query(None, ge=2000, le=2100, description="Anul contabil"), current_user: CurrentUser = Depends(get_current_user) ): """ Returnează analiza scadențelor pentru orizontul de planificare selectat - Necesită autentificare JWT - Logică: Include TOATE restanțele + scadențele viitoare din perioada selectată - luna/an: perioada contabilă de referință (dacă nu sunt specificate, folosește ultima perioadă) - Perioade disponibile: * 7d: Toate restanțele + scadențe următoarelor 7 zile * 1m: Toate restanțele + scadențe următoarelor 30 zile * 3m: Toate restanțele + scadențe următoarelor 90 zile * 6m: Toate restanțele + scadențe următoarelor 180 zile * 12m: Toate restanțele + scadențe următoarelor 365 zile * all: Toate soldurile (fără filtru) - Compară scadențele clienți vs furnizori - Calculează balanța și oferă recomandări - Returnează metadate cu statistici complete - Include metadata cache pentru Telegram Bot (X-Include-Cache-Metadata header) """ try: # Verifică dacă utilizatorul are acces la firma specificată if str(company) not in current_user.companies: raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}") result = await DashboardService.get_maturity_analysis(company, period, luna=luna, an=an, request=request) # Convert to dict if needed result_dict = result.dict() if hasattr(result, 'dict') else result # Add cache metadata if requested (for Telegram Bot) include_metadata = request.headers.get('X-Include-Cache-Metadata', '').lower() == 'true' if include_metadata: cache_hit = getattr(request.state, 'cache_hit', False) response_time = getattr(request.state, 'response_time_ms', 0) cache_source = getattr(request.state, 'cache_source', None) result_dict['cache_hit'] = cache_hit result_dict['response_time_ms'] = response_time # Always include cache_source, even if None result_dict['cache_source'] = cache_source return result_dict except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"Eroare la obținerea analizei scadențelor: {str(e)}") raise HTTPException(status_code=500, detail=f"Eroare la obținerea analizei scadențelor: {str(e)}") @router.get("/monthly-flows") async def get_monthly_flows( company: int = Query(..., description="ID-ul firmei"), luna: Optional[int] = Query(None, ge=1, le=12, description="Luna contabilă (1-12)"), an: Optional[int] = Query(None, ge=2000, le=2100, description="Anul contabil"), current_user: CurrentUser = Depends(get_current_user) ): """ Returnează fluxurile lunare pentru firma selectată - Necesită autentificare JWT - Returnează date pentru analiza fluxurilor lunare - luna/an: perioada contabilă de referință (dacă nu sunt specificate, folosește ultima perioadă) """ try: # Verifică dacă utilizatorul are acces la firma specificată if str(company) not in current_user.companies: raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}") result = await DashboardService.get_monthly_flows(company, luna=luna, an=an) return result except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"Eroare la obținerea fluxurilor lunare: {str(e)}") raise HTTPException(status_code=500, detail=f"Eroare la obținerea fluxurilor lunare: {str(e)}") @router.get("/treasury-breakdown") async def get_treasury_breakdown( request: Request, company: int = Query(..., description="ID-ul firmei"), luna: Optional[int] = Query(None, ge=1, le=12, description="Luna contabilă (1-12)"), an: Optional[int] = Query(None, ge=2000, le=2100, description="Anul contabil"), current_user: CurrentUser = Depends(get_current_user) ): """ Returnează defalcarea trezoreriei pentru firma selectată - Necesită autentificare JWT - Returnează distribuția soldurilor pe conturi și tipuri - luna/an: perioada contabilă de referință (dacă nu sunt specificate, folosește ultima perioadă) - Include metadata cache pentru Telegram Bot (X-Include-Cache-Metadata header) """ try: # Verifică dacă utilizatorul are acces la firma specificată if str(company) not in current_user.companies: raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}") result = await DashboardService.get_treasury_breakdown(company, luna=luna, an=an, request=request) # Convert to dict if needed result_dict = result.dict() if hasattr(result, 'dict') else result # Add cache metadata if requested (for Telegram Bot) include_metadata = request.headers.get('X-Include-Cache-Metadata', '').lower() == 'true' if include_metadata: cache_hit = getattr(request.state, 'cache_hit', False) response_time = getattr(request.state, 'response_time_ms', 0) cache_source = getattr(request.state, 'cache_source', None) result_dict['cache_hit'] = cache_hit result_dict['response_time_ms'] = response_time # Always include cache_source, even if None result_dict['cache_source'] = cache_source return result_dict except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"Eroare la obținerea defalcării trezoreriei: {str(e)}") raise HTTPException(status_code=500, detail=f"Eroare la obținerea defalcării trezoreriei: {str(e)}") @router.get("/net-balance-breakdown") async def get_net_balance_breakdown( request: Request, company: int = Query(..., description="ID-ul firmei"), luna: Optional[int] = Query(None, ge=1, le=12, description="Luna contabilă (1-12)"), an: Optional[int] = Query(None, ge=2000, le=2100, description="Anul contabil"), current_user: CurrentUser = Depends(get_current_user) ): """ Returnează defalcarea balanței nete pentru firma selectată - Necesită autentificare JWT - Returnează analiza detaliată a balanței nete - luna/an: perioada contabilă de referință (dacă nu sunt specificate, folosește ultima perioadă) - Include metadata cache pentru Telegram Bot (X-Include-Cache-Metadata header) """ try: # Verifică dacă utilizatorul are acces la firma specificată if str(company) not in current_user.companies: raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}") result = await DashboardService.get_net_balance_breakdown(company, luna=luna, an=an, request=request) # Convert to dict if needed result_dict = result.dict() if hasattr(result, 'dict') else result # Add cache metadata if requested (for Telegram Bot) include_metadata = request.headers.get('X-Include-Cache-Metadata', '').lower() == 'true' if include_metadata: cache_hit = getattr(request.state, 'cache_hit', False) response_time = getattr(request.state, 'response_time_ms', 0) cache_source = getattr(request.state, 'cache_source', None) result_dict['cache_hit'] = cache_hit result_dict['response_time_ms'] = response_time # Always include cache_source, even if None result_dict['cache_source'] = cache_source return result_dict except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"Eroare la obținerea defalcării balanței nete: {str(e)}") raise HTTPException(status_code=500, detail=f"Eroare la obținerea defalcării balanței nete: {str(e)}") @router.get("/current-period") async def get_current_period( company: int = Query(..., description="ID-ul firmei"), current_user: CurrentUser = Depends(get_current_user) ): """ Returnează perioada curentă (an și lună) din calendarul Oracle - Necesită autentificare JWT - Returnează anul, luna și perioada curentă în format YYYY-MM - Folosit pentru afișarea lunii curente în dashboard """ try: # Verifică dacă utilizatorul are acces la firma specificată if str(company) not in current_user.companies: raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}") result = await DashboardService.get_current_period(company) return result except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"Eroare la obținerea perioadei curente: {str(e)}") raise HTTPException(status_code=500, detail=f"Eroare la obținerea perioadei curente: {str(e)}")