diff --git a/.venv/Admin.py b/.venv/Admin.py
new file mode 100644
index 0000000..10de175
--- /dev/null
+++ b/.venv/Admin.py
@@ -0,0 +1,305 @@
+from gettext import textdomain
+
+from typing import Optional
+import telebot
+from telebot import types
+import uuid # Для генерации уникальных имен файлов
+from datetime import datetime # Для работы с датами и временем
+import sqlite3 as sq
+import os
+
+# Импортируем bot из основного файла (будет установлен в initialize)
+bot = telebot.TeleBot("8575877961:AAHgw2xCBZagYmj1s-LwlA-dhAcgXALxVzE")
+
+def get_connection():
+ # Путь к вашей базе данных
+ db_path = os.path.join(os.path.dirname(__file__), 'student_studios_bot (1).db')
+ conn = sq.connect(db_path)
+ return conn
+#======================
+#========ADMIN=========
+#======================
+# Функция инициализации модуля
+def initialize(bot_instance):
+ global bot
+ bot = bot_instance
+ print("Admin module initialized")
+ return bot
+
+def admin_start(message):
+ markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
+ btn1 = types.KeyboardButton('Статистика')
+ btn2 = types.KeyboardButton('Рассылка')
+ btn3 = types.KeyboardButton('Назначить администратора')
+ markup.row(btn1, btn2, btn3)
+ btn4 = types.KeyboardButton('Изменить информацию о студиях')
+ markup.row(btn4)
+
+ bot.send_message(message.chat.id,
+ f' Добро пожаловать, {message.from_user.first_name}.',
+ parse_mode='html',
+ reply_markup=markup)
+
+# Регистрация всех хендлеров администратора
+
+# Функции-обработчики (без декораторов!)
+def handle_statistics(message):
+ """Обработчик кнопки 'Статистика'"""
+ bot.send_message(message.chat.id, f'Статистика активности:', parse_mode='html')
+
+def handle_broadcast(message):
+ """Обработчик кнопки 'Рассылка'"""
+ bot.send_message(message.chat.id, f'Введите сообщение:', parse_mode='html')
+
+def handle_give_role(message):
+ """Обработчик кнопки 'Назначить администратора'"""
+ bot.send_message(message.chat.id, "Перешлите сообщение пользователя или введите @username")
+ bot.register_next_step_handler(message, get_admin_username)
+
+
+# Главный обработчик для админа
+@bot.message_handler(func=lambda message: True, content_types=['text'])
+def handle_admin_message(message):
+ """Обрабатывает сообщения для админа"""
+ if not message.text:
+ return False
+
+ text=message.text
+ print(text)
+ # Обработка кнопок из admin_start
+ if text == 'Статистика':
+ handle_statistics(message)
+ return True
+
+ elif text == 'Рассылка':
+ handle_broadcast(message)
+ # Для рассылки нужно обработать следующий шаг
+ # Это можно сделать через bot.register_next_step_handler
+ bot.register_next_step_handler(message, process_broadcast)
+ return True
+
+ elif text == 'Назначить администратора':
+ handle_give_role(message)
+ # Следующий шаг - получение username
+ bot.register_next_step_handler(message, get_admin_username)
+ return True
+
+ elif text == 'Изменить информацию о студиях':
+ handle_studio_info(message)
+ return True
+
+ else:
+ # Если сообщение не обработано
+ return False
+
+#Надо забирать еще айдишник в тг
+def get_admin_username(message):
+ # Получаем username
+ admin_username = None
+
+ if message.forward_from:
+ if message.forward_from.username:
+ admin_username = f"@{message.forward_from.username}"
+ else:
+ admin_username = str(message.forward_from.id)
+ bot.send_message(message.chat.id, f"Username: {admin_username}")
+
+ elif message.text and '@' in message.text:
+ admin_username = message.text
+ bot.send_message(message.chat.id, f"Username получен: {admin_username}")
+
+ else:
+ bot.send_message(message.chat.id, "Ошибка! Введите @username или перешлите сообщение")
+ return
+
+ # Проверяем, существует ли пользователь в базе
+ user_exists, existing_name, existing_phone = check_user_exists(admin_username)
+
+ if user_exists:
+ # Если пользователь существует, используем сохраненные данные
+ save_admin_to_db(admin_username, existing_name, existing_phone, message.chat.id)
+ return
+
+ # Если пользователь не существует, запрашиваем ФИО
+ bot.send_message(message.chat.id, "Теперь введите ФИО нового администратора:")
+ bot.register_next_step_handler(message, get_admin_name, admin_username)
+
+
+def get_admin_name(message, admin_username):
+ # Получаем ФИО
+ admin_name = message.text
+
+ if not admin_name or len(admin_name) < 2:
+ bot.send_message(message.chat.id, "ФИО должно быть не короче 2 символов")
+ return
+
+ bot.send_message(message.chat.id, f"ФИО: {admin_name}")
+
+ # Запрашиваем номер телефона
+ bot.send_message(message.chat.id, "Теперь введите номер телефона администратора (например, +79123456789):")
+ bot.register_next_step_handler(message, get_admin_phone, admin_username, admin_name)
+
+
+def get_admin_phone(message, admin_username, admin_name):
+ # Получаем номер телефона
+ admin_phone = message.text
+
+ # Простая валидация номера телефона
+ if not admin_phone or len(admin_phone) < 5:
+ bot.send_message(message.chat.id, "Номер телефона должен быть не короче 5 символов")
+ return
+
+ bot.send_message(message.chat.id, f"Номер телефона: {admin_phone}")
+
+ # Сохраняем в базу данных
+ save_admin_to_db(admin_username, admin_name, admin_phone, message.chat.id)
+
+
+def check_user_exists(username):
+ """Проверяет, существует ли пользователь в базе и возвращает его данные"""
+ with get_connection() as conn:
+ cursor = conn.cursor()
+
+ # Ищем пользователя в базе
+ cursor.execute("""
+ SELECT full_name, phone_number
+ FROM users
+ WHERE username = ?
+ """, (username,))
+
+ result = cursor.fetchone()
+
+ if result :
+ # Пользователь найден, возвращаем его данные
+ full_name = result[0]
+ phone = result[1] if len(result) > 1 else None
+
+ if full_name and not phone:
+ return False, None, None # Заставляем ввести все заново
+
+ return True, full_name, phone
+ else:
+ # Пользователь не найден
+ return False, None, None
+
+
+def save_admin_to_db(username, name, phone, chat_id):
+ with get_connection() as conn:
+ cursor = conn.cursor()
+
+ # Получаем или создаем telegram_id для пользователя
+ # В пересланных сообщениях используем forward_from.id, в текстовом вводе - нужно получить ID пользователя
+ # chat_id - это ID чата отправителя (обычно совпадает с user_id для личных чатов)
+
+ telegram_id = None
+
+ # Проверяем, есть ли пользователь в базе
+ if '@' in username:
+ # Если username начинается с @
+ clean_username = username.replace('@', '')
+ cursor.execute("SELECT telegram_id FROM users WHERE username LIKE ?", (f'%{clean_username}%',))
+ result = cursor.fetchone()
+
+ if result:
+ telegram_id = result[0]
+ else:
+ # Для новых пользователей используем chat_id как telegram_id
+ telegram_id = chat_id
+ else:
+ # Если username - это число (ID)
+ try:
+ telegram_id = int(username)
+ except ValueError:
+ telegram_id = chat_id
+
+ # Проверяем, существует ли пользователь с таким telegram_id
+ cursor.execute("SELECT COUNT(*) FROM users WHERE telegram_id = ?", (telegram_id,))
+ user_exists = cursor.fetchone()[0] > 0
+
+ if user_exists:
+ # Обновляем существующего пользователя
+ cursor.execute("""
+ UPDATE users
+ SET username = ?,
+ role = 'head',
+ full_name = ?,
+ phone_number = ?,
+ created_at = datetime('now')
+ WHERE telegram_id = ?
+ """, (username, name, phone, telegram_id))
+ bot.send_message(chat_id,
+ f"✅ Пользователь обновлен как администратор\n"
+ f"🆔 Telegram ID: {telegram_id}\n"
+ f"👤 Username: {username}\n"
+ f"📛 ФИО: {name}\n"
+ f"📞 Телефон: {phone}")
+ else:
+ # Добавляем нового пользователя
+ cursor.execute("""
+ INSERT INTO users (telegram_id, username, full_name, phone_number, role, created_at)
+ VALUES (?, ?, ?, ?, 'head', datetime('now'))
+ """, (telegram_id, username, name, phone))
+ bot.send_message(chat_id,
+ f"✅ Добавлен администратор:\n"
+ f"🆔 Telegram ID: {telegram_id}\n"
+ f"👤 Username: {username}\n"
+ f"📛 ФИО: {name}\n"
+ f"📞 Телефон: {phone}\n"
+ f"🎖️ Роль: Администратор (руководитель) студии")
+
+ # Теперь добавляем в таблицу studios, используя telegram_id как head_user_id
+ cursor.execute("""
+ INSERT INTO studios (name, description, contacts, promo_photo_id,
+ promo_video_id, is_active, head_user_id, invite_code)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
+ """, (f"Студия {name}", f"Студия под руководством {name}",
+ phone, None, None, 1, telegram_id, None))
+
+ conn.commit()
+
+
+# def save_admin_to_db(username, name, phone, chat_id):
+# with get_connection() as conn:
+# cursor = conn.cursor()
+#
+# # Проверяем, есть ли пользователь в базе
+# cursor.execute("SELECT user_id FROM users WHERE username = ?", (username,))
+# user = cursor.fetchone()
+#
+# if user:
+# # Обновляем существующего пользователя
+# cursor.execute("""
+# UPDATE users
+# SET role = 'head',
+# full_name = ?,
+# phone_number = ?,
+# created_at = datetime('now')
+# WHERE username = ?
+# """, (name, phone, username))
+# bot.send_message(chat_id,
+# f"✅ {username} назначен администратором\n"
+# f"📛 ФИО: {name}\n"
+# f"📞 Телефон: {phone}")
+# else:
+# # Добавляем нового пользователя
+# cursor.execute("""
+# INSERT INTO users (username, full_name, phone_number, role, created_at)
+# VALUES (?, ?, ?, 'head', datetime('now'))
+# """, (username, name, phone))
+# bot.send_message(chat_id,
+# f"✅ Добавлен администратор:\n"
+# f"👤 Username: {username}\n"
+# f"📛 ФИО: {name}\n"
+# f"📞 Телефон: {phone}\n"
+# f"🎖️ Роль: Администратор (руководитель) студии")
+# #Добавляем в таблицу studios студию с данным админом
+# user_id = cursor.lastrowid
+# cursor.execute("""
+# INSERT INTO studios (name, description, contacts, promo_photo_id, promo_video_id, is_active,
+# head_user_id, invite_code)
+# VALUES (?, ?, ?, ?, ?,? ,?, ?)
+# """, ("None" , None, None, None , None, 0, user_id , None))
+#
+# conn.commit()
+
+
diff --git a/.venv/Head_of_studio.py b/.venv/Head_of_studio.py
new file mode 100644
index 0000000..2c1ef0d
--- /dev/null
+++ b/.venv/Head_of_studio.py
@@ -0,0 +1,2078 @@
+import telebot
+from telebot import types
+import logging
+import sqlite3
+from typing import Optional, List, Dict, Any
+from datetime import datetime
+
+logging.basicConfig(
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ level=logging.INFO
+)
+logger = logging.getLogger(__name__)
+
+bot = telebot.TeleBot("8575877961:AAHgw2xCBZagYmj1s-LwlA-dhAcgXALxVzE")
+
+<<<<<<< HEAD
+=======
+# Инициализируем менеджер студии
+studio_manager = StudioManager(bot)
+user_sessions = {} # Хранит активные сессии руководителей
+user_edit_states = {} # Добавляем словарь для состояний редактирования
+studio_manager.user_states = {} # Хранит состояние просмотра заявок
+
+#======================
+#====HEAD OF STUDIO====
+#======================
+def cleanup_stale_sessions():
+ #Очистка зависших сессий более чем на час - пока не решила куда вставить
+ current_time = datetime.now()
+ stale_users = []
+
+ for user_id, session in user_sessions.items():
+ if (current_time - session['start_time']).seconds > 3600: # 1 час
+ stale_users.append(user_id)
+
+ for user_id in stale_users:
+ del user_sessions[user_id]
+ if user_id in studio_manager.user_states:
+ del studio_manager.user_states[user_id]
+ logger.warning(f"Очищена зависшая сессия пользователя {user_id}")
+>>>>>>> 2c58de2511d2324c74225b79a43c74abe47df76c
+
+
+def get_connection(db_path: str = "bd2.db") -> sqlite3.Connection:
+ """Возвращает соединение с БД"""
+ conn = sqlite3.connect(db_path, check_same_thread=False)
+ conn.execute("PRAGMA foreign_keys = ON")
+ return conn
+
+
+def get_user_by_telegram_id(telegram_id: int) -> Optional[Dict[str, Any]]:
+ """Получает данные пользователя по Telegram ID"""
+ try:
+ with get_connection() as conn:
+ cursor = conn.cursor()
+ cursor.execute("""
+ SELECT user_id,
+ username,
+ full_name,
+ phone_number,
+ role,
+ student_group,
+ telegram_id,
+ email
+ FROM users
+ WHERE telegram_id = ?
+ """, (str(telegram_id),))
+ result = cursor.fetchone()
+
+ if result:
+ return {
+ 'user_id': result[0],
+ 'username': result[1],
+ 'full_name': result[2],
+ 'phone_number': result[3],
+ 'role': result[4],
+ 'student_group': result[5],
+ 'telegram_id': result[6],
+ 'email': result[7]
+ }
+ except Exception as e:
+ logger.error(f"Ошибка получения данных пользователя: {e}")
+ return None
+
+
+def get_studio_by_head_id(user_id: int) -> Optional[Dict[str, Any]]:
+ """Получает студию по ID руководителя"""
+ try:
+ with get_connection() as conn:
+ cursor = conn.cursor()
+ cursor.execute("""
+ SELECT studio_id,
+ name,
+ description,
+ contacts,
+ promo_photo_id,
+ promo_video_id,
+ is_active,
+ head_user_id,
+ invite_code
+ FROM studios
+ WHERE head_user_id = ?
+ """, (user_id,))
+ result = cursor.fetchone()
+
+ if result:
+ return {
+ 'studio_id': result[0],
+ 'name': result[1],
+ 'description': result[2],
+ 'contacts': result[3],
+ 'promo_photo_id': result[4],
+ 'promo_video_id': result[5],
+ 'is_active': bool(result[6]),
+ 'head_user_id': result[7],
+ 'invite_code': result[8]
+ }
+ except Exception as e:
+ logger.error(f"Ошибка получения студии: {e}")
+ return None
+
+
+def get_studio_statistics(studio_id: int) -> Dict[str, int]:
+ """Получает статистику по заявкам студии"""
+ try:
+ with get_connection() as conn:
+ cursor = conn.cursor()
+
+ cursor.execute("""
+ SELECT COUNT(*) as total,
+ SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending,
+ SUM(CASE WHEN status = 'accepted' THEN 1 ELSE 0 END) as accepted,
+ SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END) as rejected
+ FROM applications
+ WHERE studio_id = ?
+ """, (studio_id,))
+
+ stats_row = cursor.fetchone()
+
+ return {
+ 'total': stats_row[0] if stats_row else 0,
+ 'pending': stats_row[1] if stats_row else 0,
+ 'accepted': stats_row[2] if stats_row else 0,
+ 'rejected': stats_row[3] if stats_row else 0
+ }
+ except Exception as e:
+ logger.error(f"Ошибка получения статистики: {e}")
+ return {'total': 0, 'pending': 0, 'accepted': 0, 'rejected': 0}
+
+
+def update_studio_info(studio_id: int, description: str = None, contacts: str = None) -> bool:
+ """Обновляет информацию о студии"""
+ try:
+ with get_connection() as conn:
+ cursor = conn.cursor()
+
+ if description and contacts:
+ cursor.execute("""
+ UPDATE studios
+ SET description = ?,
+ contacts = ?
+ WHERE studio_id = ?
+ """, (description, contacts, studio_id))
+ elif description:
+ cursor.execute("""
+ UPDATE studios
+ SET description = ?
+ WHERE studio_id = ?
+ """, (description, studio_id))
+ elif contacts:
+ cursor.execute("""
+ UPDATE studios
+ SET contacts = ?
+ WHERE studio_id = ?
+ """, (contacts, studio_id))
+
+ conn.commit()
+ return cursor.rowcount > 0
+ except Exception as e:
+ logger.error(f"Ошибка обновления информации студии: {e}")
+ return False
+
+
+def get_pending_applications(studio_id: int) -> List[Dict[str, Any]]:
+ """Получает ожидающие заявки для студии"""
+ try:
+ with get_connection() as conn:
+ cursor = conn.cursor()
+ cursor.execute("""
+ SELECT a.application_id,
+ a.summary,
+ a.created_at,
+ u.user_id,
+ u.full_name,
+ u.student_group,
+ u.username,
+ u.phone_number,
+ u.email,
+ u.telegram_id
+ FROM applications a
+ JOIN users u ON a.user_id = u.user_id
+ WHERE a.studio_id = ?
+ AND a.status = 'pending'
+ ORDER BY a.created_at ASC
+ """, (studio_id,))
+
+ rows = cursor.fetchall()
+ applications = []
+
+ for row in rows:
+ applications.append({
+ 'application_id': row[0],
+ 'summary': row[1],
+ 'created_at': row[2],
+ 'user_id': row[3],
+ 'full_name': row[4],
+ 'student_group': row[5],
+ 'username': row[6],
+ 'phone_number': row[7],
+ 'email': row[8],
+ 'student_telegram_id': row[9]
+ })
+
+ return applications
+ except Exception as e:
+ logger.error(f"Ошибка получения заявок: {e}")
+ return []
+
+
+def get_application_details(application_id: int) -> Optional[Dict[str, Any]]:
+ """Получает детальную информацию о заявке"""
+ try:
+ with get_connection() as conn:
+ cursor = conn.cursor()
+
+ cursor.execute("""
+ SELECT a.application_id,
+ a.summary,
+ a.status,
+ a.created_at,
+ u.full_name,
+ u.student_group,
+ u.username,
+ u.phone_number,
+ u.email,
+ u.telegram_id,
+ s.name as studio_name,
+ s.studio_id
+ FROM applications a
+ JOIN users u ON a.user_id = u.user_id
+ JOIN studios s ON a.studio_id = s.studio_id
+ WHERE a.application_id = ?
+ """, (application_id,))
+
+ row = cursor.fetchone()
+ if not row:
+ return None
+
+ cursor.execute("""
+ SELECT question_text, answer_text
+ FROM application_answers
+ WHERE application_id = ?
+ ORDER BY created_at
+ """, (application_id,))
+
+ answers = cursor.fetchall()
+ answers_dict = {question: answer for question, answer in answers}
+
+ return {
+ 'application_id': row[0],
+ 'summary': row[1],
+ 'status': row[2],
+ 'created_at': row[3],
+ 'full_name': row[4],
+ 'student_group': row[5],
+ 'username': row[6],
+ 'phone_number': row[7],
+ 'email': row[8],
+ 'student_telegram_id': row[9],
+ 'studio_name': row[10],
+ 'studio_id': row[11],
+ 'answers': answers_dict
+ }
+ except Exception as e:
+ logger.error(f"Ошибка получения деталей заявки: {e}")
+ return None
+
+
+def update_application_status(application_id: int, status: str) -> bool:
+ """Обновляет статус заявки"""
+ try:
+ with get_connection() as conn:
+ cursor = conn.cursor()
+ cursor.execute("""
+ UPDATE applications
+ SET status = ?,
+ updated_at = ?
+ WHERE application_id = ?
+ """, (status, datetime.now(), application_id))
+
+ conn.commit()
+ return cursor.rowcount > 0
+ except Exception as e:
+ logger.error(f"Ошибка обновления статуса заявки: {e}")
+ return False
+
+
+
+def create_head_menu() -> types.ReplyKeyboardMarkup:
+ """Создает меню для руководителя"""
+ markup = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2)
+
+<<<<<<< HEAD
+ btn_new_applications = types.KeyboardButton('Новые заявки')
+ btn_statistics = types.KeyboardButton('Статистика')
+ btn_edit_studio = types.KeyboardButton('Изменить информацию о студии')
+=======
+ btn_statistics = types.KeyboardButton('Отчет по заявкам')
+ btn_applications = types.KeyboardButton("Новые заявки")
+ markup.row(btn_statistics, btn_applications)
+
+ btn_edit = types.KeyboardButton('Изменить информацию о студии')
+>>>>>>> 2c58de2511d2324c74225b79a43c74abe47df76c
+ btn_refresh = types.KeyboardButton('Обновить')
+
+ markup.row(btn_new_applications, btn_statistics)
+ markup.row(btn_edit_studio)
+ markup.row(btn_refresh)
+<<<<<<< HEAD
+
+ return markup
+
+
+def create_student_menu() -> types.ReplyKeyboardMarkup:
+ """Создает меню для студента"""
+ markup = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2)
+
+ btn_catalog = types.KeyboardButton('Каталог студий')
+ btn_my_applications = types.KeyboardButton('Мои заявки')
+ btn_new_application = types.KeyboardButton('Подать заявку')
+
+ markup.row(btn_catalog)
+ markup.row(btn_my_applications, btn_new_application)
+
+ return markup
+
+
+def create_head_inline_menu() -> types.InlineKeyboardMarkup:
+ """Создает inline-меню для руководителя"""
+ markup = types.InlineKeyboardMarkup(row_width=2)
+
+ btn_new_applications = types.InlineKeyboardButton('Новые заявки', callback_data='head_new_applications')
+ btn_statistics = types.InlineKeyboardButton('Статистика', callback_data='head_statistics')
+ btn_edit_studio = types.InlineKeyboardButton('Изменить информацию', callback_data='head_edit_studio')
+
+ markup.add(btn_new_applications, btn_statistics)
+ markup.add(btn_edit_studio)
+
+ return markup
+
+review_states = {}
+edit_states = {}
+
+
+
+def handle_head_start(message: types.Message) -> bool:
+ """Обработка входа руководителя"""
+ telegram_id = message.from_user.id
+ first_name = message.from_user.first_name
+
+ logger.info(f"=== ВХОД РУКОВОДИТЕЛЯ: telegram_id={telegram_id} ({first_name}) ===")
+
+ # Проверяем, является ли руководителем
+ studio_data = get_head_studio_data(telegram_id)
+
+ if not studio_data:
+ return False
+
+ # Показать приветственное сообщение
+ studio_name = studio_data['name']
+ stats = get_studio_statistics(studio_data['studio_id'])
+
+ welcome_message = (
+ f"Добро пожаловать, {first_name}!\n\n"
+ f"Студия: {studio_name}\n\n"
+ f"Статистика заявок:\n"
+ f"Всего: {stats.get('total', 0)}\n"
+ f"Ожидают: {stats.get('pending', 0)}\n"
+ f"✅ Принято: {stats.get('accepted', 0)}\n"
+ f"❌ Отклонено: {stats.get('rejected', 0)}\n\n"
+ )
+
+ markup = create_head_menu()
+
+=======
+ markup.row(btn_edit)
+ return markup
+
+
+@bot.message_handler(commands=['head'])
+def handle_head_start(message):
+ """Обработчик команды для входа руководителя"""
+ user_id = message.from_user.id
+ first_name = message.from_user.first_name
+
+ print(f"\n=== DEBUG /head команда ===")
+ print(f"Пользователь: {user_id} ({first_name})")
+
+ studio_id = studio_manager.get_studio_id_for_head(user_id)
+ print(f"studio_id из базы: {studio_id}")
+
+ if not studio_id:
+ print("Пользователь не найден как руководитель в базе данных")
+ bot.send_message(
+ message.chat.id,
+ "❌ Доступ запрещен\n\n"
+ "Вы не зарегистрированы как руководитель студии.\n\n"
+ f"Ваш ID: {user_id}",
+ parse_mode='HTML'
+ )
+ return
+
+ # Получаем название студии
+ studio_name = studio_manager.get_studio_name(studio_id)
+ print(f"Название студии: {studio_name}")
+
+ # Получаем количество ожидающих заявок
+ applications = studio_manager.get_new_applications(studio_id)
+ pending_count = len(applications)
+
+ welcome_message = (
+ f"👋 Добро пожаловать, {first_name}!\n\n"
+ f"Студия: {studio_name}\n"
+ f"Ожидающих заявок: {pending_count}"
+ )
+
+ # Отправляем главное меню
+ markup = create_main_menu_markup(user_id)
+>>>>>>> 2c58de2511d2324c74225b79a43c74abe47df76c
+ bot.send_message(
+ message.chat.id,
+ welcome_message,
+ parse_mode='HTML',
+ reply_markup=markup
+ )
+ print("=== Конец /head команды ===\n")
+
+<<<<<<< HEAD
+ logger.info(f"✅ Показано меню руководителя для студии '{studio_name}'")
+ return True
+
+
+def get_head_studio_data(telegram_id: int) -> Optional[dict]:
+ """Получает данные студии для руководителя"""
+ user_data = get_user_by_telegram_id(telegram_id)
+
+ if not user_data or user_data['role'] != 'head':
+ logger.info(f"❌ Пользователь {telegram_id} не является руководителем")
+ return None
+
+ studio_data = get_studio_by_head_id(user_data['user_id'])
+
+ if not studio_data:
+ logger.warning(f"У руководителя {telegram_id} не назначена студия")
+ return None
+
+ return studio_data
+
+
+def handle_statistics(message: types.Message):
+ """Обработка кнопки статистики"""
+ telegram_id = message.from_user.id
+
+ logger.info(f"=== СТАТИСТИКА ОТ telegram_id={telegram_id} ===")
+
+ # Проверяем, является ли руководителем
+ studio_data = get_head_studio_data(telegram_id)
+ if not studio_data:
+ send_access_denied(message.chat.id)
+ return
+
+ # Получаем статистику
+ stats = get_studio_statistics(studio_data['studio_id'])
+ studio_name = studio_data['name']
+
+ message_text = (
+ f"Статистика по заявкам\n\n"
+ f"Студия: {studio_name}\n\n"
+ f"Общая статистика:\n"
+ f"📋 Всего заявок: {stats.get('total', 0)}\n"
+ f"Ожидают рассмотрения: {stats.get('pending', 0)}\n"
+ f"✅ Принято: {stats.get('accepted', 0)}\n"
+ f"❌ Отклонено: {stats.get('rejected', 0)}\n\n"
+ )
+
+ bot.send_message(
+ message.chat.id,
+ message_text,
+ parse_mode='HTML'
+ )
+
+
+def handle_edit_studio_info(message: types.Message):
+ """Обработка кнопки изменения информации о студии"""
+ telegram_id = message.from_user.id
+
+ logger.info(f"=== ИЗМЕНЕНИЕ ИНФОРМАЦИИ ОТ telegram_id={telegram_id} ===")
+
+ # Проверяем, является ли руководителем
+ studio_data = get_head_studio_data(telegram_id)
+ if not studio_data:
+ send_access_denied(message.chat.id)
+ return
+=======
+@bot.message_handler(func=lambda message: message.text == "Новые заявки")
+def handle_applications_start(message):
+ """Начало просмотра заявок"""
+ user_id = message.from_user.id
+ chat_id = message.chat.id
+
+ print(f"\n=== DEBUG: Начало просмотра заявок ===")
+ print(f"Время: {datetime.now()}")
+ print(f"Пользователь ID: {user_id}")
+ print(f"Chat ID: {chat_id}")
+
+ # Проверяем, не идет ли уже просмотр
+ if user_id in studio_manager.user_states:
+ print(f"Ошибка: у пользователя {user_id} уже есть активный просмотр")
+ bot.send_message(
+ chat_id,
+ "⚠️ Просмотр заявок уже начат\n\n"
+ "Завершите текущий просмотр или нажмите 'Прекратить просмотр'.",
+ parse_mode='HTML'
+ )
+ return
+
+ # Получаем studio_id с детальной отладкой
+ print(f"Вызов studio_manager.get_studio_id_for_head({user_id})...")
+ studio_id = studio_manager.get_studio_id_for_head(user_id)
+ print(f"Полученный studio_id: {studio_id}")
+
+ if not studio_id:
+ print(f"ОШИБКА: studio_id не найден для пользователя {user_id}")
+
+ # Детальная диагностика проблемы
+ conn = get_connection()
+ cursor = conn.cursor()
+
+ try:
+ # 1. Проверяем таблицу studios
+ cursor.execute("""
+ SELECT studio_id, name, head_user_id
+ FROM studios
+ WHERE head_user_id = ?
+ """, (user_id,))
+ studio_result = cursor.fetchone()
+ print(f"Результат прямого SQL запроса в studios: {studio_result}")
+
+ if studio_result:
+ print(f"Найдена студия: ID={studio_result[0]}, Название={studio_result[1]}, Head={studio_result[2]}")
+ else:
+ print(f"Запись в studios с head_user_id={user_id} НЕ НАЙДЕНА")
+
+ # Показываем все студии для отладки
+ cursor.execute("SELECT studio_id, name, head_user_id FROM studios ORDER BY studio_id")
+ all_studios = cursor.fetchall()
+ print(f"Все студии в базе ({len(all_studios)} шт.):")
+ for studio in all_studios:
+ print(f" ID: {studio[0]}, Название: '{studio[1]}', Руководитель: {studio[2]}")
+
+ # 2. Проверяем таблицу users
+ cursor.execute("SELECT user_id, full_name, role FROM users WHERE user_id = ?", (user_id,))
+ user_result = cursor.fetchone()
+ print(f"Запись в users: {user_result}")
+
+ if user_result:
+ print(f"Пользователь найден: {user_result[1]}, роль: {user_result[2]}")
+ else:
+ print(f"ВНИМАНИЕ: Пользователь с ID={user_id} не найден в таблице users")
+
+ except Exception as e:
+ print(f"Ошибка при диагностике: {e}")
+ finally:
+ conn.close()
+
+ # Отправляем информативное сообщение об ошибке
+ error_message = (
+ f"❌ Ошибка доступа\n\n"
+ f"Ваш Telegram ID: {user_id}\n"
+ f"ID студии: {studio_id or 'Не найден'}\n\n"
+ f"Возможные причины:\n"
+ f"1. Вы не привязаны к студии как руководитель\n"
+ f"2. Ваш ID ({user_id}) не указан в поле head_user_id\n"
+ f"3. Студия не активирована (is_active = 0)\n\n"
+ f"Решение:\n"
+ f"• Обратитесь к администратору\n"
+ f"• Используйте команду /check_access для диагностики"
+ )
+
+ bot.send_message(
+ chat_id,
+ error_message,
+ parse_mode='HTML'
+ )
+ return
+
+ # Успешно получили studio_id - продолжаем
+ print(f"УСПЕХ: Найдена студия ID={studio_id}")
+
+ # Получаем название студии
+ studio_name = studio_manager.get_studio_name(studio_id)
+ print(f"Название студии: '{studio_name}'")
+
+ # Запускаем просмотр заявок
+ print(f"Вызов studio_manager.start_reviewing_applications_with_id({user_id}, {studio_id})...")
+ success = studio_manager.start_reviewing_applications_with_id(user_id, studio_id)
+
+ if not success:
+ print(f"Ошибка: start_reviewing_applications_with_id вернула False")
+ bot.send_message(
+ chat_id,
+ "❌ Не удалось начать просмотр\n\n"
+ "Возможно, возникла ошибка при загрузке заявок.",
+ parse_mode='HTML'
+ )
+ return
+
+ print(f"Просмотр успешно запущен. Состояние пользователя создано.")
+
+ # Проверяем, создано ли состояние
+ if user_id not in studio_manager.user_states:
+ print(f"КРИТИЧЕСКАЯ ОШИБКА: состояние пользователя не создано")
+ bot.send_message(
+ chat_id,
+ "❌ Внутренняя ошибка\n\n"
+ "Не удалось инициализировать сессию просмотра.",
+ parse_mode='HTML'
+ )
+ return
+
+ # Получаем информацию о заявках
+ state = studio_manager.user_states[user_id]
+ applications = state.get('applications', [])
+ total_apps = len(applications)
+
+ print(f"Загружено заявок: {total_apps}")
+
+ # Создаем сессию для статистики
+ user_sessions[user_id] = {
+ 'start_time': datetime.now(),
+ 'studio_name': studio_name,
+ 'studio_id': studio_id,
+ 'total_applications': total_apps,
+ 'processed': 0,
+ 'accepted': 0,
+ 'rejected': 0,
+ 'skipped': 0
+ }
+
+ print(f"Сессия создана: {user_sessions[user_id]}")
+
+ # Отправляем информационное сообщение
+ if total_apps > 0:
+ welcome_message = (
+ f"Начинаем просмотр заявок\n\n"
+ f"Студия: {studio_name}\n"
+ f"Всего заявок: {total_apps}\n\n"
+ f"Используйте кнопки для принятия решений:\n"
+ f"✅ Принять — одобрить заявку\n"
+ f"❌ Отклонить — отказать кандидату\n"
+ f"⏭ Пропустить — оставить на потом\n"
+ f"Прекратить — завершить просмотр\n\n"
+ f"Прогресс отображается внизу каждой заявки."
+ )
+ else:
+ welcome_message = (
+ f"Нет новых заявок\n\n"
+ f"Студия: {studio_name}\n"
+ f"Ожидающих заявок: 0\n\n"
+ f"Новых заявок для рассмотрения нет.\n"
+ f"Вы можете проверить позже или нажать 'Обновить'."
+ )
+
+ bot.send_message(
+ chat_id,
+ welcome_message,
+ parse_mode='HTML'
+ )
+
+ # Если есть заявки, показываем первую
+ if total_apps > 0:
+ print(f"Показываем первую заявку из {total_apps}...")
+ studio_manager.show_next_application(chat_id, user_id)
+ else:
+ # Если заявок нет, очищаем состояние
+ print(f"Заявок нет, очищаем состояние...")
+ if user_id in studio_manager.user_states:
+ del studio_manager.user_states[user_id]
+ if user_id in user_sessions:
+ del user_sessions[user_id]
+
+ # Возвращаем главное меню
+ markup = create_main_menu_markup(user_id)
+ bot.send_message(
+ chat_id,
+ "Возврат в главное меню",
+ parse_mode='HTML',
+ reply_markup=markup
+ )
+
+ print(f"=== DEBUG: Конец обработки ===\n")
+
+@bot.message_handler(func=lambda message: message.text.lower() == 'отчет по заявкам')
+def handle_statistics(message):
+ user_id = message.from_user.id
+
+ studio_id = studio_manager.get_studio_id_for_head(user_id)
+ if not studio_id:
+ bot.send_message(
+ message.chat.id,
+ "❌ 2Ошибка доступа",
+ parse_mode='HTML'
+ )
+ return
+
+ stats = studio_manager.get_statistics(studio_id)
+ studio_name = studio_manager.get_studio_name(studio_id)
+
+ # Форматируем сообщение
+ message_text = studio_manager.format_statistics_message(stats, studio_name)
+ bot.send_message(message.chat.id, message_text, parse_mode='HTML')
+>>>>>>> 2c58de2511d2324c74225b79a43c74abe47df76c
+
+ # Сохраняем состояние редактирования
+ edit_states[telegram_id] = {
+ 'action': 'editing_studio',
+ 'studio_id': studio_data['studio_id'],
+ 'step': 'description'
+ }
+
+ studio_name = studio_data['name']
+ current_description = studio_data.get('description', 'Не указано')
+ current_contacts = studio_data.get('contacts', 'Не указаны')
+
+ edit_message = (
+ f"Изменение информации о студии\n\n"
+ f"Студия: {studio_name}\n\n"
+ f"Текущее описание:\n{current_description}\n\n"
+ f"Текущие контакты:\n{current_contacts}\n\n"
+ f"Шаг 1 из 2: Введите новое описание студии (или напишите 'пропустить' чтобы оставить текущее):"
+ )
+
+<<<<<<< HEAD
+=======
+@bot.message_handler(func=lambda message: message.text == 'Обновить')
+def handle_refresh(message):
+ user_id = message.from_user.id
+ print(f"\n=== DEBUG Обновление ===")
+ print(f"Пользователь: {user_id}")
+
+ studio_id = studio_manager.get_studio_id_for_head(user_id)
+ print(f"studio_id: {studio_id}")
+
+ if not studio_id:
+ print("Ошибка: studio_id не найден")
+ bot.send_message(
+ message.chat.id,
+ "❌ 3Ошибка доступа\n\n"
+ f"User ID: {user_id}\n"
+ f"Studio ID: {studio_id}",
+ parse_mode='HTML'
+ )
+ return
+
+ # Получаем актуальный список заявок
+ applications = studio_manager.get_new_applications(studio_id)
+ studio_name = studio_manager.get_studio_name(studio_id)
+
+ # Обновляем меню
+ markup = create_main_menu_markup(user_id)
+
+ if applications:
+ message_text = (
+ f"Список заявок обновлен\n\n"
+ f"Заявок в очереди: {len(applications)}\n\n"
+ f"Нажмите 'Новые заявки' для начала просмотра"
+ )
+ else:
+ message_text = (
+ f"Список заявок обновлен\n\n"
+ f"Заявок в очереди: 0\n\n"
+ f"Новых заявок пока нет"
+ )
+
+>>>>>>> 2c58de2511d2324c74225b79a43c74abe47df76c
+ bot.send_message(
+ message.chat.id,
+ edit_message,
+ parse_mode='HTML'
+ )
+
+
+def handle_edit_description(message: types.Message):
+ """Обработка ввода нового описания"""
+ telegram_id = message.from_user.id
+
+ if telegram_id not in edit_states or edit_states[telegram_id]['action'] != 'editing_studio':
+ return
+
+ studio_id = edit_states[telegram_id]['studio_id']
+ new_description = message.text.strip()
+
+ if new_description.lower() == 'пропустить':
+ new_description = None
+ edit_states[telegram_id]['new_description'] = None
+ else:
+ edit_states[telegram_id]['new_description'] = new_description
+
+ edit_states[telegram_id]['step'] = 'contacts'
+
+ next_message = (
+ f"Шаг 2 из 2: Введите новые контакты студии\n"
+ f"(или напишите 'пропустить' чтобы оставить текущие):"
+ )
+
+ bot.send_message(
+ message.chat.id,
+ next_message,
+ parse_mode='HTML'
+ )
+
+
+def handle_edit_contacts(message: types.Message):
+ """Обработка ввода новых контактов"""
+ telegram_id = message.from_user.id
+
+ if telegram_id not in edit_states or edit_states[telegram_id]['action'] != 'editing_studio':
+ return
+
+ new_contacts = message.text.strip()
+ studio_id = edit_states[telegram_id]['studio_id']
+
+ if new_contacts.lower() == 'пропустить':
+ new_contacts = None
+
+ new_description = edit_states[telegram_id].get('new_description')
+
+ # Обновляем информацию в БД
+ success = update_studio_info(studio_id, new_description, new_contacts)
+
+ if success:
+ result_message = "✅ Информация о студии успешно обновлена!"
+ logger.info(f"✅ Обновлена информация о студии {studio_id}")
+ else:
+ result_message = "❌ Ошибка при обновлении информации"
+ logger.error(f"❌ Ошибка обновления информации студии {studio_id}")
+
+ # Очищаем состояние
+ if telegram_id in edit_states:
+ del edit_states[telegram_id]
+
+ bot.send_message(
+ message.chat.id,
+ result_message,
+ parse_mode='HTML'
+ )
+
+ # Возвращаем в главное меню с inline кнопками
+ return_to_head_menu_with_inline(message.chat.id)
+
+
+def send_access_denied(chat_id: int):
+ """Отправляет сообщение об отказе в доступе"""
+ bot.send_message(
+ chat_id,
+ "❌ Доступ запрещен\n\n"
+ "Эта функция доступна только руководителям студий.",
+ parse_mode='HTML'
+ )
+
+
+def return_to_head_menu_with_inline(chat_id: int):
+ """Возвращает в главное меню руководителя с inline кнопками"""
+ markup = create_head_menu()
+ inline_markup = create_head_inline_menu()
+
+ bot.send_message(
+ chat_id,
+ "Вы вернулись в главное меню руководителя\n\n",
+ parse_mode='HTML',
+ reply_markup=markup
+ )
+
+ bot.send_message(
+ chat_id,
+ "Быстрые действия:",
+ parse_mode='HTML',
+ reply_markup=inline_markup
+ )
+
+
+def format_application_message(app_data: Dict[str, Any], current_num: int, total: int) -> str:
+ """Форматирует сообщение с информацией о заявке"""
+ message = f"Заявка #{app_data['application_id']}\n\n"
+ message += f"Студент: {app_data['full_name']}\n"
+
+ if app_data.get('student_group'):
+ message += f"Группа: {app_data['student_group']}\n"
+
+ if app_data.get('username'):
+ message += f"Telegram: @{app_data['username']}\n"
+
+ if app_data.get('phone_number'):
+ message += f"Телефон: {app_data['phone_number']}\n"
+
+ if app_data.get('email'):
+ message += f"Email: {app_data['email']}\n"
+
+ message += f"\nСводка:\n{app_data.get('summary', 'Нет сводки')}\n"
+
+ message += f"\nПрогресс: {current_num} из {total}"
+
+ return message
+
+
+def start_reviewing_applications(telegram_id: int, studio_id: int, chat_id: int) -> bool:
+ """Начинает просмотр заявок руководителем"""
+ try:
+ applications = get_pending_applications(studio_id)
+
+ if not applications:
+ logger.info(f"ℹНет новых заявок для студии {studio_id}")
+ return False
+
+ review_states[telegram_id] = {
+ 'studio_id': studio_id,
+ 'applications': applications,
+ 'current_index': 0,
+ 'chat_id': chat_id,
+ 'status': 'reviewing'
+ }
+
+ # Убираем навигационное сообщение, сразу показываем первую заявку
+ show_next_application(chat_id, telegram_id)
+ return True
+
+ except Exception as e:
+ logger.error(f"Ошибка начала просмотра для руководителя {telegram_id}: {e}")
+ return False
+
+
+def show_next_application(chat_id: int, telegram_id: int):
+ """Показывает следующую заявку в очереди"""
+ if telegram_id not in review_states:
+ return
+
+ state = review_states[telegram_id]
+ applications = state['applications']
+ current_index = state['current_index']
+
+ if current_index >= len(applications):
+ bot.send_message(
+ chat_id,
+ "🎉 Все заявки рассмотрены!\n\n"
+ "Новых заявок в очереди нет.",
+ parse_mode='HTML'
+ )
+
+ del review_states[telegram_id]
+ return_to_head_menu_with_inline(chat_id)
+ return
+
+ app_data = applications[current_index]
+ app_id = app_data['application_id']
+
+ details = get_application_details(app_id)
+
+ if not details:
+ bot.send_message(
+ chat_id,
+ f"Ошибка загрузки заявки #{app_id}. Пропускаем...",
+ parse_mode='HTML'
+ )
+ state['current_index'] += 1
+ show_next_application(chat_id, telegram_id)
+ return
+
+ state['current_application_id'] = app_id
+
+ message_text = format_application_message(details, current_index + 1, len(applications))
+
+ markup = types.InlineKeyboardMarkup(row_width=2)
+
+ markup.add(
+ types.InlineKeyboardButton("✅ Принять", callback_data=f"accept:{app_id}"),
+ types.InlineKeyboardButton("❌ Отклонить", callback_data=f"reject:{app_id}")
+ )
+
+ # Изменено с "Связаться" на "Пропустить"
+ markup.add(
+ types.InlineKeyboardButton("Пропустить", callback_data=f"skip:{app_id}"),
+ types.InlineKeyboardButton("Прекратить", callback_data="stop_review")
+ )
+
+ bot.send_message(
+ chat_id,
+ message_text,
+ parse_mode='HTML',
+ reply_markup=markup
+ )
+
+
+def send_notification_to_student(student_telegram_id: str, student_name: str, studio_name: str, action: str):
+ """Отправляет уведомление студенту"""
+ try:
+ if not student_telegram_id:
+ return
+
+ if action == 'accepted':
+ message = (
+ f"🎉 Поздравляем, {student_name}!\n\n"
+ f"Ваша заявка в студию '{studio_name}' одобрена!\n\n"
+ f"Руководитель свяжется с вами для дальнейших инструкций."
+ )
+ elif action == 'rejected':
+ message = (
+ f"Уважаемый(ая) {student_name}!\n\n"
+ f"К сожалению, ваша заявка в студию '{studio_name}' отклонена.\n\n"
+ f"Не расстраивайтесь! Вы можете подать заявку в другие студии."
+ )
+ else:
+ return
+
+ bot.send_message(
+ student_telegram_id,
+ message,
+ parse_mode='HTML'
+ )
+
+ logger.info(f"✅ Уведомление отправлено студенту {student_telegram_id}")
+
+ except Exception as e:
+ logger.error(f"Ошибка отправки уведомления студенту: {e}")
+
+
+def remove_processed_application(telegram_id: int, application_id: int):
+ """Удаляет обработанную заявку из состояния пользователя"""
+ if telegram_id not in review_states:
+ return
+
+ state = review_states[telegram_id]
+ applications = state['applications']
+
+ new_applications = [
+ app for app in applications
+ if app['application_id'] != application_id
+ ]
+
+ state['applications'] = new_applications
+
+ if state['current_index'] >= len(new_applications):
+ state['current_index'] = 0
+
+
+def move_application_to_end(telegram_id: int, application_id: int):
+ """Перемещает заявку в конец очереди"""
+ if telegram_id not in review_states:
+ return
+
+ state = review_states[telegram_id]
+ applications = state['applications']
+
+ target_app = None
+ other_apps = []
+
+ for app in applications:
+ if app['application_id'] == application_id:
+ target_app = app
+ else:
+ other_apps.append(app)
+
+ if target_app:
+ state['applications'] = other_apps + [target_app]
+ state['current_index'] = 0
+
+
+def handle_application_action(call):
+ """Обрабатывает действия руководителя с заявками"""
+ telegram_id = call.from_user.id
+
+ if telegram_id not in review_states:
+ bot.answer_callback_query(call.id, "Сессия устарела. Начните просмотр заново.")
+ return
+
+ action_data = call.data
+ state = review_states[telegram_id]
+
+ if action_data == "stop_review":
+ bot.answer_callback_query(call.id, "Просмотр прекращен")
+
+ try:
+ bot.delete_message(call.message.chat.id, call.message.message_id)
+ except:
+ pass
+
+ del review_states[telegram_id]
+ return_to_head_menu_with_inline(call.message.chat.id)
+ return
+
+ parts = action_data.split(":")
+ if len(parts) != 2:
+ bot.answer_callback_query(call.id, "Ошибка обработки")
+ return
+
+ action, app_id_str = parts
+ app_id = int(app_id_str)
+
+ if state.get('current_application_id') != app_id:
+ bot.answer_callback_query(call.id, "Заявка уже обработана")
+ return
+
+ try:
+ bot.delete_message(call.message.chat.id, call.message.message_id)
+ except:
+ pass
+
+ if action == "accept":
+ accept_application(call, app_id, telegram_id)
+ elif action == "reject":
+ reject_application(call, app_id, telegram_id)
+ elif action == "skip": # Изменено с "contact" на "skip"
+ skip_application(call, app_id, telegram_id)
+
+
+def accept_application(call, application_id: int, telegram_id: int):
+ """Принять заявку"""
+ try:
+ app_data = get_application_details(application_id)
+ if not app_data:
+ bot.answer_callback_query(call.id, "❌ Ошибка при принятии")
+ return
+
+ success = update_application_status(application_id, 'accepted')
+
+ if success:
+ send_notification_to_student(
+ app_data['student_telegram_id'],
+ app_data['full_name'],
+ app_data['studio_name'],
+ 'accepted'
+ )
+
+ bot.answer_callback_query(call.id, "✅ Заявка принята")
+
+ remove_processed_application(telegram_id, application_id)
+ state = review_states[telegram_id]
+ show_next_application(state['chat_id'], telegram_id)
+ else:
+ bot.answer_callback_query(call.id, "❌ Ошибка при принятии")
+
+ except Exception as e:
+ logger.error(f"Ошибка принятия заявки {application_id}: {e}")
+ bot.answer_callback_query(call.id, "❌ Ошибка при принятии")
+
+
+def reject_application(call, application_id: int, telegram_id: int):
+ """Отклонить заявку"""
+ try:
+ app_data = get_application_details(application_id)
+ if not app_data:
+ bot.answer_callback_query(call.id, "❌ Ошибка при отклонении")
+ return
+
+ success = update_application_status(application_id, 'rejected')
+
+ if success:
+ send_notification_to_student(
+ app_data['student_telegram_id'],
+ app_data['full_name'],
+ app_data['studio_name'],
+ 'rejected'
+ )
+
+ bot.answer_callback_query(call.id, "❌ Заявка отклонена")
+
+ remove_processed_application(telegram_id, application_id)
+ state = review_states[telegram_id]
+ show_next_application(state['chat_id'], telegram_id)
+ else:
+ bot.answer_callback_query(call.id, "❌ Ошибка при отклонении")
+
+ except Exception as e:
+ logger.error(f"Ошибка отклонения заявки {application_id}: {e}")
+ bot.answer_callback_query(call.id, "❌ Ошибка при отклонении")
+
+
+def skip_application(call, application_id: int, telegram_id: int):
+ """Пропустить заявку (переместить в конец)"""
+ try:
+ bot.answer_callback_query(call.id, "Заявка пропущена")
+
+ move_application_to_end(telegram_id, application_id)
+ state = review_states[telegram_id]
+ show_next_application(state['chat_id'], telegram_id)
+
+ except Exception as e:
+ logger.error(f"Ошибка пропуска заявки {application_id}: {e}")
+ bot.answer_callback_query(call.id, "❌ Ошибка")
+
+
+# ==================== ОБРАБОТЧИКИ INLINE КНОПОК ====================
+
+@bot.callback_query_handler(func=lambda call: call.data.startswith('head_'))
+def handle_head_inline_callbacks(call):
+ """Обработка inline кнопок руководителя"""
+ action = call.data
+
+ if action == 'head_new_applications':
+ handle_new_applications_inline(call)
+ elif action == 'head_statistics':
+ handle_statistics_inline(call)
+ elif action == 'head_edit_studio':
+ handle_edit_studio_inline(call)
+
+
+def handle_new_applications_inline(call):
+ """Обработка inline кнопки 'Новые заявки'"""
+ telegram_id = call.from_user.id
+
+ user_data = get_user_by_telegram_id(telegram_id)
+ if not user_data or user_data['role'] != 'head':
+ bot.answer_callback_query(call.id, "❌ Доступ запрещен")
+ return
+
+ studio_data = get_studio_by_head_id(user_data['user_id'])
+ if not studio_data:
+ bot.answer_callback_query(call.id, "❌ У вас не назначена студия")
+ return
+
+ if telegram_id in review_states:
+ bot.answer_callback_query(call.id, "Просмотр уже начат")
+ return
+
+ success = start_reviewing_applications(
+ telegram_id,
+ studio_data['studio_id'],
+ call.message.chat.id
+ )
+
+ if not success:
+ bot.answer_callback_query(call.id, "✅ Нет новых заявок")
+ bot.send_message(
+ call.message.chat.id,
+ "✅ Нет новых заявок\n\n"
+ "Все заявки уже рассмотрены или новых пока нет.",
+ parse_mode='HTML'
+ )
+ else:
+ bot.answer_callback_query(call.id, "Начинаем просмотр заявок")
+
+
+def handle_statistics_inline(call):
+ """Обработка inline кнопки 'Статистика'"""
+ telegram_id = call.from_user.id
+
+ studio_data = get_head_studio_data(telegram_id)
+ if not studio_data:
+ bot.answer_callback_query(call.id, "❌ Доступ запрещен")
+ return
+
+ stats = get_studio_statistics(studio_data['studio_id'])
+ studio_name = studio_data['name']
+
+ message_text = (
+ f"Статистика по заявкам\n\n"
+ f"Студия: {studio_name}\n\n"
+ f"Общая статистика:\n"
+ f"Всего заявок: {stats.get('total', 0)}\n"
+ f"Ожидают рассмотрения: {stats.get('pending', 0)}\n"
+ f"✅ Принято: {stats.get('accepted', 0)}\n"
+ f"❌ Отклонено: {stats.get('rejected', 0)}"
+ )
+
+ bot.answer_callback_query(call.id, "Статистика загружена")
+ bot.send_message(
+ call.message.chat.id,
+ message_text,
+ parse_mode='HTML'
+ )
+
+
+def handle_edit_studio_inline(call):
+ """Обработка inline кнопки 'Изменить информацию'"""
+ telegram_id = call.from_user.id
+
+ studio_data = get_head_studio_data(telegram_id)
+ if not studio_data:
+ bot.answer_callback_query(call.id, "❌ Доступ запрещен")
+ return
+
+ edit_states[telegram_id] = {
+ 'action': 'editing_studio',
+ 'studio_id': studio_data['studio_id'],
+ 'step': 'description'
+ }
+
+ studio_name = studio_data['name']
+ current_description = studio_data.get('description', 'Не указано')
+ current_contacts = studio_data.get('contacts', 'Не указаны')
+
+ edit_message = (
+ f"Изменение информации о студии\n\n"
+ f"Студия: {studio_name}\n\n"
+ f"Текущее описание:\n{current_description}\n\n"
+ f"Текущие контакты:\n{current_contacts}\n\n"
+ f"Шаг 1 из 2: Введите новое описание студии (или напишите 'пропустить' чтобы оставить текущее):"
+ )
+
+ bot.answer_callback_query(call.id, "Режим редактирования")
+ bot.send_message(
+ call.message.chat.id,
+ edit_message,
+ parse_mode='HTML'
+ )
+
+
+@bot.message_handler(commands=['start'])
+def handle_start(message):
+ """Главная команда запуска бота"""
+ telegram_id = message.from_user.id
+ first_name = message.from_user.first_name
+
+ logger.info(f"=== КОМАНДА /start ОТ telegram_id={telegram_id} ({first_name}) ===")
+
+ is_head = handle_head_start(message)
+
+ if not is_head:
+ markup = create_student_menu()
+
+ bot.send_message(
+ message.chat.id,
+ f"👨🎓 Добро пожаловать, {first_name}!\n\n"
+ f"Роль: Студент\n\n"
+ f"Выберите действие в меню ниже:",
+ parse_mode='HTML',
+ reply_markup=markup
+ )
+ logger.info(f"✅ Показано меню студента для {telegram_id}")
+
+
+@bot.message_handler(func=lambda message: message.text == 'Новые заявки')
+def handle_new_applications(message):
+ """Начало просмотра заявок руководителем"""
+ telegram_id = message.from_user.id
+
+ logger.info(f"=== КНОПКА 'НОВЫЕ ЗАЯВКИ' ОТ telegram_id={telegram_id} ===")
+
+ user_data = get_user_by_telegram_id(telegram_id)
+ if not user_data or user_data['role'] != 'head':
+ bot.send_message(
+ message.chat.id,
+ "❌ Доступ запрещен\n\n"
+ "Эта функция доступна только руководителям студий.",
+ parse_mode='HTML'
+ )
+ return
+
+ studio_data = get_studio_by_head_id(user_data['user_id'])
+ if not studio_data:
+ bot.send_message(
+ message.chat.id,
+ "❌ Ошибка\n\n"
+ "У вас не назначена студия.",
+ parse_mode='HTML'
+ )
+ return
+
+ if telegram_id in review_states:
+ bot.send_message(
+ message.chat.id,
+ "⚠️ Просмотр уже начат\n\n"
+ "Завершите текущий просмотр перед началом нового.",
+ parse_mode='HTML'
+ )
+ return
+
+ logger.info(f"✅ Запуск просмотра заявок для студии {studio_data['studio_id']}")
+
+ success = start_reviewing_applications(
+ telegram_id,
+ studio_data['studio_id'],
+ message.chat.id
+ )
+
+ if not success:
+ bot.send_message(
+ message.chat.id,
+ "✅ Нет новых заявок\n\n"
+ "Все заявки уже рассмотрены или новых пока нет.",
+ parse_mode='HTML'
+ )
+
+
+@bot.message_handler(func=lambda message: message.text == 'Статистика')
+def handle_statistics_button(message):
+ """Обработка кнопки статистики"""
+ handle_statistics(message)
+
+
+@bot.message_handler(func=lambda message: message.text == 'Изменить информацию о студии')
+def handle_edit_studio_button(message):
+ """Обработка кнопки изменения информации о студии"""
+ handle_edit_studio_info(message)
+
+
+@bot.message_handler(func=lambda message: message.text == 'Обновить')
+def handle_refresh(message):
+ """Обновление информации"""
+ telegram_id = message.from_user.id
+
+ logger.info(f"=== КНОПКА 'ОБНОВИТЬ' ОТ telegram_id={telegram_id} ===")
+
+ user_data = get_user_by_telegram_id(telegram_id)
+
+ if user_data and user_data['role'] == 'head':
+ studio_data = get_studio_by_head_id(user_data['user_id'])
+
+ if studio_data:
+ stats = get_studio_statistics(studio_data['studio_id'])
+ studio_name = studio_data['name']
+
+ markup = create_head_menu()
+ inline_markup = create_head_inline_menu()
+
+ response = (
+ f"✅ Информация обновлена\n\n"
+ f"Студия: {studio_name}\n"
+ f"Новых заявок: {stats.get('pending', 0)}\n"
+ f"Всего заявок: {stats.get('total', 0)}\n\n"
+ )
+
+ bot.send_message(
+ message.chat.id,
+ response,
+ parse_mode='HTML',
+ reply_markup=markup
+ )
+
+ bot.send_message(
+ message.chat.id,
+ "Выберите действие:",
+ parse_mode='HTML',
+ reply_markup=inline_markup
+ )
+
+ logger.info(f"✅ Обновлено меню руководителя")
+ else:
+ markup = create_head_menu()
+ response = "❌ У вас не назначена студия"
+
+ bot.send_message(
+ message.chat.id,
+ response,
+ parse_mode='HTML',
+ reply_markup=markup
+ )
+ else:
+ markup = create_student_menu()
+ response = "✅ Информация обновлена"
+
+ bot.send_message(
+ message.chat.id,
+ response,
+ parse_mode='HTML',
+ reply_markup=markup
+ )
+
+
+@bot.callback_query_handler(func=lambda call: call.data.startswith(('accept:', 'reject:', 'skip:')) or
+ call.data == 'stop_review')
+def handle_review_callbacks(call):
+ """Обработка callback-ов от просмотра заявок"""
+ logger.info(f"=== CALLBACK: {call.data} от telegram_id={call.from_user.id} ===")
+ handle_application_action(call)
+
+
+<<<<<<< HEAD
+@bot.message_handler(func=lambda message: True)
+def handle_all_messages(message):
+ """Обработка всех остальных сообщений"""
+ telegram_id = message.from_user.id
+
+ if telegram_id in review_states:
+ bot.send_message(
+ message.chat.id,
+ "⚠️ Вы в режиме просмотра заявок\n\n"
+ "Используйте кнопки под заявкой для действий.",
+ parse_mode='HTML'
+ )
+ return
+
+ if telegram_id in edit_states:
+ state = edit_states[telegram_id]
+
+ if state['action'] == 'editing_studio':
+ if state['step'] == 'description':
+ handle_edit_description(message)
+ elif state['step'] == 'contacts':
+ handle_edit_contacts(message)
+ return
+
+ user_data = get_user_by_telegram_id(telegram_id)
+
+ if user_data and user_data['role'] == 'head':
+ markup = create_head_menu()
+ response = "Главное меню руководителя"
+ else:
+ markup = create_student_menu()
+ response = "Главное меню студента"
+
+ bot.send_message(
+ message.chat.id,
+ response,
+ parse_mode='HTML',
+ reply_markup=markup
+ )
+
+
+if __name__ == "__main__":
+ logger.info("=" * 50)
+ logger.info("БОТ ЗАПУЩЕН")
+ logger.info("=" * 50)
+
+ bot.polling(none_stop=True)
+
+
+
+
+
+
+
+
+#
+# from gettext import textdomain
+#
+# import telebot
+# from telebot import types
+# import sqlite3
+# from typing import Optional, List, Tuple
+# from datetime import datetime
+# import logging
+#
+# from studio_manager import StudioManager
+#
+# #В коде логгер нигде не используется???
+# logging.basicConfig(
+# # %(asctime)s — время события
+# # %(name)s — имя логгера (модуля)
+# # %(levelname)s — уровень важности (INFO, ERROR, WARNING)
+# # %(message)s — текст сообщения
+# format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+# # на уровне INFO: запуск/остановка процессов, критические действия пользователей, ошибки доступа,но не обычные действия пользователей
+# level=logging.INFO
+# )
+# logger = logging.getLogger(__name__)
+#
+# bot = telebot.TeleBot("8575877961:AAHgw2xCBZagYmj1s-LwlA-dhAcgXALxVzE")
+#
+# # Инициализируем менеджер студии
+# studio_manager = StudioManager(bot)
+# user_sessions = {} # Хранит активные сессии руководителей
+#
+# studio_manager.user_states = {} # Хранит состояние просмотра заявок
+#
+# #======================
+# #====HEAD OF STUDIO====
+# #======================
+# def cleanup_stale_sessions():
+# #Очистка зависших сессий более чем на час - пока не решила куда вставить
+# current_time = datetime.now()
+# stale_users = []
+#
+# for user_id, session in user_sessions.items():
+# if (current_time - session['start_time']).seconds > 3600: # 1 час
+# stale_users.append(user_id)
+#
+# for user_id in stale_users:
+# del user_sessions[user_id]
+# if user_id in studio_manager.user_states:
+# del studio_manager.user_states[user_id]
+# logger.warning(f"Очищена зависшая сессия пользователя {user_id}")
+#
+# def show_main_menu(markup):
+# btn1 = types.KeyboardButton('')
+# btn2=types.KeyboardButton('Новые заявки')
+# markup.row(btn1, btn2)
+# btn3 = types.KeyboardButton('Изменить информацию о студии')
+#
+#
+# def get_connection() -> sqlite3.Connection:
+# """Возвращает соединение с БД"""
+# return sqlite3.connect("student_studios_bot.db", check_same_thread=False)
+#
+# def create_main_menu_markup(user_id: int = None) -> types.ReplyKeyboardMarkup:
+# """Создает главное меню руководителя"""
+# markup = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2)
+#
+#
+# btn_statistics = types.KeyboardButton('Отчет по заявкам')
+# btn_applications = types.KeyboardButton("Новые заявки")
+# markup.row(btn_statistics, btn_applications)
+#
+# btn_refresh = types.KeyboardButton('Обновить')
+# markup.row(btn_refresh)
+#
+# return markup
+#
+# @bot.message_handler(commands=['head'])
+# def handle_head_start(message):
+# """Обработчик команды для входа руководителя"""
+# user_id = message.from_user.id
+# first_name = message.from_user.first_name
+#
+# studio_id = studio_manager.get_studio_id_for_head(user_id)
+#
+# if not studio_id:
+# bot.send_message(
+# message.chat.id,
+# "❌ Доступ запрещен\n\n"
+# "Вы не зарегистрированы как руководитель студии.",
+# parse_mode='HTML'
+# )
+# return
+#
+# # Получаем название студии
+# studio_name = studio_manager.get_studio_name(studio_id)
+#
+# # Получаем количество ожидающих заявок
+# applications = studio_manager.get_new_applications(studio_id)
+# pending_count = len(applications)
+#
+# welcome_message = (
+# f"👋 Добро пожаловать, {first_name}!\n\n"
+# )
+#
+# # Отправляем главное меню
+# markup = create_main_menu_markup(user_id)
+# bot.send_message(
+# message.chat.id,
+# welcome_message,
+# parse_mode='HTML',
+# reply_markup=markup
+# )
+#
+# @bot.message_handler(func=lambda message: message.text.startswith('📋 Новые заявки'))
+# def handle_applications_start(message):
+# """Начало просмотра заявок"""
+# user_id = message.from_user.id
+#
+# # Проверяем, не идет ли уже просмотр
+# if user_id in studio_manager.user_states:
+# bot.send_message(
+# message.chat.id,
+# "⚠️ Просмотр уже начат\n\n"
+# "Завершите текущий просмотр или нажмите 'Прекратить просмотр'.",
+# parse_mode='HTML'
+# )
+# return
+#
+# studio_id = studio_manager.get_studio_id_for_head(user_id)
+# if not studio_id:
+# bot.send_message(
+# message.chat.id,
+# "❌ 1Ошибка доступа",
+# parse_mode='HTML'
+# )
+# return
+#
+# # Получаем название студии
+# studio_name = studio_manager.get_studio_name(studio_id)
+# success = studio_manager.start_reviewing_applications_with_id(user_id, studio_id)
+#
+# if not success:
+# return
+#
+# # Запускаем сессию
+# user_sessions[user_id] = {
+# 'start_time': datetime.now(),
+# 'studio_name': studio_name,
+# 'total_applications': len(studio_manager.user_states[user_id]['applications']),
+# 'processed': 0,
+# 'accepted': 0,
+# 'rejected': 0
+# }
+#
+# # Информационное сообщение
+# state = studio_manager.user_states[user_id]
+# total_apps = len(state['applications'])
+#
+# bot.send_message(
+# message.chat.id,
+# f"Начинаем просмотр заявок\n\n"
+# f"Студия: {studio_name}\n"
+# f"Всего заявок: {total_apps}\n",
+# parse_mode='HTML'
+# )
+#
+# # Показываем первую заявку
+# studio_manager.show_next_application(message.chat.id, user_id)
+#
+# @bot.message_handler(func=lambda message: message.text == 'отчет по заявкам')
+# def handle_statistics(message):
+# user_id = message.from_user.id
+#
+# studio_id = studio_manager.get_studio_id_for_head(user_id)
+# if not studio_id:
+# bot.send_message(
+# message.chat.id,
+# "❌ 2Ошибка доступа",
+# parse_mode='HTML'
+# )
+# return
+#
+# stats = studio_manager.get_statistics(studio_id)
+# studio_name = studio_manager.get_studio_name(studio_id)
+#
+# # Форматируем сообщение
+# message_text = studio_manager.format_statistics_message(stats, studio_name)
+#
+#
+#
+# @bot.message_handler(func=lambda message: message.text == 'Обновить')
+# def handle_refresh(message):
+# """Обновление списка заявок"""
+# user_id = message.from_user.id
+# print(user_id)
+#
+# studio_id = studio_manager.get_studio_id_for_head(user_id)
+# if not studio_id:
+# bot.send_message(
+# message.chat.id,
+# "❌ 3Ошибка доступа",
+# parse_mode='HTML'
+# )
+# return
+#
+# # Получаем актуальный список заявок
+# applications = studio_manager.get_new_applications(studio_id)
+# studio_name = studio_manager.get_studio_name(studio_id)
+#
+# # Обновляем меню
+# markup = create_main_menu_markup(user_id)
+#
+# if applications:
+# message_text = (
+# f"Список заявок обновлен\n\n"
+# f"Заявок в очереди: {len(applications)}\n\n"
+# f"Нажмите 'Новые заявки' для начала просмотра"
+# )
+# else:
+# message_text = (
+# f"Список заявок обновлен\n\n"
+# f"Заявок в очереди: 0\n\n"
+# f"Новых заявок пока нет"
+# )
+#
+# bot.send_message(
+# message.chat.id,
+# message_text,
+# parse_mode='HTML',
+# reply_markup=markup
+# )
+#
+# @bot.message_handler(func=lambda message: message.text == 'Главное меню')
+# def handle_main_menu(message):
+# """Возврат в главное меню"""
+# user_id = message.from_user.id
+#
+# # Очищаем состояние просмотра
+# if user_id in studio_manager.user_states:
+# del studio_manager.user_states[user_id]
+#
+#
+# del user_sessions[user_id]
+#
+# # Возвращаем главное меню
+# markup = create_main_menu_markup(user_id)
+# bot.send_message(
+# message.chat.id,
+# "🏠 Главное меню руководителя",
+# parse_mode='HTML',
+# reply_markup=markup
+# )
+#
+# @bot.callback_query_handler(func=lambda call: call.data.startswith(('accept:', 'reject:', 'skip:')) or
+# call.data == 'stop_review')
+# def handle_review_callbacks(call):
+# """Обработка callback-ов от просмотра заявок"""
+# user_id = call.from_user.id
+#
+# # Обновляем статистику сессии
+# if user_id in user_sessions:
+# session = user_sessions[user_id]
+#
+# if call.data.startswith('accept:'):
+# session['accepted'] += 1
+# session['processed'] += 1
+# elif call.data.startswith('reject:'):
+# session['rejected'] += 1
+# session['processed'] += 1
+# elif call.data.startswith('skip:'):
+# session['processed'] += 1
+#
+# # Передаем обработку в менеджер студии
+# studio_manager.handle_application_action(call)
+#
+# @bot.message_handler(func=lambda message: True)
+# def handle_other_messages(message):
+# """Обработчик всех других сообщений"""
+# user_id = message.from_user.id
+#
+# # Если пользователь в режиме просмотра
+# if user_id in studio_manager.user_states:
+# bot.send_message(
+# message.chat.id,
+# "⚠️ Вы в режиме просмотра заявок\n\n"
+# "Используйте кнопки под заявкой для действий.",
+# parse_mode='HTML'
+# )
+# else:
+# # Проверяем, является ли руководителем
+# # studio_id = studio_manager.get_studio_id_for_head(user_id)
+# studio_id = True
+#
+# if studio_id:
+# # Показываем меню руководителя
+# markup = create_main_menu_markup(user_id)
+# bot.send_message(
+# message.chat.id,
+# "Главное меню руководителя",
+# parse_mode='HTML',
+# reply_markup=markup
+# )
+# else:
+# # Показываем обычное меню
+# markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
+# btn1 = types.KeyboardButton('Каталог студий')
+# markup.row(btn1)
+# btn2 = types.KeyboardButton('Мои заявки')
+# btn3 = types.KeyboardButton('Подать заявку')
+# markup.row(btn2, btn3)
+#
+# bot.send_message(
+# message.chat.id,
+# "👋 Добро пожаловать в бот студенческих студий!",
+# parse_mode='HTML',
+# reply_markup=markup
+# )
+
+=======
+>>>>>>> 2c58de2511d2324c74225b79a43c74abe47df76c
+
+
+# # ---------------------------ИЗМЕНЕНИЕ ИНФОРМАЦИИ О СТУДИИ---------------------------
+
+def show_current_studio_info(chat_id: int, studio_id: int):
+ """Показывает текущую информацию о студии"""
+ conn = get_connection()
+ cursor = conn.cursor()
+
+ cursor.execute("""
+ SELECT name, description, contacts FROM studios WHERE id = ?
+ """, (studio_id,))
+
+ result = cursor.fetchone()
+ conn.close()
+
+ if result:
+ name, description, contacts = result
+
+ # Форматируем сообщение
+ name_display = name if name else '❌ Не указано'
+ description_display = description if description else '❌ Не указано'
+ contacts_display = contacts if contacts else '❌ Не указаны'
+
+ message = (
+ "Текущая информация о студии:\n\n"
+ f"Название:\n{name_display}\n\n"
+ f"Описание:\n{description_display}\n\n"
+ f"Контакты:\n{contacts_display}\n\n"
+ "Выберите, что хотите изменить:"
+ )
+ else:
+ message = "Информация о студии не найдена в базе данных"
+
+ bot.send_message(chat_id, message)
+
+
+def create_edit_menu_markup() -> types.ReplyKeyboardMarkup:
+ """Создает меню для редактирования информации о студии"""
+ markup = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2)
+
+ btn_name = types.KeyboardButton('Изменить название')
+ btn_description = types.KeyboardButton('Изменить описание')
+ btn_contacts = types.KeyboardButton('Изменить контакты')
+ markup.row(btn_name, btn_description)
+ markup.row(btn_contacts)
+
+ btn_save_all = types.KeyboardButton('Сохранить все изменения')
+ btn_cancel = types.KeyboardButton('Отмена')
+ markup.row(btn_save_all, btn_cancel)
+
+ return markup
+
+
+def create_main_menu_markup(user_id: int = None) -> types.ReplyKeyboardMarkup:
+ """Создает главное меню руководителя"""
+ markup = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2)
+
+ btn_statistics = types.KeyboardButton('Отчет по заявкам')
+ btn_applications = types.KeyboardButton("Новые заявки")
+ markup.row(btn_statistics, btn_applications)
+
+ btn_edit = types.KeyboardButton('Изменить информацию о студии')
+ btn_refresh = types.KeyboardButton('Обновить')
+ markup.row(btn_refresh)
+ markup.row(btn_edit)
+ return markup
+
+
+@bot.message_handler(func=lambda message: message.text.lower() == 'изменить информацию о студии')
+def handle_edit_studio_start(message):
+ """Начало редактирования информации о студии"""
+ print(f"КНОПКА НАЖАТА: {message.text}")
+ user_id = message.from_user.id
+ print(f"Пользователь: {user_id}")
+
+ # Проверяем, не идет ли уже просмотр заявок
+ if user_id in studio_manager.user_states:
+ print("Пользователь в режиме просмотра")
+ bot.send_message(
+ message.chat.id,
+ "Сначала завершите просмотр заявок"
+ )
+ return
+
+ studio_id = studio_manager.get_studio_id_for_head(user_id)
+ print(f"ID студии: {studio_id}")
+
+ if not studio_id:
+ print("Ошибка: studio_id не найден")
+ bot.send_message(
+ message.chat.id,
+ "Ошибка доступа"
+ )
+ return
+
+ # Инициализируем состояние редактирования
+ user_edit_states[user_id] = {
+ 'studio_id': studio_id,
+ 'changes': {},
+ 'awaiting_input': None
+ }
+ print(f"Создано состояние: {user_edit_states[user_id]}")
+
+ # Показываем текущую информацию
+ print("Показываем информацию о студии...")
+ show_current_studio_info(message.chat.id, studio_id)
+
+ # Показываем меню редактирования
+ print("Показываем меню редактирования...")
+ markup = create_edit_menu_markup()
+ bot.send_message(
+ message.chat.id,
+ "Редактирование информации о студии\n\n"
+ "Выберите параметр для изменения или 'Сохранить все изменения' для сохранения.",
+ reply_markup=markup
+ )
+ print("Функция завершена")
+
+
+from Studio_editing import (
+ handle_edit_field_selection,
+ handle_field_input,
+ handle_save_all_changes,
+ handle_edit_cancel,
+ show_preview
+)
+
+
+@bot.message_handler(func=lambda message: message.text in ['Изменить название', 'Изменить описание', 'Изменить контакты'])
+def handle_edit_field(message):
+ """Обработка выбора поля для редактирования"""
+ handle_edit_field_selection(message, bot, user_edit_states, create_edit_menu_markup)
+
+
+@bot.message_handler(func=lambda message: user_edit_states.get(message.from_user.id, {}).get('awaiting_input'))
+def handle_edit_input(message):
+ """Обработка ввода нового значения для поля"""
+ handle_field_input(message, bot, user_edit_states, create_edit_menu_markup)
+
+ # Показываем предпросмотр после ввода
+ user_id = message.from_user.id
+ if user_id in user_edit_states and not user_edit_states[user_id].get('awaiting_input'):
+ show_preview(message.chat.id, user_id, bot, user_edit_states)
+
+
+@bot.message_handler(func=lambda message: message.text == 'Сохранить все изменения')
+def handle_save_changes(message):
+ """Сохранение всех изменений в БД"""
+ handle_save_all_changes(message, bot, user_edit_states, create_main_menu_markup)
+
+
+@bot.message_handler(func=lambda message: message.text == 'Отмена')
+def handle_cancel_edit(message):
+ """Отмена редактирования"""
+ handle_edit_cancel(message, bot, user_edit_states, create_main_menu_markup)
+
+
+@bot.message_handler(content_types=['photo'])
+def handle_photo_for_editing(message):
+ """
+ Обработчик фото во время редактирования студии.
+
+ Этот обработчик срабатывает, когда пользователь отправляет фото.
+ Проверяем, находится ли пользователь на этапе ожидания изображения.
+ """
+ user_id = message.from_user.id
+
+ # Проверяем, находится ли пользователь на этапе ожидания изображения
+ if studio_editor.get_user_step(user_id) != 'waiting_for_image':
+ # Если нет - игнорируем фото или сообщаем об ошибке
+ return
+
+ # Получаем информацию о самом большом фото (Telegram отправляет несколько размеров)
+ file_id = message.photo[-1].file_id
+
+ # Скачиваем фото с серверов Telegram
+ file_info = bot.get_file(file_id)
+ image_bytes = bot.download_file(file_info.file_path)
+
+ # Передаем изображение в редактор студии
+ response, is_completed = studio_editor.handle_image(user_id, image_bytes)
+
+ # Отправляем ответ пользователю
+ bot.send_message(
+ message.chat.id,
+ response,
+ parse_mode='html'
+ )
+
+#
+# @bot.message_handler(commands=['cancel'])
+# def cancel_editing(message):
+# """
+# Обработчик команды /cancel для отмены редактирования.
+# """
+# user_id = message.from_user.id
+# response = studio_editor.cancel_editing(user_id)
+#
+# # Возвращаем в главное меню
+# markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
+# show_main_menu(markup)
+#
+# bot.send_message(
+# message.chat.id,
+# response,
+# parse_mode='html',
+# reply_markup=markup
+# )
+
+<<<<<<< HEAD
+
+=======
+# if __name__ == '__main__':
+# print("Бот запущен...")
+# bot.polling(none_stop=True)
+>>>>>>> 2c58de2511d2324c74225b79a43c74abe47df76c
diff --git a/.venv/Studio_editing.py b/.venv/Studio_editing.py
new file mode 100644
index 0000000..ec39012
--- /dev/null
+++ b/.venv/Studio_editing.py
@@ -0,0 +1,218 @@
+import sqlite3
+from typing import Dict, Any
+import logging
+from telebot import types
+
+logger = logging.getLogger(__name__)
+
+def handle_edit_field_selection(message, bot, user_edit_states, create_edit_menu_markup):
+ """Обработка выбора поля для редактирования"""
+ user_id = message.from_user.id
+
+ if user_id not in user_edit_states:
+ bot.send_message(message.chat.id, "❌ Сессия редактирования не активна")
+ return
+
+ edit_state = user_edit_states[user_id]
+
+ # Определяем какое поле редактируем
+ field_map = {
+ 'Изменить название': ('name', 'введите новое название студии'),
+ 'Изменить описание': ('description', 'введите новое описание студии'),
+ 'Изменить контакты': ('contacts', 'введите новые контакты (телефон, email, соцсети)')
+ }
+
+ field_name, prompt = field_map[message.text]
+ edit_state['awaiting_input'] = field_name
+
+ # Сохраняем текущий текст для возможной отмены
+ if field_name not in edit_state['changes']:
+ conn = get_connection()
+ cursor = conn.cursor()
+ cursor.execute(f"SELECT {field_name} FROM studios WHERE id = ?",
+ (edit_state['studio_id'],))
+ result = cursor.fetchone()
+ conn.close()
+ if result:
+ current_value = result[0]
+ edit_state['changes'][f'old_{field_name}'] = current_value
+
+ bot.send_message(
+ message.chat.id,
+ f"✏️ Редактирование {field_name}\n\n"
+ f"Пожалуйста, {prompt}:\n\n"
+ "Для отмены ввода отправьте 'отмена'",
+ parse_mode='HTML'
+ )
+
+
+def handle_field_input(message, bot, user_edit_states, create_edit_menu_markup):
+ """Обработка ввода нового значения для поля"""
+ user_id = message.from_user.id
+
+ if user_id not in user_edit_states:
+ return
+
+ edit_state = user_edit_states[user_id]
+ field_name = edit_state.get('awaiting_input')
+
+ if not field_name:
+ return
+
+ # Проверяем отмену
+ if message.text.lower() == 'отмена':
+ if f'old_{field_name}' in edit_state['changes']:
+ # Восстанавливаем старое значение
+ old_value = edit_state['changes'][f'old_{field_name}']
+ edit_state['changes'][field_name] = old_value
+ del edit_state['changes'][f'old_{field_name}']
+
+ edit_state['awaiting_input'] = None
+
+ bot.send_message(
+ message.chat.id,
+ "⏪ Ввод отменен",
+ parse_mode='HTML',
+ reply_markup=create_edit_menu_markup()
+ )
+ return
+
+ # Сохраняем новое значение
+ edit_state['changes'][field_name] = message.text
+ edit_state['awaiting_input'] = None
+
+ # Подтверждаем ввод
+ confirmation_text = {
+ 'name': 'Название',
+ 'description': 'Описание',
+ 'contacts': 'Контакты'
+ }
+
+ bot.send_message(
+ message.chat.id,
+ f"✅ {confirmation_text.get(field_name, 'Поле')} обновлено\n\n"
+ f"Новое значение сохранено в черновике.\n\n"
+ f"Продолжайте редактирование или нажмите 'Сохранить все изменения'",
+ parse_mode='HTML',
+ reply_markup=create_edit_menu_markup()
+ )
+
+ # Показываем предпросмотр
+ show_preview(message.chat.id, user_id, bot, user_edit_states)
+
+
+def show_preview(chat_id: int, user_id: int, bot, user_edit_states):
+ """Показывает предпросмотр изменений"""
+ if user_id not in user_edit_states:
+ return
+
+ edit_state = user_edit_states[user_id]
+ studio_id = edit_state['studio_id']
+
+ # Получаем текущие данные
+ conn = get_connection()
+ cursor = conn.cursor()
+ cursor.execute("SELECT name, description, contacts FROM studios WHERE id = ?", (studio_id,))
+ current_data = cursor.fetchone()
+ conn.close()
+
+ if not current_data:
+ return
+
+ current_name, current_description, current_contacts = current_data
+
+ # Применяем изменения из черновика
+ preview_name = edit_state['changes'].get('name', current_name)
+ preview_description = edit_state['changes'].get('description', current_description)
+ preview_contacts = edit_state['changes'].get('contacts', current_contacts)
+
+ message = (
+ "Предпросмотр изменений:\n\n"
+ f"Название: {preview_name if preview_name else '❌ Не указано'}\n"
+ f"Описание:\n{preview_description if preview_description else '❌ Не указано'}\n"
+ f"Контакты:\n{preview_contacts if preview_contacts else '❌ Не указаны'}\n\n"
+ f"Изменено полей: {len([k for k in edit_state['changes'].keys() if not k.startswith('old_')])}"
+ )
+
+ bot.send_message(chat_id, message, parse_mode='HTML')
+
+
+def handle_save_all_changes(message, bot, user_edit_states, create_main_menu_markup):
+ """Сохранение всех изменений в БД"""
+ user_id = message.from_user.id
+
+ if user_id not in user_edit_states:
+ bot.send_message(message.chat.id, "❌ Нет изменений для сохранения")
+ return
+
+ edit_state = user_edit_states[user_id]
+
+ # Фильтруем только настоящие изменения (не старые значения)
+ real_changes = {k: v for k, v in edit_state['changes'].items() if not k.startswith('old_')}
+
+ if not real_changes:
+ bot.send_message(message.chat.id, "❌ Нет изменений для сохранения")
+ return
+
+ # Сохраняем изменения в БД
+ conn = get_connection()
+ cursor = conn.cursor()
+
+ try:
+ # Формируем SQL запрос
+ set_clauses = []
+ params = []
+
+ for field, value in real_changes.items():
+ set_clauses.append(f"{field} = ?")
+ params.append(value)
+
+ params.append(edit_state['studio_id'])
+
+ update_query = f"""
+ UPDATE studios
+ SET {', '.join(set_clauses)}
+ WHERE id = ?
+ """
+
+ cursor.execute(update_query, params)
+ conn.commit()
+
+ # Логируем изменения
+ logger.info(f"Студия {edit_state['studio_id']} обновлена пользователем {user_id}")
+
+ bot.send_message(
+ message.chat.id,
+ "✅ Все изменения сохранены!",
+ parse_mode='HTML',
+ reply_markup=create_main_menu_markup(user_id)
+ )
+
+ except Exception as e:
+ conn.rollback()
+ logger.error(f"Ошибка при сохранении изменений студии: {e}")
+ bot.send_message(
+ message.chat.id,
+ f"❌ Ошибка при сохранении:\n{str(e)}",
+ parse_mode='HTML'
+ )
+ finally:
+ conn.close()
+
+ # Очищаем состояние редактирования
+ del user_edit_states[user_id]
+
+
+def handle_edit_cancel(message, bot, user_edit_states, create_main_menu_markup):
+ """Отмена редактирования"""
+ user_id = message.from_user.id
+
+ if user_id in user_edit_states:
+ del user_edit_states[user_id]
+
+ bot.send_message(
+ message.chat.id,
+ "❌ Редактирование отменено",
+ parse_mode='HTML',
+ reply_markup=create_main_menu_markup(user_id)
+ )
\ No newline at end of file
diff --git a/.venv/Studio_editor(headStudFunc).py b/.venv/Studio_editor(headStudFunc).py
new file mode 100644
index 0000000..709eaea
--- /dev/null
+++ b/.venv/Studio_editor(headStudFunc).py
@@ -0,0 +1,184 @@
+class StudioEditor:
+ """
+ Класс для управления процессом редактирования студии.
+
+ Вместо сложного FSM используем простой словарь для хранения состояний.
+ Каждый пользователь имеет свой набор данных.
+ """
+
+ def __init__(self):
+ """
+ Инициализация редактора студии.
+
+ Создаем словарь для хранения состояния пользователей:
+ - Ключ: user_id (ID пользователя в Telegram)
+ - Значение: словарь с данными и этапом редактирования
+ """
+ self.user_sessions = {}
+ # Пример структуры данных для одного пользователя:
+ # {
+ # 'step': 'waiting_for_name', # Текущий этап
+ # 'data': { # Собранные данные
+ # 'name': 'Название студии',
+ # 'image': b'...', # Бинарные данные изображения
+ # 'description': 'Описание'
+ # }
+ # }
+
+ def start_editing(self, user_id):
+ """
+ Начинает процесс редактирования для пользователя.
+
+ Args:
+ user_id (int): ID пользователя в Telegram
+
+ Returns:
+ str: Сообщение для отправки пользователю
+ """
+ # Сбрасываем предыдущую сессию, если была
+ self.user_sessions[user_id] = {
+ 'step': 'waiting_for_name', # Первый шаг - ждем название
+ 'data': {} # Пока данных нет
+ }
+
+ return "🎬 Редактирование студии\n\nШаг 1 из 3\n📝 Введите новое название студии:"
+
+ def handle_message(self, user_id, message_text):
+ """
+ Обрабатывает текстовое сообщение от пользователя.
+
+ Args:
+ user_id (int): ID пользователя
+ message_text (str): Текст сообщения
+
+ Returns:
+ tuple: (сообщение для пользователя, этап завершен?)
+ """
+ # Проверяем, есть ли активная сессия у пользователя
+ if user_id not in self.user_sessions:
+ return "❌ Сначала начните редактирование студии!", False
+
+ session = self.user_sessions[user_id]
+ current_step = session['step']
+
+ # Обрабатываем в зависимости от текущего этапа
+ if current_step == 'waiting_for_name':
+ # Этап 1: Получаем название студии
+ if len(message_text.strip()) < 2:
+ return "❌ Название слишком короткое! Введите название длиннее 2 символов.", False
+
+ # Сохраняем название
+ session['data']['name'] = message_text.strip()
+ session['step'] = 'waiting_for_image' # Переходим к следующему этапу
+
+ return ("✅ Название сохранено!\n\n"
+ "Шаг 2 из 3\n"
+ "🖼️ Отправьте новое промо-изображение:\n\n"
+ "Просто отправьте фото в чат ⤵️", False)
+
+ elif current_step == 'waiting_for_description':
+ # Этап 3: Получаем описание студии
+ session['data']['description'] = message_text.strip()
+
+ # Завершаем процесс редактирования
+ result = self._finish_editing(user_id)
+ return result, True # True означает, что процесс завершен
+
+ else:
+ # Если этап не предусматривает текстовый ввод
+ return "⏳ Пожалуйста, отправьте изображение или завершите текущий этап.", False
+
+ def handle_image(self, user_id, image_bytes):
+ """
+ Обрабатывает изображение от пользователя.
+
+ Args:
+ user_id (int): ID пользователя
+ image_bytes (bytes): Бинарные данные изображения
+
+ Returns:
+ tuple: (сообщение для пользователя, этап завершен?)
+ """
+ # Проверяем, есть ли активная сессия
+ if user_id not in self.user_sessions:
+ return "❌ Сначала начните редактирование студии!", False
+
+ session = self.user_sessions[user_id]
+
+ # Проверяем, что мы на этапе ожидания изображения
+ if session['step'] != 'waiting_for_image':
+ return "❌ Сейчас не время отправлять изображение!", False
+
+ # Сохраняем изображение
+ session['data']['image'] = image_bytes
+ session['step'] = 'waiting_for_description' # Переходим к описанию
+
+ return ("✅ Изображение сохранено!\n\n"
+ "Шаг 3 из 3\n"
+ "📝 Введите новое описание студии:", False)
+
+ def _finish_editing(self, user_id):
+ """
+ Завершает процесс редактирования.
+
+ Args:
+ user_id (int): ID пользователя
+
+ Returns:
+ str: Итоговое сообщение с собранными данными
+ """
+ if user_id not in self.user_sessions:
+ return "❌ Нет активной сессии редактирования!"
+
+ session = self.user_sessions[user_id]
+ data = session['data']
+
+ # Формируем итоговое сообщение
+ result_message = (
+ "✅ Редактирование завершено!\n\n"
+ f"Название: {data.get('name', 'Не указано')}\n"
+ f"Описание: {data.get('description', 'Не указано')}\n"
+ f"Изображение: {'Загружено' if 'image' in data else 'Не загружено'}\n\n"
+ "Данные готовы для сохранения в базу данных."
+ )
+
+ # Здесь будет код для сохранения в БД (позже добавим)
+ # save_to_database(data)
+
+ # Очищаем сессию пользователя
+ del self.user_sessions[user_id]
+
+ return result_message
+
+ def cancel_editing(self, user_id):
+ """
+ Отменяет процесс редактирования.
+
+ Args:
+ user_id (int): ID пользователя
+
+ Returns:
+ str: Сообщение об отмене
+ """
+ if user_id in self.user_sessions:
+ del self.user_sessions[user_id]
+
+ return "❌ Редактирование отменено."
+
+ def get_user_step(self, user_id):
+ """
+ Получает текущий этап пользователя.
+
+ Args:
+ user_id (int): ID пользователя
+
+ Returns:
+ str or None: Текущий этап или None если сессии нет
+ """
+ if user_id in self.user_sessions:
+ return self.user_sessions[user_id]['step']
+ return None
+
+
+# Создаем глобальный экземпляр редактора
+studio_editor = StudioEditor()
\ No newline at end of file
diff --git a/.venv/applications_manager.py b/.venv/applications_manager.py
new file mode 100644
index 0000000..63751e8
--- /dev/null
+++ b/.venv/applications_manager.py
@@ -0,0 +1,132 @@
+import sqlite3
+from datetime import datetime, timedelta
+from typing import List, Dict
+from telebot import types
+
+
+class ApplicationsManager:
+ def __init__(self, bot):
+ self.bot = bot
+ self.db_path = "student_studios_bot (1).db"
+
+ def get_connection(self):
+ return sqlite3.connect(self.db_path, check_same_thread=False)
+
+ def get_user_applications(self, telegram_id: int) -> List[Dict]:
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ try:
+ # 1. Находим user_id по telegram_id
+ cursor.execute("SELECT user_id FROM users WHERE telegram_id = ?", (telegram_id,))
+ user_result = cursor.fetchone()
+
+ if not user_result:
+ print(f"Пользователь с telegram_id {telegram_id} не найден!")
+ return []
+
+ user_id = user_result[0]
+
+ # 2. Ищем заявки по user_id
+ cursor.execute("""
+ SELECT application_id, studio_id, status, created_at
+ FROM applications
+ WHERE user_id = ?
+ ORDER BY created_at DESC
+ """, (user_id,))
+
+ rows = cursor.fetchall()
+ print(f"Найдено заявок для пользователя с telegram_id {telegram_id} (user_id={user_id}): {len(rows)}")
+
+ applications = []
+
+ for row in rows:
+ studio_name = f"Студия #{row[1]}"
+ try:
+ cursor.execute("SELECT name FROM studios WHERE studio_id = ?", (row[1],))
+ studio_result = cursor.fetchone()
+ if studio_result:
+ studio_name = studio_result[0]
+ except:
+ pass
+
+ applications.append({
+ 'id': row[0],
+ 'studio_id': row[1],
+ 'status': row[2],
+ 'created_at': row[3],
+ 'studio_name': studio_name
+ })
+
+ return applications
+
+ except Exception as e:
+ print(f"Ошибка при получении заявок: {e}")
+ import traceback
+ traceback.print_exc()
+ return []
+ finally:
+ conn.close()
+
+ def show_user_applications(self, chat_id: int, user_id: int):
+ applications = self.get_user_applications(user_id)
+
+ if not applications:
+ self.bot.send_message(
+ chat_id,
+ "У вас пока нет заявок",
+ reply_markup=markup
+ )
+ return
+
+ message = "Ваши заявки:\n\n"
+
+ for i, app in enumerate(applications, 1):
+ try:
+ created_str = app['created_at']
+
+ if ' ' in created_str:
+ date_part, time_part = created_str.split(' ')
+ time_without_seconds = ':'.join(time_part.split(':')[:2])
+ date_str = f"{date_part} {time_without_seconds}"
+ else:
+ date_str = created_str
+
+ except Exception as e:
+ print(f"Ошибка форматирования даты {app['created_at']}: {e}")
+ date_str = app['created_at']
+
+ status_text = {
+ 'pending': '⏳ На рассмотрении',
+ 'approved': '✅ Принята',
+ 'rejected': '❌ Отклонена'
+ }.get(app['status'], app['status'])
+
+ message += f"{i}. {app['studio_name']}\n"
+ message += f"Статус заявки: {status_text}\n"
+ message += f"Дата создания: {date_str}\n\n"
+
+ self.bot.send_message(
+ chat_id,
+ message,
+ parse_mode='HTML',
+ )
+
+ def cleanup_old_applications(self):
+ conn = self.get_connection()
+ cursor = conn.cursor()
+
+ try:
+ week_ago = datetime.now() - timedelta(days=7)
+ week_ago_str = week_ago.strftime('%Y-%m-%d %H:%M:')
+
+ cursor.execute("""
+ DELETE FROM applications
+ WHERE status IN ('approved', 'rejected') AND updated_at < ?
+ """, (week_ago_str,))
+
+ conn.commit()
+ except:
+ conn.rollback()
+ finally:
+ conn.close()
\ No newline at end of file
diff --git a/.venv/auth.py b/.venv/auth.py
new file mode 100644
index 0000000..9ffafd5
--- /dev/null
+++ b/.venv/auth.py
@@ -0,0 +1,44 @@
+import sqlite3
+import logging
+from typing import Optional
+
+logger = logging.getLogger(__name__)
+
+def get_connection(db_path: str = "student_studios_bot (1).db") -> sqlite3.Connection:
+ """Возвращает соединение с БД"""
+ return sqlite3.connect(db_path, check_same_thread=False)
+
+def get_user_role(username: str) -> str:
+ """Получение роли пользователя из базы данных"""
+ if not username:
+ return 'student'
+
+ try:
+ with get_connection() as conn:
+ cursor = conn.cursor()
+ cursor.execute("SELECT role FROM users WHERE username = ?", (f"@{username}",))
+ result = cursor.fetchone()
+ return result[0] if result else 'student'
+ except Exception as e:
+ logger.error(f"Database error getting role: {e}")
+ return 'student'
+
+def get_studio_id_for_head(user_id: int) -> Optional[int]:
+ """Получает ID студии, за которой закреплен руководитель"""
+ try:
+ with get_connection() as conn:
+ cursor = conn.cursor()
+ cursor.execute("SELECT studio_id FROM studios WHERE head_user_id = ?", (user_id,))
+ result = cursor.fetchone()
+ return result[0] if result else None
+ except Exception as e:
+ logger.error(f"Error getting studio for head {user_id}: {e}")
+<<<<<<< HEAD
+ return None
+=======
+ return None
+
+
+
+
+>>>>>>> 2c58de2511d2324c74225b79a43c74abe47df76c
diff --git a/.venv/bd2.db b/.venv/bd2.db
new file mode 100644
index 0000000..8a157f7
Binary files /dev/null and b/.venv/bd2.db differ
diff --git a/.venv/catalog.py b/.venv/catalog.py
new file mode 100644
index 0000000..9483582
--- /dev/null
+++ b/.venv/catalog.py
@@ -0,0 +1,63 @@
+import telebot
+from telebot import types
+from typing import List, Tuple
+import logging
+from database import load_active_studios
+
+logger = logging.getLogger(__name__)
+
+
+def show_studio_catalog(bot, chat_id: int):
+ """Показывает каталог студий"""
+ studios = load_active_studios()
+
+ if not studios:
+ bot.send_message(chat_id, 'На данный момент активных студий нет.')
+ return
+
+ markup = types.InlineKeyboardMarkup()
+ for studio_id, name in studios:
+ btn = types.InlineKeyboardButton(name, callback_data=f'info:{studio_id}')
+ markup.row(btn)
+
+ bot.send_message(chat_id, 'Нажми на студию для просмотра:', reply_markup=markup)
+
+
+def show_studio_info(bot, call):
+ """Показывает информацию о студии"""
+ studio_id = int(call.data.split(':')[1])
+
+ try:
+ from database import get_connection
+ with get_connection() as conn:
+ cursor = conn.cursor()
+ cursor.execute("""
+ SELECT name, description, contacts
+ FROM studios
+ WHERE studio_id = ?
+ """, (studio_id,))
+ result = cursor.fetchone()
+
+ if result:
+ name, description, contacts = result
+ text = f"{name}\n\n{description or 'Описание скоро появится.'}"
+
+ if contacts:
+ text += f"\n\nКонтакты:\n{contacts}"
+
+ markup = types.InlineKeyboardMarkup()
+ btn_apply = types.InlineKeyboardButton("📝 Подать заявку", callback_data=f'apply:{studio_id}')
+ markup.row(btn_apply)
+
+ bot.edit_message_text(
+ text,
+ call.message.chat.id,
+ call.message.message_id,
+ parse_mode='html',
+ reply_markup=markup
+ )
+ else:
+ bot.answer_callback_query(call.id, "Студия не найдена")
+ except Exception as e:
+ logger.error(f"Error showing studio info: {e}")
+ bot.answer_callback_query(call.id, "Ошибка при загрузке информации")
\ No newline at end of file
diff --git a/.venv/database.py b/.venv/database.py
new file mode 100644
index 0000000..afb6401
--- /dev/null
+++ b/.venv/database.py
@@ -0,0 +1,114 @@
+import sqlite3
+from datetime import datetime
+from typing import Optional, Dict, Any
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+def get_connection(db_path: str = "student_studios_bot (1).db") -> sqlite3.Connection:
+ """Возвращает соединение с БД"""
+ return sqlite3.connect(db_path, check_same_thread=False)
+
+
+def get_user_by_telegram_id(telegram_id: int) -> Optional[Dict[str, Any]]:
+ """Получает данные пользователя по Telegram ID"""
+ try:
+ with get_connection() as conn:
+ cursor = conn.cursor()
+ cursor.execute("""
+ SELECT full_name, student_group, phone_number, email
+ FROM users
+ WHERE telegram_id = ?
+ """, (telegram_id,))
+ result = cursor.fetchone()
+
+ if result:
+ return {
+ 'full_name': result[0] or '',
+ 'student_group': result[1] or '',
+ 'phone_number': result[2] or '',
+ 'email': result[3] or ''
+ }
+ except Exception as e:
+ logger.error(f"Error getting user data: {e}")
+ return None
+
+
+def save_application_to_db(telegram_id: int, studio_id: int, answers: dict,
+ user_first_name: str = None, user_last_name: str = None) -> Optional[int]:
+ """Сохраняет заявку в БД"""
+ try:
+ with get_connection() as conn:
+ cursor = conn.cursor()
+
+ # Получаем или создаем пользователя
+ cursor.execute("SELECT user_id FROM users WHERE telegram_id = ?", (telegram_id,))
+ user_result = cursor.fetchone()
+
+ if user_result:
+ user_id = user_result[0]
+ else:
+ # Получаем ФИО из ответов или Telegram
+ full_name = answers.get('full_name')
+ if not full_name:
+ full_name = f"{user_first_name or ''} {user_last_name or ''}".strip()
+ if not full_name:
+ full_name = "Не указано"
+
+ cursor.execute("""
+ INSERT INTO users (telegram_id, full_name, created_at)
+ VALUES (?, ?, ?)
+ """, (telegram_id, full_name, datetime.now()))
+ user_id = cursor.lastrowid
+
+ # Генерируем сводку
+ from users_collector import QuestionnaireBuilder
+ summary = QuestionnaireBuilder.generate_summary(studio_id, answers)
+
+ # Сохраняем заявку
+ cursor.execute("""
+ INSERT INTO applications (user_id, studio_id, summary, status, created_at)
+ VALUES (?, ?, ?, ?, ?)
+ """, (user_id, studio_id, summary, 'pending', datetime.now()))
+
+ application_id = cursor.lastrowid
+
+ # Сохраняем ответы на вопросы
+ for question_id, answer in answers.items():
+ from questions import get_question_obj
+ q_obj = get_question_obj(question_id)
+ question_text = q_obj.text if q_obj else question_id
+
+ cursor.execute("""
+ INSERT INTO application_answers
+ (application_id, question_id, question_text, answer_text)
+ VALUES (?, ?, ?, ?)
+ """, (application_id, question_id, question_text, answer))
+
+ conn.commit()
+ return application_id
+
+ except Exception as e:
+ logger.error(f"Error saving application: {e}")
+ return None
+
+
+def load_active_studios():
+ """Загрузка списка активных студий"""
+ try:
+ with get_connection() as conn:
+ cursor = conn.cursor()
+ cursor.execute("SELECT studio_id, name FROM studios WHERE is_active = 1 ORDER BY studio_id")
+ return cursor.fetchall()
+ except Exception as e:
+ logger.error(f"Error loading studios: {e}")
+<<<<<<< HEAD
+ return []
+=======
+ return []
+
+
+
+
+>>>>>>> 2c58de2511d2324c74225b79a43c74abe47df76c
diff --git a/.venv/questionnaire_flow.py b/.venv/questionnaire_flow.py
new file mode 100644
index 0000000..7c2d72d
--- /dev/null
+++ b/.venv/questionnaire_flow.py
@@ -0,0 +1,462 @@
+import telebot
+from telebot import types
+from typing import Dict, Any
+import logging
+<<<<<<< HEAD
+from datetime import datetime
+
+from users_collector import QuestionnaireBuilder
+=======
+
+from users_collector import QuestionnaireBuilder # Этот импорт должен быть
+>>>>>>> 2c58de2511d2324c74225b79a43c74abe47df76c
+from questions import get_question_obj
+from database import save_application_to_db
+
+logger = logging.getLogger(__name__)
+
+
+class QuestionnaireFlow:
+ """Управление процессом заполнения анкеты"""
+
+ def __init__(self, bot):
+ self.bot = bot
+ self.user_states = {}
+
+ def start_questionnaire(self, call, studio_id: int):
+ """Начало анкеты для выбранной студии"""
+ user_id = call.from_user.id
+
+ studio_config = QuestionnaireBuilder.get_questionnaire_for_studio(studio_id)
+ if not studio_config:
+ self.bot.answer_callback_query(call.id, "Анкета для этой студии не настроена")
+ return False
+
+ self.user_states[user_id] = {
+ 'state': 'questionnaire',
+ 'studio_id': studio_id,
+ 'studio_name': studio_config.get('name', 'Студия'),
+ 'current_question_index': 0,
+ 'answers': {},
+ 'questionnaire': studio_config
+ }
+
+ # Получаем данные пользователя для автозаполнения
+ user_data = self._get_user_data(user_id)
+ self.user_states[user_id]['user_data'] = user_data
+
+ if QuestionnaireBuilder.can_autofill(studio_id, user_data):
+ self._offer_autofill(call, studio_id, user_data)
+ else:
+ self._start_manual_filling(call, studio_id)
+ return True
+
+ def _get_user_data(self, user_id: int) -> Dict[str, Any]:
+ """Получает данные пользователя из БД"""
+ from database import get_user_by_telegram_id
+ user = get_user_by_telegram_id(user_id)
+<<<<<<< HEAD
+=======
+
+ if user:
+ logger.info(f"Найдены данные пользователя {user_id}: {user}")
+ else:
+ logger.info(f"Данные пользователя {user_id} не найдены в БД")
+
+>>>>>>> 2c58de2511d2324c74225b79a43c74abe47df76c
+ return user if user else {}
+
+ def _offer_autofill(self, call, studio_id: int, user_data: Dict[str, Any]):
+ """Предлагает автозаполнение"""
+ autofill_fields = QuestionnaireBuilder.get_autofill_fields(studio_id)
+ autofill_info = []
+
+<<<<<<< HEAD
+ for field in autofill_fields:
+ value = user_data.get(field, '')
+ if value:
+=======
+ # Логируем для отладки
+ logger.info(f"Поля для автозаполнения: {autofill_fields}")
+ logger.info(f"Данные пользователя: {user_data}")
+
+ for field in autofill_fields:
+ value = user_data.get(field, '')
+ logger.info(f"Поле {field}: значение '{value}', тип {type(value)}")
+ if value and str(value).strip():
+>>>>>>> 2c58de2511d2324c74225b79a43c74abe47df76c
+ field_name = {
+ 'full_name': 'ФИО',
+ 'student_group': 'Группа',
+ 'phone_number': 'Телефон',
+ 'email': 'Email'
+ }.get(field, field)
+ autofill_info.append(f"{field_name}: {value}")
+
+ studio_name = QuestionnaireBuilder.get_studio_name(studio_id)
+ message = f"*Анкета для студии: {studio_name}*\n\n"
+
+ if autofill_info:
+ message += "Найдены ваши данные из предыдущих анкет:\n"
+ for info in autofill_info:
+ message += f"• {info}\n"
+ message += "\nХотите использовать их для автозаполнения?"
+ else:
+<<<<<<< HEAD
+ message += "Хотите использовать основную информацию для автозаполнения?"
+
+ markup = types.InlineKeyboardMarkup()
+ markup.row(
+ types.InlineKeyboardButton("Использовать мои данные", callback_data='use_autofill'),
+ types.InlineKeyboardButton("Заполнить вручную", callback_data='manual_fill')
+ )
+=======
+ message += "К сожалению, данных для автозаполнения не найдено.\nХотите заполнить анкету вручную?"
+
+ markup = types.InlineKeyboardMarkup()
+ if autofill_info:
+ markup.row(
+ types.InlineKeyboardButton("Использовать мои данные", callback_data='use_autofill'),
+ types.InlineKeyboardButton("Заполнить вручную", callback_data='manual_fill')
+ )
+ else:
+ markup.row(
+ types.InlineKeyboardButton("Заполнить вручную", callback_data='manual_fill')
+ )
+>>>>>>> 2c58de2511d2324c74225b79a43c74abe47df76c
+
+ self.bot.edit_message_text(
+ message,
+ call.message.chat.id,
+ call.message.message_id,
+ parse_mode='Markdown',
+ reply_markup=markup
+ )
+
+ def _start_manual_filling(self, call, studio_id: int):
+ """Начинает ручное заполнение анкеты"""
+ user_id = call.from_user.id
+ studio_name = QuestionnaireBuilder.get_studio_name(studio_id)
+
+ self.user_states[user_id]['state'] = 'answering'
+
+ self.bot.edit_message_text(
+ f"Начинаем заполнение анкеты для студии *{studio_name}*.\n\n"
+ f"Пожалуйста, ответьте на несколько вопросов:",
+ call.message.chat.id,
+ call.message.message_id,
+ parse_mode='Markdown'
+ )
+
+ self._ask_next_question(call.message.chat.id, user_id)
+
+ def _ask_next_question(self, chat_id: int, user_id: int):
+ """Задает следующий вопрос"""
+ if user_id not in self.user_states:
+ return
+
+ state = self.user_states[user_id]
+<<<<<<< HEAD
+ studio_id = state['studio_id']
+ question_ids = QuestionnaireBuilder.get_question_sequence(studio_id)
+ current_index = state.get('current_question_index', 0)
+
+=======
+ question_ids = state['questionnaire'].get('questions', [])
+ current_index = state.get('current_question_index', 0)
+
+ # Пропускаем уже заполненные вопросы
+ while current_index < len(question_ids):
+ qid = question_ids[current_index]
+ # Если вопрос уже заполнен (например, через автозаполнение), пропускаем его
+ if qid in state['answers']:
+ logger.info(f"Вопрос {qid} уже заполнен, пропускаем")
+ current_index += 1
+ state['current_question_index'] = current_index
+ else:
+ break
+
+>>>>>>> 2c58de2511d2324c74225b79a43c74abe47df76c
+ # Проверяем, не заполнили ли мы все вопросы
+ if current_index >= len(question_ids):
+ self._show_summary(chat_id, user_id)
+ return
+
+ # Получаем текущий вопрос
+ qid = question_ids[current_index]
+ q_obj = get_question_obj(qid)
+
+ if not q_obj:
+ # Пропускаем несуществующий вопрос
+ state['current_question_index'] += 1
+ self._ask_next_question(chat_id, user_id)
+ return
+
+ # Сохраняем текущий вопрос
+ state['current_question_id'] = qid
+
+<<<<<<< HEAD
+ # Отправляем вопрос
+ if q_obj.type == 'select' and q_obj.options:
+ # Для вопросов с выбором создаем клавиатуру
+ markup = types.ReplyKeyboardMarkup(resize_keyboard=True, one_time_keyboard=True)
+ for option in q_obj.options:
+ markup.add(types.KeyboardButton(option))
+ self.bot.send_message(chat_id, q_obj.text, reply_markup=markup)
+=======
+ logger.info(f"Задаем вопрос {qid}, тип: {q_obj.type}")
+
+ # Проверяем, может быть вопрос уже заполнен автозаполнением?
+ if qid in state['answers']:
+ logger.info(f"Вопрос {qid} уже имеет ответ: {state['answers'][qid]}")
+ state['current_question_index'] += 1
+ self._ask_next_question(chat_id, user_id)
+ return
+
+ # Отправляем вопрос с нужным типом клавиатуры
+ if q_obj.type == 'select' and q_obj.options:
+ # Для вопросов с выбором создаем INLINE клавиатуру
+ markup = types.InlineKeyboardMarkup(row_width=1)
+ for option in q_obj.options:
+ markup.add(types.InlineKeyboardButton(
+ option,
+ callback_data=f"select_answer:{qid}:{option}"
+ ))
+
+ self.bot.send_message(
+ chat_id,
+ q_obj.text,
+ reply_markup=markup
+ )
+
+ elif q_obj.type == 'boolean':
+ # Для boolean вопросов используем reply-клавиатуру
+ markup = types.ReplyKeyboardMarkup(resize_keyboard=True, one_time_keyboard=True)
+ markup.add("Да", "Нет")
+
+ self.bot.send_message(
+ chat_id,
+ q_obj.text,
+ reply_markup=markup
+ )
+
+>>>>>>> 2c58de2511d2324c74225b79a43c74abe47df76c
+ else:
+ # Для обычных вопросов без клавиатуры
+ self.bot.send_message(chat_id, q_obj.text)
+
+ def process_answer(self, message):
+<<<<<<< HEAD
+ """Обработка ответа на вопрос"""
+=======
+ """Обработка текстового ответа на вопрос"""
+>>>>>>> 2c58de2511d2324c74225b79a43c74abe47df76c
+ user_id = message.from_user.id
+
+ if user_id not in self.user_states:
+ return
+
+ state = self.user_states[user_id]
+ if state.get('state') != 'answering':
+ return
+
+ current_qid = state.get('current_question_id')
+ answer_text = message.text.strip()
+
+ if not current_qid:
+ return
+
+ # Получаем объект вопроса для валидации
+ q_obj = get_question_obj(current_qid)
+
+<<<<<<< HEAD
+=======
+ # Проверяем тип вопроса - если это select, то обрабатывается через callback
+ if q_obj and q_obj.type == 'select':
+ return # Пропускаем, select обрабатывается через callback
+
+>>>>>>> 2c58de2511d2324c74225b79a43c74abe47df76c
+ if q_obj:
+ is_valid, error_msg = q_obj.validate(answer_text)
+ if not is_valid:
+ self.bot.send_message(message.chat.id, f"❌ {error_msg}\n\nПопробуйте еще раз:")
+ return
+
+ # Сохраняем ответ
+ state['answers'][current_qid] = answer_text
+
+ # Увеличиваем индекс и задаем следующий вопрос
+ state['current_question_index'] += 1
+
+<<<<<<< HEAD
+ # Убираем клавиатуру для следующего вопроса
+ remove_markup = types.ReplyKeyboardRemove()
+ self.bot.send_message(message.chat.id, "Отлично! Следующий вопрос:", reply_markup=remove_markup)
+
+ self._ask_next_question(message.chat.id, user_id)
+=======
+ # Отправляем подтверждение для вопросов с reply-клавиатурой
+ if q_obj and q_obj.type == 'boolean':
+ # Сначала отправляем подтверждение с текстом
+ self.bot.send_message(
+ message.chat.id,
+ f"✅ Ответ принят: {answer_text}",
+ reply_markup=types.ReplyKeyboardRemove()
+ )
+
+ self._ask_next_question(message.chat.id, user_id)
+
+ def process_select_answer(self, call):
+ """Обработка выбора из inline-клавиатуры для select вопросов"""
+ user_id = call.from_user.id
+
+ if user_id not in self.user_states:
+ self.bot.answer_callback_query(call.id, "Сессия устарела")
+ return
+
+ state = self.user_states[user_id]
+ if state.get('state') != 'answering':
+ self.bot.answer_callback_query(call.id, "Неверное состояние")
+ return
+
+ # Разбираем callback_data: "select_answer:question_id:selected_option"
+ parts = call.data.split(':')
+ if len(parts) != 3:
+ self.bot.answer_callback_query(call.id, "Ошибка данных")
+ return
+
+ qid = parts[1]
+ selected_option = parts[2]
+
+ # Проверяем, что это текущий вопрос
+ current_qid = state.get('current_question_id')
+ if qid != current_qid:
+ self.bot.answer_callback_query(call.id, "Неверный вопрос")
+ return
+
+ # Сохраняем ответ
+ state['answers'][qid] = selected_option
+
+ # Увеличиваем индекс вопроса
+ state['current_question_index'] += 1
+
+ # Удаляем inline-клавиатуру и показываем выбранный вариант
+ self.bot.edit_message_text(
+ f"❓ {get_question_obj(qid).text if get_question_obj(qid) else 'Вопрос'}\n"
+ f"✅ Вы выбрали: {selected_option}",
+ call.message.chat.id,
+ call.message.message_id
+ )
+
+ # Задаем следующий вопрос
+ self._ask_next_question(call.message.chat.id, user_id)
+
+ def _show_summary(self, chat_id: int, user_id: int):
+ """Показывает сводку по заполненной анкете"""
+ if user_id not in self.user_states:
+ return
+
+ state = self.user_states[user_id]
+ studio_id = state['studio_id']
+ answers = state['answers']
+
+ # Генерируем сводку
+ summary = "📋 *Сводка вашей заявки*\n\n"
+
+ for qid, answer in answers.items():
+ q_obj = get_question_obj(qid)
+ question_text = q_obj.text if q_obj else qid
+ summary += f"*{question_text}*\n"
+ summary += f"{answer}\n\n"
+
+ # Отправляем сводку пользователю
+ self.bot.send_message(
+ chat_id,
+ summary,
+ parse_mode='Markdown'
+ )
+
+ # Подтверждение отправки
+ markup = types.InlineKeyboardMarkup()
+ markup.row(
+ types.InlineKeyboardButton("✅ Отправить заявку", callback_data='confirm_application'),
+ types.InlineKeyboardButton("✏️ Заполнить заново", callback_data='restart_questionnaire')
+ )
+
+ self.bot.send_message(
+ chat_id,
+ "Проверьте информацию выше. Все верно?",
+ reply_markup=markup
+ )
+
+ state['state'] = 'reviewing'
+
+ def _confirm_application(self, call):
+ """Подтверждение и сохранение заявки"""
+ user_id = call.from_user.id
+
+ if user_id not in self.user_states:
+ self.bot.answer_callback_query(call.id, "Сессия устарела")
+ return
+
+ state = self.user_states[user_id]
+
+ # Сохраняем заявку в БД
+ application_id = save_application_to_db(
+ telegram_id=user_id,
+ studio_id=state['studio_id'],
+ answers=state['answers'],
+ user_first_name=call.from_user.first_name,
+ user_last_name=call.from_user.last_name
+ )
+
+ if application_id:
+ # Удаляем состояние пользователя
+ if user_id in self.user_states:
+ del self.user_states[user_id]
+
+ # Сообщаем об успехе
+ self.bot.edit_message_text(
+ "✅ *Ваша заявка успешно отправлена!*\n\n"
+ f"Номер заявки: #{application_id}\n"
+ "С вами свяжутся в ближайшее время.",
+ call.message.chat.id,
+ call.message.message_id,
+ parse_mode='Markdown'
+ )
+
+ # Показываем главное меню
+ self._show_main_menu(call.message.chat.id, call.from_user)
+ else:
+ self.bot.edit_message_text(
+ "❌ Произошла ошибка при сохранении заявки. Попробуйте позже.",
+ call.message.chat.id,
+ call.message.message_id
+ )
+
+ def _restart_questionnaire(self, call):
+ """Перезапуск заполнения анкеты"""
+ user_id = call.from_user.id
+ if user_id in self.user_states:
+ state = self.user_states[user_id]
+ studio_id = state['studio_id']
+
+ # Начинаем заново
+ self.start_questionnaire(call, studio_id)
+
+ def _show_main_menu(self, chat_id: int, user):
+ """Показывает главное меню"""
+ markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
+ btn1 = types.KeyboardButton('Каталог студий')
+ markup.row(btn1)
+ btn2 = types.KeyboardButton('Мои заявки')
+ btn3 = types.KeyboardButton('Подать заявку')
+ markup.row(btn2, btn3)
+
+ self.bot.send_message(
+ chat_id,
+ 'Главное меню',
+ parse_mode='html',
+ reply_markup=markup
+ )
+>>>>>>> 2c58de2511d2324c74225b79a43c74abe47df76c
diff --git a/.venv/questionnew.py b/.venv/questionnew.py
new file mode 100644
index 0000000..2b34ebd
--- /dev/null
+++ b/.venv/questionnew.py
@@ -0,0 +1,123 @@
+from typing import Dict, Any, Callable, Optional
+import re
+
+
+class Question:
+ """Класс для хранения вопроса"""
+
+ def __init__(self,
+ question_id: str,
+ text: str,
+ question_type: str = 'text',
+ is_required: bool = True,
+ autofill_field: Optional[str] = None,
+ validation_func: Optional[Callable] = None,
+ options: Optional[list] = None):
+ self.id = question_id
+ self.text = text
+ self.type = question_type # text, phone, email, boolean, select
+ self.is_required = is_required
+ self.autofill_field = autofill_field
+ self.validation_func = validation_func
+ self.options = options or []
+
+ def validate(self, answer: str) -> tuple[bool, str]:
+ """Валидация ответа"""
+ answer = answer.strip()
+
+ if self.is_required and not answer:
+ return False, "Это обязательный вопрос"
+
+ if self.validation_func:
+ return self.validation_func(answer)
+
+ # Базовая валидация по типу
+ if self.type == 'phone_number':
+ phone_digits = re.sub(r'[^\d]', '', answer)
+ if len(phone_digits) < 10:
+ return False, "Введите корректный номер телефона"
+
+ elif self.type == 'email':
+ if '@' not in answer or '.' not in answer:
+ return False, "Введите корректный email"
+
+ elif self.type == 'boolean':
+ if answer.lower() not in ['да', 'нет']:
+ return False, "Ответьте 'да' или 'нет'"
+
+ elif self.type == 'select':
+ if answer not in self.options:
+ return False, f"Выберите вариант из списка: {', '.join(self.options)}"
+
+ return True, ""
+
+
+# ========== СЛОВАРЬ ВСЕХ ВОПРОСОВ ==========
+ALL_QUESTIONS = {
+ 'full_name': ("Укажите ваше ФИО (полностью):",
+ Question('full_name', 'ФИО', autofill_field='full_name')),
+
+ 'group': ("Укажите номер вашей группу:",
+ Question('group', 'Группа', autofill_field='student_group')),
+
+ 'phone_number': ("Укажите ваш номер телефона:",
+ Question('phone_number', 'Телефон', 'phone_number', autofill_field='phone_number')),
+
+ 'email': ("Укажите ваш email адрес:",
+ Question('email', 'Email', 'email', autofill_field='email')),
+
+ 'experience': ("Расскажите о вашем опыте в данной деятельности:",
+ Question('experience', 'Опыт в деятельности')),
+
+ 'motivation': ("Почему вы хотите вступить в эту студию?",
+ Question('motivation', 'Мотивация вступления')),
+
+ 'activeness': ("Готовы ли вы участвовать в мероприятиях студии? (да/нет)",
+ Question('activeness', 'Участие в мероприятиях', 'boolean')),
+
+ 'media': ("Какое направление вам интересно?\nВарианты: Фотостудия, Видеостудия, Студия дизайна, Журналистика и СММ",
+ Question('media', 'Направление медиацентра', 'select',
+ options=['Фотостудия', 'Видеостудия', 'Студия дизайна', 'Журналистика и СММ'])),
+
+ 'music_instrument': ("На каком инструменте вы играете (если играете)?",
+ Question('music_instrument', 'Музыкальный инструмент')),
+
+ 'singing_experience': ("Есть ли у вас опыт пения? Если да, опишите его:",
+ Question('singing_experience', 'Опыт пения')),
+
+ 'improvisation': ("Как вы относитесь к импровизации?",
+ Question('improvisation', 'Импровизация')),
+
+ 'tech_skills': ("Какими техническими навыками вы владеете?",
+ Question('tech_skills', 'Технические навыки')),
+
+ 'organizational_exp': ("Был ли у вас опыт организации мероприятий? Опишите:",
+ Question('organizational_exp', 'Организационный опыт')),
+
+ 'quote': ("Девиз по жизни или цитата, которая вас вдохновляет:",
+ Question('quote', 'Цитата')),
+
+ 'project_ideas': ("Есть ли у вас идеи для проектов в студии?",
+ Question('project_ideas', 'Идеи для проектов')),
+
+ 'leadership': ("Есть ли у вас лидерский опыт?",
+ Question('leadership', 'Лидерские качества')),
+
+ 'public_speaking': ("Как вы относитесь к публичным выступлениям?",
+ Question('public_speaking', 'Публичные выступления')),
+
+ 'feedback_source': ("Откуда вы узнали о нашей студии?",
+ Question('feedback_source', 'Источник информации')),
+}
+
+
+def get_question_handler(question_id: str) -> str:
+ """Возвращает текст вопроса по ID"""
+ result = ALL_QUESTIONS.get(question_id)
+ return result[0] if result else ""
+
+
+def get_question_obj(question_id: str) -> Optional[Question]:
+ """Возвращает объект вопроса по ID"""
+ result = ALL_QUESTIONS.get(question_id)
+ return result[1] if result else None
\ No newline at end of file
diff --git a/.venv/questions.py b/.venv/questions.py
new file mode 100644
index 0000000..2a0e45b
--- /dev/null
+++ b/.venv/questions.py
@@ -0,0 +1,177 @@
+from typing import Dict, Any, Callable, Optional
+import re
+
+
+class Question:
+ """Класс для хранения вопроса"""
+
+ def __init__(self,
+ question_id: str,
+ text: str, # Полный текст вопроса должен передаваться
+ question_type: str = 'text',
+ is_required: bool = True,
+ autofill_field: Optional[str] = None,
+ validation_func: Optional[Callable] = None,
+ options: Optional[list] = None):
+ self.id = question_id
+ self.text = text # Используйте переданный текст
+ self.type = question_type # text, phone, email, boolean, select
+ self.is_required = is_required
+ self.autofill_field = autofill_field
+ self.validation_func = validation_func
+ self.options = options or []
+
+ def validate(self, answer: str) -> tuple[bool, str]:
+ """Валидация ответа"""
+ answer = answer.strip()
+
+ if self.is_required and not answer:
+ return False, "Это обязательный вопрос"
+
+ if self.validation_func:
+ return self.validation_func(answer)
+
+ # Базовая валидация по типу
+ if self.type == 'phone_number':
+ phone_digits = re.sub(r'[^\d]', '', answer)
+ if len(phone_digits) < 10:
+ return False, "Введите корректный номер телефона"
+
+ elif self.type == 'email':
+ if '@' not in answer or '.' not in answer:
+ return False, "Введите корректный email"
+
+ elif self.type == 'boolean':
+ if answer.lower() not in ['да', 'нет']:
+ return False, "Ответьте 'да' или 'нет'"
+
+ elif self.type == 'select':
+ if answer not in self.options:
+ return False, f"Выберите вариант из списка: {', '.join(self.options)}"
+
+ return True, ""
+
+
+# ========== СЛОВАРЬ ВСЕХ ВОПРОСОВ ==========
+# ИСПРАВЛЕНО: Теперь сразу создаем объекты Question с полным текстом
+ALL_QUESTIONS = {
+ 'full_name': Question(
+ 'full_name',
+ 'Укажите ваше ФИО (полностью):',
+ autofill_field='full_name'
+ ),
+
+ 'group': Question(
+ 'group',
+ 'Укажите номер вашей группу:',
+ autofill_field='student_group'
+ ),
+
+ 'phone_number': Question(
+ 'phone_number',
+ 'Укажите ваш номер телефона:',
+ 'phone_number',
+ autofill_field='phone_number'
+ ),
+
+ 'email': Question(
+ 'email',
+ 'Укажите ваш email адрес:',
+ 'email',
+ autofill_field='email'
+ ),
+
+ 'experience': Question(
+ 'experience',
+ 'Расскажите о вашем опыте в данной деятельности:'
+ ),
+
+ 'motivation': Question(
+ 'motivation',
+ 'Почему вы хотите вступить в эту студию?'
+ ),
+
+ 'expectations': Question(
+ 'expectations',
+ 'Какие у вас ожидания от участия в студии?'
+ ),
+
+ 'activeness': Question(
+ 'activeness',
+ 'Готовы ли вы участвовать в мероприятиях студии? (да/нет)',
+ 'boolean'
+ ),
+
+ 'media': Question(
+ 'media',
+ 'Какое направление вам интересно?\nВарианты: Фотостуди, Видеостудия, Студия дизайна, Журналистика и СММ',
+ 'select',
+ options=['Фотостуди', 'Видеостудия', 'Студия дизайна', 'Журналистика и СММ']
+ ),
+
+ 'music_instrument': Question(
+ 'music_instrument',
+ 'На каком инструменте вы играете (если играете)?'
+ ),
+
+ 'singing_experience': Question(
+ 'singing_experience',
+ 'Есть ли у вас опыт пения? Если да, опишите его:'
+ ),
+
+ 'improvisation': Question(
+ 'improvisation',
+ 'Как вы относитесь к импровизации?'
+ ),
+
+ 'tech_skills': Question(
+ 'tech_skills',
+ 'Какими техническими навыками вы владеете?'
+ ),
+
+ 'organizational_exp': Question(
+ 'organizational_exp',
+ 'Был ли у вас опыт организации мероприятий? Опишите:'
+ ),
+
+ 'quote': Question(
+ 'quote',
+ 'Девиз по жизни или цитата, которая вас вдохновляет:'
+ ),
+
+ 'project_ideas': Question(
+ 'project_ideas',
+ 'Есть ли у вас идеи для проектов в студии?'
+ ),
+
+ 'leadership': Question(
+ 'leadership',
+ 'Есть ли у вас лидерский опыт?'
+ ),
+
+ 'why_choose_us': Question(
+ 'why_choose_us',
+ 'Почему вы выбрали именно нашу студию?'
+ ),
+
+ 'feedback_source': Question(
+ 'feedback_source',
+ 'Откуда вы узнали о нашей студии?'
+ ),
+
+ 'public_speaking': Question(
+ 'public_speaking',
+ 'Как вы относитесь к публичным выступлениям?'
+ ),
+}
+
+
+def get_question_handler(question_id: str) -> str:
+ """Возвращает текст вопроса по ID"""
+ q_obj = ALL_QUESTIONS.get(question_id)
+ return q_obj.text if q_obj else ""
+
+
+def get_question_obj(question_id: str) -> Optional[Question]:
+ """Возвращает объект вопроса по ID"""
+ return ALL_QUESTIONS.get(question_id)
diff --git a/.venv/studio_manager.py b/.venv/studio_manager.py
new file mode 100644
index 0000000..0950256
--- /dev/null
+++ b/.venv/studio_manager.py
@@ -0,0 +1,1115 @@
+import telebot
+from telebot import types
+import logging
+from typing import Dict, Any, List
+from database import get_pending_applications, get_application_details, update_application_status, get_student_by_user_id
+
+logging.basicConfig(
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ level=logging.INFO
+)
+logger = logging.getLogger(__name__)
+
+<<<<<<< HEAD
+=======
+class StudioManager:
+ """Класс для управления анкетами руководителем студии"""
+ # user_states: состояние каждого руководителя(какие заявки просматривает, текущая позиция)
+ def __init__(self, bot, db_path: str = "student_studios_bot (1).db"):
+
+ self.bot = bot
+ self.db_path = db_path
+
+ # Состояния пользователей
+ self.user_states = {}
+
+ # Кэш заявок для быстрого доступа
+ self.applications_cache = {}
+
+ def get_connection(self) -> sqlite3.Connection:
+ """Возвращает соединение с БД"""
+ return sqlite3.connect(self.db_path, check_same_thread=False)
+
+ def start_reviewing_applications_with_id(self, user_id: int, studio_id: int) -> bool:
+ """Начинает просмотр заявок для указанного пользователя и студии"""
+ try:
+ # Получаем новые заявки
+ applications = self.get_new_applications(studio_id)
+
+ if not applications:
+ return False
+
+ # Сохраняем состояние пользователя
+ self.user_states[user_id] = {
+ 'studio_id': studio_id,
+ 'applications': applications,
+ 'current_index': 0,
+ 'status': 'reviewing'
+ }
+
+ return True
+
+ except Exception as e:
+ logger.error(f"Error starting review for user {user_id}, studio {studio_id}: {e}")
+ return False
+
+ def get_studio_id_for_head(self, user_id: int) -> Optional[int]:
+ try:
+ with self.get_connection() as conn:
+ cursor = conn.cursor()
+ print(f"\n[DEBUG] Ищу студию для user_id: {user_id}")
+
+ cursor.execute("""
+ SELECT studio_id
+ FROM studios
+ WHERE head_user_id = ?
+ """, (user_id,))
+
+ result = cursor.fetchone()
+ print(f"[DEBUG] Результат запроса: {result}")
+
+ if result:
+ print(f"[DEBUG] Найдена студия ID: {result[0]}")
+ else:
+ print(f"[DEBUG] Студия не найдена для user_id: {user_id}")
+ # Покажем все студии для отладки
+ cursor.execute("SELECT studio_id, name, head_user_id FROM studios")
+ all_studios = cursor.fetchall()
+ print(f"[DEBUG] Все студии в базе: {all_studios}")
+
+ return result[0] if result else None
+ except Exception as e:
+ logger.error(f"Error getting studio for head {user_id}: {e}")
+ return None
+
+ def get_new_applications(self, studio_id: int) -> List[Tuple[int, str, str, str]]:
+ #Получает ВСЕ новые заявки
+ #Возвращает:Список кортежей (application_id, summary, full_name, created_at)
+>>>>>>> 2c58de2511d2324c74225b79a43c74abe47df76c
+
+class StudioManager:
+ def __init__(self, bot):
+ self.bot = bot
+ self.user_states = {} # {telegram_id: state_dict}
+
+
+ def start_reviewing_applications(self, telegram_id: int, studio_id: int, chat_id: int) -> bool:
+ """Начинает просмотр заявок руководителем"""
+ try:
+ # Получаем ожидающие заявки
+ applications = get_pending_applications(studio_id)
+
+ if not applications:
+ logger.info(f"Нет новых заявок для студии {studio_id}")
+ return False
+
+ # Сохраняем состояние пользователя
+ self.user_states[telegram_id] = {
+ 'studio_id': studio_id,
+ 'applications': applications,
+ 'current_index': 0,
+ 'chat_id': chat_id,
+ 'status': 'reviewing'
+ }
+
+ # Информационное сообщение
+ self.bot.send_message(
+ chat_id,
+ f"Начинаем просмотр заявок\n\n"
+ f"Всего заявок: {len(applications)}\n\n"
+ f"Заявки показываются по одной. Используйте кнопки для действий:\n",
+ parse_mode='HTML'
+ )
+
+ self.show_next_application(chat_id, telegram_id)
+ return True
+
+ except Exception as e:
+ logger.error(f"Ошибка начала просмотра для руководителя {telegram_id}: {e}")
+ return False
+
+
+ def show_next_application(self, chat_id: int, telegram_id: int):
+ """Показывает следующую заявку в очереди"""
+ if telegram_id not in self.user_states:
+ return
+
+ state = self.user_states[telegram_id]
+ applications = state['applications']
+ current_index = state['current_index']
+
+ if current_index >= len(applications):
+ # Все заявки обработаны
+ self.bot.send_message(
+ chat_id,
+ "🎉 Все заявки рассмотрены!\n\n"
+ "Новых заявок в очереди нет.",
+ parse_mode='HTML'
+ )
+
+ # Очищаем состояние
+ del self.user_states[telegram_id]
+ return
+
+ # Получаем текущую заявку
+ app_data = applications[current_index]
+ app_id = app_data['application_id']
+
+ # Получаем детальную информацию
+ details = get_application_details(app_id)
+
+ if not details:
+ # Если ошибка, пропускаем эту заявку
+ self.bot.send_message(
+ chat_id,
+ f"⚠️ Ошибка загрузки заявки #{app_id}. Пропускаем...",
+ parse_mode='HTML'
+ )
+ state['current_index'] += 1
+ self.show_next_application(chat_id, telegram_id)
+ return
+
+ # Сохраняем ID текущей заявки
+ state['current_application_id'] = app_id
+
+ # Форматируем сообщение
+ message_text = self.format_application_message(details, current_index + 1, len(applications))
+
+ # Создаем inline-клавиатуру
+ markup = types.InlineKeyboardMarkup(row_width=2)
+
+ markup.add(
+ types.InlineKeyboardButton("✅ Принять", callback_data=f"accept:{app_id}"),
+ types.InlineKeyboardButton("❌ Отклонить", callback_data=f"reject:{app_id}")
+ )
+
+ markup.add(
+ types.InlineKeyboardButton("📞 Связаться", callback_data=f"contact:{app_id}"),
+ types.InlineKeyboardButton("Прекратить", callback_data="stop_review")
+ )
+
+ # Отправляем сообщение
+ self.bot.send_message(
+ chat_id,
+ message_text,
+ parse_mode='HTML',
+ reply_markup=markup
+ )
+
+ def format_application_message(self, app_data: Dict[str, Any], current_num: int, total: int) -> str:
+ """Форматирует сообщение с информацией о заявке"""
+ message = f"Заявка #{app_data['application_id']}\n\n"
+ message += f"Студент: {app_data['full_name']}\n"
+
+ if app_data.get('student_group'):
+ message += f"Группа: {app_data['student_group']}\n"
+
+ if app_data.get('username'):
+ message += f"Telegram: @{app_data['username']}\n"
+
+ if app_data.get('phone_number'):
+ message += f"Телефон: {app_data['phone_number']}\n"
+
+ if app_data.get('email'):
+ message += f"Email: {app_data['email']}\n"
+
+ message += f"\nСводка:\n{app_data.get('summary', 'Нет сводки')}\n"
+
+ if app_data.get('answers'):
+ message += f"\nОтветы на вопросы:\n"
+ for i, (question, answer) in enumerate(app_data['answers'].items(), 1):
+ message += f"\n{i}. {question}\n{answer}\n"
+
+ message += f"\nПрогресс: {current_num} из {total}"
+
+ return message
+
+ def handle_application_action(self, call):
+ """Обрабатывает действия руководителя с заявками"""
+ telegram_id = call.from_user.id
+
+ if telegram_id not in self.user_states:
+ self.bot.answer_callback_query(call.id, "Сессия устарела. Начните просмотр заново.")
+ return
+
+ action_data = call.data
+ state = self.user_states[telegram_id]
+
+ if action_data == "stop_review":
+ # Прекращаем просмотр
+ self.bot.answer_callback_query(call.id, "Просмотр прекращен")
+
+ # Удаляем последнее сообщение с заявкой
+ try:
+ self.bot.delete_message(call.message.chat.id, call.message.message_id)
+ except:
+ pass
+
+ # Очищаем состояние
+ del self.user_states[telegram_id]
+
+ # Возвращаемся в главное меню
+ self.return_to_main_menu(call.message.chat.id)
+ return
+
+ # Извлекаем ID заявки и действие
+ parts = action_data.split(":")
+ if len(parts) != 2:
+ self.bot.answer_callback_query(call.id, "Ошибка обработки")
+ return
+
+ action, app_id_str = parts
+ app_id = int(app_id_str)
+
+ # Проверяем, что это текущая заявка
+ if state.get('current_application_id') != app_id:
+ self.bot.answer_callback_query(call.id, "Заявка уже обработана")
+ return
+
+ # Удаляем текущее сообщение
+ try:
+ self.bot.delete_message(call.message.chat.id, call.message.message_id)
+ except:
+ pass
+
+ # Обрабатываем действие
+ if action == "accept":
+ self.accept_application(call, app_id, telegram_id)
+ elif action == "reject":
+ self.reject_application(call, app_id, telegram_id)
+ elif action == "contact":
+ self.contact_student(call, app_id, telegram_id)
+
+ def accept_application(self, call, application_id: int, telegram_id: int):
+ """Принять заявку"""
+ try:
+ # Получаем данные заявки
+ app_data = get_application_details(application_id)
+ if not app_data:
+ self.bot.answer_callback_query(call.id, "❌ Ошибка при принятии")
+ return
+
+ # Обновляем статус
+ success = update_application_status(application_id, 'accepted')
+
+ if success:
+ # Отправляем уведомление студенту
+ self.send_notification_to_student(
+ app_data['student_telegram_id'],
+ app_data['full_name'],
+ app_data['studio_name'],
+ 'accepted'
+ )
+
+ self.bot.answer_callback_query(call.id, "✅ Заявка принята")
+
+ # Удаляем заявку из состояния и показываем следующую
+ self.remove_processed_application(telegram_id, application_id)
+ state = self.user_states[telegram_id]
+ self.show_next_application(state['chat_id'], telegram_id)
+ else:
+ self.bot.answer_callback_query(call.id, "❌ Ошибка при принятии")
+
+ except Exception as e:
+ logger.error(f"Ошибка принятия заявки {application_id}: {e}")
+ self.bot.answer_callback_query(call.id, "❌ Ошибка при принятии")
+
+ def reject_application(self, call, application_id: int, telegram_id: int):
+ """Отклонить заявку"""
+ try:
+
+ app_data = get_application_details(application_id)
+ if not app_data:
+ self.bot.answer_callback_query(call.id, "❌ Ошибка при отклонении")
+ return
+
+ success = update_application_status(application_id, 'rejected')
+
+ if success:
+ self.send_notification_to_student(
+ app_data['student_telegram_id'],
+ app_data['full_name'],
+ app_data['studio_name'],
+ 'rejected'
+ )
+
+ self.bot.answer_callback_query(call.id, "❌ Заявка отклонена")
+
+ # Удаляем заявку из состояния и показываем следующую
+ self.remove_processed_application(telegram_id, application_id)
+ state = self.user_states[telegram_id]
+ self.show_next_application(state['chat_id'], telegram_id)
+ else:
+ self.bot.answer_callback_query(call.id, "❌ Ошибка при отклонении")
+
+ except Exception as e:
+ logger.error(f"Ошибка отклонения заявки {application_id}: {e}")
+ self.bot.answer_callback_query(call.id, "❌ Ошибка при отклонении")
+
+ def contact_student(self, call, application_id: int, telegram_id: int):
+ """Показать контакты студента и переместить заявку в конец"""
+ try:
+ # Получаем данные заявки
+ app_data = get_application_details(application_id)
+ if not app_data:
+ self.bot.answer_callback_query(call.id, "❌ Ошибка")
+ return
+
+ # Формируем контактную информацию
+ contact_info = f"Контакты студента:\n\n"
+ contact_info += f"Имя: {app_data['full_name']}\n"
+
+ if app_data.get('username'):
+ contact_info += f"Telegram: @{app_data['username']}\n"
+
+ if app_data.get('phone_number'):
+ contact_info += f"Телефон: {app_data['phone_number']}\n"
+
+ if app_data.get('email'):
+ contact_info += f"Email: {app_data['email']}\n"
+
+ if app_data.get('student_group'):
+ contact_info += f"Группа: {app_data['student_group']}\n"
+
+
+ # Перемещаем заявку в конец очереди
+ self.move_application_to_end(telegram_id, application_id)
+ state = self.user_states[telegram_id]
+ self.show_next_application(state['chat_id'], telegram_id)
+
+ except Exception as e:
+ logger.error(f"Ошибка получения контактов для заявки {application_id}: {e}")
+ self.bot.answer_callback_query(call.id, "❌ Ошибка")
+
+ def send_notification_to_student(self, student_telegram_id: str, student_name: str, studio_name: str, action: str):
+ """Отправляет уведомление студенту"""
+ try:
+ if not student_telegram_id:
+ return
+
+ if action == 'accepted':
+ message = (
+ f"🎉 Поздравляем, {student_name}!\n\n"
+ f"Ваша заявка в студию '{studio_name}' одобрена!\n\n"
+ f"Руководитель свяжется с вами для дальнейших инструкций."
+ )
+ elif action == 'rejected':
+ message = (
+ f"Уважаемый(ая) {student_name}!\n\n"
+ f"К сожалению, ваша заявка в студию '{studio_name}' отклонена.\n\n"
+ f"Не расстраивайтесь! Вы можете подать заявку в другие студии."
+ )
+ else:
+ return
+
+ self.bot.send_message(
+ student_telegram_id,
+ message,
+ parse_mode='HTML'
+ )
+
+ logger.info(f"✅ Уведомление отправлено студенту {student_telegram_id}")
+
+ except Exception as e:
+ logger.error(f"Ошибка отправки уведомления студенту: {e}")
+
+
+ def remove_processed_application(self, telegram_id: int, application_id: int):
+ """Удаляет обработанную заявку из состояния пользователя"""
+ if telegram_id not in self.user_states:
+ return
+
+ state = self.user_states[telegram_id]
+ applications = state['applications']
+
+ new_applications = [
+ app for app in applications
+ if app['application_id'] != application_id
+ ]
+
+ state['applications'] = new_applications
+
+ if state['current_index'] >= len(new_applications):
+ state['current_index'] = 0
+
+ def move_application_to_end(self, telegram_id: int, application_id: int):
+ """Перемещает заявку в конец очереди"""
+ if telegram_id not in self.user_states:
+ return
+
+ state = self.user_states[telegram_id]
+ applications = state['applications']
+
+ # Находим заявку
+ target_app = None
+ other_apps = []
+
+ for app in applications:
+ if app['application_id'] == application_id:
+ target_app = app
+ else:
+ other_apps.append(app)
+
+ if target_app:
+ state['applications'] = other_apps + [target_app]
+ # Переходим к следующей заявке
+ state['current_index'] = 0
+
+ def return_to_main_menu(self, chat_id: int):
+ """Возвращает в главное меню руководителя"""
+ from head_of_studio import HeadOfStudio
+ from telebot import types
+
+ # Создаем меню руководителя
+ markup = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2)
+
+ btn_new_applications = types.KeyboardButton('Новые заявки')
+ btn_statistics = types.KeyboardButton('Статистика')
+ btn_edit_studio = types.KeyboardButton('Изменить информацию о студии')
+ btn_refresh = types.KeyboardButton('Обновить')
+
+ markup.row(btn_new_applications, btn_statistics)
+ markup.row(btn_edit_studio)
+ markup.row(btn_refresh)
+
+ self.bot.send_message(
+ chat_id,
+ parse_mode='HTML',
+ reply_markup=markup
+ )
+
+
+
+
+
+# import telebot
+# from telebot import types
+# import sqlite3
+# from typing import Dict, Any, Optional, List, Tuple
+# import logging
+# from datetime import datetime
+#
+# logging.basicConfig(
+# format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+# level=logging.INFO
+# )
+# logger = logging.getLogger(__name__)
+#
+# class StudioManager:
+# """Класс для управления анкетами руководителем студии"""
+# # user_states: состояние каждого руководителя(какие заявки просматривает, текущая позиция)
+# def __init__(self, bot, db_path: str = "student_studios_bot (1).db"):
+#
+# self.bot = bot
+# self.db_path = db_path
+#
+# # Состояния пользователей
+# self.user_states = {}
+#
+# # Кэш заявок для быстрого доступа
+# self.applications_cache = {}
+#
+# def get_connection(self) -> sqlite3.Connection:
+# """Возвращает соединение с БД"""
+# return sqlite3.connect(self.db_path, check_same_thread=False)
+#
+# def start_reviewing_applications_with_id(self, user_id: int, studio_id: int) -> bool:
+# """Начинает просмотр заявок для указанного пользователя и студии"""
+# try:
+# # Получаем новые заявки
+# applications = self.get_new_applications(studio_id)
+#
+# if not applications:
+# return False
+#
+# # Сохраняем состояние пользователя
+# self.user_states[user_id] = {
+# 'studio_id': studio_id,
+# 'applications': applications,
+# 'current_index': 0,
+# 'status': 'reviewing'
+# }
+#
+# return True
+#
+# except Exception as e:
+# logger.error(f"Error starting review for user {user_id}, studio {studio_id}: {e}")
+# return False
+#
+# def get_studio_id_for_head(self, user_id: int) -> Optional[int]:
+# try:
+# with self.get_connection() as conn:
+# cursor = conn.cursor()
+# print(f"\n[DEBUG] Ищу студию для user_id: {user_id}")
+#
+# cursor.execute("""
+# SELECT studio_id
+# FROM studios
+# WHERE head_user_id = ?
+# """, (user_id,))
+#
+# result = cursor.fetchone()
+# print(f"[DEBUG] Результат запроса: {result}")
+#
+# if result:
+# print(f"[DEBUG] Найдена студия ID: {result[0]}")
+# else:
+# print(f"[DEBUG] Студия не найдена для user_id: {user_id}")
+# # Покажем все студии для отладки
+# cursor.execute("SELECT studio_id, name, head_user_id FROM studios")
+# all_studios = cursor.fetchall()
+# print(f"[DEBUG] Все студии в базе: {all_studios}")
+#
+# return result[0] if result else None
+# except Exception as e:
+# logger.error(f"Error getting studio for head {user_id}: {e}")
+# return None
+#
+# def get_new_applications(self, studio_id: int) -> List[Tuple[int, str, str, str]]:
+# #Получает ВСЕ новые заявки
+# #Возвращает:Список кортежей (application_id, summary, full_name, created_at)
+#
+# try:
+# with self.get_connection() as conn:
+# cursor = conn.cursor()
+# cursor.execute("""
+# SELECT
+# a.application_id,
+# a.summary,
+# u.full_name,
+# a.created_at,
+# u.username,
+# u.phone_number,
+# u.email
+# FROM applications a
+# JOIN users u ON a.user_id = u.user_id
+# WHERE a.studio_id = ?
+# AND a.status = 'pending'
+# ORDER BY a.created_at ASC
+# """, (studio_id,))
+#
+# return cursor.fetchall()
+# except Exception as e:
+# logger.error(f"Error getting applications for studio {studio_id}: {e}")
+# return []
+#
+# def get_application_details(self, application_id: int) -> Optional[Dict[str, Any]]:
+# #Получает детальную информацию о заявке по ее айди
+# try:
+# with self.get_connection() as conn:
+# cursor = conn.cursor()
+#
+# # Получаем основную информацию о заявке
+# cursor.execute("""
+# SELECT
+# a.application_id, a.summary, a.status, a.created_at,
+# u.user_id, u.telegram_id, u.full_name, u.student_group,
+# u.phone_number, u.email, u.username,
+# s.name as studio_name
+# FROM applications a
+# JOIN users u ON a.user_id = u.user_id
+# JOIN studios s ON a.studio_id = s.studio_id
+# WHERE a.application_id = ?
+# """, (application_id,))
+#
+# result = cursor.fetchone()
+# if not result:
+# return None
+#
+# # Получаем ответы на вопросы
+# cursor.execute("""
+# SELECT question_text, answer_text
+# FROM application_answers
+# WHERE application_id = ?
+# ORDER BY answer_id
+# """, (application_id,))
+#
+# answers = cursor.fetchall()
+#
+# # Формируем словарь с данными
+# app_data = {
+# 'application_id': result[0],
+# 'summary': result[1],
+# 'status': result[2],
+# 'created_at': result[3],
+# 'user_id': result[4],
+# 'user_telegram_id': result[5],
+# 'full_name': result[6],
+# 'student_group': result[7],
+# 'phone_number': result[8],
+# 'email': result[9],
+# 'username': result[10],
+# 'studio_name': result[11],
+# 'answers': dict(answers) if answers else {}
+# }
+#
+# # Сохраняем в кэш
+# self.applications_cache[application_id] = app_data
+# return app_data
+#
+# except Exception as e:
+# logger.error(f"Error getting application details {application_id}: {e}")
+# return None
+#
+# def format_application_message(self, app_data: Dict[str, Any]) -> str:
+# """Форматирует сообщение с информацией о заявке"""
+# if not app_data:
+# return "❌ Ошибка загрузки заявки"
+#
+# message = f"Заявка #{app_data['application_id']}\n"
+# message += f"Студент: {app_data['full_name']}\n"
+# message += f"Группа: {app_data['student_group'] or 'Не указана'}\n"
+#
+# if app_data.get('username'):
+# message += f"Telegram: @{app_data['username']}\n"
+#
+# if app_data.get('phone_number'):
+# message += f"Телефон: {app_data['phone_number']}\n"
+#
+# if app_data.get('email'):
+# message += f"Email: {app_data['email']}\n\n"
+# else:
+# message += "\n"
+#
+# # Добавляем ответы на вопросы
+# if app_data.get('answers'):
+# message += "Ответы на вопросы:\n"
+# for i, (question, answer) in enumerate(app_data['answers'].items(), 1):
+# message += f"{i}. {question}\n{answer}\n\n"
+# elif app_data.get('summary'):
+# message += "Сводка анкеты:\n"
+# # Преобразуем Markdown в HTML для лучшего отображения
+# summary = app_data['summary'].replace('*', '').replace('_', '')
+# message += f"{summary}\n\n"
+#
+# # Показываем прогресс
+# if 'user_id' in self.user_states:
+# user_id = None
+# for uid, state in self.user_states.items():
+# if state.get('current_application_id') == app_data['application_id']:
+# user_id = uid
+# break
+#
+# if user_id:
+# state = self.user_states[user_id]
+# current_idx = state.get('current_index', 0) + 1
+# total = len(state.get('applications', []))
+# message += f"Прогресс: {current_idx} из {total}"
+#
+# return message
+#
+# def start_reviewing_applications(self, message):
+# """Начинает просмотр заявок руководителем"""
+# user_id = message.from_user.id
+#
+# # Получаем студию руководителя
+# studio_id = self.get_studio_id_for_head(user_id)
+# if not studio_id:
+# self.bot.send_message(
+# message.chat.id,
+# "❌ Вы не закреплены ни за одной студией.",
+# parse_mode='HTML'
+# )
+# return
+#
+# # Получаем новые заявки
+# applications = self.get_new_applications(studio_id)
+#
+# if not applications:
+# self.bot.send_message(
+# message.chat.id,
+# "Нет новых заявок для рассмотрения.",
+# parse_mode='HTML'
+# )
+# return
+#
+# # Получаем название студии для сообщения
+# studio_name = self.get_studio_name(studio_id)
+#
+# # Показываем информацию о количестве заявок
+# self.bot.send_message(
+# message.chat.id,
+# f"{studio_name}\n"
+# f"Найдено заявок: {len(applications)}\n\n",
+# parse_mode='HTML'
+# )
+#
+# # Сохраняем состояние пользователя
+# self.user_states[user_id] = {
+# 'studio_id': studio_id,
+# 'applications': applications,
+# 'current_index': 0,
+# 'status': 'reviewing'
+# }
+#
+# # Показываем первую заявку
+# self.show_next_application(message.chat.id, user_id)
+#
+# def show_next_application(self, chat_id: int, user_id: int):
+# """Показывает следующую заявку в очереди"""
+# if user_id not in self.user_states:
+# return
+#
+# state = self.user_states[user_id]
+# applications = state['applications']
+# current_index = state['current_index']
+#
+# if current_index >= len(applications):
+# # Все заявки обработаны
+# self.bot.send_message(
+# chat_id,
+# "Все заявки рассмотрены!\n\n"
+# "Новых заявок в очереди нет.",
+# parse_mode='HTML'
+# )
+#
+# # Очищаем состояние
+# del self.user_states[user_id]
+# return
+#
+# # Получаем текущую заявку
+# app_id, summary, full_name, created_at, username, phone, email = applications[current_index]
+#
+# # Получаем данные заявки
+# app_data = self.get_application_details(app_id)
+#
+# if not app_data:
+# # Если не удалось загрузить, пропускаем эту заявку
+# self.bot.send_message(
+# chat_id,
+# f"⚠️ Ошибка загрузки заявки #{app_id}.",
+# parse_mode='HTML'
+# )
+#
+# # Увеличиваем индекс и показываем следующую
+# state['current_index'] += 1
+# self.show_next_application(chat_id, user_id)
+# return
+#
+# # Сохраняем ID текущей заявки
+# state['current_application_id'] = app_id
+#
+# # Форматируем сообщение
+# message_text = self.format_application_message(app_data)
+#
+# markup = types.InlineKeyboardMarkup()
+#
+# markup.row(
+# types.InlineKeyboardButton("✅ Принять", callback_data=f"accept:{app_id}"),
+# types.InlineKeyboardButton("❌ Отклонить", callback_data=f"reject:{app_id}")
+# )
+#
+# markup.row(
+# types.InlineKeyboardButton("⏭ Пропустить", callback_data=f"skip:{app_id}")
+# )
+#
+# markup.row(
+# types.InlineKeyboardButton("⏹ Прекратить просмотр", callback_data="stop_review")
+# )
+#
+# # Отправляем сообщение
+# self.bot.send_message(
+# chat_id,
+# message_text,
+# parse_mode='HTML',
+# reply_markup=markup
+# )
+#
+# def handle_application_action(self, call):
+#
+# user_id = call.from_user.id
+#
+# if user_id not in self.user_states:
+# self.bot.answer_callback_query(call.id, "Сессия устарела")
+# return
+#
+# action_data = call.data
+# state = self.user_states[user_id]
+#
+# if action_data == "stop_review":
+# # Прекращаем просмотр
+# self.bot.answer_callback_query(call.id, "Просмотр прекращен")
+#
+# # Показываем сообщение о прекращении
+# self.bot.edit_message_text(
+# "Просмотр заявок прекращен\n\n"
+# "Вы можете возобновить в любой момент.",
+# call.message.chat.id,
+# call.message.message_id,
+# parse_mode='HTML'
+# )
+#
+# # Очищаем состояние
+# del self.user_states[user_id]
+# return
+#
+#
+# # Извлекаем ID заявки и действие
+# parts = action_data.split(":")
+# if len(parts) != 2:
+# self.bot.answer_callback_query(call.id, "Ошибка")
+# return
+#
+# action, app_id_str = parts
+# app_id = int(app_id_str)
+#
+# # Проверяем, что это текущая заявка
+# if state.get('current_application_id') != app_id:
+# self.bot.answer_callback_query(call.id, "Заявка уже обработана")
+# return
+#
+# # Обрабатываем действие
+# if action == "accept":
+# self.accept_application(call, app_id)
+# elif action == "reject":
+# self.reject_application(call, app_id)
+# elif action == "skip":
+# self.skip_application(call, app_id)
+#
+# def accept_application(self, call, application_id: int):
+# """Принять заявку """
+# try:
+# with self.get_connection() as conn:
+# cursor = conn.cursor()
+#
+# # Обновляем статус заявки
+# cursor.execute("""
+# UPDATE applications
+# SET status = 'accepted',
+# processed_at = ?
+# WHERE application_id = ?
+# """, (datetime.now(), application_id))
+#
+# conn.commit()
+#
+# # Получаем данные студента для уведомления
+# cursor.execute("""
+# SELECT u.telegram_id, s.name, u.full_name
+# FROM applications a
+# JOIN users u ON a.user_id = u.user_id
+# JOIN studios s ON a.studio_id = s.studio_id
+# WHERE a.application_id = ?
+# """, (application_id,))
+#
+# result = cursor.fetchone()
+# if result:
+# student_telegram_id, studio_name, student_name = result
+# # Отправляем уведомление студенту
+# try:
+# self.bot.send_message(
+# student_telegram_id,
+# f"🎉 Поздравляем, {student_name}!\n\n"
+# f"Ваша заявка в студию '{studio_name}' одобрена!\n\n",
+# parse_mode='HTML'
+# )
+# except Exception as e:
+# logger.error(f"Error notifying student {student_telegram_id}: {e}")
+#
+# self.bot.answer_callback_query(call.id, "✅ Заявка принята")
+#
+# # Удаляем заявку из состояния пользователя
+# self.remove_application_from_state(call.from_user.id, application_id)
+#
+# # Удаляем сообщение с заявкой
+# self.bot.delete_message(call.message.chat.id, call.message.message_id)
+#
+# # Показываем следующую заявку (если есть)
+# self.show_next_application(call.message.chat.id, call.from_user.id)
+#
+# except Exception as e:
+# logger.error(f"Error accepting application {application_id}: {e}")
+# self.bot.answer_callback_query(call.id, "❌ Ошибка при принятии")
+#
+# def reject_application(self, call, application_id: int):
+# """ Отклонить заявку"""
+# try:
+# with self.get_connection() as conn:
+# cursor = conn.cursor()
+#
+# # Обновляем статус заявки
+# cursor.execute("""
+# UPDATE applications
+# SET status = 'rejected',
+# processed_at = ?
+# WHERE application_id = ?
+# """, (datetime.now(), application_id))
+#
+# conn.commit()
+#
+# # Получаем данные студента для уведомления
+# cursor.execute("""
+# SELECT u.telegram_id, s.name, u.full_name
+# FROM applications a
+# JOIN users u ON a.user_id = u.user_id
+# JOIN studios s ON a.studio_id = s.studio_id
+# WHERE a.application_id = ?
+# """, (application_id,))
+#
+# result = cursor.fetchone()
+# if result:
+# student_telegram_id, studio_name, student_name = result
+# try:
+# self.bot.send_message(
+# student_telegram_id,
+# f"Уважаемый(ая) {student_name}!\n\n"
+# f"К сожалению, ваша заявка в студию '{studio_name}' не может быть принята.\n\n"
+# f"Не расстраивайтесь! Вы можете подать заявку в другие студии "
+# f"или попробовать снова в следующем семестре.",
+# parse_mode='HTML'
+# )
+# except Exception as e:
+# logger.error(f"Error notifying student {student_telegram_id}: {e}")
+#
+# self.bot.answer_callback_query(call.id, "❌ Заявка отклонена")
+#
+# # Удаляем заявку из состояния пользователя
+# self.remove_application_from_state(call.from_user.id, application_id)
+#
+# # Удаляем сообщение с заявкой
+# self.bot.delete_message(call.message.chat.id, call.message.message_id)
+#
+# # Показываем следующую заявку
+# self.show_next_application(call.message.chat.id, call.from_user.id)
+#
+# except Exception as e:
+# logger.error(f"Error rejecting application {application_id}: {e}")
+# self.bot.answer_callback_query(call.id, "❌ Ошибка при отклонении")
+#
+#
+# def skip_application(self, call, application_id: int):
+# """Пропустить заявку (оставить в очереди)"""
+# # Перемещаем заявку в конец очереди
+# self.move_application_to_end(call.from_user.id, application_id)
+#
+# self.bot.answer_callback_query(call.id, "Заявка перемещена в конец очереди")
+#
+# # Удаляем текущее сообщение и показываем следующую заявку
+# self.bot.delete_message(call.message.chat.id, call.message.message_id)
+# self.show_next_application(call.message.chat.id, call.from_user.id)
+#
+# def remove_application_from_state(self, user_id: int, application_id: int):
+# """Удаляет заявку из состояния пользователя"""
+# if user_id not in self.user_states:
+# return
+#
+# state = self.user_states[user_id]
+# applications = state['applications']
+#
+# # Удаляем заявку из списка
+# new_applications = [
+# app for app in applications
+# if app[0] != application_id
+# ]
+#
+# # Обновляем список заявок
+# state['applications'] = new_applications
+#
+# # Если текущая заявка была удалена, корректируем индекс
+# if state.get('current_index') >= len(new_applications):
+# state['current_index'] = max(0, len(new_applications) - 1)
+#
+# def move_application_to_end(self, user_id: int, application_id: int):
+# """Перемещает заявку в конец очереди """
+# if user_id not in self.user_states:
+# return
+#
+# state = self.user_states[user_id]
+# applications = state['applications']
+#
+# # Находим заявку
+# target_application = None
+# new_applications = []
+#
+# for app in applications:
+# if app[0] == application_id:
+# target_application = app
+# else:
+# new_applications.append(app)
+#
+# # Если нашли заявку, добавляем ее в конец
+# if target_application:
+# new_applications.append(target_application)
+#
+# # Обновляем список заявок
+# state['applications'] = new_applications
+#
+# # Корректируем текущий индекс
+# # Текущая заявка будет удалена из сообщения,
+# # поэтому показываем следующую по порядку
+# # Индекс не меняем, так как список сдвинулся
+# if state.get('current_index') >= len(new_applications):
+# state['current_index'] = len(new_applications) - 1
+#
+# def get_studio_name(self, studio_id: int) -> str:
+# """Получает название студии по ID"""
+# try:
+# with self.get_connection() as conn:
+# cursor = conn.cursor()
+# cursor.execute(
+# "SELECT name FROM studios WHERE studio_id = ?",
+# (studio_id,)
+# )
+# result = cursor.fetchone()
+# return result[0] if result else "Неизвестная студия"
+# except Exception as e:
+# logger.error(f"Error getting studio name: {e}")
+# return "Неизвестная студия"
+#
+# def get_statistics(self, studio_id: int) -> Dict[str, Any]:
+# """Получает отчет по заявкам студии"""
+# try:
+# with self.get_connection() as conn:
+# cursor = conn.cursor()
+#
+# cursor.execute("""
+# SELECT
+# COUNT(*) as total,
+# SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending,
+# SUM(CASE WHEN status = 'accepted' THEN 1 ELSE 0 END) as accepted,
+# SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END) as rejected
+# FROM applications
+# WHERE studio_id = ?
+# """, (studio_id,))
+#
+# stats = cursor.fetchone()
+#
+# # Статистика по дням (последние 7 дней)
+# cursor.execute("""
+# SELECT
+# DATE(created_at) as date,
+# COUNT(*) as count
+# FROM applications
+# WHERE studio_id = ?
+# AND created_at >= DATE('now', '-7 days')
+# GROUP BY DATE(created_at)
+# ORDER BY date DESC
+# """, (studio_id,))
+#
+# daily_stats = cursor.fetchall()
+#
+#
+#
+# except Exception as e:
+# logger.error(f"Error getting statistics for studio {studio_id}: {e}")
+# return {}
+#
+# def format_statistics_message(self, stats: Dict[str, Any], studio_name: str) -> str:
+# """Форматирует сообщение с отчетом"""
+# if not stats:
+# return "❌ Не удалось получить отчет"
+#
+# message = f"отчет по заявкам студии '{studio_name}'\n\n"
+#
+# message += f"Общая статистика:\n"
+# message += f"📋 Всего заявок: {stats.get('total', 0)}\n"
+# message += f"⏳ Ожидают рассмотрения: {stats.get('pending', 0)}\n"
+# message += f"✅ Принято: {stats.get('accepted', 0)}\n"
+# message += f"❌ Отклонено: {stats.get('rejected', 0)}\n\n"
+#
+# if stats.get('daily'):
+# message += "За последние 7 дней:\n"
+# for date, count in stats['daily'][:7]:
+# message += f"{date}: {count} заявок\n"
+# message += "\n"
+#
+# return message
\ No newline at end of file
diff --git a/.venv/usernew.py b/.venv/usernew.py
new file mode 100644
index 0000000..e5fd317
--- /dev/null
+++ b/.venv/usernew.py
@@ -0,0 +1,202 @@
+import telebot
+from telebot import types
+import logging
+
+from auth import get_user_role
+from database import load_active_studios
+from questionnaire_flow import QuestionnaireFlow
+from catalog import show_studio_catalog, show_studio_info
+from users_collector import QuestionnaireBuilder
+from questions import get_question_obj
+
+
+
+# Настройка логирования
+logging.basicConfig(
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ level=logging.INFO
+)
+logger = logging.getLogger(__name__)
+
+bot = telebot.TeleBot("8575877961:AAHgw2xCBZagYmj1s-LwlA-dhAcgXALxVzE")
+
+# Инициализация компонентов
+questionnaire_flow = QuestionnaireFlow(bot)
+
+# Инициализация модуля администратора
+from Admin import initialize as init_admin
+
+init_admin(bot)
+
+
+@bot.message_handler(commands=['start'])
+def start(message):
+ """Главный обработчик команды /start"""
+ username = message.from_user.username
+ role = get_user_role(username)
+
+ if role == 'admin':
+ from Admin import admin_start
+ admin_start(message)
+ return
+ elif role == 'head':
+ from head_of_studio import handle_head_start
+ handle_head_start(message)
+ return
+
+ # Для обычных пользователей
+ user_start(message)
+
+
+def user_start(message):
+ """Старт для обычного пользователя"""
+ markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
+ btn1 = types.KeyboardButton('Каталог студий')
+ markup.row(btn1)
+ btn2 = types.KeyboardButton('Мои заявки')
+ btn3 = types.KeyboardButton('Подать заявку')
+ markup.row(btn2, btn3)
+
+ bot.send_message(
+ message.chat.id,
+ 'Привет, в этом боте ты можешь узнать про творческие студии'
+ ' ГУАП и стать частью активной студенческой жизни!',
+ parse_mode='html',
+ reply_markup=markup
+ )
+
+
+@bot.message_handler(func=lambda message: message.text == 'Каталог студий')
+def handle_catalog(message):
+ """Показ каталога студий"""
+ show_studio_catalog(bot, message.chat.id)
+
+
+@bot.message_handler(func=lambda message: message.text == 'Подать заявку')
+def handle_apply_from_menu(message):
+ """Подать заявку из меню"""
+ show_studio_catalog(bot, message.chat.id)
+
+
+@bot.callback_query_handler(func=lambda call: call.data.startswith('info:'))
+def handle_studio_info(call):
+ """Показ информации о студии"""
+ show_studio_info(bot, call)
+
+
+@bot.callback_query_handler(func=lambda call: call.data.startswith('apply:'))
+def handle_apply(call):
+ """Начало заполнения анкеты"""
+ studio_id = int(call.data.split(':')[1])
+ questionnaire_flow.start_questionnaire(call, studio_id)
+
+
+@bot.callback_query_handler(func=lambda call: call.data in ['use_autofill', 'manual_fill'])
+def handle_autofill_choice(call):
+ """Обработка выбора автозаполнения"""
+ user_id = call.from_user.id
+
+ if user_id not in questionnaire_flow.user_states:
+ bot.answer_callback_query(call.id, "Сессия устарела")
+ return
+
+ state = questionnaire_flow.user_states[user_id]
+ studio_id = state['studio_id']
+
+ if call.data == 'use_autofill':
+ user_data = state['user_data']
+ logger.info(f"Данные пользователя для автозаполнения: {user_data}")
+
+ # Получаем все вопросы для этой студии
+ questions = QuestionnaireBuilder.get_question_objects(studio_id)
+
+ for q in questions:
+ if q.autofill_field and q.autofill_field in user_data:
+ value = user_data[q.autofill_field]
+ if value and str(value).strip():
+ # Сохраняем значение в ответы
+ state['answers'][q.id] = str(value)
+ logger.info(f"Автозаполнение: {q.id} = {value}")
+
+ bot.edit_message_text(
+ "✅ Данные автозаполнены.\nПродолжаем с оставшимися вопросами...",
+ call.message.chat.id,
+ call.message.message_id
+ )
+
+ # Начинаем опрос
+ state['state'] = 'answering'
+ questionnaire_flow._ask_next_question(call.message.chat.id, user_id)
+
+@bot.callback_query_handler(func=lambda call: call.data.startswith('answer:'))
+def handle_select_answer(call):
+ """Обработка выбора из инлайн-клавиатуры"""
+ if questionnaire_flow.user_states.get(call.from_user.id, {}).get('state') == 'answering':
+ questionnaire_flow._handle_select_answer(call)
+
+@bot.message_handler(func=lambda message: True, content_types=['text'])
+def handle_all_text_messages(message):
+ """Обработка всех текстовых сообщений"""
+ user_id = message.from_user.id
+ username = message.from_user.username
+
+ # Проверяем, находится ли пользователь в процессе заполнения анкеты
+ if user_id in questionnaire_flow.user_states and questionnaire_flow.user_states[user_id].get(
+ 'state') == 'answering':
+ questionnaire_flow.process_answer(message)
+ return
+
+ role = get_user_role(username)
+
+ if role == "admin":
+ from Admin import handle_admin_message
+ handle_admin_message(message)
+ return
+
+ # Обработка других команд
+ if message.text == 'Мои заявки':
+ bot.send_message(message.chat.id, "Функция 'Мои заявки' в разработке")
+ else:
+ bot.send_message(message.chat.id, "Используйте кнопки меню или команду /start")
+
+
+@bot.callback_query_handler(func=lambda call: call.data == 'confirm_application')
+def handle_confirm_application(call):
+ """Обработка подтверждения заявки"""
+ if call.from_user.id in questionnaire_flow.user_states:
+ questionnaire_flow._confirm_application(call)
+
+
+@bot.callback_query_handler(func=lambda call: call.data == 'restart_questionnaire')
+def handle_restart_questionnaire(call):
+ """Обработка перезаполнения анкеты"""
+ user_id = call.from_user.id
+ if user_id in questionnaire_flow.user_states:
+ state = questionnaire_flow.user_states[user_id]
+ studio_id = state['studio_id']
+
+ # Начинаем заново
+ questionnaire_flow.start_questionnaire(call, studio_id)
+
+# В самом конце файла, перед запуском бота, добавьте:
+
+@bot.callback_query_handler(func=lambda call: call.data.startswith('select_answer:'))
+def handle_select_answer(call):
+ """Обработка выбора из inline-клавиатуры для select вопросов"""
+ questionnaire_flow.process_select_answer(call)
+
+@bot.callback_query_handler(func=lambda call: call.data == 'confirm_application')
+def handle_confirm_application(call):
+ """Обработка подтверждения заявки"""
+ questionnaire_flow._confirm_application(call)
+
+@bot.callback_query_handler(func=lambda call: call.data == 'restart_questionnaire')
+def handle_restart_questionnaire(call):
+ """Обработка перезаполнения анкеты"""
+ questionnaire_flow._restart_questionnaire(call)
+
+
+# Запуск бота
+if __name__ == '__main__':
+ print("Бот запущен...")
+ bot.polling(none_stop=True)
\ No newline at end of file
diff --git a/.venv/userrrrr.py b/.venv/userrrrr.py
new file mode 100644
index 0000000..307481b
--- /dev/null
+++ b/.venv/userrrrr.py
@@ -0,0 +1,189 @@
+import telebot
+from telebot import types
+import logging
+
+from auth import get_user_role
+from database import load_active_studios
+from questionnaire_flow import QuestionnaireFlow
+from catalog import show_studio_catalog, show_studio_info
+from users_collector import QuestionnaireBuilder
+from questions import get_question_obj
+from applications_manager import ApplicationsManager
+
+# Настройка логирования
+logging.basicConfig(
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ level=logging.INFO
+)
+logger = logging.getLogger(__name__)
+
+bot = telebot.TeleBot("8575877961:AAHgw2xCBZagYmj1s-LwlA-dhAcgXALxVzE")
+
+# Инициализация компонентов
+questionnaire_flow = QuestionnaireFlow(bot)
+applications_manager = ApplicationsManager(bot)
+
+# Инициализация модуля администратора
+from Admin import initialize as init_admin
+
+init_admin(bot)
+
+@bot.message_handler(commands=['start'])
+def start(message):
+ """Главный обработчик команды /start"""
+ username = message.from_user.username
+ role = get_user_role(username)
+
+ if role == 'admin':
+ from Admin import admin_start
+ admin_start(message)
+ return
+ elif role == 'head':
+ from head_of_studio import handle_head_start
+ handle_head_start(message)
+ return
+
+ user_start(message)
+
+def user_start(message):
+ """Старт для обычного пользователя"""
+ markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
+ btn1 = types.KeyboardButton('Каталог студий')
+ markup.row(btn1)
+ btn2 = types.KeyboardButton('Мои заявки')
+ markup.row(btn2)
+
+ bot.send_message(
+ message.chat.id,
+ 'Привет, в этом боте ты можешь узнать про творческие студии'
+ ' ГУАП и стать частью активной студенческой жизни!',
+ parse_mode='html',
+ reply_markup=markup
+ )
+
+def create_main_user_menu():
+ """Создает главное меню пользователя"""
+ markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
+ btn1 = types.KeyboardButton('Каталог студий')
+ markup.row(btn1)
+ btn2 = types.KeyboardButton('Мои заявки')
+ markup.row(btn2)
+ return markup
+
+@bot.message_handler(func=lambda message: message.text == 'Каталог студий')
+def handle_catalog(message):
+ """Показ каталога студий"""
+ show_studio_catalog(bot, message.chat.id)
+
+@bot.message_handler(func=lambda message: message.text == 'Мои заявки')
+def handle_my_applications(message):
+ """Показ заявок пользователя"""
+ user_id = message.from_user.id
+ applications_manager.show_user_applications(message.chat.id, user_id)
+
+@bot.callback_query_handler(func=lambda call: call.data.startswith('info:'))
+def handle_studio_info(call):
+ """Показ информации о студии"""
+ show_studio_info(bot, call)
+
+@bot.callback_query_handler(func=lambda call: call.data.startswith('apply:'))
+def handle_apply(call):
+ """Начало заполнения анкеты"""
+ studio_id = int(call.data.split(':')[1])
+ questionnaire_flow.start_questionnaire(call, studio_id)
+
+
+@bot.callback_query_handler(func=lambda call: call.data in ['use_autofill', 'manual_fill'])
+def handle_autofill_choice(call):
+ """Обработка выбора автозаполнения"""
+ user_id = call.from_user.id
+
+ if user_id not in questionnaire_flow.user_states:
+ bot.answer_callback_query(call.id, "Сессия устарела")
+ return
+
+ state = questionnaire_flow.user_states[user_id]
+ studio_id = state['studio_id']
+
+ if call.data == 'use_autofill':
+ user_data = state['user_data']
+ logger.info(f"Данные пользователя для автозаполнения: {user_data}")
+
+ questions = QuestionnaireBuilder.get_question_objects(studio_id)
+
+ for q in questions:
+ if q.autofill_field and q.autofill_field in user_data:
+ value = user_data[q.autofill_field]
+ if value and str(value).strip():
+ # Сохраняем значение в ответы
+ state['answers'][q.id] = str(value)
+ logger.info(f"Автозаполнение: {q.id} = {value}")
+
+ bot.edit_message_text(
+ "✅ Данные автозаполнены.\nПродолжаем с оставшимися вопросами...",
+ call.message.chat.id,
+ call.message.message_id
+ )
+
+ state['state'] = 'answering'
+ questionnaire_flow._ask_next_question(call.message.chat.id, user_id)
+
+@bot.callback_query_handler(func=lambda call: call.data.startswith('answer:'))
+def handle_select_answer(call):
+ """Обработка выбора из инлайн-клавиатуры"""
+ if questionnaire_flow.user_states.get(call.from_user.id, {}).get('state') == 'answering':
+ questionnaire_flow._handle_select_answer(call)
+
+@bot.message_handler(func=lambda message: True, content_types=['text'])
+def handle_all_text_messages(message):
+ """Обработка всех текстовых сообщений"""
+ user_id = message.from_user.id
+ username = message.from_user.username
+
+ if user_id in questionnaire_flow.user_states and questionnaire_flow.user_states[user_id].get(
+ 'state') == 'answering':
+ questionnaire_flow.process_answer(message)
+ return
+
+ role = get_user_role(username)
+
+ if role == "admin":
+ from Admin import handle_admin_message
+ handle_admin_message(message)
+ return
+ if message.text == 'Мои заявки':
+ bot.send_message(message.chat.id, "Функция 'Мои заявки' в разработке")
+
+@bot.callback_query_handler(func=lambda call: call.data == 'confirm_application')
+def handle_confirm_application(call):
+ """Обработка подтверждения заявки"""
+ if call.from_user.id in questionnaire_flow.user_states:
+ questionnaire_flow._confirm_application(call)
+
+ markup = create_main_user_menu()
+ bot.send_message(
+ call.message.chat.id,
+ "✅ Ваша заявка успешно отправлена!\n\n"
+ "Вы можете:\n"
+ "• Просмотреть свои заявки в разделе 'Мои заявки'\n",
+ parse_mode='HTML',
+ reply_markup=markup
+ )
+
+@bot.callback_query_handler(func=lambda call: call.data == 'restart_questionnaire')
+def handle_restart_questionnaire(call):
+ """Обработка перезаполнения анкеты"""
+ user_id = call.from_user.id
+ if user_id in questionnaire_flow.user_states:
+ state = questionnaire_flow.user_states[user_id]
+ studio_id = state['studio_id']
+
+ questionnaire_flow.start_questionnaire(call, studio_id)
+
+@bot.callback_query_handler(func=lambda call: call.data.startswith('select_answer:'))
+def handle_select_answer_callback(call):
+ questionnaire_flow.process_select_answer(call)
+
+if __name__ == '__main__':
+ print("Бот запущен...")
+ bot.polling(none_stop=True)
\ No newline at end of file
diff --git a/.venv/userrrrrr.py b/.venv/userrrrrr.py
new file mode 100644
index 0000000..4c06346
--- /dev/null
+++ b/.venv/userrrrrr.py
@@ -0,0 +1,203 @@
+import telebot
+from telebot import types
+import logging
+
+from auth import get_user_role
+from database import load_active_studios
+from questionnaire_flow import QuestionnaireFlow
+from catalog import show_studio_catalog, show_studio_info
+from users_collector import QuestionnaireBuilder
+from questions import get_question_obj
+from applications_manager import ApplicationsManager
+
+
+# Настройка логирования
+logging.basicConfig(
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ level=logging.INFO
+)
+logger = logging.getLogger(__name__)
+
+bot = telebot.TeleBot("8575877961:AAHgw2xCBZagYmj1s-LwlA-dhAcgXALxVzE")
+
+# Инициализация компонентов
+questionnaire_flow = QuestionnaireFlow(bot)
+applications_manager = ApplicationsManager(bot)
+
+# Инициализация модуля администратора
+from Admin import initialize as init_admin
+
+init_admin(bot)
+
+
+@bot.message_handler(commands=['start'])
+def start(message):
+ """Главный обработчик команды /start"""
+ username = message.from_user.username
+ role = get_user_role(username)
+
+ if role == 'admin':
+ from Admin import admin_start
+ admin_start(message)
+ return
+ elif role == 'head':
+ from head_of_studio import handle_head_start
+ handle_head_start(message)
+ return
+
+ user_start(message)
+
+
+def user_start(message):
+ """Старт для обычного пользователя"""
+ markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
+ btn1 = types.KeyboardButton('Каталог студий')
+ markup.row(btn1)
+ btn2 = types.KeyboardButton('Мои заявки')
+ markup.row(btn2)
+
+ bot.send_message(
+ message.chat.id,
+ 'Привет, в этом боте ты можешь узнать про творческие студии'
+ ' ГУАП и стать частью активной студенческой жизни!',
+ parse_mode='html',
+ reply_markup=markup
+ )
+
+def create_main_user_menu():
+ """Создает главное меню пользователя"""
+ markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
+ btn1 = types.KeyboardButton('Каталог студий')
+ markup.row(btn1)
+ btn2 = types.KeyboardButton('Мои заявки')
+ markup.row(btn2)
+ return markup
+
+
+@bot.message_handler(func=lambda message: message.text == 'Каталог студий')
+def handle_catalog(message):
+ """Показ каталога студий"""
+ show_studio_catalog(bot, message.chat.id)
+
+@bot.message_handler(func=lambda message: message.text == 'Мои заявки')
+def handle_my_applications(message):
+ """Показ заявок пользователя"""
+ user_id = message.from_user.id
+ applications_manager.show_user_applications(message.chat.id, user_id)
+
+
+@bot.callback_query_handler(func=lambda call: call.data.startswith('info:'))
+def handle_studio_info(call):
+ """Показ информации о студии"""
+ show_studio_info(bot, call)
+
+
+@bot.callback_query_handler(func=lambda call: call.data.startswith('apply:'))
+def handle_apply(call):
+ """Начало заполнения анкеты"""
+ studio_id = int(call.data.split(':')[1])
+ questionnaire_flow.start_questionnaire(call, studio_id)
+
+
+@bot.callback_query_handler(func=lambda call: call.data in ['use_autofill', 'manual_fill'])
+def handle_autofill_choice(call):
+ """Обработка выбора автозаполнения"""
+ user_id = call.from_user.id
+
+ if user_id not in questionnaire_flow.user_states:
+ bot.answer_callback_query(call.id, "Сессия устарела")
+ return
+
+ state = questionnaire_flow.user_states[user_id]
+ studio_id = state['studio_id']
+
+ if call.data == 'use_autofill':
+ user_data = state['user_data']
+ logger.info(f"Данные пользователя для автозаполнения: {user_data}")
+
+ questions = QuestionnaireBuilder.get_question_objects(studio_id)
+
+ for q in questions:
+ if q.autofill_field and q.autofill_field in user_data:
+ value = user_data[q.autofill_field]
+ if value and str(value).strip():
+ # Сохраняем значение в ответы
+ state['answers'][q.id] = str(value)
+ logger.info(f"Автозаполнение: {q.id} = {value}")
+
+ bot.edit_message_text(
+ "✅ Данные автозаполнены.\nПродолжаем с оставшимися вопросами...",
+ call.message.chat.id,
+ call.message.message_id
+ )
+
+ # Начинаем опрос
+ state['state'] = 'answering'
+ questionnaire_flow._ask_next_question(call.message.chat.id, user_id)
+
+@bot.callback_query_handler(func=lambda call: call.data.startswith('answer:'))
+def handle_select_answer(call):
+ """Обработка выбора из инлайн-клавиатуры"""
+ if questionnaire_flow.user_states.get(call.from_user.id, {}).get('state') == 'answering':
+ questionnaire_flow._handle_select_answer(call)
+
+@bot.message_handler(func=lambda message: True, content_types=['text'])
+def handle_all_text_messages(message):
+ """Обработка всех текстовых сообщений"""
+ user_id = message.from_user.id
+ username = message.from_user.username
+
+ # Проверяем, находится ли пользователь в процессе заполнения анкеты
+ if user_id in questionnaire_flow.user_states and questionnaire_flow.user_states[user_id].get(
+ 'state') == 'answering':
+ questionnaire_flow.process_answer(message)
+ return
+
+ role = get_user_role(username)
+
+ if role == "admin":
+ from Admin import handle_admin_message
+ handle_admin_message(message)
+ return
+ if message.text == 'Мои заявки':
+ bot.send_message(message.chat.id, "Функция 'Мои заявки' в разработке")
+
+
+@bot.callback_query_handler(func=lambda call: call.data == 'confirm_application')
+def handle_confirm_application(call):
+ """Обработка подтверждения заявки"""
+ if call.from_user.id in questionnaire_flow.user_states:
+ questionnaire_flow._confirm_application(call)
+
+ markup = create_main_user_menu()
+ bot.send_message(
+ call.message.chat.id,
+ "✅ Ваша заявка успешно отправлена!\n\n"
+ "Вы можете:\n"
+ "• Просмотреть свои заявки в разделе 'Мои заявки'\n"
+ "• Изучить другие студии в каталоге",
+ parse_mode='HTML',
+ reply_markup=markup
+ )
+
+
+@bot.callback_query_handler(func=lambda call: call.data == 'restart_questionnaire')
+def handle_restart_questionnaire(call):
+ """Обработка перезаполнения анкеты"""
+ user_id = call.from_user.id
+ if user_id in questionnaire_flow.user_states:
+ state = questionnaire_flow.user_states[user_id]
+ studio_id = state['studio_id']
+
+ questionnaire_flow.start_questionnaire(call, studio_id)
+
+
+@bot.callback_query_handler(func=lambda call: call.data.startswith('select_answer:'))
+def handle_select_answer_callback(call):
+ questionnaire_flow.process_select_answer(call)
+
+
+# Запуск бота
+if __name__ == '__main__':
+ print("Бот запущен...")
+ bot.polling(none_stop=True)
\ No newline at end of file
diff --git a/.venv/users_collector.py b/.venv/users_collector.py
new file mode 100644
index 0000000..793e590
--- /dev/null
+++ b/.venv/users_collector.py
@@ -0,0 +1,301 @@
+from typing import Dict, List, Any, Optional, Callable
+from questions import get_question_obj
+
+
+class QuestionnaireBuilder:
+ """Сборщик анкет для разных студий"""
+
+ STUDIO_QUESTIONNAIRES = {
+ 1: {
+ 'name': 'Танцевальная студия',
+ 'questions': [
+ 'full_name', # 1. ФИО
+ 'group', # 2. Группа
+ 'phone_number', # 3. Телефон
+ 'email', # 4. Email
+ 'project_ideas', # 5. Идеи для проектов
+ 'experience', # 6. Опыт
+ 'motivation',
+ 'activeness', # 8. Участие в мероприятиях
+ ]
+ },
+ 6: {
+ 'name': 'КВН',
+ 'questions': [
+ 'full_name', # 1. ФИО
+ 'group', # 2. Группа
+ 'phone_number', # 3. Телефон
+ 'email', # 4. Email
+ 'experience', # 5. Опыт
+ 'quote', # 6. Цитата (ИСПРАВЛЕНО: было 'quot')
+ 'public_speaking', # 7. Публичные выступления (ИСПРАВЛЕНО: было 'public_speakin')
+ ]
+ },
+ 2: {
+ 'name': 'Театральная студия',
+ 'questions': [
+ 'full_name', # 1. ФИО
+ 'group', # 2. Группа
+ 'phone_number', # 3. Телефон
+ 'email', # 4. Email
+ 'experience', # 5. Опыт
+ 'motivation', # 6. Мотивация
+ 'improvisation', # 7. Импровизация
+ 'feedback_source', # 8. Источник информации
+ ]
+ },
+ 4: {
+ 'name': 'МУЗГУАП',
+ 'questions': [
+ 'full_name', # 1. ФИО
+ 'group', # 2. Группа
+ 'phone_number', # 3. Телефон
+ 'email', # 4. Email
+ 'music_instrument', # 5. Музыкальный инструмент
+ 'singing_experience', # 6. Опыт пения
+ 'public_speaking', # 7. Публичные выступления (ИСПРАВЛЕНО)
+ 'activeness', # 8. Участие в мероприятиях
+ ]
+ },
+ 5: {
+ 'name': 'Медиацентр',
+ 'questions': [
+ 'full_name', # 1. ФИО
+ 'group', # 2. Группа
+ 'phone_number', # 3. Телефон
+ 'email', # 4. Email
+ 'media', # 5. Направление медиа
+ 'experience', # 6. Опыт
+ 'project_ideas', # 7. Идеи для проектов
+ ]
+ },
+ 3: {
+ 'name': 'Студия технического обеспечения мероприятий',
+ 'questions': [
+ 'full_name', # 1. ФИО
+ 'group', # 2. Группа
+ 'phone_number', # 3. Телефон
+ 'email', # 4. Email
+ 'tech_skills', # 5. Технические навыки
+ 'organizational_exp', # 6. Организационный опыт
+ 'leadership', # 7. Лидерские качества
+ 'feedback_source', # 8. Источник информации
+ ]
+ },
+ 7: {
+ 'name': 'Студия организации мероприятий',
+ 'questions': [
+ 'full_name', # 1. ФИО
+ 'group', # 2. Группа
+ 'phone_number', # 3. Телефон
+ 'email', # 4. Email
+ 'organizational_exp', # 5. Организационный опыт
+ 'motivation', # 6. Мотивация
+ 'activeness', # 7. Участие в мероприятиях
+ 'tech_skills', # 8. Технические навыки
+ ]
+ },
+ 8: {
+ 'name': 'Студия ведущих',
+ 'questions': [
+ 'full_name', # 1. ФИО
+ 'group', # 2. Группа
+ 'phone_number', # 3. Телефон
+ 'email', # 4. Email
+ 'experience', # 5. Опыт
+ 'public_speaking', # 6. Публичные выступления (ИСПРАВЛЕНО)
+ 'quote', # 7. Цитата (ИСПРАВЛЕНО: было 'quot')
+ 'improvisation', # 8. Импровизация
+ ]
+ },
+ }
+
+ @classmethod
+ def get_questionnaire_for_studio(cls, studio_id: int) -> Optional[Dict[str, Any]]:
+ return cls.STUDIO_QUESTIONNAIRES.get(studio_id)
+
+ @classmethod
+ def get_studio_name(cls, studio_id: int) -> str:
+ config = cls.get_questionnaire_for_studio(studio_id)
+ return config.get('name', 'Неизвестная студия') if config else 'Неизвестная студия'
+
+ @classmethod
+ def get_question_sequence(cls, studio_id: int) -> List[str]:
+ config = cls.get_questionnaire_for_studio(studio_id)
+ return config.get('questions', []) if config else []
+
+ @classmethod
+ def get_question_objects(cls, studio_id: int) -> List[Any]:
+ question_ids = cls.get_question_sequence(studio_id)
+ questions = []
+
+ for qid in question_ids:
+ q_obj = get_question_obj(qid)
+ if q_obj:
+ questions.append(q_obj)
+
+ return questions
+
+ @classmethod
+ def get_autofill_fields(cls, studio_id: int) -> List[str]:
+ questions = cls.get_question_objects(studio_id)
+ autofill_fields = []
+
+ for q in questions:
+ if q.autofill_field:
+ autofill_fields.append(q.autofill_field)
+
+ return list(set(autofill_fields))
+
+ @classmethod
+ def can_autofill(cls, studio_id: int, user_data: Dict[str, Any]) -> bool:
+ autofill_fields = cls.get_autofill_fields(studio_id)
+
+ if not autofill_fields:
+ return False
+
+ for field in autofill_fields:
+ value = user_data.get(field, '')
+ if value and str(value).strip():
+ return True
+
+ return False
+
+ @classmethod
+ def generate_summary(cls, studio_id: int, answers: Dict[str, str]) -> str:
+ """Генерирует сводку по ответам"""
+ studio_name = cls.get_studio_name(studio_id)
+ summary = f"Заявка в студию: {studio_name}\n\n"
+
+ for qid, answer in answers.items():
+ q_obj = get_question_obj(qid)
+ question_text = q_obj.text if q_obj else qid
+ summary += f"❓ {question_text}\n"
+ summary += f"📝 {answer}\n\n"
+
+ return summary
+
+
+
+
+
+
+
+
+ # @classmethod
+ # def get_questionnaire_for_studio(cls, studio_id: int) -> Optional[Dict[str, Any]]:
+ # """Получает конфигурацию анкеты для студии"""
+ # return cls.STUDIO_QUESTIONNAIRES.get(studio_id)
+ #
+ # @classmethod
+ # def get_studio_name(cls, studio_id: int) -> str:
+ # """Получает название студии"""
+ # config = cls.get_questionnaire_for_studio(studio_id)
+ # return config.get('name', 'Неизвестная студия') if config else 'Неизвестная студия'
+ #
+ # @classmethod
+ # def get_question_sequence(cls, studio_id: int) -> List[str]:
+ # """Получает последовательность вопросов для студии"""
+ # config = cls.get_questionnaire_for_studio(studio_id)
+ # return config.get('questions', []) if config else []
+ #
+ # @classmethod
+ # def build_questionnaire_handlers(cls, studio_id: int) -> List[Callable]:
+ # """Собирает список обработчиков вопросов для студии"""
+ # question_ids = cls.get_question_sequence(studio_id)
+ # handlers = []
+ #
+ # for qid in question_ids:
+ # handler = get_question_handler(qid)
+ # if handler:
+ # handlers.append(handler)
+ #
+ # return handlers
+ #
+ # @classmethod
+ # def get_question_objects(cls, studio_id: int) -> List[Any]:
+ # """Получает объекты вопросов для студии"""
+ # question_ids = cls.get_question_sequence(studio_id)
+ # questions = []
+ #
+ # for qid in question_ids:
+ # q_obj = get_question_obj(qid)
+ # if q_obj:
+ # questions.append(q_obj)
+ #
+ # return questions
+ #
+ # @classmethod
+ # def get_autofill_fields(cls, studio_id: int) -> List[str]:
+ # """Получает список полей для автозаполнения"""
+ # questions = cls.get_question_objects(studio_id)
+ # autofill_fields = []
+ #
+ # for q in questions:
+ # if q.autofill_field:
+ # autofill_fields.append(q.autofill_field)
+ #
+ # # Убираем дубликаты
+ # return list(set(autofill_fields))
+ #
+ # #У нас каждый раз новая анкета, зачем автозаполнение??
+ # @classmethod
+ # def can_autofill(cls, studio_id: int, user_data: Dict[str, Any]) -> bool:
+ # """Проверяет, можно ли автозаполнить анкету"""
+ # autofill_fields = cls.get_autofill_fields(studio_id)
+ #
+ # if not autofill_fields:
+ # return False
+ #
+ # # Проверяем, сколько полей можно автозаполнить
+ # fillable_count = 0
+ # for field in autofill_fields:
+ # value = user_data.get(field, '')
+ # if value and str(value).strip():
+ # fillable_count += 1
+ #
+ # return fillable_count == len(autofill_fields)
+ #
+ # @classmethod
+ # def generate_summary(cls, studio_id: int, answers: Dict[str, str]) -> str:
+ # """Генерирует сводку по ответам"""
+ # questions = cls.get_question_objects(studio_id)
+ # studio_name = cls.get_studio_name(studio_id)
+ #
+ # lines = [f"📋 *Анкета для студии: {studio_name}*\n"]
+ #
+ # for i, q in enumerate(questions, 1):
+ # answer = answers.get(q.id, "❌ Не заполнено")
+ # lines.append(f"\n{i}. *{q.text}*\n{answer}")
+ #
+ # return "\n".join(lines)
+ #
+ # @classmethod
+ # def validate_answers(cls, studio_id: int, answers: Dict[str, str]) -> tuple[bool, Dict[str, str]]:
+ # """Валидирует все ответы анкеты"""
+ # questions = cls.get_question_objects(studio_id)
+ # errors = {}
+ #
+ # for q in questions:
+ # answer = answers.get(q.id, '').strip()
+ #
+ # if q.is_required and not answer:
+ # errors[q.id] = "Обязательный вопрос не заполнен"
+ # continue
+ #
+ # is_valid, error_msg = q.validate(answer)
+ # if not is_valid:
+ # errors[q.id] = error_msg
+ #
+ # return len(errors) == 0, errors
+
+
+
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
deleted file mode 100644
index 81d81fe..0000000
--- a/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# Coursework-
\ No newline at end of file
diff --git a/student_studios_bot.db b/student_studios_bot.db
deleted file mode 100644
index 827d44b..0000000
Binary files a/student_studios_bot.db and /dev/null differ
diff --git "a/\320\272\321\203\321\200\321\201\320\276\320\262\320\260\321\217.ipynb" "b/\320\272\321\203\321\200\321\201\320\276\320\262\320\260\321\217.ipynb"
new file mode 100644
index 0000000..c46696c
--- /dev/null
+++ "b/\320\272\321\203\321\200\321\201\320\276\320\262\320\260\321\217.ipynb"
@@ -0,0 +1,59 @@
+{
+ "nbformat": 4,
+ "nbformat_minor": 0,
+ "metadata": {
+ "colab": {
+ "provenance": [],
+ "authorship_tag": "ABX9TyOvtb5h6X+Er4UdtIIApWWB",
+ "include_colab_link": true
+ },
+ "kernelspec": {
+ "name": "python3",
+ "display_name": "Python 3"
+ },
+ "language_info": {
+ "name": "python"
+ }
+ },
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "view-in-github",
+ "colab_type": "text"
+ },
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "id": "biU6qS7vJ9ke",
+ "outputId": "d9daad1a-8a25-4f8e-a67c-e9a310813b4a",
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ }
+ },
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "Cloning into 'Coursework-'...\n",
+ "remote: Enumerating objects: 33, done.\u001b[K\n",
+ "remote: Counting objects: 100% (33/33), done.\u001b[K\n",
+ "remote: Compressing objects: 100% (27/27), done.\u001b[K\n",
+ "remote: Total 33 (delta 10), reused 21 (delta 2), pack-reused 0 (from 0)\u001b[K\n",
+ "Receiving objects: 100% (33/33), 15.76 KiB | 7.88 MiB/s, done.\n",
+ "Resolving deltas: 100% (10/10), done.\n"
+ ]
+ }
+ ],
+ "source": [
+ "!git clone https://github.com/Rina969/Coursework-"
+ ]
+ }
+ ]
+}
\ No newline at end of file