Files
roa2web-service-auto/reports-app/backend/app/routers/dashboard.py
Marius Mutu 6b13ffa183 Initial commit: ROA2WEB - FastAPI + Vue.js + Telegram Bot
Modern ERP Reports Application with microservices architecture

Tech Stack:
- Backend: FastAPI + python-oracledb (Oracle DB integration)
- Frontend: Vue.js 3 + PrimeVue + Vite
- Telegram Bot: python-telegram-bot + SQLite
- Infrastructure: Shared database pool, JWT authentication, SSH tunnel

Features:
- FastAPI backend with async Oracle connection pool
- Vue.js 3 responsive frontend with PrimeVue components
- Telegram bot alternative interface
- Microservices architecture with shared components
- Complete deployment support (Linux Docker + Windows IIS)
- Comprehensive testing (Playwright E2E + pytest)

Repository Structure:
- reports-app/ - Main application (backend, frontend, telegram-bot)
- shared/ - Shared components (database pool, auth, utils)
- deployment/ - Deployment scripts (Linux & Windows)
- docs/ - Project documentation
- security/ - Security scanning and git hooks
2025-10-25 14:55:08 +03:00

327 lines
13 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, Query
from typing import Optional
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), '../../../../shared'))
from auth.dependencies import get_current_user
from 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", response_model=DashboardSummary)
async def get_dashboard_summary(
company: str = Query(description="Codul firmei"),
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
"""
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)
return result
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(
company: str = Query(description="Codul firmei"),
period: str = Query(default="30d", description="Perioada pentru trends: 7d, 30d, ytd, 12m"),
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)
- 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)
# The service now returns the data in the correct format
# Return it directly as TrendsResponse
return TrendsResponse(**result)
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"),
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,
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(
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"),
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ă
- 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
"""
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)
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 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"),
current_user: CurrentUser = Depends(get_current_user)
):
"""
Returnează fluxurile lunare pentru firma selectată
- Necesită autentificare JWT
- Returnează date pentru analiza fluxurilor lunare
"""
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)
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(
company: int = Query(..., description="ID-ul firmei"),
current_user: CurrentUser = Depends(get_current_user)
):
"""
Returnează defalcarea trezoreriei pentru firma selectată
- Necesită autentificare JWT
- Returnează distribuția soldurilor pe conturi și tipuri
"""
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)
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 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(
company: int = Query(..., description="ID-ul firmei"),
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
"""
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)
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 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)}")