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:
2025-11-07 22:39:09 +02:00
parent 87bd04e3ff
commit 2a37959d80
5 changed files with 759 additions and 60 deletions

View File

@@ -51,6 +51,11 @@ class TrendsResponse(BaseModel):
metadata: Dict[str, Any] metadata: Dict[str, Any]
growth_rates: Optional[Dict[str, float]] = None 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): class DashboardSummary(BaseModel):
"""Model pentru toate datele dashboard-ului""" """Model pentru toate datele dashboard-ului"""
# CLIENȚI - statistici existente # CLIENȚI - statistici existente

View File

@@ -1,4 +1,4 @@
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query, Request
from typing import Optional from typing import Optional
import sys import sys
import os import os
@@ -14,25 +14,42 @@ from ..services.dashboard_service import DashboardService
router = APIRouter() router = APIRouter()
@router.get("/summary", response_model=DashboardSummary) @router.get("/summary")
async def get_dashboard_summary( async def get_dashboard_summary(
request: Request,
company: str = Query(description="Codul firmei"), company: str = Query(description="Codul firmei"),
current_user: CurrentUser = Depends(get_current_user) current_user: CurrentUser = Depends(get_current_user)
): ):
""" """
Obține toate datele pentru dashboard într-un singur apel Obține toate datele pentru dashboard într-un singur apel
- Necesită autentificare JWT - Necesită autentificare JWT
- Returnează statistici clienți/furnizori și trezorerie - Returnează statistici clienți/furnizori și trezorerie
- Include metadata cache pentru Telegram Bot (X-Include-Cache-Metadata header)
""" """
try: try:
# Verifică dacă utilizatorul are acces la firma specificată # Verifică dacă utilizatorul are acces la firma specificată
if company not in current_user.companies: if company not in current_user.companies:
raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}") raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}")
result = await DashboardService.get_complete_summary(company, current_user.username) result = await DashboardService.get_complete_summary(company, current_user.username, request=request)
return result
# 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: except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
except Exception as e: except Exception as e:
@@ -40,6 +57,7 @@ async def get_dashboard_summary(
@router.get("/trends", response_model=TrendsResponse) @router.get("/trends", response_model=TrendsResponse)
async def get_dashboard_trends( async def get_dashboard_trends(
request: Request,
company: str = Query(description="Codul firmei"), company: str = Query(description="Codul firmei"),
period: str = Query(default="30d", description="Perioada pentru trends: 7d, 30d, ytd, 12m"), period: str = Query(default="30d", description="Perioada pentru trends: 7d, 30d, ytd, 12m"),
compare_previous: bool = Query(default=True, description="Compară cu perioada anterioară"), 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) Obține trenduri pentru indicatorii principali (clienți/furnizori)
- period: "7d" (7 zile), "30d" (30 zile), "ytd" (year to date), "12m" (12 luni) - period: "7d" (7 zile), "30d" (30 zile), "ytd" (year to date), "12m" (12 luni)
- compare_previous: dacă să compare cu perioada anterioară - compare_previous: dacă să compare cu perioada anterioară
- Necesită autentificare JWT - Necesită autentificare JWT
@@ -57,21 +75,34 @@ async def get_dashboard_trends(
# Verifică dacă utilizatorul are acces la firma specificată # Verifică dacă utilizatorul are acces la firma specificată
if company not in current_user.companies: if company not in current_user.companies:
raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}") raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}")
# Validează perioada # Validează perioada
valid_periods = ["7d", "30d", "ytd", "12m"] valid_periods = ["7d", "30d", "ytd", "12m"]
if period not in valid_periods: if period not in valid_periods:
raise HTTPException( raise HTTPException(
status_code=400, status_code=400,
detail=f"Perioadă nevalidă: {period}. Valori permise: {', '.join(valid_periods)}" detail=f"Perioadă nevalidă: {period}. Valori permise: {', '.join(valid_periods)}"
) )
# Obține datele de trenduri # Obține datele de trenduri
result = await DashboardService.get_trends(int(company), period) result = await DashboardService.get_trends(int(company), period, request=request)
# The service now returns the data in the correct format # Convert to dict if needed
# Return it directly as TrendsResponse result_dict = result.dict() if hasattr(result, 'dict') else result
return TrendsResponse(**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: except ValueError as e:
logger.error(f"Value error in trends endpoint: {str(e)}") logger.error(f"Value error in trends endpoint: {str(e)}")
@@ -191,6 +222,7 @@ async def get_cashflow(
@router.get("/maturity") @router.get("/maturity")
async def get_maturity_analysis( async def get_maturity_analysis(
request: Request,
company: int = Query(..., description="ID-ul firmei"), 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"), 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) current_user: CurrentUser = Depends(get_current_user)
@@ -210,15 +242,31 @@ async def get_maturity_analysis(
- Compară scadențele clienți vs furnizori - Compară scadențele clienți vs furnizori
- Calculează balanța și oferă recomandări - Calculează balanța și oferă recomandări
- Returnează metadate cu statistici complete - Returnează metadate cu statistici complete
- Include metadata cache pentru Telegram Bot (X-Include-Cache-Metadata header)
""" """
try: try:
# Verifică dacă utilizatorul are acces la firma specificată # Verifică dacă utilizatorul are acces la firma specificată
if str(company) not in current_user.companies: if str(company) not in current_user.companies:
raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}") raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}")
result = await DashboardService.get_maturity_analysis(company, period) result = await DashboardService.get_maturity_analysis(company, period, request=request)
return result
# 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: except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
except Exception as e: except Exception as e:
@@ -252,23 +300,40 @@ async def get_monthly_flows(
@router.get("/treasury-breakdown") @router.get("/treasury-breakdown")
async def get_treasury_breakdown( async def get_treasury_breakdown(
request: Request,
company: int = Query(..., description="ID-ul firmei"), company: int = Query(..., description="ID-ul firmei"),
current_user: CurrentUser = Depends(get_current_user) current_user: CurrentUser = Depends(get_current_user)
): ):
""" """
Returnează defalcarea trezoreriei pentru firma selectată Returnează defalcarea trezoreriei pentru firma selectată
- Necesită autentificare JWT - Necesită autentificare JWT
- Returnează distribuția soldurilor pe conturi și tipuri - Returnează distribuția soldurilor pe conturi și tipuri
- Include metadata cache pentru Telegram Bot (X-Include-Cache-Metadata header)
""" """
try: try:
# Verifică dacă utilizatorul are acces la firma specificată # Verifică dacă utilizatorul are acces la firma specificată
if str(company) not in current_user.companies: if str(company) not in current_user.companies:
raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}") raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}")
result = await DashboardService.get_treasury_breakdown(company) result = await DashboardService.get_treasury_breakdown(company, request=request)
return result
# 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: except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
except Exception as e: except Exception as e:
@@ -277,22 +342,39 @@ async def get_treasury_breakdown(
@router.get("/net-balance-breakdown") @router.get("/net-balance-breakdown")
async def get_net_balance_breakdown( async def get_net_balance_breakdown(
request: Request,
company: int = Query(..., description="ID-ul firmei"), company: int = Query(..., description="ID-ul firmei"),
current_user: CurrentUser = Depends(get_current_user) current_user: CurrentUser = Depends(get_current_user)
): ):
""" """
Returnează defalcarea balanței nete pentru firma selectată Returnează defalcarea balanței nete pentru firma selectată
- Necesită autentificare JWT - Necesită autentificare JWT
- Returnează analiza detaliată a balanței nete - Returnează analiza detaliată a balanței nete
- Include metadata cache pentru Telegram Bot (X-Include-Cache-Metadata header)
""" """
try: try:
# Verifică dacă utilizatorul are acces la firma specificată # Verifică dacă utilizatorul are acces la firma specificată
if str(company) not in current_user.companies: if str(company) not in current_user.companies:
raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}") raise HTTPException(status_code=403, detail=f"Nu aveți acces la firma {company}")
result = await DashboardService.get_net_balance_breakdown(company) result = await DashboardService.get_net_balance_breakdown(company, request=request)
return result
# 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: except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))

View File

@@ -234,15 +234,20 @@ class BackendAPIClient:
Returns: Returns:
Dict with dashboard data (sold_total, facturi, plati, etc.) Dict with dashboard data (sold_total, facturi, plati, etc.)
Includes _cache_hit and _response_time_ms metadata
""" """
try: try:
if not self.client: if not self.client:
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT) 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( response = await self.client.get(
"/api/dashboard/summary", "/api/dashboard/summary",
params={"company": str(company_id)}, params={"company": str(company_id)},
headers=self._get_auth_headers(jwt_token) headers=headers
) )
return await self._handle_response(response) return await self._handle_response(response)
@@ -270,9 +275,13 @@ class BackendAPIClient:
if not self.client: if not self.client:
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT) 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( response = await self.client.get(
f"/api/dashboard/treasury-breakdown?company={company_id}", f"/api/dashboard/treasury-breakdown?company={company_id}",
headers=self._get_auth_headers(jwt_token) headers=headers
) )
return await self._handle_response(response) return await self._handle_response(response)
@@ -302,9 +311,13 @@ class BackendAPIClient:
if not self.client: if not self.client:
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT) 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( response = await self.client.get(
f"/api/dashboard/detailed-data?company={company_id}&data_type={data_type}", 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) return await self._handle_response(response)
@@ -334,9 +347,13 @@ class BackendAPIClient:
if not self.client: if not self.client:
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT) 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( response = await self.client.get(
f"/api/dashboard/maturity?company={company_id}&period={period}", f"/api/dashboard/maturity?company={company_id}&period={period}",
headers=self._get_auth_headers(jwt_token) headers=headers
) )
return await self._handle_response(response) return await self._handle_response(response)
@@ -364,9 +381,13 @@ class BackendAPIClient:
if not self.client: if not self.client:
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT) 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( response = await self.client.get(
f"/api/dashboard/performance?company={company_id}", f"/api/dashboard/performance?company={company_id}",
headers=self._get_auth_headers(jwt_token) headers=headers
) )
return await self._handle_response(response) return await self._handle_response(response)
@@ -396,9 +417,13 @@ class BackendAPIClient:
if not self.client: if not self.client:
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT) 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( response = await self.client.get(
f"/api/dashboard/monthly-flows?company={company_id}&months={months}", 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) return await self._handle_response(response)
@@ -428,9 +453,13 @@ class BackendAPIClient:
if not self.client: if not self.client:
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT) 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( response = await self.client.get(
f"/api/dashboard/trends?company={company_id}&period={period}", f"/api/dashboard/trends?company={company_id}&period={period}",
headers=self._get_auth_headers(jwt_token) headers=headers
) )
return await self._handle_response(response) return await self._handle_response(response)
@@ -476,9 +505,6 @@ class BackendAPIClient:
if filters: if filters:
params.update(filters) params.update(filters)
# ⚠️ DEBUGGING: Log exact parameters being sent
logger.info(f"📤 Searching invoices with params: {params}")
response = await self.client.get( response = await self.client.get(
"/api/invoices/", "/api/invoices/",
params=params, params=params,
@@ -487,13 +513,10 @@ class BackendAPIClient:
data = await self._handle_response(response) data = await self._handle_response(response)
# ⚠️ DEBUGGING: Log response
if isinstance(data, dict) and 'invoices' in data: if isinstance(data, dict) and 'invoices' in data:
invoice_list = data['invoices'] invoice_list = data['invoices']
logger.info(f"📥 Received {len(invoice_list)} invoices from backend")
return invoice_list return invoice_list
elif isinstance(data, list): elif isinstance(data, list):
logger.info(f"📥 Received {len(data)} invoices from backend (direct list)")
return data return data
else: else:
logger.warning(f"📥 Unexpected response format: {type(data)}") logger.warning(f"📥 Unexpected response format: {type(data)}")
@@ -626,6 +649,113 @@ class BackendAPIClient:
logger.error(f"Failed to export report: {e}") logger.error(f"Failed to export report: {e}")
return None 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 # HEALTH CHECK
# ========================================================================= # =========================================================================

View File

@@ -69,6 +69,7 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
if result: if result:
# Success! # Success!
username = result['username'] username = result['username']
jwt_token = result['jwt_token']
# Show main menu with buttons for newly linked user # Show main menu with buttons for newly linked user
session_manager = get_session_manager() 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_name = company['name'] if company else None
company_cui = company.get('cui') 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 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 # Single welcome message with menu
if company_name: if company_name:
@@ -209,6 +221,11 @@ Dupa conectarea contului, foloseste **butoanele interactive** pentru:
/help - Acest mesaj de ajutor /help - Acest mesaj de ajutor
/unlink - Deconecteaza contul (securitate) /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:** **Conectare cont:**
1. Loghează-te în aplicația web ROA2WEB 1. Loghează-te în aplicația web ROA2WEB
2. Accesează Setări → Telegram Linking 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): async def companies_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
""" """
Handle /companies command. 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.") await update.message.reply_text("Eroare la incarcarea trezoreriei.")
return 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) # Format combined response (casa + banca) - rotunjit la leu (0 zecimale)
casa_total = round(treasury_data['casa']['total']) casa_total = round(treasury_data['casa']['total'])
banca_total = round(treasury_data['banca']['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 from app.bot.menus import format_response_with_company
text = format_response_with_company(content, company['name']) 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 # Add buttons to view details
from telegram import InlineKeyboardButton, InlineKeyboardMarkup from telegram import InlineKeyboardButton, InlineKeyboardMarkup
keyboard = 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_name = company['name'] if company else None
company_cui = company.get('cui') 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) # Create main menu (user is authenticated if they passed the is_linked check)
from app.bot.menus import create_main_menu, get_menu_message 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) menu_text = get_menu_message(company_name, company_cui)
await update.message.reply_text( await update.message.reply_text(
@@ -708,11 +898,19 @@ async def trezorerie_casa_command(update: Update, context: ContextTypes.DEFAULT_
return return
# Format response # 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 from app.bot.menus import create_action_buttons, format_response_with_company
content = format_treasury_casa_response(treasury_data['casa']) content = format_treasury_casa_response(treasury_data['casa'])
response = format_response_with_company(content, company['name']) 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) keyboard = create_action_buttons("casa", show_export=True)
await update.message.reply_text( await update.message.reply_text(
@@ -773,11 +971,19 @@ async def trezorerie_banca_command(update: Update, context: ContextTypes.DEFAULT
return return
# Format response # 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 from app.bot.menus import create_action_buttons, format_response_with_company
content = format_treasury_banca_response(treasury_data['banca']) content = format_treasury_banca_response(treasury_data['banca'])
response = format_response_with_company(content, company['name']) 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) keyboard = create_action_buttons("banca", show_export=True)
await update.message.reply_text( 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.") await update.message.reply_text("Eroare la incarcarea datelor clienti.")
return 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 # 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 from app.bot.menus import create_client_list_keyboard, format_response_with_company
content = format_clients_balance_response( content = format_clients_balance_response(
@@ -847,6 +1058,11 @@ async def clienti_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
clients_data['maturity'] clients_data['maturity']
) )
response = format_response_with_company(content, company['name']) 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) keyboard = create_client_list_keyboard(clients_data['clients'], page=0)
await update.message.reply_text( 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.") await update.message.reply_text("Eroare la incarcarea datelor furnizori.")
return 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 # 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 from app.bot.menus import create_supplier_list_keyboard, format_response_with_company
content = format_suppliers_balance_response( content = format_suppliers_balance_response(
@@ -916,6 +1137,11 @@ async def furnizori_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
suppliers_data['maturity'] suppliers_data['maturity']
) )
response = format_response_with_company(content, company['name']) 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) keyboard = create_supplier_list_keyboard(suppliers_data['suppliers'], page=0)
await update.message.reply_text( await update.message.reply_text(
@@ -976,7 +1202,7 @@ async def evolutie_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
return return
# Format response # 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 from app.bot.menus import create_action_buttons, format_response_with_company
content = format_cashflow_evolution_response( content = format_cashflow_evolution_response(
@@ -984,6 +1210,14 @@ async def evolutie_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
evolution_data['monthly'] evolution_data['monthly']
) )
response = format_response_with_company(content, company['name']) 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) keyboard = create_action_buttons("evolutie", show_export=False, show_refresh=False)
await update.message.reply_text( 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 from app.bot.helpers import get_treasury_breakdown_split
treasury_data = await get_treasury_breakdown_split(company['id'], jwt_token) 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 from app.bot.menus import create_action_buttons, format_response_with_company
content = format_treasury_casa_response(treasury_data['casa']) content = format_treasury_casa_response(treasury_data['casa'])
response = format_response_with_company(content, company['name']) 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) keyboard = create_action_buttons("casa", show_export=False, show_refresh=False)
try: 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 from app.bot.helpers import get_treasury_breakdown_split
treasury_data = await get_treasury_breakdown_split(company['id'], jwt_token) 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 from app.bot.menus import create_action_buttons, format_response_with_company
content = format_treasury_banca_response(treasury_data['banca']) content = format_treasury_banca_response(treasury_data['banca'])
response = format_response_with_company(content, company['name']) 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) keyboard = create_action_buttons("banca", show_export=False, show_refresh=False)
try: 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 from app.bot.helpers import get_clients_with_maturity
clients_data = await get_clients_with_maturity(company['id'], jwt_token) 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 from app.bot.menus import create_client_list_keyboard, format_response_with_company
content = format_clients_balance_response( 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'] clients_data['maturity']
) )
response = format_response_with_company(content, company['name']) 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) keyboard = create_client_list_keyboard(clients_data['clients'], page=0)
await query.edit_message_text( 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 from app.bot.helpers import get_suppliers_with_maturity
suppliers_data = await get_suppliers_with_maturity(company['id'], jwt_token) 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 from app.bot.menus import create_supplier_list_keyboard, format_response_with_company
content = format_suppliers_balance_response( 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'] suppliers_data['maturity']
) )
response = format_response_with_company(content, company['name']) 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) keyboard = create_supplier_list_keyboard(suppliers_data['suppliers'], page=0)
await query.edit_message_text( 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 from app.bot.helpers import get_cashflow_evolution_data
evolution_data = await get_cashflow_evolution_data(company['id'], jwt_token) 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 from app.bot.menus import create_action_buttons, format_response_with_company
content = format_cashflow_evolution_response( 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'] evolution_data['monthly']
) )
response = format_response_with_company(content, company['name']) 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) keyboard = create_action_buttons("evolutie", show_export=False, show_refresh=False)
await query.edit_message_text( 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 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": elif action == "select_company":
# ✅ MODIFICARE: Folosim funcția comună # ✅ MODIFICARE: Folosim funcția comună
await _handle_selectcompany_view( 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) auth_data = await get_user_auth_data(telegram_user_id)
is_authenticated = auth_data is not None 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 from app.bot.menus import create_main_menu, get_menu_message
company_name = company['name'] if company else None company_name = company['name'] if company else None
company_cui = company.get('cui') 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) menu_text = get_menu_message(company_name, company_cui)
await query.edit_message_text( 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:"): elif callback_data.startswith("nav:back:"):
await handle_navigation_back(query, telegram_user_id, callback_data) 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 ========== # ========== PAGINATION CALLBACKS ==========
elif callback_data.startswith("clients_page:"): elif callback_data.startswith("clients_page:"):
@@ -2144,11 +2577,20 @@ async def _handle_sold_view(
await query_or_update.message.reply_text(error_msg) await query_or_update.message.reply_text(error_msg)
return 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 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) content = format_dashboard_response(data)
response = format_response_with_company(content, company['name']) 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) keyboard = create_action_buttons("sold", show_export=False, show_refresh=False)
if is_callback: if is_callback:

