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:
Claude Agent
2026-01-20 17:32:48 +00:00
parent 15327687f4
commit dd4b90f922
14 changed files with 6800 additions and 237 deletions

View File

@@ -127,12 +127,16 @@ class UnifiedSettings(BaseSettings):
@property
def data_entry_database_url(self) -> str:
"""Get SQLite database URL for async (Data Entry)."""
return f"sqlite+aiosqlite:///{self.data_entry_sqlite_database_path}"
# Resolve to absolute path for Windows/IIS compatibility
abs_path = Path(self.data_entry_sqlite_database_path).resolve()
return f"sqlite+aiosqlite:///{abs_path}"
@property
def data_entry_sync_database_url(self) -> str:
"""Get SQLite database URL for sync operations (Alembic)."""
return f"sqlite:///{self.data_entry_sqlite_database_path}"
# Resolve to absolute path for Windows/IIS compatibility
abs_path = Path(self.data_entry_sqlite_database_path).resolve()
return f"sqlite:///{abs_path}"
@property
def data_entry_upload_path_resolved(self) -> Path:

View File

@@ -61,12 +61,16 @@ class Settings(BaseSettings):
@property
def database_url(self) -> str:
"""Get SQLite database URL for async."""
return f"sqlite+aiosqlite:///{self.sqlite_database_path}"
# Resolve to absolute path for Windows/IIS compatibility
abs_path = Path(self.sqlite_database_path).resolve()
return f"sqlite+aiosqlite:///{abs_path}"
@property
def sync_database_url(self) -> str:
"""Get SQLite database URL for sync operations (Alembic)."""
return f"sqlite:///{self.sqlite_database_path}"
# Resolve to absolute path for Windows/IIS compatibility
abs_path = Path(self.sqlite_database_path).resolve()
return f"sqlite:///{abs_path}"
@property
def upload_path_resolved(self) -> Path:

View File

@@ -1,6 +1,7 @@
"""Alembic environment configuration."""
import os
from pathlib import Path
from logging.config import fileConfig
from dotenv import load_dotenv
@@ -24,7 +25,8 @@ from backend.modules.data_entry.db.models.ocr_settings import UserOCRPreference,
config = context.config
# Override sqlalchemy.url from environment variable if set
db_path = os.getenv("SQLITE_DATABASE_PATH", "data/receipts/receipts.db")
# Resolve to absolute path for Windows/IIS compatibility
db_path = Path(os.getenv("SQLITE_DATABASE_PATH", "data/receipts/receipts.db")).resolve()
config.set_main_option("sqlalchemy.url", f"sqlite:///{db_path}")
# Interpret the config file for Python logging.

View File

