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