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": [ + "\"Open" + ] + }, + { + "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