@@ -0,0 +1,873 @@
"""
Pydantic models pentru indicatori financiari.
Definește structurile de date pentru:
- BalanceSheetAggregates: Solduri agregate din balanța de verificare
- IndicatorResult: Rezultatul standardizat cu sparkline data pentru vizualizare trend
- Modele pentru indicatori de lichiditate, eficiență, risc și Altman Z-Score
"""
from decimal import Decimal
from typing import Optional, List
from pydantic import BaseModel, Field
class BalanceSheetAggregates(BaseModel):
"""
Solduri agregate din balanța de verificare (VBAL) pe categorii de conturi.
Agregă datele din VBAL folosind prefixe de conturi conform Planului de
Conturi General (PCG) românesc pentru calculul indicatorilor financiari.
Attributes:
company_id: ID-ul firmei
luna: Luna contabilă (1-12)
an: Anul contabil
active_imobilizate: Active imobilizate nete (brut - amortizări - ajustări)
stocuri: Valoarea stocurilor (brut - ajustări depreciere)
creante: Creanțe nete (brut - ajustări depreciere)
disponibilitati: Disponibilități bănești (bancă + casă)
capital_propriu: Capital social + prime + rezerve
rezultat: Rezultat reportat + rezultatul exercițiului curent
datorii_termen_lung: Datorii cu scadență > 1 an
datorii_curente: Datorii cu scadență <= 1 an
venituri: Venituri din exploatare (pentru calcul EBIT)
cheltuieli_operationale: Cheltuieli de exploatare (pentru calcul EBIT)
"""
company_id: int = Field(description="ID-ul firmei")
luna: int = Field(ge=1, le=12, description="Luna contabilă (1-12)")
an: int = Field(ge=2000, le=2100, description="Anul contabil")
# Active
active_imobilizate: Decimal = Field(
default=Decimal('0'),
description="Active imobilizate nete (Clasa 2 - amortizări)"
)
stocuri: Decimal = Field(
default=Decimal('0'),
description="Stocuri (Clasa 3 - ajustări depreciere)"
)
creante: Decimal = Field(
default=Decimal('0'),
description="Creanțe comerciale și alte creanțe (4111, 461, etc.)"
)
disponibilitati: Decimal = Field(
default=Decimal('0'),
description="Disponibilități (bancă 512x + casă 531x)"
)
# Pasive - Capitaluri
capital_propriu: Decimal = Field(
default=Decimal('0'),
description="Capital social + prime + rezerve (101, 104, 105, 106)"
)
rezultat: Decimal = Field(
default=Decimal('0'),
description="Rezultat reportat + rezultat curent (107, 117, 121)"
)
# Pasive - Datorii
datorii_termen_lung: Decimal = Field(
default=Decimal('0'),
description="Datorii pe termen lung (Clasa 16)"
)
datorii_curente: Decimal = Field(
default=Decimal('0'),
description="Datorii curente (401, 421, 4423, 462, etc.)"
)
# Venituri și Cheltuieli (pentru calcul EBIT în Altman Z-Score)
venituri: Decimal = Field(
default=Decimal('0'),
description="Venituri din exploatare (Clasa 7)"
)
cheltuieli_operationale: Decimal = Field(
default=Decimal('0'),
description="Cheltuieli operaționale (Clasa 6 fără 66x)"
)
cheltuieli_financiare: Decimal = Field(
default=Decimal('0'),
description="Cheltuieli financiare (Clasa 66 - dobânzi, diferențe curs)"
)
# Computed properties pentru calculele ulterioare
@property
def active_curente(self) -> Decimal:
"""Active curente = stocuri + creanțe + disponibilități"""
return self.stocuri + self.creante + self.disponibilitati
@property
def total_active(self) -> Decimal:
"""Total active = active imobilizate + active curente"""
return self.active_imobilizate + self.active_curente
@property
def total_datorii(self) -> Decimal:
"""Total datorii = datorii termen lung + datorii curente"""
return self.datorii_termen_lung + self.datorii_curente
@property
def capitaluri_proprii(self) -> Decimal:
"""Capitaluri proprii = capital propriu + rezultat"""
return self.capital_propriu + self.rezultat
@property
def ebit(self) -> Decimal:
"""EBIT (Earnings Before Interest and Taxes) = venituri - cheltuieli operaționale"""
return self.venituri - self.cheltuieli_operationale
@property
def working_capital(self) -> Decimal:
"""Working capital (fond de rulment) = active curente - datorii curente"""
return self.active_curente - self.datorii_curente
class Config:
"""Configurare Pydantic"""
from_attributes = True
json_schema_extra = {
"example": {
"company_id": 123,
"luna": 12,
"an": 2024,
"active_imobilizate": 1500000.00,
"stocuri": 350000.00,
"creante": 420000.00,
"disponibilitati": 180000.00,
"capital_propriu": 800000.00,
"rezultat": 250000.00,
"datorii_termen_lung": 500000.00,
"datorii_curente": 400000.00,
"venituri": 2500000.00,
"cheltuieli_operationale": 2100000.00
}
}
class IndicatorResult(BaseModel):
"""
Rezultatul standardizat pentru un indicator financiar.
Fiecare indicator returnează valoarea calculată împreună cu
statusul (good/warning/danger), pragurile de referință și
date pentru sparkline (evoluția pe ultimele 12 luni).
Attributes:
value: Valoarea calculată a indicatorului (None dacă nu se poate calcula)
status: Statusul indicatorului - 'good', 'warning', sau 'danger'
threshold_min: Pragul minim pentru status 'good'
threshold_max: Pragul maxim pentru status 'good' (opțional)
message: Mesaj explicativ (opțional, pentru cazuri speciale)
sparkline_data: Array cu valorile indicatorului pe ultimele 12 luni (pentru vizualizare trend)
sparkline_labels: Array cu etichetele lunilor în format 'MMM YY' (ex: 'Feb 24', 'Mar 24')
"""
value: Optional[float] = Field(
default=None,
description="Valoarea calculată a indicatorului"
)
status: str = Field(
default="warning",
description="Statusul: 'good', 'warning', sau 'danger'"
)
threshold_min: Optional[float] = Field(
default=None,
description="Pragul minim pentru status 'good'"
)
threshold_max: Optional[float] = Field(
default=None,
description="Pragul maxim pentru status 'good'"
)
message: Optional[str] = Field(
default=None,
description="Mesaj explicativ pentru cazuri speciale"
)
sparkline_data: Optional[List[Optional[float]]] = Field(
default=None,
description="Array cu valorile indicatorului pe ultimele 12 luni (pentru sparkline)"
)
sparkline_labels: Optional[List[str]] = Field(
default=None,
description="Array cu etichetele lunilor în format 'MMM YY' (ex: 'Feb 24', 'Mar 24')"
)
class Config:
json_schema_extra = {
"example": {
"value": 1.25,
"status": "good",
"threshold_min": 1.0,
"threshold_max": None,
"message": None,
"sparkline_data": [1.15, 1.18, 1.20, 1.22, 1.19, 1.21, 1.23, 1.25, 1.24, 1.26, 1.25, 1.25],
"sparkline_labels": ["Feb 24", "Mar 24", "Apr 24", "May 24", "Jun 24", "Jul 24", "Aug 24", "Sep 24", "Oct 24", "Nov 24", "Dec 24", "Jan 25"]
}
}
class LiquidityIndicators(BaseModel):
"""
Indicatori de lichiditate pentru evaluarea capacității de plată
a datoriilor pe termen scurt.
Attributes:
lichiditate_curenta: Current Ratio = active_curente / datorii_curente
- Măsoară capacitatea de a plăti datoriile pe termen scurt cu active curente
- Good: >= 2.0, Warning: 1.0-2.0, Danger: < 1.0
lichiditate_imediata: Quick Ratio = (disponibilități + creanțe) / datorii_curente
- Măsoară capacitatea de plată fără a depinde de vânzarea stocurilor
- Good: >= 1.0, Warning: 0.5-1.0, Danger: < 0.5
lichiditate_vedere: Cash Ratio = disponibilități / datorii_curente
- Măsoară capacitatea de plată imediată doar din numerar
- Good: >= 0.2, Warning: 0.1-0.2, Danger: < 0.1
"""
lichiditate_curenta: IndicatorResult = Field(
description="Current Ratio = active_curente / datorii_curente"
)
lichiditate_imediata: IndicatorResult = Field(
description="Quick Ratio = (disponibilități + creanțe) / datorii_curente"
)
lichiditate_vedere: IndicatorResult = Field(
description="Cash Ratio = disponibilități / datorii_curente"
)
class Config:
json_schema_extra = {
"example": {
"lichiditate_curenta": {
"value": 2.37,
"status": "good",
"threshold_min": 2.0,
"threshold_max": None
},
"lichiditate_imediata": {
"value": 1.50,
"status": "good",
"threshold_min": 1.0,
"threshold_max": None
},
"lichiditate_vedere": {
"value": 0.45,
"status": "good",
"threshold_min": 0.2,
"threshold_max": None
}
}
}
class EfficiencyIndicators(BaseModel):
"""
Indicatori de eficiență pentru evaluarea vitezei de conversie
a resurselor în numerar (working capital efficiency).
Attributes:
dso: Days Sales Outstanding (Durata medie de încasare)
- Formula: (clienti_sold / facturari_lunare) * 30
- Măsoară câte zile durează în medie încasarea creanțelor
- Good: < 30 zile, Warning: 30-45 zile, Danger: > 45 zile
dpo: Days Payables Outstanding (Durata medie de plată)
- Formula: (furnizori_sold / achizitii_lunare) * 30
- Măsoară câte zile durează în medie plata furnizorilor
- Valoare mai mare = folosim mai mult creditul furnizorilor
cash_conversion_cycle: Ciclu de conversie a numerarului
- Formula: DSO - DPO
- Pozitiv = numerar blocat în ciclul de afaceri
- Negativ = folosim creditul furnizorilor (favorabil)
rata_incasare: Rata de încasare (Collection Rate)
- Formula: incasari / facturari * 100
- Măsoară ce procent din facturări s-a încasat
- Good: >= 95%, Warning: 80-95%, Danger: < 80%
rata_plata: Rata de plată (Payment Rate)
- Formula: plati / achizitii * 100
- Măsoară ce procent din achiziții s-a achitat
"""
dso: IndicatorResult = Field(
description="Days Sales Outstanding = (clienti_sold / facturari_lunare) * 30"
)
dpo: IndicatorResult = Field(
description="Days Payables Outstanding = (furnizori_sold / achizitii_lunare) * 30"
)
cash_conversion_cycle: IndicatorResult = Field(
description="Cash Conversion Cycle = DSO - DPO"
)
rata_incasare: IndicatorResult = Field(
description="Rata de încasare = incasari / facturari * 100"
)
rata_plata: IndicatorResult = Field(
description="Rata de plată = plati / achizitii * 100"
)
class Config:
json_schema_extra = {
"example": {
"dso": {
"value": 28.5,
"status": "good",
"threshold_min": None,
"threshold_max": 30
},
"dpo": {
"value": 35.2,
"status": "good",
"threshold_min": None,
"threshold_max": None,
"message": "Folosim bine creditul furnizorilor"
},
"cash_conversion_cycle": {
"value": -6.7,
"status": "good",
"threshold_min": None,
"threshold_max": 0,
"message": "Ciclu negativ = finanțare gratuită de la furnizori"
},
"rata_incasare": {
"value": 92.5,
"status": "warning",
"threshold_min": 95,
"threshold_max": None
},
"rata_plata": {
"value": 88.3,
"status": "good",
"threshold_min": None,
"threshold_max": None
}
}
}
class RiskIndicators(BaseModel):
"""
Indicatori de risc și aging pentru evaluarea sănătății
portofoliului de creanțe și datorii.
Attributes:
creante_restante_pct: Procentul creanțelor restante din total clienți
- Formula: clienti_sold_restant / clienti_sold_total * 100
- Măsoară ce procent din creanțe sunt depășite ca termen
- Good: < 20%, Warning: 20-30%, Danger: > 30%
creante_90plus_pct: Procentul creanțelor restante > 90 zile din total
- Formula: clienti_restant_90plus / clienti_sold_total * 100
- Indică creanțele cu risc mare de nerecuperare
- Good: < 5%, Warning: 5-10%, Danger: > 10%
datorii_restante_pct: Procentul datoriilor restante din total furnizori
- Formula: furnizori_sold_restant / furnizori_sold_total * 100
- Măsoară nivelul de întârzieri la plată
- Good: < 10%, Warning: 10-20%, Danger: > 20%
raport_datorii_trezorerie: Raportul între datorii furnizori și trezorerie
- Formula: furnizori_sold_total / trezorerie
- Indică câte luni de cash sunt necesare pentru a plăti furnizorii
- Good: < 2, Warning: 2-4, Danger: > 4
"""
creante_restante_pct: IndicatorResult = Field(
description="Procentul creanțelor restante = clienti_sold_restant / clienti_sold_total * 100"
)
creante_90plus_pct: IndicatorResult = Field(
description="Procentul creanțelor > 90 zile = clienti_restant_90plus / clienti_sold_total * 100"
)
datorii_restante_pct: IndicatorResult = Field(
description="Procentul datoriilor restante = furnizori_sold_restant / furnizori_sold_total * 100"
)
raport_datorii_trezorerie: IndicatorResult = Field(
description="Raport datorii/trezorerie = furnizori_sold_total / trezorerie"
)
class Config:
json_schema_extra = {
"example": {
"creante_restante_pct": {
"value": 15.5,
"status": "good",
"threshold_min": None,
"threshold_max": 20
},
"creante_90plus_pct": {
"value": 3.2,
"status": "good",
"threshold_min": None,
"threshold_max": 5
},
"datorii_restante_pct": {
"value": 8.5,
"status": "good",
"threshold_min": None,
"threshold_max": 10
},
"raport_datorii_trezorerie": {
"value": 1.8,
"status": "good",
"threshold_min": None,
"threshold_max": 2,
"message": "Trezorerie suficientă pentru acoperirea datoriilor"
}
}
}
class CashFlowIndicators(BaseModel):
"""
Indicatori de cash flow pentru evaluarea generării și consumului de numerar.
Attributes:
flux_net_lunar: Fluxul net de numerar lunar (încasări - plăți)
- Formula: incasari_luna - plati_luna
- Pozitiv = firma generează numerar, Negativ = firma consumă numerar
- Good: > 0, Danger: < 0
cash_flow_ytd: Fluxul de numerar cumulat de la începutul anului (Year-To-Date)
- Formula: suma fluxurilor nete de la ianuarie până la luna curentă
- Arată tendința generală a anului în curs
flux_net_yoy_pct: Variația procentuală YoY (Year-over-Year)
- Formula: (cf_curent - cf_anterior) / abs(cf_anterior) * 100
- Compară cash flow-ul curent cu aceeași perioadă din anul anterior
acoperire_cash_flow: Rata de acoperire a datoriilor restante
- Formula: cash_flow_ytd / datorii_restante
- Arată de câte ori cash flow-ul YTD poate acoperi datoriile restante
- Good: > 1 (cash flow suficient), Danger: < 0.5
"""
flux_net_lunar: IndicatorResult = Field(
description="Flux net lunar = incasari_luna - plati_luna"
)
cash_flow_ytd: IndicatorResult = Field(
description="Cash flow cumulat YTD = suma fluxurilor de la ianuarie"
)
flux_net_yoy_pct: IndicatorResult = Field(
description="Variația YoY = (cf_curent - cf_anterior) / abs(cf_anterior) * 100"
)
acoperire_cash_flow: IndicatorResult = Field(
description="Acoperire datorii = cash_flow_ytd / datorii_restante"
)
class Config:
json_schema_extra = {
"example": {
"flux_net_lunar": {
"value": 125000.50,
"status": "good",
"threshold_min": 0,
"threshold_max": None,
"message": "Firma generează numerar"
},
"cash_flow_ytd": {
"value": 850000.00,
"status": "good",
"threshold_min": 0,
"threshold_max": None
},
"flux_net_yoy_pct": {
"value": 15.5,
"status": "good",
"threshold_min": 0,
"threshold_max": None,
"message": "Creștere cash flow față de anul anterior"
},
"acoperire_cash_flow": {
"value": 2.5,
"status": "good",
"threshold_min": 1.0,
"threshold_max": None,
"message": "Cash flow suficient pentru acoperirea datoriilor"
}
}
}
class DynamicsIndicators(BaseModel):
"""
Indicatori de dinamică pentru evaluarea evoluției vânzărilor și achizițiilor.
Arată dacă afacerea crește sau scade prin comparație YoY (Year-over-Year).
Attributes:
crestere_vanzari_yoy: Creșterea procentuală a vânzărilor față de anul anterior
- Formula: (facturari_curent - facturari_anterior) / facturari_anterior * 100
- Măsoară dinamica vânzărilor - creștere sau scădere
- Good: > 5%, Warning: 0-5%, Danger: < 0%
crestere_achizitii_yoy: Creșterea procentuală a achizițiilor față de anul anterior
- Formula: (achizitii_curent - achizitii_anterior) / achizitii_anterior * 100
- Creșterea achizițiilor poate indica expansiune sau costuri mai mari
marja_implicita: Marja implicită din diferența facturări - achiziții
- Formula: (facturari - achizitii) / facturari * 100
- Arată ce procent din vânzări rămâne după achiziții
- Good: > 20%, Warning: 10-20%, Danger: < 10%
"""
crestere_vanzari_yoy: IndicatorResult = Field(
description="Creștere vânzări YoY = (facturari_curent - facturari_anterior) / facturari_anterior * 100"
)
crestere_achizitii_yoy: IndicatorResult = Field(
description="Creștere achiziții YoY = (achizitii_curent - achizitii_anterior) / achizitii_anterior * 100"
)
marja_implicita: IndicatorResult = Field(
description="Marja implicită = (facturari - achizitii) / facturari * 100"
)
class Config:
json_schema_extra = {
"example": {
"crestere_vanzari_yoy": {
"value": 12.5,
"status": "good",
"threshold_min": 5.0,
"threshold_max": None,
"message": "Creștere semnificativă a vânzărilor"
},
"crestere_achizitii_yoy": {
"value": 8.3,
"status": "good",
"threshold_min": None,
"threshold_max": None,
"message": "Achiziții în creștere"
},
"marja_implicita": {
"value": 25.5,
"status": "good",
"threshold_min": 20.0,
"threshold_max": None,
"message": "Marjă implicită sănătoasă"
}
}
}
class AltmanZScore(BaseModel):
"""
Altman Z-Score pentru evaluarea riscului de faliment.
Folosim formula modificată pentru companii private (Z'-Score):
Z' = 6.56*X1 + 3.26*X2 + 6.72*X3 + 1.05*X4
Coeficienții sunt specifici pentru companii care nu sunt listate la bursă,
unde se folosește valoarea contabilă a capitalurilor proprii în loc de
valoarea de piață a acțiunilor.
Componente:
X1: Working Capital / Total Assets
- Măsoară lichiditatea pe termen scurt
- Working capital = active curente - datorii curente
X2: Retained Earnings / Total Assets
- Măsoară profitabilitatea cumulată (rezultat reportat)
- Include conturile 117 și 121 (rezultat reportat + rezultat curent)
X3: EBIT / Total Assets
- Măsoară eficiența operațională
- EBIT = venituri din exploatare - cheltuieli operaționale
X4: Book Value of Equity / Total Liabilities
- Măsoară solvabilitatea (acoperirea datoriilor cu capital propriu)
- Capital propriu / (datorii curente + datorii termen lung)
Zone de risc:
- Safe Zone (zscore > 2.60): Risc minim de faliment
- Grey Zone (1.10 <= zscore <= 2.60): Risc moderat, necesită atenție
- Distress Zone (zscore < 1.10): Risc ridicat de faliment
Attributes:
zscore: Scorul Altman Z calculat
status: Zona de risc ('safe', 'grey', sau 'distress')
x1: Componenta X1 (Working Capital / Total Assets)
x2: Componenta X2 (Retained Earnings / Total Assets)
x3: Componenta X3 (EBIT / Total Assets)
x4: Componenta X4 (Equity / Total Liabilities)
working_capital: Fondul de rulment (active curente - datorii curente)
total_assets: Total active
"""
zscore: IndicatorResult = Field(
description="Scorul Altman Z = 6.56*X1 + 3.26*X2 + 6.72*X3 + 1.05*X4"
)
x1: IndicatorResult = Field(
description="X1 = Working Capital / Total Assets (lichiditate)"
)
x2: IndicatorResult = Field(
description="X2 = Retained Earnings / Total Assets (profitabilitate)"
)
x3: IndicatorResult = Field(
description="X3 = EBIT / Total Assets (eficiență operațională)"
)
x4: IndicatorResult = Field(
description="X4 = Capitaluri Proprii / Datorii Totale (solvabilitate)"
)
# Valori absolute pentru verificare manuală în balanță
capital_de_lucru: IndicatorResult = Field(
description="Capital de lucru = Active Curente - Datorii Curente"
)
active_totale: IndicatorResult = Field(
description="Active Totale = Active Imobilizate + Active Curente"
)
datorii_totale: IndicatorResult = Field(
description="Datorii Totale = Datorii Curente + Datorii Termen Lung"
)
class Config:
json_schema_extra = {
"example": {
"zscore": {
"value": 3.25,
"status": "safe",
"threshold_min": 2.60,
"threshold_max": None,
"message": "Zona sigură - risc minim de faliment"
},
"x1": {
"value": 0.25,
"status": "good",
"threshold_min": 0,
"threshold_max": None
},
"x2": {
"value": 0.15,
"status": "good",
"threshold_min": 0,
"threshold_max": None
},
"x3": {
"value": 0.12,
"status": "good",
"threshold_min": 0,
"threshold_max": None
},
"x4": {
"value": 1.80,
"status": "good",
"threshold_min": 1.0,
"threshold_max": None
},
"capital_de_lucru": {
"value": 450000.00,
"status": "good",
"message": "Active Curente - Datorii Curente"
},
"active_totale": {
"value": 1800000.00,
"status": "good",
"message": "Active Imobilizate + Active Curente"
},
"datorii_totale": {
"value": 1200000.00,
"status": "good",
"message": "Datorii Curente + Datorii Termen Lung"
}
}
}
class ProfitabilityIndicators(BaseModel):
"""
Indicatori de profitabilitate pentru evaluarea randamentului afacerii.
Calculează indicatori cheie pentru evaluarea profitabilității pe baza
datelor din balanța de verificare (VBAL): venituri, cheltuieli, active, capital.
Attributes:
cifra_afaceri: Total venituri din activitatea operațională (Clasa 7)
- Reprezintă volumul total al vânzărilor
- Sursa: suma conturilor 70x-75x din VBAL
cheltuieli_totale: Total cheltuieli operaționale (Clasa 6)
- Reprezintă costurile activității
- Sursa: suma conturilor 60x-65x din VBAL
profit_brut: Diferența dintre venituri și cheltuieli (EBIT)
- Formula: cifra_afaceri - cheltuieli_totale
- Good: > 0, Danger: < 0
marja_profit_brut: Procentul de profit din vânzări
- Formula: profit_brut / cifra_afaceri * 100
- Good: > 10%, Warning: 5-10%, Danger: < 5%
roa: Return on Assets - randamentul activelor
- Formula: profit_brut / total_active * 100
- Măsoară eficiența utilizării activelor
- Good: > 5%, Warning: 2-5%, Danger: < 2%
roe: Return on Equity - randamentul capitalului propriu
- Formula: profit_brut / capitaluri_proprii * 100
- Măsoară randamentul pentru acționari
- Good: > 10%, Warning: 5-10%, Danger: < 5%
"""
cifra_afaceri: IndicatorResult = Field(
description="Cifra de afaceri = Total venituri operaționale (Clasa 7)"
)
# Cheltuieli separate pentru verificare
cheltuieli_operationale: IndicatorResult = Field(
description="Cheltuieli operaționale = Clasa 60x-65x + 68x (fără dobânzi 66x)"
)
cheltuieli_financiare: IndicatorResult = Field(
description="Cheltuieli financiare = Clasa 66x (dobânzi, diferențe curs valutar)"
)
cheltuieli_totale: IndicatorResult = Field(
description="Cheltuieli totale = Operaționale + Financiare"
)
profit_brut: IndicatorResult = Field(
description="Profit brut (EBIT) = Venituri - Cheltuieli operaționale"
)
marja_profit_brut: IndicatorResult = Field(
description="Marja de profit = Profit brut / Cifra afaceri * 100"
)
# Indicatori de bază pentru verificare ROA/ROE
active_totale: IndicatorResult = Field(
description="Active Totale - bază calcul ROA"
)
capitaluri_proprii: IndicatorResult = Field(
description="Capitaluri Proprii - bază calcul ROE"
)
roa: IndicatorResult = Field(
description="Randament Active (ROA) = Profit / Active Totale * 100"
)
roe: IndicatorResult = Field(
description="Randament Capitaluri (ROE) = Profit / Capital Propriu * 100"
)
class Config:
json_schema_extra = {
"example": {
"cifra_afaceri": {
"value": 2500000.00,
"status": "good",
"threshold_min": None,
"threshold_max": None
},
"cheltuieli_totale": {
"value": 2100000.00,
"status": "good",
"threshold_min": None,
"threshold_max": None
},
"profit_brut": {
"value": 400000.00,
"status": "good",
"threshold_min": 0,
"threshold_max": None,
"message": "Profit operațional pozitiv"
},
"marja_profit_brut": {
"value": 16.0,
"status": "good",
"threshold_min": 10.0,
"threshold_max": None
},
"roa": {
"value": 8.5,
"status": "good",
"threshold_min": 5.0,
"threshold_max": None,
"message": "Randament bun al activelor"
},
"roe": {
"value": 15.2,
"status": "good",
"threshold_min": 10.0,
"threshold_max": None,
"message": "Randament atractiv pentru acționari"
}
}
}
class FinancialIndicatorsResponse(BaseModel):
"""
Răspunsul complet al endpoint-ului /api/reports/dashboard/financial-indicators.
Agregă toți indicatorii financiari calculați pentru o firmă și perioadă dată.
Acest model este folosit pentru serializarea JSON a răspunsului API.
Attributes:
lichiditate: Indicatori de lichiditate (Current Ratio, Quick Ratio, Cash Ratio)
eficienta: Indicatori de eficiență (DSO, DPO, CCC, rate încasare/plată)
risc: Indicatori de risc (creanțe/datorii restante, raport datorii/trezorerie)
cash_flow: Indicatori de cash flow (flux net lunar, YTD, YoY, acoperire)
dinamica: Indicatori de dinamică (creștere vânzări/achiziții YoY, marjă)
altman_zscore: Scorul Altman Z-Score și componentele X1-X4
profitabilitate: Indicatori de profitabilitate (ROA, ROE, marjă profit)
Usage:
GET /api/reports/dashboard/financial-indicators?company=123&luna=12&an=2024
Response example:
{
"lichiditate": { ... },
"eficienta": { ... },
"risc": { ... },
"cash_flow": { ... },
"dinamica": { ... },
"altman_zscore": { ... },
"profitabilitate": { ... }
}
"""
lichiditate: LiquidityIndicators = Field(
description="Indicatori de lichiditate: Current Ratio, Quick Ratio, Cash Ratio"
)
eficienta: EfficiencyIndicators = Field(
description="Indicatori de eficiență: DSO, DPO, CCC, rate încasare/plată"
)
risc: RiskIndicators = Field(
description="Indicatori de risc: creanțe/datorii restante, raport datorii/trezorerie"
)
cash_flow: CashFlowIndicators = Field(
description="Indicatori de cash flow: flux net lunar, YTD, YoY, acoperire"
)
dinamica: DynamicsIndicators = Field(
description="Indicatori de dinamică: creștere vânzări/achiziții YoY, marjă implicită"
)
altman_zscore: AltmanZScore = Field(
description="Altman Z-Score și componentele X1-X4"
)
profitabilitate: ProfitabilityIndicators = Field(
description="Indicatori de profitabilitate: ROA, ROE, marja de profit"
)
class Config:
json_schema_extra = {
"example": {
"lichiditate": {
"lichiditate_curenta": {"value": 2.37, "status": "good", "threshold_min": 2.0},
"lichiditate_imediata": {"value": 1.50, "status": "good", "threshold_min": 1.0},
"lichiditate_vedere": {"value": 0.45, "status": "good", "threshold_min": 0.2}
},
"eficienta": {
"dso": {"value": 28.5, "status": "good", "threshold_max": 30},
"dpo": {"value": 35.2, "status": "good"},
"cash_conversion_cycle": {"value": -6.7, "status": "good", "threshold_max": 0},
"rata_incasare": {"value": 92.5, "status": "warning", "threshold_min": 95},
"rata_plata": {"value": 88.3, "status": "good"}
},
"risc": {
"creante_restante_pct": {"value": 15.5, "status": "good", "threshold_max": 20},
"creante_90plus_pct": {"value": 3.2, "status": "good", "threshold_max": 5},
"datorii_restante_pct": {"value": 8.5, "status": "good", "threshold_max": 10},
"raport_datorii_trezorerie": {"value": 1.8, "status": "good", "threshold_max": 2}
},
"cash_flow": {
"flux_net_lunar": {"value": 125000.50, "status": "good", "threshold_min": 0},
"cash_flow_ytd": {"value": 850000.00, "status": "good", "threshold_min": 0},
"flux_net_yoy_pct": {"value": 15.5, "status": "good", "threshold_min": 0},
"acoperire_cash_flow": {"value": 2.5, "status": "good", "threshold_min": 1.0}
},
"dinamica": {
"crestere_vanzari_yoy": {"value": 12.5, "status": "good", "threshold_min": 5.0},
"crestere_achizitii_yoy": {"value": 8.3, "status": "good"},
"marja_implicita": {"value": 25.5, "status": "good", "threshold_min": 20.0}
},
"altman_zscore": {
"zscore": {"value": 3.25, "status": "safe", "threshold_min": 2.60},
"x1": {"value": 0.25, "status": "good"},
"x2": {"value": 0.15, "status": "good"},
"x3": {"value": 0.12, "status": "good"},
"x4": {"value": 1.80, "status": "good"},
"working_capital": 450000.00,
"total_assets": 1800000.00
}
}
}

View File

@@ -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)}"
)

File diff suppressed because it is too large Load Diff