from __future__ import annotations
from html import escape
from app.categories import category_label
from app.domain import FamilyInfo, Totals, Transaction, TransactionKind
from app.parser import format_money
MONTHS = (
"",
"январь",
"февраль",
"март",
"апрель",
"май",
"июнь",
"июль",
"август",
"сентябрь",
"октябрь",
"ноябрь",
"декабрь",
)
WELCOME = """Привет! Я помогу держать деньги под контролем 👋
Просто присылай сумму и описание — я сам определю категорию:
467 ярче
3000 бензин
+ 85000 зарплата
Обычная запись — расход. Знак + или слово доход — поступление.
Каждый вечер я пришлю короткий итог дня и месяца. А в разделе «Семья» можно подключить общий бюджет с супругом. Начнём?"""
HELP_TEXT = """Как записывать операции
Расход:
1250 продукты
3 500 ремонт машины
Доход:
+ 75000 зарплата
доход 5000 подработка
Можно вводить копейки: 199,90 кофе.
После записи категорию можно изменить, а ошибочную операцию — удалить. Кнопки внизу показывают итоги и аналитику.
В семейном режиме кнопка «Общий долг» включает автоматический расчёт: расходы увеличивают долг, доходы уменьшают.
Через «Категории» можно добавить свои категории и подкатегории. Кнопка «Изменить категорию» переносит любую запись, в том числе из «Другое»."""
def transaction_confirmation(transaction: Transaction, debt: int | None = None) -> str:
is_income = transaction.kind is TransactionKind.INCOME
heading = "Доход записан" if is_income else "Расход записан"
icon = "🟢" if is_income else "🔴"
signed_amount = transaction.amount_kopecks if is_income else -transaction.amount_kopecks
debt_line = f"\n\n{debt_short_text(debt)}" if debt is not None else ""
return (
f"{icon} {heading}\n\n"
f"{format_money(signed_amount, signed=True)} · {escape(transaction.description)}\n"
f"{transaction_category_text(transaction)}{debt_line}"
)
def summary_text(title: str, totals: Totals) -> str:
balance_icon = "🟢" if totals.balance >= 0 else "🔴"
return (
f"{escape(title)}\n\n"
f"↗️ Доходы {format_money(totals.income)}\n"
f"↘️ Расходы {format_money(totals.expense)}\n"
f"━━━━━━━━━━━━\n"
f"{balance_icon} Баланс {format_money(totals.balance, signed=True)}"
)
def analytics_text(period_name: str, category_totals: dict, total: int) -> str:
title = f"📊 Расходы {escape(period_name)}"
if total == 0:
return f"{title}\n\nПока расходов нет. Самое время записать первый 👇"
lines = [title, f"Всего: {format_money(total)}", ""]
for label, amount in category_totals.items():
percent = round(amount / total * 100)
filled = max(1, round(percent / 10))
bar = "▰" * filled + "▱" * (10 - filled)
lines.append(escape(label))
lines.append(f"{bar} {percent}% · {format_money(amount)}")
lines.append("")
return "\n".join(lines).rstrip()
def recent_text(transactions: list[Transaction]) -> str:
if not transactions:
return "🧾 Последние записи\n\nЗдесь пока пусто."
lines = ["🧾 Последние записи", ""]
for transaction in transactions:
is_income = transaction.kind is TransactionKind.INCOME
amount = transaction.amount_kopecks if is_income else -transaction.amount_kopecks
author = f" · {escape(transaction.author_name)}" if transaction.author_name else ""
lines.append(
f"{transaction.occurred_at:%d.%m} {format_money(amount, signed=True)} "
f"{escape(transaction.description)}{author}"
)
lines.append("")
lines.append("Удалить или исправить запись можно сразу после её добавления.")
return "\n".join(lines)
def family_status_text(info: FamilyInfo | None) -> str:
if info is None:
return (
"👨👩👧 Семейный бюджет\n\n"
"Объедините два Telegram-аккаунта. После подключения вы оба будете видеть "
"общие итоги, аналитику и новые записи в реальном времени."
)
members = "\n".join(f"• {escape(name)}" for name in info.members)
waiting = (
"\n\nОтправьте код супругу. Подключение произойдёт только после вашего подтверждения."
if len(info.members) < 2
else ""
)
chat = (
f"\n\n💬 Общий чат: {escape(info.notification_chat_title or 'Подключён')}"
if info.notification_chat_id is not None
else (
"\n\n💬 Общий чат пока не подключён. Создатель бюджета должен добавить "
"бота в семейную группу, стать администратором группы и отправить там /family_chat."
)
)
return (
"👨👩👧 Семейный бюджет подключён\n\n"
f"Участники:\n{members}\n\n"
f"Код приглашения: {escape(info.invite_code)}"
f"{waiting}{chat}"
)
def family_transaction_notice(transaction: Transaction, debt: int | None = None) -> str:
is_income = transaction.kind is TransactionKind.INCOME
action = "добавил(а) доход" if is_income else "добавил(а) расход"
icon = "🟢" if is_income else "🔴"
amount = transaction.amount_kopecks if is_income else -transaction.amount_kopecks
author = escape(transaction.author_name or "Супруг")
debt_line = f"\n\n{debt_short_text(debt)}" if debt is not None else ""
return (
f"👨👩👧 {author} {action}\n\n"
f"{icon} {format_money(amount, signed=True)} · {escape(transaction.description)}\n"
f"{transaction_category_text(transaction)}{debt_line}"
)
def daily_report_text(day: Totals, month: Totals, debt: int | None = None) -> str:
balance_icon = "✨" if day.balance >= 0 else "📌"
debt_section = f"\n\n{debt_short_text(debt)}" if debt is not None else ""
return (
"🌙 Итог дня\n\n"
f"Потрачено сегодня: {format_money(day.expense)}\n"
f"Получено сегодня: {format_money(day.income)}\n"
f"{balance_icon} Баланс дня: {format_money(day.balance, signed=True)}\n\n"
"С начала месяца\n"
f"Расходы: {format_money(month.expense)}\n"
f"Доходы: {format_money(month.income)}\n"
f"Баланс: {format_money(month.balance, signed=True)}"
f"{debt_section}"
)
def debt_short_text(debt: int) -> str:
if debt > 0:
return f"💳 Общий долг: {format_money(debt)}"
if debt < 0:
return f"🌿 Семейный резерв: {format_money(abs(debt))}"
return "🎉 Общий долг: погашен"
def debt_status_text(debt: int | None, can_edit: bool) -> str:
if debt is None:
status = "Общий долг пока не указан."
else:
status = debt_short_text(debt)
edit_hint = (
"\n\nУкажите текущую сумму долга. С этого момента каждый расход будет её увеличивать, "
"а каждый доход — уменьшать."
if can_edit
else "\n\nИзменить исходную сумму может только создатель семейного бюджета."
)
return f"💳 Общий долг\n\n{status}{edit_hint}"
def transaction_category_text(transaction: Transaction) -> str:
if transaction.budget_category_name and transaction.parent_category_name:
return (
f"{escape(transaction.parent_category_icon or '📁')} "
f"{escape(transaction.parent_category_name)} → "
f"{escape(transaction.budget_category_icon or '📁')} "
f"{escape(transaction.budget_category_name)}"
)
if transaction.budget_category_name:
return (
f"{escape(transaction.budget_category_icon or '📁')} "
f"{escape(transaction.budget_category_name)}"
)
return category_label(transaction.category)