feat: Add A-Z filter for clients/suppliers in Telegram bot

- Add A-Z alphabetical filter keyboard for clients and suppliers lists
  (same pattern as company selection, without emoji)
- Increase clients/suppliers list pagination from 10 to 20 items per page
- Remove emoji from company A-Z filter button for consistency
- Add 6 new callback handlers: clients_alpha_menu, clients_alpha:LETTER,
  clients_alpha_page:PAGE:LETTER, and supplier equivalents
- Dashboard service and models updates
- Telegram bot: email handlers, auth, DB operations, internal API improvements
- Frontend: dashboard cards updates (CashFlow, Clienti, Furnizori, Treasury)
- Frontend: SolduriCompactCard and CollapsibleCard improvements
- DashboardView enhancements
- start.sh and run-with-restart.sh script updates
- IIS web.config and service worker updates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-02-21 14:34:15 +00:00
parent 1366dbc11c
commit 30f55cf18b
28 changed files with 1671 additions and 520 deletions

View File

@@ -2,6 +2,22 @@ from pydantic import BaseModel
from decimal import Decimal
from typing import List, Dict, Optional, Any
class BudgetDebtSubAccount(BaseModel):
"""Cont individual din cadrul unui grup de datorii buget"""
cont: str # ex: "4311"
label: str # ex: "4311 - CAS angajat"
precedent: Decimal # sold luna precedentă (pozitiv=datorie, negativ=creanță)
curent: Decimal # sold luna curentă (pozitiv=datorie, negativ=creanță)
class BudgetDebtGroup(BaseModel):
"""Grup de datorii la buget (TVA / BASS / CAM)"""
key: str # 'TVA', 'BASS', 'CAM'
label: str # 'TVA', 'BASS', 'CAM'
precedent: Decimal # total grup luna prec (semn ±)
curent: Decimal # total grup luna crt (semn ±)
sub_accounts: List[BudgetDebtSubAccount] = []
class TreasuryAccount(BaseModel):
"""Cont de trezorerie (bancă/casă)"""
cont: str # 5121, 5124, 5311, 5314
@@ -126,4 +142,8 @@ class DashboardSummary(BaseModel):
tva_plata_precedent: Decimal = Decimal('0')
tva_recuperat_precedent: Decimal = Decimal('0')
tva_plata_curent: Decimal = Decimal('0')
tva_recuperat_curent: Decimal = Decimal('0')
tva_recuperat_curent: Decimal = Decimal('0')
# DATORII LA BUGET - breakdown pe grupe (TVA / BASS / CAM) cu sub-conturi
budget_debt_breakdown: List[BudgetDebtGroup] = []
budget_debt_total_precedent: Decimal = Decimal('0') # suma tuturor grupurilor luna prec

View File

