Тикеты заявок, «пробное занятие», доработка ветки вебинара

- Тикеты: add_lead возвращает id заявки; при передаче менеджеру клиент получает
  «🎫 Номер вашей заявки: №N», тот же номер — в карточке оператору и в письме.
  Один тикет на заход (сбрасывается при новом старте воронки/интенсива).
- Кнопка «Записаться на пробное» → «Записаться на пробное занятие» (лейблы + распознавание).
- Ветка вебинара: вход по callback-кнопке и по тексту «записаться на вебинар»,
  текст выровнен по ТЗ, после регистрации состояние FREE_CHAT — основное меню
  остаётся доступным (не блокирует продающую ветку). Дата/время — в config.

Тесты: test_baza_question_ticket, test_webinar_registration. pytest 36 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kenik
2026-07-26 10:04:19 +03:00
parent 7889a19744
commit 7caecb1c72
4 changed files with 111 additions and 10 deletions
+19 -1
View File
@@ -377,7 +377,7 @@ class DatabaseManager:
def add_lead(self, user_id: int, source: str, name: str, phone: str,
service: Optional[str] = None, call_time: Optional[str] = None,
promo: Optional[str] = None, marketing: bool = False):
"""Записать завершённую заявку в единый журнал leads (для статистики и истории)."""
"""Записать завершённую заявку в единый журнал leads. Возвращает id заявки (тикет)."""
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute(
@@ -386,6 +386,7 @@ class DatabaseManager:
(user_id, source, name, phone, service, call_time, promo, 1 if marketing else 0)
)
conn.commit()
return cursor.lastrowid
def get_marketing_subscribers(self) -> list:
"""Список user_id, давших согласие на маркетинговую рассылку (consent_2 = 'Согласен')."""
@@ -606,6 +607,23 @@ class DatabaseManager:
return answer
return None
# --- Вебинар: список зарегистрированных ---
def get_webinar_registered_users(self) -> list:
"""Список user_id с меткой 'webinar_registered' (профиль JSON)."""
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute("SELECT user_id, profile FROM user_states WHERE profile IS NOT NULL")
result = []
for uid, profile_json in cursor.fetchall():
try:
profile = json.loads(profile_json) if profile_json else {}
except (ValueError, TypeError):
profile = {}
metki = profile.get("metki", [])
if "webinar_registered" in metki:
result.append(uid)
return result
# --- Отклики: статусы и статистика ---
def set_application_status(self, app_id: int, status: str) -> bool:
with self._get_connection() as conn: