""" API Router for Trial Balance (Balanță de Verificare) """ from fastapi import APIRouter, Depends, HTTPException, Query from typing import Optional from datetime import date from decimal import Decimal import sys import os import math sys.path.append(os.path.join(os.path.dirname(__file__), '../../../../shared')) from auth.dependencies import get_current_user from auth.models import CurrentUser from database.oracle_pool import oracle_pool from ..models.trial_balance import ( TrialBalanceItem, TrialBalanceFilters, TrialBalancePagination, TrialBalanceResponse ) router = APIRouter() @router.get("/", response_model=TrialBalanceResponse) async def get_trial_balance( company: str = Query(description="Codul firmei (ID)"), luna: Optional[int] = Query(None, ge=1, le=12, description="Luna (1-12), default: luna curentă"), an: Optional[int] = Query(None, ge=2000, le=2100, description="An, default: anul curent"), cont_filter: Optional[str] = Query(None, description="Filtru număr cont (ex: '512', '4111')"), denumire_filter: Optional[str] = Query(None, description="Filtru denumire cont (partial match, case-insensitive)"), sort_by: str = Query("CONT", description="Coloană pentru sortare"), sort_order: str = Query("asc", description="Ordinea sortării (asc | desc)"), page: int = Query(1, ge=1, description="Pagina"), page_size: int = Query(50, ge=1, le=500, description="Mărimea paginii"), current_user: CurrentUser = Depends(get_current_user) ): """ Obține balanța de verificare sintetică pentru o firmă - Necesită autentificare JWT - Utilizatorul trebuie să aibă acces la firma specificată - Suportă filtrare după cont și denumire - Suportă paginare și sortare """ try: # Verifică dacă utilizatorul are acces la firma specificată if company not in current_user.companies: raise HTTPException( status_code=403, detail=f"Nu aveți acces la firma {company}" ) # Setează valorile implicite pentru lună și an (luna și anul curent) current_date = date.today() if luna is None: luna = current_date.month if an is None: an = current_date.year # Validează sort_order if sort_order.lower() not in ['asc', 'desc']: sort_order = 'asc' # Validează sort_by (previne SQL injection) # Real column names from VBAL VIEW valid_sort_columns = ['CONT', 'DENUMIRE', 'PRECDEB', 'PRECCRED', 'RULDEB', 'RULCRED', 'SOLDDEB', 'SOLDCRED'] if sort_by.upper() not in valid_sort_columns: sort_by = 'CONT' # Obține datele din Oracle async with oracle_pool.get_connection() as connection: with connection.cursor() as cursor: # Obține schema din v_nom_firme company_id = int(company) schema_query = """ SELECT schema FROM CONTAFIN_ORACLE.v_nom_firme WHERE id_firma = :company_id """ cursor.execute(schema_query, {'company_id': company_id}) schema_result = cursor.fetchone() if not schema_result: raise HTTPException( status_code=404, detail=f"Schema nu a fost găsită pentru firma {company}" ) schema = schema_result[0] # Construiește query-ul de bază pentru VBAL VIEW # VBAL este un VIEW în fiecare schemă de companie # Structura reală: CONT, DENUMIRE, AN, LUNA, PRECDEB, PRECCRED, RULDEB, RULCRED, SOLDDEB, SOLDCRED base_query = f""" SELECT CONT, NVL(DENUMIRE, '') as DENUMIRE, NVL(PRECDEB, 0) as PRECDEB, NVL(PRECCRED, 0) as PRECCRED, NVL(RULDEB, 0) as RULDEB, NVL(RULCRED, 0) as RULCRED, NVL(SOLDDEB, 0) as SOLDDEB, NVL(SOLDCRED, 0) as SOLDCRED FROM {schema}.VBAL WHERE AN = :an AND LUNA = :luna """ params = { 'an': an, 'luna': luna } # Adaugă filtre dinamice if cont_filter: base_query += " AND CONT LIKE :cont_filter" params['cont_filter'] = f"{cont_filter}%" if denumire_filter: base_query += " AND UPPER(DENUMIRE) LIKE UPPER(:denumire_filter)" params['denumire_filter'] = f"%{denumire_filter}%" # Count total pentru paginare count_query = f"SELECT COUNT(*) FROM ({base_query})" cursor.execute(count_query, params) total_count = cursor.fetchone()[0] # Adaugă sortare base_query += f" ORDER BY {sort_by.upper()} {sort_order.upper()}" # Paginare Oracle (ROW_NUMBER în loc de ROWNUM pentru a funcționa cu ORDER BY) offset = (page - 1) * page_size limit = offset + page_size paginated_query = f""" SELECT * FROM ( SELECT a.*, ROWNUM rnum FROM ( {base_query} ) a WHERE ROWNUM <= :limit ) WHERE rnum > :offset """ params['offset'] = offset params['limit'] = limit cursor.execute(paginated_query, params) rows = cursor.fetchall() # Procesează rezultatele # Index columns: CONT(0), DENUMIRE(1), PRECDEB(2), PRECCRED(3), # RULDEB(4), RULCRED(5), SOLDDEB(6), SOLDCRED(7), rnum(8) items = [] for row in rows: item = TrialBalanceItem( cont=row[0] or '', denumire=row[1] or '', sold_precedent_debit=Decimal(str(row[2] or 0)), sold_precedent_credit=Decimal(str(row[3] or 0)), rulaj_lunar_debit=Decimal(str(row[4] or 0)), rulaj_lunar_credit=Decimal(str(row[5] or 0)), sold_final_debit=Decimal(str(row[6] or 0)), sold_final_credit=Decimal(str(row[7] or 0)) ) items.append(item.dict()) # Calculează paginarea total_pages = math.ceil(total_count / page_size) if page_size > 0 else 0 # Construiește răspunsul response_data = { "items": items, "pagination": { "total_items": total_count, "total_pages": total_pages, "current_page": page, "page_size": page_size }, "filters_applied": { "luna": luna, "an": an, "cont_filter": cont_filter, "denumire_filter": denumire_filter } } return TrialBalanceResponse( success=True, data=response_data ) except HTTPException: # Re-raise HTTP exceptions raise except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: # Log the error for debugging import logging logging.error(f"Error fetching trial balance: {str(e)}") raise HTTPException( status_code=500, detail=f"Eroare la obținerea balanței de verificare: {str(e)}" )