@@ -2,7 +2,7 @@
import os
from shared.database.oracle_pool import oracle_pool
from ..models.dashboard import DashboardSummary, TreasuryAccount, TrendData
from ..models.dashboard import DashboardSummary, TreasuryAccount, TrendData, BudgetDebtGroup, BudgetDebtSubAccount
from ..cache.decorators import cached
from decimal import Decimal
from typing import Dict, Any, List, Optional
@@ -12,6 +12,45 @@ import logging
logger = logging.getLogger(__name__)
# Grupuri de conturi pentru Datorii la Buget (3 grupe: TVA / BASS / CAM)
BUDGET_GROUPS = [
{
'key': 'TVA',
'label': 'TVA',
'accounts': ['4423', '4424'],
},
{
'key': 'BASS',
'label': 'BASS',
'accounts': ['431', '4311', '4313', '4315', '4316', '437', '444', '4411', '4418', '446', '447'],
},
{
'key': 'CAM',
'label': 'CAM',
'accounts': ['436'],
},
]
ACCOUNT_LABELS = {
'4311': '4311 - CAS angajat',
'4313': '4313 - CAS accidente muncă',
'4315': '4315 - CAS suplimentar',
'4316': '4316 - CASS angajat',
'431': '431 - CAS (cont total)',
'437': '437 - CASS (cont total)',
'444': '444 - Impozit salarii',
'4411': '4411 - Impozit pe profit',
'4418': '4418 - Impozit amânat',
'446': '446 - Alte impozite și taxe',
'447': '447 - Fonduri speciale',
'4423': '4423 - TVA de plată',
'4424': '4424 - TVA de recuperat',
'4426': '4426 - TVA deductibilă',
'4427': '4427 - TVA colectată',
'436': '436 - CAM',
}
class DashboardService:
"""Service pentru dashboard - date agregate"""
@@ -412,13 +451,17 @@ class DashboardService:
AND ((cont IN ('5121','5311') AND soldcred - solddeb != 0)
OR (cont IN ('5124','5314') AND soldvalcred - soldvaldeb != 0))
GROUP BY cont, nume, nume_val
ORDER BY cont, nume
ORDER BY
CASE WHEN cont IN ('5121','5311') THEN 0 ELSE 1 END,
cont,
UPPER(nume)
"""
cursor.execute(treasury_query, period_params)
treasury_rows = cursor.fetchall()
# Query 3: Solduri TVA din tabelul vbal (folosește același period_cte)
# Query 3: Solduri datorii buget din tabelul vbal (43xx + 44xx)
# Extins față de TVA-only pentru a include CAS, CASS, impozite, etc.
tva_query = f"""
{period_cte}
SELECT
@@ -426,34 +469,42 @@ class DashboardService:
precdeb,
preccred,
ruldeb,
rulcred
rulcred,
solddeb,
soldcred
FROM {schema}.vbal
WHERE an = (SELECT anul FROM luna_curenta)
AND luna = (SELECT luna FROM luna_curenta)
AND cont IN ('4423', '4424', '4426', '4427')
AND (cont LIKE '43%' OR cont LIKE '44%')
ORDER BY cont
"""
cursor.execute(tva_query, period_params)
tva_rows = cursor.fetchall()
# Procesare solduri TVA
tva_data = {
'4423': {'precdeb': Decimal('0'), 'preccred': Decimal('0'), 'ruldeb': Decimal('0'), 'rulcred': Decimal('0')},
'4424': {'precdeb': Decimal('0'), 'preccred': Decimal('0'), 'ruldeb': Decimal('0'), 'rulcred': Decimal('0')},
'4426': {'precdeb': Decimal('0'), 'preccred': Decimal('0'), 'ruldeb': Decimal('0'), 'rulcred': Decimal('0')},
'4427': {'precdeb': Decimal('0'), 'preccred': Decimal('0'), 'ruldeb': Decimal('0'), 'rulcred': Decimal('0')}
}
# Procesare solduri: dict generic pentru toate conturile 43xx/44xx
_zero = {'precdeb': Decimal('0'), 'preccred': Decimal('0'), 'ruldeb': Decimal('0'), 'rulcred': Decimal('0'), 'solddeb': Decimal('0'), 'soldcred': Decimal('0')}
all_budget_data: Dict[str, Any] = {}
for row in tva_rows:
cont = row[0]
if cont in tva_data:
tva_data[cont]['precdeb'] = Decimal(str(row[1] or 0))
tva_data[cont]['preccred'] = Decimal(str(row[2] or 0))
tva_data[cont]['ruldeb'] = Decimal(str(row[3] or 0))
tva_data[cont]['rulcred'] = Decimal(str(row[4] or 0))
all_budget_data[cont] = {
'precdeb': Decimal(str(row[1] or 0)),
'preccred': Decimal(str(row[2] or 0)),
'ruldeb': Decimal(str(row[3] or 0)),
'rulcred': Decimal(str(row[4] or 0)),
'solddeb': Decimal(str(row[5] or 0)),
'soldcred': Decimal(str(row[6] or 0)),
}
# Backward compat: tva_data dict cu doar conturile TVA clasice
tva_data = {
k: all_budget_data.get(k, dict(_zero))
for k in ['4423', '4424', '4426', '4427']
}
# Calcul TVA Luna Precedentă - FIE de plată (4423) FIE de recuperat (4424)
# Primary: folosim soldurile conturilor de regularizare TVA (luna anterioară închisă)
sold_4423 = tva_data['4423']['preccred'] - tva_data['4423']['precdeb']
sold_4424 = tva_data['4424']['precdeb'] - tva_data['4424']['preccred']
@@ -464,18 +515,97 @@ class DashboardService:
tva_recuperat_precedent = sold_4424
tva_plata_precedent = Decimal('0')
else:
tva_plata_precedent = Decimal('0')
tva_recuperat_precedent = Decimal('0')
# Fallback: când luna anterioară nu e închisă (4423/4424 = 0),
# calculăm soldul lunar net al conturilor 4427/4426
sold_4427_prec = tva_data['4427']['preccred'] - tva_data['4427']['precdeb']
sold_4426_prec = tva_data['4426']['precdeb'] - tva_data['4426']['preccred']
diferenta_prec = sold_4427_prec - sold_4426_prec
if diferenta_prec > 0:
tva_plata_precedent = diferenta_prec
tva_recuperat_precedent = Decimal('0')
else:
tva_recuperat_precedent = -diferenta_prec
tva_plata_precedent = Decimal('0')
# Calcul TVA Luna Curentă - FIE de plată FIE de recuperat
diferenta_curent = tva_data['4427']['rulcred'] - tva_data['4426']['ruldeb']
# Calcul TVA Luna Curentă - rulaj lunar 4423/4424 (nu sold cumulat)
sold_4423_cur = tva_data['4423']['rulcred'] - tva_data['4423']['ruldeb']
sold_4424_cur = tva_data['4424']['ruldeb'] - tva_data['4424']['rulcred']
if diferenta_curent > 0:
tva_plata_curent = diferenta_curent
if sold_4423_cur > 0:
tva_plata_curent = sold_4423_cur
tva_recuperat_curent = Decimal('0')
else:
tva_recuperat_curent = -diferenta_curent
elif sold_4424_cur > 0:
tva_recuperat_curent = sold_4424_cur
tva_plata_curent = Decimal('0')
else:
# Fallback: rulaj lunar net 4427/4426 (ruldeb/rulcred = doar luna curentă)
sold_4427_cur = tva_data['4427']['rulcred'] - tva_data['4427']['ruldeb']
sold_4426_cur = tva_data['4426']['ruldeb'] - tva_data['4426']['rulcred']
diferenta_curent = sold_4427_cur - sold_4426_cur
if diferenta_curent > 0:
tva_plata_curent = diferenta_curent
tva_recuperat_curent = Decimal('0')
else:
tva_recuperat_curent = -diferenta_curent
tva_plata_curent = Decimal('0')
# Calcul Datorii la Buget - 3 grupe (TVA / BASS / CAM) cu sub-conturi
budget_debt_breakdown = []
for group_def in BUDGET_GROUPS:
sub_accounts = []
group_prec = Decimal('0')
group_cur = Decimal('0')
if group_def['key'] == 'TVA':
# TVA special: valorile deja calculate (semn ±)
tva_prec = tva_plata_precedent - tva_recuperat_precedent
tva_cur = tva_plata_curent - tva_recuperat_curent
group_prec = tva_prec
group_cur = tva_cur
# Sub-conturi TVA (doar cele cu sold non-zero)
for cont in ['4423', '4424', '4426', '4427']:
if cont in all_budget_data:
d = all_budget_data[cont]
if cont == '4424': # creanță → semn negativ
val_prec = -(d['precdeb'] - d['preccred'])
val_cur = -(d['ruldeb'] - d['rulcred'])
else: # 4423, 4426, 4427: rulcred - ruldeb (lunar, nu cumulat)
val_prec = d['preccred'] - d['precdeb']
val_cur = d['rulcred'] - d['ruldeb']
if val_prec != 0 or val_cur != 0:
sub_accounts.append(BudgetDebtSubAccount(
cont=cont,
label=ACCOUNT_LABELS[cont],
precedent=val_prec,
curent=val_cur,
))
else:
# BASS și CAM: matching exact pe codul contului
for account_code in group_def['accounts']:
if account_code in all_budget_data:
d = all_budget_data[account_code]
val_prec = d['preccred'] - d['precdeb'] # pozitiv = datorie
val_cur = d['rulcred'] - d['ruldeb'] # rulaj luna curentă (consistent cu TVA)
if val_prec != 0 or val_cur != 0:
sub_accounts.append(BudgetDebtSubAccount(
cont=account_code,
label=ACCOUNT_LABELS.get(account_code, account_code),
precedent=val_prec,
curent=val_cur,
))
group_prec += val_prec
group_cur += val_cur
if group_prec != 0 or group_cur != 0 or sub_accounts:
budget_debt_breakdown.append(BudgetDebtGroup(
key=group_def['key'],
label=group_def['label'],
precedent=group_prec,
curent=group_cur,
sub_accounts=sub_accounts,
))
budget_debt_total_precedent = sum(g.precedent for g in budget_debt_breakdown)
# Procesare trezorerie
treasury_accounts = []
@@ -562,7 +692,11 @@ class DashboardService:
tva_plata_precedent=tva_plata_precedent,
tva_recuperat_precedent=tva_recuperat_precedent,
tva_plata_curent=tva_plata_curent,
tva_recuperat_curent=tva_recuperat_curent
tva_recuperat_curent=tva_recuperat_curent,
# Datorii la buget pe grupe cu sub-conturi
budget_debt_breakdown=budget_debt_breakdown,
budget_debt_total_precedent=budget_debt_total_precedent,
)
@staticmethod
@@ -1691,7 +1825,9 @@ class DashboardService:
AND vb.luna = lc.luna
AND vb.cont IN ('5311', '5314', '5328', '5121', '5124')
AND (vb.solddeb - vb.soldcred) <> 0
ORDER BY tip, vb.cont
ORDER BY tip,
CASE WHEN vb.cont IN ('5121','5311') THEN 0 ELSE 1 END,
UPPER(vb.nume)
"""
cursor.execute(treasury_query, period_params)