Enhance Telegram bot UI with YTD comparison, 12-month evolution, and improved navigation

- Add YTD year-over-year comparison table for cash flow evolution
- Extend monthly evolution from 6 to 12 months with dynamic year extraction
- Simplify monthly view to show only Net values aligned with YTD table
- Upgrade client/supplier display from Top 5 to Top 10 with alphabetical sorting
- Remove Refresh and Export buttons from dashboard and evolution views
- Add get_trends() API method for 12-month historical data from backend
- Fix default years to 2025/2024 for accurate YTD calculations

Changes:
- client.py: New get_trends() method calls /api/dashboard/trends endpoint
- helpers.py: Rewrite get_cashflow_evolution_data() to use trends and calculate YTD
- formatters.py: Complete redesign with YTD table and simplified 12-month Net view
- menus.py: Alphabetical sorting for clients/suppliers, removed refresh buttons
- handlers.py: Disabled refresh/export buttons on dashboard and evolution views

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-07 02:30:28 +02:00
parent a4ee394091
commit 87bd04e3ff
5 changed files with 244 additions and 78 deletions

View File

@@ -203,9 +203,9 @@ def format_clients_balance_response(
text += f" - In termen: {in_term:,} RON\n"
text += f" - Restanta: {overdue:,} RON\n\n"
# Top clients
# Top 10 clients
if clients:
text += f"**Top Clienti** ({len(clients)} total):\n"
text += f"**Top 10 Clienti** ({len(clients)} total):\n"
# Sort by balance descending
sorted_clients = sorted(
clients,
@@ -213,12 +213,12 @@ def format_clients_balance_response(
reverse=True
)
for idx, client in enumerate(sorted_clients[:5], 1):
for idx, client in enumerate(sorted_clients[:10], 1):
name = client.get('name', 'N/A')
balance = round(client.get('balance', 0))
text += f"{idx}. {name}: {balance:,} RON\n"
if len(clients) > 5:
if len(clients) > 10:
text += f"\nApasa butonul pentru lista completa"
else:
text += "Nu exista clienti cu solduri."
@@ -260,9 +260,9 @@ def format_suppliers_balance_response(
text += f" - In termen: {in_term:,} RON\n"
text += f" - Restanta: {overdue:,} RON\n\n"
# Top suppliers
# Top 10 suppliers
if suppliers:
text += f"**Top Furnizori** ({len(suppliers)} total):\n"
text += f"**Top 10 Furnizori** ({len(suppliers)} total):\n"
# Sort by balance descending
sorted_suppliers = sorted(
suppliers,
@@ -270,12 +270,12 @@ def format_suppliers_balance_response(
reverse=True
)
for idx, supplier in enumerate(sorted_suppliers[:5], 1):
for idx, supplier in enumerate(sorted_suppliers[:10], 1):
name = supplier.get('name', 'N/A')
balance = round(supplier.get('balance', 0))
text += f"{idx}. {name}: {balance:,} RON\n"
if len(suppliers) > 5:
if len(suppliers) > 10:
text += f"\nApasa butonul pentru lista completa"
else:
text += "Nu exista furnizori cu solduri."
@@ -289,56 +289,117 @@ def format_cashflow_evolution_response(
company_name: str = None
) -> str:
"""
Format cash flow evolution data (content only, no header).
Format cash flow evolution data - Table format with mini-charts.
Args:
performance_data: Dict with incasari_total, plati_total, net
monthly_data: Dict with months, incasari, plati arrays
performance_data: Dict with current_year and previous_year YTD data
monthly_data: Dict with months, incasari, plati arrays + prev year data
company_name: Company name (kept for compatibility, not used)
Returns:
Formatted Markdown string for Telegram
Formatted Markdown string for Telegram (monospace table)
Example:
performance = {'incasari_total': 100000, 'plati_total': 80000, 'net': 20000}
monthly = {'months': ['Ian', 'Feb'], 'incasari': [50000, 50000], 'plati': [40000, 40000]}
text = format_cashflow_evolution_response(performance, monthly)
YTD 2024 vs 2023:
2024 2023 Δ Trend
Inc: 500,000 480,000 +4.2% ████░
Plt: 450,000 440,000 +2.3% ███░
Net: 50,000 40,000 +25.0% █████
"""
text = ""
# Performance summary - rotunjit la leu (0 zecimale)
incasari_total = round(performance_data.get('incasari_total', 0))
plati_total = round(performance_data.get('plati_total', 0))
net = round(performance_data.get('net', 0))
# Helper functions
def calc_percent_change(current: float, previous: float) -> str:
"""Calculate percentage change: +4.2% or -3.5%"""
if previous == 0:
return "+100%" if current > 0 else "0.0%"
change = ((current - previous) / previous) * 100
sign = "+" if change >= 0 else ""
return f"{sign}{change:.1f}%"
text += "**Rezumat:**\n"
text += f" - Total Incasari: {incasari_total:,} RON\n"
text += f" - Total Plati: {plati_total:,} RON\n"
text += f" - Net Cash Flow: {net:,} RON\n\n"
def create_mini_chart(current: float, previous: float, width: int = 5) -> str:
"""Create mini bar chart: ████░ (proportional bars)"""
if current == 0 and previous == 0:
return "" * width
# Monthly breakdown
max_val = max(current, previous)
if max_val == 0:
return "" * width
curr_bars = int((current / max_val) * width)
prev_bars = int((previous / max_val) * width)
# Use filled and light blocks
filled = "" * curr_bars
light = "" * (width - curr_bars)
return filled + light
def get_trend_arrow(current: float, previous: float) -> str:
"""Get trend arrow: ↑ or ↓ or →"""
if current > previous * 1.02: # More than 2% increase
return ""
elif current < previous * 0.98: # More than 2% decrease
return ""
else:
return ""
# Extract YTD data
current = performance_data.get('current_year', {})
previous = performance_data.get('previous_year', {})
current_year = current.get('year', '2024')
previous_year = previous.get('year', '2023')
inc_cur = round(current.get('incasari', 0))
plt_cur = round(current.get('plati', 0))
net_cur = round(current.get('net', 0))
inc_prev = round(previous.get('incasari', 0))
plt_prev = round(previous.get('plati', 0))
net_prev = round(previous.get('net', 0))
# YTD Table Header
text += f"**YTD {current_year} vs {previous_year}:**\n"
text += f"` {current_year:>10} {previous_year:>10} Δ `\n"
# YTD Rows
inc_pct = calc_percent_change(inc_cur, inc_prev)
text += f"`Inc: {inc_cur:>10,} {inc_prev:>10,} {inc_pct:>6}`\n"
plt_pct = calc_percent_change(plt_cur, plt_prev)
text += f"`Plt: {plt_cur:>10,} {plt_prev:>10,} {plt_pct:>6}`\n"
net_pct = calc_percent_change(net_cur, net_prev)
text += f"`Net: {net_cur:>10,} {net_prev:>10,} {net_pct:>6}`\n\n"
# Monthly Evolution Table - Simplified (Net only)
months = monthly_data.get('months', [])
incasari = monthly_data.get('incasari', [])
plati = monthly_data.get('plati', [])
incasari_prev = monthly_data.get('incasari_prev', [])
plati_prev = monthly_data.get('plati_prev', [])
if months and len(months) > 0:
text += "**Evolutie Lunara** (ultimele luni):\n"
text += "**Evolutie Net (12 luni):**\n"
text += f"` {current_year:>10} {previous_year:>10} Δ `\n"
# Show last 6 months
display_count = min(6, len(months))
for i in range(display_count):
month = months[-(display_count - i)]
inc = round(incasari[-(display_count - i)]) if i < len(incasari) else 0
plt = round(plati[-(display_count - i)]) if i < len(plati) else 0
net_month = inc - plt
for i, month in enumerate(months):
inc = incasari[i] if i < len(incasari) else 0
plt = plati[i] if i < len(plati) else 0
inc_p = incasari_prev[i] if i < len(incasari_prev) else 0
plt_p = plati_prev[i] if i < len(plati_prev) else 0
# Simple ASCII bar
net_indicator = "+" if net_month > 0 else "-" if net_month < 0 else "="
net = inc - plt
net_p = inc_p - plt_p
text += f"\n**{month}:**\n"
text += f" {net_indicator} Incasari: {inc:,} RON\n"
text += f" {net_indicator} Plati: {plt:,} RON\n"
text += f" {net_indicator} Net: {net_month:,} RON"
# Extract short month name (first 3 chars before apostrophe)
month_short = month.split("'")[0][:3] if "'" in month else month[:3]
# Calculate percentage change
net_pct = calc_percent_change(net, net_p)
# Format row: Luna Net'current Net'prev Δ (aligned with YTD)
text += f"`{month_short:<4} {int(net):>10,} {int(net_p):>10,} {net_pct:>6}`\n"
else:
text += "Nu exista date lunare disponibile."