feat(financial-indicators): Complete Financial Indicators Dashboard Card
Implementare completă a card-ului Indicatori Financiari în Dashboard Solduri: Backend: - Model FinancialIndicators cu 22+ indicatori organizați pe categorii - Service cu calcule din VBAL (Lichiditate, Eficiență, Risc, Cash Flow, Dinamică) - Altman Z-Score cu toate componentele (X1-X4) și valori absolute - Profitabilitate cu ROA, ROE, Cifra Afaceri, Cheltuieli separate (operaționale/financiare) - Caching inteligent pe company_id, luna, an Frontend: - FinancialIndicatorsCard.vue cu 4 indicatori principali collapsed - Expanded view grupat pe categorii (desktop + mobile BottomSheet) - Subindicatori pentru verificare manuală în balanță - Traduceri complete în română - Dark mode support complet - Sparklines cu tooltips - Responsive design (desktop grid + mobile carousel) Documentație: - PRD complet cu specificații și formule - Descrieri cu conturi din planul contabil român (OMFP 1802/2014) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,10 @@ import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
from ..models.dashboard import DashboardSummary, TrendsResponse, TrendData
|
||||
from ..models.financial_indicators import FinancialIndicatorsResponse
|
||||
from ..services.dashboard_service import DashboardService
|
||||
from ..services.financial_indicators_service import FinancialIndicatorsService
|
||||
from ..cache.decorators import cached
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -427,4 +430,153 @@ async def get_current_period(
|
||||
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)}")
|
||||
raise HTTPException(status_code=500, detail=f"Eroare la obținerea perioadei curente: {str(e)}")
|
||||
|
||||
|
||||
@router.get(
|
||||
"/financial-indicators",
|
||||
response_model=FinancialIndicatorsResponse,
|
||||
tags=["dashboard"]
|
||||
)
|
||||
async def get_financial_indicators(
|
||||
request: Request,
|
||||
company: int = Query(..., description="ID-ul firmei (required)"),
|
||||
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"),
|
||||
include_sparklines: bool = Query(True, description="Include date istorice pentru sparklines (12 luni)"),
|
||||
current_user: CurrentUser = Depends(get_current_user)
|
||||
) -> FinancialIndicatorsResponse:
|
||||
"""
|
||||
Returnează toți indicatorii financiari calculați pentru firma selectată.
|
||||
|
||||
Acest endpoint agregă datele din:
|
||||
- Lichiditate: Current Ratio, Quick Ratio, Cash Ratio
|
||||
- Eficiență: DSO, DPO, Cash Conversion Cycle, rate încasare/plată
|
||||
- Risc: creanțe/datorii restante, raport datorii/trezorerie
|
||||
- Cash Flow: flux net lunar, YTD, YoY, acoperire
|
||||
- Dinamică: creștere vânzări/achiziții YoY, marjă implicită
|
||||
- Altman Z-Score: scor și componente X1-X4
|
||||
|
||||
Parametri:
|
||||
- company (required): ID-ul firmei pentru care se calculează indicatorii
|
||||
- luna (optional): Luna contabilă (1-12). Dacă nu este specificată,
|
||||
se folosește ultima perioadă disponibilă.
|
||||
- an (optional): Anul contabil (2000-2100). Dacă nu este specificat,
|
||||
se folosește anul curent.
|
||||
- include_sparklines (optional, default=true): Dacă să includă date istorice
|
||||
pentru vizualizarea trendului pe ultimele 12 luni (sparkline_data și sparkline_labels
|
||||
în fiecare indicator)
|
||||
|
||||
Cache:
|
||||
- TTL: 30 minute pentru indicatori curenți (cache_type='financial_indicators')
|
||||
- TTL: 1 oră pentru date istorice sparkline (cache_type='financial_indicators_historical')
|
||||
- Se invalidează automat la schimbarea datelor din balanță
|
||||
|
||||
Necesită autentificare JWT și acces la firma specificată.
|
||||
"""
|
||||
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}"
|
||||
)
|
||||
|
||||
# Dacă luna/an nu sunt specificate, obținem perioada curentă
|
||||
# Folosim variabile tipizate explicit pentru a evita erori de tip
|
||||
resolved_luna: int
|
||||
resolved_an: int
|
||||
|
||||
if luna is None or an is None:
|
||||
try:
|
||||
current_period = await DashboardService.get_current_period(company)
|
||||
resolved_luna = luna if luna is not None else current_period.get('luna', 12)
|
||||
resolved_an = an if an is not None else current_period.get('an', 2024)
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not get current period: {e}, using defaults")
|
||||
from datetime import datetime
|
||||
resolved_luna = luna if luna is not None else datetime.now().month
|
||||
resolved_an = an if an is not None else datetime.now().year
|
||||
else:
|
||||
resolved_luna = luna
|
||||
resolved_an = an
|
||||
|
||||
# Dacă include_sparklines este True, folosim metoda care include datele istorice
|
||||
if include_sparklines:
|
||||
response = await FinancialIndicatorsService.get_indicators_with_sparklines(
|
||||
company, resolved_luna, resolved_an, months=12
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Financial indicators with sparklines for company {company}, "
|
||||
f"luna={resolved_luna}, an={resolved_an}: "
|
||||
f"Z-Score={response.altman_zscore.zscore.value} ({response.altman_zscore.zscore.status})"
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
# Dacă include_sparklines este False, calculăm doar indicatorii curenți
|
||||
import asyncio
|
||||
|
||||
# Apelăm serviciul pentru fiecare categorie de indicatori
|
||||
lichiditate_task = FinancialIndicatorsService.calculate_liquidity_indicators(
|
||||
company, resolved_luna, resolved_an
|
||||
)
|
||||
eficienta_task = FinancialIndicatorsService.calculate_efficiency_indicators(
|
||||
company, resolved_luna, resolved_an
|
||||
)
|
||||
risc_task = FinancialIndicatorsService.calculate_risk_indicators(
|
||||
company, resolved_luna, resolved_an
|
||||
)
|
||||
cash_flow_task = FinancialIndicatorsService.calculate_cashflow_indicators(
|
||||
company, resolved_luna, resolved_an
|
||||
)
|
||||
dinamica_task = FinancialIndicatorsService.calculate_dynamics_indicators(
|
||||
company, resolved_luna, resolved_an
|
||||
)
|
||||
altman_task = FinancialIndicatorsService.calculate_altman_zscore(
|
||||
company, resolved_luna, resolved_an
|
||||
)
|
||||
|
||||
# Executăm toate calculele în paralel pentru performanță
|
||||
(
|
||||
lichiditate,
|
||||
eficienta,
|
||||
risc,
|
||||
cash_flow,
|
||||
dinamica,
|
||||
altman_zscore
|
||||
) = await asyncio.gather(
|
||||
lichiditate_task,
|
||||
eficienta_task,
|
||||
risc_task,
|
||||
cash_flow_task,
|
||||
dinamica_task,
|
||||
altman_task
|
||||
)
|
||||
|
||||
# Construim răspunsul
|
||||
response = FinancialIndicatorsResponse(
|
||||
lichiditate=lichiditate,
|
||||
eficienta=eficienta,
|
||||
risc=risc,
|
||||
cash_flow=cash_flow,
|
||||
dinamica=dinamica,
|
||||
altman_zscore=altman_zscore
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Financial indicators for company {company}, luna={resolved_luna}, an={resolved_an}: "
|
||||
f"Z-Score={altman_zscore.zscore.value} ({altman_zscore.zscore.status})"
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"Eroare la obținerea indicatorilor financiari: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Eroare la obținerea indicatorilor financiari: {str(e)}"
|
||||
)
|
||||
Reference in New Issue
Block a user