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:
@@ -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."
|
||||
|
||||
|
||||
Reference in New Issue
Block a user