# Plan: Corectii Report Generator - 28.11.2025 ## Probleme de Rezolvat 1. **Analiza Prajitorie** - intrarile si iesirile apar pe randuri diferite in loc de coloane 2. **Query-uri Financiare "No Data"** - DSO/DPO, Solduri clienti/furnizori, Aging, Pozitia Cash, Ciclu Conversie Cash nu afiseaza date (user confirma ca datele EXISTA) 3. **Recomandari in Sumar Executiv** - trebuie incluse sub KPIs in sheet-ul Sumar Executiv 4. **Reordonare Sheet-uri** - agregatele (indicatori_agregati, portofoliu_clienti, concentrare_risc) trebuie mutate imediat dupa Sumar Executiv --- ## ISSUE 1: Analiza Prajitorie - Restructurare din Randuri in Coloane ### Fisier: `queries.py` liniile 450-478 ### Problema Curenta Query-ul grupeaza dupa `tip_miscare` (Intrare/Iesire/Transformare), creand randuri separate: ``` luna | tip | tip_miscare | cantitate_intrata | cantitate_iesita 2024-01 | Materii prime | Intrare | 1000 | 0 2024-01 | Materii prime | Iesire | 0 | 800 ``` ### Output Cerut Un rand per Luna + Tip cu coloane separate pentru Intrari si Iesiri: ``` luna | tip | cantitate_intrari | valoare_intrari | cantitate_iesiri | valoare_iesiri | sold_net 2024-01 | Materii prime | 1000 | 50000 | 800 | 40000 | 10000 ``` ### Solutia: Inlocuieste ANALIZA_PRAJITORIE (liniile 450-478) ```sql ANALIZA_PRAJITORIE = """ SELECT TO_CHAR(r.dataact, 'YYYY-MM') AS luna, CASE WHEN r.cont = '301' THEN 'Materii prime' WHEN r.cont = '341' THEN 'Semifabricate' WHEN r.cont = '345' THEN 'Produse finite' ELSE 'Altele' END AS tip, -- Intrari: cantitate > 0 AND cante = 0 ROUND(SUM(CASE WHEN r.cant > 0 AND NVL(r.cante, 0) = 0 THEN r.cant ELSE 0 END), 2) AS cantitate_intrari, ROUND(SUM(CASE WHEN r.cant > 0 AND NVL(r.cante, 0) = 0 THEN r.cant * NVL(r.pret, 0) ELSE 0 END), 2) AS valoare_intrari, -- Iesiri: cant = 0 AND cante > 0 ROUND(SUM(CASE WHEN NVL(r.cant, 0) = 0 AND r.cante > 0 THEN r.cante ELSE 0 END), 2) AS cantitate_iesiri, ROUND(SUM(CASE WHEN NVL(r.cant, 0) = 0 AND r.cante > 0 THEN r.cante * NVL(r.pret, 0) ELSE 0 END), 2) AS valoare_iesiri, -- Transformari: cant > 0 AND cante > 0 (intrare si iesire simultan) ROUND(SUM(CASE WHEN r.cant > 0 AND r.cante > 0 THEN r.cant ELSE 0 END), 2) AS cantitate_transformari_in, ROUND(SUM(CASE WHEN r.cant > 0 AND r.cante > 0 THEN r.cante ELSE 0 END), 2) AS cantitate_transformari_out, -- Sold net ROUND(SUM(NVL(r.cant, 0) - NVL(r.cante, 0)), 2) AS sold_net_cantitate, ROUND(SUM((NVL(r.cant, 0) - NVL(r.cante, 0)) * NVL(r.pret, 0)), 2) AS sold_net_valoare FROM vrul r WHERE r.cont IN ('301', '341', '345') AND r.dataact >= ADD_MONTHS(TRUNC(SYSDATE), -:months) GROUP BY TO_CHAR(r.dataact, 'YYYY-MM'), CASE WHEN r.cont = '301' THEN 'Materii prime' WHEN r.cont = '341' THEN 'Semifabricate' WHEN r.cont = '345' THEN 'Produse finite' ELSE 'Altele' END ORDER BY luna, tip """ ``` ### Modificari Cheie 1. **Eliminat** `tip_miscare` din SELECT si GROUP BY 2. **Agregare conditionala** cu `CASE WHEN ... THEN ... ELSE 0 END` in SUM() 3. **Coloane separate** pentru fiecare tip de miscare 4. **Adaugat coloane valoare** pe langa cantitati ### Update Legends in main.py (in jurul liniei 224) Adauga in dictionarul `legends`: ```python 'analiza_prajitorie': { 'CANTITATE_INTRARI': 'Cantitate intrata (cant > 0, cante = 0)', 'VALOARE_INTRARI': 'Valoare intrari = cantitate x pret', 'CANTITATE_IESIRI': 'Cantitate iesita (cant = 0, cante > 0)', 'VALOARE_IESIRI': 'Valoare iesiri = cantitate x pret', 'CANTITATE_TRANSFORMARI_IN': 'Cantitate intrata in transformari', 'CANTITATE_TRANSFORMARI_OUT': 'Cantitate iesita din transformari', 'SOLD_NET_CANTITATE': 'Sold net = Total intrari - Total iesiri', 'SOLD_NET_VALOARE': 'Valoare neta a soldului' } ``` --- ## ISSUE 2: Query-uri Financiare "No Data" - DIAGNOSTIC NECESAR ### Query-uri Afectate | Query | View Folosit | Linie in queries.py | Filtru Curent | |-------|--------------|---------------------|---------------| | DSO_DPO | vbalanta_parteneri | 796-844 | `an = EXTRACT(YEAR FROM SYSDATE) AND luna = EXTRACT(MONTH FROM SYSDATE)` | | SOLDURI_CLIENTI | vbalanta_parteneri | 636-654 | Acelasi + `cont LIKE '4111%'` | | SOLDURI_FURNIZORI | vbalanta_parteneri | 659-677 | Acelasi + `cont LIKE '401%'` | | AGING_CREANTE | vireg_parteneri | 682-714 | `cont LIKE '4111%' OR '461%'` | | FACTURI_RESTANTE | vireg_parteneri | 719-734 | Acelasi + `datascad < SYSDATE` | | POZITIA_CASH | vbal | 849-872 | `cont LIKE '512%' OR '531%'` | | CICLU_CONVERSIE_CASH | Multiple | 877-940 | Combina toate de mai sus | ### User-ul confirma ca DATELE EXISTA - trebuie diagnosticat problema ### Cauze Posibile 1. Numele view-urilor difera in baza de date 2. Numele coloanelor difera (`an`, `luna`, `solddeb`, `soldcred`) 3. Prefixele codurilor de cont nu se potrivesc (4111%, 401%, 512%) 4. Pragurile HAVING sunt prea restrictive (`> 1`, `> 100`) ### FIX IMEDIAT: Relaxeaza Pragurile HAVING **SOLDURI_CLIENTI** (linia 652): ```sql -- DE LA: HAVING ABS(SUM(b.solddeb - b.soldcred)) > 1 -- LA: HAVING ABS(SUM(b.solddeb - b.soldcred)) > 0.01 ``` **SOLDURI_FURNIZORI** (linia 675): ```sql -- DE LA: HAVING ABS(SUM(b.soldcred - b.solddeb)) > 1 -- LA: HAVING ABS(SUM(b.soldcred - b.solddeb)) > 0.01 ``` **AGING_CREANTE** (linia 712): ```sql -- DE LA: HAVING SUM(sold_ramas) > 100 -- LA: HAVING SUM(sold_ramas) > 0.01 ``` **AGING_DATORII** (linia 770): ```sql -- DE LA: HAVING SUM(sold_ramas) > 100 -- LA: HAVING SUM(sold_ramas) > 0.01 ``` **POZITIA_CASH** (linia 870): ```sql -- DE LA: HAVING ABS(SUM(b.solddeb - b.soldcred)) > 0.01 -- Deja OK, dar verifica daca vbal exista ``` ### Daca Tot Nu Functioneaza - Verifica View-urile Ruleaza in Oracle: ```sql -- Verifica daca view-urile exista SELECT view_name FROM user_views WHERE view_name IN ('VBALANTA_PARTENERI', 'VIREG_PARTENERI', 'VBAL', 'VRUL'); -- Verifica daca exista date pentru luna curenta SELECT an, luna, COUNT(*) FROM vbalanta_parteneri WHERE an = EXTRACT(YEAR FROM SYSDATE) GROUP BY an, luna ORDER BY luna DESC; -- Verifica prefixele de cont existente SELECT DISTINCT SUBSTR(cont, 1, 4) AS prefix_cont FROM vbalanta_parteneri WHERE an = EXTRACT(YEAR FROM SYSDATE); ``` --- ## ISSUE 3: Recomandari in Sumar Executiv ### Stare Curenta - Sheet `sumar_executiv` (linia 166) - contine doar KPIs - Sheet `recomandari` (linia 168) - sheet separat cu toate recomandarile ### Solutia: Metoda noua in report_generator.py ### Adauga metoda noua in clasa `ExcelReportGenerator` (dupa linia 167 in report_generator.py) ```python def add_sheet_with_recommendations(self, name: str, df: pd.DataFrame, recommendations_df: pd.DataFrame, title: str = None, description: str = None, legend: dict = None, top_n_recommendations: int = 5): """Adauga sheet formatat cu KPIs si top recomandari dedesubt""" sheet_name = name[:31] ws = self.wb.create_sheet(title=sheet_name) start_row = 1 # Adauga titlu if title: ws.cell(row=start_row, column=1, value=title) ws.cell(row=start_row, column=1).font = Font(bold=True, size=14) start_row += 1 # Adauga descriere if description: ws.cell(row=start_row, column=1, value=description) ws.cell(row=start_row, column=1).font = Font(italic=True, size=10, color='666666') start_row += 1 # Adauga timestamp ws.cell(row=start_row, column=1, value=f"Generat: {datetime.now().strftime('%Y-%m-%d %H:%M')}") ws.cell(row=start_row, column=1).font = Font(size=9, color='999999') start_row += 2 # === SECTIUNEA 1: KPIs === if df is not None and not df.empty: # Header for col_idx, col_name in enumerate(df.columns, 1): cell = ws.cell(row=start_row, column=col_idx, value=col_name) cell.font = self.header_font cell.fill = self.header_fill cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True) cell.border = self.border # Date for row_idx, row in enumerate(df.itertuples(index=False), start_row + 1): for col_idx, value in enumerate(row, 1): cell = ws.cell(row=row_idx, column=col_idx, value=value) cell.border = self.border if isinstance(value, (int, float)): cell.number_format = '#,##0.00' if isinstance(value, float) else '#,##0' cell.alignment = Alignment(horizontal='right') start_row = start_row + len(df) + 3 # === SECTIUNEA 2: TOP RECOMANDARI === if recommendations_df is not None and not recommendations_df.empty: ws.cell(row=start_row, column=1, value="Top Recomandari Prioritare") ws.cell(row=start_row, column=1).font = Font(bold=True, size=12, color='366092') start_row += 1 # Sorteaza dupa prioritate (ALERTA primul, apoi ATENTIE, apoi OK) df_sorted = recommendations_df.copy() status_order = {'ALERTA': 0, 'ATENTIE': 1, 'OK': 2} df_sorted['_order'] = df_sorted['STATUS'].map(status_order).fillna(3) df_sorted = df_sorted.sort_values('_order').head(top_n_recommendations) df_sorted = df_sorted.drop(columns=['_order']) # Coloane de afisat display_cols = ['STATUS', 'CATEGORIE', 'INDICATOR', 'VALOARE', 'RECOMANDARE'] display_cols = [c for c in display_cols if c in df_sorted.columns] # Header cu background mov for col_idx, col_name in enumerate(display_cols, 1): cell = ws.cell(row=start_row, column=col_idx, value=col_name) cell.font = self.header_font cell.fill = PatternFill(start_color='8E44AD', end_color='8E44AD', fill_type='solid') cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True) cell.border = self.border # Randuri cu colorare dupa status for row_idx, (_, row) in enumerate(df_sorted.iterrows(), start_row + 1): status = row.get('STATUS', 'OK') for col_idx, col_name in enumerate(display_cols, 1): value = row.get(col_name, '') cell = ws.cell(row=row_idx, column=col_idx, value=value) cell.border = self.border cell.alignment = Alignment(wrap_text=True) # Colorare condiționata if status == 'ALERTA': cell.fill = PatternFill(start_color='FADBD8', end_color='FADBD8', fill_type='solid') elif status == 'ATENTIE': cell.fill = PatternFill(start_color='FCF3CF', end_color='FCF3CF', fill_type='solid') else: cell.fill = PatternFill(start_color='D5F5E3', end_color='D5F5E3', fill_type='solid') # Auto-adjust latime coloane for col_idx in range(1, 8): ws.column_dimensions[get_column_letter(col_idx)].width = 22 ws.freeze_panes = ws.cell(row=5, column=1) ``` ### Modifica main.py - Loop-ul de Creare Sheet-uri (in jurul liniei 435) ```python for query_name in sheet_order: if query_name in results: # Tratare speciala pentru 'sumar_executiv' - adauga recomandari sub KPIs if query_name == 'sumar_executiv': query_info = QUERIES.get(query_name, {}) excel_gen.add_sheet_with_recommendations( name='Sumar Executiv', df=results['sumar_executiv'], recommendations_df=results.get('recomandari'), title=query_info.get('title', 'Sumar Executiv'), description=query_info.get('description', ''), legend=legends.get('sumar_executiv'), top_n_recommendations=5 ) # Pastreaza sheet-ul complet de recomandari elif query_name == 'recomandari': excel_gen.add_sheet( name='RECOMANDARI', df=results['recomandari'], title='Recomandari Automate (Lista Completa)', description='Toate insight-urile si actiunile sugerate bazate pe analiza datelor', legend=legends.get('recomandari') ) elif query_name in QUERIES: # ... logica existenta neschimbata ``` --- ## ISSUE 4: Reordonare Sheet-uri ### Fisier: `main.py` liniile 165-221 ### Noul sheet_order (inlocuieste complet liniile 165-221) ```python sheet_order = [ # SUMAR EXECUTIV 'sumar_executiv', 'sumar_executiv_yoy', 'recomandari', # INDICATORI AGREGATI (MUTATI SUS - imagine de ansamblu) 'indicatori_agregati_venituri', 'indicatori_agregati_venituri_yoy', 'portofoliu_clienti', 'concentrare_risc', 'concentrare_risc_yoy', 'sezonalitate_lunara', # INDICATORI GENERALI & LICHIDITATE 'indicatori_generali', 'indicatori_lichiditate', 'clasificare_datorii', 'grad_acoperire_datorii', 'proiectie_lichiditate', # ALERTE 'vanzari_sub_cost', 'clienti_marja_mica', # CICLU CASH 'ciclu_conversie_cash', # ANALIZA CLIENTI 'marja_per_client', 'clienti_ranking_profit', 'frecventa_clienti', 'concentrare_clienti', 'trending_clienti', 'marja_client_categorie', # PRODUSE 'top_produse', 'marja_per_categorie', 'marja_per_gestiune', 'articole_negestionabile', 'productie_vs_revanzare', # PRETURI 'dispersie_preturi', 'clienti_sub_medie', 'evolutie_discount', # FINANCIAR 'dso_dpo', 'dso_dpo_yoy', 'solduri_clienti', 'aging_creante', 'facturi_restante', 'solduri_furnizori', 'aging_datorii', 'facturi_restante_furnizori', 'pozitia_cash', # ISTORIC 'vanzari_lunare', # STOC 'stoc_curent', 'stoc_lent', 'rotatie_stocuri', # PRODUCTIE 'analiza_prajitorie', ] ``` --- ## Ordinea de Implementare ### Pasul 1: queries.py 1. Inlocuieste ANALIZA_PRAJITORIE (liniile 450-478) cu versiunea cu agregare conditionala 2. Relaxeaza pragurile HAVING in: - SOLDURI_CLIENTI (linia 652): `> 1` -> `> 0.01` - SOLDURI_FURNIZORI (linia 675): `> 1` -> `> 0.01` - AGING_CREANTE (linia 712): `> 100` -> `> 0.01` - AGING_DATORII (linia 770): `> 100` -> `> 0.01` ### Pasul 2: report_generator.py 1. Adauga metoda `add_sheet_with_recommendations()` dupa linia 167 2. Asigura-te ca importurile includ `PatternFill`, `get_column_letter` din openpyxl ### Pasul 3: main.py 1. Inlocuieste array-ul `sheet_order` (liniile 165-221) 2. Modifica loop-ul de creare sheet-uri pentru `sumar_executiv` (in jurul liniei 435) 3. Adauga legend pentru `analiza_prajitorie` in dictionarul `legends` ### Pasul 4: Testare 1. Ruleaza cu `python main.py --months 1` pentru test rapid 2. Verifica sheet-ul `analiza_prajitorie` - format columnar 3. Verifica query-urile financiare - trebuie sa returneze date 4. Verifica `Sumar Executiv` - sectiune recomandari dedesubt 5. Verifica ordinea sheet-urilor - agregatele dupa sumar --- ## Fisiere Critice | Fisier | Ce se modifica | Linii | |--------|----------------|-------| | `queries.py` | ANALIZA_PRAJITORIE SQL | 450-478 | | `queries.py` | HAVING thresholds | 652, 675, 712, 770 | | `report_generator.py` | Metoda noua | dupa 167 | | `main.py` | sheet_order array | 165-221 | | `main.py` | Loop creare sheet-uri | ~435 | | `main.py` | legends dict | ~224 | --- ## Note pentru Sesiunea Viitoare 1. **Prioritate ALERTA**: Query-urile financiare "no data" - user-ul a confirmat ca datele EXISTA. Daca relaxarea HAVING nu rezolva, trebuie verificate numele view-urilor si coloanelor in Oracle. 2. **Import necesar** in report_generator.py: ```python from openpyxl.utils import get_column_letter from openpyxl.styles import PatternFill ``` 3. **Testare**: Dupa implementare, ruleaza raportul si verifica fiecare din cele 4 fix-uri.