Stage 06.1 - journal management UI, export and system menu redesign

This commit is contained in:
2026-04-27 15:02:56 +03:00
parent 1fb72ced58
commit f6fc300e84
19 changed files with 1935 additions and 421 deletions

View File

@@ -19,6 +19,7 @@ from src.telegram.ui.currency_ui import (
is_zero_balance,
)
from src.telegram.ui.exchange_error import (
classify_exchange_error,
show_callback_exchange_error,
show_message_exchange_error,
)
@@ -52,42 +53,6 @@ def _portfolio_warning_keyboard() -> InlineKeyboardMarkup:
return builder.as_markup()
def _safe_log_info(
journal: JournalService,
event_type: str,
message: str,
payload: dict | None = None,
) -> None:
try:
journal.log_info(event_type, message, payload)
except Exception:
pass
def _safe_log_warning(
journal: JournalService,
event_type: str,
message: str,
payload: dict | None = None,
) -> None:
try:
journal.log_warning(event_type, message, payload)
except Exception:
pass
def _safe_log_error(
journal: JournalService,
event_type: str,
message: str,
payload: dict | None = None,
) -> None:
try:
journal.log_error(event_type, message, payload)
except Exception:
pass
def sort_balances(items: list[BalanceSummary]) -> list[BalanceSummary]:
def sort_key(item: BalanceSummary) -> tuple[int, str]:
currency = item.currency.upper()
@@ -103,32 +68,31 @@ async def _render_portfolio_screen(
user_id: int | None,
chat_id: int | None,
edit_mode: bool,
action: str,
) -> None:
service = AccountsService()
exchange_service = ExchangeService()
journal = JournalService()
_safe_log_info(
journal,
"user_open_portfolio",
"Пользователь открыл экран портфеля.",
{
"user_id": user_id,
"chat_id": chat_id,
},
journal.log_ui_info(
event_type="portfolio_open_requested",
message="Запрошено открытие экрана портфеля.",
screen="portfolio",
action=action,
user_id=user_id,
chat_id=chat_id,
)
balances = service.get_live_balance_summary()
if not balances:
_safe_log_warning(
journal,
"portfolio_empty",
"Портфель открыт, но баланс пуст.",
{
"user_id": user_id,
"chat_id": chat_id,
},
journal.log_ui_warning(
event_type="portfolio_empty",
message="Нет данных по балансу.",
screen="portfolio",
action=action,
user_id=user_id,
chat_id=chat_id,
)
text = (
@@ -147,15 +111,14 @@ async def _render_portfolio_screen(
visible_balances = sort_balances(visible_balances)
if not visible_balances:
_safe_log_warning(
journal,
"portfolio_zero_balances",
"Портфель открыт, но все балансы нулевые.",
{
"user_id": user_id,
"chat_id": chat_id,
"assets_count": len(balances),
},
journal.log_ui_warning(
event_type="portfolio_zero_balances",
message="Нет активов с балансом.",
screen="portfolio",
action=action,
user_id=user_id,
chat_id=chat_id,
payload={"assets_count": len(balances)},
)
text = (
@@ -211,30 +174,25 @@ async def _render_portfolio_screen(
has_partial_data = len(missing_estimate_assets) > 0
if missing_estimate_assets:
lines.extend(
[
"🟡 <b>Данные загружены частично</b>",
]
)
lines.append("🟡 <b>Данные загружены частично</b>")
if has_any_estimate:
lines.append(f"ОЦЕНКА · ~${format_usd_amount(total_estimated_usd)}")
if missing_estimate_assets:
lines.append(
f"Нет оценки: {', '.join(missing_estimate_assets)}"
)
lines.append(f"Нет оценки: {', '.join(missing_estimate_assets)}")
for block in asset_blocks:
lines.extend(block)
_safe_log_info(
journal,
"portfolio_open_success",
"Портфель успешно показан пользователю.",
{
"user_id": user_id,
"chat_id": chat_id,
journal.log_ui_info(
event_type="portfolio_open_success",
message="Портфель загружен.",
screen="portfolio",
action=action,
user_id=user_id,
chat_id=chat_id,
payload={
"assets_count": len(visible_balances),
"estimated_usd": round(total_estimated_usd, 2) if has_any_estimate else None,
"missing_estimate_assets": missing_estimate_assets,
@@ -242,25 +200,23 @@ async def _render_portfolio_screen(
)
if missing_estimate_assets:
_safe_log_warning(
journal,
"portfolio_partial_estimate",
"Портфель показан частично: не для всех активов доступна USD-оценка.",
{
"user_id": user_id,
"chat_id": chat_id,
"missing_estimate_assets": missing_estimate_assets,
journal.log_ui_warning(
event_type="portfolio_partial_estimate",
message="Портфель загружен частично.",
screen="portfolio",
action=action,
user_id=user_id,
chat_id=chat_id,
payload={
"assets_count": len(visible_balances),
"estimated_assets": len(visible_balances) - len(missing_estimate_assets),
"failed_assets": missing_estimate_assets,
},
)
if has_partial_data:
lines.extend(
[
"",
now_line(),
]
)
lines.extend(["", now_line()])
text = "\n".join(lines).rstrip()
reply_markup = (
@@ -288,18 +244,20 @@ async def open_portfolio(message: Message, state: FSMContext) -> None:
user_id=user_id,
chat_id=chat_id,
edit_mode=False,
action="open",
)
except ExchangeError as exc:
journal = JournalService()
_safe_log_error(
journal,
"portfolio_open_error",
f"Не удалось открыть портфель: {exc}",
{
"user_id": user_id,
"chat_id": chat_id,
},
JournalService().log_ui_error(
event_type="portfolio_open_error",
message="Не удалось загрузить портфель.",
screen="portfolio",
action="open",
user_id=user_id,
chat_id=chat_id,
error_type=classify_exchange_error(exc),
raw_error=str(exc),
)
await show_message_exchange_error(
message,
title="<b>💼 Портфель</b>",
@@ -327,19 +285,21 @@ async def retry_portfolio(callback: CallbackQuery, state: FSMContext) -> None:
user_id=user_id,
chat_id=chat_id,
edit_mode=True,
action="retry",
)
await callback.answer()
except ExchangeError as exc:
journal = JournalService()
_safe_log_error(
journal,
"portfolio_retry_error",
f"Не удалось повторно открыть портфель: {exc}",
{
"user_id": user_id,
"chat_id": chat_id,
},
JournalService().log_ui_error(
event_type="portfolio_retry_error",
message="Не удалось обновить портфель.",
screen="portfolio",
action="retry",
user_id=user_id,
chat_id=chat_id,
error_type=classify_exchange_error(exc),
raw_error=str(exc),
)
await show_callback_exchange_error(
callback,
title="<b>💼 Портфель</b>",