Files

273 lines
9.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import decimal
import html
from typing import Any
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from .domain import (
MAX_DEAL_MESSAGE_LENGTH,
DealStageFilter,
)
class DealFormatter:
"""Тексты карточек и списков Telegram."""
@staticmethod
def truncate(text: str, limit: int) -> str:
if len(text) <= limit:
return text
return text[: limit - 3].rstrip() + "..."
@staticmethod
def list_title(
stage_filter: DealStageFilter,
page: int,
total_deals: int,
total_pages: int
) -> str:
title = html.escape(stage_filter.title)
return (
f"<b>Сделки: {title}</b>\n"
f"Всего сделок: <b>{total_deals}</b>\n"
f"Страница <b>{page + 1}</b> из <b>{total_pages}</b>"
)
@staticmethod
def money(value: Any, currency: Any) -> str:
try:
amount = decimal.Decimal(str(value or "0"))
formatted = f"{amount:,.2f}".replace(",", " ")
except decimal.InvalidOperation:
formatted = html.escape(str(value or "0"))
return f"{formatted} {html.escape(str(currency or ''))}".strip()
@classmethod
def deal_summary(cls, deal: dict) -> str:
summary = " · ".join(
(
f"#{deal.get('ID', '-')}",
str(deal.get("TITLE") or "Без названия"),
cls.money(deal.get("OPPORTUNITY"), deal.get("CURRENCY_ID"))
)
)
return cls.truncate(summary, 60)
@classmethod
def deal_details(cls, deal: dict) -> str:
deal_id = html.escape(str(deal.get("ID", "-")))
title = html.escape(str(deal.get("TITLE") or "Без названия"))
stage = html.escape(
str(deal.get("STAGE_NAME") or deal.get("STAGE_ID") or "-")
)
source = html.escape(
str(deal.get("SOURCE_NAME") or deal.get("SOURCE_ID") or "-")
)
assigned = html.escape(str(deal.get("ASSIGNED_BY_ID") or "не назначен"))
date = html.escape(str(deal.get("DATE_CREATE") or "-"))
client = html.escape(str(deal.get("CLIENT_NAME") or "не указан"))
company = html.escape(str(deal.get("CLIENT_COMPANY") or ""))
phone = html.escape(str(deal.get("CLIENT_PHONE") or "не найден"))
comments = html.escape(str(deal.get("COMMENTS") or "")).strip()
lines = [
f"<b>Сделка #{deal_id}</b>",
f"<b>{title}</b>",
"",
f"Клиент: <code>{client}</code>",
*([f"Компания: <code>{company}</code>"] if company else []),
f"Телефон клиента: <code>{phone}</code>",
f"Стадия: <code>{stage}</code>",
f"Источник сделки: <code>{source}</code>",
f"Сумма: {cls.money(deal.get('OPPORTUNITY'), deal.get('CURRENCY_ID'))}",
f"Ответственный: <code>{assigned}</code>",
f"Дата создания: <code>{date}</code>"
]
if comments:
lines.extend(["", f"<b>Комментарий:</b>\n{comments}"])
return cls.truncate("\n".join(lines), MAX_DEAL_MESSAGE_LENGTH)
@classmethod
def deal_history(cls, deal_id: str, events: list[dict]) -> str:
lines = [f"<b>История сделки #{html.escape(deal_id)}</b>"]
if not events:
lines.extend(["", "Изменения стадий пока не найдены."])
return "\n".join(lines)
event_names = {
"1": "Сделка создана",
"2": "Переход на стадию",
"3": "Переход на финальную стадию",
"5": "Изменение воронки"
}
for event in events:
date = html.escape(
str(event.get("CREATED_TIME") or "дата не указана"))
event_type = str(event.get("TYPE_ID") or "")
name = event_names.get(event_type, "Изменение стадии")
stage = html.escape(str(
event.get("STAGE_NAME")
or event.get("STAGE_ID")
or "не указана"
))
lines.extend(
[
"",
f"• <b>{name}</b>",
f" Стадия: <code>{stage}</code>",
f" {date}"
]
)
return cls.truncate("\n".join(lines), MAX_DEAL_MESSAGE_LENGTH)
class DealKeyboards:
@staticmethod
def deals_page(
deals: list[dict],
stage_filters: tuple[DealStageFilter, ...],
stage_key: str,
page: int,
has_next: bool
) -> InlineKeyboardMarkup:
filter_buttons = [
InlineKeyboardButton(
text=("✓ " if stage.key == stage_key else "") + stage.title,
callback_data=f"deals:page:{stage.key}:0"
)
for stage in stage_filters
]
rows = [
[
InlineKeyboardButton(
text=DealFormatter.deal_summary(deal),
callback_data=f"deal:view:{deal['ID']}"
)
]
for deal in deals
]
navigation = []
if page > 0:
navigation.append(
InlineKeyboardButton(
text="Назад",
callback_data=f"deals:page:{stage_key}:{page - 1}"
)
)
if has_next:
navigation.append(
InlineKeyboardButton(
text="Вперед",
callback_data=f"deals:page:{stage_key}:{page + 1}"
)
)
if navigation:
rows.append(navigation)
rows.append(
[
InlineKeyboardButton(
text="Обновить",
callback_data=f"deals:page:{stage_key}:{page}"
)
]
)
rows.extend(
filter_buttons[index: index + 2]
for index in range(0, len(filter_buttons), 2)
)
return InlineKeyboardMarkup(inline_keyboard=rows)
@staticmethod
def deal_card(
deal: dict,
viewer_bitrix_user_id: int
) -> InlineKeyboardMarkup:
deal_id = str(deal["ID"])
responsible_id = str(deal.get("ASSIGNED_BY_ID") or "")
rows = []
if (
str(deal.get("IS_NEW") or "").upper() == "Y"
and responsible_id != str(viewer_bitrix_user_id)
):
rows.append(
[
InlineKeyboardButton(
text="Стать ответственным и взять в работу",
callback_data=(
f"deal:assign:{deal_id}:{responsible_id}")
)
]
)
if (
responsible_id == str(viewer_bitrix_user_id)
and deal.get("NEXT_STAGE_ID")
):
next_stage_name = DealFormatter.truncate(
str(deal.get("NEXT_STAGE_NAME") or "следующая стадия"),
42,
)
if deal.get("NEXT_STAGE_IS_FINAL"):
button_text = f"Завершить: {next_stage_name}"
else:
button_text = f"Следующая стадия: {next_stage_name}"
rows.append(
[
InlineKeyboardButton(
text=button_text,
callback_data=f"deal:advance:{deal_id}",
)
]
)
rows.append(
[
InlineKeyboardButton(
text="Позвонить позже",
callback_data=f"deal:remind:{deal_id}"
)
]
)
rows.append(
[
InlineKeyboardButton(
text="История изменений",
callback_data=f"deal:history:{deal_id}"
)
]
)
rows.append(
[
InlineKeyboardButton(
text="К списку сделок",
callback_data="deals:page:new:0"
)
]
)
return InlineKeyboardMarkup(inline_keyboard=rows)
@staticmethod
def deal_history(deal_id: str) -> InlineKeyboardMarkup:
return InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="К сделке",
callback_data=f"deal:view:{deal_id}"
)
],
[
InlineKeyboardButton(
text="К списку сделок",
callback_data="deals:page:new:0"
)
],
]
)