feat: Enhance invoice management with PDF optimization and date fixes
Optimize PDF export layout with compact columns and more space for partner names. Add accounting period display to invoices matching Trial Balance format. Fix date filtering to use local timezone instead of UTC. Update invoice ordering to chronological sequence (DATAACT, NRACT, NUME). **Backend changes:** - Add accounting period query from calendar table - Add currency (valuta) and cont filter support - Change invoice ordering to chronological (DATAACT ASC, NRACT ASC, NUME) - Add accounting_period field to InvoiceListResponse model **Frontend changes:** - Optimize PDF column widths (37% for partner names, compact numeric columns) - Add custom column width support in exportUtils - Fix date conversion from UTC to local timezone (prevents day shift) - Add accounting period display in PDF exports - Enhance E2E test coverage **Cleanup:** - Remove obsolete Trial Balance feature documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,8 @@ class InvoiceBase(BaseModel):
|
||||
contract: Optional[str] = Field(description="Numărul contractului")
|
||||
cod_fiscal: Optional[str] = Field(description="Codul fiscal")
|
||||
reg_comert: Optional[str] = Field(description="Registrul comerțului")
|
||||
cont: Optional[str] = Field(description="Contul contabil")
|
||||
valuta: str = Field(default="RON", description="Valuta (RON, EUR, USD, etc.)")
|
||||
|
||||
class Invoice(InvoiceBase):
|
||||
"""Model complet pentru factură cu calcule financiare"""
|
||||
@@ -45,11 +47,12 @@ class InvoiceFilter(BaseModel):
|
||||
date_from: Optional[date] = Field(description="Data de început")
|
||||
date_to: Optional[date] = Field(description="Data de sfârșit")
|
||||
partner_name: Optional[str] = Field(description="Filtru după nume")
|
||||
cont: Optional[str] = Field(description="Filtru după cont contabil")
|
||||
only_unpaid: bool = Field(default=True, description="Doar neachitate")
|
||||
min_amount: Optional[Decimal] = Field(description="Suma minimă")
|
||||
max_amount: Optional[Decimal] = Field(description="Suma maximă")
|
||||
page: int = Field(default=1, ge=1, description="Pagina")
|
||||
page_size: int = Field(default=50, ge=1, le=1000, description="Mărimea paginii")
|
||||
page_size: int = Field(default=50, ge=1, le=10000000, description="Mărimea paginii")
|
||||
|
||||
class InvoiceListResponse(BaseModel):
|
||||
"""Răspuns pentru lista de facturi"""
|
||||
@@ -60,6 +63,7 @@ class InvoiceListResponse(BaseModel):
|
||||
page: int
|
||||
page_size: int
|
||||
has_more: bool
|
||||
accounting_period: Optional[dict] = Field(default=None, description="Perioada contabilă (an, luna)")
|
||||
|
||||
class InvoiceSummary(BaseModel):
|
||||
"""Rezumat pentru facturi - pentru dashboard"""
|
||||
|
||||
@@ -22,11 +22,12 @@ async def get_invoices(
|
||||
date_from: Optional[str] = Query(None, description="Data început (YYYY-MM-DD)"),
|
||||
date_to: Optional[str] = Query(None, description="Data sfârșit (YYYY-MM-DD)"),
|
||||
partner_name: Optional[str] = Query(None, description="Filtru nume partener"),
|
||||
cont: Optional[str] = Query(None, description="Filtru după cont contabil"),
|
||||
only_unpaid: bool = Query(True, description="Doar facturile neachitate"),
|
||||
min_amount: Optional[float] = Query(None, description="Suma minimă"),
|
||||
max_amount: Optional[float] = Query(None, description="Suma maximă"),
|
||||
page: int = Query(1, ge=1, description="Pagina"),
|
||||
page_size: int = Query(50, ge=1, le=1000, description="Mărimea paginii"),
|
||||
page_size: int = Query(50, ge=1, le=10000000, description="Mărimea paginii"),
|
||||
current_user: CurrentUser = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
@@ -63,6 +64,7 @@ async def get_invoices(
|
||||
date_from=date_from_obj,
|
||||
date_to=date_to_obj,
|
||||
partner_name=partner_name,
|
||||
cont=cont,
|
||||
only_unpaid=only_unpaid,
|
||||
min_amount=min_amount,
|
||||
max_amount=max_amount,
|
||||
@@ -89,10 +91,10 @@ async def get_invoices_summary(
|
||||
# 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}")
|
||||
|
||||
|
||||
result = await InvoiceService.get_invoice_summary(company, partner_type, current_user.username)
|
||||
return result
|
||||
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Eroare la obținerea rezumatului facturilor: {str(e)}")
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ async def get_bank_cash_register(
|
||||
date_to: Optional[str] = Query(None, description="Data sfârșit (YYYY-MM-DD)"),
|
||||
partner_name: Optional[str] = Query(None, description="Filtru nume partener"),
|
||||
page: int = Query(1, ge=1, description="Pagina"),
|
||||
page_size: int = Query(50, ge=1, le=1000, description="Mărimea paginii"),
|
||||
page_size: int = Query(50, ge=1, le=10000000, description="Mărimea paginii"),
|
||||
current_user: CurrentUser = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
|
||||
@@ -58,7 +58,7 @@ class InvoiceService:
|
||||
|
||||
# Query cu calculele corecte pentru solduri
|
||||
base_query = f"""
|
||||
SELECT
|
||||
SELECT
|
||||
vp.NUME,
|
||||
vp.NRACT,
|
||||
vp.DATAACT,
|
||||
@@ -66,22 +66,23 @@ class InvoiceService:
|
||||
vp.CONTRACT,
|
||||
vp.COD_FISCAL,
|
||||
vp.REG_COMERT,
|
||||
CASE
|
||||
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
|
||||
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
|
||||
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
|
||||
WHEN vp.CONT IN ('401','404','462') THEN
|
||||
(vp.PRECCRED + vp.CREDIT) - (vp.PRECDEB + vp.DEBIT) -- Sold furnizori
|
||||
END as sold,
|
||||
vp.CONT,
|
||||
CASE
|
||||
NVL(vp.NUME_VAL, 'RON') as valuta,
|
||||
CASE
|
||||
WHEN vp.DATASCAD < SYSDATE THEN 'restant'
|
||||
ELSE 'in_termen'
|
||||
END as status
|
||||
@@ -109,7 +110,11 @@ class InvoiceService:
|
||||
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
|
||||
@@ -134,9 +139,22 @@ class InvoiceService:
|
||||
cursor.execute(count_query, params)
|
||||
total_count = cursor.fetchone()[0]
|
||||
|
||||
# Adaugă ORDER BY și paginare
|
||||
base_query += " ORDER BY vp.DATAACT DESC, vp.NUME, vp.NRACT"
|
||||
|
||||
# Get accounting period (luna, an) from calendar
|
||||
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
|
||||
@@ -147,7 +165,7 @@ class InvoiceService:
|
||||
"""
|
||||
params['offset'] = offset
|
||||
params['limit'] = limit
|
||||
|
||||
|
||||
cursor.execute(paginated_query, params)
|
||||
rows = cursor.fetchall()
|
||||
|
||||
@@ -168,8 +186,9 @@ class InvoiceService:
|
||||
achitat = Decimal(str(row[9] or 0))
|
||||
sold = Decimal(str(row[10] or 0))
|
||||
cont = row[11]
|
||||
status = row[12]
|
||||
|
||||
valuta = row[12] or 'RON'
|
||||
status = row[13]
|
||||
|
||||
invoice_data = {
|
||||
'nume': nume or '',
|
||||
'nract': nract or 0,
|
||||
@@ -178,9 +197,11 @@ class InvoiceService:
|
||||
'contract': contract,
|
||||
'cod_fiscal': cod_fiscal,
|
||||
'reg_comert': reg_comert,
|
||||
'cont': cont,
|
||||
'totctva': total_facturat,
|
||||
'achitat': achitat,
|
||||
'soldfinal': sold
|
||||
'soldfinal': sold,
|
||||
'valuta': valuta
|
||||
}
|
||||
|
||||
invoice = Invoice(**invoice_data)
|
||||
@@ -194,7 +215,8 @@ class InvoiceService:
|
||||
total_amount=total_amount,
|
||||
page=filter_params.page,
|
||||
page_size=filter_params.page_size,
|
||||
has_more=len(invoices) == filter_params.page_size
|
||||
has_more=len(invoices) == filter_params.page_size,
|
||||
accounting_period=accounting_period
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
||||
Reference in New Issue
Block a user