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:
2025-11-07 01:02:03 +02:00
parent 20f6c52785
commit a4ee394091
3 changed files with 405 additions and 131 deletions

View File

@@ -187,7 +187,8 @@ def get_menu_message(
def create_main_menu(
company_name: Optional[str] = None,
company_cui: Optional[str] = None
company_cui: Optional[str] = None,
is_authenticated: bool = True
) -> InlineKeyboardMarkup:
"""
Create main menu keyboard (Level 1) with financial options.
@@ -197,6 +198,7 @@ def create_main_menu(
Args:
company_name: Active company name, or None if no company selected
company_cui: Company fiscal code (CUI), or None
is_authenticated: Whether user is authenticated (affects Login/Logout button)
Returns:
InlineKeyboardMarkup with main menu buttons
@@ -240,48 +242,52 @@ def create_main_menu(
]
])
# Row 5: Help button (full width)
keyboard.append([
InlineKeyboardButton("Help", callback_data="action:help")
])
# Row 5: Help/Logout buttons (authenticated) or Login button (non-authenticated)
if is_authenticated:
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)
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).
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)
[Menu] (full width)
Or:
[Refresh] (if show_export=False)
[Înapoi] (if show_back=True, full width)
[Menu] (full width)
[Menu] (full width, always shown)
Args:
current_view: View identifier for refresh callback (e.g., "sold", "clienti")
show_export: Whether to show Export button
show_back: Whether to show Back button to list
show_refresh: Whether to show Refresh button
Returns:
InlineKeyboardMarkup with action buttons
"""
keyboard = []
# Row 1: Refresh and optionally Export
if show_export:
keyboard.append([
InlineKeyboardButton("Refresh", callback_data=f"action:refresh:{current_view}"),
InlineKeyboardButton("Export", callback_data=f"action:export:{current_view}")
])
else:
keyboard.append([
InlineKeyboardButton("Refresh", callback_data=f"action:refresh:{current_view}")
])
# Row 1: Refresh and optionally Export (only if show_refresh is True)
if show_refresh:
if show_export:
keyboard.append([
InlineKeyboardButton("Refresh", callback_data=f"action:refresh:{current_view}"),
InlineKeyboardButton("Export", callback_data=f"action:export:{current_view}")
])
else:
keyboard.append([
InlineKeyboardButton("Refresh", callback_data=f"action:refresh:{current_view}")
])
# Row 2: Back to List (if show_back is True)
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"
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([
InlineKeyboardButton(
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"
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([
InlineKeyboardButton(
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 = []
# 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
total_invoices = len(invoices)
total_pages = (total_invoices + max_items - 1) // max_items # Ceiling division
@@ -517,7 +536,7 @@ def create_invoice_list_keyboard(
# Previous button
if page > 0:
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)
@@ -528,7 +547,7 @@ def create_invoice_list_keyboard(
# Next button
if page < total_pages - 1:
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)