Files
roa2web-service-auto/reports-app/backend/app/services/dashboard_service.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

1843 lines
92 KiB
Python

import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), '../../../../shared'))
from database.oracle_pool import oracle_pool
from ..models.dashboard import DashboardSummary, TreasuryAccount, TrendData
from decimal import Decimal
from typing import Dict, Any, List
from datetime import datetime, timedelta
import logging
logger = logging.getLogger(__name__)
class DashboardService:
"""Service pentru dashboard - date agregate"""
@staticmethod
async def get_complete_summary(company: str, username: str) -> DashboardSummary:
"""
Obține toate datele pentru dashboard într-un singur apel
Execută 2 query-uri separate: facturi și trezorerie
"""
async with oracle_pool.get_connection() as connection:
with connection.cursor() as cursor:
# Obține schema
company_id = int(company)
schema_query = "SELECT schema FROM CONTAFIN_ORACLE.v_nom_firme WHERE id_firma = :company_id"
cursor.execute(schema_query, {'company_id': company_id})
schema_result = cursor.fetchone()
if not schema_result:
raise ValueError(f"Schema nu a fost găsită pentru id_firma {company_id}")
schema = schema_result[0]
# Query 1: Statistici facturi cu breakdown pe perioade - FIXED ORA-00937
facturi_query = f"""
WITH luna_curenta AS (
SELECT anul, luna FROM {schema}.calendar
WHERE anul*12+luna = (SELECT MAX(anul*12+luna) FROM {schema}.calendar)
),
perioada_stats AS (
SELECT
an, luna,
-- CLIENȚI
SUM(CASE
WHEN cont IN ('4111','461')
THEN precdeb + debit ELSE 0
END) as clienti_facturat_luna,
SUM(CASE
WHEN cont IN ('4111','461')
THEN preccred + credit ELSE 0
END) as clienti_incasat_luna,
-- FURNIZORI
SUM(CASE
WHEN cont IN ('401','404','462')
THEN preccred + credit ELSE 0
END) as furnizori_facturat_luna,
SUM(CASE
WHEN cont IN ('401','404','462')
THEN precdeb + debit ELSE 0
END) as furnizori_achitat_luna
FROM {schema}.vireg_parteneri
WHERE cont IN ('4111', '461', '401', '404', '462')
AND an >= (SELECT anul-1 FROM luna_curenta)
GROUP BY an, luna
),
facturi_stats AS (
SELECT
-- CLIENȚI - Totaluri
SUM(CASE
WHEN cont IN ('4111','461')
THEN precdeb + debit ELSE 0
END) as clienti_total_facturat,
SUM(CASE
WHEN cont IN ('4111','461')
THEN preccred + credit ELSE 0
END) as clienti_total_incasat,
SUM(CASE
WHEN cont = '419'
THEN (preccred + credit) - (precdeb + debit) ELSE 0
END) as clienti_avansuri,
-- CLIENȚI - Sold Net Total
SUM(CASE
WHEN cont IN ('4111','461')
THEN (precdeb + debit) - (preccred + credit)
WHEN cont = '419'
THEN -((preccred + credit) - (precdeb + debit))
ELSE 0
END) as clienti_sold_total,
-- CLIENȚI - Sold În Termen (datascad >= azi sau NULL)
SUM(CASE
WHEN cont IN ('4111','461')
AND (datascad IS NULL OR datascad >= TRUNC(SYSDATE))
THEN (precdeb + debit) - (preccred + credit) ELSE 0
END) as clienti_sold_in_termen,
-- CLIENȚI - Sold Restant (datascad < azi)
SUM(CASE
WHEN cont IN ('4111','461')
AND datascad < TRUNC(SYSDATE)
THEN (precdeb + debit) - (preccred + credit) ELSE 0
END) as clienti_sold_restant,
-- CLIENȚI - Restanțe pe perioade
SUM(CASE
WHEN cont IN ('4111','461')
AND TRUNC(SYSDATE) - datascad BETWEEN 1 AND 7
THEN (precdeb + debit) - (preccred + credit) ELSE 0
END) as clienti_restant_7,
SUM(CASE
WHEN cont IN ('4111','461')
AND TRUNC(SYSDATE) - datascad BETWEEN 8 AND 14
THEN (precdeb + debit) - (preccred + credit) ELSE 0
END) as clienti_restant_14,
SUM(CASE
WHEN cont IN ('4111','461')
AND TRUNC(SYSDATE) - datascad BETWEEN 15 AND 30
THEN (precdeb + debit) - (preccred + credit) ELSE 0
END) as clienti_restant_30,
SUM(CASE
WHEN cont IN ('4111','461')
AND TRUNC(SYSDATE) - datascad BETWEEN 31 AND 60
THEN (precdeb + debit) - (preccred + credit) ELSE 0
END) as clienti_restant_60,
SUM(CASE
WHEN cont IN ('4111','461')
AND TRUNC(SYSDATE) - datascad BETWEEN 61 AND 90
THEN (precdeb + debit) - (preccred + credit) ELSE 0
END) as clienti_restant_90,
SUM(CASE
WHEN cont IN ('4111','461')
AND TRUNC(SYSDATE) - datascad > 90
THEN (precdeb + debit) - (preccred + credit) ELSE 0
END) as clienti_restant_90plus,
-- CLIENȚI - Scadențe pe perioade (datascad în viitor)
SUM(CASE
WHEN cont IN ('4111','461')
AND datascad - TRUNC(SYSDATE) BETWEEN 1 AND 7
THEN (precdeb + debit) - (preccred + credit) ELSE 0
END) as clienti_scadent_7,
SUM(CASE
WHEN cont IN ('4111','461')
AND datascad - TRUNC(SYSDATE) BETWEEN 8 AND 14
THEN (precdeb + debit) - (preccred + credit) ELSE 0
END) as clienti_scadent_14,
SUM(CASE
WHEN cont IN ('4111','461')
AND datascad - TRUNC(SYSDATE) BETWEEN 15 AND 30
THEN (precdeb + debit) - (preccred + credit) ELSE 0
END) as clienti_scadent_30,
SUM(CASE
WHEN cont IN ('4111','461')
AND datascad - TRUNC(SYSDATE) BETWEEN 31 AND 60
THEN (precdeb + debit) - (preccred + credit) ELSE 0
END) as clienti_scadent_60,
SUM(CASE
WHEN cont IN ('4111','461')
AND datascad - TRUNC(SYSDATE) BETWEEN 61 AND 90
THEN (precdeb + debit) - (preccred + credit) ELSE 0
END) as clienti_scadent_90,
SUM(CASE
WHEN cont IN ('4111','461')
AND datascad - TRUNC(SYSDATE) > 90
THEN (precdeb + debit) - (preccred + credit) ELSE 0
END) as clienti_scadent_90plus,
-- FURNIZORI - Totaluri
SUM(CASE
WHEN cont IN ('401','404','462')
THEN preccred + credit ELSE 0
END) as furnizori_total_facturat,
SUM(CASE
WHEN cont IN ('401','404','462')
THEN precdeb + debit ELSE 0
END) as furnizori_total_achitat,
SUM(CASE
WHEN cont LIKE '409%'
THEN (precdeb + debit) - (preccred + credit) ELSE 0
END) as furnizori_avansuri,
-- FURNIZORI - Sold Net Total
SUM(CASE
WHEN cont IN ('401','404','462')
THEN (preccred + credit) - (precdeb + debit)
WHEN cont LIKE '409%'
THEN -((precdeb + debit) - (preccred + credit))
ELSE 0
END) as furnizori_sold_total,
-- FURNIZORI - Sold În Termen
SUM(CASE
WHEN cont IN ('401','404','462')
AND (datascad IS NULL OR datascad >= TRUNC(SYSDATE))
THEN (preccred + credit) - (precdeb + debit) ELSE 0
END) as furnizori_sold_in_termen,
-- FURNIZORI - Sold Restant
SUM(CASE
WHEN cont IN ('401','404','462')
AND datascad < TRUNC(SYSDATE)
THEN (preccred + credit) - (precdeb + debit) ELSE 0
END) as furnizori_sold_restant,
-- FURNIZORI - Restanțe pe perioade
SUM(CASE
WHEN cont IN ('401','404','462')
AND TRUNC(SYSDATE) - datascad BETWEEN 1 AND 7
THEN (preccred + credit) - (precdeb + debit) ELSE 0
END) as furnizori_restant_7,
SUM(CASE
WHEN cont IN ('401','404','462')
AND TRUNC(SYSDATE) - datascad BETWEEN 8 AND 14
THEN (preccred + credit) - (precdeb + debit) ELSE 0
END) as furnizori_restant_14,
SUM(CASE
WHEN cont IN ('401','404','462')
AND TRUNC(SYSDATE) - datascad BETWEEN 15 AND 30
THEN (preccred + credit) - (precdeb + debit) ELSE 0
END) as furnizori_restant_30,
SUM(CASE
WHEN cont IN ('401','404','462')
AND TRUNC(SYSDATE) - datascad BETWEEN 31 AND 60
THEN (preccred + credit) - (precdeb + debit) ELSE 0
END) as furnizori_restant_60,
SUM(CASE
WHEN cont IN ('401','404','462')
AND TRUNC(SYSDATE) - datascad BETWEEN 61 AND 90
THEN (preccred + credit) - (precdeb + debit) ELSE 0
END) as furnizori_restant_90,
SUM(CASE
WHEN cont IN ('401','404','462')
AND TRUNC(SYSDATE) - datascad > 90
THEN (preccred + credit) - (precdeb + debit) ELSE 0
END) as furnizori_restant_90plus,
-- FURNIZORI - Scadențe pe perioade
SUM(CASE
WHEN cont IN ('401','404','462')
AND datascad - TRUNC(SYSDATE) BETWEEN 1 AND 7
THEN (preccred + credit) - (precdeb + debit) ELSE 0
END) as furnizori_scadent_7,
SUM(CASE
WHEN cont IN ('401','404','462')
AND datascad - TRUNC(SYSDATE) BETWEEN 8 AND 14
THEN (preccred + credit) - (precdeb + debit) ELSE 0
END) as furnizori_scadent_14,
SUM(CASE
WHEN cont IN ('401','404','462')
AND datascad - TRUNC(SYSDATE) BETWEEN 15 AND 30
THEN (preccred + credit) - (precdeb + debit) ELSE 0
END) as furnizori_scadent_30,
SUM(CASE
WHEN cont IN ('401','404','462')
AND datascad - TRUNC(SYSDATE) BETWEEN 31 AND 60
THEN (preccred + credit) - (precdeb + debit) ELSE 0
END) as furnizori_scadent_60,
SUM(CASE
WHEN cont IN ('401','404','462')
AND datascad - TRUNC(SYSDATE) BETWEEN 61 AND 90
THEN (preccred + credit) - (precdeb + debit) ELSE 0
END) as furnizori_scadent_90,
SUM(CASE
WHEN cont IN ('401','404','462')
AND datascad - TRUNC(SYSDATE) > 90
THEN (preccred + credit) - (precdeb + debit) ELSE 0
END) as furnizori_scadent_90plus
FROM {schema}.vireg_parteneri
WHERE an = (SELECT anul FROM luna_curenta)
AND luna = (SELECT luna FROM luna_curenta)
AND cont IN ('4111', '461', '419', '401', '404', '462', '409','4091','4092','4093','4094')
)
SELECT
fs.*,
-- BREAKDOWN pe perioade - Luna anterioară
(SELECT NVL(clienti_facturat_luna, 0) FROM perioada_stats p
WHERE p.an*12+p.luna = (SELECT anul*12+luna-1 FROM luna_curenta)) as clienti_facturat_luna_anterioara,
(SELECT NVL(furnizori_facturat_luna, 0) FROM perioada_stats p
WHERE p.an*12+p.luna = (SELECT anul*12+luna-1 FROM luna_curenta)) as furnizori_facturat_luna_anterioara,
-- BREAKDOWN pe perioade - Anul curent vs anterior
(SELECT NVL(SUM(clienti_facturat_luna), 0) FROM perioada_stats p
WHERE p.an = (SELECT anul FROM luna_curenta)) as clienti_facturat_an_curent,
(SELECT NVL(SUM(clienti_facturat_luna), 0) FROM perioada_stats p
WHERE p.an = (SELECT anul-1 FROM luna_curenta)) as clienti_facturat_an_anterior,
(SELECT NVL(SUM(furnizori_facturat_luna), 0) FROM perioada_stats p
WHERE p.an = (SELECT anul FROM luna_curenta)) as furnizori_facturat_an_curent,
(SELECT NVL(SUM(furnizori_facturat_luna), 0) FROM perioada_stats p
WHERE p.an = (SELECT anul-1 FROM luna_curenta)) as furnizori_facturat_an_anterior
FROM facturi_stats fs
"""
cursor.execute(facturi_query)
facturi_row = cursor.fetchone()
# Query 2: Trezorerie
treasury_query = f"""
WITH luna_curenta AS (
SELECT anul, luna FROM {schema}.calendar
WHERE anul*12+luna = (SELECT MAX(anul*12+luna) FROM {schema}.calendar)
)
SELECT
cont,
nume as nume_banca,
CASE
WHEN cont = '5121' THEN 'Bancă LEI'
WHEN cont = '5124' THEN 'Bancă VALUTA'
WHEN cont = '5311' THEN 'Casă LEI'
WHEN cont = '5314' THEN 'Casă VALUTA'
END as nume_cont,
SUM(CASE
WHEN cont IN ('5121','5311') THEN solddeb - soldcred
WHEN cont IN ('5124','5314') THEN soldvaldeb - soldvalcred
END) as sold,
CASE
WHEN cont IN ('5121','5311') THEN 'RON'
WHEN cont IN ('5124','5314') THEN NVL(nume_val, 'EUR')
END as valuta
FROM {schema}.vbalanta_parteneri
WHERE an = (SELECT anul FROM luna_curenta)
AND luna = (SELECT luna FROM luna_curenta)
AND cont IN ('5121', '5124', '5311', '5314')
AND ((cont IN ('5121','5311') AND soldcred - solddeb != 0)
OR (cont IN ('5124','5314') AND soldvalcred - soldvaldeb != 0))
GROUP BY cont, nume, nume_val
ORDER BY cont, nume
"""
cursor.execute(treasury_query)
treasury_rows = cursor.fetchall()
# Procesare trezorerie
treasury_accounts = []
treasury_totals = {}
for row in treasury_rows:
account = TreasuryAccount(
cont=row[0],
nume_banca=row[1],
nume_cont=row[2],
sold=Decimal(str(row[3] or 0)),
valuta=row[4]
)
treasury_accounts.append(account)
# Calculează totaluri pe valută
if account.valuta not in treasury_totals:
treasury_totals[account.valuta] = Decimal('0')
treasury_totals[account.valuta] += account.sold
# Returnează rezultatul complet cu toate câmpurile calculate
return DashboardSummary(
# CLIENȚI - Totaluri principale
clienti_total_facturat=Decimal(str(facturi_row[0] or 0)),
clienti_total_incasat=Decimal(str(facturi_row[1] or 0)),
clienti_avansuri=Decimal(str(facturi_row[2] or 0)),
clienti_sold_total=Decimal(str(facturi_row[3] or 0)),
clienti_sold_in_termen=Decimal(str(facturi_row[4] or 0)),
clienti_sold_restant=Decimal(str(facturi_row[5] or 0)),
# CLIENȚI - Restanțe pe perioade
clienti_restant_7=Decimal(str(facturi_row[6] or 0)),
clienti_restant_14=Decimal(str(facturi_row[7] or 0)),
clienti_restant_30=Decimal(str(facturi_row[8] or 0)),
clienti_restant_60=Decimal(str(facturi_row[9] or 0)),
clienti_restant_90=Decimal(str(facturi_row[10] or 0)),
clienti_restant_90plus=Decimal(str(facturi_row[11] or 0)),
# CLIENȚI - Scadențe pe perioade
clienti_scadent_7=Decimal(str(facturi_row[12] or 0)),
clienti_scadent_14=Decimal(str(facturi_row[13] or 0)),
clienti_scadent_30=Decimal(str(facturi_row[14] or 0)),
clienti_scadent_60=Decimal(str(facturi_row[15] or 0)),
clienti_scadent_90=Decimal(str(facturi_row[16] or 0)),
clienti_scadent_90plus=Decimal(str(facturi_row[17] or 0)),
# FURNIZORI - Totaluri principale
furnizori_total_facturat=Decimal(str(facturi_row[18] or 0)),
furnizori_total_achitat=Decimal(str(facturi_row[19] or 0)),
furnizori_avansuri=Decimal(str(facturi_row[20] or 0)),
furnizori_sold_total=Decimal(str(facturi_row[21] or 0)),
furnizori_sold_in_termen=Decimal(str(facturi_row[22] or 0)),
furnizori_sold_restant=Decimal(str(facturi_row[23] or 0)),
# FURNIZORI - Restanțe pe perioade
furnizori_restant_7=Decimal(str(facturi_row[24] or 0)),
furnizori_restant_14=Decimal(str(facturi_row[25] or 0)),
furnizori_restant_30=Decimal(str(facturi_row[26] or 0)),
furnizori_restant_60=Decimal(str(facturi_row[27] or 0)),
furnizori_restant_90=Decimal(str(facturi_row[28] or 0)),
furnizori_restant_90plus=Decimal(str(facturi_row[29] or 0)),
# FURNIZORI - Scadențe pe perioade
furnizori_scadent_7=Decimal(str(facturi_row[30] or 0)),
furnizori_scadent_14=Decimal(str(facturi_row[31] or 0)),
furnizori_scadent_30=Decimal(str(facturi_row[32] or 0)),
furnizori_scadent_60=Decimal(str(facturi_row[33] or 0)),
furnizori_scadent_90=Decimal(str(facturi_row[34] or 0)),
furnizori_scadent_90plus=Decimal(str(facturi_row[35] or 0)),
# TREZORERIE
treasury_accounts=treasury_accounts,
treasury_totals_by_currency=treasury_totals,
# Date suplimentare pentru trend analysis
clienti_facturat_luna_anterioara=Decimal(str(facturi_row[36] or 0)),
furnizori_facturat_luna_anterioara=Decimal(str(facturi_row[37] or 0)),
clienti_facturat_an_curent=Decimal(str(facturi_row[38] or 0)),
clienti_facturat_an_anterior=Decimal(str(facturi_row[39] or 0)),
furnizori_facturat_an_curent=Decimal(str(facturi_row[40] or 0)),
furnizori_facturat_an_anterior=Decimal(str(facturi_row[41] or 0))
)
@staticmethod
async def get_trends(company_id: int, period: str = "12m") -> Dict[str, Any]:
"""Get comprehensive trend analysis data for all dashboard indicators"""
try:
async with oracle_pool.get_connection() as connection:
with connection.cursor() as cursor:
# Get schema for company
schema_query = """
SELECT schema
FROM CONTAFIN_ORACLE.v_nom_firme
WHERE id_firma = :company_id
"""
cursor.execute(schema_query, company_id=company_id)
schema_result = cursor.fetchone()
if not schema_result:
raise ValueError(f"Schema not found for company {company_id}")
schema = schema_result[0]
# Get current period
current_period_query = f"""
WITH luna_curenta AS (
SELECT anul, luna FROM {schema}.calendar
WHERE anul*12+luna = (SELECT MAX(anul*12+luna) FROM {schema}.calendar)
)
SELECT anul, luna FROM luna_curenta
"""
cursor.execute(current_period_query)
current_period = cursor.fetchone()
if not current_period:
# Fallback to current system date
current_year = 2024
current_month = 12
else:
current_year = current_period[0]
current_month = current_period[1]
# Determine period parameters
if period == 'ytd':
# Year to date - from January to current month of current year
start_year = current_year
start_month = 1
end_year = current_year
end_month = current_month
elif period == '12m':
# Last 12 months
from datetime import date
from dateutil.relativedelta import relativedelta
current_date = date(current_year, current_month, 1)
start_date = current_date - relativedelta(months=11) # 12 months including current
start_year = start_date.year
start_month = start_date.month
end_year = current_year
end_month = current_month
else:
# Default to 12 months
from datetime import date
from dateutil.relativedelta import relativedelta
current_date = date(current_year, current_month, 1)
start_date = current_date - relativedelta(months=11)
start_year = start_date.year
start_month = start_date.month
end_year = current_year
end_month = current_month
# Calculate previous period (12 months before current period)
from datetime import date
from dateutil.relativedelta import relativedelta
prev_start_date = date(start_year, start_month, 1) - relativedelta(months=12)
prev_end_date = date(end_year, end_month, 1) - relativedelta(months=12)
prev_start_year = prev_start_date.year
prev_start_month = prev_start_date.month
prev_end_year = prev_end_date.year
prev_end_month = prev_end_date.month
# Comprehensive trends query for all indicators (current + previous period)
trends_query = f"""
WITH trend_periods AS (
SELECT DISTINCT an as anul, luna
FROM {schema}.vbalanta_parteneri
WHERE (an*100 + luna >= {start_year * 100 + start_month})
AND (an*100 + luna <= {end_year * 100 + end_month})
),
prev_trend_periods AS (
SELECT DISTINCT an as anul, luna
FROM {schema}.vbalanta_parteneri
WHERE (an*100 + luna >= {prev_start_year * 100 + prev_start_month})
AND (an*100 + luna <= {prev_end_year * 100 + prev_end_month})
),
comprehensive_data AS (
SELECT
tp.anul,
tp.luna,
tp.anul||'-'||LPAD(tp.luna,2,'0') as perioada,
-- CLIENTI - facturat (cifra de afaceri - vanzari)
COALESCE(SUM(CASE
WHEN vb.cont in ('4111', '461') THEN vb.debit
ELSE 0
END), 0) as clienti_facturat,
-- CLIENTI - incasat (incasari de la clienti)
COALESCE(SUM(CASE
WHEN vb.cont IN ('4111', '461') THEN vb.credit
WHEN vb.cont in ('419') THEN vb.debit
ELSE 0
END), 0) as clienti_incasat,
-- FURNIZORI - facturat (achizitii)
COALESCE(SUM(CASE
WHEN vb.cont in ('401', '404', '462') THEN vb.credit
ELSE 0
END), 0) as furnizori_facturat,
-- FURNIZORI - achitat (plati catre furnizori)
COALESCE(SUM(CASE
WHEN vb.cont in ('401', '404', '462') THEN vb.debit
WHEN vb.cont in ('409') THEN vb.credit
ELSE 0
END), 0) as furnizori_achitat,
-- CLIENTI SOLD (balanta clienti)
COALESCE(SUM(CASE
WHEN vb.cont IN ('4111', '461') THEN vb.solddeb - vb.soldcred
WHEN vb.cont IN ('419') THEN vb.soldcred - vb.solddeb
ELSE 0
END), 0) as clienti_sold,
-- FURNIZORI SOLD (balanta furnizori)
COALESCE(SUM(CASE
WHEN vb.cont in ('401', '404', '462') THEN vb.soldcred - vb.solddeb
WHEN vb.cont IN ('409') THEN vb.solddeb - vb.soldcred
ELSE 0
END), 0) as furnizori_sold,
-- TREZORERIE SOLD
COALESCE(SUM(CASE
WHEN vb.cont IN ('5121','5311','5124','5314') THEN vb.solddeb - vb.soldcred
ELSE 0
END), 0) as trezorerie_sold
FROM trend_periods tp
LEFT JOIN {schema}.vbalanta_parteneri vb
ON vb.an = tp.anul
AND vb.luna = tp.luna
AND vb.cont in ('4111', '461', '419', '401', '404', '462', '409','5121','5311','5124','5314')
GROUP BY tp.anul, tp.luna
),
prev_comprehensive_data AS (
SELECT
tp.anul,
tp.luna,
tp.anul||'-'||LPAD(tp.luna,2,'0') as perioada,
-- CLIENTI - facturat (cifra de afaceri - vanzari)
COALESCE(SUM(CASE
WHEN vb.cont in ('4111', '461') THEN vb.debit
ELSE 0
END), 0) as clienti_facturat,
-- CLIENTI - incasat (incasari de la clienti)
COALESCE(SUM(CASE
WHEN vb.cont IN ('4111', '461') THEN vb.credit
WHEN vb.cont in ('419') THEN vb.debit
ELSE 0
END), 0) as clienti_incasat,
-- FURNIZORI - facturat (achizitii)
COALESCE(SUM(CASE
WHEN vb.cont in ('401', '404', '462') THEN vb.credit
ELSE 0
END), 0) as furnizori_facturat,
-- FURNIZORI - achitat (plati catre furnizori)
COALESCE(SUM(CASE
WHEN vb.cont in ('401', '404', '462') THEN vb.debit
WHEN vb.cont in ('409') THEN vb.credit
ELSE 0
END), 0) as furnizori_achitat,
-- CLIENTI SOLD (balanta clienti)
COALESCE(SUM(CASE
WHEN vb.cont IN ('4111', '461') THEN vb.solddeb - vb.soldcred
WHEN vb.cont IN ('419') THEN vb.soldcred - vb.solddeb
ELSE 0
END), 0) as clienti_sold,
-- FURNIZORI SOLD (balanta furnizori)
COALESCE(SUM(CASE
WHEN vb.cont in ('401', '404', '462') THEN vb.soldcred - vb.solddeb
WHEN vb.cont IN ('409') THEN vb.solddeb - vb.soldcred
ELSE 0
END), 0) as furnizori_sold,
-- TREZORERIE SOLD
COALESCE(SUM(CASE
WHEN vb.cont IN ('5121','5311','5124','5314') THEN vb.solddeb - vb.soldcred
ELSE 0
END), 0) as trezorerie_sold
FROM prev_trend_periods tp
LEFT JOIN {schema}.vbalanta_parteneri vb
ON vb.an = tp.anul
AND vb.luna = tp.luna
AND vb.cont in ('4111', '461', '419', '401', '404', '462', '409','5121','5311','5124','5314')
GROUP BY tp.anul, tp.luna
)
SELECT
'current' as data_type,
cd.anul,
cd.luna,
cd.perioada,
cd.clienti_facturat,
cd.clienti_incasat,
cd.furnizori_facturat,
cd.furnizori_achitat,
cd.clienti_sold,
cd.furnizori_sold,
cd.trezorerie_sold
FROM comprehensive_data cd
UNION ALL
SELECT
'previous' as data_type,
pcd.anul,
pcd.luna,
pcd.perioada,
pcd.clienti_facturat,
pcd.clienti_incasat,
pcd.furnizori_facturat,
pcd.furnizori_achitat,
pcd.clienti_sold,
pcd.furnizori_sold,
pcd.trezorerie_sold
FROM prev_comprehensive_data pcd
ORDER BY data_type DESC, anul ASC, luna ASC
"""
cursor.execute(trends_query)
all_results = cursor.fetchall()
# Separate current and previous results
result = [row[1:] for row in all_results if row[0] == 'current']
prev_result = [row[1:] for row in all_results if row[0] == 'previous']
if not result:
# Return empty arrays in the expected format
return {
"periods": [],
"clienti_facturat": [],
"clienti_incasat": [],
"furnizori_facturat": [],
"furnizori_achitat": [],
"clienti_sold": [],
"furnizori_sold": [],
"trezorerie_sold": [],
"rata_incasare_clienti": [],
"rata_achitare_furnizori": [],
"previous_periods": [],
"clienti_facturat_prev": [],
"clienti_incasat_prev": [],
"furnizori_facturat_prev": [],
"furnizori_achitat_prev": [],
"clienti_sold_prev": [],
"furnizori_sold_prev": [],
"trezorerie_sold_prev": [],
"metadata": {
"period": period,
"company_id": company_id,
"data_points": 0,
"grouping": "monthly"
}
}
# Process results into the expected format
periods = []
clienti_facturat = []
clienti_incasat = []
furnizori_facturat = []
furnizori_achitat = []
clienti_sold = []
furnizori_sold = []
trezorerie_sold = []
rata_incasare_clienti = []
rata_achitare_furnizori = []
# Process previous period results
previous_periods = []
clienti_facturat_prev = []
clienti_incasat_prev = []
furnizori_facturat_prev = []
furnizori_achitat_prev = []
clienti_sold_prev = []
furnizori_sold_prev = []
trezorerie_sold_prev = []
for row in result:
# After row[1:], indices are: 0=anul, 1=luna, 2=perioada, 3=clienti_facturat, etc.
periods.append(row[2]) # perioada
clienti_facturat.append(float(row[3] or 0))
clienti_incasat.append(float(row[4] or 0))
furnizori_facturat.append(float(row[5] or 0))
furnizori_achitat.append(float(row[6] or 0))
clienti_sold.append(float(row[7] or 0))
furnizori_sold.append(float(row[8] or 0))
trezorerie_sold.append(float(row[9] or 0))
# Calculate collection and payment rates
cf = float(row[3] or 0) # clienti_facturat
ci = float(row[4] or 0) # clienti_incasat
ff = float(row[5] or 0) # furnizori_facturat
fa = float(row[6] or 0) # furnizori_achitat
# Collection rate (rata incasare clienti)
rata_incasare = (ci / cf * 100) if cf > 0 else 0
rata_incasare_clienti.append(round(rata_incasare, 2))
# Payment rate (rata achitare furnizori)
rata_achitare = (fa / ff * 100) if ff > 0 else 0
rata_achitare_furnizori.append(round(rata_achitare, 2))
# Process previous period data
for row in prev_result:
previous_periods.append(row[2]) # perioada
clienti_facturat_prev.append(float(row[3] or 0))
clienti_incasat_prev.append(float(row[4] or 0))
furnizori_facturat_prev.append(float(row[5] or 0))
furnizori_achitat_prev.append(float(row[6] or 0))
clienti_sold_prev.append(float(row[7] or 0))
furnizori_sold_prev.append(float(row[8] or 0))
trezorerie_sold_prev.append(float(row[9] or 0))
# Calculate growth rates
growth_rates = {}
if len(periods) >= 2:
datasets = {
'clienti_facturat': clienti_facturat,
'clienti_incasat': clienti_incasat,
'furnizori_facturat': furnizori_facturat,
'furnizori_achitat': furnizori_achitat,
'trezorerie_sold': trezorerie_sold
}
for key, values in datasets.items():
if len(values) >= 2:
previous_value = values[0]
current_value = values[-1]
if previous_value != 0:
growth_rate = round((current_value - previous_value) / abs(previous_value) * 100, 2)
else:
growth_rate = 100.0 if current_value > 0 else -100.0 if current_value < 0 else 0.0
growth_rates[key] = growth_rate
return {
"periods": periods,
"clienti_facturat": clienti_facturat,
"clienti_incasat": clienti_incasat,
"furnizori_facturat": furnizori_facturat,
"furnizori_achitat": furnizori_achitat,
"clienti_sold": clienti_sold,
"furnizori_sold": furnizori_sold,
"trezorerie_sold": trezorerie_sold,
"rata_incasare_clienti": rata_incasare_clienti,
"rata_achitare_furnizori": rata_achitare_furnizori,
"previous_periods": previous_periods,
"clienti_facturat_prev": clienti_facturat_prev,
"clienti_incasat_prev": clienti_incasat_prev,
"furnizori_facturat_prev": furnizori_facturat_prev,
"furnizori_achitat_prev": furnizori_achitat_prev,
"clienti_sold_prev": clienti_sold_prev,
"furnizori_sold_prev": furnizori_sold_prev,
"trezorerie_sold_prev": trezorerie_sold_prev,
"metadata": {
"period": period,
"company_id": company_id,
"data_points": len(periods),
"previous_data_points": len(previous_periods),
"grouping": "monthly"
},
"growth_rates": growth_rates
}
except Exception as e:
logger.error(f"Error getting comprehensive trends: {str(e)}")
raise
@staticmethod
async def get_detailed_data(company: str, data_type: str, page: int = 1, page_size: int = 25, search: str = ""):
"""
Obține date detaliate pentru tabelele din dashboard
Fixed to use existing vireg_parteneri view instead of missing tables
"""
logger.info(f"get_detailed_data called: company={company}, data_type={data_type}, page={page}")
async with oracle_pool.get_connection() as connection:
with connection.cursor() as cursor:
try:
# Get schema for company
schema_query = """
SELECT schema
FROM CONTAFIN_ORACLE.v_nom_firme
WHERE id_firma = :company_id
"""
cursor.execute(schema_query, {'company_id': int(company)})
schema_result = cursor.fetchone()
if not schema_result:
logger.error(f"Schema not found for company {company}")
return {"error": "Schema not found for company", "data": [], "total": 0}
schema = schema_result[0]
logger.info(f"Found schema: {schema}")
# Calculate offset for pagination
offset = (page - 1) * page_size
logger.info(f"Pagination params: page={page}, page_size={page_size}, offset={offset}")
# Handle treasury early return
if data_type == "treasury":
return {
"data": [],
"total": 0,
"page": page,
"page_size": page_size,
"total_pages": 0,
"message": "Date detaliate pentru trezorerie nu sunt disponibile"
}
# Build query based on data type
if data_type == "clients":
# Query cu paginare pe CLIENȚI (nu pe facturi individuale)
base_query = f"""
WITH luna_curenta AS (
SELECT anul, luna FROM {schema}.calendar
WHERE anul*12+luna = (SELECT MAX(anul*12+luna) FROM {schema}.calendar)
),
clienti_cu_sold AS (
-- Pasul 1: Identifică TOȚI clienții cu sold != 0
SELECT DISTINCT vp.nume as client_name
FROM {schema}.vireg_parteneri vp, luna_curenta lc
WHERE vp.an = lc.anul
AND vp.luna = lc.luna
AND vp.cont IN ('4111','461')
AND vp.nume IS NOT NULL
AND ((vp.precdeb + vp.debit) - (vp.preccred + vp.credit)) <> 0
AND (UPPER(vp.nume) LIKE UPPER('%{search}%') OR '{search}' = '')
ORDER BY vp.nume ASC
),
clienti_pagina AS (
-- Pasul 2: Paginează pe CLIENȚI (25 clienți/pagină)
SELECT * FROM (
SELECT t.*, ROWNUM as rn FROM clienti_cu_sold t WHERE ROWNUM <= {offset + page_size}
) WHERE rn > {offset}
)
-- Pasul 3: Ia TOATE facturile pentru clienții din pagina curentă
SELECT
vp.nume as client,
vp.nract as numar_document,
vp.dataact as data_document,
(vp.precdeb + vp.debit) as facturat,
(vp.preccred + vp.credit) as incasat,
(vp.precdeb + vp.debit) - (vp.preccred + vp.credit) as sold,
NVL(vp.datascad, vp.dataact + 30) as data_scadenta,
CASE
WHEN NVL(vp.datascad, vp.dataact + 30) < TRUNC(SYSDATE) THEN 'Restant'
ELSE 'In termen'
END as status
FROM {schema}.vireg_parteneri vp, luna_curenta lc, clienti_pagina cp
WHERE vp.an = lc.anul
AND vp.luna = lc.luna
AND vp.nume = cp.client_name
AND vp.cont IN ('4111','461')
AND ((vp.precdeb + vp.debit) - (vp.preccred + vp.credit)) <> 0
ORDER BY vp.nume ASC, vp.dataact DESC
"""
elif data_type == "suppliers":
# Query cu paginare pe FURNIZORI (nu pe facturi individuale)
base_query = f"""
WITH luna_curenta AS (
SELECT anul, luna FROM {schema}.calendar
WHERE anul*12+luna = (SELECT MAX(anul*12+luna) FROM {schema}.calendar)
),
furnizori_cu_sold AS (
-- Pasul 1: Identifică TOȚI furnizorii cu sold != 0
SELECT DISTINCT vp.nume as furnizor_name
FROM {schema}.vireg_parteneri vp, luna_curenta lc
WHERE vp.an = lc.anul
AND vp.luna = lc.luna
AND vp.cont IN ('401','404','462')
AND vp.nume IS NOT NULL
AND ((vp.preccred + vp.credit) - (vp.precdeb + vp.debit)) <> 0
AND (UPPER(vp.nume) LIKE UPPER('%{search}%') OR '{search}' = '')
ORDER BY vp.nume ASC
),
furnizori_pagina AS (
-- Pasul 2: Paginează pe FURNIZORI (25 furnizori/pagină)
SELECT * FROM (
SELECT t.*, ROWNUM as rn FROM furnizori_cu_sold t WHERE ROWNUM <= {offset + page_size}
) WHERE rn > {offset}
)
-- Pasul 3: Ia TOATE facturile pentru furnizorii din pagina curentă
SELECT
vp.nume as furnizor,
vp.nract as numar_document,
vp.dataact as data_document,
(vp.preccred + vp.credit) as facturat,
(vp.precdeb + vp.debit) as achitat,
(vp.preccred + vp.credit) - (vp.precdeb + vp.debit) as sold,
NVL(vp.datascad, vp.dataact + 30) as data_scadenta,
CASE
WHEN NVL(vp.datascad, vp.dataact + 30) < TRUNC(SYSDATE) THEN 'Restant'
ELSE 'In termen'
END as status
FROM {schema}.vireg_parteneri vp, luna_curenta lc, furnizori_pagina fp
WHERE vp.an = lc.anul
AND vp.luna = lc.luna
AND vp.nume = fp.furnizor_name
AND vp.cont IN ('401','404','462')
AND ((vp.preccred + vp.credit) - (vp.precdeb + vp.debit)) <> 0
ORDER BY vp.nume ASC, vp.dataact DESC
"""
else:
return {"error": "Invalid data type", "data": [], "total": 0}
# Get total count of CLIENȚI/FURNIZORI (not individual invoices)
if data_type == "clients":
count_query = f"""
SELECT COUNT(DISTINCT vp.nume)
FROM {schema}.vireg_parteneri vp,
(SELECT anul, luna FROM {schema}.calendar WHERE anul*12+luna = (SELECT MAX(anul*12+luna) FROM {schema}.calendar)) lc
WHERE vp.an = lc.anul
AND vp.luna = lc.luna
AND vp.cont IN ('4111','461')
AND vp.nume IS NOT NULL
AND ((vp.precdeb + vp.debit) - (vp.preccred + vp.credit)) <> 0
AND (UPPER(vp.nume) LIKE UPPER('%{search}%') OR '{search}' = '')
"""
elif data_type == "suppliers":
count_query = f"""
SELECT COUNT(DISTINCT vp.nume)
FROM {schema}.vireg_parteneri vp,
(SELECT anul, luna FROM {schema}.calendar WHERE anul*12+luna = (SELECT MAX(anul*12+luna) FROM {schema}.calendar)) lc
WHERE vp.an = lc.anul
AND vp.luna = lc.luna
AND vp.cont IN ('401','404','462')
AND vp.nume IS NOT NULL
AND ((vp.preccred + vp.credit) - (vp.precdeb + vp.debit)) <> 0
AND (UPPER(vp.nume) LIKE UPPER('%{search}%') OR '{search}' = '')
"""
cursor.execute(count_query)
total = cursor.fetchone()[0]
logger.info(f"Total {data_type}: {total}")
# Execute base query directly (pagination already included in CTE)
logger.info(f"Executing query with offset={offset}, page_size={page_size}")
cursor.execute(base_query)
columns = [desc[0].lower() for desc in cursor.description]
data = []
for row in cursor.fetchall():
# Map row data to column names
data.append(dict(zip(columns, row)))
# Count unique clients/suppliers in returned data
if data_type == "clients":
unique_names = len(set(row.get('client') for row in data))
logger.info(f"Returned {len(data)} invoices from {unique_names} unique clients (expected max {page_size} clients)")
elif data_type == "suppliers":
unique_names = len(set(row.get('furnizor') for row in data))
logger.info(f"Returned {len(data)} invoices from {unique_names} unique suppliers (expected max {page_size} suppliers)")
logger.info(f"Detailed data query returned {len(data)} rows out of {total} total")
return {
"data": data,
"total": total,
"page": page,
"page_size": page_size,
"total_pages": (total + page_size - 1) // page_size
}
except Exception as e:
logger.error(f"Error in get_detailed_data: {str(e)}")
return {"error": f"Database error: {str(e)}", "data": [], "total": 0}
@staticmethod
async def get_performance_data(company_id: int, period: str = "7d") -> Dict[str, Any]:
"""
Calculează performanța încasări/plăți pentru perioada selectată
Args:
company_id: ID-ul companiei
period: Perioada ("7d", "1m", "3m", "6m", "ytd", "12m")
Returns:
{
labels: List[str] - etichete pentru perioadele de timp
incasari: List[float] - valorile încasărilor pe perioadă
plati: List[float] - valorile plăților pe perioadă
indicators: {
rataIncasare: float - rata de încasare (%)
cashConversion: int - zilele de conversie cash
workingCapital: float - capitalul de lucru
trend: str - tendința ("up", "down", "stable")
}
}
"""
try:
async with oracle_pool.get_connection() as connection:
with connection.cursor() as cursor:
# Get schema
schema_query = "SELECT schema FROM CONTAFIN_ORACLE.v_nom_firme WHERE id_firma = :company_id"
cursor.execute(schema_query, {'company_id': company_id})
schema_result = cursor.fetchone()
if not schema_result:
raise ValueError(f"Schema nu a fost găsită pentru id_firma {company_id}")
schema = schema_result[0]
# Pentru acum returnăm date mock cu structura corectă
# TODO: Implementați query-uri Oracle pentru perioada selectată
# Mock data based on period
if period == "7d":
labels = ["Lun", "Mar", "Mie", "Joi", "Vin", "Sâm", "Dum"]
incasari = [45000, 52000, 38000, 61000, 49000, 33000, 28000]
plati = [31000, 44000, 29000, 48000, 37000, 25000, 19000]
elif period == "1m":
labels = ["Săpt 1", "Săpt 2", "Săpt 3", "Săpt 4"]
incasari = [185000, 201000, 168000, 195000]
plati = [142000, 156000, 133000, 151000]
elif period == "3m":
labels = ["Luna 1", "Luna 2", "Luna 3"]
incasari = [749000, 698000, 823000]
plati = [582000, 519000, 634000]
elif period == "6m":
labels = ["Ian", "Feb", "Mar", "Apr", "Mai", "Iun"]
incasari = [749000, 698000, 823000, 756000, 681000, 792000]
plati = [582000, 519000, 634000, 588000, 523000, 612000]
elif period == "ytd":
labels = ["Ian", "Feb", "Mar", "Apr", "Mai", "Iun", "Iul", "Aug", "Sep", "Oct", "Nov", "Dec"]
incasari = [749000, 698000, 823000, 756000, 681000, 792000, 834000, 712000, 768000, 695000, 743000, 821000]
plati = [582000, 519000, 634000, 588000, 523000, 612000, 647000, 553000, 596000, 539000, 577000, 638000]
else: # 12m
labels = ["Ian", "Feb", "Mar", "Apr", "Mai", "Iun", "Iul", "Aug", "Sep", "Oct", "Nov", "Dec"]
incasari = [749000, 698000, 823000, 756000, 681000, 792000, 834000, 712000, 768000, 695000, 743000, 821000]
plati = [582000, 519000, 634000, 588000, 523000, 612000, 647000, 553000, 596000, 539000, 577000, 638000]
# Calculate indicators
total_incasari = sum(incasari)
total_plati = sum(plati)
rata_incasare = round((total_incasari / (total_incasari * 1.15)) * 100, 1) if total_incasari > 0 else 0 # Assuming 15% more was invoiced
cash_conversion = 45 # Days - mock value
working_capital = total_incasari - total_plati
# Determine trend
if len(incasari) >= 2:
trend = "up" if incasari[-1] > incasari[-2] else "down" if incasari[-1] < incasari[-2] else "stable"
else:
trend = "stable"
return {
"labels": labels,
"incasari": incasari,
"plati": plati,
"indicators": {
"rataIncasare": rata_incasare,
"cashConversion": cash_conversion,
"workingCapital": working_capital,
"trend": trend
}
}
except Exception as e:
logger.error(f"Error getting performance data: {str(e)}")
raise
@staticmethod
async def get_cashflow_forecast(company_id: int, period: str = "7d") -> Dict[str, Any]:
"""
Calculează previziunea cash flow bazată pe scadențe
Args:
company_id: ID-ul companiei
period: Perioada ("7d", "1m", "3m", "6m")
Returns:
{
periods: List[str] - perioadele de timp
inflows: List[float] - intrările de cash estimate
outflows: List[float] - ieșirile de cash estimate
netFlow: List[float] - fluxul net pe perioadă
cumulative: List[float] - fluxul cumulativ
criticalDays: List[str] - zilele cu deficit critic
netTotal: float - totalul net al perioadei
}
"""
try:
async with oracle_pool.get_connection() as connection:
with connection.cursor() as cursor:
# Get schema
schema_query = "SELECT schema FROM CONTAFIN_ORACLE.v_nom_firme WHERE id_firma = :company_id"
cursor.execute(schema_query, {'company_id': company_id})
schema_result = cursor.fetchone()
if not schema_result:
raise ValueError(f"Schema nu a fost găsită pentru id_firma {company_id}")
schema = schema_result[0]
# Pentru acum returnăm date mock cu structura corectă
# TODO: Implementați query-uri pentru scadențe viitoare
# Mock data based on period
if period == "7d":
periods = ["Lun", "Mar", "Mie", "Joi", "Vin", "Sâm", "Dum"]
inflows = [25000, 18000, 32000, 45000, 28000, 15000, 12000]
outflows = [31000, 22000, 19000, 38000, 25000, 18000, 8000]
elif period == "1m":
periods = ["Săpt 1", "Săpt 2", "Săpt 3", "Săpt 4"]
inflows = [95000, 112000, 88000, 105000]
outflows = [108000, 92000, 75000, 89000]
elif period == "3m":
periods = ["Luna 1", "Luna 2", "Luna 3"]
inflows = [400000, 365000, 420000]
outflows = [364000, 298000, 385000]
else: # 6m
periods = ["Ian", "Feb", "Mar", "Apr", "Mai", "Iun"]
inflows = [400000, 365000, 420000, 385000, 342000, 396000]
outflows = [364000, 298000, 385000, 356000, 289000, 367000]
# Calculate net flow and cumulative
net_flow = [inf - out for inf, out in zip(inflows, outflows)]
cumulative = []
running_total = 150000 # Starting cash position (mock)
for net in net_flow:
running_total += net
cumulative.append(running_total)
# Identify critical days (negative cumulative)
critical_days = []
for i, (period_name, cum) in enumerate(zip(periods, cumulative)):
if cum < 0:
critical_days.append(period_name)
net_total = sum(net_flow)
return {
"periods": periods,
"inflows": inflows,
"outflows": outflows,
"netFlow": net_flow,
"cumulative": cumulative,
"criticalDays": critical_days,
"netTotal": net_total
}
except Exception as e:
logger.error(f"Error getting cashflow forecast: {str(e)}")
raise
@staticmethod
async def get_maturity_analysis(company_id: int, period: str = "7d") -> Dict[str, Any]:
"""
Analizează scadențele clienți vs furnizori cu date reale din Oracle
Args:
company_id: ID-ul companiei
period: Perioada ("7d", "1m", "3m", "6m", "12m", "over12m")
Returns:
{
clients: List[Dict] - [{name, amount, dueDate, daysOverdue}, ...]
suppliers: List[Dict] - [{name, amount, dueDate, daysOverdue}, ...]
balance: float - balanța între clienți și furnizori
recommendations: List[str] - recomandări pentru îmbunătățire
}
"""
try:
async with oracle_pool.get_connection() as connection:
with connection.cursor() as cursor:
# Get schema
schema_query = "SELECT schema FROM CONTAFIN_ORACLE.v_nom_firme WHERE id_firma = :company_id"
cursor.execute(schema_query, {'company_id': company_id})
schema_result = cursor.fetchone()
if not schema_result:
raise ValueError(f"Schema nu a fost găsită pentru id_firma {company_id}")
schema = schema_result[0]
# Determină filtrele pentru perioada selectată (orizont de planificare)
# Logică: Include TOATE restanțele + scadențele viitoare din perioada selectată
if period == "7d":
days_filter = "datascad <= TRUNC(SYSDATE) + 7"
elif period == "1m":
days_filter = "datascad <= TRUNC(SYSDATE) + 30"
elif period == "3m":
days_filter = "datascad <= TRUNC(SYSDATE) + 90"
elif period == "6m":
days_filter = "datascad <= TRUNC(SYSDATE) + 180"
elif period == "12m":
days_filter = "datascad <= TRUNC(SYSDATE) + 365"
else: # "all" - toate soldurile
days_filter = "1=1"
# Query pentru clienți (facturi de încasat)
clients_query = f"""
WITH luna_curenta AS
(SELECT anul, luna
FROM {schema}.calendar
WHERE anul * 12 + luna =
(SELECT MAX(anul * 12 + luna) FROM {schema}.calendar))
SELECT client_name,
SUM(amount) as amount,
MAX(due_date) as due_date,
MAX(days_overdue) as days_overdue
FROM (SELECT vp.nume as client_name,
((vp.precdeb + vp.debit) - (vp.preccred + vp.credit)) as amount,
NVL(vp.datascad, vp.DATAACT + 30) as due_date,
TRUNC(SYSDATE) - NVL(vp.datascad, vp.DATAACT + 30) as days_overdue
FROM {schema}.vireg_parteneri vp, luna_curenta lc
WHERE vp.an = lc.anul
AND vp.luna = lc.luna
AND vp.cont IN ('4111', '461')
AND vp.nume IS NOT NULL
AND {days_filter}
AND ((vp.precdeb + vp.debit) - (vp.preccred + vp.credit)) <> 0)
GROUP BY client_name
HAVING SUM (amount) <> 0
ORDER BY days_overdue desc
"""
cursor.execute(clients_query)
clients_rows = cursor.fetchall()
clients = []
for row in clients_rows:
client_name = row[0]
amount = float(row[1] or 0)
due_date = row[2].strftime('%Y-%m-%d') if row[2] else None
days_overdue = int(row[3] or 0)
clients.append({
"name": client_name,
"amount": amount,
"dueDate": due_date,
"daysOverdue": days_overdue
})
# Sortare îmbunătățită: Restanțe primele (descrescător), apoi scadențe viitoare (crescător)
clients.sort(key=lambda x: (-1 if x["daysOverdue"] > 0 else 1, -x["daysOverdue"] if x["daysOverdue"] > 0 else x["daysOverdue"]))
# Query pentru furnizori (facturi de plătit)
suppliers_query = f"""
WITH luna_curenta AS
(SELECT anul, luna
FROM {schema}.calendar
WHERE anul * 12 + luna =
(SELECT MAX(anul * 12 + luna) FROM {schema}.calendar))
SELECT client_name,
SUM(amount) as amount,
MIN(due_date) as due_date,
MAX(days_overdue) as days_overdue
FROM (SELECT vp.nume as client_name,
((vp.preccred + vp.credit)-(vp.precdeb + vp.debit)) as amount,
NVL(vp.datascad, vp.DATAACT + 30) as due_date,
TRUNC(SYSDATE) - NVL(vp.datascad, vp.DATAACT + 30) as days_overdue
FROM {schema}.vireg_parteneri vp, luna_curenta lc
WHERE vp.an = lc.anul
AND vp.luna = lc.luna
AND vp.cont IN ('401', '404', '462')
AND vp.nume IS NOT NULL
AND {days_filter}
AND ((vp.preccred + vp.credit)-(vp.precdeb + vp.debit)) <> 0)
GROUP BY client_name
HAVING SUM (amount) <> 0
ORDER BY days_overdue desc
"""
cursor.execute(suppliers_query)
suppliers_rows = cursor.fetchall()
suppliers = []
for row in suppliers_rows:
supplier_name = row[0]
amount = float(row[1] or 0)
due_date = row[2].strftime('%Y-%m-%d') if row[2] else None
days_overdue = int(row[3] or 0)
suppliers.append({
"name": supplier_name,
"amount": amount,
"dueDate": due_date,
"daysOverdue": days_overdue
})
# Sortare îmbunătățită: Restanțe primele (descrescător), apoi scadențe viitoare (crescător)
suppliers.sort(key=lambda x: (-1 if x["daysOverdue"] > 0 else 1, -x["daysOverdue"] if x["daysOverdue"] > 0 else x["daysOverdue"]))
# Calculează balanța
total_clients = sum(c["amount"] for c in clients)
total_suppliers = sum(s["amount"] for s in suppliers)
balance = total_clients - total_suppliers
# Generează recomandări bazate pe date reale
recommendations = []
if balance < -50000:
recommendations.extend([
"Deficit de cash flow identificat - prioritizați încasările",
"Negociați termeni de plată mai buni cu furnizorii",
"Considerați finanțare pe termen scurt"
])
elif balance > 100000:
recommendations.extend([
"Surplus de cash disponibil pentru investiții",
"Considerați plăți anticipate pentru reduceri",
"Evaluați oportunități de investire a excesului"
])
else:
recommendations.append("Balanța cash flow este echilibrată")
overdue_clients = [c for c in clients if c["daysOverdue"] > 0]
if overdue_clients:
total_overdue = sum(c["amount"] for c in overdue_clients)
recommendations.append(f"Atenție: {len(overdue_clients)} clienți în restanță (total: {total_overdue:,.0f} RON)")
overdue_suppliers = [s for s in suppliers if s["daysOverdue"] > 0]
if overdue_suppliers:
total_overdue = sum(s["amount"] for s in overdue_suppliers)
recommendations.append(f"Urgent: {len(overdue_suppliers)} furnizori în restanță (total: {total_overdue:,.0f} RON)")
# Adaugă recomandări specifice pentru clienți cu restanțe mari
critical_clients = [c for c in overdue_clients if c["daysOverdue"] > 30]
if critical_clients:
recommendations.append(f"Critică: {len(critical_clients)} clienți cu restanțe peste 30 de zile")
# Adaugă metadate pentru context complet
metadata = {
"period": period,
"total_clients": len(clients),
"total_suppliers": len(suppliers),
"overdue_clients": len(overdue_clients),
"overdue_suppliers": len(overdue_suppliers),
"critical_clients": len(critical_clients) if critical_clients else 0,
"total_overdue_amount_clients": sum(c["amount"] for c in overdue_clients) if overdue_clients else 0,
"total_overdue_amount_suppliers": sum(s["amount"] for s in overdue_suppliers) if overdue_suppliers else 0
}
return {
"clients": clients,
"suppliers": suppliers,
"balance": balance,
"recommendations": recommendations,
"metadata": metadata
}
except Exception as e:
logger.error(f"Error getting maturity analysis: {str(e)}")
raise
@staticmethod
async def get_monthly_flows(company: int) -> Dict[str, Any]:
"""
Obține fluxurile lunare de intrare și ieșire pentru luna curentă
"""
try:
async with oracle_pool.get_connection() as connection:
with connection.cursor() as cursor:
# Obține schema
company_id = company
schema_query = "SELECT schema FROM CONTAFIN_ORACLE.v_nom_firme WHERE id_firma = :company_id"
cursor.execute(schema_query, {'company_id': company_id})
schema_result = cursor.fetchone()
if not schema_result:
raise ValueError(f"Schema nu a fost găsită pentru id_firma {company_id}")
schema = schema_result[0]
# Query pentru fluxuri lunare
flows_query = f"""
WITH luna_curenta AS
(SELECT anul, luna, anul || '-' || LPAD(luna, 2, '0') as period
FROM {schema}.calendar
WHERE anul * 12 + luna =
(SELECT MAX(c2.anul * 12 + c2.luna)
FROM {schema}.calendar c2))
SELECT
SUM(CASE
WHEN vp.cont IN ('4111', '461') THEN
vp.credit
WHEN vp.cont IN ('419') THEN
vp.debit
ELSE
0
END) as monthly_inflows,
SUM(CASE
WHEN vp.cont IN ('401', '404', '462') THEN
vp.debit
WHEN vp.cont IN ('409') THEN
vp.credit
ELSE
0
END) as monthly_outflows,
MAX(lc.period) as period
FROM {schema}.vireg_parteneri vp, luna_curenta lc
WHERE vp.an = lc.anul
AND vp.luna = lc.luna
AND vp.cont IN ('4111', '461', '419', '401', '404', '462', '409')
"""
cursor.execute(flows_query)
flow_row = cursor.fetchone()
if not flow_row:
return {
"inflows": 0,
"outflows": 0,
"period": "2025-08",
"currency": "RON"
}
return {
"inflows": float(flow_row[0] or 0),
"outflows": float(flow_row[1] or 0),
"period": flow_row[2] or "2025-08",
"currency": "RON"
}
except Exception as e:
logger.error(f"Error in get_monthly_flows: {str(e)}")
raise
@staticmethod
async def get_treasury_breakdown(company: int) -> Dict[str, Any]:
"""
Obține breakdown-ul trezoreriei pe casă și bancă
"""
try:
async with oracle_pool.get_connection() as connection:
with connection.cursor() as cursor:
# Obține schema
company_id = company
schema_query = "SELECT schema FROM CONTAFIN_ORACLE.v_nom_firme WHERE id_firma = :company_id"
cursor.execute(schema_query, {'company_id': company_id})
schema_result = cursor.fetchone()
if not schema_result:
raise ValueError(f"Schema nu a fost găsită pentru id_firma {company_id}")
schema = schema_result[0]
# Query pentru breakdown trezorerie - cu nume reale și sub-breakdown
treasury_query = f"""
WITH luna_curenta AS
(SELECT anul, luna
FROM {schema}.calendar
WHERE anul * 12 + luna =
(SELECT MAX(c2.anul * 12 + c2.luna) FROM {schema}.calendar c2))
SELECT
vb.cont,
vb.nume as nume_real,
(vb.solddeb - vb.soldcred) as sold,
CASE
WHEN vb.cont IN ('5311', '5314', '5328') THEN 'casa'
WHEN vb.cont IN ('5121', '5124') THEN 'banca'
END as tip
FROM {schema}.vbalanta_parteneri vb, luna_curenta lc
WHERE vb.an = lc.anul
AND vb.luna = lc.luna
AND vb.cont IN ('5311', '5314', '5328', '5121', '5124')
AND (vb.solddeb - vb.soldcred) <> 0
ORDER BY tip, vb.cont
"""
cursor.execute(treasury_query)
treasury_rows = cursor.fetchall()
if not treasury_rows:
return {
"total": 0,
"breakdown": {
"casa": {"total": 0, "items": []},
"banca": {"total": 0, "items": []}
},
"currency": "RON"
}
# Procesare rezultate cu grupare pe tip
casa_items = []
banca_items = []
casa_total = 0
banca_total = 0
for row in treasury_rows:
cont = row[0]
nume_real = row[1] # Nume din vbalanta_parteneri.nume
sold = float(row[2] or 0)
tip = row[3]
item = {
"nume": nume_real or f"Cont {cont}", # Fallback la nume generic
"cont": cont,
"sold": sold
}
if tip == 'casa':
casa_items.append(item)
casa_total += sold
else: # banca
banca_items.append(item)
banca_total += sold
total = casa_total + banca_total
return {
"total": total,
"breakdown": {
"casa": {
"total": casa_total,
"items": casa_items
},
"banca": {
"total": banca_total,
"items": banca_items
}
},
"currency": "RON"
}
except Exception as e:
logger.error(f"Error in get_treasury_breakdown: {str(e)}")
raise
@staticmethod
async def get_net_balance_breakdown(company: int) -> Dict[str, Any]:
"""
Obține breakdown-ul balanței nete pe clienți și furnizori cu detaliere pe perioade
"""
try:
async with oracle_pool.get_connection() as connection:
with connection.cursor() as cursor:
# Obține schema
company_id = company
schema_query = "SELECT schema FROM CONTAFIN_ORACLE.v_nom_firme WHERE id_firma = :company_id"
cursor.execute(schema_query, {'company_id': company_id})
schema_result = cursor.fetchone()
if not schema_result:
raise ValueError(f"Schema nu a fost găsită pentru id_firma {company_id}")
schema = schema_result[0]
# Query extins pentru breakdown detaliat pe perioade
balance_query = f"""
WITH luna_curenta AS (
SELECT anul, luna
FROM {schema}.calendar
WHERE anul*12+luna = (SELECT MAX(c2.anul*12+c2.luna) FROM {schema}.calendar c2)
)
SELECT
-- CLIENȚI - Sold total
SUM(CASE
WHEN vp.cont IN ('4111','461')
THEN (vp.precdeb + vp.debit) - (vp.preccred + vp.credit)
WHEN vp.cont = '419'
THEN -((vp.preccred + vp.credit) - (vp.precdeb + vp.debit))
ELSE 0
END) as clienti_total,
-- CLIENȚI - În termen (total)
SUM(CASE
WHEN vp.cont IN ('4111','461')
AND NVL(vp.datascad, vp.dataact + 30) >= TRUNC(SYSDATE)
THEN (vp.precdeb + vp.debit) - (vp.preccred + vp.credit)
WHEN vp.cont = '419'
AND NVL(vp.datascad, vp.dataact + 30) >= TRUNC(SYSDATE)
THEN -((vp.preccred + vp.credit) - (vp.precdeb + vp.debit))
ELSE 0
END) as clienti_in_termen,
-- CLIENȚI - Restanți (total)
SUM(CASE
WHEN vp.cont IN ('4111','461')
AND NVL(vp.datascad, vp.dataact + 30) < TRUNC(SYSDATE)
THEN (vp.precdeb + vp.debit) - (vp.preccred + vp.credit)
WHEN vp.cont = '419'
AND NVL(vp.datascad, vp.dataact + 30) < TRUNC(SYSDATE)
THEN -((vp.preccred + vp.credit) - (vp.precdeb + vp.debit))
ELSE 0
END) as clienti_restanti,
-- CLIENȚI - Restanți pe perioade
SUM(CASE WHEN vp.cont IN ('4111','461') AND TRUNC(SYSDATE) - NVL(vp.datascad, vp.dataact + 30) BETWEEN 1 AND 7
THEN (vp.precdeb + vp.debit) - (vp.preccred + vp.credit) ELSE 0 END) as clienti_restant_7,
SUM(CASE WHEN vp.cont IN ('4111','461') AND TRUNC(SYSDATE) - NVL(vp.datascad, vp.dataact + 30) BETWEEN 8 AND 14
THEN (vp.precdeb + vp.debit) - (vp.preccred + vp.credit) ELSE 0 END) as clienti_restant_14,
SUM(CASE WHEN vp.cont IN ('4111','461') AND TRUNC(SYSDATE) - NVL(vp.datascad, vp.dataact + 30) BETWEEN 15 AND 30
THEN (vp.precdeb + vp.debit) - (vp.preccred + vp.credit) ELSE 0 END) as clienti_restant_30,
SUM(CASE WHEN vp.cont IN ('4111','461') AND TRUNC(SYSDATE) - NVL(vp.datascad, vp.dataact + 30) BETWEEN 31 AND 60
THEN (vp.precdeb + vp.debit) - (vp.preccred + vp.credit) ELSE 0 END) as clienti_restant_60,
SUM(CASE WHEN vp.cont IN ('4111','461') AND TRUNC(SYSDATE) - NVL(vp.datascad, vp.dataact + 30) BETWEEN 61 AND 90
THEN (vp.precdeb + vp.debit) - (vp.preccred + vp.credit) ELSE 0 END) as clienti_restant_90,
SUM(CASE WHEN vp.cont IN ('4111','461') AND TRUNC(SYSDATE) - NVL(vp.datascad, vp.dataact + 30) > 90
THEN (vp.precdeb + vp.debit) - (vp.preccred + vp.credit) ELSE 0 END) as clienti_restant_90plus,
-- FURNIZORI - Sold total
SUM(CASE
WHEN vp.cont IN ('401','404','462')
THEN (vp.preccred + vp.credit) - (vp.precdeb + vp.debit)
WHEN vp.cont IN ('409')
THEN -((vp.precdeb + vp.debit) - (vp.preccred + vp.credit))
ELSE 0
END) as furnizori_total,
-- FURNIZORI - În termen (total)
SUM(CASE
WHEN vp.cont IN ('401','404','462') AND NVL(vp.datascad, vp.dataact + 30) >= TRUNC(SYSDATE)
THEN (vp.preccred + vp.credit) - (vp.precdeb + vp.debit)
WHEN vp.cont IN ('409') AND NVL(vp.datascad, vp.dataact + 30) >= TRUNC(SYSDATE)
THEN -((vp.precdeb + vp.debit) - (vp.preccred + vp.credit))
ELSE 0
END) as furnizori_in_termen,
-- FURNIZORI - Restanți (total)
SUM(CASE
WHEN vp.cont IN ('401','404','462') AND NVL(vp.datascad, vp.dataact + 30) < TRUNC(SYSDATE)
THEN (vp.preccred + vp.credit) - (vp.precdeb + vp.debit)
WHEN vp.cont IN ('409') AND NVL(vp.datascad, vp.dataact + 30) < TRUNC(SYSDATE)
THEN -((vp.precdeb + vp.debit) - (vp.preccred + vp.credit))
ELSE 0
END) as furnizori_restanti,
-- FURNIZORI - Restanți pe perioade
SUM(CASE WHEN vp.cont IN ('401','404','462') AND TRUNC(SYSDATE) - NVL(vp.datascad, vp.dataact + 30) BETWEEN 1 AND 7
THEN (vp.preccred + vp.credit) - (vp.precdeb + vp.debit) ELSE 0 END) as furnizori_restant_7,
SUM(CASE WHEN vp.cont IN ('401','404','462') AND TRUNC(SYSDATE) - NVL(vp.datascad, vp.dataact + 30) BETWEEN 8 AND 14
THEN (vp.preccred + vp.credit) - (vp.precdeb + vp.debit) ELSE 0 END) as furnizori_restant_14,
SUM(CASE WHEN vp.cont IN ('401','404','462') AND TRUNC(SYSDATE) - NVL(vp.datascad, vp.dataact + 30) BETWEEN 15 AND 30
THEN (vp.preccred + vp.credit) - (vp.precdeb + vp.debit) ELSE 0 END) as furnizori_restant_30,
SUM(CASE WHEN vp.cont IN ('401','404','462') AND TRUNC(SYSDATE) - NVL(vp.datascad, vp.dataact + 30) BETWEEN 31 AND 60
THEN (vp.preccred + vp.credit) - (vp.precdeb + vp.debit) ELSE 0 END) as furnizori_restant_60,
SUM(CASE WHEN vp.cont IN ('401','404','462') AND TRUNC(SYSDATE) - NVL(vp.datascad, vp.dataact + 30) BETWEEN 61 AND 90
THEN (vp.preccred + vp.credit) - (vp.precdeb + vp.debit) ELSE 0 END) as furnizori_restant_90,
SUM(CASE WHEN vp.cont IN ('401','404','462') AND TRUNC(SYSDATE) - NVL(vp.datascad, vp.dataact + 30) > 90
THEN (vp.preccred + vp.credit) - (vp.precdeb + vp.debit) ELSE 0 END) as furnizori_restant_90plus
FROM {schema}.vireg_parteneri vp, luna_curenta lc
WHERE vp.an = lc.anul
AND vp.luna = lc.luna
AND vp.cont IN ('4111', '461', '419', '401', '404', '462','409')
"""
cursor.execute(balance_query)
row = cursor.fetchone()
if not row:
return {
"clienti_total": 0,
"furnizori_total": 0,
"breakdown": {
"clienti": {
"total": 0,
"in_termen": {"total": 0},
"restant": {"total": 0, "perioade": {}}
},
"furnizori": {
"total": 0,
"in_termen": {"total": 0},
"restant": {"total": 0, "perioade": {}}
}
},
"currency": "RON"
}
# Procesare rezultate - INDEXARE CORECTATĂ
# Coloane: 0-8 = clienti (9 coloane), 9-17 = furnizori (9 coloane)
return {
"clienti_total": float(row[0] or 0),
"furnizori_total": float(row[9] or 0), # CORECTAT: row[9] nu row[10]
"breakdown": {
"clienti": {
"total": float(row[0] or 0),
"in_termen": {
"total": float(row[1] or 0)
},
"restant": {
"total": float(row[2] or 0),
"perioade": {
"7_zile": float(row[3] or 0),
"14_zile": float(row[4] or 0),
"30_zile": float(row[5] or 0),
"60_zile": float(row[6] or 0),
"90_zile": float(row[7] or 0),
"peste_90_zile": float(row[8] or 0)
}
}
},
"furnizori": {
"total": float(row[9] or 0), # CORECTAT: row[9] nu row[10]
"in_termen": {
"total": float(row[10] or 0) # CORECTAT: row[10] nu row[11]
},
"restant": {
"total": float(row[11] or 0), # CORECTAT: row[11] nu row[12]
"perioade": {
"7_zile": float(row[12] or 0), # CORECTAT
"14_zile": float(row[13] or 0), # CORECTAT
"30_zile": float(row[14] or 0), # CORECTAT
"60_zile": float(row[15] or 0), # CORECTAT
"90_zile": float(row[16] or 0), # CORECTAT
"peste_90_zile": float(row[17] or 0) # CORECTAT
}
}
}
},
"currency": "RON"
}
except Exception as e:
logger.error(f"Error in get_net_balance_breakdown: {str(e)}")
raise
@staticmethod
async def get_current_period(company: int) -> Dict[str, Any]:
"""
Obține perioada curentă (an și lună) din calendarul Oracle
Args:
company: ID-ul companiei
Returns:
{
year: int - anul curent
month: int - luna curentă (1-12)
period: str - perioada în format YYYY-MM
}
"""
try:
async with oracle_pool.get_connection() as connection:
with connection.cursor() as cursor:
# Obține schema
company_id = company
schema_query = "SELECT schema FROM CONTAFIN_ORACLE.v_nom_firme WHERE id_firma = :company_id"
cursor.execute(schema_query, {'company_id': company_id})
schema_result = cursor.fetchone()
if not schema_result:
raise ValueError(f"Schema nu a fost găsită pentru id_firma {company_id}")
schema = schema_result[0]
# Query pentru perioada curentă din calendar Oracle
current_period_query = f"""
SELECT anul, luna, anul || '-' || LPAD(luna, 2, '0') as period
FROM {schema}.calendar
WHERE anul*12+luna = (SELECT MAX(anul*12+luna) FROM {schema}.calendar)
"""
cursor.execute(current_period_query)
period_row = cursor.fetchone()
if not period_row:
# Fallback la data curentă sistem
from datetime import datetime
now = datetime.now()
return {
"year": now.year,
"month": now.month,
"period": f"{now.year}-{now.month:02d}"
}
return {
"year": int(period_row[0]),
"month": int(period_row[1]),
"period": period_row[2]
}
except Exception as e:
logger.error(f"Error in get_current_period: {str(e)}")
raise