mirror of
https://gitlab.com/MoonTestUse1/AdministrationItDepartmens.git
synced 2025-08-14 00:25:46 +02:00
Проверка
This commit is contained in:
19
backend/app/bot/__init__.py
Normal file
19
backend/app/bot/__init__.py
Normal file
@@ -0,0 +1,19 @@
|
||||
"""Bot initialization module"""
|
||||
from aiogram import Bot, Dispatcher
|
||||
from .config import settings
|
||||
from .handlers import callbacks_router, start_router
|
||||
|
||||
# Initialize bot and dispatcher
|
||||
bot = Bot(token=settings.bot_token)
|
||||
dp = Dispatcher()
|
||||
|
||||
# Include routers only once during initialization
|
||||
dp.include_router(callbacks_router)
|
||||
dp.include_router(start_router)
|
||||
|
||||
async def start_bot():
|
||||
"""Start the bot"""
|
||||
try:
|
||||
await dp.start_polling(bot, skip_updates=True)
|
||||
except Exception as e:
|
||||
print(f"Error starting bot: {e}")
|
69
backend/app/bot/bot.py
Normal file
69
backend/app/bot/bot.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from aiogram import Bot, Dispatcher
|
||||
from app.bot.config import settings
|
||||
|
||||
bot = Bot(token="7677506032:AAHduD5EePz3bE23DKlo35KoOp2_9lZuS34")
|
||||
dp = Dispatcher()
|
||||
from .handlers import start, status
|
||||
|
||||
|
||||
async def start_bot():
|
||||
"""Start the bot"""
|
||||
try:
|
||||
await dp.start_polling(bot, skip_updates=True)
|
||||
finally:
|
||||
await bot.session.close()
|
||||
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from sqlalchemy.orm import Session
|
||||
from ..database import get_db
|
||||
from .. import models
|
||||
|
||||
# Создаем обработчик нажатия кнопки
|
||||
@dp.callback_query_handler(lambda c: c.data.startswith('status_'))
|
||||
async def process_status_button(callback_query: types.CallbackQuery):
|
||||
try:
|
||||
# Получаем ID заявки и новый статус из callback_data
|
||||
_, request_id, new_status = callback_query.data.split('_')
|
||||
request_id = int(request_id)
|
||||
|
||||
# Получаем сессию базы данных
|
||||
db = next(get_db())
|
||||
|
||||
# Обновляем статус в базе данных
|
||||
request = db.query(models.Request).filter(models.Request.id == request_id).first()
|
||||
if request:
|
||||
request.status = new_status
|
||||
db.commit()
|
||||
|
||||
# Обновляем сообщение в боте
|
||||
await callback_query.message.edit_text(
|
||||
f"Заявка №{request_id}\nСтатус: {new_status}",
|
||||
reply_markup=get_updated_keyboard(request_id, new_status)
|
||||
)
|
||||
|
||||
# Отправляем уведомление о успешном обновлении
|
||||
await callback_query.answer("Статус успешно обновлен!")
|
||||
else:
|
||||
await callback_query.answer("Заявка не найдена!", show_alert=True)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in process_status_button: {e}")
|
||||
await callback_query.answer("Произошла ошибка при обновлении статуса", show_alert=True)
|
||||
|
||||
def get_updated_keyboard(request_id: int, current_status: str) -> InlineKeyboardMarkup:
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
|
||||
if current_status != "in_progress":
|
||||
keyboard.add(InlineKeyboardButton(
|
||||
"В работе",
|
||||
callback_data=f"status_{request_id}_in_progress"
|
||||
))
|
||||
|
||||
if current_status != "completed":
|
||||
keyboard.add(InlineKeyboardButton(
|
||||
"Завершено",
|
||||
callback_data=f"status_{request_id}_completed"
|
||||
))
|
||||
|
||||
return keyboard
|
26
backend/app/bot/config.py
Normal file
26
backend/app/bot/config.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Configuration module for the Telegram bot.
|
||||
Contains all necessary settings and constants.
|
||||
"""
|
||||
from pydantic_settings import BaseSettings
|
||||
from pydantic import Field
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""Bot configuration settings"""
|
||||
bot_token: str = Field("7677506032:AAHduD5EePz3bE23DKlo35KoOp2_9lZuS34", env="TELEGRAM_BOT_TOKEN")
|
||||
chat_id: str = Field("5057752127", env="TELEGRAM_CHAT_ID")
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
env_file_encoding = "utf-8"
|
||||
|
||||
# Create settings instance
|
||||
settings = Settings()
|
||||
|
||||
# Request status constants
|
||||
class RequestStatus:
|
||||
"""Constants for request statuses"""
|
||||
NEW = "new"
|
||||
IN_PROGRESS = "in_progress"
|
||||
COMPLETED = "completed"
|
||||
CANCELLED = "cancelled"
|
73
backend/app/bot/constants.py
Normal file
73
backend/app/bot/constants.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class RequestStatus(str, Enum):
|
||||
NEW = "new"
|
||||
IN_PROGRESS = "in_progress"
|
||||
RESOLVED = "resolved"
|
||||
CLOSED = "closed"
|
||||
|
||||
|
||||
class RequestPriority(str, Enum):
|
||||
LOW = "low"
|
||||
MEDIUM = "medium"
|
||||
HIGH = "high"
|
||||
CRITICAL = "critical"
|
||||
|
||||
|
||||
class Department(str, Enum):
|
||||
AHO = "aho"
|
||||
GKH = "gkh"
|
||||
GENERAL = "general"
|
||||
|
||||
|
||||
class RequestType(str, Enum):
|
||||
HARDWARE = "hardware"
|
||||
SOFTWARE = "software"
|
||||
NETWORK = "network"
|
||||
ACCESS = "access"
|
||||
OTHER = "other"
|
||||
|
||||
|
||||
STATUS_LABELS = {
|
||||
RequestStatus.NEW: "Новая",
|
||||
RequestStatus.IN_PROGRESS: "В работе",
|
||||
RequestStatus.RESOLVED: "Решена",
|
||||
RequestStatus.CLOSED: "Закрыта",
|
||||
}
|
||||
|
||||
PRIORITY_LABELS = {
|
||||
RequestPriority.LOW: "Низкий",
|
||||
RequestPriority.MEDIUM: "Средний",
|
||||
RequestPriority.HIGH: "Высокий",
|
||||
RequestPriority.CRITICAL: "Критический",
|
||||
}
|
||||
|
||||
PRIORITY_EMOJI = {
|
||||
RequestPriority.LOW: "🟢",
|
||||
RequestPriority.MEDIUM: "🟡",
|
||||
RequestPriority.HIGH: "🟠",
|
||||
RequestPriority.CRITICAL: "🔴",
|
||||
}
|
||||
|
||||
DEPARTMENT_LABELS = {
|
||||
Department.AHO: "Административно-хозяйственный отдел",
|
||||
Department.GKH: "Жилищно-коммунальное хозяйство",
|
||||
Department.GENERAL: "Общий отдел",
|
||||
}
|
||||
|
||||
REQUEST_TYPE_LABELS = {
|
||||
RequestType.HARDWARE: "Проблемы с оборудованием",
|
||||
RequestType.SOFTWARE: "Проблемы с ПО",
|
||||
RequestType.NETWORK: "Проблемы с сетью",
|
||||
RequestType.ACCESS: "Доступ к системам",
|
||||
RequestType.OTHER: "Другое",
|
||||
}
|
||||
|
||||
REQUEST_TYPE_EMOJI = {
|
||||
RequestType.HARDWARE: "🖥️",
|
||||
RequestType.SOFTWARE: "💿",
|
||||
RequestType.NETWORK: "🌐",
|
||||
RequestType.ACCESS: "🔑",
|
||||
RequestType.OTHER: "📝",
|
||||
}
|
6
backend/app/bot/handlers/__init__.py
Normal file
6
backend/app/bot/handlers/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
"""Handlers initialization"""
|
||||
from .callbacks import router as callbacks_router
|
||||
from .start import router as start_router
|
||||
from .callbacks import get_updated_keyboard
|
||||
|
||||
__all__ = ['callbacks_router', 'start_router', 'get_updated_keyboard']
|
70
backend/app/bot/handlers/callbacks.py
Normal file
70
backend/app/bot/handlers/callbacks.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""Handlers for callback queries"""
|
||||
from aiogram import Router, types
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from sqlalchemy.orm import Session
|
||||
from ...database import get_db
|
||||
from ... import models
|
||||
from ..config import RequestStatus
|
||||
|
||||
router = Router()
|
||||
|
||||
@router.callback_query(lambda c: c.data and c.data.startswith('status_'))
|
||||
async def process_status_button(callback_query: types.CallbackQuery):
|
||||
"""
|
||||
Handle status update button clicks.
|
||||
Updates request status in database and updates message in Telegram.
|
||||
"""
|
||||
try:
|
||||
# Parse callback data
|
||||
_, request_id, new_status = callback_query.data.split('_')
|
||||
request_id = int(request_id)
|
||||
|
||||
# Get database session
|
||||
db = next(get_db())
|
||||
|
||||
# Update request status
|
||||
request = db.query(models.Request).filter(models.Request.id == request_id).first()
|
||||
if request:
|
||||
request.status = new_status
|
||||
db.commit()
|
||||
|
||||
# Update message in Telegram
|
||||
await callback_query.message.edit_text(
|
||||
f"Заявка №{request_id}\n"
|
||||
f"Статус: {new_status}\n"
|
||||
f"Описание: {request.description}",
|
||||
reply_markup=get_updated_keyboard(request_id, new_status)
|
||||
)
|
||||
|
||||
await callback_query.answer("Статус успешно обновлен!")
|
||||
else:
|
||||
await callback_query.answer("Заявка не найдена!", show_alert=True)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in process_status_button: {e}")
|
||||
await callback_query.answer(
|
||||
"Произошла ошибка при обновлении статуса",
|
||||
show_alert=True
|
||||
)
|
||||
|
||||
def get_updated_keyboard(request_id: int, current_status: str) -> InlineKeyboardMarkup:
|
||||
"""Create keyboard with status update buttons"""
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[])
|
||||
|
||||
if current_status != RequestStatus.IN_PROGRESS:
|
||||
keyboard.inline_keyboard.append([
|
||||
InlineKeyboardButton(
|
||||
text="В работе",
|
||||
callback_data=f"status_{request_id}_{RequestStatus.IN_PROGRESS}"
|
||||
)
|
||||
])
|
||||
|
||||
if current_status != RequestStatus.COMPLETED:
|
||||
keyboard.inline_keyboard.append([
|
||||
InlineKeyboardButton(
|
||||
text="Завершено",
|
||||
callback_data=f"status_{request_id}_{RequestStatus.COMPLETED}"
|
||||
)
|
||||
])
|
||||
|
||||
return keyboard
|
11
backend/app/bot/handlers/start.py
Normal file
11
backend/app/bot/handlers/start.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Handler for start command and other basic commands"""
|
||||
from aiogram import Router, types
|
||||
from aiogram.filters import Command
|
||||
from ..config import settings
|
||||
|
||||
router = Router()
|
||||
|
||||
@router.message(Command("start"))
|
||||
async def cmd_start(message: types.Message):
|
||||
"""Handle /start command"""
|
||||
await message.answer("Бот для обработки заявок запущен!")
|
61
backend/app/bot/handlers/status.py
Normal file
61
backend/app/bot/handlers/status.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from aiogram import types, F
|
||||
from logging import getLogger
|
||||
from sqlalchemy.orm import Session
|
||||
from ...database import get_db
|
||||
from ...crud import requests
|
||||
from ..bot import dp
|
||||
from ..keyboards import create_status_keyboard
|
||||
from ..messages import format_request_message
|
||||
from ..constants import STATUS_LABELS
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
@dp.callback_query(F.data.startswith("status_"))
|
||||
async def process_status_update(callback: types.CallbackQuery):
|
||||
try:
|
||||
parts = callback.data.split("_")
|
||||
logger.info(f"Received callback data: {callback.data}")
|
||||
|
||||
if len(parts) < 3:
|
||||
logger.error(f"Invalid callback data format: {parts}")
|
||||
await callback.answer("Неверный формат данных", show_alert=True)
|
||||
return
|
||||
|
||||
request_id = int(parts[1])
|
||||
new_status = "_".join(parts[2:]) if len(parts) > 3 else parts[2]
|
||||
|
||||
logger.info(
|
||||
f"Processing status update: request_id={request_id}, new_status={new_status}"
|
||||
)
|
||||
|
||||
db = next(get_db())
|
||||
try:
|
||||
updated_request = requests.update_request_status(db, request_id, new_status)
|
||||
if not updated_request:
|
||||
logger.warning(f"Request not found: {request_id}")
|
||||
await callback.answer("Заявка не найдена", show_alert=True)
|
||||
return
|
||||
|
||||
new_message = format_request_message(updated_request)
|
||||
new_keyboard = create_status_keyboard(request_id, new_status)
|
||||
|
||||
await callback.message.edit_text(
|
||||
text=new_message, parse_mode="HTML", reply_markup=new_keyboard
|
||||
)
|
||||
|
||||
await callback.answer(f"Статус обновлен: {STATUS_LABELS[new_status]}")
|
||||
logger.info(
|
||||
f"Successfully updated request {request_id} to status {new_status}"
|
||||
)
|
||||
|
||||
except ValueError as e:
|
||||
logger.error(f"Value error while updating status: {e}")
|
||||
await callback.answer(str(e), show_alert=True)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing callback: {e}", exc_info=True)
|
||||
await callback.answer(
|
||||
"Произошла ошибка при обновлении статуса", show_alert=True
|
||||
)
|
33
backend/app/bot/keyboards.py
Normal file
33
backend/app/bot/keyboards.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from logging import getLogger
|
||||
from .constants import STATUS_LABELS
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
def create_status_keyboard(
|
||||
request_id: int, current_status: str
|
||||
) -> InlineKeyboardMarkup:
|
||||
status_transitions = {
|
||||
"new": ["in_progress"],
|
||||
"in_progress": ["resolved"],
|
||||
"resolved": ["closed"],
|
||||
"closed": [],
|
||||
}
|
||||
|
||||
buttons = []
|
||||
available_statuses = status_transitions.get(current_status, [])
|
||||
|
||||
for status in available_statuses:
|
||||
callback_data = f"status_{request_id}_{status}"
|
||||
logger.debug(f"Creating button with callback_data: {callback_data}")
|
||||
buttons.append(
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text=STATUS_LABELS[status], callback_data=callback_data
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=buttons)
|
||||
logger.debug(f"Created keyboard: {keyboard}")
|
||||
return keyboard
|
37
backend/app/bot/messages.py
Normal file
37
backend/app/bot/messages.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from datetime import datetime
|
||||
from .constants import (
|
||||
STATUS_LABELS,
|
||||
PRIORITY_LABELS,
|
||||
PRIORITY_EMOJI,
|
||||
DEPARTMENT_LABELS,
|
||||
REQUEST_TYPE_LABELS,
|
||||
REQUEST_TYPE_EMOJI,
|
||||
)
|
||||
|
||||
|
||||
def format_request_message(request_data: dict) -> str:
|
||||
created_at = datetime.fromisoformat(request_data["created_at"]).strftime(
|
||||
"%d.%m.%Y %H:%M"
|
||||
)
|
||||
|
||||
# Get translated values
|
||||
department = DEPARTMENT_LABELS.get(
|
||||
request_data["department"], request_data["department"]
|
||||
)
|
||||
request_type = REQUEST_TYPE_LABELS.get(
|
||||
request_data["request_type"], request_data["request_type"]
|
||||
)
|
||||
priority = PRIORITY_LABELS.get(request_data["priority"], request_data["priority"])
|
||||
status = STATUS_LABELS.get(request_data.get("status", "new"), "Неизвестно")
|
||||
|
||||
return (
|
||||
f"📋 <b>Заявка #{request_data['id']}</b>\n\n"
|
||||
f"👤 <b>Сотрудник:</b> {request_data['employee_last_name']} {request_data['employee_first_name']}\n"
|
||||
f"🏢 <b>Отдел:</b> {department}\n"
|
||||
f"🚪 <b>Кабинет:</b> {request_data['office']}\n"
|
||||
f"{REQUEST_TYPE_EMOJI.get(request_data['request_type'], '📝')} <b>Тип заявки:</b> {request_type}\n"
|
||||
f"{PRIORITY_EMOJI.get(request_data['priority'], '⚪')} <b>Приоритет:</b> {priority}\n\n"
|
||||
f"📝 <b>Описание:</b>\n<blockquote>{request_data['description']}</blockquote>\n\n"
|
||||
f"🕒 <b>Создана:</b> {created_at}\n"
|
||||
f"📊 <b>Статус:</b> {status}"
|
||||
)
|
23
backend/app/bot/notifications.py
Normal file
23
backend/app/bot/notifications.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""Notifications module for the Telegram bot"""
|
||||
from .config import settings
|
||||
from . import bot
|
||||
from .handlers import get_updated_keyboard
|
||||
|
||||
async def send_notification(request_data: dict):
|
||||
"""Send notification about new request to Telegram chat"""
|
||||
message_text = (
|
||||
f"Новая заявка №{request_data['id']}\n"
|
||||
f"Отдел: {request_data['department']}\n"
|
||||
f"Тип: {request_data['request_type']}\n"
|
||||
f"Приоритет: {request_data['priority']}\n"
|
||||
f"Описание: {request_data['description']}"
|
||||
)
|
||||
|
||||
try:
|
||||
await bot.send_message(
|
||||
chat_id=settings.chat_id,
|
||||
text=message_text,
|
||||
reply_markup=get_updated_keyboard(request_data['id'], "new")
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Error sending notification: {e}")
|
Reference in New Issue
Block a user