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

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

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

View File

@@ -105,7 +105,8 @@ class BackendAPIClient:
async def verify_user(
self,
oracle_username: str,
linking_code: str
linking_code: str,
server_id: Optional[str] = None
) -> Optional[Dict[str, Any]]:
"""
Verify user exists in Oracle and get JWT token.
@@ -139,7 +140,8 @@ class BackendAPIClient:
"/api/telegram/auth/verify-user",
json={
"linking_code": linking_code,
"oracle_username": oracle_username
"oracle_username": oracle_username,
"server_id": server_id
}
)
@@ -185,12 +187,13 @@ class BackendAPIClient:
logger.error(f"Failed to refresh token: {e}")
return None
async def verify_email(self, email: str) -> dict:
async def verify_email(self, email: str, server_id: Optional[str] = None) -> dict:
"""
Verify if email exists in Oracle database
Args:
email: Email address to verify
server_id: Optional Oracle server ID (for multi-server mode)
Returns:
dict with 'success' (bool), 'username' (str or None), and 'message' (str)
@@ -204,7 +207,7 @@ class BackendAPIClient:
response = await self.client.post(
"/api/telegram/auth/verify-email",
json={"email": email}
json={"email": email, "server_id": server_id}
)
response.raise_for_status()
return response.json()
@@ -229,7 +232,8 @@ class BackendAPIClient:
email: str,
password: str,
telegram_user_id: int,
session_token: str
session_token: str,
server_id: Optional[str] = None
) -> dict:
"""
Login via email + password with session token
@@ -239,6 +243,7 @@ class BackendAPIClient:
password: Oracle password
telegram_user_id: Telegram user ID
session_token: Signed token from code validation
server_id: Optional Oracle server ID (for multi-server mode)
Returns:
Login response with JWT tokens and user data
@@ -256,7 +261,8 @@ class BackendAPIClient:
"email": email,
"password": password,
"telegram_user_id": telegram_user_id,
"session_token": session_token
"session_token": session_token,
"server_id": server_id
},
timeout=30.0 # 30 seconds timeout
)
@@ -298,6 +304,52 @@ class BackendAPIClient:
"message": "Eroare de conexiune"
}
async def switch_server(
self,
jwt_token: str,
oracle_username: str,
new_server_id: str,
oracle_password: str = None
) -> dict:
"""
Switch the active Oracle server for the authenticated user.
Args:
jwt_token: Current JWT access token (used for authentication)
oracle_username: Oracle username of the current user
new_server_id: Target Oracle server ID
oracle_password: Oracle password on the new server (required if servers have different passwords)
Returns:
Dict with success, access_token, refresh_token, message
"""
try:
if not self.client or self.client.is_closed:
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
payload = {"oracle_username": oracle_username, "new_server_id": new_server_id}
if oracle_password:
payload["oracle_password"] = oracle_password
response = await self.client.post(
"/api/telegram/auth/switch-server",
json=payload,
headers=self._get_auth_headers(jwt_token)
)
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as e:
logger.error(f"Switch server HTTP error: {e.response.status_code}")
try:
return {"success": False, "message": e.response.json().get("detail", "Eroare")}
except Exception:
return {"success": False, "message": "Eroare la schimbarea serverului"}
except Exception as e:
logger.error(f"Switch server error: {e}")
return {"success": False, "message": "Eroare de conexiune"}
async def get_user_companies(self, jwt_token: str) -> List[Dict[str, Any]]:
"""
Get list of companies the user has access to.