Чистка старого ИКП-флоу: удалены форма-заявка, карьера/вакансии, меню FAQ и осиротевшая админка
Из ТЗ Кибершколы БАЗА эти сценарии заменены воронкой подбора, поэтому убран мёртвый код: - клиентские FSM: FORM_* (форма-заявка, возвращающийся клиент, редактирование, согласия, выбор услуги, карусель) и VAC_* (карьера: игроки/сотрудники); - меню «Частые вопросы» и config.FAQ_ANSWERS; - осиротевшая админка: управление услугами + карусель, вакансии, кандидаты (в панели и в мастере ADMIN_SVC_*/ADMIN_VAC_*), блок статистики по откликам; - старый _handoff_to_operator и _notify_operator_about_lead (заменены _baza_to_manager); - неиспользуемые клавиатуры (форма/услуги/вакансии/согласия) и импорты (validators, random, string). Оставлено (используется/по ТЗ): воронка БАЗА, быстрые ответы (custom_faq), стата по лидам, рассылка, управление админами, чат с оператором, логотип. Слой БД не тронут — существующие установки не ломаются (таблицы services/applications/vacancy_options просто не используются). bot.py: -1490 строк. Тесты: pytest 28 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -145,44 +145,7 @@ INTENSIVE_INFO = (
|
|||||||
f"Стоимость участия — {INTENSIVE_PRICE} за все 4 тренировки."
|
f"Стоимость участия — {INTENSIVE_PRICE} за все 4 тренировки."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Standard replies and patterns
|
# Доступные услуги (направления) — используются для сидирования БД (services)
|
||||||
FAQ_ANSWERS = {
|
|
||||||
"цены": (
|
|
||||||
"💰 Тарифы ИКП на тренировки (направления: CS2, Dota 2, World of Tanks):\n\n"
|
|
||||||
"• Разовая тренировка — 870 ₽\n"
|
|
||||||
"• Абонемент на 4 занятия (групповой) — 2 790 ₽\n"
|
|
||||||
"• Абонемент на 8 занятий (групповой) — 4 990 ₽\n"
|
|
||||||
"• Абонемент на 4 занятия (индивидуальный) — 3 290 ₽\n"
|
|
||||||
"• Абонемент на 8 занятий (индивидуальный) — 5 990 ₽\n\n"
|
|
||||||
"Оставьте заявку через меню — менеджер подберёт оптимальный формат и направление!"
|
|
||||||
),
|
|
||||||
"контакты": (
|
|
||||||
"📞 Связаться с ИКП:\n"
|
|
||||||
"• Email (поддержка): support@infocyber.pro\n"
|
|
||||||
"• Заявки: group-zakaz@infocyber.pro\n"
|
|
||||||
"• Telegram: https://t.me/ikp_team\n"
|
|
||||||
"• Сайт: https://infocyber.pro/"
|
|
||||||
),
|
|
||||||
"услуги": (
|
|
||||||
"⚙️ ИКП — тренировки по киберспортивным дисциплинам:\n\n"
|
|
||||||
"1. Counter-Strike 2 — aim, раскидки гранат, командная тактика\n"
|
|
||||||
"2. Dota 2 — разбор реплеев, позиционка, фарм и тимплей\n"
|
|
||||||
"3. World of Tanks — позиционирование, сведение, тактика боёв\n\n"
|
|
||||||
"Форматы: разовые занятия, групповые и индивидуальные абонементы (4 и 8 занятий)."
|
|
||||||
),
|
|
||||||
"о компании": (
|
|
||||||
"🏢 ИКП — Игра. Киберспорт. Профессионалы.\n\n"
|
|
||||||
"Мощная экосистема для взрывного роста игроков: лучшие тренеры и глубокая data-аналитика. "
|
|
||||||
"Организация основана 1 сентября 2025 года.\n\n"
|
|
||||||
"Мы на площадках:\n"
|
|
||||||
"• ВКонтакте: https://vk.com/ikpro\n"
|
|
||||||
"• VK Live (трансляции): https://live.vkvideo.ru/ikp\n"
|
|
||||||
"• Telegram: https://t.me/ikp_team\n"
|
|
||||||
"• Lolka: https://vk.com/away.php?to=https%3A%2F%2Flolka.gg%2FlNfM3F9DD&utf=1"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
# Доступные услуги (направления) для выбора в форме заявки
|
|
||||||
SERVICES = {
|
SERVICES = {
|
||||||
"1": {
|
"1": {
|
||||||
"name": "Counter-Strike 2 🔫",
|
"name": "Counter-Strike 2 🔫",
|
||||||
|
|||||||
+1
-197
@@ -116,12 +116,8 @@ def get_admin_keyboard(is_super: bool = False) -> str:
|
|||||||
"""Клавиатура админ-панели с разделами. Супер-админу — раздел «Администраторы»."""
|
"""Клавиатура админ-панели с разделами. Супер-админу — раздел «Администраторы»."""
|
||||||
keyboard = VkKeyboard(one_time=False)
|
keyboard = VkKeyboard(one_time=False)
|
||||||
keyboard.add_button("Статистика 📊", color=VkKeyboardColor.SECONDARY)
|
keyboard.add_button("Статистика 📊", color=VkKeyboardColor.SECONDARY)
|
||||||
keyboard.add_button("Кандидаты 👥", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("Управление услугами ⚙️", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_button("Вакансии 💼", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("Быстрые ответы 💬", color=VkKeyboardColor.SECONDARY)
|
keyboard.add_button("Быстрые ответы 💬", color=VkKeyboardColor.SECONDARY)
|
||||||
|
keyboard.add_line()
|
||||||
keyboard.add_button("Рассылка 📣", color=VkKeyboardColor.SECONDARY)
|
keyboard.add_button("Рассылка 📣", color=VkKeyboardColor.SECONDARY)
|
||||||
keyboard.add_line()
|
keyboard.add_line()
|
||||||
if is_super:
|
if is_super:
|
||||||
@@ -131,20 +127,6 @@ def get_admin_keyboard(is_super: bool = False) -> str:
|
|||||||
return keyboard.get_keyboard()
|
return keyboard.get_keyboard()
|
||||||
|
|
||||||
|
|
||||||
def get_svc_admin_keyboard() -> str:
|
|
||||||
"""Клавиатура раздела управления услугами (направлениями)."""
|
|
||||||
keyboard = VkKeyboard(one_time=False)
|
|
||||||
keyboard.add_button("➕ Добавить услугу", color=VkKeyboardColor.PRIMARY)
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("✏️ Изменить услугу", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_button("🖼 Сменить картинку", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("🗑 Удалить услугу", color=VkKeyboardColor.NEGATIVE)
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("В админку ↩️", color=VkKeyboardColor.SECONDARY)
|
|
||||||
return keyboard.get_keyboard()
|
|
||||||
|
|
||||||
|
|
||||||
def get_faq_admin_keyboard() -> str:
|
def get_faq_admin_keyboard() -> str:
|
||||||
"""Клавиатура раздела управления быстрыми ответами."""
|
"""Клавиатура раздела управления быстрыми ответами."""
|
||||||
keyboard = VkKeyboard(one_time=False)
|
keyboard = VkKeyboard(one_time=False)
|
||||||
@@ -179,55 +161,6 @@ def get_cancel_admin_keyboard() -> str:
|
|||||||
return keyboard.get_keyboard()
|
return keyboard.get_keyboard()
|
||||||
|
|
||||||
|
|
||||||
def get_vacancy_choice_keyboard() -> str:
|
|
||||||
"""Выбор направления в разделе 'Карьера в ИКП'."""
|
|
||||||
keyboard = VkKeyboard(one_time=False)
|
|
||||||
keyboard.add_button("Игроком в состав 🎮", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("Сотрудником 💼", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("Назад в меню ↩️", color=VkKeyboardColor.SECONDARY)
|
|
||||||
return keyboard.get_keyboard()
|
|
||||||
|
|
||||||
|
|
||||||
def get_vacancy_cancel_keyboard() -> str:
|
|
||||||
"""Клавиатура отмены отклика на свободном текстовом шаге."""
|
|
||||||
keyboard = VkKeyboard(one_time=False)
|
|
||||||
keyboard.add_button("Отменить ❌", color=VkKeyboardColor.NEGATIVE)
|
|
||||||
return keyboard.get_keyboard()
|
|
||||||
|
|
||||||
|
|
||||||
def get_vacancy_back_keyboard() -> str:
|
|
||||||
"""Клавиатура с одной кнопкой «Назад в меню» — под карусель выбора в «Карьере»."""
|
|
||||||
keyboard = VkKeyboard(one_time=False)
|
|
||||||
keyboard.add_button("Назад в меню ↩️", color=VkKeyboardColor.SECONDARY)
|
|
||||||
return keyboard.get_keyboard()
|
|
||||||
|
|
||||||
|
|
||||||
def get_faq_keyboard() -> str:
|
|
||||||
"""Клавиатура раздела Частых Вопросов (FAQ)."""
|
|
||||||
keyboard = VkKeyboard(one_time=False)
|
|
||||||
keyboard.add_button("Цены 💰", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_button("Услуги ⚙️", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("Контакты 📞", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_button("О компании 🏢", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("Наши соцсети 🔗", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("Назад в меню ↩️", color=VkKeyboardColor.SECONDARY)
|
|
||||||
return keyboard.get_keyboard()
|
|
||||||
|
|
||||||
|
|
||||||
def get_consent_docs_keyboard() -> str:
|
|
||||||
"""Инлайн-кнопки со ссылками на оферту и политику обработки ПД."""
|
|
||||||
keyboard = VkKeyboard(inline=True)
|
|
||||||
keyboard.add_openlink_button("📄 Оферта", getattr(config, "OFFER_URL", "https://infocyber.pro/"))
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_openlink_button("🔐 Политика обработки ПД", getattr(config, "POLICY_URL", "https://infocyber.pro/"))
|
|
||||||
return keyboard.get_keyboard()
|
|
||||||
|
|
||||||
|
|
||||||
def get_socials_keyboard() -> str:
|
def get_socials_keyboard() -> str:
|
||||||
"""Клавиатура с кнопками-ссылками на площадки ИКП (открываются в один тап)."""
|
"""Клавиатура с кнопками-ссылками на площадки ИКП (открываются в один тап)."""
|
||||||
keyboard = VkKeyboard(one_time=False)
|
keyboard = VkKeyboard(one_time=False)
|
||||||
@@ -244,13 +177,6 @@ def get_socials_keyboard() -> str:
|
|||||||
return keyboard.get_keyboard()
|
return keyboard.get_keyboard()
|
||||||
|
|
||||||
|
|
||||||
def get_cancel_keyboard() -> str:
|
|
||||||
"""Клавиатура отмены заполнения формы."""
|
|
||||||
keyboard = VkKeyboard(one_time=False)
|
|
||||||
keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE)
|
|
||||||
return keyboard.get_keyboard()
|
|
||||||
|
|
||||||
|
|
||||||
def get_operator_keyboard() -> str:
|
def get_operator_keyboard() -> str:
|
||||||
"""Клавиатура при активном диалоге с оператором."""
|
"""Клавиатура при активном диалоге с оператором."""
|
||||||
keyboard = VkKeyboard(one_time=False)
|
keyboard = VkKeyboard(one_time=False)
|
||||||
@@ -258,125 +184,3 @@ def get_operator_keyboard() -> str:
|
|||||||
return keyboard.get_keyboard()
|
return keyboard.get_keyboard()
|
||||||
|
|
||||||
|
|
||||||
def get_binary_keyboard(show_skip: bool = False) -> str:
|
|
||||||
"""Клавиатура с кнопками Согласен / Не согласен."""
|
|
||||||
keyboard = VkKeyboard(one_time=False)
|
|
||||||
keyboard.add_button("Согласен(а) и подтверждаю ✅", color=VkKeyboardColor.PRIMARY)
|
|
||||||
keyboard.add_button("Не согласен(а) ❌", color=VkKeyboardColor.NEGATIVE)
|
|
||||||
if show_skip:
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("Пропустить ➡️", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE)
|
|
||||||
return keyboard.get_keyboard()
|
|
||||||
|
|
||||||
|
|
||||||
def get_returning_keyboard() -> str:
|
|
||||||
"""Нижняя клавиатура возвращающегося клиента: подтвердить или отменить."""
|
|
||||||
keyboard = VkKeyboard(one_time=False)
|
|
||||||
keyboard.add_button("Да, всё верно ✅", color=VkKeyboardColor.PRIMARY)
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE)
|
|
||||||
return keyboard.get_keyboard()
|
|
||||||
|
|
||||||
|
|
||||||
def get_returning_changes_keyboard() -> str:
|
|
||||||
"""Инлайн-кнопки под сообщением: сменить имя / телефон / заполнить заново."""
|
|
||||||
keyboard = VkKeyboard(inline=True)
|
|
||||||
keyboard.add_button("Сменить имя 👤", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_button("Сменить телефон 📞", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("Заполнить заново 🔄", color=VkKeyboardColor.SECONDARY)
|
|
||||||
return keyboard.get_keyboard()
|
|
||||||
|
|
||||||
|
|
||||||
def get_calldate_keyboard() -> str:
|
|
||||||
"""Клавиатура выбора ДНЯ для звонка (сегодня/завтра + ближайшие даты)."""
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
wd = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"]
|
|
||||||
today = datetime.now()
|
|
||||||
keyboard = VkKeyboard(one_time=False)
|
|
||||||
keyboard.add_button("Сегодня", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_button("Завтра", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_line()
|
|
||||||
for i in (2, 3, 4):
|
|
||||||
d = today + timedelta(days=i)
|
|
||||||
keyboard.add_button(f"{wd[d.weekday()]} {d.strftime('%d.%m')}", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE)
|
|
||||||
return keyboard.get_keyboard()
|
|
||||||
|
|
||||||
|
|
||||||
def get_calltime_keyboard() -> str:
|
|
||||||
"""Клавиатура выбора ВРЕМЕНИ (слоты в рабочие часы)."""
|
|
||||||
keyboard = VkKeyboard(one_time=False)
|
|
||||||
keyboard.add_button("10:00", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_button("12:00", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_button("14:00", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("16:00", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_button("18:00", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_button("20:00", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("Любое время", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE)
|
|
||||||
return keyboard.get_keyboard()
|
|
||||||
|
|
||||||
|
|
||||||
def get_marketing_keyboard() -> str:
|
|
||||||
"""Клавиатура для необязательного согласия на маркетинговую рассылку."""
|
|
||||||
keyboard = VkKeyboard(one_time=False)
|
|
||||||
keyboard.add_button("Согласен на рассылку ✅", color=VkKeyboardColor.PRIMARY)
|
|
||||||
keyboard.add_button("Без рассылки ➡️", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE)
|
|
||||||
return keyboard.get_keyboard()
|
|
||||||
|
|
||||||
|
|
||||||
def get_services_keyboard() -> str:
|
|
||||||
"""Клавиатура со списком доступных услуг."""
|
|
||||||
keyboard = VkKeyboard(one_time=False)
|
|
||||||
first = True
|
|
||||||
for key, svc in config.SERVICES.items():
|
|
||||||
if not first:
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button(svc["name"], color=VkKeyboardColor.SECONDARY)
|
|
||||||
first = False
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE)
|
|
||||||
return keyboard.get_keyboard()
|
|
||||||
|
|
||||||
|
|
||||||
def get_confirm_service_keyboard() -> str:
|
|
||||||
"""Клавиатура для подтверждения выбранной услуги."""
|
|
||||||
keyboard = VkKeyboard(one_time=False)
|
|
||||||
keyboard.add_button("Да, подтверждаю ✅", color=VkKeyboardColor.PRIMARY)
|
|
||||||
keyboard.add_button("Нет, выбрать другую 🔄", color=VkKeyboardColor.NEGATIVE)
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE)
|
|
||||||
return keyboard.get_keyboard()
|
|
||||||
|
|
||||||
|
|
||||||
def get_review_keyboard() -> str:
|
|
||||||
"""Клавиатура для финальной проверки данных пользователем."""
|
|
||||||
keyboard = VkKeyboard(one_time=False)
|
|
||||||
keyboard.add_button("Да, всё верно! ✅", color=VkKeyboardColor.PRIMARY)
|
|
||||||
keyboard.add_button("Изменить данные 🔄", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("Отменить заполнение ❌", color=VkKeyboardColor.NEGATIVE)
|
|
||||||
return keyboard.get_keyboard()
|
|
||||||
|
|
||||||
|
|
||||||
def get_edit_menu_keyboard() -> str:
|
|
||||||
"""Клавиатура меню изменения полей."""
|
|
||||||
keyboard = VkKeyboard(one_time=False)
|
|
||||||
keyboard.add_button("Изменить Имя 👤", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_button("Изменить Телефон 📞", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("Изменить Услугу 🎮", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_button("Изменить Время 🕒", color=VkKeyboardColor.SECONDARY)
|
|
||||||
keyboard.add_line()
|
|
||||||
keyboard.add_button("Назад к проверке ↩️", color=VkKeyboardColor.SECONDARY)
|
|
||||||
return keyboard.get_keyboard()
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -109,21 +109,6 @@ def _admin():
|
|||||||
return bot.config.SUPER_ADMIN_IDS[0]
|
return bot.config.SUPER_ADMIN_IDS[0]
|
||||||
|
|
||||||
|
|
||||||
def test_admin_services_crud(app):
|
|
||||||
a = _admin()
|
|
||||||
app.handle_message(a, "Управление услугами ⚙️")
|
|
||||||
app.handle_message(a, "➕ Добавить услугу")
|
|
||||||
app.handle_message(a, "Тест-у | 1 ₽ | описание")
|
|
||||||
nid = [k for k, v in bot.db.get_services().items() if v["name"] == "Тест-у"]
|
|
||||||
assert nid
|
|
||||||
n = nid[0]
|
|
||||||
app.handle_message(a, "✏️ Изменить услугу"); app.handle_message(a, f"{n}. Тест-у")
|
|
||||||
app.handle_message(a, "цена | 999 ₽")
|
|
||||||
assert bot.db.get_services()[n]["price"] == "999 ₽"
|
|
||||||
app.handle_message(a, "🗑 Удалить услугу"); app.handle_message(a, f"{n}. Тест-у")
|
|
||||||
assert n not in bot.db.get_services()
|
|
||||||
|
|
||||||
|
|
||||||
def test_admin_faq_crud(app):
|
def test_admin_faq_crud(app):
|
||||||
a = _admin()
|
a = _admin()
|
||||||
app.handle_message(a, "Быстрые ответы 💬")
|
app.handle_message(a, "Быстрые ответы 💬")
|
||||||
@@ -132,14 +117,6 @@ def test_admin_faq_crud(app):
|
|||||||
assert any(kw == "вопрос" for _, kw, _ in bot.db.get_custom_faq())
|
assert any(kw == "вопрос" for _, kw, _ in bot.db.get_custom_faq())
|
||||||
|
|
||||||
|
|
||||||
def test_admin_vacancy_crud(app):
|
|
||||||
a = _admin()
|
|
||||||
app.handle_message(a, "Вакансии 💼")
|
|
||||||
app.handle_message(a, "➕ Вакансия игрок")
|
|
||||||
app.handle_message(a, "Valorant 🎯")
|
|
||||||
assert any(l == "Valorant 🎯" for _, l in bot.db.get_vacancy_options("player"))
|
|
||||||
|
|
||||||
|
|
||||||
def test_admin_add_remove_admin(app):
|
def test_admin_add_remove_admin(app):
|
||||||
a = _admin()
|
a = _admin()
|
||||||
app.handle_message(a, "Администраторы 👑")
|
app.handle_message(a, "Администраторы 👑")
|
||||||
|
|||||||
Reference in New Issue
Block a user