Воронка БАЗА: приведение к ТЗ (метки, сохранённые данные, тексты, шаги)
Закрыты расхождения с ТЗ: - Метки (§12) переименованы под спецификацию: role_player, game_dota2, format_help, interest_trial, interest_summer_intensive, manager_requested, source_main_menu, source_keyword_summer; убраны служебные age_*/level_* метки. - Сохранённые данные (§13): кодовое слово, дата запуска бота (started_at), дата передачи менеджеру (manager_at), интересы (пробное/интенсив). Карточка менеджеру дополнена этими полями, заголовок «Новый запрос из бота». - «Пока не знаю, нужен совет» (§7): показ собранных данных + свободный текст → менеджеру. - «Задать вопрос» (§9): промпт «напишите одним сообщением» → BAZA_QUESTION → передача. - Летний интенсив: свой набор уровней (§10.3), экран младше-12 (Передать менеджеру/ Другие тренировки), двухшаговая оплата (Оплатить интенсив → ссылка) (§10.4), различение запуска по кодовому слову vs меню. - Индивидуальная ветка без «пробного» (§6). Fallback-текст дословно по §15. - «Выбрать абонемент» ведёт на страницу тарифов ссылкой (§8). Тексты веток — по ТЗ. Тесты воронки обновлены и дополнены (интенсив-оплата, младше-12 интенсив, свободный вопрос, «нужен совет»). pytest 30 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -528,8 +528,14 @@ class VKBotApplication:
|
|||||||
self._handle_baza_int_info(user_id, text_clean, text_lower)
|
self._handle_baza_int_info(user_id, text_clean, text_lower)
|
||||||
elif state == "BAZA_INT_AGE":
|
elif state == "BAZA_INT_AGE":
|
||||||
self._handle_baza_int_age(user_id, text_clean, text_lower)
|
self._handle_baza_int_age(user_id, text_clean, text_lower)
|
||||||
|
elif state == "BAZA_INT_YOUNG":
|
||||||
|
self._handle_baza_int_young(user_id, text_clean, text_lower)
|
||||||
elif state == "BAZA_INT_LEVEL":
|
elif state == "BAZA_INT_LEVEL":
|
||||||
self._handle_baza_int_level(user_id, text_clean, text_lower)
|
self._handle_baza_int_level(user_id, text_clean, text_lower)
|
||||||
|
elif state == "BAZA_INT_PAY_INTRO":
|
||||||
|
self._handle_baza_int_pay_intro(user_id, text_clean, text_lower)
|
||||||
|
elif state == "BAZA_QUESTION":
|
||||||
|
self._handle_baza_question(user_id, text_clean, text_lower)
|
||||||
elif state == "OPERATOR_CHAT":
|
elif state == "OPERATOR_CHAT":
|
||||||
self._handle_operator_chat(user_id, text_clean, text_lower)
|
self._handle_operator_chat(user_id, text_clean, text_lower)
|
||||||
else:
|
else:
|
||||||
@@ -554,10 +560,13 @@ class VKBotApplication:
|
|||||||
self._baza_start_funnel(user_id)
|
self._baza_start_funnel(user_id)
|
||||||
return
|
return
|
||||||
|
|
||||||
# 2. Летний интенсив CS2 (+ кодовые слова «лето», «интенсив» и т.п.)
|
# 2. Летний интенсив CS2 (кнопка меню)
|
||||||
if text_lower in ["летний интенсив cs2 🔥", "летний интенсив cs2", "летний интенсив", "интенсив"] \
|
if text_lower in ["летний интенсив cs2 🔥", "летний интенсив cs2", "летний интенсив"]:
|
||||||
or any(t in text_lower for t in config.SUMMER_TRIGGERS):
|
self._baza_start_intensive(user_id, via_keyword=False)
|
||||||
self._baza_start_intensive(user_id)
|
return
|
||||||
|
# 2.1 Кодовые слова рекламной кампании (лето/интенсив и т.п.) — сразу в ветку интенсива
|
||||||
|
if any(t in text_lower for t in config.SUMMER_TRIGGERS):
|
||||||
|
self._baza_start_intensive(user_id, via_keyword=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
# 3. Посмотреть цены
|
# 3. Посмотреть цены
|
||||||
@@ -567,17 +576,20 @@ class VKBotApplication:
|
|||||||
|
|
||||||
# 3.1 Кнопки под ценами
|
# 3.1 Кнопки под ценами
|
||||||
if text_lower in ["записаться на пробное", "пробное", "пробное занятие"]:
|
if text_lower in ["записаться на пробное", "пробное", "пробное занятие"]:
|
||||||
|
db.add_metka(user_id, "interest_trial")
|
||||||
self._baza_send_payment(user_id, "trial", "Пробное занятие")
|
self._baza_send_payment(user_id, "trial", "Пробное занятие")
|
||||||
return
|
return
|
||||||
if text_lower in ["выбрать абонемент", "абонемент", "абонементы"]:
|
if text_lower in ["выбрать абонемент", "абонемент", "абонементы"]:
|
||||||
# Подбираем формат через воронку, затем предложим точный тариф
|
# §8: кнопка ведёт на страницу с тарифами
|
||||||
self._baza_start_funnel(user_id)
|
self._baza_send_openlink(user_id, "tariffs",
|
||||||
|
"Открываю страницу с тарифами — там можно выбрать и оплатить абонемент 👇",
|
||||||
|
"Открыть тарифы 💳")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 4. Задать вопрос / оператор / менеджер — передаём менеджеру
|
# 4. Задать вопрос — просим написать вопрос одним сообщением (§9)
|
||||||
if text_lower in ["задать вопрос 💬", "задать вопрос", "вопрос", "задать вопрос менеджеру 💬",
|
if text_lower in ["задать вопрос 💬", "задать вопрос", "вопрос", "задать вопрос менеджеру 💬",
|
||||||
"оператор", "менеджер", "человек", "позвать оператора", "связь с менеджером"]:
|
"оператор", "менеджер", "человек", "позвать оператора", "связь с менеджером"]:
|
||||||
self._baza_to_manager(user_id, reason="Прямой вопрос из меню")
|
self._baza_ask_question(user_id)
|
||||||
return
|
return
|
||||||
|
|
||||||
# 5. Площадки — кнопки-ссылки
|
# 5. Площадки — кнопки-ссылки
|
||||||
@@ -622,11 +634,10 @@ class VKBotApplication:
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# 10. Fallback — не поняли запрос
|
# 10. Fallback — не поняли запрос (§15, дословно)
|
||||||
self.send_message(
|
self.send_message(
|
||||||
user_id,
|
user_id,
|
||||||
"🤔 Не совсем понял запрос. Давайте я помогу подобрать тренировку или отвечу на вопрос — "
|
"Пока не получилось определить запрос. Выберите подходящий вариант или напишите вопрос менеджеру.",
|
||||||
"выберите на кнопках ниже 👇",
|
|
||||||
keyboard=get_main_keyboard(is_admin=self._is_admin(user_id))
|
keyboard=get_main_keyboard(is_admin=self._is_admin(user_id))
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -640,8 +651,23 @@ class VKBotApplication:
|
|||||||
sep = "&" if "?" in url else "?"
|
sep = "&" if "?" in url else "?"
|
||||||
return f"{url}{sep}{utm}"
|
return f"{url}{sep}{utm}"
|
||||||
|
|
||||||
def _baza_send_greeting(self, user_id: int):
|
def _baza_send_openlink(self, user_id: int, link_key: str, message: str, button: str,
|
||||||
|
campaign: str = "main"):
|
||||||
|
"""Отправляет сообщение с инлайн-кнопкой-ссылкой (без перевода на оплату/лид)."""
|
||||||
|
link = self._utm(config.LINKS.get(link_key, config.LINKS["tariffs"]), campaign)
|
||||||
|
kb = VkKeyboard(inline=True)
|
||||||
|
kb.add_openlink_button(button, link)
|
||||||
|
self.send_message(user_id, message, keyboard=kb.get_keyboard())
|
||||||
|
|
||||||
|
def _baza_started(self, user_id: int):
|
||||||
|
"""Фиксирует факт запуска бота: метка bot_started + дата первого запуска (один раз)."""
|
||||||
db.add_metka(user_id, "bot_started")
|
db.add_metka(user_id, "bot_started")
|
||||||
|
if not db.get_profile(user_id).get("started_at"):
|
||||||
|
from datetime import datetime
|
||||||
|
db.set_profile_field(user_id, "started_at", datetime.now().strftime("%Y-%m-%d %H:%M"))
|
||||||
|
|
||||||
|
def _baza_send_greeting(self, user_id: int):
|
||||||
|
self._baza_started(user_id)
|
||||||
self.send_message(
|
self.send_message(
|
||||||
user_id,
|
user_id,
|
||||||
config.GREETING_BAZA + "\n\nВыберите действие на кнопках ниже 👇",
|
config.GREETING_BAZA + "\n\nВыберите действие на кнопках ниже 👇",
|
||||||
@@ -651,7 +677,8 @@ class VKBotApplication:
|
|||||||
|
|
||||||
def _baza_nav(self, user_id: int, text_lower: str) -> bool:
|
def _baza_nav(self, user_id: int, text_lower: str) -> bool:
|
||||||
"""Общие кнопки внутри воронки: меню / вопрос менеджеру. True — если обработали."""
|
"""Общие кнопки внутри воронки: меню / вопрос менеджеру. True — если обработали."""
|
||||||
if text_lower in ["в главное меню ↩️", "главное меню", "меню", "в меню", "выход"]:
|
if text_lower in ["в главное меню ↩️", "главное меню", "меню", "в меню", "выход",
|
||||||
|
"вернуться в меню ↩️", "вернуться в меню", "посмотреть другие тренировки"]:
|
||||||
db.update_user_state(user_id, "FREE_CHAT")
|
db.update_user_state(user_id, "FREE_CHAT")
|
||||||
self.send_message(
|
self.send_message(
|
||||||
user_id, "🏠 Главное меню Кибершколы БАЗА. Чем помочь?",
|
user_id, "🏠 Главное меню Кибершколы БАЗА. Чем помочь?",
|
||||||
@@ -659,17 +686,17 @@ class VKBotApplication:
|
|||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
if text_lower in ["задать вопрос менеджеру 💬", "задать вопрос менеджеру", "вопрос менеджеру",
|
if text_lower in ["задать вопрос менеджеру 💬", "задать вопрос менеджеру", "вопрос менеджеру",
|
||||||
|
"задать вопрос 💬", "задать вопрос", "сначала задать вопрос 💬", "сначала задать вопрос",
|
||||||
"менеджер", "оператор"]:
|
"менеджер", "оператор"]:
|
||||||
self._baza_to_manager(user_id, reason="Вопрос из подбора тренировки")
|
self._baza_ask_question(user_id)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _baza_start_funnel(self, user_id: int):
|
def _baza_start_funnel(self, user_id: int):
|
||||||
"""Старт воронки «Подобрать тренировку»."""
|
"""Старт воронки «Подобрать тренировку»."""
|
||||||
db.clear_profile(user_id)
|
self._baza_started(user_id)
|
||||||
db.set_profile_field(user_id, "source", "Подбор тренировки")
|
db.set_profile_field(user_id, "source", "Основное меню")
|
||||||
db.add_metka(user_id, "bot_started")
|
db.add_metka(user_id, "source_main_menu")
|
||||||
db.add_metka(user_id, "funnel_started")
|
|
||||||
db.update_user_state(user_id, "BAZA_FORWHO")
|
db.update_user_state(user_id, "BAZA_FORWHO")
|
||||||
self.send_message(
|
self.send_message(
|
||||||
user_id,
|
user_id,
|
||||||
@@ -682,10 +709,10 @@ class VKBotApplication:
|
|||||||
if self._baza_nav(user_id, text_lower):
|
if self._baza_nav(user_id, text_lower):
|
||||||
return
|
return
|
||||||
if text_lower in ["для себя", "себе", "для меня"]:
|
if text_lower in ["для себя", "себе", "для меня"]:
|
||||||
db.set_profile_field(user_id, "role", "Для себя (занимается сам)")
|
db.set_profile_field(user_id, "role", "Игрок (пишет сам)")
|
||||||
db.add_metka(user_id, "role_self")
|
db.add_metka(user_id, "role_player")
|
||||||
elif text_lower in ["для подростка", "для ребёнка", "для ребенка", "для сына", "для дочери", "для подростка "]:
|
elif text_lower in ["для подростка", "для ребёнка", "для ребенка", "для сына", "для дочери", "для подростка "]:
|
||||||
db.set_profile_field(user_id, "role", "Для подростка (пишет родитель)")
|
db.set_profile_field(user_id, "role", "Родитель (пишет за подростка)")
|
||||||
db.add_metka(user_id, "role_parent")
|
db.add_metka(user_id, "role_parent")
|
||||||
else:
|
else:
|
||||||
self.send_message(user_id, "Выберите вариант на кнопках ниже 👇", keyboard=get_baza_forwho_keyboard())
|
self.send_message(user_id, "Выберите вариант на кнопках ниже 👇", keyboard=get_baza_forwho_keyboard())
|
||||||
@@ -712,18 +739,17 @@ class VKBotApplication:
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
if "12" in text_lower or "13" in text_lower:
|
if "12" in text_lower or "13" in text_lower:
|
||||||
age, metka = "12–13 лет", "age_12_13"
|
age = "12–13 лет"
|
||||||
elif "14" in text_lower or "15" in text_lower:
|
elif "14" in text_lower or "15" in text_lower:
|
||||||
age, metka = "14–15 лет", "age_14_15"
|
age = "14–15 лет"
|
||||||
elif "16" in text_lower:
|
elif "16" in text_lower:
|
||||||
age, metka = "16 лет и старше", "age_16plus"
|
age = "16 лет и старше"
|
||||||
else:
|
else:
|
||||||
self.send_message(user_id, "Выберите возраст на кнопках ниже 👇", keyboard=get_baza_age_keyboard())
|
self.send_message(user_id, "Выберите возраст на кнопках ниже 👇", keyboard=get_baza_age_keyboard())
|
||||||
return
|
return
|
||||||
db.set_profile_field(user_id, "age", age)
|
db.set_profile_field(user_id, "age", age)
|
||||||
db.add_metka(user_id, metka)
|
|
||||||
db.update_user_state(user_id, "BAZA_GAME")
|
db.update_user_state(user_id, "BAZA_GAME")
|
||||||
self.send_message(user_id, "Какая игра интересует?", keyboard=get_baza_game_keyboard())
|
self.send_message(user_id, "По какой игре хотите заниматься?", keyboard=get_baza_game_keyboard())
|
||||||
|
|
||||||
def _handle_baza_young(self, user_id: int, text: str, text_lower: str):
|
def _handle_baza_young(self, user_id: int, text: str, text_lower: str):
|
||||||
if text_lower.startswith("обсудить"):
|
if text_lower.startswith("обсудить"):
|
||||||
@@ -747,14 +773,17 @@ class VKBotApplication:
|
|||||||
if "cs" in text_lower or "кс" in text_lower or "контр" in text_lower:
|
if "cs" in text_lower or "кс" in text_lower or "контр" in text_lower:
|
||||||
game, metka = "CS2", "game_cs2"
|
game, metka = "CS2", "game_cs2"
|
||||||
elif "dota" in text_lower or "дота" in text_lower:
|
elif "dota" in text_lower or "дота" in text_lower:
|
||||||
game, metka = "Dota 2", "game_dota"
|
game, metka = "Dota 2", "game_dota2"
|
||||||
elif "tank" in text_lower or "танк" in text_lower or "wot" in text_lower:
|
elif "tank" in text_lower or "танк" in text_lower or "wot" in text_lower:
|
||||||
game, metka = "World of Tanks", "game_wot"
|
game, metka = "World of Tanks", "game_wot"
|
||||||
elif "друг" in text_lower:
|
elif "друг" in text_lower:
|
||||||
db.update_user_state(user_id, "BAZA_GAME_OTHER")
|
db.update_user_state(user_id, "BAZA_GAME_OTHER")
|
||||||
self.send_message(
|
self.send_message(
|
||||||
user_id, "Напишите, какая игра интересует — передам менеджеру, он подскажет по возможностям.",
|
user_id,
|
||||||
keyboard=get_baza_game_keyboard()
|
"Сейчас основные направления БАЗЫ — CS2, Dota 2 и World of Tanks.\n"
|
||||||
|
"Напишите название игры одним сообщением. Мы передадим запрос менеджеру и сообщим, "
|
||||||
|
"можем ли предложить подходящего тренера.",
|
||||||
|
keyboard=get_baza_menu_only_keyboard()
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
@@ -763,47 +792,48 @@ class VKBotApplication:
|
|||||||
db.set_profile_field(user_id, "game", game)
|
db.set_profile_field(user_id, "game", game)
|
||||||
db.add_metka(user_id, metka)
|
db.add_metka(user_id, metka)
|
||||||
db.update_user_state(user_id, "BAZA_LEVEL")
|
db.update_user_state(user_id, "BAZA_LEVEL")
|
||||||
self.send_message(user_id, "Какой сейчас уровень игры у ученика?", keyboard=get_baza_level_keyboard())
|
self.send_message(user_id, "Как бы вы описали текущий уровень игрока?", keyboard=get_baza_level_keyboard())
|
||||||
|
|
||||||
def _handle_baza_game_other(self, user_id: int, text: str, text_lower: str):
|
def _handle_baza_game_other(self, user_id: int, text: str, text_lower: str):
|
||||||
if self._baza_nav(user_id, text_lower):
|
if self._baza_nav(user_id, text_lower):
|
||||||
return
|
return
|
||||||
if text_lower.startswith("назад"):
|
if text_lower.startswith("назад"):
|
||||||
db.update_user_state(user_id, "BAZA_GAME")
|
db.update_user_state(user_id, "BAZA_GAME")
|
||||||
self.send_message(user_id, "Какая игра интересует?", keyboard=get_baza_game_keyboard())
|
self.send_message(user_id, "По какой игре хотите заниматься?", keyboard=get_baza_game_keyboard())
|
||||||
return
|
return
|
||||||
db.set_profile_field(user_id, "game", f"Другая игра: {text.strip()}")
|
db.set_profile_field(user_id, "game", f"Другая игра: {text.strip()}")
|
||||||
db.add_metka(user_id, "game_other")
|
db.add_metka(user_id, "game_other")
|
||||||
self._baza_to_manager(user_id, reason="Интересует другая игра")
|
self.send_message(user_id, "Спасибо! Передаю запрос менеджеру.")
|
||||||
|
self._baza_to_manager(user_id, reason="Интересует другая игра",
|
||||||
|
client_text="Спасибо! Передаю запрос менеджеру. Он ответит здесь в ближайшее время.")
|
||||||
|
|
||||||
def _handle_baza_level(self, user_id: int, text: str, text_lower: str):
|
def _handle_baza_level(self, user_id: int, text: str, text_lower: str):
|
||||||
if self._baza_nav(user_id, text_lower):
|
if self._baza_nav(user_id, text_lower):
|
||||||
return
|
return
|
||||||
if text_lower.startswith("назад"):
|
if text_lower.startswith("назад"):
|
||||||
db.update_user_state(user_id, "BAZA_GAME")
|
db.update_user_state(user_id, "BAZA_GAME")
|
||||||
self.send_message(user_id, "Какая игра интересует?", keyboard=get_baza_game_keyboard())
|
self.send_message(user_id, "По какой игре хотите заниматься?", keyboard=get_baza_game_keyboard())
|
||||||
return
|
return
|
||||||
levels = {
|
levels = {
|
||||||
"только начинает": ("Только начинает", "level_novice"),
|
"только начинает": "Только начинает",
|
||||||
"играет для себя": ("Играет для себя", "level_casual"),
|
"играет для себя": "Играет для себя",
|
||||||
"есть рейтинг или ранг": ("Есть рейтинг/ранг", "level_ranked"),
|
"есть рейтинг или ранг": "Есть рейтинг или ранг",
|
||||||
"играет в команде или турнирах": ("Команда/турниры", "level_pro"),
|
"играет в команде или турнирах": "Играет в команде или турнирах",
|
||||||
}
|
}
|
||||||
match = levels.get(text_lower)
|
match = levels.get(text_lower)
|
||||||
if not match:
|
if not match:
|
||||||
self.send_message(user_id, "Выберите уровень на кнопках ниже 👇", keyboard=get_baza_level_keyboard())
|
self.send_message(user_id, "Выберите уровень на кнопках ниже 👇", keyboard=get_baza_level_keyboard())
|
||||||
return
|
return
|
||||||
db.set_profile_field(user_id, "level", match[0])
|
db.set_profile_field(user_id, "level", match)
|
||||||
db.add_metka(user_id, match[1])
|
|
||||||
db.update_user_state(user_id, "BAZA_FORMAT")
|
db.update_user_state(user_id, "BAZA_FORMAT")
|
||||||
self.send_message(user_id, "Какой формат тренировок предпочтительнее?", keyboard=get_baza_format_keyboard())
|
self.send_message(user_id, "Какой формат рассматриваете?", keyboard=get_baza_format_keyboard())
|
||||||
|
|
||||||
def _handle_baza_format(self, user_id: int, text: str, text_lower: str):
|
def _handle_baza_format(self, user_id: int, text: str, text_lower: str):
|
||||||
if self._baza_nav(user_id, text_lower):
|
if self._baza_nav(user_id, text_lower):
|
||||||
return
|
return
|
||||||
if text_lower.startswith("назад"):
|
if text_lower.startswith("назад"):
|
||||||
db.update_user_state(user_id, "BAZA_LEVEL")
|
db.update_user_state(user_id, "BAZA_LEVEL")
|
||||||
self.send_message(user_id, "Какой сейчас уровень игры у ученика?", keyboard=get_baza_level_keyboard())
|
self.send_message(user_id, "Как бы вы описали текущий уровень игрока?", keyboard=get_baza_level_keyboard())
|
||||||
return
|
return
|
||||||
if "групп" in text_lower:
|
if "групп" in text_lower:
|
||||||
db.set_profile_field(user_id, "format", "Групповые тренировки")
|
db.set_profile_field(user_id, "format", "Групповые тренировки")
|
||||||
@@ -811,9 +841,11 @@ class VKBotApplication:
|
|||||||
db.update_user_state(user_id, "BAZA_GROUP")
|
db.update_user_state(user_id, "BAZA_GROUP")
|
||||||
self.send_message(
|
self.send_message(
|
||||||
user_id,
|
user_id,
|
||||||
"Групповые тренировки — занятия в мини-группе с тренером.\n\n"
|
"Групповые тренировки подойдут тем, кто хочет развиваться вместе с командой: разбирать "
|
||||||
"🧑🤝🧑 Групповые:\n• 4 занятия — 2 790 ₽\n• 8 занятий — 4 990 ₽\n\n"
|
"ошибки, тренировать игровые навыки и учиться взаимодействовать с другими игроками.\n\n"
|
||||||
"Можно начать с пробного занятия. Что выберете?",
|
"Стоимость групповых тренировок:\n"
|
||||||
|
"• 4 тренировки — 2 790 ₽\n• 8 тренировок — 4 990 ₽\n\n"
|
||||||
|
"Можно сначала записаться на пробное занятие или сразу выбрать абонемент.",
|
||||||
keyboard=get_baza_group_keyboard()
|
keyboard=get_baza_group_keyboard()
|
||||||
)
|
)
|
||||||
elif "индивид" in text_lower:
|
elif "индивид" in text_lower:
|
||||||
@@ -822,20 +854,27 @@ class VKBotApplication:
|
|||||||
db.update_user_state(user_id, "BAZA_INDIVIDUAL")
|
db.update_user_state(user_id, "BAZA_INDIVIDUAL")
|
||||||
self.send_message(
|
self.send_message(
|
||||||
user_id,
|
user_id,
|
||||||
"Индивидуальные тренировки — персональная работа с тренером один на один.\n\n"
|
"На индивидуальных тренировках тренер работает с конкретными задачами игрока: разбирает "
|
||||||
"👤 Индивидуальные:\n• 1 занятие — 870 ₽\n• 4 занятия — 3 290 ₽\n• 8 занятий — 5 990 ₽\n\n"
|
"игру, помогает исправлять ошибки и составляет персональный план развития.\n\n"
|
||||||
"Можно начать с пробного занятия. Что выберете?",
|
"Стоимость индивидуальных тренировок:\n"
|
||||||
|
"• 1 тренировка — 870 ₽\n• 4 тренировки — 3 290 ₽\n• 8 тренировок — 5 990 ₽",
|
||||||
keyboard=get_baza_individual_keyboard()
|
keyboard=get_baza_individual_keyboard()
|
||||||
)
|
)
|
||||||
elif "не знаю" in text_lower or "совет" in text_lower:
|
elif "не знаю" in text_lower or "совет" in text_lower:
|
||||||
db.set_profile_field(user_id, "format", "Нужен совет по формату")
|
db.set_profile_field(user_id, "format", "Пока не определился, нужен совет")
|
||||||
db.add_metka(user_id, "need_advice")
|
db.add_metka(user_id, "format_help")
|
||||||
db.update_user_state(user_id, "BAZA_HELP")
|
db.update_user_state(user_id, "BAZA_HELP")
|
||||||
|
p = db.get_profile(user_id)
|
||||||
|
known = (f"Игра: {p.get('game', '—')}\n"
|
||||||
|
f"Возраст: {p.get('age', '—')}\n"
|
||||||
|
f"Уровень: {p.get('level', '—')}")
|
||||||
self.send_message(
|
self.send_message(
|
||||||
user_id,
|
user_id,
|
||||||
"Без проблем — подскажем 🙌 Можно записаться на пробное занятие и определиться на практике "
|
"Ничего страшного — поможем определиться.\n\n"
|
||||||
"или задать вопрос менеджеру, он поможет выбрать формат.",
|
"Мы уже знаем:\n" + known + "\n\n"
|
||||||
keyboard=get_baza_help_keyboard()
|
"Напишите одним сообщением, что для вас особенно важно: расписание, стоимость, "
|
||||||
|
"выбор тренера, результат или формат занятий.",
|
||||||
|
keyboard=get_baza_menu_only_keyboard()
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.send_message(user_id, "Выберите формат на кнопках ниже 👇", keyboard=get_baza_format_keyboard())
|
self.send_message(user_id, "Выберите формат на кнопках ниже 👇", keyboard=get_baza_format_keyboard())
|
||||||
@@ -848,6 +887,7 @@ class VKBotApplication:
|
|||||||
self.send_message(user_id, "Какой формат тренировок предпочтительнее?", keyboard=get_baza_format_keyboard())
|
self.send_message(user_id, "Какой формат тренировок предпочтительнее?", keyboard=get_baza_format_keyboard())
|
||||||
return
|
return
|
||||||
if "пробное" in text_lower:
|
if "пробное" in text_lower:
|
||||||
|
db.add_metka(user_id, "interest_trial")
|
||||||
self._baza_send_payment(user_id, "trial", "Пробное занятие (группа)")
|
self._baza_send_payment(user_id, "trial", "Пробное занятие (группа)")
|
||||||
elif "4" in text_lower:
|
elif "4" in text_lower:
|
||||||
self._baza_send_payment(user_id, "group_4", "Групповой абонемент, 4 занятия")
|
self._baza_send_payment(user_id, "group_4", "Групповой абонемент, 4 занятия")
|
||||||
@@ -863,9 +903,7 @@ class VKBotApplication:
|
|||||||
db.update_user_state(user_id, "BAZA_FORMAT")
|
db.update_user_state(user_id, "BAZA_FORMAT")
|
||||||
self.send_message(user_id, "Какой формат тренировок предпочтительнее?", keyboard=get_baza_format_keyboard())
|
self.send_message(user_id, "Какой формат тренировок предпочтительнее?", keyboard=get_baza_format_keyboard())
|
||||||
return
|
return
|
||||||
if "пробное" in text_lower:
|
if "1" in text_lower:
|
||||||
self._baza_send_payment(user_id, "trial", "Пробное занятие (индивид.)")
|
|
||||||
elif "1" in text_lower:
|
|
||||||
self._baza_send_payment(user_id, "individual_1", "Индивидуальное, 1 занятие")
|
self._baza_send_payment(user_id, "individual_1", "Индивидуальное, 1 занятие")
|
||||||
elif "4" in text_lower:
|
elif "4" in text_lower:
|
||||||
self._baza_send_payment(user_id, "individual_4", "Индивидуальные, 4 занятия")
|
self._baza_send_payment(user_id, "individual_4", "Индивидуальные, 4 занятия")
|
||||||
@@ -875,15 +913,17 @@ class VKBotApplication:
|
|||||||
self.send_message(user_id, "Выберите вариант на кнопках ниже 👇", keyboard=get_baza_individual_keyboard())
|
self.send_message(user_id, "Выберите вариант на кнопках ниже 👇", keyboard=get_baza_individual_keyboard())
|
||||||
|
|
||||||
def _handle_baza_help(self, user_id: int, text: str, text_lower: str):
|
def _handle_baza_help(self, user_id: int, text: str, text_lower: str):
|
||||||
|
"""Ветка «Пока не знаю» (§7): пользователь пишет свободным текстом → менеджеру."""
|
||||||
if self._baza_nav(user_id, text_lower):
|
if self._baza_nav(user_id, text_lower):
|
||||||
return
|
return
|
||||||
if "пробное" in text_lower:
|
db.set_profile_field(user_id, "message", text.strip())
|
||||||
self._baza_send_payment(user_id, "trial", "Пробное занятие")
|
self.send_message(
|
||||||
return
|
user_id,
|
||||||
if "подобрать" in text_lower:
|
"Спасибо! Передаю информацию менеджеру. Он посмотрит ваш запрос и поможет подобрать "
|
||||||
self._baza_start_funnel(user_id)
|
"подходящий вариант."
|
||||||
return
|
)
|
||||||
self.send_message(user_id, "Выберите вариант на кнопках ниже 👇", keyboard=get_baza_help_keyboard())
|
self._baza_to_manager(user_id, reason="Нужен совет по формату",
|
||||||
|
client_text="Менеджер скоро подключится к диалогу. Можете дополнить запрос здесь.")
|
||||||
|
|
||||||
def _baza_send_payment(self, user_id: int, link_key: str, tariff_label: str, campaign: str = "main"):
|
def _baza_send_payment(self, user_id: int, link_key: str, tariff_label: str, campaign: str = "main"):
|
||||||
"""Показывает ссылку на оплату/запись + кнопки «проблема с оплатой» / «вопрос менеджеру»."""
|
"""Показывает ссылку на оплату/запись + кнопки «проблема с оплатой» / «вопрос менеджеру»."""
|
||||||
@@ -897,33 +937,41 @@ class VKBotApplication:
|
|||||||
kb.add_line()
|
kb.add_line()
|
||||||
kb.add_button("Проблема с оплатой", color=VkKeyboardColor.NEGATIVE)
|
kb.add_button("Проблема с оплатой", color=VkKeyboardColor.NEGATIVE)
|
||||||
kb.add_line()
|
kb.add_line()
|
||||||
kb.add_button("Задать вопрос менеджеру 💬", color=VkKeyboardColor.SECONDARY)
|
kb.add_button("Задать вопрос", color=VkKeyboardColor.SECONDARY)
|
||||||
self.send_message(
|
self.send_message(
|
||||||
user_id,
|
user_id,
|
||||||
f"Вы выбрали: {tariff_label}.\n\n"
|
"Вы переходите на страницу оплаты. После оформления менеджер проверит заявку и свяжется "
|
||||||
"Нажмите «Перейти к оплате 💳» — откроется страница оплаты. "
|
"с вами в этом диалоге.\n\n"
|
||||||
"После оплаты менеджер свяжется с вами и подскажет время занятия.\n\n"
|
"Если ссылка не откроется или возникнет вопрос — нажмите кнопку ниже.",
|
||||||
"Если что-то не получается — нажмите «Проблема с оплатой», и мы поможем.",
|
|
||||||
keyboard=kb.get_keyboard()
|
keyboard=kb.get_keyboard()
|
||||||
)
|
)
|
||||||
|
|
||||||
def _handle_baza_pay(self, user_id: int, text: str, text_lower: str):
|
def _handle_baza_pay(self, user_id: int, text: str, text_lower: str):
|
||||||
if text_lower in ["проблема с оплатой", "проблема", "не получается", "ошибка оплаты"]:
|
if text_lower in ["проблема с оплатой", "проблема", "не получается", "ошибка оплаты"]:
|
||||||
db.add_metka(user_id, "payment_problem")
|
db.add_metka(user_id, "payment_problem")
|
||||||
self._baza_to_manager(user_id, reason="Проблема с оплатой")
|
reason = ("Проблема с оплатой интенсива"
|
||||||
|
if "интенсив" in (db.get_profile(user_id).get("tariff", "").lower()) else "Проблема с оплатой")
|
||||||
|
self._baza_to_manager(user_id, reason=reason,
|
||||||
|
client_text="Передал вас менеджеру — поможем с оплатой. Опишите, что произошло.")
|
||||||
return
|
return
|
||||||
if self._baza_nav(user_id, text_lower):
|
if self._baza_nav(user_id, text_lower):
|
||||||
return
|
return
|
||||||
# Любой другой текст — считаем вопросом и передаём менеджеру
|
# Любой другой текст — считаем вопросом, сохраняем и передаём менеджеру
|
||||||
db.set_profile_field(user_id, "message", text.strip())
|
db.set_profile_field(user_id, "message", text.strip())
|
||||||
self._baza_to_manager(user_id, reason="Вопрос на шаге оплаты")
|
self.send_message(user_id, "Спасибо! Вопрос передан менеджеру.")
|
||||||
|
self._baza_to_manager(user_id, reason="Вопрос на шаге оплаты",
|
||||||
|
client_text="Менеджер скоро ответит здесь.")
|
||||||
|
|
||||||
# ---------- Летний интенсив CS2 ----------
|
# ---------- Летний интенсив CS2 ----------
|
||||||
|
|
||||||
def _baza_start_intensive(self, user_id: int):
|
def _baza_start_intensive(self, user_id: int, via_keyword: bool = False):
|
||||||
db.set_profile_field(user_id, "source", "Летний интенсив CS2")
|
self._baza_started(user_id)
|
||||||
db.add_metka(user_id, "bot_started")
|
db.set_profile_field(user_id, "source", "Кодовое слово «лето»" if via_keyword else "Основное меню")
|
||||||
db.add_metka(user_id, "intensive_interest")
|
db.add_metka(user_id, "source_keyword_summer" if via_keyword else "source_main_menu")
|
||||||
|
if via_keyword:
|
||||||
|
db.set_profile_field(user_id, "keyword", "лето")
|
||||||
|
db.set_profile_field(user_id, "interest_summer", True)
|
||||||
|
db.add_metka(user_id, "interest_summer_intensive")
|
||||||
db.update_user_state(user_id, "BAZA_INT_INFO")
|
db.update_user_state(user_id, "BAZA_INT_INFO")
|
||||||
self.send_message(
|
self.send_message(
|
||||||
user_id,
|
user_id,
|
||||||
@@ -936,7 +984,7 @@ class VKBotApplication:
|
|||||||
return
|
return
|
||||||
if "участвовать" in text_lower or "записаться" in text_lower or "хочу" in text_lower or "да" == text_lower:
|
if "участвовать" in text_lower or "записаться" in text_lower or "хочу" in text_lower or "да" == text_lower:
|
||||||
db.update_user_state(user_id, "BAZA_INT_AGE")
|
db.update_user_state(user_id, "BAZA_INT_AGE")
|
||||||
self.send_message(user_id, "Отлично! Сколько лет участнику?", keyboard=get_baza_age_keyboard())
|
self.send_message(user_id, "Отлично! Сколько лет игроку?", keyboard=get_baza_age_keyboard())
|
||||||
return
|
return
|
||||||
self.send_message(user_id, "Выберите действие на кнопках ниже 👇", keyboard=get_baza_intensive_keyboard())
|
self.send_message(user_id, "Выберите действие на кнопках ниже 👇", keyboard=get_baza_intensive_keyboard())
|
||||||
|
|
||||||
@@ -949,8 +997,13 @@ class VKBotApplication:
|
|||||||
return
|
return
|
||||||
if "младше 12" in text_lower or "меньше 12" in text_lower:
|
if "младше 12" in text_lower or "меньше 12" in text_lower:
|
||||||
db.set_profile_field(user_id, "age", "Младше 12 лет")
|
db.set_profile_field(user_id, "age", "Младше 12 лет")
|
||||||
db.add_metka(user_id, "age_under12")
|
db.update_user_state(user_id, "BAZA_INT_YOUNG")
|
||||||
self._baza_to_manager(user_id, reason="Интенсив: участник младше 12 лет")
|
self.send_message(
|
||||||
|
user_id,
|
||||||
|
"Основная программа интенсива рассчитана на участников от 12 лет.\n"
|
||||||
|
"Мы можем передать запрос менеджеру, чтобы он уточнил у тренера, возможен ли подходящий формат.",
|
||||||
|
keyboard=get_baza_int_young_keyboard()
|
||||||
|
)
|
||||||
return
|
return
|
||||||
if "12" in text_lower or "13" in text_lower:
|
if "12" in text_lower or "13" in text_lower:
|
||||||
age = "12–13 лет"
|
age = "12–13 лет"
|
||||||
@@ -963,28 +1016,86 @@ class VKBotApplication:
|
|||||||
return
|
return
|
||||||
db.set_profile_field(user_id, "age", age)
|
db.set_profile_field(user_id, "age", age)
|
||||||
db.update_user_state(user_id, "BAZA_INT_LEVEL")
|
db.update_user_state(user_id, "BAZA_INT_LEVEL")
|
||||||
self.send_message(user_id, "Какой сейчас уровень игры в CS2?", keyboard=get_baza_level_keyboard())
|
self.send_message(user_id, "Какой сейчас уровень в CS2?", keyboard=get_baza_int_level_keyboard())
|
||||||
|
|
||||||
|
def _handle_baza_int_young(self, user_id: int, text: str, text_lower: str):
|
||||||
|
if text_lower.startswith("передать"):
|
||||||
|
self._baza_to_manager(user_id, reason="Интенсив: участник младше 12 лет",
|
||||||
|
client_text="Передал запрос менеджеру — он уточнит формат и ответит здесь.")
|
||||||
|
return
|
||||||
|
if self._baza_nav(user_id, text_lower): # «Посмотреть другие тренировки» → меню
|
||||||
|
return
|
||||||
|
self.send_message(user_id, "Выберите вариант на кнопках ниже 👇", keyboard=get_baza_int_young_keyboard())
|
||||||
|
|
||||||
def _handle_baza_int_level(self, user_id: int, text: str, text_lower: str):
|
def _handle_baza_int_level(self, user_id: int, text: str, text_lower: str):
|
||||||
if self._baza_nav(user_id, text_lower):
|
if self._baza_nav(user_id, text_lower):
|
||||||
return
|
return
|
||||||
if text_lower.startswith("назад"):
|
if text_lower.startswith("назад"):
|
||||||
db.update_user_state(user_id, "BAZA_INT_AGE")
|
db.update_user_state(user_id, "BAZA_INT_AGE")
|
||||||
self.send_message(user_id, "Сколько лет участнику?", keyboard=get_baza_age_keyboard())
|
self.send_message(user_id, "Сколько лет игроку?", keyboard=get_baza_age_keyboard())
|
||||||
return
|
return
|
||||||
levels = {
|
levels = {
|
||||||
"только начинает": "Только начинает",
|
"только начинаю": "Только начинаю",
|
||||||
"играет для себя": "Играет для себя",
|
"уже знаю основы": "Уже знаю основы",
|
||||||
"есть рейтинг или ранг": "Есть рейтинг/ранг",
|
"играю регулярно": "Играю регулярно",
|
||||||
"играет в команде или турнирах": "Команда/турниры",
|
"есть рейтинг или командный опыт": "Есть рейтинг или командный опыт",
|
||||||
}
|
}
|
||||||
match = levels.get(text_lower)
|
match = levels.get(text_lower)
|
||||||
if not match:
|
if not match:
|
||||||
self.send_message(user_id, "Выберите уровень на кнопках ниже 👇", keyboard=get_baza_level_keyboard())
|
self.send_message(user_id, "Выберите уровень на кнопках ниже 👇", keyboard=get_baza_int_level_keyboard())
|
||||||
return
|
return
|
||||||
db.set_profile_field(user_id, "level", match)
|
db.set_profile_field(user_id, "level", match)
|
||||||
db.set_profile_field(user_id, "game", "CS2 (интенсив)")
|
db.set_profile_field(user_id, "game", "CS2 (интенсив)")
|
||||||
self._baza_send_payment(user_id, "summer", f"Летний интенсив CS2 — {config.INTENSIVE_PRICE}", campaign="summer")
|
db.update_user_state(user_id, "BAZA_INT_PAY_INTRO")
|
||||||
|
self.send_message(
|
||||||
|
user_id,
|
||||||
|
"Отлично! Можно забронировать место и оплатить участие на сайте.\n\n"
|
||||||
|
f"Стоимость — {config.INTENSIVE_PRICE} за все 4 тренировки.\n\n"
|
||||||
|
"После оплаты менеджер подтвердит участие и пришлёт организационную информацию в этот диалог.",
|
||||||
|
keyboard=get_baza_int_pay_keyboard()
|
||||||
|
)
|
||||||
|
|
||||||
|
def _handle_baza_int_pay_intro(self, user_id: int, text: str, text_lower: str):
|
||||||
|
if "оплатить" in text_lower:
|
||||||
|
db.set_profile_field(user_id, "tariff", f"Летний интенсив CS2 — {config.INTENSIVE_PRICE}")
|
||||||
|
self._baza_send_payment(user_id, "summer", f"Летний интенсив CS2 — {config.INTENSIVE_PRICE}",
|
||||||
|
campaign="summer")
|
||||||
|
return
|
||||||
|
if self._baza_nav(user_id, text_lower): # «Сначала задать вопрос» / «Вернуться в меню»
|
||||||
|
return
|
||||||
|
self.send_message(user_id, "Выберите вариант на кнопках ниже 👇", keyboard=get_baza_int_pay_keyboard())
|
||||||
|
|
||||||
|
# ---------- Свободный вопрос менеджеру / цены / карточка ----------
|
||||||
|
|
||||||
|
def _baza_ask_question(self, user_id: int):
|
||||||
|
"""§9: просит написать вопрос одним сообщением, затем передаёт менеджеру."""
|
||||||
|
db.add_metka(user_id, "manager_requested")
|
||||||
|
db.update_user_state(user_id, "BAZA_QUESTION")
|
||||||
|
p = db.get_profile(user_id)
|
||||||
|
known = ""
|
||||||
|
pieces = [("Интерес", "летний интенсив CS2" if p.get("interest_summer") else None),
|
||||||
|
("Возраст", p.get("age")), ("Уровень", p.get("level"))]
|
||||||
|
known_lines = [f"{lbl}: {val}" for lbl, val in pieces if val]
|
||||||
|
if known_lines:
|
||||||
|
known = "\n\nМенеджер получит информацию, которую мы уже собрали:\n" + "\n".join(known_lines)
|
||||||
|
self.send_message(
|
||||||
|
user_id,
|
||||||
|
"Напишите вопрос одним сообщением. Менеджер увидит ваш запрос и подключится к диалогу.\n"
|
||||||
|
"Все ответы, которые вы уже дали боту, сохранятся — повторять их не потребуется." + known,
|
||||||
|
keyboard=get_baza_menu_only_keyboard()
|
||||||
|
)
|
||||||
|
|
||||||
|
def _handle_baza_question(self, user_id: int, text: str, text_lower: str):
|
||||||
|
if text_lower in ["в главное меню ↩️", "главное меню", "меню", "в меню",
|
||||||
|
"посмотреть другие тренировки", "вернуться в меню ↩️"]:
|
||||||
|
db.update_user_state(user_id, "FREE_CHAT")
|
||||||
|
self.send_message(user_id, "🏠 Главное меню Кибершколы БАЗА. Чем помочь?",
|
||||||
|
keyboard=get_main_keyboard(is_admin=self._is_admin(user_id)))
|
||||||
|
return
|
||||||
|
db.set_profile_field(user_id, "message", text.strip())
|
||||||
|
self.send_message(user_id, "Спасибо! Вопрос передан менеджеру.")
|
||||||
|
self._baza_to_manager(user_id, reason="Вопрос от пользователя",
|
||||||
|
client_text="Менеджер скоро подключится к диалогу.")
|
||||||
|
|
||||||
# ---------- Цены / передача менеджеру / карточка ----------
|
# ---------- Цены / передача менеджеру / карточка ----------
|
||||||
|
|
||||||
@@ -995,30 +1106,44 @@ class VKBotApplication:
|
|||||||
def _baza_manager_card(self, user_id: int, reason: str = "") -> str:
|
def _baza_manager_card(self, user_id: int, reason: str = "") -> str:
|
||||||
p = db.get_profile(user_id)
|
p = db.get_profile(user_id)
|
||||||
vk_name = self._vk_name(user_id) or "—"
|
vk_name = self._vk_name(user_id) or "—"
|
||||||
lines = ["🎯 Новый лид — Кибершкола БАЗА", ""]
|
lines = ["🎯 Новый запрос из бота — Кибершкола БАЗА", ""]
|
||||||
if reason:
|
if reason:
|
||||||
lines.append(f"❗ Повод: {reason}")
|
lines.append(f"❗ Повод: {reason}")
|
||||||
lines.append(f"👤 Клиент: {vk_name}")
|
lines.append(f"👤 Клиент: {vk_name}")
|
||||||
lines.append(f"🔗 Профиль: vk.com/id{user_id}")
|
lines.append(f"🔗 Профиль: vk.com/id{user_id}")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
# Пустые поля в карточку не выводим (§11)
|
||||||
|
interest = []
|
||||||
|
if p.get("interest_trial"):
|
||||||
|
interest.append("пробное")
|
||||||
|
if p.get("interest_summer"):
|
||||||
|
interest.append("летний интенсив")
|
||||||
fields = [
|
fields = [
|
||||||
("source", "Источник"), ("role", "Для кого"), ("age", "Возраст"),
|
("source", "Источник"), ("keyword", "Кодовое слово"), ("role", "Кто обращается"),
|
||||||
("game", "Игра"), ("level", "Уровень"), ("format", "Формат"),
|
("age", "Возраст"), ("game", "Игра"), ("level", "Уровень"),
|
||||||
("tariff", "Интерес / тариф"), ("message", "Сообщение"),
|
("format", "Формат"), ("tariff", "Выбранный тариф"), ("message", "Сообщение пользователя"),
|
||||||
]
|
]
|
||||||
for key, label in fields:
|
for key, label in fields:
|
||||||
val = p.get(key)
|
val = p.get(key)
|
||||||
if val:
|
if val:
|
||||||
lines.append(f"• {label}: {val}")
|
lines.append(f"• {label}: {val}")
|
||||||
|
if interest:
|
||||||
|
lines.append("• Интерес: " + ", ".join(interest))
|
||||||
|
if p.get("started_at"):
|
||||||
|
lines.append(f"• Запуск бота: {p['started_at']}")
|
||||||
|
if p.get("manager_at"):
|
||||||
|
lines.append(f"• Передан менеджеру: {p['manager_at']}")
|
||||||
metki = p.get("metki")
|
metki = p.get("metki")
|
||||||
if metki:
|
if metki:
|
||||||
lines.append("")
|
lines.append("")
|
||||||
lines.append("🏷 Метки: " + ", ".join(metki))
|
lines.append("🏷 Метки: " + ", ".join(metki))
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
def _baza_to_manager(self, user_id: int, reason: str = ""):
|
def _baza_to_manager(self, user_id: int, reason: str = "", client_text: Optional[str] = None):
|
||||||
"""Передаёт лид менеджеру с карточкой профиля и переводит в чат с оператором."""
|
"""Передаёт лид менеджеру с карточкой профиля и переводит в чат с оператором."""
|
||||||
db.add_metka(user_id, "lead_manager")
|
db.add_metka(user_id, "manager_requested")
|
||||||
|
from datetime import datetime
|
||||||
|
db.set_profile_field(user_id, "manager_at", datetime.now().strftime("%Y-%m-%d %H:%M"))
|
||||||
card = self._baza_manager_card(user_id, reason)
|
card = self._baza_manager_card(user_id, reason)
|
||||||
self._notify_operators("💬 Запрос менеджеру\n\n" + card)
|
self._notify_operators("💬 Запрос менеджеру\n\n" + card)
|
||||||
try:
|
try:
|
||||||
@@ -1026,6 +1151,7 @@ class VKBotApplication:
|
|||||||
except Exception as mail_err:
|
except Exception as mail_err:
|
||||||
logger.error(f"Не удалось отправить email о лиде: {mail_err}")
|
logger.error(f"Не удалось отправить email о лиде: {mail_err}")
|
||||||
db.update_user_state(user_id, "OPERATOR_CHAT")
|
db.update_user_state(user_id, "OPERATOR_CHAT")
|
||||||
|
if client_text is None:
|
||||||
if self._within_work_hours():
|
if self._within_work_hours():
|
||||||
client_text = ("💬 Передал вас менеджеру — он ответит здесь в ближайшее время.\n"
|
client_text = ("💬 Передал вас менеджеру — он ответит здесь в ближайшее время.\n"
|
||||||
"Можете написать свой вопрос прямо сейчас.")
|
"Можете написать свой вопрос прямо сейчас.")
|
||||||
@@ -1034,6 +1160,9 @@ class VKBotApplication:
|
|||||||
f"до {getattr(config, 'WORK_HOUR_END', 21)}:00).\n"
|
f"до {getattr(config, 'WORK_HOUR_END', 21)}:00).\n"
|
||||||
"Я передал ваш запрос — ответим, как начнётся рабочий день. "
|
"Я передал ваш запрос — ответим, как начнётся рабочий день. "
|
||||||
"Можете оставить сообщение здесь.")
|
"Можете оставить сообщение здесь.")
|
||||||
|
elif not self._within_work_hours():
|
||||||
|
client_text += (f"\n\n🌙 Сейчас нерабочее время (работаем с {getattr(config, 'WORK_HOUR_START', 9)}:00 "
|
||||||
|
f"до {getattr(config, 'WORK_HOUR_END', 21)}:00) — ответим утром.")
|
||||||
self.send_message(user_id, client_text, keyboard=get_operator_keyboard())
|
self.send_message(user_id, client_text, keyboard=get_operator_keyboard())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+34
-4
@@ -88,17 +88,47 @@ def get_baza_group_keyboard() -> str:
|
|||||||
|
|
||||||
|
|
||||||
def get_baza_individual_keyboard() -> str:
|
def get_baza_individual_keyboard() -> str:
|
||||||
return _kb([["Записаться на пробное"], ["Купить 1 тренировку"],
|
return _kb([["Купить 1 тренировку"],
|
||||||
["Купить 4 тренировки", "Купить 8 тренировок"]],
|
["Купить 4 тренировки", "Купить 8 тренировок"]],
|
||||||
back=True, ask_manager=True)
|
back=True, ask_manager=True)
|
||||||
|
|
||||||
|
|
||||||
def get_baza_help_keyboard() -> str:
|
def get_baza_menu_only_keyboard() -> str:
|
||||||
return _kb([["Записаться на пробное"], ["Подобрать заново 🎯"]], ask_manager=True)
|
"""Только «В главное меню» — для шагов со свободным вводом (совет/вопрос)."""
|
||||||
|
return _kb([])
|
||||||
|
|
||||||
|
|
||||||
def get_baza_intensive_keyboard() -> str:
|
def get_baza_intensive_keyboard() -> str:
|
||||||
return _kb([["Хочу участвовать 🔥"]], ask_manager=True)
|
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_baza_int_level_keyboard() -> str:
|
||||||
|
return _kb([["Только начинаю"], ["Уже знаю основы"],
|
||||||
|
["Играю регулярно"], ["Есть рейтинг или командный опыт"]], back=True)
|
||||||
|
|
||||||
|
|
||||||
|
def get_baza_int_pay_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_baza_int_young_keyboard() -> str:
|
||||||
|
keyboard = VkKeyboard(one_time=False)
|
||||||
|
keyboard.add_button("Передать менеджеру 💬", color=VkKeyboardColor.SECONDARY)
|
||||||
|
keyboard.add_line()
|
||||||
|
keyboard.add_button("Посмотреть другие тренировки", color=VkKeyboardColor.SECONDARY)
|
||||||
|
return keyboard.get_keyboard()
|
||||||
|
|
||||||
|
|
||||||
def get_baza_prices_keyboard() -> str:
|
def get_baza_prices_keyboard() -> str:
|
||||||
|
|||||||
+36
-6
@@ -20,8 +20,9 @@ def test_baza_group_funnel_to_payment(app):
|
|||||||
app.handle_message(u, "Групповые тренировки"); assert _state(u) == "BAZA_GROUP"
|
app.handle_message(u, "Групповые тренировки"); assert _state(u) == "BAZA_GROUP"
|
||||||
app.handle_message(u, "Купить 4 тренировки"); assert _state(u) == "BAZA_PAY"
|
app.handle_message(u, "Купить 4 тренировки"); assert _state(u) == "BAZA_PAY"
|
||||||
p = _prof(u)
|
p = _prof(u)
|
||||||
assert p["role"].startswith("Для подростка") and p["game"] == "CS2"
|
assert p["role"].startswith("Родитель") and p["game"] == "CS2"
|
||||||
assert p["format"] == "Групповые тренировки" and "4" in p["tariff"]
|
assert p["format"] == "Групповые тренировки" and "4" in p["tariff"]
|
||||||
|
assert p.get("started_at") # дата запуска сохранена (§13)
|
||||||
assert {"role_parent", "game_cs2", "format_group", "clicked_payment", "lead_hot"} <= set(p["metki"])
|
assert {"role_parent", "game_cs2", "format_group", "clicked_payment", "lead_hot"} <= set(p["metki"])
|
||||||
|
|
||||||
|
|
||||||
@@ -47,7 +48,8 @@ def test_baza_under12_goes_to_manager(app):
|
|||||||
app.handle_message(u, "Младше 12 лет"); assert _state(u) == "BAZA_YOUNG"
|
app.handle_message(u, "Младше 12 лет"); assert _state(u) == "BAZA_YOUNG"
|
||||||
app.handle_message(u, "Обсудить с менеджером 💬")
|
app.handle_message(u, "Обсудить с менеджером 💬")
|
||||||
assert _state(u) == "OPERATOR_CHAT"
|
assert _state(u) == "OPERATOR_CHAT"
|
||||||
assert "age_under12" in _prof(u)["metki"]
|
assert _prof(u)["age"] == "Младше 12 лет"
|
||||||
|
assert "manager_requested" in _prof(u)["metki"]
|
||||||
|
|
||||||
|
|
||||||
def test_baza_other_game_goes_to_manager(app):
|
def test_baza_other_game_goes_to_manager(app):
|
||||||
@@ -66,14 +68,42 @@ def test_baza_intensive_by_keyword(app):
|
|||||||
app.handle_message(u, "лето"); assert _state(u) == "BAZA_INT_INFO"
|
app.handle_message(u, "лето"); assert _state(u) == "BAZA_INT_INFO"
|
||||||
app.handle_message(u, "Хочу участвовать 🔥"); assert _state(u) == "BAZA_INT_AGE"
|
app.handle_message(u, "Хочу участвовать 🔥"); assert _state(u) == "BAZA_INT_AGE"
|
||||||
app.handle_message(u, "14–15 лет"); assert _state(u) == "BAZA_INT_LEVEL"
|
app.handle_message(u, "14–15 лет"); assert _state(u) == "BAZA_INT_LEVEL"
|
||||||
app.handle_message(u, "Играет в команде или турнирах"); assert _state(u) == "BAZA_PAY"
|
app.handle_message(u, "Играю регулярно"); assert _state(u) == "BAZA_INT_PAY_INTRO"
|
||||||
assert "intensive_interest" in _prof(u)["metki"]
|
app.handle_message(u, "Оплатить интенсив 💳"); assert _state(u) == "BAZA_PAY"
|
||||||
|
p = _prof(u)
|
||||||
|
assert p["keyword"] == "лето" and p.get("interest_summer")
|
||||||
|
assert {"source_keyword_summer", "interest_summer_intensive", "clicked_payment"} <= set(p["metki"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_baza_under12_intensive_to_manager(app):
|
||||||
|
u = 70017
|
||||||
|
app.handle_message(u, "Летний интенсив CS2 🔥")
|
||||||
|
app.handle_message(u, "Хочу участвовать 🔥")
|
||||||
|
app.handle_message(u, "Младше 12 лет"); assert _state(u) == "BAZA_INT_YOUNG"
|
||||||
|
app.handle_message(u, "Передать менеджеру 💬"); assert _state(u) == "OPERATOR_CHAT"
|
||||||
|
|
||||||
|
|
||||||
def test_baza_question_button_handoff(app):
|
def test_baza_question_button_handoff(app):
|
||||||
|
"""«Задать вопрос» (§9): сначала промпт, затем сообщение → менеджеру."""
|
||||||
u = 70015
|
u = 70015
|
||||||
app.handle_message(u, "Задать вопрос 💬"); assert _state(u) == "OPERATOR_CHAT"
|
app.handle_message(u, "Задать вопрос 💬"); assert _state(u) == "BAZA_QUESTION"
|
||||||
assert "lead_manager" in _prof(u)["metki"]
|
app.handle_message(u, "Когда занятия?"); assert _state(u) == "OPERATOR_CHAT"
|
||||||
|
assert _prof(u)["message"] == "Когда занятия?"
|
||||||
|
assert "manager_requested" in _prof(u)["metki"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_baza_help_free_text_to_manager(app):
|
||||||
|
"""§7: «Пока не знаю» → свободный текст → менеджеру, format_help."""
|
||||||
|
u = 70018
|
||||||
|
app.handle_message(u, "Подобрать тренировку 🎯")
|
||||||
|
app.handle_message(u, "Для себя")
|
||||||
|
app.handle_message(u, "14–15 лет")
|
||||||
|
app.handle_message(u, "CS2")
|
||||||
|
app.handle_message(u, "Только начинает")
|
||||||
|
app.handle_message(u, "Пока не знаю, нужен совет"); assert _state(u) == "BAZA_HELP"
|
||||||
|
app.handle_message(u, "важно расписание"); assert _state(u) == "OPERATOR_CHAT"
|
||||||
|
assert _prof(u)["message"] == "важно расписание"
|
||||||
|
assert "format_help" in _prof(u)["metki"]
|
||||||
|
|
||||||
|
|
||||||
def test_baza_prices_and_menu(app):
|
def test_baza_prices_and_menu(app):
|
||||||
|
|||||||
Reference in New Issue
Block a user