Compare commits
2 Commits
8e076af166
...
bc05d02319
| Author | SHA1 | Date | |
|---|---|---|---|
| bc05d02319 | |||
| 38d8f9c6d2 |
223
main.py
223
main.py
@@ -63,6 +63,156 @@ from report_generator import (
|
|||||||
from recommendations import RecommendationsEngine
|
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:
|
class PerformanceLogger:
|
||||||
"""Tracks execution time for each operation to identify bottlenecks."""
|
"""Tracks execution time for each operation to identify bottlenecks."""
|
||||||
|
|
||||||
@@ -131,18 +281,23 @@ class PerformanceLogger:
|
|||||||
|
|
||||||
class OracleConnection:
|
class OracleConnection:
|
||||||
"""Context manager for Oracle database connection"""
|
"""Context manager for Oracle database connection"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.connection = None
|
self.connection = None
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
try:
|
try:
|
||||||
print(f"🔌 Conectare la Oracle: {get_dsn()}...")
|
print(f"🔌 Conectare la Oracle: {get_dsn()}...")
|
||||||
|
# Add TCP keepalive and timeout settings to prevent connection drops
|
||||||
self.connection = oracledb.connect(
|
self.connection = oracledb.connect(
|
||||||
user=ORACLE_CONFIG['user'],
|
user=ORACLE_CONFIG['user'],
|
||||||
password=ORACLE_CONFIG['password'],
|
password=ORACLE_CONFIG['password'],
|
||||||
dsn=get_dsn()
|
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!")
|
print("✓ Conectat cu succes!")
|
||||||
return self.connection
|
return self.connection
|
||||||
except oracledb.Error as e:
|
except oracledb.Error as e:
|
||||||
@@ -634,7 +789,7 @@ def generate_reports(args):
|
|||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
||||||
# --- Sheet 0: DASHBOARD COMPLET (toate secțiunile într-o singură vedere) ---
|
# --- Sheet 0: DASHBOARD COMPLET (toate secțiunile într-o singură vedere) ---
|
||||||
perf.start("EXCEL: Dashboard Complet sheet (9 sections)")
|
perf.start("EXCEL: Dashboard Complet sheet (12 sections)")
|
||||||
excel_gen.add_consolidated_sheet(
|
excel_gen.add_consolidated_sheet(
|
||||||
name='Dashboard Complet',
|
name='Dashboard Complet',
|
||||||
sheet_title='Dashboard Executiv - Vedere Completă',
|
sheet_title='Dashboard Executiv - Vedere Completă',
|
||||||
@@ -644,50 +799,80 @@ def generate_reports(args):
|
|||||||
{
|
{
|
||||||
'title': 'KPIs cu Comparație YoY',
|
'title': 'KPIs cu Comparație YoY',
|
||||||
'df': results.get('kpi_consolidated', pd.DataFrame()),
|
'df': results.get('kpi_consolidated', pd.DataFrame()),
|
||||||
'description': 'Indicatori cheie de performanță - curent vs anterior'
|
'description': 'Indicatori cheie de performanță - curent vs anterior',
|
||||||
|
'explanation': PDF_EXPLANATIONS['kpis']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': 'Recomandări Prioritare',
|
'title': 'Recomandări Prioritare',
|
||||||
'df': results.get('recomandari', pd.DataFrame()).head(10),
|
'df': results.get('recomandari', pd.DataFrame()).head(10),
|
||||||
'description': 'Top 10 acțiuni sugerate bazate pe analiză'
|
'description': 'Top 10 acțiuni sugerate bazate pe analiză',
|
||||||
|
'explanation': PDF_EXPLANATIONS['recomandari']
|
||||||
},
|
},
|
||||||
# Venituri
|
# Venituri
|
||||||
{
|
{
|
||||||
'title': 'Venituri per Linie Business',
|
'title': 'Venituri per Linie Business',
|
||||||
'df': results.get('venituri_consolidated', pd.DataFrame()),
|
'df': results.get('venituri_consolidated', pd.DataFrame()),
|
||||||
'description': 'Producție proprie, Materii prime, Marfă revândută'
|
'description': 'Producție proprie, Materii prime, Marfă revândută',
|
||||||
|
'explanation': PDF_EXPLANATIONS['venituri']
|
||||||
},
|
},
|
||||||
# Clienți și Risc
|
# Clienți și Risc
|
||||||
{
|
{
|
||||||
'title': 'Portofoliu Clienți',
|
'title': 'Portofoliu Clienți',
|
||||||
'df': results.get('portofoliu_clienti', pd.DataFrame()),
|
'df': results.get('portofoliu_clienti', pd.DataFrame()),
|
||||||
'description': 'Structura și segmentarea clienților'
|
'description': 'Structura și segmentarea clienților',
|
||||||
|
'explanation': PDF_EXPLANATIONS['portofoliu_clienti']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': 'Concentrare Risc YoY',
|
'title': 'Concentrare Risc YoY',
|
||||||
'df': results.get('risc_consolidated', pd.DataFrame()),
|
'df': results.get('risc_consolidated', pd.DataFrame()),
|
||||||
'description': 'Dependența de clienții mari - curent vs anterior'
|
'description': 'Dependența de clienții mari - curent vs anterior',
|
||||||
|
'explanation': PDF_EXPLANATIONS['risc_concentrare']
|
||||||
},
|
},
|
||||||
# Tablou Financiar
|
# Tablou Financiar - with DYNAMIC explanations
|
||||||
{
|
{
|
||||||
'title': 'Indicatori Generali',
|
'title': 'Indicatori Generali',
|
||||||
'df': results.get('indicatori_generali', pd.DataFrame()),
|
'df': results.get('indicatori_generali', pd.DataFrame()),
|
||||||
'description': 'Sold clienți, furnizori, cifra afaceri'
|
'description': 'Ratii financiare: indatorare, autonomie, datorii, rentabilitate',
|
||||||
|
'explanation': generate_indicatori_generali_explanation(results.get('indicatori_generali'))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': 'Indicatori Lichiditate',
|
'title': 'Indicatori Lichiditate',
|
||||||
'df': results.get('indicatori_lichiditate', pd.DataFrame()),
|
'df': results.get('indicatori_lichiditate', pd.DataFrame()),
|
||||||
'description': 'Zile rotație stoc, creanțe, datorii'
|
'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',
|
'title': 'Clasificare Datorii',
|
||||||
'df': results.get('clasificare_datorii', pd.DataFrame()),
|
'df': results.get('clasificare_datorii', pd.DataFrame()),
|
||||||
'description': 'Datorii pe intervale de întârziere'
|
'description': 'Datorii pe intervale de întârziere',
|
||||||
|
'explanation': PDF_EXPLANATIONS['clasificare_datorii']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': 'Proiecție Lichiditate',
|
'title': 'Proiecție Lichiditate',
|
||||||
'df': results.get('proiectie_lichiditate', pd.DataFrame()),
|
'df': results.get('proiectie_lichiditate', pd.DataFrame()),
|
||||||
'description': 'Previziune încasări și plăți pe 30 zile'
|
'description': 'Previziune încasări și plăți pe 30 zile',
|
||||||
|
'explanation': PDF_EXPLANATIONS['proiectie_lichiditate']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -745,6 +930,7 @@ def generate_reports(args):
|
|||||||
|
|
||||||
# Pagina 2-3: DASHBOARD COMPLET (toate secțiunile într-o vedere unificată)
|
# Pagina 2-3: DASHBOARD COMPLET (toate secțiunile într-o vedere unificată)
|
||||||
perf.start("PDF: Dashboard Complet page (4 sections)")
|
perf.start("PDF: Dashboard Complet page (4 sections)")
|
||||||
|
pdf_gen.add_explanation(PDF_EXPLANATIONS['kpis'])
|
||||||
pdf_gen.add_consolidated_page(
|
pdf_gen.add_consolidated_page(
|
||||||
'Dashboard Complet',
|
'Dashboard Complet',
|
||||||
sections=[
|
sections=[
|
||||||
@@ -783,6 +969,7 @@ def generate_reports(args):
|
|||||||
|
|
||||||
# Alerte (vânzări sub cost, clienți marjă mică)
|
# Alerte (vânzări sub cost, clienți marjă mică)
|
||||||
perf.start("PDF: Alerts section")
|
perf.start("PDF: Alerts section")
|
||||||
|
pdf_gen.add_explanation(PDF_EXPLANATIONS['alerte_critice'])
|
||||||
pdf_gen.add_alerts_section({
|
pdf_gen.add_alerts_section({
|
||||||
'vanzari_sub_cost': results.get('vanzari_sub_cost', pd.DataFrame()),
|
'vanzari_sub_cost': results.get('vanzari_sub_cost', pd.DataFrame()),
|
||||||
'clienti_marja_mica': results.get('clienti_marja_mica', pd.DataFrame())
|
'clienti_marja_mica': results.get('clienti_marja_mica', pd.DataFrame())
|
||||||
@@ -798,6 +985,7 @@ def generate_reports(args):
|
|||||||
# Grafic: Evoluția Vânzărilor Lunare
|
# Grafic: Evoluția Vânzărilor Lunare
|
||||||
if 'vanzari_lunare' in results and not results['vanzari_lunare'].empty:
|
if 'vanzari_lunare' in results and not results['vanzari_lunare'].empty:
|
||||||
perf.start("PDF: Chart - vanzari_lunare")
|
perf.start("PDF: Chart - vanzari_lunare")
|
||||||
|
pdf_gen.add_explanation(PDF_EXPLANATIONS['evolutie_lunara'])
|
||||||
fig = create_monthly_chart(results['vanzari_lunare'])
|
fig = create_monthly_chart(results['vanzari_lunare'])
|
||||||
pdf_gen.add_chart_image(fig, "Evoluția Vânzărilor și Marjei")
|
pdf_gen.add_chart_image(fig, "Evoluția Vânzărilor și Marjei")
|
||||||
perf.stop()
|
perf.stop()
|
||||||
@@ -805,15 +993,17 @@ def generate_reports(args):
|
|||||||
# Grafic: Concentrare Clienți
|
# Grafic: Concentrare Clienți
|
||||||
if 'concentrare_clienti' in results and not results['concentrare_clienti'].empty:
|
if 'concentrare_clienti' in results and not results['concentrare_clienti'].empty:
|
||||||
perf.start("PDF: Chart - concentrare_clienti")
|
perf.start("PDF: Chart - concentrare_clienti")
|
||||||
|
pdf_gen.add_explanation(PDF_EXPLANATIONS['concentrare_clienti'])
|
||||||
fig = create_client_concentration_chart(results['concentrare_clienti'])
|
fig = create_client_concentration_chart(results['concentrare_clienti'])
|
||||||
pdf_gen.add_chart_image(fig, "Concentrare Clienți")
|
pdf_gen.add_chart_image(fig, "Concentrare Clienți")
|
||||||
perf.stop()
|
perf.stop()
|
||||||
|
|
||||||
pdf_gen.add_page_break()
|
pdf_gen.add_page_break()
|
||||||
|
|
||||||
# Grafic: Ciclu Conversie Cash
|
# Grafic: Ciclu Conversie Cash - with DYNAMIC explanation showing actual values
|
||||||
if 'ciclu_conversie_cash' in results and not results['ciclu_conversie_cash'].empty:
|
if 'ciclu_conversie_cash' in results and not results['ciclu_conversie_cash'].empty:
|
||||||
perf.start("PDF: Chart - ciclu_conversie_cash")
|
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'])
|
fig = create_cash_cycle_chart(results['ciclu_conversie_cash'])
|
||||||
pdf_gen.add_chart_image(fig, "Ciclu Conversie Cash (DIO + DSO - DPO)")
|
pdf_gen.add_chart_image(fig, "Ciclu Conversie Cash (DIO + DSO - DPO)")
|
||||||
perf.stop()
|
perf.stop()
|
||||||
@@ -826,6 +1016,7 @@ def generate_reports(args):
|
|||||||
perf.stop()
|
perf.stop()
|
||||||
|
|
||||||
# Tabel: Top clienți
|
# Tabel: Top clienți
|
||||||
|
pdf_gen.add_explanation(PDF_EXPLANATIONS['top_clienti_produse'])
|
||||||
pdf_gen.add_table_section(
|
pdf_gen.add_table_section(
|
||||||
"Top 15 Clienți după Vânzări",
|
"Top 15 Clienți după Vânzări",
|
||||||
results.get('marja_per_client'),
|
results.get('marja_per_client'),
|
||||||
@@ -854,6 +1045,7 @@ def generate_reports(args):
|
|||||||
# Tabel: Aging Creanțe
|
# Tabel: Aging Creanțe
|
||||||
if 'aging_creante' in results and not results['aging_creante'].empty:
|
if 'aging_creante' in results and not results['aging_creante'].empty:
|
||||||
pdf_gen.add_page_break()
|
pdf_gen.add_page_break()
|
||||||
|
pdf_gen.add_explanation(PDF_EXPLANATIONS['aging_creante'])
|
||||||
pdf_gen.add_table_section(
|
pdf_gen.add_table_section(
|
||||||
"Aging Creanțe (Vechime Facturi Neîncasate)",
|
"Aging Creanțe (Vechime Facturi Neîncasate)",
|
||||||
results.get('aging_creante'),
|
results.get('aging_creante'),
|
||||||
@@ -864,6 +1056,7 @@ def generate_reports(args):
|
|||||||
# Tabel: Stoc lent
|
# Tabel: Stoc lent
|
||||||
if 'stoc_lent' in results and not results['stoc_lent'].empty:
|
if 'stoc_lent' in results and not results['stoc_lent'].empty:
|
||||||
pdf_gen.add_page_break()
|
pdf_gen.add_page_break()
|
||||||
|
pdf_gen.add_explanation(PDF_EXPLANATIONS['stoc_lent'])
|
||||||
pdf_gen.add_table_section(
|
pdf_gen.add_table_section(
|
||||||
"Stoc Lent (>90 zile fără mișcare)",
|
"Stoc Lent (>90 zile fără mișcare)",
|
||||||
results.get('stoc_lent'),
|
results.get('stoc_lent'),
|
||||||
|
|||||||
419
queries.py
419
queries.py
@@ -1,6 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
SQL Queries for Data Intelligence Report
|
SQL Queries for Data Intelligence Report
|
||||||
All queries use the existing views: fact_vfacturi2, fact_vfacturi_detalii, vstoc, vrul
|
OPTIMIZED: All queries use base tables (vanzari, vanzari_detalii) with Oracle hints
|
||||||
|
for index usage (IDX_VANZARI_NR) instead of views (fact_vfacturi2, fact_vfacturi_detalii).
|
||||||
|
Other views: vstoc, vrul are still used where appropriate.
|
||||||
|
|
||||||
IMPORTANT: Price calculation considers pret_cu_tva flag:
|
IMPORTANT: Price calculation considers pret_cu_tva flag:
|
||||||
- If pret_cu_tva = 1: price includes VAT, must divide by (1 + proc_tvav/100)
|
- If pret_cu_tva = 1: price includes VAT, must divide by (1 + proc_tvav/100)
|
||||||
@@ -9,13 +11,13 @@ Formula: CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 1. MARJA PER CLIENT
|
# 1. MARJA PER CLIENT (OPTIMIZAT - folosește tabele de bază + indexuri)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
MARJA_PER_CLIENT = """
|
MARJA_PER_CLIENT = """
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
f.id_part,
|
f.id_part,
|
||||||
f.client,
|
p.denumire AS client,
|
||||||
f.cod_fiscal,
|
p.cod_fiscal,
|
||||||
COUNT(DISTINCT f.id_vanzare) AS nr_facturi,
|
COUNT(DISTINCT f.id_vanzare) AS nr_facturi,
|
||||||
ROUND(SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END), 2) AS vanzari_fara_tva,
|
ROUND(SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END), 2) AS vanzari_fara_tva,
|
||||||
ROUND(SUM(d.cantitate * d.pret_achizitie), 2) AS cost_total,
|
ROUND(SUM(d.cantitate * d.pret_achizitie), 2) AS cost_total,
|
||||||
@@ -25,25 +27,25 @@ SELECT
|
|||||||
THEN SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)) * 100.0 / SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END)
|
THEN SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)) * 100.0 / SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END)
|
||||||
ELSE 0 END
|
ELSE 0 END
|
||||||
, 2) AS procent_marja
|
, 2) AS procent_marja
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
|
LEFT JOIN nom_parteneri p ON f.id_part = p.id_part
|
||||||
WHERE f.sters = 0
|
WHERE f.sters = 0
|
||||||
AND d.sters = 0
|
|
||||||
AND f.tip > 0
|
AND f.tip > 0
|
||||||
AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -:months)
|
||||||
GROUP BY f.id_part, f.client, f.cod_fiscal
|
GROUP BY f.id_part, p.denumire, p.cod_fiscal
|
||||||
ORDER BY marja_bruta DESC
|
ORDER BY marja_bruta DESC
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 2. CLIENȚI CU MARJĂ MICĂ (sub prag)
|
# 2. CLIENȚI CU MARJĂ MICĂ (sub prag) - OPTIMIZAT
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
CLIENTI_MARJA_MICA = """
|
CLIENTI_MARJA_MICA = """
|
||||||
SELECT * FROM (
|
SELECT * FROM (
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
f.client,
|
p.denumire AS client,
|
||||||
f.cod_fiscal,
|
p.cod_fiscal,
|
||||||
ROUND(SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END), 2) AS vanzari_fara_tva,
|
ROUND(SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END), 2) AS vanzari_fara_tva,
|
||||||
ROUND(SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)), 2) AS marja_bruta,
|
ROUND(SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)), 2) AS marja_bruta,
|
||||||
ROUND(
|
ROUND(
|
||||||
@@ -51,12 +53,13 @@ SELECT * FROM (
|
|||||||
THEN SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)) * 100.0 / SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END)
|
THEN SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)) * 100.0 / SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END)
|
||||||
ELSE 0 END
|
ELSE 0 END
|
||||||
, 2) AS procent_marja
|
, 2) AS procent_marja
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
LEFT JOIN nom_parteneri p ON f.id_part = p.id_part
|
||||||
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -:months)
|
||||||
GROUP BY f.id_part, f.client, f.cod_fiscal
|
GROUP BY f.id_part, p.denumire, p.cod_fiscal
|
||||||
HAVING SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) > :min_sales
|
HAVING SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) > :min_sales
|
||||||
)
|
)
|
||||||
WHERE procent_marja < :margin_threshold
|
WHERE procent_marja < :margin_threshold
|
||||||
@@ -64,12 +67,12 @@ ORDER BY vanzari_fara_tva DESC
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 3. MARJA PER CATEGORIE (Grupă + Subgrupă)
|
# 3. MARJA PER CATEGORIE (Grupă + Subgrupă) - OPTIMIZAT
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
MARJA_PER_CATEGORIE = """
|
MARJA_PER_CATEGORIE = """
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
NVL(sg.grupa, 'NECLASIFICAT') AS grupa,
|
NVL(sg.grupa, 'NECLASIFICAT') AS grupa,
|
||||||
NVL(d.subgrupa, 'NECLASIFICAT') AS subgrupa,
|
NVL(sg.subgrupa, 'NECLASIFICAT') AS subgrupa,
|
||||||
COUNT(DISTINCT f.id_vanzare) AS nr_facturi,
|
COUNT(DISTINCT f.id_vanzare) AS nr_facturi,
|
||||||
ROUND(SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END), 2) AS vanzari_fara_tva,
|
ROUND(SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END), 2) AS vanzari_fara_tva,
|
||||||
ROUND(SUM(d.cantitate * d.pret_achizitie), 2) AS cost_total,
|
ROUND(SUM(d.cantitate * d.pret_achizitie), 2) AS cost_total,
|
||||||
@@ -79,21 +82,22 @@ SELECT
|
|||||||
THEN SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)) * 100.0 / SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END)
|
THEN SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)) * 100.0 / SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END)
|
||||||
ELSE 0 END
|
ELSE 0 END
|
||||||
, 2) AS procent_marja
|
, 2) AS procent_marja
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
LEFT JOIN vgest_art_sbgr sg ON d.id_subgrupa = sg.id_subgrupa
|
LEFT JOIN nom_articole art ON d.id_articol = art.id_articol
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
LEFT JOIN vgest_art_sbgr sg ON art.id_subgrupa = sg.id_subgrupa
|
||||||
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -:months)
|
||||||
GROUP BY sg.id_grupa, sg.grupa, d.id_subgrupa, d.subgrupa
|
GROUP BY sg.id_grupa, sg.grupa, art.id_subgrupa, sg.subgrupa
|
||||||
ORDER BY vanzari_fara_tva DESC
|
ORDER BY vanzari_fara_tva DESC
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 4. PRODUCȚIE PROPRIE vs MARFĂ REVÂNDUTĂ
|
# 4. PRODUCȚIE PROPRIE vs MARFĂ REVÂNDUTĂ - OPTIMIZAT
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
PRODUCTIE_VS_REVANZARE = """
|
PRODUCTIE_VS_REVANZARE = """
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
CASE
|
CASE
|
||||||
WHEN d.cont IN ('341', '345') THEN 'Producție proprie'
|
WHEN d.cont IN ('341', '345') THEN 'Producție proprie'
|
||||||
WHEN d.cont = '301' THEN 'Materii prime'
|
WHEN d.cont = '301' THEN 'Materii prime'
|
||||||
@@ -107,11 +111,11 @@ SELECT
|
|||||||
THEN SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)) * 100.0 / SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END)
|
THEN SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)) * 100.0 / SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END)
|
||||||
ELSE 0 END
|
ELSE 0 END
|
||||||
, 2) AS procent_marja
|
, 2) AS procent_marja
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -:months)
|
||||||
GROUP BY CASE
|
GROUP BY CASE
|
||||||
WHEN d.cont IN ('341', '345') THEN 'Producție proprie'
|
WHEN d.cont IN ('341', '345') THEN 'Producție proprie'
|
||||||
WHEN d.cont = '301' THEN 'Materii prime'
|
WHEN d.cont = '301' THEN 'Materii prime'
|
||||||
@@ -125,18 +129,21 @@ ORDER BY vanzari_fara_tva DESC
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
DISPERSIE_PRETURI = """
|
DISPERSIE_PRETURI = """
|
||||||
WITH preturi_detalii AS (
|
WITH preturi_detalii AS (
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
d.id_articol,
|
d.id_articol,
|
||||||
d.denumire,
|
a.denumire,
|
||||||
d.subgrupa,
|
g.subgrupa,
|
||||||
f.id_part,
|
f.id_part,
|
||||||
f.client,
|
p.denumire as client,
|
||||||
CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END AS pret_fara_tva,
|
CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END AS pret_fara_tva,
|
||||||
d.cantitate,
|
d.cantitate,
|
||||||
MIN(CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) OVER (PARTITION BY d.id_articol) AS pret_min_global
|
MIN(CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) OVER (PARTITION BY d.id_articol) AS pret_min_global
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
LEFT JOIN nom_parteneri p ON f.id_part = p.id_part
|
||||||
|
LEFT JOIN nom_articole a ON d.id_articol = a.id_articol
|
||||||
|
LEFT JOIN gest_art_sbgr g ON a.id_subgrupa = g.id_subgrupa
|
||||||
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
||||||
AND d.pret > 0
|
AND d.pret > 0
|
||||||
@@ -206,23 +213,25 @@ FETCH FIRST 100 ROWS ONLY
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 7. VÂNZĂRI SUB COST (ALERTĂ CRITICĂ)
|
# 7. VÂNZĂRI SUB COST (ALERTĂ CRITICĂ) - OPTIMIZAT
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
VANZARI_SUB_COST = """
|
VANZARI_SUB_COST = """
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
f.data_act,
|
f.data_act,
|
||||||
f.serie_act || ' ' || f.numar_act AS factura,
|
f.serie_act || ' ' || f.numar_act AS factura,
|
||||||
f.client,
|
p.denumire AS client,
|
||||||
d.denumire AS produs,
|
NVL2(d.id_articol, art.denumire, d.explicatie) AS produs,
|
||||||
d.cantitate,
|
d.cantitate,
|
||||||
ROUND(CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END, 2) AS pret_vanzare,
|
ROUND(CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END, 2) AS pret_vanzare,
|
||||||
ROUND(d.pret_achizitie, 2) AS cost,
|
ROUND(d.pret_achizitie, 2) AS cost,
|
||||||
ROUND((CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie) * d.cantitate, 2) AS pierdere
|
ROUND((CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie) * d.cantitate, 2) AS pierdere
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
LEFT JOIN nom_parteneri p ON f.id_part = p.id_part
|
||||||
|
LEFT JOIN nom_articole art ON d.id_articol = art.id_articol
|
||||||
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -:months)
|
||||||
AND d.pret_achizitie > 0
|
AND d.pret_achizitie > 0
|
||||||
AND d.pret > 0
|
AND d.pret > 0
|
||||||
AND CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END < d.pret_achizitie
|
AND CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END < d.pret_achizitie
|
||||||
@@ -235,20 +244,21 @@ FETCH FIRST 100 ROWS ONLY
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
TRENDING_CLIENTI = """
|
TRENDING_CLIENTI = """
|
||||||
WITH vanzari_perioade AS (
|
WITH vanzari_perioade AS (
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
f.id_part,
|
f.id_part,
|
||||||
f.client,
|
p.denumire AS client,
|
||||||
SUM(CASE WHEN f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -12)
|
SUM(CASE WHEN f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -12)
|
||||||
THEN d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END ELSE 0 END) AS vanzari_an_curent,
|
THEN d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END ELSE 0 END) AS vanzari_an_curent,
|
||||||
SUM(CASE WHEN f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -24)
|
SUM(CASE WHEN f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -24)
|
||||||
AND f.data_act < ADD_MONTHS(TRUNC(SYSDATE), -12)
|
AND f.data_act < ADD_MONTHS(TRUNC(SYSDATE), -12)
|
||||||
THEN d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END ELSE 0 END) AS vanzari_an_trecut
|
THEN d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END ELSE 0 END) AS vanzari_an_trecut
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
LEFT JOIN nom_parteneri p ON f.id_part = p.id_part
|
||||||
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -24)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -24)
|
||||||
GROUP BY f.id_part, f.client
|
GROUP BY f.id_part, p.denumire
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
client,
|
client,
|
||||||
@@ -276,24 +286,26 @@ ORDER BY variatie_procent DESC NULLS LAST
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
CONCENTRARE_CLIENTI = """
|
CONCENTRARE_CLIENTI = """
|
||||||
WITH total_vanzari AS (
|
WITH total_vanzari AS (
|
||||||
SELECT SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS total
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
FROM fact_vfacturi2 f
|
SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS total
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
FROM vanzari f
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
||||||
),
|
),
|
||||||
vanzari_client AS (
|
vanzari_client AS (
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
f.client,
|
p.denumire AS client,
|
||||||
SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS vanzari,
|
SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS vanzari,
|
||||||
ROW_NUMBER() OVER (ORDER BY SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) DESC) AS rn
|
ROW_NUMBER() OVER (ORDER BY SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) DESC) AS rn
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
LEFT JOIN nom_parteneri p ON f.id_part = p.id_part
|
||||||
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
||||||
GROUP BY f.id_part, f.client
|
GROUP BY f.id_part, p.denumire
|
||||||
),
|
),
|
||||||
top_clienti AS (
|
top_clienti AS (
|
||||||
SELECT
|
SELECT
|
||||||
@@ -556,10 +568,10 @@ WHERE s.cant > 0
|
|||||||
# 16. TOP PRODUSE DUPĂ VÂNZĂRI
|
# 16. TOP PRODUSE DUPĂ VÂNZĂRI
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
TOP_PRODUSE = """
|
TOP_PRODUSE = """
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
d.denumire AS produs,
|
NVL2(d.id_articol, a.denumire, d.explicatie) AS produs,
|
||||||
NVL(d.subgrupa, 'NECLASIFICAT') AS subgrupa,
|
NVL(g.subgrupa, 'NECLASIFICAT') AS subgrupa,
|
||||||
d.um,
|
a.um,
|
||||||
ROUND(SUM(d.cantitate), 2) AS cantitate_vanduta,
|
ROUND(SUM(d.cantitate), 2) AS cantitate_vanduta,
|
||||||
ROUND(SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END), 2) AS valoare_vanzari,
|
ROUND(SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END), 2) AS valoare_vanzari,
|
||||||
ROUND(SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)), 2) AS marja_bruta,
|
ROUND(SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)), 2) AS marja_bruta,
|
||||||
@@ -568,22 +580,24 @@ SELECT
|
|||||||
THEN SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)) * 100.0 / SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END)
|
THEN SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)) * 100.0 / SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END)
|
||||||
ELSE 0 END
|
ELSE 0 END
|
||||||
, 2) AS procent_marja
|
, 2) AS procent_marja
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
LEFT JOIN nom_articole a ON d.id_articol = a.id_articol
|
||||||
|
LEFT JOIN gest_art_sbgr g ON a.id_subgrupa = g.id_subgrupa
|
||||||
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
||||||
GROUP BY d.id_articol, d.denumire, d.subgrupa, d.um
|
GROUP BY d.id_articol, NVL2(d.id_articol, a.denumire, d.explicatie), g.subgrupa, a.um
|
||||||
ORDER BY valoare_vanzari DESC
|
ORDER BY valoare_vanzari DESC
|
||||||
FETCH FIRST 50 ROWS ONLY
|
FETCH FIRST 50 ROWS ONLY
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 17. MARJA PER GESTIUNE (doar articole gestionabile)
|
# 17. MARJA PER GESTIUNE (doar articole gestionabile) - OPTIMIZAT
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
MARJA_PER_GESTIUNE = """
|
MARJA_PER_GESTIUNE = """
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
d.nume_gestiune,
|
g.nume_gestiune,
|
||||||
COUNT(DISTINCT f.id_vanzare) AS nr_facturi,
|
COUNT(DISTINCT f.id_vanzare) AS nr_facturi,
|
||||||
ROUND(SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END), 2) AS vanzari_fara_tva,
|
ROUND(SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END), 2) AS vanzari_fara_tva,
|
||||||
ROUND(SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)), 2) AS marja_bruta,
|
ROUND(SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)), 2) AS marja_bruta,
|
||||||
@@ -592,25 +606,26 @@ SELECT
|
|||||||
THEN SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)) * 100.0 / SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END)
|
THEN SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)) * 100.0 / SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END)
|
||||||
ELSE 0 END
|
ELSE 0 END
|
||||||
, 2) AS procent_marja
|
, 2) AS procent_marja
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
|
LEFT JOIN nom_gestiuni g ON d.id_gestiune = g.id_gestiune
|
||||||
JOIN nom_articole a ON d.id_articol = a.id_articol
|
JOIN nom_articole a ON d.id_articol = a.id_articol
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -:months)
|
||||||
AND NVL(a.in_stoc, 1) = 1
|
AND NVL(a.in_stoc, 1) = 1
|
||||||
GROUP BY d.id_gestiune, d.nume_gestiune
|
GROUP BY d.id_gestiune, g.nume_gestiune
|
||||||
ORDER BY vanzari_fara_tva DESC
|
ORDER BY vanzari_fara_tva DESC
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 18. ARTICOLE NEGESTIONABILE (servicii, etc.)
|
# 18. ARTICOLE NEGESTIONABILE (servicii, etc.) - OPTIMIZAT
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
ARTICOLE_NEGESTIONABILE = """
|
ARTICOLE_NEGESTIONABILE = """
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
NVL(d.denumire, 'NECUNOSCUT') AS denumire,
|
NVL(art.denumire, d.explicatie) AS denumire,
|
||||||
NVL(d.subgrupa, 'NECLASIFICAT') AS subgrupa,
|
NVL(sg.subgrupa, 'NECLASIFICAT') AS subgrupa,
|
||||||
d.um,
|
art.um,
|
||||||
COUNT(DISTINCT f.id_vanzare) AS nr_facturi,
|
COUNT(DISTINCT f.id_vanzare) AS nr_facturi,
|
||||||
ROUND(SUM(d.cantitate), 2) AS cantitate_vanduta,
|
ROUND(SUM(d.cantitate), 2) AS cantitate_vanduta,
|
||||||
ROUND(SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END), 2) AS vanzari_fara_tva,
|
ROUND(SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END), 2) AS vanzari_fara_tva,
|
||||||
@@ -621,14 +636,15 @@ SELECT
|
|||||||
THEN SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)) * 100.0 / SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END)
|
THEN SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)) * 100.0 / SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END)
|
||||||
ELSE 0 END
|
ELSE 0 END
|
||||||
, 2) AS procent_marja
|
, 2) AS procent_marja
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
JOIN nom_articole a ON d.id_articol = a.id_articol
|
LEFT JOIN nom_articole art ON d.id_articol = art.id_articol
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
LEFT JOIN vgest_art_sbgr sg ON art.id_subgrupa = sg.id_subgrupa
|
||||||
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -:months)
|
||||||
AND NVL(a.in_stoc, 0) = 0
|
AND NVL(art.in_stoc, 0) = 0
|
||||||
GROUP BY d.id_articol, d.denumire, d.subgrupa, d.um
|
GROUP BY d.id_articol, art.denumire, d.explicatie, sg.subgrupa, art.um
|
||||||
ORDER BY vanzari_fara_tva DESC
|
ORDER BY vanzari_fara_tva DESC
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -889,6 +905,7 @@ sold_furnizori AS (
|
|||||||
SELECT
|
SELECT
|
||||||
'DSO (Zile incasare clienti)' AS indicator,
|
'DSO (Zile incasare clienti)' AS indicator,
|
||||||
ROUND(NVL(sc.total_creante, 0) * 365 / NULLIF(v.total_vanzari, 0), 0) AS zile,
|
ROUND(NVL(sc.total_creante, 0) * 365 / NULLIF(v.total_vanzari, 0), 0) AS zile,
|
||||||
|
'(Creante clienti x 365) / Vanzari anuale (cont 4111 x 365 / jurnal vanzari vjv2025)' AS formula,
|
||||||
CASE
|
CASE
|
||||||
WHEN NVL(sc.total_creante, 0) * 365 / NULLIF(v.total_vanzari, 0) > 60 THEN 'ALERTA'
|
WHEN NVL(sc.total_creante, 0) * 365 / NULLIF(v.total_vanzari, 0) > 60 THEN 'ALERTA'
|
||||||
WHEN NVL(sc.total_creante, 0) * 365 / NULLIF(v.total_vanzari, 0) > 45 THEN 'ATENTIE'
|
WHEN NVL(sc.total_creante, 0) * 365 / NULLIF(v.total_vanzari, 0) > 45 THEN 'ATENTIE'
|
||||||
@@ -899,6 +916,7 @@ UNION ALL
|
|||||||
SELECT
|
SELECT
|
||||||
'DPO (Zile plata furnizori)' AS indicator,
|
'DPO (Zile plata furnizori)' AS indicator,
|
||||||
ROUND(NVL(sf.total_datorii, 0) * 365 / NULLIF(a.total_achizitii, 0), 0) AS zile,
|
ROUND(NVL(sf.total_datorii, 0) * 365 / NULLIF(a.total_achizitii, 0), 0) AS zile,
|
||||||
|
'(Datorii furnizori x 365) / Achizitii anuale (cont 401 x 365 / jurnal cumparari vjc2025)' AS formula,
|
||||||
CASE
|
CASE
|
||||||
WHEN NVL(sf.total_datorii, 0) * 365 / NULLIF(a.total_achizitii, 0) < 15 THEN 'ATENTIE'
|
WHEN NVL(sf.total_datorii, 0) * 365 / NULLIF(a.total_achizitii, 0) < 15 THEN 'ATENTIE'
|
||||||
ELSE 'OK'
|
ELSE 'OK'
|
||||||
@@ -948,10 +966,11 @@ WITH metrici AS (
|
|||||||
AND b.an = EXTRACT(YEAR FROM SYSDATE)
|
AND b.an = EXTRACT(YEAR FROM SYSDATE)
|
||||||
AND b.luna = EXTRACT(MONTH FROM SYSDATE)) AS stoc_curent,
|
AND b.luna = EXTRACT(MONTH FROM SYSDATE)) AS stoc_curent,
|
||||||
-- COGS din facturi (preț achiziție articole vândute)
|
-- COGS din facturi (preț achiziție articole vândute)
|
||||||
(SELECT SUM(d.cantitate * d.pret_achizitie)
|
(SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
FROM fact_vfacturi2 f
|
SUM(d.cantitate * d.pret_achizitie)
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
FROM vanzari f
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -12)) AS cogs_12_luni,
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -12)) AS cogs_12_luni,
|
||||||
-- Creanțe clienți
|
-- Creanțe clienți
|
||||||
@@ -1015,18 +1034,21 @@ WITH metrici AS (
|
|||||||
SELECT
|
SELECT
|
||||||
'DIO (Zile stoc)' AS indicator,
|
'DIO (Zile stoc)' AS indicator,
|
||||||
ROUND(NVL(m.stoc_curent, 0) * 365 / NULLIF(m.cogs_12_luni, 0), 0) AS zile,
|
ROUND(NVL(m.stoc_curent, 0) * 365 / NULLIF(m.cogs_12_luni, 0), 0) AS zile,
|
||||||
|
'(Stoc x 365) / Cost marfuri vandute (clasa 3xx x 365 / cost achizitie 12 luni)' AS formula,
|
||||||
'Zile medii pentru transformarea stocului in vanzare' AS explicatie
|
'Zile medii pentru transformarea stocului in vanzare' AS explicatie
|
||||||
FROM metrici m
|
FROM metrici m
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT
|
SELECT
|
||||||
'DSO (Zile incasare)' AS indicator,
|
'DSO (Zile incasare)' AS indicator,
|
||||||
ROUND(NVL(m.creante, 0) * 365 / NULLIF(m.vanzari_12_luni, 0), 0) AS zile,
|
ROUND(NVL(m.creante, 0) * 365 / NULLIF(m.vanzari_12_luni, 0), 0) AS zile,
|
||||||
|
'(Creante x 365) / Vanzari anuale (cont 4111 x 365 / jurnal vanzari vjv2025)' AS formula,
|
||||||
'Zile medii pentru incasarea creantelor' AS explicatie
|
'Zile medii pentru incasarea creantelor' AS explicatie
|
||||||
FROM metrici m
|
FROM metrici m
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT
|
SELECT
|
||||||
'DPO (Zile plata)' AS indicator,
|
'DPO (Zile plata)' AS indicator,
|
||||||
ROUND(NVL(m.datorii_furnizori, 0) * 365 / NULLIF(m.achizitii_12_luni, 0), 0) AS zile,
|
ROUND(NVL(m.datorii_furnizori, 0) * 365 / NULLIF(m.achizitii_12_luni, 0), 0) AS zile,
|
||||||
|
'(Datorii furnizori x 365) / Achizitii anuale (cont 401 x 365 / jurnal cumparari vjc2025)' AS formula,
|
||||||
'Zile medii pentru plata furnizorilor' AS explicatie
|
'Zile medii pentru plata furnizorilor' AS explicatie
|
||||||
FROM metrici m
|
FROM metrici m
|
||||||
UNION ALL
|
UNION ALL
|
||||||
@@ -1037,16 +1059,17 @@ SELECT
|
|||||||
NVL(m.creante, 0) * 365 / NULLIF(m.vanzari_12_luni, 0) -
|
NVL(m.creante, 0) * 365 / NULLIF(m.vanzari_12_luni, 0) -
|
||||||
NVL(m.datorii_furnizori, 0) * 365 / NULLIF(m.achizitii_12_luni, 0)
|
NVL(m.datorii_furnizori, 0) * 365 / NULLIF(m.achizitii_12_luni, 0)
|
||||||
, 0) AS zile,
|
, 0) AS zile,
|
||||||
|
'DIO + DSO - DPO (zile de la plata furnizor pana la incasare client)' AS formula,
|
||||||
'DIO + DSO - DPO = zile de la plata furnizor pana la incasare client' AS explicatie
|
'DIO + DSO - DPO = zile de la plata furnizor pana la incasare client' AS explicatie
|
||||||
FROM metrici m
|
FROM metrici m
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 26. INDICATORI AGREGATI VENITURI (Revenue mix per linie de business)
|
# 26. INDICATORI AGREGATI VENITURI (Revenue mix per linie de business) - OPTIMIZAT
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
INDICATORI_AGREGATI_VENITURI = """
|
INDICATORI_AGREGATI_VENITURI = """
|
||||||
WITH vanzari_detaliate AS (
|
WITH vanzari_detaliate AS (
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
CASE
|
CASE
|
||||||
WHEN d.cont IN ('341', '345') THEN 'Productie proprie'
|
WHEN d.cont IN ('341', '345') THEN 'Productie proprie'
|
||||||
WHEN d.cont = '301' THEN 'Materii prime'
|
WHEN d.cont = '301' THEN 'Materii prime'
|
||||||
@@ -1054,11 +1077,11 @@ WITH vanzari_detaliate AS (
|
|||||||
END AS linie_business,
|
END AS linie_business,
|
||||||
d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END AS vanzare,
|
d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END AS vanzare,
|
||||||
d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie) AS marja
|
d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie) AS marja
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -:months)
|
||||||
),
|
),
|
||||||
total AS (
|
total AS (
|
||||||
SELECT SUM(vanzare) AS total_vanzari, SUM(marja) AS total_marja
|
SELECT SUM(vanzare) AS total_vanzari, SUM(marja) AS total_marja
|
||||||
@@ -1082,14 +1105,14 @@ ORDER BY vanzari_ron DESC
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
SEZONALITATE_LUNARA = """
|
SEZONALITATE_LUNARA = """
|
||||||
WITH vanzari_lunare AS (
|
WITH vanzari_lunare AS (
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
EXTRACT(MONTH FROM f.data_act) AS nr_luna,
|
EXTRACT(MONTH FROM f.data_act) AS nr_luna,
|
||||||
TO_CHAR(f.data_act, 'Month', 'NLS_DATE_LANGUAGE=ROMANIAN') AS luna,
|
TO_CHAR(f.data_act, 'Month', 'NLS_DATE_LANGUAGE=ROMANIAN') AS luna,
|
||||||
EXTRACT(YEAR FROM f.data_act) AS an,
|
EXTRACT(YEAR FROM f.data_act) AS an,
|
||||||
SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS vanzari
|
SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS vanzari
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -24)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -24)
|
||||||
GROUP BY EXTRACT(MONTH FROM f.data_act), TO_CHAR(f.data_act, 'Month', 'NLS_DATE_LANGUAGE=ROMANIAN'), EXTRACT(YEAR FROM f.data_act)
|
GROUP BY EXTRACT(MONTH FROM f.data_act), TO_CHAR(f.data_act, 'Month', 'NLS_DATE_LANGUAGE=ROMANIAN'), EXTRACT(YEAR FROM f.data_act)
|
||||||
@@ -1129,51 +1152,51 @@ ORDER BY s.nr_luna
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
PORTOFOLIU_CLIENTI = """
|
PORTOFOLIU_CLIENTI = """
|
||||||
WITH clienti_activi_3_luni AS (
|
WITH clienti_activi_3_luni AS (
|
||||||
SELECT COUNT(DISTINCT f.id_part) AS cnt
|
SELECT /*+ INDEX(f IDX_VANZARI_NR) */ COUNT(DISTINCT f.id_part) AS cnt
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
WHERE f.sters = 0 AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
WHERE f.sters = 0 AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -3)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -3)
|
||||||
),
|
),
|
||||||
clienti_activi_12_luni AS (
|
clienti_activi_12_luni AS (
|
||||||
SELECT COUNT(DISTINCT f.id_part) AS cnt
|
SELECT /*+ INDEX(f IDX_VANZARI_NR) */ COUNT(DISTINCT f.id_part) AS cnt
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
WHERE f.sters = 0 AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
WHERE f.sters = 0 AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -12)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -12)
|
||||||
),
|
),
|
||||||
clienti_noi AS (
|
clienti_noi AS (
|
||||||
SELECT COUNT(DISTINCT f.id_part) AS cnt
|
SELECT /*+ INDEX(f IDX_VANZARI_NR) */ COUNT(DISTINCT f.id_part) AS cnt
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
WHERE f.sters = 0 AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
WHERE f.sters = 0 AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -12)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -12)
|
||||||
AND f.id_part NOT IN (
|
AND f.id_part NOT IN (
|
||||||
SELECT DISTINCT f2.id_part
|
SELECT /*+ INDEX(f2 IDX_VANZARI_NR) */ DISTINCT f2.id_part
|
||||||
FROM fact_vfacturi2 f2
|
FROM vanzari f2
|
||||||
WHERE f2.sters = 0 AND f2.tip > 0 AND f2.tip NOT IN (7, 8, 9, 24)
|
WHERE f2.sters = 0 AND f2.tip > 0 AND f2.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f2.data_act < ADD_MONTHS(TRUNC(SYSDATE), -12)
|
AND f2.data_act < ADD_MONTHS(TRUNC(SYSDATE), -12)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
clienti_pierduti AS (
|
clienti_pierduti AS (
|
||||||
SELECT COUNT(DISTINCT f.id_part) AS cnt
|
SELECT /*+ INDEX(f IDX_VANZARI_NR) */ COUNT(DISTINCT f.id_part) AS cnt
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
WHERE f.sters = 0 AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
WHERE f.sters = 0 AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -24)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -24)
|
||||||
AND f.data_act < ADD_MONTHS(TRUNC(SYSDATE), -6)
|
AND f.data_act < ADD_MONTHS(TRUNC(SYSDATE), -6)
|
||||||
AND f.id_part NOT IN (
|
AND f.id_part NOT IN (
|
||||||
SELECT DISTINCT f2.id_part
|
SELECT /*+ INDEX(f2 IDX_VANZARI_NR) */ DISTINCT f2.id_part
|
||||||
FROM fact_vfacturi2 f2
|
FROM vanzari f2
|
||||||
WHERE f2.sters = 0 AND f2.tip > 0 AND f2.tip NOT IN (7, 8, 9, 24)
|
WHERE f2.sters = 0 AND f2.tip > 0 AND f2.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f2.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -6)
|
AND f2.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -6)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
clienti_inactivi AS (
|
clienti_inactivi AS (
|
||||||
SELECT COUNT(DISTINCT f.id_part) AS cnt
|
SELECT /*+ INDEX(f IDX_VANZARI_NR) */ COUNT(DISTINCT f.id_part) AS cnt
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
WHERE f.sters = 0 AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
WHERE f.sters = 0 AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -6)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -6)
|
||||||
AND f.data_act < ADD_MONTHS(TRUNC(SYSDATE), -3)
|
AND f.data_act < ADD_MONTHS(TRUNC(SYSDATE), -3)
|
||||||
AND f.id_part NOT IN (
|
AND f.id_part NOT IN (
|
||||||
SELECT DISTINCT f2.id_part
|
SELECT /*+ INDEX(f2 IDX_VANZARI_NR) */ DISTINCT f2.id_part
|
||||||
FROM fact_vfacturi2 f2
|
FROM vanzari f2
|
||||||
WHERE f2.sters = 0 AND f2.tip > 0 AND f2.tip NOT IN (7, 8, 9, 24)
|
WHERE f2.sters = 0 AND f2.tip > 0 AND f2.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f2.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -3)
|
AND f2.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -3)
|
||||||
)
|
)
|
||||||
@@ -1194,24 +1217,25 @@ SELECT 'Clienti inactivi (3-6 luni)' AS indicator, cnt AS valoare, 'Risc de pier
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
FRECVENTA_CLIENTI = """
|
FRECVENTA_CLIENTI = """
|
||||||
WITH frecventa_curenta AS (
|
WITH frecventa_curenta AS (
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
f.id_part,
|
f.id_part,
|
||||||
f.client,
|
p.denumire AS client,
|
||||||
COUNT(DISTINCT f.id_vanzare) AS comenzi_12_luni,
|
COUNT(DISTINCT f.id_vanzare) AS comenzi_12_luni,
|
||||||
ROUND(COUNT(DISTINCT f.id_vanzare) / 12.0, 2) AS comenzi_pe_luna,
|
ROUND(COUNT(DISTINCT f.id_vanzare) / 12.0, 2) AS comenzi_pe_luna,
|
||||||
ROUND(SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END), 2) AS valoare_12_luni
|
ROUND(SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END), 2) AS valoare_12_luni
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
LEFT JOIN nom_parteneri p ON f.id_part = p.id_part
|
||||||
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -12)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -12)
|
||||||
GROUP BY f.id_part, f.client
|
GROUP BY f.id_part, p.denumire
|
||||||
),
|
),
|
||||||
frecventa_anterioara AS (
|
frecventa_anterioara AS (
|
||||||
SELECT
|
SELECT /*+ INDEX(f IDX_VANZARI_NR) */
|
||||||
f.id_part,
|
f.id_part,
|
||||||
COUNT(DISTINCT f.id_vanzare) AS comenzi_an_anterior
|
COUNT(DISTINCT f.id_vanzare) AS comenzi_an_anterior
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
WHERE f.sters = 0
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -24)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -24)
|
||||||
@@ -1240,21 +1264,22 @@ ORDER BY fc.valoare_12_luni DESC
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
CONCENTRARE_RISC = """
|
CONCENTRARE_RISC = """
|
||||||
WITH total_vanzari AS (
|
WITH total_vanzari AS (
|
||||||
SELECT SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS total
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
FROM fact_vfacturi2 f
|
SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS total
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
FROM vanzari f
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
||||||
),
|
),
|
||||||
vanzari_client AS (
|
vanzari_client AS (
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
f.id_part,
|
f.id_part,
|
||||||
SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS vanzari,
|
SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS vanzari,
|
||||||
ROW_NUMBER() OVER (ORDER BY SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) DESC) AS rn
|
ROW_NUMBER() OVER (ORDER BY SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) DESC) AS rn
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
||||||
GROUP BY f.id_part
|
GROUP BY f.id_part
|
||||||
@@ -1301,18 +1326,19 @@ GROUP BY tv.total
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
CLIENTI_RANKING_PROFIT = """
|
CLIENTI_RANKING_PROFIT = """
|
||||||
WITH vanzari_client AS (
|
WITH vanzari_client AS (
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
f.id_part,
|
f.id_part,
|
||||||
f.client,
|
p.denumire AS client,
|
||||||
SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS vanzari_fara_tva,
|
SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS vanzari_fara_tva,
|
||||||
SUM(d.cantitate * d.pret_achizitie) AS cost_total,
|
SUM(d.cantitate * d.pret_achizitie) AS cost_total,
|
||||||
SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)) AS profit_brut
|
SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)) AS profit_brut
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
LEFT JOIN nom_parteneri p ON f.id_part = p.id_part
|
||||||
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
||||||
GROUP BY f.id_part, f.client
|
GROUP BY f.id_part, p.denumire
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
client,
|
client,
|
||||||
@@ -1328,11 +1354,11 @@ ORDER BY profit_brut DESC
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 32. MARJA CLIENT CATEGORIE (Marjă per categorie per client)
|
# 32. MARJA CLIENT CATEGORIE (Marjă per categorie per client) - OPTIMIZAT
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
MARJA_CLIENT_CATEGORIE = """
|
MARJA_CLIENT_CATEGORIE = """
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
f.client,
|
p.denumire AS client,
|
||||||
NVL(sg.grupa, 'NECLASIFICAT') AS categoria,
|
NVL(sg.grupa, 'NECLASIFICAT') AS categoria,
|
||||||
ROUND(SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END), 2) AS vanzari,
|
ROUND(SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END), 2) AS vanzari,
|
||||||
ROUND(SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)), 2) AS marja,
|
ROUND(SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)), 2) AS marja,
|
||||||
@@ -1346,15 +1372,17 @@ SELECT
|
|||||||
NULLIF(SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END), 0) < 15 THEN 'MARJA MICA'
|
NULLIF(SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END), 0) < 15 THEN 'MARJA MICA'
|
||||||
ELSE 'OK'
|
ELSE 'OK'
|
||||||
END AS status_marja
|
END AS status_marja
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
LEFT JOIN vgest_art_sbgr sg ON d.id_subgrupa = sg.id_subgrupa
|
LEFT JOIN nom_parteneri p ON f.id_part = p.id_part
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
LEFT JOIN nom_articole art ON d.id_articol = art.id_articol
|
||||||
|
LEFT JOIN vgest_art_sbgr sg ON art.id_subgrupa = sg.id_subgrupa
|
||||||
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -:months)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -:months)
|
||||||
GROUP BY f.id_part, f.client, sg.id_grupa, sg.grupa
|
GROUP BY f.id_part, p.denumire, sg.id_grupa, sg.grupa
|
||||||
HAVING SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) > 1000
|
HAVING SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) > 1000
|
||||||
ORDER BY f.client, vanzari DESC
|
ORDER BY p.denumire, vanzari DESC
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -1362,27 +1390,28 @@ ORDER BY f.client, vanzari DESC
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
EVOLUTIE_DISCOUNT = """
|
EVOLUTIE_DISCOUNT = """
|
||||||
WITH preturi_vechi AS (
|
WITH preturi_vechi AS (
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
d.id_articol,
|
d.id_articol,
|
||||||
d.denumire,
|
a.denumire,
|
||||||
AVG(CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS pret_mediu_vechi
|
AVG(CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS pret_mediu_vechi
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
LEFT JOIN nom_articole a ON d.id_articol = a.id_articol
|
||||||
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -12)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -12)
|
||||||
AND f.data_act < ADD_MONTHS(TRUNC(SYSDATE), -6)
|
AND f.data_act < ADD_MONTHS(TRUNC(SYSDATE), -6)
|
||||||
AND d.pret > 0
|
AND d.pret > 0
|
||||||
GROUP BY d.id_articol, d.denumire
|
GROUP BY d.id_articol, a.denumire
|
||||||
HAVING COUNT(*) >= 5
|
HAVING COUNT(*) >= 5
|
||||||
),
|
),
|
||||||
preturi_noi AS (
|
preturi_noi AS (
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
d.id_articol,
|
d.id_articol,
|
||||||
AVG(CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS pret_mediu_nou
|
AVG(CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS pret_mediu_nou
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -6)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -6)
|
||||||
AND d.pret > 0
|
AND d.pret > 0
|
||||||
@@ -1436,13 +1465,13 @@ activ AS (
|
|||||||
AND b.luna = EXTRACT(MONTH FROM SYSDATE)
|
AND b.luna = EXTRACT(MONTH FROM SYSDATE)
|
||||||
),
|
),
|
||||||
-- Vanzari si profit din ultimele 12 luni
|
-- Vanzari si profit din ultimele 12 luni
|
||||||
vanzari AS (
|
vanzari_calc AS (
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS total_vanzari,
|
SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS total_vanzari,
|
||||||
SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)) AS profit_brut
|
SUM(d.cantitate * (CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END - d.pret_achizitie)) AS profit_brut
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -12)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -12)
|
||||||
)
|
)
|
||||||
@@ -1454,6 +1483,7 @@ SELECT
|
|||||||
WHEN NVL(dt.datorii_totale, 0) / NULLIF(cp.capital_propriu, 0) > 1 THEN 'ATENTIE'
|
WHEN NVL(dt.datorii_totale, 0) / NULLIF(cp.capital_propriu, 0) > 1 THEN 'ATENTIE'
|
||||||
ELSE 'OK'
|
ELSE 'OK'
|
||||||
END AS status,
|
END AS status,
|
||||||
|
'Datorii / Capital Propriu (cont 16x,40x,42x,44x,46x / cont 10x,11x,12x,117)' AS formula,
|
||||||
'< 1 = bine, > 2 = risc' AS interpretare,
|
'< 1 = bine, > 2 = risc' AS interpretare,
|
||||||
CASE
|
CASE
|
||||||
WHEN NVL(dt.datorii_totale, 0) / NULLIF(cp.capital_propriu, 0) > 2 THEN 'Reduceti datoriile sau cresteti capitalul propriu'
|
WHEN NVL(dt.datorii_totale, 0) / NULLIF(cp.capital_propriu, 0) > 2 THEN 'Reduceti datoriile sau cresteti capitalul propriu'
|
||||||
@@ -1470,6 +1500,7 @@ SELECT
|
|||||||
WHEN NVL(cp.capital_propriu, 0) / NULLIF(ac.total_activ, 0) < 0.5 THEN 'ATENTIE'
|
WHEN NVL(cp.capital_propriu, 0) / NULLIF(ac.total_activ, 0) < 0.5 THEN 'ATENTIE'
|
||||||
ELSE 'OK'
|
ELSE 'OK'
|
||||||
END AS status,
|
END AS status,
|
||||||
|
'Capital Propriu / Total Active (cont 10x,11x,12x,117 / Active clasa 1-5)' AS formula,
|
||||||
'> 0.5 = bine, < 0.3 = risc' AS interpretare,
|
'> 0.5 = bine, < 0.3 = risc' AS interpretare,
|
||||||
CASE
|
CASE
|
||||||
WHEN NVL(cp.capital_propriu, 0) / NULLIF(ac.total_activ, 0) < 0.3 THEN 'Dependenta prea mare de creditori'
|
WHEN NVL(cp.capital_propriu, 0) / NULLIF(ac.total_activ, 0) < 0.3 THEN 'Dependenta prea mare de creditori'
|
||||||
@@ -1486,6 +1517,7 @@ SELECT
|
|||||||
WHEN NVL(dt.datorii_totale, 0) / NULLIF(ac.total_activ, 0) > 0.5 THEN 'ATENTIE'
|
WHEN NVL(dt.datorii_totale, 0) / NULLIF(ac.total_activ, 0) > 0.5 THEN 'ATENTIE'
|
||||||
ELSE 'OK'
|
ELSE 'OK'
|
||||||
END AS status,
|
END AS status,
|
||||||
|
'Datorii / Total Active (cont 16x,40x,42x,44x,46x / Active clasa 1-5)' AS formula,
|
||||||
'< 0.5 = bine, > 0.7 = risc' AS interpretare,
|
'< 0.5 = bine, > 0.7 = risc' AS interpretare,
|
||||||
CASE
|
CASE
|
||||||
WHEN NVL(dt.datorii_totale, 0) / NULLIF(ac.total_activ, 0) > 0.7 THEN 'Risc de insolventa - reduceti datoriile'
|
WHEN NVL(dt.datorii_totale, 0) / NULLIF(ac.total_activ, 0) > 0.7 THEN 'Risc de insolventa - reduceti datoriile'
|
||||||
@@ -1502,13 +1534,14 @@ SELECT
|
|||||||
WHEN NVL(v.profit_brut, 0) * 100 / NULLIF(v.total_vanzari, 0) < 5 THEN 'ATENTIE'
|
WHEN NVL(v.profit_brut, 0) * 100 / NULLIF(v.total_vanzari, 0) < 5 THEN 'ATENTIE'
|
||||||
ELSE 'OK'
|
ELSE 'OK'
|
||||||
END AS status,
|
END AS status,
|
||||||
|
'(Profit Brut / Vanzari) x 100 (din facturi ultimele 12 luni)' AS formula,
|
||||||
'> 5% = bine, < 3% = risc' AS interpretare,
|
'> 5% = bine, < 3% = risc' AS interpretare,
|
||||||
CASE
|
CASE
|
||||||
WHEN NVL(v.profit_brut, 0) * 100 / NULLIF(v.total_vanzari, 0) < 3 THEN 'Marja periculoasa - revizuiti preturile si costurile'
|
WHEN NVL(v.profit_brut, 0) * 100 / NULLIF(v.total_vanzari, 0) < 3 THEN 'Marja periculoasa - revizuiti preturile si costurile'
|
||||||
WHEN NVL(v.profit_brut, 0) * 100 / NULLIF(v.total_vanzari, 0) < 5 THEN 'Optimizati costurile sau cresteti preturile'
|
WHEN NVL(v.profit_brut, 0) * 100 / NULLIF(v.total_vanzari, 0) < 5 THEN 'Optimizati costurile sau cresteti preturile'
|
||||||
ELSE 'Marja acceptabila'
|
ELSE 'Marja acceptabila'
|
||||||
END AS recomandare
|
END AS recomandare
|
||||||
FROM vanzari v
|
FROM vanzari_calc v
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT
|
SELECT
|
||||||
'ROA - Rentabilitatea activelor (%)' AS indicator,
|
'ROA - Rentabilitatea activelor (%)' AS indicator,
|
||||||
@@ -1518,13 +1551,14 @@ SELECT
|
|||||||
WHEN NVL(v.profit_brut, 0) * 100 / NULLIF(ac.total_activ, 0) < 5 THEN 'ATENTIE'
|
WHEN NVL(v.profit_brut, 0) * 100 / NULLIF(ac.total_activ, 0) < 5 THEN 'ATENTIE'
|
||||||
ELSE 'OK'
|
ELSE 'OK'
|
||||||
END AS status,
|
END AS status,
|
||||||
|
'(Profit Brut / Total Active) x 100 (facturi 12 luni / balanta)' AS formula,
|
||||||
'> 5% = bine, < 2% = slab' AS interpretare,
|
'> 5% = bine, < 2% = slab' AS interpretare,
|
||||||
CASE
|
CASE
|
||||||
WHEN NVL(v.profit_brut, 0) * 100 / NULLIF(ac.total_activ, 0) < 2 THEN 'Activele nu genereaza profit suficient - optimizati utilizarea'
|
WHEN NVL(v.profit_brut, 0) * 100 / NULLIF(ac.total_activ, 0) < 2 THEN 'Activele nu genereaza profit suficient - optimizati utilizarea'
|
||||||
WHEN NVL(v.profit_brut, 0) * 100 / NULLIF(ac.total_activ, 0) < 5 THEN 'Cresteti eficienta utilizarii activelor'
|
WHEN NVL(v.profit_brut, 0) * 100 / NULLIF(ac.total_activ, 0) < 5 THEN 'Cresteti eficienta utilizarii activelor'
|
||||||
ELSE 'Activele sunt utilizate eficient'
|
ELSE 'Activele sunt utilizate eficient'
|
||||||
END AS recomandare
|
END AS recomandare
|
||||||
FROM vanzari v, activ ac
|
FROM vanzari_calc v, activ ac
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT
|
SELECT
|
||||||
'Rotatia activelor' AS indicator,
|
'Rotatia activelor' AS indicator,
|
||||||
@@ -1533,12 +1567,13 @@ SELECT
|
|||||||
WHEN NVL(v.total_vanzari, 0) / NULLIF(ac.total_activ, 0) < 0.5 THEN 'ATENTIE'
|
WHEN NVL(v.total_vanzari, 0) / NULLIF(ac.total_activ, 0) < 0.5 THEN 'ATENTIE'
|
||||||
ELSE 'OK'
|
ELSE 'OK'
|
||||||
END AS status,
|
END AS status,
|
||||||
|
'Vanzari / Total Active (facturi 12 luni / balanta)' AS formula,
|
||||||
'> 1 = eficient' AS interpretare,
|
'> 1 = eficient' AS interpretare,
|
||||||
CASE
|
CASE
|
||||||
WHEN NVL(v.total_vanzari, 0) / NULLIF(ac.total_activ, 0) < 0.5 THEN 'Active subutilizate - cresteti vanzarile'
|
WHEN NVL(v.total_vanzari, 0) / NULLIF(ac.total_activ, 0) < 0.5 THEN 'Active subutilizate - cresteti vanzarile'
|
||||||
ELSE 'Activele genereaza vanzari eficient'
|
ELSE 'Activele genereaza vanzari eficient'
|
||||||
END AS recomandare
|
END AS recomandare
|
||||||
FROM vanzari v, activ ac
|
FROM vanzari_calc v, activ ac
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -1586,6 +1621,7 @@ SELECT
|
|||||||
WHEN (NVL(c.cash_total, 0) + NVL(cr.creante_total, 0) + NVL(st.stoc_total, 0)) / NULLIF(dc.datorii_total, 0) < 1.5 THEN 'ATENTIE'
|
WHEN (NVL(c.cash_total, 0) + NVL(cr.creante_total, 0) + NVL(st.stoc_total, 0)) / NULLIF(dc.datorii_total, 0) < 1.5 THEN 'ATENTIE'
|
||||||
ELSE 'OK'
|
ELSE 'OK'
|
||||||
END AS status,
|
END AS status,
|
||||||
|
'(Cash + Creante + Stoc) / Datorii curente (cont 512x,531x + 4111 + vstoc / cont 401x,404x,462x)' AS formula,
|
||||||
'>= 1.5 ideal, >= 1.0 acceptabil' AS interpretare,
|
'>= 1.5 ideal, >= 1.0 acceptabil' AS interpretare,
|
||||||
CASE
|
CASE
|
||||||
WHEN (NVL(c.cash_total, 0) + NVL(cr.creante_total, 0) + NVL(st.stoc_total, 0)) / NULLIF(dc.datorii_total, 0) < 1.0 THEN 'Risc de lichiditate - nu puteti acoperi datoriile curente'
|
WHEN (NVL(c.cash_total, 0) + NVL(cr.creante_total, 0) + NVL(st.stoc_total, 0)) / NULLIF(dc.datorii_total, 0) < 1.0 THEN 'Risc de lichiditate - nu puteti acoperi datoriile curente'
|
||||||
@@ -1602,6 +1638,7 @@ SELECT
|
|||||||
WHEN (NVL(c.cash_total, 0) + NVL(cr.creante_total, 0)) / NULLIF(dc.datorii_total, 0) < 1.0 THEN 'ATENTIE'
|
WHEN (NVL(c.cash_total, 0) + NVL(cr.creante_total, 0)) / NULLIF(dc.datorii_total, 0) < 1.0 THEN 'ATENTIE'
|
||||||
ELSE 'OK'
|
ELSE 'OK'
|
||||||
END AS status,
|
END AS status,
|
||||||
|
'(Cash + Creante) / Datorii curente - fara stocuri (cont 512x,531x + 4111 / cont 401x,404x,462x)' AS formula,
|
||||||
'>= 1.0 ideal, >= 0.7 acceptabil' AS interpretare,
|
'>= 1.0 ideal, >= 0.7 acceptabil' AS interpretare,
|
||||||
CASE
|
CASE
|
||||||
WHEN (NVL(c.cash_total, 0) + NVL(cr.creante_total, 0)) / NULLIF(dc.datorii_total, 0) < 0.7 THEN 'Dependenta de stocuri pentru plata datoriilor'
|
WHEN (NVL(c.cash_total, 0) + NVL(cr.creante_total, 0)) / NULLIF(dc.datorii_total, 0) < 0.7 THEN 'Dependenta de stocuri pentru plata datoriilor'
|
||||||
@@ -1618,6 +1655,7 @@ SELECT
|
|||||||
WHEN NVL(c.cash_total, 0) / NULLIF(dc.datorii_total, 0) < 0.2 THEN 'ATENTIE'
|
WHEN NVL(c.cash_total, 0) / NULLIF(dc.datorii_total, 0) < 0.2 THEN 'ATENTIE'
|
||||||
ELSE 'OK'
|
ELSE 'OK'
|
||||||
END AS status,
|
END AS status,
|
||||||
|
'Cash / Datorii curente (cont 512x,531x / cont 401x,404x,462x)' AS formula,
|
||||||
'>= 0.2 ideal, >= 0.1 minim' AS interpretare,
|
'>= 0.2 ideal, >= 0.1 minim' AS interpretare,
|
||||||
CASE
|
CASE
|
||||||
WHEN NVL(c.cash_total, 0) / NULLIF(dc.datorii_total, 0) < 0.1 THEN 'Cash insuficient - risc la plati urgente'
|
WHEN NVL(c.cash_total, 0) / NULLIF(dc.datorii_total, 0) < 0.1 THEN 'Cash insuficient - risc la plati urgente'
|
||||||
@@ -1633,6 +1671,7 @@ SELECT
|
|||||||
WHEN (NVL(c.cash_total, 0) + NVL(cr.creante_total, 0) + NVL(st.stoc_total, 0)) - NVL(dc.datorii_total, 0) < 0 THEN 'ALERTA'
|
WHEN (NVL(c.cash_total, 0) + NVL(cr.creante_total, 0) + NVL(st.stoc_total, 0)) - NVL(dc.datorii_total, 0) < 0 THEN 'ALERTA'
|
||||||
ELSE 'OK'
|
ELSE 'OK'
|
||||||
END AS status,
|
END AS status,
|
||||||
|
'Active curente - Datorii curente (Cash + Creante + Stoc - Datorii furnizori)' AS formula,
|
||||||
'Pozitiv = OK, Negativ = probleme' AS interpretare,
|
'Pozitiv = OK, Negativ = probleme' AS interpretare,
|
||||||
CASE
|
CASE
|
||||||
WHEN (NVL(c.cash_total, 0) + NVL(cr.creante_total, 0) + NVL(st.stoc_total, 0)) - NVL(dc.datorii_total, 0) < 0 THEN 'Fond de rulment negativ - necesita finantare'
|
WHEN (NVL(c.cash_total, 0) + NVL(cr.creante_total, 0) + NVL(st.stoc_total, 0)) - NVL(dc.datorii_total, 0) < 0 THEN 'Fond de rulment negativ - necesita finantare'
|
||||||
@@ -1957,10 +1996,11 @@ DSO_DPO_YOY = """
|
|||||||
WITH
|
WITH
|
||||||
-- Metrici curente
|
-- Metrici curente
|
||||||
vanzari_curente AS (
|
vanzari_curente AS (
|
||||||
SELECT SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS total_vanzari
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
FROM fact_vfacturi2 f
|
SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS total_vanzari
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
FROM vanzari f
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -12)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -12)
|
||||||
),
|
),
|
||||||
@@ -1986,10 +2026,11 @@ sold_furnizori_curent AS (
|
|||||||
),
|
),
|
||||||
-- Metrici anterioare (aproximare - vanzari an anterior)
|
-- Metrici anterioare (aproximare - vanzari an anterior)
|
||||||
vanzari_anterioare AS (
|
vanzari_anterioare AS (
|
||||||
SELECT SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS total_vanzari
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
FROM fact_vfacturi2 f
|
SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS total_vanzari
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
FROM vanzari f
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -24)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -24)
|
||||||
AND f.data_act < ADD_MONTHS(TRUNC(SYSDATE), -12)
|
AND f.data_act < ADD_MONTHS(TRUNC(SYSDATE), -12)
|
||||||
@@ -2057,12 +2098,12 @@ CONCENTRARE_RISC_YOY = """
|
|||||||
WITH
|
WITH
|
||||||
-- Single scan for current year: compute total + per-client with ranking
|
-- Single scan for current year: compute total + per-client with ranking
|
||||||
vanzari_curent AS (
|
vanzari_curent AS (
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
f.id_part,
|
f.id_part,
|
||||||
SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS vanzari
|
SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS vanzari
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -12)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -12)
|
||||||
GROUP BY f.id_part
|
GROUP BY f.id_part
|
||||||
@@ -2081,12 +2122,12 @@ metrics_curent AS (
|
|||||||
),
|
),
|
||||||
-- Single scan for previous year
|
-- Single scan for previous year
|
||||||
vanzari_anterior AS (
|
vanzari_anterior AS (
|
||||||
SELECT
|
SELECT /*+ LEADING(f d) USE_NL(d) INDEX(f IDX_VANZARI_NR) */
|
||||||
f.id_part,
|
f.id_part,
|
||||||
SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS vanzari
|
SUM(d.cantitate * CASE WHEN d.pret_cu_tva = 1 THEN d.pret / (1 + d.proc_tvav/100) ELSE d.pret END) AS vanzari
|
||||||
FROM fact_vfacturi2 f
|
FROM vanzari f
|
||||||
JOIN fact_vfacturi_detalii d ON d.id_vanzare = f.id_vanzare
|
JOIN vanzari_detalii d ON d.id_vanzare = f.id_vanzare AND d.sters = 0
|
||||||
WHERE f.sters = 0 AND d.sters = 0
|
WHERE f.sters = 0
|
||||||
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
AND f.tip > 0 AND f.tip NOT IN (7, 8, 9, 24)
|
||||||
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -24)
|
AND f.data_act >= ADD_MONTHS(TRUNC(SYSDATE), -24)
|
||||||
AND f.data_act < ADD_MONTHS(TRUNC(SYSDATE), -12)
|
AND f.data_act < ADD_MONTHS(TRUNC(SYSDATE), -12)
|
||||||
|
|||||||
@@ -5,11 +5,45 @@ Generates Excel and PDF reports from query results
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import unicodedata
|
||||||
|
import re
|
||||||
import matplotlib
|
import matplotlib
|
||||||
matplotlib.use('Agg') # Non-interactive backend
|
matplotlib.use('Agg') # Non-interactive backend
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import matplotlib.ticker as ticker
|
import matplotlib.ticker as ticker
|
||||||
from openpyxl import Workbook
|
from openpyxl import Workbook
|
||||||
|
|
||||||
|
|
||||||
|
# Romanian diacritics mapping for PDF (Helvetica doesn't support them)
|
||||||
|
DIACRITICS_MAP = {
|
||||||
|
'ă': 'a', 'Ă': 'A',
|
||||||
|
'â': 'a', 'Â': 'A',
|
||||||
|
'î': 'i', 'Î': 'I',
|
||||||
|
'ș': 's', 'Ș': 'S',
|
||||||
|
'ş': 's', 'Ş': 'S', # Alternative encoding
|
||||||
|
'ț': 't', 'Ț': 'T',
|
||||||
|
'ţ': 't', 'Ţ': 'T', # Alternative encoding
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def remove_diacritics(text):
|
||||||
|
"""Remove Romanian diacritics from text for PDF compatibility"""
|
||||||
|
if not isinstance(text, str):
|
||||||
|
return text
|
||||||
|
for diacritic, replacement in DIACRITICS_MAP.items():
|
||||||
|
text = text.replace(diacritic, replacement)
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_for_pdf(value, max_length=None):
|
||||||
|
"""Sanitize value for PDF: remove diacritics and optionally truncate"""
|
||||||
|
if value is None:
|
||||||
|
return ''
|
||||||
|
text = str(value)
|
||||||
|
text = remove_diacritics(text)
|
||||||
|
if max_length and len(text) > max_length:
|
||||||
|
text = text[:max_length-3] + '...'
|
||||||
|
return text
|
||||||
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
|
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
|
||||||
from openpyxl.utils.dataframe import dataframe_to_rows
|
from openpyxl.utils.dataframe import dataframe_to_rows
|
||||||
from openpyxl.chart import BarChart, LineChart, PieChart, Reference
|
from openpyxl.chart import BarChart, LineChart, PieChart, Reference
|
||||||
@@ -47,6 +81,14 @@ class ExcelReportGenerator:
|
|||||||
top=Side(style='thin'),
|
top=Side(style='thin'),
|
||||||
bottom=Side(style='thin')
|
bottom=Side(style='thin')
|
||||||
)
|
)
|
||||||
|
# Style for manager-friendly explanations
|
||||||
|
self.explanation_fill = PatternFill(start_color='F8F9FA', end_color='F8F9FA', fill_type='solid')
|
||||||
|
self.explanation_border = Border(
|
||||||
|
left=Side(style='thin', color='DEE2E6'),
|
||||||
|
right=Side(style='thin', color='DEE2E6'),
|
||||||
|
top=Side(style='thin', color='DEE2E6'),
|
||||||
|
bottom=Side(style='thin', color='DEE2E6')
|
||||||
|
)
|
||||||
|
|
||||||
def add_sheet(self, name: str, df: pd.DataFrame, title: str = None, description: str = None, legend: dict = None):
|
def add_sheet(self, name: str, df: pd.DataFrame, title: str = None, description: str = None, legend: dict = None):
|
||||||
"""Add a formatted sheet to the workbook with optional legend"""
|
"""Add a formatted sheet to the workbook with optional legend"""
|
||||||
@@ -87,7 +129,7 @@ class ExcelReportGenerator:
|
|||||||
start_row += 1
|
start_row += 1
|
||||||
|
|
||||||
if df is None or df.empty:
|
if df is None or df.empty:
|
||||||
ws.cell(row=start_row, column=1, value="Nu există date pentru această analiză.")
|
ws.cell(row=start_row, column=1, value="Nu exista date pentru această analiză.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Write headers
|
# Write headers
|
||||||
@@ -304,6 +346,7 @@ class ExcelReportGenerator:
|
|||||||
section_title = section.get('title', '')
|
section_title = section.get('title', '')
|
||||||
df = section.get('df')
|
df = section.get('df')
|
||||||
description = section.get('description', '')
|
description = section.get('description', '')
|
||||||
|
explanation = section.get('explanation', '')
|
||||||
legend = section.get('legend', {})
|
legend = section.get('legend', {})
|
||||||
|
|
||||||
# Section separator
|
# Section separator
|
||||||
@@ -315,7 +358,23 @@ class ExcelReportGenerator:
|
|||||||
cell.font = Font(bold=True, color='FFFFFF', size=11)
|
cell.font = Font(bold=True, color='FFFFFF', size=11)
|
||||||
start_row += 1
|
start_row += 1
|
||||||
|
|
||||||
# Section description
|
# Manager-friendly explanation box (if provided)
|
||||||
|
if explanation:
|
||||||
|
# Merge cells for explanation box (columns 1-8)
|
||||||
|
ws.merge_cells(start_row=start_row, start_column=1, end_row=start_row, end_column=8)
|
||||||
|
cell = ws.cell(row=start_row, column=1, value=f"💡 {explanation}")
|
||||||
|
cell.fill = self.explanation_fill
|
||||||
|
cell.border = self.explanation_border
|
||||||
|
cell.font = Font(size=9, color='555555')
|
||||||
|
cell.alignment = Alignment(wrap_text=True, vertical='center')
|
||||||
|
ws.row_dimensions[start_row].height = 40 # Taller row for wrapped text
|
||||||
|
# Apply border to merged cells
|
||||||
|
for col in range(1, 9):
|
||||||
|
ws.cell(row=start_row, column=col).fill = self.explanation_fill
|
||||||
|
ws.cell(row=start_row, column=col).border = self.explanation_border
|
||||||
|
start_row += 1
|
||||||
|
|
||||||
|
# Section description (technical)
|
||||||
if description:
|
if description:
|
||||||
ws.cell(row=start_row, column=1, value=description)
|
ws.cell(row=start_row, column=1, value=description)
|
||||||
ws.cell(row=start_row, column=1).font = Font(italic=True, size=9, color='666666')
|
ws.cell(row=start_row, column=1).font = Font(italic=True, size=9, color='666666')
|
||||||
@@ -325,7 +384,7 @@ class ExcelReportGenerator:
|
|||||||
|
|
||||||
# Check for empty data
|
# Check for empty data
|
||||||
if df is None or df.empty:
|
if df is None or df.empty:
|
||||||
ws.cell(row=start_row, column=1, value="Nu există date pentru această secțiune.")
|
ws.cell(row=start_row, column=1, value="Nu exista date pentru această secțiune.")
|
||||||
ws.cell(row=start_row, column=1).font = Font(italic=True, color='999999')
|
ws.cell(row=start_row, column=1).font = Font(italic=True, color='999999')
|
||||||
start_row += 3
|
start_row += 3
|
||||||
continue
|
continue
|
||||||
@@ -457,126 +516,192 @@ class PDFReportGenerator:
|
|||||||
fontSize=8,
|
fontSize=8,
|
||||||
textColor=colors.gray
|
textColor=colors.gray
|
||||||
))
|
))
|
||||||
|
# Table cell styles with word wrapping
|
||||||
|
self.styles.add(ParagraphStyle(
|
||||||
|
name='TableCell',
|
||||||
|
parent=self.styles['Normal'],
|
||||||
|
fontSize=7,
|
||||||
|
leading=9,
|
||||||
|
wordWrap='CJK',
|
||||||
|
))
|
||||||
|
self.styles.add(ParagraphStyle(
|
||||||
|
name='TableCellBold',
|
||||||
|
parent=self.styles['Normal'],
|
||||||
|
fontSize=7,
|
||||||
|
leading=9,
|
||||||
|
fontName='Helvetica-Bold',
|
||||||
|
))
|
||||||
|
# Explanation style for manager-friendly text boxes
|
||||||
|
self.styles.add(ParagraphStyle(
|
||||||
|
name='Explanation',
|
||||||
|
parent=self.styles['Normal'],
|
||||||
|
fontSize=9,
|
||||||
|
textColor=colors.HexColor('#555555'),
|
||||||
|
backColor=colors.HexColor('#F8F9FA'),
|
||||||
|
borderPadding=8,
|
||||||
|
spaceBefore=5,
|
||||||
|
spaceAfter=10,
|
||||||
|
))
|
||||||
|
|
||||||
|
def make_cell_paragraph(self, text, bold=False):
|
||||||
|
"""Create a Paragraph for table cell with word wrapping"""
|
||||||
|
style = self.styles['TableCellBold'] if bold else self.styles['TableCell']
|
||||||
|
return Paragraph(sanitize_for_pdf(text), style)
|
||||||
|
|
||||||
|
def add_explanation(self, text: str):
|
||||||
|
"""Add a manager-friendly explanation box before a section"""
|
||||||
|
# Create a table with background color to simulate a box
|
||||||
|
explanation_para = Paragraph(sanitize_for_pdf(text), self.styles['Explanation'])
|
||||||
|
box_table = Table([[explanation_para]], colWidths=[16*cm])
|
||||||
|
box_table.setStyle(TableStyle([
|
||||||
|
('BACKGROUND', (0, 0), (-1, -1), colors.HexColor('#F8F9FA')),
|
||||||
|
('BOX', (0, 0), (-1, -1), 0.5, colors.HexColor('#DEE2E6')),
|
||||||
|
('TOPPADDING', (0, 0), (-1, -1), 8),
|
||||||
|
('BOTTOMPADDING', (0, 0), (-1, -1), 8),
|
||||||
|
('LEFTPADDING', (0, 0), (-1, -1), 10),
|
||||||
|
('RIGHTPADDING', (0, 0), (-1, -1), 10),
|
||||||
|
]))
|
||||||
|
self.elements.append(box_table)
|
||||||
|
self.elements.append(Spacer(1, 0.3*cm))
|
||||||
|
|
||||||
def add_title_page(self, report_date: datetime = None):
|
def add_title_page(self, report_date: datetime = None):
|
||||||
"""Add title page"""
|
"""Add title page"""
|
||||||
if report_date is None:
|
if report_date is None:
|
||||||
report_date = datetime.now()
|
report_date = datetime.now()
|
||||||
|
|
||||||
self.elements.append(Spacer(1, 3*cm))
|
self.elements.append(Spacer(1, 3*cm))
|
||||||
self.elements.append(Paragraph(self.company_name, self.styles['CustomTitle']))
|
self.elements.append(Paragraph(remove_diacritics(self.company_name), self.styles['CustomTitle']))
|
||||||
self.elements.append(Spacer(1, 1*cm))
|
self.elements.append(Spacer(1, 1*cm))
|
||||||
self.elements.append(Paragraph(
|
self.elements.append(Paragraph(
|
||||||
f"Raport generat: {report_date.strftime('%d %B %Y, %H:%M')}",
|
f"Raport generat: {report_date.strftime('%d %B %Y, %H:%M')}",
|
||||||
self.styles['Normal']
|
self.styles['Normal']
|
||||||
))
|
))
|
||||||
self.elements.append(Paragraph(
|
self.elements.append(Paragraph(
|
||||||
"Perioada analizată: Ultimele 12 luni",
|
"Perioada analizata: Ultimele 12 luni",
|
||||||
self.styles['Normal']
|
self.styles['Normal']
|
||||||
))
|
))
|
||||||
self.elements.append(PageBreak())
|
self.elements.append(PageBreak())
|
||||||
|
|
||||||
def add_kpi_section(self, kpi_df: pd.DataFrame):
|
def add_kpi_section(self, kpi_df: pd.DataFrame):
|
||||||
"""Add KPI summary section"""
|
"""Add KPI summary section"""
|
||||||
self.elements.append(Paragraph("📊 Sumar Executiv - KPIs", self.styles['SectionHeader']))
|
self.elements.append(Paragraph("Sumar Executiv - KPIs", self.styles['SectionHeader']))
|
||||||
|
|
||||||
if kpi_df is not None and not kpi_df.empty:
|
if kpi_df is not None and not kpi_df.empty:
|
||||||
data = [['Indicator', 'Valoare', 'UM']]
|
# Header row with bold style
|
||||||
|
header_style = ParagraphStyle(
|
||||||
|
'TableHeaderCell', parent=self.styles['Normal'],
|
||||||
|
fontSize=9, fontName='Helvetica-Bold', textColor=colors.white
|
||||||
|
)
|
||||||
|
data = [[
|
||||||
|
Paragraph('Indicator', header_style),
|
||||||
|
Paragraph('Valoare', header_style),
|
||||||
|
Paragraph('UM', header_style)
|
||||||
|
]]
|
||||||
for _, row in kpi_df.iterrows():
|
for _, row in kpi_df.iterrows():
|
||||||
data.append([
|
data.append([
|
||||||
str(row.get('INDICATOR', '')),
|
self.make_cell_paragraph(row.get('INDICATOR', '')),
|
||||||
str(row.get('VALOARE', '')),
|
self.make_cell_paragraph(row.get('VALOARE', '')),
|
||||||
str(row.get('UM', ''))
|
self.make_cell_paragraph(row.get('UM', ''))
|
||||||
])
|
])
|
||||||
|
|
||||||
table = Table(data, colWidths=[8*cm, 4*cm, 2*cm])
|
table = Table(data, colWidths=[8*cm, 4*cm, 2*cm])
|
||||||
table.setStyle(TableStyle([
|
table.setStyle(TableStyle([
|
||||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#366092')),
|
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#366092')),
|
||||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
||||||
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
||||||
('ALIGN', (1, 1), (1, -1), 'RIGHT'),
|
('ALIGN', (1, 1), (1, -1), 'RIGHT'),
|
||||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||||
('FONTSIZE', (0, 0), (-1, 0), 10),
|
|
||||||
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
||||||
|
('TOPPADDING', (0, 1), (-1, -1), 4),
|
||||||
|
('BOTTOMPADDING', (0, 1), (-1, -1), 4),
|
||||||
('GRID', (0, 0), (-1, -1), 0.5, colors.gray),
|
('GRID', (0, 0), (-1, -1), 0.5, colors.gray),
|
||||||
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#f0f0f0')])
|
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#f0f0f0')])
|
||||||
]))
|
]))
|
||||||
self.elements.append(table)
|
self.elements.append(table)
|
||||||
|
|
||||||
self.elements.append(Spacer(1, 0.5*cm))
|
self.elements.append(Spacer(1, 0.5*cm))
|
||||||
|
|
||||||
def add_alerts_section(self, alerts_data: dict):
|
def add_alerts_section(self, alerts_data: dict):
|
||||||
"""Add critical alerts section"""
|
"""Add critical alerts section"""
|
||||||
self.elements.append(Paragraph("🚨 Alerte Critice", self.styles['SectionHeader']))
|
self.elements.append(Paragraph("Alerte Critice", self.styles['SectionHeader']))
|
||||||
|
|
||||||
# Vânzări sub cost
|
# Vanzari sub cost
|
||||||
if 'vanzari_sub_cost' in alerts_data and not alerts_data['vanzari_sub_cost'].empty:
|
if 'vanzari_sub_cost' in alerts_data and not alerts_data['vanzari_sub_cost'].empty:
|
||||||
df = alerts_data['vanzari_sub_cost']
|
df = alerts_data['vanzari_sub_cost']
|
||||||
count = len(df)
|
count = len(df)
|
||||||
total_loss = df['PIERDERE'].sum() if 'PIERDERE' in df.columns else 0
|
total_loss = df['PIERDERE'].sum() if 'PIERDERE' in df.columns else 0
|
||||||
|
|
||||||
self.elements.append(Paragraph(
|
self.elements.append(Paragraph(
|
||||||
f"⛔ VÂNZĂRI SUB COST: {count} tranzacții cu pierdere totală de {abs(total_loss):,.2f} RON",
|
f"VANZARI SUB COST: {count} tranzactii cu pierdere totala de {abs(total_loss):,.2f} RON",
|
||||||
self.styles['AlertHeader']
|
self.styles['AlertHeader']
|
||||||
))
|
))
|
||||||
|
|
||||||
# Show top 5
|
# Show top 5
|
||||||
top5 = df.head(5)
|
top5 = df.head(5)
|
||||||
if not top5.empty:
|
if not top5.empty:
|
||||||
cols_to_show = ['FACTURA', 'CLIENT', 'PRODUS', 'PIERDERE']
|
cols_to_show = ['FACTURA', 'CLIENT', 'PRODUS', 'PIERDERE']
|
||||||
cols_to_show = [c for c in cols_to_show if c in top5.columns]
|
cols_to_show = [c for c in cols_to_show if c in top5.columns]
|
||||||
if cols_to_show:
|
if cols_to_show:
|
||||||
data = [cols_to_show] + top5[cols_to_show].values.tolist()
|
# Header with Paragraph
|
||||||
|
data = [[self.make_cell_paragraph(c, bold=True) for c in cols_to_show]]
|
||||||
|
for _, row in top5.iterrows():
|
||||||
|
row_data = [self.make_cell_paragraph(row.get(c, '')) for c in cols_to_show]
|
||||||
|
data.append(row_data)
|
||||||
table = Table(data, colWidths=[3*cm, 4*cm, 5*cm, 2*cm])
|
table = Table(data, colWidths=[3*cm, 4*cm, 5*cm, 2*cm])
|
||||||
table.setStyle(TableStyle([
|
table.setStyle(TableStyle([
|
||||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#c0392b')),
|
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#c0392b')),
|
||||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
||||||
('FONTSIZE', (0, 0), (-1, -1), 8),
|
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||||
|
('TOPPADDING', (0, 0), (-1, -1), 4),
|
||||||
|
('BOTTOMPADDING', (0, 0), (-1, -1), 4),
|
||||||
('GRID', (0, 0), (-1, -1), 0.5, colors.gray),
|
('GRID', (0, 0), (-1, -1), 0.5, colors.gray),
|
||||||
]))
|
]))
|
||||||
self.elements.append(table)
|
self.elements.append(table)
|
||||||
|
|
||||||
# Clienți cu marjă mică
|
# Clienti cu marja mica
|
||||||
if 'clienti_marja_mica' in alerts_data and not alerts_data['clienti_marja_mica'].empty:
|
if 'clienti_marja_mica' in alerts_data and not alerts_data['clienti_marja_mica'].empty:
|
||||||
df = alerts_data['clienti_marja_mica']
|
df = alerts_data['clienti_marja_mica']
|
||||||
count = len(df)
|
count = len(df)
|
||||||
|
|
||||||
self.elements.append(Spacer(1, 0.3*cm))
|
self.elements.append(Spacer(1, 0.3*cm))
|
||||||
self.elements.append(Paragraph(
|
self.elements.append(Paragraph(
|
||||||
f"⚠️ CLIENȚI CU MARJĂ MICĂ (<15%): {count} clienți necesită renegociere",
|
f"CLIENTI CU MARJA MICA (<15%): {count} clienti necesita renegociere",
|
||||||
self.styles['AlertHeader']
|
self.styles['AlertHeader']
|
||||||
))
|
))
|
||||||
|
|
||||||
top5 = df.head(5)
|
top5 = df.head(5)
|
||||||
if not top5.empty:
|
if not top5.empty:
|
||||||
cols_to_show = ['CLIENT', 'VANZARI_FARA_TVA', 'PROCENT_MARJA']
|
cols_to_show = ['CLIENT', 'VANZARI_FARA_TVA', 'PROCENT_MARJA']
|
||||||
cols_to_show = [c for c in cols_to_show if c in top5.columns]
|
cols_to_show = [c for c in cols_to_show if c in top5.columns]
|
||||||
if cols_to_show:
|
if cols_to_show:
|
||||||
data = [cols_to_show]
|
# Header with Paragraph
|
||||||
|
data = [[self.make_cell_paragraph(c, bold=True) for c in cols_to_show]]
|
||||||
for _, row in top5.iterrows():
|
for _, row in top5.iterrows():
|
||||||
data.append([
|
data.append([
|
||||||
str(row.get('CLIENT', ''))[:30],
|
self.make_cell_paragraph(row.get('CLIENT', '')),
|
||||||
f"{row.get('VANZARI_FARA_TVA', 0):,.0f}",
|
self.make_cell_paragraph(f"{row.get('VANZARI_FARA_TVA', 0):,.0f}"),
|
||||||
f"{row.get('PROCENT_MARJA', 0):.1f}%"
|
self.make_cell_paragraph(f"{row.get('PROCENT_MARJA', 0):.1f}%")
|
||||||
])
|
])
|
||||||
table = Table(data, colWidths=[6*cm, 3*cm, 2*cm])
|
table = Table(data, colWidths=[6*cm, 3*cm, 2*cm])
|
||||||
table.setStyle(TableStyle([
|
table.setStyle(TableStyle([
|
||||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#e67e22')),
|
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#e67e22')),
|
||||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
||||||
('FONTSIZE', (0, 0), (-1, -1), 8),
|
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||||
|
('TOPPADDING', (0, 0), (-1, -1), 4),
|
||||||
|
('BOTTOMPADDING', (0, 0), (-1, -1), 4),
|
||||||
('GRID', (0, 0), (-1, -1), 0.5, colors.gray),
|
('GRID', (0, 0), (-1, -1), 0.5, colors.gray),
|
||||||
]))
|
]))
|
||||||
self.elements.append(table)
|
self.elements.append(table)
|
||||||
|
|
||||||
self.elements.append(Spacer(1, 0.5*cm))
|
self.elements.append(Spacer(1, 0.5*cm))
|
||||||
|
|
||||||
def add_chart_image(self, fig, title: str):
|
def add_chart_image(self, fig, title: str):
|
||||||
"""Add a matplotlib figure as image"""
|
"""Add a matplotlib figure as image"""
|
||||||
self.elements.append(Paragraph(title, self.styles['SectionHeader']))
|
self.elements.append(Paragraph(remove_diacritics(title), self.styles['SectionHeader']))
|
||||||
|
|
||||||
# Save figure to buffer
|
# Save figure to buffer
|
||||||
buf = io.BytesIO()
|
buf = io.BytesIO()
|
||||||
fig.savefig(buf, format='png', dpi=150, bbox_inches='tight')
|
fig.savefig(buf, format='png', dpi=200, bbox_inches='tight')
|
||||||
buf.seek(0)
|
buf.seek(0)
|
||||||
|
|
||||||
# Add to PDF
|
# Add to PDF
|
||||||
@@ -587,60 +712,60 @@ class PDFReportGenerator:
|
|||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def add_table_section(self, title: str, df: pd.DataFrame, columns: list = None, max_rows: int = 15):
|
def add_table_section(self, title: str, df: pd.DataFrame, columns: list = None, max_rows: int = 15):
|
||||||
"""Add a data table section"""
|
"""Add a data table section with word-wrapped cells"""
|
||||||
self.elements.append(Paragraph(title, self.styles['SectionHeader']))
|
self.elements.append(Paragraph(remove_diacritics(title), self.styles['SectionHeader']))
|
||||||
|
|
||||||
if df is None or df.empty:
|
if df is None or df.empty:
|
||||||
self.elements.append(Paragraph("Nu există date.", self.styles['Normal']))
|
self.elements.append(Paragraph("Nu exista date.", self.styles['Normal']))
|
||||||
return
|
return
|
||||||
|
|
||||||
# Select columns
|
# Select columns
|
||||||
if columns:
|
if columns:
|
||||||
cols = [c for c in columns if c in df.columns]
|
cols = [c for c in columns if c in df.columns]
|
||||||
else:
|
else:
|
||||||
cols = list(df.columns)[:6] # Max 6 columns for PDF
|
cols = list(df.columns)[:6] # Max 6 columns for PDF
|
||||||
|
|
||||||
if not cols:
|
if not cols:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Prepare data
|
# Prepare data with Paragraph cells for word wrapping
|
||||||
data = [cols]
|
data = [[self.make_cell_paragraph(c, bold=True) for c in cols]]
|
||||||
for _, row in df.head(max_rows).iterrows():
|
for _, row in df.head(max_rows).iterrows():
|
||||||
row_data = []
|
row_data = []
|
||||||
for col in cols:
|
for col in cols:
|
||||||
val = row.get(col, '')
|
val = row.get(col, '')
|
||||||
if isinstance(val, float):
|
if isinstance(val, float):
|
||||||
row_data.append(f"{val:,.2f}")
|
row_data.append(self.make_cell_paragraph(f"{val:,.2f}"))
|
||||||
elif isinstance(val, int):
|
elif isinstance(val, int):
|
||||||
row_data.append(f"{val:,}")
|
row_data.append(self.make_cell_paragraph(f"{val:,}"))
|
||||||
else:
|
else:
|
||||||
row_data.append(str(val)[:25]) # Truncate long strings
|
row_data.append(self.make_cell_paragraph(val))
|
||||||
data.append(row_data)
|
data.append(row_data)
|
||||||
|
|
||||||
# Calculate column widths
|
# Calculate column widths
|
||||||
n_cols = len(cols)
|
n_cols = len(cols)
|
||||||
col_width = 16*cm / n_cols
|
col_width = 16*cm / n_cols
|
||||||
|
|
||||||
table = Table(data, colWidths=[col_width] * n_cols)
|
table = Table(data, colWidths=[col_width] * n_cols)
|
||||||
table.setStyle(TableStyle([
|
table.setStyle(TableStyle([
|
||||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#366092')),
|
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#366092')),
|
||||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
||||||
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
||||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||||
('FONTSIZE', (0, 0), (-1, -1), 7),
|
('TOPPADDING', (0, 0), (-1, -1), 4),
|
||||||
('BOTTOMPADDING', (0, 0), (-1, 0), 8),
|
('BOTTOMPADDING', (0, 0), (-1, -1), 4),
|
||||||
('GRID', (0, 0), (-1, -1), 0.5, colors.gray),
|
('GRID', (0, 0), (-1, -1), 0.5, colors.gray),
|
||||||
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#f5f5f5')])
|
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#f5f5f5')])
|
||||||
]))
|
]))
|
||||||
|
|
||||||
self.elements.append(table)
|
self.elements.append(table)
|
||||||
|
|
||||||
if len(df) > max_rows:
|
if len(df) > max_rows:
|
||||||
self.elements.append(Paragraph(
|
self.elements.append(Paragraph(
|
||||||
f"... și încă {len(df) - max_rows} înregistrări (vezi Excel pentru lista completă)",
|
f"... si inca {len(df) - max_rows} inregistrari (vezi Excel pentru lista completa)",
|
||||||
self.styles['SmallText']
|
self.styles['SmallText']
|
||||||
))
|
))
|
||||||
|
|
||||||
self.elements.append(Spacer(1, 0.5*cm))
|
self.elements.append(Spacer(1, 0.5*cm))
|
||||||
|
|
||||||
def add_page_break(self):
|
def add_page_break(self):
|
||||||
@@ -649,7 +774,7 @@ class PDFReportGenerator:
|
|||||||
|
|
||||||
def add_consolidated_page(self, page_title: str, sections: list):
|
def add_consolidated_page(self, page_title: str, sections: list):
|
||||||
"""
|
"""
|
||||||
Add a consolidated PDF page with multiple sections.
|
Add a consolidated PDF page with multiple sections and word-wrapped cells.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
page_title: Main title for the page
|
page_title: Main title for the page
|
||||||
@@ -660,7 +785,7 @@ class PDFReportGenerator:
|
|||||||
- 'max_rows': Max rows to display (default 15)
|
- 'max_rows': Max rows to display (default 15)
|
||||||
"""
|
"""
|
||||||
# Page title
|
# Page title
|
||||||
self.elements.append(Paragraph(page_title, self.styles['SectionHeader']))
|
self.elements.append(Paragraph(remove_diacritics(page_title), self.styles['SectionHeader']))
|
||||||
self.elements.append(Spacer(1, 0.3*cm))
|
self.elements.append(Spacer(1, 0.3*cm))
|
||||||
|
|
||||||
for section in sections:
|
for section in sections:
|
||||||
@@ -678,10 +803,10 @@ class PDFReportGenerator:
|
|||||||
spaceAfter=5,
|
spaceAfter=5,
|
||||||
textColor=colors.HexColor('#2C3E50')
|
textColor=colors.HexColor('#2C3E50')
|
||||||
)
|
)
|
||||||
self.elements.append(Paragraph(section_title, subsection_style))
|
self.elements.append(Paragraph(remove_diacritics(section_title), subsection_style))
|
||||||
|
|
||||||
if df is None or df.empty:
|
if df is None or df.empty:
|
||||||
self.elements.append(Paragraph("Nu există date.", self.styles['Normal']))
|
self.elements.append(Paragraph("Nu exista date.", self.styles['Normal']))
|
||||||
self.elements.append(Spacer(1, 0.3*cm))
|
self.elements.append(Spacer(1, 0.3*cm))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -694,18 +819,18 @@ class PDFReportGenerator:
|
|||||||
if not cols:
|
if not cols:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Prepare data
|
# Prepare data with Paragraph cells for word wrapping
|
||||||
data = [cols]
|
data = [[self.make_cell_paragraph(c, bold=True) for c in cols]]
|
||||||
for _, row in df.head(max_rows).iterrows():
|
for _, row in df.head(max_rows).iterrows():
|
||||||
row_data = []
|
row_data = []
|
||||||
for col in cols:
|
for col in cols:
|
||||||
val = row.get(col, '')
|
val = row.get(col, '')
|
||||||
if isinstance(val, float):
|
if isinstance(val, float):
|
||||||
row_data.append(f"{val:,.2f}")
|
row_data.append(self.make_cell_paragraph(f"{val:,.2f}"))
|
||||||
elif isinstance(val, int):
|
elif isinstance(val, int):
|
||||||
row_data.append(f"{val:,}")
|
row_data.append(self.make_cell_paragraph(f"{val:,}"))
|
||||||
else:
|
else:
|
||||||
row_data.append(str(val)[:30]) # Truncate long strings
|
row_data.append(self.make_cell_paragraph(val))
|
||||||
data.append(row_data)
|
data.append(row_data)
|
||||||
|
|
||||||
# Calculate column widths
|
# Calculate column widths
|
||||||
@@ -719,9 +844,9 @@ class PDFReportGenerator:
|
|||||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#366092')),
|
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#366092')),
|
||||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
||||||
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
||||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||||
('FONTSIZE', (0, 0), (-1, -1), 7),
|
('TOPPADDING', (0, 0), (-1, -1), 4),
|
||||||
('BOTTOMPADDING', (0, 0), (-1, 0), 6),
|
('BOTTOMPADDING', (0, 0), (-1, -1), 4),
|
||||||
('GRID', (0, 0), (-1, -1), 0.5, colors.gray),
|
('GRID', (0, 0), (-1, -1), 0.5, colors.gray),
|
||||||
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#f5f5f5')])
|
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#f5f5f5')])
|
||||||
]
|
]
|
||||||
@@ -743,7 +868,7 @@ class PDFReportGenerator:
|
|||||||
|
|
||||||
if len(df) > max_rows:
|
if len(df) > max_rows:
|
||||||
self.elements.append(Paragraph(
|
self.elements.append(Paragraph(
|
||||||
f"... și încă {len(df) - max_rows} înregistrări",
|
f"... si inca {len(df) - max_rows} inregistrari",
|
||||||
self.styles['SmallText']
|
self.styles['SmallText']
|
||||||
))
|
))
|
||||||
|
|
||||||
@@ -764,11 +889,11 @@ class PDFReportGenerator:
|
|||||||
df_sorted = df_sorted.sort_values('_order').head(7)
|
df_sorted = df_sorted.sort_values('_order').head(7)
|
||||||
|
|
||||||
for _, row in df_sorted.iterrows():
|
for _, row in df_sorted.iterrows():
|
||||||
status = row.get('STATUS', 'OK')
|
status = sanitize_for_pdf(row.get('STATUS', 'OK'))
|
||||||
indicator = row.get('INDICATOR', '')
|
indicator = sanitize_for_pdf(row.get('INDICATOR', ''))
|
||||||
valoare = row.get('VALOARE', '')
|
valoare = sanitize_for_pdf(row.get('VALOARE', ''))
|
||||||
explicatie = row.get('EXPLICATIE', '')
|
explicatie = sanitize_for_pdf(row.get('EXPLICATIE', ''))
|
||||||
recomandare = row.get('RECOMANDARE', '')
|
recomandare = sanitize_for_pdf(row.get('RECOMANDARE', ''))
|
||||||
|
|
||||||
# Color based on status
|
# Color based on status
|
||||||
if status == 'ALERTA':
|
if status == 'ALERTA':
|
||||||
@@ -819,128 +944,202 @@ class PDFReportGenerator:
|
|||||||
print(f"✓ PDF salvat: {self.output_path}")
|
print(f"✓ PDF salvat: {self.output_path}")
|
||||||
|
|
||||||
|
|
||||||
|
# Modern minimalist chart colors
|
||||||
|
CHART_COLORS = {
|
||||||
|
'primary': '#2C3E50', # Dark blue-gray
|
||||||
|
'secondary': '#7F8C8D', # Gray
|
||||||
|
'accent': '#E74C3C', # Red for alerts/negative
|
||||||
|
'positive': '#27AE60', # Green for positive trends
|
||||||
|
'light': '#ECF0F1', # Light background
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def setup_chart_style():
|
||||||
|
"""Apply modern minimalist styling to charts"""
|
||||||
|
plt.rcParams.update({
|
||||||
|
'font.family': 'sans-serif',
|
||||||
|
'font.size': 10,
|
||||||
|
'axes.titlesize': 12,
|
||||||
|
'axes.titleweight': 'bold',
|
||||||
|
'axes.spines.top': False,
|
||||||
|
'axes.spines.right': False,
|
||||||
|
'axes.grid': True,
|
||||||
|
'grid.alpha': 0.3,
|
||||||
|
'grid.linestyle': '--',
|
||||||
|
'figure.facecolor': 'white',
|
||||||
|
'axes.facecolor': 'white',
|
||||||
|
'axes.edgecolor': '#7F8C8D',
|
||||||
|
'xtick.color': '#7F8C8D',
|
||||||
|
'ytick.color': '#7F8C8D',
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def create_monthly_chart(df: pd.DataFrame) -> plt.Figure:
|
def create_monthly_chart(df: pd.DataFrame) -> plt.Figure:
|
||||||
"""Create monthly sales and margin chart"""
|
"""Create monthly sales chart - modern minimalist style"""
|
||||||
|
setup_chart_style()
|
||||||
if df is None or df.empty:
|
if df is None or df.empty:
|
||||||
fig, ax = plt.subplots(figsize=(12, 6))
|
fig, ax = plt.subplots(figsize=(12, 5))
|
||||||
ax.text(0.5, 0.5, 'Nu există date', ha='center', va='center')
|
ax.text(0.5, 0.5, 'Nu exista date', ha='center', va='center')
|
||||||
return fig
|
return fig
|
||||||
|
|
||||||
fig, ax1 = plt.subplots(figsize=(12, 6))
|
fig, ax = plt.subplots(figsize=(12, 5))
|
||||||
|
|
||||||
x = range(len(df))
|
x = range(len(df))
|
||||||
|
|
||||||
# Bar chart for sales
|
# Single color bars with clean styling
|
||||||
bars = ax1.bar(x, df['VANZARI_FARA_TVA'], color='#366092', alpha=0.7, label='Vânzări')
|
bars = ax.bar(x, df['VANZARI_FARA_TVA'], color=CHART_COLORS['primary'], alpha=0.85,
|
||||||
ax1.set_xlabel('Luna')
|
edgecolor='white', linewidth=0.5)
|
||||||
ax1.set_ylabel('Vânzări (RON)', color='#366092')
|
|
||||||
ax1.tick_params(axis='y', labelcolor='#366092')
|
|
||||||
ax1.yaxis.set_major_formatter(ticker.FuncFormatter(lambda x, p: f'{x/1000:,.0f}k'))
|
|
||||||
|
|
||||||
# Line chart for margin
|
# Add value labels on top of bars
|
||||||
ax2 = ax1.twinx()
|
for bar in bars:
|
||||||
line = ax2.plot(x, df['MARJA_BRUTA'], color='#e74c3c', linewidth=2, marker='o', label='Marja')
|
height = bar.get_height()
|
||||||
ax2.set_ylabel('Marja (RON)', color='#e74c3c')
|
ax.text(bar.get_x() + bar.get_width()/2., height,
|
||||||
ax2.tick_params(axis='y', labelcolor='#e74c3c')
|
f'{height/1000:,.0f}k', ha='center', va='bottom', fontsize=8, color='#555')
|
||||||
ax2.yaxis.set_major_formatter(ticker.FuncFormatter(lambda x, p: f'{x/1000:,.0f}k'))
|
|
||||||
|
|
||||||
# X-axis labels
|
# Clean axis formatting
|
||||||
ax1.set_xticks(x)
|
ax.set_xlabel('Luna', fontsize=10, color='#555')
|
||||||
ax1.set_xticklabels(df['LUNA'], rotation=45, ha='right')
|
ax.set_ylabel('Vanzari (RON)', fontsize=10, color='#555')
|
||||||
|
ax.yaxis.set_major_formatter(ticker.FuncFormatter(lambda x, p: f'{x/1000:,.0f}k'))
|
||||||
|
ax.set_xticks(x)
|
||||||
|
ax.set_xticklabels(df['LUNA'], rotation=45, ha='right', fontsize=9)
|
||||||
|
|
||||||
# Legend
|
# Add subtle trend line if margin data exists
|
||||||
lines1, labels1 = ax1.get_legend_handles_labels()
|
if 'MARJA_BRUTA' in df.columns:
|
||||||
lines2, labels2 = ax2.get_legend_handles_labels()
|
ax2 = ax.twinx()
|
||||||
ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper left')
|
ax2.plot(x, df['MARJA_BRUTA'], color=CHART_COLORS['accent'], linewidth=2,
|
||||||
|
marker='o', markersize=4, label='Marja Bruta', alpha=0.8)
|
||||||
|
ax2.set_ylabel('Marja (RON)', fontsize=10, color=CHART_COLORS['accent'])
|
||||||
|
ax2.yaxis.set_major_formatter(ticker.FuncFormatter(lambda x, p: f'{x/1000:,.0f}k'))
|
||||||
|
ax2.spines['right'].set_visible(True)
|
||||||
|
ax2.spines['right'].set_color(CHART_COLORS['accent'])
|
||||||
|
ax2.tick_params(axis='y', colors=CHART_COLORS['accent'])
|
||||||
|
|
||||||
plt.title('Evoluția Vânzărilor și Marjei Lunare')
|
ax.set_title('Evolutia Vanzarilor Lunare', fontsize=12, fontweight='bold', color='#2C3E50')
|
||||||
plt.tight_layout()
|
plt.tight_layout()
|
||||||
|
|
||||||
return fig
|
return fig
|
||||||
|
|
||||||
|
|
||||||
def create_client_concentration_chart(df: pd.DataFrame) -> plt.Figure:
|
def create_client_concentration_chart(df: pd.DataFrame) -> plt.Figure:
|
||||||
"""Create client concentration pie chart"""
|
"""Create client concentration chart - horizontal bars (easier to read than pie)"""
|
||||||
|
setup_chart_style()
|
||||||
if df is None or df.empty:
|
if df is None or df.empty:
|
||||||
fig, ax = plt.subplots(figsize=(10, 8))
|
fig, ax = plt.subplots(figsize=(12, 6))
|
||||||
ax.text(0.5, 0.5, 'Nu există date', ha='center', va='center')
|
ax.text(0.5, 0.5, 'Nu exista date', ha='center', va='center')
|
||||||
return fig
|
return fig
|
||||||
|
|
||||||
|
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
|
||||||
|
|
||||||
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
|
|
||||||
|
|
||||||
# Pie chart - Top 10 vs Others
|
|
||||||
top10 = df.head(10)
|
top10 = df.head(10)
|
||||||
others_pct = 100 - top10['PROCENT_CUMULAT'].iloc[-1] if len(top10) >= 10 else 0
|
|
||||||
|
|
||||||
sizes = list(top10['PROCENT_DIN_TOTAL'])
|
# Left: Horizontal bar chart showing client share (cleaner than pie)
|
||||||
if others_pct > 0:
|
y_pos = range(len(top10)-1, -1, -1) # Reverse for top-to-bottom
|
||||||
sizes.append(others_pct)
|
colors = [CHART_COLORS['primary'] if pct < 25 else CHART_COLORS['accent']
|
||||||
labels = list(top10['CLIENT'].str[:20]) # Truncate names
|
for pct in top10['PROCENT_DIN_TOTAL']]
|
||||||
if others_pct > 0:
|
|
||||||
labels.append('Alții')
|
|
||||||
|
|
||||||
colors_list = plt.cm.Set3(range(len(sizes)))
|
bars = ax1.barh(y_pos, top10['PROCENT_DIN_TOTAL'], color=colors, alpha=0.85,
|
||||||
|
edgecolor='white', linewidth=0.5)
|
||||||
|
ax1.set_yticks(y_pos)
|
||||||
|
ax1.set_yticklabels([c[:25] for c in top10['CLIENT']], fontsize=9)
|
||||||
|
ax1.set_xlabel('% din Vanzari Totale', fontsize=10)
|
||||||
|
ax1.set_title('Top 10 Clienti - Pondere Vanzari', fontsize=11, fontweight='bold')
|
||||||
|
|
||||||
ax1.pie(sizes, labels=None, colors=colors_list, autopct='%1.1f%%', startangle=90)
|
# Add percentage labels
|
||||||
ax1.set_title('Concentrare Top 10 Clienți')
|
for bar, pct in zip(bars, top10['PROCENT_DIN_TOTAL']):
|
||||||
ax1.legend(labels, loc='center left', bbox_to_anchor=(1, 0.5), fontsize=8)
|
ax1.text(bar.get_width() + 0.5, bar.get_y() + bar.get_height()/2,
|
||||||
|
f'{pct:.1f}%', va='center', fontsize=8, color='#555')
|
||||||
|
|
||||||
# Bar chart - Pareto
|
# Add 25% threshold line
|
||||||
ax2.bar(range(len(top10)), top10['VANZARI'], color='#366092', alpha=0.7)
|
ax1.axvline(x=25, color=CHART_COLORS['accent'], linestyle='--', alpha=0.7,
|
||||||
|
label='Prag risc 25%')
|
||||||
|
ax1.legend(loc='lower right', fontsize=8)
|
||||||
|
|
||||||
|
# Right: Pareto chart with cumulative line
|
||||||
|
x = range(len(top10))
|
||||||
|
ax2.bar(x, top10['VANZARI'], color=CHART_COLORS['primary'], alpha=0.85,
|
||||||
|
edgecolor='white', linewidth=0.5)
|
||||||
|
|
||||||
|
# Cumulative line on secondary axis
|
||||||
ax2_twin = ax2.twinx()
|
ax2_twin = ax2.twinx()
|
||||||
ax2_twin.plot(range(len(top10)), top10['PROCENT_CUMULAT'], 'r-o', linewidth=2)
|
ax2_twin.plot(x, top10['PROCENT_CUMULAT'], color=CHART_COLORS['accent'],
|
||||||
ax2_twin.axhline(y=80, color='green', linestyle='--', alpha=0.5, label='80%')
|
linewidth=2, marker='o', markersize=4)
|
||||||
|
ax2_twin.axhline(y=80, color=CHART_COLORS['positive'], linestyle='--',
|
||||||
|
alpha=0.7, linewidth=1.5, label='Prag 80%')
|
||||||
|
|
||||||
ax2.set_xticks(range(len(top10)))
|
ax2.set_xticks(x)
|
||||||
ax2.set_xticklabels([c[:15] for c in top10['CLIENT']], rotation=45, ha='right', fontsize=8)
|
ax2.set_xticklabels([c[:12] for c in top10['CLIENT']], rotation=45, ha='right', fontsize=8)
|
||||||
ax2.set_ylabel('Vânzări (RON)')
|
ax2.set_ylabel('Vanzari (RON)', fontsize=10)
|
||||||
ax2_twin.set_ylabel('% Cumulat')
|
ax2.yaxis.set_major_formatter(ticker.FuncFormatter(lambda x, p: f'{x/1000:,.0f}k'))
|
||||||
ax2.set_title('Analiză Pareto Clienți')
|
ax2_twin.set_ylabel('% Cumulat', fontsize=10, color=CHART_COLORS['accent'])
|
||||||
|
ax2_twin.set_ylim(0, 105)
|
||||||
|
ax2.set_title('Analiza Pareto - Concentrare Clienti', fontsize=11, fontweight='bold')
|
||||||
|
ax2_twin.legend(loc='center right', fontsize=8)
|
||||||
|
|
||||||
plt.tight_layout()
|
plt.tight_layout()
|
||||||
return fig
|
return fig
|
||||||
|
|
||||||
|
|
||||||
def create_production_chart(df: pd.DataFrame) -> plt.Figure:
|
def create_production_chart(df: pd.DataFrame) -> plt.Figure:
|
||||||
"""Create production vs resale comparison chart"""
|
"""Create production vs resale comparison chart - modern minimalist style"""
|
||||||
|
setup_chart_style()
|
||||||
if df is None or df.empty:
|
if df is None or df.empty:
|
||||||
fig, ax = plt.subplots(figsize=(10, 6))
|
fig, ax = plt.subplots(figsize=(10, 5))
|
||||||
ax.text(0.5, 0.5, 'Nu există date', ha='center', va='center')
|
ax.text(0.5, 0.5, 'Nu exista date', ha='center', va='center')
|
||||||
return fig
|
return fig
|
||||||
|
|
||||||
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
|
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
|
||||||
|
|
||||||
# Bar chart - Sales by type
|
|
||||||
x = range(len(df))
|
x = range(len(df))
|
||||||
ax1.bar(x, df['VANZARI_FARA_TVA'], color=['#366092', '#e74c3c', '#2ecc71'][:len(df)])
|
|
||||||
|
# Left: Sales by type (two-color scheme: primary + secondary)
|
||||||
|
bar_colors = [CHART_COLORS['primary'], CHART_COLORS['secondary']][:len(df)]
|
||||||
|
if len(df) > 2:
|
||||||
|
bar_colors = [CHART_COLORS['primary']] * len(df)
|
||||||
|
|
||||||
|
bars = ax1.bar(x, df['VANZARI_FARA_TVA'], color=bar_colors, alpha=0.85,
|
||||||
|
edgecolor='white', linewidth=0.5)
|
||||||
ax1.set_xticks(x)
|
ax1.set_xticks(x)
|
||||||
ax1.set_xticklabels(df['TIP_PRODUS'], rotation=15)
|
ax1.set_xticklabels(df['TIP_PRODUS'], rotation=15, fontsize=9)
|
||||||
ax1.set_ylabel('Vânzări (RON)')
|
ax1.set_ylabel('Vanzari (RON)', fontsize=10)
|
||||||
ax1.set_title('Vânzări per Tip Produs')
|
ax1.set_title('Vanzari per Tip Produs', fontsize=11, fontweight='bold')
|
||||||
ax1.yaxis.set_major_formatter(ticker.FuncFormatter(lambda x, p: f'{x/1000:,.0f}k'))
|
ax1.yaxis.set_major_formatter(ticker.FuncFormatter(lambda x, p: f'{x/1000:,.0f}k'))
|
||||||
|
|
||||||
# Bar chart - Margin %
|
# Add value labels
|
||||||
colors = ['#2ecc71' if m > 20 else '#e67e22' if m > 15 else '#e74c3c' for m in df['PROCENT_MARJA']]
|
for bar in bars:
|
||||||
ax2.bar(x, df['PROCENT_MARJA'], color=colors)
|
height = bar.get_height()
|
||||||
|
ax1.text(bar.get_x() + bar.get_width()/2., height,
|
||||||
|
f'{height/1000:,.0f}k', ha='center', va='bottom', fontsize=9, color='#555')
|
||||||
|
|
||||||
|
# Right: Margin % with color-coded status
|
||||||
|
colors = [CHART_COLORS['positive'] if m > 20 else CHART_COLORS['secondary'] if m > 15
|
||||||
|
else CHART_COLORS['accent'] for m in df['PROCENT_MARJA']]
|
||||||
|
bars2 = ax2.bar(x, df['PROCENT_MARJA'], color=colors, alpha=0.85,
|
||||||
|
edgecolor='white', linewidth=0.5)
|
||||||
ax2.set_xticks(x)
|
ax2.set_xticks(x)
|
||||||
ax2.set_xticklabels(df['TIP_PRODUS'], rotation=15)
|
ax2.set_xticklabels(df['TIP_PRODUS'], rotation=15, fontsize=9)
|
||||||
ax2.set_ylabel('Marjă (%)')
|
ax2.set_ylabel('Marja (%)', fontsize=10)
|
||||||
ax2.set_title('Marjă per Tip Produs')
|
ax2.set_title('Marja per Tip Produs', fontsize=11, fontweight='bold')
|
||||||
ax2.axhline(y=15, color='red', linestyle='--', alpha=0.5, label='Prag minim 15%')
|
ax2.axhline(y=15, color=CHART_COLORS['accent'], linestyle='--', alpha=0.7,
|
||||||
ax2.legend()
|
linewidth=1.5, label='Prag minim 15%')
|
||||||
|
ax2.legend(loc='upper right', fontsize=8)
|
||||||
|
|
||||||
|
# Add value labels
|
||||||
|
for bar, val in zip(bars2, df['PROCENT_MARJA']):
|
||||||
|
ax2.text(bar.get_x() + bar.get_width()/2., bar.get_height(),
|
||||||
|
f'{val:.1f}%', ha='center', va='bottom', fontsize=9, color='#555')
|
||||||
|
|
||||||
plt.tight_layout()
|
plt.tight_layout()
|
||||||
return fig
|
return fig
|
||||||
|
|
||||||
|
|
||||||
def create_cash_cycle_chart(df: pd.DataFrame) -> plt.Figure:
|
def create_cash_cycle_chart(df: pd.DataFrame) -> plt.Figure:
|
||||||
"""Create cash conversion cycle visualization"""
|
"""Create cash conversion cycle visualization - modern minimalist style"""
|
||||||
|
setup_chart_style()
|
||||||
if df is None or df.empty:
|
if df is None or df.empty:
|
||||||
fig, ax = plt.subplots(figsize=(10, 6))
|
fig, ax = plt.subplots(figsize=(10, 5))
|
||||||
ax.text(0.5, 0.5, 'Nu exista date', ha='center', va='center')
|
ax.text(0.5, 0.5, 'Nu exista date', ha='center', va='center')
|
||||||
return fig
|
return fig
|
||||||
|
|
||||||
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
|
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
|
||||||
|
|
||||||
# Extract values
|
# Extract values
|
||||||
indicators = df['INDICATOR'].tolist() if 'INDICATOR' in df.columns else []
|
indicators = df['INDICATOR'].tolist() if 'INDICATOR' in df.columns else []
|
||||||
@@ -951,12 +1150,12 @@ def create_cash_cycle_chart(df: pd.DataFrame) -> plt.Figure:
|
|||||||
ax2.text(0.5, 0.5, 'Date incomplete', ha='center', va='center')
|
ax2.text(0.5, 0.5, 'Date incomplete', ha='center', va='center')
|
||||||
return fig
|
return fig
|
||||||
|
|
||||||
# Colors for each component
|
# Simplified color scheme
|
||||||
colors_map = {
|
colors_map = {
|
||||||
'DIO': '#3498db', # Blue for inventory
|
'DIO': CHART_COLORS['primary'], # Dark blue for inventory
|
||||||
'DSO': '#e74c3c', # Red for receivables
|
'DSO': CHART_COLORS['secondary'], # Gray for receivables
|
||||||
'DPO': '#2ecc71', # Green for payables
|
'DPO': CHART_COLORS['positive'], # Green for payables (reduces cycle)
|
||||||
'CCC': '#9b59b6' # Purple for total cycle
|
'CCC': CHART_COLORS['accent'] # Red for total cycle result
|
||||||
}
|
}
|
||||||
|
|
||||||
bar_colors = []
|
bar_colors = []
|
||||||
@@ -966,59 +1165,64 @@ def create_cash_cycle_chart(df: pd.DataFrame) -> plt.Figure:
|
|||||||
bar_colors.append(color)
|
bar_colors.append(color)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
bar_colors.append('#95a5a6')
|
bar_colors.append(CHART_COLORS['secondary'])
|
||||||
|
|
||||||
# Bar chart
|
# Left: Component bars
|
||||||
x = range(len(indicators))
|
x = range(len(indicators))
|
||||||
bars = ax1.bar(x, zile, color=bar_colors, alpha=0.8)
|
bars = ax1.bar(x, zile, color=bar_colors, alpha=0.85, edgecolor='white', linewidth=0.5)
|
||||||
ax1.set_xticks(x)
|
ax1.set_xticks(x)
|
||||||
ax1.set_xticklabels([ind[:20] for ind in indicators], rotation=45, ha='right', fontsize=9)
|
ax1.set_xticklabels([ind[:20] for ind in indicators], rotation=45, ha='right', fontsize=9)
|
||||||
ax1.set_ylabel('Zile')
|
ax1.set_ylabel('Zile', fontsize=10)
|
||||||
ax1.set_title('Ciclu Conversie Cash - Componente')
|
ax1.set_title('Ciclu Conversie Cash - Componente', fontsize=11, fontweight='bold')
|
||||||
|
|
||||||
# Add value labels on bars
|
# Add value labels
|
||||||
for bar, val in zip(bars, zile):
|
for bar, val in zip(bars, zile):
|
||||||
height = bar.get_height()
|
ax1.text(bar.get_x() + bar.get_width()/2., bar.get_height(),
|
||||||
ax1.text(bar.get_x() + bar.get_width()/2., height,
|
f'{int(val)}', ha='center', va='bottom', fontsize=10, fontweight='bold', color='#555')
|
||||||
f'{int(val)}',
|
|
||||||
ha='center', va='bottom', fontsize=10, fontweight='bold')
|
|
||||||
|
|
||||||
# Waterfall-style visualization
|
# Right: Formula visualization
|
||||||
# DIO + DSO - DPO = CCC
|
|
||||||
dio = next((z for i, z in zip(indicators, zile) if 'DIO' in i.upper()), 0)
|
dio = next((z for i, z in zip(indicators, zile) if 'DIO' in i.upper()), 0)
|
||||||
dso = next((z for i, z in zip(indicators, zile) if 'DSO' in i.upper() and 'DIO' not in i.upper()), 0)
|
dso = next((z for i, z in zip(indicators, zile) if 'DSO' in i.upper() and 'DIO' not in i.upper()), 0)
|
||||||
dpo = next((z for i, z in zip(indicators, zile) if 'DPO' in i.upper()), 0)
|
dpo = next((z for i, z in zip(indicators, zile) if 'DPO' in i.upper()), 0)
|
||||||
ccc = dio + dso - dpo
|
ccc = dio + dso - dpo
|
||||||
|
|
||||||
waterfall_labels = ['DIO\n(Zile Stoc)', 'DSO\n(Zile Incasare)', 'DPO\n(Zile Plata)', 'CCC\n(Ciclu Total)']
|
bars2 = ax2.bar([0, 1, 2], [dio, dso, dpo],
|
||||||
waterfall_values = [dio, dso, -dpo, ccc]
|
color=[CHART_COLORS['primary'], CHART_COLORS['secondary'], CHART_COLORS['positive']],
|
||||||
waterfall_colors = ['#3498db', '#e74c3c', '#2ecc71', '#9b59b6']
|
alpha=0.85, edgecolor='white', linewidth=0.5)
|
||||||
|
|
||||||
# Calculate positions for waterfall
|
# CCC result line with color based on health
|
||||||
cumulative = [0]
|
if ccc > 60:
|
||||||
for i, v in enumerate(waterfall_values[:-1]):
|
ccc_color = CHART_COLORS['accent']
|
||||||
cumulative.append(cumulative[-1] + v)
|
elif ccc > 30:
|
||||||
|
ccc_color = CHART_COLORS['secondary']
|
||||||
ax2.bar([0, 1, 2], [dio, dso, dpo], color=['#3498db', '#e74c3c', '#2ecc71'], alpha=0.8)
|
else:
|
||||||
ax2.axhline(y=ccc, color='#9b59b6', linewidth=3, linestyle='--', label=f'CCC = {int(ccc)} zile')
|
ccc_color = CHART_COLORS['positive']
|
||||||
|
|
||||||
|
ax2.axhline(y=ccc, color=ccc_color, linewidth=3, linestyle='--',
|
||||||
|
label=f'CCC = {int(ccc)} zile')
|
||||||
ax2.set_xticks([0, 1, 2])
|
ax2.set_xticks([0, 1, 2])
|
||||||
ax2.set_xticklabels(['DIO\n(+Stoc)', 'DSO\n(+Incasare)', 'DPO\n(-Plata)'], fontsize=9)
|
ax2.set_xticklabels(['DIO\n(+Stoc)', 'DSO\n(+Incasare)', 'DPO\n(-Plata)'], fontsize=9)
|
||||||
ax2.set_ylabel('Zile')
|
ax2.set_ylabel('Zile', fontsize=10)
|
||||||
ax2.set_title('Formula: DIO + DSO - DPO = CCC')
|
ax2.set_title('Formula: DIO + DSO - DPO = CCC', fontsize=11, fontweight='bold')
|
||||||
ax2.legend(loc='upper right')
|
ax2.legend(loc='upper right', fontsize=9)
|
||||||
|
|
||||||
|
# Add value labels
|
||||||
|
for bar, val in zip(bars2, [dio, dso, dpo]):
|
||||||
|
ax2.text(bar.get_x() + bar.get_width()/2., bar.get_height(),
|
||||||
|
f'{int(val)}', ha='center', va='bottom', fontsize=10, fontweight='bold', color='#555')
|
||||||
|
|
||||||
# Add annotation explaining the result
|
# Status annotation
|
||||||
if ccc > 60:
|
if ccc > 60:
|
||||||
verdict = "Ciclu lung - capital blocat mult timp"
|
verdict = "Ciclu lung - capital blocat mult timp"
|
||||||
verdict_color = '#c0392b'
|
verdict_color = CHART_COLORS['accent']
|
||||||
elif ccc > 30:
|
elif ccc > 30:
|
||||||
verdict = "Ciclu moderat - poate fi optimizat"
|
verdict = "Ciclu moderat - poate fi optimizat"
|
||||||
verdict_color = '#d68910'
|
verdict_color = CHART_COLORS['secondary']
|
||||||
else:
|
else:
|
||||||
verdict = "Ciclu eficient - capital rotit rapid"
|
verdict = "Ciclu eficient - capital rotit rapid"
|
||||||
verdict_color = '#27ae60'
|
verdict_color = CHART_COLORS['positive']
|
||||||
|
|
||||||
ax2.text(0.5, -0.15, verdict, transform=ax2.transAxes,
|
ax2.text(0.5, -0.18, verdict, transform=ax2.transAxes,
|
||||||
ha='center', fontsize=10, color=verdict_color, fontweight='bold')
|
ha='center', fontsize=10, color=verdict_color, fontweight='bold')
|
||||||
|
|
||||||
plt.tight_layout()
|
plt.tight_layout()
|
||||||
|
|||||||
Reference in New Issue
Block a user