""" Service pentru logica facturi - Portează query-urile din aplicația Flask """ # import sys # Removed - no longer needed import os from shared.database.oracle_pool import oracle_pool from typing import List, Tuple, Optional from ..models.invoice import Invoice, InvoiceFilter, InvoiceListResponse, InvoiceSummary from ..cache.decorators import cached from decimal import Decimal import logging logger = logging.getLogger(__name__) class InvoiceService: """Service pentru gestionarea facturilor""" @staticmethod @cached(cache_type='schema', key_params=['company_id', 'server_id']) async def _get_schema(company_id: int, server_id: Optional[str] = None) -> str: """Obține schema pentru company_id (CACHED PERMANENT)""" async with oracle_pool.get_connection(server_id) as connection: with connection.cursor() as cursor: schema_query = """ SELECT schema FROM CONTAFIN_ORACLE.v_nom_firme WHERE id_firma = :company_id """ cursor.execute(schema_query, {'company_id': company_id}) schema_result = cursor.fetchone() if not schema_result: raise ValueError(f"Schema not found for company {company_id}") return schema_result[0] @staticmethod @cached(cache_type='invoices', key_params=['filter_params', 'username', 'server_id']) async def get_invoices(filter_params: InvoiceFilter, username: str, server_id: Optional[str] = None) -> InvoiceListResponse: """ Obține lista de facturi - Query simplu pentru afișare în tabel (CACHED 10 min) """ company_id = int(filter_params.company) schema = await InvoiceService._get_schema(company_id, server_id) async with oracle_pool.get_connection(server_id) as connection: with connection.cursor() as cursor: # Determină conturile în funcție de partner_type if filter_params.partner_type == "CLIENTI": conturi = "'4111', '461'" elif filter_params.partner_type == "FURNIZORI": conturi = "'401', '404', '462'" else: conturi = "'4111'" # default # Determine period to use: from params or MAX from calendar if filter_params.luna and filter_params.an: period_condition = "vp.an = :an AND vp.luna = :luna" use_param_period = True else: period_condition = f"""vp.an = (SELECT anul FROM {schema}.calendar WHERE anul*12+luna = (SELECT MAX(anul*12+luna) FROM {schema}.calendar)) AND vp.luna = (SELECT luna FROM {schema}.calendar WHERE anul*12+luna = (SELECT MAX(anul*12+luna) FROM {schema}.calendar))""" use_param_period = False # Query cu calculele corecte pentru solduri base_query = f""" SELECT vp.NUME, vp.NRACT, vp.DATAACT, vp.DATASCAD, vp.CONTRACT, vp.COD_FISCAL, vp.REG_COMERT, CASE WHEN vp.CONT IN ('4111','461') THEN vp.PRECDEB + vp.DEBIT -- Total facturat clienți WHEN vp.CONT IN ('401','404','462') THEN vp.PRECCRED + vp.CREDIT -- Total facturat furnizori END as total_facturat, CASE WHEN vp.CONT IN ('4111','461') THEN vp.PRECCRED + vp.CREDIT -- Încasat clienți WHEN vp.CONT IN ('401','404','462') THEN vp.PRECDEB + vp.DEBIT -- Achitat furnizori END as achitat, CASE WHEN vp.CONT IN ('4111','461') THEN (vp.PRECDEB + vp.DEBIT) - (vp.PRECCRED + vp.CREDIT) -- Sold clienți WHEN vp.CONT IN ('401','404','462') THEN (vp.PRECCRED + vp.CREDIT) - (vp.PRECDEB + vp.DEBIT) -- Sold furnizori END as sold, vp.CONT, NVL(vp.NUME_VAL, 'RON') as valuta, CASE WHEN vp.DATASCAD < SYSDATE THEN 'restant' ELSE 'in_termen' END as status FROM {schema}.vireg_parteneri vp WHERE {period_condition} AND ( (:partner_type = 'CLIENTI' AND vp.cont IN ('4111', '461')) OR (:partner_type = 'FURNIZORI' AND vp.cont IN ('401', '404', '462')) ) """ params = {'partner_type': filter_params.partner_type} # Add period params if using explicit period if use_param_period: params['an'] = filter_params.an params['luna'] = filter_params.luna if filter_params.partner_name: base_query += " AND UPPER(vp.nume) LIKE UPPER(:partner_name)" params['partner_name'] = f"%{filter_params.partner_name}%" if filter_params.cont: base_query += " AND vp.cont = :cont" params['cont'] = filter_params.cont if filter_params.min_amount: base_query += " AND total_facturat >= :min_amount" params['min_amount'] = filter_params.min_amount if filter_params.max_amount: base_query += " AND total_facturat <= :max_amount" params['max_amount'] = filter_params.max_amount if filter_params.only_unpaid: # Nu putem folosi aliasul "sold" în WHERE în Oracle, trebuie să repetăm calculul base_query += """ AND ( CASE WHEN vp.CONT IN ('4111','461') THEN (vp.PRECDEB + vp.DEBIT) - (vp.PRECCRED + vp.CREDIT) WHEN vp.CONT IN ('401','404','462') THEN (vp.PRECCRED + vp.CREDIT) - (vp.PRECDEB + vp.DEBIT) END ) > 0""" # Count total pentru paginare count_query = f"SELECT COUNT(*) FROM ({base_query})" cursor.execute(count_query, params) total_count = cursor.fetchone()[0] # Query pentru TOTAL SOLD din TOATE facturile filtrate (nu doar pagina curentă) total_sold_query = f""" SELECT NVL(SUM(sold), 0) as total_sold FROM ({base_query}) """ cursor.execute(total_sold_query, params) total_sold_result = cursor.fetchone() total_sold_all = Decimal(str(total_sold_result[0])) if total_sold_result else Decimal('0.00') # Get accounting period - use params if provided, else from calendar if use_param_period: accounting_period = { 'an': filter_params.an, 'luna': filter_params.luna } else: period_query = f""" SELECT anul, luna FROM {schema}.calendar WHERE anul*12+luna = (SELECT MAX(anul*12+luna) FROM {schema}.calendar) """ cursor.execute(period_query) period_result = cursor.fetchone() accounting_period = { 'an': period_result[0] if period_result else None, 'luna': period_result[1] if period_result else None } # Adaugă ORDER BY și paginare - Ordonare cronologică (DATAACT, NRACT, NUME) base_query += " ORDER BY vp.DATAACT ASC, vp.NRACT ASC, vp.NUME" # Paginare Oracle offset = (filter_params.page - 1) * filter_params.page_size limit = offset + filter_params.page_size paginated_query = f""" SELECT * FROM ( SELECT ROWNUM as rn, t.* FROM ({base_query}) t WHERE ROWNUM <= :limit ) WHERE rn > :offset """ params['offset'] = offset params['limit'] = limit cursor.execute(paginated_query, params) rows = cursor.fetchall() # Procesează rezultatele cu structura nouă invoices = [] total_amount = Decimal('0.00') for row in rows: # Skip ROWNUM, extrage valorile din query-ul nou nume = row[1] nract = row[2] dataact = row[3] datascad = row[4] contract = row[5] cod_fiscal = row[6] reg_comert = row[7] total_facturat = Decimal(str(row[8] or 0)) achitat = Decimal(str(row[9] or 0)) sold = Decimal(str(row[10] or 0)) cont = row[11] valuta = row[12] or 'RON' status = row[13] invoice_data = { 'nume': nume or '', 'nract': nract or 0, 'dataact': dataact, 'datascad': datascad, 'contract': contract, 'cod_fiscal': cod_fiscal, 'reg_comert': reg_comert, 'cont': cont, 'totctva': total_facturat, 'achitat': achitat, 'soldfinal': sold, 'valuta': valuta } invoice = Invoice(**invoice_data) invoices.append(invoice) total_amount += total_facturat return InvoiceListResponse( invoices=invoices, total_count=total_count, filtered_count=len(invoices), total_amount=total_amount, page=filter_params.page, page_size=filter_params.page_size, has_more=len(invoices) == filter_params.page_size, accounting_period=accounting_period, # Total sold din TOATE facturile filtrate total_sold_all=total_sold_all ) @staticmethod async def get_invoice_details(company: str, invoice_number: str, username: str, server_id: Optional[str] = None) -> Invoice: """ Obține detaliile unei facturi specifice """ async with oracle_pool.get_connection(server_id) as connection: with connection.cursor() as cursor: # Obține schema din v_nom_firme bazat pe id_firma company_id = int(company) schema_query = "SELECT schema FROM CONTAFIN_ORACLE.v_nom_firme WHERE id_firma = :company_id" cursor.execute(schema_query, {'company_id': company_id}) schema_result = cursor.fetchone() if not schema_result: raise ValueError(f"Schema nu a fost găsită pentru id_firma {company_id}") schema = schema_result[0] # Query simplu pentru detalii factură detail_query = f""" SELECT NUME, NRACT, DATAACT, DATASCAD, CONTRACT, COD_FISCAL, REG_COMERT, PRECDEB, PRECCRED, DEBIT, CREDIT, CONT FROM {schema}.vireg_parteneri WHERE nract = :invoice_number AND an = (select anul from {schema}.calendar where anul*12+luna = (select max(anul*12+luna) as anmax from {schema}.calendar)) AND luna = (select luna from {schema}.calendar where anul*12+luna = (select max(anul*12+luna) as anmax from {schema}.calendar)) """ cursor.execute(detail_query, {'invoice_number': invoice_number}) row = cursor.fetchone() if not row: raise ValueError(f"Factura {invoice_number} nu a fost găsită") # Extrage valorile nume = row[0] nract = row[1] dataact = row[2] datascad = row[3] contract = row[4] cod_fiscal = row[5] reg_comert = row[6] precdeb = Decimal(str(row[7] or 0)) preccred = Decimal(str(row[8] or 0)) debit = Decimal(str(row[9] or 0)) credit = Decimal(str(row[10] or 0)) cont = row[11] # Calculează valorile în funcție de tipul contului if cont in ('4111', '461'): # CLIENTI totctva = precdeb + debit achitat = preccred + credit soldfinal = precdeb - preccred + debit - credit else: # FURNIZORI totctva = preccred + credit achitat = precdeb + debit soldfinal = preccred - precdeb + credit - debit invoice_data = { 'nume': nume or '', 'nract': nract or 0, 'dataact': dataact, 'datascad': datascad, 'contract': contract, 'cod_fiscal': cod_fiscal, 'reg_comert': reg_comert, 'totctva': totctva, 'achitat': achitat, 'soldfinal': soldfinal } return Invoice(**invoice_data)