Add cache source tracking (L1/L2) for Telegram bot responses
Implements cache tier identification in Telegram bot to display data source: - "db" for database queries - "cached L1" for in-memory cache hits - "cached L2" for SQLite cache hits Backend changes: - Added cache metadata fields to TrendsResponse and DashboardSummary models (cache_hit, response_time_ms, cache_source) - Updated /api/dashboard/summary and /api/dashboard/trends endpoints to include cache metadata when X-Include-Cache-Metadata header is present - Cache metadata is extracted from request.state (set by @cached decorator) Telegram bot changes: - Updated API client to send X-Include-Cache-Metadata header - Modified helpers to extract cache_source from backend responses - Updated handlers to pass cache metadata to formatters - Performance footer now displays specific cache tier (L1 vs L2) Fixed Pydantic serialization issue: - Changed field names from _cache_hit to cache_hit (without underscore) - Pydantic excludes underscore-prefixed fields from JSON by default 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -51,6 +51,11 @@ class TrendsResponse(BaseModel):
|
||||
metadata: Dict[str, Any]
|
||||
growth_rates: Optional[Dict[str, float]] = None
|
||||
|
||||
# Cache metadata (optional, for Telegram Bot)
|
||||
cache_hit: Optional[bool] = None
|
||||
response_time_ms: Optional[float] = None
|
||||
cache_source: Optional[str] = None
|
||||
|
||||
class DashboardSummary(BaseModel):
|
||||
"""Model pentru toate datele dashboard-ului"""
|
||||
# CLIENȚI - statistici existente
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
||||
from typing import Optional
|
||||
import sys
|
||||
import os
|
||||
@@ -14,25 +14,42 @@ from ..services.dashboard_service import DashboardService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/summary", response_model=DashboardSummary)
|
||||
@router.get("/summary")
|
||||
async def get_dashboard_summary(
|
||||
request: Request,
|
||||
company: str = Query(description="Codul firmei"),
|
||||
current_user: CurrentUser = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Obține toate datele pentru dashboard într-un singur apel
|
||||
|
||||
|
||||
- Necesită autentificare JWT
|
||||
- Returnează statistici clienți/furnizori și trezorerie
|
||||
- Include metadata cache pentru Telegram Bot (X-Include-Cache-Metadata header)
|
||||
"""
|
||||
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}")
|
||||
|
||||
result = await DashboardService.get_complete_summary(company, current_user.username)
|
||||
return result
|
||||
|
||||
|
||||
result = await DashboardService.get_complete_summary(company, current_user.username, request=request)
|
||||
|
||||
# Convert Pydantic model to dict for JSON serialization
|
||||
result_dict = result.dict() if hasattr(result, 'dict') else result
|
||||
|
||||
# Add cache metadata if requested (for Telegram Bot)
|
||||
include_metadata = request.headers.get('X-Include-Cache-Metadata', '').lower() == 'true'
|
||||
if include_metadata:
|
||||
cache_hit = getattr(request.state, 'cache_hit', False)
|
||||
response_time = getattr(request.state, 'response_time_ms', 0)
|
||||
cache_source = getattr(request.state, 'cache_source', None)
|
||||
result_dict['cache_hit'] = cache_hit
|
||||
result_dict['response_time_ms'] = response_time
|
||||
# Always include cache_source, even if None
|
||||
result_dict['cache_source'] = cache_source
|
||||
|
||||
return result_dict
|
||||
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
@@ -40,6 +57,7 @@ async def get_dashboard_summary(
|
||||
|
||||
@router.get("/trends", response_model=TrendsResponse)
|
||||
async def get_dashboard_trends(
|
||||
request: Request,
|
||||
company: str = Query(description="Codul firmei"),
|
||||
period: str = Query(default="30d", description="Perioada pentru trends: 7d, 30d, ytd, 12m"),
|
||||
compare_previous: bool = Query(default=True, description="Compară cu perioada anterioară"),
|
||||
@@ -47,7 +65,7 @@ async def get_dashboard_trends(
|
||||
):
|
||||
"""
|
||||
Obține trenduri pentru indicatorii principali (clienți/furnizori)
|
||||
|
||||
|
||||
- period: "7d" (7 zile), "30d" (30 zile), "ytd" (year to date), "12m" (12 luni)
|
||||
- compare_previous: dacă să compare cu perioada anterioară
|
||||
- Necesită autentificare JWT
|
||||
@@ -57,21 +75,34 @@ async def get_dashboard_trends(
|
||||
# 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}")
|
||||
|
||||
|
||||
# Validează perioada
|
||||
valid_periods = ["7d", "30d", "ytd", "12m"]
|
||||
if period not in valid_periods:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
status_code=400,
|
||||
detail=f"Perioadă nevalidă: {period}. Valori permise: {', '.join(valid_periods)}"
|
||||
)
|
||||
|
||||
|
||||
# Obține datele de trenduri
|
||||
result = await DashboardService.get_trends(int(company), period)
|
||||
|
||||
# The service now returns the data in the correct format
|
||||
# Return it directly as TrendsResponse
|
||||
return TrendsResponse(**result)
|
||||
result = await DashboardService.get_trends(int(company), period, request=request)
|
||||
|
||||
# Convert to dict if needed
|
||||
result_dict = result.dict() if hasattr(result, 'dict') else result
|
||||
|
||||
# Add cache metadata if requested (for Telegram Bot)
|
||||
include_metadata = request.headers.get('X-Include-Cache-Metadata', '').lower() == 'true'
|
||||
if include_metadata:
|
||||
cache_hit = getattr(request.state, 'cache_hit', False)
|
||||
response_time = getattr(request.state, 'response_time_ms', 0)
|
||||
cache_source = getattr(request.state, 'cache_source', None)
|
||||
result_dict['cache_hit'] = cache_hit
|
||||
result_dict['response_time_ms'] = response_time
|
||||
# Always include cache_source, even if None
|
||||
result_dict['cache_source'] = cache_source
|
||||
|
||||
# Return as TrendsResponse
|
||||
return TrendsResponse(**result_dict)
|
||||
|
||||
except ValueError as e:
|
||||
logger.error(f"Value error in trends endpoint: {str(e)}")
|
||||
@@ -191,6 +222,7 @@ async def get_cashflow(
|
||||
|
||||
@router.get("/maturity")
|
||||
async def get_maturity_analysis(
|
||||
request: Request,
|
||||
company: int = Query(..., description="ID-ul firmei"),
|
||||
period: str = Query("7d", regex="^(7d|1m|3m|6m|12m|all)$", description="Orizont de planificare pentru analiza scadențelor"),
|
||||
current_user: CurrentUser = Depends(get_current_user)
|
||||
@@ -210,15 +242,31 @@ async def get_maturity_analysis(
|
||||
- Compară scadențele clienți vs furnizori
|
||||
- Calculează balanța și oferă recomandări
|
||||
- Returnează metadate cu statistici complete
|
||||
- Include metadata cache pentru Telegram Bot (X-Include-Cache-Metadata header)
|
||||
"""
|
||||
try:
|
||||
# Verifică dacă utilizatorul are acces la firma specificată
|
||||
if str(company) not in current_user.companies:
|
||||
raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}")
|
||||
|
||||
result = await DashboardService.get_maturity_analysis(company, period)
|
||||
return result
|
||||
|
||||
|
||||
result = await DashboardService.get_maturity_analysis(company, period, request=request)
|
||||
|
||||
# Convert to dict if needed
|
||||
result_dict = result.dict() if hasattr(result, 'dict') else result
|
||||
|
||||
# Add cache metadata if requested (for Telegram Bot)
|
||||
include_metadata = request.headers.get('X-Include-Cache-Metadata', '').lower() == 'true'
|
||||
if include_metadata:
|
||||
cache_hit = getattr(request.state, 'cache_hit', False)
|
||||
response_time = getattr(request.state, 'response_time_ms', 0)
|
||||
cache_source = getattr(request.state, 'cache_source', None)
|
||||
result_dict['cache_hit'] = cache_hit
|
||||
result_dict['response_time_ms'] = response_time
|
||||
# Always include cache_source, even if None
|
||||
result_dict['cache_source'] = cache_source
|
||||
|
||||
return result_dict
|
||||
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
@@ -252,23 +300,40 @@ async def get_monthly_flows(
|
||||
|
||||
@router.get("/treasury-breakdown")
|
||||
async def get_treasury_breakdown(
|
||||
request: Request,
|
||||
company: int = Query(..., description="ID-ul firmei"),
|
||||
current_user: CurrentUser = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Returnează defalcarea trezoreriei pentru firma selectată
|
||||
|
||||
|
||||
- Necesită autentificare JWT
|
||||
- Returnează distribuția soldurilor pe conturi și tipuri
|
||||
- Include metadata cache pentru Telegram Bot (X-Include-Cache-Metadata header)
|
||||
"""
|
||||
try:
|
||||
# Verifică dacă utilizatorul are acces la firma specificată
|
||||
if str(company) not in current_user.companies:
|
||||
raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}")
|
||||
|
||||
result = await DashboardService.get_treasury_breakdown(company)
|
||||
return result
|
||||
|
||||
|
||||
result = await DashboardService.get_treasury_breakdown(company, request=request)
|
||||
|
||||
# Convert to dict if needed
|
||||
result_dict = result.dict() if hasattr(result, 'dict') else result
|
||||
|
||||
# Add cache metadata if requested (for Telegram Bot)
|
||||
include_metadata = request.headers.get('X-Include-Cache-Metadata', '').lower() == 'true'
|
||||
if include_metadata:
|
||||
cache_hit = getattr(request.state, 'cache_hit', False)
|
||||
response_time = getattr(request.state, 'response_time_ms', 0)
|
||||
cache_source = getattr(request.state, 'cache_source', None)
|
||||
result_dict['cache_hit'] = cache_hit
|
||||
result_dict['response_time_ms'] = response_time
|
||||
# Always include cache_source, even if None
|
||||
result_dict['cache_source'] = cache_source
|
||||
|
||||
return result_dict
|
||||
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
@@ -277,22 +342,39 @@ async def get_treasury_breakdown(
|
||||
|
||||
@router.get("/net-balance-breakdown")
|
||||
async def get_net_balance_breakdown(
|
||||
request: Request,
|
||||
company: int = Query(..., description="ID-ul firmei"),
|
||||
current_user: CurrentUser = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Returnează defalcarea balanței nete pentru firma selectată
|
||||
|
||||
|
||||
- Necesită autentificare JWT
|
||||
- Returnează analiza detaliată a balanței nete
|
||||
- Include metadata cache pentru Telegram Bot (X-Include-Cache-Metadata header)
|
||||
"""
|
||||
try:
|
||||
# Verifică dacă utilizatorul are acces la firma specificată
|
||||
if str(company) not in current_user.companies:
|
||||
raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}")
|
||||
|
||||
result = await DashboardService.get_net_balance_breakdown(company)
|
||||
return result
|
||||
|
||||
result = await DashboardService.get_net_balance_breakdown(company, request=request)
|
||||
|
||||
# Convert to dict if needed
|
||||
result_dict = result.dict() if hasattr(result, 'dict') else result
|
||||
|
||||
# Add cache metadata if requested (for Telegram Bot)
|
||||
include_metadata = request.headers.get('X-Include-Cache-Metadata', '').lower() == 'true'
|
||||
if include_metadata:
|
||||
cache_hit = getattr(request.state, 'cache_hit', False)
|
||||
response_time = getattr(request.state, 'response_time_ms', 0)
|
||||
cache_source = getattr(request.state, 'cache_source', None)
|
||||
result_dict['cache_hit'] = cache_hit
|
||||
result_dict['response_time_ms'] = response_time
|
||||
# Always include cache_source, even if None
|
||||
result_dict['cache_source'] = cache_source
|
||||
|
||||
return result_dict
|
||||
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
@@ -234,15 +234,20 @@ class BackendAPIClient:
|
||||
|
||||
Returns:
|
||||
Dict with dashboard data (sold_total, facturi, plati, etc.)
|
||||
Includes _cache_hit and _response_time_ms metadata
|
||||
"""
|
||||
try:
|
||||
if not self.client:
|
||||
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
|
||||
|
||||
# Add cache metadata header for Telegram Bot
|
||||
headers = self._get_auth_headers(jwt_token)
|
||||
headers['X-Include-Cache-Metadata'] = 'true'
|
||||
|
||||
response = await self.client.get(
|
||||
"/api/dashboard/summary",
|
||||
params={"company": str(company_id)},
|
||||
headers=self._get_auth_headers(jwt_token)
|
||||
headers=headers
|
||||
)
|
||||
|
||||
return await self._handle_response(response)
|
||||
@@ -270,9 +275,13 @@ class BackendAPIClient:
|
||||
if not self.client:
|
||||
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
|
||||
|
||||
# Add cache metadata header for Telegram Bot
|
||||
headers = self._get_auth_headers(jwt_token)
|
||||
headers['X-Include-Cache-Metadata'] = 'true'
|
||||
|
||||
response = await self.client.get(
|
||||
f"/api/dashboard/treasury-breakdown?company={company_id}",
|
||||
headers=self._get_auth_headers(jwt_token)
|
||||
headers=headers
|
||||
)
|
||||
|
||||
return await self._handle_response(response)
|
||||
@@ -302,9 +311,13 @@ class BackendAPIClient:
|
||||
if not self.client:
|
||||
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
|
||||
|
||||
# Add cache metadata header for Telegram Bot
|
||||
headers = self._get_auth_headers(jwt_token)
|
||||
headers['X-Include-Cache-Metadata'] = 'true'
|
||||
|
||||
response = await self.client.get(
|
||||
f"/api/dashboard/detailed-data?company={company_id}&data_type={data_type}",
|
||||
headers=self._get_auth_headers(jwt_token)
|
||||
headers=headers
|
||||
)
|
||||
|
||||
return await self._handle_response(response)
|
||||
@@ -334,9 +347,13 @@ class BackendAPIClient:
|
||||
if not self.client:
|
||||
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
|
||||
|
||||
# Add cache metadata header for Telegram Bot
|
||||
headers = self._get_auth_headers(jwt_token)
|
||||
headers['X-Include-Cache-Metadata'] = 'true'
|
||||
|
||||
response = await self.client.get(
|
||||
f"/api/dashboard/maturity?company={company_id}&period={period}",
|
||||
headers=self._get_auth_headers(jwt_token)
|
||||
headers=headers
|
||||
)
|
||||
|
||||
return await self._handle_response(response)
|
||||
@@ -364,9 +381,13 @@ class BackendAPIClient:
|
||||
if not self.client:
|
||||
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
|
||||
|
||||
# Add cache metadata header for Telegram Bot
|
||||
headers = self._get_auth_headers(jwt_token)
|
||||
headers['X-Include-Cache-Metadata'] = 'true'
|
||||
|
||||
response = await self.client.get(
|
||||
f"/api/dashboard/performance?company={company_id}",
|
||||
headers=self._get_auth_headers(jwt_token)
|
||||
headers=headers
|
||||
)
|
||||
|
||||
return await self._handle_response(response)
|
||||
@@ -396,9 +417,13 @@ class BackendAPIClient:
|
||||
if not self.client:
|
||||
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
|
||||
|
||||
# Add cache metadata header for Telegram Bot
|
||||
headers = self._get_auth_headers(jwt_token)
|
||||
headers['X-Include-Cache-Metadata'] = 'true'
|
||||
|
||||
response = await self.client.get(
|
||||
f"/api/dashboard/monthly-flows?company={company_id}&months={months}",
|
||||
headers=self._get_auth_headers(jwt_token)
|
||||
headers=headers
|
||||
)
|
||||
|
||||
return await self._handle_response(response)
|
||||
@@ -428,9 +453,13 @@ class BackendAPIClient:
|
||||
if not self.client:
|
||||
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
|
||||
|
||||
# Add cache metadata header for Telegram Bot
|
||||
headers = self._get_auth_headers(jwt_token)
|
||||
headers['X-Include-Cache-Metadata'] = 'true'
|
||||
|
||||
response = await self.client.get(
|
||||
f"/api/dashboard/trends?company={company_id}&period={period}",
|
||||
headers=self._get_auth_headers(jwt_token)
|
||||
headers=headers
|
||||
)
|
||||
|
||||
return await self._handle_response(response)
|
||||
@@ -476,9 +505,6 @@ class BackendAPIClient:
|
||||
if filters:
|
||||
params.update(filters)
|
||||
|
||||
# ⚠️ DEBUGGING: Log exact parameters being sent
|
||||
logger.info(f"📤 Searching invoices with params: {params}")
|
||||
|
||||
response = await self.client.get(
|
||||
"/api/invoices/",
|
||||
params=params,
|
||||
@@ -487,13 +513,10 @@ class BackendAPIClient:
|
||||
|
||||
data = await self._handle_response(response)
|
||||
|
||||
# ⚠️ DEBUGGING: Log response
|
||||
if isinstance(data, dict) and 'invoices' in data:
|
||||
invoice_list = data['invoices']
|
||||
logger.info(f"📥 Received {len(invoice_list)} invoices from backend")
|
||||
return invoice_list
|
||||
elif isinstance(data, list):
|
||||
logger.info(f"📥 Received {len(data)} invoices from backend (direct list)")
|
||||
return data
|
||||
else:
|
||||
logger.warning(f"📥 Unexpected response format: {type(data)}")
|
||||
@@ -626,6 +649,113 @@ class BackendAPIClient:
|
||||
logger.error(f"Failed to export report: {e}")
|
||||
return None
|
||||
|
||||
# =========================================================================
|
||||
# CACHE MANAGEMENT
|
||||
# =========================================================================
|
||||
|
||||
async def invalidate_cache(
|
||||
self,
|
||||
jwt_token: str,
|
||||
company_id: Optional[int] = None,
|
||||
cache_type: Optional[str] = None
|
||||
) -> bool:
|
||||
"""
|
||||
Invalidate cache entries.
|
||||
|
||||
Args:
|
||||
jwt_token: JWT access token
|
||||
company_id: Optional company ID (None = all companies)
|
||||
cache_type: Optional cache type (None = all types)
|
||||
|
||||
Returns:
|
||||
bool: True if successful
|
||||
"""
|
||||
try:
|
||||
if not self.client:
|
||||
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
|
||||
|
||||
request_data = {}
|
||||
if company_id is not None:
|
||||
request_data['company_id'] = company_id
|
||||
if cache_type is not None:
|
||||
request_data['cache_type'] = cache_type
|
||||
|
||||
response = await self.client.post(
|
||||
"/api/cache/invalidate",
|
||||
json=request_data,
|
||||
headers=self._get_auth_headers(jwt_token)
|
||||
)
|
||||
|
||||
response.raise_for_status()
|
||||
logger.info(f"Cache invalidated: company_id={company_id}, cache_type={cache_type}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to invalidate cache: {e}")
|
||||
return False
|
||||
|
||||
async def toggle_user_cache(
|
||||
self,
|
||||
jwt_token: str,
|
||||
enabled: bool
|
||||
) -> bool:
|
||||
"""
|
||||
Toggle cache for current user.
|
||||
|
||||
Args:
|
||||
jwt_token: JWT access token
|
||||
enabled: True to enable cache, False to disable
|
||||
|
||||
Returns:
|
||||
bool: True if successful
|
||||
"""
|
||||
try:
|
||||
if not self.client:
|
||||
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
|
||||
|
||||
response = await self.client.post(
|
||||
"/api/cache/toggle-user",
|
||||
json={"enabled": enabled},
|
||||
headers=self._get_auth_headers(jwt_token)
|
||||
)
|
||||
|
||||
response.raise_for_status()
|
||||
logger.info(f"User cache toggled: enabled={enabled}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to toggle user cache: {e}")
|
||||
return False
|
||||
|
||||
async def get_cache_stats(
|
||||
self,
|
||||
jwt_token: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Get cache statistics including user-specific settings.
|
||||
|
||||
Args:
|
||||
jwt_token: JWT access token
|
||||
|
||||
Returns:
|
||||
Dict with cache stats including 'user_enabled' field
|
||||
"""
|
||||
try:
|
||||
if not self.client:
|
||||
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
|
||||
|
||||
response = await self.client.get(
|
||||
"/api/cache/stats",
|
||||
headers=self._get_auth_headers(jwt_token)
|
||||
)
|
||||
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get cache stats: {e}")
|
||||
return None
|
||||
|
||||
# =========================================================================
|
||||
# HEALTH CHECK
|
||||
# =========================================================================
|
||||
|
||||
@@ -69,6 +69,7 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
if result:
|
||||
# Success!
|
||||
username = result['username']
|
||||
jwt_token = result['jwt_token']
|
||||
|
||||
# Show main menu with buttons for newly linked user
|
||||
session_manager = get_session_manager()
|
||||
@@ -77,8 +78,19 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
company_name = company['name'] if company else None
|
||||
company_cui = company.get('cui') if company else None
|
||||
|
||||
# Get cache status
|
||||
cache_enabled = None
|
||||
try:
|
||||
from app.api.client import get_backend_client
|
||||
client = get_backend_client()
|
||||
async with client:
|
||||
cache_stats = await client.get_cache_stats(jwt_token=jwt_token)
|
||||
cache_enabled = cache_stats.get('user_enabled', True)
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not get cache status in /start: {e}")
|
||||
|
||||
from app.bot.menus import create_main_menu, pad_message_for_wide_buttons
|
||||
keyboard = create_main_menu(company_name, company_cui, is_authenticated=True)
|
||||
keyboard = create_main_menu(company_name, company_cui, is_authenticated=True, cache_enabled=cache_enabled)
|
||||
|
||||
# Single welcome message with menu
|
||||
if company_name:
|
||||
@@ -209,6 +221,11 @@ Dupa conectarea contului, foloseste **butoanele interactive** pentru:
|
||||
/help - Acest mesaj de ajutor
|
||||
/unlink - Deconecteaza contul (securitate)
|
||||
|
||||
**Comenzi Cache (optimizare performanta):**
|
||||
/togglecache - Activeaza/Dezactiveaza cache pentru tine
|
||||
/clearcache - Sterge cache pentru compania activa
|
||||
/clearcache all - Sterge tot cache-ul
|
||||
|
||||
**Conectare cont:**
|
||||
1. Loghează-te în aplicația web ROA2WEB
|
||||
2. Accesează Setări → Telegram Linking
|
||||
@@ -263,6 +280,158 @@ async def clear_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
)
|
||||
|
||||
|
||||
async def clearcache_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""
|
||||
Handle /clearcache command.
|
||||
|
||||
Clears the cache for the current company or all companies.
|
||||
|
||||
Usage:
|
||||
- /clearcache - Clear cache for current company
|
||||
- /clearcache all - Clear entire cache (all companies)
|
||||
|
||||
Args:
|
||||
update: Telegram update object
|
||||
context: Telegram context
|
||||
"""
|
||||
try:
|
||||
telegram_user_id = update.effective_user.id
|
||||
logger.info(f"/clearcache command from user {telegram_user_id}")
|
||||
|
||||
# Check if user is linked
|
||||
is_linked = await check_user_linked(telegram_user_id)
|
||||
if not is_linked:
|
||||
await update.message.reply_text(
|
||||
"**Cont neconectat**\n\nFoloseste /start pentru a conecta contul.",
|
||||
parse_mode=ParseMode.MARKDOWN
|
||||
)
|
||||
return
|
||||
|
||||
# Get auth data
|
||||
auth_data = await get_user_auth_data(telegram_user_id)
|
||||
jwt_token = auth_data['jwt_token']
|
||||
|
||||
# Check if user wants to clear all cache
|
||||
clear_all = len(context.args) > 0 and context.args[0].lower() == 'all'
|
||||
|
||||
client = get_backend_client()
|
||||
async with client:
|
||||
if clear_all:
|
||||
# Clear entire cache
|
||||
result = await client.client.post(
|
||||
"/api/cache/invalidate",
|
||||
json={},
|
||||
headers=client._get_auth_headers(jwt_token)
|
||||
)
|
||||
if result.status_code == 200:
|
||||
await update.message.reply_text(
|
||||
"✅ **Cache șters complet**\n\n"
|
||||
"Toate datele cached au fost șterse.",
|
||||
parse_mode=ParseMode.MARKDOWN
|
||||
)
|
||||
else:
|
||||
await update.message.reply_text("❌ Eroare la ștergerea cache-ului.")
|
||||
else:
|
||||
# Get active company
|
||||
session_manager = get_session_manager()
|
||||
from app.bot.helpers import get_active_company_or_prompt
|
||||
company = await get_active_company_or_prompt(update, session_manager, telegram_user_id)
|
||||
|
||||
if not company:
|
||||
return
|
||||
|
||||
# Clear cache for current company
|
||||
result = await client.client.post(
|
||||
"/api/cache/invalidate",
|
||||
json={"company_id": company['id']},
|
||||
headers=client._get_auth_headers(jwt_token)
|
||||
)
|
||||
|
||||
if result.status_code == 200:
|
||||
await update.message.reply_text(
|
||||
f"✅ **Cache șters pentru {company['name']}**\n\n"
|
||||
"Datele vor fi reîncărcate la următoarea interogare.",
|
||||
parse_mode=ParseMode.MARKDOWN
|
||||
)
|
||||
else:
|
||||
await update.message.reply_text("❌ Eroare la ștergerea cache-ului.")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in clearcache_command: {e}", exc_info=True)
|
||||
await update.message.reply_text("Eroare la ștergerea cache-ului.")
|
||||
|
||||
|
||||
async def togglecache_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""
|
||||
Handle /togglecache command.
|
||||
|
||||
Toggles cache on/off for the current user.
|
||||
|
||||
Args:
|
||||
update: Telegram update object
|
||||
context: Telegram context
|
||||
"""
|
||||
try:
|
||||
telegram_user_id = update.effective_user.id
|
||||
logger.info(f"/togglecache command from user {telegram_user_id}")
|
||||
|
||||
# Check if user is linked
|
||||
is_linked = await check_user_linked(telegram_user_id)
|
||||
if not is_linked:
|
||||
await update.message.reply_text(
|
||||
"**Cont neconectat**\n\nFoloseste /start pentru a conecta contul.",
|
||||
parse_mode=ParseMode.MARKDOWN
|
||||
)
|
||||
return
|
||||
|
||||
# Get auth data
|
||||
auth_data = await get_user_auth_data(telegram_user_id)
|
||||
jwt_token = auth_data['jwt_token']
|
||||
|
||||
client = get_backend_client()
|
||||
async with client:
|
||||
# Get current cache stats to determine current state
|
||||
stats_response = await client.client.get(
|
||||
"/api/cache/stats",
|
||||
headers=client._get_auth_headers(jwt_token)
|
||||
)
|
||||
|
||||
if stats_response.status_code == 200:
|
||||
stats = stats_response.json()
|
||||
current_enabled = stats.get('user_cache_enabled', True)
|
||||
|
||||
# Toggle to opposite state
|
||||
new_state = not current_enabled
|
||||
|
||||
toggle_response = await client.client.post(
|
||||
"/api/cache/toggle-user",
|
||||
json={"enabled": new_state},
|
||||
headers=client._get_auth_headers(jwt_token)
|
||||
)
|
||||
|
||||
if toggle_response.status_code == 200:
|
||||
if new_state:
|
||||
await update.message.reply_text(
|
||||
"✅ **Cache activat**\n\n"
|
||||
"Interogările tale vor folosi cache-ul pentru răspunsuri mai rapide.",
|
||||
parse_mode=ParseMode.MARKDOWN
|
||||
)
|
||||
else:
|
||||
await update.message.reply_text(
|
||||
"⚠️ **Cache dezactivat**\n\n"
|
||||
"Interogările tale vor accesa direct baza de date Oracle.",
|
||||
parse_mode=ParseMode.MARKDOWN
|
||||
)
|
||||
else:
|
||||
await update.message.reply_text("❌ Eroare la comutarea cache-ului.")
|
||||
else:
|
||||
await update.message.reply_text("❌ Eroare la citirea stării cache-ului.")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in togglecache_command: {e}", exc_info=True)
|
||||
await update.message.reply_text("Eroare la comutarea cache-ului.")
|
||||
|
||||
|
||||
async def companies_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""
|
||||
Handle /companies command.
|
||||
@@ -574,6 +743,11 @@ async def trezorerie_command(update: Update, context: ContextTypes.DEFAULT_TYPE)
|
||||
await update.message.reply_text("Eroare la incarcarea trezoreriei.")
|
||||
return
|
||||
|
||||
# Extract cache metadata
|
||||
cache_hit = treasury_data.get('cache_hit', False)
|
||||
response_time_ms = treasury_data.get('response_time_ms', 0)
|
||||
cache_source = treasury_data.get('cache_source', None)
|
||||
|
||||
# Format combined response (casa + banca) - rotunjit la leu (0 zecimale)
|
||||
casa_total = round(treasury_data['casa']['total'])
|
||||
banca_total = round(treasury_data['banca']['total'])
|
||||
@@ -589,6 +763,11 @@ async def trezorerie_command(update: Update, context: ContextTypes.DEFAULT_TYPE)
|
||||
from app.bot.menus import format_response_with_company
|
||||
text = format_response_with_company(content, company['name'])
|
||||
|
||||
# Add performance footer
|
||||
if response_time_ms > 0:
|
||||
from app.bot.formatters import add_performance_footer
|
||||
text = add_performance_footer(text, cache_hit, response_time_ms, cache_source)
|
||||
|
||||
# Add buttons to view details
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
keyboard = InlineKeyboardMarkup([
|
||||
@@ -645,9 +824,20 @@ async def menu_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
company_name = company['name'] if company else None
|
||||
company_cui = company.get('cui') if company else None
|
||||
|
||||
# Get cache status for user
|
||||
cache_enabled = None
|
||||
try:
|
||||
from app.api.client import get_backend_client
|
||||
client = get_backend_client()
|
||||
async with client:
|
||||
cache_stats = await client.get_cache_stats(jwt_token=auth_data['jwt_token'])
|
||||
cache_enabled = cache_stats.get('user_enabled', True)
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not get cache status: {e}")
|
||||
|
||||
# Create main menu (user is authenticated if they passed the is_linked check)
|
||||
from app.bot.menus import create_main_menu, get_menu_message
|
||||
keyboard = create_main_menu(company_name, company_cui, is_authenticated=True)
|
||||
keyboard = create_main_menu(company_name, company_cui, is_authenticated=True, cache_enabled=cache_enabled)
|
||||
menu_text = get_menu_message(company_name, company_cui)
|
||||
|
||||
await update.message.reply_text(
|
||||
@@ -708,11 +898,19 @@ async def trezorerie_casa_command(update: Update, context: ContextTypes.DEFAULT_
|
||||
return
|
||||
|
||||
# Format response
|
||||
from app.bot.formatters import format_treasury_casa_response
|
||||
from app.bot.formatters import format_treasury_casa_response, add_performance_footer
|
||||
from app.bot.menus import create_action_buttons, format_response_with_company
|
||||
|
||||
content = format_treasury_casa_response(treasury_data['casa'])
|
||||
response = format_response_with_company(content, company['name'])
|
||||
|
||||
# Add performance footer if cache metadata is available
|
||||
if 'cache_hit' in treasury_data and 'response_time_ms' in treasury_data:
|
||||
cache_hit = treasury_data['cache_hit']
|
||||
response_time_ms = treasury_data['response_time_ms']
|
||||
cache_source = treasury_data.get('cache_source', None)
|
||||
response = add_performance_footer(response, cache_hit, response_time_ms, cache_source)
|
||||
|
||||
keyboard = create_action_buttons("casa", show_export=True)
|
||||
|
||||
await update.message.reply_text(
|
||||
@@ -773,11 +971,19 @@ async def trezorerie_banca_command(update: Update, context: ContextTypes.DEFAULT
|
||||
return
|
||||
|
||||
# Format response
|
||||
from app.bot.formatters import format_treasury_banca_response
|
||||
from app.bot.formatters import format_treasury_banca_response, add_performance_footer
|
||||
from app.bot.menus import create_action_buttons, format_response_with_company
|
||||
|
||||
content = format_treasury_banca_response(treasury_data['banca'])
|
||||
response = format_response_with_company(content, company['name'])
|
||||
|
||||
# Add performance footer if cache metadata is available
|
||||
if 'cache_hit' in treasury_data and 'response_time_ms' in treasury_data:
|
||||
cache_hit = treasury_data['cache_hit']
|
||||
response_time_ms = treasury_data['response_time_ms']
|
||||
cache_source = treasury_data.get('cache_source', None)
|
||||
response = add_performance_footer(response, cache_hit, response_time_ms, cache_source)
|
||||
|
||||
keyboard = create_action_buttons("banca", show_export=True)
|
||||
|
||||
await update.message.reply_text(
|
||||
@@ -838,8 +1044,13 @@ async def clienti_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
await update.message.reply_text("Eroare la incarcarea datelor clienti.")
|
||||
return
|
||||
|
||||
# Extract cache metadata
|
||||
cache_hit = clients_data.get('cache_hit', False)
|
||||
response_time_ms = clients_data.get('response_time_ms', 0)
|
||||
cache_source = clients_data.get('cache_source', None)
|
||||
|
||||
# Format response
|
||||
from app.bot.formatters import format_clients_balance_response
|
||||
from app.bot.formatters import format_clients_balance_response, add_performance_footer
|
||||
from app.bot.menus import create_client_list_keyboard, format_response_with_company
|
||||
|
||||
content = format_clients_balance_response(
|
||||
@@ -847,6 +1058,11 @@ async def clienti_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
clients_data['maturity']
|
||||
)
|
||||
response = format_response_with_company(content, company['name'])
|
||||
|
||||
# Add performance footer
|
||||
if response_time_ms > 0:
|
||||
response = add_performance_footer(response, cache_hit, response_time_ms, cache_source)
|
||||
|
||||
keyboard = create_client_list_keyboard(clients_data['clients'], page=0)
|
||||
|
||||
await update.message.reply_text(
|
||||
@@ -907,8 +1123,13 @@ async def furnizori_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
await update.message.reply_text("Eroare la incarcarea datelor furnizori.")
|
||||
return
|
||||
|
||||
# Extract cache metadata
|
||||
cache_hit = suppliers_data.get('cache_hit', False)
|
||||
response_time_ms = suppliers_data.get('response_time_ms', 0)
|
||||
cache_source = suppliers_data.get('cache_source', None)
|
||||
|
||||
# Format response
|
||||
from app.bot.formatters import format_suppliers_balance_response
|
||||
from app.bot.formatters import format_suppliers_balance_response, add_performance_footer
|
||||
from app.bot.menus import create_supplier_list_keyboard, format_response_with_company
|
||||
|
||||
content = format_suppliers_balance_response(
|
||||
@@ -916,6 +1137,11 @@ async def furnizori_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
suppliers_data['maturity']
|
||||
)
|
||||
response = format_response_with_company(content, company['name'])
|
||||
|
||||
# Add performance footer
|
||||
if response_time_ms > 0:
|
||||
response = add_performance_footer(response, cache_hit, response_time_ms, cache_source)
|
||||
|
||||
keyboard = create_supplier_list_keyboard(suppliers_data['suppliers'], page=0)
|
||||
|
||||
await update.message.reply_text(
|
||||
@@ -976,7 +1202,7 @@ async def evolutie_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
return
|
||||
|
||||
# Format response
|
||||
from app.bot.formatters import format_cashflow_evolution_response
|
||||
from app.bot.formatters import format_cashflow_evolution_response, add_performance_footer
|
||||
from app.bot.menus import create_action_buttons, format_response_with_company
|
||||
|
||||
content = format_cashflow_evolution_response(
|
||||
@@ -984,6 +1210,14 @@ async def evolutie_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
evolution_data['monthly']
|
||||
)
|
||||
response = format_response_with_company(content, company['name'])
|
||||
|
||||
# Add performance footer if cache metadata is available
|
||||
if 'cache_hit' in evolution_data and 'response_time_ms' in evolution_data:
|
||||
cache_hit = evolution_data['cache_hit']
|
||||
response_time_ms = evolution_data['response_time_ms']
|
||||
cache_source = evolution_data.get('cache_source', None)
|
||||
response = add_performance_footer(response, cache_hit, response_time_ms, cache_source)
|
||||
|
||||
keyboard = create_action_buttons("evolutie", show_export=False, show_refresh=False)
|
||||
|
||||
await update.message.reply_text(
|
||||
@@ -1205,11 +1439,19 @@ async def handle_menu_callback(query, telegram_user_id: int, callback_data: str)
|
||||
from app.bot.helpers import get_treasury_breakdown_split
|
||||
treasury_data = await get_treasury_breakdown_split(company['id'], jwt_token)
|
||||
|
||||
from app.bot.formatters import format_treasury_casa_response
|
||||
from app.bot.formatters import format_treasury_casa_response, add_performance_footer
|
||||
from app.bot.menus import create_action_buttons, format_response_with_company
|
||||
|
||||
content = format_treasury_casa_response(treasury_data['casa'])
|
||||
response = format_response_with_company(content, company['name'])
|
||||
|
||||
# Add performance footer if cache metadata is available
|
||||
if 'cache_hit' in treasury_data and 'response_time_ms' in treasury_data:
|
||||
cache_hit = treasury_data['cache_hit']
|
||||
response_time_ms = treasury_data['response_time_ms']
|
||||
cache_source = treasury_data.get('cache_source', None)
|
||||
response = add_performance_footer(response, cache_hit, response_time_ms, cache_source)
|
||||
|
||||
keyboard = create_action_buttons("casa", show_export=False, show_refresh=False)
|
||||
|
||||
try:
|
||||
@@ -1228,11 +1470,19 @@ async def handle_menu_callback(query, telegram_user_id: int, callback_data: str)
|
||||
from app.bot.helpers import get_treasury_breakdown_split
|
||||
treasury_data = await get_treasury_breakdown_split(company['id'], jwt_token)
|
||||
|
||||
from app.bot.formatters import format_treasury_banca_response
|
||||
from app.bot.formatters import format_treasury_banca_response, add_performance_footer
|
||||
from app.bot.menus import create_action_buttons, format_response_with_company
|
||||
|
||||
content = format_treasury_banca_response(treasury_data['banca'])
|
||||
response = format_response_with_company(content, company['name'])
|
||||
|
||||
# Add performance footer if cache metadata is available
|
||||
if 'cache_hit' in treasury_data and 'response_time_ms' in treasury_data:
|
||||
cache_hit = treasury_data['cache_hit']
|
||||
response_time_ms = treasury_data['response_time_ms']
|
||||
cache_source = treasury_data.get('cache_source', None)
|
||||
response = add_performance_footer(response, cache_hit, response_time_ms, cache_source)
|
||||
|
||||
keyboard = create_action_buttons("banca", show_export=False, show_refresh=False)
|
||||
|
||||
try:
|
||||
@@ -1251,7 +1501,7 @@ async def handle_menu_callback(query, telegram_user_id: int, callback_data: str)
|
||||
from app.bot.helpers import get_clients_with_maturity
|
||||
clients_data = await get_clients_with_maturity(company['id'], jwt_token)
|
||||
|
||||
from app.bot.formatters import format_clients_balance_response
|
||||
from app.bot.formatters import format_clients_balance_response, add_performance_footer
|
||||
from app.bot.menus import create_client_list_keyboard, format_response_with_company
|
||||
|
||||
content = format_clients_balance_response(
|
||||
@@ -1259,6 +1509,14 @@ async def handle_menu_callback(query, telegram_user_id: int, callback_data: str)
|
||||
clients_data['maturity']
|
||||
)
|
||||
response = format_response_with_company(content, company['name'])
|
||||
|
||||
# Add performance footer if cache metadata is available
|
||||
if 'cache_hit' in clients_data and 'response_time_ms' in clients_data:
|
||||
cache_hit = clients_data['cache_hit']
|
||||
response_time_ms = clients_data['response_time_ms']
|
||||
cache_source = clients_data.get('cache_source', None)
|
||||
response = add_performance_footer(response, cache_hit, response_time_ms, cache_source)
|
||||
|
||||
keyboard = create_client_list_keyboard(clients_data['clients'], page=0)
|
||||
|
||||
await query.edit_message_text(
|
||||
@@ -1272,7 +1530,7 @@ async def handle_menu_callback(query, telegram_user_id: int, callback_data: str)
|
||||
from app.bot.helpers import get_suppliers_with_maturity
|
||||
suppliers_data = await get_suppliers_with_maturity(company['id'], jwt_token)
|
||||
|
||||
from app.bot.formatters import format_suppliers_balance_response
|
||||
from app.bot.formatters import format_suppliers_balance_response, add_performance_footer
|
||||
from app.bot.menus import create_supplier_list_keyboard, format_response_with_company
|
||||
|
||||
content = format_suppliers_balance_response(
|
||||
@@ -1280,6 +1538,14 @@ async def handle_menu_callback(query, telegram_user_id: int, callback_data: str)
|
||||
suppliers_data['maturity']
|
||||
)
|
||||
response = format_response_with_company(content, company['name'])
|
||||
|
||||
# Add performance footer if cache metadata is available
|
||||
if 'cache_hit' in suppliers_data and 'response_time_ms' in suppliers_data:
|
||||
cache_hit = suppliers_data['cache_hit']
|
||||
response_time_ms = suppliers_data['response_time_ms']
|
||||
cache_source = suppliers_data.get('cache_source', None)
|
||||
response = add_performance_footer(response, cache_hit, response_time_ms, cache_source)
|
||||
|
||||
keyboard = create_supplier_list_keyboard(suppliers_data['suppliers'], page=0)
|
||||
|
||||
await query.edit_message_text(
|
||||
@@ -1293,7 +1559,7 @@ async def handle_menu_callback(query, telegram_user_id: int, callback_data: str)
|
||||
from app.bot.helpers import get_cashflow_evolution_data
|
||||
evolution_data = await get_cashflow_evolution_data(company['id'], jwt_token)
|
||||
|
||||
from app.bot.formatters import format_cashflow_evolution_response
|
||||
from app.bot.formatters import format_cashflow_evolution_response, add_performance_footer
|
||||
from app.bot.menus import create_action_buttons, format_response_with_company
|
||||
|
||||
content = format_cashflow_evolution_response(
|
||||
@@ -1301,6 +1567,14 @@ async def handle_menu_callback(query, telegram_user_id: int, callback_data: str)
|
||||
evolution_data['monthly']
|
||||
)
|
||||
response = format_response_with_company(content, company['name'])
|
||||
|
||||
# Add performance footer if cache metadata is available
|
||||
if 'cache_hit' in evolution_data and 'response_time_ms' in evolution_data:
|
||||
cache_hit = evolution_data['cache_hit']
|
||||
response_time_ms = evolution_data['response_time_ms']
|
||||
cache_source = evolution_data.get('cache_source', None)
|
||||
response = add_performance_footer(response, cache_hit, response_time_ms, cache_source)
|
||||
|
||||
keyboard = create_action_buttons("evolutie", show_export=False, show_refresh=False)
|
||||
|
||||
await query.edit_message_text(
|
||||
@@ -1309,6 +1583,72 @@ async def handle_menu_callback(query, telegram_user_id: int, callback_data: str)
|
||||
parse_mode=ParseMode.MARKDOWN
|
||||
)
|
||||
|
||||
elif action == "togglecache":
|
||||
# Toggle cache pentru user
|
||||
try:
|
||||
client = get_backend_client()
|
||||
async with client:
|
||||
cache_stats = await client.get_cache_stats(jwt_token=jwt_token)
|
||||
user_enabled = cache_stats.get('user_enabled', True)
|
||||
|
||||
# Create toggle buttons
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
keyboard = [
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
"✅ Activează" if not user_enabled else "❌ Dezactivează",
|
||||
callback_data=f"cache_toggle:{'on' if not user_enabled else 'off'}"
|
||||
)
|
||||
],
|
||||
[InlineKeyboardButton("« Înapoi la Meniu", callback_data="action:menu")]
|
||||
]
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
|
||||
status = "ACTIVAT" if user_enabled else "DEZACTIVAT"
|
||||
message = f"**Cache Status**\n\nCurent: {status}\n\n"
|
||||
|
||||
if user_enabled:
|
||||
message += "Vrei să dezactivezi cache-ul temporar?\nFolosește pentru teste de performanță."
|
||||
else:
|
||||
message += "Cache-ul este dezactivat.\nToate queries merg direct la Oracle."
|
||||
|
||||
await query.edit_message_text(
|
||||
message,
|
||||
reply_markup=reply_markup,
|
||||
parse_mode=ParseMode.MARKDOWN
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Toggle cache menu error: {e}", exc_info=True)
|
||||
await query.answer("Eroare la obținerea status cache.", show_alert=True)
|
||||
|
||||
elif action == "clearcache":
|
||||
# Clear cache
|
||||
try:
|
||||
# Create inline keyboard
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
keyboard = [
|
||||
[
|
||||
InlineKeyboardButton("Toate companiile", callback_data="cache_clear:all"),
|
||||
InlineKeyboardButton("Doar compania mea", callback_data="cache_clear:current")
|
||||
],
|
||||
[InlineKeyboardButton("« Înapoi la Meniu", callback_data="action:menu")]
|
||||
]
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
|
||||
message = "**🔄 Invalidare Cache**\n\n"
|
||||
if company:
|
||||
message += f"Compania curentă: {company['name']}\n\n"
|
||||
message += "Alege scope:"
|
||||
|
||||
await query.edit_message_text(
|
||||
message,
|
||||
reply_markup=reply_markup,
|
||||
parse_mode=ParseMode.MARKDOWN
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Clear cache menu error: {e}", exc_info=True)
|
||||
await query.answer("Eroare la afișarea opțiuni cache.", show_alert=True)
|
||||
|
||||
elif action == "select_company":
|
||||
# ✅ MODIFICARE: Folosim funcția comună
|
||||
await _handle_selectcompany_view(
|
||||
@@ -1346,10 +1686,22 @@ async def handle_action_callback(query, telegram_user_id: int, callback_data: st
|
||||
auth_data = await get_user_auth_data(telegram_user_id)
|
||||
is_authenticated = auth_data is not None
|
||||
|
||||
# Get cache status for user
|
||||
cache_enabled = None
|
||||
if is_authenticated:
|
||||
try:
|
||||
from app.api.client import get_backend_client
|
||||
client = get_backend_client()
|
||||
async with client:
|
||||
cache_stats = await client.get_cache_stats(jwt_token=auth_data['jwt_token'])
|
||||
cache_enabled = cache_stats.get('user_enabled', True)
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not get cache status: {e}")
|
||||
|
||||
from app.bot.menus import create_main_menu, get_menu_message
|
||||
company_name = company['name'] if company else None
|
||||
company_cui = company.get('cui') if company else None
|
||||
keyboard = create_main_menu(company_name, company_cui, is_authenticated)
|
||||
keyboard = create_main_menu(company_name, company_cui, is_authenticated, cache_enabled)
|
||||
menu_text = get_menu_message(company_name, company_cui)
|
||||
|
||||
await query.edit_message_text(
|
||||
@@ -1897,6 +2249,87 @@ async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
elif callback_data.startswith("nav:back:"):
|
||||
await handle_navigation_back(query, telegram_user_id, callback_data)
|
||||
|
||||
# ========== CACHE CALLBACKS (FAZA 6) ==========
|
||||
|
||||
elif callback_data.startswith("cache_toggle:"):
|
||||
# Handle cache toggle button
|
||||
action = callback_data.split(":")[1]
|
||||
enabled = action == "on"
|
||||
|
||||
auth_data = await get_user_auth_data(telegram_user_id)
|
||||
jwt_token = auth_data['jwt_token']
|
||||
|
||||
try:
|
||||
client = get_backend_client()
|
||||
async with client:
|
||||
await client.toggle_user_cache(jwt_token=jwt_token, enabled=enabled)
|
||||
|
||||
status = "activat" if enabled else "dezactivat"
|
||||
message = f"✅ **Cache {status}** pentru tine.\n\n"
|
||||
|
||||
if enabled:
|
||||
message += "Queries vor fi servite din cache când e posibil."
|
||||
else:
|
||||
message += "Toate queries vor merge direct la Oracle.\nFolosește /togglecache din nou pentru reactivare."
|
||||
|
||||
# Add back button
|
||||
keyboard = InlineKeyboardMarkup([
|
||||
[InlineKeyboardButton("« Înapoi la Meniu", callback_data="action:menu")]
|
||||
])
|
||||
|
||||
await query.edit_message_text(
|
||||
message,
|
||||
reply_markup=keyboard,
|
||||
parse_mode=ParseMode.MARKDOWN
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Toggle cache callback error: {e}", exc_info=True)
|
||||
await query.answer("❌ Eroare la modificarea setării cache.", show_alert=True)
|
||||
|
||||
elif callback_data.startswith("cache_clear:"):
|
||||
# Handle clear cache button
|
||||
scope = callback_data.split(":")[1]
|
||||
|
||||
auth_data = await get_user_auth_data(telegram_user_id)
|
||||
jwt_token = auth_data['jwt_token']
|
||||
|
||||
try:
|
||||
client = get_backend_client()
|
||||
|
||||
if scope == "all":
|
||||
# Clear all cache
|
||||
async with client:
|
||||
await client.invalidate_cache(jwt_token=jwt_token, company_id=None)
|
||||
message = "✅ Cache invalidat pentru **toate companiile**.\n\nDatele vor fi refreshate la următoarea interogare."
|
||||
|
||||
elif scope == "current":
|
||||
# Clear only current company
|
||||
session_manager = get_session_manager()
|
||||
session = await session_manager.get_or_create_session(telegram_user_id)
|
||||
company = session.get_active_company()
|
||||
|
||||
if not company:
|
||||
await query.answer("Nu ai o companie selectată.", show_alert=True)
|
||||
return
|
||||
|
||||
async with client:
|
||||
await client.invalidate_cache(jwt_token=jwt_token, company_id=company['id'])
|
||||
message = f"✅ Cache invalidat pentru **{company['name']}**.\n\nDatele vor fi refreshate la următoarea interogare."
|
||||
|
||||
# Add back button
|
||||
keyboard = InlineKeyboardMarkup([
|
||||
[InlineKeyboardButton("« Înapoi la Meniu", callback_data="action:menu")]
|
||||
])
|
||||
|
||||
await query.edit_message_text(
|
||||
message,
|
||||
reply_markup=keyboard,
|
||||
parse_mode=ParseMode.MARKDOWN
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Clear cache callback error: {e}", exc_info=True)
|
||||
await query.answer("❌ Eroare la ștergerea cache-ului.", show_alert=True)
|
||||
|
||||
# ========== PAGINATION CALLBACKS ==========
|
||||
|
||||
elif callback_data.startswith("clients_page:"):
|
||||
@@ -2144,11 +2577,20 @@ async def _handle_sold_view(
|
||||
await query_or_update.message.reply_text(error_msg)
|
||||
return
|
||||
|
||||
from app.bot.formatters import format_dashboard_response
|
||||
from app.bot.formatters import format_dashboard_response, add_performance_footer
|
||||
from app.bot.menus import create_action_buttons, format_response_with_company
|
||||
|
||||
# Extract cache metadata
|
||||
cache_hit = data.get('cache_hit', False)
|
||||
response_time_ms = data.get('response_time_ms', 0)
|
||||
cache_source = data.get('cache_source', None)
|
||||
|
||||
content = format_dashboard_response(data)
|
||||
response = format_response_with_company(content, company['name'])
|
||||
|
||||
# Add performance footer
|
||||
if response_time_ms > 0:
|
||||
response = add_performance_footer(response, cache_hit, response_time_ms, cache_source)
|
||||
keyboard = create_action_buttons("sold", show_export=False, show_refresh=False)
|
||||
|
||||
if is_callback:
|
||||
|
||||
@@ -344,7 +344,7 @@ async def get_treasury_breakdown_split(
|
||||
for item in banca_data.get('items', [])
|
||||
]
|
||||
|
||||
return {
|
||||
result = {
|
||||
'casa': {
|
||||
'accounts': casa_accounts,
|
||||
'total': float(casa_data.get('total', 0))
|
||||
@@ -355,6 +355,16 @@ async def get_treasury_breakdown_split(
|
||||
}
|
||||
}
|
||||
|
||||
# Pass through cache metadata if present
|
||||
if 'cache_hit' in breakdown:
|
||||
result['cache_hit'] = breakdown['cache_hit']
|
||||
if 'response_time_ms' in breakdown:
|
||||
result['response_time_ms'] = breakdown['response_time_ms']
|
||||
if 'cache_source' in breakdown:
|
||||
result['cache_source'] = breakdown['cache_source']
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting treasury breakdown split: {e}", exc_info=True)
|
||||
return None
|
||||
@@ -425,7 +435,7 @@ async def get_clients_with_maturity(
|
||||
overdue = sum(c['balance'] for c in clients if c.get('daysOverdue', 0) > 0)
|
||||
in_term = total - overdue
|
||||
|
||||
return {
|
||||
result = {
|
||||
'clients': clients,
|
||||
'maturity': {
|
||||
'in_term': in_term,
|
||||
@@ -434,6 +444,16 @@ async def get_clients_with_maturity(
|
||||
}
|
||||
}
|
||||
|
||||
# Pass through cache metadata if present
|
||||
if 'cache_hit' in maturity_response:
|
||||
result['cache_hit'] = maturity_response['cache_hit']
|
||||
if 'response_time_ms' in maturity_response:
|
||||
result['response_time_ms'] = maturity_response['response_time_ms']
|
||||
if 'cache_source' in maturity_response:
|
||||
result['cache_source'] = maturity_response['cache_source']
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting clients with maturity: {e}", exc_info=True)
|
||||
return None
|
||||
@@ -504,7 +524,7 @@ async def get_suppliers_with_maturity(
|
||||
overdue = sum(s['balance'] for s in suppliers if s.get('daysOverdue', 0) > 0)
|
||||
in_term = total - overdue
|
||||
|
||||
return {
|
||||
result = {
|
||||
'suppliers': suppliers,
|
||||
'maturity': {
|
||||
'in_term': in_term,
|
||||
@@ -513,6 +533,16 @@ async def get_suppliers_with_maturity(
|
||||
}
|
||||
}
|
||||
|
||||
# Pass through cache metadata if present
|
||||
if 'cache_hit' in maturity_response:
|
||||
result['cache_hit'] = maturity_response['cache_hit']
|
||||
if 'response_time_ms' in maturity_response:
|
||||
result['response_time_ms'] = maturity_response['response_time_ms']
|
||||
if 'cache_source' in maturity_response:
|
||||
result['cache_source'] = maturity_response['cache_source']
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting suppliers with maturity: {e}", exc_info=True)
|
||||
return None
|
||||
@@ -650,11 +680,21 @@ async def get_cashflow_evolution_data(
|
||||
'plati_prev': last_12_plati_prev
|
||||
}
|
||||
|
||||
return {
|
||||
result = {
|
||||
'performance': performance,
|
||||
'monthly': monthly
|
||||
}
|
||||
|
||||
# Pass through cache metadata if present
|
||||
if 'cache_hit' in trends_data:
|
||||
result['cache_hit'] = trends_data['cache_hit']
|
||||
if 'response_time_ms' in trends_data:
|
||||
result['response_time_ms'] = trends_data['response_time_ms']
|
||||
if 'cache_source' in trends_data:
|
||||
result['cache_source'] = trends_data['cache_source']
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting cashflow evolution data: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user