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)