View File

@@ -344,7 +344,7 @@ async def get_treasury_breakdown_split(
for item in banca_data.get('items', []) for item in banca_data.get('items', [])
] ]
return { result = {
'casa': { 'casa': {
'accounts': casa_accounts, 'accounts': casa_accounts,
'total': float(casa_data.get('total', 0)) '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: except Exception as e:
logger.error(f"Error getting treasury breakdown split: {e}", exc_info=True) logger.error(f"Error getting treasury breakdown split: {e}", exc_info=True)
return None 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) overdue = sum(c['balance'] for c in clients if c.get('daysOverdue', 0) > 0)
in_term = total - overdue in_term = total - overdue
return { result = {
'clients': clients, 'clients': clients,
'maturity': { 'maturity': {
'in_term': in_term, '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: except Exception as e:
logger.error(f"Error getting clients with maturity: {e}", exc_info=True) logger.error(f"Error getting clients with maturity: {e}", exc_info=True)
return None 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) overdue = sum(s['balance'] for s in suppliers if s.get('daysOverdue', 0) > 0)
in_term = total - overdue in_term = total - overdue
return { result = {
'suppliers': suppliers, 'suppliers': suppliers,
'maturity': { 'maturity': {
'in_term': in_term, '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: except Exception as e:
logger.error(f"Error getting suppliers with maturity: {e}", exc_info=True) logger.error(f"Error getting suppliers with maturity: {e}", exc_info=True)
return None return None
@@ -650,11 +680,21 @@ async def get_cashflow_evolution_data(
'plati_prev': last_12_plati_prev 'plati_prev': last_12_plati_prev
} }
return { result = {
'performance': performance, 'performance': performance,
'monthly': monthly '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: except Exception as e:
logger.error(f"Error getting cashflow evolution data: {e}", exc_info=True) logger.error(f"Error getting cashflow evolution data: {e}", exc_info=True)
return None return None