Implement unified Telegram bot interface with Login/Logout and fix callback_data limits
**Interface improvements:** - Add persistent Login/Logout buttons to main menu - Help button now updates text above menu (not separate message) - Expired token automatically transforms menu to Login state - Consolidate linking success messages (single welcome + menu) **Fix callback_data length limits (Telegram 64-byte limit):** - Truncate client/supplier names to 40 chars in callback_data - Use full names for API calls but truncated for buttons - Fix pagination buttons for long partner names (30 chars limit) - Search entities by prefix match to handle truncated names **Treasury improvements:** - Show ALL bank/cash accounts (removed 5-item limit) - Remove unnecessary Refresh/Export buttons from treasury views - Handle "Message is not modified" error gracefully **Bug fixes:** - Fix Markdown parsing errors (replace <cod> with `CODUL_TAU`) - Fix silent errors when token expires (show user-friendly message) - Fix Button_data_invalid errors on pagination and details 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -124,13 +124,10 @@ def format_treasury_casa_response(data: Dict[str, Any], company_name: str = None
|
|||||||
casa_accounts = data.get('accounts', [])
|
casa_accounts = data.get('accounts', [])
|
||||||
if casa_accounts:
|
if casa_accounts:
|
||||||
text += "**Conturi de Casa:**\n"
|
text += "**Conturi de Casa:**\n"
|
||||||
for acc in casa_accounts[:5]: # Max 5
|
for acc in casa_accounts: # Show all accounts
|
||||||
name = acc.get('name', 'N/A')
|
name = acc.get('name', 'N/A')
|
||||||
balance = round(acc.get('balance', 0))
|
balance = round(acc.get('balance', 0))
|
||||||
text += f" - {name}: {balance:,} RON\n"
|
text += f" - {name}: {balance:,} RON\n"
|
||||||
|
|
||||||
if len(casa_accounts) > 5:
|
|
||||||
text += f" ... si inca {len(casa_accounts) - 5} conturi"
|
|
||||||
else:
|
else:
|
||||||
text += "Nu exista conturi de casa configurate."
|
text += "Nu exista conturi de casa configurate."
|
||||||
|
|
||||||
@@ -162,13 +159,10 @@ def format_treasury_banca_response(data: Dict[str, Any], company_name: str = Non
|
|||||||
bank_accounts = data.get('accounts', [])
|
bank_accounts = data.get('accounts', [])
|
||||||
if bank_accounts:
|
if bank_accounts:
|
||||||
text += "**Conturi Bancare:**\n"
|
text += "**Conturi Bancare:**\n"
|
||||||
for acc in bank_accounts[:5]: # Max 5
|
for acc in bank_accounts: # Show all accounts
|
||||||
name = acc.get('name', 'N/A')
|
name = acc.get('name', 'N/A')
|
||||||
balance = round(acc.get('balance', 0))
|
balance = round(acc.get('balance', 0))
|
||||||
text += f" - {name}: {balance:,} RON\n"
|
text += f" - {name}: {balance:,} RON\n"
|
||||||
|
|
||||||
if len(bank_accounts) > 5:
|
|
||||||
text += f" ... si inca {len(bank_accounts) - 5} conturi"
|
|
||||||
else:
|
else:
|
||||||
text += "Nu exista conturi bancare configurate."
|
text += "Nu exista conturi bancare configurate."
|
||||||
|
|
||||||
|
|||||||
@@ -69,24 +69,6 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
if result:
|
if result:
|
||||||
# Success!
|
# Success!
|
||||||
username = result['username']
|
username = result['username']
|
||||||
companies = result.get('companies', [])
|
|
||||||
|
|
||||||
companies_text = ""
|
|
||||||
if companies:
|
|
||||||
companies_text = "\n\n**Companiile tale:**\n"
|
|
||||||
for comp_id in companies[:3]: # Show first 3 (companies are IDs as strings)
|
|
||||||
companies_text += f"- Companie ID: {comp_id}\n"
|
|
||||||
|
|
||||||
if len(companies) > 3:
|
|
||||||
companies_text += f"- ... si inca {len(companies) - 3} companii\n"
|
|
||||||
|
|
||||||
await update.message.reply_text(
|
|
||||||
f"**Cont conectat cu succes**\n\n"
|
|
||||||
f"Bun venit, **{username}**\n"
|
|
||||||
f"{companies_text}\n"
|
|
||||||
f"Foloseste meniul sau /help pentru comenzi.",
|
|
||||||
parse_mode=ParseMode.MARKDOWN
|
|
||||||
)
|
|
||||||
|
|
||||||
# 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()
|
||||||
@@ -95,12 +77,27 @@ 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
|
||||||
|
|
||||||
from app.bot.menus import create_main_menu, get_menu_message
|
from app.bot.menus import create_main_menu, pad_message_for_wide_buttons
|
||||||
keyboard = create_main_menu(company_name, company_cui)
|
keyboard = create_main_menu(company_name, company_cui, is_authenticated=True)
|
||||||
menu_text = get_menu_message(company_name, company_cui)
|
|
||||||
|
# Single welcome message with menu
|
||||||
|
if company_name:
|
||||||
|
welcome_text = (
|
||||||
|
f"**Cont conectat cu succes**\n\n"
|
||||||
|
f"Bun venit, **{username}**!\n\n"
|
||||||
|
f"{company_name}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
welcome_text = (
|
||||||
|
f"**Cont conectat cu succes**\n\n"
|
||||||
|
f"Bun venit, **{username}**!\n\n"
|
||||||
|
f"Selectează o companie pentru a continua"
|
||||||
|
)
|
||||||
|
|
||||||
|
welcome_message = pad_message_for_wide_buttons(welcome_text)
|
||||||
|
|
||||||
await update.message.reply_text(
|
await update.message.reply_text(
|
||||||
menu_text,
|
welcome_message,
|
||||||
reply_markup=keyboard,
|
reply_markup=keyboard,
|
||||||
parse_mode=ParseMode.MARKDOWN
|
parse_mode=ParseMode.MARKDOWN
|
||||||
)
|
)
|
||||||
@@ -111,9 +108,9 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
# Failed linking
|
# Failed linking
|
||||||
await update.message.reply_text(
|
await update.message.reply_text(
|
||||||
"**Cod invalid sau expirat**\n\n"
|
"**Cod invalid sau expirat**\n\n"
|
||||||
"Genereaza un cod nou din aplicatia web si trimite:\n"
|
"Generează un cod nou din aplicația web și trimite:\n"
|
||||||
"/start <cod>\n\n"
|
"`/start CODUL_TAU`\n\n"
|
||||||
"Codul expira in 15 minute.",
|
"Codul expiră în 15 minute.",
|
||||||
parse_mode=ParseMode.MARKDOWN
|
parse_mode=ParseMode.MARKDOWN
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -135,11 +132,12 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
company = session.get_active_company()
|
company = session.get_active_company()
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
# Create main menu
|
# Create main menu
|
||||||
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)
|
keyboard = create_main_menu(company_name, company_cui, is_authenticated=True)
|
||||||
menu_text = get_menu_message(company_name)
|
menu_text = get_menu_message(company_name, company_cui)
|
||||||
|
|
||||||
await update.message.reply_text(
|
await update.message.reply_text(
|
||||||
f"Bun venit, **{username}**\n\n{menu_text}",
|
f"Bun venit, **{username}**\n\n{menu_text}",
|
||||||
@@ -212,10 +210,10 @@ Dupa conectarea contului, foloseste **butoanele interactive** pentru:
|
|||||||
/unlink - Deconecteaza contul (securitate)
|
/unlink - Deconecteaza contul (securitate)
|
||||||
|
|
||||||
**Conectare cont:**
|
**Conectare cont:**
|
||||||
1. Logheaza-te in aplicatia web ROA2WEB
|
1. Loghează-te în aplicația web ROA2WEB
|
||||||
2. Acceseaza Setari > Telegram Linking
|
2. Accesează Setări → Telegram Linking
|
||||||
3. Genereaza cod (valabil 15 minute)
|
3. Generează cod (valabil 15 minute)
|
||||||
4. Trimite codul in Telegram: /start <cod>
|
4. Trimite codul în Telegram: `/start CODUL_TAU`
|
||||||
|
|
||||||
**Securitate:**
|
**Securitate:**
|
||||||
Datele sunt protejate prin autentificare JWT.
|
Datele sunt protejate prin autentificare JWT.
|
||||||
@@ -647,9 +645,9 @@ 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
|
||||||
|
|
||||||
# Create main menu
|
# 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)
|
keyboard = create_main_menu(company_name, company_cui, is_authenticated=True)
|
||||||
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(
|
||||||
@@ -1049,25 +1047,6 @@ async def handle_text_message(update: Update, context: ContextTypes.DEFAULT_TYPE
|
|||||||
if result:
|
if result:
|
||||||
# Success!
|
# Success!
|
||||||
username = result['username']
|
username = result['username']
|
||||||
companies = result.get('companies', [])
|
|
||||||
|
|
||||||
companies_text = ""
|
|
||||||
if companies:
|
|
||||||
companies_text = "\n\n**Companiile tale:**\n"
|
|
||||||
for comp_id in companies[:3]: # Show first 3
|
|
||||||
companies_text += f"- Companie ID: {comp_id}\n"
|
|
||||||
|
|
||||||
if len(companies) > 3:
|
|
||||||
companies_text += f"- ... si inca {len(companies) - 3} companii\n"
|
|
||||||
|
|
||||||
await update.message.reply_text(
|
|
||||||
f"**Cont conectat cu succes**\n\n"
|
|
||||||
f"Bun venit, **{username}**\n"
|
|
||||||
f"{companies_text}\n"
|
|
||||||
f"Foloseste meniul sau /help pentru comenzi.",
|
|
||||||
parse_mode=ParseMode.MARKDOWN
|
|
||||||
)
|
|
||||||
|
|
||||||
# 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()
|
||||||
session = await session_manager.get_or_create_session(telegram_user_id)
|
session = await session_manager.get_or_create_session(telegram_user_id)
|
||||||
@@ -1075,12 +1054,27 @@ async def handle_text_message(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
|
||||||
|
|
||||||
from app.bot.menus import create_main_menu, get_menu_message
|
from app.bot.menus import create_main_menu, pad_message_for_wide_buttons
|
||||||
keyboard = create_main_menu(company_name, company_cui)
|
keyboard = create_main_menu(company_name, company_cui, is_authenticated=True)
|
||||||
menu_text = get_menu_message(company_name, company_cui)
|
|
||||||
|
# Single welcome message with menu
|
||||||
|
if company_name:
|
||||||
|
welcome_text = (
|
||||||
|
f"**Cont conectat cu succes**\n\n"
|
||||||
|
f"Bun venit, **{username}**!\n\n"
|
||||||
|
f"{company_name}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
welcome_text = (
|
||||||
|
f"**Cont conectat cu succes**\n\n"
|
||||||
|
f"Bun venit, **{username}**!\n\n"
|
||||||
|
f"Selectează o companie pentru a continua"
|
||||||
|
)
|
||||||
|
|
||||||
|
welcome_message = pad_message_for_wide_buttons(welcome_text)
|
||||||
|
|
||||||
await update.message.reply_text(
|
await update.message.reply_text(
|
||||||
menu_text,
|
welcome_message,
|
||||||
reply_markup=keyboard,
|
reply_markup=keyboard,
|
||||||
parse_mode=ParseMode.MARKDOWN
|
parse_mode=ParseMode.MARKDOWN
|
||||||
)
|
)
|
||||||
@@ -1140,6 +1134,29 @@ async def handle_menu_callback(query, telegram_user_id: int, callback_data: str)
|
|||||||
|
|
||||||
# Get auth data
|
# Get auth data
|
||||||
auth_data = await get_user_auth_data(telegram_user_id)
|
auth_data = await get_user_auth_data(telegram_user_id)
|
||||||
|
|
||||||
|
# If user is not authenticated and trying to access financial data, show auth required message
|
||||||
|
if auth_data is None and action != "select_company":
|
||||||
|
from app.bot.menus import create_main_menu, pad_message_for_wide_buttons
|
||||||
|
keyboard = create_main_menu(company_name=None, company_cui=None, is_authenticated=False)
|
||||||
|
menu_text = pad_message_for_wide_buttons(
|
||||||
|
"⚠️ **Autentificare necesară**\n\n"
|
||||||
|
"Pentru a accesa date financiare,\n"
|
||||||
|
"apasă **Login** și urmează instrucțiunile."
|
||||||
|
)
|
||||||
|
|
||||||
|
await query.edit_message_text(
|
||||||
|
menu_text,
|
||||||
|
reply_markup=keyboard,
|
||||||
|
parse_mode=ParseMode.MARKDOWN
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# If action is select_company and user is not authenticated, allow it (will show empty list or error)
|
||||||
|
if action == "select_company" and auth_data is None:
|
||||||
|
await query.answer("Pentru a vedea companiile, trebuie să te autentifici mai întâi.", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
jwt_token = auth_data['jwt_token']
|
jwt_token = auth_data['jwt_token']
|
||||||
|
|
||||||
# Get active company
|
# Get active company
|
||||||
@@ -1193,13 +1210,18 @@ async def handle_menu_callback(query, telegram_user_id: int, callback_data: str)
|
|||||||
|
|
||||||
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'])
|
||||||
keyboard = create_action_buttons("casa", show_export=True)
|
keyboard = create_action_buttons("casa", show_export=False, show_refresh=False)
|
||||||
|
|
||||||
await query.edit_message_text(
|
try:
|
||||||
response,
|
await query.edit_message_text(
|
||||||
reply_markup=keyboard,
|
response,
|
||||||
parse_mode=ParseMode.MARKDOWN
|
reply_markup=keyboard,
|
||||||
)
|
parse_mode=ParseMode.MARKDOWN
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
# Ignore "Message is not modified" error
|
||||||
|
if "Message is not modified" not in str(e):
|
||||||
|
raise
|
||||||
|
|
||||||
elif action == "banca":
|
elif action == "banca":
|
||||||
# Trezorerie banca
|
# Trezorerie banca
|
||||||
@@ -1211,13 +1233,18 @@ async def handle_menu_callback(query, telegram_user_id: int, callback_data: str)
|
|||||||
|
|
||||||
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'])
|
||||||
keyboard = create_action_buttons("banca", show_export=True)
|
keyboard = create_action_buttons("banca", show_export=False, show_refresh=False)
|
||||||
|
|
||||||
await query.edit_message_text(
|
try:
|
||||||
response,
|
await query.edit_message_text(
|
||||||
reply_markup=keyboard,
|
response,
|
||||||
parse_mode=ParseMode.MARKDOWN
|
reply_markup=keyboard,
|
||||||
)
|
parse_mode=ParseMode.MARKDOWN
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
# Ignore "Message is not modified" error
|
||||||
|
if "Message is not modified" not in str(e):
|
||||||
|
raise
|
||||||
|
|
||||||
elif action == "clienti":
|
elif action == "clienti":
|
||||||
# Sold clienți + listă cu paginare
|
# Sold clienți + listă cu paginare
|
||||||
@@ -1296,10 +1323,10 @@ async def handle_menu_callback(query, telegram_user_id: int, callback_data: str)
|
|||||||
|
|
||||||
async def handle_action_callback(query, telegram_user_id: int, callback_data: str):
|
async def handle_action_callback(query, telegram_user_id: int, callback_data: str):
|
||||||
"""
|
"""
|
||||||
Handle action button clicks (Refresh, Export, Menu).
|
Handle action button clicks (Refresh, Export, Menu, Help, Login, Logout).
|
||||||
|
|
||||||
Callback format: action:{type}:{view}
|
Callback format: action:{type}:{view}
|
||||||
Types: refresh, export, menu
|
Types: refresh, export, menu, help, login, logout
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
query: CallbackQuery object
|
query: CallbackQuery object
|
||||||
@@ -1315,10 +1342,14 @@ async def handle_action_callback(query, telegram_user_id: int, callback_data: st
|
|||||||
session = await session_manager.get_or_create_session(telegram_user_id)
|
session = await session_manager.get_or_create_session(telegram_user_id)
|
||||||
company = session.get_active_company()
|
company = session.get_active_company()
|
||||||
|
|
||||||
|
# Check if user is authenticated
|
||||||
|
auth_data = await get_user_auth_data(telegram_user_id)
|
||||||
|
is_authenticated = auth_data is not None
|
||||||
|
|
||||||
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)
|
keyboard = create_main_menu(company_name, company_cui, is_authenticated)
|
||||||
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(
|
||||||
@@ -1334,10 +1365,14 @@ async def handle_action_callback(query, telegram_user_id: int, callback_data: st
|
|||||||
# Check if it's a detail view (client_detail:name or supplier_detail:name)
|
# Check if it's a detail view (client_detail:name or supplier_detail:name)
|
||||||
if view.startswith("client_detail:"):
|
if view.startswith("client_detail:"):
|
||||||
entity_name = view.split(":", 1)[1] # Extract entity name
|
entity_name = view.split(":", 1)[1] # Extract entity name
|
||||||
await handle_details_callback(query, telegram_user_id, f"details:client:{entity_name}:0")
|
# Limit name to 40 chars for Telegram callback_data limit (64 bytes)
|
||||||
|
safe_name = entity_name[:40] if len(entity_name) > 40 else entity_name
|
||||||
|
await handle_details_callback(query, telegram_user_id, f"details:client:{safe_name}:0")
|
||||||
elif view.startswith("supplier_detail:"):
|
elif view.startswith("supplier_detail:"):
|
||||||
entity_name = view.split(":", 1)[1] # Extract entity name
|
entity_name = view.split(":", 1)[1] # Extract entity name
|
||||||
await handle_details_callback(query, telegram_user_id, f"details:supplier:{entity_name}:0")
|
# Limit name to 40 chars for Telegram callback_data limit (64 bytes)
|
||||||
|
safe_name = entity_name[:40] if len(entity_name) > 40 else entity_name
|
||||||
|
await handle_details_callback(query, telegram_user_id, f"details:supplier:{safe_name}:0")
|
||||||
else:
|
else:
|
||||||
# Regular menu view refresh
|
# Regular menu view refresh
|
||||||
await handle_menu_callback(query, telegram_user_id, f"menu:{view}")
|
await handle_menu_callback(query, telegram_user_id, f"menu:{view}")
|
||||||
@@ -1346,6 +1381,101 @@ async def handle_action_callback(query, telegram_user_id: int, callback_data: st
|
|||||||
# Export functionality (placeholder for now)
|
# Export functionality (placeholder for now)
|
||||||
await query.answer("Functia de export va fi disponibila in curand", show_alert=True)
|
await query.answer("Functia de export va fi disponibila in curand", show_alert=True)
|
||||||
|
|
||||||
|
elif action_type == "help":
|
||||||
|
# Show help message above menu (edit current message)
|
||||||
|
from app.bot.menus import pad_message_for_wide_buttons, create_main_menu
|
||||||
|
|
||||||
|
# Get auth status and company info
|
||||||
|
session_manager = get_session_manager()
|
||||||
|
session = await session_manager.get_or_create_session(telegram_user_id)
|
||||||
|
company = session.get_active_company()
|
||||||
|
|
||||||
|
auth_data = await get_user_auth_data(telegram_user_id)
|
||||||
|
is_authenticated = auth_data is not None
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
help_text = pad_message_for_wide_buttons(
|
||||||
|
"**Ghid Rapid**\n\n"
|
||||||
|
"**Selectare Companie** - Alege compania activă\n\n"
|
||||||
|
"**Sold Companie** - Dashboard financiar complet\n"
|
||||||
|
"**Trezorerie Casa** - Situație conturi cash\n"
|
||||||
|
"**Trezorerie Banca** - Situație conturi bancare\n"
|
||||||
|
"**Sold Clienti** - Clienți + facturi neplătite\n"
|
||||||
|
"**Sold Furnizori** - Furnizori + facturi\n"
|
||||||
|
"**Evolutie Incasari** - Trend lunar încasări\n\n"
|
||||||
|
"**Logout** - Deconectează contul\n\n"
|
||||||
|
"_Toate datele sunt în timp real din Oracle._"
|
||||||
|
)
|
||||||
|
|
||||||
|
await query.edit_message_text(
|
||||||
|
help_text,
|
||||||
|
reply_markup=keyboard,
|
||||||
|
parse_mode=ParseMode.MARKDOWN
|
||||||
|
)
|
||||||
|
|
||||||
|
elif action_type == "logout":
|
||||||
|
# Show logout confirmation
|
||||||
|
from app.bot.menus import pad_message_for_wide_buttons
|
||||||
|
confirmation_text = pad_message_for_wide_buttons(
|
||||||
|
"**Confirmare Deconectare**\n\n"
|
||||||
|
"Ești sigur că vrei să deconectezi contul?\n\n"
|
||||||
|
"Accesul la date va fi oprit.\n"
|
||||||
|
"Poți reconecta oricând cu un cod nou."
|
||||||
|
)
|
||||||
|
|
||||||
|
keyboard = InlineKeyboardMarkup([
|
||||||
|
[
|
||||||
|
InlineKeyboardButton("Da, deconectează", callback_data="logout_confirm"),
|
||||||
|
InlineKeyboardButton("Anulează", callback_data="logout_cancel")
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|
||||||
|
await query.edit_message_text(
|
||||||
|
confirmation_text,
|
||||||
|
reply_markup=keyboard,
|
||||||
|
parse_mode=ParseMode.MARKDOWN
|
||||||
|
)
|
||||||
|
|
||||||
|
elif action_type == "login":
|
||||||
|
# Prompt user to enter link code directly (same as login_prompt functionality)
|
||||||
|
from telegram import ForceReply
|
||||||
|
from app.bot.menus import pad_message_for_wide_buttons
|
||||||
|
|
||||||
|
# Edit the current message with instructions
|
||||||
|
login_text = pad_message_for_wide_buttons(
|
||||||
|
"**Conectare Cont ROA2WEB**\n\n"
|
||||||
|
"Trimite-mi codul primit din aplicația web.\n\n"
|
||||||
|
"Poți trimite:\n"
|
||||||
|
"• Doar codul: ABC12XYZ\n"
|
||||||
|
"• Sau comanda: /start ABC12XYZ\n\n"
|
||||||
|
"Codul expiră în 15 minute."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Buttons for help or cancel
|
||||||
|
keyboard = InlineKeyboardMarkup([
|
||||||
|
[InlineKeyboardButton("Cum obțin codul?", callback_data="login_help")],
|
||||||
|
[InlineKeyboardButton("« Anulează", callback_data="action:menu")]
|
||||||
|
])
|
||||||
|
|
||||||
|
await query.edit_message_text(
|
||||||
|
login_text,
|
||||||
|
reply_markup=keyboard,
|
||||||
|
parse_mode=ParseMode.MARKDOWN
|
||||||
|
)
|
||||||
|
|
||||||
|
# Send a follow-up message with ForceReply to prompt input
|
||||||
|
await query.message.reply_text(
|
||||||
|
"Scrie sau lipește codul aici:",
|
||||||
|
reply_markup=ForceReply(
|
||||||
|
selective=True,
|
||||||
|
input_field_placeholder="ABC12XYZ"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def handle_details_callback(query, telegram_user_id: int, callback_data: str):
|
async def handle_details_callback(query, telegram_user_id: int, callback_data: str):
|
||||||
"""
|
"""
|
||||||
@@ -1373,26 +1503,35 @@ async def handle_details_callback(query, telegram_user_id: int, callback_data: s
|
|||||||
company = session.get_active_company()
|
company = session.get_active_company()
|
||||||
|
|
||||||
if detail_type == "client":
|
if detail_type == "client":
|
||||||
# Get client invoices by name
|
|
||||||
from app.bot.helpers import get_client_invoices
|
|
||||||
invoices = await get_client_invoices(company['id'], entity_name, jwt_token)
|
|
||||||
|
|
||||||
# Get client details (from clients list)
|
# Get client details (from clients list)
|
||||||
|
# entity_name might be truncated to 40 chars, so search by startswith
|
||||||
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)
|
||||||
client = next((c for c in clients_data['clients'] if c['name'] == entity_name), None)
|
|
||||||
|
# Find client by full or partial name match
|
||||||
|
client = next((c for c in clients_data['clients'] if c['name'].startswith(entity_name)), None)
|
||||||
|
|
||||||
if not client:
|
if not client:
|
||||||
await query.answer("Client negasit", show_alert=True)
|
await query.answer("Client negăsit", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Use FULL client name for invoice search (not truncated)
|
||||||
|
full_client_name = client['name']
|
||||||
|
|
||||||
|
# Get client invoices by FULL name
|
||||||
|
from app.bot.helpers import get_client_invoices
|
||||||
|
invoices = await get_client_invoices(company['id'], full_client_name, jwt_token)
|
||||||
|
|
||||||
# Format response
|
# Format response
|
||||||
from app.bot.formatters import format_client_detail_response
|
from app.bot.formatters import format_client_detail_response
|
||||||
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_client_detail_response(client, invoices)
|
content = format_client_detail_response(client, invoices)
|
||||||
response = format_response_with_company(content, company['name'])
|
response = format_response_with_company(content, company['name'])
|
||||||
keyboard = create_action_buttons(f"client_detail:{entity_name}", show_export=False, show_back=True)
|
|
||||||
|
# Use truncated name for callback_data (to stay within 64 byte limit)
|
||||||
|
safe_name = entity_name[:40] if len(entity_name) > 40 else entity_name
|
||||||
|
keyboard = create_action_buttons(f"client_detail:{safe_name}", show_export=False, show_back=True, show_refresh=False)
|
||||||
|
|
||||||
await query.edit_message_text(
|
await query.edit_message_text(
|
||||||
response,
|
response,
|
||||||
@@ -1401,26 +1540,35 @@ async def handle_details_callback(query, telegram_user_id: int, callback_data: s
|
|||||||
)
|
)
|
||||||
|
|
||||||
elif detail_type == "supplier":
|
elif detail_type == "supplier":
|
||||||
# Get supplier invoices by name
|
|
||||||
from app.bot.helpers import get_supplier_invoices
|
|
||||||
invoices = await get_supplier_invoices(company['id'], entity_name, jwt_token)
|
|
||||||
|
|
||||||
# Get supplier details (from suppliers list)
|
# Get supplier details (from suppliers list)
|
||||||
|
# entity_name might be truncated to 40 chars, so search by startswith
|
||||||
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)
|
||||||
supplier = next((s for s in suppliers_data['suppliers'] if s['name'] == entity_name), None)
|
|
||||||
|
# Find supplier by full or partial name match
|
||||||
|
supplier = next((s for s in suppliers_data['suppliers'] if s['name'].startswith(entity_name)), None)
|
||||||
|
|
||||||
if not supplier:
|
if not supplier:
|
||||||
await query.answer("Furnizor negasit", show_alert=True)
|
await query.answer("Furnizor negăsit", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Use FULL supplier name for invoice search (not truncated)
|
||||||
|
full_supplier_name = supplier['name']
|
||||||
|
|
||||||
|
# Get supplier invoices by FULL name
|
||||||
|
from app.bot.helpers import get_supplier_invoices
|
||||||
|
invoices = await get_supplier_invoices(company['id'], full_supplier_name, jwt_token)
|
||||||
|
|
||||||
# Format response
|
# Format response
|
||||||
from app.bot.formatters import format_supplier_detail_response
|
from app.bot.formatters import format_supplier_detail_response
|
||||||
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_supplier_detail_response(supplier, invoices)
|
content = format_supplier_detail_response(supplier, invoices)
|
||||||
response = format_response_with_company(content, company['name'])
|
response = format_response_with_company(content, company['name'])
|
||||||
keyboard = create_action_buttons(f"supplier_detail:{entity_name}", show_export=False, show_back=True)
|
|
||||||
|
# Use truncated name for callback_data (to stay within 64 byte limit)
|
||||||
|
safe_name = entity_name[:40] if len(entity_name) > 40 else entity_name
|
||||||
|
keyboard = create_action_buttons(f"supplier_detail:{safe_name}", show_export=False, show_back=True, show_refresh=False)
|
||||||
|
|
||||||
await query.edit_message_text(
|
await query.edit_message_text(
|
||||||
response,
|
response,
|
||||||
@@ -1582,6 +1730,63 @@ async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
"Companie negasita sau nu ai acces la ea."
|
"Companie negasita sau nu ai acces la ea."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# ========== LOGOUT CALLBACKS ==========
|
||||||
|
|
||||||
|
elif callback_data == "logout_confirm":
|
||||||
|
# Logout user (same as unlink but shows menu after)
|
||||||
|
from app.auth.linking import unlink_user
|
||||||
|
|
||||||
|
success = await unlink_user(telegram_user_id)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
# Clear session too
|
||||||
|
session_manager = get_session_manager()
|
||||||
|
await session_manager.delete_session(telegram_user_id)
|
||||||
|
|
||||||
|
# Show login menu (non-authenticated)
|
||||||
|
from app.bot.menus import create_main_menu, get_menu_message, pad_message_for_wide_buttons
|
||||||
|
keyboard = create_main_menu(company_name=None, company_cui=None, is_authenticated=False)
|
||||||
|
menu_text = pad_message_for_wide_buttons(
|
||||||
|
"**Deconectat cu succes**\n\n"
|
||||||
|
"Contul tău a fost deconectat.\n\n"
|
||||||
|
"Pentru a te reconecta, apasă **Login**."
|
||||||
|
)
|
||||||
|
|
||||||
|
await query.edit_message_text(
|
||||||
|
menu_text,
|
||||||
|
reply_markup=keyboard,
|
||||||
|
parse_mode=ParseMode.MARKDOWN
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await query.edit_message_text(
|
||||||
|
"A apărut o eroare la deconectare.\n"
|
||||||
|
"Te rog încearcă din nou."
|
||||||
|
)
|
||||||
|
|
||||||
|
elif callback_data == "logout_cancel":
|
||||||
|
# Cancel logout - return to main menu
|
||||||
|
session_manager = get_session_manager()
|
||||||
|
session = await session_manager.get_or_create_session(telegram_user_id)
|
||||||
|
company = session.get_active_company()
|
||||||
|
|
||||||
|
# Check auth
|
||||||
|
auth_data = await get_user_auth_data(telegram_user_id)
|
||||||
|
is_authenticated = auth_data is not None
|
||||||
|
|
||||||
|
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)
|
||||||
|
menu_text = get_menu_message(company_name, company_cui)
|
||||||
|
|
||||||
|
await query.edit_message_text(
|
||||||
|
menu_text,
|
||||||
|
reply_markup=keyboard,
|
||||||
|
parse_mode=ParseMode.MARKDOWN
|
||||||
|
)
|
||||||
|
|
||||||
|
# ========== UNLINK CALLBACKS (LEGACY) ==========
|
||||||
|
|
||||||
elif callback_data == "unlink_confirm":
|
elif callback_data == "unlink_confirm":
|
||||||
# Unlink user
|
# Unlink user
|
||||||
from app.auth.linking import unlink_user
|
from app.auth.linking import unlink_user
|
||||||
@@ -1595,8 +1800,8 @@ async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
|
|
||||||
await query.edit_message_text(
|
await query.edit_message_text(
|
||||||
"**Cont deconectat cu succes**\n\n"
|
"**Cont deconectat cu succes**\n\n"
|
||||||
"Datele tale au fost sterse din sistem.\n\n"
|
"Datele tale au fost șterse din sistem.\n\n"
|
||||||
"Pentru a te reconecta, foloseste /start <cod>",
|
"Pentru a te reconecta, folosește `/start CODUL_TAU`",
|
||||||
parse_mode=ParseMode.MARKDOWN
|
parse_mode=ParseMode.MARKDOWN
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -1616,16 +1821,16 @@ async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
elif callback_data == "login_help":
|
elif callback_data == "login_help":
|
||||||
# Show detailed help on how to get link code
|
# Show detailed help on how to get link code
|
||||||
await query.edit_message_text(
|
await query.edit_message_text(
|
||||||
"**Cum obtin codul de link?**\n\n"
|
"**Cum obțin codul de link?**\n\n"
|
||||||
"1. Logheaza-te in aplicatia web ROA2WEB\n"
|
"1. Loghează-te în aplicația web ROA2WEB\n"
|
||||||
"2. Mergi la: Setari -> Telegram Linking\n"
|
"2. Mergi la: Setări → Telegram Linking\n"
|
||||||
"3. Apasa \"Genereaza Cod\"\n"
|
"3. Apasă **Generează Cod**\n"
|
||||||
"4. Vei primi un cod din 8 caractere (ex: ABC12XYZ)\n"
|
"4. Vei primi un cod din 8 caractere (ex: ABC12XYZ)\n"
|
||||||
"5. Trimite-mi comanda: /start <codul_tau>\n\n"
|
"5. Trimite-mi comanda: `/start CODUL_TAU`\n\n"
|
||||||
"Important: Codul expira in 15 minute.",
|
"**Important:** Codul expiră în 15 minute.",
|
||||||
reply_markup=InlineKeyboardMarkup([
|
reply_markup=InlineKeyboardMarkup([
|
||||||
[InlineKeyboardButton("Am deja cod - Linkez acum", callback_data="login_prompt")],
|
[InlineKeyboardButton("Am deja cod - Linkez acum", callback_data="login_prompt")],
|
||||||
[InlineKeyboardButton("« Inapoi la Bun Venit", callback_data="login_back")]
|
[InlineKeyboardButton("« Înapoi la Meniu", callback_data="action:menu")]
|
||||||
]),
|
]),
|
||||||
parse_mode=ParseMode.MARKDOWN
|
parse_mode=ParseMode.MARKDOWN
|
||||||
)
|
)
|
||||||
@@ -1845,6 +2050,62 @@ async def error_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
# COMMON HANDLER FUNCTIONS (pentru consistență comenzi/butoane)
|
# COMMON HANDLER FUNCTIONS (pentru consistență comenzi/butoane)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
async def _handle_expired_auth(query_or_update, telegram_user_id: int, auth_data: Optional[Dict[str, Any]]) -> bool:
|
||||||
|
"""
|
||||||
|
Check if auth_data is None (token expired/refresh failed) and send user-friendly message.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query_or_update: Update (command) or CallbackQuery (button)
|
||||||
|
telegram_user_id: Telegram user ID
|
||||||
|
auth_data: Authentication data from get_user_auth_data()
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if auth is expired (stop execution), False if auth is valid (continue)
|
||||||
|
"""
|
||||||
|
if auth_data is None:
|
||||||
|
logger.warning(f"Auth expired for user {telegram_user_id}, sending re-authentication message")
|
||||||
|
|
||||||
|
# Create re-authentication message
|
||||||
|
message = (
|
||||||
|
"⚠️ **Sesiunea ta a expirat**\n\n"
|
||||||
|
"Token-ul de autentificare a expirat și nu a putut fi reînnoit automat.\n\n"
|
||||||
|
"**Pentru a continua:**\n"
|
||||||
|
"1. Accesează aplicația web ROA2WEB\n"
|
||||||
|
"2. Loginează-te cu contul tău Oracle\n"
|
||||||
|
"3. Generează un nou cod de link pentru Telegram\n"
|
||||||
|
"4. Trimite comanda `/start CODUL_TAU`\n\n"
|
||||||
|
"_Sau folosește `/unlink` pentru a deconecta contul actual._"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Send message based on source type
|
||||||
|
from telegram import CallbackQuery
|
||||||
|
if isinstance(query_or_update, CallbackQuery):
|
||||||
|
# It's a button callback - transform menu to Login menu
|
||||||
|
await query_or_update.answer("Sesiunea a expirat. Te rog să te reconectezi.", show_alert=True)
|
||||||
|
|
||||||
|
# Transform the current message (menu) to Login menu
|
||||||
|
from app.bot.menus import create_main_menu, pad_message_for_wide_buttons
|
||||||
|
keyboard = create_main_menu(company_name=None, company_cui=None, is_authenticated=False)
|
||||||
|
menu_text = pad_message_for_wide_buttons(
|
||||||
|
"⚠️ **Sesiunea a expirat**\n\n"
|
||||||
|
"Pentru a continua, apasă **Login**\n"
|
||||||
|
"și urmează instrucțiunile."
|
||||||
|
)
|
||||||
|
|
||||||
|
await query_or_update.edit_message_text(
|
||||||
|
menu_text,
|
||||||
|
reply_markup=keyboard,
|
||||||
|
parse_mode=ParseMode.MARKDOWN
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# It's a command (Update) - just send message
|
||||||
|
await query_or_update.message.reply_text(message, parse_mode=ParseMode.MARKDOWN)
|
||||||
|
|
||||||
|
return True # Stop execution
|
||||||
|
|
||||||
|
return False # Continue execution
|
||||||
|
|
||||||
|
|
||||||
async def _handle_sold_view(
|
async def _handle_sold_view(
|
||||||
query_or_update,
|
query_or_update,
|
||||||
telegram_user_id: int,
|
telegram_user_id: int,
|
||||||
|
|||||||
@@ -187,7 +187,8 @@ def get_menu_message(
|
|||||||
|
|
||||||
def create_main_menu(
|
def create_main_menu(
|
||||||
company_name: Optional[str] = None,
|
company_name: Optional[str] = None,
|
||||||
company_cui: Optional[str] = None
|
company_cui: Optional[str] = None,
|
||||||
|
is_authenticated: bool = True
|
||||||
) -> InlineKeyboardMarkup:
|
) -> InlineKeyboardMarkup:
|
||||||
"""
|
"""
|
||||||
Create main menu keyboard (Level 1) with financial options.
|
Create main menu keyboard (Level 1) with financial options.
|
||||||
@@ -197,6 +198,7 @@ def create_main_menu(
|
|||||||
Args:
|
Args:
|
||||||
company_name: Active company name, or None if no company selected
|
company_name: Active company name, or None if no company selected
|
||||||
company_cui: Company fiscal code (CUI), or None
|
company_cui: Company fiscal code (CUI), or None
|
||||||
|
is_authenticated: Whether user is authenticated (affects Login/Logout button)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
InlineKeyboardMarkup with main menu buttons
|
InlineKeyboardMarkup with main menu buttons
|
||||||
@@ -240,48 +242,52 @@ def create_main_menu(
|
|||||||
]
|
]
|
||||||
])
|
])
|
||||||
|
|
||||||
# Row 5: Help button (full width)
|
# Row 5: Help/Logout buttons (authenticated) or Login button (non-authenticated)
|
||||||
keyboard.append([
|
if is_authenticated:
|
||||||
InlineKeyboardButton("Help", callback_data="action:help")
|
keyboard.append([
|
||||||
])
|
InlineKeyboardButton("Help", callback_data="action:help"),
|
||||||
|
InlineKeyboardButton("Logout", callback_data="action:logout")
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
keyboard.append([
|
||||||
|
InlineKeyboardButton("Login", callback_data="action:login")
|
||||||
|
])
|
||||||
|
|
||||||
return InlineKeyboardMarkup(keyboard)
|
return InlineKeyboardMarkup(keyboard)
|
||||||
|
|
||||||
|
|
||||||
def create_action_buttons(current_view: str, show_export: bool = True, show_back: bool = False) -> InlineKeyboardMarkup:
|
def create_action_buttons(current_view: str, show_export: bool = True, show_back: bool = False, show_refresh: bool = True) -> InlineKeyboardMarkup:
|
||||||
"""
|
"""
|
||||||
Create action buttons for responses (Refresh, Export, Back, Menu).
|
Create action buttons for responses (Refresh, Export, Back, Menu).
|
||||||
|
|
||||||
Layout (buttons made wide by message text padding):
|
Layout (buttons made wide by message text padding):
|
||||||
[Refresh] [Export] (if show_export=True)
|
[Refresh] [Export] (if show_refresh=True and show_export=True)
|
||||||
|
[Refresh] (if show_refresh=True and show_export=False)
|
||||||
[Înapoi] (if show_back=True, full width)
|
[Înapoi] (if show_back=True, full width)
|
||||||
[Menu] (full width)
|
[Menu] (full width, always shown)
|
||||||
|
|
||||||
Or:
|
|
||||||
[Refresh] (if show_export=False)
|
|
||||||
[Înapoi] (if show_back=True, full width)
|
|
||||||
[Menu] (full width)
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
current_view: View identifier for refresh callback (e.g., "sold", "clienti")
|
current_view: View identifier for refresh callback (e.g., "sold", "clienti")
|
||||||
show_export: Whether to show Export button
|
show_export: Whether to show Export button
|
||||||
show_back: Whether to show Back button to list
|
show_back: Whether to show Back button to list
|
||||||
|
show_refresh: Whether to show Refresh button
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
InlineKeyboardMarkup with action buttons
|
InlineKeyboardMarkup with action buttons
|
||||||
"""
|
"""
|
||||||
keyboard = []
|
keyboard = []
|
||||||
|
|
||||||
# Row 1: Refresh and optionally Export
|
# Row 1: Refresh and optionally Export (only if show_refresh is True)
|
||||||
if show_export:
|
if show_refresh:
|
||||||
keyboard.append([
|
if show_export:
|
||||||
InlineKeyboardButton("Refresh", callback_data=f"action:refresh:{current_view}"),
|
keyboard.append([
|
||||||
InlineKeyboardButton("Export", callback_data=f"action:export:{current_view}")
|
InlineKeyboardButton("Refresh", callback_data=f"action:refresh:{current_view}"),
|
||||||
])
|
InlineKeyboardButton("Export", callback_data=f"action:export:{current_view}")
|
||||||
else:
|
])
|
||||||
keyboard.append([
|
else:
|
||||||
InlineKeyboardButton("Refresh", callback_data=f"action:refresh:{current_view}")
|
keyboard.append([
|
||||||
])
|
InlineKeyboardButton("Refresh", callback_data=f"action:refresh:{current_view}")
|
||||||
|
])
|
||||||
|
|
||||||
# Row 2: Back to List (if show_back is True)
|
# Row 2: Back to List (if show_back is True)
|
||||||
if show_back:
|
if show_back:
|
||||||
@@ -344,10 +350,15 @@ def create_client_list_keyboard(clients: List[Dict], max_items: int = 10, page:
|
|||||||
balance_str = f"{balance:,.0f}" if balance else "0"
|
balance_str = f"{balance:,.0f}" if balance else "0"
|
||||||
|
|
||||||
button_text = f"{client_name} - {balance_str} RON"
|
button_text = f"{client_name} - {balance_str} RON"
|
||||||
|
|
||||||
|
# Limit callback_data to 64 bytes (Telegram limit)
|
||||||
|
# Use only first 40 chars of name to stay within limit
|
||||||
|
safe_name = client_name[:40] if len(client_name) > 40 else client_name
|
||||||
|
|
||||||
keyboard.append([
|
keyboard.append([
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
button_text,
|
button_text,
|
||||||
callback_data=f"details:client:{client_name}:0" # name:page
|
callback_data=f"details:client:{safe_name}:0" # name:page
|
||||||
)
|
)
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -417,10 +428,15 @@ def create_supplier_list_keyboard(suppliers: List[Dict], max_items: int = 10, pa
|
|||||||
balance_str = f"{balance:,.0f}" if balance else "0"
|
balance_str = f"{balance:,.0f}" if balance else "0"
|
||||||
|
|
||||||
button_text = f"{supplier_name} - {balance_str} RON"
|
button_text = f"{supplier_name} - {balance_str} RON"
|
||||||
|
|
||||||
|
# Limit callback_data to 64 bytes (Telegram limit)
|
||||||
|
# Use only first 40 chars of name to stay within limit
|
||||||
|
safe_name = supplier_name[:40] if len(supplier_name) > 40 else supplier_name
|
||||||
|
|
||||||
keyboard.append([
|
keyboard.append([
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
button_text,
|
button_text,
|
||||||
callback_data=f"details:supplier:{supplier_name}:0" # name:page
|
callback_data=f"details:supplier:{safe_name}:0" # name:page
|
||||||
)
|
)
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -480,6 +496,9 @@ def create_invoice_list_keyboard(
|
|||||||
"""
|
"""
|
||||||
keyboard = []
|
keyboard = []
|
||||||
|
|
||||||
|
# Limit partner_name to 30 chars for Telegram callback_data limit (64 bytes)
|
||||||
|
safe_partner_name = partner_name[:30] if len(partner_name) > 30 else partner_name
|
||||||
|
|
||||||
# Calculate pagination
|
# Calculate pagination
|
||||||
total_invoices = len(invoices)
|
total_invoices = len(invoices)
|
||||||
total_pages = (total_invoices + max_items - 1) // max_items # Ceiling division
|
total_pages = (total_invoices + max_items - 1) // max_items # Ceiling division
|
||||||
@@ -517,7 +536,7 @@ def create_invoice_list_keyboard(
|
|||||||
# Previous button
|
# Previous button
|
||||||
if page > 0:
|
if page > 0:
|
||||||
nav_buttons.append(
|
nav_buttons.append(
|
||||||
InlineKeyboardButton("< Anterior", callback_data=f"invoices_page:{partner_type}:{partner_name}:{page-1}")
|
InlineKeyboardButton("< Anterior", callback_data=f"invoices_page:{partner_type}:{safe_partner_name}:{page-1}")
|
||||||
)
|
)
|
||||||
|
|
||||||
# Page indicator (non-clickable)
|
# Page indicator (non-clickable)
|
||||||
@@ -528,7 +547,7 @@ def create_invoice_list_keyboard(
|
|||||||
# Next button
|
# Next button
|
||||||
if page < total_pages - 1:
|
if page < total_pages - 1:
|
||||||
nav_buttons.append(
|
nav_buttons.append(
|
||||||
InlineKeyboardButton("Următor >", callback_data=f"invoices_page:{partner_type}:{partner_name}:{page+1}")
|
InlineKeyboardButton("Următor >", callback_data=f"invoices_page:{partner_type}:{safe_partner_name}:{page+1}")
|
||||||
)
|
)
|
||||||
|
|
||||||
keyboard.append(nav_buttons)
|
keyboard.append(nav_buttons)
|
||||||
|
|||||||
Reference in New Issue
Block a user