Files
vending_data_intelligence_r…/main.py
Marius Mutu bc05d02319 Add manager-friendly explanations and dynamic generators
main.py:
- Add PDF_EXPLANATIONS dict with Romanian explanations for all report sections
- Add dynamic explanation generators:
  - generate_indicatori_generali_explanation() - financial ratios with values
  - generate_indicatori_lichiditate_explanation() - liquidity ratios
  - generate_ciclu_cash_explanation() - cash conversion cycle
  - generate_solduri_clienti/furnizori_explanation() - balance summaries

report_generator.py:
- Add diacritics removal for PDF compatibility (Helvetica font)
- Add sanitize_for_pdf() helper function
- Add explanation_fill and explanation_border styles for info boxes
- Enhance add_consolidated_sheet() with explanation parameter
- Add merged explanation boxes with light grey background

These changes complement the FORMULA column additions, providing
managers with contextual explanations throughout the reports.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 16:20:30 +02:00

1131 lines
50 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Data Intelligence Report Generator
===================================
Generates comprehensive Excel and PDF reports with financial analytics
for ERP data stored in Oracle Database.
Usage:
python main.py [--months 12] [--output-dir ./output]
Author: Claude AI for ROMFAST SRL
"""
import sys
import argparse
from datetime import datetime
from pathlib import Path
import time
import warnings
warnings.filterwarnings('ignore')
# Check dependencies
def check_dependencies():
"""Check if all required packages are installed"""
missing = []
packages = {
'oracledb': 'oracledb',
'pandas': 'pandas',
'openpyxl': 'openpyxl',
'matplotlib': 'matplotlib',
'reportlab': 'reportlab',
'dotenv': 'python-dotenv'
}
for import_name, pip_name in packages.items():
try:
__import__(import_name)
except ImportError:
missing.append(pip_name)
if missing:
print("❌ Pachete lipsă. Rulează:")
print(f" pip install {' '.join(missing)} --break-system-packages")
sys.exit(1)
check_dependencies()
import oracledb
import pandas as pd
from dateutil.relativedelta import relativedelta
from config import (
ORACLE_CONFIG, get_dsn, OUTPUT_DIR, COMPANY_NAME,
ANALYSIS_MONTHS, MIN_SALES_FOR_ANALYSIS, LOW_MARGIN_THRESHOLD,
RECOMMENDATION_THRESHOLDS
)
from queries import QUERIES
from report_generator import (
ExcelReportGenerator, PDFReportGenerator,
create_monthly_chart, create_client_concentration_chart, create_production_chart,
create_cash_cycle_chart
)
from recommendations import RecommendationsEngine
# Manager-friendly explanations for PDF and Excel sections (Romanian without diacritics)
PDF_EXPLANATIONS = {
# KPIs and Dashboard
'kpis': "Indicatorii cheie (KPIs) ofera o imagine de ansamblu a performantei. Vanzarile totale arata volumul de business, marja bruta arata profitabilitatea (ideal peste 20%), iar TREND-ul compara cu anul trecut - CRESTERE e pozitiv, SCADERE necesita investigare.",
'recomandari': "Sistemul analizeaza automat datele si genereaza alerte prioritizate. ALERTA (rosu) = problema critica ce necesita actiune imediata; ATENTIE (galben) = risc de monitorizat; OK (verde) = situatie normala.",
# Charts
'evolutie_lunara': "Graficul arata tendinta vanzarilor pe ultimele 12 luni. Urmariti sezonalitatea (luni slabe/puternice) si tendinta generala. Daca volumul creste dar marja scade, exista presiune pe preturi.",
'concentrare_clienti': "Dependenta de clientii mari - regula 80/20: ideal max 60-70% din vanzari de la top 20% clienti. Daca un singur client depaseste 25% din vanzari, exista risc major - diversificati portofoliul.",
'ciclu_cash': "Ciclul de conversie cash (CCC) arata cate zile sunt blocati banii: DIO (zile stoc) + DSO (zile incasare) - DPO (zile plata furnizori). Sub 30 zile = excelent, 30-60 = acceptabil, peste 60 = problema de lichiditate.",
# Alerts and tables
'alerte_critice': "Vanzarile sub cost sunt tranzactii in pierdere - verificati preturile imediat. Clientii cu marja sub 15% consuma resurse fara profit adecvat - renegociati sau reduceti prioritatea.",
'top_clienti_produse': "Clasamentele arata cei mai importanti clienti si produse dupa vanzari si marja. Concentrati-va pe clientii cu marja mare si investigati pe cei cu marja mica (<15%).",
'aging_creante': "Analiza vechimii creantelor arata suma facturilor neincasate pe intervale de timp. Creantele peste 90 de zile reprezinta risc major de neincasare - contactati urgent acesti clienti.",
'stoc_lent': "Stocul lent blocheaza capital si ocupa spatiu. Produsele fara miscare peste 90 zile trebuie lichidate prin promotii sau trecute in pierdere daca sunt expirate/depasate.",
# Excel-specific sections
'venituri': "Vanzarile pe linii de business arata contributia fiecarui segment. Productia proprie are de obicei marja mai mare (25-40%), revanzarea are marja mai mica (10-20%) dar volum mai mare.",
'portofoliu_clienti': "Structura portofoliului de clienti pe segmente. Un portofoliu sanatos are diversificare pe mai multe segmente, fara dependenta excesiva de o singura categorie.",
'risc_concentrare': "Indicatorii de concentrare arata cat de dependenti sunteti de putini clienti. Top 5 clienti ideal sub 50% din vanzari, top 10 ideal sub 70%. Urmariti trendul YoY.",
'indicatori_generali': "Indicatori financiari structurali: grad indatorare (Datorii/Capital - ideal <1), autonomie financiara (Capital/Active - ideal >0.5), rata datoriilor (Datorii/Active - ideal <0.5), marja neta (Profit/Vanzari %), ROA (Profit/Active %), rotatia activelor.",
'indicatori_lichiditate': "Ratii de lichiditate: Lichiditate curenta (Active curente/Datorii curente - ideal >=1.5), Quick Ratio (fara stocuri - ideal >=1.0), Cash Ratio (Cash/Datorii - ideal >=0.2), Fond de rulment (Active curente - Datorii curente).",
'ciclu_conversie_cash': "Ciclul de conversie cash (CCC) = DIO + DSO - DPO. DIO = zile stoc (cat timp sta marfa pe stoc), DSO = zile incasare (cat timp asteptam banii de la clienti), DPO = zile plata (in cat timp platim furnizorii). CCC sub 30 zile = excelent, 30-60 = acceptabil, peste 60 = probleme de lichiditate.",
'solduri_clienti': "Solduri de incasat de la clienti - creante comerciale din cont 4111. Verificati vechimea si urgentati incasarile peste 30 zile pentru a imbunatati cash-flow-ul.",
'solduri_furnizori': "Datorii catre furnizori din cont 401. Prioritizati platile in functie de scadente si relatia comerciala pentru a evita penalizari si pastrarea relatiilor bune.",
'clasificare_datorii': "Datoriile pe intervale de intarziere. Datoriile sub 30 zile sunt normale, 30-60 zile necesita atentie, peste 60 zile afecteaza relatia cu furnizorii.",
'proiectie_lichiditate': "Previziunea de cash pe 30 zile bazata pe scadente. Sold negativ = risc de lipsă numerar - asigurati finantare suplimentara sau urgentati incasarile."
}
# =============================================================================
# DYNAMIC EXPLANATION GENERATORS - Create explanations with actual values
# =============================================================================
def generate_indicatori_generali_explanation(df):
"""Generate dynamic explanation with actual values and formulas for financial indicators."""
if df is None or df.empty:
return "Nu exista date pentru indicatorii financiari generali."
parts = ["INDICATORI FINANCIARI:"]
for _, row in df.iterrows():
ind = row.get('INDICATOR', '')
val = row.get('VALOARE', 0)
status = row.get('STATUS', '')
if val is None:
val = 0
if 'indatorare' in ind.lower():
parts.append(f"Grad indatorare = {val:.2f} (Datorii/Capital propriu) - {status}")
elif 'autonomie' in ind.lower():
parts.append(f"Autonomie financiara = {val:.2f} (Capital/Active) - {status}")
elif 'rata datoriilor' in ind.lower():
parts.append(f"Rata datoriilor = {val:.2f} (Datorii/Active) - {status}")
elif 'marja' in ind.lower():
parts.append(f"Marja neta = {val:.1f}% - {status}")
elif 'roa' in ind.lower():
parts.append(f"ROA = {val:.1f}% (Profit/Active) - {status}")
elif 'rotatia' in ind.lower():
parts.append(f"Rotatia activelor = {val:.2f} - {status}")
return " | ".join(parts) if len(parts) > 1 else parts[0]
def generate_indicatori_lichiditate_explanation(df):
"""Generate dynamic explanation for liquidity ratios with actual values."""
if df is None or df.empty:
return "Nu exista date pentru indicatorii de lichiditate."
parts = ["RATII LICHIDITATE:"]
for _, row in df.iterrows():
ind = row.get('INDICATOR', '')
val = row.get('VALOARE', 0)
status = row.get('STATUS', '')
if val is None:
val = 0
if 'curenta' in ind.lower() and 'lichiditate' in ind.lower():
parts.append(f"Lichiditate curenta = {val:.2f} (Active curente/Datorii curente, ideal >= 1.5) - {status}")
elif 'rapida' in ind.lower() or 'quick' in ind.lower():
parts.append(f"Quick Ratio = {val:.2f} (fara stocuri, ideal >= 1.0) - {status}")
elif 'cash' in ind.lower():
parts.append(f"Cash Ratio = {val:.2f} (Cash/Datorii, ideal >= 0.2) - {status}")
elif 'fond' in ind.lower() or 'rulment' in ind.lower():
parts.append(f"Fond de rulment = {val:,.0f} RON - {status}")
return " | ".join(parts) if len(parts) > 1 else parts[0]
def generate_ciclu_cash_explanation(df):
"""Generate dynamic explanation for cash conversion cycle with actual values."""
if df is None or df.empty:
return "Nu exista date pentru ciclul de conversie cash."
dio = dso = dpo = ccc = 0
for _, row in df.iterrows():
ind = str(row.get('INDICATOR', '')).upper()
zile = row.get('ZILE', 0)
if zile is None:
zile = 0
if 'DIO' in ind and 'DSO' not in ind:
dio = zile
elif 'DSO' in ind and 'DIO' not in ind:
dso = zile
elif 'DPO' in ind:
dpo = zile
elif 'CCC' in ind or 'CICLU' in ind:
ccc = zile
# Calculate CCC if not directly available
if ccc == 0 and (dio > 0 or dso > 0):
ccc = dio + dso - dpo
status = "EXCELENT" if ccc < 30 else "ACCEPTABIL" if ccc < 60 else "PROBLEME LICHIDITATE"
return (f"CICLU CONVERSIE CASH: DIO = {dio:.0f} zile (stoc -> vanzare) + "
f"DSO = {dso:.0f} zile (factura -> incasare) - "
f"DPO = {dpo:.0f} zile (achizitie -> plata) = "
f"CCC = {ccc:.0f} zile. STATUS: {status}")
def generate_solduri_clienti_explanation(df):
"""Generate dynamic explanation for customer balances with actual totals."""
if df is None or df.empty:
return "Nu exista solduri de incasat de la clienti."
total = df['SOLD_CURENT'].sum() if 'SOLD_CURENT' in df.columns else 0
count = len(df)
top1_pct = (df['SOLD_CURENT'].iloc[0] / total * 100) if total > 0 and len(df) > 0 else 0
return (f"CREANTE CLIENTI: Total de incasat = {total:,.0f} RON de la {count} clienti. "
f"Top 1 client = {top1_pct:.1f}% din total. "
f"Verificati vechimea creantelor si urgentati incasarile peste 30 zile.")
def generate_solduri_furnizori_explanation(df):
"""Generate dynamic explanation for supplier balances with actual totals."""
if df is None or df.empty:
return "Nu exista datorii catre furnizori."
total = df['SOLD_CURENT'].sum() if 'SOLD_CURENT' in df.columns else 0
count = len(df)
return (f"DATORII FURNIZORI: Total de platit = {total:,.0f} RON catre {count} furnizori. "
f"Verificati scadentele si prioritizati platile pentru a evita penalizari.")
class PerformanceLogger:
"""Tracks execution time for each operation to identify bottlenecks."""
def __init__(self):
self.timings = []
self.start_time = time.perf_counter()
self.phase_start = None
self.phase_name = None
def start(self, name: str):
"""Start timing a named operation."""
self.phase_name = name
self.phase_start = time.perf_counter()
print(f"⏱️ [{self._timestamp()}] START: {name}")
def stop(self, rows: int = None):
"""Stop timing and record duration."""
if self.phase_start is None:
return
duration = time.perf_counter() - self.phase_start
self.timings.append({
'name': self.phase_name,
'duration': duration,
'rows': rows
})
rows_info = f" ({rows} rows)" if rows else ""
print(f"✅ [{self._timestamp()}] DONE: {self.phase_name} - {duration:.2f}s{rows_info}")
self.phase_start = None
def _timestamp(self):
return datetime.now().strftime("%H:%M:%S")
def summary(self, output_path: str = None):
"""Print summary sorted by duration (slowest first)."""
total = time.perf_counter() - self.start_time
print("\n" + "="*70)
print("📊 PERFORMANCE SUMMARY (sorted by duration, slowest first)")
print("="*70)
sorted_timings = sorted(self.timings, key=lambda x: x['duration'], reverse=True)
lines = []
for t in sorted_timings:
pct = (t['duration'] / total) * 100 if total > 0 else 0
rows_info = f" [{t['rows']} rows]" if t['rows'] else ""
line = f"{t['duration']:8.2f}s ({pct:5.1f}%) - {t['name']}{rows_info}"
print(line)
lines.append(line)
print("-"*70)
print(f"TOTAL: {total:.2f}s ({total/60:.1f} minutes)")
# Save to file
if output_path:
log_file = f"{output_path}/performance_log.txt"
with open(log_file, 'w', encoding='utf-8') as f:
f.write(f"Performance Log - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write("="*70 + "\n\n")
for line in lines:
f.write(line + "\n")
f.write("\n" + "-"*70 + "\n")
f.write(f"TOTAL: {total:.2f}s ({total/60:.1f} minutes)\n")
print(f"\n📝 Log saved to: {log_file}")
class OracleConnection:
"""Context manager for Oracle database connection"""
def __init__(self):
self.connection = None
def __enter__(self):
try:
print(f"🔌 Conectare la Oracle: {get_dsn()}...")
# Add TCP keepalive and timeout settings to prevent connection drops
self.connection = oracledb.connect(
user=ORACLE_CONFIG['user'],
password=ORACLE_CONFIG['password'],
dsn=get_dsn(),
tcp_connect_timeout=30,
expire_time=5 # Send TCP keepalive every 5 minutes
)
# Set call timeout to 10 minutes for slow queries
self.connection.call_timeout = 600000 # milliseconds
print("✓ Conectat cu succes!")
return self.connection
except oracledb.Error as e:
print(f"❌ Eroare conexiune Oracle: {e}")
raise
def __exit__(self, exc_type, exc_val, exc_tb):
if self.connection:
self.connection.close()
print("✓ Conexiune închisă.")
def execute_query(connection, query_name: str, query_info: dict) -> pd.DataFrame:
"""Execute a query and return results as DataFrame"""
try:
sql = query_info['sql']
params = query_info.get('params', {})
print(f" 📊 Executare: {query_name}...", end=" ")
with connection.cursor() as cursor:
cursor.execute(sql, params)
columns = [col[0] for col in cursor.description]
rows = cursor.fetchall()
df = pd.DataFrame(rows, columns=columns)
print(f"✓ ({len(df)} rânduri)")
return df
except oracledb.Error as e:
print(f"❌ Eroare: {e}")
return pd.DataFrame()
def generate_reports(args):
"""Main function to generate all reports"""
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
excel_path = OUTPUT_DIR / f"data_intelligence_report_{timestamp}.xlsx"
pdf_path = OUTPUT_DIR / f"data_intelligence_report_{timestamp}.pdf"
print("\n" + "="*60)
print(" DATA INTELLIGENCE REPORT GENERATOR")
print("="*60)
print(f" Perioada: Ultimele {args.months} luni")
print(f" Output: {OUTPUT_DIR}")
print("="*60 + "\n")
# Update parameters with command line arguments
for query_info in QUERIES.values():
if 'months' in query_info.get('params', {}):
query_info['params']['months'] = args.months
# Calculate reporting period string
end_date = datetime.now()
start_date = end_date - relativedelta(months=args.months)
period_str = f"Perioada: {start_date.strftime('%d.%m.%Y')} - {end_date.strftime('%d.%m.%Y')}"
# Add period to descriptions for queries with months parameter
for query_name, query_info in QUERIES.items():
if 'months' in query_info.get('params', {}):
original_desc = query_info.get('description', '')
query_info['description'] = f"{original_desc}\n{period_str}"
# Connect and execute queries
results = {}
perf = PerformanceLogger() # Initialize performance logger
with OracleConnection() as conn:
print("\n📥 Extragere date din Oracle:\n")
for query_name, query_info in QUERIES.items():
perf.start(f"QUERY: {query_name}")
df = execute_query(conn, query_name, query_info)
results[query_name] = df
perf.stop(rows=len(df) if df is not None and not df.empty else 0)
# Generate Excel Report
print("\n📝 Generare raport Excel...")
excel_gen = ExcelReportGenerator(excel_path)
# Generate recommendations based on all data
perf.start("RECOMMENDATIONS: analyze_all")
recommendations_engine = RecommendationsEngine(RECOMMENDATION_THRESHOLDS)
recommendations_df = recommendations_engine.analyze_all(results)
results['recomandari'] = recommendations_df
perf.stop(rows=len(recommendations_df))
print(f"{len(recommendations_df)} recomandări generate")
# =========================================================================
# CONSOLIDARE DATE PENTRU VEDERE DE ANSAMBLU
# =========================================================================
print("\n📊 Consolidare date pentru vedere de ansamblu...")
# --- Consolidare 1: Vedere Executivă (KPIs + YoY) ---
perf.start("CONSOLIDATION: kpi_consolidated")
# Folosim direct sumar_executiv_yoy care are deja toate coloanele necesare:
# INDICATOR, VALOARE_CURENTA, VALOARE_ANTERIOARA, VARIATIE_PROCENT, TREND
if 'sumar_executiv_yoy' in results and not results['sumar_executiv_yoy'].empty:
df_kpi = results['sumar_executiv_yoy'].copy()
# Adaugă coloana UM bazată pe tipul indicatorului
df_kpi['UM'] = df_kpi['INDICATOR'].apply(lambda x:
'%' if '%' in x or 'marja' in x.lower() else
'buc' if 'numar' in x.lower() else 'RON'
)
results['kpi_consolidated'] = df_kpi
else:
# Fallback la sumar_executiv simplu (fără YoY)
results['kpi_consolidated'] = results.get('sumar_executiv', pd.DataFrame())
perf.stop()
# --- Consolidare 2: Indicatori Venituri (Current + YoY) ---
perf.start("CONSOLIDATION: venituri_consolidated")
if 'indicatori_agregati_venituri' in results and 'indicatori_agregati_venituri_yoy' in results:
df_venituri = results['indicatori_agregati_venituri'].copy()
df_venituri_yoy = results['indicatori_agregati_venituri_yoy'].copy()
if not df_venituri.empty and not df_venituri_yoy.empty:
# Merge pe LINIE_BUSINESS
df_venituri_yoy = df_venituri_yoy.rename(columns={
'VANZARI': 'VANZARI_ANTERIOARE',
'MARJA': 'MARJA_ANTERIOARA'
})
df_venituri_combined = pd.merge(
df_venituri,
df_venituri_yoy[['LINIE_BUSINESS', 'VANZARI_ANTERIOARE', 'VARIATIE_PROCENT', 'TREND']],
on='LINIE_BUSINESS',
how='left'
)
df_venituri_combined = df_venituri_combined.rename(columns={'VANZARI': 'VANZARI_CURENTE'})
results['venituri_consolidated'] = df_venituri_combined
else:
results['venituri_consolidated'] = df_venituri
else:
results['venituri_consolidated'] = results.get('indicatori_agregati_venituri', pd.DataFrame())
perf.stop()
# --- Consolidare 3: Clienți și Risc (Portofoliu + Concentrare + YoY) ---
perf.start("CONSOLIDATION: risc_consolidated")
if 'concentrare_risc' in results and 'concentrare_risc_yoy' in results:
df_risc = results['concentrare_risc'].copy()
df_risc_yoy = results['concentrare_risc_yoy'].copy()
if not df_risc.empty and not df_risc_yoy.empty:
# Merge pe INDICATOR
df_risc = df_risc.rename(columns={'PROCENT': 'PROCENT_CURENT'})
df_risc_combined = pd.merge(
df_risc,
df_risc_yoy[['INDICATOR', 'PROCENT_ANTERIOR', 'VARIATIE', 'TREND']],
on='INDICATOR',
how='left'
)
results['risc_consolidated'] = df_risc_combined
else:
results['risc_consolidated'] = df_risc
else:
results['risc_consolidated'] = results.get('concentrare_risc', pd.DataFrame())
perf.stop()
print("✓ Consolidări finalizate")
# Add sheets in logical order - CONSOLIDAT primul, apoi detalii
sheet_order = [
# CONSOLIDAT - Vedere de Ansamblu (înlocuiește sheet-urile individuale)
'vedere_ansamblu', # KPIs + YoY + Recomandări
'indicatori_venituri', # Venituri Current + YoY merged
'clienti_risc', # Portofoliu + Concentrare + YoY
'tablou_financiar', # 5 secțiuni financiare
# DETALII - Sheet-uri individuale pentru analiză profundă
'sezonalitate_lunara',
# ALERTE
'vanzari_sub_cost',
'clienti_marja_mica',
# CICLU CASH
'ciclu_conversie_cash',
# ANALIZA CLIENTI
'marja_per_client',
'clienti_ranking_profit',
'frecventa_clienti',
'concentrare_clienti',
'trending_clienti',
'marja_client_categorie',
# PRODUSE
'top_produse',
'marja_per_categorie',
'marja_per_gestiune',
'articole_negestionabile',
'productie_vs_revanzare',
# PRETURI
'dispersie_preturi',
'clienti_sub_medie',
'evolutie_discount',
# FINANCIAR
'dso_dpo',
'dso_dpo_yoy',
'solduri_clienti',
'aging_creante',
'facturi_restante',
'solduri_furnizori',
'aging_datorii',
'facturi_restante_furnizori',
'pozitia_cash',
# ISTORIC
'vanzari_lunare',
# STOC
'stoc_curent',
'stoc_lent',
'rotatie_stocuri',
# PRODUCTIE
'analiza_prajitorie',
]
# Legends for each sheet explaining column calculations
legends = {
'recomandari': {
'CATEGORIE': 'Domeniu: Marja, Clienti, Stoc, Financiar',
'STATUS': 'OK = bine, ATENTIE = necesita atentie, ALERTA = actiune urgenta',
'VEZI_DETALII': 'Sheet-ul cu date detaliate pentru acest indicator'
},
'marja_per_client': {
'VANZARI_FARA_TVA': 'SUM(cantitate × preț) din fact_vfacturi_detalii',
'COST_TOTAL': 'SUM(cantitate × pret_achizitie)',
'MARJA_BRUTA': 'Vânzări - Cost = SUM(cantitate × (preț - pret_achizitie))',
'PROCENT_MARJA': 'Marja Brută / Vânzări × 100'
},
'clienti_marja_mica': {
'VANZARI_FARA_TVA': 'SUM(cantitate × preț) pentru client',
'MARJA_BRUTA': 'Vânzări - Cost',
'PROCENT_MARJA': 'Marja / Vânzări × 100 (sub 15% = alertă)'
},
'vanzari_sub_cost': {
'PRET_VANZARE': 'Preț unitar din factură (fact_vfacturi_detalii.pret)',
'COST': 'Preț achiziție (pret_achizitie)',
'PIERDERE': '(Preț vânzare - Cost) × Cantitate (negativ = pierdere)'
},
'stoc_curent': {
'TIP_GESTIUNE': 'Preț vânzare (nr_pag=7) sau Preț achiziție',
'VALOARE_STOC_ACHIZITIE': '(cants+cant-cante) × pret din vstoc',
'VALOARE_STOC_VANZARE': 'Doar pentru gestiuni preț vânzare, altfel gol'
},
'stoc_lent': {
'CANTITATE': 'Stoc final = cants + cant - cante',
'VALOARE': 'Cantitate × preț achiziție',
'ZILE_FARA_MISCARE': 'Zile de la ultima ieșire (dataout) sau intrare'
},
'rotatie_stocuri': {
'VALOARE_STOC': 'Stoc curent (cants+cant-cante) × preț achiziție',
'VANZARI_12_LUNI': 'Doar vânzări (nu transferuri/consumuri) din ultimele 12 luni',
'ROTATIE': 'Vânzări / Stoc (de câte ori s-a rotit stocul)',
'ZILE_STOC': 'La ritmul actual, în câte zile se epuizează'
},
'dispersie_preturi': {
'NR_TRANZACTII': 'Număr total linii factură pentru acest produs',
'VARIATIE_PROCENT': '(Preț Max - Preț Min) / Preț Mediu × 100',
'NR_LA_PRET_MIN': 'Câte tranzacții au fost la prețul minim',
'CLIENT_PRET_MIN': 'Primul client care a cumpărat la preț minim'
},
'top_produse': {
'VALOARE_VANZARI': 'SUM(cantitate × preț)',
'MARJA_BRUTA': 'SUM(cantitate × (preț - pret_achizitie))',
'PROCENT_MARJA': 'Marja / Vânzări × 100'
},
'marja_per_categorie': {
'VANZARI_FARA_TVA': 'Total vânzări pe subgrupă',
'COST_TOTAL': 'Total cost achiziție pe subgrupă',
'PROCENT_MARJA': 'Marja / Vânzări × 100'
},
'marja_per_gestiune': {
'VANZARI_FARA_TVA': 'Total vânzări pe gestiune (doar articole gestionabile)',
'MARJA_BRUTA': 'Total marjă pe gestiune',
'PROCENT_MARJA': 'Marja / Vânzări × 100'
},
'articole_negestionabile': {
'DENUMIRE': 'Nume articol negestionabil (in_stoc=0)',
'VANZARI_FARA_TVA': 'Total vânzări pentru articole care nu se țin pe stoc',
'MARJA_BRUTA': 'Vânzări - Cost',
'PROCENT_MARJA': 'Marja / Vânzări × 100'
},
'vanzari_lunare': {
'VANZARI_FARA_TVA': 'Total vânzări în lună',
'MARJA_BRUTA': 'Total marjă în lună',
'NR_FACTURI': 'Număr facturi emise',
'NR_CLIENTI': 'Clienți unici activi'
},
# NEW legends for financial and aggregated sheets
'indicatori_agregati_venituri': {
'LINIE_BUSINESS': 'Producție proprie / Materii prime / Marfă revândută',
'PROCENT_VENITURI': 'Contribuția la totalul vânzărilor',
'CONTRIBUTIE_PROFIT': 'Contribuția la profitul total (%)'
},
'sezonalitate_lunara': {
'MEDIE_VANZARI': 'Media vânzărilor pe 24 luni pentru această lună',
'DEVIERE_PROCENT': 'Cât de mult deviază de la media globală',
'CLASIFICARE': 'LUNĂ PUTERNICĂ / LUNĂ SLABĂ / NORMAL'
},
'portofoliu_clienti': {
'VALOARE': 'Numărul de clienți în fiecare categorie',
'EXPLICATIE': 'Definiția categoriei de clienți'
},
'concentrare_risc': {
'PROCENT': 'Procentul din vânzări pentru Top N clienți',
'STATUS': 'OK / ATENTIE / RISC MARE'
},
'ciclu_conversie_cash': {
'INDICATOR': 'DIO (zile stoc) + DSO (zile încasare) - DPO (zile plată)',
'ZILE': 'Numărul de zile pentru fiecare component',
'EXPLICATIE': 'Ce reprezintă fiecare indicator'
},
'clienti_ranking_profit': {
'RANG_PROFIT': 'Poziția clientului după profit (nu vânzări)',
'RANG_VANZARI': 'Poziția clientului după vânzări',
'PROFIT_BRUT': 'Vânzări - Cost = profitul efectiv adus'
},
'frecventa_clienti': {
'COMENZI_PE_LUNA': 'Media comenzilor pe lună',
'VALOARE_MEDIE_COMANDA': 'Valoarea medie per comandă',
'EVOLUTIE_FRECVENTA_YOY': 'Schimbarea frecvenței față de anul trecut'
},
'marja_client_categorie': {
'STATUS_MARJA': 'OK / MARJĂ MICĂ (<15%) / PIERDERE (negativă)',
'CATEGORIA': 'Grupa de produse',
'PROCENT_MARJA': 'Marja pentru acest client la această categorie'
},
'evolutie_discount': {
'PRET_INITIAL': 'Prețul mediu în primele 6 luni',
'PRET_ACTUAL': 'Prețul mediu în ultimele 6 luni',
'VARIATIE_PRET_PROCENT': 'Scăderea/creșterea prețului (negativ = discount)'
},
'dso_dpo': {
'DSO': 'Days Sales Outstanding - zile medii încasare clienți',
'DPO': 'Days Payables Outstanding - zile medii plată furnizori',
'STATUS': 'OK / ATENTIE / ALERTA'
},
'solduri_clienti': {
'SOLD_CURENT': 'Suma de încasat de la client (din cont 4111)',
'TIP_SOLD': 'Creanță (ne datorează) sau Avans client (am încasat în avans)'
},
'aging_creante': {
'NEAJUNS_SCADENTA': 'Facturi nescadente încă',
'ZILE_1_30': 'Restanțe 1-30 zile',
'PESTE_90_ZILE': 'Restanțe critice >90 zile - risc de neîncasare'
},
'facturi_restante': {
'ZILE_INTARZIERE': 'Zile de la scadență',
'SUMA_RESTANTA': 'Valoarea rămasă de încasat'
},
'aging_datorii': {
'NEAJUNS_SCADENTA': 'Datorii neajunse la scadență',
'ZILE_1_30': 'Restanțe 1-30 zile',
'ZILE_31_60': 'Restanțe 31-60 zile',
'ZILE_61_90': 'Restanțe 61-90 zile',
'PESTE_90_ZILE': 'Restanțe critice >90 zile',
'TOTAL_SOLD': 'Total datorii către furnizor'
},
'facturi_restante_furnizori': {
'ZILE_INTARZIERE': 'Zile de la scadență',
'SUMA_RESTANTA': 'Valoarea rămasă de plătit'
},
'solduri_furnizori': {
'SOLD_CURENT': 'Suma de plătit furnizorului (din cont 401)',
'TIP_SOLD': 'Datorie (trebuie să plătim) sau Avans (am plătit în avans)'
},
'pozitia_cash': {
'SOLD_CURENT': 'Disponibilul curent în cont/casă',
'DESCRIERE': 'Tipul contului (bancă/casă, lei/valută)'
},
# =====================================================================
# NEW: Legends for Indicatori Generali, Lichiditate, YoY sheets
# =====================================================================
'indicatori_generali': {
'INDICATOR': 'Grad îndatorare, autonomie financiară, ROA, marjă netă',
'VALOARE': 'Valoarea calculată a indicatorului',
'STATUS': 'OK / ATENȚIE / ALERTĂ bazat pe praguri standard',
'RECOMANDARE': 'Acțiune sugerată pentru îmbunătățire'
},
'indicatori_lichiditate': {
'INDICATOR': 'Lichiditate curentă, rapidă, cash ratio, fond de rulment',
'VALOARE': 'Valoarea calculată (rată sau sumă RON)',
'STATUS': 'OK / ATENȚIE / ALERTĂ',
'INTERPRETARE': 'Ce înseamnă valoarea pentru business'
},
'clasificare_datorii': {
'CATEGORIE': 'Termen scurt (<30z) / mediu (31-90z) / lung (>90z)',
'VALOARE': 'Suma datoriilor în categoria respectivă',
'NR_FACTURI': 'Numărul facturilor în acea categorie'
},
'grad_acoperire_datorii': {
'VALOARE': 'Cash disponibil + încasări așteptate vs plăți scadente',
'ACOPERIRE': 'OK / ATENȚIE / DEFICIT - dacă puteți plăti datoriile',
'EXPLICATIE': 'Ce înseamnă pentru fluxul de numerar'
},
'proiectie_lichiditate': {
'PERIOADA': 'Azi / 30 zile / 60 zile / 90 zile',
'SOLD_PROIECTAT': 'Cash estimat la sfârșitul perioadei',
'FLUX_NET': 'Încasări - Plăți pentru perioada respectivă',
'STATUS': 'OK dacă sold pozitiv, ALERTĂ dacă negativ'
},
'sumar_executiv_yoy': {
'VALOARE_CURENTA': 'Valoarea din ultimele 12 luni',
'VALOARE_ANTERIOARA': 'Valoarea din anul anterior (12-24 luni)',
'VARIATIE_PROCENT': 'Creștere/scădere procentuală',
'TREND': 'CREȘTERE / SCĂDERE / STABIL'
},
'dso_dpo_yoy': {
'VALOARE_CURENTA': 'Zile încasare/plată actuale',
'VALOARE_ANTERIOARA': 'Zile în perioada anterioară',
'VARIATIE_ZILE': 'Diferența în zile (+ = mai rău pentru DSO, mai bine pentru DPO)',
'TREND': 'ÎMBUNĂTĂȚIRE / DETERIORARE / STABIL'
},
'concentrare_risc_yoy': {
'PROCENT_CURENT': '% vânzări la Top N clienți - an curent',
'PROCENT_ANTERIOR': '% vânzări la Top N clienți - an trecut',
'VARIATIE': 'Schimbarea în puncte procentuale',
'TREND': 'DIVERSIFICARE (bine) / CONCENTRARE (risc) / STABIL'
},
'indicatori_agregati_venituri_yoy': {
'LINIE_BUSINESS': 'Producție proprie / Materii prime / Marfă',
'VANZARI_CURENTE': 'Vânzări în ultimele 12 luni',
'VANZARI_ANTERIOARE': 'Vânzări în perioada anterioară',
'VARIATIE_PROCENT': 'Creștere/scădere procentuală',
'TREND': 'CREȘTERE / SCĂDERE / STABIL'
},
'analiza_prajitorie': {
'CANTITATE_INTRARI': 'Cantitate intrata (cant > 0, cante = 0)',
'VALOARE_INTRARI': 'Valoare intrari = cantitate x pret',
'CANTITATE_IESIRI': 'Cantitate iesita (cant = 0, cante > 0)',
'VALOARE_IESIRI': 'Valoare iesiri = cantitate x pret',
'CANTITATE_TRANSFORMARI_IN': 'Cantitate intrata in transformari',
'CANTITATE_TRANSFORMARI_OUT': 'Cantitate iesita din transformari',
'SOLD_NET_CANTITATE': 'Sold net = Total intrari - Total iesiri',
'SOLD_NET_VALOARE': 'Valoare neta a soldului'
},
# =====================================================================
# LEGENDS FOR CONSOLIDATED SHEETS
# =====================================================================
'vedere_ansamblu': {
'INDICATOR': 'Denumirea indicatorului de business',
'VALOARE_CURENTA': 'Valoare în perioada curentă (ultimele 12 luni)',
'UM': 'Unitate de măsură',
'VALOARE_ANTERIOARA': 'Valoare în perioada anterioară (12-24 luni)',
'VARIATIE_PROCENT': 'Variație procentuală YoY',
'TREND': 'CREȘTERE/SCĂDERE/STABIL',
'STATUS': 'OK = bine, ATENȚIE = necesită atenție, ALERTĂ = acțiune urgentă',
'CATEGORIE': 'Domeniu: Marja, Clienți, Stoc, Financiar',
'RECOMANDARE': 'Acțiune sugerată'
},
'indicatori_venituri': {
'LINIE_BUSINESS': 'Producție proprie / Materii prime / Marfă revândută',
'VANZARI_CURENTE': 'Vânzări în ultimele 12 luni',
'PROCENT_VENITURI': 'Contribuția la totalul vânzărilor (%)',
'MARJA': 'Marja brută pe linia de business',
'PROCENT_MARJA': 'Marja procentuală',
'VANZARI_ANTERIOARE': 'Vânzări în perioada anterioară',
'VARIATIE_PROCENT': 'Creștere/scădere procentuală YoY',
'TREND': 'CREȘTERE / SCĂDERE / STABIL'
},
'clienti_risc': {
'CATEGORIE': 'Tipul de categorie clienți',
'VALOARE': 'Numărul de clienți sau valoarea',
'EXPLICATIE': 'Explicația categoriei',
'INDICATOR': 'Top 1/5/10 clienți',
'PROCENT_CURENT': '% vânzări la Top N clienți - an curent',
'PROCENT_ANTERIOR': '% vânzări la Top N clienți - an trecut',
'VARIATIE': 'Schimbarea în puncte procentuale',
'TREND': 'DIVERSIFICARE (bine) / CONCENTRARE (risc) / STABIL',
'STATUS': 'OK / ATENTIE / RISC MARE'
},
'tablou_financiar': {
'INDICATOR': 'Denumirea indicatorului financiar',
'VALOARE': 'Valoarea calculată',
'STATUS': 'OK / ATENȚIE / ALERTĂ',
'RECOMANDARE': 'Acțiune sugerată pentru îmbunătățire',
'INTERPRETARE': 'Ce înseamnă valoarea pentru business'
}
}
# =========================================================================
# GENERARE SHEET-URI CONSOLIDATE EXCEL
# =========================================================================
# --- Sheet 0: DASHBOARD COMPLET (toate secțiunile într-o singură vedere) ---
perf.start("EXCEL: Dashboard Complet sheet (12 sections)")
excel_gen.add_consolidated_sheet(
name='Dashboard Complet',
sheet_title='Dashboard Executiv - Vedere Completă',
sheet_description='Toate indicatorii cheie consolidați într-o singură vedere rapidă',
sections=[
# KPIs și Recomandări
{
'title': 'KPIs cu Comparație YoY',
'df': results.get('kpi_consolidated', pd.DataFrame()),
'description': 'Indicatori cheie de performanță - curent vs anterior',
'explanation': PDF_EXPLANATIONS['kpis']
},
{
'title': 'Recomandări Prioritare',
'df': results.get('recomandari', pd.DataFrame()).head(10),
'description': 'Top 10 acțiuni sugerate bazate pe analiză',
'explanation': PDF_EXPLANATIONS['recomandari']
},
# Venituri
{
'title': 'Venituri per Linie Business',
'df': results.get('venituri_consolidated', pd.DataFrame()),
'description': 'Producție proprie, Materii prime, Marfă revândută',
'explanation': PDF_EXPLANATIONS['venituri']
},
# Clienți și Risc
{
'title': 'Portofoliu Clienți',
'df': results.get('portofoliu_clienti', pd.DataFrame()),
'description': 'Structura și segmentarea clienților',
'explanation': PDF_EXPLANATIONS['portofoliu_clienti']
},
{
'title': 'Concentrare Risc YoY',
'df': results.get('risc_consolidated', pd.DataFrame()),
'description': 'Dependența de clienții mari - curent vs anterior',
'explanation': PDF_EXPLANATIONS['risc_concentrare']
},
# Tablou Financiar - with DYNAMIC explanations
{
'title': 'Indicatori Generali',
'df': results.get('indicatori_generali', pd.DataFrame()),
'description': 'Ratii financiare: indatorare, autonomie, datorii, rentabilitate',
'explanation': generate_indicatori_generali_explanation(results.get('indicatori_generali'))
},
{
'title': 'Indicatori Lichiditate',
'df': results.get('indicatori_lichiditate', pd.DataFrame()),
'description': 'Capacitatea de plata pe termen scurt',
'explanation': generate_indicatori_lichiditate_explanation(results.get('indicatori_lichiditate'))
},
# NEW: Ciclu Conversie Cash (was missing from Dashboard)
{
'title': 'Ciclu Conversie Cash',
'df': results.get('ciclu_conversie_cash', pd.DataFrame()),
'description': 'DIO (zile stoc) + DSO (zile incasare) - DPO (zile plata) = CCC',
'explanation': generate_ciclu_cash_explanation(results.get('ciclu_conversie_cash'))
},
# NEW: Solduri Clienti (Top 10 - was missing from Dashboard)
{
'title': 'Solduri Clienti (Top 10)',
'df': results.get('solduri_clienti', pd.DataFrame()).head(10) if results.get('solduri_clienti') is not None and not results.get('solduri_clienti', pd.DataFrame()).empty else pd.DataFrame(),
'description': 'Creante de incasat din cont 4111',
'explanation': generate_solduri_clienti_explanation(results.get('solduri_clienti'))
},
# NEW: Solduri Furnizori (Top 10 - was missing from Dashboard)
{
'title': 'Solduri Furnizori (Top 10)',
'df': results.get('solduri_furnizori', pd.DataFrame()).head(10) if results.get('solduri_furnizori') is not None and not results.get('solduri_furnizori', pd.DataFrame()).empty else pd.DataFrame(),
'description': 'Datorii de platit din cont 401',
'explanation': generate_solduri_furnizori_explanation(results.get('solduri_furnizori'))
},
{
'title': 'Clasificare Datorii',
'df': results.get('clasificare_datorii', pd.DataFrame()),
'description': 'Datorii pe intervale de întârziere',
'explanation': PDF_EXPLANATIONS['clasificare_datorii']
},
{
'title': 'Proiecție Lichiditate',
'df': results.get('proiectie_lichiditate', pd.DataFrame()),
'description': 'Previziune încasări și plăți pe 30 zile',
'explanation': PDF_EXPLANATIONS['proiectie_lichiditate']
}
]
)
perf.stop()
# NOTE: Sheet-urile individuale (Vedere Ansamblu, Indicatori Venituri, Clienti si Risc,
# Tablou Financiar) au fost eliminate - toate datele sunt acum în Dashboard Complet
# --- Adaugă restul sheet-urilor de detaliu ---
# Skip sheet-urile care sunt acum în view-urile consolidate
consolidated_sheets = {
'vedere_ansamblu', 'indicatori_venituri', 'clienti_risc', 'tablou_financiar',
# Sheet-uri incluse în consolidări (nu mai sunt separate):
'sumar_executiv', 'sumar_executiv_yoy', 'recomandari',
'indicatori_agregati_venituri', 'indicatori_agregati_venituri_yoy',
'portofoliu_clienti', 'concentrare_risc', 'concentrare_risc_yoy',
'indicatori_generali', 'indicatori_lichiditate', 'clasificare_datorii',
'grad_acoperire_datorii', 'proiectie_lichiditate'
}
for query_name in sheet_order:
# Skip consolidated sheets and their source sheets
if query_name in consolidated_sheets:
continue
if query_name in results and query_name in QUERIES:
query_info = QUERIES[query_name]
# Create short sheet name from query name
sheet_name = query_name.replace('_', ' ').title()[:31]
perf.start(f"EXCEL: {query_name} detail sheet")
excel_gen.add_sheet(
name=sheet_name,
df=results[query_name],
title=query_info.get('title', query_name),
description=query_info.get('description', ''),
legend=legends.get(query_name)
)
df_rows = len(results[query_name]) if results[query_name] is not None else 0
perf.stop(rows=df_rows)
perf.start("EXCEL: Save workbook")
excel_gen.save()
perf.stop()
# =========================================================================
# GENERARE PDF - PAGINI CONSOLIDATE
# =========================================================================
print("\n📄 Generare raport PDF...")
pdf_gen = PDFReportGenerator(pdf_path, company_name=COMPANY_NAME)
# Pagina 1: Titlu
perf.start("PDF: Title page")
pdf_gen.add_title_page()
perf.stop()
# Pagina 2-3: DASHBOARD COMPLET (toate secțiunile într-o vedere unificată)
perf.start("PDF: Dashboard Complet page (4 sections)")
pdf_gen.add_explanation(PDF_EXPLANATIONS['kpis'])
pdf_gen.add_consolidated_page(
'Dashboard Complet',
sections=[
{
'title': 'KPIs cu Comparație YoY',
'df': results.get('kpi_consolidated', pd.DataFrame()),
'columns': ['INDICATOR', 'VALOARE_CURENTA', 'UM', 'VALOARE_ANTERIOARA', 'VARIATIE_PROCENT', 'TREND'],
'max_rows': 6
},
{
'title': 'Recomandări Prioritare',
'df': results.get('recomandari', pd.DataFrame()),
'columns': ['STATUS', 'CATEGORIE', 'INDICATOR', 'RECOMANDARE'],
'max_rows': 5
},
{
'title': 'Venituri per Linie Business',
'df': results.get('venituri_consolidated', pd.DataFrame()),
'columns': ['LINIE_BUSINESS', 'VANZARI_CURENTE', 'PROCENT_VENITURI', 'VARIATIE_PROCENT', 'TREND'],
'max_rows': 5
},
{
'title': 'Concentrare Risc YoY',
'df': results.get('risc_consolidated', pd.DataFrame()),
'columns': ['INDICATOR', 'PROCENT_CURENT', 'PROCENT_ANTERIOR', 'TREND'],
'max_rows': 4
}
]
)
perf.stop()
# NOTE: Paginile individuale (Vedere Executivă, Indicatori Venituri, Clienți și Risc,
# Tablou Financiar) au fost eliminate - toate datele sunt acum în Dashboard Complet
pdf_gen.add_page_break()
# Alerte (vânzări sub cost, clienți marjă mică)
perf.start("PDF: Alerts section")
pdf_gen.add_explanation(PDF_EXPLANATIONS['alerte_critice'])
pdf_gen.add_alerts_section({
'vanzari_sub_cost': results.get('vanzari_sub_cost', pd.DataFrame()),
'clienti_marja_mica': results.get('clienti_marja_mica', pd.DataFrame())
})
perf.stop()
pdf_gen.add_page_break()
# =========================================================================
# PAGINI DE GRAFICE ȘI DETALII
# =========================================================================
# Grafic: Evoluția Vânzărilor Lunare
if 'vanzari_lunare' in results and not results['vanzari_lunare'].empty:
perf.start("PDF: Chart - vanzari_lunare")
pdf_gen.add_explanation(PDF_EXPLANATIONS['evolutie_lunara'])
fig = create_monthly_chart(results['vanzari_lunare'])
pdf_gen.add_chart_image(fig, "Evoluția Vânzărilor și Marjei")
perf.stop()
# Grafic: Concentrare Clienți
if 'concentrare_clienti' in results and not results['concentrare_clienti'].empty:
perf.start("PDF: Chart - concentrare_clienti")
pdf_gen.add_explanation(PDF_EXPLANATIONS['concentrare_clienti'])
fig = create_client_concentration_chart(results['concentrare_clienti'])
pdf_gen.add_chart_image(fig, "Concentrare Clienți")
perf.stop()
pdf_gen.add_page_break()
# Grafic: Ciclu Conversie Cash - with DYNAMIC explanation showing actual values
if 'ciclu_conversie_cash' in results and not results['ciclu_conversie_cash'].empty:
perf.start("PDF: Chart - ciclu_conversie_cash")
pdf_gen.add_explanation(generate_ciclu_cash_explanation(results.get('ciclu_conversie_cash')))
fig = create_cash_cycle_chart(results['ciclu_conversie_cash'])
pdf_gen.add_chart_image(fig, "Ciclu Conversie Cash (DIO + DSO - DPO)")
perf.stop()
# Grafic: Producție vs Revânzare
if 'productie_vs_revanzare' in results and not results['productie_vs_revanzare'].empty:
perf.start("PDF: Chart - productie_vs_revanzare")
fig = create_production_chart(results['productie_vs_revanzare'])
pdf_gen.add_chart_image(fig, "Producție Proprie vs Revânzare")
perf.stop()
# Tabel: Top clienți
pdf_gen.add_explanation(PDF_EXPLANATIONS['top_clienti_produse'])
pdf_gen.add_table_section(
"Top 15 Clienți după Vânzări",
results.get('marja_per_client'),
columns=['CLIENT', 'VANZARI_FARA_TVA', 'MARJA_BRUTA', 'PROCENT_MARJA'],
max_rows=15
)
pdf_gen.add_page_break()
# Tabel: Top produse
pdf_gen.add_table_section(
"Top 15 Produse după Vânzări",
results.get('top_produse'),
columns=['PRODUS', 'VALOARE_VANZARI', 'MARJA_BRUTA', 'PROCENT_MARJA'],
max_rows=15
)
# Tabel: Trending clienți
pdf_gen.add_table_section(
"Trending Clienți (YoY)",
results.get('trending_clienti'),
columns=['CLIENT', 'VANZARI_12_LUNI', 'VANZARI_AN_ANTERIOR', 'VARIATIE_PROCENT', 'TREND'],
max_rows=15
)
# Tabel: Aging Creanțe
if 'aging_creante' in results and not results['aging_creante'].empty:
pdf_gen.add_page_break()
pdf_gen.add_explanation(PDF_EXPLANATIONS['aging_creante'])
pdf_gen.add_table_section(
"Aging Creanțe (Vechime Facturi Neîncasate)",
results.get('aging_creante'),
columns=['CLIENT', 'NEAJUNS_SCADENTA', 'ZILE_1_30', 'ZILE_31_60', 'PESTE_90_ZILE', 'TOTAL_SOLD'],
max_rows=15
)
# Tabel: Stoc lent
if 'stoc_lent' in results and not results['stoc_lent'].empty:
pdf_gen.add_page_break()
pdf_gen.add_explanation(PDF_EXPLANATIONS['stoc_lent'])
pdf_gen.add_table_section(
"Stoc Lent (>90 zile fără mișcare)",
results.get('stoc_lent'),
columns=['PRODUS', 'NUME_GESTIUNE', 'CANTITATE', 'VALOARE', 'ZILE_FARA_MISCARE'],
max_rows=20
)
perf.start("PDF: Save document")
pdf_gen.save()
perf.stop()
# Performance Summary
perf.summary(output_path=str(args.output_dir))
# Summary
print("\n" + "="*60)
print(" ✅ RAPOARTE GENERATE CU SUCCES!")
print("="*60)
print(f"\n 📊 Excel: {excel_path}")
print(f" 📄 PDF: {pdf_path}")
print("\n" + "="*60)
return excel_path, pdf_path
def main():
"""Entry point"""
parser = argparse.ArgumentParser(
description='Data Intelligence Report Generator',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Exemple:
python main.py # Raport pentru ultimele 12 luni
python main.py --months 6 # Raport pentru ultimele 6 luni
python main.py --output-dir /tmp # Salvare în alt director
"""
)
parser.add_argument(
'--months', '-m',
type=int,
default=ANALYSIS_MONTHS,
help=f'Numărul de luni pentru analiză (default: {ANALYSIS_MONTHS})'
)
parser.add_argument(
'--output-dir', '-o',
type=Path,
default=OUTPUT_DIR,
help=f'Directorul pentru output (default: {OUTPUT_DIR})'
)
args = parser.parse_args()
# Ensure output directory exists
args.output_dir.mkdir(parents=True, exist_ok=True)
try:
generate_reports(args)
except KeyboardInterrupt:
print("\n\n⚠️ Întrerupt de utilizator.")
sys.exit(1)
except Exception as e:
print(f"\n❌ Eroare: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == '__main__':
main()